@niujiajie/freespec 0.0.0-stage → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +58 -2
  3. package/bin/freespec.js +5 -0
  4. package/dist/cli/index.d.ts +25 -0
  5. package/dist/cli/index.js +359 -0
  6. package/dist/commands/shared-output.d.ts +18 -0
  7. package/dist/commands/shared-output.js +61 -0
  8. package/dist/commands/workflow/default-schema.d.ts +2 -0
  9. package/dist/commands/workflow/default-schema.js +4 -0
  10. package/dist/commands/workflow/index.d.ts +13 -0
  11. package/dist/commands/workflow/index.js +10 -0
  12. package/dist/commands/workflow/instructions.d.ts +39 -0
  13. package/dist/commands/workflow/instructions.js +565 -0
  14. package/dist/commands/workflow/new-change.d.ts +19 -0
  15. package/dist/commands/workflow/new-change.js +135 -0
  16. package/dist/commands/workflow/shared.d.ts +94 -0
  17. package/dist/commands/workflow/shared.js +169 -0
  18. package/dist/commands/workflow/status.d.ts +18 -0
  19. package/dist/commands/workflow/status.js +201 -0
  20. package/dist/core/artifact-graph/graph.d.ts +72 -0
  21. package/dist/core/artifact-graph/graph.js +165 -0
  22. package/dist/core/artifact-graph/index.d.ts +9 -0
  23. package/dist/core/artifact-graph/index.js +14 -0
  24. package/dist/core/artifact-graph/instruction-loader.d.ts +208 -0
  25. package/dist/core/artifact-graph/instruction-loader.js +268 -0
  26. package/dist/core/artifact-graph/outputs.d.ts +19 -0
  27. package/dist/core/artifact-graph/outputs.js +351 -0
  28. package/dist/core/artifact-graph/resolver.d.ts +89 -0
  29. package/dist/core/artifact-graph/resolver.js +342 -0
  30. package/dist/core/artifact-graph/schema.d.ts +27 -0
  31. package/dist/core/artifact-graph/schema.js +160 -0
  32. package/dist/core/artifact-graph/state.d.ts +12 -0
  33. package/dist/core/artifact-graph/state.js +31 -0
  34. package/dist/core/artifact-graph/types.d.ts +40 -0
  35. package/dist/core/artifact-graph/types.js +59 -0
  36. package/dist/core/available-tools.d.ts +18 -0
  37. package/dist/core/available-tools.js +49 -0
  38. package/dist/core/change-layout.d.ts +17 -0
  39. package/dist/core/change-layout.js +30 -0
  40. package/dist/core/change-metadata/index.d.ts +2 -0
  41. package/dist/core/change-metadata/index.js +2 -0
  42. package/dist/core/change-metadata/schema.d.ts +23 -0
  43. package/dist/core/change-metadata/schema.js +46 -0
  44. package/dist/core/change-status-policy.d.ts +49 -0
  45. package/dist/core/change-status-policy.js +50 -0
  46. package/dist/core/command-generation/adapters/amazon-q.d.ts +17 -0
  47. package/dist/core/command-generation/adapters/amazon-q.js +32 -0
  48. package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
  49. package/dist/core/command-generation/adapters/antigravity.js +27 -0
  50. package/dist/core/command-generation/adapters/atomcode.d.ts +24 -0
  51. package/dist/core/command-generation/adapters/atomcode.js +46 -0
  52. package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
  53. package/dist/core/command-generation/adapters/auggie.js +28 -0
  54. package/dist/core/command-generation/adapters/bob.d.ts +18 -0
  55. package/dist/core/command-generation/adapters/bob.js +33 -0
  56. package/dist/core/command-generation/adapters/claude.d.ts +13 -0
  57. package/dist/core/command-generation/adapters/claude.js +32 -0
  58. package/dist/core/command-generation/adapters/cline.d.ts +14 -0
  59. package/dist/core/command-generation/adapters/cline.js +27 -0
  60. package/dist/core/command-generation/adapters/codeassistant.d.ts +15 -0
  61. package/dist/core/command-generation/adapters/codeassistant.js +29 -0
  62. package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
  63. package/dist/core/command-generation/adapters/codebuddy.js +29 -0
  64. package/dist/core/command-generation/adapters/codestudio.d.ts +13 -0
  65. package/dist/core/command-generation/adapters/codestudio.js +27 -0
  66. package/dist/core/command-generation/adapters/command-code.d.ts +19 -0
  67. package/dist/core/command-generation/adapters/command-code.js +34 -0
  68. package/dist/core/command-generation/adapters/continue.d.ts +13 -0
  69. package/dist/core/command-generation/adapters/continue.js +33 -0
  70. package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
  71. package/dist/core/command-generation/adapters/costrict.js +28 -0
  72. package/dist/core/command-generation/adapters/crush.d.ts +13 -0
  73. package/dist/core/command-generation/adapters/crush.js +30 -0
  74. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  75. package/dist/core/command-generation/adapters/cursor.js +31 -0
  76. package/dist/core/command-generation/adapters/devin.d.ts +19 -0
  77. package/dist/core/command-generation/adapters/devin.js +36 -0
  78. package/dist/core/command-generation/adapters/easycode.d.ts +17 -0
  79. package/dist/core/command-generation/adapters/easycode.js +28 -0
  80. package/dist/core/command-generation/adapters/factory.d.ts +13 -0
  81. package/dist/core/command-generation/adapters/factory.js +28 -0
  82. package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
  83. package/dist/core/command-generation/adapters/gemini.js +27 -0
  84. package/dist/core/command-generation/adapters/gigacode.d.ts +17 -0
  85. package/dist/core/command-generation/adapters/gigacode.js +31 -0
  86. package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
  87. package/dist/core/command-generation/adapters/github-copilot.js +27 -0
  88. package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
  89. package/dist/core/command-generation/adapters/iflow.js +30 -0
  90. package/dist/core/command-generation/adapters/index.d.ts +40 -0
  91. package/dist/core/command-generation/adapters/index.js +40 -0
  92. package/dist/core/command-generation/adapters/junie.d.ts +13 -0
  93. package/dist/core/command-generation/adapters/junie.js +27 -0
  94. package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
  95. package/dist/core/command-generation/adapters/kilocode.js +23 -0
  96. package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
  97. package/dist/core/command-generation/adapters/kiro.js +27 -0
  98. package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
  99. package/dist/core/command-generation/adapters/lingma.js +30 -0
  100. package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
  101. package/dist/core/command-generation/adapters/oh-my-pi.js +42 -0
  102. package/dist/core/command-generation/adapters/opencode.d.ts +14 -0
  103. package/dist/core/command-generation/adapters/opencode.js +38 -0
  104. package/dist/core/command-generation/adapters/pi.d.ts +18 -0
  105. package/dist/core/command-generation/adapters/pi.js +39 -0
  106. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  107. package/dist/core/command-generation/adapters/qoder.js +30 -0
  108. package/dist/core/command-generation/adapters/qwen.d.ts +17 -0
  109. package/dist/core/command-generation/adapters/qwen.js +31 -0
  110. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  111. package/dist/core/command-generation/adapters/roocode.js +27 -0
  112. package/dist/core/command-generation/adapters/trae.d.ts +13 -0
  113. package/dist/core/command-generation/adapters/trae.js +28 -0
  114. package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
  115. package/dist/core/command-generation/adapters/zcode.js +33 -0
  116. package/dist/core/command-generation/generator.d.ts +29 -0
  117. package/dist/core/command-generation/generator.js +43 -0
  118. package/dist/core/command-generation/index.d.ts +22 -0
  119. package/dist/core/command-generation/index.js +24 -0
  120. package/dist/core/command-generation/invocation.d.ts +73 -0
  121. package/dist/core/command-generation/invocation.js +77 -0
  122. package/dist/core/command-generation/registry.d.ts +36 -0
  123. package/dist/core/command-generation/registry.js +114 -0
  124. package/dist/core/command-generation/toml.d.ts +19 -0
  125. package/dist/core/command-generation/toml.js +39 -0
  126. package/dist/core/command-generation/types.d.ts +63 -0
  127. package/dist/core/command-generation/types.js +8 -0
  128. package/dist/core/command-generation/yaml.d.ts +36 -0
  129. package/dist/core/command-generation/yaml.js +48 -0
  130. package/dist/core/command-surface.d.ts +15 -0
  131. package/dist/core/command-surface.js +33 -0
  132. package/dist/core/config-prompts.d.ts +9 -0
  133. package/dist/core/config-prompts.js +56 -0
  134. package/dist/core/config-schema.d.ts +98 -0
  135. package/dist/core/config-schema.js +308 -0
  136. package/dist/core/config.d.ts +50 -0
  137. package/dist/core/config.js +122 -0
  138. package/dist/core/file-state.d.ts +36 -0
  139. package/dist/core/file-state.js +144 -0
  140. package/dist/core/file-view.d.ts +13 -0
  141. package/dist/core/file-view.js +14 -0
  142. package/dist/core/global-config.d.ts +101 -0
  143. package/dist/core/global-config.js +182 -0
  144. package/dist/core/id.d.ts +17 -0
  145. package/dist/core/id.js +30 -0
  146. package/dist/core/index.d.ts +6 -0
  147. package/dist/core/index.js +7 -0
  148. package/dist/core/init.d.ts +57 -0
  149. package/dist/core/init.js +836 -0
  150. package/dist/core/list.d.ts +11 -0
  151. package/dist/core/list.js +197 -0
  152. package/dist/core/migration.d.ts +121 -0
  153. package/dist/core/migration.js +540 -0
  154. package/dist/core/onboarding-commands.d.ts +30 -0
  155. package/dist/core/onboarding-commands.js +38 -0
  156. package/dist/core/openspec-root.d.ts +47 -0
  157. package/dist/core/openspec-root.js +196 -0
  158. package/dist/core/planning-home.d.ts +16 -0
  159. package/dist/core/planning-home.js +67 -0
  160. package/dist/core/profile-sync-drift.d.ts +31 -0
  161. package/dist/core/profile-sync-drift.js +201 -0
  162. package/dist/core/profiles.d.ts +19 -0
  163. package/dist/core/profiles.js +33 -0
  164. package/dist/core/project-config.d.ts +132 -0
  165. package/dist/core/project-config.js +519 -0
  166. package/dist/core/references.d.ts +10 -0
  167. package/dist/core/references.js +46 -0
  168. package/dist/core/root-selection.d.ts +59 -0
  169. package/dist/core/root-selection.js +132 -0
  170. package/dist/core/shared/allowed-tools.d.ts +12 -0
  171. package/dist/core/shared/allowed-tools.js +12 -0
  172. package/dist/core/shared/ide-restart.d.ts +26 -0
  173. package/dist/core/shared/ide-restart.js +42 -0
  174. package/dist/core/shared/index.d.ts +11 -0
  175. package/dist/core/shared/index.js +11 -0
  176. package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
  177. package/dist/core/shared/skill-content-equivalence.js +45 -0
  178. package/dist/core/shared/skill-files.d.ts +7 -0
  179. package/dist/core/shared/skill-files.js +43 -0
  180. package/dist/core/shared/skill-generation.d.ts +49 -0
  181. package/dist/core/shared/skill-generation.js +125 -0
  182. package/dist/core/shared/skill-paths.d.ts +13 -0
  183. package/dist/core/shared/skill-paths.js +23 -0
  184. package/dist/core/shared/tool-detection.d.ts +99 -0
  185. package/dist/core/shared/tool-detection.js +318 -0
  186. package/dist/core/shared-skill-target.d.ts +51 -0
  187. package/dist/core/shared-skill-target.js +239 -0
  188. package/dist/core/store/errors.d.ts +20 -0
  189. package/dist/core/store/errors.js +22 -0
  190. package/dist/core/store/index.d.ts +2 -0
  191. package/dist/core/store/index.js +2 -0
  192. package/dist/core/styles/palette.d.ts +7 -0
  193. package/dist/core/styles/palette.js +8 -0
  194. package/dist/core/templates/optional-workflow.d.ts +69 -0
  195. package/dist/core/templates/optional-workflow.js +140 -0
  196. package/dist/core/templates/types.d.ts +19 -0
  197. package/dist/core/templates/types.js +5 -0
  198. package/dist/core/templates/workflows/personal.d.ts +22 -0
  199. package/dist/core/templates/workflows/personal.js +225 -0
  200. package/dist/core/update-plan.d.ts +127 -0
  201. package/dist/core/update-plan.js +390 -0
  202. package/dist/core/update.d.ts +154 -0
  203. package/dist/core/update.js +723 -0
  204. package/dist/core/version-check.d.ts +188 -0
  205. package/dist/core/version-check.js +906 -0
  206. package/dist/core/zod-issues.d.ts +4 -0
  207. package/dist/core/zod-issues.js +10 -0
  208. package/dist/index.d.ts +3 -0
  209. package/dist/index.js +3 -0
  210. package/dist/prompts/searchable-multi-select.d.ts +35 -0
  211. package/dist/prompts/searchable-multi-select.js +188 -0
  212. package/dist/telemetry/config.d.ts +37 -0
  213. package/dist/telemetry/config.js +148 -0
  214. package/dist/telemetry/index.d.ts +39 -0
  215. package/dist/telemetry/index.js +217 -0
  216. package/dist/telemetry/opt-out.d.ts +17 -0
  217. package/dist/telemetry/opt-out.js +27 -0
  218. package/dist/ui/ascii-patterns.d.ts +16 -0
  219. package/dist/ui/ascii-patterns.js +133 -0
  220. package/dist/ui/welcome-screen.d.ts +18 -0
  221. package/dist/ui/welcome-screen.js +199 -0
  222. package/dist/utils/change-metadata.d.ts +116 -0
  223. package/dist/utils/change-metadata.js +324 -0
  224. package/dist/utils/change-utils.d.ts +79 -0
  225. package/dist/utils/change-utils.js +121 -0
  226. package/dist/utils/ci.d.ts +12 -0
  227. package/dist/utils/ci.js +16 -0
  228. package/dist/utils/command-references.d.ts +104 -0
  229. package/dist/utils/command-references.js +190 -0
  230. package/dist/utils/date.d.ts +8 -0
  231. package/dist/utils/date.js +13 -0
  232. package/dist/utils/executable-path.d.ts +5 -0
  233. package/dist/utils/executable-path.js +44 -0
  234. package/dist/utils/file-system.d.ts +58 -0
  235. package/dist/utils/file-system.js +432 -0
  236. package/dist/utils/index.d.ts +6 -0
  237. package/dist/utils/index.js +9 -0
  238. package/dist/utils/interactive.d.ts +85 -0
  239. package/dist/utils/interactive.js +174 -0
  240. package/dist/utils/item-discovery.d.ts +15 -0
  241. package/dist/utils/item-discovery.js +33 -0
  242. package/dist/utils/line-endings.d.ts +34 -0
  243. package/dist/utils/line-endings.js +46 -0
  244. package/dist/utils/match.d.ts +3 -0
  245. package/dist/utils/match.js +22 -0
  246. package/dist/utils/nested-change.d.ts +35 -0
  247. package/dist/utils/nested-change.js +185 -0
  248. package/dist/utils/shell-detection.d.ts +20 -0
  249. package/dist/utils/shell-detection.js +94 -0
  250. package/dist/utils/task-progress.d.ts +67 -0
  251. package/dist/utils/task-progress.js +184 -0
  252. package/package.json +74 -4
  253. package/schemas//351/273/230/350/256/244/schema.yaml +68 -0
  254. package/schemas//351/273/230/350/256/244/templates//344/273/273/345/212/241/346/270/205/345/215/225.md +25 -0
  255. package/schemas//351/273/230/350/256/244/templates//346/265/213/350/257/225/347/224/250/344/276/213.md +34 -0
  256. package/schemas//351/273/230/350/256/244/templates//350/256/276/350/256/241/346/226/207/346/241/243.md +172 -0
  257. package/schemas//351/273/230/350/256/244/templates//351/234/200/346/261/202/346/226/207/346/241/243.md +19 -0
  258. package/skill-files//344/273/273/345/212/241/references//346/212/244/346/240/217.md +43 -0
  259. package/skill-files//344/273/273/345/212/241/references//347/262/222/345/272/246.md +23 -0
  260. package/skill-files//344/273/273/345/212/241/references//347/272/246/346/235/237.md +8 -0
  261. package/skill-files//344/273/273/345/212/241/scripts/check-tasks.mjs +83 -0
  262. package/skill-files//345/206/231Java/references/Java/350/247/204/350/214/203.md +25 -0
  263. package/skill-files//345/206/231Java/references//345/215/261/351/231/251/345/206/231/346/263/225.md +27 -0
  264. package/skill-files//345/206/231Java/references//345/256/214/346/210/220/345/257/271/347/205/247.md +15 -0
  265. package/skill-files//345/206/231Java/references//346/212/244/346/240/217.md +35 -0
  266. package/skill-files//345/206/231Java/references//347/272/246/346/235/237.md +8 -0
  267. package/skill-files//345/206/231Java/references//350/270/251/345/235/221.md +17 -0
  268. package/skill-files//345/256/241Java/references//346/212/244/346/240/217.md +19 -0
  269. package/skill-files//345/256/241Java/references//347/244/272/344/276/213.md +23 -0
  270. package/skill-files//345/256/241Java/references//347/272/246/346/235/237.md +7 -0
  271. package/skill-files//345/256/241Java/references//350/247/204/345/210/231.md +23 -0
  272. package/skill-files//345/256/241Java/references//351/242/206/345/237/237/346/243/200/351/241/271.md +58 -0
  273. package/skill-files//345/256/241/346/226/207/346/241/243/references//346/212/244/346/240/217.md +18 -0
  274. package/skill-files//345/256/241/346/226/207/346/241/243/references//347/272/246/346/235/237.md +8 -0
  275. package/skill-files//345/256/241/346/226/207/346/241/243/references//347/273/264/345/272/246.md +17 -0
  276. package/skill-files//345/256/241/346/226/207/346/241/243/references//350/270/251/345/235/221.md +11 -0
  277. package/skill-files//346/226/260/345/273/272/references//344/272/244/346/216/245.md +11 -0
  278. package/skill-files//346/226/260/345/273/272/references//346/212/244/346/240/217.md +14 -0
  279. package/skill-files//346/226/260/345/273/272/references//347/272/246/346/235/237.md +7 -0
  280. package/skill-files//346/267/261/346/200/235/config/prompts.yaml +114 -0
  281. package/skill-files//346/267/261/346/200/235/references//344/270/211/346/226/271/346/240/270/345/257/271.md +72 -0
  282. package/skill-files//346/267/261/346/200/235/references//344/276/213/345/255/220.md +39 -0
  283. package/skill-files//346/267/261/346/200/235/references//346/200/216/344/271/210/346/203/263.md +61 -0
  284. package/skill-files//346/267/261/346/200/235/references//346/212/244/346/240/217.md +26 -0
  285. package/skill-files//346/267/261/346/200/235/references//347/202/271/351/200/211/346/217/220/351/227/256.md +66 -0
  286. package/skill-files//346/267/261/346/200/235/references//347/231/273/350/256/260.md +45 -0
  287. package/skill-files//346/267/261/346/200/235/references//347/272/246/346/235/237.md +9 -0
  288. package/skill-files//347/224/250/344/276/213/references//346/200/216/344/271/210/345/206/231.md +13 -0
  289. package/skill-files//347/224/250/344/276/213/references//346/212/244/346/240/217.md +42 -0
  290. package/skill-files//347/224/250/344/276/213/references//347/272/246/346/235/237.md +5 -0
  291. package/skill-files//347/224/250/344/276/213/scripts/check-cases.mjs +70 -0
  292. package/skill-files//350/256/276/350/256/241/references//345/207/206/345/244/207.md +25 -0
  293. package/skill-files//350/256/276/350/256/241/references//346/212/244/346/240/217.md +134 -0
  294. package/skill-files//350/256/276/350/256/241/references//346/226/207/351/243/216.md +17 -0
  295. package/skill-files//350/256/276/350/256/241/references//347/225/214/351/235/242.md +18 -0
  296. package/skill-files//350/256/276/350/256/241/references//347/261/273/345/236/213.md +19 -0
  297. package/skill-files//350/256/276/350/256/241/references//347/272/246/346/235/237.md +25 -0
  298. package/skill-files//350/256/276/350/256/241/scripts/_smoke.mjs +72 -0
  299. package/skill-files//350/256/276/350/256/241/scripts/check-design.mjs +331 -0
  300. package/skill-files//351/234/200/346/261/202/references//346/212/244/346/240/217.md +26 -0
  301. package/skill-files//351/234/200/346/261/202/references//346/226/207/351/243/216.md +23 -0
  302. package/skill-files//351/234/200/346/261/202/references//346/267/261/346/200/235/346/221/204/345/205/245.md +21 -0
  303. package/skill-files//351/234/200/346/261/202/references//347/272/246/346/235/237.md +8 -0
  304. package/skill-files//351/234/200/346/261/202/references//351/234/200/346/261/202/345/276/205/345/256/232.md +13 -0
  305. package/skill-files//351/234/200/346/261/202/scripts/check-requirements.mjs +60 -0
  306. package/skill-files//351/252/214/346/224/266/references//346/212/244/346/240/217.md +27 -0
  307. package/skill-files//351/252/214/346/224/266/references//346/240/270/345/257/271.md +17 -0
  308. package/skill-files//351/252/214/346/224/266/references//347/272/246/346/235/237.md +7 -0
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Telemetry module for anonymous usage analytics.
3
+ *
4
+ * Privacy-first design:
5
+ * - Only tracks command name and version
6
+ * - No arguments, file paths, or content
7
+ * - Opt-out via OPENSPEC_TELEMETRY=0, DO_NOT_TRACK=1, or
8
+ * `freespec config set telemetry.enabled false`
9
+ * - Auto-disabled in CI environments
10
+ * - Anonymous ID is a random UUID with no relation to the user
11
+ *
12
+ * Events are sent with a plain fetch to PostHog's stable public `/batch/`
13
+ * endpoint — the same one posthog-node used — instead of through the SDK.
14
+ * The SDK's only remaining job here was the wire format: every reliability
15
+ * knob was already forced to "send one event immediately, time-bounded,
16
+ * never retry, never throw". Carrying `posthog-node` for that shipped its
17
+ * fast-moving transitive tree (`@posthog/core`, `@posthog/types`, multiple
18
+ * releases per day) to every downstream consumer, where supply-chain age
19
+ * policies such as pnpm's `minimumReleaseAge` rejected the freshly published
20
+ * versions and broke installs (#1390).
21
+ */
22
+ import { randomUUID } from 'crypto';
23
+ import { getGlobalConfig, isGlobalConfigUnreadable } from '../core/global-config.js';
24
+ import { isCiEnvironment } from '../utils/ci.js';
25
+ import { getTelemetryConfig, updateTelemetryConfig } from './config.js';
26
+ import { isTelemetryOptedOutByEnv } from './opt-out.js';
27
+ // PostHog API key - public key for client-side analytics
28
+ // This is safe to embed as it only allows sending events, not reading data
29
+ const POSTHOG_API_KEY = 'phc_Hthu8YvaIJ9QaFKyTG4TbVwkbd5ktcAFzVTKeMmoW2g';
30
+ // Using reverse proxy to avoid ad blockers and keep traffic on our domain
31
+ const POSTHOG_HOST = 'https://edge.openspec.dev';
32
+ const TELEMETRY_REQUEST_TIMEOUT_MS = 1000;
33
+ let anonymousId = null;
34
+ /**
35
+ * Requests started by trackCommand and not yet settled, so shutdown can
36
+ * flush them before the process exits. Each request is individually
37
+ * time-bounded, so awaiting them cannot stall exit for more than the
38
+ * request timeout.
39
+ */
40
+ const pendingEvents = new Set();
41
+ async function safeTelemetryFetch(url, options) {
42
+ try {
43
+ const response = await fetch(url, options);
44
+ // Telemetry never reads the body, but undici keeps the connection
45
+ // occupied until the body is consumed or canceled — dispose of it on
46
+ // every path so no socket outlives shutdown().
47
+ if (response.body) {
48
+ await response.body.cancel();
49
+ }
50
+ if (response.ok) {
51
+ return response;
52
+ }
53
+ }
54
+ catch {
55
+ // Silent failure - telemetry should never surface network noise
56
+ }
57
+ return new Response(null, { status: 204 });
58
+ }
59
+ /**
60
+ * Check if telemetry is enabled.
61
+ *
62
+ * Precedence (first match wins):
63
+ * 1. OPENSPEC_TELEMETRY set to anything but an on-value (1/true/yes/on) → disabled
64
+ * 2. DO_NOT_TRACK set to anything but an off-value (0/false/no/off) → disabled
65
+ * 3. CI set to a truthy/on value → disabled (same rule as version-check)
66
+ * 4. global config telemetry.enabled === false → disabled
67
+ * 5. global config file exists but cannot be parsed → disabled
68
+ * 6. otherwise enabled (unset config means on; opt-out model)
69
+ *
70
+ * Kept synchronous so call sites need not become async. Reads config via
71
+ * sync getGlobalConfig() rather than async getTelemetryConfig().
72
+ */
73
+ export function isTelemetryEnabled() {
74
+ // Explicit opt-out, and the DO_NOT_TRACK standard. Both are read
75
+ // tolerantly (see opt-out.ts): an opt-out that only worked for one exact
76
+ // spelling would leave users tracked who believe they are not.
77
+ if (isTelemetryOptedOutByEnv()) {
78
+ return false;
79
+ }
80
+ // Auto-disable in CI environments (providers use true/1/yes/…)
81
+ if (isCiEnvironment()) {
82
+ return false;
83
+ }
84
+ // Global config opt-out (env/CI remain hard overrides above)
85
+ if (getGlobalConfig().telemetry?.enabled === false) {
86
+ return false;
87
+ }
88
+ // A config file that cannot be parsed reads as defaults, which carry no
89
+ // opt-out, but the file itself may hold one. Unknown is not consent.
90
+ if (isGlobalConfigUnreadable()) {
91
+ return false;
92
+ }
93
+ return true;
94
+ }
95
+ /**
96
+ * Get or create the anonymous user ID.
97
+ * Lazily generates a UUID on first call and persists it.
98
+ */
99
+ export async function getOrCreateAnonymousId() {
100
+ // Return cached value if available
101
+ if (anonymousId) {
102
+ return anonymousId;
103
+ }
104
+ // Try to load from config
105
+ const config = await getTelemetryConfig();
106
+ if (config.anonymousId) {
107
+ anonymousId = config.anonymousId;
108
+ return anonymousId;
109
+ }
110
+ // Generate new UUID and persist
111
+ anonymousId = randomUUID();
112
+ await updateTelemetryConfig({ anonymousId });
113
+ return anonymousId;
114
+ }
115
+ /**
116
+ * Send one capture event to PostHog's batch endpoint. Fire-and-forget:
117
+ * bounded by the request timeout, never throws, never retries.
118
+ */
119
+ function sendEvent(distinctId, event, properties) {
120
+ const body = JSON.stringify({
121
+ api_key: POSTHOG_API_KEY,
122
+ batch: [
123
+ {
124
+ type: 'capture',
125
+ event,
126
+ distinct_id: distinctId,
127
+ properties,
128
+ timestamp: new Date().toISOString(),
129
+ },
130
+ ],
131
+ });
132
+ const request = safeTelemetryFetch(`${POSTHOG_HOST}/batch/`, {
133
+ method: 'POST',
134
+ headers: { 'content-type': 'application/json' },
135
+ body,
136
+ signal: AbortSignal.timeout(TELEMETRY_REQUEST_TIMEOUT_MS),
137
+ }).then(() => undefined);
138
+ pendingEvents.add(request);
139
+ void request.finally(() => pendingEvents.delete(request));
140
+ }
141
+ /**
142
+ * Track a command execution.
143
+ *
144
+ * @param commandName - The command name (e.g., 'init', 'change:apply')
145
+ * @param version - The FreeSpec version
146
+ */
147
+ export async function trackCommand(commandName, version) {
148
+ if (!isTelemetryEnabled()) {
149
+ return;
150
+ }
151
+ try {
152
+ // Disclosure before collection. A --json run defers the notice (printing
153
+ // it would corrupt machine-readable output), and for a user whose runs
154
+ // are all --json it may never have appeared — so nothing is sent, and no
155
+ // anonymous id is created, until the notice has actually been shown.
156
+ if (!(await getTelemetryConfig()).noticeSeen) {
157
+ return;
158
+ }
159
+ const userId = await getOrCreateAnonymousId();
160
+ sendEvent(userId, 'command_executed', {
161
+ command: commandName,
162
+ version: version,
163
+ surface: 'cli',
164
+ $ip: null, // Explicitly disable IP tracking
165
+ });
166
+ }
167
+ catch {
168
+ // Silent failure - telemetry should never break CLI
169
+ }
170
+ }
171
+ /**
172
+ * Show first-run telemetry notice if not already seen.
173
+ */
174
+ export async function maybeShowTelemetryNotice(options = {}) {
175
+ if (!isTelemetryEnabled()) {
176
+ return;
177
+ }
178
+ try {
179
+ const config = await getTelemetryConfig();
180
+ if (config.noticeSeen) {
181
+ return;
182
+ }
183
+ // In --json mode the notice would pollute stdout and break parsers, so
184
+ // defer it: skip the notice AND leave noticeSeen unset so the disclosure
185
+ // still appears on the user's first later non-JSON run.
186
+ if (options.silent) {
187
+ return;
188
+ }
189
+ // Display notice on stderr, not stdout: stdout is reserved for command
190
+ // output (raw passthrough text, JSON, etc.) and must stay parser/pipe-safe.
191
+ console.error('Note: FreeSpec collects anonymous usage stats. Opt out: OPENSPEC_TELEMETRY=0 or freespec config set telemetry.enabled false');
192
+ // Mark as seen
193
+ await updateTelemetryConfig({ noticeSeen: true });
194
+ }
195
+ catch {
196
+ // Silent failure - telemetry should never break CLI
197
+ }
198
+ }
199
+ /**
200
+ * Flush pending telemetry events.
201
+ * Call this before CLI exit.
202
+ */
203
+ export async function shutdown() {
204
+ if (pendingEvents.size === 0) {
205
+ return;
206
+ }
207
+ try {
208
+ await Promise.allSettled([...pendingEvents]);
209
+ }
210
+ catch {
211
+ // Silent failure - telemetry should never break CLI exit
212
+ }
213
+ finally {
214
+ pendingEvents.clear();
215
+ }
216
+ }
217
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Telemetry opt-out signals, shared by telemetry and the version check so both
3
+ * outbound surfaces honor exactly the same values.
4
+ *
5
+ * Parsing is tolerant in the style of isCiEnvironment(): a user who wrote
6
+ * DO_NOT_TRACK=true meant it, and an exact-match test that silently kept
7
+ * sending would be a privacy control that does not work. Ambiguity fails
8
+ * safe — a value we cannot read as "on" suppresses the request rather than
9
+ * enabling it.
10
+ */
11
+ /** True when `DO_NOT_TRACK` is set to anything but an explicit off-value. */
12
+ export declare function isDoNotTrackSet(env?: NodeJS.ProcessEnv): boolean;
13
+ /** True when `OPENSPEC_TELEMETRY` is set to anything but an explicit on-value. */
14
+ export declare function isTelemetryDisabledByEnv(env?: NodeJS.ProcessEnv): boolean;
15
+ /** True when either opt-out signal asks us to stay off the network. */
16
+ export declare function isTelemetryOptedOutByEnv(env?: NodeJS.ProcessEnv): boolean;
17
+ //# sourceMappingURL=opt-out.d.ts.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Telemetry opt-out signals, shared by telemetry and the version check so both
3
+ * outbound surfaces honor exactly the same values.
4
+ *
5
+ * Parsing is tolerant in the style of isCiEnvironment(): a user who wrote
6
+ * DO_NOT_TRACK=true meant it, and an exact-match test that silently kept
7
+ * sending would be a privacy control that does not work. Ambiguity fails
8
+ * safe — a value we cannot read as "on" suppresses the request rather than
9
+ * enabling it.
10
+ */
11
+ const ON_VALUES = new Set(['1', 'true', 'yes', 'on']);
12
+ const OFF_VALUES = new Set(['', '0', 'false', 'no', 'off']);
13
+ /** True when `DO_NOT_TRACK` is set to anything but an explicit off-value. */
14
+ export function isDoNotTrackSet(env = process.env) {
15
+ const value = env.DO_NOT_TRACK;
16
+ return value !== undefined && !OFF_VALUES.has(value.trim().toLowerCase());
17
+ }
18
+ /** True when `OPENSPEC_TELEMETRY` is set to anything but an explicit on-value. */
19
+ export function isTelemetryDisabledByEnv(env = process.env) {
20
+ const value = env.OPENSPEC_TELEMETRY;
21
+ return value !== undefined && !ON_VALUES.has(value.trim().toLowerCase());
22
+ }
23
+ /** True when either opt-out signal asks us to stay off the network. */
24
+ export function isTelemetryOptedOutByEnv(env = process.env) {
25
+ return isTelemetryDisabledByEnv(env) || isDoNotTrackSet(env);
26
+ }
27
+ //# sourceMappingURL=opt-out.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * ASCII art animation patterns for the welcome screen.
3
+ * FreeSpec logo animation - diamond/rhombus shape with hollow center "O".
4
+ */
5
+ /**
6
+ * Welcome animation frames - FreeSpec logo building from center
7
+ * 7 rows × 6 columns diamond with hollow center "O"
8
+ * Center bar is 2 cols × 3 rows (rows 3,4,5 cols 3,4)
9
+ * Each frame is an array of strings (lines of ASCII art)
10
+ * Grid: 6 cols × 2 chars = 12 chars wide
11
+ */
12
+ export declare const WELCOME_ANIMATION: {
13
+ interval: number;
14
+ frames: string[][];
15
+ };
16
+ //# sourceMappingURL=ascii-patterns.d.ts.map
@@ -0,0 +1,133 @@
1
+ /**
2
+ * ASCII art animation patterns for the welcome screen.
3
+ * FreeSpec logo animation - diamond/rhombus shape with hollow center "O".
4
+ */
5
+ // Detect if full Unicode is supported
6
+ const supportsUnicode = process.platform !== 'win32' ||
7
+ !!process.env.WT_SESSION || // Windows Terminal
8
+ !!process.env.TERM_PROGRAM; // Modern terminal
9
+ // Character set based on Unicode support
10
+ // Block characters for pixel-art aesthetic
11
+ const CHARS = supportsUnicode
12
+ ? { full: '██', dim: '░░', empty: ' ' }
13
+ : { full: '##', dim: '++', empty: ' ' };
14
+ const _ = CHARS.empty;
15
+ const F = CHARS.full;
16
+ const D = CHARS.dim;
17
+ /**
18
+ * Welcome animation frames - FreeSpec logo building from center
19
+ * 7 rows × 6 columns diamond with hollow center "O"
20
+ * Center bar is 2 cols × 3 rows (rows 3,4,5 cols 3,4)
21
+ * Each frame is an array of strings (lines of ASCII art)
22
+ * Grid: 6 cols × 2 chars = 12 chars wide
23
+ */
24
+ export const WELCOME_ANIMATION = {
25
+ interval: 120,
26
+ frames: [
27
+ // Frame 1: Empty
28
+ [
29
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
30
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
31
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
32
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
33
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
34
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
35
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
36
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
37
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
38
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
39
+ ],
40
+ // Frame 2: Center blocks appear (dim) - 2x3 center bar
41
+ [
42
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
43
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
44
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
45
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
46
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
47
+ `${_}${_}${_}${_}${D}${D}${_}${_}`,
48
+ `${_}${_}${_}${_}${D}${D}${_}${_}`,
49
+ `${_}${_}${_}${_}${D}${D}${_}${_}`,
50
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
51
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
52
+ ],
53
+ // Frame 3: Center blocks solidify
54
+ [
55
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
56
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
57
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
58
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
59
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
60
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
61
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
62
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
63
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
64
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
65
+ ],
66
+ // Frame 4: Top and bottom points appear
67
+ [
68
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
69
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
70
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
71
+ `${_}${_}${_}${_}${D}${D}${_}${_}`,
72
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
73
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
74
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
75
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
76
+ `${_}${_}${_}${_}${_}${_}${_}${_}`,
77
+ `${_}${_}${_}${_}${D}${D}${_}${_}`,
78
+ ],
79
+ // Frame 5: Inner ring forming
80
+ [
81
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
82
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
83
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
84
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
85
+ `${_}${_}${_}${D}${_}${_}${D}${_}`,
86
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
87
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
88
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
89
+ `${_}${_}${_}${D}${_}${_}${D}${_}`,
90
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
91
+ ],
92
+ // Frame 6: Outer ring appearing
93
+ [
94
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
95
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
96
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
97
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
98
+ `${_}${_}${_}${F}${_}${_}${F}${_}`,
99
+ `${_}${_}${D}${_}${F}${F}${_}${D}`,
100
+ `${_}${_}${D}${_}${F}${F}${_}${D}`,
101
+ `${_}${_}${D}${_}${F}${F}${_}${D}`,
102
+ `${_}${_}${_}${F}${_}${_}${F}${_}`,
103
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
104
+ ],
105
+ // Frame 7: Full logo
106
+ [
107
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
108
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
109
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
110
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
111
+ `${_}${_}${_}${F}${_}${_}${F}${_}`,
112
+ `${_}${_}${F}${_}${F}${F}${_}${F}`,
113
+ `${_}${_}${F}${_}${F}${F}${_}${F}`,
114
+ `${_}${_}${F}${_}${F}${F}${_}${F}`,
115
+ `${_}${_}${_}${F}${_}${_}${F}${_}`,
116
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
117
+ ],
118
+ // Frame 8: Hold complete logo
119
+ [
120
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
121
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
122
+ `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
123
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
124
+ `${_}${_}${_}${F}${_}${_}${F}${_}`,
125
+ `${_}${_}${F}${_}${F}${F}${_}${F}`,
126
+ `${_}${_}${F}${_}${F}${F}${_}${F}`,
127
+ `${_}${_}${F}${_}${F}${F}${_}${F}`,
128
+ `${_}${_}${_}${F}${_}${_}${F}${_}`,
129
+ `${_}${_}${_}${_}${F}${F}${_}${_}`,
130
+ ],
131
+ ],
132
+ };
133
+ //# sourceMappingURL=ascii-patterns.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Animated welcome screen for the experimental artifact workflow setup.
3
+ * Shows side-by-side layout with animated ASCII art on left and welcome text on right.
4
+ */
5
+ /**
6
+ * Best-effort check of the OS-level reduced-motion preference (#722).
7
+ * Any lookup failure (missing binary, unset key, timeout) means
8
+ * "no preference detected" and animation stays enabled.
9
+ */
10
+ export declare function prefersReducedMotion(platform?: NodeJS.Platform): boolean;
11
+ /**
12
+ * Shows the animated welcome screen.
13
+ * Returns when user presses Enter.
14
+ */
15
+ export declare function showWelcomeScreen(workflows: readonly string[], options?: {
16
+ animate?: boolean;
17
+ }): Promise<void>;
18
+ //# sourceMappingURL=welcome-screen.d.ts.map
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Animated welcome screen for the experimental artifact workflow setup.
3
+ * Shows side-by-side layout with animated ASCII art on left and welcome text on right.
4
+ */
5
+ import chalk from 'chalk';
6
+ import { execFileSync, } from 'node:child_process';
7
+ import { WELCOME_ANIMATION } from './ascii-patterns.js';
8
+ import { getOnboardingCommands } from '../core/onboarding-commands.js';
9
+ // Minimum terminal width for side-by-side layout
10
+ const MIN_WIDTH = 60;
11
+ // Width of the ASCII art column (with padding)
12
+ const ART_COLUMN_WIDTH = 24;
13
+ /**
14
+ * Welcome text content (right column)
15
+ */
16
+ function getWelcomeText(workflows) {
17
+ const onboardingCommands = getOnboardingCommands(workflows);
18
+ const quickStart = [];
19
+ if (onboardingCommands.length > 0) {
20
+ const commandWidth = Math.max(...onboardingCommands.map((c) => c.command.length));
21
+ quickStart.push(chalk.white('Quick start after setup:'));
22
+ for (const { command, description } of onboardingCommands) {
23
+ quickStart.push(` ${chalk.yellow(command.padEnd(commandWidth + 1))} ${chalk.dim(description)}`);
24
+ }
25
+ // These are the canonical names. How each tool spells them differs
26
+ // (/freespec-新建, @freespec-新建, $freespec-新建 ...) and cannot be known
27
+ // until tools are picked, one prompt later — so flag it rather than let the
28
+ // canonical form read as the literal thing to type. "Getting started"
29
+ // prints the real spelling once the selection is known.
30
+ quickStart.push(chalk.dim(' (spelling varies by tool)'));
31
+ quickStart.push('');
32
+ }
33
+ return [
34
+ chalk.white.bold('Welcome to FreeSpec'),
35
+ chalk.dim('变更目录、可选文档、按技能实现'),
36
+ '',
37
+ chalk.white('This setup will configure:'),
38
+ chalk.dim(' • Agent Skills for AI tools'),
39
+ // Not "freespec slash commands": this screen runs before tool selection, and
40
+ // skills-only tools (Codex, Kimi Code, ...) correctly get no command files
41
+ // at all. The exact spelling per tool is printed in "Getting started".
42
+ chalk.dim(' • Workflow commands, if supported'),
43
+ '',
44
+ ...quickStart,
45
+ chalk.cyan('Press Enter to select tools...'),
46
+ ];
47
+ }
48
+ /**
49
+ * Renders a single frame with side-by-side layout
50
+ */
51
+ function renderFrame(artLines, textLines) {
52
+ const maxLines = Math.max(artLines.length, textLines.length);
53
+ const lines = [];
54
+ for (let i = 0; i < maxLines; i++) {
55
+ const artLine = artLines[i] || '';
56
+ const textLine = textLines[i] || '';
57
+ // Pad the art column to fixed width
58
+ const paddedArt = artLine.padEnd(ART_COLUMN_WIDTH);
59
+ // Color the ASCII art with cyan for visual appeal
60
+ const coloredArt = chalk.cyan(paddedArt);
61
+ // Clear line before writing to prevent residual characters
62
+ lines.push(`\x1b[2K${coloredArt}${textLine}`);
63
+ }
64
+ return lines.join('\n');
65
+ }
66
+ const REDUCED_MOTION_EXEC_OPTIONS = {
67
+ encoding: 'utf8',
68
+ timeout: 500,
69
+ // SIGKILL so a wedged lookup can never outlive the timeout and stall init.
70
+ killSignal: 'SIGKILL',
71
+ stdio: ['ignore', 'pipe', 'ignore'],
72
+ };
73
+ /**
74
+ * Best-effort check of the OS-level reduced-motion preference (#722).
75
+ * Any lookup failure (missing binary, unset key, timeout) means
76
+ * "no preference detected" and animation stays enabled.
77
+ */
78
+ export function prefersReducedMotion(platform = process.platform) {
79
+ try {
80
+ if (platform === 'darwin') {
81
+ // The key only exists once the user has toggled Reduce Motion; when it
82
+ // is unset `defaults` exits non-zero and lands in the catch below.
83
+ const out = execFileSync('defaults', ['read', 'com.apple.universalaccess', 'reduceMotion'], REDUCED_MOTION_EXEC_OPTIONS);
84
+ return out.trim() === '1';
85
+ }
86
+ if (platform === 'linux') {
87
+ const out = execFileSync('gsettings', ['get', 'org.gnome.desktop.interface', 'enable-animations'], REDUCED_MOTION_EXEC_OPTIONS);
88
+ return out.trim() === 'false';
89
+ }
90
+ }
91
+ catch {
92
+ // Detection is best-effort only.
93
+ }
94
+ return false;
95
+ }
96
+ /**
97
+ * Checks if the terminal supports animation
98
+ */
99
+ function canAnimate() {
100
+ // Must be TTY
101
+ if (!process.stdout.isTTY)
102
+ return false;
103
+ // Respect NO_COLOR
104
+ if (process.env.NO_COLOR)
105
+ return false;
106
+ // Manual override for users who need reduced motion (#722). Presence is
107
+ // what counts: even an empty value disables the animation.
108
+ if (process.env.OPENSPEC_NO_ANIMATION !== undefined)
109
+ return false;
110
+ // Check terminal width
111
+ const columns = process.stdout.columns || 80;
112
+ if (columns < MIN_WIDTH)
113
+ return false;
114
+ // Last so only interactive terminals pay for the OS lookup
115
+ if (prefersReducedMotion())
116
+ return false;
117
+ return true;
118
+ }
119
+ /**
120
+ * Wait for Enter key press
121
+ */
122
+ async function waitForEnter() {
123
+ if (!process.stdin.isTTY) {
124
+ return;
125
+ }
126
+ // Keep all interactive input on Inquirer's keypress lifecycle. Mixing a raw
127
+ // `data` listener between Inquirer prompts breaks arrow/space keys on Windows.
128
+ const { createPrompt, isEnterKey, useKeypress } = await import('@inquirer/core');
129
+ const prompt = createPrompt((_config, done) => {
130
+ useKeypress((key) => {
131
+ if (key.ctrl && key.name === 'c') {
132
+ process.stdout.write('\n');
133
+ process.exit(0);
134
+ }
135
+ if (isEnterKey(key)) {
136
+ done(undefined);
137
+ }
138
+ });
139
+ return '';
140
+ });
141
+ await prompt({});
142
+ }
143
+ /**
144
+ * Shows the animated welcome screen.
145
+ * Returns when user presses Enter.
146
+ */
147
+ export async function showWelcomeScreen(workflows, options = {}) {
148
+ const textLines = getWelcomeText(workflows);
149
+ if (options.animate === false || !canAnimate()) {
150
+ // Fallback: show static welcome. The "Press Enter" line is only honest
151
+ // when we actually wait; in a TTY, returning immediately would let the
152
+ // Enter it asks for fall through into the tool picker and submit the
153
+ // pre-selected tools sight-unseen. Without a TTY, drop the line instead.
154
+ const staticLines = process.stdin.isTTY
155
+ ? textLines
156
+ : textLines.filter((line) => !line.includes('Press Enter'));
157
+ const frame = WELCOME_ANIMATION.frames[3]; // Peak frame
158
+ process.stdout.write('\n' + renderFrame(frame, staticLines) + '\n\n');
159
+ await waitForEnter();
160
+ return;
161
+ }
162
+ let frameIndex = 0;
163
+ let running = true;
164
+ let isFirstRender = true;
165
+ // Content height for cursor movement between frames
166
+ const numContentLines = Math.max(WELCOME_ANIMATION.frames[0].length, textLines.length);
167
+ const frameHeight = numContentLines + 1; // internal newlines (11) + trailing newlines (2) = 13
168
+ // Total height including initial newline (for cleanup)
169
+ const totalHeight = frameHeight + 1; // 14
170
+ // Initial render
171
+ process.stdout.write('\n');
172
+ // Animation loop
173
+ const interval = setInterval(() => {
174
+ if (!running)
175
+ return;
176
+ const frame = WELCOME_ANIMATION.frames[frameIndex];
177
+ // Move cursor up to overwrite previous frame (always after first render)
178
+ if (!isFirstRender) {
179
+ process.stdout.write(`\x1b[${frameHeight}A`);
180
+ }
181
+ isFirstRender = false;
182
+ // Render current frame
183
+ process.stdout.write(renderFrame(frame, textLines) + '\n\n');
184
+ // Advance to next frame
185
+ frameIndex = (frameIndex + 1) % WELCOME_ANIMATION.frames.length;
186
+ }, WELCOME_ANIMATION.interval);
187
+ // Wait for Enter
188
+ await waitForEnter();
189
+ // Stop animation
190
+ running = false;
191
+ clearInterval(interval);
192
+ // Clear the welcome screen and move on
193
+ process.stdout.write(`\x1b[${totalHeight}A`);
194
+ for (let i = 0; i < totalHeight; i++) {
195
+ process.stdout.write('\x1b[2K\n'); // Clear line
196
+ }
197
+ process.stdout.write(`\x1b[${totalHeight}A`); // Move back up
198
+ }
199
+ //# sourceMappingURL=welcome-screen.js.map