@flisk-sh/flisk 0.0.0-stage → 1.3.190

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 (440) hide show
  1. package/CHANGELOG.md +492 -0
  2. package/LICENSE +21 -0
  3. package/README.md +479 -2
  4. package/Tiltfile +151 -0
  5. package/cli/bin/flisk.js +42 -0
  6. package/cli/dist/agent-runtimes/custom.js +335 -0
  7. package/cli/dist/agent-runtimes/registry.js +34 -0
  8. package/cli/dist/backend-frameworks/elysia.js +38 -0
  9. package/cli/dist/backend-frameworks/express.js +49 -0
  10. package/cli/dist/backend-frameworks/fastify.js +43 -0
  11. package/cli/dist/backend-frameworks/h3.js +37 -0
  12. package/cli/dist/backend-frameworks/koa.js +47 -0
  13. package/cli/dist/backend-frameworks/mcp.js +86 -0
  14. package/cli/dist/backend-frameworks/nestjs.js +55 -0
  15. package/cli/dist/backend-frameworks/registry.js +134 -0
  16. package/cli/dist/backend-frameworks/types.js +1 -0
  17. package/cli/dist/backend-languages/bun.js +76 -0
  18. package/cli/dist/backend-languages/go.js +125 -0
  19. package/cli/dist/backend-languages/node.js +122 -0
  20. package/cli/dist/backend-languages/python.js +94 -0
  21. package/cli/dist/backend-languages/registry.js +37 -0
  22. package/cli/dist/backend-languages/rust.js +82 -0
  23. package/cli/dist/backend-languages/types.js +1 -0
  24. package/cli/dist/cli.js +29 -0
  25. package/cli/dist/commands/__tests__/project-e2e-helpers.js +49 -0
  26. package/cli/dist/commands/__tests__/ui-loading-screen.test.js +16 -0
  27. package/cli/dist/commands/agent.js +182 -0
  28. package/cli/dist/commands/completion-candidates.js +116 -0
  29. package/cli/dist/commands/completion.js +843 -0
  30. package/cli/dist/commands/config.js +362 -0
  31. package/cli/dist/commands/doctor.js +1504 -0
  32. package/cli/dist/commands/down.js +114 -0
  33. package/cli/dist/commands/eject.js +108 -0
  34. package/cli/dist/commands/help.js +152 -0
  35. package/cli/dist/commands/import.js +164 -0
  36. package/cli/dist/commands/logs.js +119 -0
  37. package/cli/dist/commands/maintainers.js +102 -0
  38. package/cli/dist/commands/mcp.js +28 -0
  39. package/cli/dist/commands/networks.js +306 -0
  40. package/cli/dist/commands/project.js +447 -0
  41. package/cli/dist/commands/projects.js +103 -0
  42. package/cli/dist/commands/registry.js +53 -0
  43. package/cli/dist/commands/resource.js +992 -0
  44. package/cli/dist/commands/resources.js +127 -0
  45. package/cli/dist/commands/restart.js +89 -0
  46. package/cli/dist/commands/runtime.js +75 -0
  47. package/cli/dist/commands/stack.js +171 -0
  48. package/cli/dist/commands/stacks.js +71 -0
  49. package/cli/dist/commands/status.js +211 -0
  50. package/cli/dist/commands/ui.js +725 -0
  51. package/cli/dist/commands/up.js +609 -0
  52. package/cli/dist/commands/upgrade.js +840 -0
  53. package/cli/dist/commands/version.js +10 -0
  54. package/cli/dist/components/Accessible.js +11 -0
  55. package/cli/dist/components/BaseTooltip.js +30 -0
  56. package/cli/dist/components/DetailPanel.js +33 -0
  57. package/cli/dist/components/FileTree.js +51 -0
  58. package/cli/dist/components/ResourceSelectInput.js +42 -0
  59. package/cli/dist/components/ResourceTable.js +35 -0
  60. package/cli/dist/components/ServiceIssues.js +13 -0
  61. package/cli/dist/components/TUIHeader.js +27 -0
  62. package/cli/dist/components/TabBar.js +27 -0
  63. package/cli/dist/components/Tooltip.js +13 -0
  64. package/cli/dist/components/index.js +11 -0
  65. package/cli/dist/components/ui-theme.js +120 -0
  66. package/cli/dist/config/platform-standards.js +182 -0
  67. package/cli/dist/frontend-frameworks/lit.js +64 -0
  68. package/cli/dist/frontend-frameworks/preact.js +62 -0
  69. package/cli/dist/frontend-frameworks/qwik.js +61 -0
  70. package/cli/dist/frontend-frameworks/react.js +69 -0
  71. package/cli/dist/frontend-frameworks/registry.js +83 -0
  72. package/cli/dist/frontend-frameworks/solid.js +62 -0
  73. package/cli/dist/frontend-frameworks/svelte.js +67 -0
  74. package/cli/dist/frontend-frameworks/tanstack-router.js +101 -0
  75. package/cli/dist/frontend-frameworks/types.js +1 -0
  76. package/cli/dist/frontend-frameworks/vanilla.js +59 -0
  77. package/cli/dist/frontend-frameworks/vue.js +72 -0
  78. package/cli/dist/generator/__tests__/golden-helpers.js +43 -0
  79. package/cli/dist/generator/extension-fetch.js +291 -0
  80. package/cli/dist/generator/resource-templates.js +133 -0
  81. package/cli/dist/generator/template-engine.js +801 -0
  82. package/cli/dist/index.js +10 -0
  83. package/cli/dist/mcp/server.js +101 -0
  84. package/cli/dist/mcp/tools.js +351 -0
  85. package/cli/dist/program.js +37 -0
  86. package/cli/dist/types/index.js +38 -0
  87. package/cli/dist/utils/agent-host.js +36 -0
  88. package/cli/dist/utils/atomic-write.js +36 -0
  89. package/cli/dist/utils/bun-floor.js +9 -0
  90. package/cli/dist/utils/bun-requirement.js +10 -0
  91. package/cli/dist/utils/cache.js +63 -0
  92. package/cli/dist/utils/cold-preflight.js +257 -0
  93. package/cli/dist/utils/command-helpers.js +57 -0
  94. package/cli/dist/utils/constants.js +160 -0
  95. package/cli/dist/utils/discovery-context.js +71 -0
  96. package/cli/dist/utils/discovery-paths.js +93 -0
  97. package/cli/dist/utils/docker.js +34 -0
  98. package/cli/dist/utils/doctor-crashloop.js +161 -0
  99. package/cli/dist/utils/doctor-fix.js +391 -0
  100. package/cli/dist/utils/doctor-host.js +66 -0
  101. package/cli/dist/utils/doctor-report.js +78 -0
  102. package/cli/dist/utils/doctor-runtime.js +880 -0
  103. package/cli/dist/utils/doctor-starlark.js +132 -0
  104. package/cli/dist/utils/doctor-wiring.js +698 -0
  105. package/cli/dist/utils/down-cleanup.js +125 -0
  106. package/cli/dist/utils/env-validator.js +266 -0
  107. package/cli/dist/utils/errors.js +175 -0
  108. package/cli/dist/utils/exec-async.js +31 -0
  109. package/cli/dist/utils/file-helpers.js +104 -0
  110. package/cli/dist/utils/formatting.js +165 -0
  111. package/cli/dist/utils/generated-paths.js +51 -0
  112. package/cli/dist/utils/help-command.js +11 -0
  113. package/cli/dist/utils/host-port-config.js +140 -0
  114. package/cli/dist/utils/host-port-plan.js +100 -0
  115. package/cli/dist/utils/json-output.js +29 -0
  116. package/cli/dist/utils/machine-output.js +33 -0
  117. package/cli/dist/utils/paths.js +43 -0
  118. package/cli/dist/utils/platform.js +58 -0
  119. package/cli/dist/utils/port-assignment.js +75 -0
  120. package/cli/dist/utils/port-conflict-fix.js +25 -0
  121. package/cli/dist/utils/port-fallback-notice.js +17 -0
  122. package/cli/dist/utils/port-listener-probe.js +20 -0
  123. package/cli/dist/utils/project-config-defaults.js +17 -0
  124. package/cli/dist/utils/project-features.js +91 -0
  125. package/cli/dist/utils/project-templates.js +32 -0
  126. package/cli/dist/utils/prompt.js +97 -0
  127. package/cli/dist/utils/resource-features.js +184 -0
  128. package/cli/dist/utils/resource-kind.js +9 -0
  129. package/cli/dist/utils/schema-version-gate.js +45 -0
  130. package/cli/dist/utils/search-status.js +12 -0
  131. package/cli/dist/utils/secret-redaction.js +73 -0
  132. package/cli/dist/utils/service-config-checks.js +189 -0
  133. package/cli/dist/utils/service-manifest.js +254 -0
  134. package/cli/dist/utils/service-runtime-state.js +66 -0
  135. package/cli/dist/utils/service-urls.js +112 -0
  136. package/cli/dist/utils/services.js +360 -0
  137. package/cli/dist/utils/shared-infra.js +45 -0
  138. package/cli/dist/utils/shared-platform-postgres.js +232 -0
  139. package/cli/dist/utils/single-flight.js +21 -0
  140. package/cli/dist/utils/smoke.js +401 -0
  141. package/cli/dist/utils/spinner.js +40 -0
  142. package/cli/dist/utils/stack-features.js +15 -0
  143. package/cli/dist/utils/stack-status.js +14 -0
  144. package/cli/dist/utils/startup-report.js +78 -0
  145. package/cli/dist/utils/status-message.js +21 -0
  146. package/cli/dist/utils/status-ports.js +33 -0
  147. package/cli/dist/utils/suggestions.js +63 -0
  148. package/cli/dist/utils/tar.js +46 -0
  149. package/cli/dist/utils/tdk-version.js +51 -0
  150. package/cli/dist/utils/terminal-layout.js +19 -0
  151. package/cli/dist/utils/tilt-events.js +194 -0
  152. package/cli/dist/utils/tilt-logs.js +61 -0
  153. package/cli/dist/utils/tilt-port-owner.js +63 -0
  154. package/cli/dist/utils/tilt-process.js +54 -0
  155. package/cli/dist/utils/tilt-resource-state.js +32 -0
  156. package/cli/dist/utils/tilt-startup.js +49 -0
  157. package/cli/dist/utils/tilt-unreachable-message.js +9 -0
  158. package/cli/dist/utils/tilt.js +104 -0
  159. package/cli/dist/utils/ui-keymap.js +129 -0
  160. package/cli/dist/utils/ui-service-state.js +34 -0
  161. package/cli/dist/utils/up-only.js +28 -0
  162. package/cli/dist/utils/up-readiness.js +118 -0
  163. package/cli/dist/utils/url-actions.js +103 -0
  164. package/cli/dist/utils/validation.js +62 -0
  165. package/cli/dist/utils/which.js +56 -0
  166. package/cli/dist/utils/windows-doctor.js +176 -0
  167. package/cli/dist-bundle/chunk-6EA4DGCY.js +2 -0
  168. package/cli/dist-bundle/chunk-BZ4H7MM4.js +3 -0
  169. package/cli/dist-bundle/chunk-CLCUIDBH.js +510 -0
  170. package/cli/dist-bundle/cli.js +2017 -0
  171. package/cli/dist-bundle/cold-preflight-SY26O2IG.js +2 -0
  172. package/cli/dist-bundle/devtools-ZUHXW33W.js +5 -0
  173. package/cli/dist-bundle/extension-fetch-J4FFH7TJ.js +2 -0
  174. package/cli/package.json +83 -0
  175. package/cli/shared-platform-engineering/docker-templates/bun-hoisted-symlink-fix.sh +25 -0
  176. package/cli/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +466 -0
  177. package/cli/shared-platform-engineering/docker-templates/install-deps.sh +154 -0
  178. package/cli/shared-platform-engineering/docker-templates/prisma-bun-client-link-fix.sh +14 -0
  179. package/cli/shared-platform-engineering/docker-templates/prisma-normalize-client.sh +58 -0
  180. package/cli/templates/.tiltignore.hbs +72 -0
  181. package/cli/templates/TILT_RESOURCE_DEFAULTS.star.hbs +220 -0
  182. package/cli/templates/TILT_TECH_STACK.star.hbs +149 -0
  183. package/cli/templates/Tiltfile.hbs +381 -0
  184. package/cli/templates/spec.master.hbs +192 -0
  185. package/discovery/AGENTS.md +362 -0
  186. package/discovery/TILT_DISCOVERY.star +429 -0
  187. package/discovery/Tiltfile +58 -0
  188. package/discovery/config.star +265 -0
  189. package/discovery/constants.star +167 -0
  190. package/discovery/deterministic.star +213 -0
  191. package/discovery/discovery_daemon.star +193 -0
  192. package/discovery/discovery_orchestrator.star +492 -0
  193. package/discovery/filtering.star +7 -0
  194. package/discovery/identity-management-backend/service.yaml +26 -0
  195. package/discovery/identity-management-frontend/service.yaml +29 -0
  196. package/discovery/identity-sdk/service.yaml +9 -0
  197. package/discovery/incremental_discovery.star +215 -0
  198. package/discovery/indexing.star +9 -0
  199. package/discovery/json_manifest_scanner.star +57 -0
  200. package/discovery/libraries.star +48 -0
  201. package/discovery/loading.star +25 -0
  202. package/discovery/manifest/constants.star +106 -0
  203. package/discovery/manifest/loading.star +418 -0
  204. package/discovery/manifest/normalize.star +289 -0
  205. package/discovery/manifest/validation.star +110 -0
  206. package/discovery/normalization.star +13 -0
  207. package/discovery/registry.star +562 -0
  208. package/discovery/resource_snapshot.py +384 -0
  209. package/discovery/resource_snapshot.star +145 -0
  210. package/discovery/schemas.star +3 -0
  211. package/discovery/services/platform/cdc/debezium/nats-connector/service.yaml +9 -0
  212. package/discovery/services/platform/cdc/nats-http-bridge/service.yaml +9 -0
  213. package/discovery/services/platform/orchestrator/orchestrator-glue-backend/service.yaml +26 -0
  214. package/discovery/services/platform/synthetic-monitor/service.yaml +9 -0
  215. package/discovery/test_resource_snapshot.py +253 -0
  216. package/discovery/validation.star +80 -0
  217. package/discovery/versioning.star +3 -0
  218. package/engine/AGENTS.md +706 -0
  219. package/engine/README.md +31 -0
  220. package/engine/REFACTORING_MANIFEST_SYSTEM.md +974 -0
  221. package/engine/Tiltfile +86 -0
  222. package/engine/assets/docker/golden-layers.Dockerfile +204 -0
  223. package/engine/assets/docker/wake-gateway/Dockerfile +28 -0
  224. package/engine/assets/docker/wake-gateway/decide.test.ts +177 -0
  225. package/engine/assets/docker/wake-gateway/decide.ts +135 -0
  226. package/engine/assets/docker/wake-gateway/server.ts +363 -0
  227. package/engine/assets/scripts/cleanup-completed-migrator-images.sh +39 -0
  228. package/engine/assets/scripts/cleanup-tilt-images.sh +122 -0
  229. package/engine/assets/scripts/mac-m1-resource-monitor.sh +123 -0
  230. package/engine/config.star +5 -0
  231. package/engine/entrypoint.star +101 -0
  232. package/engine/lifecycle/orchestrator.star +263 -0
  233. package/engine/paths.star +719 -0
  234. package/engine/schemas/project-schema.json +147 -0
  235. package/engine/schemas/service-schema.json +537 -0
  236. package/engine/scripts/README.md +89 -0
  237. package/engine/scripts/build-and-publish-lib.sh +207 -0
  238. package/engine/scripts/check-lib-publish.sh +87 -0
  239. package/engine/scripts/debug-container.sh +265 -0
  240. package/engine/scripts/generate-manifest.sh +200 -0
  241. package/engine/spec.master +702 -0
  242. package/engine/spec.master.example +341 -0
  243. package/engine/topologies/README.md +33 -0
  244. package/engine/topologies/platform/docker/AGENTS.md +59 -0
  245. package/engine/topologies/platform/docker/auth/auth.star +207 -0
  246. package/engine/topologies/platform/docker/build/frontend_build_plan.star +65 -0
  247. package/engine/topologies/platform/docker/build/golden_image.star +37 -0
  248. package/engine/topologies/platform/docker/build/golden_image_build.star +144 -0
  249. package/engine/topologies/platform/docker/build/golden_image_constants.star +100 -0
  250. package/engine/topologies/platform/docker/build/golden_image_dockerfile.star +263 -0
  251. package/engine/topologies/platform/docker/compose/compose.star +637 -0
  252. package/engine/topologies/platform/docker/compose/compose_helpers.star +54 -0
  253. package/engine/topologies/platform/docker/compose/traefik_standalone.star +171 -0
  254. package/engine/topologies/platform/docker/config/bunignore.star +144 -0
  255. package/engine/topologies/platform/docker/config/dockerignore.star +152 -0
  256. package/engine/topologies/platform/docker/config/healthcheck.star +58 -0
  257. package/engine/topologies/platform/docker/constants.star +211 -0
  258. package/engine/topologies/platform/docker/dockerfile/dockerfile.star +56 -0
  259. package/engine/topologies/platform/docker/dockerfile/language_dockerfile.star +264 -0
  260. package/engine/topologies/platform/docker/generators/golden_docker_generator_v2.star +76 -0
  261. package/engine/topologies/platform/docker/index.star +40 -0
  262. package/engine/topologies/platform/docker/layers/bun_helpers.star +25 -0
  263. package/engine/topologies/platform/docker/layers/infisical/index.star +42 -0
  264. package/engine/topologies/platform/docker/layers/infisical/infisical_docker.star +264 -0
  265. package/engine/topologies/platform/docker/layers/l1_base_layers.star +89 -0
  266. package/engine/topologies/platform/docker/layers/l2_dependency_layers.star +147 -0
  267. package/engine/topologies/platform/docker/layers/l3_builder_layers.star +229 -0
  268. package/engine/topologies/platform/docker/layers/l4_runtime_layers.star +282 -0
  269. package/engine/topologies/platform/docker/layers/prisma/constants.star +138 -0
  270. package/engine/topologies/platform/docker/layers/prisma/db_readiness.star +98 -0
  271. package/engine/topologies/platform/docker/layers/prisma/filesystem_setup.star +131 -0
  272. package/engine/topologies/platform/docker/layers/prisma/migrate_script.star +100 -0
  273. package/engine/topologies/platform/docker/layers/prisma/prisma_build.star +128 -0
  274. package/engine/topologies/platform/docker/layers/prisma/prisma_runtime.star +205 -0
  275. package/engine/topologies/platform/docker/layers/prisma/secret_resolution.star +121 -0
  276. package/engine/topologies/platform/docker/networking/api_path_constants.star +206 -0
  277. package/engine/topologies/platform/docker/networking/sablier_container_cycle.star +31 -0
  278. package/engine/topologies/platform/docker/networking/traefik.star +266 -0
  279. package/engine/topologies/platform/docker/networking/traefik_constants.star +148 -0
  280. package/engine/topologies/platform/docker/networking/traefik_helpers.star +162 -0
  281. package/engine/topologies/platform/docker/networking/traefik_static_routes.star +165 -0
  282. package/engine/topologies/platform/docker/secrets/env_secrets.star +74 -0
  283. package/engine/topologies/platform/docker/secrets/infisical.star +109 -0
  284. package/engine/topologies/platform/net/AGENTS.md +64 -0
  285. package/engine/topologies/platform/net/proxy.star +6 -0
  286. package/engine/topologies/platform/registries/local.star +6 -0
  287. package/engine/topologies/platform/registries/verdaccio_loader.star +20 -0
  288. package/engine/topologies/platform/security/AGENTS.md +49 -0
  289. package/engine/topologies/platform/security/secrets.star +241 -0
  290. package/engine/topologies/platform/state/AGENTS.md +64 -0
  291. package/engine/topologies/platform/state/relational.star +6 -0
  292. package/engine/topologies/tilt/common/AGENTS.md +59 -0
  293. package/engine/topologies/tilt/common/utils.star +409 -0
  294. package/engine/topologies/tilt/common/utils_debug.star +71 -0
  295. package/engine/topologies/tilt/common/utils_deterministic.star +170 -0
  296. package/engine/topologies/tilt/common/utils_docker_networks.star +54 -0
  297. package/engine/topologies/tilt/common/utils_env.star +92 -0
  298. package/engine/topologies/tilt/common/utils_output.star +99 -0
  299. package/engine/topologies/tilt/config/AGENTS.md +68 -0
  300. package/engine/topologies/tilt/config/defaults.star +5 -0
  301. package/engine/topologies/tilt/config/feature_flags.star +12 -0
  302. package/engine/topologies/tilt/config/global.star +57 -0
  303. package/engine/topologies/tilt/config/profiles.star +398 -0
  304. package/engine/topologies/tilt/generators/agents_md.star +64 -0
  305. package/engine/topologies/tilt/generators/api_gateway.star +147 -0
  306. package/engine/topologies/tilt/generators/index.star +126 -0
  307. package/engine/topologies/tilt/generators/infisical/README.md +424 -0
  308. package/engine/topologies/tilt/generators/infisical/examples.star +314 -0
  309. package/engine/topologies/tilt/generators/infisical/index.star +59 -0
  310. package/engine/topologies/tilt/generators/infisical/machine_identity.star +422 -0
  311. package/engine/topologies/tilt/generators/infisical/organization.star +536 -0
  312. package/engine/topologies/tilt/generators/infisical/path_manager.star +478 -0
  313. package/engine/topologies/tilt/generators/infisical/secrets_generator.star +422 -0
  314. package/engine/topologies/tilt/generators/nginx_conf.star +145 -0
  315. package/engine/topologies/tilt/generators/npmrc.star +132 -0
  316. package/engine/topologies/tilt/generators/tsconfig.star +25 -0
  317. package/engine/topologies/tilt/generators/typescript/backend_library_tsconfig.star +73 -0
  318. package/engine/topologies/tilt/generators/typescript/backend_tsconfig.star +137 -0
  319. package/engine/topologies/tilt/generators/typescript/common.star +9 -0
  320. package/engine/topologies/tilt/generators/typescript/frontend_library_tsconfig.star +78 -0
  321. package/engine/topologies/tilt/generators/typescript/frontend_tsconfig.star +140 -0
  322. package/engine/topologies/tilt/generators/typescript/include.star +25 -0
  323. package/engine/topologies/tilt/generators/typescript/playwright_config.star +42 -0
  324. package/engine/topologies/tilt/generators/typescript/prisma_config.star +51 -0
  325. package/engine/topologies/tilt/generators/validators.star +868 -0
  326. package/engine/topologies/tilt/generators/vite/backend.star +80 -0
  327. package/engine/topologies/tilt/generators/vite/frameworks/lit.star +92 -0
  328. package/engine/topologies/tilt/generators/vite/frameworks/preact.star +94 -0
  329. package/engine/topologies/tilt/generators/vite/frameworks/qwik.star +94 -0
  330. package/engine/topologies/tilt/generators/vite/frameworks/react.star +128 -0
  331. package/engine/topologies/tilt/generators/vite/frameworks/solid.star +94 -0
  332. package/engine/topologies/tilt/generators/vite/frameworks/svelte.star +94 -0
  333. package/engine/topologies/tilt/generators/vite/frameworks/vanilla.star +92 -0
  334. package/engine/topologies/tilt/generators/vite/frameworks/vue.star +94 -0
  335. package/engine/topologies/tilt/generators/vite/frontend.star +153 -0
  336. package/engine/topologies/tilt/generators/vite/generators.star +45 -0
  337. package/engine/topologies/tilt/generators/vite/helpers.star +256 -0
  338. package/engine/topologies/tilt/generators/vite/library.star +67 -0
  339. package/engine/topologies/tilt/generators/vite/sdk.star +75 -0
  340. package/engine/topologies/tilt/generators/vite/templates.star +460 -0
  341. package/engine/topologies/tilt/generators/vite_config.star +24 -0
  342. package/engine/topologies/tilt/manifest/AGENTS.md +61 -0
  343. package/engine/topologies/tilt/manifest/README.md +679 -0
  344. package/engine/topologies/tilt/manifest/__init__.star +220 -0
  345. package/engine/topologies/tilt/manifest/constants.star +262 -0
  346. package/engine/topologies/tilt/manifest/errors.star +268 -0
  347. package/engine/topologies/tilt/manifest/integration.star +348 -0
  348. package/engine/topologies/tilt/manifest/json_to_yaml.py +61 -0
  349. package/engine/topologies/tilt/manifest/loader.star +239 -0
  350. package/engine/topologies/tilt/manifest/mapper.star +229 -0
  351. package/engine/topologies/tilt/manifest/parser.star +490 -0
  352. package/engine/topologies/tilt/manifest/schema.star +502 -0
  353. package/engine/topologies/tilt/manifest/tests/test_integration.star +278 -0
  354. package/engine/topologies/tilt/manifest/tests/test_loader_parser.star +475 -0
  355. package/engine/topologies/tilt/manifest/tests/test_manifest.star +319 -0
  356. package/engine/topologies/tilt/manifest/tests/test_validator.star +472 -0
  357. package/engine/topologies/tilt/manifest/validator.star +744 -0
  358. package/engine/topologies/tilt/manifest/yaml_to_json.py +20 -0
  359. package/engine/topologies/tilt/resources/AGENTS.md +71 -0
  360. package/engine/topologies/tilt/resources/build_concurrency.star +56 -0
  361. package/engine/topologies/tilt/resources/conditions.star +5 -0
  362. package/engine/topologies/tilt/resources/databases.star +246 -0
  363. package/engine/topologies/tilt/resources/declaration.star +5 -0
  364. package/engine/topologies/tilt/resources/deps.star +5 -0
  365. package/engine/topologies/tilt/resources/infra-loader.star +504 -0
  366. package/engine/topologies/tilt/resources/libs.star +32 -0
  367. package/engine/topologies/tilt/resources/libs_discovery.star +104 -0
  368. package/engine/topologies/tilt/resources/libs_paths.star +81 -0
  369. package/engine/topologies/tilt/resources/libs_resource_definition.star +153 -0
  370. package/engine/topologies/tilt/resources/libs_setup.star +166 -0
  371. package/engine/topologies/tilt/resources/libs_tsconfig.star +39 -0
  372. package/engine/topologies/tilt/resources/orchestrator/apply.star +79 -0
  373. package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +864 -0
  374. package/engine/topologies/tilt/resources/orchestrator/apply_manifest_orchestration.star +90 -0
  375. package/engine/topologies/tilt/resources/orchestrator/apply_migrator_orchestration.star +150 -0
  376. package/engine/topologies/tilt/resources/orchestrator/apply_resource_validation.star +38 -0
  377. package/engine/topologies/tilt/resources/orchestrator/apply_runtime_flags.star +46 -0
  378. package/engine/topologies/tilt/resources/orchestrator/builders/typescript.star +193 -0
  379. package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/AGENTS.md +75 -0
  380. package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/index.star +23 -0
  381. package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/resolver.star +136 -0
  382. package/engine/topologies/tilt/resources/orchestrator/generators/env.star +219 -0
  383. package/engine/topologies/tilt/resources/orchestrator/generators/frontend.star +172 -0
  384. package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +577 -0
  385. package/engine/topologies/tilt/resources/orchestrator/helpers.star +35 -0
  386. package/engine/topologies/tilt/resources/orchestrator.star +56 -0
  387. package/engine/topologies/tilt/resources/ordering.star +5 -0
  388. package/engine/topologies/tilt/resources/shared-platform-postgres.star +30 -0
  389. package/engine/topologies/tilt/resources/triggers.star +5 -0
  390. package/engine/topologies/tilt/resources/watchers.star +304 -0
  391. package/engine/where-things-live.star +719 -0
  392. package/examples/tdk-example/LICENSE +21 -0
  393. package/examples/tdk-example/README.md +23 -0
  394. package/examples/tdk-example/TILT_SERVICE_DEFAULTS.star +42 -0
  395. package/examples/tdk-example/TILT_TECH_STACK.star +67 -0
  396. package/examples/tdk-example/Tiltfile +25 -0
  397. package/examples/tdk-example/services/platform/messaging/docker-compose.yml +26 -0
  398. package/examples/tdk-example/services/shop/orders-api/package.json +20 -0
  399. package/examples/tdk-example/services/shop/orders-api/service.json +14 -0
  400. package/examples/tdk-example/services/shop/orders-api/src/index.ts +81 -0
  401. package/examples/tdk-example/services/shop/orders-app/index.html +12 -0
  402. package/examples/tdk-example/services/shop/orders-app/package.json +23 -0
  403. package/examples/tdk-example/services/shop/orders-app/public/.gitkeep +0 -0
  404. package/examples/tdk-example/services/shop/orders-app/service.json +9 -0
  405. package/examples/tdk-example/services/shop/orders-app/src/main.tsx +9 -0
  406. package/examples/tdk-example/services/shop/orders-app/src/orders.tsx +67 -0
  407. package/examples/tdk-example/services/shop/orders-app/src/style.css +15 -0
  408. package/examples/tdk-example/services/shop/orders-worker/package.json +18 -0
  409. package/examples/tdk-example/services/shop/orders-worker/service.json +13 -0
  410. package/examples/tdk-example/services/shop/orders-worker/src/index.ts +33 -0
  411. package/ext/Tiltfile +66 -0
  412. package/ext/ide-components/README.md +221 -0
  413. package/ext/ide-components/code_viewer/server.py +208 -0
  414. package/ext/ide-components/config_inspector/server.py +1052 -0
  415. package/ext/ide-components/file_browser/server.py +287 -0
  416. package/ext/ide-components/shared/config_service.py +471 -0
  417. package/ext/ide-components/shared/file_utils.py +300 -0
  418. package/ext/ide-components/shared/http_utils.py +234 -0
  419. package/ext/ide-components/shared/static/styles.css +1334 -0
  420. package/ext/ide-components/shared/syntax_highlighter.py +142 -0
  421. package/ext/ide-components/shared/template_engine.py +125 -0
  422. package/ext/ide-components/shared/templates/base.html +121 -0
  423. package/ext/ide-components/shared/test_config_service.py +328 -0
  424. package/ext/ide-components/shared/tilt_integration.py +160 -0
  425. package/ext/ui-enhancements/cron-status-server.sh +51 -0
  426. package/ext/ui-enhancements/cron-status-tracker.sh +148 -0
  427. package/ext/ui-enhancements/extension.yaml +36 -0
  428. package/ext/ui-enhancements/tiltfile.py +101 -0
  429. package/ext/ui-enhancements/ui/cron-jobs-tab.css +396 -0
  430. package/ext/ui-enhancements/ui/help-panel.css +279 -0
  431. package/ext/ui-enhancements/ui/icons.css +125 -0
  432. package/ext/ui-enhancements/ui/tooltips.css +114 -0
  433. package/package.json +94 -4
  434. package/shared-platform-engineering/docker-templates/bun-hoisted-symlink-fix.sh +25 -0
  435. package/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +466 -0
  436. package/shared-platform-engineering/docker-templates/install-deps.sh +154 -0
  437. package/shared-platform-engineering/docker-templates/prisma-bun-client-link-fix.sh +14 -0
  438. package/shared-platform-engineering/docker-templates/prisma-normalize-client.sh +58 -0
  439. package/specs/specs/TILT_SERVICE_DEFAULTS.star +207 -0
  440. package/specs/specs/TILT_TECH_STACK.star +161 -0
