redmine-context 1.0.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 (229) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +449 -0
  3. package/dist/bundle/index.d.ts +5 -0
  4. package/dist/bundle/index.js +5 -0
  5. package/dist/bundle/json.d.ts +90 -0
  6. package/dist/bundle/json.js +266 -0
  7. package/dist/bundle/markdown.d.ts +75 -0
  8. package/dist/bundle/markdown.js +294 -0
  9. package/dist/bundle/search-list.d.ts +43 -0
  10. package/dist/bundle/search-list.js +53 -0
  11. package/dist/bundle/stable-stringify.d.ts +26 -0
  12. package/dist/bundle/stable-stringify.js +50 -0
  13. package/dist/cache/contract.d.ts +157 -0
  14. package/dist/cache/contract.js +0 -0
  15. package/dist/cache/disk-index.d.ts +82 -0
  16. package/dist/cache/disk-index.js +220 -0
  17. package/dist/cache/disk.d.ts +133 -0
  18. package/dist/cache/disk.js +313 -0
  19. package/dist/cache/gc.d.ts +78 -0
  20. package/dist/cache/gc.js +123 -0
  21. package/dist/cache/get-or-compute.d.ts +36 -0
  22. package/dist/cache/get-or-compute.js +52 -0
  23. package/dist/cache/index.d.ts +9 -0
  24. package/dist/cache/index.js +8 -0
  25. package/dist/cache/keys.d.ts +76 -0
  26. package/dist/cache/keys.js +78 -0
  27. package/dist/cache/memory.d.ts +48 -0
  28. package/dist/cache/memory.js +110 -0
  29. package/dist/cache-first.d.ts +127 -0
  30. package/dist/cache-first.js +227 -0
  31. package/dist/client/errors.d.ts +33 -0
  32. package/dist/client/errors.js +49 -0
  33. package/dist/client/http.d.ts +110 -0
  34. package/dist/client/http.js +207 -0
  35. package/dist/client/index.d.ts +5 -0
  36. package/dist/client/index.js +5 -0
  37. package/dist/client/issues.d.ts +71 -0
  38. package/dist/client/issues.js +100 -0
  39. package/dist/client/search.d.ts +58 -0
  40. package/dist/client/search.js +81 -0
  41. package/dist/config/credentials.d.ts +247 -0
  42. package/dist/config/credentials.js +427 -0
  43. package/dist/config/doctor.d.ts +123 -0
  44. package/dist/config/doctor.js +260 -0
  45. package/dist/config/index.d.ts +6 -0
  46. package/dist/config/index.js +6 -0
  47. package/dist/config/keyring.d.ts +96 -0
  48. package/dist/config/keyring.js +158 -0
  49. package/dist/config/login.d.ts +97 -0
  50. package/dist/config/login.js +189 -0
  51. package/dist/config/settings.d.ts +94 -0
  52. package/dist/config/settings.js +140 -0
  53. package/dist/contract.d.ts +173 -0
  54. package/dist/contract.js +27 -0
  55. package/dist/core.d.ts +1 -0
  56. package/dist/core.js +8 -0
  57. package/dist/extract/audio-extractor.d.ts +105 -0
  58. package/dist/extract/audio-extractor.js +156 -0
  59. package/dist/extract/audio.d.ts +126 -0
  60. package/dist/extract/audio.js +184 -0
  61. package/dist/extract/dispatcher.d.ts +132 -0
  62. package/dist/extract/dispatcher.js +115 -0
  63. package/dist/extract/download.d.ts +111 -0
  64. package/dist/extract/download.js +261 -0
  65. package/dist/extract/duration.d.ts +106 -0
  66. package/dist/extract/duration.js +148 -0
  67. package/dist/extract/ffmpeg.d.ts +56 -0
  68. package/dist/extract/ffmpeg.js +95 -0
  69. package/dist/extract/gguf.d.ts +137 -0
  70. package/dist/extract/gguf.js +215 -0
  71. package/dist/extract/index.d.ts +19 -0
  72. package/dist/extract/index.js +19 -0
  73. package/dist/extract/magic.d.ts +80 -0
  74. package/dist/extract/magic.js +282 -0
  75. package/dist/extract/ooxml.d.ts +131 -0
  76. package/dist/extract/ooxml.js +336 -0
  77. package/dist/extract/pdf.d.ts +147 -0
  78. package/dist/extract/pdf.js +322 -0
  79. package/dist/extract/queue.d.ts +167 -0
  80. package/dist/extract/queue.js +217 -0
  81. package/dist/extract/subprocess.d.ts +145 -0
  82. package/dist/extract/subprocess.js +181 -0
  83. package/dist/extract/tesseract.d.ts +153 -0
  84. package/dist/extract/tesseract.js +321 -0
  85. package/dist/extract/video-extractor.d.ts +84 -0
  86. package/dist/extract/video-extractor.js +89 -0
  87. package/dist/extract/video.d.ts +198 -0
  88. package/dist/extract/video.js +418 -0
  89. package/dist/extract/which.d.ts +63 -0
  90. package/dist/extract/which.js +72 -0
  91. package/dist/extract/whisper-extract.d.ts +211 -0
  92. package/dist/extract/whisper-extract.js +323 -0
  93. package/dist/extract/whisper.d.ts +50 -0
  94. package/dist/extract/whisper.js +67 -0
  95. package/dist/extract/zip.d.ts +50 -0
  96. package/dist/extract/zip.js +165 -0
  97. package/dist/extract-issue-attachments.d.ts +82 -0
  98. package/dist/extract-issue-attachments.js +156 -0
  99. package/dist/fetch-attachment-text.d.ts +113 -0
  100. package/dist/fetch-attachment-text.js +155 -0
  101. package/dist/fetch-issue-bundle.d.ts +81 -0
  102. package/dist/fetch-issue-bundle.js +93 -0
  103. package/dist/fetch-issue-search.d.ts +74 -0
  104. package/dist/fetch-issue-search.js +120 -0
  105. package/dist/index.d.ts +21 -0
  106. package/dist/index.js +67 -0
  107. package/dist/normalize/collections.d.ts +52 -0
  108. package/dist/normalize/collections.js +170 -0
  109. package/dist/normalize/helpers.d.ts +35 -0
  110. package/dist/normalize/helpers.js +59 -0
  111. package/dist/normalize/index.d.ts +2 -0
  112. package/dist/normalize/index.js +2 -0
  113. package/dist/normalize/issue.d.ts +34 -0
  114. package/dist/normalize/issue.js +154 -0
  115. package/dist/surfaces/cli/commands.d.ts +61 -0
  116. package/dist/surfaces/cli/commands.js +262 -0
  117. package/dist/surfaces/cli/main.d.ts +32 -0
  118. package/dist/surfaces/cli/main.js +210 -0
  119. package/dist/surfaces/cli/prompts.d.ts +66 -0
  120. package/dist/surfaces/cli/prompts.js +147 -0
  121. package/dist/surfaces/cli/tty.d.ts +27 -0
  122. package/dist/surfaces/cli/tty.js +35 -0
  123. package/dist/surfaces/cli/types.d.ts +39 -0
  124. package/dist/surfaces/cli/types.js +7 -0
  125. package/dist/surfaces/mcp/server.d.ts +171 -0
  126. package/dist/surfaces/mcp/server.js +427 -0
  127. package/dist/surfaces/tui/app.d.ts +55 -0
  128. package/dist/surfaces/tui/app.js +180 -0
  129. package/dist/surfaces/tui/attachment-status.d.ts +79 -0
  130. package/dist/surfaces/tui/attachment-status.js +113 -0
  131. package/dist/surfaces/tui/components/breadcrumb.d.ts +7 -0
  132. package/dist/surfaces/tui/components/breadcrumb.js +31 -0
  133. package/dist/surfaces/tui/components/gradient-text.d.ts +23 -0
  134. package/dist/surfaces/tui/components/gradient-text.js +75 -0
  135. package/dist/surfaces/tui/components/scroll-view.d.ts +25 -0
  136. package/dist/surfaces/tui/components/scroll-view.js +77 -0
  137. package/dist/surfaces/tui/components/spinner.d.ts +12 -0
  138. package/dist/surfaces/tui/components/spinner.js +39 -0
  139. package/dist/surfaces/tui/components/text-input.d.ts +45 -0
  140. package/dist/surfaces/tui/components/text-input.js +114 -0
  141. package/dist/surfaces/tui/format-file-size.d.ts +24 -0
  142. package/dist/surfaces/tui/format-file-size.js +43 -0
  143. package/dist/surfaces/tui/glyphs.d.ts +47 -0
  144. package/dist/surfaces/tui/glyphs.js +84 -0
  145. package/dist/surfaces/tui/hooks/use-auth-guard.d.ts +39 -0
  146. package/dist/surfaces/tui/hooks/use-auth-guard.js +135 -0
  147. package/dist/surfaces/tui/hooks/use-doctor-status.d.ts +64 -0
  148. package/dist/surfaces/tui/hooks/use-doctor-status.js +123 -0
  149. package/dist/surfaces/tui/hooks/use-escape-interceptor.d.ts +25 -0
  150. package/dist/surfaces/tui/hooks/use-escape-interceptor.js +65 -0
  151. package/dist/surfaces/tui/hooks/use-exit-guard.d.ts +18 -0
  152. package/dist/surfaces/tui/hooks/use-exit-guard.js +66 -0
  153. package/dist/surfaces/tui/hooks/use-export-bundle.d.ts +62 -0
  154. package/dist/surfaces/tui/hooks/use-export-bundle.js +100 -0
  155. package/dist/surfaces/tui/hooks/use-issue-detail.d.ts +61 -0
  156. package/dist/surfaces/tui/hooks/use-issue-detail.js +132 -0
  157. package/dist/surfaces/tui/hooks/use-issue-search.d.ts +71 -0
  158. package/dist/surfaces/tui/hooks/use-issue-search.js +168 -0
  159. package/dist/surfaces/tui/hooks/use-list-navigation.d.ts +44 -0
  160. package/dist/surfaces/tui/hooks/use-list-navigation.js +82 -0
  161. package/dist/surfaces/tui/hooks/use-media-binaries.d.ts +24 -0
  162. package/dist/surfaces/tui/hooks/use-media-binaries.js +44 -0
  163. package/dist/surfaces/tui/hooks/use-my-issues.d.ts +66 -0
  164. package/dist/surfaces/tui/hooks/use-my-issues.js +151 -0
  165. package/dist/surfaces/tui/hooks/use-onboarding-callbacks.d.ts +11 -0
  166. package/dist/surfaces/tui/hooks/use-onboarding-callbacks.js +104 -0
  167. package/dist/surfaces/tui/hooks/use-terminal-width.d.ts +52 -0
  168. package/dist/surfaces/tui/hooks/use-terminal-width.js +90 -0
  169. package/dist/surfaces/tui/index.d.ts +50 -0
  170. package/dist/surfaces/tui/index.js +158 -0
  171. package/dist/surfaces/tui/instance.d.ts +37 -0
  172. package/dist/surfaces/tui/instance.js +36 -0
  173. package/dist/surfaces/tui/job-registry.d.ts +113 -0
  174. package/dist/surfaces/tui/job-registry.js +123 -0
  175. package/dist/surfaces/tui/job-status.d.ts +45 -0
  176. package/dist/surfaces/tui/job-status.js +81 -0
  177. package/dist/surfaces/tui/navigation.d.ts +74 -0
  178. package/dist/surfaces/tui/navigation.js +87 -0
  179. package/dist/surfaces/tui/palettes.d.ts +38 -0
  180. package/dist/surfaces/tui/palettes.js +244 -0
  181. package/dist/surfaces/tui/screen.d.ts +30 -0
  182. package/dist/surfaces/tui/screen.js +51 -0
  183. package/dist/surfaces/tui/screens/about.d.ts +2 -0
  184. package/dist/surfaces/tui/screens/about.js +35 -0
  185. package/dist/surfaces/tui/screens/appearance.d.ts +2 -0
  186. package/dist/surfaces/tui/screens/appearance.js +74 -0
  187. package/dist/surfaces/tui/screens/config.d.ts +2 -0
  188. package/dist/surfaces/tui/screens/config.js +82 -0
  189. package/dist/surfaces/tui/screens/doctor.d.ts +2 -0
  190. package/dist/surfaces/tui/screens/doctor.js +109 -0
  191. package/dist/surfaces/tui/screens/export.d.ts +2 -0
  192. package/dist/surfaces/tui/screens/export.js +168 -0
  193. package/dist/surfaces/tui/screens/home-selection.d.ts +70 -0
  194. package/dist/surfaces/tui/screens/home-selection.js +80 -0
  195. package/dist/surfaces/tui/screens/home.d.ts +2 -0
  196. package/dist/surfaces/tui/screens/home.js +200 -0
  197. package/dist/surfaces/tui/screens/issue-detail.d.ts +6 -0
  198. package/dist/surfaces/tui/screens/issue-detail.js +182 -0
  199. package/dist/surfaces/tui/screens/jobs.d.ts +7 -0
  200. package/dist/surfaces/tui/screens/jobs.js +89 -0
  201. package/dist/surfaces/tui/screens/loaded-issue-context.d.ts +49 -0
  202. package/dist/surfaces/tui/screens/loaded-issue-context.js +57 -0
  203. package/dist/surfaces/tui/screens/onboarding/api-key.d.ts +2 -0
  204. package/dist/surfaces/tui/screens/onboarding/api-key.js +69 -0
  205. package/dist/surfaces/tui/screens/onboarding/login.d.ts +2 -0
  206. package/dist/surfaces/tui/screens/onboarding/login.js +50 -0
  207. package/dist/surfaces/tui/screens/onboarding/mode.d.ts +2 -0
  208. package/dist/surfaces/tui/screens/onboarding/mode.js +42 -0
  209. package/dist/surfaces/tui/screens/onboarding/onboarding-context.d.ts +221 -0
  210. package/dist/surfaces/tui/screens/onboarding/onboarding-context.js +131 -0
  211. package/dist/surfaces/tui/screens/onboarding/success.d.ts +2 -0
  212. package/dist/surfaces/tui/screens/onboarding/success.js +41 -0
  213. package/dist/surfaces/tui/screens/onboarding/url.d.ts +24 -0
  214. package/dist/surfaces/tui/screens/onboarding/url.js +84 -0
  215. package/dist/surfaces/tui/screens/onboarding/validating.d.ts +1 -0
  216. package/dist/surfaces/tui/screens/onboarding/validating.js +86 -0
  217. package/dist/surfaces/tui/screens/welcome.d.ts +6 -0
  218. package/dist/surfaces/tui/screens/welcome.js +95 -0
  219. package/dist/surfaces/tui/status-color.d.ts +21 -0
  220. package/dist/surfaces/tui/status-color.js +23 -0
  221. package/dist/surfaces/tui/symbols.d.ts +231 -0
  222. package/dist/surfaces/tui/symbols.js +14 -0
  223. package/dist/surfaces/tui/terminal-colors.d.ts +29 -0
  224. package/dist/surfaces/tui/terminal-colors.js +39 -0
  225. package/dist/surfaces/tui/theme.d.ts +156 -0
  226. package/dist/surfaces/tui/theme.js +86 -0
  227. package/dist/surfaces/tui/truncate.d.ts +31 -0
  228. package/dist/surfaces/tui/truncate.js +81 -0
  229. package/package.json +93 -0
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Diagnóstico dos binários de mídia (M3-11 #53, M4-01 #57, ADR-002).
3
+ *
4
+ * O núcleo do comando `doctor`: detecta a presença dos binários externos que a
5
+ * extração local de mídia precisa (`tesseract` do OCR, `ffmpeg` do vídeo,
6
+ * `whisper.cpp` da transcrição) e, quando ausentes, devolve uma instrução de
7
+ * instalação ADEQUADA AO SO. Também reporta o status do modelo GGUF do
8
+ * whisper.cpp no cache esperado.
9
+ *
10
+ * DECISÕES (todas exercitadas por testes):
11
+ * - PLATAFORMA INJETÁVEL: cada hint depende de `platform`, injetável para testar
12
+ * os três SOs num único host — o default é `process.platform`.
13
+ * - REUSO DA DETECÇÃO DOS EXTRATORES: localização e leitura de versão
14
+ * (`findTesseract`/`findFfmpeg`/`findWhisper` + `detect*Version`) vêm dos
15
+ * módulos de extração (ADR-002) — não reimplementadas, e injetáveis para manter
16
+ * os testes isolados do filesystem/binário reais.
17
+ * - DEGRADAÇÃO GRACIOSA: nunca lança; um binário ou modelo ausente é um estado
18
+ * NORMAL (`found: false`) reportado com a dica — o `doctor` é justamente o que
19
+ * orienta o usuário a instalar (ADR-002).
20
+ * - OPT-IN `--download-binaries` SÓ ONDE HÁ ARTEFATO OFICIAL: ffmpeg (BtbN) e
21
+ * whisper.cpp (releases) mencionam o download automático futuro; tesseract
22
+ * (sem artefato estático) só instrui a instalação por SO (ADR-002).
23
+ * - MODELO É UMA ENTRADA DO DIAGNÓSTICO: o status do GGUF entra na MESMA lista
24
+ * de {@link BinaryDiagnosis} que as superfícies (CLI/TUI) já iteram — nova
25
+ * entrada exibida SEM mudança de código nelas.
26
+ */
27
+ import { readdirSync } from 'node:fs';
28
+ import { join } from 'node:path';
29
+ import { detectFfmpegVersion as defaultDetectFfmpegVersion, findFfmpeg as defaultFindFfmpeg, } from '../extract/ffmpeg.js';
30
+ import { detectPdftotextVersion as defaultDetectPdftotextVersion, findPdftotext as defaultFindPdftotext, } from '../extract/pdf.js';
31
+ import { detectTesseractVersion as defaultDetectTesseractVersion, findTesseract as defaultFindTesseract, } from '../extract/tesseract.js';
32
+ import { findWhisper as defaultFindWhisper, whisperModelDir as defaultWhisperModelDir, } from '../extract/whisper.js';
33
+ /** Path convencional do tesseract no Windows citado no hint (ADR-002). */
34
+ const WINDOWS_CONVENTIONAL_PATH = 'C:\\Program Files\\Tesseract-OCR';
35
+ /** Menção padronizada ao opt-in de download automático (ADR-002; o download é a #58+). */
36
+ const DOWNLOAD_OPT_IN = 'ou, no futuro, o opt-in `--download-binaries`';
37
+ /**
38
+ * Instrução de instalação do `tesseract` para um SO. Pura e injetável — cada
39
+ * plataforma gera uma dica testável isoladamente (ADR-002). SEM menção a
40
+ * `--download-binaries`: o tesseract não tem artefato estático oficial.
41
+ *
42
+ * @param platform - Plataforma alvo (`process.platform`).
43
+ * @returns Comando/instrução de instalação adequado ao SO.
44
+ * @example
45
+ * tesseractInstallHint('darwin'); // 'brew install tesseract tesseract-lang'
46
+ */
47
+ export function tesseractInstallHint(platform) {
48
+ switch (platform) {
49
+ case 'darwin':
50
+ // tesseract-lang traz o traineddata de 'por' (o default do projeto é por+eng).
51
+ return 'brew install tesseract tesseract-lang';
52
+ case 'win32':
53
+ return `winget install UB-Mannheim.TesseractOCR (ou instale em ${WINDOWS_CONVENTIONAL_PATH})`;
54
+ default:
55
+ // Reason: Linux e demais UNIX — as duas famílias de gerenciador mais comuns.
56
+ return 'sudo apt install tesseract-ocr (ou: sudo dnf install tesseract)';
57
+ }
58
+ }
59
+ /**
60
+ * Instrução de instalação do `pdftotext` (poppler) para um SO. Pura e injetável —
61
+ * cada plataforma gera uma dica testável isoladamente (ADR-002).
62
+ *
63
+ * @param platform - Plataforma alvo (`process.platform`).
64
+ * @returns Comando/instrução de instalação adequado ao SO.
65
+ * @example
66
+ * pdftotextInstallHint('darwin'); // 'brew install poppler'
67
+ */
68
+ export function pdftotextInstallHint(platform) {
69
+ switch (platform) {
70
+ case 'darwin':
71
+ return 'brew install poppler';
72
+ case 'win32':
73
+ // Reason: os builds Windows do poppler vêm do repackage oschwartz10612 —
74
+ // disponível tanto no winget quanto no chocolatey.
75
+ return 'winget install oschwartz10612.Poppler (ou: choco install poppler)';
76
+ default:
77
+ // Reason: Linux e demais UNIX — as duas famílias de gerenciador mais comuns.
78
+ return 'sudo apt install poppler-utils (ou: sudo dnf install poppler-utils)';
79
+ }
80
+ }
81
+ /**
82
+ * Instrução de instalação do `ffmpeg` por SO, COM menção ao opt-in
83
+ * `--download-binaries` e às builds estáticas BtbN (artefato oficial; ADR-002).
84
+ *
85
+ * @param platform - Plataforma alvo (`process.platform`).
86
+ * @returns Instrução de instalação/obtenção adequada ao SO.
87
+ * @example
88
+ * ffmpegInstallHint('darwin'); // 'brew install ffmpeg ...'
89
+ */
90
+ export function ffmpegInstallHint(platform) {
91
+ const btbn = 'builds estáticos BtbN (github.com/BtbN/FFmpeg-Builds)';
92
+ switch (platform) {
93
+ case 'darwin':
94
+ return `brew install ffmpeg (${DOWNLOAD_OPT_IN}; ${btbn})`;
95
+ case 'win32':
96
+ return `winget install Gyan.FFmpeg (${DOWNLOAD_OPT_IN}; ${btbn})`;
97
+ default:
98
+ return `sudo apt install ffmpeg (ou: sudo dnf install ffmpeg; ${DOWNLOAD_OPT_IN}; ${btbn})`;
99
+ }
100
+ }
101
+ /**
102
+ * Instrução de instalação do whisper.cpp por SO, COM menção ao opt-in
103
+ * `--download-binaries` e às releases do GitHub (artefato oficial; ADR-002).
104
+ *
105
+ * @param platform - Plataforma alvo (`process.platform`).
106
+ * @returns Instrução de instalação/obtenção adequada ao SO.
107
+ * @example
108
+ * whisperInstallHint('darwin'); // 'brew install whisper-cpp ...'
109
+ */
110
+ export function whisperInstallHint(platform) {
111
+ const releases = 'releases em github.com/ggml-org/whisper.cpp/releases';
112
+ switch (platform) {
113
+ case 'darwin':
114
+ return `brew install whisper-cpp (${DOWNLOAD_OPT_IN}; ${releases})`;
115
+ case 'win32':
116
+ return `baixe as ${releases} (${DOWNLOAD_OPT_IN})`;
117
+ default:
118
+ return `brew install whisper-cpp ou compile o whisper.cpp (${DOWNLOAD_OPT_IN}; ${releases})`;
119
+ }
120
+ }
121
+ /** Extensão dos modelos do whisper.cpp no cache. */
122
+ const GGUF_EXTENSION = '.gguf';
123
+ /**
124
+ * Monta um {@link BinaryDiagnosis} sem injetar chaves `undefined` (respeita
125
+ * `exactOptionalPropertyTypes`): `path`/`version` só entram quando presentes.
126
+ *
127
+ * @param name - Nome do item diagnosticado.
128
+ * @param installHint - Instrução de instalação/obtenção.
129
+ * @param found - Caminho/versão quando encontrado; `undefined` = ausente.
130
+ * @returns O diagnóstico tipado.
131
+ */
132
+ function makeDiagnosis(name, installHint, found) {
133
+ if (found === undefined) {
134
+ return { name, found: false, installHint };
135
+ }
136
+ const base = { name, found: true, path: found.path, installHint };
137
+ return found.version !== undefined ? { ...base, version: found.version } : base;
138
+ }
139
+ /**
140
+ * Diagnostica o `tesseract`: localiza-o, lê a versão quando presente e anexa a
141
+ * instrução de instalação do SO.
142
+ *
143
+ * @param platform - SO para o hint.
144
+ * @param find - Localizador do binário.
145
+ * @param detectVersion - Leitor de versão.
146
+ * @returns O diagnóstico do tesseract.
147
+ */
148
+ async function diagnoseTesseract(platform, find, detectVersion) {
149
+ const installHint = tesseractInstallHint(platform);
150
+ const located = find();
151
+ if (located === undefined) {
152
+ return makeDiagnosis('tesseract', installHint, undefined);
153
+ }
154
+ const version = await detectVersion(located.path);
155
+ return makeDiagnosis('tesseract', installHint, { path: located.path, version });
156
+ }
157
+ /**
158
+ * Diagnostica o `ffmpeg`: localiza-o, lê a versão (1ª linha de `ffmpeg -version`)
159
+ * quando presente e anexa a instrução por SO (com opt-in/BtbN).
160
+ *
161
+ * @param platform - SO para o hint.
162
+ * @param find - Localizador do binário.
163
+ * @param detectVersion - Leitor de versão.
164
+ * @returns O diagnóstico do ffmpeg.
165
+ */
166
+ async function diagnoseFfmpeg(platform, find, detectVersion) {
167
+ const installHint = ffmpegInstallHint(platform);
168
+ const located = find();
169
+ if (located === undefined) {
170
+ return makeDiagnosis('ffmpeg', installHint, undefined);
171
+ }
172
+ const version = await detectVersion(located.path);
173
+ return makeDiagnosis('ffmpeg', installHint, { path: located.path, version });
174
+ }
175
+ /** Diagnostica o `pdftotext` (poppler): localização, versão e hint por SO. */
176
+ async function diagnosePdftotext(platform, locate, detectVersion) {
177
+ const hint = pdftotextInstallHint(platform);
178
+ const located = locate();
179
+ if (located === undefined) {
180
+ return { name: 'pdftotext', found: false, installHint: hint };
181
+ }
182
+ const version = await detectVersion(located.path);
183
+ const base = { name: 'pdftotext', found: true, path: located.path, installHint: hint };
184
+ return version !== undefined ? { ...base, version } : base;
185
+ }
186
+ /**
187
+ * Diagnostica o `whisper.cpp`: localiza QUALQUER binário conhecido (`whisper-cli`
188
+ * / `whisper-cpp` / `main`) e reporta o caminho encontrado (que revela qual).
189
+ * Sem versão: o whisper.cpp não tem `--version` estável — o path é a evidência.
190
+ *
191
+ * @param platform - SO para o hint.
192
+ * @param find - Localizador do binário.
193
+ * @returns O diagnóstico do whisper.cpp (sempre sem `version`).
194
+ */
195
+ function diagnoseWhisper(platform, find) {
196
+ const installHint = whisperInstallHint(platform);
197
+ const located = find();
198
+ if (located === undefined) {
199
+ return makeDiagnosis('whisper.cpp', installHint, undefined);
200
+ }
201
+ // Reason: sem versão legível — o path (que inclui `whisper-cli`/`main`) é a
202
+ // evidência da presença e de QUAL binário está instalado (ADR-002).
203
+ return makeDiagnosis('whisper.cpp', installHint, { path: located.path });
204
+ }
205
+ /**
206
+ * Diagnostica o modelo GGUF do whisper.cpp: verifica se há ao menos um `.gguf`
207
+ * no diretório de cache canônico ({@link defaultWhisperModelDir}). Nunca lança:
208
+ * diretório inexistente/ilegível conta como ausente (degradação graciosa).
209
+ *
210
+ * @param modelDir - Diretório canônico dos modelos.
211
+ * @param listDir - Lista os arquivos do diretório.
212
+ * @returns O diagnóstico do modelo (path = arquivo GGUF quando presente).
213
+ */
214
+ function diagnoseWhisperModel(modelDir, listDir) {
215
+ const hint = `baixe um modelo GGUF (ex.: ggml-base) para ${modelDir} (${DOWNLOAD_OPT_IN}, via #58)`;
216
+ let gguf;
217
+ try {
218
+ gguf = listDir(modelDir).find((file) => file.toLowerCase().endsWith(GGUF_EXTENSION));
219
+ }
220
+ catch {
221
+ // Reason: diretório ainda não criado (setup fresco) ou ilegível — ausente.
222
+ gguf = undefined;
223
+ }
224
+ if (gguf === undefined) {
225
+ return makeDiagnosis('modelo whisper (GGUF)', hint, undefined);
226
+ }
227
+ return makeDiagnosis('modelo whisper (GGUF)', hint, { path: join(modelDir, gguf) });
228
+ }
229
+ /**
230
+ * Diagnostica os binários e o modelo de mídia: `tesseract`, `ffmpeg`,
231
+ * `whisper.cpp` e o modelo GGUF. Cada um localizado (PATH + locais convencionais
232
+ * / cache), com versão quando legível, sempre com a instrução de instalação do
233
+ * SO. A ordem é estável (a mesma exibida por CLI/TUI).
234
+ *
235
+ * @param options - Deps injetáveis (plataforma, localizadores, versões, modelo).
236
+ * Ver {@link DiagnoseBinariesOptions}.
237
+ * @returns A lista de {@link BinaryDiagnosis} na ordem
238
+ * `[tesseract, pdftotext, ffmpeg, whisper.cpp, modelo]`.
239
+ * @example
240
+ * for (const item of await diagnoseBinaries()) {
241
+ * if (!item.found) logger.warn(item.installHint);
242
+ * }
243
+ */
244
+ export async function diagnoseBinaries(options = {}) {
245
+ const platform = options.platform ?? process.platform;
246
+ const findTesseract = options.findTesseract ?? defaultFindTesseract;
247
+ const detectTesseractVersion = options.detectTesseractVersion ?? defaultDetectTesseractVersion;
248
+ const findFfmpeg = options.findFfmpeg ?? defaultFindFfmpeg;
249
+ const detectFfmpegVersion = options.detectFfmpegVersion ?? defaultDetectFfmpegVersion;
250
+ const findWhisper = options.findWhisper ?? defaultFindWhisper;
251
+ const modelDir = (options.whisperModelDir ?? defaultWhisperModelDir)();
252
+ const listDir = options.listDir ?? ((dir) => readdirSync(dir));
253
+ return [
254
+ await diagnoseTesseract(platform, findTesseract, detectTesseractVersion),
255
+ await diagnosePdftotext(platform, options.findPdftotext ?? defaultFindPdftotext, options.detectPdftotextVersion ?? defaultDetectPdftotextVersion),
256
+ await diagnoseFfmpeg(platform, findFfmpeg, detectFfmpegVersion),
257
+ diagnoseWhisper(platform, findWhisper),
258
+ diagnoseWhisperModel(modelDir, listDir),
259
+ ];
260
+ }
@@ -0,0 +1,6 @@
1
+ export declare const MODULE_NAME: "config";
2
+ export { loginWithPassword, validateApiKey, RedmineLoginError, type LoginOptions, type LoginResult, type ValidateApiKeyOptions, } from './login.js';
3
+ export { CascadingCredentialStore, CredentialStoreError, EnvCredentialStore, FileCredentialStore, MigratingCredentialCascade, createCredentialCascade, defaultCredentialsPath, describeCredentialSource, normalizeInstanceUrl, resolveApiKey, type CredentialCascadeOptions, type CredentialSourceKind, type CredentialStore, type EnvCredentialStoreOptions, type FileCredentialStoreOptions, } from './credentials.js';
4
+ export { KeyringCredentialStore, type KeyringCredentialStoreOptions, type KeyringEntry, type KeyringModule, type KeyringModuleLoader, } from './keyring.js';
5
+ export { FileSettingsStore, defaultSettingsPath, defaultSettingsStore, resolveInstanceUrl, type FileSettingsStoreOptions, type InstanceUrlOrigin, type ResolvedInstanceUrl, type Settings, type SettingsStore, } from './settings.js';
6
+ export { diagnoseBinaries, tesseractInstallHint, pdftotextInstallHint, ffmpegInstallHint, whisperInstallHint, type BinaryDiagnosis, type DiagnoseBinariesOptions, } from './doctor.js';
@@ -0,0 +1,6 @@
1
+ export const MODULE_NAME = 'config';
2
+ export { loginWithPassword, validateApiKey, RedmineLoginError, } from './login.js';
3
+ export { CascadingCredentialStore, CredentialStoreError, EnvCredentialStore, FileCredentialStore, MigratingCredentialCascade, createCredentialCascade, defaultCredentialsPath, describeCredentialSource, normalizeInstanceUrl, resolveApiKey, } from './credentials.js';
4
+ export { KeyringCredentialStore, } from './keyring.js';
5
+ export { FileSettingsStore, defaultSettingsPath, defaultSettingsStore, resolveInstanceUrl, } from './settings.js';
6
+ export { diagnoseBinaries, tesseractInstallHint, pdftotextInstallHint, ffmpegInstallHint, whisperInstallHint, } from './doctor.js';
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Credential store da api_key no keychain nativo do SO (M2-14).
3
+ *
4
+ * Implementa a mesma interface {@link CredentialStore} do M1 sobre o keychain do
5
+ * sistema operacional via `@napi-rs/keyring` (macOS Keychain, Windows Credential
6
+ * Manager, libsecret no Linux). O pacote distribui binários pré-compilados pelas
7
+ * próprias `optionalDependencies` por plataforma, então `node-gyp` nunca é
8
+ * acionado numa instalação limpa.
9
+ *
10
+ * O módulo nativo é carregado sob demanda por import dinâmico com `try/catch`: se
11
+ * não houver prebuild para a plataforma (ex.: uma variante `musl` sem binário), o
12
+ * carregamento falha de forma controlada — o store fica indisponível (`get`
13
+ * devolve `undefined`; `set`/`delete` viram no-op) e um único aviso é emitido via
14
+ * logger, permitindo que a cascata siga para o arquivo. Erros do keychain em
15
+ * runtime (ler/gravar/remover) também degradam sem derrubar o processo.
16
+ *
17
+ * O service usado é `redmine-context` e o account é a instância normalizada por
18
+ * {@link normalizeInstanceUrl}, mantendo a mesma chave estável do store de
19
+ * arquivo. A api_key nunca é logada nem incluída em mensagens de aviso.
20
+ *
21
+ * Esta issue apenas introduz o store e suas exportações; a ordem da cascata
22
+ * (keychain → arquivo → env) e a migração ficam para o M2-15 (#38).
23
+ */
24
+ import type { Logger } from '../client/index.js';
25
+ import { type CredentialStore } from './credentials.js';
26
+ /**
27
+ * Superfície mínima da `Entry` de `@napi-rs/keyring` usada por este store.
28
+ * Declarada localmente para permitir mock nos testes e desacoplar do tipo nativo.
29
+ */
30
+ export interface KeyringEntry {
31
+ /** Recupera o segredo; `null` (ou erro `NoEntry`) quando não existe. */
32
+ getPassword(): string | null;
33
+ /** Persiste (ou substitui) o segredo desta entrada. */
34
+ setPassword(password: string): void;
35
+ /** Remove a credencial; `false` quando não havia o que remover. */
36
+ deleteCredential(): boolean;
37
+ }
38
+ /** Superfície mínima do módulo `@napi-rs/keyring`: apenas o construtor `Entry`. */
39
+ export interface KeyringModule {
40
+ /** Construtor de uma entrada para `(service, account)`. */
41
+ Entry: new (service: string, account: string) => KeyringEntry;
42
+ }
43
+ /**
44
+ * Carregador do módulo nativo. Injetável para testes; o padrão faz o import
45
+ * dinâmico de `@napi-rs/keyring`.
46
+ */
47
+ export type KeyringModuleLoader = () => Promise<KeyringModule>;
48
+ /** Opções do {@link KeyringCredentialStore}. */
49
+ export interface KeyringCredentialStoreOptions {
50
+ /** Service registrado no keychain; default `redmine-context`. */
51
+ service?: string;
52
+ /** Logger para o aviso único de indisponibilidade/degradação; default no-op. */
53
+ logger?: Logger;
54
+ /** Carregador do módulo nativo; default import dinâmico (injetável em testes). */
55
+ loader?: KeyringModuleLoader;
56
+ }
57
+ /**
58
+ * Implementação de {@link CredentialStore} sobre o keychain nativo do SO.
59
+ *
60
+ * Todas as operações degradam de forma segura: indisponibilidade do módulo ou
61
+ * erros de runtime do keychain nunca derrubam o processo — `get` devolve
62
+ * `undefined` e `set`/`delete` viram no-op, com aviso via logger.
63
+ */
64
+ export declare class KeyringCredentialStore implements CredentialStore {
65
+ private readonly service;
66
+ private readonly logger;
67
+ private readonly loader;
68
+ /** Import memoizado: resolve para o módulo ou `undefined` se indisponível. */
69
+ private modulePromise;
70
+ /** Garante que o aviso de indisponibilidade seja emitido uma única vez. */
71
+ private unavailableWarned;
72
+ /** @param options - Ver {@link KeyringCredentialStoreOptions}. */
73
+ constructor(options?: KeyringCredentialStoreOptions);
74
+ /**
75
+ * Indica se o keychain nativo está disponível nesta plataforma (há prebuild e
76
+ * o módulo carrega). Usado pela cascata M2 para decidir a migração e o destino
77
+ * das escritas. O carregamento é memoizado, então o custo é pago uma só vez.
78
+ *
79
+ * @returns `true` se o módulo nativo carregou; `false` se indisponível.
80
+ */
81
+ isAvailable(): Promise<boolean>;
82
+ /** @inheritdoc */
83
+ get(instance: string): Promise<string | undefined>;
84
+ /** @inheritdoc */
85
+ set(instance: string, apiKey: string): Promise<void>;
86
+ /** @inheritdoc */
87
+ delete(instance: string): Promise<void>;
88
+ /** Constrói a `Entry` para a instância, ou `undefined` se indisponível. */
89
+ private entryFor;
90
+ /** Carrega (uma vez) o módulo nativo; `undefined` se não houver prebuild. */
91
+ private load;
92
+ /** Emite (uma única vez) o aviso de módulo indisponível. */
93
+ private warnUnavailable;
94
+ /** Emite um aviso de degradação de runtime — sem nunca expor a api_key. */
95
+ private warnRuntime;
96
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Credential store da api_key no keychain nativo do SO (M2-14).
3
+ *
4
+ * Implementa a mesma interface {@link CredentialStore} do M1 sobre o keychain do
5
+ * sistema operacional via `@napi-rs/keyring` (macOS Keychain, Windows Credential
6
+ * Manager, libsecret no Linux). O pacote distribui binários pré-compilados pelas
7
+ * próprias `optionalDependencies` por plataforma, então `node-gyp` nunca é
8
+ * acionado numa instalação limpa.
9
+ *
10
+ * O módulo nativo é carregado sob demanda por import dinâmico com `try/catch`: se
11
+ * não houver prebuild para a plataforma (ex.: uma variante `musl` sem binário), o
12
+ * carregamento falha de forma controlada — o store fica indisponível (`get`
13
+ * devolve `undefined`; `set`/`delete` viram no-op) e um único aviso é emitido via
14
+ * logger, permitindo que a cascata siga para o arquivo. Erros do keychain em
15
+ * runtime (ler/gravar/remover) também degradam sem derrubar o processo.
16
+ *
17
+ * O service usado é `redmine-context` e o account é a instância normalizada por
18
+ * {@link normalizeInstanceUrl}, mantendo a mesma chave estável do store de
19
+ * arquivo. A api_key nunca é logada nem incluída em mensagens de aviso.
20
+ *
21
+ * Esta issue apenas introduz o store e suas exportações; a ordem da cascata
22
+ * (keychain → arquivo → env) e a migração ficam para o M2-15 (#38).
23
+ */
24
+ import { normalizeInstanceUrl } from './credentials.js';
25
+ /** Especificador do módulo nativo carregado dinamicamente. */
26
+ const KEYRING_MODULE = '@napi-rs/keyring';
27
+ /** Service padrão registrado no keychain do SO. */
28
+ const DEFAULT_SERVICE = 'redmine-context';
29
+ /** Logger no-op: usado quando nenhum logger é injetado. */
30
+ const noopLogger = { warn: () => undefined };
31
+ /** Loader padrão: import dinâmico do binding nativo. */
32
+ const defaultLoader = async () => {
33
+ const mod = await import(KEYRING_MODULE);
34
+ return mod;
35
+ };
36
+ /** Verifica se um erro do keyring é "entrada não encontrada" (NoEntry). */
37
+ function isNoEntry(cause) {
38
+ const message = cause instanceof Error ? cause.message.toLowerCase() : String(cause).toLowerCase();
39
+ return message.includes('no matching entry') || message.includes('noentry');
40
+ }
41
+ /**
42
+ * Implementação de {@link CredentialStore} sobre o keychain nativo do SO.
43
+ *
44
+ * Todas as operações degradam de forma segura: indisponibilidade do módulo ou
45
+ * erros de runtime do keychain nunca derrubam o processo — `get` devolve
46
+ * `undefined` e `set`/`delete` viram no-op, com aviso via logger.
47
+ */
48
+ export class KeyringCredentialStore {
49
+ service;
50
+ logger;
51
+ loader;
52
+ /** Import memoizado: resolve para o módulo ou `undefined` se indisponível. */
53
+ modulePromise;
54
+ /** Garante que o aviso de indisponibilidade seja emitido uma única vez. */
55
+ unavailableWarned = false;
56
+ /** @param options - Ver {@link KeyringCredentialStoreOptions}. */
57
+ constructor(options = {}) {
58
+ this.service = options.service ?? DEFAULT_SERVICE;
59
+ this.logger = options.logger ?? noopLogger;
60
+ this.loader = options.loader ?? defaultLoader;
61
+ }
62
+ /**
63
+ * Indica se o keychain nativo está disponível nesta plataforma (há prebuild e
64
+ * o módulo carrega). Usado pela cascata M2 para decidir a migração e o destino
65
+ * das escritas. O carregamento é memoizado, então o custo é pago uma só vez.
66
+ *
67
+ * @returns `true` se o módulo nativo carregou; `false` se indisponível.
68
+ */
69
+ async isAvailable() {
70
+ return (await this.load()) !== undefined;
71
+ }
72
+ /** @inheritdoc */
73
+ async get(instance) {
74
+ const entry = await this.entryFor(instance);
75
+ if (entry === undefined) {
76
+ return undefined;
77
+ }
78
+ try {
79
+ const value = entry.getPassword();
80
+ return value !== null && value.length > 0 ? value : undefined;
81
+ }
82
+ catch (cause) {
83
+ if (isNoEntry(cause)) {
84
+ return undefined;
85
+ }
86
+ this.warnRuntime('ler', cause);
87
+ return undefined;
88
+ }
89
+ }
90
+ /** @inheritdoc */
91
+ async set(instance, apiKey) {
92
+ const entry = await this.entryFor(instance);
93
+ if (entry === undefined) {
94
+ return;
95
+ }
96
+ try {
97
+ entry.setPassword(apiKey);
98
+ }
99
+ catch (cause) {
100
+ this.warnRuntime('gravar', cause);
101
+ }
102
+ }
103
+ /** @inheritdoc */
104
+ async delete(instance) {
105
+ const entry = await this.entryFor(instance);
106
+ if (entry === undefined) {
107
+ return;
108
+ }
109
+ try {
110
+ entry.deleteCredential();
111
+ }
112
+ catch (cause) {
113
+ // Remover uma entrada ausente é um no-op esperado, não um erro.
114
+ if (isNoEntry(cause)) {
115
+ return;
116
+ }
117
+ this.warnRuntime('remover', cause);
118
+ }
119
+ }
120
+ /** Constrói a `Entry` para a instância, ou `undefined` se indisponível. */
121
+ async entryFor(instance) {
122
+ const mod = await this.load();
123
+ if (mod === undefined) {
124
+ return undefined;
125
+ }
126
+ const account = normalizeInstanceUrl(instance);
127
+ try {
128
+ return new mod.Entry(this.service, account);
129
+ }
130
+ catch (cause) {
131
+ this.warnRuntime('inicializar', cause);
132
+ return undefined;
133
+ }
134
+ }
135
+ /** Carrega (uma vez) o módulo nativo; `undefined` se não houver prebuild. */
136
+ async load() {
137
+ this.modulePromise ??= this.loader().then((mod) => mod, (cause) => {
138
+ this.warnUnavailable(cause);
139
+ return undefined;
140
+ });
141
+ return this.modulePromise;
142
+ }
143
+ /** Emite (uma única vez) o aviso de módulo indisponível. */
144
+ warnUnavailable(cause) {
145
+ if (this.unavailableWarned) {
146
+ return;
147
+ }
148
+ this.unavailableWarned = true;
149
+ const reason = cause instanceof Error ? cause.message : String(cause);
150
+ this.logger.warn(`Keychain nativo indisponível (${reason}); as credenciais seguirão pelo arquivo. ` +
151
+ 'Instale o prebuild de @napi-rs/keyring para esta plataforma para usar o keychain do SO.');
152
+ }
153
+ /** Emite um aviso de degradação de runtime — sem nunca expor a api_key. */
154
+ warnRuntime(action, cause) {
155
+ const reason = cause instanceof Error ? cause.message : String(cause);
156
+ this.logger.warn(`Falha ao ${action} a credencial no keychain do SO: ${reason}.`);
157
+ }
158
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Login por senha (Basic auth) para descobrir a api_key do usuário (M1-07).
3
+ *
4
+ * Fluxo: `GET {baseUrl}/users/current.json` com `Authorization: Basic
5
+ * base64(user:pass)`. A senha nunca é logada nem persistida; qualquer texto
6
+ * que possa vazar (mensagens de rede/parse) é redigido antes de compor um erro.
7
+ *
8
+ * A política de TLS (https obrigatório; `insecure` apenas com aviso ruidoso) é
9
+ * reutilizada de `client/http.ts` via {@link validateBaseUrl} — não duplicada.
10
+ * O fallback de 2FA (copiar a api_key em `/my/account`) segue o ADR-003.
11
+ */
12
+ import { type Logger } from '../client/index.js';
13
+ /** Opções do login por senha. */
14
+ export interface LoginOptions {
15
+ /** URL base da instância Redmine (ex.: `https://redmine.example`). */
16
+ baseUrl: string;
17
+ /** Login/identificador do usuário no Redmine. */
18
+ username: string;
19
+ /** Senha em texto puro — usada só para o header Basic, nunca logada. */
20
+ password: string;
21
+ /** Permite `http://` (sem TLS) com aviso ruidoso. Default: `false`. */
22
+ insecure?: boolean;
23
+ /** Logger para o aviso de conexão insegura; default no-op. */
24
+ logger?: Logger;
25
+ }
26
+ /** Resultado do login: api_key descoberta e identidade mínima do usuário. */
27
+ export interface LoginResult {
28
+ /** api_key do usuário, extraída de `/users/current.json`. */
29
+ apiKey: string;
30
+ /** Identidade mínima do usuário autenticado. */
31
+ user: {
32
+ /** Id numérico do usuário no Redmine. */
33
+ id: number;
34
+ /** Login do usuário. */
35
+ login: string;
36
+ /** Nome de exibição (`firstname lastname`, ou `login` se ausentes). */
37
+ name: string;
38
+ };
39
+ }
40
+ /**
41
+ * Erro de login por senha que não se enquadra num status HTTP tipado do client
42
+ * (entrada inválida, REST desabilitada, corpo inesperado, falha de rede/parse).
43
+ * Sempre construído com a mensagem já redigida — nunca carrega a senha.
44
+ */
45
+ export declare class RedmineLoginError extends Error {
46
+ constructor(message: string);
47
+ }
48
+ /**
49
+ * Autentica por senha e descobre a api_key do usuário.
50
+ *
51
+ * @param options - Ver {@link LoginOptions}.
52
+ * @returns {@link LoginResult} com a api_key e a identidade do usuário.
53
+ * @throws {RedmineLoginError} Entrada inválida, corpo sem usuário/api_key,
54
+ * REST desabilitada, falha de rede ou JSON inválido (senha sempre redigida).
55
+ * @throws {RedmineAuthError} Em 401 — com instrução de fallback 2FA (ADR-003).
56
+ * @throws {Error} Se a política de TLS for violada (via `validateBaseUrl`).
57
+ * @example
58
+ * const { apiKey, user } = await loginWithPassword({
59
+ * baseUrl: 'https://redmine.example',
60
+ * username: 'alice',
61
+ * password: '****',
62
+ * });
63
+ */
64
+ export declare function loginWithPassword(options: LoginOptions): Promise<LoginResult>;
65
+ /** Opções da validação de uma api_key colada diretamente (fallback de 2FA). */
66
+ export interface ValidateApiKeyOptions {
67
+ /** URL base da instância Redmine (ex.: `https://redmine.example`). */
68
+ baseUrl: string;
69
+ /** api_key colada pelo usuário — usada só para o header, nunca logada. */
70
+ apiKey: string;
71
+ /** Permite `http://` (sem TLS) com aviso ruidoso. Default: `false`. */
72
+ insecure?: boolean;
73
+ /** Logger para o aviso de conexão insegura; default no-op. */
74
+ logger?: Logger;
75
+ }
76
+ /**
77
+ * Valida uma api_key colada diretamente pelo usuário (fallback de 2FA do
78
+ * ADR-003, quando `loginWithPassword` falha com 401) contra `GET
79
+ * {baseUrl}/users/current.json`, autenticando com o header
80
+ * `X-Redmine-API-Key` em vez de Basic auth. A key nunca é logada; qualquer
81
+ * texto que possa vazar é redigido antes de compor um erro — mesma política
82
+ * de {@link loginWithPassword}.
83
+ *
84
+ * @param options - Ver {@link ValidateApiKeyOptions}.
85
+ * @returns {@link LoginResult} com a própria api_key (ecoada de volta, para
86
+ * simetria com {@link loginWithPassword}) e a identidade do usuário.
87
+ * @throws {RedmineLoginError} Entrada inválida, corpo sem usuário, REST
88
+ * desabilitada, falha de rede ou JSON inválido (key sempre redigida).
89
+ * @throws {RedmineAuthError} Em 401 — api_key inválida ou expirada.
90
+ * @throws {Error} Se a política de TLS for violada (via `validateBaseUrl`).
91
+ * @example
92
+ * const { apiKey, user } = await validateApiKey({
93
+ * baseUrl: 'https://redmine.example',
94
+ * apiKey: pastedKey,
95
+ * });
96
+ */
97
+ export declare function validateApiKey(options: ValidateApiKeyOptions): Promise<LoginResult>;