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,106 @@
1
+ /**
2
+ * Sonda de DURAÇÃO de vídeo via `ffprobe` (M4-09, #65, ADR-002).
3
+ *
4
+ * Mede a duração de um vídeo ANTES de convertê-lo/transcrevê-lo, para que o
5
+ * pipeline ({@link extractVideoTranscript}) possa PULAR com aviso os vídeos que
6
+ * excedem o limite configurável (default 20 min, ADR-002) — sem gastar CPU na
7
+ * conversão/transcrição de mídia longa.
8
+ *
9
+ * Extraído de `video.ts` para manter cada módulo sob o limite de tamanho e com um
10
+ * propósito único (Rule #24): aqui vive SÓ a sondagem de duração; a orquestração
11
+ * (skip + preservação do keyframe) permanece em `video.ts`.
12
+ *
13
+ * DECISÕES DE SEGURANÇA/ROBUSTEZ (ADR-002), como no restante da milestone:
14
+ * - INVOCAÇÃO SEM SHELL: reusa {@link runWithWatchdog} (`execFile`), lista de args
15
+ * explícita — o `inputPath` NUNCA é interpolado numa string de shell.
16
+ * - `-protocol_whitelist file`: o ffprobe só pode abrir o arquivo local dado, nunca
17
+ * protocolos remotos embutidos em playlists/manifests hostis.
18
+ * - ENV SANITIZADO: só `PATH` — segredos do processo pai não vazam para o ffprobe.
19
+ * - TIMEOUT + KILL: watchdog `SIGTERM`→ graça →`SIGKILL`.
20
+ * - DEGRADAÇÃO GRACIOSA: ffprobe ausente, exit != 0, timeout ou saída não-parseável
21
+ * NÃO lançam; devolvem `{ status: 'unavailable', reason }` — o chamador decide o
22
+ * comportamento seguro (o pipeline PROSSEGUE, não bloqueia).
23
+ *
24
+ * Fronteira: módulo de core (pipeline de extração) — sem `console.*` (ADR-005).
25
+ */
26
+ import type { Logger } from '../client/index.js';
27
+ /** Invocação concreta do `ffprobe` passada ao {@link FfprobeRunner}. */
28
+ export interface FfprobeInvocation {
29
+ /** Caminho absoluto do binário `ffprobe` já resolvido. */
30
+ readonly bin: string;
31
+ /** Argumentos do ffprobe (sem shell), incluindo `-protocol_whitelist file`. */
32
+ readonly args: readonly string[];
33
+ /** Env SANITIZADO do subprocesso (só `PATH`). */
34
+ readonly env: NodeJS.ProcessEnv;
35
+ /** Timeout antes do `SIGTERM` (ms). */
36
+ readonly timeoutMs: number;
37
+ /** Graça `SIGTERM` → `SIGKILL` (ms). */
38
+ readonly killGraceMs: number;
39
+ }
40
+ /**
41
+ * Executor injetável do subprocesso ffprobe. Resolve com o `stdout` (a duração
42
+ * crua) no sucesso (exit 0); rejeita em falha — com um erro de nome
43
+ * `FfprobeTimeoutError` para sinalizar estouro de timeout.
44
+ */
45
+ export type FfprobeRunner = (invocation: FfprobeInvocation) => Promise<string>;
46
+ /** Sonda de duração bem-sucedida: duração do container em segundos. */
47
+ export interface DurationProbeOk {
48
+ readonly status: 'ok';
49
+ /** Duração total do vídeo em segundos (float), como reportada pelo ffprobe. */
50
+ readonly seconds: number;
51
+ }
52
+ /**
53
+ * Sonda de duração INDISPONÍVEL (graciosa, ADR-002): o ffprobe está ausente, falhou,
54
+ * estourou o timeout ou devolveu uma duração não-parseável. O chamador decide o
55
+ * comportamento seguro — o pipeline PROSSEGUE sem bloquear (ver {@link extractVideoTranscript}).
56
+ */
57
+ export interface DurationProbeUnavailable {
58
+ readonly status: 'unavailable';
59
+ /** Motivo canônico (`ffprobe-nao-instalado` | `duracao-indisponivel` | `erro-ffprobe` | `timeout`). */
60
+ readonly reason: string;
61
+ /** Mensagem de erro subjacente, quando houver. */
62
+ readonly error?: string;
63
+ }
64
+ /** Resultado tipado da sonda de duração via ffprobe. */
65
+ export type DurationProbeResult = DurationProbeOk | DurationProbeUnavailable;
66
+ /** Opções (todas injetáveis para testes herméticos) de {@link probeVideoDuration}. */
67
+ export interface ProbeVideoDurationOptions {
68
+ /** Localizador do `ffprobe`; default {@link findFfprobe}. */
69
+ readonly findFfprobeBinary?: () => string | undefined;
70
+ /** Executor do subprocesso; default: watchdog real com `execFile`. */
71
+ readonly run?: FfprobeRunner;
72
+ /** Timeout antes do `SIGTERM` (ms); default {@link DEFAULT_PROBE_TIMEOUT_MS}. */
73
+ readonly timeoutMs?: number;
74
+ /** Graça `SIGTERM` → `SIGKILL` (ms); default {@link PROBE_KILL_GRACE_MS}. */
75
+ readonly killGraceMs?: number;
76
+ /** Logger para o aviso de binário ausente; sem default de lib (ADR-003). */
77
+ readonly logger?: Logger;
78
+ }
79
+ /**
80
+ * Parseia a duração (segundos, float) da saída crua do `ffprobe`. Devolve
81
+ * `undefined` para saída vazia, `N/A`, não-numérica ou negativa — sinal de que a
82
+ * duração é INDISPONÍVEL (não presumimos zero nem crashamos).
83
+ *
84
+ * @param stdout - Saída crua do ffprobe (`format=duration`, uma linha).
85
+ * @returns A duração em segundos, ou `undefined` se não parseável.
86
+ * @example
87
+ * parseFfprobeDuration('123.45\n'); // 123.45
88
+ * parseFfprobeDuration('N/A'); // undefined
89
+ */
90
+ export declare function parseFfprobeDuration(stdout: string): number | undefined;
91
+ /**
92
+ * Mede a duração de um vídeo via `ffprobe` ANTES de convertê-lo/transcrevê-lo
93
+ * (M4-09, #65). REUSA o subprocesso seguro compartilhado (sem shell,
94
+ * `-protocol_whitelist file`, env sanitizado, watchdog `SIGTERM`→`SIGKILL`). NUNCA
95
+ * lança (degradação graciosa, ADR-002): ffprobe ausente, exit != 0, timeout ou
96
+ * saída não-parseável viram `{ status: 'unavailable', reason }` — cabe ao chamador
97
+ * decidir o comportamento seguro (o pipeline PROSSEGUE quando a duração é indisponível).
98
+ *
99
+ * @param inputPath - Caminho absoluto do vídeo já baixado no cache.
100
+ * @param options - Deps injetáveis + timeouts + logger — ver {@link ProbeVideoDurationOptions}.
101
+ * @returns `ok` com `seconds` no sucesso; `unavailable` com motivo caso contrário.
102
+ * @example
103
+ * const probe = await probeVideoDuration('/cache/att/clip.mp4');
104
+ * if (probe.status === 'ok' && probe.seconds > 1200) skipTranscription();
105
+ */
106
+ export declare function probeVideoDuration(inputPath: string, options?: ProbeVideoDurationOptions): Promise<DurationProbeResult>;
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Sonda de DURAÇÃO de vídeo via `ffprobe` (M4-09, #65, ADR-002).
3
+ *
4
+ * Mede a duração de um vídeo ANTES de convertê-lo/transcrevê-lo, para que o
5
+ * pipeline ({@link extractVideoTranscript}) possa PULAR com aviso os vídeos que
6
+ * excedem o limite configurável (default 20 min, ADR-002) — sem gastar CPU na
7
+ * conversão/transcrição de mídia longa.
8
+ *
9
+ * Extraído de `video.ts` para manter cada módulo sob o limite de tamanho e com um
10
+ * propósito único (Rule #24): aqui vive SÓ a sondagem de duração; a orquestração
11
+ * (skip + preservação do keyframe) permanece em `video.ts`.
12
+ *
13
+ * DECISÕES DE SEGURANÇA/ROBUSTEZ (ADR-002), como no restante da milestone:
14
+ * - INVOCAÇÃO SEM SHELL: reusa {@link runWithWatchdog} (`execFile`), lista de args
15
+ * explícita — o `inputPath` NUNCA é interpolado numa string de shell.
16
+ * - `-protocol_whitelist file`: o ffprobe só pode abrir o arquivo local dado, nunca
17
+ * protocolos remotos embutidos em playlists/manifests hostis.
18
+ * - ENV SANITIZADO: só `PATH` — segredos do processo pai não vazam para o ffprobe.
19
+ * - TIMEOUT + KILL: watchdog `SIGTERM`→ graça →`SIGKILL`.
20
+ * - DEGRADAÇÃO GRACIOSA: ffprobe ausente, exit != 0, timeout ou saída não-parseável
21
+ * NÃO lançam; devolvem `{ status: 'unavailable', reason }` — o chamador decide o
22
+ * comportamento seguro (o pipeline PROSSEGUE, não bloqueia).
23
+ *
24
+ * Fronteira: módulo de core (pipeline de extração) — sem `console.*` (ADR-005).
25
+ */
26
+ import { findFfprobe } from './ffmpeg.js';
27
+ import { runWithWatchdog, sanitizedEnv } from './subprocess.js';
28
+ /** Timeout default da sonda `ffprobe` antes do `SIGTERM` (ms) — a leitura é rápida. */
29
+ const DEFAULT_PROBE_TIMEOUT_MS = 15_000;
30
+ /** Graça `SIGTERM` → `SIGKILL` da sonda `ffprobe` (ms). */
31
+ const PROBE_KILL_GRACE_MS = 2_000;
32
+ /** Teto do stdout/stderr do ffprobe (1 MiB) — a saída é uma única linha (a duração). */
33
+ const PROBE_MAX_BUFFER_BYTES = 1024 * 1024;
34
+ /** Erro interno: o watchdog matou o ffprobe por estourar o timeout. */
35
+ class FfprobeTimeoutError extends Error {
36
+ timeoutMs;
37
+ constructor(timeoutMs) {
38
+ super(`ffprobe excedeu o timeout de ${timeoutMs}ms e foi encerrado`);
39
+ this.timeoutMs = timeoutMs;
40
+ this.name = 'FfprobeTimeoutError';
41
+ }
42
+ }
43
+ /**
44
+ * Monta os argumentos do `ffprobe` para ler SÓ a duração do container em `inputPath`,
45
+ * como um número cru em segundos. `-protocol_whitelist file` precede o arquivo de
46
+ * entrada (ADR-002): o ffprobe só pode abrir o arquivo local dado, nunca protocolos
47
+ * remotos embutidos em playlists/manifests hostis. `-of default=nw=1:nk=1` imprime o
48
+ * valor sem chave nem cabeçalho de seção — só o número.
49
+ *
50
+ * @param inputPath - Caminho absoluto do vídeo de entrada.
51
+ * @returns Lista de argumentos, na ordem exigida pelo ffprobe.
52
+ */
53
+ function buildFfprobeArgs(inputPath) {
54
+ return [
55
+ '-v',
56
+ 'error', // silencioso, exceto erros
57
+ '-protocol_whitelist',
58
+ 'file', // ADR-002: só o arquivo local dado, nada remoto
59
+ '-show_entries',
60
+ 'format=duration', // só a duração do container
61
+ '-of',
62
+ 'default=nw=1:nk=1', // saída crua: sem wrapper, sem chave
63
+ inputPath,
64
+ ];
65
+ }
66
+ /**
67
+ * Executor default da sonda: delega ao watchdog compartilhado
68
+ * ({@link runWithWatchdog}) — ffprobe SEM shell, env sanitizado e escalonamento
69
+ * `SIGTERM` → graça → `SIGKILL`. Diferente do ffmpeg, aqui o `stdout` IMPORTA (é a
70
+ * duração). O estouro de timeout é sinalizado com {@link FfprobeTimeoutError}.
71
+ *
72
+ * @param invocation - Ver {@link FfprobeInvocation}.
73
+ * @returns O `stdout` do ffprobe (a duração crua) no sucesso.
74
+ */
75
+ function defaultProbeRun(invocation) {
76
+ const { bin, args, env, timeoutMs, killGraceMs } = invocation;
77
+ return runWithWatchdog({ bin, args, env, timeoutMs, killGraceMs, maxBuffer: PROBE_MAX_BUFFER_BYTES }, { makeTimeoutError: (ms) => new FfprobeTimeoutError(ms) });
78
+ }
79
+ /** `true` se o erro sinaliza estouro de timeout da sonda (por nome, robusto a DI). */
80
+ function isProbeTimeout(error) {
81
+ return error instanceof Error && error.name === 'FfprobeTimeoutError';
82
+ }
83
+ /**
84
+ * Parseia a duração (segundos, float) da saída crua do `ffprobe`. Devolve
85
+ * `undefined` para saída vazia, `N/A`, não-numérica ou negativa — sinal de que a
86
+ * duração é INDISPONÍVEL (não presumimos zero nem crashamos).
87
+ *
88
+ * @param stdout - Saída crua do ffprobe (`format=duration`, uma linha).
89
+ * @returns A duração em segundos, ou `undefined` se não parseável.
90
+ * @example
91
+ * parseFfprobeDuration('123.45\n'); // 123.45
92
+ * parseFfprobeDuration('N/A'); // undefined
93
+ */
94
+ export function parseFfprobeDuration(stdout) {
95
+ const trimmed = stdout.trim();
96
+ if (trimmed.length === 0)
97
+ return undefined;
98
+ const seconds = Number.parseFloat(trimmed);
99
+ // Reason: `parseFloat('N/A')` → NaN; duração negativa é dado degenerado. Em ambos,
100
+ // tratamos como indisponível em vez de presumir um valor (degradação graciosa).
101
+ if (!Number.isFinite(seconds) || seconds < 0)
102
+ return undefined;
103
+ return seconds;
104
+ }
105
+ /**
106
+ * Mede a duração de um vídeo via `ffprobe` ANTES de convertê-lo/transcrevê-lo
107
+ * (M4-09, #65). REUSA o subprocesso seguro compartilhado (sem shell,
108
+ * `-protocol_whitelist file`, env sanitizado, watchdog `SIGTERM`→`SIGKILL`). NUNCA
109
+ * lança (degradação graciosa, ADR-002): ffprobe ausente, exit != 0, timeout ou
110
+ * saída não-parseável viram `{ status: 'unavailable', reason }` — cabe ao chamador
111
+ * decidir o comportamento seguro (o pipeline PROSSEGUE quando a duração é indisponível).
112
+ *
113
+ * @param inputPath - Caminho absoluto do vídeo já baixado no cache.
114
+ * @param options - Deps injetáveis + timeouts + logger — ver {@link ProbeVideoDurationOptions}.
115
+ * @returns `ok` com `seconds` no sucesso; `unavailable` com motivo caso contrário.
116
+ * @example
117
+ * const probe = await probeVideoDuration('/cache/att/clip.mp4');
118
+ * if (probe.status === 'ok' && probe.seconds > 1200) skipTranscription();
119
+ */
120
+ export async function probeVideoDuration(inputPath, options = {}) {
121
+ const findBinary = options.findFfprobeBinary ?? (() => findFfprobe()?.path);
122
+ const bin = findBinary();
123
+ if (bin === undefined) {
124
+ options.logger?.warn('ffprobe: binário não encontrado; a duração do vídeo não será medida ' +
125
+ '(a transcrição segue sem o limite de duração) — instale o ffmpeg/ffprobe (ver doctor)');
126
+ return { status: 'unavailable', reason: 'ffprobe-nao-instalado' };
127
+ }
128
+ const run = options.run ?? defaultProbeRun;
129
+ const timeoutMs = options.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
130
+ const killGraceMs = options.killGraceMs ?? PROBE_KILL_GRACE_MS;
131
+ const args = buildFfprobeArgs(inputPath);
132
+ try {
133
+ const stdout = await run({ bin, args, env: sanitizedEnv(), timeoutMs, killGraceMs });
134
+ const seconds = parseFfprobeDuration(stdout);
135
+ if (seconds === undefined) {
136
+ return { status: 'unavailable', reason: 'duracao-indisponivel' };
137
+ }
138
+ return { status: 'ok', seconds };
139
+ }
140
+ catch (error) {
141
+ const message = error instanceof Error ? error.message : String(error);
142
+ return {
143
+ status: 'unavailable',
144
+ reason: isProbeTimeout(error) ? 'timeout' : 'erro-ffprobe',
145
+ error: message,
146
+ };
147
+ }
148
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Localização e versão do binário `ffmpeg` (M4-01, #57, ADR-002).
3
+ *
4
+ * O `doctor` (#57) usa estas funções para reportar a presença do ffmpeg — que a
5
+ * milestone M4 usará para extrair a faixa de áudio + 1 keyframe de vídeos (ADR-002)
6
+ * — sem reimplementar a detecção em cada superfície. Segue o padrão de
7
+ * {@link findTesseract}: PATH + locais convencionais (incl. `/opt/homebrew/bin`),
8
+ * puro e injetável, degradando graciosamente quando ausente.
9
+ */
10
+ import { findExecutable } from './which.js';
11
+ /** Resultado de {@link findFfmpeg}: caminho absoluto do binário localizado. */
12
+ export interface FfmpegLocation {
13
+ /** Caminho absoluto do executável `ffmpeg` encontrado. */
14
+ readonly path: string;
15
+ }
16
+ /**
17
+ * Localiza o binário `ffmpeg` no `PATH` e em locais convencionais por plataforma.
18
+ * Função pura e reutilizável pelo `doctor` (#57). Não executa o binário.
19
+ *
20
+ * @param deps - Deps injetáveis (plataforma/PATH/executabilidade) — ver
21
+ * {@link findExecutable}. Default: ambiente real.
22
+ * @returns A localização encontrada, ou `undefined` se não instalado.
23
+ * @example
24
+ * const found = findFfmpeg();
25
+ * if (found === undefined) logger.warn('ffmpeg não instalado');
26
+ */
27
+ export declare function findFfmpeg(deps?: Parameters<typeof findExecutable>[2]): FfmpegLocation | undefined;
28
+ /** Resultado de {@link findFfprobe}: caminho absoluto do binário localizado. */
29
+ export interface FfprobeLocation {
30
+ /** Caminho absoluto do executável `ffprobe` encontrado. */
31
+ readonly path: string;
32
+ }
33
+ /**
34
+ * Localiza o binário `ffprobe` no `PATH` e em locais convencionais por plataforma
35
+ * (os mesmos do ffmpeg — o ffprobe vem no MESMO pacote). Função pura e injetável;
36
+ * não executa o binário. Degrada para `undefined` quando ausente (ADR-002): sem
37
+ * ffprobe, a #65 não bloqueia — apenas não mede a duração.
38
+ *
39
+ * @param deps - Deps injetáveis (plataforma/PATH/executabilidade) — ver
40
+ * {@link findExecutable}. Default: ambiente real.
41
+ * @returns A localização encontrada, ou `undefined` se não instalado.
42
+ * @example
43
+ * const probe = findFfprobe();
44
+ * if (probe === undefined) logger.warn('ffprobe não instalado; duração não medida');
45
+ */
46
+ export declare function findFfprobe(deps?: Parameters<typeof findExecutable>[2]): FfprobeLocation | undefined;
47
+ /**
48
+ * Lê a versão do binário via `ffmpeg -version` (primeira linha:
49
+ * `ffmpeg version X.Y.Z ...` — em builds distro pode vir `n6.1.1` ou hash git;
50
+ * capturamos o primeiro token após `version`). Não lança: retorna `undefined`
51
+ * se o binário falhar ou a saída for inesperada.
52
+ *
53
+ * @param bin - Caminho absoluto do binário.
54
+ * @returns A versão detectada (ex.: `6.1.1`), ou `undefined`.
55
+ */
56
+ export declare function detectFfmpegVersion(bin: string): Promise<string | undefined>;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Localização e versão do binário `ffmpeg` (M4-01, #57, ADR-002).
3
+ *
4
+ * O `doctor` (#57) usa estas funções para reportar a presença do ffmpeg — que a
5
+ * milestone M4 usará para extrair a faixa de áudio + 1 keyframe de vídeos (ADR-002)
6
+ * — sem reimplementar a detecção em cada superfície. Segue o padrão de
7
+ * {@link findTesseract}: PATH + locais convencionais (incl. `/opt/homebrew/bin`),
8
+ * puro e injetável, degradando graciosamente quando ausente.
9
+ */
10
+ import { execFile } from 'node:child_process';
11
+ import { findExecutable } from './which.js';
12
+ /** Nome base do binário do ffmpeg. */
13
+ const FFMPEG_BINARY = 'ffmpeg';
14
+ /**
15
+ * Nome base do binário do `ffprobe` — a ferramenta de SONDA do pacote ffmpeg. É
16
+ * distribuída junto do ffmpeg (mesmo diretório/pacote em brew/apt/BtbN), por isso
17
+ * reusa os MESMOS locais convencionais ({@link CONVENTIONAL}). Usada pela #65 para
18
+ * medir a duração de um vídeo ANTES de transcrevê-lo (limite configurável, ADR-002).
19
+ */
20
+ const FFPROBE_BINARY = 'ffprobe';
21
+ /**
22
+ * Locais convencionais do `ffmpeg` por família de SO, consultados após o `PATH`.
23
+ * No Windows não há caminho canônico (builds estáticos BtbN são descompactados
24
+ * em qualquer lugar) — cobrimos os destinos mais comuns como best-effort.
25
+ */
26
+ const CONVENTIONAL = {
27
+ unix: ['/opt/homebrew/bin', '/usr/local/bin', '/usr/bin'],
28
+ windows: ['C:\\ffmpeg\\bin', 'C:\\Program Files\\ffmpeg\\bin'],
29
+ };
30
+ /** Graça de timeout ao ler a versão do ffmpeg (ms) — a chamada é instantânea. */
31
+ const VERSION_TIMEOUT_MS = 2_000;
32
+ /**
33
+ * Localiza o binário `ffmpeg` no `PATH` e em locais convencionais por plataforma.
34
+ * Função pura e reutilizável pelo `doctor` (#57). Não executa o binário.
35
+ *
36
+ * @param deps - Deps injetáveis (plataforma/PATH/executabilidade) — ver
37
+ * {@link findExecutable}. Default: ambiente real.
38
+ * @returns A localização encontrada, ou `undefined` se não instalado.
39
+ * @example
40
+ * const found = findFfmpeg();
41
+ * if (found === undefined) logger.warn('ffmpeg não instalado');
42
+ */
43
+ export function findFfmpeg(deps) {
44
+ const located = findExecutable([FFMPEG_BINARY], CONVENTIONAL, deps);
45
+ return located !== undefined ? { path: located.path } : undefined;
46
+ }
47
+ /**
48
+ * Localiza o binário `ffprobe` no `PATH` e em locais convencionais por plataforma
49
+ * (os mesmos do ffmpeg — o ffprobe vem no MESMO pacote). Função pura e injetável;
50
+ * não executa o binário. Degrada para `undefined` quando ausente (ADR-002): sem
51
+ * ffprobe, a #65 não bloqueia — apenas não mede a duração.
52
+ *
53
+ * @param deps - Deps injetáveis (plataforma/PATH/executabilidade) — ver
54
+ * {@link findExecutable}. Default: ambiente real.
55
+ * @returns A localização encontrada, ou `undefined` se não instalado.
56
+ * @example
57
+ * const probe = findFfprobe();
58
+ * if (probe === undefined) logger.warn('ffprobe não instalado; duração não medida');
59
+ */
60
+ export function findFfprobe(deps) {
61
+ const located = findExecutable([FFPROBE_BINARY], CONVENTIONAL, deps);
62
+ return located !== undefined ? { path: located.path } : undefined;
63
+ }
64
+ /**
65
+ * Monta o env MÍNIMO do subprocesso: só `PATH`. Nenhum segredo do pai (ex.:
66
+ * `REDMINE_API_KEY`) vaza para o ffmpeg (ADR-002).
67
+ *
68
+ * @returns Env sanitizado para o subprocesso.
69
+ */
70
+ function sanitizedEnv() {
71
+ return { PATH: process.env.PATH ?? '/usr/bin:/bin' };
72
+ }
73
+ /**
74
+ * Lê a versão do binário via `ffmpeg -version` (primeira linha:
75
+ * `ffmpeg version X.Y.Z ...` — em builds distro pode vir `n6.1.1` ou hash git;
76
+ * capturamos o primeiro token após `version`). Não lança: retorna `undefined`
77
+ * se o binário falhar ou a saída for inesperada.
78
+ *
79
+ * @param bin - Caminho absoluto do binário.
80
+ * @returns A versão detectada (ex.: `6.1.1`), ou `undefined`.
81
+ */
82
+ export function detectFfmpegVersion(bin) {
83
+ return new Promise((resolve) => {
84
+ execFile(bin, ['-version'], { env: sanitizedEnv(), encoding: 'utf8', windowsHide: true, timeout: VERSION_TIMEOUT_MS }, (error, stdout) => {
85
+ if (error !== null) {
86
+ resolve(undefined);
87
+ return;
88
+ }
89
+ // Reason: 1ª linha "ffmpeg version <token> ..."; o token pode ser semver
90
+ // puro, prefixado (`n6.1`) ou um hash — reportamos o token cru como veio.
91
+ const match = /ffmpeg version (\S+)/i.exec(stdout);
92
+ resolve(match?.[1]);
93
+ });
94
+ });
95
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Download do modelo GGUF do whisper.cpp com verificação de integridade
3
+ * (M4-02, #58, ADR-002).
4
+ *
5
+ * A transcrição local (ADR-002) exige um modelo `.gguf`, artefato independente do
6
+ * binário `whisper-cli` ({@link findWhisper}). Este módulo baixa esse modelo de
7
+ * forma AUDITÁVEL e SEGURA, respeitando a política híbrida de binários do ADR-002:
8
+ *
9
+ * - CONSTANTES PINADAS: URL HTTPS oficial ({@link GGUF_MODEL_URL}) e SHA-256
10
+ * esperado ({@link GGUF_MODEL_SHA256}) ficam no código, não em config remota — a
11
+ * integridade não depende de o servidor "dizer" qual é o hash certo.
12
+ * - SÓ HTTPS: qualquer URL `http://` é RECUSADA antes de tocar a rede (defesa
13
+ * contra downgrade/MITM em ambiente corporativo).
14
+ * - CHECKSUM OBRIGATÓRIO: o conteúdo é gravado num `.part`, seu SHA-256 é comparado
15
+ * ao pinado e só então renomeado para o destino; divergência → o `.part` é
16
+ * DESCARTADO e um erro claro (esperado vs. obtido) é lançado.
17
+ * - RETOMADA VIA HTTP RANGE (M4-03, #59): se um `.part` de N bytes já existe, o
18
+ * request pede `Range: bytes=N-` e ANEXA os bytes recebidos (`206 Partial Content`),
19
+ * evitando rebaixar do zero. O checksum é sempre do arquivo COMPLETO. Fallbacks
20
+ * graciosos: servidor que IGNORA o Range (`200 OK` com o corpo inteiro) recomeça
21
+ * do zero sem corromper; `.part` inservível (`416`) é descartado e o download
22
+ * reinicia limpo.
23
+ * - NUNCA EM MCP HEADLESS: o download é OPT-IN INTERATIVO (`--download-binaries`,
24
+ * ADR-002). Quando a chamada sinaliza ambiente MCP/headless ({@link DownloadGgufOptions.headless}),
25
+ * a função RECUSA antes de qualquer IO, com erro acionável — download silencioso
26
+ * de executáveis/modelos é vetor de supply chain.
27
+ *
28
+ * Todas as dependências de efeito colateral (fetch/fs) são INJETÁVEIS
29
+ * ({@link GgufDeps}) para tornar os testes herméticos — sem rede nem FS real.
30
+ */
31
+ /**
32
+ * Modelo GGUF default: `ggml-tiny` (multilíngue, ~78 MB) do repositório oficial
33
+ * `ggerganov/whisper.cpp` no Hugging Face. Escolhido por ser o MENOR modelo
34
+ * multilíngue oficial — mantém o auto-detect de idioma do ADR-002 com o menor
35
+ * custo de download para o primeiro contato. Modelos maiores (base/small) são
36
+ * evolução futura configurável; a MECÂNICA de download+verificação é idêntica.
37
+ */
38
+ export declare const GGUF_MODEL_NAME: "ggml-tiny.bin";
39
+ /** URL HTTPS oficial e fixa do {@link GGUF_MODEL_NAME} (Hugging Face resolve). */
40
+ export declare const GGUF_MODEL_URL: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin";
41
+ /**
42
+ * SHA-256 hex esperado do {@link GGUF_MODEL_NAME}. É a ÂNCORA de integridade:
43
+ * o arquivo baixado só é aceito se seu hash bater exatamente com esta constante.
44
+ */
45
+ export declare const GGUF_MODEL_SHA256: "be07e048e1e599ad46341c8d2a135645097a538221678b7acdd1b1919c6e1b21";
46
+ /** Categorias de falha do download — discriminante acionável do {@link GgufDownloadError}. */
47
+ export type GgufDownloadFailure = 'headless' | 'insecure-url' | 'http-error' | 'checksum-mismatch';
48
+ /**
49
+ * Erro tipado de qualquer recusa/falha do download do modelo GGUF. O campo
50
+ * {@link GgufDownloadError.code} permite ao chamador (CLI/doctor) reagir de forma
51
+ * específica sem casar strings de mensagem.
52
+ */
53
+ export declare class GgufDownloadError extends Error {
54
+ /** Categoria da falha — ver {@link GgufDownloadFailure}. */
55
+ readonly code: GgufDownloadFailure;
56
+ constructor(code: GgufDownloadFailure, message: string);
57
+ }
58
+ /** Opções de rede repassadas ao `fetch` — hoje apenas cabeçalhos (ex.: `Range`). */
59
+ export interface GgufFetchInit {
60
+ /** Cabeçalhos HTTP a enviar (ex.: `{ Range: 'bytes=1024-' }`). */
61
+ readonly headers?: Record<string, string>;
62
+ }
63
+ /**
64
+ * Dependências injetáveis de efeito colateral (rede + filesystem). O default
65
+ * ({@link defaultGgufDeps}) usa `fetch` global e `node:fs/promises`; os testes
66
+ * injetam spies para permanecerem herméticos.
67
+ */
68
+ export interface GgufDeps {
69
+ /**
70
+ * Busca a URL (com cabeçalhos opcionais, ex.: `Range`) e devolve a resposta —
71
+ * wrapper fino sobre o `fetch` global.
72
+ */
73
+ readonly fetch: (url: string, init?: GgufFetchInit) => Promise<Response>;
74
+ /** Cria o diretório de destino recursivamente (idempotente). */
75
+ readonly mkdir: (dir: string) => Promise<void>;
76
+ /** Grava (truncando) os bytes baixados num caminho (o `.part`) — download novo. */
77
+ readonly writeFile: (path: string, data: Uint8Array) => Promise<void>;
78
+ /** ANEXA bytes ao fim do `.part` existente — usado na retomada via Range. */
79
+ readonly appendFile: (path: string, data: Uint8Array) => Promise<void>;
80
+ /** Lê o conteúdo atual do `.part` (para compor o checksum do arquivo completo). */
81
+ readonly readPart: (path: string) => Promise<Uint8Array>;
82
+ /**
83
+ * Tamanho, em bytes, de um `.part` já existente; `undefined` se ele não existe —
84
+ * é o ponto de retomada (`Range: bytes=<tamanho>-`).
85
+ */
86
+ readonly statPart: (path: string) => Promise<number | undefined>;
87
+ /** Renomeia o `.part` verificado para o destino final. */
88
+ readonly rename: (from: string, to: string) => Promise<void>;
89
+ /** Remove um caminho (o `.part` descartado), sem lançar se ausente. */
90
+ readonly rm: (path: string) => Promise<void>;
91
+ }
92
+ /** Opções de {@link downloadGgufModel}. Tudo tem default seguro para produção. */
93
+ export interface DownloadGgufOptions {
94
+ /** Diretório de destino do modelo. Default: {@link whisperModelDir}. */
95
+ readonly destDir?: string;
96
+ /**
97
+ * Indica ambiente MCP/headless. Quando `true`, o download é RECUSADO (opt-in
98
+ * interativo, ADR-002). Default: `false`.
99
+ */
100
+ readonly headless?: boolean;
101
+ /** URL de origem (HTTPS). Default: {@link GGUF_MODEL_URL}. */
102
+ readonly url?: string;
103
+ /** SHA-256 hex esperado. Default: {@link GGUF_MODEL_SHA256}. */
104
+ readonly sha256?: string;
105
+ /** Dependências injetáveis (fetch/fs). Default: {@link defaultGgufDeps}. */
106
+ readonly deps?: GgufDeps;
107
+ }
108
+ /**
109
+ * Constrói as dependências reais de produção: `fetch` global e `node:fs/promises`
110
+ * (`mkdir` recursivo, `rm` com `force`). Isolado para manter {@link downloadGgufModel}
111
+ * testável sem tocar rede/FS.
112
+ *
113
+ * @returns Deps ligadas ao ambiente real.
114
+ */
115
+ export declare function defaultGgufDeps(): GgufDeps;
116
+ /**
117
+ * Baixa o modelo GGUF do whisper.cpp e o grava no cache local, verificando o
118
+ * SHA-256 pinado antes de considerá-lo válido (ADR-002, M4-02).
119
+ *
120
+ * Ordem dos portões (todos exercitados por testes): (1) guard headless — recusa
121
+ * antes de qualquer IO; (2) só HTTPS — recusa `http://` sem tocar a rede; (3)
122
+ * RETOMADA — se um `.part` de N bytes já existe, pede `Range: bytes=N-` e ANEXA o
123
+ * `206`; se o servidor ignora o Range (`200`) recomeça do zero, e um `.part`
124
+ * inservível (`416`) é descartado antes de reiniciar limpo; (4) compara o SHA-256
125
+ * do arquivo COMPLETO ao esperado — se bater, renomeia atomicamente para o destino
126
+ * e devolve o caminho; se não, DESCARTA o `.part` e lança {@link GgufDownloadError}
127
+ * com esperado vs. obtido.
128
+ *
129
+ * @param options - Destino, flag headless, origem/checksum e deps — ver {@link DownloadGgufOptions}.
130
+ * @returns Caminho absoluto do modelo `.gguf`/`.bin` gravado e verificado.
131
+ * @throws {GgufDownloadError} `headless` (ambiente MCP), `insecure-url` (não-HTTPS),
132
+ * `http-error` (resposta não-2xx) ou `checksum-mismatch` (integridade divergente).
133
+ * @example
134
+ * const modelPath = await downloadGgufModel({ headless: false });
135
+ * logger.info(`modelo pronto em ${modelPath}`);
136
+ */
137
+ export declare function downloadGgufModel(options?: DownloadGgufOptions): Promise<string>;