ffai-cli 0.2.0__tar.gz

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 (41) hide show
  1. ffai_cli-0.2.0/LICENSE +21 -0
  2. ffai_cli-0.2.0/PKG-INFO +620 -0
  3. ffai_cli-0.2.0/README.md +585 -0
  4. ffai_cli-0.2.0/ffai/__init__.py +5 -0
  5. ffai_cli-0.2.0/ffai/__main__.py +5 -0
  6. ffai_cli-0.2.0/ffai/cli.py +97 -0
  7. ffai_cli-0.2.0/ffai/command_builder.py +287 -0
  8. ffai_cli-0.2.0/ffai/config.py +251 -0
  9. ffai_cli-0.2.0/ffai/errors.py +31 -0
  10. ffai_cli-0.2.0/ffai/executor.py +255 -0
  11. ffai_cli-0.2.0/ffai/i18n.py +400 -0
  12. ffai_cli-0.2.0/ffai/llm.py +181 -0
  13. ffai_cli-0.2.0/ffai/models.py +454 -0
  14. ffai_cli-0.2.0/ffai/pandoc_builder.py +254 -0
  15. ffai_cli-0.2.0/ffai/parser.py +112 -0
  16. ffai_cli-0.2.0/ffai/prompt.py +296 -0
  17. ffai_cli-0.2.0/ffai/providers.py +122 -0
  18. ffai_cli-0.2.0/ffai/repl.py +933 -0
  19. ffai_cli-0.2.0/ffai/state.py +291 -0
  20. ffai_cli-0.2.0/ffai/tools.py +177 -0
  21. ffai_cli-0.2.0/ffai/utils.py +91 -0
  22. ffai_cli-0.2.0/ffai_cli.egg-info/PKG-INFO +620 -0
  23. ffai_cli-0.2.0/ffai_cli.egg-info/SOURCES.txt +39 -0
  24. ffai_cli-0.2.0/ffai_cli.egg-info/dependency_links.txt +1 -0
  25. ffai_cli-0.2.0/ffai_cli.egg-info/entry_points.txt +2 -0
  26. ffai_cli-0.2.0/ffai_cli.egg-info/requires.txt +9 -0
  27. ffai_cli-0.2.0/ffai_cli.egg-info/top_level.txt +1 -0
  28. ffai_cli-0.2.0/pyproject.toml +63 -0
  29. ffai_cli-0.2.0/setup.cfg +4 -0
  30. ffai_cli-0.2.0/tests/test_cli.py +236 -0
  31. ffai_cli-0.2.0/tests/test_command_builder.py +284 -0
  32. ffai_cli-0.2.0/tests/test_config.py +123 -0
  33. ffai_cli-0.2.0/tests/test_executor.py +157 -0
  34. ffai_cli-0.2.0/tests/test_i18n.py +70 -0
  35. ffai_cli-0.2.0/tests/test_integration.py +197 -0
  36. ffai_cli-0.2.0/tests/test_llm.py +339 -0
  37. ffai_cli-0.2.0/tests/test_pandoc_builder.py +344 -0
  38. ffai_cli-0.2.0/tests/test_parser.py +121 -0
  39. ffai_cli-0.2.0/tests/test_providers.py +69 -0
  40. ffai_cli-0.2.0/tests/test_state.py +326 -0
  41. ffai_cli-0.2.0/tests/test_tools.py +65 -0
