@dynamicworks/br-openspec 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +210 -0
  3. package/README.pt-BR.md +212 -0
  4. package/bin/openspec.js +3 -0
  5. package/dist/cli/index.d.ts +2 -0
  6. package/dist/cli/index.js +484 -0
  7. package/dist/commands/change.d.ts +35 -0
  8. package/dist/commands/change.js +278 -0
  9. package/dist/commands/completion.d.ts +72 -0
  10. package/dist/commands/completion.js +258 -0
  11. package/dist/commands/config.d.ts +36 -0
  12. package/dist/commands/config.js +553 -0
  13. package/dist/commands/feedback.d.ts +9 -0
  14. package/dist/commands/feedback.js +184 -0
  15. package/dist/commands/schema.d.ts +6 -0
  16. package/dist/commands/schema.js +869 -0
  17. package/dist/commands/show.d.ts +14 -0
  18. package/dist/commands/show.js +133 -0
  19. package/dist/commands/spec.d.ts +15 -0
  20. package/dist/commands/spec.js +226 -0
  21. package/dist/commands/tools.d.ts +11 -0
  22. package/dist/commands/tools.js +252 -0
  23. package/dist/commands/validate.d.ts +24 -0
  24. package/dist/commands/validate.js +295 -0
  25. package/dist/commands/workflow/index.d.ts +17 -0
  26. package/dist/commands/workflow/index.js +12 -0
  27. package/dist/commands/workflow/instructions.d.ts +29 -0
  28. package/dist/commands/workflow/instructions.js +328 -0
  29. package/dist/commands/workflow/new-change.d.ts +11 -0
  30. package/dist/commands/workflow/new-change.js +44 -0
  31. package/dist/commands/workflow/schemas.d.ts +10 -0
  32. package/dist/commands/workflow/schemas.js +35 -0
  33. package/dist/commands/workflow/shared.d.ts +57 -0
  34. package/dist/commands/workflow/shared.js +117 -0
  35. package/dist/commands/workflow/status.d.ts +14 -0
  36. package/dist/commands/workflow/status.js +76 -0
  37. package/dist/commands/workflow/templates.d.ts +16 -0
  38. package/dist/commands/workflow/templates.js +70 -0
  39. package/dist/core/archive.d.ts +11 -0
  40. package/dist/core/archive.js +322 -0
  41. package/dist/core/artifact-graph/graph.d.ts +56 -0
  42. package/dist/core/artifact-graph/graph.js +141 -0
  43. package/dist/core/artifact-graph/index.d.ts +8 -0
  44. package/dist/core/artifact-graph/index.js +14 -0
  45. package/dist/core/artifact-graph/instruction-loader.d.ts +143 -0
  46. package/dist/core/artifact-graph/instruction-loader.js +217 -0
  47. package/dist/core/artifact-graph/outputs.d.ts +14 -0
  48. package/dist/core/artifact-graph/outputs.js +39 -0
  49. package/dist/core/artifact-graph/resolver.d.ts +81 -0
  50. package/dist/core/artifact-graph/resolver.js +258 -0
  51. package/dist/core/artifact-graph/schema.d.ts +13 -0
  52. package/dist/core/artifact-graph/schema.js +108 -0
  53. package/dist/core/artifact-graph/state.d.ts +12 -0
  54. package/dist/core/artifact-graph/state.js +31 -0
  55. package/dist/core/artifact-graph/types.d.ts +45 -0
  56. package/dist/core/artifact-graph/types.js +43 -0
  57. package/dist/core/available-tools.d.ts +17 -0
  58. package/dist/core/available-tools.js +43 -0
  59. package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
  60. package/dist/core/command-generation/adapters/amazon-q.js +26 -0
  61. package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
  62. package/dist/core/command-generation/adapters/antigravity.js +26 -0
  63. package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
  64. package/dist/core/command-generation/adapters/auggie.js +27 -0
  65. package/dist/core/command-generation/adapters/bob.d.ts +14 -0
  66. package/dist/core/command-generation/adapters/bob.js +45 -0
  67. package/dist/core/command-generation/adapters/claude.d.ts +13 -0
  68. package/dist/core/command-generation/adapters/claude.js +50 -0
  69. package/dist/core/command-generation/adapters/cline.d.ts +14 -0
  70. package/dist/core/command-generation/adapters/cline.js +27 -0
  71. package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
  72. package/dist/core/command-generation/adapters/codebuddy.js +28 -0
  73. package/dist/core/command-generation/adapters/codex.d.ts +16 -0
  74. package/dist/core/command-generation/adapters/codex.js +39 -0
  75. package/dist/core/command-generation/adapters/continue.d.ts +13 -0
  76. package/dist/core/command-generation/adapters/continue.js +28 -0
  77. package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
  78. package/dist/core/command-generation/adapters/costrict.js +27 -0
  79. package/dist/core/command-generation/adapters/crush.d.ts +13 -0
  80. package/dist/core/command-generation/adapters/crush.js +30 -0
  81. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  82. package/dist/core/command-generation/adapters/cursor.js +44 -0
  83. package/dist/core/command-generation/adapters/factory.d.ts +13 -0
  84. package/dist/core/command-generation/adapters/factory.js +27 -0
  85. package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
  86. package/dist/core/command-generation/adapters/gemini.js +26 -0
  87. package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
  88. package/dist/core/command-generation/adapters/github-copilot.js +26 -0
  89. package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
  90. package/dist/core/command-generation/adapters/iflow.js +29 -0
  91. package/dist/core/command-generation/adapters/index.d.ts +32 -0
  92. package/dist/core/command-generation/adapters/index.js +32 -0
  93. package/dist/core/command-generation/adapters/junie.d.ts +13 -0
  94. package/dist/core/command-generation/adapters/junie.js +26 -0
  95. package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
  96. package/dist/core/command-generation/adapters/kilocode.js +23 -0
  97. package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
  98. package/dist/core/command-generation/adapters/kiro.js +26 -0
  99. package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
  100. package/dist/core/command-generation/adapters/lingma.js +30 -0
  101. package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
  102. package/dist/core/command-generation/adapters/opencode.js +29 -0
  103. package/dist/core/command-generation/adapters/pi.d.ts +18 -0
  104. package/dist/core/command-generation/adapters/pi.js +55 -0
  105. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  106. package/dist/core/command-generation/adapters/qoder.js +30 -0
  107. package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
  108. package/dist/core/command-generation/adapters/qwen.js +26 -0
  109. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  110. package/dist/core/command-generation/adapters/roocode.js +27 -0
  111. package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
  112. package/dist/core/command-generation/adapters/windsurf.js +51 -0
  113. package/dist/core/command-generation/generator.d.ts +21 -0
  114. package/dist/core/command-generation/generator.js +27 -0
  115. package/dist/core/command-generation/index.d.ts +22 -0
  116. package/dist/core/command-generation/index.js +24 -0
  117. package/dist/core/command-generation/registry.d.ts +36 -0
  118. package/dist/core/command-generation/registry.js +98 -0
  119. package/dist/core/command-generation/types.d.ts +56 -0
  120. package/dist/core/command-generation/types.js +8 -0
  121. package/dist/core/completions/command-registry.d.ts +7 -0
  122. package/dist/core/completions/command-registry.js +462 -0
  123. package/dist/core/completions/completion-provider.d.ts +60 -0
  124. package/dist/core/completions/completion-provider.js +102 -0
  125. package/dist/core/completions/factory.d.ts +64 -0
  126. package/dist/core/completions/factory.js +75 -0
  127. package/dist/core/completions/generators/bash-generator.d.ts +32 -0
  128. package/dist/core/completions/generators/bash-generator.js +174 -0
  129. package/dist/core/completions/generators/fish-generator.d.ts +32 -0
  130. package/dist/core/completions/generators/fish-generator.js +157 -0
  131. package/dist/core/completions/generators/powershell-generator.d.ts +33 -0
  132. package/dist/core/completions/generators/powershell-generator.js +208 -0
  133. package/dist/core/completions/generators/zsh-generator.d.ts +44 -0
  134. package/dist/core/completions/generators/zsh-generator.js +250 -0
  135. package/dist/core/completions/installers/bash-installer.d.ts +87 -0
  136. package/dist/core/completions/installers/bash-installer.js +319 -0
  137. package/dist/core/completions/installers/fish-installer.d.ts +43 -0
  138. package/dist/core/completions/installers/fish-installer.js +143 -0
  139. package/dist/core/completions/installers/powershell-installer.d.ts +102 -0
  140. package/dist/core/completions/installers/powershell-installer.js +400 -0
  141. package/dist/core/completions/installers/zsh-installer.d.ts +125 -0
  142. package/dist/core/completions/installers/zsh-installer.js +450 -0
  143. package/dist/core/completions/templates/bash-templates.d.ts +6 -0
  144. package/dist/core/completions/templates/bash-templates.js +24 -0
  145. package/dist/core/completions/templates/fish-templates.d.ts +7 -0
  146. package/dist/core/completions/templates/fish-templates.js +39 -0
  147. package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
  148. package/dist/core/completions/templates/powershell-templates.js +25 -0
  149. package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
  150. package/dist/core/completions/templates/zsh-templates.js +36 -0
  151. package/dist/core/completions/types.d.ts +79 -0
  152. package/dist/core/completions/types.js +2 -0
  153. package/dist/core/config-prompts.d.ts +9 -0
  154. package/dist/core/config-prompts.js +34 -0
  155. package/dist/core/config-schema.d.ts +86 -0
  156. package/dist/core/config-schema.js +213 -0
  157. package/dist/core/config.d.ts +18 -0
  158. package/dist/core/config.js +38 -0
  159. package/dist/core/converters/json-converter.d.ts +6 -0
  160. package/dist/core/converters/json-converter.js +51 -0
  161. package/dist/core/global-config.d.ts +44 -0
  162. package/dist/core/global-config.js +125 -0
  163. package/dist/core/index.d.ts +2 -0
  164. package/dist/core/index.js +3 -0
  165. package/dist/core/init.d.ts +37 -0
  166. package/dist/core/init.js +549 -0
  167. package/dist/core/is-project-initialized.d.ts +12 -0
  168. package/dist/core/is-project-initialized.js +18 -0
  169. package/dist/core/legacy-cleanup.d.ts +162 -0
  170. package/dist/core/legacy-cleanup.js +515 -0
  171. package/dist/core/list.d.ts +9 -0
  172. package/dist/core/list.js +172 -0
  173. package/dist/core/migration.d.ts +23 -0
  174. package/dist/core/migration.js +109 -0
  175. package/dist/core/parsers/change-parser.d.ts +13 -0
  176. package/dist/core/parsers/change-parser.js +197 -0
  177. package/dist/core/parsers/markdown-parser.d.ts +26 -0
  178. package/dist/core/parsers/markdown-parser.js +228 -0
  179. package/dist/core/parsers/requirement-blocks.d.ts +37 -0
  180. package/dist/core/parsers/requirement-blocks.js +201 -0
  181. package/dist/core/parsers/spec-structure.d.ts +9 -0
  182. package/dist/core/parsers/spec-structure.js +88 -0
  183. package/dist/core/profile-sync-drift.d.ts +38 -0
  184. package/dist/core/profile-sync-drift.js +200 -0
  185. package/dist/core/profiles.d.ts +26 -0
  186. package/dist/core/profiles.js +40 -0
  187. package/dist/core/project-config.d.ts +64 -0
  188. package/dist/core/project-config.js +224 -0
  189. package/dist/core/schemas/base.schema.d.ts +13 -0
  190. package/dist/core/schemas/base.schema.js +13 -0
  191. package/dist/core/schemas/change.schema.d.ts +73 -0
  192. package/dist/core/schemas/change.schema.js +31 -0
  193. package/dist/core/schemas/index.d.ts +4 -0
  194. package/dist/core/schemas/index.js +4 -0
  195. package/dist/core/schemas/spec.schema.d.ts +18 -0
  196. package/dist/core/schemas/spec.schema.js +15 -0
  197. package/dist/core/shared/index.d.ts +8 -0
  198. package/dist/core/shared/index.js +8 -0
  199. package/dist/core/shared/skill-generation.d.ts +49 -0
  200. package/dist/core/shared/skill-generation.js +96 -0
  201. package/dist/core/shared/tool-detection.d.ts +71 -0
  202. package/dist/core/shared/tool-detection.js +158 -0
  203. package/dist/core/specs-apply.d.ts +73 -0
  204. package/dist/core/specs-apply.js +393 -0
  205. package/dist/core/styles/palette.d.ts +7 -0
  206. package/dist/core/styles/palette.js +8 -0
  207. package/dist/core/templates/index.d.ts +8 -0
  208. package/dist/core/templates/index.js +9 -0
  209. package/dist/core/templates/skill-templates.d.ts +20 -0
  210. package/dist/core/templates/skill-templates.js +19 -0
  211. package/dist/core/templates/types.d.ts +19 -0
  212. package/dist/core/templates/types.js +5 -0
  213. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  214. package/dist/core/templates/workflows/apply-change.js +308 -0
  215. package/dist/core/templates/workflows/archive-change.d.ts +10 -0
  216. package/dist/core/templates/workflows/archive-change.js +271 -0
  217. package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
  218. package/dist/core/templates/workflows/bulk-archive-change.js +492 -0
  219. package/dist/core/templates/workflows/continue-change.d.ts +10 -0
  220. package/dist/core/templates/workflows/continue-change.js +232 -0
  221. package/dist/core/templates/workflows/explore.d.ts +10 -0
  222. package/dist/core/templates/workflows/explore.js +463 -0
  223. package/dist/core/templates/workflows/feedback.d.ts +9 -0
  224. package/dist/core/templates/workflows/feedback.js +108 -0
  225. package/dist/core/templates/workflows/ff-change.d.ts +10 -0
  226. package/dist/core/templates/workflows/ff-change.js +198 -0
  227. package/dist/core/templates/workflows/new-change.d.ts +10 -0
  228. package/dist/core/templates/workflows/new-change.js +21 -0
  229. package/dist/core/templates/workflows/onboard.d.ts +10 -0
  230. package/dist/core/templates/workflows/onboard.js +21 -0
  231. package/dist/core/templates/workflows/propose.d.ts +10 -0
  232. package/dist/core/templates/workflows/propose.js +216 -0
  233. package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
  234. package/dist/core/templates/workflows/sync-specs.js +272 -0
  235. package/dist/core/templates/workflows/upstream-sync.d.ts +10 -0
  236. package/dist/core/templates/workflows/upstream-sync.js +116 -0
  237. package/dist/core/templates/workflows/verify-change.d.ts +10 -0
  238. package/dist/core/templates/workflows/verify-change.js +21 -0
  239. package/dist/core/tools-manager.d.ts +56 -0
  240. package/dist/core/tools-manager.js +215 -0
  241. package/dist/core/update.d.ts +77 -0
  242. package/dist/core/update.js +538 -0
  243. package/dist/core/validation/constants.d.ts +34 -0
  244. package/dist/core/validation/constants.js +40 -0
  245. package/dist/core/validation/types.d.ts +18 -0
  246. package/dist/core/validation/types.js +2 -0
  247. package/dist/core/validation/validator.d.ts +33 -0
  248. package/dist/core/validation/validator.js +419 -0
  249. package/dist/core/view.d.ts +8 -0
  250. package/dist/core/view.js +169 -0
  251. package/dist/index.d.ts +3 -0
  252. package/dist/index.js +3 -0
  253. package/dist/messages/index.d.ts +867 -0
  254. package/dist/messages/index.js +1960 -0
  255. package/dist/prompts/searchable-multi-select.d.ts +28 -0
  256. package/dist/prompts/searchable-multi-select.js +160 -0
  257. package/dist/telemetry/config.d.ts +38 -0
  258. package/dist/telemetry/config.js +136 -0
  259. package/dist/telemetry/index.d.ts +31 -0
  260. package/dist/telemetry/index.js +165 -0
  261. package/dist/ui/ascii-patterns.d.ts +16 -0
  262. package/dist/ui/ascii-patterns.js +133 -0
  263. package/dist/ui/welcome-screen.d.ts +10 -0
  264. package/dist/ui/welcome-screen.js +147 -0
  265. package/dist/utils/change-metadata.d.ts +51 -0
  266. package/dist/utils/change-metadata.js +147 -0
  267. package/dist/utils/change-utils.d.ts +62 -0
  268. package/dist/utils/change-utils.js +121 -0
  269. package/dist/utils/command-references.d.ts +18 -0
  270. package/dist/utils/command-references.js +20 -0
  271. package/dist/utils/file-system.d.ts +41 -0
  272. package/dist/utils/file-system.js +302 -0
  273. package/dist/utils/index.d.ts +6 -0
  274. package/dist/utils/index.js +9 -0
  275. package/dist/utils/interactive.d.ts +18 -0
  276. package/dist/utils/interactive.js +21 -0
  277. package/dist/utils/item-discovery.d.ts +4 -0
  278. package/dist/utils/item-discovery.js +72 -0
  279. package/dist/utils/match.d.ts +3 -0
  280. package/dist/utils/match.js +22 -0
  281. package/dist/utils/shell-detection.d.ts +20 -0
  282. package/dist/utils/shell-detection.js +41 -0
  283. package/dist/utils/task-progress.d.ts +8 -0
  284. package/dist/utils/task-progress.js +37 -0
  285. package/package.json +84 -0
  286. package/schemas/spec-driven/schema.yaml +153 -0
  287. package/schemas/spec-driven/templates/design.md +19 -0
  288. package/schemas/spec-driven/templates/proposal.md +23 -0
  289. package/schemas/spec-driven/templates/spec.md +8 -0
  290. package/schemas/spec-driven/templates/tasks.md +9 -0
  291. package/scripts/postinstall.js +83 -0