@@ -0,0 +1,725 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Copyright (c) 2026 Flisk Landscape contributors
3
+ // SPDX-License-Identifier: MIT
4
+ import { Command } from "commander";
5
+ import { Box, render, Text, useApp, useInput, useStdin, useStdout } from "ink";
6
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
7
+ import { DetailPanel, FileTree, ResourceSelectInput, ResourceTable, ServiceIssues, TabBar, TUIHeader, } from "../components/index.js";
8
+ import { TABS } from "../components/TabBar.js";
9
+ import { createTUITheme, sectionTitle, TUIThemeContext, useTUITheme, } from "../components/ui-theme.js";
10
+ import { errorFactories, requireProjectRoot } from "../utils/errors.js";
11
+ import { readSavedHostPortPlan } from "../utils/host-port-config.js";
12
+ import { findProjectRoot, getPackageVersion } from "../utils/paths.js";
13
+ import { isApiServiceType } from "../utils/resource-kind.js";
14
+ import { describeSearch } from "../utils/search-status.js";
15
+ import { buildAdvertisedTargets } from "../utils/service-urls.js";
16
+ import { clearMetadataCache, discoverResources, discoverStacks, getResourceMetadata, getStackMetadata, } from "../utils/services.js";
17
+ import { singleFlight } from "../utils/single-flight.js";
18
+ import { createStatusMessageController } from "../utils/status-message.js";
19
+ import { getListRowFromMouseY, getTerminalRuleWidth } from "../utils/terminal-layout.js";
20
+ import { isTiltAvailable } from "../utils/tilt.js";
21
+ import { loadTiltEvents, stripTerminalControls, TiltEventsLoadError, } from "../utils/tilt-events.js";
22
+ import { getTiltPollingPort } from "../utils/tilt-startup.js";
23
+ import { formatUiKeyHint, formatUiKeyNames, UI_KEYMAP } from "../utils/ui-keymap.js";
24
+ import { applyServiceStates, fetchServiceStates, } from "../utils/ui-service-state.js";
25
+ import { copyToClipboard, openUrl } from "../utils/url-actions.js";
26
+ // biome-ignore lint/correctness/noUnusedFunctionParameters: reserved callback prop kept in the component API
27
+ const HelpPanel = ({ onClose }) => {
28
+ const theme = useTUITheme();
29
+ return (_jsxs(Box, { ...(theme.ascii ? {} : { borderStyle: "single", borderColor: theme.accent }), paddingX: 2, paddingY: 1, flexDirection: "column", width: 60, children: [_jsx(Text, { bold: true, color: theme.accent, children: "Keyboard Shortcuts" }), _jsx(Box, { marginY: 1, flexDirection: "column", children: ["Navigation", "Actions"].map((group) => (_jsxs(Box, { marginTop: group === "Actions" ? 1 : 0, flexDirection: "column", children: [_jsx(Text, { bold: true, underline: true, color: theme.foreground, children: group }), UI_KEYMAP.filter((shortcut) => shortcut.group === group).map((shortcut) => (_jsx(Text, { color: theme.foreground, children: ` ${formatUiKeyNames(shortcut.id, theme.ascii)} ${shortcut.label}` }, shortcut.id)))] }, group))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, dimColor: theme.dimMuted, children: "Press any key to close..." }) })] }));
30
+ };
31
+ const SPINNER_FRAMES = ["⠋", "⠙", "â š", "â ¸", "â ŧ", "â ´", "â Ļ", "â §", "⠇", "⠏"];
32
+ const ASCII_SPINNER_FRAMES = ["-", "/", "|", "\\"];
33
+ export const LoadingScreen = ({ message = "Discovering resources...", animated = true, }) => {
34
+ const theme = useTUITheme();
35
+ const spinnerFrames = theme.ascii ? ASCII_SPINNER_FRAMES : SPINNER_FRAMES;
36
+ const [frame, setFrame] = useState(0);
37
+ useEffect(() => {
38
+ if (!animated)
39
+ return;
40
+ const timer = setInterval(() => setFrame((f) => (f + 1) % spinnerFrames.length), 80);
41
+ return () => clearInterval(timer);
42
+ }, [animated, spinnerFrames.length]);
43
+ return (_jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: theme.accent, children: [theme.bannerStart, " Flisk NEON EDITION ", theme.bannerEnd] }), _jsx(Box, { marginY: 1 }), _jsxs(Text, { color: theme.foreground, children: [animated && _jsxs(Text, { color: theme.accent, children: [spinnerFrames[frame], " "] }), message] })] }));
44
+ };
45
+ // biome-ignore lint/correctness/noUnusedFunctionParameters: reserved callback prop kept in the component API
46
+ const ErrorScreen = ({ error, onRetry }) => {
47
+ const theme = useTUITheme();
48
+ return (_jsxs(Box, { flexDirection: "column", padding: 2, alignItems: "center", children: [_jsx(Text, { bold: true, color: theme.error, children: "Could Not Load Services" }), _jsx(Box, { marginY: 1 }), _jsxs(Text, { color: theme.error, children: [theme.ascii ? "[x]" : "\u2717", " ", error] }), _jsx(Box, { marginY: 1 }), _jsx(Text, { color: theme.muted, children: "Troubleshooting:" }), _jsx(Text, { color: theme.muted, children: " 1. Check that every service.json is valid JSON" }), _jsxs(Text, { color: theme.muted, children: [" ", "2. Run flisk from your project (the folder with the Tiltfile)"] }), _jsx(Text, { color: theme.muted, children: " 3. Try: flisk status --verbose" }), _jsx(Box, { marginY: 1 }), _jsx(Text, { color: theme.accent, children: "Press [r] to retry or [q] to quit" })] }));
49
+ };
50
+ const EmptyState = ({ message }) => {
51
+ const theme = useTUITheme();
52
+ return (_jsxs(Box, { flexDirection: "column", padding: 2, alignItems: "center", children: [_jsx(Text, { bold: true, color: theme.warning, children: "No Services Found" }), _jsx(Box, { marginY: 1 }), _jsxs(Text, { color: theme.muted, children: [theme.ascii ? "[*]" : "\u25c9", " No service.json files found"] }), _jsx(Box, { marginY: 1 }), _jsx(Text, { color: theme.foreground, children: "To get started:" }), _jsx(Text, { color: theme.foreground, children: " 1. Run: flisk project" }), _jsx(Text, { color: theme.foreground, children: " 2. Run: flisk resource api --type backend" }), _jsx(Box, { marginY: 1 }), _jsx(Text, { color: theme.accent, children: "Press [r] to refresh or [q] to quit" }), message && (_jsx(Box, { paddingX: 1, height: 1, children: _jsxs(Text, { color: theme.accent, children: [theme.bannerStart, " ", message, " ", theme.bannerEnd] }) }))] }));
53
+ };
54
+ function formatResourceStatus(resource) {
55
+ const statuses = [];
56
+ if (resource.runtimeStatus)
57
+ statuses.push(`Runtime ${resource.runtimeStatus}`);
58
+ if (resource.updateStatus)
59
+ statuses.push(`Update ${resource.updateStatus}`);
60
+ if (resource.hasPendingChanges)
61
+ statuses.push("Changes pending");
62
+ return statuses.join("; ") || "No status reported";
63
+ }
64
+ function formatEventTime(value) {
65
+ if (!value)
66
+ return "Current";
67
+ const date = new Date(value);
68
+ return Number.isNaN(date.getTime())
69
+ ? value.slice(0, 20)
70
+ : date
71
+ .toISOString()
72
+ .replace("T", " ")
73
+ .replace(/\.\d{3}Z$/, "Z");
74
+ }
75
+ function formatEventDetails(value, width) {
76
+ const normalized = stripTerminalControls(value).replace(/\s+/g, " ").trim();
77
+ const suffix = "...";
78
+ const maxLength = Math.max(suffix.length + 8, Math.floor(width) - 2);
79
+ return normalized.length > maxLength
80
+ ? normalized.slice(0, maxLength - suffix.length).trimEnd() + suffix
81
+ : normalized;
82
+ }
83
+ export const TUIApp = ({ animated = true }) => {
84
+ const theme = useTUITheme();
85
+ const { exit } = useApp();
86
+ const { stdout } = useStdout();
87
+ const ttyStdout = stdout;
88
+ const { stdin, setRawMode } = useStdin();
89
+ const [activeTab, setActiveTab] = useState("overview");
90
+ const [selectedStack, setSelectedStack] = useState(null);
91
+ const [selectedService, setSelectedService] = useState(null);
92
+ const [message, setMessage] = useState("");
93
+ const [highlightedIndex, setHighlightedIndex] = useState(0);
94
+ const [showHelp, setShowHelp] = useState(false);
95
+ const [searchQuery, setSearchQuery] = useState("");
96
+ const [isSearching, setIsSearching] = useState(false);
97
+ const [terminalWidth, setTerminalWidth] = useState(ttyStdout.columns || 120);
98
+ const [terminalRows, setTerminalRows] = useState(ttyStdout.rows || 24);
99
+ const [selectedFile, setSelectedFile] = useState(null);
100
+ const [mouseEnabled, setMouseEnabled] = useState(true);
101
+ const [listTop, setListTop] = useState(null);
102
+ const [listStart, setListStart] = useState(0);
103
+ // Reserve the header, tab bar, hints and footer; keep the cursor visible.
104
+ const pageSize = Math.max(1, terminalRows - 14);
105
+ const handleListLayout = useCallback((top, firstVisible = 0) => {
106
+ setListTop(top);
107
+ setListStart(firstVisible);
108
+ }, []);
109
+ const [loading, setLoading] = useState(true);
110
+ const [error, setError] = useState(null);
111
+ const [showTooltips, setShowTooltips] = useState(true);
112
+ const [showEnabledOnly, setShowEnabledOnly] = useState(true);
113
+ const [eventSnapshot, setEventSnapshot] = useState(null);
114
+ const [eventLoadState, setEventLoadState] = useState("idle");
115
+ const [eventLoadError, setEventLoadError] = useState(null);
116
+ const eventRequestRef = useRef(0);
117
+ const statusMessage = useMemo(() => createStatusMessageController(setMessage), []);
118
+ useEffect(() => () => statusMessage.dispose(), [statusMessage]);
119
+ const projectRoot = findProjectRoot() || "unknown";
120
+ const ingressPort = projectRoot === "unknown"
121
+ ? undefined
122
+ : (readSavedHostPortPlan(projectRoot)?.ingressHttp ?? undefined);
123
+ const [{ stacks, services }, setDiscovered] = useState({ stacks: [], services: [] });
124
+ // What the running Tilt says about each service: the same answer `flisk status` gives. Empty while no Tilt answers.
125
+ const [serviceStates, setServiceStates] = useState({});
126
+ useEffect(() => {
127
+ let cancelled = false;
128
+ const tiltPort = getTiltPollingPort(process.env.TILT_PORT);
129
+ // A Tilt call can outlast the 5 s interval; never start a poll while the last one is still running.
130
+ const run = singleFlight((next) => {
131
+ if (cancelled)
132
+ return;
133
+ setServiceStates((previous) => JSON.stringify(previous) === JSON.stringify(next) ? previous : next);
134
+ });
135
+ const poll = () => run(() => fetchServiceStates(services, tiltPort));
136
+ void poll();
137
+ const timer = setInterval(() => void poll(), 5000);
138
+ return () => {
139
+ cancelled = true;
140
+ clearInterval(timer);
141
+ };
142
+ }, [services]);
143
+ // Re-read service.json files from disk. An empty project is not an error:
144
+ // it renders EmptyState. Only a failed discovery shows ErrorScreen.
145
+ const refresh = useCallback(() => {
146
+ clearMetadataCache();
147
+ try {
148
+ setDiscovered({ stacks: discoverStacks(), services: discoverResources() });
149
+ setError(null);
150
+ return true;
151
+ }
152
+ catch (err) {
153
+ setError(err instanceof Error ? err.message : String(err));
154
+ return false;
155
+ }
156
+ finally {
157
+ setLoading(false);
158
+ }
159
+ }, []);
160
+ const refreshEvents = useCallback(async () => {
161
+ const requestId = ++eventRequestRef.current;
162
+ setEventLoadState("loading");
163
+ setEventLoadError(null);
164
+ try {
165
+ const snapshot = await loadTiltEvents();
166
+ if (requestId !== eventRequestRef.current)
167
+ return;
168
+ setEventSnapshot(snapshot);
169
+ setEventLoadState("ready");
170
+ }
171
+ catch (error) {
172
+ if (requestId !== eventRequestRef.current)
173
+ return;
174
+ setEventSnapshot(null);
175
+ if (error instanceof TiltEventsLoadError && error.kind === "unavailable") {
176
+ setEventLoadState("unavailable");
177
+ return;
178
+ }
179
+ setEventLoadError(error instanceof Error ? error.message : String(error));
180
+ setEventLoadState("error");
181
+ }
182
+ }, []);
183
+ useEffect(() => {
184
+ refresh();
185
+ }, [refresh]);
186
+ useEffect(() => {
187
+ if (activeTab !== "events")
188
+ return;
189
+ void refreshEvents();
190
+ return () => {
191
+ eventRequestRef.current += 1;
192
+ };
193
+ }, [activeTab, refreshEvents]);
194
+ const selectedStackData = useMemo(() => {
195
+ if (!selectedStack)
196
+ return null;
197
+ const stack = stacks.find((s) => s.name === selectedStack);
198
+ if (!stack)
199
+ return null;
200
+ return {
201
+ stack,
202
+ metadata: applyServiceStates(getStackMetadata(stack), serviceStates),
203
+ };
204
+ }, [selectedStack, stacks, serviceStates]);
205
+ const selectedServiceData = useMemo(() => {
206
+ if (!selectedService)
207
+ return null;
208
+ const service = services.find((s) => s.name === selectedService);
209
+ if (!service)
210
+ return null;
211
+ return {
212
+ service,
213
+ metadata: getResourceMetadata(service),
214
+ };
215
+ }, [selectedService, services]);
216
+ const filteredStacks = useMemo(() => {
217
+ if (!searchQuery)
218
+ return stacks;
219
+ return stacks.filter((s) => s.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
220
+ s.resources.some((svc) => svc.name.toLowerCase().includes(searchQuery.toLowerCase())));
221
+ }, [stacks, searchQuery]);
222
+ const filteredServices = useMemo(() => {
223
+ let filtered = services;
224
+ if (showEnabledOnly) {
225
+ filtered = filtered.filter((s) => s.config?.enabled !== false);
226
+ }
227
+ if (searchQuery) {
228
+ filtered = filtered.filter((s) => s.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
229
+ (s.stack || "").toLowerCase().includes(searchQuery.toLowerCase()));
230
+ }
231
+ return filtered;
232
+ }, [services, searchQuery, showEnabledOnly]);
233
+ const getItems = useCallback(() => {
234
+ if (activeTab === "overview") {
235
+ return filteredStacks.map((stack) => ({
236
+ label: `${stack.name} (${stack.resources.length} resources)`,
237
+ value: stack.name,
238
+ }));
239
+ }
240
+ if (activeTab === "resources") {
241
+ if (selectedStackData) {
242
+ return selectedStackData.stack.resources.map((s) => {
243
+ const stackLabel = s.stack && s.stack !== "unknown" ? ` [${s.stack}]` : "";
244
+ const enabledLabel = s.config?.enabled === false ? " [DISABLED]" : "";
245
+ return {
246
+ label: `${s.name}${stackLabel}${enabledLabel}`,
247
+ value: s.name,
248
+ };
249
+ });
250
+ }
251
+ return filteredStacks.map((stack) => ({
252
+ label: `${stack.name} (${stack.resources.length} resources)`,
253
+ value: stack.name,
254
+ }));
255
+ }
256
+ if (activeTab === "files") {
257
+ if (selectedServiceData) {
258
+ return selectedServiceData.metadata.autogeneratedFiles.map((f) => ({
259
+ label: `${f.name} (${f.type})`,
260
+ value: f.path,
261
+ }));
262
+ }
263
+ return filteredServices.map((s) => {
264
+ const stackPrefix = s.stack && s.stack !== "unknown" ? `${s.stack}/` : "";
265
+ const enabledLabel = s.config?.enabled === false ? " [DISABLED]" : "";
266
+ return {
267
+ label: `${stackPrefix}${s.name}${enabledLabel}`,
268
+ value: s.name,
269
+ };
270
+ });
271
+ }
272
+ if (activeTab === "config") {
273
+ return filteredServices.map((s) => {
274
+ const stackPrefix = s.stack && s.stack !== "unknown" ? `${s.stack}/` : "";
275
+ const stackLabel = s.stack ? ` [${s.stack}]` : "";
276
+ const enabledLabel = s.config?.enabled === false ? " [DISABLED]" : "";
277
+ return {
278
+ label: `${stackPrefix}${s.name}${stackLabel}${enabledLabel}`,
279
+ value: s.name,
280
+ };
281
+ });
282
+ }
283
+ return [];
284
+ }, [activeTab, filteredStacks, filteredServices, selectedStackData, selectedServiceData]);
285
+ const items = useMemo(getItems, [getItems]);
286
+ const selectableListVisible = !loading &&
287
+ !error &&
288
+ !showHelp &&
289
+ (activeTab === "overview" ||
290
+ (activeTab === "resources" && !selectedStackData) ||
291
+ (activeTab === "files" && !selectedServiceData) ||
292
+ (activeTab === "config" && !selectedServiceData));
293
+ // Only the stack and service lists are filtered by the query; drilled-in views are not.
294
+ const searchList = activeTab === "overview" || (activeTab === "resources" && !selectedStackData)
295
+ ? { total: stacks.length }
296
+ : (activeTab === "files" && !selectedServiceData) || activeTab === "config"
297
+ ? { total: services.length }
298
+ : null;
299
+ const searchStatus = searchList
300
+ ? describeSearch(searchQuery, items.length, searchList.total)
301
+ : null;
302
+ useEffect(() => {
303
+ if (highlightedIndex >= items.length && items.length > 0) {
304
+ setHighlightedIndex(items.length - 1);
305
+ }
306
+ else if (items.length === 0) {
307
+ setHighlightedIndex(0);
308
+ }
309
+ }, [highlightedIndex, items.length]);
310
+ const handleSelect = useCallback((item) => {
311
+ if (activeTab === "overview") {
312
+ setSelectedStack(item.value);
313
+ setSelectedService(null);
314
+ statusMessage.show(`Selected stack: ${item.value}`, 2000);
315
+ }
316
+ else if (activeTab === "resources") {
317
+ if (selectedStack && !selectedService) {
318
+ setSelectedService(item.value);
319
+ statusMessage.show(`Selected service: ${item.value}`, 2000);
320
+ }
321
+ else {
322
+ setSelectedStack(item.value);
323
+ setSelectedService(null);
324
+ }
325
+ }
326
+ else if (activeTab === "files") {
327
+ if (selectedService) {
328
+ setSelectedFile(item.value);
329
+ statusMessage.show(`Selected file: ${item.label}`, 2000);
330
+ }
331
+ else {
332
+ setSelectedService(item.value);
333
+ statusMessage.show(`Selected service: ${item.value}`, 2000);
334
+ }
335
+ }
336
+ else if (activeTab === "config") {
337
+ setSelectedService(item.value);
338
+ statusMessage.show(`Viewing config for: ${item.value}`, 2000);
339
+ }
340
+ }, [activeTab, selectedStack, selectedService, statusMessage]);
341
+ const resolveCurrentResource = useCallback(() => {
342
+ if (selectedService) {
343
+ return services.find((s) => s.name === selectedService) ?? null;
344
+ }
345
+ const currentItem = items[highlightedIndex];
346
+ if (currentItem) {
347
+ const directService = services.find((s) => s.name === currentItem.value);
348
+ if (directService) {
349
+ return directService;
350
+ }
351
+ const stack = stacks.find((st) => st.name === currentItem.value);
352
+ if (stack && stack.resources.length > 0) {
353
+ const frontend = stack.resources.find((s) => s.config?.appType === "frontend");
354
+ if (frontend)
355
+ return frontend;
356
+ const api = stack.resources.find((s) => isApiServiceType(s.config?.appType));
357
+ if (api)
358
+ return api;
359
+ return stack.resources[0] ?? null;
360
+ }
361
+ }
362
+ if (selectedStackData && selectedStackData.stack.resources.length > 0) {
363
+ const frontend = selectedStackData.stack.resources.find((s) => s.config?.appType === "frontend");
364
+ if (frontend)
365
+ return frontend;
366
+ const api = selectedStackData.stack.resources.find((s) => isApiServiceType(s.config?.appType));
367
+ if (api)
368
+ return api;
369
+ return selectedStackData.stack.resources[0] ?? null;
370
+ }
371
+ return null;
372
+ }, [selectedService, items, highlightedIndex, services, stacks, selectedStackData]);
373
+ useEffect(() => {
374
+ setRawMode(true);
375
+ stdout.write("\x1b[?1000h");
376
+ stdout.write("\x1b[?1006h");
377
+ return () => {
378
+ setRawMode(false);
379
+ stdout.write("\x1b[?1000l");
380
+ stdout.write("\x1b[?1006l");
381
+ };
382
+ }, [setRawMode, stdout]);
383
+ useEffect(() => {
384
+ if (!mouseEnabled)
385
+ return;
386
+ const handleMouseData = (data) => {
387
+ const str = data.toString();
388
+ // Parse SGR 1006 mouse protocol: ESC[<btn;x;yM or ESC[<btn;x;ym
389
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: \\x1b ESC is intentional (ANSI SGR/CSI sequence parsing)
390
+ const sgrMatch = str.match(/\x1b\[<(\d+);(\d+);(\d+)([Mm])/);
391
+ const btnText = sgrMatch?.[1];
392
+ const yText = sgrMatch?.[3];
393
+ const action = sgrMatch?.[4];
394
+ if (btnText !== undefined && yText !== undefined && action !== undefined) {
395
+ const btn = parseInt(btnText, 10);
396
+ const y = parseInt(yText, 10);
397
+ const release = action === "m";
398
+ // Check if it's a left click (btn & 0b11 == 0 means left button)
399
+ const isLeftClick = (btn & 0b11) === 0;
400
+ if (isLeftClick && !release) {
401
+ const listRow = selectableListVisible && listTop !== null ? getListRowFromMouseY(y, listTop) : -1;
402
+ const itemIndex = listStart + listRow;
403
+ if (listRow >= 0 && listRow < pageSize && itemIndex < items.length) {
404
+ setHighlightedIndex(itemIndex);
405
+ const item = items[itemIndex];
406
+ if (item) {
407
+ handleSelect(item);
408
+ }
409
+ }
410
+ }
411
+ return;
412
+ }
413
+ };
414
+ stdin.on("data", handleMouseData);
415
+ return () => {
416
+ stdin.off("data", handleMouseData);
417
+ stdin.removeAllListeners("data");
418
+ };
419
+ }, [
420
+ stdin,
421
+ items,
422
+ listTop,
423
+ listStart,
424
+ pageSize,
425
+ selectableListVisible,
426
+ mouseEnabled,
427
+ handleSelect,
428
+ ]);
429
+ const handleResize = useCallback(() => {
430
+ const tty = stdout;
431
+ setTerminalWidth(tty.columns || 120);
432
+ setTerminalRows(tty.rows || 24);
433
+ }, [stdout]);
434
+ useEffect(() => {
435
+ stdout.on("resize", handleResize);
436
+ return () => {
437
+ stdout.off("resize", handleResize);
438
+ };
439
+ }, [stdout, handleResize]);
440
+ // Keep this dispatcher in sync with UI_KEYMAP; the shared map drives the help panel and footer.
441
+ useInput((input, key) => {
442
+ if (error) {
443
+ if (input === "r" || input === "R") {
444
+ refresh();
445
+ return;
446
+ }
447
+ if (input === "q" || key.escape) {
448
+ exit();
449
+ return;
450
+ }
451
+ return;
452
+ }
453
+ if (showHelp) {
454
+ setShowHelp(false);
455
+ return;
456
+ }
457
+ if (isSearching) {
458
+ if (key.return) {
459
+ setIsSearching(false);
460
+ return;
461
+ }
462
+ if (key.escape) {
463
+ setIsSearching(false);
464
+ setSearchQuery("");
465
+ return;
466
+ }
467
+ if (key.backspace || key.delete) {
468
+ setSearchQuery((prev) => prev.slice(0, -1));
469
+ return;
470
+ }
471
+ if (input && !key.ctrl && !key.meta) {
472
+ setSearchQuery((prev) => prev + input);
473
+ return;
474
+ }
475
+ return;
476
+ }
477
+ if (input === "q" && !key.ctrl && !key.meta) {
478
+ exit();
479
+ return;
480
+ }
481
+ if (key.escape) {
482
+ if (selectedFile) {
483
+ setSelectedFile(null);
484
+ return;
485
+ }
486
+ if (selectedService) {
487
+ setSelectedService(null);
488
+ return;
489
+ }
490
+ if (selectedStack) {
491
+ setSelectedStack(null);
492
+ return;
493
+ }
494
+ statusMessage.show("Press q to quit", 2000);
495
+ return;
496
+ }
497
+ if (input === "?") {
498
+ setShowHelp(true);
499
+ return;
500
+ }
501
+ if (input === "r") {
502
+ if (activeTab === "events") {
503
+ void refreshEvents();
504
+ }
505
+ else if (refresh()) {
506
+ statusMessage.show("Data refreshed", 1500);
507
+ }
508
+ return;
509
+ }
510
+ if (input === "/") {
511
+ setIsSearching(true);
512
+ setSearchQuery("");
513
+ setMessage("Search: ");
514
+ return;
515
+ }
516
+ if (input === "m") {
517
+ setMouseEnabled((prev) => {
518
+ const newState = !prev;
519
+ statusMessage.show(newState ? "Mouse support enabled" : "Mouse support disabled");
520
+ return newState;
521
+ });
522
+ return;
523
+ }
524
+ if (input === "t") {
525
+ setShowTooltips((prev) => {
526
+ const newState = !prev;
527
+ statusMessage.show(newState ? "Tooltips enabled" : "Tooltips disabled");
528
+ return newState;
529
+ });
530
+ return;
531
+ }
532
+ if (input === "e") {
533
+ setShowEnabledOnly((prev) => {
534
+ const newState = !prev;
535
+ statusMessage.show(newState ? "Showing enabled services only" : "Showing all services (including disabled)", 1500);
536
+ return newState;
537
+ });
538
+ return;
539
+ }
540
+ if (input === "o") {
541
+ const resource = resolveCurrentResource();
542
+ const target = resource ? buildAdvertisedTargets([resource], ingressPort)[0] : undefined;
543
+ if (!target?.url) {
544
+ statusMessage.show("No URL for this resource", 2000);
545
+ }
546
+ else {
547
+ void openUrl(target.url).then((opened) => {
548
+ statusMessage.show(opened ? `Opening ${target.url}` : `Failed to open ${target.url}`, 2000);
549
+ });
550
+ }
551
+ return;
552
+ }
553
+ if (input === "y") {
554
+ const resource = resolveCurrentResource();
555
+ const target = resource ? buildAdvertisedTargets([resource], ingressPort)[0] : undefined;
556
+ if (!target?.url) {
557
+ statusMessage.show("No URL for this resource", 2000);
558
+ }
559
+ else {
560
+ void copyToClipboard(target.url).then((copied) => {
561
+ if (copied) {
562
+ statusMessage.show(`Copied ${target.url}`, 2000);
563
+ }
564
+ else {
565
+ statusMessage.show(`Failed to copy ${target.url}`, 2000);
566
+ }
567
+ });
568
+ }
569
+ return;
570
+ }
571
+ if (key.tab) {
572
+ const tabs = TABS.map((tab) => tab.id);
573
+ const currentIdx = tabs.indexOf(activeTab);
574
+ const nextIdx = key.shift
575
+ ? (currentIdx - 1 + tabs.length) % tabs.length
576
+ : (currentIdx + 1) % tabs.length;
577
+ setActiveTab(tabs[nextIdx]);
578
+ return;
579
+ }
580
+ const shortcutTab = TABS.find((tab) => tab.shortcut === input);
581
+ if (shortcutTab) {
582
+ setActiveTab(shortcutTab.id);
583
+ return;
584
+ }
585
+ const plainKey = !key.ctrl && !key.meta;
586
+ if (items.length > 0 && (key.upArrow || (plainKey && input === "k"))) {
587
+ setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1));
588
+ }
589
+ if (items.length > 0 && (key.downArrow || (plainKey && input === "j"))) {
590
+ setHighlightedIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0));
591
+ }
592
+ if (key.home || (plainKey && input === "g"))
593
+ setHighlightedIndex(0);
594
+ if (key.end || (plainKey && input === "G"))
595
+ setHighlightedIndex(Math.max(0, items.length - 1));
596
+ if (key.pageUp)
597
+ setHighlightedIndex((prev) => Math.max(0, prev - pageSize));
598
+ if (key.pageDown)
599
+ setHighlightedIndex((prev) => Math.min(Math.max(0, items.length - 1), prev + pageSize));
600
+ if (key.return || input === " ") {
601
+ const currentItem = items[highlightedIndex];
602
+ if (currentItem) {
603
+ handleSelect(currentItem);
604
+ }
605
+ return;
606
+ }
607
+ });
608
+ const fileTreeNodes = useMemo(() => {
609
+ if (selectedServiceData) {
610
+ return [
611
+ {
612
+ name: selectedServiceData.service.name,
613
+ path: selectedServiceData.service.path,
614
+ type: "directory",
615
+ children: selectedServiceData.metadata.autogeneratedFiles.map((f) => ({
616
+ name: f.name,
617
+ path: f.path,
618
+ type: "file",
619
+ fileType: f.type,
620
+ size: f.size,
621
+ lastModified: f.lastModified,
622
+ })),
623
+ },
624
+ ];
625
+ }
626
+ return services.map((s) => ({
627
+ name: s.name,
628
+ path: s.path,
629
+ type: "directory",
630
+ children: [],
631
+ }));
632
+ }, [selectedServiceData, services]);
633
+ const showSidebar = terminalWidth > 100;
634
+ const mainPanelWidth = showSidebar ? terminalWidth - 45 : terminalWidth - 4;
635
+ if (loading) {
636
+ return _jsx(LoadingScreen, { message: "Discovering resources...", animated: animated });
637
+ }
638
+ if (error) {
639
+ return _jsx(ErrorScreen, { error: error, onRetry: refresh });
640
+ }
641
+ const header = (_jsxs(_Fragment, { children: [_jsx(TUIHeader, { projectRoot: projectRoot, resourceCount: services.length, terminalWidth: terminalWidth, version: getPackageVersion() }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { color: theme.muted, children: (theme.ascii ? "-" : "\u2500").repeat(getTerminalRuleWidth(terminalWidth)) }) })] }));
642
+ return (_jsxs(Box, { flexDirection: "column", height: ttyStdout.rows || 24, children: [header, isSearching && (_jsxs(Box, { paddingX: 1, height: 1, children: [_jsxs(Text, { color: theme.warning, children: ["Search: ", searchQuery, "_"] }), searchStatus && (_jsx(Text, { color: theme.muted, dimColor: theme.dimMuted, children: ` ${searchStatus.summary}` }))] })), searchStatus?.emptyMessage && (_jsx(Box, { paddingX: 1, height: 1, children: _jsx(Text, { color: theme.muted, children: searchStatus.emptyMessage }) })), !isSearching && message && (_jsx(Box, { paddingX: 1, height: 1, children: _jsxs(Text, { color: theme.accent, children: [theme.bannerStart, " ", message, " ", theme.bannerEnd] }) })), !isSearching && !message && showTooltips && !showHelp && (_jsx(Box, { paddingX: 1, height: 1, children: _jsx(Text, { color: theme.muted, dimColor: theme.dimMuted, children: createHelpHint(activeTab, selectedStack, selectedService, showEnabledOnly, theme.ascii) }) })), showHelp && (_jsx(Box, { paddingX: 1, flexGrow: 1, children: _jsx(HelpPanel, { onClose: () => setShowHelp(false) }) })), !showHelp && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(TabBar, { activeTab: activeTab, onTabChange: setActiveTab, terminalWidth: terminalWidth }) }), _jsxs(Box, { flexDirection: "row", paddingX: 1, flexGrow: 1, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, width: mainPanelWidth, children: [activeTab === "overview" &&
643
+ (services.length === 0 ? (_jsx(EmptyState, { message: message })) : (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: theme.muted, children: "Stacks" }) }), _jsx(Box, { marginTop: 1, flexGrow: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex, isActive: !isSearching, maxVisibleItems: pageSize, width: mainPanelWidth, onLayout: handleListLayout }) })] }))), activeTab === "resources" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: theme.muted, children: sectionTitle(theme, "Resources") }) }), selectedStackData ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: theme.muted, children: ["Stack: ", selectedStackData.stack.name] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(ResourceTable, { resources: selectedStackData.metadata.resources, maxWidth: terminalWidth - (showSidebar ? 50 : 10) }), _jsx(ServiceIssues, { resources: selectedStackData.metadata.resources })] })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.muted, children: "Select a stack to view resources" }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex, isActive: !isSearching, maxVisibleItems: pageSize, width: mainPanelWidth, onLayout: handleListLayout }) })] }))] })), activeTab === "files" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: theme.muted, children: sectionTitle(theme, "Autogenerated Files") }) }), selectedServiceData ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: theme.muted, children: ["Service: ", selectedServiceData.service.name] }), _jsx(Box, { marginTop: 1, children: _jsx(FileTree, { nodes: fileTreeNodes, selectedPath: selectedFile || undefined }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.muted, children: "Select a service to view files" }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex, isActive: !isSearching, maxVisibleItems: pageSize, width: mainPanelWidth, onLayout: handleListLayout }) })] }))] })), activeTab === "events" && (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: theme.muted, children: sectionTitle(theme, "Event Timeline") }) }), (eventLoadState === "idle" || eventLoadState === "loading") && (_jsx(Text, { color: theme.muted, children: "Loading Tilt events..." })), eventLoadState === "unavailable" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.warning, children: "Tilt is not running or its UI is unreachable." }), _jsx(Text, { color: theme.muted, children: "Start the stack with flisk up, then press [r] to retry." })] })), eventLoadState === "error" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.error, children: "Could not load Tilt events." }), _jsx(Text, { color: theme.muted, children: formatEventDetails(eventLoadError ?? "Unknown Tilt error", mainPanelWidth) }), _jsx(Text, { color: theme.muted, children: "Press [r] to retry." })] })), eventLoadState === "ready" && (_jsxs(_Fragment, { children: [eventSnapshot && eventSnapshot.resources.length > 0 && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.muted, children: "Current resources" }), eventSnapshot.resources.slice(0, 12).map((resource) => (_jsxs(Text, { color: theme.accent, children: [resource.name, ": ", formatResourceStatus(resource)] }, resource.name))), eventSnapshot.resources.length > 12 && (_jsx(Text, { color: theme.muted, children: "Showing first 12 resources" }))] })), _jsx(Text, { bold: true, color: theme.muted, children: "Recent events" }), eventSnapshot && eventSnapshot.events.length > 20 && (_jsx(Text, { color: theme.muted, children: "Showing first 20 events" })), eventSnapshot?.events.length ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: eventSnapshot.events.slice(0, 20).map((event) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: event.kind === "failed"
644
+ ? theme.error
645
+ : event.kind === "warning"
646
+ ? theme.warning
647
+ : event.kind === "running"
648
+ ? theme.info
649
+ : theme.success, children: [formatEventTime(event.occurredAt), " ", event.resourceName, " ", event.title] }), event.details && (_jsx(Text, { color: theme.muted, children: formatEventDetails(event.details, mainPanelWidth) }))] }, event.id))) })) : (_jsx(Text, { color: theme.muted, children: "No recent build events are available from Tilt yet." }))] }))] })), activeTab === "config" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: theme.muted, children: sectionTitle(theme, "Configuration") }) }), selectedServiceData ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: theme.accent, children: selectedServiceData.service.configPath }), _jsx(Box, { marginTop: 1, ...(theme.ascii
650
+ ? {}
651
+ : { borderStyle: "single", borderColor: theme.border }), paddingX: 1, children: _jsx(Text, { color: theme.muted, wrap: "wrap", children: JSON.stringify(selectedServiceData.service.config, null, 2).slice(0, 1000) }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.muted, children: "Select a service to view configuration" }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex, isActive: !isSearching, maxVisibleItems: pageSize, width: mainPanelWidth, onLayout: handleListLayout }) })] }))] }))] }), showSidebar && (_jsx(Box, { marginLeft: 2, children: _jsx(DetailPanel, { stack: selectedStackData?.stack || null, service: selectedServiceData?.service || null, stackMetadata: selectedStackData?.metadata || null, visible: !!selectedStack || !!selectedService }) }))] }), _jsxs(Box, { ...(theme.ascii ? {} : { borderStyle: "single", borderColor: theme.border }), paddingX: 1, height: 3, flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: theme.accent, bold: true, children: [theme.bannerStart, " ", activeTab] }), _jsxs(Text, { color: theme.success, children: [theme.ascii ? "[+]" : "\u25cf", " ", services.filter((s) => s.stack).length, " in stack"] }), _jsxs(Text, { color: theme.warning, children: [theme.ascii ? "-" : "\u25cb", " ", services.filter((s) => !s.stack).length, " no stack"] })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: theme.muted, children: ["Stacks: ", stacks.length] }), _jsxs(Text, { color: theme.muted, children: ["Services: ", services.length] }), _jsxs(Text, { color: theme.muted, children: [theme.ascii ? "Mouse" : "đŸ–ąī¸", " ", mouseEnabled ? "ON" : "OFF", theme.separator, theme.ascii ? "Info" : "â„šī¸", " ", showTooltips ? "ON" : "OFF", theme.separator, _jsx(Text, { color: showEnabledOnly ? theme.success : theme.warning, children: showEnabledOnly
652
+ ? theme.ascii
653
+ ? "[+] enabled"
654
+ : "\u2713 enabled"
655
+ : theme.ascii
656
+ ? "[+] all"
657
+ : "\u2713 all" }), theme.separator, formatUiKeyHint("help", "Help", theme.ascii), theme.separator, formatUiKeyHint("quit", "Quit", theme.ascii)] })] })] })] }))] }));
658
+ };
659
+ /** Compose footer guidance for the current tab and terminal character set. */
660
+ export function createHelpHint(activeTab, selectedStack, selectedService, showEnabledOnly, ascii) {
661
+ const separator = ascii ? " | " : " │ ";
662
+ const enabledHint = formatUiKeyHint("toggle-enabled", showEnabledOnly ? "show all" : "enabled only", ascii);
663
+ const common = [enabledHint, formatUiKeyHint("help", "help", ascii)];
664
+ const cycleTabs = formatUiKeyHint("cycle-tabs", "Tabs", ascii);
665
+ if (activeTab === "overview") {
666
+ const introduction = selectedStack
667
+ ? `Stack "${selectedStack}" selected. ${formatUiKeyHint("select", "view", ascii)}`
668
+ : formatUiKeyHint("list-navigation", "Navigate", ascii);
669
+ const controls = selectedStack
670
+ ? [formatUiKeyHint("back", "back", ascii), ...common]
671
+ : [formatUiKeyHint("select", "Select", ascii), ...common];
672
+ return [introduction, ...controls].join(separator);
673
+ }
674
+ if (activeTab === "resources") {
675
+ return [
676
+ cycleTabs,
677
+ formatUiKeyHint("open-url", "open", ascii),
678
+ formatUiKeyHint("copy-url", "copy", ascii),
679
+ formatUiKeyHint("refresh", "Refresh", ascii),
680
+ formatUiKeyHint("search", "Search", ascii),
681
+ ...common,
682
+ ].join(separator);
683
+ }
684
+ if (activeTab === "files" && selectedService) {
685
+ return [`Service "${selectedService}"`, formatUiKeyHint("back", "Back", ascii), ...common].join(separator);
686
+ }
687
+ if (activeTab === "files") {
688
+ return ["Select service to view files", ...common].join(separator);
689
+ }
690
+ if (activeTab === "events") {
691
+ return [
692
+ formatUiKeyHint("refresh", "Refresh events", ascii),
693
+ formatUiKeyHint("cycle-tabs", "Tabs", ascii),
694
+ formatUiKeyHint("help", "help", ascii),
695
+ formatUiKeyHint("quit", "Quit", ascii),
696
+ ].join(separator);
697
+ }
698
+ if (activeTab === "config") {
699
+ return ["View configurations", ...common].join(separator);
700
+ }
701
+ return [
702
+ formatUiKeyHint("cycle-tabs", "Tabs", ascii),
703
+ formatUiKeyHint("direct-tabs", "Tabs", ascii),
704
+ ...common,
705
+ formatUiKeyHint("quit", "Quit", ascii),
706
+ ].join(separator);
707
+ }
708
+ const TUIRoot = ({ animated, highContrast, }) => (_jsx(TUIThemeContext.Provider, { value: createTUITheme(Boolean(highContrast)), children: _jsx(TUIApp, { animated: animated }) }));
709
+ export const uiCommand = new Command("ui")
710
+ .description("Interactive TUI for managing stacks and services (Neon Edition)")
711
+ .alias("interactive")
712
+ .option("-v, --verbose", "Enable verbose output", false)
713
+ .option("--no-animations", "Disable animations")
714
+ .option("--high-contrast", "Enable high contrast mode")
715
+ .action(async (options) => {
716
+ const tiltAvailable = await isTiltAvailable();
717
+ if (!tiltAvailable) {
718
+ errorFactories.tiltNotInstalled().exit();
719
+ }
720
+ requireProjectRoot();
721
+ // The app has no Static content, so its live layout starts at row 1 in the alternate screen.
722
+ render(_jsx(TUIRoot, { animated: options.animations, highContrast: options.highContrast }), {
723
+ alternateScreen: true,
724
+ });
725
+ });