ffai_cli-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ffai contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,620 @@
1
+ Metadata-Version: 2.4
2
+ Name: ffai-cli
3
+ Version: 0.2.0
4
+ Summary: Natural-language, stateful media and document format conversion assistant (FFmpeg + Pandoc)
5
+ Author: ffai contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Lixuannan/ffai
8
+ Project-URL: Repository, https://github.com/Lixuannan/ffai
9
+ Project-URL: Issues, https://github.com/Lixuannan/ffai/issues
10
+ Keywords: ffmpeg,pandoc,cli,llm,video,document,conversion,openai
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Natural Language :: English
15
+ Classifier: Natural Language :: Chinese (Simplified)
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Multimedia :: Video
21
+ Classifier: Topic :: Text Processing
22
+ Classifier: Topic :: Text Processing :: Markup
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: openai<3,>=1.40.0
27
+ Requires-Dist: pydantic<3,>=2.7
28
+ Requires-Dist: python-dotenv<2,>=1.0
29
+ Requires-Dist: rich<15,>=13.7
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest<10,>=8.0; extra == "dev"
32
+ Requires-Dist: pytest-cov<8,>=5.0; extra == "dev"
33
+ Requires-Dist: ruff<1,>=0.6; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # ffai
37
+
38
+ English | [简体中文](README.zh-CN.md)
39
+
40
+ `ffai` is an interactive, stateful natural-language format conversion assistant.
41
+ Describe a media operation or a document conversion, a language model updates a
42
+ structured task, and ffai builds the actual command locally with **FFmpeg**
43
+ (media files) or **Pandoc** (documents, ebooks, markup, and PDF). Nothing is
44
+ executed until you review the complete command and confirm it.
45
+
46
+ ```text
47
+ ffai v0.2.0
48
+ Natural-language interface for media and document conversion
49
+
50
+ ffai> add notes.md
51
+ ✓ Added input: /home/you/notes.md
52
+ Detected Pandoc from the input file type; the tool was selected automatically.
53
+ ffai> setoutput notes.docx
54
+ ✓ Output set: /home/you/notes.docx
55
+ ffai> convert to a Word document with a table of contents
56
+ ╭─ ffai ──────────────────────────────────────────────────────────╮
57
+ │ Enabling a table of contents for the Word output. │
58
+ ╰─────────────────────────────────────────────────────────────────╯
59
+ ffai> preview
60
+ ╭─ Preview not executed ──────────────────────────────────────────╮
61
+ │ pandoc --toc -o /home/you/notes.docx /home/you/notes.md │
62
+ ╰─────────────────────────────────────────────────────────────────╯
63
+ ffai> exec
64
+ ```
65
+
66
+ ## Features
67
+
68
+ - Two external tools behind one REPL: FFmpeg for media and Pandoc for documents.
69
+ - Extension-based tool routing when you add files, plus an explicit
70
+ `tool <ffmpeg|pandoc>` command to switch at any time.
71
+ - Each tool keeps its own task, so switching preserves the other tool's work;
72
+ `reset` clears only the active task.
73
+ - Incremental natural-language edits; existing fields are preserved unless the
74
+ request changes them.
75
+ - Structured LLM output validated by Pydantic. A response is a tool-scoped
76
+ patch, and the model never emits a shell command.
77
+ - Safe command construction from an argv list, with no `shell=True`.
78
+ - FFmpeg: multi-input support with explicit stream maps and `filter_complex`.
79
+ - Pandoc: table of contents, section numbering, metadata, templates, reference
80
+ documents, citeproc with bibliographies, PDF engines, syntax highlighting,
81
+ and embedded resources.
82
+ - Rich task panels, syntax-highlighted command previews, confirmation prompts,
83
+ and live FFmpeg progress.
84
+ - Provider presets for OpenAI, OpenRouter, DeepSeek, GLM, Kimi, and Qwen, plus
85
+ a custom OpenAI-compatible endpoint.
86
+ - First-run API setup wizard; `.env` settings are written with owner-only
87
+ permissions. The `config` command displays or updates the active settings.
88
+ - English and Simplified Chinese UI with locale detection and an explicit
89
+ language override. Natural-language replies follow the user's language.
90
+
91
+ ## Requirements
92
+
93
+ - Python 3.11 or newer
94
+ - FFmpeg and/or Pandoc on `PATH`. Both are supported; install at least the tool
95
+ you intend to use.
96
+ - An OpenAI-compatible API key and model. ffai requires this configuration
97
+ before the REPL starts.
98
+ - `ffprobe` on `PATH` for an estimated FFmpeg progress percentage (optional;
99
+ execution and live elapsed time still work without it)
100
+ - A LaTeX or Typst engine only for Pandoc PDF output (for example Tectonic or a
101
+ TeX Live distribution). Non-PDF document conversions do not need one.
102
+
103
+ ## Installation
104
+
105
+ Install from PyPI:
106
+
107
+ ```bash
108
+ pip install ffai-cli
109
+ ```
110
+
111
+ For an isolated command install:
112
+
113
+ ```bash
114
+ pipx install ffai-cli
115
+ ```
116
+
117
+ The distribution is named `ffai-cli` on PyPI; the Python package and the
118
+ `ffai` command keep the shorter name.
119
+
120
+ Verify the entry point:
121
+
122
+ ```bash
123
+ ffai --version
124
+ ```
125
+
126
+ ### From source
127
+
128
+ ```bash
129
+ git clone https://github.com/Lixuannan/ffai.git
130
+ cd ffai
131
+ python -m venv .venv
132
+ source .venv/bin/activate
133
+ pip install -e .
134
+ ```
135
+
136
+ ### FFmpeg
137
+
138
+ Install FFmpeg with the package manager you use, or download a build from
139
+ https://ffmpeg.org/download.html:
140
+
141
+ ```bash
142
+ # macOS
143
+ brew install ffmpeg
144
+
145
+ # Debian / Ubuntu
146
+ sudo apt install ffmpeg
147
+
148
+ # Windows (Chocolatey)
149
+ choco install ffmpeg
150
+ ```
151
+
152
+ ### Pandoc
153
+
154
+ ```bash
155
+ # macOS
156
+ brew install pandoc
157
+
158
+ # Debian / Ubuntu
159
+ sudo apt install pandoc
160
+
161
+ # Windows (Chocolatey)
162
+ choco install pandoc
163
+ ```
164
+
165
+ You can also download an installer for your platform from
166
+ https://pandoc.org/installing.html. Verify with `pandoc --version`.
167
+
168
+ ### PDF engine for Pandoc
169
+
170
+ Pandoc writes PDF by invoking an external engine. Install one if you want PDF
171
+ output:
172
+
173
+ ```bash
174
+ # Tectonic (standalone, downloads packages on demand)
175
+ brew install tectonic
176
+ # or
177
+ cargo install tectonic
178
+ ```
179
+
180
+ A TeX Live installation that provides `xelatex`, `lualatex`, `pdflatex`, or
181
+ `latexmk` also works. A Typst installation can be selected as the PDF engine
182
+ instead of a LaTeX engine. ffai restricts `pdf_engine` to the engines supported
183
+ by the command builder (see
184
+ [Command construction and safety](#command-construction-and-safety)).
185
+
186
+ ## Quick start
187
+
188
+ ### FFmpeg: crop and encode a video
189
+
190
+ ```text
191
+ ffai> add input.mp4
192
+ ✓ Added input: /media/input.mp4
193
+ ffai> setoutput final.mp4
194
+ ✓ Output set: /media/final.mp4
195
+ ffai> crop the video to 1920x1080, keeping the center framing
196
+ ╭─ ffai ──────────────────────────────────────────────────────────╮
197
+ │ Updating the video filter chain to scale and center-crop. │
198
+ ╰─────────────────────────────────────────────────────────────────╯
199
+ ffai> use H.265 with CRF 23
200
+ ...
201
+ ffai> preview
202
+ ╭─ Preview not executed ──────────────────────────────────────────╮
203
+ │ ffmpeg -hide_banner -nostdin -y -i /media/input.mp4 \ │
204
+ │ -vf 'scale=1920:1080:force_original_aspect_ratio=increase' │
205
+ │ -c:v libx265 -crf 23 /media/final.mp4 │
206
+ ╰─────────────────────────────────────────────────────────────────╯
207
+ ffai> exec
208
+ ```
209
+
210
+ `exec` asks for confirmation before FFmpeg runs.
211
+
212
+ ### Pandoc: Markdown to Word with a table of contents
213
+
214
+ ```text
215
+ ffai> add notes.md
216
+ ✓ Added input: /home/you/notes.md
217
+ Detected Pandoc from the input file type; the tool was selected automatically.
218
+ ffai> setoutput notes.docx
219
+ ✓ Output set: /home/you/notes.docx
220
+ ffai> convert to a Word document with a table of contents
221
+ ffai> preview
222
+ ╭─ Preview not executed ──────────────────────────────────────────╮
223
+ │ pandoc --toc -o /home/you/notes.docx /home/you/notes.md │
224
+ ╰─────────────────────────────────────────────────────────────────╯
225
+ ffai> exec
226
+ ```
227
+
228
+ ### Pandoc: Markdown to PDF with xelatex
229
+
230
+ ```text
231
+ ffai> add report.md
232
+ ✓ Added input: /home/you/report.md
233
+ ffai> setoutput report.pdf
234
+ ✓ Output set: /home/you/report.pdf
235
+ ffai> export to PDF with a table of contents using xelatex
236
+ ffai> preview
237
+ ╭─ Preview not executed ──────────────────────────────────────────╮
238
+ │ pandoc --toc --pdf-engine xelatex \ │
239
+ │ -o /home/you/report.pdf /home/you/report.md │
240
+ ╰─────────────────────────────────────────────────────────────────╯
241
+ ffai> exec
242
+ ```
243
+
244
+ Pandoc infers the input and output formats from the file extensions; ffai adds
245
+ `-f`/`-t` only when you set an explicit format. PDF output requires the selected
246
+ PDF engine to be installed.
247
+
248
+ ### Switching tools manually
249
+
250
+ ```text
251
+ ffai> tool pandoc
252
+ ✓ Conversion tool set to Pandoc.
253
+ ffai> tool ffmpeg
254
+ ✓ Conversion tool set to FFmpeg.
255
+ ```
256
+
257
+ Switching never discards the other tool's task. `show`, `preview`, `exec`, and
258
+ `reset` act on the active tool.
259
+
260
+ ## Built-in commands
261
+
262
+ | Command | Action |
263
+ | --- | --- |
264
+ | `add <file> [file ...]` | Add existing, readable input files to the active task. Duplicates are ignored. When the active task is still empty, recognized extensions can select the tool. |
265
+ | `setoutput <file>` | Set an output whose parent directory exists. The output cannot overwrite an input. |
266
+ | `tool <ffmpeg\|pandoc>` | Switch the active tool. Each tool keeps its own inputs, output, and fields. |
267
+ | `show` | Display the active task, including inputs, output, and the tool-specific fields. |
268
+ | `preview` | Build and display the complete command without executing it. |
269
+ | `exec` | Display the command and ask for confirmation before running it. |
270
+ | `reset` | Clear the active tool's task only; the other tool's task is kept. |
271
+ | `config`, `config show` | Display the active API configuration with the key masked. |
272
+ | `config set` | Interactively update API settings and save them to `.env`. |
273
+ | `help` | Show command help. |
274
+ | `exit`, `quit` | Exit the REPL. |
275
+
276
+ Paths use shell-like quoting, so spaces in filenames work:
277
+
278
+ ```text
279
+ add "my input video.mp4"
280
+ setoutput "renders/final cut.mp4"
281
+ ```
282
+
283
+ Exact Chinese aliases are also accepted: `预览`, `显示`, `执行`, `重置`,
284
+ `配置`, `工具`, `帮助`, `退出`.
285
+
286
+ Any unrecognized input is sent to the LLM. For example:
287
+
288
+ ```text
289
+ convert to 1080p
290
+ use H.265 at CRF 23
291
+ audio AAC at 192k
292
+ crop to 1920x1080, center composition
293
+ remove all video filters
294
+ convert the document to EPUB
295
+ add a table of contents and number the sections
296
+ export to PDF with xelatex
297
+ use the template letter.template
298
+ ```
299
+
300
+ Only the task's input files and output path are controlled by `add` and
301
+ `setoutput`; the LLM cannot change them. Auxiliary Pandoc paths such as
302
+ `template`, `reference_doc`, `bibliography`, `csl`, and `resource_path` can be
303
+ set through natural language and are validated against the local filesystem.
304
+
305
+ ## How tool routing works
306
+
307
+ - Adding files to an empty task (no inputs and no output yet) inspects their
308
+ extensions. If all recognized extensions belong to one tool, that tool becomes
309
+ active. The REPL announces the detected tool.
310
+ - If the extensions are mixed across tools, unknown, or the active task already
311
+ has inputs or an output, the current tool is kept.
312
+ - `tool ffmpeg` and `tool pandoc` switch manually at any time. `add` only
313
+ auto-selects a tool while the active task is still empty (no inputs and no
314
+ output); after that it keeps the current tool.
315
+ - Each tool has its own task. `show`, `preview`, and `exec` use the active task;
316
+ the other tool's task is preserved until you reset it.
317
+
318
+ Representative extension routing (the complete sets live in `ffai/tools.py`):
319
+
320
+ | Tool | Example extensions |
321
+ | --- | --- |
322
+ | FFmpeg | `.mp4`, `.mkv`, `.mov`, `.avi`, `.webm`, `.ts`, `.mp3`, `.m4a`, `.wav`, `.flac`, `.ogg`, `.opus`, `.jpg`, `.png`, `.gif`, `.webp` |
323
+ | Pandoc | `.md`, `.markdown`, `.txt`, `.docx`, `.odt`, `.html`, `.epub`, `.tex`, `.rst`, `.org`, `.adoc`, `.ipynb`, `.pptx` |
324
+
325
+ Unknown extensions keep the current tool. Use `tool` when a file type is not
326
+ recognized.
327
+
328
+ ## Pandoc task fields
329
+
330
+ Natural language fills these fields. `from_format` and `to_format` are only
331
+ emitted when set; otherwise Pandoc infers the formats from file extensions.
332
+
333
+ | Field | Meaning |
334
+ | --- | --- |
335
+ | `from_format`, `to_format` | Explicit Pandoc reader/writer format names, for example `gfm` or `docx`. |
336
+ | `standalone` | Produce a standalone document. |
337
+ | `toc`, `toc_depth` | Add a table of contents, optionally with a depth from 1 to 6. |
338
+ | `number_sections` | Number section headings. |
339
+ | `shift_heading_level_by` | Shift heading levels by an offset from -10 to 10. |
340
+ | `metadata` | Repeatable `key=value` metadata entries. |
341
+ | `template` | Local template file used for the output. |
342
+ | `reference_doc` | Local reference document used for styling. |
343
+ | `pdf_engine` | PDF engine; restricted to the allowlist below. |
344
+ | `highlight_style` | Syntax-highlighting theme; restricted to the allowlist below. |
345
+ | `embed_resources` | Embed images and other resources into the output. |
346
+ | `citeproc` | Run Pandoc's citation processor. |
347
+ | `bibliography` | One or more local bibliography files. |
348
+ | `csl` | Local CSL citation style file. |
349
+ | `resource_path` | One or more local directories searched for resources. |
350
+ | `extra_options` | Allowlisted Pandoc options with a fixed number of values. |
351
+ | `description` | Short summary of the conversion. |
352
+
353
+ `pdf_engine` accepts `pdflatex`, `xelatex`, `lualatex`, `latexmk`, `tectonic`,
354
+ `typst`, `context`, `wkhtmltopdf`, `weasyprint`, `prince`, or `pdfroff`.
355
+ `highlight_style` accepts `pygments`, `tango`, `espresso`, `zenburn`, `kate`,
356
+ `monochrome`, `breezedark`, or `haddock`.
357
+
358
+ ## Supported formats
359
+
360
+ - **FFmpeg** handles the media containers, codecs, filters, and image formats
361
+ supported by your FFmpeg build. Common examples include MP4, MKV, MOV, AVI,
362
+ WebM, MP3, AAC, WAV, FLAC, Ogg/Opus, GIF, and JPEG/PNG/WebP images.
363
+ - **Pandoc** reads and writes the document formats supported by your Pandoc
364
+ version, including Markdown variants, HTML, EPUB, DOCX, ODT, RTF, LaTeX,
365
+ reStructuredText, Org, AsciiDoc, MediaWiki, Textile, Jupyter notebooks,
366
+ OPML, and PPTX output. PDF output is produced through an external PDF engine.
367
+
368
+ In both cases the installed tool is the authority on format availability; ffai
369
+ passes through what the task expresses and rejects values that violate its
370
+ validation rules.
371
+
372
+ ## Configuration
373
+
374
+ API configuration is mandatory. On first launch, ffai requires a complete
375
+ configuration and opens an interactive setup wizard before showing the
376
+ `ffai>` prompt. Non-interactive launches without configuration exit with code
377
+ 2. Configuration can also be provided through `.env` or the process
378
+ environment:
379
+
380
+ ```bash
381
+ cp .env.example .env
382
+ ```
383
+
384
+ The three settings are (`.env` and the process environment are both read;
385
+ process environment values take precedence over `.env`):
386
+
387
+ | Variable | Required for LLM | Meaning |
388
+ | --- | --- | --- |
389
+ | `OPENAI_API_KEY` | yes | API key |
390
+ | `OPENAI_MODEL` | yes | Chat model identifier |
391
+ | `OPENAI_BASE_URL` | no | OpenAI-compatible endpoint; omit for the OpenAI default |
392
+
393
+ ### Provider presets
394
+
395
+ The setup wizard and `config set` offer these presets. The provider's base URL
396
+ is filled in automatically; the model is suggested but can be edited because
397
+ availability varies by account and region. Every provider stores its key in
398
+ `OPENAI_API_KEY`.
399
+
400
+ | Provider | Base URL | Suggested model | Documentation |
401
+ | --- | --- | --- | --- |
402
+ | OpenAI | `https://api.openai.com/v1` | `gpt-5.4-mini` | [OpenAI GPT-5.4 mini](https://developers.openai.com/api/docs/models/gpt-5.4-mini) |
403
+ | OpenRouter | `https://openrouter.ai/api/v1` | `openai/gpt-5.4-mini` | [OpenRouter quickstart](https://openrouter.ai/docs/quickstart) |
404
+ | DeepSeek | `https://api.deepseek.com` | `deepseek-flash` | [DeepSeek API docs](https://api-docs.deepseek.com/) |
405
+ | GLM (Zhipu) | `https://open.bigmodel.cn/api/paas/v4` | `glm-5.3` | [GLM-5.3 docs](https://docs.bigmodel.cn/cn/guide/models/text/glm-5.3) |
406
+ | Kimi (Moonshot) | `https://api.moonshot.cn/v1` | `kimi-k3.1-pro` | [Kimi API docs](https://platform.kimi.com/docs/get-api-key) |
407
+ | Qwen (DashScope) | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `qwen3.8-flash` | [DashScope base URLs](https://www.alibabacloud.com/help/zh/model-studio/base-url) |
408
+ | Custom | Enter manually | Enter manually | Any OpenAI-compatible endpoint |
409
+
410
+ Select a provider by its list number or ID (`openai`, `openrouter`, `deepseek`,
411
+ `glm`, `kimi`, `qwen`, `custom`). OpenRouter model IDs are vendor-prefixed.
412
+ The Qwen preset uses the China-region endpoint; its API key must match the
413
+ selected region, so use a regional compatible-mode URL for other regions.
414
+
415
+ OpenAI environment example:
416
+
417
+ ```dotenv
418
+ OPENAI_API_KEY=sk-your-key
419
+ OPENAI_MODEL=gpt-5.4-mini
420
+ # OPENAI_BASE_URL=https://api.openai.com/v1
421
+ ```
422
+
423
+ DeepSeek environment example:
424
+
425
+ ```dotenv
426
+ OPENAI_API_KEY=sk-your-deepseek-key
427
+ OPENAI_BASE_URL=https://api.deepseek.com
428
+ OPENAI_MODEL=deepseek-flash
429
+ ```
430
+
431
+ The placeholder values from `.env.example` are treated as unset, so copying
432
+ the example file still triggers setup. The wizard saves settings to `.env` in
433
+ the current directory with mode `0600`. It never echoes the API key and
434
+ displays only a masked form. Use the REPL command `config` to show the active
435
+ configuration and `config set` to change it. When `config set` prompts, Enter
436
+ keeps a current value; choose another provider by number or ID to switch.
437
+ Ctrl+C cancels without writing anything.
438
+
439
+ Start ffai from the project directory (or any directory with the same exported
440
+ variables).
441
+
442
+ ### UI language
443
+
444
+ The interface auto-detects Chinese from `LC_ALL`, `LC_MESSAGES`, or `LANG`;
445
+ otherwise it uses English. Override the choice with `FFAI_LANG=en` /
446
+ `FFAI_LANG=zh` (process environment or `.env`) or the `--lang en` / `--lang zh`
447
+ flag. LLM replies follow the language used in your request.
448
+
449
+ ## Architecture
450
+
451
+ ```text
452
+ user input
453
+ │
454
+ ├── built-in command ───────────────► SessionState (local validation)
455
+ │
456
+ └── natural language
457
+ │
458
+ ▼
459
+ OpenAI-compatible LLM
460
+ │ JSON: intent + tool-scoped patch (ffmpeg | pandoc)
461
+ ▼
462
+ Pydantic validation ──► SessionState.apply_patch
463
+ │
464
+ ▼
465
+ tool builder (command_builder | pandoc_builder) argv list, no shell
466
+ │
467
+ ▼
468
+ preview ──► user confirmation ──► executor (subprocess, live FFmpeg progress)
469
+ ```
470
+
471
+ The task and patch schemas are defined in `ffai/models.py`. Every patch
472
+ targets exactly one tool:
473
+
474
+ ```python
475
+ class ToolPatch(BaseModel):
476
+ ffmpeg: FFmpegPatch | None = None
477
+ pandoc: PandocPatch | None = None
478
+ # Exactly one tool; omitted fields are unchanged, explicit null clears.
479
+ ```
480
+
481
+ The model returns one of three intents:
482
+
483
+ - `apply`: apply a non-empty patch for exactly one tool (an empty tool payload
484
+ only selects that tool).
485
+ - `clarify`: ask one focused question; no task mutation.
486
+ - `noop`: explain without mutating state.
487
+
488
+ The full model result also includes `explanation` and an optional `question`.
489
+ `inputs` and `output` are not patchable. Each tool keeps its own task in
490
+ `SessionState`, so patches and switches never merge fields across tools.
491
+
492
+ ### Modules
493
+
494
+ | Module | Responsibility |
495
+ | --- | --- |
496
+ | `ffai/cli.py` | Argparse entry point and top-level error handling |
497
+ | `ffai/repl.py` | REPL loop, dispatch, Rich presentation, confirmation |
498
+ | `ffai/parser.py` | Built-in command parsing versus natural language |
499
+ | `ffai/models.py` | Tool-scoped task, patch, and LLM decision schemas |
500
+ | `ffai/tools.py` | Tool registry, binaries, and extension-based routing |
501
+ | `ffai/state.py` | Per-tool session state and filesystem-validated changes |
502
+ | `ffai/prompt.py` | Two-tool system prompt and current-state context |
503
+ | `ffai/llm.py` | OpenAI-compatible client, JSON extraction, validation retry |
504
+ | `ffai/command_builder.py` | FFmpeg task-to-argv conversion and extra-option allowlist |
505
+ | `ffai/pandoc_builder.py` | Pandoc task-to-argv conversion, path checks, option allowlist |
506
+ | `ffai/executor.py` | Generic subprocess execution, progress parsing, interrupts |
507
+ | `ffai/config.py` | `.env`/environment configuration and safe persistence |
508
+ | `ffai/utils.py` | Command rendering, duration probing, environment scrubbing |
509
+ | `ffai/i18n.py` | English and Simplified Chinese UI strings and language detection |
510
+
511
+ ## Command construction and safety
512
+
513
+ Shared guarantees:
514
+
515
+ - The LLM output is limited to the `ToolPatch` schema. Unknown fields are
516
+ rejected, and no model-generated shell or Python code is interpreted.
517
+ - Commands are invoked with an argument vector and `shell=False`.
518
+ - Inputs and outputs are normalized local paths and checked for existence,
519
+ readability, parent-directory validity, and accidental overwrite of an input.
520
+ - Execution always displays the complete argv-rendered command and asks for
521
+ confirmation. Existing outputs are flagged before confirmation.
522
+ - The tool runs with stdin closed and with an environment scrubbed of variables
523
+ whose names contain `KEY`, `SECRET`, `TOKEN`, `PASSWORD`, `PASSWD`, or
524
+ `CREDENTIAL`.
525
+ - Ctrl+C terminates the tool's process group, escalating from SIGTERM to SIGKILL
526
+ after a timeout, and reports the run as interrupted.
527
+ - A successful exit is verified against the expected output file; a zero exit
528
+ with a missing or empty output is reported as a failure.
529
+
530
+ FFmpeg-specific rules:
531
+
532
+ - Multiple inputs require explicit `maps` or `filter_complex`; inputs are never
533
+ implicitly concatenated.
534
+ - `filter_complex` cannot be combined with the simple video/audio filter chains.
535
+ - Stream-copy mode rejects filters, CRF, preset, or bitrates that would be
536
+ ignored or invalid.
537
+ - `extra_options` accepts only a conservative allowlist of common output
538
+ options with known arity. Options that set codecs, filters, maps, overwrite
539
+ behavior, or side files are rejected. The allowlist is defined in
540
+ `ffai/command_builder.py` and embedded in the LLM prompt.
541
+
542
+ Pandoc-specific rules:
543
+
544
+ - `--filter`, `--lua-filter`, `--pdf-engine-opt`, and `--data-dir` are not
545
+ expressible: no Pandoc option can execute an arbitrary program or add an
546
+ arbitrary engine argument.
547
+ - `pdf_engine` is restricted to the allowlist shown above; arbitrary executable
548
+ paths are rejected.
549
+ - `highlight_style` is restricted to the documented allowlist.
550
+ - `template`, `reference_doc`, `bibliography`, and `csl` must point at existing,
551
+ readable local files; `resource_path` entries must be existing local
552
+ directories.
553
+ - `extra_options` accepts only a small allowlist of Pandoc options with fixed
554
+ arity. Shell fragments and options outside the allowlist are rejected.
555
+ - `from_format` and `to_format` are only passed as `-f`/`-t` when explicitly
556
+ set, and are validated as single format-name tokens.
557
+ - `metadata` entries are passed as one `-M key=value` argv pair each.
558
+
559
+ It remains your responsibility to confirm the visible command and output path.
560
+ Both FFmpeg and Pandoc are powerful tools; the confirmation screen is the final
561
+ authorization step.
562
+
563
+ ## Troubleshooting
564
+
565
+ | Symptom | Fix |
566
+ | --- | --- |
567
+ | `FFmpeg was not found on PATH` | Install FFmpeg (see [Installation](#installation)), open a new terminal, and check `ffmpeg -version`. |
568
+ | `Pandoc was not found on PATH` | Install Pandoc (see [Installation](#installation)), open a new terminal, and check `pandoc --version`. |
569
+ | PDF conversion fails with a missing engine such as `pdflatex` | Install a PDF engine (Tectonic, TeX Live, or Typst) and make sure it is on `PATH`. Select the engine you installed in your request, for example "export to PDF with xelatex" or "use tectonic". |
570
+ | Pandoc exits with an unknown format error | Pass a format that your Pandoc version supports; check `pandoc --list-input-formats` and `pandoc --list-output-formats`. |
571
+ | No progress percentage for FFmpeg | Install `ffprobe`; without it the REPL shows only elapsed time. Pandoc runs do not report a percentage. |
572
+ | A tool is reported missing at startup | ffai warns about each missing tool; install the ones you need. The other tool remains usable. |
573
+
574
+ ## Development
575
+
576
+ ```bash
577
+ python -m venv .venv
578
+ source .venv/bin/activate
579
+ pip install -e '.[dev]'
580
+ ruff check .
581
+ pytest
582
+ ```
583
+
584
+ The parser, state transitions, command builders, configuration, and LLM
585
+ response parsing are testable without a network connection. The LLM boundary is
586
+ the only non-deterministic external dependency in the core path. Real executor
587
+ tests run when the corresponding tool is available.
588
+
589
+ ### Publishing
590
+
591
+ Releases go to PyPI from the `publish.yml` GitHub Actions workflow using
592
+ [Trusted Publishing](https://docs.pypi.org/trusted-publishers/), so no API token
593
+ is stored in the repository. One-time setup on PyPI: add a pending publisher for
594
+ `ffai-cli` with owner `Lixuannan`, repository `ffai`, workflow `publish.yml`, and
595
+ environment `pypi`. For each release, bump `version` in `pyproject.toml` and
596
+ `__version__` in `ffai/__init__.py`, then publish a GitHub Release to trigger
597
+ the upload. PyPI never allows reusing a version number.
598
+
599
+ ```bash
600
+ python -m build
601
+ twine check dist/*
602
+ ```
603
+
604
+ ## Limitations
605
+
606
+ - One output file per tool task; no batch or persistent session storage.
607
+ - Local file inputs only.
608
+ - Only the extra-option allowlists are accepted. The FFmpeg and Pandoc
609
+ allowlists are defined in `ffai/command_builder.py` and `ffai/pandoc_builder.py`
610
+ and embedded in the LLM prompt; unlisted options are rejected instead of
611
+ passed through.
612
+ - Pandoc PDF output requires a separately installed PDF engine.
613
+ - Filter expressions and option values are syntactically checked only at the
614
+ boundaries ffai knows about. The external tool remains the authority on graph
615
+ validity, codec availability, and format support.
616
+ - Estimated progress requires `ffprobe` and applies to FFmpeg only.
617
+
618
+ ## License
619
+
620
+ MIT.