@@ -0,0 +1,28 @@
1
+ interface Choice {
2
+ name: string;
3
+ value: string;
4
+ description?: string;
5
+ configured?: boolean;
6
+ detected?: boolean;
7
+ configuredLabel?: string;
8
+ preSelected?: boolean;
9
+ }
10
+ interface Config {
11
+ message: string;
12
+ choices: Choice[];
13
+ pageSize?: number;
14
+ validate?: (selected: string[]) => boolean | string;
15
+ }
16
+ /**
17
+ * A searchable multi-select prompt with visible search box,
18
+ * selected items display, and intuitive keyboard navigation.
19
+ *
20
+ * - Type to filter choices
21
+ * - ↑↓ to navigate
22
+ * - Space to toggle highlighted item selection
23
+ * - Backspace to remove last selected item (or delete search char)
24
+ * - Enter to confirm selections
25
+ */
26
+ export declare function searchableMultiSelect(config: Config): Promise<string[]>;
27
+ export default searchableMultiSelect;
28
+ //# sourceMappingURL=searchable-multi-select.d.ts.map
@@ -0,0 +1,160 @@
1
+ import chalk from 'chalk';
2
+ import { PROMPT_MESSAGES } from '../messages/index.js';
3
+ /**
4
+ * Create the searchable multi-select prompt.
5
+ * Uses dynamic import to prevent pre-commit hook hangs (see #367).
6
+ */
7
+ async function createSearchableMultiSelect() {
8
+ const { createPrompt, useState, useKeypress, useMemo, usePrefix, isEnterKey, isBackspaceKey, isUpKey, isDownKey, } = await import('@inquirer/core');
9
+ return createPrompt((config, done) => {
10
+ const { message, choices, pageSize = 15, validate } = config;
11
+ const [searchText, setSearchText] = useState('');
12
+ const [selectedValues, setSelectedValues] = useState(() => choices.filter(c => c.preSelected).map(c => c.value));
13
+ const [cursor, setCursor] = useState(0);
14
+ const [status, setStatus] = useState('idle');
15
+ const [error, setError] = useState(null);
16
+ const prefix = usePrefix({ status });
17
+ // Filter choices by search
18
+ const filteredChoices = useMemo(() => {
19
+ if (!searchText.trim())
20
+ return choices;
21
+ const term = searchText.toLowerCase();
22
+ return choices.filter((c) => c.name.toLowerCase().includes(term) ||
23
+ c.value.toLowerCase().includes(term));
24
+ }, [searchText, choices]);
25
+ const selectedSet = useMemo(() => new Set(selectedValues), [selectedValues]);
26
+ const choiceMap = useMemo(() => new Map(choices.map((c) => [c.value, c])), [choices]);
27
+ useKeypress((key) => {
28
+ if (status === 'done')
29
+ return;
30
+ // Enter to confirm/submit
31
+ if (isEnterKey(key)) {
32
+ if (validate) {
33
+ const result = validate(selectedValues);
34
+ if (result !== true) {
35
+ setError(typeof result === 'string' ? result : PROMPT_MESSAGES.invalid);
36
+ return;
37
+ }
38
+ }
39
+ setStatus('done');
40
+ done(selectedValues);
41
+ return;
42
+ }
43
+ // Space to toggle selection
44
+ if (key.name === 'space') {
45
+ const choice = filteredChoices[cursor];
46
+ if (choice) {
47
+ if (selectedSet.has(choice.value)) {
48
+ setSelectedValues(selectedValues.filter(v => v !== choice.value));
49
+ }
50
+ else {
51
+ setSelectedValues([...selectedValues, choice.value]);
52
+ }
53
+ }
54
+ return;
55
+ }
56
+ // Backspace to remove or delete search char
57
+ if (isBackspaceKey(key)) {
58
+ if (searchText === '' && selectedValues.length > 0) {
59
+ setSelectedValues(selectedValues.slice(0, -1));
60
+ }
61
+ else {
62
+ setSearchText(searchText.slice(0, -1));
63
+ setCursor(0);
64
+ }
65
+ return;
66
+ }
67
+ // Navigation
68
+ if (isUpKey(key)) {
69
+ setCursor(Math.max(0, cursor - 1));
70
+ return;
71
+ }
72
+ if (isDownKey(key)) {
73
+ setCursor(Math.min(filteredChoices.length - 1, cursor + 1));
74
+ return;
75
+ }
76
+ // Character input - handle printable characters
77
+ if (key.name && key.name.length === 1 && !key.ctrl) {
78
+ setSearchText(searchText + key.name);
79
+ setCursor(0);
80
+ }
81
+ });
82
+ // Render done state
83
+ if (status === 'done') {
84
+ const names = selectedValues
85
+ .map((v) => choiceMap.get(v)?.name ?? v)
86
+ .join(', ');
87
+ return `${prefix} ${chalk.bold(message)} ${chalk.cyan(names || PROMPT_MESSAGES.none)}`;
88
+ }
89
+ // Render active state
90
+ const lines = [];
91
+ lines.push(`${prefix} ${chalk.bold(message)}`);
92
+ // Selected chips
93
+ const chips = selectedValues.length > 0
94
+ ? selectedValues
95
+ .map((v) => chalk.bgCyan.black(` ${choiceMap.get(v)?.name} `))
96
+ .join(' ')
97
+ : chalk.dim(PROMPT_MESSAGES.noneSelected);
98
+ lines.push(` ${PROMPT_MESSAGES.selected} ${chips}`);
99
+ // Search box
100
+ lines.push(` ${PROMPT_MESSAGES.search} ${chalk.yellow('[')}${searchText || chalk.dim(PROMPT_MESSAGES.typeToFilter)}${chalk.yellow(']')}`);
101
+ // Instructions
102
+ lines.push(` ${chalk.cyan('↑↓')} ${PROMPT_MESSAGES.navigate} • ${chalk.cyan('Space')} ${PROMPT_MESSAGES.toggle} • ${chalk.cyan('Backspace')} ${PROMPT_MESSAGES.remove} • ${chalk.cyan('Enter')} ${PROMPT_MESSAGES.confirm}`);
103
+ // List
104
+ if (filteredChoices.length === 0) {
105
+ lines.push(chalk.yellow(` ${PROMPT_MESSAGES.noMatches}`));
106
+ }
107
+ else {
108
+ // Calculate pagination
109
+ const startIndex = Math.max(0, Math.min(cursor - Math.floor(pageSize / 2), filteredChoices.length - pageSize));
110
+ const endIndex = Math.min(startIndex + pageSize, filteredChoices.length);
111
+ const visibleChoices = filteredChoices.slice(startIndex, endIndex);
112
+ for (let i = 0; i < visibleChoices.length; i++) {
113
+ const item = visibleChoices[i];
114
+ const actualIndex = startIndex + i;
115
+ const isActive = actualIndex === cursor;
116
+ const selected = selectedSet.has(item.value);
117
+ const icon = selected ? chalk.green('◉') : chalk.dim('○');
118
+ const arrow = isActive ? chalk.cyan('›') : ' ';
119
+ const name = isActive ? chalk.cyan(item.name) : item.name;
120
+ const isRefresh = selected && item.configured;
121
+ const statusLabel = !selected
122
+ ? item.configured
123
+ ? ` ${PROMPT_MESSAGES.configured}`
124
+ : item.detected
125
+ ? ` ${PROMPT_MESSAGES.detected}`
126
+ : ''
127
+ : '';
128
+ const suffix = selected
129
+ ? chalk.dim(isRefresh ? ` ${PROMPT_MESSAGES.refresh}` : ` ${PROMPT_MESSAGES.selectedLabel}`)
130
+ : chalk.dim(statusLabel);
131
+ lines.push(` ${arrow} ${icon} ${name}${suffix}`);
132
+ }
133
+ // Show pagination indicator if needed
134
+ if (filteredChoices.length > pageSize) {
135
+ const currentPage = Math.floor(cursor / pageSize) + 1;
136
+ const totalPages = Math.ceil(filteredChoices.length / pageSize);
137
+ lines.push(chalk.dim(` (${currentPage}/${totalPages})`));
138
+ }
139
+ }
140
+ if (error)
141
+ lines.push(chalk.red(` ${error}`));
142
+ return lines.join('\n');
143
+ });
144
+ }
145
+ /**
146
+ * A searchable multi-select prompt with visible search box,
147
+ * selected items display, and intuitive keyboard navigation.
148
+ *
149
+ * - Type to filter choices
150
+ * - ↑↓ to navigate
151
+ * - Space to toggle highlighted item selection
152
+ * - Backspace to remove last selected item (or delete search char)
153
+ * - Enter to confirm selections
154
+ */
155
+ export async function searchableMultiSelect(config) {
156
+ const prompt = await createSearchableMultiSelect();
157
+ return prompt(config);
158
+ }
159
+ export default searchableMultiSelect;
160
+ //# sourceMappingURL=searchable-multi-select.js.map
@@ -0,0 +1,38 @@
1
+ export declare const CONFIG_DIR_NAME = "openspec";
2
+ export declare const CONFIG_FILE_NAME = "config.json";
3
+ export interface TelemetryConfig {
4
+ anonymousId?: string;
5
+ noticeSeen?: boolean;
6
+ }
7
+ export interface GlobalConfig {
8
+ telemetry?: TelemetryConfig;
9
+ [key: string]: unknown;
10
+ }
11
+ /**
12
+ * Get the path to the global config file.
13
+ * Follows XDG Base Directory Specification and platform conventions.
14
+ *
15
+ * - All platforms: $XDG_CONFIG_HOME/openspec/ if XDG_CONFIG_HOME is set
16
+ * - Unix/macOS fallback: ~/.config/openspec/
17
+ * - Windows fallback: %APPDATA%/openspec/
18
+ */
19
+ export declare function getConfigPath(): string;
20
+ /**
21
+ * Read the global config file.
22
+ * Returns an empty object if the file doesn't exist.
23
+ */
24
+ export declare function readConfig(): Promise<GlobalConfig>;
25
+ /**
26
+ * Write to the global config file.
27
+ * Preserves existing fields and merges in new values.
28
+ */
29
+ export declare function writeConfig(updates: Partial<GlobalConfig>): Promise<void>;
30
+ /**
31
+ * Get the telemetry config section.
32
+ */
33
+ export declare function getTelemetryConfig(): Promise<TelemetryConfig>;
34
+ /**
35
+ * Update the telemetry config section.
36
+ */
37
+ export declare function updateTelemetryConfig(updates: Partial<TelemetryConfig>): Promise<void>;
38
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Global configuration for telemetry state.
3
+ * Stores anonymous ID and notice-seen flag in the platform-appropriate config directory.
4
+ */
5
+ import { promises as fs } from 'fs';
6
+ import path from 'path';
7
+ import os from 'os';
8
+ import { GLOBAL_CONFIG_DIR_NAME, GLOBAL_CONFIG_FILE_NAME, getGlobalConfigDir, } from '../core/global-config.js';
9
+ // Constants
10
+ export const CONFIG_DIR_NAME = GLOBAL_CONFIG_DIR_NAME;
11
+ export const CONFIG_FILE_NAME = GLOBAL_CONFIG_FILE_NAME;
12
+ function getConfigDir() {
13
+ return getGlobalConfigDir();
14
+ }
15
+ function getLegacyConfigPath() {
16
+ return path.join(os.homedir(), '.config', CONFIG_DIR_NAME, CONFIG_FILE_NAME);
17
+ }
18
+ async function readConfigFile(configPath) {
19
+ try {
20
+ const content = await fs.readFile(configPath, 'utf-8');
21
+ return { status: 'ok', config: JSON.parse(content) };
22
+ }
23
+ catch (error) {
24
+ if (error.code === 'ENOENT') {
25
+ return { status: 'missing' };
26
+ }
27
+ // If parse fails or another read error occurs, ignore the file.
28
+ return { status: 'invalid', config: {} };
29
+ }
30
+ }
31
+ async function writeConfigFile(configPath, config) {
32
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
33
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2) + '\n');
34
+ }
35
+ function hasMissingTelemetryFields(config) {
36
+ const telemetry = config.telemetry;
37
+ return (!telemetry ||
38
+ telemetry.anonymousId === undefined ||
39
+ telemetry.noticeSeen === undefined);
40
+ }
41
+ function mergeLegacyTelemetry(config, legacyConfig) {
42
+ const legacyTelemetry = legacyConfig.telemetry;
43
+ if (!legacyTelemetry) {
44
+ return undefined;
45
+ }
46
+ const currentTelemetry = config.telemetry ?? {};
47
+ const shouldMigrate = (currentTelemetry.anonymousId === undefined && legacyTelemetry.anonymousId !== undefined) ||
48
+ (currentTelemetry.noticeSeen === undefined && legacyTelemetry.noticeSeen !== undefined);
49
+ if (!shouldMigrate) {
50
+ return undefined;
51
+ }
52
+ return {
53
+ ...config,
54
+ telemetry: {
55
+ ...legacyTelemetry,
56
+ ...currentTelemetry,
57
+ },
58
+ };
59
+ }
60
+ async function migrateLegacyTelemetryConfig(configPath, config, persist) {
61
+ const legacyConfigPath = getLegacyConfigPath();
62
+ if (path.resolve(configPath) === path.resolve(legacyConfigPath) || !hasMissingTelemetryFields(config)) {
63
+ return config;
64
+ }
65
+ const legacyRead = await readConfigFile(legacyConfigPath);
66
+ if (legacyRead.status !== 'ok') {
67
+ return config;
68
+ }
69
+ const migrated = mergeLegacyTelemetry(config, legacyRead.config);
70
+ if (!migrated) {
71
+ return config;
72
+ }
73
+ if (persist) {
74
+ try {
75
+ await writeConfigFile(configPath, migrated);
76
+ }
77
+ catch {
78
+ // Preserve telemetry for this run even if the one-time migration cannot be persisted.
79
+ }
80
+ }
81
+ return migrated;
82
+ }
83
+ /**
84
+ * Get the path to the global config file.
85
+ * Follows XDG Base Directory Specification and platform conventions.
86
+ *
87
+ * - All platforms: $XDG_CONFIG_HOME/openspec/ if XDG_CONFIG_HOME is set
88
+ * - Unix/macOS fallback: ~/.config/openspec/
89
+ * - Windows fallback: %APPDATA%/openspec/
90
+ */
91
+ export function getConfigPath() {
92
+ const configDir = getConfigDir();
93
+ return path.join(configDir, CONFIG_FILE_NAME);
94
+ }
95
+ /**
96
+ * Read the global config file.
97
+ * Returns an empty object if the file doesn't exist.
98
+ */
99
+ export async function readConfig() {
100
+ const configPath = getConfigPath();
101
+ const read = await readConfigFile(configPath);
102
+ const config = read.status === 'ok' ? read.config : {};
103
+ return migrateLegacyTelemetryConfig(configPath, config, read.status !== 'invalid');
104
+ }
105
+ /**
106
+ * Write to the global config file.
107
+ * Preserves existing fields and merges in new values.
108
+ */
109
+ export async function writeConfig(updates) {
110
+ const configPath = getConfigPath();
111
+ // Read existing config and merge
112
+ const existing = await readConfig();
113
+ const merged = { ...existing, ...updates };
114
+ // Deep merge for telemetry object
115
+ if (updates.telemetry && existing.telemetry) {
116
+ merged.telemetry = { ...existing.telemetry, ...updates.telemetry };
117
+ }
118
+ await writeConfigFile(configPath, merged);
119
+ }
120
+ /**
121
+ * Get the telemetry config section.
122
+ */
123
+ export async function getTelemetryConfig() {
124
+ const config = await readConfig();
125
+ return config.telemetry ?? {};
126
+ }
127
+ /**
128
+ * Update the telemetry config section.
129
+ */
130
+ export async function updateTelemetryConfig(updates) {
131
+ const existing = await getTelemetryConfig();
132
+ await writeConfig({
133
+ telemetry: { ...existing, ...updates },
134
+ });
135
+ }
136
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Check if telemetry is enabled.
3
+ *
4
+ * Disabled when:
5
+ * - OPENSPEC_TELEMETRY=0
6
+ * - DO_NOT_TRACK=1
7
+ * - CI=true (any CI environment)
8
+ */
9
+ export declare function isTelemetryEnabled(): boolean;
10
+ /**
11
+ * Get or create the anonymous user ID.
12
+ * Lazily generates a UUID on first call and persists it.
13
+ */
14
+ export declare function getOrCreateAnonymousId(): Promise<string>;
15
+ /**
16
+ * Track a command execution.
17
+ *
18
+ * @param commandName - The command name (e.g., 'init', 'change:apply')
19
+ * @param version - The OpenSpec version
20
+ */
21
+ export declare function trackCommand(commandName: string, version: string): Promise<void>;
22
+ /**
23
+ * Show first-run telemetry notice if not already seen.
24
+ */
25
+ export declare function maybeShowTelemetryNotice(): Promise<void>;
26
+ /**
27
+ * Shutdown the PostHog client and flush pending events.
28
+ * Call this before CLI exit.
29
+ */
30
+ export declare function shutdown(): Promise<void>;
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,165 @@
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 or DO_NOT_TRACK=1
8
+ * - Auto-disabled in CI environments
9
+ * - Anonymous ID is a random UUID with no relation to the user
10
+ */
11
+ import { PostHog } from 'posthog-node';
12
+ import { randomUUID } from 'crypto';
13
+ import { getTelemetryConfig, updateTelemetryConfig } from './config.js';
14
+ import { TELEMETRY_MESSAGES } from '../messages/index.js';
15
+ // PostHog API key - public key for client-side analytics
16
+ // This is safe to embed as it only allows sending events, not reading data
17
+ const POSTHOG_API_KEY = 'phc_Hthu8YvaIJ9QaFKyTG4TbVwkbd5ktcAFzVTKeMmoW2g';
18
+ // Using reverse proxy to avoid ad blockers and keep traffic on our domain
19
+ const POSTHOG_HOST = 'https://edge.openspec.dev';
20
+ const TELEMETRY_REQUEST_TIMEOUT_MS = 1000;
21
+ let posthogClient = null;
22
+ let anonymousId = null;
23
+ async function safeTelemetryFetch(url, options) {
24
+ try {
25
+ const response = await fetch(url, options);
26
+ if (response.ok) {
27
+ return response;
28
+ }
29
+ }
30
+ catch {
31
+ // Silent failure - telemetry should never surface network noise
32
+ }
33
+ return new Response(null, { status: 204 });
34
+ }
35
+ /**
36
+ * Check if telemetry is enabled.
37
+ *
38
+ * Disabled when:
39
+ * - OPENSPEC_TELEMETRY=0
40
+ * - DO_NOT_TRACK=1
41
+ * - CI=true (any CI environment)
42
+ */
43
+ export function isTelemetryEnabled() {
44
+ // Check explicit opt-out
45
+ if (process.env.OPENSPEC_TELEMETRY === '0') {
46
+ return false;
47
+ }
48
+ // Respect DO_NOT_TRACK standard
49
+ if (process.env.DO_NOT_TRACK === '1') {
50
+ return false;
51
+ }
52
+ // Auto-disable in CI environments
53
+ if (process.env.CI === 'true') {
54
+ return false;
55
+ }
56
+ return true;
57
+ }
58
+ /**
59
+ * Get or create the anonymous user ID.
60
+ * Lazily generates a UUID on first call and persists it.
61
+ */
62
+ export async function getOrCreateAnonymousId() {
63
+ // Return cached value if available
64
+ if (anonymousId) {
65
+ return anonymousId;
66
+ }
67
+ // Try to load from config
68
+ const config = await getTelemetryConfig();
69
+ if (config.anonymousId) {
70
+ anonymousId = config.anonymousId;
71
+ return anonymousId;
72
+ }
73
+ // Generate new UUID and persist
74
+ anonymousId = randomUUID();
75
+ await updateTelemetryConfig({ anonymousId });
76
+ return anonymousId;
77
+ }
78
+ /**
79
+ * Get the PostHog client instance.
80
+ * Creates it on first call with CLI-optimized settings.
81
+ */
82
+ function getClient() {
83
+ if (!posthogClient) {
84
+ posthogClient = new PostHog(POSTHOG_API_KEY, {
85
+ host: POSTHOG_HOST,
86
+ flushAt: 1, // Send immediately, don't batch
87
+ flushInterval: 0, // No timer-based flushing
88
+ fetchRetryCount: 0,
89
+ requestTimeout: TELEMETRY_REQUEST_TIMEOUT_MS,
90
+ preloadFeatureFlags: false,
91
+ disableRemoteConfig: true,
92
+ disableSurveys: true,
93
+ fetch: safeTelemetryFetch,
94
+ });
95
+ }
96
+ return posthogClient;
97
+ }
98
+ /**
99
+ * Track a command execution.
100
+ *
101
+ * @param commandName - The command name (e.g., 'init', 'change:apply')
102
+ * @param version - The OpenSpec version
103
+ */
104
+ export async function trackCommand(commandName, version) {
105
+ if (!isTelemetryEnabled()) {
106
+ return;
107
+ }
108
+ try {
109
+ const userId = await getOrCreateAnonymousId();
110
+ const client = getClient();
111
+ client.capture({
112
+ distinctId: userId,
113
+ event: 'command_executed',
114
+ properties: {
115
+ command: commandName,
116
+ version: version,
117
+ surface: 'cli',
118
+ $ip: null, // Explicitly disable IP tracking
119
+ },
120
+ });
121
+ }
122
+ catch {
123
+ // Silent failure - telemetry should never break CLI
124
+ }
125
+ }
126
+ /**
127
+ * Show first-run telemetry notice if not already seen.
128
+ */
129
+ export async function maybeShowTelemetryNotice() {
130
+ if (!isTelemetryEnabled()) {
131
+ return;
132
+ }
133
+ try {
134
+ const config = await getTelemetryConfig();
135
+ if (config.noticeSeen) {
136
+ return;
137
+ }
138
+ // Display notice
139
+ console.log(TELEMETRY_MESSAGES.firstRunNotice);
140
+ // Mark as seen
141
+ await updateTelemetryConfig({ noticeSeen: true });
142
+ }
143
+ catch {
144
+ // Silent failure - telemetry should never break CLI
145
+ }
146
+ }
147
+ /**
148
+ * Shutdown the PostHog client and flush pending events.
149
+ * Call this before CLI exit.
150
+ */
151
+ export async function shutdown() {
152
+ if (!posthogClient) {
153
+ return;
154
+ }
155
+ try {
156
+ await posthogClient.shutdown();
157
+ }
158
+ catch {
159
+ // Silent failure - telemetry should never break CLI exit
160
+ }
161
+ finally {
162
+ posthogClient = null;
163
+ }
164
+ }
165
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * ASCII art animation patterns for the welcome screen.
3
+ * BR-OpenSpec logo animation - diamond/rhombus shape with hollow center "O".
4
+ */
5
+ /**
6
+ * Welcome animation frames - BR-OpenSpec 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