Compare commits

...

214 commits

Author SHA1 Message Date
github-actions[bot] b682dad740 chore(release): Update version to v1.4.478
Some checks failed
Update Version File and Create Tag / update-version (push) Has been cancelled
Go Build / Run tests (push) Has been cancelled
2026-09-07 19:17:43 +00:00
Kayvan Sylvan e91b4f0af1
Merge pull request #2216 from ctbaum/fix/gpt-6-raw-mode
fix: enable raw mode for GPT-6 models
2026-09-07 12:15:38 -07:00
ctbaum f8bc07f2d7 chore: incoming 2216 changelog entry 2026-09-06 12:57:58 +02:00
ctbaum 8db08ad20f fix: enable raw mode for GPT-6 models 2026-09-06 12:57:33 +02:00
github-actions[bot] 6b0914d1bb chore(release): Update version to v1.4.477
Some checks failed
Go Build / Run tests (push) Has been cancelled
Update Version File and Create Tag / update-version (push) Has been cancelled
2026-09-03 22:45:25 +00:00
Kayvan Sylvan 3ca2133819
Merge pull request #2211 from ksylvan/fix-left-behind-tempdir
fix: prevent pattern loader temporary directory leaks
2026-09-03 15:42:28 -07:00
Kayvan Sylvan f141b68f12 fix: prevent pattern loader temporary directory leaks
- Create pattern temporary directories only during database population.
- Remove temporary directories after successful or failed downloads.
- Add regression tests for lazy creation and cleanup.
- Document pattern loader cleanup behavior in changelog.
2026-09-03 15:28:35 -07:00
github-actions[bot] 8dce453ed7 chore(release): Update version to v1.4.476 2026-09-03 22:05:51 +00:00
Kayvan Sylvan 4637fa1a22
Merge pull request #2210 from ksylvan/add-pzero-to-vendors
feat: add Pzero as an OpenAI-compatible AI provider
2026-09-03 15:03:29 -07:00
Kayvan Sylvan a3d1dc2cae feat: add Pzero as an OpenAI-compatible AI provider
- Register Pzero with its OpenAI-compatible API endpoint.
- Disable unsupported Responses API behavior for Pzero requests.
- List Pzero among supported providers in README documentation.
- Add Pzero integration details to incoming changelog.
2026-09-03 15:00:39 -07:00
github-actions[bot] 00f56a76e0 chore(release): Update version to v1.4.475 2026-09-03 16:48:09 +00:00
Kayvan Sylvan e9b32ba546
Merge pull request #2209 from kadiryildiz283/feat/add-turkish-locale
feat(i18n): add Turkish (tr) translation
2026-09-03 09:45:59 -07:00
Kayvan Sylvan 4835506879 chore: incoming 2209 changelog entry 2026-09-03 09:43:13 -07:00
Kayvan Sylvan 852ffd8468 chore: add newline to turkish locale json file 2026-09-03 09:43:08 -07:00
kadiryildiz283 fc0b2c0294 feat(i18n): add Turkish (tr) translation 2026-09-03 09:09:57 +03:00
github-actions[bot] a8afe60608 chore(release): Update version to v1.4.474
Some checks are pending
Go Build / Run tests (push) Waiting to run
Update Version File and Create Tag / update-version (push) Waiting to run
2026-09-03 00:58:10 +00:00
Kayvan Sylvan 892c70a8bb
Merge pull request #2206 from ksylvan/fix/storage-path-traversal
fix: confine storage names and authenticate Ollama serve
2026-09-02 17:55:55 -07:00
Kayvan Sylvan 63846b523c chore: incoming 2206 changelog entry 2026-09-02 16:00:22 -07:00
Kayvan Sylvan f37c71f9f7 fix: secure storage paths and authenticate REST and Ollama servers
- Reject unsafe cross-platform storage names and traversal attempts.
- Confine symlink targets within configured filesystem storage directories.
- Require API keys for every non-loopback server binding.
- Authenticate Ollama routes and securely forward configured credentials.
- Validate chat pattern, context, and session names early.
- Sanitize client errors to hide internal filesystem details.
- Default REST server binding to loopback port 8080.
- Add regression coverage for traversal, symlinks, and authentication.
2026-09-02 15:59:59 -07:00
github-actions[bot] cc2a9d39d6 chore(release): Update version to v1.4.473
Some checks failed
Go Build / Run tests (push) Has been cancelled
Update Version File and Create Tag / update-version (push) Has been cancelled
2026-08-29 16:37:15 +00:00
Kayvan Sylvan 95c5b7d6ba
Merge pull request #2203 from pacocartones/fix/sse-utf8-stream-boundary
fix(web): preserve multi-byte UTF-8 split across SSE chunks in chat stream
2026-08-29 09:34:56 -07:00
Kayvan Sylvan b136259cfb chore: simplify incoming changelog snippet 2026-08-29 09:30:10 -07:00
Kayvan Sylvan 52f63d0795 chore: incoming 2203 changelog entry 2026-08-29 09:27:15 -07:00
Kayvan Sylvan 2c510971dd fix: preserve split UTF-8 characters in streaming responses
- Decode API response chunks with persistent streaming state
- Reuse streaming decoder when inspecting chat backend events
2026-08-29 09:26:35 -07:00
Kayvan Sylvan b185a5c790 Merge branch 'main' into fix/sse-utf8-stream-boundary 2026-08-29 08:59:06 -07:00
github-actions[bot] 8fd6ce2418 chore(release): Update version to v1.4.472 2026-08-29 15:48:52 +00:00
Kayvan Sylvan 8aeaa1bb5e
Merge pull request #2198 from cuihuan/add-synthorai-provider
feat(providers): add Synthorai as an OpenAI-compatible provider
2026-08-29 08:46:36 -07:00
Kayvan Sylvan 3ad89a3516 chore: simplify incoming changelog snippet 2026-08-29 08:43:51 -07:00
Kayvan Sylvan cd54c1a82f Merge branch 'main' into add-synthorai-provider 2026-08-29 08:42:12 -07:00
Kayvan Sylvan 435b89d936 chore: incoming 2198 changelog entry 2026-08-29 08:41:47 -07:00
Paco Cartones c10bd5a380 fix(web): preserve multi-byte UTF-8 split across SSE chunks in chat stream
createMessageStream decoded each network chunk with a fresh TextDecoder and no
{ stream: true }, so a multi-byte UTF-8 rune (CJK, emoji) split across a chunk
boundary was corrupted into U+FFFD before the frame buffer ever saw it. Hoist a
single persistent decoder and decode with { stream: true } so pending bytes
carry across chunks.
2026-08-29 00:17:59 +00:00
Kayvan Sylvan 374d2dc967 chore: delete generate_changelog noise in README.
Some checks failed
Go Build / Run tests (push) Waiting to run
Update Version File and Create Tag / update-version (push) Waiting to run
Patterns Artifact / Zip and Upload Patterns Folder (push) Has been cancelled
2026-08-28 07:49:20 -07:00
github-actions[bot] 54a7775e5b chore(release): Update version to v1.4.471 2026-08-28 02:55:44 +00:00
Kayvan Sylvan 19b87b33a2
Merge pull request #2200 from ksylvan/fix/codex-oauth-token-persist
fix: persist Codex OAuth tokens after refresh
2026-08-27 19:53:41 -07:00
Kayvan Sylvan 1b907fe318 chore: incoming 2200 changelog entry 2026-08-27 18:46:25 -07:00
Kayvan Sylvan 9b724f8cec fix: persist refreshed Codex tokens and reactivate configured vendors
- Persist rotated Codex OAuth tokens after every successful refresh.
- Reload stored credentials under cross-process locks before refreshing.
- Write environment updates atomically with secret-only file permissions.
- Preserve existing environment values while skipping empty replacements.
- Activate configured vendors lazily and surface configuration failures immediately.
- Reuse valid refresh tokens before starting interactive authentication.
- Localize Codex setup, provider, request, and persistence errors.
- Upgrade AI, cloud, telemetry, and supporting Go dependencies.
- Set Claude Opus 5 as default changelog summarizer.
- Document `.env` token sensitivity and rewrite behavior.
2026-08-27 18:12:08 -07:00
cuihuan 733a1810bf feat(providers): add Synthorai as an OpenAI-compatible provider
Synthorai (https://synthorai.io) reaches Anthropic, OpenAI, Google, Z.ai,
Moonshot, DeepSeek and Qwen models through one OpenAI-compatible base URL,
billed at each upstream's own list price.
2026-08-26 11:26:51 +08:00
github-actions[bot] 338b89cfe9 chore(release): Update version to v1.4.470 2026-08-04 01:20:12 +00:00
Kayvan Sylvan c9211929c4
Merge pull request #2186 from giodamelio/update-nixpkgs
Update Nixpkgs version for newer Go version
2026-08-03 18:17:55 -07:00
Kayvan Sylvan 9614bf2f16 chore: incoming 2186 changelog entry 2026-08-03 18:11:08 -07:00
Giovanni d'Amelio 2580d130c0 chore: update Nixpkgs version for newer Go version
The go.mod now requires go >= 1.26.0 and the current Nixpkgs has 1.25.4
2026-08-03 13:10:08 -07:00
github-actions[bot] 0cb6a9facc chore(release): Update version to v1.4.469 2026-08-03 17:58:51 +00:00
Kayvan Sylvan 6627337e54
Merge pull request #2185 from ksylvan/issue-2184-pdf-inspector-wasm
feat(web): replace PDF.js pipeline with pdf-inspector WASM worker
2026-08-03 10:56:37 -07:00
Kayvan Sylvan 56d330f5e8 fix(web): keep chat history within viewport 2026-08-03 10:52:29 -07:00
Kayvan Sylvan 829fa3bd07 fix(web): show processed file attachment indicator 2026-08-03 06:44:07 -07:00
Kayvan Sylvan 28273a0533 build: upgrade SvelteKit, Shiki, and Vite web dependencies
- Upgrade SvelteKit to 2.70.2 for framework maintenance updates
- Upgrade Shiki to 4.4.1 across syntax highlighting packages
- Upgrade Vite to 8.2.0 and Rolldown to 1.2.1
- Synchronize npm and pnpm lockfiles with transitive dependencies
2026-08-02 17:07:01 -07:00
Kayvan Sylvan ab64555925 Merge branch 'main' into issue-2184-pdf-inspector-wasm 2026-08-02 16:36:24 -07:00
github-actions[bot] cb340b6b02 chore(release): Update version to v1.4.468 2026-08-02 23:32:11 +00:00
Kayvan Sylvan 32ff0b5591
Merge pull request #2182 from drawliin/fix/ollama-stream-channel-close 2026-08-02 16:29:55 -07:00
Ssam 2b1b31ca3f
Merge branch 'main' into fix/ollama-stream-channel-close 2026-08-03 00:28:08 +01:00
Kayvan Sylvan b90a07bc19 feat(web): replace PDF.js conversion with WASM worker
- Process PDF attachments through reusable pdf-inspector WASM worker
- Transfer ArrayBuffers and initialize WASM once per worker
- Reject pending conversions and recreate workers after crashes
- Parse attachments without triggering duplicate chat requests
- Surface OCR, encoding, and empty-content conversion errors
- Remove PDF.js pipeline and obsolete transitive dependencies
- Add tests for conversion results and chat boundaries
2026-08-02 16:25:25 -07:00
Kayvan Sylvan a4b89f65c2 chore: incoming 2182 changelog entry 2026-08-02 16:24:27 -07:00
github-actions[bot] 9e92bca148 chore(release): Update version to v1.4.467 2026-07-31 15:49:11 +00:00
Kayvan Sylvan fe2fbe9cff
Merge pull request #2171 from OdinKral/pr/pattern-audit-script
feat(scripts): add pattern/maintenance audit script
2026-07-31 08:46:47 -07:00
Kayvan Sylvan c9fa92bf03
Merge branch 'main' into pr/pattern-audit-script 2026-07-31 08:41:19 -07:00
Kayvan Sylvan 7a638ff3cc chore: incoming 2171 changelog entry 2026-07-31 07:30:11 -07:00
github-actions[bot] bd4c9c93d2 chore(release): Update version to v1.4.466 2026-07-30 23:07:25 +00:00
Kayvan Sylvan b12a1ef1eb
Merge pull request #2116 from danielmiessler/dependabot/npm_and_yarn/web/npm_and_yarn-9ebba0a229
fix(web): repair the chat page and modernize the build toolchain
2026-07-30 16:05:03 -07:00
Kayvan Sylvan 8135a11606 docs: correct PR 2116 contributor attribution
- Credit ksylvan and Dependabot for PR 2116 changes
2026-07-30 16:01:24 -07:00
Kayvan Sylvan fba2543895 chore: incoming 2116 changelog entry 2026-07-30 15:57:32 -07:00
Kayvan Sylvan e8fd77c40b style(web): format the files added in this branch
Prettier reflows these three files, which the branch adds or rewrites. The count
of files that prettier reports goes from 333 to 330. The rest of that count is
work that this branch does not touch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 15:50:41 -07:00
Kayvan Sylvan 9f38ee0eff chore: incoming 2116 changelog entry 2026-07-30 15:21:17 -07:00
Kayvan Sylvan 1b1feea2b5 feat: modernize web stack and harden chat error handling
- Upgrade Tailwind, Skeleton, SvelteKit, Vite, and supporting dependencies.
- Replace removed Skeleton components with compatible local implementations.
- Report pre-stream chat failures without duplicating streamed errors.
- Return empty model arrays and skip malformed vendor lists.
- Prevent stream completion crashes and normalize displayed error messages.
- Stack toasts correctly and add warning notification support.
- Restore linting, formatting, testing, and pnpm override configuration.
- Preserve custom themes through Tailwind CSS-based configuration migration.
2026-07-30 15:06:58 -07:00
Kayvan Sylvan 737c3ac372
Merge pull request #2178 from OdinKral/feat/generate-frontmatter-pattern
feat(patterns): add generate_frontmatter for PKM/Obsidian users
2026-07-30 12:41:50 -07:00
Kayvan Sylvan e25005c8d5 feat: register generate_frontmatter for pattern discovery workflows
- Add PKM YAML metadata guidance to pattern explanations
- Classify frontmatter generation under conversion, extraction, and writing
- Register descriptions and extracts for pattern suggestion workflows
- Credit both contributors in the incoming changelog entry
2026-07-30 12:37:21 -07:00
Kayvan Sylvan 9e685112af chore: incoming 2178 changelog entry 2026-07-30 12:28:19 -07:00
drawliin e65c35d63e fix(ollama): close stream channel on errors 2026-07-30 02:28:31 +01:00
github-actions[bot] d8e934df22 chore(release): Update version to v1.4.465 2026-07-29 22:41:15 +00:00
Kayvan Sylvan 7f2e663cb9
Merge pull request #2180 from ksylvan/fix/grok-web-search-help
fix: include Grok in localized --search help text
2026-07-29 15:39:04 -07:00
Kayvan Sylvan 01f88e5c2b chore: incoming 2180 changelog entry 2026-07-29 15:28:13 -07:00
Kayvan Sylvan 9730808119 fix: include Grok in localized --search help text
PR #2092 added Grok (xAI) web search support and updated the go-flags
struct tag in internal/cli/flags.go, but `fabric --help` renders flag
descriptions from the i18n message catalog (internal/cli/help.go maps
"search" -> "enable_web_search_tool"), not from the struct tag. The
catalog value still listed only Anthropic, OpenAI, and Gemini, so the
Grok support stayed undiscoverable in --help even though it works.

Append "Grok" to the enable_web_search_tool value in all 11 locales,
using each locale's own list separator, and update the generated README
help block to match. No functional change.

The zsh and fish completion strings were already corrected in #2179,
so this carries forward only the remaining parts of #2140.

Co-Authored-By: Ken Hartman <khartman@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 15:22:22 -07:00
github-actions[bot] 6cef8c10ad chore(release): Update version to v1.4.464 2026-07-29 22:15:14 +00:00
Kayvan Sylvan cd8c249744
Merge pull request #2179 from ksylvan/fix-completion-scripts
fix: declare completion arguments and synchronize CLI help
2026-07-29 15:12:51 -07:00
Kayvan Sylvan 080311effc docs: clarify show-metadata token details across CLI surfaces
- Specify input and output tokens in flag help text
- Synchronize metadata descriptions across shell completion definitions
- Localize clarified metadata guidance across supported languages
- Record description clarification in incoming changelog entry
2026-07-29 15:11:05 -07:00
Kayvan Sylvan f95bff568f chore: incoming 2179 changelog entry 2026-07-29 15:01:41 -07:00
Kayvan Sylvan ec0b7246c4 fix: declare completion arguments and synchronize CLI help
- Declare Fish completion arguments for dynamic and fixed values
- Enable filtered file suggestions for supported path options
- Add Spotify, transcription, metadata, and wire-debug completion options
- Include Grok among providers supporting web search completion
- Localize pattern, Spotify, and metadata help descriptions consistently
- Document expanded completion coverage and wire-level debug behavior
- Describe YouTube visual flags directly in generated help
2026-07-29 14:56:27 -07:00
OdinKral 3b03c17f9a feat(scripts): add pattern/maintenance audit script
Adds scripts/audit-patterns.sh, a dependency-free report over the
patterns library and a few adjacent consistency checks:

  1. Thin patterns     — system.md under 15 lines (likely stubs)
  2. Bloated patterns  — system.md over 50 KB (likely embedded examples)
  3. Stale model refs  — hardcoded GPT-4 / ChatGPT / vendor model names
  4. Missing INPUT     — pattern has no `# INPUT` section
  5. i18n key gaps     — keys in en.json missing from other locales
  6. Completion gaps   — long flags in flags.go absent from completions

Reports only; it never edits patterns. Exits 0 by default so it can be
run casually, or `--strict` to exit 1 on findings for CI use.

Against the current tree it reports 109 issues, which is what motivated
the separate normalization and de-bloating changes.
2026-07-29 08:22:34 -04:00
github-actions[bot] 4ba8ac2919 chore(release): Update version to v1.4.463 2026-07-28 17:10:26 +00:00
Kayvan Sylvan 3e7c4b902a
Merge pull request #2168 from ksylvan/i18n-audit
fix: complete localized setup and error messages for supported locales
2026-07-28 10:08:09 -07:00
Kayvan Sylvan 177363a702 chore: incoming 2168 changelog entry 2026-07-28 10:04:43 -07:00
Kayvan Sylvan 3ff434ed2b fix: complete localized setup and error messages across supported locales
- Translate Bedrock setup prompts across nine supported locales
- Localize datetime and system template errors consistently
- Translate Persian Spotify errors and setup guidance
- Correct Japanese and Polish file operation log labels
2026-07-28 09:42:40 -07:00
github-actions[bot] ba6feced62 chore(release): Update version to v1.4.462 2026-07-28 15:30:08 +00:00
Kayvan Sylvan 69dbf44ad1
Merge pull request #2167 from ksylvan/fix/pattern-path-traversal-2123-re-created
fix: block path traversal in pattern name lookup (closes #2094)
2026-07-28 08:27:46 -07:00
Kayvan Sylvan 64b1fc1fab chore: renumber incoming changelog entry 2123 -> 2167 2026-07-28 08:24:41 -07:00
Kayvan Sylvan 37fc6686fa docs: credit security fix contributors and note translations
- Credit both contributors for the path traversal security fix.
- Document new translations for the invalid pattern message.
2026-07-28 08:15:07 -07:00
Kayvan Sylvan e36a231f3f chore: incoming 2123 changelog entry 2026-07-28 08:11:17 -07:00
Kayvan Sylvan 54347b5cf5 fix(i18n): translate pattern_invalid_name into all 10 non-English locales 2026-07-28 08:08:03 -07:00
OdinKral 1e650266ab fix: block path traversal in pattern name lookup (closes #2094)
Pattern names containing ".." could be used to escape the patterns
directory and read arbitrary files via filepath.Join. Guard added at
the top of getFromDB; i18n key pattern_invalid_name added to all
11 locale files; test cases cover all common traversal variants.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 08:08:03 -07:00
github-actions[bot] d49a61a32e chore(release): Update version to v1.4.461 2026-07-28 14:40:46 +00:00
Kayvan Sylvan 0dbf9cb0ec
Merge pull request #2152 from AUTHENSOR/fix/extension-executor-shell-injection 2026-07-28 07:38:30 -07:00
Kayvan Sylvan 5ff3330040 chore: incoming 2152 changelog entry 2026-07-28 07:34:37 -07:00
github-actions[bot] d85544c88a chore(release): Update version to v1.4.460 2026-07-24 19:03:05 +00:00
Kayvan Sylvan 2469552d94
Merge pull request #2166 from ksylvan/support-opus-5
feat: add Claude Opus 5 support and refresh dependencies
2026-07-24 12:00:49 -07:00
Kayvan Sylvan 094691b148 chore: incoming 2166 changelog entry 2026-07-24 11:59:01 -07:00
Kayvan Sylvan ff3dd1a5ad chore: tidy Go module dependencies and checksums
- Remove the unused `swag/jsonname` indirect module dependency.
- Prune stale checksums for superseded dependency versions.
- Retain checksums for currently resolved module versions.
- Refresh the fixture module checksum for version 0.27.3.
2026-07-24 11:54:21 -07:00
Kayvan Sylvan c32ee25f03 feat: add Claude Opus 5 support and refresh dependencies
## CHANGES

- Add Claude Opus 5 to supported model selection
- Disable sampling parameters for Claude Opus 5 requests
- Restrict one-million-token beta headers to compatible Claude models
- Remove unsupported 200K-context models from beta header mapping
- Upgrade Anthropic, AWS, Ollama, Google, and supporting dependencies
2026-07-24 11:47:51 -07:00
github-actions[bot] 34e02fdca2 chore(release): Update version to v1.4.459 2026-07-16 02:04:00 +00:00
Kayvan Sylvan 4dc7e5a1f2
Merge pull request #2135 from octo-patch/feature/upgrade-minimax-m3
feat: upgrade MiniMax default model to M3
2026-07-15 19:01:35 -07:00
Kayvan Sylvan 72d626ebec
Merge branch 'main' into feature/upgrade-minimax-m3 2026-07-15 18:55:00 -07:00
Kayvan Sylvan a1f8549620 chore: incoming 2135 changelog entry 2026-07-15 18:54:28 -07:00
github-actions[bot] 82e14bb8f3 chore(release): Update version to v1.4.458 2026-07-12 19:21:54 +00:00
Kayvan Sylvan c7f07b6f1b
Merge pull request #2161 from ksylvan/anthropic-max-tokens-fix
fix: respect Anthropic chat option max token overrides
2026-07-12 12:19:38 -07:00
Kayvan Sylvan f96a971168 chore: incoming 2161 changelog entry 2026-07-12 12:17:54 -07:00
Kayvan Sylvan 6e44e1c66a fix: respect Anthropic chat option max token overrides
- Use configured Anthropic max tokens as default
- Apply chat option max tokens when provided
- Preserve existing behavior for missing token overrides
- Add tests for default max token selection
- Add tests for explicit max token overrides
2026-07-12 11:58:36 -07:00
Kayvan Sylvan 5a5f800f68 chore: clean up ChangeLog
Some checks failed
Go Build / Run tests (push) Has been cancelled
Update Version File and Create Tag / update-version (push) Has been cancelled
2026-07-09 14:33:59 -07:00
github-actions[bot] 1748c816ec chore(release): Update version to v1.4.457 2026-07-09 21:09:43 +00:00
Kayvan Sylvan 67d0cd09ed
Merge pull request #2156 from ksylvan/chore/add-closed-ok-to-changelog
Make it possible to back-fill missing ChangeLog entries
2026-07-09 14:07:19 -07:00
Kayvan Sylvan 47774fa8c7 chore: incoming 2156 changelog entry 2026-07-09 14:05:55 -07:00
Kayvan Sylvan f6a9b74dac feat: allow changelog generation for closed pull requests
- Add `--closed-ok` flag to bypass open-state validation
- Skip mergeability checks when processing closed pull requests
- Store closed pull request allowance in generator configuration
- Guide users toward `--closed-ok` in validation errors
- Record incoming changelog entries for pull requests 2155 and 2156
- Refresh changelog database with new incoming metadata
2026-07-09 14:05:19 -07:00
github-actions[bot] 43aa95e966 chore(release): Update version to v1.4.456 2026-07-09 20:22:05 +00:00
Kayvan Sylvan 35c058fb4e
Merge pull request #2155 from ksylvan/support-new-anthropic-models
Claude Sonnet 5 Anthropic support
2026-07-09 13:19:47 -07:00
Kayvan Sylvan 6a999ab23e chore: cleanups - tidying the duplicate model listed 2026-07-09 13:09:50 -07:00
Kayvan Sylvan 5f34b6812e feat: add Claude Sonnet 5 Anthropic support
- Add Claude Sonnet 5 to supported Anthropic models
- Omit sampling parameters for Claude Sonnet 5 requests
- Centralize Anthropic sampling restrictions behind prefix matching
- Enable one-million-token context beta for Claude 5 models
- Remove older Claude 4 aliases from model listings
- Update Anthropic tests for Sonnet 5 beta mapping
- Refresh Go dependencies across AI provider integrations
2026-07-09 13:04:48 -07:00
Authensor 3eba7a51d4 fix: shell-escape extension values to prevent command injection
The extension executor runs commands via 'sh -c' with user-controlled
values interpolated into the command string without escaping. A value
containing shell metacharacters (;, |, $(), backticks) is executed by
the shell, enabling command injection.

User input flows from content processed through a pattern into the
extension system via the InputSentinel, then into formatCommand which
interpolates it into the cmd_template, then into exec.Command('sh', '-c').
No escaping is applied at any point.

Fix: wrap all user-controlled values (value, numbered pipe-split values)
in single quotes with embedded-single-quote escaping before interpolation.
This ensures sh -c treats them as literal arguments, not shell syntax.

The existing tests pass unchanged because the inner sh strips the single
quotes, so the executed command receives the same argument values.

Regression test added: ShellInjectionBlocked verifies that input
'hello; touch /marker' does not create the marker file.

Signed-off-by: John Kearney <johndanielkearney@gmail.com>
2026-07-01 21:55:21 -05:00
github-actions[bot] a420eaf63c chore(release): Update version to v1.4.455
Some checks failed
Go Build / Run tests (push) Has been cancelled
Patterns Artifact / Zip and Upload Patterns Folder (push) Has been cancelled
Update Version File and Create Tag / update-version (push) Has been cancelled
2026-06-09 21:57:16 +00:00
Kayvan Sylvan 51eeff1109
Merge pull request #2138 from ksylvan/fix-github-429-plus-claude-fable-5
New Claude Fable model + cache OpenAI model discovery and handle provider rate limits
2026-06-09 14:54:53 -07:00
Kayvan Sylvan adabdf1e68 chore: incoming 2138 changelog entry 2026-06-09 14:47:37 -07:00
Kayvan Sylvan 59d26a7e89 feat: cache OpenAI model discovery and handle provider rate limits
- Add persistent cache for provider model discovery results
- Serve stale model caches during discovery failures
- Return concise localized errors for rate-limited model fetches
- Send GitHub Models API version header automatically
- Add Claude Fable 5 Anthropic model support
- Omit sampling parameters for Claude Fable 5
- Update model rate-limit translations across supported locales
- Add tests for cache, rate-limit, and GitHub headers
- Update Go dependencies for AI provider integrations
2026-06-09 14:36:46 -07:00
github-actions[bot] 29b32f9ff2 chore(release): Update version to v1.4.454 2026-06-02 23:31:29 +00:00
Kayvan Sylvan 8e452510e0
Merge pull request #2136 from ksylvan/opus-4-8-sampling-params-fix
chore: extend sampling param exclusion to Opus 4.8 models
2026-06-02 16:29:14 -07:00
Kayvan Sylvan 69a79e6efc chore: incoming 2136 changelog entry 2026-06-02 16:27:34 -07:00
Kayvan Sylvan 73fa72e279 chore: incoming 2136 changelog entry 2026-06-02 16:27:01 -07:00
Kayvan Sylvan 4b4821a235 chore: extend sampling param exclusion to Opus 4.8 models
- Add Opus 4.8 to sampling param exclusion check
- Update comment to mention Opus 4.8 models
- Match `claude-opus-4-8` model prefix alongside 4.7
2026-06-02 16:25:48 -07:00
octo-patch be1540781d feat: upgrade MiniMax default model to M3
- Add MiniMax-M3 to the static MiniMax model list as the new default
- Retain MiniMax-M2.7 and MiniMax-M2.7-highspeed as available alternatives
- Remove deprecated older models (M2.5 / M2.5-highspeed / M2.5-lightning / M2 / M2.1 / M2.1-lightning) from the static list

MiniMax-M3 is the new flagship model and becomes the default selection by being placed first in the static model list.
2026-06-01 21:16:41 +08:00
github-actions[bot] 7ede03225d chore(release): Update version to v1.4.453 2026-05-28 19:37:31 +00:00
Kayvan Sylvan 4f073730a6
Merge pull request #2132 from ksylvan/claude-4-8
Add Claude Opus 4.8 model and bump Go toolchain and dependencies
2026-05-28 12:35:04 -07:00
Kayvan Sylvan 134171de7e modernize ./...
/Users/kayvan/src/fabric/internal/plugins/ai/codex/errors.go:61:5: errors.As can be simplified using AsType[*openaiapi.Error]
/Users/kayvan/src/fabric/internal/cli/flags.go:126:2: NumField/Field loop can simplified using Type.Fields iteration
/Users/kayvan/src/fabric/internal/cli/help.go:145:2: NumField/Field loop can simplified using Type.Fields iteration
/Users/kayvan/src/fabric/internal/cli/help.go:225:2: NumField/Field loop can simplified using Type.Fields iteration
/Users/kayvan/src/fabric/internal/i18n/i18n.go:222:15: strings.Split call can be simplified using strings.Cut
2026-05-28 12:27:27 -07:00
Kayvan Sylvan 4527a5b919 chore: incoming 2132 changelog entry 2026-05-28 12:19:07 -07:00
Kayvan Sylvan 387b226c4e chore: downgrade invopop/jsonschema and drop unused indirect dependencies
## CHANGES

- downgrade `invopop/jsonschema` from v0.14.0 to v0.13.0
- remove `pb33f/ordered-map/v2` indirect dependency
- remove `go.yaml.in/yaml/v4` release-candidate indirect dependency
- update `go.sum` to match revised module set
2026-05-28 12:18:25 -07:00
Kayvan Sylvan 4dfaf61e93 chore: bump Go toolchain and dependencies, add Claude Opus 4.8 model
# CHANGES

- Upgrade Go toolchain to 1.26.0
- Bump anthropic-sdk-go to v1.46.0
- Add Claude Opus 4.8 to supported models
- Update AWS SDK and Bedrock service modules
- Bump ollama client to v0.24.0
- Refresh OpenTelemetry, gRPC, and genai dependencies
- Update go-git, sqlite3, and assorted indirect modules
2026-05-28 12:11:30 -07:00
OdinKral 175772e8a0 feat(patterns): add generate_frontmatter pattern for PKM users
Adds a pattern that generates clean, paste-ready YAML frontmatter for
any text input. Designed for PKM systems such as Obsidian and Logseq
but works for any markdown-based note vault.

Given a document, article, transcript, or rough draft it produces:
  title, aliases, tags (specific + hierarchical), type, author,
  source, date, summary, and status fields.

Usage:
  cat article.md | fabric -p generate_frontmatter
  pbpaste       | fabric -p generate_frontmatter
  yt URL        | fabric -p generate_frontmatter

Addresses feature request #1235.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 11:41:20 -04:00
github-actions[bot] 6a9b55a096 chore(release): Update version to v1.4.452 2026-05-04 22:18:46 +00:00
Kayvan Sylvan 90cd2bef9d
Merge pull request #2111 from ksylvan/anthopic-4-7-migration-fixes
fix: omit Anthropic sampling params for Claude Opus 4.7
2026-05-04 15:16:34 -07:00
Kayvan Sylvan 6f4154a4ae chore: incoming 2111 changelog entry 2026-05-04 14:57:40 -07:00
Kayvan Sylvan d2995ee02e fix: omit Anthropic sampling params for Claude Opus 4.7
## CHANGES

- Add Opus 4.7 sampling parameter guard
- Omit temperature and top_p for incompatible models
- Preserve existing TopP and temperature selection behavior
- Cover Opus 4.7 omission with unit test
2026-05-04 14:48:35 -07:00
github-actions[bot] 8a3058e2b8 chore(release): Update version to v1.4.451 2026-04-23 08:55:50 +00:00
Kayvan Sylvan d7cc49f25c
Merge pull request #2086 from majiayu000/fix/issue-2084-vendor-model-prefix-parsing
fix: parse vendor prefix from model name for vendor/model convention
2026-04-23 01:47:37 -07:00
Kayvan Sylvan d7259fd6ae chore: incoming 2086 changelog entry 2026-04-23 01:36:55 -07:00
Kayvan Sylvan c8bfd3f886 chore: incoming 2086 changelog entry 2026-04-23 01:36:06 -07:00
Kayvan Sylvan 774376d19b
Merge branch 'main' into fix/issue-2084-vendor-model-prefix-parsing 2026-04-23 01:35:14 -07:00
Kayvan Sylvan 7bc470da5f
Merge pull request #2079 from teamsincetoday/add-commerce-intelligence-patterns
Add 3 commerce intelligence patterns: affiliate extraction, video entities, monetization
2026-04-23 01:26:42 -07:00
Kayvan Sylvan 4d896b47d8 chore: incoming 2079 changelog entry 2026-04-23 01:25:09 -07:00
Kayvan Sylvan 995c666e67 feat: add monetization analysis and commerce extraction patterns to suggest_pattern
# CHANGES

- Register `analyze_monetization_opportunities` pattern under ANALYSIS and BUSINESS categories
- Register `extract_affiliate_products` pattern under BUSINESS and EXTRACT categories
- Register `extract_video_commerce_entities` pattern under BUSINESS and EXTRACT categories
- Describe monetization analysis for affiliate, sponsorship, and product revenue
- Describe affiliate product extraction separating sponsored from organic mentions
- Describe commerce entity extraction with category and purchase likelihood
2026-04-23 01:24:19 -07:00
Kayvan Sylvan 5973722f1a feat: add three creator monetization patterns for affiliate and commerce analysis
# CHANGES

- Add `analyze_monetization_opportunities` pattern for creator revenue strategy
- Add `extract_affiliate_products` pattern for surfacing affiliate opportunities
- Add `extract_video_commerce_entities` pattern for video transcript commerce
- Register new patterns in `pattern_descriptions.json` with appropriate tags
- Add full pattern extracts to `pattern_extracts.json` for each addition
- Update `pattern_explanations.md` with descriptions of three new patterns
2026-04-23 01:20:45 -07:00
Kayvan Sylvan c86bd1cda9
Merge branch 'main' into add-commerce-intelligence-patterns 2026-04-23 00:55:19 -07:00
github-actions[bot] 6f4e2fad9b chore(release): Update version to v1.4.450 2026-04-23 07:48:15 +00:00
Kayvan Sylvan b545eb97a0
Merge pull request #2092 from Resistor52/feat/grokai-search-grounding
feat(openai): add GrokAI search grounding via xAI Responses API
2026-04-23 00:46:05 -07:00
Kayvan Sylvan 11f2683d50 Merge branch 'main' into feat/grokai-search-grounding 2026-04-23 00:37:28 -07:00
github-actions[bot] 3496213649 chore(release): Update version to v1.4.449 2026-04-23 07:28:24 +00:00
Kayvan Sylvan 9930b6be71
Merge pull request #2103 from danielmiessler/dependabot/go_modules/go_modules-d4b3b64db6
chore(deps): bump github.com/go-git/go-git/v5 from 5.17.2 to 5.18.0 in the go_modules group across 1 directory
2026-04-23 00:25:49 -07:00
Kayvan Sylvan e8d4558129 chore: fix changelog enties 2026-04-23 00:22:49 -07:00
Kayvan Sylvan 2a73277776 chore: incoming 2103 changelog entry 2026-04-23 00:19:21 -07:00
Kayvan Sylvan a0f4fb4acd chore: bump Go module dependencies and upgrade Vite to 8.0.8 in web
- Upgrade Vite from 5.4.21 to 8.0.8 in web
- Bump @sveltejs/vite-plugin-svelte from 4.0.4 to 7.0.0
- Upgrade vite-plugin-tailwind-purgecss from 0.2.1 to 0.3.5
- Bump AWS SDK Go v2 modules to latest patches
- Update ollama client from 0.20.4 to 0.21.1
- Upgrade go-git from 5.17.2 to 5.18.0
- Bump hasura go-graphql-client from 0.15.1 to 0.16.0
- Update perplexity-go from 2.15.0 to 2.16.1
- Refresh google api, genai, and transitive Go modules
2026-04-23 00:18:31 -07:00
Kayvan Sylvan f69f1ba67d
Merge pull request #2104 from sitiom/patch-1
docs: add Scoop installation instructions
2026-04-22 23:56:11 -07:00
Kayvan Sylvan 2b613a4787 docs: add Scoop install instructions and expand cSpell dictionary
- Add Windows Scoop install section to Chinese README
- Link Scoop install entry in both README tables of contents
- Extend cSpell dictionary with APIM, MSAL, and related terms
- Ignore `.vscode/**` paths during cSpell checks
- Allow `strong` tag in cSpell markdown configuration
2026-04-22 23:53:45 -07:00
Ryan 3e7a3a88f7
docs: add Scoop installation instructions 2026-04-21 16:48:13 +08:00
Kayvan Sylvan b726895299 chore: incoming 2092 changelog entry 2026-04-20 08:12:42 -07:00
github-actions[bot] 9743e10273 chore(release): Update version to v1.4.448 2026-04-17 03:23:57 +00:00
Kayvan Sylvan 82d8269da7
Merge pull request #2098 from ksylvan/fix/codex-no-response
Fix Codex Empty Response bug
2026-04-16 20:21:30 -07:00
Kayvan Sylvan 78f9d7c1f7 fix: fall back to streamed delta text when completed Codex response is empty
- Prefer extracted completed text only when content stays non-empty
- Fall back to accumulated streamed delta text otherwise
- Preserve streamed response text before completed response evaluation
- Add regression test for empty completed output text
- Simulate SSE delta stream followed by blank completion
- Verify Send returns delta text when completion lacks content
2026-04-16 20:16:23 -07:00
github-actions[bot] 99dcff0f36 chore(release): Update version to v1.4.447 2026-04-17 02:56:22 +00:00
Kayvan Sylvan 27f0f6643a
Merge pull request #2097 from ksylvan/kayvan/opus_4_7
Add Claude Opus 4.7 model support and bump Anthropic SDK to v1.37.0
2026-04-16 19:54:10 -07:00
Kayvan Sylvan b40095b4c3 docs: Update README Recent Major Features to mention Opus 4.7 2026-04-16 19:53:11 -07:00
Kayvan Sylvan 8d20796050 chore: incoming 2097 changelog entry 2026-04-16 19:40:36 -07:00
Kayvan Sylvan d2537208df feat: add Claude Opus 4.7 model support and bump Anthropic SDK to v1.37.0
- Upgrade `anthropic-sdk-go` dependency from v1.34.0 to v1.37.0
- Add `claude-opus-4-7` to supported models list
- Enable 1M context window beta for Opus 4.7
- Update model beta comments to reflect Opus 4.7 support
2026-04-16 19:35:39 -07:00
github-actions[bot] 09f306ad2b chore(release): Update version to v1.4.446 2026-04-15 15:55:13 +00:00
Kayvan Sylvan 5ed0314681
Merge pull request #2093 from alecjmckanna/feat/readpattern
feat: add --readpattern flag to print pattern contents to terminal
2026-04-15 08:52:48 -07:00
Kayvan Sylvan b3a610ddc6 chore: incoming 2093 changelog entry 2026-04-15 08:51:17 -07:00
Kayvan Sylvan 3277c3e5a7 feat: add --readpattern flag to shell completions
- Add `--readpattern` completion to zsh with pattern name suggestions
- Add `--readpattern` to bash completion options list
- Include `--readpattern` in bash pattern completion case handler
- Add `--readpattern` completion to fish with pattern arguments
2026-04-15 08:50:39 -07:00
Kayvan Sylvan 5ae039e726 chore: incoming 2093 changelog entry 2026-04-15 08:21:58 -07:00
alecjmckanna d32e770948
chore: incoming 2093 changelog entry 2026-04-15 01:13:34 -04:00
alecjmckanna 0d7e1f7d52
feat: add --readpattern flag to print pattern contents to terminal
Adds a new `--readpattern <name>` CLI flag that prints the raw contents
of a named pattern's system.md file to stdout. This makes it easy to
inspect what instructions a pattern sends to the model without having
to navigate the filesystem manually.

The implementation respects custom patterns directories: it checks the
user's custom patterns directory first before falling back to the main
patterns directory, consistent with how all other pattern lookups work.
2026-04-15 00:43:16 -04:00
Kayvan Sylvan 76c164a041 docs: update Docker config mount path for appuser
- Replace container config mount path from root to appuser
- Align setup example with non-root container home directory
- Align pattern usage example with appuser config location
- Align REST API example with updated config mount target
- Update English and Chinese README Docker instructions consistently
2026-04-12 22:04:09 -07:00
github-actions[bot] 3d43e1dca2 chore(release): Update version to v1.4.445 2026-04-13 04:54:30 +00:00
Kayvan Sylvan 27c025be54
Merge pull request #2091 from jimscard/jimscard/dockerfile-best-practices-cve-fixes
Update Dockerfile for best practices and critical CVE fixes
2026-04-12 21:52:16 -07:00
Kayvan Sylvan 56a1a7f21e fix: verify Go tarball checksums and fix config directory ownership
- Add SHA256 checksum args for amd64 and arm64 Go tarballs
- Verify downloaded Go tarball integrity via `sha256sum -c`
- Remove root-owned `/root/.config/fabric` directory creation
- Create fabric config directory under non-root `appuser` home
- Set proper ownership on `appuser` config directory with `chown`
2026-04-12 21:44:41 -07:00
Kayvan Sylvan e387e7b86f refactor: replace manual reverse loops with slices.Backward iterator
- Use `slices.Backward` for reverse iteration in copilot response extraction
- Use `slices.Backward` for reverse iteration in gemini TTS extraction
- Add `slices` import to copilot and gemini packages
- Remove manual index-based reverse loop patterns
- Remove redundant comment about copilot response message type
2026-04-12 21:18:29 -07:00
Kayvan Sylvan 7fd6733c12 chore: incoming 2091 changelog entry 2026-04-12 21:00:30 -07:00
Kenneth G. Hartman 7ced82f782 feat(openai): add GrokAI search grounding via xAI Responses API
xAI's Responses API accepts web_search and x_search tool types, but
fabric hardcoded OpenAI's web_search_preview tool name in
buildResponseParams, causing GrokAI plus --search to fail with HTTP 422.

This adds two new fields to openai_compatible.ProviderConfig:
- WebSearchToolName: override the default web_search tool name string
- EnableXSearch: append xAI's x_search tool when search is enabled

Both fields are empty/false by default, preserving backwards
compatibility for all existing providers. GrokAI now sets
WebSearchToolName to "web_search" and EnableXSearch to true.

Tests added in openai_test.go cover the new override paths and
confirm the default provider behavior is unchanged.

Verified with live xAI API key: fabric -V GrokAI --search "query"
now returns grounded results with real source URLs.
2026-04-11 12:27:16 -04:00
Jim Scardelis b046ddb518 Update Dockerfile for best practices and CVE fixes
Update the Dockerfile to bring it closer to current container best practices while fixing multiple critical CVEs.

This pins Alpine 3.21 and Go 1.25.9 explicitly, installs the Go toolchain in the builder stage so the image no longer depends on an unavailable upstream golang tag, upgrades setuptools to pick up the CVE-2025-47273 fix, refreshes the yt-dlp installation path, and runs the final image as a non-root user.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 01:09:22 -07:00
github-actions[bot] 42311fe04b chore(release): Update version to v1.4.444 2026-04-09 21:11:05 +00:00
Kayvan Sylvan b9aff6c0a9
Merge pull request #2088 from ksylvan/dependabot-fixes-20260409
Combined dependabot fixes plus other Go module upgrades
2026-04-09 14:07:33 -07:00
Kayvan Sylvan 014f946420 chore: incoming 2088 changelog entry 2026-04-09 14:04:46 -07:00
Kayvan Sylvan 996a73ca8d chore: bump Go module dependencies to latest versions
- Upgrade `anthropic-sdk-go` from v1.27.1 to v1.34.0
- Upgrade `ollama` from v0.18.2 to v0.20.4
- Bump AWS SDK v2 packages to latest patch releases
- Update `go-git/v5` from v5.17.1 to v5.17.2
- Upgrade `go-sqlite3` from v1.14.37 to v1.14.42
- Bump `google.golang.org/api` from v0.272.0 to v0.275.0
- Upgrade `google.golang.org/genai` from v1.51.0 to v1.53.0
- Update OpenTelemetry packages from v1.42.0 to v1.43.0
- Bump `golang.org/x` crypto, net, sys, text, and mod packages
- Upgrade `google.golang.org/grpc` from v1.79.3 to v1.80.0
2026-04-09 13:52:08 -07:00
Kayvan Sylvan 90d42c8d4f Merge remote-tracking branch 'upstream/dependabot/go_modules/go_modules-f67f74747b' into dependabot-fixes-20260409 2026-04-09 13:48:23 -07:00
Kayvan Sylvan 18fb502c41 Merge remote-tracking branch 'upstream/dependabot/npm_and_yarn/web/npm_and_yarn-1aa352c751' into dependabot-fixes-20260409 2026-04-09 13:47:55 -07:00
github-actions[bot] 62b25020e5 chore(release): Update version to v1.4.443 2026-04-09 06:56:16 +00:00
Kayvan Sylvan 3fa1dcde14
Merge pull request #2073 from sathvikc/feat/youtube-visual-extraction
feat(youtube): Implement visual text extraction via FFmpeg and OCR
2026-04-08 23:53:55 -07:00
majiayu000 cb3a0e5aec
fix: parse vendor prefix from model name when vendor is not specified
When users pass a model string like "ollama/llama3", the lookup fails
because no model literally named "ollama/llama3" exists — the model is
stored as "llama3" under the "Ollama" vendor group. This adds fallback
logic to split the first path segment and check if it matches a known
vendor, resolving the "could not find vendor" error for prefixed models.

Fixes #2084

Signed-off-by: majiayu000 <1835304752@qq.com>
2026-04-08 18:17:59 +08:00
Kayvan Sylvan ceec9335c1 chore: fix gitignore bad merge 2026-04-06 08:43:19 -07:00
Kayvan Sylvan febf2c429c feat: add --visual, --visual-sensitivity, and --visual-fps flags to shell completions
- Add `--visual` flag for OCR and FFmpeg video data extraction
- Add `--visual-sensitivity` option for FFmpeg scene detection tolerance
- Add `--visual-fps` option for fixed frame-per-second extraction
- Update zsh completions with three new visual flags
- Update bash completions opts list with visual flags
- Add visual flags to bash simple-argument completion case
- Update fish completions with visual flag descriptions
2026-04-06 06:24:38 -07:00
Kayvan Sylvan d65b8b8213 chore: add ksylvan to the attribution for the PR 2026-04-05 16:10:58 -07:00
Kayvan Sylvan c78576deb7 feat: internationalize YouTube visual extraction flags and error messages
- Move visual flag descriptions to i18n locale system
- Add visual extraction strings to all 11 locale files
- Replace hardcoded English error messages with i18n lookups
- Register visual flags in `flagDescriptionMap` for help system
- Remove inline `description` tags from visual CLI flag structs
- Localize FFmpeg, Tesseract, and yt-dlp error messages
- Add `youtube_visual_frame_cue` translation key across locales
2026-04-05 15:22:28 -07:00
Kayvan Sylvan 428df31486 docs: add visual extraction options for YouTube videos to Chinese README
- Add `--visual` OCR and FFmpeg extraction flag documentation
- Add `--visual-sensitivity` scene detection tolerance option
- Add `--visual-fps` fixed frame rate extraction option
- Document visual options in Chinese localized README
2026-04-05 15:00:25 -07:00
Kayvan Sylvan d1217ae0fd chore: incoming 2073 changelog entry 2026-04-05 09:18:55 -07:00
Kayvan Sylvan aeb709e7e8 Merge branch 'main' into feat/youtube-visual-extraction 2026-04-05 09:17:24 -07:00
Kayvan Sylvan 6115342314 chore: add maestro artifacts to gitignore 2026-04-05 08:52:44 -07:00
Kayvan Sylvan dd461857ab
Merge pull request #2078 from Ocheretovich/patch-1
docs: make README badges clickable
2026-04-05 07:00:22 -07:00
Since Today ae8b910ef7 Add three commerce intelligence patterns for creator monetization
Patterns added:
- extract_affiliate_products: extracts sponsored + organic affiliate opportunities from any transcript
- extract_video_commerce_entities: identifies all commercial entities in video content by category, timestamp position, and purchase likelihood
- analyze_monetization_opportunities: maps audience intent to revenue opportunities (affiliate, sponsorship, digital products)

These fill a gap in the existing extract_sponsors pattern — sponsors are just one slice; organic product mentions often convert better and these patterns surface both.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 10:00:56 +02:00
Ocheretovich 8ae3342bf3
docs: make README badges clickable 2026-04-03 08:39:48 +03:00
dependabot[bot] 8b730d1967
chore(deps): bump github.com/go-git/go-git/v5
Bumps the go_modules group with 1 update in the / directory: [github.com/go-git/go-git/v5](https://github.com/go-git/go-git).


Updates `github.com/go-git/go-git/v5` from 5.17.0 to 5.17.1
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/compare/v5.17.0...v5.17.1)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-version: 5.17.1
  dependency-type: direct:production
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 17:16:33 +00:00
Sathvik C 631d02cc27 style(youtube): remove unnecessary blank lines in GrabVisual function 2026-03-27 08:07:10 -05:00
dependabot[bot] 704ef832ca
chore(deps): bump the npm_and_yarn group across 1 directory with 2 updates
Bumps the npm_and_yarn group with 1 update in the /web directory: [yaml](https://github.com/eemeli/yaml).


Updates `yaml` from 2.8.2 to 2.8.3
- [Release notes](https://github.com/eemeli/yaml/releases)
- [Commits](https://github.com/eemeli/yaml/compare/v2.8.2...v2.8.3)

Updates `yaml` from 1.10.2 to 1.10.3
- [Release notes](https://github.com/eemeli/yaml/releases)
- [Commits](https://github.com/eemeli/yaml/compare/v2.8.2...v2.8.3)

Updates `flatted` from 3.4.1 to 3.4.2
- [Commits](https://github.com/WebReflection/flatted/compare/v3.4.1...v3.4.2)

---
updated-dependencies:
- dependency-name: yaml
  dependency-version: 2.8.3
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: yaml
  dependency-version: 1.10.3
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-26 14:03:37 +00:00
github-actions[bot] 83b77a59bd chore(release): Update version to v1.4.442 2026-03-25 23:59:29 +00:00
Kayvan Sylvan 294954d7e6
Merge pull request #2075 from ksylvan/kayvan/pr-2063
refactor: extract OAuth and auth logic from Codex client module

Thanks @mikaelpr for the initial work on this.
2026-03-25 16:57:21 -07:00
Kayvan Sylvan c271816d2d feat: add i18n translations for Codex OAuth and error messages
- Add 17 new Codex-related i18n keys across all locale files
- Replace hardcoded English strings in OAuth flow with i18n lookups
- Internationalize Codex error messages in `errors.go`
- Localize token exchange and refresh failure messages
- Translate OAuth callback server responses and browser fallback text
- Add translations for usage limit and request status errors
- Cover DE, EN, ES, FA, FR, IT, JA, PL, PT-BR, PT-PT, ZH locales
2026-03-25 16:49:12 -07:00
Kayvan Sylvan 2e0abdd78a refactor: propagate context.Context through Vendor interface methods
- Add `context.Context` parameter to `ListModels` interface method
- Add `context.Context` parameter to `SendStream` interface method
- Thread caller context through `Chatter.Send` instead of using `context.Background()`
- Update all vendor implementations to accept context parameter
- Introduce `publicError` type wrapping Codex provider errors
- Normalize Codex error messages to lowercase for consistency
- Add context-aware `sendStreamUpdate` helper in Codex client
- Remove stale `context.Background()` calls from Anthropic and Azure AI Gateway
- Update all vendor test mocks and stubs with new signatures
- Pass HTTP request context from server handler into `chatter.Send`
2026-03-25 16:09:10 -07:00
Kayvan Sylvan 070c626b7b refactor: extract OAuth and auth logic from Codex client module
- Remove OAuth flow, PKCE, and token refresh from codex.go
- Remove auth transport round-trip retry logic
- Remove unused OAuth types and helper structs
- Remove JWT parsing and token expiry utilities
- Remove error mapping and usage limit detection helpers
- Remove browser-open and version normalization functions
- Add `.maestro/` directory to `.gitignore`
- Add test for `SendStream` HTTP error mapping and channel close
- Clean up unused imports from codex client package
2026-03-25 15:32:40 -07:00
Sathvik C f812188115 fix(youtube): resolve tesseract CLI args, racy error handling, timestamp overflow, and import ordering 2026-03-24 14:04:20 -05:00
Sathvik C 8aa7a703dc refactor(youtube): implement bounded OCR concurrency, context timeouts, and CLI argument security 2026-03-24 13:35:10 -05:00
Sathvik C f44d44b077 docs(youtube): document visual extraction flags in README 2026-03-24 13:24:08 -05:00
Sathvik C e42d8b8fec feat(youtube): make visual extraction parameters configurable via CLI flags 2026-03-24 13:21:22 -05:00
Sathvik C 8368ecf35f fix(youtube): support multi-line yt-dlp outputs and modern ffmpeg syntax 2026-03-24 13:19:29 -05:00
Sathvik C 086e196ade feat(youtube): implement FFmpeg and Tesseract visual extraction 2026-03-24 12:06:57 -05:00
186 changed files with 15428 additions and 7291 deletions

3
.gitignore vendored
View file

@ -327,3 +327,6 @@ tmp/
.claude/
# Claude MEMORY directory
MEMORY/
# Maestro artifacts
.maestro

View file

@ -8,10 +8,13 @@
"anthropics",
"Aoede",
"apikey",
"APIM",
"aplicar",
"appuser",
"Astley",
"atotto",
"Autonoe",
"azurecommon",
"azureml",
"badfile",
"Behrens",
@ -131,6 +134,7 @@
"modelines",
"Moltbot",
"mpga",
"MSAL",
"mvdan",
"mychat",
"mygroup",
@ -222,6 +226,7 @@
"cSpell.ignorePaths": [
"go.mod",
".gitignore",
".vscode/**",
"CHANGELOG.md",
"scripts/installer/install.*",
"web/static/data/pattern_descriptions.json",
@ -255,6 +260,7 @@
"module",
"p",
"summary",
"strong",
"sup"
]
},

View file

@ -1,5 +1,387 @@
# Changelog
## v1.4.478 (2026-09-06)
### PR [#2216](https://github.com/danielmiessler/Fabric/pull/2216) by [ctbaum](https://github.com/ctbaum): fix: enable raw mode for GPT-6 models
- Fix: enable raw mode for GPT-6 models
## v1.4.477 (2026-09-03)
### PR [#2211](https://github.com/danielmiessler/Fabric/pull/2211) by [ksylvan](https://github.com/ksylvan): fix: prevent pattern loader temporary directory leaks
- Prevent pattern loader leaks by lazily creating temporary directories during database population and cleaning them up after successful or failed downloads.
- Add regression tests for lazy directory creation and cleanup.
## v1.4.476 (2026-09-03)
### PR [#2210](https://github.com/danielmiessler/Fabric/pull/2210) by [ksylvan](https://github.com/ksylvan): feat: add Pzero as an OpenAI-compatible AI provider
- Added Pzero as an OpenAI-compatible AI provider.
- Registered Pzero with its OpenAI-compatible API base URL.
- Added Pzero to the READMEs list of supported AI providers.
## v1.4.475 (2026-09-03)
### PR [#2209](https://github.com/danielmiessler/Fabric/pull/2209) by [kadiryildiz283](https://github.com/kadiryildiz283): feat(i18n): add Turkish (tr) translation
- Feat(i18n): add Turkish (tr) translation
## v1.4.474 (2026-09-02)
### PR [#2206](https://github.com/danielmiessler/Fabric/pull/2206) by [ksylvan](https://github.com/ksylvan): fix: confine storage names and authenticate Ollama serve
- Reject unsafe cross-platform storage names and directory traversal attempts.
- Confine symlink targets to configured filesystem storage directories.
- Require API keys for non-loopback server bindings and authenticate Ollama routes.
- Validate chat pattern, context, and session names early while preventing internal filesystem details from leaking through client errors.
- Default the REST server to loopback port 8080 and add regression coverage for traversal, symlink, and authentication security.
## v1.4.473 (2026-08-29)
### PR [#2203](https://github.com/danielmiessler/Fabric/pull/2203) by [pacocartones](https://github.com/pacocartones) and [ksylvan](https://github.com/ksylvan): fix(web): preserve multi-byte UTF-8 split across SSE chunks in chat stream
- Fix: preserve split UTF-8 characters in streaming responses
- Decode API response chunks with persistent streaming state
- Reuse streaming decoder when inspecting chat backend events
## v1.4.472 (2026-08-29)
### PR [#2198](https://github.com/danielmiessler/Fabric/pull/2198) by [cuihuan](https://github.com/cuihuan): feat(providers): add Synthorai as an OpenAI-compatible provider
- Feat(providers): add Synthorai as an OpenAI-compatible provider
### Direct commits
- Chore: delete generate_changelog noise in README.
## v1.4.471 (2026-08-28)
### PR [#2200](https://github.com/danielmiessler/Fabric/pull/2200) by [ksylvan](https://github.com/ksylvan): fix: persist Codex OAuth tokens after refresh
- Persist refreshed Codex OAuth tokens securely across processes, writing rotated credentials atomically with cross-process locks.
- Reload stored tokens before refresh and return valid in-memory tokens first, preventing stale disk values from overwriting fresh credentials.
- Reuse valid refresh tokens before launching interactive authentication, requiring account identifiers and unexpired tokens for fast-path reuse.
- Preserve existing environment values, save environment files atomically while holding locks, and enforce secret-only permissions.
- Reactivate configured vendors and surface configuration and provider failures immediately, with localized Codex prompts and persistence errors.
## v1.4.470 (2026-08-04)
### PR [#2186](https://github.com/danielmiessler/Fabric/pull/2186) by [giodamelio](https://github.com/giodamelio): Update Nixpkgs version for newer Go version
- Updated the Nixpkgs version to provide a Go release that satisfies the `go.mod` requirement of Go >= 1.26.0, as the previously pinned Nixpkgs only shipped Go 1.25.4.
## v1.4.469 (2026-08-03)
### PR [#2185](https://github.com/danielmiessler/Fabric/pull/2185) by [ksylvan](https://github.com/ksylvan): feat(web): replace PDF.js pipeline with pdf-inspector WASM worker
- Replaced the PDF.js processing pipeline with the new `pdf-inspector.worker.ts`, backed by `@firecrawl/pdf-inspector-wasm`. The worker initializes WASM one time and processes transferred ArrayBuffers.
- Added worker error handling. A worker crash now rejects all pending requests with a clear error and terminates the worker. The next request starts a new worker.
- Fixed the file attachment behavior. The app now parses each file and stores its content without a chat request. One submit sends exactly one `streamChat` call, not one call for each attached file.
- Removed the legacy PDF dependency chain: `pdf-to-markdown-core`, `pdfjs-dist`, `pdf-config.ts`, and the related build configuration. `@firecrawl/pdf-inspector-wasm` is now the only PDF dependency.
- Simplified the worker boundary in follow-up refactors: merged redundant methods, removed dead code, and inlined a single-use interface. The full test suite continued to pass.
## v1.4.468 (2026-08-02)
### PR [#2182](https://github.com/danielmiessler/Fabric/pull/2182) by [drawliin](https://github.com/drawliin): fix(ollama): close stream channel on errors
- Fix(ollama): close stream channel on errors
## v1.4.467 (2026-07-31)
### PR [#2171](https://github.com/danielmiessler/Fabric/pull/2171) by [OdinKral](https://github.com/OdinKral): feat(scripts): add pattern/maintenance audit script
- Adds `scripts/audit-patterns.sh`, a dependency-free audit tool for the patterns library that detects thin patterns (under 15 lines), bloated patterns (over 50 KB), stale hardcoded model references, missing `INPUT` sections, i18n key gaps, and shell completion gaps. The script is read-only and exits with code `0` by default, or `1` when run with `--strict` for CI integration.
## v1.4.466 (2026-07-30)
### PR [#2116](https://github.com/danielmiessler/Fabric/pull/2116) by [ksylvan](https://github.com/ksylvan) and [dependabot](https://github.com/apps/dependabot): fix(web): repair the chat page and modernize the build toolchain
- Modernized the web stack by upgrading Tailwind, Skeleton, SvelteKit, Vite, and supporting dependencies.
- Replaced removed Skeleton components with compatible local implementations to restore UI functionality.
- Improved pre-stream chat error reporting to prevent duplication of streamed errors, and hardened vendor list parsing to return empty model arrays on malformed input.
- Fixed stream completion crashes, normalized displayed error messages, corrected toast stacking order, and added warning notification support.
- Restored linting, formatting, testing, and pnpm override configuration, and preserved custom themes through the Tailwind CSS-based configuration migration.
### PR [#2178](https://github.com/danielmiessler/Fabric/pull/2178) by [OdinKral](https://github.com/OdinKral) and [ksylvan](https://github.com/ksylvan): feat(patterns): add generate_frontmatter for PKM/Obsidian users
- Add `generate_frontmatter` to conversion, extraction, and writing categories
- Document PKM-ready YAML metadata fields in pattern explanations
- Register pattern descriptions and extracts for suggestion workflows
- Credit both contributors in incoming changelog entry
## v1.4.465 (2026-07-29)
### PR [#2180](https://github.com/danielmiessler/Fabric/pull/2180) by [ksylvan](https://github.com/ksylvan): fix: include Grok in localized --search help text
- Fix: Updated all 11 locale entries in the i18n message catalog to include "Grok" in the `enable_web_search_tool` help text, ensuring Grok (xAI) web search support is discoverable via `fabric --help`. Also updates the generated README help block to match. No functional change.
## v1.4.464 (2026-07-29)
### PR [#2179](https://github.com/danielmiessler/Fabric/pull/2179) by [ksylvan](https://github.com/ksylvan): fix: declare completion arguments and synchronize CLI help
- Fix: declare completion arguments and synchronize CLI help
- Declare Fish completion arguments for dynamic and fixed values
- Enable filtered file suggestions for supported path options
- Add Spotify, transcription, metadata, and wire-debug completion options
- Include Grok among providers supporting web search completion
- Keep the input/output token detail in the show-metadata description
## v1.4.463 (2026-07-28)
### PR [#2168](https://github.com/danielmiessler/Fabric/pull/2168) by [ksylvan](https://github.com/ksylvan): fix: complete localized setup and error messages for supported locales
- Fix: complete localized setup and error messages across supported locales
- Translate Bedrock setup prompts across nine supported locales
- Localize datetime and system template errors consistently
- Translate Persian Spotify errors and setup guidance
- Correct Japanese and Polish file operation log labels
## v1.4.462 (2026-07-28)
### PR [#2123](https://github.com/danielmiessler/Fabric/pull/2123) by [ksylvan](https://github.com/ksylvan) and [OdinKral](https://github.com/OdinKral): fix: block path traversal in pattern name lookup
- **Security Fix:** Blocked path traversal attacks in pattern name lookup (closes #2094) — pattern names containing `..` could previously escape the patterns directory and read arbitrary files via `filepath.Join`; a guard has been added at the top of `getFromDB`, an i18n key `pattern_invalid_name` has been added to all 11 locale files, and test cases now cover all common traversal variants.
- New translations for the "invalid pattern" user-facing string.
## v1.4.461 (2026-07-28)
### PR [#2152](https://github.com/danielmiessler/Fabric/pull/2152) by [AUTHENSOR](https://github.com/AUTHENSOR): fix: shell-escape extension values to prevent command injection
- **Fix:** Shell-escape extension values to prevent command injection in the extension executor, which previously ran commands via `sh -c` with unescaped, user-controlled values interpolated into the command string. All user-controlled values are now wrapped in single quotes with embedded-single-quote escaping prior to interpolation, ensuring the shell treats them as literal arguments. A regression test (`ShellInjectionBlocked`) has been added to verify that malicious input (e.g., `hello; touch /marker`) does not execute unintended shell commands.
## v1.4.460 (2026-07-24)
### PR [#2166](https://github.com/danielmiessler/Fabric/pull/2166) by [ksylvan](https://github.com/ksylvan): feat: add Claude Opus 5 support and refresh dependencies
- Add Claude Opus 5 to the supported model selection.
- Disable sampling parameters for Claude Opus 5 requests.
- Restrict one-million-token beta headers to compatible Claude models only.
- Remove unsupported 200K-context models from the beta header mapping.
- Upgrade Anthropic, AWS, Ollama, Google, and supporting dependencies.
## v1.4.459 (2026-07-16)
### PR [#2135](https://github.com/danielmiessler/Fabric/pull/2135) by [octo-patch](https://github.com/octo-patch): feat: upgrade MiniMax default model to M3
- Upgraded the MiniMax default model to M3, making it the new flagship selection.
- Added MiniMax-M3 to the static model list as the first entry, establishing it as the default.
- Retained MiniMax-M2.7 and MiniMax-M2.7-highspeed as available alternative models.
- Removed deprecated models (M2.5, M2.5-highspeed, M2.5-lightning, M2, M2.1, and M2.1-lightning) from the static list.
## v1.4.458 (2026-07-12)
### PR [#2161](https://github.com/danielmiessler/Fabric/pull/2161) by [ksylvan](https://github.com/ksylvan): fix: respect Anthropic chat option max token overrides
- Fix: respect Anthropic chat option max token overrides
- Use configured Anthropic max tokens as default
- Apply chat option max tokens when provided
- Preserve existing behavior for missing token overrides
- Add tests for default max token selection
- Add tests for explicit max token overrides
### Direct commits
- Chore: clean up ChangeLog
## v1.4.457 (2026-07-09)
### PR [#2155](https://github.com/danielmiessler/Fabric/pull/2155) by [ksylvan](https://github.com/ksylvan): Claude Sonnet 5 Anthropic support
- Add Claude Sonnet 5 to supported Anthropic models
- Enable one-million-token context beta for Claude 5 models
- Omit sampling parameters for Claude Sonnet 5 requests
- Centralize Anthropic sampling restrictions behind prefix matching
- Remove older Claude 4 aliases from model listings
### PR [#2156](https://github.com/danielmiessler/Fabric/pull/2156) by [ksylvan](https://github.com/ksylvan): Make it possible to back-fill missing ChangeLog entries
- Add support for changelog generation for closed pull requests via a new `--closed-ok` flag, bypassing open-state validation.
- Skip mergeability checks when processing closed pull requests to allow smooth back-filling of missing entries.
- Store the closed pull request allowance setting in the generator configuration for consistent behavior.
- Improve error messaging to guide users toward using `--closed-ok` when validation errors occur on closed PRs.
- Introduce the `--closed-ok` flag as the primary mechanism for enabling back-fill workflows on previously closed pull requests.
## v1.4.455 (2026-06-09)
### PR [#2138](https://github.com/danielmiessler/Fabric/pull/2138) by [ksylvan](https://github.com/ksylvan): New Claude Fable model + cache OpenAI model discovery and handle provider rate limits
- Add persistent cache for provider model discovery results, improving performance and reliability of provider integrations.
- Serve stale model caches during discovery failures, ensuring continued operation when upstream providers are unavailable.
- Add Claude Fable 5 Anthropic model support, with sampling parameters automatically omitted for compatibility.
- Return concise localized errors for rate-limited model fetches, with updated translations across all supported locales.
- Update Go dependencies for AI provider integrations to keep upstream libraries current.
## v1.4.454 (2026-06-02)
### PR [#2136](https://github.com/danielmiessler/Fabric/pull/2136) by [ksylvan](https://github.com/ksylvan): chore: extend sampling param exclusion to Opus 4.8 models
- Extends the sampling parameter exclusion logic to cover Opus 4.8 models, ensuring consistent behavior alongside the existing Opus 4.7 exclusion.
- Adds the `claude-opus-4-8` model prefix to the sampling parameter exclusion check.
- Updates the associated code comment to explicitly reference Opus 4.8 models.
## v1.4.453 (2026-05-28)
### PR [#2132](https://github.com/danielmiessler/Fabric/pull/2132) by [ksylvan](https://github.com/ksylvan): Add Claude Opus 4.8 model and bump Go toolchain and dependencies
- Add Claude Opus 4.8 to the list of supported models.
- Upgrade the Go toolchain to version 1.26.0.
- Bump `anthropic-sdk-go` to v1.46.0.
- Update AWS SDK and Bedrock service modules to their latest versions.
- Bump the Ollama client to v0.24.0.
## v1.4.452 (2026-05-04)
### PR [#2111](https://github.com/danielmiessler/Fabric/pull/2111) by [ksylvan](https://github.com/ksylvan): fix: omit Anthropic sampling params for Claude Opus 4.7
- Fix: Omit Anthropic sampling parameters for Claude Opus 4.7 to ensure compatibility.
- Add a sampling parameter guard specifically for Opus 4.7.
- Omit `temperature` and `top_p` for models that do not support these parameters.
- Preserve existing `TopP` and temperature selection behavior for compatible models.
- Add unit test coverage for the Opus 4.7 sampling parameter omission.
## v1.4.451 (2026-04-23)
### PR [#2079](https://github.com/danielmiessler/Fabric/pull/2079) by [teamsincetoday](https://github.com/teamsincetoday): Add 3 commerce intelligence patterns: affiliate extraction, video entities, monetization
- Added `extract_affiliate_products` pattern to surface both sponsored and organic affiliate opportunities from any video transcript, going beyond the existing `extract_sponsors` pattern.
- Added `extract_video_commerce_entities` pattern to identify all commercial entities in video content, categorized by type, timestamp position, and purchase likelihood.
- Added `analyze_monetization_opportunities` pattern to map audience intent to revenue strategies, covering affiliate links, sponsorships, and digital products.
- Registered all three new patterns in `pattern_descriptions.json` and `pattern_extracts.json` with appropriate metadata and tags.
- Integrated all three patterns into `suggest_pattern` under the ANALYSIS, BUSINESS, and EXTRACT categories, and documented them in `pattern_explanations.md`.
### PR [#2086](https://github.com/danielmiessler/Fabric/pull/2086) by [majiayu000](https://github.com/majiayu000): fix: parse vendor prefix from model name for vendor/model convention
- **Fix:** Added fallback logic to parse the vendor prefix from a model name when no vendor is explicitly specified. When a model string such as `ollama/llama3` is passed, the lookup no longer fails with a "could not find vendor" error; instead, the first path segment is split and checked against known vendors, correctly resolving the model to `llama3` under the `Ollama` vendor group.
## v1.4.450 (2026-04-23)
### PR [#2092](https://github.com/danielmiessler/Fabric/pull/2092) by [Resistor52](https://github.com/Resistor52): feat(openai): add GrokAI search grounding via xAI Responses API
- Added support for GrokAI search grounding via xAI's Responses API, fixing HTTP 422 errors caused by a hardcoded OpenAI `web_search_preview` tool name in `buildResponseParams`.
- Introduced two new fields to `openai_compatible.ProviderConfig`: `WebSearchToolName` (to override the default web search tool name) and `EnableXSearch` (to append xAI's `x_search` tool when search is enabled).
- Both new fields default to empty/false, preserving full backwards compatibility for all existing providers.
- GrokAI is pre-configured with `WebSearchToolName` set to `"web_search"` and `EnableXSearch` set to `true`, enabling grounded search results with real source URLs via `fabric -V GrokAI --search`.
- Added tests in `openai_test.go` covering the new override paths and confirming default provider behavior remains unchanged.
## v1.4.449 (2026-04-23)
### PR [#2089](https://github.com/danielmiessler/Fabric/pull/2089) by [dependabot](https://github.com/apps/dependabot) and [ksylvan](https://github.com/ksylvan): chore(deps-dev): bump vite from 5.4.21 to 8.0.8 in /web in the npm_and_yarn group across 1 directory
- Chore(deps-dev): bump vite
Bumps the npm_and_yarn group with 1 update in the /web directory: [vite](<https://github.com/vitejs/vite/tree/HEAD/packages/vite).>
Updates `vite` from 5.4.21 to 8.0.8
- [Release notes](<https://github.com/vitejs/vite/releases)>
- [Changelog](<https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)>
- [Commits](<https://github.com/vitejs/vite/commits/v8.0.8/packages/vite)>
updated-dependencies:
- dependency-name: vite
dependency-version: 8.0.8
dependency-type: direct:development
dependency-group: npm_and_yarn
Signed-off-by: dependabot[bot] <support@github.com>
### PR [#2103](https://github.com/danielmiessler/Fabric/pull/2103) by [dependabot](https://github.com/apps/dependabot) and [ksylvan](https://github.com/ksylvan): chore(deps): bump github.com/go-git/go-git/v5 from 5.17.2 to 5.18.0 in the go_modules group across 1 directory
- Upgraded Vite from 5.4.21 to 8.0.8 in the web layer, representing a significant major-version jump with potential performance and build tooling improvements.
- Bumped `@sveltejs/vite-plugin-svelte` from 4.0.4 to 7.0.0, a major version update aligning the Svelte plugin with the upgraded Vite 8 runtime.
- Updated AWS SDK Go v2 modules to their latest patches, ensuring up-to-date cloud integration support and security fixes.
- Updated the Ollama client from 0.20.4 to 0.21.1, keeping local AI model support current with the latest client improvements.
- Upgraded `go-git` from 5.17.2 to 5.18.0, incorporating the latest fixes and improvements to Git operations within the Go module ecosystem.
### Direct commits
- Docs: add Scoop install instructions and expand cSpell dictionary
- Add Windows Scoop install section to Chinese README
- Link Scoop install entry in both README tables of contents
- Extend cSpell dictionary with APIM, MSAL, and related terms
- Ignore `.vscode/**` paths during cSpell checks
- Allow `strong` tag in cSpell markdown configuration
- Docs: add Scoop installation instructions
## v1.4.447 (2026-04-17)
### PR [#2097](https://github.com/danielmiessler/Fabric/pull/2097) by [ksylvan](https://github.com/ksylvan): Add Claude Opus 4.7 model support and bump Anthropic SDK to v1.37.0
- Added Claude Opus 4.7 model support and bumped the Anthropic SDK to v1.37.0.
- Upgraded the `anthropic-sdk-go` dependency from v1.34.0 to v1.37.0.
- Added `claude-opus-4-7` to the supported models list.
- Enabled the 1M context window beta feature for Opus 4.7.
- Updated model beta comments to reflect Opus 4.7 support.
## v1.4.446 (2026-04-15)
### PR [#2093](https://github.com/danielmiessler/Fabric/pull/2093) by [alecjmckanna](https://github.com/alecjmckanna): feat: add --readpattern flag to print pattern contents to terminal
- Adds a new `--readpattern <name>` CLI flag that prints the raw contents of a named pattern's `system.md` file to stdout, making it easy to inspect a pattern's instructions without navigating the filesystem manually.
- Custom pattern directories are respected: the user's custom patterns directory is checked first before falling back to the main patterns directory, consistent with all other pattern lookups.
### Direct commits
- Docs: update Docker config mount path for appuser
- Replace container config mount path from root to appuser
- Align setup example with non-root container home directory
- Align pattern usage example with appuser config location
- Align REST API example with updated config mount target
- Update English and Chinese README Docker instructions consistently
## v1.4.445 (2026-04-13)
### PR [#2091](https://github.com/danielmiessler/Fabric/pull/2091) by [jimscard](https://github.com/jimscard) and [ksylvan](https://github.com/ksylvan): Update Dockerfile for best practices and critical CVE fixes
- Pins Alpine 3.21 and Go 1.25.9 explicitly for reproducible, auditable builds.
- Installs the Go toolchain directly in the builder stage, removing the dependency on an unavailable upstream `golang` tag.
- Upgrades `setuptools` to remediate the critical vulnerability CVE-2025-47273.
- Refreshes the `yt-dlp` installation path to align with current packaging conventions.
- Configures the final image to run as a non-root user, reducing the container's attack surface.
## v1.4.444 (2026-04-09)
### PR [#2088](https://github.com/danielmiessler/Fabric/pull/2088) by [ksylvan](https://github.com/ksylvan): Combined dependabot fixes plus other Go module upgrades
- Upgraded `anthropic-sdk-go` from v1.27.1 to v1.34.0, bringing in several versions of improvements and fixes from the Anthropic Go SDK.
- Upgraded `ollama` from v0.18.2 to v0.20.4, incorporating two minor version bumps of enhancements to the Ollama client library.
- Upgraded `google.golang.org/grpc` from v1.79.3 to v1.80.0, picking up the latest gRPC release for Go.
- Upgraded `google.golang.org/genai` from v1.51.0 to v1.53.0 and bumped `google.golang.org/api` from v0.272.0 to v0.275.0, keeping Google AI and API client libraries current.
- Bumped `go-sqlite3` from v1.14.37 to v1.14.42, updated `go-git/v5` from v5.17.0 to v5.17.2, and refreshed `golang.org/x` packages (crypto, net, sys, text, mod) and OpenTelemetry packages from v1.42.0 to v1.43.0 alongside AWS SDK v2 patch releases.
## v1.4.443 (2026-04-06)
### PR [#2073](https://github.com/danielmiessler/Fabric/pull/2073) by [sathvikc](https://github.com/sathvikc) and [ksylvan](https://github.com/ksylvan): feat(youtube): Implement visual text extraction via FFmpeg and OCR
- Implemented FFmpeg and Tesseract-based visual text extraction from YouTube videos, enabling OCR on video frames.
- Added configurable CLI flags for visual extraction parameters, giving users fine-grained control over the feature.
- Fixed support for multi-line `yt-dlp` outputs and updated syntax compatibility with modern FFmpeg versions.
- Refactored OCR processing to use bounded concurrency, context timeouts, and hardened CLI argument handling for improved stability and security.
- Resolved multiple reliability issues including Tesseract CLI argument handling, racy error handling, and timestamp overflow bugs.
### Direct commits
- Docs: make README badges clickable
## v1.4.442 (2026-03-25)
### PR [#2075](https://github.com/danielmiessler/Fabric/pull/2075) by [ksylvan](https://github.com/ksylvan) and [mikaelpr](https://github.com/mikaelpr): refactor: extract OAuth and auth logic from Codex client module
- Refactored the Codex client module by extracting all OAuth and authentication logic into a dedicated module, improving separation of concerns.
- Removed the OAuth flow, PKCE handling, and token refresh logic from `codex.go`, streamlining the client's core responsibilities.
- Removed the auth transport round-trip retry logic, simplifying the HTTP transport layer.
- Removed JWT parsing and token expiry utilities, along with unused OAuth types and helper structs, reducing dead code in the package.
- Added a test for `SendStream` HTTP error mapping and channel close behavior, improving test coverage for the client module.
## v1.4.441 (2026-03-22)
### PR [#2068](https://github.com/danielmiessler/Fabric/pull/2068) by [dependabot](https://github.com/apps/dependabot) and [ksylvan](https://github.com/ksylvan): chore(deps): bump google.golang.org/grpc from 1.79.0 to 1.79.3 in the go_modules group across 1 directory

View file

@ -18,10 +18,10 @@
# `fabric`
![Static Badge](https://img.shields.io/badge/mission-human_flourishing_via_AI_augmentation-purple)
[![Static Badge](https://img.shields.io/badge/mission-human_flourishing_via_AI_augmentation-purple)](https://github.com/danielmiessler/fabric)
<br />
![GitHub top language](https://img.shields.io/github/languages/top/danielmiessler/fabric)
![GitHub last commit](https://img.shields.io/github/last-commit/danielmiessler/fabric)
[![GitHub top language](https://img.shields.io/github/languages/top/danielmiessler/fabric)](https://github.com/danielmiessler/fabric)
[![GitHub last commit](https://img.shields.io/github/last-commit/danielmiessler/fabric)](https://github.com/danielmiessler/fabric/commits/main)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/danielmiessler/fabric)
@ -82,6 +82,7 @@ Below are the **new features and capabilities** we've added (newest first):
### Recent Major Features
- [v1.4.447](https://github.com/danielmiessler/fabric/releases/tag/v1.4.447) (April 16, 2026) — **Claude Opus 4.7**: Updates the Anthropic SDK to v1.37.0 and adds the new [Claude Opus 4.7](https://www.anthropic.com/news/claude-opus-4-7) to the available models, including 1M-token context window support.
- [v1.4.437](https://github.com/danielmiessler/fabric/releases/tag/v1.4.437) (March 16, 2026) — **OpenAI Codex PLugin**: Fabric now supports using OpenAI Codex (with your OpenAI subscription) as a backend!
- [v1.4.417](https://github.com/danielmiessler/fabric/releases/tag/v1.4.417) (Feb 21, 2026) — **Azure AI Gateway Plugin**: Added Azure AI Gateway plugin supporting multiple backends (AWS Bedrock, Azure OpenAI, Google Vertex AI) through a unified Azure APIM Gateway with shared subscription key authentication.
- [v1.4.416](https://github.com/danielmiessler/fabric/releases/tag/v1.4.416) (Feb 21, 2026) — **Azure Entra ID Authentication**: Added Azure Entra ID authentication plugin with shared Azure utilities, Entra ID/MSAL support, and extracted common Azure logic into a reusable `azurecommon` package.
@ -136,6 +137,7 @@ Keep in mind that many of these were recorded when Fabric was Python-based, so r
- [macOS (Homebrew)](#macos-homebrew)
- [Arch Linux (AUR)](#arch-linux-aur)
- [Windows](#windows)
- [Windows (Scoop)](#windows-scoop)
- [From Source](#from-source)
- [Docker](#docker)
- [Environment Variables](#environment-variables)
@ -262,6 +264,10 @@ Use the official Microsoft supported `Winget` tool:
`winget install danielmiessler.Fabric`
#### Windows (Scoop)
`scoop install fabric-ai`
### From Source
To install Fabric, [make sure Go is installed](https://go.dev/doc/install), and then run the following command.
@ -284,13 +290,13 @@ docker run --rm -it ghcr.io/ksylvan/fabric:v1.4.305 --version
# Run setup (first time)
mkdir -p $HOME/.fabric-config
docker run --rm -it -v $HOME/.fabric-config:/root/.config/fabric kayvan/fabric:latest --setup
docker run --rm -it -v $HOME/.fabric-config:/home/appuser/.config/fabric kayvan/fabric:latest --setup
# Use Fabric with your patterns
docker run --rm -it -v $HOME/.fabric-config:/root/.config/fabric kayvan/fabric:latest -p summarize
docker run --rm -it -v $HOME/.fabric-config:/home/appuser/.config/fabric kayvan/fabric:latest -p summarize
# Run the REST API server (see REST API Server section)
docker run --rm -it -p 8080:8080 -v $HOME/.fabric-config:/root/.config/fabric kayvan/fabric:latest --serve
docker run --rm -it -p 8080:8080 -v $HOME/.fabric-config:/home/appuser/.config/fabric kayvan/fabric:latest --serve
```
**Images available at:**
@ -368,7 +374,9 @@ Fabric supports a wide range of AI providers:
- Mistral
- Novita AI
- OpenRouter
- Pzero
- SiliconCloud
- Synthorai
- Together
- Venice AI
- Z AI
@ -658,34 +666,41 @@ Application Options:
-T, --topp= Set top P (default: 0.9)
-s, --stream Stream
-P, --presencepenalty= Set presence penalty (default: 0.0)
-r, --raw Use the defaults of the model without sending chat options
(temperature, top_p, etc.). Only affects OpenAI-compatible providers.
Anthropic models always use smart parameter selection to comply with
model-specific requirements.
-r, --raw Use the defaults of the model without sending chat options (temperature,
top_p, etc.). Only affects OpenAI-compatible providers. Anthropic models
always use smart parameter selection to comply with model-specific
requirements.
-F, --frequencypenalty= Set frequency penalty (default: 0.0)
-l, --listpatterns List all patterns
--readpattern= Print the contents of the named pattern to the terminal
-L, --listmodels List all available models
-x, --listcontexts List all contexts
-X, --listsessions List all sessions
-U, --updatepatterns Update patterns
-c, --copy Copy to clipboard
-m, --model= Choose model
-V, --vendor= Specify vendor for chosen model (e.g., -V "LM Studio" -m openai/gpt-oss-20b)
-V, --vendor= Specify vendor for the selected model (e.g., -V "LM Studio" -m
openai/gpt-oss-20b)
--modelContextLength= Model context length (only affects ollama)
-o, --output= Output to file
--output-session Output the entire session (also a temporary one) to the output file
-n, --latest= Number of latest patterns to list (default: 0)
-n, --latest= Number of latest patterns to list
-d, --changeDefaultModel Change default model
-y, --youtube= YouTube video or play list "URL" to grab transcript, comments from it
and send to chat or print it put to the console and store it in the
output file
-y, --youtube= YouTube video or play list "URL" to grab transcript, comments from it and
send to chat or print it put to the console and store it in the output file
--playlist Prefer playlist over video if both ids are present in the URL
--transcript Grab transcript from YouTube video and send to chat (it is used per
default).
--transcript-with-timestamps Grab transcript from YouTube video with timestamps and send to chat
--visual Extract visual data from video using OCR and FFmpeg
--visual-sensitivity= Tolerance for FFmpeg scene detection (0.0 - 1.0) (default: 0.4)
--visual-fps= Extract a specific number of frames per second instead of using scene
detection
--comments Grab comments from YouTube video and send to chat
--metadata Output video metadata
-g, --language= Specify the Language Code for the chat, e.g. -g=en -g=zh
--yt-dlp-args= Additional arguments to pass to yt-dlp (e.g. '--cookies-from-browser brave')
--spotify= Spotify podcast or episode URL to grab metadata from and send to chat
-g, --language= Specify the Language Code for the chat, e.g. -g=en -g=zh -g=pt-BR -g=pt-PT
-u, --scrape_url= Scrape website URL to markdown using Jina AI
-q, --scrape_question= Search question using Jina AI
-e, --seed= Seed to be used for LMM generation
@ -710,29 +725,31 @@ Application Options:
--liststrategies List all strategies
--listvendors List all vendors
--shell-complete-list Output raw list without headers/formatting (for shell completion)
--search Enable web search tool for supported models (Anthropic, OpenAI, Gemini)
--search Enable web search tool for supported models (Anthropic, OpenAI, Gemini, Grok)
--search-location= Set location for web search results (e.g., 'America/Los_Angeles')
--image-file= Save generated image to specified file path (e.g., 'output.png')
--image-size= Image dimensions: 1024x1024, 1536x1024, 1024x1536, auto (default: auto)
--image-quality= Image quality: low, medium, high, auto (default: auto)
--image-compression= Compression level 0-100 for JPEG/WebP formats (default: not set)
--image-background= Background type: opaque, transparent (default: opaque, only for
PNG/WebP)
--image-background= Background type: opaque, transparent (default: opaque, only for PNG/WebP)
--suppress-think Suppress text enclosed in thinking tags
--think-start-tag= Start tag for thinking sections (default: <think>)
--think-end-tag= End tag for thinking sections (default: </think>)
--disable-responses-api Disable OpenAI Responses API (default: false)
--voice= TTS voice name for supported models (e.g., Kore, Charon, Puck)
(default: Kore)
--transcribe-file= Audio or video file to transcribe
--transcribe-model= Model to use for transcription (separate from chat model)
--split-media-file Split audio/video files larger than 25MB using ffmpeg
--voice= TTS voice name for supported models (e.g., Kore, Charon, Puck) (default:
Kore)
--list-gemini-voices List all available Gemini TTS voices
--list-transcription-models List all available transcription models
--notification Send desktop notification when command completes
--notification-command= Custom command to run for notifications (overrides built-in
notifications)
--yt-dlp-args= Additional arguments to pass to yt-dlp (e.g. '--cookies-from-browser brave')
--thinking= Set reasoning/thinking level (e.g., off, low, medium, high, or
numeric tokens for Anthropic or Google Gemini)
--notification-command= Custom command to run for notifications (overrides built-in notifications)
--thinking= Set reasoning/thinking level (e.g., off, low, medium, high, or numeric
tokens for Anthropic or Google Gemini)
--show-metadata Print metadata (input/output tokens) to stderr
--debug= Set debug level (0: off, 1: basic, 2: detailed, 3: trace)
--debug= Set debug level (0=off, 1=basic, 2=detailed, 3=trace, 4=wire)
Help Options:
-h, --help Show this help message
```
@ -745,6 +762,7 @@ Use the `--debug` flag to control runtime logging:
- `1`: basic debug info
- `2`: detailed debugging
- `3`: trace level
- `4`: wire level (full request and response bodies)
### Dry Run Mode

View file

@ -131,6 +131,7 @@ Fabric 按照现实世界中的任务来组织 Prompt允许人们在一个地
- [macOS (Homebrew)](#macos-homebrew)
- [Arch Linux (AUR)](#arch-linux-aur)
- [Windows](#windows)
- [Windows (Scoop)](#windows-scoop)
- [从源码构建](#从源码构建)
- [Docker](#docker)
- [环境变量](#环境变量)
@ -260,6 +261,12 @@ yay -S fabric-ai
winget install danielmiessler.Fabric
```
#### Windows (Scoop)
```bash
scoop install fabric-ai
```
### 从源码构建
安装 Fabric 前,请先[确保已安装 Go](https://go.dev/doc/install),然后运行:
@ -281,13 +288,13 @@ docker run --rm -it ghcr.io/ksylvan/fabric:v1.4.305 --version
# 首次运行时进行配置
mkdir -p $HOME/.fabric-config
docker run --rm -it -v $HOME/.fabric-config:/root/.config/fabric kayvan/fabric:latest --setup
docker run --rm -it -v $HOME/.fabric-config:/home/appuser/.config/fabric kayvan/fabric:latest --setup
# 使用你的 patterns
docker run --rm -it -v $HOME/.fabric-config:/root/.config/fabric kayvan/fabric:latest -p summarize
docker run --rm -it -v $HOME/.fabric-config:/home/appuser/.config/fabric kayvan/fabric:latest -p summarize
# 运行 REST API 服务器
docker run --rm -it -p 8080:8080 -v $HOME/.fabric-config:/root/.config/fabric kayvan/fabric:latest --serve
docker run --rm -it -p 8080:8080 -v $HOME/.fabric-config:/home/appuser/.config/fabric kayvan/fabric:latest --serve
```
**镜像来源:**
@ -351,7 +358,7 @@ fabric --setup
**OpenAI 兼容供应商:**
- Abacus、AIML、Cerebras、DeepSeek、DigitalOcean、GitHub Models、GrokAI、Groq、Langdock、LiteLLM、MiniMax、Mistral、Novita AI、OpenRouter、SiliconCloud、Together、Venice AI、Z AI
- Abacus、AIML、Cerebras、DeepSeek、DigitalOcean、GitHub Models、GrokAI、Groq、Langdock、LiteLLM、MiniMax、Mistral、Novita AI、OpenRouter、SiliconCloud、Synthorai、Together、Venice AI、Z AI
运行 `fabric --setup` 配置首选供应商,或使用 `fabric --listvendors` 查看所有可用供应商。
@ -453,6 +460,12 @@ cp completions/fabric.fish ~/.config/fish/completions/
fabric -h
```
处理 YouTube 视频时,还可以使用以下视觉提取选项:
- `--visual`:使用 OCR 和 FFmpeg 从视频中提取视觉信息
- `--visual-sensitivity`:设置 FFmpeg 场景检测的容差(`0.0` - `1.0`
- `--visual-fps`:按固定每秒帧数提取画面,而不是使用场景检测
将你复制的任何文本流式输入到 `fabric` 并选择你想应用的 Pattern
```bash
@ -467,6 +480,7 @@ pbpaste | fabric --pattern extract_wisdom
- `1`:基本调试信息
- `2`:详细调试
- `3`:追踪级别
- `4`wire 级别(完整的请求和响应内容)
### 演习模式

View file

@ -1,3 +1,3 @@
package main
var version = "v1.4.441"
var version = "v1.4.478"

View file

@ -191,7 +191,7 @@ Create a `.env` file next to the `generate_changelog` binary:
```bash
GITHUB_TOKEN=your_github_token_here
FABRIC_CHANGELOG_SUMMARIZE_MODEL=claude-sonnet-4-20250514
FABRIC_CHANGELOG_SUMMARIZE_MODEL=gpt-5.6-sol
```
The tool automatically loads `.env` files for convenient configuration management.
@ -226,8 +226,8 @@ The tool can generate AI-powered summaries using Fabric for more polished, profe
# Enable AI summarization
generate_changelog --ai-summarize
# Custom model (default: claude-sonnet-4-20250514)
FABRIC_CHANGELOG_SUMMARIZE_MODEL=claude-opus-4 generate_changelog --ai-summarize
# Custom model (default: claude-opus-5)
FABRIC_CHANGELOG_SUMMARIZE_MODEL=gpt-5.6-sol generate_changelog --ai-summarize
```
### AI Summary Features

Binary file not shown.

View file

@ -381,11 +381,13 @@ func (g *Generator) validatePRState(prNumber int) error {
return fmt.Errorf("failed to fetch PR %d: %w", prNumber, err)
}
if details.State != "open" {
return fmt.Errorf("PR %d is not open (current state: %s)", prNumber, details.State)
if details.State != "open" && !g.cfg.ClosedOK {
return fmt.Errorf("PR %d is not open (current state: %s); use --closed-ok to process it anyway", prNumber, details.State)
}
if !details.Mergeable {
// Only check mergeability for open PRs; GitHub returns nil for closed/merged PRs
// which would incorrectly trigger this check even when using --closed-ok
if details.State == "open" && !details.Mergeable {
return fmt.Errorf("PR %d is not mergeable - please resolve conflicts first", prNumber)
}

View file

@ -7,7 +7,7 @@ import (
"strings"
)
const DefaultSummarizeModel = "claude-sonnet-4-5"
const DefaultSummarizeModel = "claude-opus-5"
const MinContentLength = 256 // Minimum content length to consider for summarization
const prompt = `# ROLE

View file

@ -18,4 +18,5 @@ type Config struct {
Push bool
SyncDB bool
Release string
ClosedOK bool
}

View file

@ -45,6 +45,7 @@ func init() {
rootCmd.Flags().BoolVar(&cfg.Push, "push", false, "Enable automatic git push after creating an incoming entry")
rootCmd.Flags().BoolVar(&cfg.SyncDB, "sync-db", false, "Synchronize and validate database integrity with git history and GitHub PRs")
rootCmd.Flags().StringVar(&cfg.Release, "release", "", "Update GitHub release description with AI summary for version (e.g., v1.2.3)")
rootCmd.Flags().BoolVar(&cfg.ClosedOK, "closed-ok", false, "Allow processing a PR that is already closed/merged (skips open-state check)")
}
func run(cmd *cobra.Command, args []string) error {

View file

@ -84,13 +84,14 @@ _fabric() {
'(-r --raw)'{-r,--raw}'[Use the defaults of the model without sending chat options. Only affects OpenAI-compatible providers. Anthropic models always use smart parameter selection to comply with model-specific requirements.]' \
'(-F --frequencypenalty)'{-F,--frequencypenalty}'[Set frequency penalty (default: 0.0)]:frequency penalty:' \
'(-l --listpatterns)'{-l,--listpatterns}'[List all patterns]' \
'(--readpattern)--readpattern[Print the contents of the named pattern to the terminal]:pattern:_fabric_patterns' \
'(-L --listmodels)'{-L,--listmodels}'[List all available models]' \
'(-x --listcontexts)'{-x,--listcontexts}'[List all contexts]' \
'(-X --listsessions)'{-X,--listsessions}'[List all sessions]' \
'(-U --updatepatterns)'{-U,--updatepatterns}'[Update patterns]' \
'(-c --copy)'{-c,--copy}'[Copy to clipboard]' \
'(-m --model)'{-m,--model}'[Choose model]:model:_fabric_models' \
'(-V --vendor)'{-V,--vendor}'[Specify vendor for chosen model (e.g., -V "LM Studio" -m openai/gpt-oss-20b)]:vendor:_fabric_vendors' \
'(-V --vendor)'{-V,--vendor}'[Specify vendor for the selected model (e.g., -V "LM Studio" -m openai/gpt-oss-20b)]:vendor:_fabric_vendors' \
'(--modelContextLength)--modelContextLength[Model context length (only affects ollama)]:length:' \
'(-o --output)'{-o,--output}'[Output to file]:file:_files' \
'(--output-session)--output-session[Output the entire session to the output file]' \
@ -100,6 +101,9 @@ _fabric() {
'(--playlist)--playlist[Prefer playlist over video if both ids are present in the URL]' \
'(--transcript)--transcript[Grab transcript from YouTube video and send to chat]' \
'(--transcript-with-timestamps)--transcript-with-timestamps[Grab transcript from YouTube video with timestamps]' \
'(--visual)--visual[Extract visual data from video using OCR and FFmpeg]' \
'(--visual-sensitivity)--visual-sensitivity[Tolerance for FFmpeg scene detection (0.0 - 1.0)]:visual sensitivity:' \
'(--visual-fps)--visual-fps[Extract a specific number of frames per second instead of using scene detection]:frames per second:' \
'(--comments)--comments[Grab comments from YouTube video and send to chat]' \
'(--metadata)--metadata[Output video metadata]' \
'(--yt-dlp-args)--yt-dlp-args[Additional arguments to pass to yt-dlp]:yt-dlp args:' \
@ -122,7 +126,7 @@ _fabric() {
'(--api-key)--api-key[API key used to secure server routes]:api-key:' \
'(--config)--config[Path to YAML config file]:config file:_files -g "*.yaml *.yml"' \
'(--version)--version[Print current version]' \
'(--search)--search[Enable web search tool for supported models (Anthropic, OpenAI, Gemini)]' \
'(--search)--search[Enable web search tool for supported models (Anthropic, OpenAI, Gemini, Grok)]' \
'(--search-location)--search-location[Set location for web search results]:location:' \
'(--image-file)--image-file[Save generated image to specified file path]:image file:_files -g "*.png *.webp *.jpeg *.jpg"' \
'(--image-size)--image-size[Image dimensions]:size:(1024x1024 1536x1024 1024x1536 auto)' \
@ -137,6 +141,7 @@ _fabric() {
'(--listvendors)--listvendors[List all vendors]' \
'(--voice)--voice[TTS voice name for supported models]:voice:_fabric_gemini_voices' \
'(--list-gemini-voices)--list-gemini-voices[List all available Gemini TTS voices]' \
'(--list-transcription-models)--list-transcription-models[List all available transcription models]' \
'(--shell-complete-list)--shell-complete-list[Output raw list without headers/formatting (for shell completion)]' \
'(--suppress-think)--suppress-think[Suppress text enclosed in thinking tags]' \
'(--think-start-tag)--think-start-tag[Start tag for thinking sections (default: <think>)]:start tag:' \
@ -145,6 +150,7 @@ _fabric() {
'(--transcribe-file)--transcribe-file[Audio or video file to transcribe]:audio file:_files -g "*.mp3 *.mp4 *.mpeg *.mpga *.m4a *.wav *.webm"' \
'(--transcribe-model)--transcribe-model[Model to use for transcription (separate from chat model)]:transcribe model:_fabric_transcription_models' \
'(--split-media-file)--split-media-file[Split audio/video files larger than 25MB using ffmpeg]' \
'(--show-metadata)--show-metadata[Print metadata (input/output tokens) to stderr]' \
'(--debug)--debug[Set debug level (0=off, 1=basic, 2=detailed, 3=trace, 4=wire)]:debug level:(0 1 2 3 4)' \
'(--notification)--notification[Send desktop notification when command completes]' \
'(--notification-command)--notification-command[Custom command to run for notifications]:notification command:' \

View file

@ -17,7 +17,7 @@ _fabric() {
fi
# Define all possible options/flags
local opts="--pattern -p --variable -v --context -C --session --attachment -a --setup -S --temperature -t --topp -T --stream -s --presencepenalty -P --raw -r --frequencypenalty -F --listpatterns -l --listmodels -L --listcontexts -x --listsessions -X --updatepatterns -U --copy -c --model -m --vendor -V --modelContextLength --output -o --output-session --latest -n --changeDefaultModel -d --youtube -y --playlist --transcript --transcript-with-timestamps --comments --metadata --yt-dlp-args --language -g --scrape_url -u --scrape_question -q --seed -e --thinking --wipecontext -w --wipesession -W --printcontext --printsession --readability --input-has-vars --no-variable-replacement --dry-run --serve --serveOllama --address --api-key --config --search --search-location --image-file --image-size --image-quality --image-compression --image-background --suppress-think --think-start-tag --think-end-tag --disable-responses-api --transcribe-file --transcribe-model --split-media-file --voice --list-gemini-voices --notification --notification-command --debug --version --listextensions --addextension --rmextension --strategy --liststrategies --listvendors --shell-complete-list --help -h"
local opts="--pattern -p --variable -v --context -C --session --attachment -a --setup -S --temperature -t --topp -T --stream -s --presencepenalty -P --raw -r --frequencypenalty -F --listpatterns -l --readpattern --listmodels -L --listcontexts -x --listsessions -X --updatepatterns -U --copy -c --model -m --vendor -V --modelContextLength --output -o --output-session --latest -n --changeDefaultModel -d --youtube -y --playlist --transcript --transcript-with-timestamps --visual --visual-sensitivity --visual-fps --comments --metadata --yt-dlp-args --spotify --language -g --scrape_url -u --scrape_question -q --seed -e --thinking --wipecontext -w --wipesession -W --printcontext --printsession --readability --input-has-vars --no-variable-replacement --dry-run --serve --serveOllama --address --api-key --config --search --search-location --image-file --image-size --image-quality --image-compression --image-background --suppress-think --think-start-tag --think-end-tag --disable-responses-api --transcribe-file --transcribe-model --split-media-file --voice --list-gemini-voices --list-transcription-models --notification --notification-command --show-metadata --debug --version --listextensions --addextension --rmextension --strategy --liststrategies --listvendors --shell-complete-list --help -h"
# Helper function for dynamic completions
_fabric_get_list() {
@ -26,7 +26,7 @@ _fabric() {
# Handle completions based on the previous word
case "${prev}" in
-p | --pattern)
-p | --pattern | --readpattern)
COMPREPLY=($(compgen -W "$(_fabric_get_list --listpatterns)" -- "${cur}"))
return 0
;;
@ -105,7 +105,7 @@ _fabric() {
return 0
;;
# Options requiring simple arguments (no specific completion logic here)
-v | --variable | -t | --temperature | -T | --topp | -P | --presencepenalty | -F | --frequencypenalty | --modelContextLength | -n | --latest | -y | --youtube | --yt-dlp-args | -g | --language | -u | --scrape_url | -q | --scrape_question | -e | --seed | --address | --api-key | --search-location | --image-compression | --think-start-tag | --think-end-tag | --notification-command)
-v | --variable | -t | --temperature | -T | --topp | -P | --presencepenalty | -F | --frequencypenalty | --modelContextLength | -n | --latest | -y | --youtube | --visual-sensitivity | --visual-fps | --yt-dlp-args | -g | --language | -u | --scrape_url | -q | --scrape_question | -e | --seed | --address | --api-key | --search-location | --image-compression | --think-start-tag | --think-end-tag | --notification-command)
# No specific completion suggestions, user types the value
return 0
;;

View file

@ -53,54 +53,69 @@ function __fabric_get_transcription_models
end
# Main completion function
#
# Options that take a value must declare it. Use -x when only the listed values
# are valid, and -r when a file path is also valid. Fish ignores the -a list of
# an option that declares neither, and shows no suggestion after the option.
function __fabric_register_completions
set cmd $argv[1]
complete -c $cmd -f
# Flag completions with arguments
complete -c $cmd -s p -l pattern -d "Choose a pattern from the available patterns" -a "(__fabric_get_patterns)"
complete -c $cmd -s v -l variable -d "Values for pattern variables, e.g. -v=#role:expert -v=#points:30"
complete -c $cmd -s C -l context -d "Choose a context from the available contexts" -a "(__fabric_get_contexts)"
complete -c $cmd -l session -d "Choose a session from the available sessions" -a "(__fabric_get_sessions)"
complete -c $cmd -s a -l attachment -d "Attachment path or URL (e.g. for OpenAI image recognition messages)" -r
complete -c $cmd -s t -l temperature -d "Set temperature (default: 0.7)"
complete -c $cmd -s T -l topp -d "Set top P (default: 0.9)"
complete -c $cmd -s P -l presencepenalty -d "Set presence penalty (default: 0.0)"
complete -c $cmd -s F -l frequencypenalty -d "Set frequency penalty (default: 0.0)"
complete -c $cmd -s m -l model -d "Choose model" -a "(__fabric_get_models)"
complete -c $cmd -s V -l vendor -d "Specify vendor for chosen model (e.g., -V \"LM Studio\" -m openai/gpt-oss-20b)" -a "(__fabric_get_vendors)"
complete -c $cmd -l modelContextLength -d "Model context length (only affects ollama)"
complete -c $cmd -s o -l output -d "Output to file" -r
complete -c $cmd -s n -l latest -d "Number of latest patterns to list (default: 0)"
complete -c $cmd -s y -l youtube -d "YouTube video or play list URL to grab transcript, comments from it"
complete -c $cmd -s g -l language -d "Specify the Language Code for the chat, e.g. -g=en -g=zh"
complete -c $cmd -s u -l scrape_url -d "Scrape website URL to markdown using Jina AI"
complete -c $cmd -s q -l scrape_question -d "Search question using Jina AI"
complete -c $cmd -s e -l seed -d "Seed to be used for LMM generation"
complete -c $cmd -l thinking -d "Set reasoning/thinking level" -a "off low medium high"
complete -c $cmd -s w -l wipecontext -d "Wipe context" -a "(__fabric_get_contexts)"
complete -c $cmd -s W -l wipesession -d "Wipe session" -a "(__fabric_get_sessions)"
complete -c $cmd -l printcontext -d "Print context" -a "(__fabric_get_contexts)"
complete -c $cmd -l printsession -d "Print session" -a "(__fabric_get_sessions)"
complete -c $cmd -l address -d "The address to bind the REST API (default: :8080)"
complete -c $cmd -l api-key -d "API key used to secure server routes"
complete -c $cmd -l config -d "Path to YAML config file" -r -a "*.yaml *.yml"
complete -c $cmd -l search-location -d "Set location for web search results (e.g., 'America/Los_Angeles')"
complete -c $cmd -l image-file -d "Save generated image to specified file path (e.g., 'output.png')" -r -a "*.png *.webp *.jpeg *.jpg"
complete -c $cmd -l image-size -d "Image dimensions: 1024x1024, 1536x1024, 1024x1536, auto (default: auto)" -a "1024x1024 1536x1024 1024x1536 auto"
complete -c $cmd -l image-quality -d "Image quality: low, medium, high, auto (default: auto)" -a "low medium high auto"
complete -c $cmd -l image-compression -d "Compression level 0-100 for JPEG/WebP formats (default: not set)" -r
complete -c $cmd -l image-background -d "Background type: opaque, transparent (default: opaque, only for PNG/WebP)" -a "opaque transparent"
complete -c $cmd -l addextension -d "Register a new extension from config file path" -r -a "*.yaml *.yml"
complete -c $cmd -l rmextension -d "Remove a registered extension by name" -a "(__fabric_get_extensions)"
complete -c $cmd -l strategy -d "Choose a strategy from the available strategies" -a "(__fabric_get_strategies)"
complete -c $cmd -l think-start-tag -d "Start tag for thinking sections (default: <think>)"
complete -c $cmd -l think-end-tag -d "End tag for thinking sections (default: </think>)"
complete -c $cmd -l voice -d "TTS voice name for supported models (e.g., Kore, Charon, Puck)" -a "(__fabric_get_gemini_voices)"
complete -c $cmd -l transcribe-file -d "Audio or video file to transcribe" -r -a "*.mp3 *.mp4 *.mpeg *.mpga *.m4a *.wav *.webm"
complete -c $cmd -l transcribe-model -d "Model to use for transcription (separate from chat model)" -a "(__fabric_get_transcription_models)"
complete -c $cmd -l debug -d "Set debug level (0=off, 1=basic, 2=detailed, 3=trace, 4=wire)" -a "0 1 2 3 4"
complete -c $cmd -l notification-command -d "Custom command to run for notifications (overrides built-in notifications)"
# Options that take a value from a dynamic list
complete -c $cmd -s p -l pattern -x -d "Choose a pattern from the available patterns" -a "(__fabric_get_patterns)"
complete -c $cmd -l readpattern -x -d "Print the contents of the named pattern to the terminal" -a "(__fabric_get_patterns)"
complete -c $cmd -s C -l context -x -d "Choose a context from the available contexts" -a "(__fabric_get_contexts)"
complete -c $cmd -l session -x -d "Choose a session from the available sessions" -a "(__fabric_get_sessions)"
complete -c $cmd -s m -l model -x -d "Choose model" -a "(__fabric_get_models)"
complete -c $cmd -s V -l vendor -x -d "Specify vendor for the selected model (e.g., -V \"LM Studio\" -m openai/gpt-oss-20b)" -a "(__fabric_get_vendors)"
complete -c $cmd -s w -l wipecontext -x -d "Wipe context" -a "(__fabric_get_contexts)"
complete -c $cmd -s W -l wipesession -x -d "Wipe session" -a "(__fabric_get_sessions)"
complete -c $cmd -l printcontext -x -d "Print context" -a "(__fabric_get_contexts)"
complete -c $cmd -l printsession -x -d "Print session" -a "(__fabric_get_sessions)"
complete -c $cmd -l rmextension -x -d "Remove a registered extension by name" -a "(__fabric_get_extensions)"
complete -c $cmd -l strategy -x -d "Choose a strategy from the available strategies" -a "(__fabric_get_strategies)"
complete -c $cmd -l voice -x -d "TTS voice name for supported models (e.g., Kore, Charon, Puck)" -a "(__fabric_get_gemini_voices)"
complete -c $cmd -l transcribe-model -x -d "Model to use for transcription (separate from chat model)" -a "(__fabric_get_transcription_models)"
# Options that take a value from a fixed list
complete -c $cmd -l thinking -x -d "Set reasoning/thinking level" -a "off low medium high"
complete -c $cmd -l image-size -x -d "Image dimensions: 1024x1024, 1536x1024, 1024x1536, auto (default: auto)" -a "1024x1024 1536x1024 1024x1536 auto"
complete -c $cmd -l image-quality -x -d "Image quality: low, medium, high, auto (default: auto)" -a "low medium high auto"
complete -c $cmd -l image-background -x -d "Background type: opaque, transparent (default: opaque, only for PNG/WebP)" -a "opaque transparent"
complete -c $cmd -l debug -x -d "Set debug level (0=off, 1=basic, 2=detailed, 3=trace, 4=wire)" -a "0 1 2 3 4"
# Options that take a file path
complete -c $cmd -s a -l attachment -r -d "Attachment path or URL (e.g. for OpenAI image recognition messages)"
complete -c $cmd -s o -l output -r -d "Output to file"
complete -c $cmd -l config -r -d "Path to YAML config file" -a "(__fish_complete_suffix .yaml .yml)"
complete -c $cmd -l addextension -r -d "Register a new extension from config file path" -a "(__fish_complete_suffix .yaml .yml)"
complete -c $cmd -l image-file -r -d "Save generated image to specified file path (e.g., 'output.png')" -a "(__fish_complete_suffix .png .webp .jpeg .jpg)"
complete -c $cmd -l transcribe-file -r -d "Audio or video file to transcribe" -a "(__fish_complete_suffix .mp3 .mp4 .mpeg .mpga .m4a .wav .webm)"
# Options that take a value the user types
complete -c $cmd -s v -l variable -x -d "Values for pattern variables, e.g. -v=#role:expert -v=#points:30"
complete -c $cmd -s t -l temperature -x -d "Set temperature (default: 0.7)"
complete -c $cmd -s T -l topp -x -d "Set top P (default: 0.9)"
complete -c $cmd -s P -l presencepenalty -x -d "Set presence penalty (default: 0.0)"
complete -c $cmd -s F -l frequencypenalty -x -d "Set frequency penalty (default: 0.0)"
complete -c $cmd -l modelContextLength -x -d "Model context length (only affects ollama)"
complete -c $cmd -s n -l latest -x -d "Number of latest patterns to list (default: 0)"
complete -c $cmd -s y -l youtube -x -d "YouTube video or play list URL to grab transcript, comments from it"
complete -c $cmd -l visual-sensitivity -x -d "Tolerance for FFmpeg scene detection (0.0 - 1.0) (default: 0.4)"
complete -c $cmd -l visual-fps -x -d "Extract a specific number of frames per second instead of using scene detection"
complete -c $cmd -l yt-dlp-args -x -d "Additional arguments to pass to yt-dlp (e.g. '--cookies-from-browser brave')"
complete -c $cmd -l spotify -x -d "Spotify podcast or episode URL to grab metadata from and send to chat"
complete -c $cmd -s g -l language -x -d "Specify the Language Code for the chat, e.g. -g=en -g=zh"
complete -c $cmd -s u -l scrape_url -x -d "Scrape website URL to markdown using Jina AI"
complete -c $cmd -s q -l scrape_question -x -d "Search question using Jina AI"
complete -c $cmd -s e -l seed -x -d "Seed to be used for LMM generation"
complete -c $cmd -l address -x -d "The address to bind the REST API (default: :8080)"
complete -c $cmd -l api-key -x -d "API key used to secure server routes"
complete -c $cmd -l search-location -x -d "Set location for web search results (e.g., 'America/Los_Angeles')"
complete -c $cmd -l image-compression -x -d "Compression level 0-100 for JPEG/WebP formats (default: not set)"
complete -c $cmd -l think-start-tag -x -d "Start tag for thinking sections (default: <think>)"
complete -c $cmd -l think-end-tag -x -d "End tag for thinking sections (default: </think>)"
complete -c $cmd -l notification-command -x -d "Custom command to run for notifications (overrides built-in notifications)"
# Boolean flags (no arguments)
complete -c $cmd -s S -l setup -d "Run setup for all reconfigurable parts of fabric"
@ -117,14 +132,14 @@ function __fabric_register_completions
complete -c $cmd -l playlist -d "Prefer playlist over video if both ids are present in the URL"
complete -c $cmd -l transcript -d "Grab transcript from YouTube video and send to chat"
complete -c $cmd -l transcript-with-timestamps -d "Grab transcript from YouTube video with timestamps"
complete -c $cmd -l visual -d "Extract visual data from video using OCR and FFmpeg"
complete -c $cmd -l comments -d "Grab comments from YouTube video and send to chat"
complete -c $cmd -l metadata -d "Output video metadata"
complete -c $cmd -l yt-dlp-args -d "Additional arguments to pass to yt-dlp (e.g. '--cookies-from-browser brave')"
complete -c $cmd -l readability -d "Convert HTML input into a clean, readable view"
complete -c $cmd -l input-has-vars -d "Apply variables to user input"
complete -c $cmd -l no-variable-replacement -d "Disable pattern variable replacement"
complete -c $cmd -l dry-run -d "Show what would be sent to the model without actually sending it"
complete -c $cmd -l search -d "Enable web search tool for supported models (Anthropic, OpenAI, Gemini)"
complete -c $cmd -l search -d "Enable web search tool for supported models (Anthropic, OpenAI, Gemini, Grok)"
complete -c $cmd -l serve -d "Serve the Fabric Rest API"
complete -c $cmd -l serveOllama -d "Serve the Fabric Rest API with ollama endpoints"
complete -c $cmd -l version -d "Print current version"
@ -132,13 +147,14 @@ function __fabric_register_completions
complete -c $cmd -l liststrategies -d "List all strategies"
complete -c $cmd -l listvendors -d "List all vendors"
complete -c $cmd -l list-gemini-voices -d "List all available Gemini TTS voices"
complete -c $cmd -l list-transcription-models -d "List all available transcription models"
complete -c $cmd -l shell-complete-list -d "Output raw list without headers/formatting (for shell completion)"
complete -c $cmd -l suppress-think -d "Suppress text enclosed in thinking tags"
complete -c $cmd -l disable-responses-api -d "Disable OpenAI Responses API (default: false)"
complete -c $cmd -l split-media-file -d "Split audio/video files larger than 25MB using ffmpeg"
complete -c $cmd -l notification -d "Send desktop notification when command completes"
complete -c $cmd -l show-metadata -d "Print metadata (input/output tokens) to stderr"
complete -c $cmd -s h -l help -d "Show this help message"
complete -c $cmd -l spotify -d 'Spotify podcast or episode URL to grab metadata'
end
__fabric_register_completions fabric

View file

@ -0,0 +1,62 @@
# IDENTITY and PURPOSE
You are an expert at identifying monetization opportunities in creator content — specifically where affiliate commerce, sponsorships, digital products, and paid communities align with audience intent and creator authority.
You look at content through the lens of a creator business advisor: not just what was said, but what the audience is ready to buy, what problems they're trying to solve, and where trust has already been established.
Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.
# STEPS
- Read the full content and identify the creator's topic, authority signals, and audience intent.
- Identify the primary audience archetype: buyer (ready to purchase) / learner (wants to understand) / problem-solver (has a specific need) / researcher (comparing options).
- Extract all monetization signals:
- Products or services mentioned that have affiliate programs
- Topics where the creator demonstrates enough authority to sell a course or digital product
- Brand categories where the creator's audience has demonstrated high purchase intent
- Recurring needs that a subscription or community could address
- For each opportunity, estimate:
- Revenue type: affiliate / sponsorship / digital product / community / service
- Effort level: low (link in description) / medium (landing page, code) / high (course, product)
- Audience alignment: how well the opportunity fits what this audience came to consume
- Time sensitivity: evergreen / seasonal / trending
- Identify the single highest-value monetization move the creator could make within 30 days.
# OUTPUT SECTIONS
## AUDIENCE INTENT
One paragraph: who is watching this, what do they want, and how ready are they to spend money.
## MONETIZATION OPPORTUNITIES
For each opportunity identified:
- **Type**: affiliate / sponsorship / digital product / community / service
- **Opportunity**: what specifically to offer or promote
- **Revenue estimate**: rough monthly range if activated (low / medium / high — do not invent numbers)
- **Effort**: low / medium / high
- **Alignment**: why this audience would respond to it
## 30-DAY QUICK WIN
The single most actionable monetization move for this creator in the next 30 days, with specific next steps.
## MONETIZATION GAPS
What this content is leaving on the table — missed opportunities the creator is not currently capturing.
# OUTPUT INSTRUCTIONS
- Only output Markdown.
- Do not invent specific revenue numbers — use low / medium / high ranges with brief rationale.
- Do not output warnings or notes — only the requested sections.
- Be specific about the opportunity — "add Amazon affiliate links for the tools mentioned" is more useful than "consider affiliate marketing."
- Prioritize opportunities that require the least effort for the highest audience alignment.
# INPUT
INPUT:

View file

@ -0,0 +1,58 @@
# IDENTITY and PURPOSE
You are an expert at extracting commercial products, tools, services, and affiliate opportunities from content transcripts. You identify every entity that a creator could earn affiliate revenue from — whether it was explicitly promoted, casually mentioned, or demonstrated in use.
You understand that the most valuable affiliate opportunities are often the products a creator uses without thinking to mention they're affiliated with. Your job is to surface all of them.
Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.
# STEPS
- Read the entire transcript to understand the topic, creator style, and audience.
- Identify every named product, tool, service, book, course, plant, ingredient, or brand mentioned or implied.
- For each entity, determine:
- The exact name as mentioned (or inferred if clearly implied)
- The category (tool / product / service / book / course / plant / ingredient / brand)
- Whether it was explicitly recommended, casually mentioned, or visually demonstrated
- The estimated affiliate commission tier (low = <5% / mid = 5-15% / high = >15%)
- A search-ready query string for finding its affiliate program
- Separate entities that were explicitly sponsored (paid promotions) from organic mentions — organic mentions are often the highest-converting affiliate opportunities.
- Extract a short sentence for each entity explaining why an audience member would want to buy it based on how the creator presented it.
# OUTPUT SECTIONS
## SPONSORED CONTENT
Entities the creator was paid to promote. Format: `Name | Category | Search query | Commission tier`
## ORGANIC AFFILIATE OPPORTUNITIES
Products and tools mentioned without a paid arrangement — highest conversion potential. Format: `Name | Category | Context (why it was mentioned) | Commission tier | Search query`
## HIGH-CONFIDENCE BUYS
The 3-5 entities most likely to convert to a purchase, based on how enthusiastically or repeatedly the creator mentioned them.
Format: `Name | One sentence on why the audience would buy it`
## AFFILIATE GAPS
Categories or needs the creator addressed where no specific product was named — these are placement opportunities. Format: `Need described | Suggested category to fill it`
# OUTPUT INSTRUCTIONS
- Only output Markdown.
- Do not output warnings, notes, or caveats — only the requested sections.
- If a section has no entries, write "None identified."
- Keep entity names exact — do not paraphrase brand names.
- Do not duplicate entries across sections.
- Commission tier is an estimate based on typical affiliate rates for the category — label it clearly as estimated.
- Organic mentions are more valuable than sponsored ones for affiliate strategy — reflect this in your ordering.
# INPUT
INPUT:

View file

@ -0,0 +1,62 @@
# IDENTITY and PURPOSE
You are an expert at identifying every commercially relevant entity in a video transcript — the products shown, tools used, plants grown, books referenced, services mentioned, and brands displayed. You think like an affiliate manager reviewing content for placement opportunities.
You understand that video content is uniquely rich with implicit product signals: a host reaches for a specific brand of pruners, uses a particular app on screen, wears a recognizable piece of gear. You surface all of it.
Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.
# STEPS
- Read the full transcript and extract all named or clearly implied commercial entities.
- For each entity, record:
- Name (exact as spoken, or brand inferred from description)
- Category: tool / plant / material / book / course / service / software / apparel / food / other
- Timestamp or approximate position (early / mid / late) if determinable from context
- Mention type: explicit recommendation / casual use / on-screen / background / sponsored
- Audience fit: how well this product matches what the video's audience would buy
- Group entities by category.
- Note any entities mentioned multiple times — repetition is a strong buying signal.
- Identify the top 5 entities by purchase likelihood.
# OUTPUT SECTIONS
## ENTITIES BY CATEGORY
For each category with at least one entity:
### [Category Name]
- `Name` | Mention type | Position | Audience fit (high/mid/low)
## REPEATED MENTIONS
Entities mentioned more than once — strong conversion signal:
- `Name` | Number of mentions | Why it matters
## TOP 5 PURCHASE CANDIDATES
The entities most likely to drive a sale, ranked:
1. `Name` — [One sentence: why this audience buys this product]
2. ...
## CONTENT GAPS
Needs the creator addressed where no product was named — affiliate placement opportunities:
- `Need` | Suggested category
# OUTPUT INSTRUCTIONS
- Only output Markdown.
- Do not output warnings or notes — only the requested sections.
- If a section has no entries, write "None identified."
- Keep brand names exact.
- Audience fit is relative to the video's topic and likely viewer — assess contextually.
- Timestamp positions are approximate — use early (0-33%), mid (33-66%), late (66-100%) if exact times aren't determinable.
# INPUT
INPUT:

View file

@ -0,0 +1,57 @@
# IDENTITY and PURPOSE
You are an expert at knowledge management and note metadata. Given any text — a document, article, essay, book chapter, transcript, meeting notes, or rough draft — you generate clean, well-structured YAML frontmatter suitable for personal knowledge management (PKM) systems such as Obsidian, Logseq, or any markdown-based notes vault.
Your output is immediately paste-ready: valid YAML wrapped in `---` delimiters, placed at the top of the note.
Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.
# STEPS
- Read the entire input carefully to understand its content, type, and context.
- Infer the most accurate and descriptive title if one is not explicitly present.
- Identify the document type (article, chapter, meeting-notes, transcript, essay, reference, idea, etc.).
- Extract 38 specific, lowercase tags that describe the content. Prefer concrete concepts over vague categories. Avoid single-word generic tags like "notes" or "text".
- Generate 13 aliases: alternative titles or short names someone might search for.
- Write a one-sentence summary (1525 words) capturing the core argument or content.
- Identify the author or source if present; leave blank if not.
- Use today's date or the document date if detectable; otherwise omit the date field.
- Note the document's primary domain or area (e.g., productivity, philosophy, technology, science, history).
# OUTPUT
Output ONLY the YAML frontmatter block. No explanation, no preamble, no commentary after the block.
```yaml
---
title: "Exact or inferred title"
aliases:
- "Short name"
- "Alternative title"
tags:
- specific-tag
- another-tag
- domain/subtopic
type: article # article | chapter | transcript | meeting-notes | essay | reference | idea | book
author: "Author Name" # omit if unknown
source: "" # URL or citation if available; omit if not
date: YYYY-MM-DD # omit if not determinable
summary: "One sentence capturing the core content or argument of this document."
status: unprocessed # unprocessed | reading | processed | archived
---
```
# OUTPUT INSTRUCTIONS
- Output ONLY the YAML block — nothing before `---` and nothing after the closing `---`.
- Use lowercase for all tags. Use hyphens for multi-word tags (e.g., `knowledge-management`, not `KnowledgeManagement`).
- For hierarchical tags use slash notation: `philosophy/stoicism`, `technology/ai`.
- Be specific: `decision-making` is better than `thinking`; `ancient-rome` is better than `history`.
- The summary must be a complete sentence, not a fragment.
- Omit fields that cannot be reasonably inferred (author, source, date) rather than guessing.
- Do not add any fields not shown in the template above.
- Do not give warnings or notes; only output the YAML block.
# INPUT
INPUT:

View file

@ -20,236 +20,240 @@
16. **analyze_malware**: Analyse malware details, extract key indicators, techniques, and potential detection strategies, and summarize findings concisely for a malware analyst's use in identifying and responding to threats.
17. **analyze_military_strategy**: Analyse a historical battle, offering in-depth insights into strategic decisions, strengths, weaknesses, tactical approaches, logistical factors, pivotal moments, and consequences for a comprehensive military evaluation.
18. **analyze_mistakes**: Analyse past mistakes in thinking patterns, map them to current beliefs, and offer recommendations to improve accuracy in predictions.
19. **analyze_paper**: Analyses research papers by summarizing findings, evaluating rigor, and assessing quality to provide insights for documentation and review.
20. **analyze_paper_simple**: Analyzes academic papers with a focus on primary findings, research quality, and study design evaluation.
21. **analyze_patent**: Analyse a patent's field, problem, solution, novelty, inventive step, and advantages in detail while summarizing and extracting keywords.
22. **analyze_personality**: Performs a deep psychological analysis of a person in the input, focusing on their behavior, language, and psychological traits.
23. **analyze_presentation**: Reviews and critiques presentations by analyzing the content, speaker's underlying goals, self-focus, and entertainment value.
24. **analyze_product_feedback**: A prompt for analyzing and organizing user feedback by identifying themes, consolidating similar comments, and prioritizing them based on usefulness.
25. **analyze_proposition**: Analyzes a ballot proposition by identifying its purpose, impact, arguments for and against, and relevant background information.
26. **analyze_prose**: Evaluates writing for novelty, clarity, and prose, providing ratings, improvement recommendations, and an overall score.
27. **analyze_prose_json**: Evaluates writing for novelty, clarity, prose, and provides ratings, explanations, improvement suggestions, and an overall score in a JSON format.
28. **analyze_prose_pinker**: Evaluates prose based on Steven Pinker's The Sense of Style, analyzing writing style, clarity, and bad writing elements.
29. **analyze_risk**: Conducts a risk assessment of a third-party vendor, assigning a risk score and suggesting security controls based on analysis of provided documents and vendor website.
30. **analyze_sales_call**: Rates sales call performance across multiple dimensions, providing scores and actionable feedback based on transcript analysis.
31. **analyze_spiritual_text**: Compares and contrasts spiritual texts by analyzing claims and differences with the King James Bible.
32. **analyze_tech_impact**: Analyzes the societal impact, ethical considerations, and sustainability of technology projects, evaluating their outcomes and benefits.
33. **analyze_terraform_plan**: Analyzes Terraform plan outputs to assess infrastructure changes, security risks, cost implications, and compliance considerations.
34. **analyze_threat_report**: Extracts surprising insights, trends, statistics, quotes, references, and recommendations from cybersecurity threat reports, summarizing key findings and providing actionable information.
35. **analyze_threat_report_cmds**: Extract and synthesize actionable cybersecurity commands from provided materials, incorporating command-line arguments and expert insights for pentesters and non-experts.
36. **analyze_threat_report_trends**: Extract up to 50 surprising, insightful, and interesting trends from a cybersecurity threat report in markdown format.
37. **answer_interview_question**: Generates concise, tailored responses to technical interview questions, incorporating alternative approaches and evidence to demonstrate the candidate's expertise and experience.
38. **apply_ul_tags**: Apply standardized content tags to categorize topics like AI, cybersecurity, politics, and culture.
39. **ask_secure_by_design_questions**: Generates a set of security-focused questions to ensure a project is built securely by design, covering key components and considerations.
40. **ask_uncle_duke**: Coordinates a team of AI agents to research and produce multiple software development solutions based on provided specifications, and conducts detailed code reviews to ensure adherence to best practices.
41. **audit_consent**: Evaluates whether consent in interactions or agreements is genuine or manufactured by analyzing power asymmetries, information gaps, alternatives, and coercion using a five-test framework.
42. **audit_transparency**: Audits decisions, systems, and algorithms for explainability across five dimensions, assessing whether opacity is justified or serves to conceal harm from affected parties.
43. **capture_thinkers_work**: Analyze philosophers or philosophies and provide detailed summaries about their teachings, background, works, advice, and related concepts in a structured template.
44. **check_agreement**: Analyze contracts and agreements to identify important stipulations, issues, and potential gotchas, then summarize them in Markdown.
45. **check_falsifiability**: Evaluates whether claims, definitions, frameworks, and arguments meet the standard of falsifiability — whether they can be tested and potentially proven wrong.
46. **clean_text**: Fix broken or malformatted text by correcting line breaks, punctuation, capitalization, and paragraphs without altering content or spelling.
47. **coding_master**: Explain a coding concept to a beginner, providing examples, and formatting code in markdown with specific output sections like ideas, recommendations, facts, and insights.
48. **compare_and_contrast**: Compare and contrast a list of items in a markdown table, with items on the left and topics on top.
49. **concall_summary**: Analyzes earnings and conference call transcripts to extract management commentary, analyst Q&A, financial insights, risks, and executive summaries.
50. **convert_to_markdown**: Convert content to clean, complete Markdown format, preserving all original structure, formatting, links, and code blocks without alterations.
51. **create_5_sentence_summary**: Create concise summaries or answers to input at 5 different levels of depth, from 5 words to 1 word.
52. **create_academic_paper**: Generate a high-quality academic paper in LaTeX format with clear concepts, structured content, and a professional layout.
53. **create_ai_jobs_analysis**: Analyze job categories' susceptibility to automation, identify resilient roles, and provide strategies for personal adaptation to AI-driven changes in the workforce.
54. **create_aphorisms**: Find and generate a list of brief, witty statements.
55. **create_art_prompt**: Generates a detailed, compelling visual description of a concept, including stylistic references and direct AI instructions for creating art.
56. **create_better_frame**: Identifies and analyzes different frames of interpreting reality, emphasizing the power of positive, productive lenses in shaping outcomes.
57. **create_bd_issue**: Transform natural language descriptions into optimal bd create commands for issue tracking.
58. **create_coding_feature**: Generates secure and composable code features using modern technology and best practices from project specifications.
59. **create_coding_project**: Generate wireframes and starter code for any coding ideas that you have.
60. **create_command**: Helps determine the correct parameters and switches for penetration testing tools based on a brief description of the objective.
61. **create_conceptmap**: Transforms unstructured text or markdown content into an interactive HTML concept map using Vis.js by extracting key concepts and their logical relationships.
62. **create_cyber_summary**: Summarizes cybersecurity threats, vulnerabilities, incidents, and malware with a 25-word summary and categorized bullet points, after thoroughly analyzing and mapping the provided input.
63. **create_design_system**: Create comprehensive CSS design systems with tokens, typography, spacing, and components.
64. **create_design_document**: Creates a detailed design document for a system using the C4 model, addressing business and security postures, and including a system context diagram.
65. **create_diy**: Creates structured "Do It Yourself" tutorial patterns by analyzing prompts, organizing requirements, and providing step-by-step instructions in Markdown format.
66. **create_excalidraw_visualization**: Creates complex Excalidraw diagrams to visualize relationships between concepts and ideas in structured format.
67. **create_flash_cards**: Creates flashcards for key concepts, definitions, and terms with question-answer format for educational purposes.
68. **create_formal_email**: Crafts professional, clear, and respectful emails by analyzing context, tone, and purpose, ensuring proper structure and formatting.
69. **create_git_diff_commit**: Generates Git commands and commit messages for reflecting changes in a repository, using conventional commits and providing concise shell commands for updates.
70. **create_golden_rules**: Extract enforceable rules from codebases to prevent common mistakes and ensure consistency.
71. **create_graph_from_input**: Generates a CSV file with progress-over-time data for a security program, focusing on relevant metrics and KPIs.
72. **create_hormozi_offer**: Creates a customized business offer based on principles from Alex Hormozi's book, "$100M Offers."
73. **create_idea_compass**: Organizes and structures ideas by exploring their definition, evidence, sources, and related themes or consequences.
74. **create_investigation_visualization**: Creates detailed Graphviz visualizations of complex input, highlighting key aspects and providing clear, well-annotated diagrams for investigative analysis and conclusions.
75. **create_keynote**: Creates TED-style keynote presentations with a clear narrative, structured slides, and speaker notes, emphasizing impactful takeaways and cohesive flow.
76. **create_loe_document**: Creates detailed Level of Effort documents for estimating work effort, resources, and costs for tasks or projects.
77. **create_logo**: Creates simple, minimalist company logos without text, generating AI prompts for vector graphic logos based on input.
78. **create_markmap_visualization**: Transforms complex ideas into clear visualizations using MarkMap syntax, simplifying concepts into diagrams with relationships, boxes, arrows, and labels.
79. **create_mermaid_visualization**: Creates detailed, standalone visualizations of concepts using Mermaid (Markdown) syntax, ensuring clarity and coherence in diagrams.
80. **create_mermaid_visualization_for_github**: Creates standalone, detailed visualizations using Mermaid (Markdown) syntax to effectively explain complex concepts, ensuring clarity and precision.
81. **create_micro_summary**: Summarizes content into a concise, 20-word summary with main points and takeaways, formatted in Markdown.
82. **create_mnemonic_phrases**: Creates memorable mnemonic sentences from given words to aid in memory retention and learning.
83. **create_network_threat_landscape**: Analyzes open ports and services from a network scan and generates a comprehensive, insightful, and detailed security threat report in Markdown.
84. **create_newsletter_entry**: Condenses provided article text into a concise, objective, newsletter-style summary with a title in the style of Frontend Weekly.
85. **create_npc**: Generates a detailed D&D 5E NPC, including background, flaws, stats, appearance, personality, goals, and more in Markdown format.
86. **create_pattern**: Extracts, organizes, and formats LLM/AI prompts into structured sections, detailing the AI's role, instructions, output format, and any provided examples for clarity and accuracy.
87. **create_prd**: Creates a precise Product Requirements Document (PRD) in Markdown based on input.
88. **create_prediction_block**: Extracts and formats predictions from input into a structured Markdown block for a blog post.
89. **create_quiz**: Creates a three-phase reading plan based on an author or topic to help the user become significantly knowledgeable, including core, extended, and supplementary readings.
90. **create_reading_plan**: Generates review questions based on learning objectives from the input, adapted to the specified student level, and outputs them in a clear markdown format.
91. **create_recursive_outline**: Breaks down complex tasks or projects into manageable, hierarchical components with recursive outlining for clarity and simplicity.
92. **create_report_finding**: Creates a detailed, structured security finding report in markdown, including sections on Description, Risk, Recommendations, References, One-Sentence-Summary, and Quotes.
93. **create_rpg_summary**: Summarizes an in-person RPG session with key events, combat details, player stats, and role-playing highlights in a structured format.
94. **create_security_update**: Creates concise security updates for newsletters, covering stories, threats, advisories, vulnerabilities, and a summary of key issues.
95. **create_show_intro**: Creates compelling short intros for podcasts, summarizing key topics and themes discussed in the episode.
96. **create_sigma_rules**: Extracts Tactics, Techniques, and Procedures (TTPs) from security news and converts them into Sigma detection rules for host-based detections.
97. **create_slides**: Transforms content into engaging Reveal.js HTML slideshows with minimal text, using inline SVG illustrations, charts, and diagrams to visually support the presenter's narrative.
98. **create_story_about_people_interaction**: Analyze two personas, compare their dynamics, and craft a realistic, character-driven story from those insights.
99. **create_story_about_person**: Creates compelling, realistic short stories based on psychological profiles, showing how characters navigate everyday problems using strategies consistent with their personality traits.
100. **create_story_explanation**: Summarizes complex content in a clear, approachable story format that makes the concepts easy to understand.
101. **create_stride_threat_model**: Create a STRIDE-based threat model for a system design, identifying assets, trust boundaries, data flows, and prioritizing threats with mitigations.
102. **create_summary**: Summarizes content into a 20-word sentence, 10 main points (16 words max), and 5 key takeaways in Markdown format.
103. **create_tags**: Identifies at least 5 tags from text content for mind mapping tools, including authors and existing tags if present.
104. **create_threat_scenarios**: Identifies likely attack methods for any system by providing a narrative-based threat model, balancing risk and opportunity.
105. **create_ttrc_graph**: Creates a CSV file showing the progress of Time to Remediate Critical Vulnerabilities over time using given data.
106. **create_ttrc_narrative**: Creates a persuasive narrative highlighting progress in reducing the Time to Remediate Critical Vulnerabilities metric over time.
107. **create_upgrade_pack**: Extracts world model and task algorithm updates from content, providing beliefs about how the world works and task performance.
108. **create_user_story**: Writes concise and clear technical user stories for new features in complex software programs, formatted for all stakeholders.
109. **create_video_chapters**: Extracts interesting topics and timestamps from a transcript, providing concise summaries of key moments.
110. **create_visualization**: Transforms complex ideas into visualizations using intricate ASCII art, simplifying concepts where necessary.
111. **detect_mind_virus**: Detects "mind viruses" — ideas or belief systems that spread by exploiting cognitive shortcuts (fear, guilt, identity) while resisting correction through logic or evidence.
112. **detect_silent_victims**: Analyzes actions, policies, or systems to identify parties harmed but unable to speak up — future generations, voiceless groups, unaware individuals, diffuse populations, or structural victims.
113. **dialog_with_socrates**: Engages in deep, meaningful dialogues to explore and challenge beliefs using the Socratic method.
114. **enrich_blog_post**: Enhances Markdown blog files by applying instructions to improve structure, visuals, and readability for HTML rendering.
115. **explain_code**: Explains code, security tool output, configuration text, and answers questions based on the provided input.
116. **explain_docs**: Improves and restructures tool documentation into clear, concise instructions, including overviews, usage, use cases, and key features.
117. **explain_math**: Helps you understand mathematical concepts in a clear and engaging way.
118. **explain_project**: Summarizes project documentation into clear, concise sections covering the project, problem, solution, installation, usage, and examples.
119. **explain_terms**: Produces a glossary of advanced terms from content, providing a definition, analogy, and explanation of why each term matters.
120. **explain_terms_and_conditions**: Analyzes Terms and Conditions and legal agreements, translating complex legalese into plain English, identifying red flags, hidden fees, and privacy risks, with a final verdict on whether to sign.
121. **export_data_as_csv**: Extracts and outputs all data structures from the input in properly formatted CSV data.
122. **extract_algorithm_update_recommendations**: Extracts concise, practical algorithm update recommendations from the input and outputs them in a bulleted list.
123. **extract_all_quotes**: Extract all inspirational and educational quotes from content including podcasts and essays.
124. **extract_alpha**: Extracts the most novel and surprising ideas ("alpha") from content, inspired by information theory.
125. **extract_article_wisdom**: Extracts surprising, insightful, and interesting information from content, categorizing it into sections like summary, ideas, quotes, facts, references, and recommendations.
126. **extract_book_ideas**: Extracts and outputs 50 to 100 of the most surprising, insightful, and interesting ideas from a book's content.
127. **extract_book_recommendations**: Extracts and outputs 50 to 100 practical, actionable recommendations from a book's content.
128. **extract_bd_ideas**: Extract actionable ideas from content and transform into bd create commands.
129. **extract_business_ideas**: Extracts top business ideas from content and elaborates on the best 10 with unique differentiators.
130. **extract_characters**: Identify all characters (human and non-human), resolve their aliases and pronouns into canonical names, and produce detailed descriptions of each character's role, motivations, and interactions ranked by narrative importance.
131. **extract_controversial_ideas**: Extracts and outputs controversial statements and supporting quotes from the input in a structured Markdown list.
132. **extract_core_message**: Extracts and outputs a clear, concise sentence that articulates the core message of a given text or body of work.
133. **extract_ctf_writeup**: Extracts a short writeup from a warstory-like text about a cyber security engagement.
134. **extract_domains**: Extracts domains and URLs from content to identify sources used for articles, newsletters, and other publications.
135. **extract_ethical_framework**: Extracts and analyzes the implicit ethical framework embedded in any prescriptive text, checking internal consistency and whether it creates unwilling victims.
136. **extract_extraordinary_claims**: Extracts and outputs a list of extraordinary claims from conversations, focusing on scientifically disputed or false statements.
137. **extract_ideas**: Extracts and outputs all the key ideas from input, presented as 15-word bullet points in Markdown.
138. **extract_insights**: Extracts and outputs the most powerful and insightful ideas from text, formatted as 16-word bullet points in the INSIGHTS section, also IDEAS section.
139. **extract_insights_dm**: Extracts and outputs all valuable insights and a concise summary of the content, including key points and topics discussed.
140. **extract_instructions**: Extracts clear, actionable step-by-step instructions and main objectives from instructional video transcripts, organizing them into a concise list.
141. **extract_jokes**: Extracts jokes from text content, presenting each joke with its punchline in separate bullet points.
142. **extract_latest_video**: Extracts the latest video URL from a YouTube RSS feed and outputs the URL only.
143. **extract_main_activities**: Extracts key events and activities from transcripts or logs, providing a summary of what happened.
144. **extract_main_idea**: Extracts the main idea and key recommendation from the input, summarizing them in 15-word sentences.
145. **extract_mcp_servers**: Identify and summarize Model Context Protocol (MCP) servers referenced in the input along with their key details.
146. **extract_most_redeeming_thing**: Extracts the most redeeming aspect from an input, summarizing it in a single 15-word sentence.
147. **extract_patterns**: Extracts and analyzes recurring, surprising, and insightful patterns from input, providing detailed analysis and advice for builders.
148. **extract_poc**: Extracts proof of concept URLs and validation methods from security reports, providing the URL and command to run.
149. **extract_predictions**: Extracts predictions from input, including specific details such as date, confidence level, and verification method.
150. **extract_primary_problem**: Extracts the primary problem with the world as presented in a given text or body of work.
151. **extract_primary_solution**: Extracts the primary solution for the world as presented in a given text or body of work.
152. **extract_product_features**: Extracts and outputs a list of product features from the provided input in a bulleted format.
153. **extract_questions**: Extracts and outputs all questions asked by the interviewer in a conversation or interview.
154. **extract_recipe**: Extracts and outputs a recipe with a short meal description, ingredients with measurements, and preparation steps.
155. **extract_recommendations**: Extracts and outputs concise, practical recommendations from a given piece of content in a bulleted list.
156. **extract_references**: Extracts and outputs a bulleted list of references to art, stories, books, literature, and other sources from content.
157. **extract_skills**: Extracts and classifies skills from a job description into a table, separating each skill and classifying it as either hard or soft.
158. **extract_song_meaning**: Analyzes a song to provide a summary of its meaning, supported by detailed evidence from lyrics, artist commentary, and fan analysis.
159. **extract_sponsors**: Extracts and lists official sponsors and potential sponsors from a provided transcript.
160. **extract_videoid**: Extracts and outputs the video ID from any given URL.
161. **extract_wisdom**: Extracts surprising, insightful, and interesting information from text on topics like human flourishing, AI, learning, and more.
162. **extract_wisdom_agents**: Extracts valuable insights, ideas, quotes, and references from content, emphasizing topics like human flourishing, AI, learning, and technology.
163. **extract_wisdom_with_attribution**: Extracts insightful ideas and recommendations with speaker attribution for quotes, focusing on life wisdom and human flourishing.
164. **extract_wisdom_dm**: Extracts all valuable, insightful, and thought-provoking information from content, focusing on topics like human flourishing, AI, learning, and technology.
165. **extract_wisdom_nometa**: Extracts insights, ideas, quotes, habits, facts, references, and recommendations from content, focusing on human flourishing, AI, technology, and related topics.
166. **find_female_life_partner**: Analyzes criteria for finding a female life partner and provides clear, direct, and poetic descriptions.
167. **find_hidden_message**: Extracts overt and hidden political messages, justifications, audience actions, and a cynical analysis from content.
168. **find_logical_fallacies**: Identifies and analyzes fallacies in arguments, classifying them as formal or informal with detailed reasoning.
169. **fix_typos**: Proofreads and corrects typos, spelling, grammar, and punctuation errors in text.
170. **generate_code_rules**: Compile best-practice coding rules and guardrails for AI-assisted development workflows from the provided content.
171. **get_wow_per_minute**: Determines the wow-factor of content per minute based on surprise, novelty, insight, value, and wisdom, measuring how rewarding the content is for the viewer.
172. **greybeard_secure_prompt_engineer**: Creates secure, production-grade system prompts with NASA-style mission assurance, outputting hardened prompts, injection test suites, and evaluation rubrics.
173. **heal_person**: Develops a comprehensive plan for spiritual and mental healing based on psychological profiles, providing personalized recommendations for mental health improvement and overall life enhancement.
174. **humanize**: Rewrites AI-generated text to sound natural, conversational, and easy to understand, maintaining clarity and simplicity.
175. **identify_dsrp_distinctions**: Encourages creative, systems-based thinking by exploring distinctions, boundaries, and their implications, drawing on insights from prominent systems thinkers.
176. **identify_dsrp_perspectives**: Explores the concept of distinctions in systems thinking, focusing on how boundaries define ideas, influence understanding, and reveal or obscure insights.
177. **identify_dsrp_relationships**: Encourages exploration of connections, distinctions, and boundaries between ideas, inspired by systems thinkers to reveal new insights and patterns in complex systems.
178. **identify_dsrp_systems**: Encourages organizing ideas into systems of parts and wholes, inspired by systems thinkers to explore relationships and how changes in organization impact meaning and understanding.
179. **identify_job_stories**: Identifies key job stories or requirements for roles.
180. **improve_academic_writing**: Refines text into clear, concise academic language while improving grammar, coherence, and clarity, with a list of changes.
181. **improve_prompt**: Improves an LLM/AI prompt by applying expert prompt writing strategies for better results and clarity.
182. **improve_report_finding**: Improves a penetration test security finding by providing detailed descriptions, risks, recommendations, references, quotes, and a concise summary in markdown format.
183. **improve_writing**: Refines text by correcting grammar, enhancing style, improving clarity, and maintaining the original meaning. skills.
184. **judge_output**: Evaluates Honeycomb queries by judging their effectiveness, providing critiques and outcomes based on language nuances and analytics relevance.
185. **label_and_rate**: Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
186. **md_callout**: Classifies content and generates a markdown callout based on the provided text, selecting the most appropriate type.
187. **model_as_sherlock_freud**: Builds psychological models using detective reasoning and psychoanalytic insight to understand human behavior.
188. **official_pattern_template**: Template to use if you want to create new fabric patterns.
189. **predict_person_actions**: Predicts behavioral responses based on psychological profiles and challenges.
190. **prepare_7s_strategy**: Prepares a comprehensive briefing document from 7S's strategy capturing organizational profile, strategic elements, and market dynamics with clear, concise, and organized content.
191. **provide_guidance**: Provides psychological and life coaching advice, including analysis, recommendations, and potential diagnoses, with a compassionate and honest tone.
192. **rate_ai_response**: Rates the quality of AI responses by comparing them to top human expert performance, assigning a letter grade, reasoning, and providing a 1-100 score based on the evaluation.
193. **rate_ai_result**: Assesses the quality of AI/ML/LLM work by deeply analyzing content, instructions, and output, then rates performance based on multiple dimensions, including coverage, creativity, and interdisciplinary thinking.
194. **rate_content**: Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
195. **rate_value**: Produces the best possible output by deeply analyzing and understanding the input and its intended purpose.
196. **raw_query**: Fully digests and contemplates the input to produce the best possible result based on understanding the sender's intent.
197. **recommend_artists**: Recommends a personalized festival schedule with artists aligned to your favorite styles and interests, including rationale.
198. **recommend_pipeline_upgrades**: Optimizes vulnerability-checking pipelines by incorporating new information and improving their efficiency, with detailed explanations of changes.
199. **recommend_talkpanel_topics**: Produces a clean set of proposed talks or panel talking points for a person based on their interests and goals, formatted for submission to a conference organizer.
200. **recommend_yoga_practice**: Provides personalized yoga sequences, meditation guidance, and holistic lifestyle advice based on individual profiles.
201. **refine_design_document**: Refines a design document based on a design review by analyzing, mapping concepts, and implementing changes using valid Markdown.
202. **review_design**: Reviews and analyzes architecture design, focusing on clarity, component design, system integrations, security, performance, scalability, and data management.
203. **review_code**: Performs a comprehensive code review, providing detailed feedback on correctness, security, and performance.
204. **sanitize_broken_html_to_markdown**: Converts messy HTML into clean, properly formatted Markdown, applying custom styling and ensuring compatibility with Vite.
205. **suggest_pattern**: Suggests appropriate fabric patterns or commands based on user input, providing clear explanations and options for users.
206. **suggest_gt_command**: Suggest optimal Gas Town (GT) commands based on user intent and task description.
207. **suggest_openclaw_pattern**: Suggests the most appropriate Openclaw CLI command based on user intent, mapping natural language requests to commands for messaging, device management, scheduling, and automation.
208. **summarize**: Summarizes content into a 20-word sentence, main points, and takeaways, formatted with numbered lists in Markdown.
209. **summarize_board_meeting**: Creates formal meeting notes from board meeting transcripts for corporate governance documentation.
210. **summarize_debate**: Summarizes debates, identifies primary disagreement, extracts arguments, and provides analysis of evidence and argument strength to predict outcomes.
211. **summarize_git_changes**: Summarizes recent project updates from the last 7 days, focusing on key changes with enthusiasm.
212. **summarize_git_diff**: Summarizes and organizes Git diff changes with clear, succinct commit messages and bullet points.
213. **summarize_lecture**: Extracts relevant topics, definitions, and tools from lecture transcripts, providing structured summaries with timestamps and key takeaways.
214. **summarize_legislation**: Summarizes complex political proposals and legislation by analyzing key points, proposed changes, and providing balanced, positive, and cynical characterizations.
215. **summarize_meeting**: Analyzes meeting transcripts to extract a structured summary, including an overview, key points, tasks, decisions, challenges, timeline, references, and next steps.
216. **summarize_micro**: Summarizes content into a 20-word sentence, 3 main points, and 3 takeaways, formatted in clear, concise Markdown.
217. **summarize_newsletter**: Extracts the most meaningful, interesting, and useful content from a newsletter, summarizing key sections such as content, opinions, tools, companies, and follow-up items in clear, structured Markdown.
218. **summarize_paper**: Summarizes an academic paper by detailing its title, authors, technical approach, distinctive features, experimental setup, results, advantages, limitations, and conclusion in a clear, structured format using human-readable Markdown.
219. **summarize_prompt**: Summarizes AI chat prompts by describing the primary function, unique approach, and expected output in a concise paragraph. The summary is focused on the prompt's purpose without unnecessary details or formatting.
220. **summarize_pull-requests**: Summarizes pull requests for a coding project by providing a summary and listing the top PRs with human-readable descriptions.
221. **summarize_rpg_session**: Summarizes a role-playing game session by extracting key events, combat stats, character changes, quotes, and more.
222. **t_analyze_challenge_handling**: Provides 8-16 word bullet points evaluating how well challenges are being addressed, calling out any lack of effort.
223. **t_check_dunning_kruger**: Assess narratives for Dunning-Kruger patterns by contrasting self-perception with demonstrated competence and confidence cues.
224. **t_check_metrics**: Analyzes deep context from the TELOS file and input instruction, then provides a wisdom-based output while considering metrics and KPIs to assess recent improvements.
225. **t_create_h3_career**: Summarizes context and produces wisdom-based output by deeply analyzing both the TELOS File and the input instruction, considering the relationship between the two.
226. **t_create_opening_sentences**: Describes from TELOS file the person's identity, goals, and actions in 4 concise, 32-word bullet points, humbly.
227. **t_describe_life_outlook**: Describes from TELOS file a person's life outlook in 5 concise, 16-word bullet points.
228. **t_extract_intro_sentences**: Summarizes from TELOS file a person's identity, work, and current projects in 5 concise and grounded bullet points.
229. **t_extract_panel_topics**: Creates 5 panel ideas with titles and descriptions based on deep context from a TELOS file and input.
230. **t_find_blindspots**: Identify potential blindspots in thinking, frames, or models that may expose the individual to error or risk.
231. **t_find_negative_thinking**: Analyze a TELOS file and input to identify negative thinking in documents or journals, followed by tough love encouragement.
232. **t_find_neglected_goals**: Analyze a TELOS file and input instructions to identify goals or projects that have not been worked on recently.
233. **t_give_encouragement**: Analyze a TELOS file and input instructions to evaluate progress, provide encouragement, and offer recommendations for continued effort.
234. **t_red_team_thinking**: Analyze a TELOS file and input instructions to red-team thinking, models, and frames, then provide recommendations for improvement.
235. **t_threat_model_plans**: Analyze a TELOS file and input instructions to create threat models for a life plan and recommend improvements.
236. **t_visualize_mission_goals_projects**: Analyze a TELOS file and input instructions to create an ASCII art diagram illustrating the relationship of missions, goals, and projects.
237. **t_year_in_review**: Analyze a TELOS file to create insights about a person or entity, then summarize accomplishments and visualizations in bullet points.
238. **to_flashcards**: Create Anki flashcards from a given text, focusing on concise, optimized questions and answers without external context.
239. **transcribe_minutes**: Extracts (from meeting transcription) meeting minutes, identifying actionables, insightful ideas, decisions, challenges, and next steps in a structured format.
240. **translate**: Translates sentences or documentation into the specified language code while maintaining the original formatting and tone.
241. **tweet**: Provides a step-by-step guide on crafting engaging tweets with emojis, covering Twitter basics, account creation, features, and audience targeting.
242. **ultimate_law_safety**: Evaluates actions, policies, or systems against the Ultimate Law framework — a minimal, falsifiable ethical constraint that prohibits creating unwilling victims.
243. **write_essay**: Writes essays in the style of a specified author, embodying their unique voice, vocabulary, and approach. Uses `author_name` variable.
244. **write_essay_pg**: Writes concise, clear essays in the style of Paul Graham, focusing on simplicity, clarity, and illumination of the provided topic.
245. **write_hackerone_report**: Generates concise, clear, and reproducible bug bounty reports, detailing vulnerability impact, steps to reproduce, and exploit details for triagers.
246. **write_latex**: Generates syntactically correct LaTeX code for a new.tex document, ensuring proper formatting and compatibility with pdflatex.
247. **write_micro_essay**: Writes concise, clear, and illuminating essays on the given topic in the style of Paul Graham.
248. **write_nuclei_template_rule**: Generates Nuclei YAML templates for detecting vulnerabilities using HTTP requests, matchers, extractors, and dynamic data extraction.
249. **write_pull-request**: Drafts detailed pull request descriptions, explaining changes, providing reasoning, and identifying potential bugs from the git diff command output.
250. **write_semgrep_rule**: Creates accurate and working Semgrep rules based on input, following syntax guidelines and specific language considerations.
251. **youtube_summary**: Create concise, timestamped Youtube video summaries that highlight key points.
19. **analyze_monetization_opportunities**: Identifies monetization opportunities in creator content by aligning affiliate commerce, sponsorships, digital products, and communities with audience intent, effort level, and time sensitivity.
20. **analyze_paper**: Analyses research papers by summarizing findings, evaluating rigor, and assessing quality to provide insights for documentation and review.
21. **analyze_paper_simple**: Analyzes academic papers with a focus on primary findings, research quality, and study design evaluation.
22. **analyze_patent**: Analyse a patent's field, problem, solution, novelty, inventive step, and advantages in detail while summarizing and extracting keywords.
23. **analyze_personality**: Performs a deep psychological analysis of a person in the input, focusing on their behavior, language, and psychological traits.
24. **analyze_presentation**: Reviews and critiques presentations by analyzing the content, speaker's underlying goals, self-focus, and entertainment value.
25. **analyze_product_feedback**: A prompt for analyzing and organizing user feedback by identifying themes, consolidating similar comments, and prioritizing them based on usefulness.
26. **analyze_proposition**: Analyzes a ballot proposition by identifying its purpose, impact, arguments for and against, and relevant background information.
27. **analyze_prose**: Evaluates writing for novelty, clarity, and prose, providing ratings, improvement recommendations, and an overall score.
28. **analyze_prose_json**: Evaluates writing for novelty, clarity, prose, and provides ratings, explanations, improvement suggestions, and an overall score in a JSON format.
29. **analyze_prose_pinker**: Evaluates prose based on Steven Pinker's The Sense of Style, analyzing writing style, clarity, and bad writing elements.
30. **analyze_risk**: Conducts a risk assessment of a third-party vendor, assigning a risk score and suggesting security controls based on analysis of provided documents and vendor website.
31. **analyze_sales_call**: Rates sales call performance across multiple dimensions, providing scores and actionable feedback based on transcript analysis.
32. **analyze_spiritual_text**: Compares and contrasts spiritual texts by analyzing claims and differences with the King James Bible.
33. **analyze_tech_impact**: Analyzes the societal impact, ethical considerations, and sustainability of technology projects, evaluating their outcomes and benefits.
34. **analyze_terraform_plan**: Analyzes Terraform plan outputs to assess infrastructure changes, security risks, cost implications, and compliance considerations.
35. **analyze_threat_report**: Extracts surprising insights, trends, statistics, quotes, references, and recommendations from cybersecurity threat reports, summarizing key findings and providing actionable information.
36. **analyze_threat_report_cmds**: Extract and synthesize actionable cybersecurity commands from provided materials, incorporating command-line arguments and expert insights for pentesters and non-experts.
37. **analyze_threat_report_trends**: Extract up to 50 surprising, insightful, and interesting trends from a cybersecurity threat report in markdown format.
38. **answer_interview_question**: Generates concise, tailored responses to technical interview questions, incorporating alternative approaches and evidence to demonstrate the candidate's expertise and experience.
39. **apply_ul_tags**: Apply standardized content tags to categorize topics like AI, cybersecurity, politics, and culture.
40. **ask_secure_by_design_questions**: Generates a set of security-focused questions to ensure a project is built securely by design, covering key components and considerations.
41. **ask_uncle_duke**: Coordinates a team of AI agents to research and produce multiple software development solutions based on provided specifications, and conducts detailed code reviews to ensure adherence to best practices.
42. **audit_consent**: Evaluates whether consent in interactions or agreements is genuine or manufactured by analyzing power asymmetries, information gaps, alternatives, and coercion using a five-test framework.
43. **audit_transparency**: Audits decisions, systems, and algorithms for explainability across five dimensions, assessing whether opacity is justified or serves to conceal harm from affected parties.
44. **capture_thinkers_work**: Analyze philosophers or philosophies and provide detailed summaries about their teachings, background, works, advice, and related concepts in a structured template.
45. **check_agreement**: Analyze contracts and agreements to identify important stipulations, issues, and potential gotchas, then summarize them in Markdown.
46. **check_falsifiability**: Evaluates whether claims, definitions, frameworks, and arguments meet the standard of falsifiability — whether they can be tested and potentially proven wrong.
47. **clean_text**: Fix broken or malformatted text by correcting line breaks, punctuation, capitalization, and paragraphs without altering content or spelling.
48. **coding_master**: Explain a coding concept to a beginner, providing examples, and formatting code in markdown with specific output sections like ideas, recommendations, facts, and insights.
49. **compare_and_contrast**: Compare and contrast a list of items in a markdown table, with items on the left and topics on top.
50. **concall_summary**: Analyzes earnings and conference call transcripts to extract management commentary, analyst Q&A, financial insights, risks, and executive summaries.
51. **convert_to_markdown**: Convert content to clean, complete Markdown format, preserving all original structure, formatting, links, and code blocks without alterations.
52. **create_5_sentence_summary**: Create concise summaries or answers to input at 5 different levels of depth, from 5 words to 1 word.
53. **create_academic_paper**: Generate a high-quality academic paper in LaTeX format with clear concepts, structured content, and a professional layout.
54. **create_ai_jobs_analysis**: Analyze job categories' susceptibility to automation, identify resilient roles, and provide strategies for personal adaptation to AI-driven changes in the workforce.
55. **create_aphorisms**: Find and generate a list of brief, witty statements.
56. **create_art_prompt**: Generates a detailed, compelling visual description of a concept, including stylistic references and direct AI instructions for creating art.
57. **create_better_frame**: Identifies and analyzes different frames of interpreting reality, emphasizing the power of positive, productive lenses in shaping outcomes.
58. **create_bd_issue**: Transform natural language descriptions into optimal bd create commands for issue tracking.
59. **create_coding_feature**: Generates secure and composable code features using modern technology and best practices from project specifications.
60. **create_coding_project**: Generate wireframes and starter code for any coding ideas that you have.
61. **create_command**: Helps determine the correct parameters and switches for penetration testing tools based on a brief description of the objective.
62. **create_conceptmap**: Transforms unstructured text or markdown content into an interactive HTML concept map using Vis.js by extracting key concepts and their logical relationships.
63. **create_cyber_summary**: Summarizes cybersecurity threats, vulnerabilities, incidents, and malware with a 25-word summary and categorized bullet points, after thoroughly analyzing and mapping the provided input.
64. **create_design_system**: Create comprehensive CSS design systems with tokens, typography, spacing, and components.
65. **create_design_document**: Creates a detailed design document for a system using the C4 model, addressing business and security postures, and including a system context diagram.
66. **create_diy**: Creates structured "Do It Yourself" tutorial patterns by analyzing prompts, organizing requirements, and providing step-by-step instructions in Markdown format.
67. **create_excalidraw_visualization**: Creates complex Excalidraw diagrams to visualize relationships between concepts and ideas in structured format.
68. **create_flash_cards**: Creates flashcards for key concepts, definitions, and terms with question-answer format for educational purposes.
69. **create_formal_email**: Crafts professional, clear, and respectful emails by analyzing context, tone, and purpose, ensuring proper structure and formatting.
70. **create_git_diff_commit**: Generates Git commands and commit messages for reflecting changes in a repository, using conventional commits and providing concise shell commands for updates.
71. **create_golden_rules**: Extract enforceable rules from codebases to prevent common mistakes and ensure consistency.
72. **create_graph_from_input**: Generates a CSV file with progress-over-time data for a security program, focusing on relevant metrics and KPIs.
73. **create_hormozi_offer**: Creates a customized business offer based on principles from Alex Hormozi's book, "$100M Offers."
74. **create_idea_compass**: Organizes and structures ideas by exploring their definition, evidence, sources, and related themes or consequences.
75. **create_investigation_visualization**: Creates detailed Graphviz visualizations of complex input, highlighting key aspects and providing clear, well-annotated diagrams for investigative analysis and conclusions.
76. **create_keynote**: Creates TED-style keynote presentations with a clear narrative, structured slides, and speaker notes, emphasizing impactful takeaways and cohesive flow.
77. **create_loe_document**: Creates detailed Level of Effort documents for estimating work effort, resources, and costs for tasks or projects.
78. **create_logo**: Creates simple, minimalist company logos without text, generating AI prompts for vector graphic logos based on input.
79. **create_markmap_visualization**: Transforms complex ideas into clear visualizations using MarkMap syntax, simplifying concepts into diagrams with relationships, boxes, arrows, and labels.
80. **create_mermaid_visualization**: Creates detailed, standalone visualizations of concepts using Mermaid (Markdown) syntax, ensuring clarity and coherence in diagrams.
81. **create_mermaid_visualization_for_github**: Creates standalone, detailed visualizations using Mermaid (Markdown) syntax to effectively explain complex concepts, ensuring clarity and precision.
82. **create_micro_summary**: Summarizes content into a concise, 20-word summary with main points and takeaways, formatted in Markdown.
83. **create_mnemonic_phrases**: Creates memorable mnemonic sentences from given words to aid in memory retention and learning.
84. **create_network_threat_landscape**: Analyzes open ports and services from a network scan and generates a comprehensive, insightful, and detailed security threat report in Markdown.
85. **create_newsletter_entry**: Condenses provided article text into a concise, objective, newsletter-style summary with a title in the style of Frontend Weekly.
86. **create_npc**: Generates a detailed D&D 5E NPC, including background, flaws, stats, appearance, personality, goals, and more in Markdown format.
87. **create_pattern**: Extracts, organizes, and formats LLM/AI prompts into structured sections, detailing the AI's role, instructions, output format, and any provided examples for clarity and accuracy.
88. **create_prd**: Creates a precise Product Requirements Document (PRD) in Markdown based on input.
89. **create_prediction_block**: Extracts and formats predictions from input into a structured Markdown block for a blog post.
90. **create_quiz**: Creates a three-phase reading plan based on an author or topic to help the user become significantly knowledgeable, including core, extended, and supplementary readings.
91. **create_reading_plan**: Generates review questions based on learning objectives from the input, adapted to the specified student level, and outputs them in a clear markdown format.
92. **create_recursive_outline**: Breaks down complex tasks or projects into manageable, hierarchical components with recursive outlining for clarity and simplicity.
93. **create_report_finding**: Creates a detailed, structured security finding report in markdown, including sections on Description, Risk, Recommendations, References, One-Sentence-Summary, and Quotes.
94. **create_rpg_summary**: Summarizes an in-person RPG session with key events, combat details, player stats, and role-playing highlights in a structured format.
95. **create_security_update**: Creates concise security updates for newsletters, covering stories, threats, advisories, vulnerabilities, and a summary of key issues.
96. **create_show_intro**: Creates compelling short intros for podcasts, summarizing key topics and themes discussed in the episode.
97. **create_sigma_rules**: Extracts Tactics, Techniques, and Procedures (TTPs) from security news and converts them into Sigma detection rules for host-based detections.
98. **create_slides**: Transforms content into engaging Reveal.js HTML slideshows with minimal text, using inline SVG illustrations, charts, and diagrams to visually support the presenter's narrative.
99. **create_story_about_people_interaction**: Analyze two personas, compare their dynamics, and craft a realistic, character-driven story from those insights.
100. **create_story_about_person**: Creates compelling, realistic short stories based on psychological profiles, showing how characters navigate everyday problems using strategies consistent with their personality traits.
101. **create_story_explanation**: Summarizes complex content in a clear, approachable story format that makes the concepts easy to understand.
102. **create_stride_threat_model**: Create a STRIDE-based threat model for a system design, identifying assets, trust boundaries, data flows, and prioritizing threats with mitigations.
103. **create_summary**: Summarizes content into a 20-word sentence, 10 main points (16 words max), and 5 key takeaways in Markdown format.
104. **create_tags**: Identifies at least 5 tags from text content for mind mapping tools, including authors and existing tags if present.
105. **create_threat_scenarios**: Identifies likely attack methods for any system by providing a narrative-based threat model, balancing risk and opportunity.
106. **create_ttrc_graph**: Creates a CSV file showing the progress of Time to Remediate Critical Vulnerabilities over time using given data.
107. **create_ttrc_narrative**: Creates a persuasive narrative highlighting progress in reducing the Time to Remediate Critical Vulnerabilities metric over time.
108. **create_upgrade_pack**: Extracts world model and task algorithm updates from content, providing beliefs about how the world works and task performance.
109. **create_user_story**: Writes concise and clear technical user stories for new features in complex software programs, formatted for all stakeholders.
110. **create_video_chapters**: Extracts interesting topics and timestamps from a transcript, providing concise summaries of key moments.
111. **create_visualization**: Transforms complex ideas into visualizations using intricate ASCII art, simplifying concepts where necessary.
112. **detect_mind_virus**: Detects "mind viruses" — ideas or belief systems that spread by exploiting cognitive shortcuts (fear, guilt, identity) while resisting correction through logic or evidence.
113. **detect_silent_victims**: Analyzes actions, policies, or systems to identify parties harmed but unable to speak up — future generations, voiceless groups, unaware individuals, diffuse populations, or structural victims.
114. **dialog_with_socrates**: Engages in deep, meaningful dialogues to explore and challenge beliefs using the Socratic method.
115. **enrich_blog_post**: Enhances Markdown blog files by applying instructions to improve structure, visuals, and readability for HTML rendering.
116. **explain_code**: Explains code, security tool output, configuration text, and answers questions based on the provided input.
117. **explain_docs**: Improves and restructures tool documentation into clear, concise instructions, including overviews, usage, use cases, and key features.
118. **explain_math**: Helps you understand mathematical concepts in a clear and engaging way.
119. **explain_project**: Summarizes project documentation into clear, concise sections covering the project, problem, solution, installation, usage, and examples.
120. **explain_terms**: Produces a glossary of advanced terms from content, providing a definition, analogy, and explanation of why each term matters.
121. **explain_terms_and_conditions**: Analyzes Terms and Conditions and legal agreements, translating complex legalese into plain English, identifying red flags, hidden fees, and privacy risks, with a final verdict on whether to sign.
122. **export_data_as_csv**: Extracts and outputs all data structures from the input in properly formatted CSV data.
123. **extract_affiliate_products**: Extracts commercial products, tools, services, and brands from content transcripts, separating sponsored from organic mentions and estimating commission tiers for affiliate opportunities.
124. **extract_algorithm_update_recommendations**: Extracts concise, practical algorithm update recommendations from the input and outputs them in a bulleted list.
125. **extract_all_quotes**: Extract all inspirational and educational quotes from content including podcasts and essays.
126. **extract_alpha**: Extracts the most novel and surprising ideas ("alpha") from content, inspired by information theory.
127. **extract_article_wisdom**: Extracts surprising, insightful, and interesting information from content, categorizing it into sections like summary, ideas, quotes, facts, references, and recommendations.
128. **extract_book_ideas**: Extracts and outputs 50 to 100 of the most surprising, insightful, and interesting ideas from a book's content.
129. **extract_book_recommendations**: Extracts and outputs 50 to 100 practical, actionable recommendations from a book's content.
130. **extract_bd_ideas**: Extract actionable ideas from content and transform into bd create commands.
131. **extract_business_ideas**: Extracts top business ideas from content and elaborates on the best 10 with unique differentiators.
132. **extract_characters**: Identify all characters (human and non-human), resolve their aliases and pronouns into canonical names, and produce detailed descriptions of each character's role, motivations, and interactions ranked by narrative importance.
133. **extract_controversial_ideas**: Extracts and outputs controversial statements and supporting quotes from the input in a structured Markdown list.
134. **extract_core_message**: Extracts and outputs a clear, concise sentence that articulates the core message of a given text or body of work.
135. **extract_ctf_writeup**: Extracts a short writeup from a warstory-like text about a cyber security engagement.
136. **extract_domains**: Extracts domains and URLs from content to identify sources used for articles, newsletters, and other publications.
137. **extract_ethical_framework**: Extracts and analyzes the implicit ethical framework embedded in any prescriptive text, checking internal consistency and whether it creates unwilling victims.
138. **extract_extraordinary_claims**: Extracts and outputs a list of extraordinary claims from conversations, focusing on scientifically disputed or false statements.
139. **extract_ideas**: Extracts and outputs all the key ideas from input, presented as 15-word bullet points in Markdown.
140. **extract_insights**: Extracts and outputs the most powerful and insightful ideas from text, formatted as 16-word bullet points in the INSIGHTS section, also IDEAS section.
141. **extract_insights_dm**: Extracts and outputs all valuable insights and a concise summary of the content, including key points and topics discussed.
142. **extract_instructions**: Extracts clear, actionable step-by-step instructions and main objectives from instructional video transcripts, organizing them into a concise list.
143. **extract_jokes**: Extracts jokes from text content, presenting each joke with its punchline in separate bullet points.
144. **extract_latest_video**: Extracts the latest video URL from a YouTube RSS feed and outputs the URL only.
145. **extract_main_activities**: Extracts key events and activities from transcripts or logs, providing a summary of what happened.
146. **extract_main_idea**: Extracts the main idea and key recommendation from the input, summarizing them in 15-word sentences.
147. **extract_mcp_servers**: Identify and summarize Model Context Protocol (MCP) servers referenced in the input along with their key details.
148. **extract_most_redeeming_thing**: Extracts the most redeeming aspect from an input, summarizing it in a single 15-word sentence.
149. **extract_patterns**: Extracts and analyzes recurring, surprising, and insightful patterns from input, providing detailed analysis and advice for builders.
150. **extract_poc**: Extracts proof of concept URLs and validation methods from security reports, providing the URL and command to run.
151. **extract_predictions**: Extracts predictions from input, including specific details such as date, confidence level, and verification method.
152. **extract_primary_problem**: Extracts the primary problem with the world as presented in a given text or body of work.
153. **extract_primary_solution**: Extracts the primary solution for the world as presented in a given text or body of work.
154. **extract_product_features**: Extracts and outputs a list of product features from the provided input in a bulleted format.
155. **extract_questions**: Extracts and outputs all questions asked by the interviewer in a conversation or interview.
156. **extract_recipe**: Extracts and outputs a recipe with a short meal description, ingredients with measurements, and preparation steps.
157. **extract_recommendations**: Extracts and outputs concise, practical recommendations from a given piece of content in a bulleted list.
158. **extract_references**: Extracts and outputs a bulleted list of references to art, stories, books, literature, and other sources from content.
159. **extract_skills**: Extracts and classifies skills from a job description into a table, separating each skill and classifying it as either hard or soft.
160. **extract_song_meaning**: Analyzes a song to provide a summary of its meaning, supported by detailed evidence from lyrics, artist commentary, and fan analysis.
161. **extract_sponsors**: Extracts and lists official sponsors and potential sponsors from a provided transcript.
162. **extract_video_commerce_entities**: Identifies commercially relevant entities in a video transcript — products, tools, brands, services, and more — grouped by category with mention type, repetition signals, and top purchase candidates.
163. **extract_videoid**: Extracts and outputs the video ID from any given URL.
164. **extract_wisdom**: Extracts surprising, insightful, and interesting information from text on topics like human flourishing, AI, learning, and more.
165. **extract_wisdom_agents**: Extracts valuable insights, ideas, quotes, and references from content, emphasizing topics like human flourishing, AI, learning, and technology.
166. **extract_wisdom_with_attribution**: Extracts insightful ideas and recommendations with speaker attribution for quotes, focusing on life wisdom and human flourishing.
167. **extract_wisdom_dm**: Extracts all valuable, insightful, and thought-provoking information from content, focusing on topics like human flourishing, AI, learning, and technology.
168. **extract_wisdom_nometa**: Extracts insights, ideas, quotes, habits, facts, references, and recommendations from content, focusing on human flourishing, AI, technology, and related topics.
169. **find_female_life_partner**: Analyzes criteria for finding a female life partner and provides clear, direct, and poetic descriptions.
170. **find_hidden_message**: Extracts overt and hidden political messages, justifications, audience actions, and a cynical analysis from content.
171. **find_logical_fallacies**: Identifies and analyzes fallacies in arguments, classifying them as formal or informal with detailed reasoning.
172. **fix_typos**: Proofreads and corrects typos, spelling, grammar, and punctuation errors in text.
173. **generate_code_rules**: Compile best-practice coding rules and guardrails for AI-assisted development workflows from the provided content.
174. **generate_frontmatter**: Generates paste-ready YAML frontmatter for PKM notes, with title, aliases, tags, type, summary, and status fields.
175. **get_wow_per_minute**: Determines the wow-factor of content per minute based on surprise, novelty, insight, value, and wisdom, measuring how rewarding the content is for the viewer.
176. **greybeard_secure_prompt_engineer**: Creates secure, production-grade system prompts with NASA-style mission assurance, outputting hardened prompts, injection test suites, and evaluation rubrics.
177. **heal_person**: Develops a comprehensive plan for spiritual and mental healing based on psychological profiles, providing personalized recommendations for mental health improvement and overall life enhancement.
178. **humanize**: Rewrites AI-generated text to sound natural, conversational, and easy to understand, maintaining clarity and simplicity.
179. **identify_dsrp_distinctions**: Encourages creative, systems-based thinking by exploring distinctions, boundaries, and their implications, drawing on insights from prominent systems thinkers.
180. **identify_dsrp_perspectives**: Explores the concept of distinctions in systems thinking, focusing on how boundaries define ideas, influence understanding, and reveal or obscure insights.
181. **identify_dsrp_relationships**: Encourages exploration of connections, distinctions, and boundaries between ideas, inspired by systems thinkers to reveal new insights and patterns in complex systems.
182. **identify_dsrp_systems**: Encourages organizing ideas into systems of parts and wholes, inspired by systems thinkers to explore relationships and how changes in organization impact meaning and understanding.
183. **identify_job_stories**: Identifies key job stories or requirements for roles.
184. **improve_academic_writing**: Refines text into clear, concise academic language while improving grammar, coherence, and clarity, with a list of changes.
185. **improve_prompt**: Improves an LLM/AI prompt by applying expert prompt writing strategies for better results and clarity.
186. **improve_report_finding**: Improves a penetration test security finding by providing detailed descriptions, risks, recommendations, references, quotes, and a concise summary in markdown format.
187. **improve_writing**: Refines text by correcting grammar, enhancing style, improving clarity, and maintaining the original meaning. skills.
188. **judge_output**: Evaluates Honeycomb queries by judging their effectiveness, providing critiques and outcomes based on language nuances and analytics relevance.
189. **label_and_rate**: Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
190. **md_callout**: Classifies content and generates a markdown callout based on the provided text, selecting the most appropriate type.
191. **model_as_sherlock_freud**: Builds psychological models using detective reasoning and psychoanalytic insight to understand human behavior.
192. **official_pattern_template**: Template to use if you want to create new fabric patterns.
193. **predict_person_actions**: Predicts behavioral responses based on psychological profiles and challenges.
194. **prepare_7s_strategy**: Prepares a comprehensive briefing document from 7S's strategy capturing organizational profile, strategic elements, and market dynamics with clear, concise, and organized content.
195. **provide_guidance**: Provides psychological and life coaching advice, including analysis, recommendations, and potential diagnoses, with a compassionate and honest tone.
196. **rate_ai_response**: Rates the quality of AI responses by comparing them to top human expert performance, assigning a letter grade, reasoning, and providing a 1-100 score based on the evaluation.
197. **rate_ai_result**: Assesses the quality of AI/ML/LLM work by deeply analyzing content, instructions, and output, then rates performance based on multiple dimensions, including coverage, creativity, and interdisciplinary thinking.
198. **rate_content**: Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
199. **rate_value**: Produces the best possible output by deeply analyzing and understanding the input and its intended purpose.
200. **raw_query**: Fully digests and contemplates the input to produce the best possible result based on understanding the sender's intent.
201. **recommend_artists**: Recommends a personalized festival schedule with artists aligned to your favorite styles and interests, including rationale.
202. **recommend_pipeline_upgrades**: Optimizes vulnerability-checking pipelines by incorporating new information and improving their efficiency, with detailed explanations of changes.
203. **recommend_talkpanel_topics**: Produces a clean set of proposed talks or panel talking points for a person based on their interests and goals, formatted for submission to a conference organizer.
204. **recommend_yoga_practice**: Provides personalized yoga sequences, meditation guidance, and holistic lifestyle advice based on individual profiles.
205. **refine_design_document**: Refines a design document based on a design review by analyzing, mapping concepts, and implementing changes using valid Markdown.
206. **review_design**: Reviews and analyzes architecture design, focusing on clarity, component design, system integrations, security, performance, scalability, and data management.
207. **review_code**: Performs a comprehensive code review, providing detailed feedback on correctness, security, and performance.
208. **sanitize_broken_html_to_markdown**: Converts messy HTML into clean, properly formatted Markdown, applying custom styling and ensuring compatibility with Vite.
209. **suggest_pattern**: Suggests appropriate fabric patterns or commands based on user input, providing clear explanations and options for users.
210. **suggest_gt_command**: Suggest optimal Gas Town (GT) commands based on user intent and task description.
211. **suggest_openclaw_pattern**: Suggests the most appropriate Openclaw CLI command based on user intent, mapping natural language requests to commands for messaging, device management, scheduling, and automation.
212. **summarize**: Summarizes content into a 20-word sentence, main points, and takeaways, formatted with numbered lists in Markdown.
213. **summarize_board_meeting**: Creates formal meeting notes from board meeting transcripts for corporate governance documentation.
214. **summarize_debate**: Summarizes debates, identifies primary disagreement, extracts arguments, and provides analysis of evidence and argument strength to predict outcomes.
215. **summarize_git_changes**: Summarizes recent project updates from the last 7 days, focusing on key changes with enthusiasm.
216. **summarize_git_diff**: Summarizes and organizes Git diff changes with clear, succinct commit messages and bullet points.
217. **summarize_lecture**: Extracts relevant topics, definitions, and tools from lecture transcripts, providing structured summaries with timestamps and key takeaways.
218. **summarize_legislation**: Summarizes complex political proposals and legislation by analyzing key points, proposed changes, and providing balanced, positive, and cynical characterizations.
219. **summarize_meeting**: Analyzes meeting transcripts to extract a structured summary, including an overview, key points, tasks, decisions, challenges, timeline, references, and next steps.
220. **summarize_micro**: Summarizes content into a 20-word sentence, 3 main points, and 3 takeaways, formatted in clear, concise Markdown.
221. **summarize_newsletter**: Extracts the most meaningful, interesting, and useful content from a newsletter, summarizing key sections such as content, opinions, tools, companies, and follow-up items in clear, structured Markdown.
222. **summarize_paper**: Summarizes an academic paper by detailing its title, authors, technical approach, distinctive features, experimental setup, results, advantages, limitations, and conclusion in a clear, structured format using human-readable Markdown.
223. **summarize_prompt**: Summarizes AI chat prompts by describing the primary function, unique approach, and expected output in a concise paragraph. The summary is focused on the prompt's purpose without unnecessary details or formatting.
224. **summarize_pull-requests**: Summarizes pull requests for a coding project by providing a summary and listing the top PRs with human-readable descriptions.
225. **summarize_rpg_session**: Summarizes a role-playing game session by extracting key events, combat stats, character changes, quotes, and more.
226. **t_analyze_challenge_handling**: Provides 8-16 word bullet points evaluating how well challenges are being addressed, calling out any lack of effort.
227. **t_check_dunning_kruger**: Assess narratives for Dunning-Kruger patterns by contrasting self-perception with demonstrated competence and confidence cues.
228. **t_check_metrics**: Analyzes deep context from the TELOS file and input instruction, then provides a wisdom-based output while considering metrics and KPIs to assess recent improvements.
229. **t_create_h3_career**: Summarizes context and produces wisdom-based output by deeply analyzing both the TELOS File and the input instruction, considering the relationship between the two.
230. **t_create_opening_sentences**: Describes from TELOS file the person's identity, goals, and actions in 4 concise, 32-word bullet points, humbly.
231. **t_describe_life_outlook**: Describes from TELOS file a person's life outlook in 5 concise, 16-word bullet points.
232. **t_extract_intro_sentences**: Summarizes from TELOS file a person's identity, work, and current projects in 5 concise and grounded bullet points.
233. **t_extract_panel_topics**: Creates 5 panel ideas with titles and descriptions based on deep context from a TELOS file and input.
234. **t_find_blindspots**: Identify potential blindspots in thinking, frames, or models that may expose the individual to error or risk.
235. **t_find_negative_thinking**: Analyze a TELOS file and input to identify negative thinking in documents or journals, followed by tough love encouragement.
236. **t_find_neglected_goals**: Analyze a TELOS file and input instructions to identify goals or projects that have not been worked on recently.
237. **t_give_encouragement**: Analyze a TELOS file and input instructions to evaluate progress, provide encouragement, and offer recommendations for continued effort.
238. **t_red_team_thinking**: Analyze a TELOS file and input instructions to red-team thinking, models, and frames, then provide recommendations for improvement.
239. **t_threat_model_plans**: Analyze a TELOS file and input instructions to create threat models for a life plan and recommend improvements.
240. **t_visualize_mission_goals_projects**: Analyze a TELOS file and input instructions to create an ASCII art diagram illustrating the relationship of missions, goals, and projects.
241. **t_year_in_review**: Analyze a TELOS file to create insights about a person or entity, then summarize accomplishments and visualizations in bullet points.
242. **to_flashcards**: Create Anki flashcards from a given text, focusing on concise, optimized questions and answers without external context.
243. **transcribe_minutes**: Extracts (from meeting transcription) meeting minutes, identifying actionables, insightful ideas, decisions, challenges, and next steps in a structured format.
244. **translate**: Translates sentences or documentation into the specified language code while maintaining the original formatting and tone.
245. **tweet**: Provides a step-by-step guide on crafting engaging tweets with emojis, covering Twitter basics, account creation, features, and audience targeting.
246. **ultimate_law_safety**: Evaluates actions, policies, or systems against the Ultimate Law framework — a minimal, falsifiable ethical constraint that prohibits creating unwilling victims.
247. **write_essay**: Writes essays in the style of a specified author, embodying their unique voice, vocabulary, and approach. Uses `author_name` variable.
248. **write_essay_pg**: Writes concise, clear essays in the style of Paul Graham, focusing on simplicity, clarity, and illumination of the provided topic.
249. **write_hackerone_report**: Generates concise, clear, and reproducible bug bounty reports, detailing vulnerability impact, steps to reproduce, and exploit details for triagers.
250. **write_latex**: Generates syntactically correct LaTeX code for a new.tex document, ensuring proper formatting and compatibility with pdflatex.
251. **write_micro_essay**: Writes concise, clear, and illuminating essays on the given topic in the style of Paul Graham.
252. **write_nuclei_template_rule**: Generates Nuclei YAML templates for detecting vulnerabilities using HTTP requests, matchers, extractors, and dynamic data extraction.
253. **write_pull-request**: Drafts detailed pull request descriptions, explaining changes, providing reasoning, and identifying potential bugs from the git diff command output.
254. **write_semgrep_rule**: Creates accurate and working Semgrep rules based on input, following syntax guidelines and specific language considerations.
255. **youtube_summary**: Create concise, timestamped Youtube video summaries that highlight key points.

View file

@ -73,15 +73,15 @@ Match the request to one or more of these primary categories:
**AI**: ai, create_ai_jobs_analysis, create_art_prompt, create_pattern, create_prediction_block, extract_mcp_servers, extract_wisdom_agents, generate_code_rules, greybeard_secure_prompt_engineer, improve_prompt, judge_output, rate_ai_response, rate_ai_result, raw_query, suggest_pattern, summarize_prompt
**ANALYSIS**: ai, analyze_answers, analyze_bill, analyze_bill_short, analyze_candidates, analyze_cfp_submission, analyze_claims, analyze_comments, analyze_debate, analyze_discord_structure, analyze_email_headers, analyze_incident, analyze_interviewer_techniques, analyze_logs, analyze_malware, analyze_military_strategy, analyze_mistakes, analyze_paper, analyze_paper_simple, analyze_patent, analyze_personality, analyze_presentation, analyze_product_feedback, analyze_proposition, analyze_prose, analyze_prose_json, analyze_prose_pinker, analyze_risk, analyze_sales_call, analyze_spiritual_text, analyze_tech_impact, analyze_terraform_plan, analyze_threat_report, analyze_threat_report_cmds, analyze_threat_report_trends, apply_ul_tags, audit_consent, audit_transparency, check_agreement, check_falsifiability, compare_and_contrast, concall_summary, create_ai_jobs_analysis, create_golden_rules, create_idea_compass, create_investigation_visualization, create_prediction_block, create_recursive_outline, create_story_about_people_interaction, create_tags, detect_mind_virus, detect_silent_victims, dialog_with_socrates, explain_terms_and_conditions, extract_bd_ideas, extract_ethical_framework, extract_main_idea, extract_predictions, find_hidden_message, find_logical_fallacies, get_wow_per_minute, identify_dsrp_distinctions, identify_dsrp_perspectives, identify_dsrp_relationships, identify_dsrp_systems, identify_job_stories, label_and_rate, model_as_sherlock_freud, predict_person_actions, prepare_7s_strategy, provide_guidance, rate_content, rate_value, recommend_artists, recommend_talkpanel_topics, review_design, suggest_gt_command, suggest_openclaw_pattern, summarize_board_meeting, t_analyze_challenge_handling, t_check_dunning_kruger, t_check_metrics, t_describe_life_outlook, t_extract_intro_sentences, t_extract_panel_topics, t_find_blindspots, t_find_negative_thinking, t_red_team_thinking, t_threat_model_plans, t_year_in_review, ultimate_law_safety, write_hackerone_report
**ANALYSIS**: ai, analyze_answers, analyze_bill, analyze_bill_short, analyze_candidates, analyze_cfp_submission, analyze_claims, analyze_comments, analyze_debate, analyze_discord_structure, analyze_email_headers, analyze_incident, analyze_interviewer_techniques, analyze_logs, analyze_malware, analyze_military_strategy, analyze_mistakes, analyze_monetization_opportunities, analyze_paper, analyze_paper_simple, analyze_patent, analyze_personality, analyze_presentation, analyze_product_feedback, analyze_proposition, analyze_prose, analyze_prose_json, analyze_prose_pinker, analyze_risk, analyze_sales_call, analyze_spiritual_text, analyze_tech_impact, analyze_terraform_plan, analyze_threat_report, analyze_threat_report_cmds, analyze_threat_report_trends, apply_ul_tags, audit_consent, audit_transparency, check_agreement, check_falsifiability, compare_and_contrast, concall_summary, create_ai_jobs_analysis, create_golden_rules, create_idea_compass, create_investigation_visualization, create_prediction_block, create_recursive_outline, create_story_about_people_interaction, create_tags, detect_mind_virus, detect_silent_victims, dialog_with_socrates, explain_terms_and_conditions, extract_bd_ideas, extract_ethical_framework, extract_main_idea, extract_predictions, find_hidden_message, find_logical_fallacies, get_wow_per_minute, identify_dsrp_distinctions, identify_dsrp_perspectives, identify_dsrp_relationships, identify_dsrp_systems, identify_job_stories, label_and_rate, model_as_sherlock_freud, predict_person_actions, prepare_7s_strategy, provide_guidance, rate_content, rate_value, recommend_artists, recommend_talkpanel_topics, review_design, suggest_gt_command, suggest_openclaw_pattern, summarize_board_meeting, t_analyze_challenge_handling, t_check_dunning_kruger, t_check_metrics, t_describe_life_outlook, t_extract_intro_sentences, t_extract_panel_topics, t_find_blindspots, t_find_negative_thinking, t_red_team_thinking, t_threat_model_plans, t_year_in_review, ultimate_law_safety, write_hackerone_report
**BILL**: analyze_bill, analyze_bill_short
**BUSINESS**: analyze_discord_structure, check_agreement, concall_summary, create_ai_jobs_analysis, create_formal_email, create_hormozi_offer, create_loe_document, create_logo, create_newsletter_entry, create_prd, create_upgrade_pack, explain_project, extract_business_ideas, extract_characters, extract_product_features, extract_skills, extract_sponsors, identify_job_stories, prepare_7s_strategy, rate_value, t_check_metrics, t_create_h3_career, t_visualize_mission_goals_projects, t_year_in_review, transcribe_minutes
**BUSINESS**: analyze_discord_structure, analyze_monetization_opportunities, check_agreement, concall_summary, create_ai_jobs_analysis, create_formal_email, create_hormozi_offer, create_loe_document, create_logo, create_newsletter_entry, create_prd, create_upgrade_pack, explain_project, extract_affiliate_products, extract_business_ideas, extract_characters, extract_product_features, extract_skills, extract_sponsors, extract_video_commerce_entities, identify_job_stories, prepare_7s_strategy, rate_value, t_check_metrics, t_create_h3_career, t_visualize_mission_goals_projects, t_year_in_review, transcribe_minutes
**CLASSIFICATION**: apply_ul_tags
**CONVERSION**: clean_text, convert_to_markdown, create_graph_from_input, create_slides, export_data_as_csv, extract_videoid, humanize, md_callout, sanitize_broken_html_to_markdown, to_flashcards, transcribe_minutes, translate, tweet, write_latex
**CONVERSION**: clean_text, convert_to_markdown, create_graph_from_input, create_slides, export_data_as_csv, extract_videoid, generate_frontmatter, humanize, md_callout, sanitize_broken_html_to_markdown, to_flashcards, transcribe_minutes, translate, tweet, write_latex
**CR THINKING**: audit_consent, audit_transparency, capture_thinkers_work, check_falsifiability, create_idea_compass, create_markmap_visualization, create_upgrade_pack, detect_mind_virus, detect_silent_victims, dialog_with_socrates, extract_alpha, extract_controversial_ideas, extract_ethical_framework, extract_extraordinary_claims, extract_predictions, extract_primary_problem, extract_wisdom_nometa, find_hidden_message, find_logical_fallacies, summarize_debate, t_analyze_challenge_handling, t_check_dunning_kruger, t_find_blindspots, t_find_negative_thinking, t_find_neglected_goals, t_red_team_thinking, ultimate_law_safety
@ -91,7 +91,7 @@ Match the request to one or more of these primary categories:
**DEVOPS**: analyze_terraform_plan
**EXTRACT**: analyze_comments, create_aphorisms, create_golden_rules, create_tags, create_upgrade_pack, create_video_chapters, extract_algorithm_update_recommendations, extract_all_quotes, extract_alpha, extract_article_wisdom, extract_bd_ideas, extract_book_ideas, extract_book_recommendations, extract_business_ideas, extract_characters, extract_controversial_ideas, extract_core_message, extract_ctf_writeup, extract_domains, extract_ethical_framework, extract_extraordinary_claims, extract_ideas, extract_insights, extract_insights_dm, extract_instructions, extract_jokes, extract_latest_video, extract_main_activities, extract_main_idea, extract_mcp_servers, extract_most_redeeming_thing, extract_patterns, extract_poc, extract_predictions, extract_primary_problem, extract_primary_solution, extract_product_features, extract_questions, extract_recipe, extract_recommendations, extract_references, extract_skills, extract_song_meaning, extract_sponsors, extract_videoid, extract_wisdom, extract_wisdom_agents, extract_wisdom_dm, extract_wisdom_nometa, extract_wisdom_short, extract_wisdom_with_attribution, generate_code_rules, t_extract_intro_sentences, t_extract_panel_topics
**EXTRACT**: analyze_comments, create_aphorisms, create_golden_rules, create_tags, create_upgrade_pack, create_video_chapters, extract_affiliate_products, extract_algorithm_update_recommendations, extract_all_quotes, extract_alpha, extract_article_wisdom, extract_bd_ideas, extract_book_ideas, extract_book_recommendations, extract_business_ideas, extract_characters, extract_controversial_ideas, extract_core_message, extract_ctf_writeup, extract_domains, extract_ethical_framework, extract_extraordinary_claims, extract_ideas, extract_insights, extract_insights_dm, extract_instructions, extract_jokes, extract_latest_video, extract_main_activities, extract_main_idea, extract_mcp_servers, extract_most_redeeming_thing, extract_patterns, extract_poc, extract_predictions, extract_primary_problem, extract_primary_solution, extract_product_features, extract_questions, extract_recipe, extract_recommendations, extract_references, extract_skills, extract_song_meaning, extract_sponsors, extract_video_commerce_entities, extract_videoid, extract_wisdom, extract_wisdom_agents, extract_wisdom_dm, extract_wisdom_nometa, extract_wisdom_short, extract_wisdom_with_attribution, generate_code_rules, generate_frontmatter, t_extract_intro_sentences, t_extract_panel_topics
**GAMING**: create_npc, create_rpg_summary, summarize_rpg_session
@ -117,7 +117,7 @@ Match the request to one or more of these primary categories:
**WELLNESS**: analyze_spiritual_text, create_better_frame, extract_wisdom_dm, heal_person, model_as_sherlock_freud, predict_person_actions, provide_guidance, recommend_yoga_practice, t_give_encouragement
**WRITING**: analyze_prose_json, analyze_prose_pinker, apply_ul_tags, clean_text, compare_and_contrast, convert_to_markdown, create_5_sentence_summary, create_academic_paper, create_aphorisms, create_better_frame, create_design_document, create_design_system, create_diy, create_formal_email, create_hormozi_offer, create_keynote, create_micro_summary, create_newsletter_entry, create_prediction_block, create_prd, create_show_intro, create_slides, create_story_about_people_interaction, create_story_explanation, create_summary, create_tags, create_user_story, enrich_blog_post, explain_docs, explain_terms, fix_typos, humanize, improve_academic_writing, improve_writing, label_and_rate, md_callout, official_pattern_template, recommend_talkpanel_topics, refine_design_document, summarize, summarize_debate, summarize_lecture, summarize_legislation, summarize_meeting, summarize_micro, summarize_newsletter, summarize_paper, summarize_rpg_session, t_create_opening_sentences, t_describe_life_outlook, t_extract_intro_sentences, t_extract_panel_topics, t_give_encouragement, t_year_in_review, transcribe_minutes, tweet, write_essay, write_essay_pg, write_hackerone_report, write_latex, write_micro_essay, write_pull-request
**WRITING**: analyze_prose_json, analyze_prose_pinker, apply_ul_tags, clean_text, compare_and_contrast, convert_to_markdown, create_5_sentence_summary, create_academic_paper, create_aphorisms, create_better_frame, create_design_document, create_design_system, create_diy, create_formal_email, create_hormozi_offer, create_keynote, create_micro_summary, create_newsletter_entry, create_prediction_block, create_prd, create_show_intro, create_slides, create_story_about_people_interaction, create_story_explanation, create_summary, create_tags, create_user_story, enrich_blog_post, explain_docs, explain_terms, fix_typos, generate_frontmatter, humanize, improve_academic_writing, improve_writing, label_and_rate, md_callout, official_pattern_template, recommend_talkpanel_topics, refine_design_document, summarize, summarize_debate, summarize_lecture, summarize_legislation, summarize_meeting, summarize_micro, summarize_newsletter, summarize_paper, summarize_rpg_session, t_create_opening_sentences, t_describe_life_outlook, t_extract_intro_sentences, t_extract_panel_topics, t_give_encouragement, t_year_in_review, transcribe_minutes, tweet, write_essay, write_essay_pg, write_hackerone_report, write_latex, write_micro_essay, write_pull-request
## Workflow Suggestions

View file

@ -136,6 +136,10 @@ Examine battles analyzing strategic decisions to extract military lessons.
Analyze past errors to prevent similar mistakes in predictions/decisions.
### analyze_monetization_opportunities
Identify affiliate, sponsorship, digital product, and community revenue opportunities in creator content aligned with audience intent.
### analyze_paper
Analyze scientific papers to identify findings and assess conclusion.
@ -442,6 +446,10 @@ Extract world model updates/algorithms to improve decision-making.
Organize video content into timestamped chapters highlighting key topics.
### extract_affiliate_products
Extract commercial products, tools, and brands from transcripts, separating sponsored from organic mentions with commission tier estimates.
### extract_algorithm_update_recommendations
Extract recommendations for improving algorithms, focusing on steps.
@ -538,6 +546,10 @@ Extract/classify hard/soft skills from job descriptions into skill inventory.
Extract/organize sponsorship info, including names and messages.
### extract_video_commerce_entities
Identify every commercially relevant entity in a video transcript — products, tools, brands, services — with category, mention type, and purchase likelihood.
### extract_videoid
Extract/parse video IDs and URLs to create video lists.
@ -690,6 +702,10 @@ Create glossaries of advanced terms with definitions and analogies.
Proofreads and corrects typos, spelling, grammar, and punctuation errors.
### generate_frontmatter
Generate YAML frontmatter with tags, aliases and summary for PKM notes.
### humanize
Transform technical content into approachable language.

View file

@ -55,6 +55,7 @@ Use parameterized queries instead of string concatenation...
- Store secrets in environment variables or secure configuration
- Use the built-in setup process for key management
- Regularly rotate API keys
- `~/.config/fabric/.env` holds API keys and Codex OAuth tokens. Fabric writes it with mode `0600`. Treat the file as secret material. Comments and key order are not preserved when Codex tokens are updated in place.
### Input Validation

View file

@ -112,14 +112,18 @@ For manual installation or troubleshooting, see the detailed instructions below.
The completions provide intelligent suggestions for:
- **Patterns**: Tab-complete available patterns with `-p` or `--pattern`
- **Patterns**: Tab-complete available patterns with `-p`, `--pattern`, or `--readpattern`
- **Models**: Tab-complete available models with `-m` or `--model`
- **Vendors**: Tab-complete configured vendors with `-V` or `--vendor`
- **Contexts**: Tab-complete contexts for context-related flags
- **Sessions**: Tab-complete sessions for session-related flags
- **Strategies**: Tab-complete available strategies
- **Extensions**: Tab-complete registered extensions
- **Gemini Voices**: Tab-complete TTS voices for `--voice`
- **File paths**: Smart file completion for attachment, output, and config options
- **Transcription models**: Tab-complete models for `--transcribe-model`
- **Fixed value sets**: Tab-complete the accepted values of `--thinking`, `--debug`, `--image-size`,
`--image-quality`, and `--image-background`
- **File paths**: Smart file completion for attachment, output, config, image, and transcription options
- **Flag completion**: All available command-line flags and options
## Alternative Installation Method

View file

@ -139,6 +139,15 @@ const docTemplate = `{
"$ref": "#/definitions/fsdb.Pattern"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
@ -346,7 +355,6 @@ const docTemplate = `{
"type": "string"
},
"language": {
"description": "Add Language field to bind from request",
"type": "string"
},
"maxTokens": {
@ -412,9 +420,6 @@ const docTemplate = `{
"type": "number",
"format": "float64"
},
"updateChan": {
"type": "object"
},
"voice": {
"type": "string"
}

View file

@ -133,6 +133,15 @@
"$ref": "#/definitions/fsdb.Pattern"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
@ -340,7 +349,6 @@
"type": "string"
},
"language": {
"description": "Add Language field to bind from request",
"type": "string"
},
"maxTokens": {
@ -406,9 +414,6 @@
"type": "number",
"format": "float64"
},
"updateChan": {
"type": "object"
},
"voice": {
"type": "string"
}

View file

@ -50,7 +50,6 @@ definitions:
imageSize:
type: string
language:
description: Add Language field to bind from request
type: string
maxTokens:
type: integer
@ -95,8 +94,6 @@ definitions:
topP:
format: float64
type: number
updateChan:
type: object
voice:
type: string
type: object
@ -265,6 +262,12 @@ paths:
description: OK
schema:
$ref: '#/definitions/fsdb.Pattern'
"400":
description: Bad Request
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal Server Error
schema:

View file

@ -41,11 +41,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1765472234,
"narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=",
"lastModified": 1785692966,
"narHash": "sha256-vUfIeBEfpbAfZ5zjgIkYk7eHBeVfCYVjLbWnMkseYnk=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b",
"rev": "643809054d65fdd466a63e3155b8c498cb483c04",
"type": "github"
},
"original": {

174
go.mod
View file

@ -1,121 +1,125 @@
module github.com/danielmiessler/fabric
go 1.25.1
go 1.26.0
require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1
github.com/anthropics/anthropic-sdk-go v1.27.1
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0
github.com/anthropics/anthropic-sdk-go v1.67.0
github.com/atotto/clipboard v0.1.4
github.com/aws/aws-sdk-go-v2 v1.41.4
github.com/aws/aws-sdk-go-v2/config v1.32.12
github.com/aws/aws-sdk-go-v2/service/bedrock v1.57.0
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.2
github.com/gabriel-vasile/mimetype v1.4.13
github.com/aws/aws-sdk-go-v2 v1.44.0
github.com/aws/aws-sdk-go-v2/config v1.32.40
github.com/aws/aws-sdk-go-v2/service/bedrock v1.68.0
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.58.0
github.com/gabriel-vasile/mimetype v1.4.15
github.com/gin-gonic/gin v1.12.0
github.com/go-git/go-git/v5 v5.17.0
github.com/go-git/go-git/v5 v5.19.2
github.com/go-shiori/go-readability v0.0.0-20251205110129-5db1dc9836f0
github.com/google/go-github/v66 v66.0.0
github.com/hasura/go-graphql-client v0.15.1
github.com/hasura/go-graphql-client v0.16.0
github.com/jessevdk/go-flags v1.6.1
github.com/joho/godotenv v1.5.1
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/mattn/go-sqlite3 v1.14.37
github.com/mattn/go-sqlite3 v1.14.50
github.com/nicksnyder/go-i18n/v2 v2.6.1
github.com/ollama/ollama v0.18.2
github.com/ollama/ollama v0.33.1
github.com/openai/openai-go v1.12.0
github.com/otiai10/copy v1.14.1
github.com/pkg/errors v0.9.1
github.com/samber/lo v1.53.0
github.com/sgaunet/perplexity-go/v2 v2.15.0
github.com/sgaunet/perplexity-go/v2 v2.16.1
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
github.com/stretchr/testify v1.12.1
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.1
github.com/swaggo/swag v1.16.6
golang.org/x/oauth2 v0.36.0
golang.org/x/text v0.35.0
google.golang.org/api v0.272.0
golang.org/x/sys v0.47.0
golang.org/x/text v0.41.0
google.golang.org/api v0.294.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.9.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.41 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.6.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/buger/jsonparser v1.6.1 // indirect
github.com/bytedance/gopkg v0.1.4 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/go-openapi/jsonpointer v0.22.5 // indirect
github.com/go-openapi/jsonreference v0.21.5 // indirect
github.com/go-openapi/spec v0.22.4 // indirect
github.com/go-openapi/swag/conv v0.25.5 // indirect
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
github.com/go-openapi/swag/jsonutils v0.25.5 // indirect
github.com/go-openapi/swag/loading v0.25.5 // indirect
github.com/go-openapi/swag/stringutils v0.25.5 // indirect
github.com/go-openapi/swag/typeutils v0.25.5 // indirect
github.com/go-openapi/swag/yamlutils v0.25.5 // indirect
github.com/go-openapi/jsonpointer v1.0.0 // indirect
github.com/go-openapi/jsonreference v1.0.1 // indirect
github.com/go-openapi/spec v0.22.11 // indirect
github.com/go-openapi/swag/conv v0.29.1 // indirect
github.com/go-openapi/swag/jsonutils v0.29.1 // indirect
github.com/go-openapi/swag/loading v0.29.1 // indirect
github.com/go-openapi/swag/pools v0.29.1 // indirect
github.com/go-openapi/swag/stringutils v0.29.1 // indirect
github.com/go-openapi/swag/typeutils v0.29.1 // indirect
github.com/go-openapi/swag/yamlutils v0.29.1 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/invopop/jsonschema v0.14.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mailru/easyjson v0.9.2 // indirect
github.com/pb33f/ordered-map/v2 v2.3.1 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/quic-go/quic-go v0.61.0 // indirect
github.com/standard-webhooks/standard-webhooks/libraries v0.0.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
go.uber.org/mock v0.6.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.34.0 // indirect
go.mongodb.org/mongo-driver/v2 v2.8.2 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect
golang.org/x/mod v0.40.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.43.0 // indirect
golang.org/x/tools v0.49.0 // indirect
)
require (
cloud.google.com/go v0.123.0 // indirect
cloud.google.com/go/auth v0.18.2 // indirect
cloud.google.com/go/auth v0.23.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
dario.cat/mergo v1.0.2 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.4.1 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/andybalholm/cascadia v1.3.4 // indirect
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.12
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect
github.com/aws/smithy-go v1.24.2 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/coder/websocket v1.8.14 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.39
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.40 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.40 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.40 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.40 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.34.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.39.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.46.0 // indirect
github.com/aws/smithy-go v1.28.1 // indirect
github.com/bytedance/sonic v1.15.3 // indirect
github.com/bytedance/sonic/loader v0.5.2 // indirect
github.com/cloudflare/circl v1.6.5 // indirect
github.com/cloudwego/base64x v0.1.7 // indirect
github.com/coder/websocket v1.8.15 // indirect
github.com/cyphar/filepath-securejoin v0.7.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/felixge/httpsnoop v1.1.0 // indirect
github.com/gin-contrib/sse v1.1.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.8.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-git/go-billy/v5 v5.9.1 // indirect
github.com/go-logr/logr v1.4.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/go-playground/validator/v10 v10.30.3 // indirect
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c // indirect
github.com/goccy/go-json v0.10.6 // indirect
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
@ -123,45 +127,43 @@ require (
github.com/google/go-querystring v1.2.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
github.com/googleapis/gax-go/v2 v2.19.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.21 // indirect
github.com/googleapis/gax-go/v2 v2.24.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.6.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
github.com/leodido/go-urn v1.5.0 // indirect
github.com/mattn/go-isatty v0.0.24 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/otiai10/mint v1.6.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
github.com/pjbgf/sha1cd v0.6.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/skeema/knownhosts v1.3.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/gjson v1.19.0 // indirect
github.com/tidwall/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
github.com/ugorji/go/codec v1.3.2 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
golang.org/x/arch v0.25.0 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect
google.golang.org/genai v1.51.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 // indirect
go.opentelemetry.io/otel v1.46.0 // indirect
go.opentelemetry.io/otel/metric v1.46.0 // indirect
go.opentelemetry.io/otel/trace v1.46.0 // indirect
golang.org/x/arch v0.30.0 // indirect
golang.org/x/crypto v0.55.0 // indirect
golang.org/x/net v0.58.0 // indirect
golang.org/x/sync v0.22.0 // indirect
google.golang.org/genai v1.70.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 // indirect
google.golang.org/grpc v1.83.2 // indirect
google.golang.org/protobuf v1.36.12 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

683
go.sum
View file

@ -1,29 +1,31 @@
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs=
cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA=
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
cloud.google.com/go/auth v0.22.0 h1:Xp9wAKkLoeaYb5pYZZoQGz4E9sdPxIbzS3gywZE3ciQ=
cloud.google.com/go/auth v0.22.0/go.mod h1:M9o2Oz+YI2jAfxewJgb1vyI3vceHF+eohmxyzmrl+9s=
cloud.google.com/go/auth v0.23.2 h1:pxSCpfiji41hpzpPdMCftEUCezpgpqmmDdYiAjCKXxo=
cloud.google.com/go/auth v0.23.2/go.mod h1:4DhBRcqvtljQN3dJ57qtqbib5ZGCYE5f2crfiiC2EM0=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0 h1:aokoqcHvaGjiM3VpjKDfMMnF/8epJ+Q1HLJ7CudztqE=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0/go.mod h1:/WYEx9pcM9Y+Dd/APJaNlSvVSvzl54rrMdZT5+Oi2LM=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.0 h1:4gRPBpN1f6xt88yi4WR26m7XaD9OlWtVT6bWPdGUIok=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.0/go.mod h1:G7QVLxw1j1JVyrO1MA95S8m8HStaaleDZYTcfGgjB2o=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0 h1:CU4+EJeJi3TKYWEcYuSdWsjzw0nVsK/H0MSQOiPcymU=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0/go.mod h1:q0+UTSRvShwUCrR/s5HtyInYphN7Wvxb7snFM3u+SLA=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0 h1:xFaZZ+IubdftrDHnGGwZ6QvQ3KHTtWl2MCK+GMt2vxs=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0/go.mod h1:mCBhUhlMjLLJKr5aqw2TNS/VqJOie8MzWq3DAMJeKso=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 h1:4iB+IesclUXdP0ICgAabvq2FYLXrJWKx1fJQ+GxSo3Y=
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 h1:RHK7bS+HQMslb1sZpAokUt+zTVmue0hKSs2C791hhzU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
github.com/AzureAD/microsoft-authentication-library-for-go v1.9.0 h1:MDT4FxAPve5FnYn6vOL1r7RCRDG+l9cI7a5LlCuHsqA=
github.com/AzureAD/microsoft-authentication-library-for-go v1.9.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
@ -31,235 +33,229 @@ github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6Xge
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/andybalholm/cascadia v1.3.4 h1:vM2lgh0Vru9Vwyfm4cQqWP2HHMW0u0+2PAW7Q38Qufg=
github.com/andybalholm/cascadia v1.3.4/go.mod h1:BLRmbRjpEtNKieZOCCvYj4RqN+KRA41GBe/5O+G93kM=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/anthropics/anthropic-sdk-go v1.23.0 h1:YVNnxfVVPJM+zvQ1oDgTJUBtLttGpBHe1WtJBr0QeAs=
github.com/anthropics/anthropic-sdk-go v1.23.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
github.com/anthropics/anthropic-sdk-go v1.27.1 h1:7DgMZ2Ng3C2mPzJGHA30NXQTZolcF07mHd0tGaLwfzk=
github.com/anthropics/anthropic-sdk-go v1.27.1/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=
github.com/anthropics/anthropic-sdk-go v1.61.0 h1:JRTnm1tPqn5xo1xd1zfrcFDlcoWXVMvV1K68YmhpZKw=
github.com/anthropics/anthropic-sdk-go v1.61.0/go.mod h1:3EfIfmFqxH6rbiLcIP4tPFyXL/IHakx2wDG4OU+TIEI=
github.com/anthropics/anthropic-sdk-go v1.67.0 h1:tTsVSR+YD6R+1EPoMyZ9im7YKEjJpYGZk5klcDi9HRg=
github.com/anthropics/anthropic-sdk-go v1.67.0/go.mod h1:3EfIfmFqxH6rbiLcIP4tPFyXL/IHakx2wDG4OU+TIEI=
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU=
github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k=
github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 h1:3kGOqnh1pPeddVa/E37XNTaWJ8W6vrbYV9lJEkCnhuY=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
github.com/aws/aws-sdk-go-v2/config v1.32.7 h1:vxUyWGUwmkQ2g19n7JY/9YL8MfAIl7bTesIUykECXmY=
github.com/aws/aws-sdk-go-v2/config v1.32.7/go.mod h1:2/Qm5vKUU/r7Y+zUk/Ptt2MDAEKAfUtKc1+3U1Mo3oY=
github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0=
github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g=
github.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8=
github.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw=
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8=
github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
github.com/aws/aws-sdk-go-v2/service/bedrock v1.53.2 h1:Z5JspbwScfbzmOmTmlagxHVRcOJmb/Ku5kXnwdY0fto=
github.com/aws/aws-sdk-go-v2/service/bedrock v1.53.2/go.mod h1:YkwtdWa9fxpfhKuZyjb9mi+h/E3LoXFzT72BpxA9tGk=
github.com/aws/aws-sdk-go-v2/service/bedrock v1.57.0 h1:DCONGOKen9tTc35KrPbMGEr9KjIMY0eYLkFdeYSzW4c=
github.com/aws/aws-sdk-go-v2/service/bedrock v1.57.0/go.mod h1:C/1NPMi8p0/I4HKgDqXYl7WdcQvWrKBq5PqwGQjMz8U=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.49.0 h1:osqN479arsxXAIHmBbiAn+0nj7jCkuXtzgtZPSwt0sc=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.49.0/go.mod h1:siKVmJdui4dwPPtsKr3F5BAeJxW1MANWaLJnTDfgu7c=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.2 h1:x0eGAWpd1B5I/vMtrB4Q4Zuc3CXWI8wjHfPPqBSrKmM=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.2/go.mod h1:V9oTWSDC2MtS1DR71hbNET/bZ8psQp022amEBe1grJc=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 h1:2HvVAIq+YqgGotK6EkMf+KIEqTISmTYh5zLpYyeTo1Y=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20/go.mod h1:V4X406Y666khGa8ghKmphma/7C0DAtEQYhkq9z4vpbk=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 h1:gd84Omyu9JLriJVCbGApcLzVR3XtmC4ZDPcAI6Ftvds=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk=
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aws/aws-sdk-go-v2 v1.43.0 h1:fharf/WhbRAVZ1du0QL7roNFxZ6T/sWr+4Ni617bwSI=
github.com/aws/aws-sdk-go-v2 v1.43.0/go.mod h1:5pKeft2eJj+gElQ38Jqg4ibCqh+/AK33/0X3hip7IjM=
github.com/aws/aws-sdk-go-v2 v1.44.0 h1:4IbaHhtzy+4h37z4JQyO9a2QsiCml3CNYHtq5hIHigo=
github.com/aws/aws-sdk-go-v2 v1.44.0/go.mod h1:bttEH6JqnUL8LepvDVfdrds/fZ5bCIxzpe3abyUrhDU=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14 h1:3IZY0XAJquT3aHzbkHfPzy4ACPcEjVG0x87KOwtpqGY=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14/go.mod h1:zwM6veDkhGgQFqkBy+uT28AAYpLu+uFMlPl+rCg/73E=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 h1:GPRlPwz40I2B2VrBEASOA3Bi77NyeqejNLkifosX0rs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20/go.mod h1:g7PNzKcsOKWb4fkSRBA7BZVAS6Y8IcxzN+nRohhQ1Q8=
github.com/aws/aws-sdk-go-v2/config v1.32.31 h1:n4nY9O3QKoHIkL85EX+V8RcMFtOhlpTFhGArg915PXk=
github.com/aws/aws-sdk-go-v2/config v1.32.31/go.mod h1:PN0NYDCCoOpGGsZ2+elDUidmHfQBPyYzN2GCgl8HEBs=
github.com/aws/aws-sdk-go-v2/config v1.32.40 h1:lAVC9gMmKusmqDRe32dPtgKl/BWvJmMJoWELKHCAObw=
github.com/aws/aws-sdk-go-v2/config v1.32.40/go.mod h1:8xOJLbe/hOj1g4PVsfJYV7O2byq+UGET1onDdUgbwqc=
github.com/aws/aws-sdk-go-v2/credentials v1.19.30 h1:TTCvvzFU6gXa4iJecNG/0F/B0oYTiazoRECr2XyLHrY=
github.com/aws/aws-sdk-go-v2/credentials v1.19.30/go.mod h1:jKxAp2AEncnliinzpgOSZDFv6+VjvWhjw/AtbfsWT9U=
github.com/aws/aws-sdk-go-v2/credentials v1.19.39 h1:XOg8LC3Kgnsa3WiPQjc7Bi8k5IBN92cPYfIV9XMFss0=
github.com/aws/aws-sdk-go-v2/credentials v1.19.39/go.mod h1:GonTDBQ+mTpCVNwaHjj0PagspfrYYMEqOx7FehoEP/I=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.31 h1:kfVL5wAunCJycL6MOQ6aNh6PlAYEymflcjuKmrWUA0o=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.31/go.mod h1:nWfRNDAppujCQgOUd43lKT4yeLv9z3nJ3bw1G3BgQKo=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.40 h1:r5aGipEVgI9aT/tAGjdrPbDQvIAKdTrS3rUPQtG4Rmo=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.40/go.mod h1:vOD3CnPxAdkL6MWZeROkZsTlskklMFfgVFkHzx/oZpY=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.31 h1:Z8F3hfCY33IGpJjFAnv0wvtv1FIKj1GHmRDEYqy64tw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.31/go.mod h1:aVyUoytEyOViR6jhq6jula0xkc5NfBE2hgeF6BvOrao=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.40 h1:UIXlbijuB2XK1Kr57fo8iIxCuaSHJzwZ1uo+2tbEYIk=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.40/go.mod h1:wcEsL6jscjZjVUinb0Q5qD/GXOG1yT3GNfmT9HuDwzU=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.31 h1:hyOxUyXdh3AyjE93gBgsfziJag9ACwcs+ZpDBLzi8mw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.31/go.mod h1:OERqI9k0draSLB8O8woxY3q25ZWTELRK4RRoLMuMZFo=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.40 h1:xLQVRDs2NddDmK9BEyh5KSlJ1Gpy5/GIJXrV6WcVGAE=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.40/go.mod h1:XRXnpFVFGLaEVK+olDdFIM1vNa04ETW452oFGEPUxAo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.32 h1:0MrUL35H/Y4kdFfItoR5jCgtDQ4Z/8LudAoIHRfA4hE=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.32/go.mod h1:2tNZkuWz54arj8mHVf+8Y7cKkcD8Wr/fBpENgEXpjLc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.41 h1:nv/ILuCY0yXACzMQwvtt/HbqDDjemZiI0AeDbxGQlnU=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.41/go.mod h1:dzvOSpxaPqQ3j0xS6Lc1vyVuWW0RBj7s/QqYpzu3Q/0=
github.com/aws/aws-sdk-go-v2/service/bedrock v1.66.0 h1:RMRC4k42Bt3wHqWiPyFfiRrOyRhJZW7w6vAw5ZnMrPM=
github.com/aws/aws-sdk-go-v2/service/bedrock v1.66.0/go.mod h1:1asCJhndokTo9QEJ68/eZv4yJsRPV9A0ROdDx7QQBkQ=
github.com/aws/aws-sdk-go-v2/service/bedrock v1.68.0 h1:2T9SsmNe78eLtmobdnvxk088DVuUfYp9Ml9MxFMWQUQ=
github.com/aws/aws-sdk-go-v2/service/bedrock v1.68.0/go.mod h1:ANOxqA/+nqohBqirnFCF8EqJFkjAbTLYfyBl6NEjNug=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.56.0 h1:CWw8zDpnMJLwSvZd41Ncf/eJPeZ5t74UxGAu4HbM3S4=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.56.0/go.mod h1:dGxTgK2ZKWrbZv5o/8oCeO3Uch3n0w2rtSFroeJoLcE=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.58.0 h1:TDwZrhBZTHNxvGiqqDoNjdUuoveRRVfy14VeFHbbWBc=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.58.0/go.mod h1:ZnrFfnjYjXc/PC2a2hwAIS2qf1Yqk15EMLryhca2wps=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13 h1:mbRIur/BiHK6SKPjoBIXSE/hJ6g6JGRLuxQy1jGjlN4=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13/go.mod h1:ITg9em2KbJx1s0y4aqRX5OYWG6HBZ5TVR//OdpEZ2CQ=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 h1:bAdDl/HkGCcGPoe25ToSHEw23VIxt6CT5fLcg111BKg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19/go.mod h1:KaUzbLxv4CeSxh6ZCl9B4m7CuFenS8kUEaDs+f/DQr4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.31 h1:w2SIhW92DZPFrSL4ksVCr8IYff5OZwIcxg8+95tzvAI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.31/go.mod h1:wAhpCQbkov+IcvjozJbd2xRCoZybUEHNkcFunssNACg=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.40 h1:gr3Fw1cxZXNCdeo/lQ7isHEHzvHVM7z75qb2zW9aMjw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.40/go.mod h1:8z/9CmfnQhiuXD7Ykbcg4a/whSWsniE0ODSx9uwVzfk=
github.com/aws/aws-sdk-go-v2/service/signin v1.5.0 h1:OHH5iTQvVGmfHjX/5Q+vFuA/Rf2x6/95aJ/75QCQSm4=
github.com/aws/aws-sdk-go-v2/service/signin v1.5.0/go.mod h1:mCF3AK9PpL49oOrhniUXWAfhVBVQ/XbytoE5eccZUIs=
github.com/aws/aws-sdk-go-v2/service/signin v1.6.0 h1:agcr0j8YeFEzdXNo17Rg9MbbjLRjrimabwNtji4e+lU=
github.com/aws/aws-sdk-go-v2/service/signin v1.6.0/go.mod h1:qU5PxgQ4JiUOOMotzfO3+5oUda5W+8JDVKyLQqlrJik=
github.com/aws/aws-sdk-go-v2/service/sso v1.33.0 h1:CaJyYhxBE0M/HJX/YvSaSmQlsI91VHB0lKU8LtLxL3A=
github.com/aws/aws-sdk-go-v2/service/sso v1.33.0/go.mod h1:+e6BMRMPjBQoCw/WovYR9GLy2IU0z4Q77smOB1DraSg=
github.com/aws/aws-sdk-go-v2/service/sso v1.34.0 h1:FxaN8/sn61DTXNI6Gt678tFJUY8iUsCchm6Y/F/RjaA=
github.com/aws/aws-sdk-go-v2/service/sso v1.34.0/go.mod h1:vu4OY6s8LJtT8BtYG2LD6BGSZMptkYn3o5hvCPB22jc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.0 h1:tC323YV77QdafeBr6LUhLDTsboyuyHLNRwAyCP44kGU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.0/go.mod h1:SfLK1sgviHmbI+MozR9iDwDjL4cdCVZtahsjoR+z7wg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.39.0 h1:crWKPeGYTBTuBxQ3p73kjfJvt4brUIsr+Fuypko8FxY=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.39.0/go.mod h1:HjjZVhaBz0JBR/kbWKThmNDhFKS7y6EURuk493tJk9Y=
github.com/aws/aws-sdk-go-v2/service/sts v1.45.0 h1:Pd6PNlp4t8PTXxqzstICl52Wsy78vpjFZ7PRUj44mJc=
github.com/aws/aws-sdk-go-v2/service/sts v1.45.0/go.mod h1:rmQ0TnHzuLPmabgjPcsywhsSOmaBDgzR4zvDxSPsGdg=
github.com/aws/aws-sdk-go-v2/service/sts v1.46.0 h1:IZ63JdogSNNjex/jsODNv7jGDcO/xJYd9FsgyfCsp1g=
github.com/aws/aws-sdk-go-v2/service/sts v1.46.0/go.mod h1:I+rwAf3spG5dITBaAo3xXRowk8kiOhtU1kYxfvCTC44=
github.com/aws/smithy-go v1.27.4 h1:JQcphmBN4f0q/sPqXqROIItRNV/hy10cgu7CsFy616M=
github.com/aws/smithy-go v1.27.4/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aws/smithy-go v1.28.1 h1:R/nXH00c8qcfCzQVELtRw+eLQWtzv+VAIEFJ1/xxXlQ=
github.com/aws/smithy-go v1.28.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/buger/jsonparser v1.2.0 h1:4EFcvK1kD4jyj6YqNK6skK6w+y7FHHBR+XBCtxwu/6g=
github.com/buger/jsonparser v1.2.0/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/buger/jsonparser v1.6.1 h1:I0phFv0PlbLHnM7TZAVjZ2MJ2/eWRTDyuO7GLR98IEs=
github.com/buger/jsonparser v1.6.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo=
github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA=
github.com/bytedance/sonic v1.15.3 h1:P3akjLPBtV/i6bHC6LbcLjY3KuoOvfiqF8wFHeP5IhY=
github.com/bytedance/sonic v1.15.3/go.mod h1:8e51yTPdY8M6t+vvGL1c2Y1xL9i+frEeIAQAEl75NUc=
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/bytedance/sonic/loader v0.5.2 h1:0QtP1gevc1OZ6/H8Lb9BRZiCXd1Ftjd3OKuj1T1lBIo=
github.com/bytedance/sonic/loader v0.5.2/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/cloudflare/circl v1.6.4 h1:pOXuDTCEYyzydgUpQ0CQz3LsinKjiSk6nNP5Lt5K64U=
github.com/cloudflare/circl v1.6.4/go.mod h1:YxarevkLlbaHuWsxG6vmYNWBEsSp4pnp7j+4VljMavY=
github.com/cloudflare/circl v1.6.5 h1:O64F26HEqNhznd/hrC5KZXVKYuKM2rx4deZDTc4ihQA=
github.com/cloudflare/circl v1.6.5/go.mod h1:h5LNyxAc5nTue9DS5jT+48en2PSDYt3zdGnz5OstK6c=
github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI=
github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE=
github.com/cyphar/filepath-securejoin v0.7.0/go.mod h1:ymLGms/u3BYaviIiuKFnUx8EkQEZeK6cInNoAPJA3o4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=
github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE=
github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI=
github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
github.com/go-git/go-billy/v5 v5.9.1 h1:8U73XiOTfINdItHVa6z4Gv7ToObcZ6grkqQbLryLCdA=
github.com/go-git/go-billy/v5 v5.9.1/go.mod h1:ExsU+jcGwXTBOnyilvAnEM1wug1IxHr4yP2ZXsNRtV0=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s=
github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M=
github.com/go-git/go-git/v5 v5.17.0 h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM=
github.com/go-git/go-git/v5 v5.17.0/go.mod h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI=
github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY=
github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=
github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=
github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=
github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=
github.com/go-openapi/spec v0.22.3 h1:qRSmj6Smz2rEBxMnLRBMeBWxbbOvuOoElvSvObIgwQc=
github.com/go-openapi/spec v0.22.3/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs=
github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ=
github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ=
github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=
github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY=
github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0=
github.com/go-openapi/jsonreference v1.0.1 h1:4zJ7AmYDKNmD3aSpfPnFNCFA5E80/xMHUNKgydaLh38=
github.com/go-openapi/jsonreference v1.0.1/go.mod h1:dYplQXa6p5lXprLcJ8LE2iU7vNpXsAHDQ5ZAgL+Qx3A=
github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+w=
github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0=
github.com/go-openapi/spec v0.22.11 h1:/ZW28n4PCghbBpwjmvoMeyCeDwd6pIe715Ov8T3kAaQ=
github.com/go-openapi/spec v0.22.11/go.mod h1:Ypu+u1KTejey7/QCr3j1ChdjwhGgXuklbH7l8+YP1J4=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=
github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=
github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U=
github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=
github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=
github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=
github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=
github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=
github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=
github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=
github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0 h1:7SgOMTvJkM8yWrQlU8Jm18VeDPuAvB/xWrdxFJkoFag=
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
github.com/go-openapi/swag/conv v0.27.3 h1:iqJFmGEjmX3AY0lSszABFqRVqOSt99XS0LzNIMJYuhU=
github.com/go-openapi/swag/conv v0.27.3/go.mod h1:nPRmN6jgNme99hpf+nM0auDZGALWIqlwhisKPK/bQhQ=
github.com/go-openapi/swag/conv v0.29.1 h1:AC4Eh/5c/eUDOUCzzsRC9ghmFgOSBHeRMGIngY0ZUGA=
github.com/go-openapi/swag/conv v0.29.1/go.mod h1:S1X7/ZrBEZOC0Wc8AGxjbcGS92l3WEjA7aPtpl+RaqM=
github.com/go-openapi/swag/jsonutils v0.27.3 h1:1DEz+O82frtSMBcos/7XIn1GnpNTbsD4Bru4Dc/uhRc=
github.com/go-openapi/swag/jsonutils v0.27.3/go.mod h1:qiDCoQvzkMxrV3G8FLEdIU5L+EFYc0zcDOHWT3Yofvo=
github.com/go-openapi/swag/jsonutils v0.29.1 h1:AFCxs0eQZ24/QyfhVHM2t49rMz7Vv3XCsZQI6yrNy+c=
github.com/go-openapi/swag/jsonutils v0.29.1/go.mod h1:u3+sCfJpttDpcmS5kpm0yxL6GK0eWgODsx8Yw8fcqNM=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.3 h1:h/eT9kmGCDdFLJF29lOhzLtF0FmP1AX2MhLJWVebsb8=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.3/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.1 h1:BiiXE31Bx9SfpsMmOQj5KYpUhTZBpLVriVhJDuLuY2o=
github.com/go-openapi/swag/loading v0.27.3 h1:L9nQkEgzU7QgFQL+pLEMfGUKxeM4pWwGwbET9Z3weW0=
github.com/go-openapi/swag/loading v0.27.3/go.mod h1:rJ0NeaKsF4CVPnMGjPQl7JlSHzvD0bc2DKXLss1hiuE=
github.com/go-openapi/swag/loading v0.29.1 h1:FCv5fG8UhTdDJa2R7w+5O9Ekpcbw7tt0nFWvmDKGBjc=
github.com/go-openapi/swag/loading v0.29.1/go.mod h1:N0ESuem4p2oedKal8EJhciqnJ9Q9Wmt83L1CRB3Fouw=
github.com/go-openapi/swag/pools v0.27.3 h1:gXjImP3F6/56wRRcFgEPld084Y6u2gs21ikPBt8NKBk=
github.com/go-openapi/swag/pools v0.27.3/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE=
github.com/go-openapi/swag/pools v0.29.1 h1:NRogYxdEW9SjRM4mkAOji9iefO4MRXq3p/ZJcoQbUKg=
github.com/go-openapi/swag/pools v0.29.1/go.mod h1:leDcaghjkRAhCuCRv9NfJU5f0mjoU3cT/XZObhMk3pc=
github.com/go-openapi/swag/stringutils v0.27.3 h1:Ru28hnbAvN5wycALQYy8IobHvASq+FUFMlp1QzLM0JI=
github.com/go-openapi/swag/stringutils v0.27.3/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
github.com/go-openapi/swag/stringutils v0.29.1 h1:1ykunK7iJQk1uOO7+oUH1ukbsK85fFCOiCFMOVSY+F0=
github.com/go-openapi/swag/stringutils v0.29.1/go.mod h1:7fSqZ+z8Qc0tOfAAK0jVa5qFGrnIlRi6n7NeGGrr1vc=
github.com/go-openapi/swag/typeutils v0.27.3 h1:l6SSrx5eR5/WVwrGNzN6bQ9WqL04mrxNBl9YgQ3rcJ4=
github.com/go-openapi/swag/typeutils v0.27.3/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
github.com/go-openapi/swag/typeutils v0.29.1 h1:Nzv9nhnlLCRBPQqfOX+7lB6Guju370or8StT+lIOf6M=
github.com/go-openapi/swag/typeutils v0.29.1/go.mod h1:hxpgDZJVBkBsi/d3MIUosafoFdE5exaQRmVp0zwu3YE=
github.com/go-openapi/swag/yamlutils v0.27.3 h1:cRFCAoYtslYn9L9T0xWryHy1t7c1MACC+DMj3CLvwvs=
github.com/go-openapi/swag/yamlutils v0.27.3/go.mod h1:6JYBGj8sw/NawMllyZY+cTA8Mzk2etS3ZBASdcyPsiU=
github.com/go-openapi/swag/yamlutils v0.29.1 h1:69w3tsBajm7MR/fejLy7HD/3J68Ys1SeeZMEzZ3w2sk=
github.com/go-openapi/swag/yamlutils v0.29.1/go.mod h1:rgsp3vT/QdWzKwn43CigDwjOGIenPyTZMKnxEM8jZOA=
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
github.com/go-openapi/testify/enable/yaml/v2 v2.6.1 h1:Jm+/ze2rMtbD98yen92AhATGLGREDYXG56Xr4gMjEtE=
github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
github.com/go-openapi/testify/v2 v2.6.1 h1:6CNJhTjMzgaeaH8WhshcsZNPIvRemiOcFpU7seO/y7Q=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8=
github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc=
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c h1:wpkoddUomPfHiOziHZixGO5ZBS73cKqVzZipfrLmO1w=
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c/go.mod h1:oVDCh3qjJMLVUSILBRwrm+Bc6RNXGZYtoh9xdvf1ffM=
github.com/go-shiori/go-readability v0.0.0-20251205110129-5db1dc9836f0 h1:A3B75Yp163FAIf9nLlFMl4pwIj+T3uKxfI7mbvvY2Ls=
github.com/go-shiori/go-readability v0.0.0-20251205110129-5db1dc9836f0/go.mod h1:suxK0Wpz4BM3/2+z1mnOVTIWHDiMCIOGoKDCRumSsk0=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
@ -278,20 +274,22 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ=
github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
github.com/googleapis/gax-go/v2 v2.19.0 h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE=
github.com/googleapis/gax-go/v2 v2.19.0/go.mod h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA=
github.com/googleapis/enterprise-certificate-proxy v0.3.19 h1:mMOE7DN2+p76/EdIrmAy9B9bH+yC4563vmnJ34QR8i4=
github.com/googleapis/enterprise-certificate-proxy v0.3.19/go.mod h1:rSEsBUemEBZEexP2y6jPp16LUmUbjmSbcPMQizR0o4k=
github.com/googleapis/enterprise-certificate-proxy v0.3.21 h1:OFdQ3tnCX/zaQ0Cedur3D3z7kI6HiLX9g3TiAN4/DFU=
github.com/googleapis/enterprise-certificate-proxy v0.3.21/go.mod h1:L3D/IQExI6LqEjBdXcZQ1WluSgigQmSwBboFstVPM4w=
github.com/googleapis/gax-go/v2 v2.23.0 h1:Tchl7qkvE7Ip3y+ztvNufYFvkfqTe7NfLTYGIdJRLuE=
github.com/googleapis/gax-go/v2 v2.23.0/go.mod h1:rBQKOVJCdb8IFEzg+FCwlt1LP/xMDGuqUXhUG+XMXEg=
github.com/googleapis/gax-go/v2 v2.24.0 h1:myMaPYyF9MecEmvQqMqomIwn9t/4KCZN9qnwsS76wlg=
github.com/googleapis/gax-go/v2 v2.24.0/go.mod h1:IaTHBDd7NHxSCiu0vEs8pQZu4dGZrWwuSoxCnk16OFM=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hasura/go-graphql-client v0.15.1 h1:mCb5I+8Bk3FU3GKWvf/zDXkTh7FbGlqJmP3oisBdnN8=
github.com/hasura/go-graphql-client v0.15.1/go.mod h1:jfSZtBER3or+88Q9vFhWHiFMPppfYILRyl+0zsgPIIw=
github.com/hasura/go-graphql-client v0.16.0 h1:DQLfp+djj4j5NPdJkGYym8J55hpm5etML1zqgco78Qc=
github.com/hasura/go-graphql-client v0.16.0/go.mod h1:z/sO2T0zI+HnPNIevQcs+7xA6/gDOc8hgHMrNBzfL2c=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg=
github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
@ -302,14 +300,12 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@ -319,19 +315,17 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0=
github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE=
github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M=
github.com/mailru/easyjson v0.9.2/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.37 h1:3DOZp4cXis1cUIpCfXLtmlGolNLp2VEqhiB/PARNBIg=
github.com/mattn/go-sqlite3 v1.14.37/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.48 h1:7XHIgl0a8HwOaiK4E47ozLkST78rR9+OtNGx27D/TFs=
github.com/mattn/go-sqlite3 v1.14.48/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
github.com/mattn/go-sqlite3 v1.14.50 h1:dmdFvo1XG4MPzA4IkAmE9upVz/Nj31uRoM5+jC8hYbY=
github.com/mattn/go-sqlite3 v1.14.50/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -339,10 +333,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
github.com/ollama/ollama v0.16.2 h1:iZ/vV7t9QRU0MXfwWXl0+6HBb2xUULubksqK0dfB+og=
github.com/ollama/ollama v0.16.2/go.mod h1:FEk95NbAJJZk+t7cLh+bPGTul72j1O3PLLlYNV3FVZ0=
github.com/ollama/ollama v0.18.2 h1:RsOY8oZ6TufRiPgsSlKJp4/V/X+oBREscUlEHZfd554=
github.com/ollama/ollama v0.18.2/go.mod h1:tCX4IMV8DHjl3zY0THxuEkpWDZSOchJpzTuLACpMwFw=
github.com/ollama/ollama v0.32.3 h1:hASAqO6McQAgOhLG3Fcgrj//aXK5rMMWxqFM4eAKkGc=
github.com/ollama/ollama v0.32.3/go.mod h1:b1ydCt2oVg0VAg22WWDgCbwW0AyOaRKAFzlS91NI4OY=
github.com/ollama/ollama v0.33.1 h1:nEeqiZzqlO+RfjJ7AvfUgrYlV/wbrxPl2szRg9mtxPU=
github.com/ollama/ollama v0.33.1/go.mod h1:Kekx/+OtFZHmqbkVH/QUUDVcMQS+1pg1dcz4Qy7TGn4=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/openai/openai-go v1.12.0 h1:NBQCnXzqOTv5wsgNC36PrFEiskGfO5wccfCWDo9S1U0=
@ -351,10 +345,12 @@ github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY=
github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ=
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -364,23 +360,23 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/quic-go/quic-go v0.61.0 h1:ui88A53s8MSVYLC56en0KQ17HARk+9986Dn0SBfKNvA=
github.com/quic-go/quic-go v0.61.0/go.mod h1:9So2anK4Tp22URSQq00k+Vo2PNkle96ycDPDHL4s9vs=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sgaunet/perplexity-go/v2 v2.15.0 h1:YFa63ohQda5MbuLedMHzlFHtOFeifHf+G6cSwp9xKnU=
github.com/sgaunet/perplexity-go/v2 v2.15.0/go.mod h1:q9FDvA+UCmtcgFBmb0aZbDMzgcnJipowTl8Ycl+aDko=
github.com/sgaunet/perplexity-go/v2 v2.16.1 h1://Xa7P0F/eOIcJ/6SehTqgr2MRnzPfI8LdAwlAYTnTs=
github.com/sgaunet/perplexity-go/v2 v2.16.1/go.mod h1:5dckcaxoFKtJEfNRc9+OhL6p/W/TKxd0gbSzc1YkmkI=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
@ -389,6 +385,8 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/standard-webhooks/standard-webhooks/libraries v0.0.1 h1:uOfcYT+3QungH6tIGSVCR/Y3KJmgJiHcojJbMTPDZAI=
github.com/standard-webhooks/standard-webhooks/libraries v0.0.1/go.mod h1:L1MQhA6x4dn9r007T033lsaZMv9EmBAdXyU/+EF40fo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@ -403,6 +401,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY=
@ -410,8 +410,8 @@ github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxL
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
@ -424,101 +424,86 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/ugorji/go/codec v1.3.2 h1:zkEASHHyEClGeURfgNT9PJZVfAbs9oEX9QXggwWNJbc=
github.com/ugorji/go/codec v1.3.2/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.mongodb.org/mongo-driver/v2 v2.8.0 h1:CxWDGQYY8QQwNjAl/aq2sfWakdnWZynnqJ9F4DhHbP8=
go.mongodb.org/mongo-driver/v2 v2.8.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.mongodb.org/mongo-driver/v2 v2.8.2 h1:b6o2m7zL8g2URuO8urBedAylxojybKXNZTxgkOcl+2w=
go.mongodb.org/mongo-driver/v2 v2.8.2/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 h1:XmiuHzgJt067+a6kwyAzkhXooYVv3/TOw9cM2VfJgUM=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0/go.mod h1:KDgtbWKTQs4bM+VPUr6WlL9m/WXcmkCcBlIzqxPGzmI=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0 h1:2yEATaop1/a1I4psnSLgWVPLWwCzkqWakgJy7xTDVy0=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0/go.mod h1:D7J12YRapIekYyPWgGPlA/23pRmpSEZC5xJC/TTLI9U=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0 h1:B2h3uqicet1CT2N5TOFhS+Gq++9i0/CLmaxvhmhtP5s=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0/go.mod h1:dylvB+ZiiwMvsDij9O84Uy7SijLgHMX4mbkncds+4Sw=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 h1:3g7B90UzBltIDKq1/5mrTGxTnOFDV0ICOhLoxiZ8jlg=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0/go.mod h1:Ef8SuTh59BT7+ofpDxN9z+yOlc4t2GjLmKDgYNJL/NU=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc=
go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8=
go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o=
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI=
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3kLgPCwSnSE=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c=
go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y=
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE=
golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4=
go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
golang.org/x/arch v0.29.0 h1:8sSET5wB0+exBm0FGmOtdHMqjlRdV2DRD3/IV6OZgho=
golang.org/x/arch v0.29.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
golang.org/x/arch v0.30.0 h1:sB9h+1gRGa2+LauFSV0tm8bK1J2yo1bx6/Uyi/P6DTU=
golang.org/x/arch v0.30.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b h1:QoALfVG9rhQ/M7vYDScfPdWjGL9dlsVVM5VGh7aKoAA=
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -530,81 +515,60 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.266.0 h1:hco+oNCf9y7DmLeAtHJi/uBAY7n/7XC9mZPxu1ROiyk=
google.golang.org/api v0.266.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0=
google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA=
google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA=
google.golang.org/genai v1.46.0 h1:RSsfeMaV30m8PxLOW4RUIb5ybw+mw+UBf1vSpsQTQbE=
google.golang.org/genai v1.46.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk=
google.golang.org/genai v1.51.0 h1:IZGuUqgfx40INv3hLFGCbOSGp0qFqm7LVmDghzNIYqg=
google.golang.org/genai v1.51.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk=
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 h1:CogIeEXn4qWYzzQU0QqvYBM8yDF9cFYzDq9ojSpv0Js=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/api v0.290.0 h1:eMw0Xo+IfbbMlKmW7aHvpyQRv9RCXuWx/vs8AD+0x9A=
google.golang.org/api v0.290.0/go.mod h1:weJZ3lldHFYI0DBFNKpJelUDNnusTt5YaOEgxvt8ci8=
google.golang.org/api v0.294.0 h1:8gASjJxdtcIieB3OqbkLcF0FfbXVNqKtU5iozD1ssvA=
google.golang.org/api v0.294.0/go.mod h1:02qB8+Ox1ZFzcaKFMguy1nQLJmSIyvV6Ff4txJEXtl4=
google.golang.org/genai v1.65.0 h1:6QK3Rjsx0iuJjbDCE1vf1VUr1IEjRDpvexGGnhxoAIk=
google.golang.org/genai v1.65.0/go.mod h1:mDdPDFXo1Ats7f1WXVyZgWb/CkMzFWTWJruIMy7hGIU=
google.golang.org/genai v1.70.0 h1:V9oYOBvTDYbmeklOHjvxGeeLau71WiS2CWKrktQtDok=
google.golang.org/genai v1.70.0/go.mod h1:mDdPDFXo1Ats7f1WXVyZgWb/CkMzFWTWJruIMy7hGIU=
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d h1:C9v1o0/4quuhOAfmRXA2j+we0PqZIp8traLdeogF3Ms=
google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7 h1:jQ9p21COKWjP3VwuFrNRiiOTMh3mPpN45R7SLrH/HUU=
google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7/go.mod h1:KqHwBx2upmfa1XSi1WuRvC+2VGCLtooKkfmyvRbUmqA=
google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d h1:QwnJwPte4XXAkhPu26LTDIahnsMSUV0kK8HkxbC+Pc4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260724162435-b2f20204f0df h1:O3ig1i5WDDzsVzRp+cCdgelT9vXnlnOFdlEeFtL4HCc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260724162435-b2f20204f0df/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 h1:1VUiZAXyC+zmiFYi+WLtBzr68Cj8wOofHjjrA/kkizc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@ -612,6 +576,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -1,6 +1,7 @@
package cli
import (
"context"
"errors"
"fmt"
"os"
@ -88,7 +89,7 @@ func handleChatProcessing(currentFlags *Flags, registry *core.PluginRegistry, me
chatOptions.AudioFormat = "wav" // Default to WAV format
}
if session, err = chatter.Send(chatReq, chatOptions); err != nil {
if session, err = chatter.Send(context.Background(), chatReq, chatOptions); err != nil {
return
}

View file

@ -117,7 +117,7 @@ func Cli(version string) (err error) {
func processYoutubeVideo(
flags *Flags, registry *core.PluginRegistry, videoId string) (message string, err error) {
if (!flags.YouTubeComments && !flags.YouTubeMetadata) || flags.YouTubeTranscript || flags.YouTubeTranscriptWithTimestamps {
if (!flags.YouTubeComments && !flags.YouTubeMetadata && !flags.YouTubeVisual) || flags.YouTubeTranscript || flags.YouTubeTranscriptWithTimestamps {
var transcript string
var language = "en"
if flags.Language != "" || registry.Language.DefaultLanguage.Value != "" {
@ -139,6 +139,20 @@ func processYoutubeVideo(
message = AppendMessage(message, transcript)
}
if flags.YouTubeVisual {
var visualText string
var language = "en"
if flags.Language != "" {
language = flags.Language
} else if registry.Language.DefaultLanguage.Value != "" {
language = registry.Language.DefaultLanguage.Value
}
if visualText, err = registry.YouTube.GrabVisual(videoId, language, flags.YtDlpArgs, flags.YouTubeVisualSensitivity, flags.YouTubeVisualFps); err != nil {
return
}
message = AppendMessage(message, visualText)
}
if flags.YouTubeComments {
var comments []string
if comments, err = registry.YouTube.GrabComments(videoId); err != nil {

View file

@ -39,6 +39,7 @@ type Flags struct {
Raw bool `short:"r" long:"raw" yaml:"raw" description:"Use the defaults of the model without sending chat options (temperature, top_p, etc.). Only affects OpenAI-compatible providers. Anthropic models always use smart parameter selection to comply with model-specific requirements."`
FrequencyPenalty float64 `short:"F" long:"frequencypenalty" yaml:"frequencypenalty" description:"Set frequency penalty" default:"0.0"`
ListPatterns bool `short:"l" long:"listpatterns" description:"List all patterns"`
ReadPattern string `long:"readpattern" description:"Print the contents of the named pattern to the terminal"`
ListAllModels bool `short:"L" long:"listmodels" description:"List all available models"`
ListAllContexts bool `short:"x" long:"listcontexts" description:"List all contexts"`
ListAllSessions bool `short:"X" long:"listsessions" description:"List all sessions"`
@ -56,6 +57,9 @@ type Flags struct {
YouTubePlaylist bool `long:"playlist" description:"Prefer playlist over video if both ids are present in the URL"`
YouTubeTranscript bool `long:"transcript" description:"Grab transcript from YouTube video and send to chat (it is used per default)."`
YouTubeTranscriptWithTimestamps bool `long:"transcript-with-timestamps" description:"Grab transcript from YouTube video with timestamps and send to chat"`
YouTubeVisual bool `long:"visual" description:"Extract visual data from video using OCR and FFmpeg"`
YouTubeVisualSensitivity float64 `long:"visual-sensitivity" description:"Tolerance for FFmpeg scene detection (0.0 - 1.0)" default:"0.4"`
YouTubeVisualFps int `long:"visual-fps" description:"Extract a specific number of frames per second instead of using scene detection" default:"0"`
YouTubeComments bool `long:"comments" description:"Grab comments from YouTube video and send to chat"`
YouTubeMetadata bool `long:"metadata" description:"Output video metadata"`
YtDlpArgs string `long:"yt-dlp-args" yaml:"ytDlpArgs" description:"Additional arguments to pass to yt-dlp (e.g. '--cookies-from-browser brave')"`
@ -74,8 +78,8 @@ type Flags struct {
DryRun bool `long:"dry-run" description:"Show what would be sent to the model without actually sending it"`
Serve bool `long:"serve" description:"Serve the Fabric Rest API"`
ServeOllama bool `long:"serveOllama" description:"Serve the Fabric Rest API with ollama endpoints"`
ServeAddress string `long:"address" description:"The address to bind the REST API" default:":8080"`
ServeAPIKey string `long:"api-key" description:"API key used to secure server routes" default:""`
ServeAddress string `long:"address" description:"The address to bind the REST API" default:"127.0.0.1:8080"`
ServeAPIKey string `long:"api-key" env:"FABRIC_API_KEY" description:"API key used to secure server routes" default:""`
Config string `long:"config" description:"Path to YAML config file"`
Version bool `long:"version" description:"Print current version"`
ListExtensions bool `long:"listextensions" description:"List all registered extensions"`
@ -85,7 +89,7 @@ type Flags struct {
ListStrategies bool `long:"liststrategies" description:"List all strategies"`
ListVendors bool `long:"listvendors" description:"List all vendors"`
ShellCompleteOutput bool `long:"shell-complete-list" description:"Output raw list without headers/formatting (for shell completion)"`
Search bool `long:"search" description:"Enable web search tool for supported models (Anthropic, OpenAI, Gemini)"`
Search bool `long:"search" description:"Enable web search tool for supported models (Anthropic, OpenAI, Gemini, Grok)"`
SearchLocation string `long:"search-location" description:"Set location for web search results (e.g., 'America/Los_Angeles')"`
ImageFile string `long:"image-file" description:"Save generated image to specified file path (e.g., 'output.png')"`
ImageSize string `long:"image-size" description:"Image dimensions: 1024x1024, 1536x1024, 1024x1536, auto (default: auto)"`
@ -105,7 +109,7 @@ type Flags struct {
Notification bool `long:"notification" yaml:"notification" description:"Send desktop notification when command completes"`
NotificationCommand string `long:"notification-command" yaml:"notificationCommand" description:"Custom command to run for notifications (overrides built-in notifications)"`
Thinking domain.ThinkingLevel `long:"thinking" yaml:"thinking" description:"Set reasoning/thinking level (e.g., off, low, medium, high, or numeric tokens for Anthropic or Google Gemini)"`
ShowMetadata bool `long:"show-metadata" description:"Print metadata to stderr"`
ShowMetadata bool `long:"show-metadata" description:"Print metadata (input/output tokens) to stderr"`
Debug int `long:"debug" description:"Set debug level (0=off, 1=basic, 2=detailed, 3=trace, 4=wire)" default:"0"`
}
@ -119,8 +123,7 @@ func Init() (ret *Flags, err error) {
// Create mapping from flag names (both short and long) to yaml tag names
flagToYamlTag := make(map[string]string)
t := reflect.TypeFor[Flags]()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
for field := range t.Fields() {
yamlTag := field.Tag.Get("yaml")
if yamlTag != "" {
longTag := field.Tag.Get("long")

View file

@ -27,6 +27,7 @@ var flagDescriptionMap = map[string]string{
"raw": "use_model_defaults_raw_help",
"frequencypenalty": "set_frequency_penalty",
"listpatterns": "list_all_patterns",
"readpattern": "print_pattern_contents",
"listmodels": "list_all_available_models",
"listcontexts": "list_all_contexts",
"listsessions": "list_all_sessions",
@ -43,9 +44,13 @@ var flagDescriptionMap = map[string]string{
"playlist": "prefer_playlist_over_video",
"transcript": "grab_transcript_from_youtube",
"transcript-with-timestamps": "grab_transcript_with_timestamps",
"visual": "youtube_extract_visual_data_help",
"visual-sensitivity": "youtube_visual_sensitivity_help",
"visual-fps": "youtube_visual_fps_help",
"comments": "grab_comments_from_youtube",
"metadata": "output_video_metadata",
"yt-dlp-args": "additional_yt_dlp_args",
"spotify": "spotify_url_help",
"language": "specify_language_code",
"scrape_url": "scrape_website_url",
"scrape_question": "search_question_jina",
@ -91,6 +96,7 @@ var flagDescriptionMap = map[string]string{
"notification": "send_desktop_notification",
"notification-command": "custom_notification_command",
"thinking": "set_reasoning_thinking_level",
"show-metadata": "print_metadata_to_stderr",
"debug": "set_debug_level",
}
@ -139,8 +145,7 @@ func (h *TranslatedHelpWriter) getTranslatedDescription(flagName string) string
func (h *TranslatedHelpWriter) getOriginalDescription(flagName string) string {
flagsType := reflect.TypeFor[Flags]()
for i := 0; i < flagsType.NumField(); i++ {
field := flagsType.Field(i)
for field := range flagsType.Fields() {
longTag := field.Tag.Get("long")
if longTag == flagName {
@ -219,9 +224,7 @@ func (h *TranslatedHelpWriter) writeAllFlags() {
// Use direct reflection on the Flags struct to get all flag definitions
flagsType := reflect.TypeFor[Flags]()
for i := 0; i < flagsType.NumField(); i++ {
field := flagsType.Field(i)
for field := range flagsType.Fields() {
shortTag := field.Tag.Get("short")
longTag := field.Tag.Get("long")
defaultTag := field.Tag.Get("default")

View file

@ -29,6 +29,11 @@ func handleListingCommands(currentFlags *Flags, fabricDb *fsdb.Db, registry *cor
return true, nil
}
if currentFlags.ReadPattern != "" {
err = fabricDb.Patterns.PrintPattern(currentFlags.ReadPattern)
return true, err
}
if currentFlags.ListPatterns {
// Check if patterns exist before listing
var names []string

View file

@ -5,6 +5,10 @@ import (
restapi "github.com/danielmiessler/fabric/internal/server"
)
// serveOllama is a seam for tests, because the real entry point blocks
// on a listening socket.
var serveOllama = restapi.ServeOllama
// handleSetupAndServerCommands handles setup and server-related commands
// Returns (handled, error) where handled indicates if a command was processed and should exit
func handleSetupAndServerCommands(currentFlags *Flags, registry *core.PluginRegistry, version string) (handled bool, err error) {
@ -22,7 +26,7 @@ func handleSetupAndServerCommands(currentFlags *Flags, registry *core.PluginRegi
if currentFlags.ServeOllama {
registry.ConfigureVendors()
err = restapi.ServeOllama(registry, currentFlags.ServeAddress, version)
err = serveOllama(registry, currentFlags.ServeAddress, version, currentFlags.ServeAPIKey)
return true, err
}

View file

@ -0,0 +1,37 @@
package cli
import (
"testing"
"github.com/danielmiessler/fabric/internal/core"
"github.com/danielmiessler/fabric/internal/plugins/ai"
)
// The --serveOllama path must pass the address, version, and API key
// flags through to ServeOllama unchanged.
func TestHandleSetupAndServerCommands_ServeOllamaWiring(t *testing.T) {
var gotAddress, gotVersion, gotKey string
prev := serveOllama
serveOllama = func(_ *core.PluginRegistry, address, version, apiKey string) error {
gotAddress, gotVersion, gotKey = address, version, apiKey
return nil
}
defer func() { serveOllama = prev }()
registry := &core.PluginRegistry{
VendorManager: ai.NewVendorsManager(),
VendorsAll: ai.NewVendorsManager(),
}
flags := &Flags{ServeOllama: true, ServeAddress: "127.0.0.1:9999", ServeAPIKey: "secret"}
handled, err := handleSetupAndServerCommands(flags, registry, "v-test")
if err != nil {
t.Fatalf("handleSetupAndServerCommands() error = %v", err)
}
if !handled {
t.Fatal("handleSetupAndServerCommands() handled = false, want true")
}
if gotAddress != "127.0.0.1:9999" || gotVersion != "v-test" || gotKey != "secret" {
t.Fatalf("ServeOllama got (%q, %q, %q), want (127.0.0.1:9999, v-test, secret)",
gotAddress, gotVersion, gotKey)
}
}

View file

@ -57,7 +57,7 @@ func joinPromptSections(parts ...string) string {
}
// Send processes a chat request and applies file changes for create_coding_feature pattern
func (o *Chatter) Send(request *domain.ChatRequest, opts *domain.ChatOptions) (session *fsdb.Session, err error) {
func (o *Chatter) Send(ctx context.Context, request *domain.ChatRequest, opts *domain.ChatOptions) (session *fsdb.Session, err error) {
// Use o.model (normalized) for NeedsRawMode check instead of opts.Model
// This ensures case-insensitive model names work correctly (e.g., "GPT-5" → "gpt-5")
if o.vendor.NeedsRawMode(o.model) {
@ -107,7 +107,7 @@ func (o *Chatter) Send(request *domain.ChatRequest, opts *domain.ChatOptions) (s
go func() {
defer close(done)
if streamErr := o.vendor.SendStream(session.GetVendorMessages(), opts, responseChan); streamErr != nil {
if streamErr := o.vendor.SendStream(ctx, session.GetVendorMessages(), opts, responseChan); streamErr != nil {
recordFirstStreamError(errChan, streamErr)
}
}()
@ -168,7 +168,7 @@ func (o *Chatter) Send(request *domain.ChatRequest, opts *domain.ChatOptions) (s
// No errors, continue
}
} else {
if message, err = o.vendor.Send(context.Background(), session.GetVendorMessages(), opts); err != nil {
if message, err = o.vendor.Send(ctx, session.GetVendorMessages(), opts); err != nil {
return
}
if debuglog.GetLevel() >= debuglog.Wire {

View file

@ -44,11 +44,11 @@ func (m *mockVendor) Setup() error {
func (m *mockVendor) SetupFillEnvFileContent(*bytes.Buffer) {
}
func (m *mockVendor) ListModels() ([]string, error) {
func (m *mockVendor) ListModels(context.Context) ([]string, error) {
return []string{"test-model"}, nil
}
func (m *mockVendor) SendStream(messages []*chat.ChatCompletionMessage, opts *domain.ChatOptions, responseChan chan domain.StreamUpdate) error {
func (m *mockVendor) SendStream(_ context.Context, messages []*chat.ChatCompletionMessage, opts *domain.ChatOptions, responseChan chan domain.StreamUpdate) error {
// Send chunks if provided (for successful streaming test)
if m.streamChunks != nil {
for _, chunk := range m.streamChunks {
@ -180,7 +180,7 @@ func TestChatter_Send_SuppressThink(t *testing.T) {
return "<think>hidden</think> visible", nil
}
session, err := chatter.Send(request, opts)
session, err := chatter.Send(context.Background(), request, opts)
if err != nil {
t.Fatalf("Send returned error: %v", err)
}
@ -296,7 +296,7 @@ func TestChatter_Send_StreamingErrorPropagation(t *testing.T) {
}
// Call Send and expect it to return the streaming error
session, err := chatter.Send(request, opts)
session, err := chatter.Send(context.Background(), request, opts)
// Verify that the error from SendStream is propagated
if err == nil {
@ -353,7 +353,7 @@ func TestChatter_Send_StreamingErrorUpdateAndReturnDoesNotDeadlock(t *testing.T)
done := make(chan sendResult, 1)
go func() {
session, err := chatter.Send(request, opts)
session, err := chatter.Send(context.Background(), request, opts)
done <- sendResult{session: session, err: err}
}()
@ -411,7 +411,7 @@ func TestChatter_Send_StreamingSuccessfulAggregation(t *testing.T) {
}
// Call Send and expect successful aggregation
session, err := chatter.Send(request, opts)
session, err := chatter.Send(context.Background(), request, opts)
// Verify no error occurred
if err != nil {
@ -494,7 +494,7 @@ func TestChatter_Send_StreamingMetadataPropagation(t *testing.T) {
}
// Call Send
_, err := chatter.Send(request, opts)
_, err := chatter.Send(context.Background(), request, opts)
if err != nil {
t.Fatalf("Expected no error, but got: %v", err)
}

View file

@ -10,6 +10,7 @@ import (
"sort"
"strconv"
"strings"
"sync"
"github.com/danielmiessler/fabric/internal/i18n"
debuglog "github.com/danielmiessler/fabric/internal/log"
@ -73,6 +74,24 @@ func NewPluginRegistry(db *fsdb.Db) (ret *PluginRegistry, err error) {
vendors := []ai.Vendor{}
// Add non-OpenAI compatible clients
codexClient := codex.NewClient()
codexClient.WithStoreLock = func(fn func() error) error {
return db.WithEnvLock(func() error {
env, err := db.ReadEnvFile()
if err != nil {
return err
}
codexClient.LoadEnvSettings(env)
return fn()
})
}
codexClient.TokenPersist = func() error {
return db.ApplyEnvUpdates(map[string]string{
codexClient.AccessToken.EnvVariable: strings.TrimSpace(codexClient.AccessToken.Value),
codexClient.RefreshToken.EnvVariable: strings.TrimSpace(codexClient.RefreshToken.Value),
codexClient.AccountID.EnvVariable: strings.TrimSpace(codexClient.AccountID.Value),
})
}
vendors = append(vendors,
openai.NewClient(),
digitalocean.NewClient(),
@ -86,7 +105,7 @@ func NewPluginRegistry(db *fsdb.Db) (ret *PluginRegistry, err error) {
lmstudio.NewClient(),
exolab.NewClient(),
perplexity.NewClient(),
codex.NewClient(),
codexClient,
copilot.NewClient(), // Microsoft 365 Copilot
bedrock.NewClient(), // AWS Bedrock - credentials configured via setup or AWS credential chain
)
@ -123,6 +142,8 @@ func (o *PluginRegistry) ListVendors(out io.Writer) error {
type PluginRegistry struct {
Db *fsdb.Db
vendorMu sync.Mutex
VendorManager *ai.VendorsManager
VendorsAll *ai.VendorsManager
Defaults *tools.Defaults
@ -288,16 +309,12 @@ func (o *PluginRegistry) runVendorSetup() (err error) {
return pluginSetupErr
}
o.registerVendor(plugin)
if err = o.SaveEnvFile(); err != nil {
return
}
if o.VendorManager.FindByName(plugin.GetName()) == nil {
if vendor, ok := plugin.(ai.Vendor); ok {
o.VendorManager.AddVendors(vendor)
}
}
return
}
@ -349,16 +366,11 @@ func (o *PluginRegistry) runInteractiveSetup() (err error) {
if pluginSetupErr := plugin.Setup(); pluginSetupErr != nil {
println(pluginSetupErr.Error())
} else {
o.registerVendor(plugin)
if err = o.SaveEnvFile(); err != nil {
break
}
}
if o.VendorManager.FindByName(plugin.GetName()) == nil {
if vendor, ok := plugin.(ai.Vendor); ok {
o.VendorManager.AddVendors(vendor)
}
}
} else {
break
}
@ -425,11 +437,32 @@ func (o *PluginRegistry) SetupVendor(vendorName string) (err error) {
if err = o.VendorsAll.SetupVendor(vendorName, o.VendorManager.VendorsByName); err != nil {
return
}
if vendor := o.VendorsAll.FindByName(vendorName); vendor != nil {
o.registerVendor(vendor)
}
err = o.SaveEnvFile()
return
}
func (o *PluginRegistry) registerVendor(plugin plugins.Plugin) {
vendor, ok := plugin.(ai.Vendor)
if !ok {
return
}
o.vendorMu.Lock()
defer o.vendorMu.Unlock()
name := vendor.GetName()
for _, existing := range o.VendorManager.Vendors {
if strings.EqualFold(existing.GetName(), name) {
return
}
}
o.VendorManager.AddVendors(vendor)
}
func (o *PluginRegistry) ConfigureVendors() {
o.vendorMu.Lock()
defer o.vendorMu.Unlock()
o.VendorManager.Clear()
for _, vendor := range o.VendorsAll.Vendors {
if vendorErr := vendor.Configure(); vendorErr == nil && vendor.IsConfigured() {
@ -438,6 +471,34 @@ func (o *PluginRegistry) ConfigureVendors() {
}
}
func (o *PluginRegistry) activateVendor(name string) (ai.Vendor, error) {
if strings.TrimSpace(name) == "" {
return nil, nil
}
o.vendorMu.Lock()
defer o.vendorMu.Unlock()
if v := o.VendorManager.FindByName(name); v != nil {
return v, nil
}
if o.VendorsAll == nil {
return nil, nil
}
v := o.VendorsAll.FindByName(name)
if v == nil {
return nil, nil
}
if err := v.Configure(); err != nil {
return nil, err
}
if !v.IsConfigured() {
return nil, nil
}
o.VendorManager.AddVendors(v)
return v, nil
}
func (o *PluginRegistry) GetModels() (ret *ai.VendorsModels, err error) {
o.ConfigureVendors()
ret, err = o.VendorManager.GetModels()
@ -503,13 +564,25 @@ func (o *PluginRegistry) GetChatter(model string, modelContextLength int, vendor
ret.model = defaultModel
}
} else if model == "" {
if vendorName != "" {
ret.vendor = vendorManager.FindByName(vendorName)
} else {
ret.vendor = vendorManager.FindByName(defaultVendor)
name := vendorName
if name == "" {
name = defaultVendor
}
if ret.vendor, err = o.activateVendor(name); err != nil {
return
}
ret.model = defaultModel
} else {
if vendorName != "" {
if ret.vendor, err = o.activateVendor(vendorName); err != nil {
return
}
} else if !vendorManager.HasVendors() {
if _, err = o.activateVendor(defaultVendor); err != nil {
return
}
}
var models *ai.VendorsModels
if models, err = vendorManager.GetModels(); err != nil {
return
@ -539,16 +612,44 @@ func (o *PluginRegistry) GetChatter(model string, modelContextLength int, vendor
return
}
} else {
availableVendors := models.FindGroupsByItem(model)
if len(availableVendors) > 1 {
debuglog.Log("Warning: multiple vendors provide model %s: %s. Using %s. Specify --vendor to select a vendor.\n", model, strings.Join(availableVendors, ", "), availableVendors[0])
// If the model wasn't found and contains a '/', try parsing the first
// segment as a vendor name (e.g. "ollama/llama3" -> vendor "ollama", model "llama3").
if actualModelName == "" {
if idx := strings.Index(model, "/"); idx > 0 {
prefix := model[:idx]
if v := vendorManager.FindByName(prefix); v != nil {
vendorName = prefix
model = model[idx+1:]
if normalized := models.FindModelNameCaseInsensitive(model); normalized != "" {
model = normalized
}
ret.vendor = v
}
}
}
if ret.vendor == nil {
availableVendors := models.FindGroupsByItem(model)
if len(availableVendors) > 1 {
debuglog.Log("Warning: multiple vendors provide model %s: %s. Using %s. Specify --vendor to select a vendor.\n", model, strings.Join(availableVendors, ", "), availableVendors[0])
}
ret.vendor = vendorManager.FindByName(models.FindGroupsByItemFirst(model))
}
ret.vendor = vendorManager.FindByName(models.FindGroupsByItemFirst(model))
}
ret.model = model
}
if ret.vendor == nil {
name := vendorName
if name == "" {
name = defaultVendor
}
if ret.vendor, err = o.activateVendor(name); err != nil {
return
}
}
if ret.vendor == nil {
var errMsg string
if defaultModel == "" || defaultVendor == "" {

View file

@ -3,6 +3,7 @@ package core
import (
"bytes"
"context"
"errors"
"io"
"os"
"strings"
@ -10,11 +11,14 @@ import (
"github.com/danielmiessler/fabric/internal/chat"
"github.com/danielmiessler/fabric/internal/domain"
"github.com/danielmiessler/fabric/internal/i18n"
debuglog "github.com/danielmiessler/fabric/internal/log"
"github.com/danielmiessler/fabric/internal/plugins"
"github.com/danielmiessler/fabric/internal/plugins/ai"
"github.com/danielmiessler/fabric/internal/plugins/ai/codex"
"github.com/danielmiessler/fabric/internal/plugins/db/fsdb"
"github.com/danielmiessler/fabric/internal/tools"
"github.com/joho/godotenv"
)
func TestSaveEnvFile(t *testing.T) {
@ -32,18 +36,19 @@ func TestSaveEnvFile(t *testing.T) {
// testVendor implements ai.Vendor for testing purposes
type testVendor struct {
name string
models []string
name string
models []string
configureErr error
}
func (m *testVendor) GetName() string { return m.name }
func (m *testVendor) GetSetupDescription() string { return m.name }
func (m *testVendor) IsConfigured() bool { return true }
func (m *testVendor) Configure() error { return nil }
func (m *testVendor) Setup() error { return nil }
func (m *testVendor) SetupFillEnvFileContent(*bytes.Buffer) {}
func (m *testVendor) ListModels() ([]string, error) { return m.models, nil }
func (m *testVendor) SendStream([]*chat.ChatCompletionMessage, *domain.ChatOptions, chan domain.StreamUpdate) error {
func (m *testVendor) GetName() string { return m.name }
func (m *testVendor) GetSetupDescription() string { return m.name }
func (m *testVendor) IsConfigured() bool { return true }
func (m *testVendor) Configure() error { return m.configureErr }
func (m *testVendor) Setup() error { return nil }
func (m *testVendor) SetupFillEnvFileContent(*bytes.Buffer) {}
func (m *testVendor) ListModels(context.Context) ([]string, error) { return m.models, nil }
func (m *testVendor) SendStream(context.Context, []*chat.ChatCompletionMessage, *domain.ChatOptions, chan domain.StreamUpdate) error {
return nil
}
func (m *testVendor) Send(context.Context, []*chat.ChatCompletionMessage, *domain.ChatOptions) (string, error) {
@ -150,3 +155,176 @@ func TestGetChatter_RejectsExplicitCodexModelFromOtherVendor(t *testing.T) {
t.Fatal("expected GetChatter() to reject models that only belong to another vendor")
}
}
func TestGetChatter_ParsesVendorModelPrefix(t *testing.T) {
tempDir := t.TempDir()
db := fsdb.NewDb(tempDir)
ollamaVendor := &testVendor{name: "Ollama", models: []string{"some-namespace/model-name"}}
vm := ai.NewVendorsManager()
vm.AddVendors(ollamaVendor)
defaults := &tools.Defaults{
PluginBase: &plugins.PluginBase{},
Vendor: &plugins.Setting{Value: "Ollama"},
Model: &plugins.SetupQuestion{Setting: &plugins.Setting{Value: "some-namespace/model-name"}},
ModelContextLength: &plugins.SetupQuestion{Setting: &plugins.Setting{Value: "0"}},
}
registry := &PluginRegistry{Db: db, VendorManager: vm, Defaults: defaults}
chatter, err := registry.GetChatter("ollama/some-namespace/model-name", 0, "", false, false)
if err != nil {
t.Fatalf("GetChatter() error = %v", err)
}
if chatter.vendor.GetName() != "Ollama" {
t.Fatalf("expected Ollama vendor, got %s", chatter.vendor.GetName())
}
if chatter.model != "some-namespace/model-name" {
t.Fatalf("expected model 'some-namespace/model-name', got %s", chatter.model)
}
}
func TestGetChatter_VendorPrefixIgnoredWhenNotAVendor(t *testing.T) {
tempDir := t.TempDir()
db := fsdb.NewDb(tempDir)
vendorA := &testVendor{name: "VendorA", models: []string{"notavendor/model"}}
vm := ai.NewVendorsManager()
vm.AddVendors(vendorA)
defaults := &tools.Defaults{
PluginBase: &plugins.PluginBase{},
Vendor: &plugins.Setting{Value: "VendorA"},
Model: &plugins.SetupQuestion{Setting: &plugins.Setting{Value: "notavendor/model"}},
ModelContextLength: &plugins.SetupQuestion{Setting: &plugins.Setting{Value: "0"}},
}
registry := &PluginRegistry{Db: db, VendorManager: vm, Defaults: defaults}
chatter, err := registry.GetChatter("notavendor/model", 0, "", false, false)
if err != nil {
t.Fatalf("GetChatter() error = %v", err)
}
if chatter.vendor.GetName() != "VendorA" {
t.Fatalf("expected VendorA vendor, got %s", chatter.vendor.GetName())
}
if chatter.model != "notavendor/model" {
t.Fatalf("expected model 'notavendor/model', got %s", chatter.model)
}
}
func TestGetChatter_ReportsConfigureError(t *testing.T) {
if _, err := i18n.Init("en"); err != nil {
t.Fatalf("i18n.Init() error = %v", err)
}
for _, tt := range []struct {
name, model, vendor string
}{
{"empty model", "", ""},
{"model", "gpt-5.6-sol", ""},
{"named vendor", "gpt-5.6-sol", "Codex"},
} {
t.Run(tt.name, func(t *testing.T) {
configureErr := errors.New(i18n.T("codex_login_refresh_failed"))
registry := newInactiveVendorRegistry(t, "Codex", "gpt-5.6-sol", &testVendor{
name: "Codex", models: []string{"gpt-5.6-sol"}, configureErr: configureErr,
})
_, err := registry.GetChatter(tt.model, 0, tt.vendor, false, false)
if !errors.Is(err, configureErr) {
t.Fatalf("GetChatter() error = %v, want %v", err, configureErr)
}
})
}
}
func TestGetChatter_ActivatesInactiveVendor(t *testing.T) {
for _, tt := range []struct {
name, model, vendor string
}{
{"named", "gpt-5.6-sol", "Codex"},
{"default for model", "gpt-5.6-sol", ""},
} {
t.Run(tt.name, func(t *testing.T) {
codexVendor := &testVendor{name: "Codex", models: []string{"gpt-5.6-sol"}}
registry := newInactiveVendorRegistry(t, "Codex", "gpt-5.6-sol", codexVendor)
chatter, err := registry.GetChatter(tt.model, 0, tt.vendor, false, false)
if err != nil {
t.Fatalf("GetChatter() error = %v", err)
}
if chatter.vendor.GetName() != "Codex" {
t.Fatalf("vendor = %s, want Codex", chatter.vendor.GetName())
}
if chatter.model != "gpt-5.6-sol" {
t.Fatalf("model = %s, want gpt-5.6-sol", chatter.model)
}
})
}
}
func TestNewPluginRegistry_CodexTokenPersist(t *testing.T) {
dir := t.TempDir()
db := fsdb.NewDb(dir)
if err := db.SaveEnv("KEEP=old\n"); err != nil {
t.Fatalf("SaveEnv() error = %v", err)
}
registry, err := NewPluginRegistry(db)
if err != nil {
t.Fatalf("NewPluginRegistry() error = %v", err)
}
vendor := registry.VendorsAll.FindByName("Codex")
client, ok := vendor.(*codex.Client)
if !ok || client == nil {
t.Fatal("expected Codex client in VendorsAll")
}
if client.TokenPersist == nil {
t.Fatal("TokenPersist is nil")
}
client.AccessToken.Value = "access-live"
client.RefreshToken.Value = "refresh-live"
client.AccountID.Value = "acct-live"
if err := client.TokenPersist(); err != nil {
t.Fatalf("TokenPersist() error = %v", err)
}
parsed, err := godotenv.Read(db.EnvFilePath)
if err != nil {
t.Fatalf("godotenv.Read() error = %v", err)
}
if parsed["KEEP"] != "old" {
t.Fatalf("KEEP = %q, want old", parsed["KEEP"])
}
if parsed[client.AccessToken.EnvVariable] != "access-live" {
t.Fatalf("access = %q, want access-live", parsed[client.AccessToken.EnvVariable])
}
if parsed[client.RefreshToken.EnvVariable] != "refresh-live" {
t.Fatalf("refresh = %q, want refresh-live", parsed[client.RefreshToken.EnvVariable])
}
if parsed[client.AccountID.EnvVariable] != "acct-live" {
t.Fatalf("account = %q, want acct-live", parsed[client.AccountID.EnvVariable])
}
}
func newInactiveVendorRegistry(t *testing.T, defaultVendor, defaultModel string, vendor *testVendor) *PluginRegistry {
t.Helper()
db := fsdb.NewDb(t.TempDir())
all := ai.NewVendorsManager()
all.AddVendors(vendor)
defaults := &tools.Defaults{
PluginBase: &plugins.PluginBase{},
Vendor: &plugins.Setting{Value: defaultVendor},
Model: &plugins.SetupQuestion{Setting: &plugins.Setting{Value: defaultModel}},
ModelContextLength: &plugins.SetupQuestion{Setting: &plugins.Setting{Value: "0"}},
}
return &PluginRegistry{
Db: db,
VendorManager: ai.NewVendorsManager(),
VendorsAll: all,
Defaults: defaults,
}
}

View file

@ -218,8 +218,7 @@ func getLocaleCandidates(locale string) []string {
candidates = append(candidates, locale)
// If it's a regional variant, add the base language as a candidate
if strings.Contains(locale, "-") {
baseLang := strings.Split(locale, "-")[0]
if baseLang, _, found := strings.Cut(locale, "-"); found {
candidates = append(candidates, baseLang)
// Also check if the base language has a default variant

View file

@ -20,6 +20,7 @@ func TestTranslation(t *testing.T) {
{"pt", "usa a entrada original, porque não é possível aplicar a legibilidade HTML"},
{"fa", "از ورودی اصلی استفاده کن، چون نمی‌توان خوانایی HTML را اعمال کرد"},
{"it", "usa l'input originale, perché non è possibile applicare la leggibilità HTML"},
{"tr", "html okunabilirliği uygulanamadığından orijinal girdiyi kullanın"},
}
for _, tc := range testCases {

View file

@ -54,7 +54,7 @@
"bedrock_aws_access_key_label": "Geben Sie Ihre AWS Access Key ID ein (leer lassen, um die AWS-Anmeldekette zu verwenden)",
"bedrock_aws_region_label": "AWS-Region",
"bedrock_aws_secret_key_label": "Geben Sie Ihren AWS Secret Access Key ein (leer lassen, um die AWS-Anmeldekette zu verwenden)",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"bedrock_client_not_initialized": "Bedrock-Client nicht initialisiert — führen Sie 'fabric --setup' zur Konfiguration aus",
"bedrock_converse_failed": "Bedrock Converse für Modell %s fehlgeschlagen: %w",
"bedrock_conversestream_failed": "Bedrock ConverseStream für Modell %s fehlgeschlagen: %w",
"bedrock_empty_response_content": "leerer Antwortinhalt",
@ -67,21 +67,21 @@
"bedrock_listmodels_fallback": "Bedrock ListModels API fehlgeschlagen, verwende statische Fallback-Liste",
"bedrock_panic_sendstream": "Panic in SendStream: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",
"bedrock_setup_auth_prompt": "Enter 1 or 2",
"bedrock_setup_choose_auth_method": " Choose authentication method:",
"bedrock_setup_choose_model": " Choose default model (unprefixed IDs work in any region; us./eu./ap. are region-specific):",
"bedrock_setup_choose_region": " Choose AWS Region:",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API-Schlüssel / ABSK-Token (empfohlen — derselbe wie bei Claude Code)",
"bedrock_setup_auth_prompt": "Geben Sie 1 oder 2 ein",
"bedrock_setup_choose_auth_method": " Authentifizierungsmethode wählen:",
"bedrock_setup_choose_model": " Standardmodell wählen (IDs ohne Präfix funktionieren in jeder Region; us./eu./ap. sind regionsspezifisch):",
"bedrock_setup_choose_region": " AWS-Region wählen:",
"bedrock_setup_header": "[Bedrock]",
"bedrock_setup_invalid_auth_selection": "invalid selection: %s (enter 1 or 2)",
"bedrock_setup_model_custom_prompt": "Enter model ID (e.g. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Enter a different model ID",
"bedrock_setup_model_prompt": "Enter model number or 0 to type your own",
"bedrock_setup_region_custom_prompt": "Enter custom AWS region (e.g. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Enter a different region",
"bedrock_setup_region_prompt": "Enter region number or 0 for custom",
"bedrock_setup_selected_model": " ✓ Selected model: %s",
"bedrock_setup_use_with": " Use with: fabric -m %s -V Bedrock",
"bedrock_setup_invalid_auth_selection": "Ungültige Auswahl: %s (geben Sie 1 oder 2 ein)",
"bedrock_setup_model_custom_prompt": "Geben Sie die Modell-ID ein (z. B. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Eine andere Modell-ID eingeben",
"bedrock_setup_model_prompt": "Geben Sie die Modellnummer ein oder 0, um eine eigene einzugeben",
"bedrock_setup_region_custom_prompt": "Geben Sie eine eigene AWS-Region ein (z. B. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Eine andere Region eingeben",
"bedrock_setup_region_prompt": "Geben Sie die Regionsnummer ein oder 0 für eine eigene",
"bedrock_setup_selected_model": " ✓ Ausgewähltes Modell: %s",
"bedrock_setup_use_with": " Verwendung: fabric -m %s -V Bedrock",
"bedrock_unable_load_aws_config": "AWS-Konfiguration konnte nicht geladen werden: %w",
"bedrock_unable_load_aws_config_with_region": "AWS-Konfiguration konnte mit Region %s nicht geladen werden: %w",
"bedrock_unexpected_content_block_type": "unerwarteter Inhaltsblocktyp: %T",
@ -110,21 +110,43 @@
"choose_pattern_from_available": "Wähle ein Muster aus den verfügbaren Mustern",
"choose_session_from_available": "Wähle eine Sitzung aus den verfügbaren Sitzungen",
"choose_strategy_from_available": "Strategie aus den verfügbaren Strategien wählen",
"codex_api_base_url_question": "Geben Sie Ihre Codex-API-Basis-URL ein",
"codex_auth_base_url_invalid": "Ungültige Codex-Authentifizierungs-Basis-URL: %w",
"codex_auth_base_url_question": "Geben Sie Ihre Codex-OAuth-Basis-URL ein",
"codex_browser_open_fallback": "Falls Ihr Browser sich nicht geöffnet hat, navigieren Sie zu dieser URL zur Authentifizierung:",
"codex_decode_models_response_failed": "Codex-Modell-Antwort konnte nicht dekodiert werden: %w",
"codex_decode_refresh_response_failed": "Aktualisierte Codex-Token-Antwort konnte nicht dekodiert werden: %w",
"codex_decode_token_response_failed": "Codex-Token-Austausch-Antwort konnte nicht dekodiert werden: %w",
"codex_image_file_not_supported": "Der Codex-Anbieter unterstützt --image-file nicht. Verwenden Sie stattdessen einen Bildanhang.",
"codex_login_account_changed": "Die Codex-Anmeldung ist mit einem anderen ChatGPT-Konto verknüpft als in der gespeicherten Konfiguration. Bitte führen Sie 'fabric --setup' erneut aus.",
"codex_login_completed": "Codex-Anmeldung abgeschlossen",
"codex_login_failed": "Codex-Anmeldung fehlgeschlagen: %s",
"codex_login_invalid": "Ihre Codex-Anmeldung ist nicht mehr gültig. Bitte führen Sie 'fabric --setup' erneut aus.",
"codex_login_missing_account_claim": "Die Codex-Anmeldung enthielt keine ChatGPT-Konto-ID. Dieser Anmeldezustand wird nicht unterstützt.",
"codex_login_missing_auth_code": "Die Codex-Anmeldung hat keinen Autorisierungscode zurückgegeben.",
"codex_login_missing_tokens": "Die Codex-Anmeldung hat die erforderlichen Zugangs- und Aktualisierungstoken nicht zurückgegeben.",
"codex_login_refresh_failed": "Die Codex-Anmeldung konnte nicht aktualisiert werden. Bitte führen Sie 'fabric --setup' erneut aus.",
"codex_login_return_to_fabric": "Zurück zu Fabric.",
"codex_login_revoked": "Die Codex-Anmeldung ist abgelaufen oder wurde widerrufen. Bitte führen Sie 'fabric --setup' erneut aus.",
"codex_login_server_stopped": "Der Codex-Anmelde-Callback-Server wurde gestoppt, bevor die Authentifizierung abgeschlossen war.",
"codex_login_state_mismatch": "Die Codex-Anmeldung konnte nicht verifiziert werden, da der OAuth-Status nicht übereinstimmte.",
"codex_login_timed_out": "Zeitüberschreitung bei der Codex-Anmeldung vor Abschluss der Authentifizierung.",
"codex_oauth_missing_auth_code": "Fehlender Autorisierungscode",
"codex_oauth_random_state_failed": "Sicherer zufälliger OAuth-Status konnte nicht generiert werden: %w",
"codex_oauth_server_start_failed": "Lokaler OAuth-Callback-Server konnte nicht gestartet werden: %w",
"codex_oauth_state_mismatch": "Status stimmt nicht überein",
"codex_provider_error": "Codex-Anbieterfehler (Status %d): %s",
"codex_refresh_failed_status": "Codex-Anmeldung konnte nicht aktualisiert werden (Status %d)",
"codex_refresh_login_failed": "Codex-Anmeldung konnte nicht aktualisiert werden: %w",
"codex_refresh_token_required": "Codex-Aktualisierungstoken ist erforderlich. Bitte führen Sie 'fabric --setup' erneut aus.",
"codex_replay_body_unavailable": "Anfragekörper kann für Codex-Reauthentifizierungswiederholung nicht wiedergegeben werden",
"codex_request_failed": "Codex-Anfrage fehlgeschlagen: %w",
"codex_request_failed_status": "Codex-Anfrage fehlgeschlagen mit Status %d",
"codex_starting_browser_login": "Starte browserbasierte OpenAI-Anmeldung für Codex.",
"codex_token_exchange_failed": "Codex-Token-Austausch fehlgeschlagen: %w",
"codex_token_persist_failed": "Codex-Anmeldung konnte nicht gespeichert werden: %w",
"codex_token_refresh_missing_access_token": "Die Codex-Token-Aktualisierung hat kein Zugriffstoken zurückgegeben.",
"codex_usage_limit_reached": "Codex-Nutzungslimit erreicht",
"command_completed_successfully": "Befehl erfolgreich abgeschlossen",
"compression_level_jpeg_webp": "Komprimierungslevel 0-100 für JPEG/WebP-Formate (Standard: nicht gesetzt)",
"config_file_not_found": "Konfigurationsdatei nicht gefunden: %s",
@ -151,6 +173,7 @@
"custom_patterns_setup_description": "Benutzerdefinierte Patterns - Verzeichnis für Ihre benutzerdefinierten Patterns festlegen",
"custom_patterns_warning_create_directory": "Warnung: Benutzerdefiniertes Musterverzeichnis %s konnte nicht erstellt werden: %v\n",
"db_error_loading_env_file": "fehler beim Laden der .env-Datei: %w",
"db_error_updating_env_file": "fehler beim Aktualisieren der .env-Datei: %w",
"defaults_model_context_length_question": "Geben Sie die Kontextlänge des Modells ein",
"defaults_model_question": "Geben Sie den Index oder den Namen Ihres Standardmodells ein",
"defaults_setup_description": "Standard-KI-Anbieter und -Modell",
@ -161,7 +184,7 @@
"digitalocean_models_request_failed_with_status": "DigitalOcean-Modellanfrage fehlgeschlagen mit Status %d: %s",
"disable_openai_responses_api": "OpenAI Responses API deaktivieren (Standard: false)",
"disable_pattern_variable_replacement": "Mustervariablenersetzung deaktivieren",
"enable_web_search_tool": "Web-Such-Tool für unterstützte Modelle aktivieren (Anthropic, OpenAI, Gemini)",
"enable_web_search_tool": "Web-Such-Tool für unterstützte Modelle aktivieren (Anthropic, OpenAI, Gemini, Grok)",
"end_tag_thinking_sections": "End-Tag für Denk-Abschnitte",
"error_creating_audio_file": "Fehler beim Erstellen der Audio-Datei: %v",
"error_creating_file": "Fehler beim Erstellen der Datei: %v",
@ -352,6 +375,7 @@
"ollama_invalid_data_url_format": "ungültiges Data-URL-Format",
"ollama_invalid_http_timeout_using_default": "ungültiges HTTP-Zeitlimit '%s': %v, verwende Standardwert",
"ollama_invalid_num_ctx_in_request": "Ungültiger num_ctx in Anfrage: %v",
"ollama_invalid_request_body": "ungültiger Anfragetext",
"ollama_no_content_from_upstream": "Kein Inhalt vom Upstream Fabric Server erhalten",
"ollama_num_ctx_exceeds_maximum": "num_ctx überschreitet den maximal zulässigen Wert von %d",
"ollama_num_ctx_invalid_type": "num_ctx muss eine Zahl sein, ungültiger Typ erhalten",
@ -365,6 +389,7 @@
"ollama_sse_buffer_limit": "SSE Zeile überschreitet 1MB Puffer-Limit - Datenzeile zu groß",
"ollama_upstream_non_2xx": "Upstream Fabric Server hat nicht-2xx Status %d zurückgegeben: %s",
"ollama_upstream_non_2xx_body_unreadable": "Upstream Fabric Server hat nicht-2xx Status %d zurückgegeben und Body konnte nicht gelesen werden: %v",
"ollama_upstream_request_failed": "Upstream-Fabric-Server nicht erreichbar",
"ollama_upstream_returned_status": "Upstream Fabric Server hat Status %d zurückgegeben",
"ollama_warning_no_content": "Warnung: Kein Inhalt vom Upstream Fabric Server erhalten",
"ollama_warning_parse_variables": "Warnung: Fehler beim Parsen von options.variables als JSON: %v",
@ -385,6 +410,7 @@
"openai_image_failed_to_save_image": "Bild konnte nicht in %s gespeichert werden: %w",
"openai_image_saved_to": "Bild gespeichert unter: %s",
"openai_model_no_image_generation": "Modell '%s' unterstützt keine Bildgenerierung. Unterstützte Modelle: %s",
"openai_models_rate_limited": "Ratenlimit beim Abrufen der Modelle von Anbieter %s überschritten; erneuter Versuch in %s Sekunden",
"openai_models_response_too_large": "Modell-Antwort zu groß von Anbieter %s (>%d Bytes)",
"openai_unable_to_parse_models_response": "Modell-Antwort konnte nicht geparst werden; rohe Antwort: %s",
"openai_unexpected_status_code_read_error": "unerwarteter Statuscode: %d von Anbieter %s (Fehler beim Lesen der Antwort: %v)",
@ -400,6 +426,7 @@
"output_video_metadata": "Video-Metadaten ausgeben",
"path_to_yaml_config": "Pfad zur YAML-Konfigurationsdatei",
"pattern_not_found_list_available": "Pattern '%s' nicht gefunden. Führen Sie 'fabric -l' aus, um verfügbare Patterns anzuzeigen",
"pattern_invalid_name": "Ungültiger Pattern-Name: %q",
"pattern_not_found_no_patterns": "Pattern '%s' nicht gefunden.\n\nKeine Patterns installiert! Um dies zu beheben:\n • Führen Sie 'fabric --setup' aus, um Patterns zu konfigurieren und herunterzuladen\n • Oder führen Sie 'fabric -U' aus, um Patterns direkt herunterzuladen/zu aktualisieren",
"pattern_variables_help": "Werte für Mustervariablen, z.B. -v=#role:expert -v=#points:30",
"patterns_cloning_repository": "Repository %s wird geklont (Pfad: %s)...\\n",
@ -473,6 +500,8 @@
"prefer_playlist_over_video": "Playlist gegenüber Video bevorzugen, wenn beide IDs in der URL vorhanden sind",
"print_context": "Kontext ausgeben",
"print_current_version": "Aktuelle Version ausgeben",
"print_metadata_to_stderr": "Metadaten (Eingabe-/Ausgabe-Token) auf stderr ausgeben",
"print_pattern_contents": "Den Inhalt des angegebenen Musters im Terminal ausgeben",
"print_session": "Sitzung ausgeben",
"register_new_extension": "Neue Erweiterung aus Konfigurationsdateipfad registrieren",
"remove_registered_extension": "Registrierte Erweiterung nach Name entfernen",
@ -486,10 +515,12 @@
"send_desktop_notification": "Desktop-Benachrichtigung senden, wenn Befehl abgeschlossen ist",
"serve_fabric_api_ollama_endpoints": "Fabric REST API mit ollama-Endpunkten bereitstellen",
"serve_fabric_rest_api": "Fabric REST API bereitstellen",
"server_api_key_required": "Server-Start auf Nicht-Loopback-Adresse %s ohne API-Schlüssel verweigert: Setzen Sie --api-key oder FABRIC_API_KEY, oder binden Sie eine Loopback-Adresse wie 127.0.0.1:8080",
"server_chat_error": "Fehler: %v",
"server_error_marshaling_response": "Fehler beim Serialisieren der Antwort: %v",
"server_error_writing_response": "Fehler beim Schreiben der Antwort: %v",
"server_invalid_request_format": "ungültiges Anfrageformat: %v",
"server_no_api_key_warning": "REST-API-Server wird ohne API-Schlüssel-Authentifizierung gestartet. Dies kann Sicherheitsrisiken bergen.",
"sessions_creating_new": "Erstelle neue Sitzung: %s\n",
"set_debug_level": "Debug-Level festlegen (0=aus, 1=grundlegend, 2=detailliert, 3=Trace, 4=wire)",
"set_frequency_penalty": "Häufigkeitsstrafe festlegen",
@ -581,6 +612,7 @@
"spotify_show_name_label": "**Sendung**: %s",
"spotify_title_label": "**Titel**: %s",
"spotify_total_episodes_label": "**Episoden insgesamt**: %d",
"spotify_url_help": "Spotify-Podcast- oder Episoden-URL, um Metadaten abzurufen und an den Chat zu senden",
"spotify_url_label": "**URL**: %s",
"start_tag_thinking_sections": "Start-Tag für Denk-Abschnitte",
"storage_error_delete": "%s konnte nicht gelöscht werden: %v",
@ -592,6 +624,7 @@
"storage_error_save": "%s konnte nicht gespeichert werden: %v",
"storage_error_stat_entry": "Eintrag %s konnte nicht abgefragt werden: %v",
"storage_error_unmarshal": "%s konnte nicht deserialisiert werden: %s",
"storage_invalid_name": "Ungültiger Name: %q",
"strategies_available_header": "Verfügbare Strategien:",
"strategies_cloning_repository": "Repository %s wird geklont (Pfad: %s)...\\n",
"strategies_download_success": "✅ Strategien erfolgreich nach %s heruntergeladen und installiert\\n",
@ -610,11 +643,11 @@
"strategy_path_traversal": "Strategiename %q löst sich außerhalb des Strategieverzeichnisses auf",
"stream_help": "Streaming",
"suppress_thinking_tags": "In Denk-Tags eingeschlossenen Text unterdrücken",
"template_datetime_error_invalid_number": "invalid number in relative time: %q",
"template_datetime_error_invalid_relative_format": "invalid relative time format",
"template_datetime_error_invalid_unit": "invalid time unit: %q",
"template_datetime_error_relative_requires_value": "relative time requires a value",
"template_datetime_error_unknown_operation": "datetime: unknown operation %q",
"template_datetime_error_invalid_number": "ungültige Zahl in relativer Zeitangabe: %q",
"template_datetime_error_invalid_relative_format": "ungültiges Format für relative Zeitangabe",
"template_datetime_error_invalid_unit": "ungültige Zeiteinheit: %q",
"template_datetime_error_relative_requires_value": "relative Zeitangabe erfordert einen Wert",
"template_datetime_error_unknown_operation": "datetime: unbekannte Operation %q",
"template_extension_error": "Erweiterung %s Fehler: %v",
"template_file_error_expand_home_dir": "datei: home-verzeichnis konnte nicht erweitert werden: %v",
"template_file_error_invalid_line_count": "datei: ungültige zeilenanzahl %q",
@ -643,12 +676,12 @@
"template_missing_required_variable": "Erforderliche Variable fehlt: %s",
"template_plugin_error": "Plugin %s Fehler: %v",
"template_processing_stuck": "Vorlagenverarbeitung blockiert - mögliche Endlosschleife",
"template_sys_error_env_requires_var": "env: variable name required",
"template_sys_error_home": "failed to get home directory: %v",
"template_sys_error_hostname": "failed to get hostname: %v",
"template_sys_error_pwd": "failed to get working directory: %v",
"template_sys_error_unknown_operation": "sys: unknown operation %q",
"template_sys_error_user": "failed to get current user: %v",
"template_sys_error_env_requires_var": "env: Variablenname erforderlich",
"template_sys_error_home": "Home-Verzeichnis konnte nicht ermittelt werden: %v",
"template_sys_error_hostname": "Hostname konnte nicht ermittelt werden: %v",
"template_sys_error_pwd": "Arbeitsverzeichnis konnte nicht ermittelt werden: %v",
"template_sys_error_unknown_operation": "sys: unbekannte Operation %q",
"template_sys_error_user": "aktueller Benutzer konnte nicht ermittelt werden: %v",
"template_text_empty_input": "Text: Leere Eingabe für Operation %q",
"template_text_unknown_operation": "Text: Unbekannte Textoperation %q (unterstützt: upper, lower, title, trim)",
"template_unknown_plugin_namespace": "Unbekannter Plugin-Namespace: %s",
@ -698,15 +731,21 @@
"youtube_error_getting_video_details": "Fehler beim Abrufen der Videodetails: %v",
"youtube_error_parsing_duration": "Fehler beim Parsen der Videodauer: %v",
"youtube_error_saving_csv": "Fehler beim Speichern der Videos in CSV: %v",
"youtube_extract_visual_data_help": "Visuelle Daten mit OCR und FFmpeg aus dem Video extrahieren",
"youtube_failed_create_temp_dir": "temporäres Verzeichnis konnte nicht erstellt werden: %v",
"youtube_failed_fetch_comments": "Kommentare konnten nicht abgerufen werden: %v",
"youtube_failed_get_stream_url": "Stream-URL konnte über yt-dlp nicht ermittelt werden: %v",
"youtube_failed_parse_http_stream_url": "gültige HTTP-Stream-URL konnte aus der yt-dlp-Ausgabe nicht geparst werden",
"youtube_failed_walk_directory": "Verzeichnis konnte nicht durchlaufen werden: %v",
"youtube_ffmpeg_frame_extraction_failed": "FFmpeg-Frame-Extraktion fehlgeschlagen: %v, Ausgabe: %s",
"youtube_ffmpeg_required_visual_extraction": "ffmpeg wird für die visuelle Extraktion benötigt, wurde aber im PATH nicht gefunden",
"youtube_invalid_duration_string": "ungültige Dauer-Zeichenfolge: %s",
"youtube_invalid_seconds_format": "ungültiges Sekundenformat %q: %w",
"youtube_invalid_timestamp_format": "ungültiges Zeitstempel-Format: %s",
"youtube_invalid_url": "ungültige YouTube-URL, kann keine Video- oder Playlist-ID abrufen: '%s'",
"youtube_invalid_ytdlp_arguments": "ungültige yt-dlp-Argumente: %v",
"youtube_label": "YouTube",
"youtube_no_clear_text_visual_frames": "kein klarer Text in visuellen Videoframes gefunden",
"youtube_no_transcript_content": "kein Transkriptinhalt in VTT-Datei gefunden",
"youtube_no_url_provided": "Keine YouTube-URL angegeben",
"youtube_no_video_found_with_id": "kein Video mit ID gefunden: %s",
@ -717,9 +756,15 @@
"youtube_playlist_saved_to": "Wiedergabeliste gespeichert unter %s",
"youtube_rate_limit_exceeded": "YouTube-Ratenlimit überschritten. Versuche es später erneut oder verwende andere yt-dlp-Argumente wie '--sleep-requests 1', um Anfragen zu verlangsamen.",
"youtube_setup_description": "YouTube - zum Erfassen von Video-Transkripten (via yt-dlp) und Kommentaren/Metadaten (via YouTube API)",
"youtube_tesseract_frame_failed": "tesseract für Frame %d fehlgeschlagen: %v, stderr: %s",
"youtube_tesseract_required_visual_extraction": "tesseract wird für die visuelle Extraktion benötigt, wurde aber im PATH nicht gefunden",
"youtube_url_help": "YouTube-Video oder Playlist-\"URL\" zum Abrufen von Transkript und Kommentaren und Senden an Chat oder Ausgabe in Konsole und Speichern in Ausgabedatei",
"youtube_url_is_playlist_not_video": "URL ist eine Playlist, kein Video",
"youtube_video_id_title_header": "VideoID: Titel",
"youtube_visual_fps_help": "Eine bestimmte Anzahl von Frames pro Sekunde extrahieren, statt Szenenerkennung zu verwenden",
"youtube_visual_frame_cue": "[VISUELLER FRAME-HINWEIS]",
"youtube_visual_sensitivity_help": "Toleranz für die FFmpeg-Szenenerkennung (0.0 - 1.0)",
"youtube_ytdlp_not_found": "yt-dlp wurde nicht in PATH gefunden. Bitte installiere yt-dlp, um die YouTube-Transkript-Funktionalität zu nutzen",
"youtube_ytdlp_required_visual_extraction": "yt-dlp wird für die visuelle Extraktion benötigt, wurde aber im PATH nicht gefunden",
"youtube_ytdlp_stderr_error": "fehler beim Lesen von yt-dlp stderr"
}

View file

@ -110,21 +110,43 @@
"choose_pattern_from_available": "Choose a pattern from the available patterns",
"choose_session_from_available": "Choose a session from the available sessions",
"choose_strategy_from_available": "Choose a strategy from the available strategies",
"codex_api_base_url_question": "Enter your Codex API base URL",
"codex_auth_base_url_invalid": "invalid codex auth base url: %w",
"codex_auth_base_url_question": "Enter your Codex OAuth base URL",
"codex_browser_open_fallback": "If your browser did not open, navigate to this URL to authenticate:",
"codex_decode_models_response_failed": "failed to decode codex models response: %w",
"codex_decode_refresh_response_failed": "failed to decode refreshed Codex token response: %w",
"codex_decode_token_response_failed": "failed to decode codex token exchange response: %w",
"codex_image_file_not_supported": "Codex vendor does not support --image-file. Use an image attachment instead.",
"codex_login_account_changed": "Codex login is linked to a different ChatGPT account than the stored configuration. Please rerun 'fabric --setup'.",
"codex_login_completed": "Codex login completed",
"codex_login_failed": "Codex login failed: %s",
"codex_login_invalid": "Codex login is no longer valid. Please rerun 'fabric --setup'.",
"codex_login_missing_account_claim": "Codex login did not include a ChatGPT account ID. This login state is not supported.",
"codex_login_missing_auth_code": "Codex login did not return an authorization code.",
"codex_login_missing_tokens": "Codex login did not return the required access and refresh tokens.",
"codex_login_refresh_failed": "Codex login could not be refreshed. Please rerun 'fabric --setup'.",
"codex_login_return_to_fabric": "Return to Fabric.",
"codex_login_revoked": "Codex login has expired or been revoked. Please rerun 'fabric --setup'.",
"codex_login_server_stopped": "Codex login callback server stopped before authentication completed.",
"codex_login_state_mismatch": "Codex login could not be verified because the OAuth state did not match.",
"codex_login_timed_out": "Codex login timed out before authentication completed.",
"codex_oauth_missing_auth_code": "Missing authorization code",
"codex_oauth_random_state_failed": "failed to generate secure random oauth state: %w",
"codex_oauth_server_start_failed": "failed to start local oauth callback server: %w",
"codex_oauth_state_mismatch": "State mismatch",
"codex_provider_error": "codex provider error (status %d): %s",
"codex_refresh_failed_status": "failed to refresh codex login (status %d)",
"codex_refresh_login_failed": "failed to refresh Codex login: %w",
"codex_refresh_token_required": "Codex refresh token is required. Please rerun 'fabric --setup'.",
"codex_replay_body_unavailable": "request body cannot be replayed for Codex re-authentication retry",
"codex_request_failed": "codex request failed: %w",
"codex_request_failed_status": "codex request failed with status %d",
"codex_starting_browser_login": "Starting browser-based OpenAI login for Codex.",
"codex_token_exchange_failed": "codex token exchange failed: %w",
"codex_token_persist_failed": "failed to persist Codex login: %w",
"codex_token_refresh_missing_access_token": "Codex token refresh did not return an access token.",
"codex_usage_limit_reached": "codex usage limit reached",
"command_completed_successfully": "Command completed successfully",
"compression_level_jpeg_webp": "Compression level 0-100 for JPEG/WebP formats (default: not set)",
"config_file_not_found": "config file not found: %s",
@ -151,6 +173,7 @@
"custom_patterns_setup_description": "Custom Patterns - Set directory for your custom patterns",
"custom_patterns_warning_create_directory": "Warning: Could not create custom patterns directory %s: %v\n",
"db_error_loading_env_file": "error loading .env file: %w",
"db_error_updating_env_file": "error updating .env file: %w",
"defaults_model_context_length_question": "Enter model context length",
"defaults_model_question": "Enter the index or the name of your default model",
"defaults_setup_description": "Default AI Vendor and Model",
@ -161,7 +184,7 @@
"digitalocean_models_request_failed_with_status": "DigitalOcean models request failed with status %d: %s",
"disable_openai_responses_api": "Disable OpenAI Responses API (default: false)",
"disable_pattern_variable_replacement": "Disable pattern variable replacement",
"enable_web_search_tool": "Enable web search tool for supported models (Anthropic, OpenAI, Gemini)",
"enable_web_search_tool": "Enable web search tool for supported models (Anthropic, OpenAI, Gemini, Grok)",
"end_tag_thinking_sections": "End tag for thinking sections",
"error_creating_audio_file": "error creating audio file: %v",
"error_creating_file": "error creating file: %v",
@ -352,6 +375,7 @@
"ollama_invalid_data_url_format": "invalid data URL format",
"ollama_invalid_http_timeout_using_default": "invalid HTTP timeout '%s': %v, using default",
"ollama_invalid_num_ctx_in_request": "invalid num_ctx in request: %v",
"ollama_invalid_request_body": "invalid request body",
"ollama_no_content_from_upstream": "no content received from upstream Fabric server",
"ollama_num_ctx_exceeds_maximum": "num_ctx exceeds maximum allowed value of %d",
"ollama_num_ctx_invalid_type": "num_ctx must be a number, got invalid type",
@ -365,6 +389,7 @@
"ollama_sse_buffer_limit": "SSE line exceeds 1MB buffer limit - data line too large",
"ollama_upstream_non_2xx": "Upstream Fabric server returned non-2xx status %d: %s",
"ollama_upstream_non_2xx_body_unreadable": "Upstream Fabric server returned non-2xx status %d and body could not be read: %v",
"ollama_upstream_request_failed": "failed to reach upstream Fabric server",
"ollama_upstream_returned_status": "upstream Fabric server returned status %d",
"ollama_warning_no_content": "Warning: no content received from upstream Fabric server",
"ollama_warning_parse_variables": "Warning: failed to parse options.variables as JSON: %v",
@ -385,6 +410,7 @@
"openai_image_failed_to_save_image": "failed to save image to %s: %w",
"openai_image_saved_to": "Image saved to: %s",
"openai_model_no_image_generation": "model '%s' does not support image generation. Supported models: %s",
"openai_models_rate_limited": "rate limit exceeded fetching models from provider %s; retry after %s seconds",
"openai_models_response_too_large": "models response too large from provider %s (>%d bytes)",
"openai_unable_to_parse_models_response": "unable to parse models response; raw response: %s",
"openai_unexpected_status_code_read_error": "unexpected status code: %d from provider %s (failed to read response body: %v)",
@ -400,6 +426,7 @@
"output_video_metadata": "Output video metadata",
"path_to_yaml_config": "Path to YAML config file",
"pattern_not_found_list_available": "pattern '%s' not found. Run 'fabric -l' to see available patterns",
"pattern_invalid_name": "invalid pattern name: %q",
"pattern_not_found_no_patterns": "pattern '%s' not found.\n\nNo patterns are installed! To fix this:\n • Run 'fabric --setup' to configure and download patterns\n • Or run 'fabric -U' to download/update patterns directly",
"pattern_variables_help": "Values for pattern variables, e.g. -v=#role:expert -v=#points:30",
"patterns_cloning_repository": "Cloning repository %s (path: %s)...\n",
@ -473,6 +500,8 @@
"prefer_playlist_over_video": "Prefer playlist over video if both ids are present in the URL",
"print_context": "Print context",
"print_current_version": "Print current version",
"print_metadata_to_stderr": "Print metadata (input/output tokens) to stderr",
"print_pattern_contents": "Print the contents of the named pattern to the terminal",
"print_session": "Print session",
"register_new_extension": "Register a new extension from config file path",
"remove_registered_extension": "Remove a registered extension by name",
@ -486,10 +515,12 @@
"send_desktop_notification": "Send desktop notification when command completes",
"serve_fabric_api_ollama_endpoints": "Serve the Fabric Rest API with ollama endpoints",
"serve_fabric_rest_api": "Serve the Fabric Rest API",
"server_api_key_required": "refusing to serve on non-loopback address %s without an API key: set --api-key or FABRIC_API_KEY, or bind a loopback address such as 127.0.0.1:8080",
"server_chat_error": "Error: %v",
"server_error_marshaling_response": "error marshaling response: %v",
"server_error_writing_response": "error writing response: %v",
"server_invalid_request_format": "invalid request format: %v",
"server_no_api_key_warning": "Starting REST API server without API key authentication. This may pose security risks.",
"sessions_creating_new": "Creating new session: %s\n",
"set_debug_level": "Set debug level (0=off, 1=basic, 2=detailed, 3=trace, 4=wire)",
"set_frequency_penalty": "Set frequency penalty",
@ -581,6 +612,7 @@
"spotify_show_name_label": "**Show**: %s",
"spotify_title_label": "**Title**: %s",
"spotify_total_episodes_label": "**Total Episodes**: %d",
"spotify_url_help": "Spotify podcast or episode URL to grab metadata from and send to chat",
"spotify_url_label": "**URL**: %s",
"start_tag_thinking_sections": "Start tag for thinking sections",
"storage_error_delete": "could not delete %s: %v",
@ -592,6 +624,7 @@
"storage_error_save": "could not save %s: %v",
"storage_error_stat_entry": "could not stat entry %s: %v",
"storage_error_unmarshal": "could not unmarshal %s: %s",
"storage_invalid_name": "invalid name: %q",
"strategies_available_header": "Available Strategies:",
"strategies_cloning_repository": "Cloning repository %s (path: %s)...\n",
"strategies_download_success": "✅ Successfully downloaded and installed strategies to %s\n",
@ -698,15 +731,21 @@
"youtube_error_getting_video_details": "error getting video details: %v",
"youtube_error_parsing_duration": "error parsing video duration: %v",
"youtube_error_saving_csv": "error saving videos to CSV: %v",
"youtube_extract_visual_data_help": "Extract visual data from video using OCR and FFmpeg",
"youtube_failed_create_temp_dir": "failed to create temp directory: %v",
"youtube_failed_fetch_comments": "failed to fetch comments: %v",
"youtube_failed_get_stream_url": "failed to get stream URL via yt-dlp: %v",
"youtube_failed_parse_http_stream_url": "failed to parse valid HTTP stream URL from yt-dlp output",
"youtube_failed_walk_directory": "failed to walk directory: %v",
"youtube_ffmpeg_frame_extraction_failed": "ffmpeg frame extraction failed: %v, output: %s",
"youtube_ffmpeg_required_visual_extraction": "ffmpeg is required for visual extraction but not found in PATH",
"youtube_invalid_duration_string": "invalid duration string: %s",
"youtube_invalid_seconds_format": "invalid seconds format %q: %w",
"youtube_invalid_timestamp_format": "invalid timestamp format: %s",
"youtube_invalid_url": "invalid YouTube URL, can't get video or playlist ID: '%s'",
"youtube_invalid_ytdlp_arguments": "invalid yt-dlp arguments: %v",
"youtube_label": "YouTube",
"youtube_no_clear_text_visual_frames": "no clear text found in video visual frames",
"youtube_no_transcript_content": "no transcript content found in VTT file",
"youtube_no_url_provided": "No YouTube URL provided",
"youtube_no_video_found_with_id": "no video found with ID: %s",
@ -717,9 +756,15 @@
"youtube_playlist_saved_to": "Playlist saved to %s",
"youtube_rate_limit_exceeded": "YouTube rate limit exceeded. Try again later or use different yt-dlp arguments like '--sleep-requests 1' to slow down requests.",
"youtube_setup_description": "YouTube - to grab video transcripts (via yt-dlp) and comments/metadata (via YouTube API)",
"youtube_tesseract_frame_failed": "tesseract failed on frame %d: %v, stderr: %s",
"youtube_tesseract_required_visual_extraction": "tesseract is required for visual extraction but not found in PATH",
"youtube_url_help": "YouTube video or play list \"URL\" to grab transcript, comments from it and send to chat or print it put to the console and store it in the output file",
"youtube_url_is_playlist_not_video": "URL is a playlist, not a video",
"youtube_video_id_title_header": "VideoID: Title",
"youtube_visual_fps_help": "Extract a specific number of frames per second instead of using scene detection",
"youtube_visual_frame_cue": "[VISUAL FRAME CUE]",
"youtube_visual_sensitivity_help": "Tolerance for FFmpeg scene detection (0.0 - 1.0)",
"youtube_ytdlp_not_found": "yt-dlp not found in PATH. Please install yt-dlp to use YouTube transcript functionality",
"youtube_ytdlp_required_visual_extraction": "yt-dlp is required for visual extraction but not found in PATH",
"youtube_ytdlp_stderr_error": "error reading yt-dlp stderr"
}

View file

@ -54,7 +54,7 @@
"bedrock_aws_access_key_label": "Ingrese su AWS Access Key ID (deje vacío para usar la cadena de credenciales de AWS)",
"bedrock_aws_region_label": "Región de AWS",
"bedrock_aws_secret_key_label": "Ingrese su AWS Secret Access Key (deje vacío para usar la cadena de credenciales de AWS)",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"bedrock_client_not_initialized": "cliente de Bedrock no inicializado — ejecute 'fabric --setup' para configurar",
"bedrock_converse_failed": "bedrock converse falló para el modelo %s: %w",
"bedrock_conversestream_failed": "bedrock conversestream falló para el modelo %s: %w",
"bedrock_empty_response_content": "contenido de respuesta vacío",
@ -67,21 +67,21 @@
"bedrock_listmodels_fallback": "la API ListModels de Bedrock falló, usando lista estática de respaldo",
"bedrock_panic_sendstream": "pánico en SendStream: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",
"bedrock_setup_auth_prompt": "Enter 1 or 2",
"bedrock_setup_choose_auth_method": " Choose authentication method:",
"bedrock_setup_choose_model": " Choose default model (unprefixed IDs work in any region; us./eu./ap. are region-specific):",
"bedrock_setup_choose_region": " Choose AWS Region:",
"bedrock_setup_auth_option_apikey": " [1] Clave API de Bedrock / token ABSK (recomendado — la misma que usa Claude Code)",
"bedrock_setup_auth_prompt": "Introduzca 1 o 2",
"bedrock_setup_choose_auth_method": " Elija el método de autenticación:",
"bedrock_setup_choose_model": " Elija el modelo predeterminado (los ID sin prefijo funcionan en cualquier región; us./eu./ap. son específicos de región):",
"bedrock_setup_choose_region": " Elija la región de AWS:",
"bedrock_setup_header": "[Bedrock]",
"bedrock_setup_invalid_auth_selection": "invalid selection: %s (enter 1 or 2)",
"bedrock_setup_model_custom_prompt": "Enter model ID (e.g. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Enter a different model ID",
"bedrock_setup_model_prompt": "Enter model number or 0 to type your own",
"bedrock_setup_region_custom_prompt": "Enter custom AWS region (e.g. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Enter a different region",
"bedrock_setup_region_prompt": "Enter region number or 0 for custom",
"bedrock_setup_selected_model": " ✓ Selected model: %s",
"bedrock_setup_use_with": " Use with: fabric -m %s -V Bedrock",
"bedrock_setup_invalid_auth_selection": "selección inválida: %s (introduzca 1 o 2)",
"bedrock_setup_model_custom_prompt": "Introduzca el ID del modelo (p. ej. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Introducir un ID de modelo diferente",
"bedrock_setup_model_prompt": "Introduzca el número del modelo o 0 para escribir el suyo",
"bedrock_setup_region_custom_prompt": "Introduzca una región de AWS personalizada (p. ej. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Introducir una región diferente",
"bedrock_setup_region_prompt": "Introduzca el número de la región o 0 para personalizarla",
"bedrock_setup_selected_model": " ✓ Modelo seleccionado: %s",
"bedrock_setup_use_with": " Uso: fabric -m %s -V Bedrock",
"bedrock_unable_load_aws_config": "no se pudo cargar la configuración de AWS: %w",
"bedrock_unable_load_aws_config_with_region": "no se pudo cargar la configuración de AWS con región %s: %w",
"bedrock_unexpected_content_block_type": "tipo de bloque de contenido inesperado: %T",
@ -110,21 +110,43 @@
"choose_pattern_from_available": "Elige un patrón de los patrones disponibles",
"choose_session_from_available": "Elige una sesión de las sesiones disponibles",
"choose_strategy_from_available": "Elegir una estrategia de las estrategias disponibles",
"codex_api_base_url_question": "Ingrese la URL base de la API de Codex",
"codex_auth_base_url_invalid": "URL base de autenticación de Codex no válida: %w",
"codex_auth_base_url_question": "Ingrese la URL base de OAuth de Codex",
"codex_browser_open_fallback": "Si su navegador no se abrió, navegue a esta URL para autenticarse:",
"codex_decode_models_response_failed": "No se pudo decodificar la respuesta de modelos de Codex: %w",
"codex_decode_refresh_response_failed": "No se pudo decodificar la respuesta de token actualizado de Codex: %w",
"codex_decode_token_response_failed": "No se pudo decodificar la respuesta de intercambio de token de Codex: %w",
"codex_image_file_not_supported": "El proveedor Codex no admite --image-file. Use un archivo adjunto de imagen en su lugar.",
"codex_login_account_changed": "El inicio de sesión de Codex está vinculado a una cuenta de ChatGPT diferente a la configuración almacenada. Ejecute 'fabric --setup' de nuevo.",
"codex_login_completed": "Inicio de sesión de Codex completado",
"codex_login_failed": "Error en el inicio de sesión de Codex: %s",
"codex_login_invalid": "Su inicio de sesión de Codex ya no es válido. Ejecute 'fabric --setup' de nuevo.",
"codex_login_missing_account_claim": "El inicio de sesión de Codex no incluyó un ID de cuenta de ChatGPT. Este estado de inicio de sesión no es compatible.",
"codex_login_missing_auth_code": "El inicio de sesión de Codex no devolvió un código de autorización.",
"codex_login_missing_tokens": "El inicio de sesión de Codex no devolvió los tokens de acceso y actualización requeridos.",
"codex_login_refresh_failed": "No se pudo actualizar el inicio de sesión de Codex. Ejecute 'fabric --setup' de nuevo.",
"codex_login_return_to_fabric": "Volver a Fabric.",
"codex_login_revoked": "El inicio de sesión de Codex ha expirado o fue revocado. Ejecute 'fabric --setup' de nuevo.",
"codex_login_server_stopped": "El servidor de callback de inicio de sesión de Codex se detuvo antes de completar la autenticación.",
"codex_login_state_mismatch": "No se pudo verificar el inicio de sesión de Codex porque el estado OAuth no coincidió.",
"codex_login_timed_out": "El inicio de sesión de Codex agotó el tiempo de espera antes de completar la autenticación.",
"codex_oauth_missing_auth_code": "Código de autorización faltante",
"codex_oauth_random_state_failed": "No se pudo generar un estado OAuth aleatorio seguro: %w",
"codex_oauth_server_start_failed": "No se pudo iniciar el servidor local de callback OAuth: %w",
"codex_oauth_state_mismatch": "El estado no coincide",
"codex_provider_error": "error del proveedor de Codex (estado %d): %s",
"codex_refresh_failed_status": "No se pudo actualizar el inicio de sesión de Codex (estado %d)",
"codex_refresh_login_failed": "No se pudo actualizar el inicio de sesión de Codex: %w",
"codex_refresh_token_required": "Se requiere el token de actualización de Codex. Ejecute 'fabric --setup' de nuevo.",
"codex_replay_body_unavailable": "El cuerpo de la solicitud no se puede reproducir para el reintento de reautenticación de Codex",
"codex_request_failed": "La solicitud de Codex falló: %w",
"codex_request_failed_status": "La solicitud de Codex falló con estado %d",
"codex_starting_browser_login": "Iniciando inicio de sesión de OpenAI basado en navegador para Codex.",
"codex_token_exchange_failed": "El intercambio de token de Codex falló: %w",
"codex_token_persist_failed": "no se pudo guardar el inicio de sesión de Codex: %w",
"codex_token_refresh_missing_access_token": "La actualización del token de Codex no devolvió un token de acceso.",
"codex_usage_limit_reached": "Límite de uso de Codex alcanzado",
"command_completed_successfully": "Comando completado exitosamente",
"compression_level_jpeg_webp": "Nivel de compresión 0-100 para formatos JPEG/WebP (predeterminado: no establecido)",
"config_file_not_found": "archivo de configuración no encontrado: %s",
@ -151,6 +173,7 @@
"custom_patterns_setup_description": "Patrones personalizados - Establecer directorio para tus patrones personalizados",
"custom_patterns_warning_create_directory": "Advertencia: No se pudo crear el directorio de patrones personalizados %s: %v\n",
"db_error_loading_env_file": "error al cargar el archivo .env: %w",
"db_error_updating_env_file": "error al actualizar el archivo .env: %w",
"defaults_model_context_length_question": "Introduce la longitud del contexto del modelo",
"defaults_model_question": "Introduce el índice o el nombre de tu modelo predeterminado",
"defaults_setup_description": "Proveedor y modelo de IA predeterminados",
@ -161,7 +184,7 @@
"digitalocean_models_request_failed_with_status": "solicitud de modelos de DigitalOcean falló con estado %d: %s",
"disable_openai_responses_api": "Deshabilitar API de Respuestas de OpenAI (predeterminado: false)",
"disable_pattern_variable_replacement": "Deshabilitar reemplazo de variables de patrón",
"enable_web_search_tool": "Habilitar herramienta de búsqueda web para modelos soportados (Anthropic, OpenAI, Gemini)",
"enable_web_search_tool": "Habilitar herramienta de búsqueda web para modelos soportados (Anthropic, OpenAI, Gemini, Grok)",
"end_tag_thinking_sections": "Etiqueta de fin para secciones de pensamiento",
"error_creating_audio_file": "error al crear el archivo de audio: %v",
"error_creating_file": "error al crear el archivo: %v",
@ -352,6 +375,7 @@
"ollama_invalid_data_url_format": "formato de URL de datos inválido",
"ollama_invalid_http_timeout_using_default": "Tiempo de espera HTTP inválido '%s': %v, usando el valor predeterminado",
"ollama_invalid_num_ctx_in_request": "num_ctx inválido en la solicitud: %v",
"ollama_invalid_request_body": "cuerpo de solicitud no válido",
"ollama_no_content_from_upstream": "no se recibió contenido del servidor Fabric upstream",
"ollama_num_ctx_exceeds_maximum": "num_ctx excede el valor máximo permitido de %d",
"ollama_num_ctx_invalid_type": "num_ctx debe ser un número, se obtuvo tipo inválido",
@ -365,6 +389,7 @@
"ollama_sse_buffer_limit": "Línea SSE excede el límite de búfer de 1MB - línea de datos demasiado grande",
"ollama_upstream_non_2xx": "El servidor Fabric upstream devolvió estado no-2xx %d: %s",
"ollama_upstream_non_2xx_body_unreadable": "El servidor Fabric upstream devolvió estado no-2xx %d y el cuerpo no se pudo leer: %v",
"ollama_upstream_request_failed": "no se pudo conectar con el servidor Fabric ascendente",
"ollama_upstream_returned_status": "el servidor Fabric upstream devolvió estado %d",
"ollama_warning_no_content": "Advertencia: no se recibió contenido del servidor Fabric upstream",
"ollama_warning_parse_variables": "Advertencia: error al analizar options.variables como JSON: %v",
@ -385,6 +410,7 @@
"openai_image_failed_to_save_image": "no se pudo guardar la imagen en %s: %w",
"openai_image_saved_to": "Imagen guardada en: %s",
"openai_model_no_image_generation": "el modelo '%s' no soporta generación de imágenes. Modelos soportados: %s",
"openai_models_rate_limited": "límite de velocidad excedido al obtener modelos del proveedor %s; reintentar después de %s segundos",
"openai_models_response_too_large": "respuesta de modelos demasiado grande del proveedor %s (>%d bytes)",
"openai_unable_to_parse_models_response": "no se pudo analizar la respuesta de modelos; respuesta cruda: %s",
"openai_unexpected_status_code_read_error": "código de estado inesperado: %d del proveedor %s (error al leer cuerpo de respuesta: %v)",
@ -400,6 +426,7 @@
"output_video_metadata": "Salida de metadatos del video",
"path_to_yaml_config": "Ruta al archivo de configuración YAML",
"pattern_not_found_list_available": "patrón '%s' no encontrado. Ejecuta 'fabric -l' para ver los patrones disponibles",
"pattern_invalid_name": "nombre de patrón inválido: %q",
"pattern_not_found_no_patterns": "patrón '%s' no encontrado.\n\n¡No hay patrones instalados! Para solucionar esto:\n • Ejecuta 'fabric --setup' para configurar y descargar patrones\n • O ejecuta 'fabric -U' para descargar/actualizar patrones directamente",
"pattern_variables_help": "Valores para variables de patrón, ej. -v=#role:expert -v=#points:30",
"patterns_cloning_repository": "Clonando el repositorio %s (ruta: %s)...\\n",
@ -473,6 +500,8 @@
"prefer_playlist_over_video": "Preferir lista de reproducción sobre video si ambos ids están presentes en la URL",
"print_context": "Imprimir contexto",
"print_current_version": "Imprimir versión actual",
"print_metadata_to_stderr": "Imprimir metadatos (tokens de entrada/salida) en stderr",
"print_pattern_contents": "Imprimir el contenido del patrón indicado en la terminal",
"print_session": "Imprimir sesión",
"register_new_extension": "Registrar una nueva extensión desde la ruta del archivo de configuración",
"remove_registered_extension": "Eliminar una extensión registrada por nombre",
@ -486,10 +515,12 @@
"send_desktop_notification": "Enviar notificación de escritorio cuando se complete el comando",
"serve_fabric_api_ollama_endpoints": "Servir la API REST de Fabric con endpoints de ollama",
"serve_fabric_rest_api": "Servir la API REST de Fabric",
"server_api_key_required": "se rechaza servir en la dirección no loopback %s sin clave de API: configure --api-key o FABRIC_API_KEY, o use una dirección loopback como 127.0.0.1:8080",
"server_chat_error": "Error: %v",
"server_error_marshaling_response": "error al serializar la respuesta: %v",
"server_error_writing_response": "error al escribir la respuesta: %v",
"server_invalid_request_format": "formato de solicitud no válido: %v",
"server_no_api_key_warning": "Iniciando el servidor de API REST sin autenticación por clave de API. Esto puede suponer riesgos de seguridad.",
"sessions_creating_new": "Creando nueva sesión: %s\n",
"set_debug_level": "Establecer nivel de depuración (0=apagado, 1=básico, 2=detallado, 3=rastreo, 4=wire)",
"set_frequency_penalty": "Establecer penalización de frecuencia",
@ -581,6 +612,7 @@
"spotify_show_name_label": "**Programa**: %s",
"spotify_title_label": "**Título**: %s",
"spotify_total_episodes_label": "**Total de episodios**: %d",
"spotify_url_help": "URL de podcast o episodio de Spotify para obtener metadatos y enviarlos al chat",
"spotify_url_label": "**URL**: %s",
"start_tag_thinking_sections": "Etiqueta de inicio para secciones de pensamiento",
"storage_error_delete": "No se pudo eliminar %s: %v",
@ -592,6 +624,7 @@
"storage_error_save": "No se pudo guardar %s: %v",
"storage_error_stat_entry": "No se pudo obtener información de la entrada %s: %v",
"storage_error_unmarshal": "No se pudo deserializar %s: %s",
"storage_invalid_name": "nombre inválido: %q",
"strategies_available_header": "Estrategias disponibles:",
"strategies_cloning_repository": "Clonando el repositorio %s (ruta: %s)...\\n",
"strategies_download_success": "✅ Estrategias descargadas e instaladas correctamente en %s\\n",
@ -610,11 +643,11 @@
"strategy_path_traversal": "el nombre de estrategia %q se resuelve fuera del directorio de estrategias",
"stream_help": "Transmitir",
"suppress_thinking_tags": "Suprimir texto encerrado en etiquetas de pensamiento",
"template_datetime_error_invalid_number": "invalid number in relative time: %q",
"template_datetime_error_invalid_relative_format": "invalid relative time format",
"template_datetime_error_invalid_unit": "invalid time unit: %q",
"template_datetime_error_relative_requires_value": "relative time requires a value",
"template_datetime_error_unknown_operation": "datetime: unknown operation %q",
"template_datetime_error_invalid_number": "número inválido en el tiempo relativo: %q",
"template_datetime_error_invalid_relative_format": "formato de tiempo relativo inválido",
"template_datetime_error_invalid_unit": "unidad de tiempo inválida: %q",
"template_datetime_error_relative_requires_value": "el tiempo relativo requiere un valor",
"template_datetime_error_unknown_operation": "datetime: operación desconocida %q",
"template_extension_error": "Error en extensión %s: %v",
"template_file_error_expand_home_dir": "archivo: no se pudo expandir el directorio home: %v",
"template_file_error_invalid_line_count": "archivo: número de líneas no válido %q",
@ -643,12 +676,12 @@
"template_missing_required_variable": "Variable requerida faltante: %s",
"template_plugin_error": "Error en plugin %s: %v",
"template_processing_stuck": "Procesamiento de plantilla bloqueado - posible bucle infinito",
"template_sys_error_env_requires_var": "env: variable name required",
"template_sys_error_home": "failed to get home directory: %v",
"template_sys_error_hostname": "failed to get hostname: %v",
"template_sys_error_pwd": "failed to get working directory: %v",
"template_sys_error_unknown_operation": "sys: unknown operation %q",
"template_sys_error_user": "failed to get current user: %v",
"template_sys_error_env_requires_var": "env: se requiere el nombre de la variable",
"template_sys_error_home": "no se pudo obtener el directorio de inicio: %v",
"template_sys_error_hostname": "no se pudo obtener el nombre del host: %v",
"template_sys_error_pwd": "no se pudo obtener el directorio de trabajo: %v",
"template_sys_error_unknown_operation": "sys: operación desconocida %q",
"template_sys_error_user": "no se pudo obtener el usuario actual: %v",
"template_text_empty_input": "Texto: entrada vacía para la operación %q",
"template_text_unknown_operation": "Texto: operación de texto desconocida %q (soportadas: upper, lower, title, trim)",
"template_unknown_plugin_namespace": "Espacio de nombres de plugin desconocido: %s",
@ -698,15 +731,21 @@
"youtube_error_getting_video_details": "error al obtener detalles del video: %v",
"youtube_error_parsing_duration": "error al analizar la duración del video: %v",
"youtube_error_saving_csv": "error al guardar videos en CSV: %v",
"youtube_extract_visual_data_help": "Extraer datos visuales del video usando OCR y FFmpeg",
"youtube_failed_create_temp_dir": "falló al crear directorio temporal: %v",
"youtube_failed_fetch_comments": "No se pudieron obtener los comentarios: %v",
"youtube_failed_get_stream_url": "no se pudo obtener la URL del flujo mediante yt-dlp: %v",
"youtube_failed_parse_http_stream_url": "no se pudo analizar una URL HTTP de flujo válida desde la salida de yt-dlp",
"youtube_failed_walk_directory": "falló al recorrer el directorio: %v",
"youtube_ffmpeg_frame_extraction_failed": "la extracción de fotogramas con ffmpeg falló: %v, salida: %s",
"youtube_ffmpeg_required_visual_extraction": "ffmpeg es requerido para la extracción visual pero no se encontró en PATH",
"youtube_invalid_duration_string": "cadena de duración inválida: %s",
"youtube_invalid_seconds_format": "formato de segundos inválido %q: %w",
"youtube_invalid_timestamp_format": "formato de marca de tiempo inválido: %s",
"youtube_invalid_url": "URL de YouTube no válida, no se puede obtener ID de video o lista de reproducción: '%s'",
"youtube_invalid_ytdlp_arguments": "argumentos de yt-dlp inválidos: %v",
"youtube_label": "YouTube",
"youtube_no_clear_text_visual_frames": "no se encontró texto legible en los fotogramas visuales del video",
"youtube_no_transcript_content": "no se encontró contenido de transcripción en el archivo VTT",
"youtube_no_url_provided": "No se proporcionó una URL de YouTube",
"youtube_no_video_found_with_id": "no se encontró video con ID: %s",
@ -717,9 +756,15 @@
"youtube_playlist_saved_to": "Lista de reproducción guardada en %s",
"youtube_rate_limit_exceeded": "Límite de tasa de YouTube excedido. Intenta de nuevo más tarde o usa diferentes argumentos de yt-dlp como '--sleep-requests 1' para ralentizar las solicitudes.",
"youtube_setup_description": "YouTube - para obtener transcripciones de video (vía yt-dlp) y comentarios/metadatos (vía API de YouTube)",
"youtube_tesseract_frame_failed": "tesseract falló en el fotograma %d: %v, stderr: %s",
"youtube_tesseract_required_visual_extraction": "tesseract es requerido para la extracción visual pero no se encontró en PATH",
"youtube_url_help": "Video de YouTube o \"URL\" de lista de reproducción para obtener transcripción, comentarios y enviar al chat o imprimir en la consola y almacenar en el archivo de salida",
"youtube_url_is_playlist_not_video": "la URL es una lista de reproducción, no un video",
"youtube_video_id_title_header": "VideoID: Título",
"youtube_visual_fps_help": "Extraer un número específico de fotogramas por segundo en lugar de usar detección de escenas",
"youtube_visual_frame_cue": "[MARCADOR DE FOTOGRAMA VISUAL]",
"youtube_visual_sensitivity_help": "Tolerancia para la detección de escenas de FFmpeg (0.0 - 1.0)",
"youtube_ytdlp_not_found": "yt-dlp no encontrado en PATH. Por favor instala yt-dlp para usar la funcionalidad de transcripción de YouTube",
"youtube_ytdlp_required_visual_extraction": "yt-dlp es requerido para la extracción visual pero no se encontró en PATH",
"youtube_ytdlp_stderr_error": "error al leer stderr de yt-dlp"
}

View file

@ -54,7 +54,7 @@
"bedrock_aws_access_key_label": "AWS Access Key ID خود را وارد کنید (برای استفاده از زنجیره اعتبارنامه AWS خالی بگذارید)",
"bedrock_aws_region_label": "منطقه AWS",
"bedrock_aws_secret_key_label": "AWS Secret Access Key خود را وارد کنید (برای استفاده از زنجیره اعتبارنامه AWS خالی بگذارید)",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"bedrock_client_not_initialized": "کلاینت Bedrock مقداردهی نشده است — 'fabric --setup' را برای پیکربندی اجرا کنید",
"bedrock_converse_failed": "bedrock converse برای مدل %s ناموفق بود: %w",
"bedrock_conversestream_failed": "bedrock conversestream برای مدل %s ناموفق بود: %w",
"bedrock_empty_response_content": "محتوای پاسخ خالی",
@ -67,21 +67,21 @@
"bedrock_listmodels_fallback": "API ListModels Bedrock ناموفق بود، استفاده از لیست پشتیبان ثابت",
"bedrock_panic_sendstream": "پنیک در SendStream: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",
"bedrock_setup_auth_prompt": "Enter 1 or 2",
"bedrock_setup_choose_auth_method": " Choose authentication method:",
"bedrock_setup_choose_model": " Choose default model (unprefixed IDs work in any region; us./eu./ap. are region-specific):",
"bedrock_setup_choose_region": " Choose AWS Region:",
"bedrock_setup_auth_option_apikey": " [1] کلید API Bedrock / توکن ABSK (توصیه‌شده — همان کلید Claude Code)",
"bedrock_setup_auth_prompt": "1 یا 2 را وارد کنید",
"bedrock_setup_choose_auth_method": " روش احراز هویت را انتخاب کنید:",
"bedrock_setup_choose_model": " مدل پیش‌فرض را انتخاب کنید (شناسه‌های بدون پیشوند در هر منطقه‌ای کار می‌کنند؛ us./eu./ap. مخصوص منطقه هستند):",
"bedrock_setup_choose_region": " منطقه AWS را انتخاب کنید:",
"bedrock_setup_header": "[Bedrock]",
"bedrock_setup_invalid_auth_selection": "invalid selection: %s (enter 1 or 2)",
"bedrock_setup_model_custom_prompt": "Enter model ID (e.g. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Enter a different model ID",
"bedrock_setup_model_prompt": "Enter model number or 0 to type your own",
"bedrock_setup_region_custom_prompt": "Enter custom AWS region (e.g. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Enter a different region",
"bedrock_setup_region_prompt": "Enter region number or 0 for custom",
"bedrock_setup_selected_model": " ✓ Selected model: %s",
"bedrock_setup_use_with": " Use with: fabric -m %s -V Bedrock",
"bedrock_setup_invalid_auth_selection": "انتخاب نامعتبر: %s (1 یا 2 را وارد کنید)",
"bedrock_setup_model_custom_prompt": "شناسه مدل را وارد کنید (مثلاً anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] وارد کردن شناسه مدل دیگر",
"bedrock_setup_model_prompt": "شماره مدل را وارد کنید یا برای وارد کردن شناسه دلخواه 0 را بزنید",
"bedrock_setup_region_custom_prompt": "منطقه AWS دلخواه را وارد کنید (مثلاً us-east-1)",
"bedrock_setup_region_option_custom": " [0] وارد کردن منطقه دیگر",
"bedrock_setup_region_prompt": "شماره منطقه را وارد کنید یا برای منطقه دلخواه 0 را بزنید",
"bedrock_setup_selected_model": " ✓ مدل انتخاب‌شده: %s",
"bedrock_setup_use_with": " نحوه استفاده: fabric -m %s -V Bedrock",
"bedrock_unable_load_aws_config": "بارگذاری پیکربندی AWS ناموفق بود: %w",
"bedrock_unable_load_aws_config_with_region": "بارگذاری پیکربندی AWS با منطقه %s ناموفق بود: %w",
"bedrock_unexpected_content_block_type": "نوع بلوک محتوای غیرمنتظره: %T",
@ -110,21 +110,43 @@
"choose_pattern_from_available": "الگویی از الگوهای موجود انتخاب کنید",
"choose_session_from_available": "جلسه‌ای از جلسات موجود انتخاب کنید",
"choose_strategy_from_available": "انتخاب استراتژی از استراتژی‌های موجود",
"codex_api_base_url_question": "آدرس پایه API Codex را وارد کنید",
"codex_auth_base_url_invalid": "آدرس پایه احراز هویت Codex نامعتبر است: %w",
"codex_auth_base_url_question": "آدرس پایه OAuth Codex را وارد کنید",
"codex_browser_open_fallback": "اگر مرورگر شما باز نشد، برای احراز هویت به این آدرس بروید:",
"codex_decode_models_response_failed": "رمزگشایی پاسخ مدل‌های Codex ناموفق بود: %w",
"codex_decode_refresh_response_failed": "رمزگشایی پاسخ توکن بازنشانی‌شده Codex ناموفق بود: %w",
"codex_decode_token_response_failed": "رمزگشایی پاسخ تبادل توکن Codex ناموفق بود: %w",
"codex_image_file_not_supported": "ارائه‌دهنده Codex از --image-file پشتیبانی نمی‌کند. به جای آن از پیوست تصویر استفاده کنید.",
"codex_login_account_changed": "ورود Codex به حساب ChatGPT متفاوتی از پیکربندی ذخیره‌شده متصل است. لطفاً 'fabric --setup' را دوباره اجرا کنید.",
"codex_login_completed": "ورود Codex تکمیل شد",
"codex_login_failed": "ورود Codex ناموفق بود: %s",
"codex_login_invalid": "ورود Codex شما دیگر معتبر نیست. لطفاً 'fabric --setup' را دوباره اجرا کنید.",
"codex_login_missing_account_claim": "ورود Codex شامل شناسه حساب ChatGPT نبود. این وضعیت ورود پشتیبانی نمی‌شود.",
"codex_login_missing_auth_code": "ورود Codex کد مجوز را برنگرداند.",
"codex_login_missing_tokens": "ورود Codex توکن‌های دسترسی و بازنشانی مورد نیاز را برنگرداند.",
"codex_login_refresh_failed": "بازنشانی ورود Codex امکان‌پذیر نبود. لطفاً 'fabric --setup' را دوباره اجرا کنید.",
"codex_login_return_to_fabric": "بازگشت به Fabric.",
"codex_login_revoked": "ورود Codex منقضی شده یا لغو شده است. لطفاً 'fabric --setup' را دوباره اجرا کنید.",
"codex_login_server_stopped": "سرور بازگشت ورود Codex قبل از تکمیل احراز هویت متوقف شد.",
"codex_login_state_mismatch": "ورود Codex قابل تأیید نبود زیرا وضعیت OAuth مطابقت نداشت.",
"codex_login_timed_out": "زمان ورود Codex قبل از تکمیل احراز هویت به پایان رسید.",
"codex_oauth_missing_auth_code": "کد مجوز موجود نیست",
"codex_oauth_random_state_failed": "تولید وضعیت تصادفی امن OAuth ناموفق بود: %w",
"codex_oauth_server_start_failed": "راه‌اندازی سرور محلی بازگشت OAuth ناموفق بود: %w",
"codex_oauth_state_mismatch": "وضعیت مطابقت ندارد",
"codex_provider_error": "خطای ارائه‌دهنده Codex (وضعیت %d): %s",
"codex_refresh_failed_status": "بازنشانی ورود Codex ناموفق بود (وضعیت %d)",
"codex_refresh_login_failed": "بازنشانی ورود Codex ناموفق بود: %w",
"codex_refresh_token_required": "توکن بازنشانی Codex مورد نیاز است. لطفاً 'fabric --setup' را دوباره اجرا کنید.",
"codex_replay_body_unavailable": "بدنه درخواست برای تلاش مجدد احراز هویت Codex قابل بازپخش نیست",
"codex_request_failed": "درخواست Codex ناموفق بود: %w",
"codex_request_failed_status": "درخواست Codex با وضعیت %d ناموفق بود",
"codex_starting_browser_login": "شروع ورود مبتنی بر مرورگر OpenAI برای Codex.",
"codex_token_exchange_failed": "تبادل توکن Codex ناموفق بود: %w",
"codex_token_persist_failed": "ذخیره ورود Codex ناموفق بود: %w",
"codex_token_refresh_missing_access_token": "بازنشانی توکن Codex توکن دسترسی را برنگرداند.",
"codex_usage_limit_reached": "محدودیت استفاده Codex به حداکثر رسیده است",
"command_completed_successfully": "دستور با موفقیت تکمیل شد",
"compression_level_jpeg_webp": "سطح فشرده‌سازی 0-100 برای فرمت‌های JPEG/WebP (پیش‌فرض: تنظیم نشده)",
"config_file_not_found": "فایل پیکربندی یافت نشد: %s",
@ -151,6 +173,7 @@
"custom_patterns_setup_description": "الگوهای سفارشی - تنظیم دایرکتوری برای الگوهای سفارشی شما",
"custom_patterns_warning_create_directory": "هشدار: امکان ایجاد پوشه الگوهای سفارشی %s وجود ندارد: %v\n",
"db_error_loading_env_file": "خطا در بارگذاری فایل .env: %w",
"db_error_updating_env_file": "خطا در به‌روزرسانی فایل .env: %w",
"defaults_model_context_length_question": "طول زمینه مدل را وارد کنید",
"defaults_model_question": "شاخص یا نام مدل پیش‌فرض خود را وارد کنید",
"defaults_setup_description": "ارائه‌دهنده و مدل هوش مصنوعی پیش‌فرض",
@ -161,7 +184,7 @@
"digitalocean_models_request_failed_with_status": "درخواست مدل‌های DigitalOcean با وضعیت %d ناموفق بود: %s",
"disable_openai_responses_api": "غیرفعال کردن API OpenAI Responses (پیش‌فرض: false)",
"disable_pattern_variable_replacement": "غیرفعال کردن جایگزینی متغیرهای الگو",
"enable_web_search_tool": "فعال‌سازی ابزار جستجوی وب برای مدل‌های پشتیبانی شده (Anthropic، OpenAI، Gemini)",
"enable_web_search_tool": "فعال‌سازی ابزار جستجوی وب برای مدل‌های پشتیبانی شده (Anthropic، OpenAI، Gemini، Grok)",
"end_tag_thinking_sections": "تگ پایان برای بخش‌های تفکر",
"error_creating_audio_file": "خطا در ایجاد فایل صوتی: %v",
"error_creating_file": "خطا در ایجاد فایل: %v",
@ -352,6 +375,7 @@
"ollama_invalid_data_url_format": "فرمت URL داده نامعتبر",
"ollama_invalid_http_timeout_using_default": "زمان انتظار HTTP نامعتبر '%s': %v، استفاده از مقدار پیش‌فرض",
"ollama_invalid_num_ctx_in_request": "num_ctx نامعتبر در درخواست: %v",
"ollama_invalid_request_body": "بدنه درخواست نامعتبر",
"ollama_no_content_from_upstream": "هیچ محتوایی از سرور Fabric بالادستی دریافت نشد",
"ollama_num_ctx_exceeds_maximum": "num_ctx از حداکثر مقدار مجاز %d فراتر رفته است",
"ollama_num_ctx_invalid_type": "num_ctx باید یک عدد باشد، نوع نامعتبر دریافت شد",
@ -365,6 +389,7 @@
"ollama_sse_buffer_limit": "خط SSE از حد بافر 1MB فراتر رفت - خط داده بیش از حد بزرگ است",
"ollama_upstream_non_2xx": "سرور Fabric بالادستی وضعیت غیر-2xx %d را برگرداند: %s",
"ollama_upstream_non_2xx_body_unreadable": "سرور Fabric بالادستی وضعیت غیر-2xx %d را برگرداند و بدنه قابل خواندن نبود: %v",
"ollama_upstream_request_failed": "دسترسی به سرور بالادستی Fabric ممکن نیست",
"ollama_upstream_returned_status": "سرور Fabric بالادستی وضعیت %d را برگرداند",
"ollama_warning_no_content": "هشدار: هیچ محتوایی از سرور Fabric بالادستی دریافت نشد",
"ollama_warning_parse_variables": "هشدار: شکست در تجزیه options.variables به عنوان JSON: %v",
@ -385,6 +410,7 @@
"openai_image_failed_to_save_image": "ذخیره تصویر در %s ناموفق بود: %w",
"openai_image_saved_to": "تصویر ذخیره شد در: %s",
"openai_model_no_image_generation": "مدل '%s' از تولید تصویر پشتیبانی نمی‌کند. مدل‌های پشتیبانی شده: %s",
"openai_models_rate_limited": "محدودیت نرخ هنگام دریافت مدل‌ها از ارائه‌دهنده %s فراتر رفت؛ پس از %s ثانیه دوباره تلاش کنید",
"openai_models_response_too_large": "پاسخ مدل‌ها از ارائه‌دهنده %s بیش از حد بزرگ است (>%d بایت)",
"openai_unable_to_parse_models_response": "تجزیه پاسخ مدل‌ها ناموفق بود; پاسخ خام: %s",
"openai_unexpected_status_code_read_error": "کد وضعیت غیرمنتظره: %d از ارائه‌دهنده %s (خطا در خواندن پاسخ: %v)",
@ -400,6 +426,7 @@
"output_video_metadata": "نمایش فراداده ویدیو",
"path_to_yaml_config": "مسیر فایل پیکربندی YAML",
"pattern_not_found_list_available": "الگوی '%s' یافت نشد. برای مشاهده الگوهای موجود 'fabric -l' را اجرا کنید",
"pattern_invalid_name": "نام الگوی نامعتبر: %q",
"pattern_not_found_no_patterns": "الگوی '%s' یافت نشد.\n\nهیچ الگویی نصب نشده است! برای رفع این مشکل:\n • 'fabric --setup' را برای پیکربندی و دانلود الگوها اجرا کنید\n • یا 'fabric -U' را برای دانلود/به‌روزرسانی الگوها اجرا کنید",
"pattern_variables_help": "مقادیر برای متغیرهای الگو، مثال: -v=#role:expert -v=#points:30",
"patterns_cloning_repository": "در حال کلون کردن مخزن %s (مسیر: %s)...\\n",
@ -473,6 +500,8 @@
"prefer_playlist_over_video": "اولویت فهرست پخش نسبت به ویدیو اگر هر دو ID در URL موجود باشند",
"print_context": "چاپ زمینه",
"print_current_version": "چاپ نسخه فعلی",
"print_metadata_to_stderr": "چاپ فراداده (توکن‌های ورودی/خروجی) در stderr",
"print_pattern_contents": "چاپ محتوای الگوی مشخص‌شده در ترمینال",
"print_session": "چاپ جلسه",
"register_new_extension": "ثبت افزونه جدید از مسیر فایل پیکربندی",
"remove_registered_extension": "حذف افزونه ثبت شده با نام",
@ -486,10 +515,12 @@
"send_desktop_notification": "ارسال اعلان دسک‌تاپ هنگام تکمیل دستور",
"serve_fabric_api_ollama_endpoints": "سرویس API REST Fabric با نقاط پایانی ollama",
"serve_fabric_rest_api": "سرویس API REST Fabric",
"server_api_key_required": "سرویس‌دهی روی آدرس غیر loopback %s بدون کلید API رد شد: --api-key یا FABRIC_API_KEY را تنظیم کنید، یا به یک آدرس loopback مانند 127.0.0.1:8080 متصل شوید",
"server_chat_error": "خطا: %v",
"server_error_marshaling_response": "خطا در سریال‌سازی پاسخ: %v",
"server_error_writing_response": "خطا در نوشتن پاسخ: %v",
"server_invalid_request_format": "فرمت درخواست نامعتبر: %v",
"server_no_api_key_warning": "سرور REST API بدون احراز هویت کلید API راه‌اندازی می‌شود. این ممکن است خطرات امنیتی ایجاد کند.",
"sessions_creating_new": "ایجاد نشست جدید: %s\n",
"set_debug_level": "تنظیم سطح اشکال‌زدایی (0=خاموش، 1=پایه، 2=تفصیلی، 3=ردیابی، 4=wire)",
"set_frequency_penalty": "تنظیم جریمه فرکانس",
@ -546,7 +577,7 @@
"spotify_description_header": "## توضیحات",
"spotify_duration_label": "**مدت زمان**: %d دقیقه",
"spotify_episode_header": "# اپیزود اسپاتیفای",
"spotify_error_getting_metadata": "error getting Spotify metadata: %v",
"spotify_error_getting_metadata": "خطا در دریافت فراداده Spotify: %v",
"spotify_explicit_label": "**محتوای صریح**: %v",
"spotify_failed_create_request": "ایجاد درخواست ناموفق بود: %w",
"spotify_failed_create_token_request": "ایجاد درخواست توکن ناموفق بود: %w",
@ -560,14 +591,14 @@
"spotify_failed_parse_show_metadata": "تجزیه فراداده برنامه ناموفق بود: %w",
"spotify_failed_read_response_body": "خواندن متن پاسخ ناموفق بود: %w",
"spotify_failed_request_access_token": "درخواست توکن دسترسی ناموفق بود: %w",
"spotify_invalid_url": "invalid Spotify URL, can't get show or episode ID: '%s'",
"spotify_invalid_url": "نشانی Spotify نامعتبر است، دریافت شناسه برنامه یا قسمت ممکن نیست: '%s'",
"spotify_label": "Spotify",
"spotify_language_field_label": "**زبان**: %s",
"spotify_languages_label": "**زبان‌ها**: %s",
"spotify_media_type_label": "**نوع رسانه**: %s",
"spotify_no_episode_found": "no episode found with ID: %s",
"spotify_no_show_found": "no show found with ID: %s",
"spotify_not_configured": "Spotify is not configured, please run the setup procedure",
"spotify_no_episode_found": "هیچ قسمتی با این شناسه یافت نشد: %s",
"spotify_no_show_found": "هیچ برنامه‌ای با این شناسه یافت نشد: %s",
"spotify_not_configured": "Spotify پیکربندی نشده است، لطفاً روند راه‌اندازی را اجرا کنید",
"spotify_publisher_label": "**ناشر**: %s",
"spotify_release_date_label": "**تاریخ انتشار**: %s",
"spotify_search_description_label": "- **توضیحات**: %s",
@ -576,11 +607,12 @@
"spotify_search_publisher_label": "- **ناشر**: %s",
"spotify_search_results_header": "# نتایج جستجوی اسپاتیفای",
"spotify_search_url_label": "- **URL**: %s",
"spotify_setup_description": "Spotify - to grab podcast/show metadata from Spotify",
"spotify_setup_description": "Spotify - برای دریافت فراداده پادکست/برنامه از Spotify",
"spotify_show_header": "# پادکست/برنامه اسپاتیفای",
"spotify_show_name_label": "**برنامه**: %s",
"spotify_title_label": "**عنوان**: %s",
"spotify_total_episodes_label": "**مجموع اپیزودها**: %d",
"spotify_url_help": "نشانی پادکست یا قسمت Spotify برای دریافت فراداده و ارسال به گفتگو",
"spotify_url_label": "**URL**: %s",
"start_tag_thinking_sections": "تگ شروع برای بخش‌های تفکر",
"storage_error_delete": "حذف %s ناموفق بود: %v",
@ -592,6 +624,7 @@
"storage_error_save": "ذخیره %s ناموفق بود: %v",
"storage_error_stat_entry": "دریافت اطلاعات ورودی %s ناموفق بود: %v",
"storage_error_unmarshal": "بازسریال‌سازی %s ناموفق بود: %s",
"storage_invalid_name": "نام نامعتبر: %q",
"strategies_available_header": "راهبردهای موجود:",
"strategies_cloning_repository": "در حال کلون کردن مخزن %s (مسیر: %s)...\\n",
"strategies_download_success": "✅ راهبردها با موفقیت در %s دانلود و نصب شدند\\n",
@ -610,11 +643,11 @@
"strategy_path_traversal": "نام راهبرد %q خارج از دایرکتوری راهبردها حل می‌شود",
"stream_help": "پخش زنده",
"suppress_thinking_tags": "سرکوب متن محصور در تگ‌های تفکر",
"template_datetime_error_invalid_number": "invalid number in relative time: %q",
"template_datetime_error_invalid_relative_format": "invalid relative time format",
"template_datetime_error_invalid_unit": "invalid time unit: %q",
"template_datetime_error_relative_requires_value": "relative time requires a value",
"template_datetime_error_unknown_operation": "datetime: unknown operation %q",
"template_datetime_error_invalid_number": "عدد نامعتبر در زمان نسبی: %q",
"template_datetime_error_invalid_relative_format": "قالب زمان نسبی نامعتبر است",
"template_datetime_error_invalid_unit": "واحد زمانی نامعتبر: %q",
"template_datetime_error_relative_requires_value": "زمان نسبی به یک مقدار نیاز دارد",
"template_datetime_error_unknown_operation": "datetime: عملیات ناشناخته %q",
"template_extension_error": "خطای افزونه %s: %v",
"template_file_error_expand_home_dir": "فایل: گسترش پوشه خانگی ممکن نشد: %v",
"template_file_error_invalid_line_count": "فایل: تعداد خط نامعتبر %q",
@ -643,12 +676,12 @@
"template_missing_required_variable": "متغیر الزامی موجود نیست: %s",
"template_plugin_error": "خطای پلاگین %s: %v",
"template_processing_stuck": "پردازش قالب متوقف شده - احتمال حلقه بی‌نهایت",
"template_sys_error_env_requires_var": "env: variable name required",
"template_sys_error_home": "failed to get home directory: %v",
"template_sys_error_hostname": "failed to get hostname: %v",
"template_sys_error_pwd": "failed to get working directory: %v",
"template_sys_error_unknown_operation": "sys: unknown operation %q",
"template_sys_error_user": "failed to get current user: %v",
"template_sys_error_env_requires_var": "env: نام متغیر لازم است",
"template_sys_error_home": "دریافت پوشه خانگی ناموفق بود: %v",
"template_sys_error_hostname": "دریافت نام میزبان ناموفق بود: %v",
"template_sys_error_pwd": "دریافت پوشه کاری ناموفق بود: %v",
"template_sys_error_unknown_operation": "sys: عملیات ناشناخته %q",
"template_sys_error_user": "دریافت کاربر فعلی ناموفق بود: %v",
"template_text_empty_input": "متن: ورودی خالی برای عملیات %q",
"template_text_unknown_operation": "متن: عملیات متنی ناشناخته %q (پشتیبانی شده: upper, lower, title, trim)",
"template_unknown_plugin_namespace": "فضای نام پلاگین ناشناخته: %s",
@ -698,15 +731,21 @@
"youtube_error_getting_video_details": "خطا در دریافت جزئیات ویدیو: %v",
"youtube_error_parsing_duration": "خطا در تجزیه مدت زمان ویدیو: %v",
"youtube_error_saving_csv": "خطا در ذخیره ویدیوها در CSV: %v",
"youtube_extract_visual_data_help": "استخراج داده‌های بصری از ویدیو با استفاده از OCR و FFmpeg",
"youtube_failed_create_temp_dir": "ایجاد دایرکتوری موقت ناموفق بود: %v",
"youtube_failed_fetch_comments": "دریافت نظرات ناموفق بود: %v",
"youtube_failed_get_stream_url": "دریافت URL جریان از طریق yt-dlp ناموفق بود: %v",
"youtube_failed_parse_http_stream_url": "تجزیه یک URL معتبر HTTP برای جریان از خروجی yt-dlp ناموفق بود",
"youtube_failed_walk_directory": "پیمایش دایرکتوری ناموفق بود: %v",
"youtube_ffmpeg_frame_extraction_failed": "استخراج فریم با ffmpeg ناموفق بود: %v، خروجی: %s",
"youtube_ffmpeg_required_visual_extraction": "برای استخراج بصری به ffmpeg نیاز است اما در PATH پیدا نشد",
"youtube_invalid_duration_string": "رشته مدت زمان نامعتبر: %s",
"youtube_invalid_seconds_format": "فرمت ثانیه نامعتبر %q: %w",
"youtube_invalid_timestamp_format": "فرمت مهر زمانی نامعتبر: %s",
"youtube_invalid_url": "URL یوتیوب نامعتبر است، نمی‌توان ID ویدیو یا فهرست پخش را دریافت کرد: '%s'",
"youtube_invalid_ytdlp_arguments": "آرگومان‌های yt-dlp نامعتبر: %v",
"youtube_label": "YouTube",
"youtube_no_clear_text_visual_frames": "متن واضحی در فریم‌های بصری ویدیو پیدا نشد",
"youtube_no_transcript_content": "محتوای رونوشتی در فایل VTT یافت نشد",
"youtube_no_url_provided": "هیچ URL یوتیوبی ارائه نشده است",
"youtube_no_video_found_with_id": "هیچ ویدیویی با ID یافت نشد: %s",
@ -717,9 +756,15 @@
"youtube_playlist_saved_to": "لیست پخش ذخیره شد در %s",
"youtube_rate_limit_exceeded": "محدودیت نرخ یوتیوب فراتر رفته است. بعداً دوباره امتحان کنید یا از آرگومان‌های مختلف yt-dlp مانند '--sleep-requests 1' برای کاهش سرعت درخواست‌ها استفاده کنید.",
"youtube_setup_description": "YouTube - برای دریافت رونوشت ویدیو (از طریق yt-dlp) و نظرات/متادیتا (از طریق API یوتیوب)",
"youtube_tesseract_frame_failed": "tesseract روی فریم %d شکست خورد: %v، stderr: %s",
"youtube_tesseract_required_visual_extraction": "برای استخراج بصری به tesseract نیاز است اما در PATH پیدا نشد",
"youtube_url_help": "ویدیو یوتیوب یا \"URL\" فهرست پخش برای دریافت رونوشت، نظرات و ارسال به گفتگو یا چاپ در کنسول و ذخیره در فایل خروجی",
"youtube_url_is_playlist_not_video": "URL یک فهرست پخش است، نه یک ویدیو",
"youtube_video_id_title_header": "شناسه ویدیو: عنوان",
"youtube_visual_fps_help": "استخراج تعداد مشخصی فریم در هر ثانیه به‌جای استفاده از تشخیص صحنه",
"youtube_visual_frame_cue": "[نشانه فریم بصری]",
"youtube_visual_sensitivity_help": "میزان حساسیت تشخیص صحنه در FFmpeg (0.0 - 1.0)",
"youtube_ytdlp_not_found": "yt-dlp در PATH یافت نشد. لطفاً yt-dlp را نصب کنید تا از قابلیت رونویسی یوتیوب استفاده کنید",
"youtube_ytdlp_required_visual_extraction": "برای استخراج بصری به yt-dlp نیاز است اما در PATH پیدا نشد",
"youtube_ytdlp_stderr_error": "خطا در خواندن stderr yt-dlp"
}

View file

@ -54,7 +54,7 @@
"bedrock_aws_access_key_label": "Entrez votre AWS Access Key ID (laissez vide pour utiliser la chaîne d'authentification AWS)",
"bedrock_aws_region_label": "Région AWS",
"bedrock_aws_secret_key_label": "Entrez votre AWS Secret Access Key (laissez vide pour utiliser la chaîne d'authentification AWS)",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"bedrock_client_not_initialized": "client Bedrock non initialisé — exécutez 'fabric --setup' pour configurer",
"bedrock_converse_failed": "bedrock converse a échoué pour le modèle %s : %w",
"bedrock_conversestream_failed": "bedrock conversestream a échoué pour le modèle %s : %w",
"bedrock_empty_response_content": "contenu de réponse vide",
@ -67,21 +67,21 @@
"bedrock_listmodels_fallback": "API ListModels Bedrock échouée, utilisation de la liste statique de secours",
"bedrock_panic_sendstream": "panique dans SendStream : %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",
"bedrock_setup_auth_prompt": "Enter 1 or 2",
"bedrock_setup_choose_auth_method": " Choose authentication method:",
"bedrock_setup_choose_model": " Choose default model (unprefixed IDs work in any region; us./eu./ap. are region-specific):",
"bedrock_setup_choose_region": " Choose AWS Region:",
"bedrock_setup_auth_option_apikey": " [1] Clé API Bedrock / jeton ABSK (recommandé — identique à Claude Code)",
"bedrock_setup_auth_prompt": "Entrez 1 ou 2",
"bedrock_setup_choose_auth_method": " Choisissez la méthode d'authentification :",
"bedrock_setup_choose_model": " Choisissez le modèle par défaut (les ID sans préfixe fonctionnent dans toutes les régions ; us./eu./ap. sont spécifiques à une région) :",
"bedrock_setup_choose_region": " Choisissez la région AWS :",
"bedrock_setup_header": "[Bedrock]",
"bedrock_setup_invalid_auth_selection": "invalid selection: %s (enter 1 or 2)",
"bedrock_setup_model_custom_prompt": "Enter model ID (e.g. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Enter a different model ID",
"bedrock_setup_model_prompt": "Enter model number or 0 to type your own",
"bedrock_setup_region_custom_prompt": "Enter custom AWS region (e.g. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Enter a different region",
"bedrock_setup_region_prompt": "Enter region number or 0 for custom",
"bedrock_setup_selected_model": " ✓ Selected model: %s",
"bedrock_setup_use_with": " Use with: fabric -m %s -V Bedrock",
"bedrock_setup_invalid_auth_selection": "sélection invalide : %s (entrez 1 ou 2)",
"bedrock_setup_model_custom_prompt": "Entrez l'ID du modèle (par ex. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Entrer un autre ID de modèle",
"bedrock_setup_model_prompt": "Entrez le numéro du modèle ou 0 pour saisir le vôtre",
"bedrock_setup_region_custom_prompt": "Entrez une région AWS personnalisée (par ex. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Entrer une autre région",
"bedrock_setup_region_prompt": "Entrez le numéro de la région ou 0 pour une région personnalisée",
"bedrock_setup_selected_model": " ✓ Modèle sélectionné : %s",
"bedrock_setup_use_with": " Utilisation : fabric -m %s -V Bedrock",
"bedrock_unable_load_aws_config": "impossible de charger la configuration AWS : %w",
"bedrock_unable_load_aws_config_with_region": "impossible de charger la configuration AWS avec la région %s : %w",
"bedrock_unexpected_content_block_type": "type de bloc de contenu inattendu : %T",
@ -110,21 +110,43 @@
"choose_pattern_from_available": "Choisissez un motif parmi les motifs disponibles",
"choose_session_from_available": "Choisissez une session parmi les sessions disponibles",
"choose_strategy_from_available": "Choisir une stratégie parmi les stratégies disponibles",
"codex_api_base_url_question": "Entrez l'URL de base de l'API Codex",
"codex_auth_base_url_invalid": "URL de base d'authentification Codex invalide : %w",
"codex_auth_base_url_question": "Entrez l'URL de base OAuth de Codex",
"codex_browser_open_fallback": "Si votre navigateur ne s'est pas ouvert, accédez à cette URL pour vous authentifier :",
"codex_decode_models_response_failed": "Échec du décodage de la réponse des modèles Codex : %w",
"codex_decode_refresh_response_failed": "Échec du décodage de la réponse du jeton Codex rafraîchi : %w",
"codex_decode_token_response_failed": "Échec du décodage de la réponse d'échange de jeton Codex : %w",
"codex_image_file_not_supported": "Le fournisseur Codex ne prend pas en charge --image-file. Utilisez une pièce jointe image à la place.",
"codex_login_account_changed": "La connexion Codex est liée à un compte ChatGPT différent de la configuration enregistrée. Veuillez relancer 'fabric --setup'.",
"codex_login_completed": "Connexion Codex terminée",
"codex_login_failed": "Échec de la connexion Codex : %s",
"codex_login_invalid": "Votre connexion Codex n'est plus valide. Veuillez relancer 'fabric --setup'.",
"codex_login_missing_account_claim": "La connexion Codex n'a pas inclus d'identifiant de compte ChatGPT. Cet état de connexion n'est pas pris en charge.",
"codex_login_missing_auth_code": "La connexion Codex n'a pas renvoyé de code d'autorisation.",
"codex_login_missing_tokens": "La connexion Codex n'a pas renvoyé les jetons d'accès et de rafraîchissement requis.",
"codex_login_refresh_failed": "Le rafraîchissement de la connexion Codex a échoué. Veuillez relancer 'fabric --setup'.",
"codex_login_return_to_fabric": "Retourner à Fabric.",
"codex_login_revoked": "La connexion Codex a expiré ou a été révoquée. Veuillez relancer 'fabric --setup'.",
"codex_login_server_stopped": "Le serveur de rappel de connexion Codex s'est arrêté avant la fin de l'authentification.",
"codex_login_state_mismatch": "La connexion Codex n'a pas pu être vérifiée car l'état OAuth ne correspondait pas.",
"codex_login_timed_out": "La connexion Codex a expiré avant la fin de l'authentification.",
"codex_oauth_missing_auth_code": "Code d'autorisation manquant",
"codex_oauth_random_state_failed": "Échec de la génération d'un état OAuth aléatoire sécurisé : %w",
"codex_oauth_server_start_failed": "Échec du démarrage du serveur local de rappel OAuth : %w",
"codex_oauth_state_mismatch": "L'état ne correspond pas",
"codex_provider_error": "erreur du fournisseur Codex (statut %d) : %s",
"codex_refresh_failed_status": "Échec du rafraîchissement de la connexion Codex (statut %d)",
"codex_refresh_login_failed": "Échec du rafraîchissement de la connexion Codex : %w",
"codex_refresh_token_required": "Le jeton de rafraîchissement Codex est requis. Veuillez relancer 'fabric --setup'.",
"codex_replay_body_unavailable": "Le corps de la requête ne peut pas être rejoué pour la tentative de réauthentification Codex",
"codex_request_failed": "La requête Codex a échoué : %w",
"codex_request_failed_status": "La requête Codex a échoué avec le statut %d",
"codex_starting_browser_login": "Démarrage de la connexion OpenAI par navigateur pour Codex.",
"codex_token_exchange_failed": "L'échange de jeton Codex a échoué : %w",
"codex_token_persist_failed": "échec de l'enregistrement de la connexion Codex : %w",
"codex_token_refresh_missing_access_token": "Le rafraîchissement du jeton Codex n'a pas renvoyé de jeton d'accès.",
"codex_usage_limit_reached": "Limite d'utilisation Codex atteinte",
"command_completed_successfully": "Commande terminée avec succès",
"compression_level_jpeg_webp": "Niveau de compression 0-100 pour les formats JPEG/WebP (par défaut : non défini)",
"config_file_not_found": "fichier de configuration non trouvé : %s",
@ -151,6 +173,7 @@
"custom_patterns_setup_description": "Patrons personnalisés - Définir le répertoire pour vos patrons personnalisés",
"custom_patterns_warning_create_directory": "Avertissement : Impossible de créer le répertoire de modèles personnalisés %s : %v\n",
"db_error_loading_env_file": "erreur lors du chargement du fichier .env : %w",
"db_error_updating_env_file": "erreur lors de la mise à jour du fichier .env : %w",
"defaults_model_context_length_question": "Saisissez la longueur du contexte du modèle",
"defaults_model_question": "Saisissez l'index ou le nom de votre modèle par défaut",
"defaults_setup_description": "Fournisseur et modèle d'IA par défaut",
@ -161,7 +184,7 @@
"digitalocean_models_request_failed_with_status": "échec de la requête de modèles DigitalOcean avec le statut %d : %s",
"disable_openai_responses_api": "Désactiver l'API OpenAI Responses (par défaut : false)",
"disable_pattern_variable_replacement": "Désactiver le remplacement des variables de motif",
"enable_web_search_tool": "Activer l'outil de recherche web pour les modèles pris en charge (Anthropic, OpenAI, Gemini)",
"enable_web_search_tool": "Activer l'outil de recherche web pour les modèles pris en charge (Anthropic, OpenAI, Gemini, Grok)",
"end_tag_thinking_sections": "Balise de fin pour les sections de réflexion",
"error_creating_audio_file": "erreur lors de la création du fichier audio : %v",
"error_creating_file": "erreur lors de la création du fichier : %v",
@ -352,6 +375,7 @@
"ollama_invalid_data_url_format": "format d'URL de données invalide",
"ollama_invalid_http_timeout_using_default": "Délai d'expiration HTTP invalide '%s' : %v, utilisation de la valeur par défaut",
"ollama_invalid_num_ctx_in_request": "num_ctx invalide dans la requête : %v",
"ollama_invalid_request_body": "corps de requête invalide",
"ollama_no_content_from_upstream": "aucun contenu reçu du serveur Fabric en amont",
"ollama_num_ctx_exceeds_maximum": "num_ctx dépasse la valeur maximale autorisée de %d",
"ollama_num_ctx_invalid_type": "num_ctx doit être un nombre, type invalide reçu",
@ -365,6 +389,7 @@
"ollama_sse_buffer_limit": "La ligne SSE dépasse la limite de tampon de 1 Mo - ligne de données trop grande",
"ollama_upstream_non_2xx": "Le serveur Fabric en amont a renvoyé un statut non-2xx %d : %s",
"ollama_upstream_non_2xx_body_unreadable": "Le serveur Fabric en amont a renvoyé un statut non-2xx %d et le corps n'a pas pu être lu : %v",
"ollama_upstream_request_failed": "impossible de joindre le serveur Fabric en amont",
"ollama_upstream_returned_status": "le serveur Fabric en amont a renvoyé le statut %d",
"ollama_warning_no_content": "Attention : aucun contenu reçu du serveur Fabric en amont",
"ollama_warning_parse_variables": "Attention : échec de l'analyse de options.variables en JSON : %v",
@ -385,6 +410,7 @@
"openai_image_failed_to_save_image": "échec de l'enregistrement de l'image dans %s : %w",
"openai_image_saved_to": "Image enregistrée dans : %s",
"openai_model_no_image_generation": "le modèle '%s' ne prend pas en charge la génération d'images. Modèles pris en charge : %s",
"openai_models_rate_limited": "limite de débit dépassée lors de la récupération des modèles du fournisseur %s ; réessayer après %s secondes",
"openai_models_response_too_large": "réponse des modèles trop volumineuse du fournisseur %s (>%d octets)",
"openai_unable_to_parse_models_response": "impossible d'analyser la réponse des modèles ; réponse brute : %s",
"openai_unexpected_status_code_read_error": "code d'état inattendu : %d du fournisseur %s (échec de lecture du corps de réponse : %v)",
@ -400,6 +426,7 @@
"output_video_metadata": "Afficher les métadonnées de la vidéo",
"path_to_yaml_config": "Chemin vers le fichier de configuration YAML",
"pattern_not_found_list_available": "modèle '%s' non trouvé. Exécutez 'fabric -l' pour voir les modèles disponibles",
"pattern_invalid_name": "nom de modèle invalide : %q",
"pattern_not_found_no_patterns": "modèle '%s' non trouvé.\n\nAucun modèle n'est installé ! Pour résoudre ce problème :\n • Exécutez 'fabric --setup' pour configurer et télécharger les modèles\n • Ou exécutez 'fabric -U' pour télécharger/mettre à jour les modèles directement",
"pattern_variables_help": "Valeurs pour les variables de motif, ex. -v=#role:expert -v=#points:30",
"patterns_cloning_repository": "Clonage du dépôt %s (chemin : %s)...\\n",
@ -473,6 +500,8 @@
"prefer_playlist_over_video": "Préférer la liste de lecture à la vidéo si les deux IDs sont présents dans l'URL",
"print_context": "Afficher le contexte",
"print_current_version": "Afficher la version actuelle",
"print_metadata_to_stderr": "Afficher les métadonnées (jetons d'entrée/sortie) sur stderr",
"print_pattern_contents": "Afficher le contenu du motif indiqué dans le terminal",
"print_session": "Afficher la session",
"register_new_extension": "Enregistrer une nouvelle extension depuis le chemin du fichier de configuration",
"remove_registered_extension": "Supprimer une extension enregistrée par nom",
@ -486,10 +515,12 @@
"send_desktop_notification": "Envoyer une notification de bureau quand la commande se termine",
"serve_fabric_api_ollama_endpoints": "Servir l'API REST Fabric avec les endpoints ollama",
"serve_fabric_rest_api": "Servir l'API REST Fabric",
"server_api_key_required": "refus de servir sur l'adresse non loopback %s sans clé API : définissez --api-key ou FABRIC_API_KEY, ou liez une adresse loopback comme 127.0.0.1:8080",
"server_chat_error": "Erreur : %v",
"server_error_marshaling_response": "erreur de sérialisation de la réponse : %v",
"server_error_writing_response": "erreur d'écriture de la réponse : %v",
"server_invalid_request_format": "format de requête invalide : %v",
"server_no_api_key_warning": "Démarrage du serveur API REST sans authentification par clé API. Cela peut présenter des risques de sécurité.",
"sessions_creating_new": "Création d'une nouvelle session : %s\n",
"set_debug_level": "Définir le niveau de débogage (0=désactivé, 1=basique, 2=détaillé, 3=trace, 4=wire)",
"set_frequency_penalty": "Définir la pénalité de fréquence",
@ -581,6 +612,7 @@
"spotify_show_name_label": "**Émission** : %s",
"spotify_title_label": "**Titre** : %s",
"spotify_total_episodes_label": "**Épisodes au total** : %d",
"spotify_url_help": "URL de podcast ou d'épisode Spotify pour récupérer les métadonnées et les envoyer au chat",
"spotify_url_label": "**URL** : %s",
"start_tag_thinking_sections": "Balise de début pour les sections de réflexion",
"storage_error_delete": "Impossible de supprimer %s : %v",
@ -592,6 +624,7 @@
"storage_error_save": "Impossible de sauvegarder %s : %v",
"storage_error_stat_entry": "Impossible d'obtenir les informations de l'entrée %s : %v",
"storage_error_unmarshal": "Impossible de désérialiser %s : %s",
"storage_invalid_name": "nom invalide : %q",
"strategies_available_header": "Stratégies disponibles :",
"strategies_cloning_repository": "Clonage du dépôt %s (chemin : %s)...\\n",
"strategies_download_success": "✅ Stratégies téléchargées et installées avec succès dans %s\\n",
@ -610,11 +643,11 @@
"strategy_path_traversal": "le nom de stratégie %q se résout en dehors du répertoire des stratégies",
"stream_help": "Streaming",
"suppress_thinking_tags": "Supprimer le texte encadré par les balises de réflexion",
"template_datetime_error_invalid_number": "invalid number in relative time: %q",
"template_datetime_error_invalid_relative_format": "invalid relative time format",
"template_datetime_error_invalid_unit": "invalid time unit: %q",
"template_datetime_error_relative_requires_value": "relative time requires a value",
"template_datetime_error_unknown_operation": "datetime: unknown operation %q",
"template_datetime_error_invalid_number": "nombre invalide dans le temps relatif : %q",
"template_datetime_error_invalid_relative_format": "format de temps relatif invalide",
"template_datetime_error_invalid_unit": "unité de temps invalide : %q",
"template_datetime_error_relative_requires_value": "le temps relatif nécessite une valeur",
"template_datetime_error_unknown_operation": "datetime: opération inconnue %q",
"template_extension_error": "Erreur d'extension %s : %v",
"template_file_error_expand_home_dir": "fichier : impossible d'étendre le répertoire personnel : %v",
"template_file_error_invalid_line_count": "fichier : nombre de lignes invalide %q",
@ -643,12 +676,12 @@
"template_missing_required_variable": "Variable requise manquante : %s",
"template_plugin_error": "Erreur du plugin %s : %v",
"template_processing_stuck": "Traitement du modèle bloqué - boucle infinie potentielle",
"template_sys_error_env_requires_var": "env: variable name required",
"template_sys_error_home": "failed to get home directory: %v",
"template_sys_error_hostname": "failed to get hostname: %v",
"template_sys_error_pwd": "failed to get working directory: %v",
"template_sys_error_unknown_operation": "sys: unknown operation %q",
"template_sys_error_user": "failed to get current user: %v",
"template_sys_error_env_requires_var": "env: nom de variable requis",
"template_sys_error_home": "impossible d'obtenir le répertoire personnel : %v",
"template_sys_error_hostname": "impossible d'obtenir le nom d'hôte : %v",
"template_sys_error_pwd": "impossible d'obtenir le répertoire de travail : %v",
"template_sys_error_unknown_operation": "sys: opération inconnue %q",
"template_sys_error_user": "impossible d'obtenir l'utilisateur actuel : %v",
"template_text_empty_input": "Texte : entrée vide pour l'opération %q",
"template_text_unknown_operation": "Texte : opération de texte inconnue %q (supportées : upper, lower, title, trim)",
"template_unknown_plugin_namespace": "Espace de noms de plugin inconnu : %s",
@ -698,15 +731,21 @@
"youtube_error_getting_video_details": "erreur lors de l'obtention des détails de la vidéo : %v",
"youtube_error_parsing_duration": "erreur lors de l'analyse de la durée de la vidéo : %v",
"youtube_error_saving_csv": "erreur lors de l'enregistrement des vidéos en CSV : %v",
"youtube_extract_visual_data_help": "Extraire les données visuelles de la vidéo avec OCR et FFmpeg",
"youtube_failed_create_temp_dir": "échec de création du répertoire temporaire : %v",
"youtube_failed_fetch_comments": "Échec de la récupération des commentaires : %v",
"youtube_failed_get_stream_url": "échec de récupération de lURL du flux via yt-dlp : %v",
"youtube_failed_parse_http_stream_url": "échec de lanalyse dune URL HTTP de flux valide depuis la sortie de yt-dlp",
"youtube_failed_walk_directory": "échec du parcours du répertoire : %v",
"youtube_ffmpeg_frame_extraction_failed": "extraction des images avec ffmpeg échouée : %v, sortie : %s",
"youtube_ffmpeg_required_visual_extraction": "ffmpeg est requis pour lextraction visuelle mais est introuvable dans PATH",
"youtube_invalid_duration_string": "chaîne de durée invalide : %s",
"youtube_invalid_seconds_format": "format de secondes invalide %q : %w",
"youtube_invalid_timestamp_format": "format d'horodatage invalide : %s",
"youtube_invalid_url": "URL YouTube invalide, impossible d'obtenir l'ID de vidéo ou de liste de lecture : '%s'",
"youtube_invalid_ytdlp_arguments": "arguments yt-dlp invalides : %v",
"youtube_label": "YouTube",
"youtube_no_clear_text_visual_frames": "aucun texte lisible trouvé dans les images visuelles de la vidéo",
"youtube_no_transcript_content": "aucun contenu de transcription trouvé dans le fichier VTT",
"youtube_no_url_provided": "Aucune URL YouTube fournie",
"youtube_no_video_found_with_id": "aucune vidéo trouvée avec l'ID : %s",
@ -717,9 +756,15 @@
"youtube_playlist_saved_to": "Liste de lecture enregistrée dans %s",
"youtube_rate_limit_exceeded": "Limite de taux YouTube dépassée. Réessayez plus tard ou utilisez différents arguments yt-dlp comme '--sleep-requests 1' pour ralentir les requêtes.",
"youtube_setup_description": "YouTube - pour récupérer les transcriptions vidéo (via yt-dlp) et les commentaires/métadonnées (via l'API YouTube)",
"youtube_tesseract_frame_failed": "tesseract a échoué sur limage %d : %v, stderr : %s",
"youtube_tesseract_required_visual_extraction": "tesseract est requis pour lextraction visuelle mais est introuvable dans PATH",
"youtube_url_help": "Vidéo YouTube ou \"URL\" de liste de lecture pour récupérer la transcription, les commentaires et envoyer au chat ou afficher dans la console et stocker dans le fichier de sortie",
"youtube_url_is_playlist_not_video": "l'URL est une liste de lecture, pas une vidéo",
"youtube_video_id_title_header": "VideoID : Titre",
"youtube_visual_fps_help": "Extraire un nombre précis dimages par seconde au lieu dutiliser la détection de scènes",
"youtube_visual_frame_cue": "[REPÈRE DIMAGE VISUELLE]",
"youtube_visual_sensitivity_help": "Tolérance pour la détection de scènes FFmpeg (0.0 - 1.0)",
"youtube_ytdlp_not_found": "yt-dlp introuvable dans PATH. Veuillez installer yt-dlp pour utiliser la fonctionnalité de transcription YouTube",
"youtube_ytdlp_required_visual_extraction": "yt-dlp est requis pour lextraction visuelle mais est introuvable dans PATH",
"youtube_ytdlp_stderr_error": "erreur lors de la lecture du stderr de yt-dlp"
}

View file

@ -54,7 +54,7 @@
"bedrock_aws_access_key_label": "Inserisci il tuo AWS Access Key ID (lascia vuoto per usare la catena di credenziali AWS)",
"bedrock_aws_region_label": "Regione AWS",
"bedrock_aws_secret_key_label": "Inserisci il tuo AWS Secret Access Key (lascia vuoto per usare la catena di credenziali AWS)",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"bedrock_client_not_initialized": "client Bedrock non inizializzato — eseguire 'fabric --setup' per configurare",
"bedrock_converse_failed": "bedrock converse fallito per il modello %s: %w",
"bedrock_conversestream_failed": "bedrock conversestream fallito per il modello %s: %w",
"bedrock_empty_response_content": "contenuto della risposta vuoto",
@ -67,21 +67,21 @@
"bedrock_listmodels_fallback": "API ListModels Bedrock fallita, utilizzo della lista statica di fallback",
"bedrock_panic_sendstream": "panic in SendStream: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",
"bedrock_setup_auth_prompt": "Enter 1 or 2",
"bedrock_setup_choose_auth_method": " Choose authentication method:",
"bedrock_setup_choose_model": " Choose default model (unprefixed IDs work in any region; us./eu./ap. are region-specific):",
"bedrock_setup_choose_region": " Choose AWS Region:",
"bedrock_setup_auth_option_apikey": " [1] Chiave API Bedrock / token ABSK (consigliato — la stessa usata da Claude Code)",
"bedrock_setup_auth_prompt": "Inserisci 1 o 2",
"bedrock_setup_choose_auth_method": " Scegli il metodo di autenticazione:",
"bedrock_setup_choose_model": " Scegli il modello predefinito (gli ID senza prefisso funzionano in qualsiasi regione; us./eu./ap. sono specifici per regione):",
"bedrock_setup_choose_region": " Scegli la regione AWS:",
"bedrock_setup_header": "[Bedrock]",
"bedrock_setup_invalid_auth_selection": "invalid selection: %s (enter 1 or 2)",
"bedrock_setup_model_custom_prompt": "Enter model ID (e.g. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Enter a different model ID",
"bedrock_setup_model_prompt": "Enter model number or 0 to type your own",
"bedrock_setup_region_custom_prompt": "Enter custom AWS region (e.g. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Enter a different region",
"bedrock_setup_region_prompt": "Enter region number or 0 for custom",
"bedrock_setup_selected_model": " ✓ Selected model: %s",
"bedrock_setup_use_with": " Use with: fabric -m %s -V Bedrock",
"bedrock_setup_invalid_auth_selection": "selezione non valida: %s (inserisci 1 o 2)",
"bedrock_setup_model_custom_prompt": "Inserisci l'ID del modello (es. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Inserisci un ID modello diverso",
"bedrock_setup_model_prompt": "Inserisci il numero del modello o 0 per digitarne uno tuo",
"bedrock_setup_region_custom_prompt": "Inserisci una regione AWS personalizzata (es. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Inserisci una regione diversa",
"bedrock_setup_region_prompt": "Inserisci il numero della regione o 0 per personalizzarla",
"bedrock_setup_selected_model": " ✓ Modello selezionato: %s",
"bedrock_setup_use_with": " Uso: fabric -m %s -V Bedrock",
"bedrock_unable_load_aws_config": "impossibile caricare la configurazione AWS: %w",
"bedrock_unable_load_aws_config_with_region": "impossibile caricare la configurazione AWS con regione %s: %w",
"bedrock_unexpected_content_block_type": "tipo di blocco contenuto inaspettato: %T",
@ -110,21 +110,43 @@
"choose_pattern_from_available": "Scegli un pattern dai pattern disponibili",
"choose_session_from_available": "Scegli una sessione dalle sessioni disponibili",
"choose_strategy_from_available": "Scegli una strategia dalle strategie disponibili",
"codex_api_base_url_question": "Inserisci l'URL di base dell'API Codex",
"codex_auth_base_url_invalid": "URL base di autenticazione Codex non valido: %w",
"codex_auth_base_url_question": "Inserisci l'URL di base OAuth di Codex",
"codex_browser_open_fallback": "Se il browser non si è aperto, navigare a questo URL per autenticarsi:",
"codex_decode_models_response_failed": "Decodifica della risposta dei modelli Codex non riuscita: %w",
"codex_decode_refresh_response_failed": "Decodifica della risposta del token Codex aggiornato non riuscita: %w",
"codex_decode_token_response_failed": "Decodifica della risposta di scambio token Codex non riuscita: %w",
"codex_image_file_not_supported": "Il fornitore Codex non supporta --image-file. Utilizzare un allegato immagine.",
"codex_login_account_changed": "L'accesso Codex è collegato a un account ChatGPT diverso dalla configurazione salvata. Eseguire di nuovo 'fabric --setup'.",
"codex_login_completed": "Accesso Codex completato",
"codex_login_failed": "Accesso Codex non riuscito: %s",
"codex_login_invalid": "L'accesso Codex non è più valido. Eseguire di nuovo 'fabric --setup'.",
"codex_login_missing_account_claim": "L'accesso Codex non includeva un ID account ChatGPT. Questo stato di accesso non è supportato.",
"codex_login_missing_auth_code": "L'accesso Codex non ha restituito un codice di autorizzazione.",
"codex_login_missing_tokens": "L'accesso Codex non ha restituito i token di accesso e aggiornamento richiesti.",
"codex_login_refresh_failed": "Impossibile aggiornare l'accesso Codex. Eseguire di nuovo 'fabric --setup'.",
"codex_login_return_to_fabric": "Torna a Fabric.",
"codex_login_revoked": "L'accesso Codex è scaduto o è stato revocato. Eseguire di nuovo 'fabric --setup'.",
"codex_login_server_stopped": "Il server di callback dell'accesso Codex si è fermato prima del completamento dell'autenticazione.",
"codex_login_state_mismatch": "L'accesso Codex non è stato verificato perché lo stato OAuth non corrispondeva.",
"codex_login_timed_out": "L'accesso Codex è scaduto prima del completamento dell'autenticazione.",
"codex_oauth_missing_auth_code": "Codice di autorizzazione mancante",
"codex_oauth_random_state_failed": "Generazione dello stato OAuth casuale sicuro non riuscita: %w",
"codex_oauth_server_start_failed": "Avvio del server locale di callback OAuth non riuscito: %w",
"codex_oauth_state_mismatch": "Lo stato non corrisponde",
"codex_provider_error": "errore del fornitore Codex (stato %d): %s",
"codex_refresh_failed_status": "Aggiornamento dell'accesso Codex non riuscito (stato %d)",
"codex_refresh_login_failed": "Aggiornamento dell'accesso Codex non riuscito: %w",
"codex_refresh_token_required": "Il token di aggiornamento Codex è richiesto. Eseguire di nuovo 'fabric --setup'.",
"codex_replay_body_unavailable": "Il corpo della richiesta non può essere riprodotto per il tentativo di riautenticazione Codex",
"codex_request_failed": "La richiesta Codex è fallita: %w",
"codex_request_failed_status": "La richiesta Codex è fallita con stato %d",
"codex_starting_browser_login": "Avvio dell'accesso OpenAI basato su browser per Codex.",
"codex_token_exchange_failed": "Lo scambio di token Codex è fallito: %w",
"codex_token_persist_failed": "impossibile salvare l'accesso Codex: %w",
"codex_token_refresh_missing_access_token": "L'aggiornamento del token Codex non ha restituito un token di accesso.",
"codex_usage_limit_reached": "Limite di utilizzo Codex raggiunto",
"command_completed_successfully": "Comando completato con successo",
"compression_level_jpeg_webp": "Livello di compressione 0-100 per formati JPEG/WebP (predefinito: non impostato)",
"config_file_not_found": "file di configurazione non trovato: %s",
@ -151,6 +173,7 @@
"custom_patterns_setup_description": "Pattern personalizzati - Imposta la directory per i tuoi pattern personalizzati",
"custom_patterns_warning_create_directory": "Avviso: Impossibile creare la directory dei modelli personalizzati %s: %v\n",
"db_error_loading_env_file": "errore nel caricamento del file .env: %w",
"db_error_updating_env_file": "errore nell'aggiornamento del file .env: %w",
"defaults_model_context_length_question": "Inserisci la lunghezza del contesto del modello",
"defaults_model_question": "Inserisci l'indice o il nome del tuo modello predefinito",
"defaults_setup_description": "Fornitore e modello AI predefiniti",
@ -161,7 +184,7 @@
"digitalocean_models_request_failed_with_status": "richiesta modelli DigitalOcean fallita con stato %d: %s",
"disable_openai_responses_api": "Disabilita API OpenAI Responses (predefinito: false)",
"disable_pattern_variable_replacement": "Disabilita sostituzione variabili pattern",
"enable_web_search_tool": "Abilita strumento di ricerca web per modelli supportati (Anthropic, OpenAI, Gemini)",
"enable_web_search_tool": "Abilita strumento di ricerca web per modelli supportati (Anthropic, OpenAI, Gemini, Grok)",
"end_tag_thinking_sections": "Tag di fine per sezioni di pensiero",
"error_creating_audio_file": "errore nella creazione del file audio: %v",
"error_creating_file": "errore nella creazione del file: %v",
@ -352,6 +375,7 @@
"ollama_invalid_data_url_format": "formato URL dati non valido",
"ollama_invalid_http_timeout_using_default": "Timeout HTTP non valido '%s': %v, utilizzo del valore predefinito",
"ollama_invalid_num_ctx_in_request": "num_ctx non valido nella richiesta: %v",
"ollama_invalid_request_body": "corpo della richiesta non valido",
"ollama_no_content_from_upstream": "nessun contenuto ricevuto dal server Fabric upstream",
"ollama_num_ctx_exceeds_maximum": "num_ctx supera il valore massimo consentito di %d",
"ollama_num_ctx_invalid_type": "num_ctx deve essere un numero, ricevuto tipo non valido",
@ -365,6 +389,7 @@
"ollama_sse_buffer_limit": "La riga SSE supera il limite del buffer di 1MB - riga di dati troppo grande",
"ollama_upstream_non_2xx": "Il server Fabric upstream ha restituito stato non-2xx %d: %s",
"ollama_upstream_non_2xx_body_unreadable": "Il server Fabric upstream ha restituito stato non-2xx %d e il corpo non è stato leggibile: %v",
"ollama_upstream_request_failed": "impossibile raggiungere il server Fabric a monte",
"ollama_upstream_returned_status": "il server Fabric upstream ha restituito stato %d",
"ollama_warning_no_content": "Avviso: nessun contenuto ricevuto dal server Fabric upstream",
"ollama_warning_parse_variables": "Avviso: impossibile analizzare options.variables come JSON: %v",
@ -385,6 +410,7 @@
"openai_image_failed_to_save_image": "salvataggio dell'immagine in %s fallito: %w",
"openai_image_saved_to": "Immagine salvata in: %s",
"openai_model_no_image_generation": "il modello '%s' non supporta la generazione di immagini. Modelli supportati: %s",
"openai_models_rate_limited": "limite di richieste superato durante il recupero dei modelli dal provider %s; riprovare dopo %s secondi",
"openai_models_response_too_large": "risposta dei modelli troppo grande dal provider %s (>%d byte)",
"openai_unable_to_parse_models_response": "impossibile analizzare risposta modelli; risposta grezza: %s",
"openai_unexpected_status_code_read_error": "codice di stato imprevisto: %d dal provider %s (errore lettura corpo risposta: %v)",
@ -400,6 +426,7 @@
"output_video_metadata": "Output metadati video",
"path_to_yaml_config": "Percorso del file di configurazione YAML",
"pattern_not_found_list_available": "pattern '%s' non trovato. Esegui 'fabric -l' per vedere i pattern disponibili",
"pattern_invalid_name": "nome pattern non valido: %q",
"pattern_not_found_no_patterns": "pattern '%s' non trovato.\n\nNessun pattern installato! Per risolvere:\n • Esegui 'fabric --setup' per configurare e scaricare i pattern\n • Oppure esegui 'fabric -U' per scaricare/aggiornare i pattern direttamente",
"pattern_variables_help": "Valori per le variabili pattern, es. -v=#role:expert -v=#points:30",
"patterns_cloning_repository": "Clonazione del repository %s (percorso: %s)...\\n",
@ -473,6 +500,8 @@
"prefer_playlist_over_video": "Preferisci playlist al video se entrambi gli ID sono presenti nell'URL",
"print_context": "Stampa contesto",
"print_current_version": "Stampa versione corrente",
"print_metadata_to_stderr": "Stampa i metadati (token di input/output) su stderr",
"print_pattern_contents": "Stampa il contenuto del pattern indicato nel terminale",
"print_session": "Stampa sessione",
"register_new_extension": "Registra una nuova estensione dal percorso del file di configurazione",
"remove_registered_extension": "Rimuovi un'estensione registrata per nome",
@ -486,10 +515,12 @@
"send_desktop_notification": "Invia notifica desktop quando il comando è completato",
"serve_fabric_api_ollama_endpoints": "Servi l'API REST di Fabric con endpoint ollama",
"serve_fabric_rest_api": "Servi l'API REST di Fabric",
"server_api_key_required": "rifiuto di servire sull'indirizzo non loopback %s senza chiave API: impostare --api-key o FABRIC_API_KEY, oppure associare un indirizzo loopback come 127.0.0.1:8080",
"server_chat_error": "Errore: %v",
"server_error_marshaling_response": "errore nella serializzazione della risposta: %v",
"server_error_writing_response": "errore nella scrittura della risposta: %v",
"server_invalid_request_format": "formato della richiesta non valido: %v",
"server_no_api_key_warning": "Avvio del server API REST senza autenticazione con chiave API. Ciò può comportare rischi per la sicurezza.",
"sessions_creating_new": "Creazione nuova sessione: %s\n",
"set_debug_level": "Imposta livello di debug (0=spento, 1=base, 2=dettagliato, 3=traccia, 4=wire)",
"set_frequency_penalty": "Imposta penalità di frequenza",
@ -581,6 +612,7 @@
"spotify_show_name_label": "**Show**: %s",
"spotify_title_label": "**Titolo**: %s",
"spotify_total_episodes_label": "**Episodi totali**: %d",
"spotify_url_help": "URL di podcast o episodio Spotify da cui ottenere i metadati e inviarli alla chat",
"spotify_url_label": "**URL**: %s",
"start_tag_thinking_sections": "Tag di inizio per sezioni di pensiero",
"storage_error_delete": "Impossibile eliminare %s: %v",
@ -592,6 +624,7 @@
"storage_error_save": "Impossibile salvare %s: %v",
"storage_error_stat_entry": "Impossibile ottenere informazioni sulla voce %s: %v",
"storage_error_unmarshal": "Impossibile deserializzare %s: %s",
"storage_invalid_name": "nome non valido: %q",
"strategies_available_header": "Strategie disponibili:",
"strategies_cloning_repository": "Clonazione del repository %s (percorso: %s)...\\n",
"strategies_download_success": "✅ Strategie scaricate e installate correttamente in %s\\n",
@ -610,11 +643,11 @@
"strategy_path_traversal": "il nome della strategia %q si risolve al di fuori della directory delle strategie",
"stream_help": "Streaming",
"suppress_thinking_tags": "Sopprimi testo racchiuso in tag di pensiero",
"template_datetime_error_invalid_number": "invalid number in relative time: %q",
"template_datetime_error_invalid_relative_format": "invalid relative time format",
"template_datetime_error_invalid_unit": "invalid time unit: %q",
"template_datetime_error_relative_requires_value": "relative time requires a value",
"template_datetime_error_unknown_operation": "datetime: unknown operation %q",
"template_datetime_error_invalid_number": "numero non valido nel tempo relativo: %q",
"template_datetime_error_invalid_relative_format": "formato di tempo relativo non valido",
"template_datetime_error_invalid_unit": "unità di tempo non valida: %q",
"template_datetime_error_relative_requires_value": "il tempo relativo richiede un valore",
"template_datetime_error_unknown_operation": "datetime: operazione sconosciuta %q",
"template_extension_error": "Errore dell'estensione %s: %v",
"template_file_error_expand_home_dir": "file: impossibile espandere la directory home: %v",
"template_file_error_invalid_line_count": "file: numero di righe non valido %q",
@ -643,12 +676,12 @@
"template_missing_required_variable": "Variabile richiesta mancante: %s",
"template_plugin_error": "Errore del plugin %s: %v",
"template_processing_stuck": "Elaborazione del modello bloccata - possibile ciclo infinito",
"template_sys_error_env_requires_var": "env: variable name required",
"template_sys_error_home": "failed to get home directory: %v",
"template_sys_error_hostname": "failed to get hostname: %v",
"template_sys_error_pwd": "failed to get working directory: %v",
"template_sys_error_unknown_operation": "sys: unknown operation %q",
"template_sys_error_user": "failed to get current user: %v",
"template_sys_error_env_requires_var": "env: nome della variabile richiesto",
"template_sys_error_home": "impossibile ottenere la directory home: %v",
"template_sys_error_hostname": "impossibile ottenere il nome host: %v",
"template_sys_error_pwd": "impossibile ottenere la directory di lavoro: %v",
"template_sys_error_unknown_operation": "sys: operazione sconosciuta %q",
"template_sys_error_user": "impossibile ottenere l'utente corrente: %v",
"template_text_empty_input": "Testo: input vuoto per l'operazione %q",
"template_text_unknown_operation": "Testo: operazione di testo sconosciuta %q (supportate: upper, lower, title, trim)",
"template_unknown_plugin_namespace": "Namespace del plugin sconosciuto: %s",
@ -698,15 +731,21 @@
"youtube_error_getting_video_details": "errore nell'ottenere i dettagli del video: %v",
"youtube_error_parsing_duration": "errore nell'analizzare la durata del video: %v",
"youtube_error_saving_csv": "errore nel salvare i video in CSV: %v",
"youtube_extract_visual_data_help": "Estrarre dati visivi dal video usando OCR e FFmpeg",
"youtube_failed_create_temp_dir": "impossibile creare la directory temporanea: %v",
"youtube_failed_fetch_comments": "Recupero dei commenti fallito: %v",
"youtube_failed_get_stream_url": "impossibile ottenere lURL dello stream tramite yt-dlp: %v",
"youtube_failed_parse_http_stream_url": "impossibile analizzare un URL HTTP di stream valido dalloutput di yt-dlp",
"youtube_failed_walk_directory": "impossibile esplorare la directory: %v",
"youtube_ffmpeg_frame_extraction_failed": "estrazione dei fotogrammi con ffmpeg non riuscita: %v, output: %s",
"youtube_ffmpeg_required_visual_extraction": "ffmpeg è richiesto per lestrazione visiva ma non è stato trovato nel PATH",
"youtube_invalid_duration_string": "stringa di durata non valida: %s",
"youtube_invalid_seconds_format": "formato secondi non valido %q: %w",
"youtube_invalid_timestamp_format": "formato timestamp non valido: %s",
"youtube_invalid_url": "URL YouTube non valido, impossibile ottenere l'ID del video o della playlist: '%s'",
"youtube_invalid_ytdlp_arguments": "argomenti yt-dlp non validi: %v",
"youtube_label": "YouTube",
"youtube_no_clear_text_visual_frames": "nessun testo leggibile trovato nei fotogrammi visivi del video",
"youtube_no_transcript_content": "nessun contenuto di trascrizione trovato nel file VTT",
"youtube_no_url_provided": "Nessun URL YouTube fornito",
"youtube_no_video_found_with_id": "nessun video trovato con ID: %s",
@ -717,9 +756,15 @@
"youtube_playlist_saved_to": "Playlist salvata in %s",
"youtube_rate_limit_exceeded": "Limite di richieste YouTube superato. Riprova più tardi o usa argomenti yt-dlp diversi come '--sleep-requests 1' per rallentare le richieste.",
"youtube_setup_description": "YouTube - per ottenere trascrizioni video (tramite yt-dlp) e commenti/metadati (tramite API YouTube)",
"youtube_tesseract_frame_failed": "tesseract non riuscito sul fotogramma %d: %v, stderr: %s",
"youtube_tesseract_required_visual_extraction": "tesseract è richiesto per lestrazione visiva ma non è stato trovato nel PATH",
"youtube_url_help": "Video YouTube o \"URL\" della playlist per ottenere trascrizioni, commenti e inviarli alla chat o stamparli sulla console e memorizzarli nel file di output",
"youtube_url_is_playlist_not_video": "l'URL è una playlist, non un video",
"youtube_video_id_title_header": "VideoID: Titolo",
"youtube_visual_fps_help": "Estrarre un numero specifico di fotogrammi al secondo invece di usare il rilevamento scene",
"youtube_visual_frame_cue": "[INDICATORE FOTOGRAMMA VISIVO]",
"youtube_visual_sensitivity_help": "Tolleranza per il rilevamento scene di FFmpeg (0.0 - 1.0)",
"youtube_ytdlp_not_found": "yt-dlp non trovato in PATH. Per favore installa yt-dlp per usare la funzionalità di trascrizione YouTube",
"youtube_ytdlp_required_visual_extraction": "yt-dlp è richiesto per lestrazione visiva ma non è stato trovato nel PATH",
"youtube_ytdlp_stderr_error": "errore durante la lettura dello stderr di yt-dlp"
}

View file

@ -54,7 +54,7 @@
"bedrock_aws_access_key_label": "AWS Access Key IDを入力してくださいAWS認証チェーンを使用する場合は空のままにしてください",
"bedrock_aws_region_label": "AWSリージョン",
"bedrock_aws_secret_key_label": "AWS Secret Access Keyを入力してくださいAWS認証チェーンを使用する場合は空のままにしてください",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"bedrock_client_not_initialized": "Bedrockクライアントが初期化されていません — 設定するには 'fabric --setup' を実行してください",
"bedrock_converse_failed": "モデル %s のbedrock converseが失敗しました: %w",
"bedrock_conversestream_failed": "モデル %s のbedrock conversestreamが失敗しました: %w",
"bedrock_empty_response_content": "空のレスポンスコンテンツ",
@ -67,21 +67,21 @@
"bedrock_listmodels_fallback": "Bedrock ListModels APIが失敗しました、静的フォールバックリストを使用",
"bedrock_panic_sendstream": "SendStreamでパニックが発生しました: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",
"bedrock_setup_auth_prompt": "Enter 1 or 2",
"bedrock_setup_choose_auth_method": " Choose authentication method:",
"bedrock_setup_choose_model": " Choose default model (unprefixed IDs work in any region; us./eu./ap. are region-specific):",
"bedrock_setup_choose_region": " Choose AWS Region:",
"bedrock_setup_auth_option_apikey": " [1] Bedrock APIキー / ABSKトークン推奨 — Claude Code と同じ)",
"bedrock_setup_auth_prompt": "1 または 2 を入力してください",
"bedrock_setup_choose_auth_method": " 認証方法を選択してください:",
"bedrock_setup_choose_model": " デフォルトモデルを選択してくださいプレフィックスなしのIDはどのリージョンでも使用できます。us./eu./ap. はリージョン固有です):",
"bedrock_setup_choose_region": " AWSリージョンを選択してください:",
"bedrock_setup_header": "[Bedrock]",
"bedrock_setup_invalid_auth_selection": "invalid selection: %s (enter 1 or 2)",
"bedrock_setup_model_custom_prompt": "Enter model ID (e.g. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Enter a different model ID",
"bedrock_setup_model_prompt": "Enter model number or 0 to type your own",
"bedrock_setup_region_custom_prompt": "Enter custom AWS region (e.g. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Enter a different region",
"bedrock_setup_region_prompt": "Enter region number or 0 for custom",
"bedrock_setup_selected_model": " ✓ Selected model: %s",
"bedrock_setup_use_with": " Use with: fabric -m %s -V Bedrock",
"bedrock_setup_invalid_auth_selection": "無効な選択: %s1 または 2 を入力してください)",
"bedrock_setup_model_custom_prompt": "モデルIDを入力してください例: anthropic.claude-opus-4-6-v1",
"bedrock_setup_model_option_custom": " [0] 別のモデルIDを入力",
"bedrock_setup_model_prompt": "モデル番号を入力、または 0 で任意のIDを入力",
"bedrock_setup_region_custom_prompt": "カスタムAWSリージョンを入力してください例: us-east-1",
"bedrock_setup_region_option_custom": " [0] 別のリージョンを入力",
"bedrock_setup_region_prompt": "リージョン番号を入力、または 0 でカスタム指定",
"bedrock_setup_selected_model": " ✓ 選択したモデル: %s",
"bedrock_setup_use_with": " 使用方法: fabric -m %s -V Bedrock",
"bedrock_unable_load_aws_config": "AWS設定を読み込めませんでした: %w",
"bedrock_unable_load_aws_config_with_region": "リージョン %s でAWS設定を読み込めませんでした: %w",
"bedrock_unexpected_content_block_type": "予期しないコンテンツブロックタイプ: %T",
@ -110,21 +110,43 @@
"choose_pattern_from_available": "利用可能なパターンからパターンを選択",
"choose_session_from_available": "利用可能なセッションからセッションを選択",
"choose_strategy_from_available": "利用可能な戦略から戦略を選択",
"codex_api_base_url_question": "Codex APIベースURLを入力してください",
"codex_auth_base_url_invalid": "Codex認証ベースURLが無効です: %w",
"codex_auth_base_url_question": "Codex OAuthベースURLを入力してください",
"codex_browser_open_fallback": "ブラウザが開かなかった場合は、このURLに移動して認証してください:",
"codex_decode_models_response_failed": "Codexモデルレスポンスのデコードに失敗しました: %w",
"codex_decode_refresh_response_failed": "更新されたCodexトークンレスポンスのデコードに失敗しました: %w",
"codex_decode_token_response_failed": "Codexトークン交換レスポンスのデコードに失敗しました: %w",
"codex_image_file_not_supported": "Codexベンダーは--image-fileをサポートしていません。代わりに画像添付を使用してください。",
"codex_login_account_changed": "Codexログインが保存された設定とは異なるChatGPTアカウントに紐づけられています。'fabric --setup'を再実行してください。",
"codex_login_completed": "Codexログインが完了しました",
"codex_login_failed": "Codexログインに失敗しました: %s",
"codex_login_invalid": "Codexログインは無効になりました。'fabric --setup'を再実行してください。",
"codex_login_missing_account_claim": "CodexログインにChatGPTアカウントIDが含まれていませんでした。このログイン状態はサポートされていません。",
"codex_login_missing_auth_code": "Codexログインが認証コードを返しませんでした。",
"codex_login_missing_tokens": "Codexログインが必要なアクセストークンとリフレッシュトークンを返しませんでした。",
"codex_login_refresh_failed": "Codexログインを更新できませんでした。'fabric --setup'を再実行してください。",
"codex_login_return_to_fabric": "Fabricに戻る。",
"codex_login_revoked": "Codexログインが期限切れまたは取り消されました。'fabric --setup'を再実行してください。",
"codex_login_server_stopped": "認証が完了する前にCodexログインコールバックサーバーが停止しました。",
"codex_login_state_mismatch": "OAuthステートが一致しなかったため、Codexログインを検証できませんでした。",
"codex_login_timed_out": "認証が完了する前にCodexログインがタイムアウトしました。",
"codex_oauth_missing_auth_code": "認証コードがありません",
"codex_oauth_random_state_failed": "安全なランダムOAuthステートの生成に失敗しました: %w",
"codex_oauth_server_start_failed": "ローカルOAuthコールバックサーバーの起動に失敗しました: %w",
"codex_oauth_state_mismatch": "ステートが一致しません",
"codex_provider_error": "Codexベンダーエラーステータス %d: %s",
"codex_refresh_failed_status": "Codexログインの更新に失敗しましたステータス %d",
"codex_refresh_login_failed": "Codexログインの更新に失敗しました: %w",
"codex_refresh_token_required": "Codexリフレッシュトークンが必要です。'fabric --setup'を再実行してください。",
"codex_replay_body_unavailable": "Codex再認証リトライのためにリクエストボディを再送できません",
"codex_request_failed": "Codexリクエストに失敗しました: %w",
"codex_request_failed_status": "Codexリクエストがステータス %d で失敗しました",
"codex_starting_browser_login": "Codex用のブラウザベースOpenAIログインを開始しています。",
"codex_token_exchange_failed": "Codexトークン交換に失敗しました: %w",
"codex_token_persist_failed": "Codexログインの保存に失敗しました: %w",
"codex_token_refresh_missing_access_token": "Codexトークンの更新がアクセストークンを返しませんでした。",
"codex_usage_limit_reached": "Codex使用量制限に達しました",
"command_completed_successfully": "コマンドが正常に完了しました",
"compression_level_jpeg_webp": "JPEG/WebP形式の圧縮レベル0-100デフォルト未設定",
"config_file_not_found": "設定ファイルが見つかりません: %s",
@ -151,6 +173,7 @@
"custom_patterns_setup_description": "カスタムパターン - カスタムパターン用のディレクトリを設定",
"custom_patterns_warning_create_directory": "警告: カスタムパターンディレクトリ%sを作成できませんでした: %v\n",
"db_error_loading_env_file": ".envファイルの読み込みエラー: %w",
"db_error_updating_env_file": ".envファイルの更新エラー: %w",
"defaults_model_context_length_question": "モデルのコンテキスト長を入力してください",
"defaults_model_question": "デフォルトモデルのインデックスまたは名前を入力してください",
"defaults_setup_description": "デフォルトのAIプロバイダーとモデル",
@ -161,7 +184,7 @@
"digitalocean_models_request_failed_with_status": "DigitalOceanモデルリクエストがステータス%dで失敗しました: %s",
"disable_openai_responses_api": "OpenAI Responses APIを無効化デフォルトfalse",
"disable_pattern_variable_replacement": "パターン変数の置換を無効化",
"enable_web_search_tool": "サポートされているモデルAnthropic、OpenAI、Gemini)でウェブ検索ツールを有効化",
"enable_web_search_tool": "サポートされているモデルAnthropic、OpenAI、Gemini、Grok)でウェブ検索ツールを有効化",
"end_tag_thinking_sections": "思考セクションの終了タグ",
"error_creating_audio_file": "音声ファイルの作成エラー: %v",
"error_creating_file": "ファイルの作成エラー: %v",
@ -352,6 +375,7 @@
"ollama_invalid_data_url_format": "無効なデータURLフォーマット",
"ollama_invalid_http_timeout_using_default": "無効なHTTPタイムアウト '%s': %v、デフォルトを使用します",
"ollama_invalid_num_ctx_in_request": "リクエストに無効な num_ctx があります: %v",
"ollama_invalid_request_body": "無効なリクエスト本文",
"ollama_no_content_from_upstream": "アップストリームの Fabric サーバーからコンテンツを受信しませんでした",
"ollama_num_ctx_exceeds_maximum": "num_ctx が許可される最大値 %d を超えています",
"ollama_num_ctx_invalid_type": "num_ctx は数値である必要があります。無効な型を受け取りました",
@ -365,6 +389,7 @@
"ollama_sse_buffer_limit": "SSE 行が 1MB バッファー制限を超えています - データ行が大きすぎます",
"ollama_upstream_non_2xx": "アップストリームの Fabric サーバーが非 2xx ステータス %d を返しました: %s",
"ollama_upstream_non_2xx_body_unreadable": "アップストリームの Fabric サーバーが非 2xx ステータス %d を返し、ボディを読み取れませんでした: %v",
"ollama_upstream_request_failed": "上流のFabricサーバーに到達できませんでした",
"ollama_upstream_returned_status": "アップストリームの Fabric サーバーがステータス %d を返しました",
"ollama_warning_no_content": "警告: アップストリームの Fabric サーバーからコンテンツを受信しませんでした",
"ollama_warning_parse_variables": "警告: options.variables を JSON として解析できませんでした: %v",
@ -385,6 +410,7 @@
"openai_image_failed_to_save_image": "画像を %s に保存できませんでした: %w",
"openai_image_saved_to": "画像の保存先: %s",
"openai_model_no_image_generation": "モデル '%s' は画像生成をサポートしていません。サポートされているモデル: %s",
"openai_models_rate_limited": "プロバイダー %s からのモデル取得でレート制限を超過しました。%s 秒後に再試行してください",
"openai_models_response_too_large": "プロバイダー %s からのモデルレスポンスが大きすぎます(>%d バイト)",
"openai_unable_to_parse_models_response": "モデルレスポンスの解析に失敗しました; 生のレスポンス: %s",
"openai_unexpected_status_code_read_error": "予期しないステータスコード: プロバイダー %s から %d (レスポンス本文の読み取りに失敗: %v)",
@ -400,6 +426,7 @@
"output_video_metadata": "動画メタデータを出力",
"path_to_yaml_config": "YAML設定ファイルのパス",
"pattern_not_found_list_available": "パターン '%s' が見つかりません。'fabric -l'で利用可能なパターンを確認してください",
"pattern_invalid_name": "無効なパターン名: %q",
"pattern_not_found_no_patterns": "パターン '%s' が見つかりません。\n\nパターンがインストールされていません解決するには:\n • 'fabric --setup'を実行してパターンを設定・ダウンロード\n • または'fabric -U'を実行してパターンをダウンロード/更新",
"pattern_variables_help": "パターン変数の値、例:-v=#role:expert -v=#points:30",
"patterns_cloning_repository": "リポジトリ %s をクローン中 (パス: %s)...\\n",
@ -473,6 +500,8 @@
"prefer_playlist_over_video": "URLに両方のIDが存在する場合、動画よりプレイリストを優先",
"print_context": "コンテキストを出力",
"print_current_version": "現在のバージョンを出力",
"print_metadata_to_stderr": "メタデータ(入力/出力トークン)を stderr に出力",
"print_pattern_contents": "指定したパターンの内容をターミナルに出力",
"print_session": "セッションを出力",
"register_new_extension": "設定ファイルパスから新しい拡張機能を登録",
"remove_registered_extension": "名前で登録済み拡張機能を削除",
@ -486,10 +515,12 @@
"send_desktop_notification": "コマンド完了時にデスクトップ通知を送信",
"serve_fabric_api_ollama_endpoints": "ollamaエンドポイント付きのFabric REST APIを提供",
"serve_fabric_rest_api": "Fabric REST APIを提供",
"server_api_key_required": "APIキーなしで非ループバックアドレス %s での提供を拒否しました。--api-key または FABRIC_API_KEY を設定するか、127.0.0.1:8080 のようなループバックアドレスにバインドしてください",
"server_chat_error": "エラー: %v",
"server_error_marshaling_response": "レスポンスのシリアライズエラー: %v",
"server_error_writing_response": "レスポンスの書き込みエラー: %v",
"server_invalid_request_format": "無効なリクエスト形式: %v",
"server_no_api_key_warning": "APIキー認証なしでREST APIサーバーを起動しています。セキュリティ上のリスクが生じる可能性があります。",
"sessions_creating_new": "新しいセッションを作成中: %s\n",
"set_debug_level": "デバッグレベルを設定0=オフ、1=基本、2=詳細、3=トレース、4=wire",
"set_frequency_penalty": "頻度ペナルティを設定",
@ -581,6 +612,7 @@
"spotify_show_name_label": "**番組**: %s",
"spotify_title_label": "**タイトル**: %s",
"spotify_total_episodes_label": "**エピソード合計**: %d",
"spotify_url_help": "メタデータを取得してチャットに送信する Spotify のポッドキャストまたはエピソードの URL",
"spotify_url_label": "**URL**: %s",
"start_tag_thinking_sections": "思考セクションの開始タグ",
"storage_error_delete": "%sを削除できませんでした: %v",
@ -592,6 +624,7 @@
"storage_error_save": "%sを保存できませんでした: %v",
"storage_error_stat_entry": "エントリ%sの情報を取得できませんでした: %v",
"storage_error_unmarshal": "%sをデシリアライズできませんでした: %s",
"storage_invalid_name": "無効な名前: %q",
"strategies_available_header": "利用可能な戦略:",
"strategies_cloning_repository": "リポジトリ %s をクローン中 (パス: %s)...\\n",
"strategies_download_success": "✅ 戦略を %s に正常にダウンロードしてインストールしました\\n",
@ -610,11 +643,11 @@
"strategy_path_traversal": "戦略名 %q が戦略ディレクトリの外部に解決されます",
"stream_help": "ストリーミング",
"suppress_thinking_tags": "思考タグで囲まれたテキストを抑制",
"template_datetime_error_invalid_number": "invalid number in relative time: %q",
"template_datetime_error_invalid_relative_format": "invalid relative time format",
"template_datetime_error_invalid_unit": "invalid time unit: %q",
"template_datetime_error_relative_requires_value": "relative time requires a value",
"template_datetime_error_unknown_operation": "datetime: unknown operation %q",
"template_datetime_error_invalid_number": "相対時間の数値が無効です: %q",
"template_datetime_error_invalid_relative_format": "相対時間の形式が無効です",
"template_datetime_error_invalid_unit": "時間単位が無効です: %q",
"template_datetime_error_relative_requires_value": "相対時間には値が必要です",
"template_datetime_error_unknown_operation": "datetime: 不明な操作 %q",
"template_extension_error": "拡張機能%sエラー: %v",
"template_file_error_expand_home_dir": "file: ホームディレクトリを展開できませんでした: %v",
"template_file_error_invalid_line_count": "file: 無効な行数 %q",
@ -631,7 +664,7 @@
"template_file_log_cleaned_path": "File: 正規化後のパス %q",
"template_file_log_exists_for_path": "File: パス %q の exists=%v",
"template_file_log_modified_for_path": "File: パス %q の modified=%q",
"template_file_log_operation_value": "File: operation=%q value=%q",
"template_file_log_operation_value": "ファイル: operation=%q value=%q",
"template_file_log_read_bytes": "File: %d バイトを読み取りました",
"template_file_log_read_total_return_last": "File: 合計 %d 行を読み取り、最後の %d 行を返します",
"template_file_log_reading_last_lines": "File: %q から最後の %d 行を読み取り中",
@ -643,12 +676,12 @@
"template_missing_required_variable": "必須変数が不足しています: %s",
"template_plugin_error": "プラグイン%sエラー: %v",
"template_processing_stuck": "テンプレート処理が停止 - 無限ループの可能性",
"template_sys_error_env_requires_var": "env: variable name required",
"template_sys_error_home": "failed to get home directory: %v",
"template_sys_error_hostname": "failed to get hostname: %v",
"template_sys_error_pwd": "failed to get working directory: %v",
"template_sys_error_unknown_operation": "sys: unknown operation %q",
"template_sys_error_user": "failed to get current user: %v",
"template_sys_error_env_requires_var": "env: 変数名が必要です",
"template_sys_error_home": "ホームディレクトリの取得に失敗しました: %v",
"template_sys_error_hostname": "ホスト名の取得に失敗しました: %v",
"template_sys_error_pwd": "作業ディレクトリの取得に失敗しました: %v",
"template_sys_error_unknown_operation": "sys: 不明な操作 %q",
"template_sys_error_user": "現在のユーザーの取得に失敗しました: %v",
"template_text_empty_input": "テキスト: 操作%qに対する入力が空です",
"template_text_unknown_operation": "テキスト: 不明なテキスト操作%q (対応: upper, lower, title, trim)",
"template_unknown_plugin_namespace": "不明なプラグイン名前空間: %s",
@ -698,15 +731,21 @@
"youtube_error_getting_video_details": "動画の詳細取得エラー: %v",
"youtube_error_parsing_duration": "動画の長さ解析エラー: %v",
"youtube_error_saving_csv": "動画のCSV保存エラー: %v",
"youtube_extract_visual_data_help": "OCR と FFmpeg を使用して動画から視覚データを抽出",
"youtube_failed_create_temp_dir": "一時ディレクトリの作成に失敗しました: %v",
"youtube_failed_fetch_comments": "コメントの取得に失敗しました: %v",
"youtube_failed_get_stream_url": "yt-dlp 経由でストリーム URL を取得できませんでした: %v",
"youtube_failed_parse_http_stream_url": "yt-dlp の出力から有効な HTTP ストリーム URL を解析できませんでした",
"youtube_failed_walk_directory": "ディレクトリの走査に失敗しました: %v",
"youtube_ffmpeg_frame_extraction_failed": "ffmpeg によるフレーム抽出に失敗しました: %v, 出力: %s",
"youtube_ffmpeg_required_visual_extraction": "視覚抽出には ffmpeg が必要ですが、PATH に見つかりません",
"youtube_invalid_duration_string": "無効な長さ文字列: %s",
"youtube_invalid_seconds_format": "無効な秒形式 %q: %w",
"youtube_invalid_timestamp_format": "無効なタイムスタンプ形式: %s",
"youtube_invalid_url": "無効なYouTube URL、動画またはプレイリストIDを取得できません: '%s'",
"youtube_invalid_ytdlp_arguments": "無効なyt-dlp引数: %v",
"youtube_label": "YouTube",
"youtube_no_clear_text_visual_frames": "動画の視覚フレーム内に判読可能なテキストが見つかりませんでした",
"youtube_no_transcript_content": "VTTファイルにトランスクリプトコンテンツが見つかりません",
"youtube_no_url_provided": "YouTube URLが提供されていません",
"youtube_no_video_found_with_id": "IDの動画が見つかりません: %s",
@ -717,9 +756,15 @@
"youtube_playlist_saved_to": "プレイリストが %s に保存されました",
"youtube_rate_limit_exceeded": "YouTubeのレート制限を超えました。後でもう一度試すか、'--sleep-requests 1'のような異なるyt-dlp引数を使用してリクエストを遅くしてください。",
"youtube_setup_description": "YouTube - 動画の転写(yt-dlp経由)とコメント/メタデータ(YouTube API経由)を取得",
"youtube_tesseract_frame_failed": "フレーム %d で tesseract が失敗しました: %v, stderr: %s",
"youtube_tesseract_required_visual_extraction": "視覚抽出には tesseract が必要ですが、PATH に見つかりません",
"youtube_url_help": "YouTube動画またはプレイリスト\"URL\"から転写、コメントを取得してチャットに送信、またはコンソールに出力して出力ファイルに保存",
"youtube_url_is_playlist_not_video": "URLはプレイリストであり、動画ではありません",
"youtube_video_id_title_header": "動画ID: タイトル",
"youtube_visual_fps_help": "シーン検出の代わりに、1 秒あたりの特定フレーム数を抽出",
"youtube_visual_frame_cue": "[視覚フレームキュー]",
"youtube_visual_sensitivity_help": "FFmpeg のシーン検出の許容度 (0.0 - 1.0)",
"youtube_ytdlp_not_found": "PATHにyt-dlpが見つかりません。YouTubeトランスクリプト機能を使用するにはyt-dlpをインストールしてください",
"youtube_ytdlp_required_visual_extraction": "視覚抽出には yt-dlp が必要ですが、PATH に見つかりません",
"youtube_ytdlp_stderr_error": "yt-dlp stderrの読み取りエラー"
}

View file

@ -54,7 +54,7 @@
"bedrock_aws_access_key_label": "Wprowadź swój AWS Access Key ID (pozostaw puste, aby użyć łańcucha uwierzytelniania AWS)",
"bedrock_aws_region_label": "Region AWS",
"bedrock_aws_secret_key_label": "Wprowadź swój AWS Secret Access Key (pozostaw puste, aby użyć łańcucha uwierzytelniania AWS)",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"bedrock_client_not_initialized": "klient Bedrock nie jest zainicjalizowany — uruchom 'fabric --setup', aby skonfigurować",
"bedrock_converse_failed": "bedrock converse nie powiodło się dla modelu %s: %w",
"bedrock_conversestream_failed": "bedrock conversestream nie powiodło się dla modelu %s: %w",
"bedrock_empty_response_content": "pusta zawartość odpowiedzi",
@ -67,21 +67,21 @@
"bedrock_listmodels_fallback": "API ListModels Bedrock nie powiodło się, używam statycznej listy zapasowej",
"bedrock_panic_sendstream": "panika w SendStream: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",
"bedrock_setup_auth_prompt": "Enter 1 or 2",
"bedrock_setup_choose_auth_method": " Choose authentication method:",
"bedrock_setup_choose_model": " Choose default model (unprefixed IDs work in any region; us./eu./ap. are region-specific):",
"bedrock_setup_choose_region": " Choose AWS Region:",
"bedrock_setup_auth_option_apikey": " [1] Klucz API Bedrock / token ABSK (zalecane — taki sam jak w Claude Code)",
"bedrock_setup_auth_prompt": "Wprowadź 1 lub 2",
"bedrock_setup_choose_auth_method": " Wybierz metodę uwierzytelniania:",
"bedrock_setup_choose_model": " Wybierz model domyślny (identyfikatory bez prefiksu działają w każdym regionie; us./eu./ap. są specyficzne dla regionu):",
"bedrock_setup_choose_region": " Wybierz region AWS:",
"bedrock_setup_header": "[Bedrock]",
"bedrock_setup_invalid_auth_selection": "invalid selection: %s (enter 1 or 2)",
"bedrock_setup_model_custom_prompt": "Enter model ID (e.g. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Enter a different model ID",
"bedrock_setup_model_prompt": "Enter model number or 0 to type your own",
"bedrock_setup_region_custom_prompt": "Enter custom AWS region (e.g. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Enter a different region",
"bedrock_setup_region_prompt": "Enter region number or 0 for custom",
"bedrock_setup_selected_model": " ✓ Selected model: %s",
"bedrock_setup_use_with": " Use with: fabric -m %s -V Bedrock",
"bedrock_setup_invalid_auth_selection": "nieprawidłowy wybór: %s (wprowadź 1 lub 2)",
"bedrock_setup_model_custom_prompt": "Wprowadź identyfikator modelu (np. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Wprowadź inny identyfikator modelu",
"bedrock_setup_model_prompt": "Wprowadź numer modelu lub 0, aby podać własny",
"bedrock_setup_region_custom_prompt": "Wprowadź własny region AWS (np. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Wprowadź inny region",
"bedrock_setup_region_prompt": "Wprowadź numer regionu lub 0, aby podać własny",
"bedrock_setup_selected_model": " ✓ Wybrany model: %s",
"bedrock_setup_use_with": " Użycie: fabric -m %s -V Bedrock",
"bedrock_unable_load_aws_config": "nie można załadować konfiguracji AWS: %w",
"bedrock_unable_load_aws_config_with_region": "nie można załadować konfiguracji AWS dla regionu %s: %w",
"bedrock_unexpected_content_block_type": "nieoczekiwany typ bloku zawartości: %T",
@ -110,21 +110,43 @@
"choose_pattern_from_available": "Wybierz wzorzec spośród dostępnych wzorców",
"choose_session_from_available": "Wybierz sesję spośród dostępnych sesji",
"choose_strategy_from_available": "Wybierz strategię spośród dostępnych strategii",
"codex_api_base_url_question": "Podaj bazowy URL API Codex",
"codex_auth_base_url_invalid": "Nieprawidłowy bazowy URL uwierzytelniania Codex: %w",
"codex_auth_base_url_question": "Podaj bazowy URL OAuth Codex",
"codex_browser_open_fallback": "Jeśli przeglądarka się nie otworzyła, przejdź pod ten URL, aby się uwierzytelnić:",
"codex_decode_models_response_failed": "Nie udało się zdekodować odpowiedzi modeli Codex: %w",
"codex_decode_refresh_response_failed": "Nie udało się zdekodować odpowiedzi odświeżonego tokenu Codex: %w",
"codex_decode_token_response_failed": "Nie udało się zdekodować odpowiedzi wymiany tokenu Codex: %w",
"codex_image_file_not_supported": "Dostawca Codex nie obsługuje --image-file. Użyj załącznika graficznego.",
"codex_login_account_changed": "Logowanie Codex jest powiązane z innym kontem ChatGPT niż zapisana konfiguracja. Uruchom ponownie 'fabric --setup'.",
"codex_login_completed": "Logowanie Codex zakończone",
"codex_login_failed": "Logowanie Codex nie powiodło się: %s",
"codex_login_invalid": "Twoje logowanie Codex nie jest już ważne. Uruchom ponownie 'fabric --setup'.",
"codex_login_missing_account_claim": "Logowanie Codex nie zawierało identyfikatora konta ChatGPT. Ten stan logowania nie jest obsługiwany.",
"codex_login_missing_auth_code": "Logowanie Codex nie zwróciło kodu autoryzacji.",
"codex_login_missing_tokens": "Logowanie Codex nie zwróciło wymaganych tokenów dostępu i odświeżania.",
"codex_login_refresh_failed": "Nie udało się odświeżyć logowania Codex. Uruchom ponownie 'fabric --setup'.",
"codex_login_return_to_fabric": "Wróć do Fabric.",
"codex_login_revoked": "Logowanie Codex wygasło lub zostało unieważnione. Uruchom ponownie 'fabric --setup'.",
"codex_login_server_stopped": "Serwer zwrotny logowania Codex zatrzymał się przed zakończeniem uwierzytelniania.",
"codex_login_state_mismatch": "Nie można zweryfikować logowania Codex, ponieważ stan OAuth nie był zgodny.",
"codex_login_timed_out": "Upłynął limit czasu logowania Codex przed zakończeniem uwierzytelniania.",
"codex_oauth_missing_auth_code": "Brak kodu autoryzacji",
"codex_oauth_random_state_failed": "Nie udało się wygenerować bezpiecznego losowego stanu OAuth: %w",
"codex_oauth_server_start_failed": "Nie udało się uruchomić lokalnego serwera zwrotnego OAuth: %w",
"codex_oauth_state_mismatch": "Stan nie jest zgodny",
"codex_provider_error": "błąd dostawcy Codex (status %d): %s",
"codex_refresh_failed_status": "Nie udało się odświeżyć logowania Codex (status %d)",
"codex_refresh_login_failed": "Nie udało się odświeżyć logowania Codex: %w",
"codex_refresh_token_required": "Token odświeżania Codex jest wymagany. Uruchom ponownie 'fabric --setup'.",
"codex_replay_body_unavailable": "Treść żądania nie może być odtworzona dla ponownej próby uwierzytelnienia Codex",
"codex_request_failed": "Żądanie Codex nie powiodło się: %w",
"codex_request_failed_status": "Żądanie Codex nie powiodło się ze statusem %d",
"codex_starting_browser_login": "Uruchamianie logowania OpenAI przez przeglądarkę dla Codex.",
"codex_token_exchange_failed": "Wymiana tokenu Codex nie powiodła się: %w",
"codex_token_persist_failed": "nie udało się zapisać logowania Codex: %w",
"codex_token_refresh_missing_access_token": "Odświeżenie tokenu Codex nie zwróciło tokenu dostępu.",
"codex_usage_limit_reached": "Osiągnięto limit użycia Codex",
"command_completed_successfully": "Polecenie zakończone pomyślnie",
"compression_level_jpeg_webp": "Poziom kompresji 0-100 dla formatów JPEG/WebP (domyślnie: nie ustawiony)",
"config_file_not_found": "plik konfiguracyjny nie został znaleziony: %s",
@ -151,6 +173,7 @@
"custom_patterns_setup_description": "Niestandardowe wzorce - Ustaw katalog dla swoich niestandardowych wzorców",
"custom_patterns_warning_create_directory": "Ostrzeżenie: Nie można utworzyć katalogu niestandardowych wzorców %s: %v\n",
"db_error_loading_env_file": "błąd podczas ładowania pliku .env: %w",
"db_error_updating_env_file": "błąd podczas aktualizacji pliku .env: %w",
"defaults_model_context_length_question": "Podaj długość kontekstu modelu",
"defaults_model_question": "Podaj indeks lub nazwę domyślnego modelu",
"defaults_setup_description": "Domyślny dostawca AI i model",
@ -161,7 +184,7 @@
"digitalocean_models_request_failed_with_status": "Żądanie modeli DigitalOcean nie powiodło się ze statusem %d: %s",
"disable_openai_responses_api": "Wyłącz API odpowiedzi OpenAI (domyślnie: false)",
"disable_pattern_variable_replacement": "Wyłącz zastępowanie zmiennych wzorców",
"enable_web_search_tool": "Włącz narzędzie wyszukiwania internetowego dla obsługiwanych modeli (Anthropic, OpenAI, Gemini)",
"enable_web_search_tool": "Włącz narzędzie wyszukiwania internetowego dla obsługiwanych modeli (Anthropic, OpenAI, Gemini, Grok)",
"end_tag_thinking_sections": "Tag końcowy dla sekcji myślenia",
"error_creating_audio_file": "błąd podczas tworzenia pliku audio: %v",
"error_creating_file": "błąd podczas tworzenia pliku: %v",
@ -352,6 +375,7 @@
"ollama_invalid_data_url_format": "nieprawidłowy format data URL",
"ollama_invalid_http_timeout_using_default": "nieprawidłowy limit czasu HTTP '%s': %v, używam domyślnego",
"ollama_invalid_num_ctx_in_request": "nieprawidłowa wartość num_ctx w żądaniu: %v",
"ollama_invalid_request_body": "nieprawidłowa treść żądania",
"ollama_no_content_from_upstream": "nie odebrano zawartości z upstream serwera fabric",
"ollama_num_ctx_exceeds_maximum": "num_ctx przekracza maksymalną dozwoloną wartość %d",
"ollama_num_ctx_invalid_type": "num_ctx musi być liczbą, podano nieprawidłowy typ",
@ -365,6 +389,7 @@
"ollama_sse_buffer_limit": "Linia SSE przekracza limit bufora 1MB - linia danych zbyt duża",
"ollama_upstream_non_2xx": "Upstream serwer fabric zwrócił status inny niż 2xx %d: %s",
"ollama_upstream_non_2xx_body_unreadable": "Upstream serwer fabric zwrócił status inny niż 2xx %d i nie można odczytać treści: %v",
"ollama_upstream_request_failed": "nie można połączyć się z serwerem nadrzędnym Fabric",
"ollama_upstream_returned_status": "upstream serwer fabric zwrócił status %d",
"ollama_warning_no_content": "Ostrzeżenie: nie odebrano zawartości z upstream serwera fabric",
"ollama_warning_parse_variables": "Ostrzeżenie: nie udało się przetworzyć options.variables jako JSON: %v",
@ -385,6 +410,7 @@
"openai_image_failed_to_save_image": "nie udało się zapisać obrazu do %s: %w",
"openai_image_saved_to": "Obraz zapisano do: %s",
"openai_model_no_image_generation": "model '%s' nie obsługuje generowania obrazów. Obsługiwane modele: %s",
"openai_models_rate_limited": "przekroczono limit żądań podczas pobierania modeli od dostawcy %s; spróbuj ponownie za %s sekund",
"openai_models_response_too_large": "odpowiedź z modelami zbyt duża od dostawcy %s (>%d bajtów)",
"openai_unable_to_parse_models_response": "nie można przetworzyć odpowiedzi z modelami; surowa odpowiedź: %s",
"openai_unexpected_status_code_read_error": "nieoczekiwany kod statusu: %d od dostawcy %s (nie udało się odczytać treści odpowiedzi: %v)",
@ -400,6 +426,7 @@
"output_video_metadata": "Wyprowadź metadane wideo",
"path_to_yaml_config": "Ścieżka do pliku konfiguracyjnego YAML",
"pattern_not_found_list_available": "wzorzec '%s' nie został znaleziony. Uruchom 'fabric -l', aby zobaczyć dostępne wzorce",
"pattern_invalid_name": "nieprawidłowa nazwa wzorca: %q",
"pattern_not_found_no_patterns": "wzorzec '%s' nie został znaleziony.\n\nNie zainstalowano żadnych wzorców! Aby to naprawić:\n • Uruchom 'fabric --setup', aby skonfigurować i pobrać wzorce\n • Lub uruchom 'fabric -U', aby bezpośrednio pobrać/zaktualizować wzorce",
"pattern_variables_help": "Wartości dla zmiennych wzorców, np. -v=#role:ekspert -v=#points:30",
"patterns_cloning_repository": "Klonowanie repozytorium %s (ścieżka: %s)...\n",
@ -473,6 +500,8 @@
"prefer_playlist_over_video": "Preferuj playlistę nad filmem, jeśli oba identyfikatory są obecne w URL",
"print_context": "Wydrukuj kontekst",
"print_current_version": "Wydrukuj bieżącą wersję",
"print_metadata_to_stderr": "Wypisz metadane (tokeny wejściowe/wyjściowe) na stderr",
"print_pattern_contents": "Wypisz zawartość wskazanego wzorca w terminalu",
"print_session": "Wydrukuj sesję",
"register_new_extension": "Zarejestruj nowe rozszerzenie z pliku konfiguracyjnego",
"remove_registered_extension": "Usuń zarejestrowane rozszerzenie według nazwy",
@ -486,10 +515,12 @@
"send_desktop_notification": "Wyślij powiadomienie pulpitu po zakończeniu polecenia",
"serve_fabric_api_ollama_endpoints": "Uruchom fabric Rest API z endpointami ollama",
"serve_fabric_rest_api": "Uruchom fabric Rest API",
"server_api_key_required": "odmowa udostępniania na adresie %s spoza pętli zwrotnej bez klucza API: ustaw --api-key lub FABRIC_API_KEY, albo powiąż adres pętli zwrotnej, np. 127.0.0.1:8080",
"server_chat_error": "Błąd: %v",
"server_error_marshaling_response": "błąd podczas serializacji odpowiedzi: %v",
"server_error_writing_response": "błąd podczas zapisywania odpowiedzi: %v",
"server_invalid_request_format": "nieprawidłowy format żądania: %v",
"server_no_api_key_warning": "Uruchamianie serwera REST API bez uwierzytelniania kluczem API. Może to stwarzać zagrożenia bezpieczeństwa.",
"sessions_creating_new": "Tworzenie nowej sesji: %s\n",
"set_debug_level": "Ustaw poziom debugowania (0=wyłączone, 1=podstawowe, 2=szczegółowe, 3=śledzenie, 4=surowe)",
"set_frequency_penalty": "Ustaw karę częstotliwości",
@ -581,6 +612,7 @@
"spotify_show_name_label": "**Program**: %s",
"spotify_title_label": "**Tytuł**: %s",
"spotify_total_episodes_label": "**Łączna liczba odcinków**: %d",
"spotify_url_help": "URL podcastu lub odcinka Spotify do pobrania metadanych i wysłania do czatu",
"spotify_url_label": "**URL**: %s",
"start_tag_thinking_sections": "Tag początkowy dla sekcji myślenia",
"storage_error_delete": "nie można usunąć %s: %v",
@ -592,6 +624,7 @@
"storage_error_save": "nie można zapisać %s: %v",
"storage_error_stat_entry": "nie można pobrać informacji o wpisie %s: %v",
"storage_error_unmarshal": "nie można deserializować %s: %s",
"storage_invalid_name": "nieprawidłowa nazwa: %q",
"strategies_available_header": "Dostępne strategie:",
"strategies_cloning_repository": "Klonowanie repozytorium %s (ścieżka: %s)...\n",
"strategies_download_success": "✅ Pomyślnie pobrano i zainstalowano strategie w %s\n",
@ -631,7 +664,7 @@
"template_file_log_cleaned_path": "File: oczyszczona ścieżka %q",
"template_file_log_exists_for_path": "File: exists=%v dla ścieżki %q",
"template_file_log_modified_for_path": "File: modified=%q dla ścieżki %q",
"template_file_log_operation_value": "File: operation=%q value=%q",
"template_file_log_operation_value": "Plik: operation=%q value=%q",
"template_file_log_read_bytes": "File: odczytano %d bajtów",
"template_file_log_read_total_return_last": "File: odczytano łącznie %d linii, zwracam ostatnie %d",
"template_file_log_reading_last_lines": "File: odczytywanie ostatnich %d linii z %q",
@ -698,15 +731,21 @@
"youtube_error_getting_video_details": "błąd podczas pobierania szczegółów wideo: %v",
"youtube_error_parsing_duration": "błąd podczas przetwarzania czasu trwania wideo: %v",
"youtube_error_saving_csv": "błąd podczas zapisywania filmów do CSV: %v",
"youtube_extract_visual_data_help": "Wyodrębnij dane wizualne z filmu przy użyciu OCR i FFmpeg",
"youtube_failed_create_temp_dir": "nie udało się utworzyć katalogu tymczasowego: %v",
"youtube_failed_fetch_comments": "nie udało się pobrać komentarzy: %v",
"youtube_failed_get_stream_url": "nie udało się pobrać adresu URL strumienia przez yt-dlp: %v",
"youtube_failed_parse_http_stream_url": "nie udało się przetworzyć prawidłowego adresu URL strumienia HTTP z danych wyjściowych yt-dlp",
"youtube_failed_walk_directory": "nie udało się przejść przez katalog: %v",
"youtube_ffmpeg_frame_extraction_failed": "ekstrakcja klatek przez ffmpeg nie powiodła się: %v, wyjście: %s",
"youtube_ffmpeg_required_visual_extraction": "ffmpeg jest wymagany do ekstrakcji wizualnej, ale nie został znaleziony w PATH",
"youtube_invalid_duration_string": "nieprawidłowy ciąg czasu trwania: %s",
"youtube_invalid_seconds_format": "nieprawidłowy format sekund %q: %w",
"youtube_invalid_timestamp_format": "nieprawidłowy format znacznika czasu: %s",
"youtube_invalid_url": "nieprawidłowy URL YouTube, nie można uzyskać identyfikatora wideo lub playlisty: '%s'",
"youtube_invalid_ytdlp_arguments": "nieprawidłowe argumenty yt-dlp: %v",
"youtube_label": "YouTube",
"youtube_no_clear_text_visual_frames": "nie znaleziono czytelnego tekstu w wizualnych klatkach wideo",
"youtube_no_transcript_content": "nie znaleziono zawartości transkrypcji w pliku VTT",
"youtube_no_url_provided": "Nie podano URL YouTube",
"youtube_no_video_found_with_id": "nie znaleziono wideo o ID: %s",
@ -717,9 +756,15 @@
"youtube_playlist_saved_to": "Playlista zapisana do %s",
"youtube_rate_limit_exceeded": "Przekroczono limit żądań YouTube. Spróbuj ponownie później lub użyj innych argumentów yt-dlp, np. '--sleep-requests 1', aby spowolnić żądania.",
"youtube_setup_description": "YouTube - do pobierania transkrypcji wideo (przez yt-dlp) i komentarzy/metadanych (przez API YouTube)",
"youtube_tesseract_frame_failed": "tesseract nie powiódł się dla klatki %d: %v, stderr: %s",
"youtube_tesseract_required_visual_extraction": "tesseract jest wymagany do ekstrakcji wizualnej, ale nie został znaleziony w PATH",
"youtube_url_help": "URL wideo lub playlisty YouTube do pobrania transkrypcji, komentarzy i wysłania do czatu lub wypisania na konsolę i zapisania w pliku wyjściowym",
"youtube_url_is_playlist_not_video": "URL jest playlistą, nie filmem",
"youtube_video_id_title_header": "ID wideo: Tytuł",
"youtube_visual_fps_help": "Wyodrębnij określoną liczbę klatek na sekundę zamiast używać wykrywania scen",
"youtube_visual_frame_cue": "[ZNACZNIK KLATKI WIZUALNEJ]",
"youtube_visual_sensitivity_help": "Czułość wykrywania scen FFmpeg (0.0 - 1.0)",
"youtube_ytdlp_not_found": "nie znaleziono yt-dlp w PATH. Zainstaluj yt-dlp, aby korzystać z funkcji transkrypcji YouTube",
"youtube_ytdlp_required_visual_extraction": "yt-dlp jest wymagany do ekstrakcji wizualnej, ale nie został znaleziony w PATH",
"youtube_ytdlp_stderr_error": "błąd podczas odczytu stderr yt-dlp"
}

View file

@ -54,7 +54,7 @@
"bedrock_aws_access_key_label": "Digite seu AWS Access Key ID (deixe vazio para usar a cadeia de credenciais AWS)",
"bedrock_aws_region_label": "Regiao AWS",
"bedrock_aws_secret_key_label": "Digite seu AWS Secret Access Key (deixe vazio para usar a cadeia de credenciais AWS)",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"bedrock_client_not_initialized": "cliente Bedrock não inicializado — execute 'fabric --setup' para configurar",
"bedrock_converse_failed": "bedrock converse falhou para o modelo %s: %w",
"bedrock_conversestream_failed": "bedrock conversestream falhou para o modelo %s: %w",
"bedrock_empty_response_content": "conteudo de resposta vazio",
@ -67,21 +67,21 @@
"bedrock_listmodels_fallback": "API ListModels do Bedrock falhou, usando lista estática de fallback",
"bedrock_panic_sendstream": "panico no SendStream: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",
"bedrock_setup_auth_prompt": "Enter 1 or 2",
"bedrock_setup_choose_auth_method": " Choose authentication method:",
"bedrock_setup_choose_model": " Choose default model (unprefixed IDs work in any region; us./eu./ap. are region-specific):",
"bedrock_setup_choose_region": " Choose AWS Region:",
"bedrock_setup_auth_option_apikey": " [1] Chave API Bedrock / token ABSK (recomendado — a mesma usada pelo Claude Code)",
"bedrock_setup_auth_prompt": "Digite 1 ou 2",
"bedrock_setup_choose_auth_method": " Escolha o método de autenticação:",
"bedrock_setup_choose_model": " Escolha o modelo padrão (IDs sem prefixo funcionam em qualquer região; us./eu./ap. são específicos de região):",
"bedrock_setup_choose_region": " Escolha a região AWS:",
"bedrock_setup_header": "[Bedrock]",
"bedrock_setup_invalid_auth_selection": "invalid selection: %s (enter 1 or 2)",
"bedrock_setup_model_custom_prompt": "Enter model ID (e.g. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Enter a different model ID",
"bedrock_setup_model_prompt": "Enter model number or 0 to type your own",
"bedrock_setup_region_custom_prompt": "Enter custom AWS region (e.g. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Enter a different region",
"bedrock_setup_region_prompt": "Enter region number or 0 for custom",
"bedrock_setup_selected_model": " ✓ Selected model: %s",
"bedrock_setup_use_with": " Use with: fabric -m %s -V Bedrock",
"bedrock_setup_invalid_auth_selection": "seleção inválida: %s (digite 1 ou 2)",
"bedrock_setup_model_custom_prompt": "Digite o ID do modelo (ex. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Digitar um ID de modelo diferente",
"bedrock_setup_model_prompt": "Digite o número do modelo ou 0 para informar o seu",
"bedrock_setup_region_custom_prompt": "Digite uma região AWS personalizada (ex. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Digitar uma região diferente",
"bedrock_setup_region_prompt": "Digite o número da região ou 0 para personalizar",
"bedrock_setup_selected_model": " ✓ Modelo selecionado: %s",
"bedrock_setup_use_with": " Uso: fabric -m %s -V Bedrock",
"bedrock_unable_load_aws_config": "nao foi possivel carregar a configuracao AWS: %w",
"bedrock_unable_load_aws_config_with_region": "nao foi possivel carregar a configuracao AWS com regiao %s: %w",
"bedrock_unexpected_content_block_type": "tipo de bloco de conteudo inesperado: %T",
@ -110,21 +110,43 @@
"choose_pattern_from_available": "Escolha um padrão entre os padrões disponíveis",
"choose_session_from_available": "Escolha uma sessão das sessões disponíveis",
"choose_strategy_from_available": "Escolher uma estratégia das estratégias disponíveis",
"codex_api_base_url_question": "Insira a URL base da API do Codex",
"codex_auth_base_url_invalid": "URL base de autenticação do Codex inválida: %w",
"codex_auth_base_url_question": "Insira a URL base de OAuth do Codex",
"codex_browser_open_fallback": "Se o navegador não abriu, navegue até esta URL para se autenticar:",
"codex_decode_models_response_failed": "Falha ao decodificar a resposta de modelos do Codex: %w",
"codex_decode_refresh_response_failed": "Falha ao decodificar a resposta do token atualizado do Codex: %w",
"codex_decode_token_response_failed": "Falha ao decodificar a resposta de troca de token do Codex: %w",
"codex_image_file_not_supported": "O provedor Codex não suporta --image-file. Use um anexo de imagem.",
"codex_login_account_changed": "O login do Codex está vinculado a uma conta ChatGPT diferente da configuração salva. Execute 'fabric --setup' novamente.",
"codex_login_completed": "Login do Codex concluído",
"codex_login_failed": "Falha no login do Codex: %s",
"codex_login_invalid": "Seu login do Codex não é mais válido. Execute 'fabric --setup' novamente.",
"codex_login_missing_account_claim": "O login do Codex não incluiu um ID de conta ChatGPT. Este estado de login não é suportado.",
"codex_login_missing_auth_code": "O login do Codex não retornou um código de autorização.",
"codex_login_missing_tokens": "O login do Codex não retornou os tokens de acesso e atualização necessários.",
"codex_login_refresh_failed": "Não foi possível atualizar o login do Codex. Execute 'fabric --setup' novamente.",
"codex_login_return_to_fabric": "Voltar para o Fabric.",
"codex_login_revoked": "O login do Codex expirou ou foi revogado. Execute 'fabric --setup' novamente.",
"codex_login_server_stopped": "O servidor de callback do login do Codex parou antes da autenticação ser concluída.",
"codex_login_state_mismatch": "O login do Codex não pôde ser verificado porque o estado OAuth não correspondeu.",
"codex_login_timed_out": "O login do Codex expirou antes da autenticação ser concluída.",
"codex_oauth_missing_auth_code": "Código de autorização ausente",
"codex_oauth_random_state_failed": "Falha ao gerar estado OAuth aleatório seguro: %w",
"codex_oauth_server_start_failed": "Falha ao iniciar o servidor local de callback OAuth: %w",
"codex_oauth_state_mismatch": "O estado não corresponde",
"codex_provider_error": "erro do fornecedor Codex (status %d): %s",
"codex_refresh_failed_status": "Falha ao atualizar o login do Codex (status %d)",
"codex_refresh_login_failed": "Falha ao atualizar o login do Codex: %w",
"codex_refresh_token_required": "O token de atualização do Codex é obrigatório. Execute 'fabric --setup' novamente.",
"codex_replay_body_unavailable": "O corpo da requisição não pode ser reproduzido para a tentativa de reautenticação do Codex",
"codex_request_failed": "A requisição do Codex falhou: %w",
"codex_request_failed_status": "A requisição do Codex falhou com status %d",
"codex_starting_browser_login": "Iniciando login OpenAI baseado em navegador para o Codex.",
"codex_token_exchange_failed": "A troca de token do Codex falhou: %w",
"codex_token_persist_failed": "falha ao persistir o login do Codex: %w",
"codex_token_refresh_missing_access_token": "A atualização do token do Codex não retornou um token de acesso.",
"codex_usage_limit_reached": "Limite de uso do Codex atingido",
"command_completed_successfully": "Comando concluído com sucesso",
"compression_level_jpeg_webp": "Nível de compressão 0-100 para formatos JPEG/WebP (padrão: não definido)",
"config_file_not_found": "arquivo de configuração não encontrado: %s",
@ -151,6 +173,7 @@
"custom_patterns_setup_description": "Padrões personalizados - Definir diretório para seus padrões personalizados",
"custom_patterns_warning_create_directory": "Aviso: Não foi possível criar o diretório de padrões personalizados %s: %v\n",
"db_error_loading_env_file": "erro ao carregar o arquivo .env: %w",
"db_error_updating_env_file": "erro ao atualizar o arquivo .env: %w",
"defaults_model_context_length_question": "Informe o comprimento do contexto do modelo",
"defaults_model_question": "Informe o índice ou o nome do seu modelo padrão",
"defaults_setup_description": "Provedor e modelo de IA padrão",
@ -161,7 +184,7 @@
"digitalocean_models_request_failed_with_status": "requisição de modelos do DigitalOcean falhou com status %d: %s",
"disable_openai_responses_api": "Desabilitar API OpenAI Responses (padrão: false)",
"disable_pattern_variable_replacement": "Desabilitar substituição de variáveis de padrão",
"enable_web_search_tool": "Habilitar ferramenta de busca web para modelos suportados (Anthropic, OpenAI, Gemini)",
"enable_web_search_tool": "Habilitar ferramenta de busca web para modelos suportados (Anthropic, OpenAI, Gemini, Grok)",
"end_tag_thinking_sections": "Tag final para seções de pensamento",
"error_creating_audio_file": "erro ao criar arquivo de áudio: %v",
"error_creating_file": "erro ao criar arquivo: %v",
@ -352,6 +375,7 @@
"ollama_invalid_data_url_format": "formato de URL de dados inválido",
"ollama_invalid_http_timeout_using_default": "Tempo limite HTTP inválido '%s': %v, usando o padrão",
"ollama_invalid_num_ctx_in_request": "num_ctx inválido na requisição: %v",
"ollama_invalid_request_body": "corpo de solicitação inválido",
"ollama_no_content_from_upstream": "nenhum conteúdo recebido do servidor Fabric upstream",
"ollama_num_ctx_exceeds_maximum": "num_ctx excede o valor máximo permitido de %d",
"ollama_num_ctx_invalid_type": "num_ctx deve ser um número, recebeu tipo inválido",
@ -365,6 +389,7 @@
"ollama_sse_buffer_limit": "Linha SSE excede limite de buffer de 1MB - linha de dados muito grande",
"ollama_upstream_non_2xx": "Servidor Fabric upstream retornou status não-2xx %d: %s",
"ollama_upstream_non_2xx_body_unreadable": "Servidor Fabric upstream retornou status não-2xx %d e o corpo não pôde ser lido: %v",
"ollama_upstream_request_failed": "falha ao conectar ao servidor Fabric upstream",
"ollama_upstream_returned_status": "servidor Fabric upstream retornou status %d",
"ollama_warning_no_content": "Aviso: nenhum conteúdo recebido do servidor Fabric upstream",
"ollama_warning_parse_variables": "Aviso: falha ao analisar options.variables como JSON: %v",
@ -385,6 +410,7 @@
"openai_image_failed_to_save_image": "falha ao salvar a imagem em %s: %w",
"openai_image_saved_to": "Imagem salva em: %s",
"openai_model_no_image_generation": "o modelo '%s' não suporta geração de imagens. Modelos suportados: %s",
"openai_models_rate_limited": "limite de taxa excedido ao buscar modelos do provedor %s; tente novamente após %s segundos",
"openai_models_response_too_large": "resposta de modelos muito grande do provedor %s (>%d bytes)",
"openai_unable_to_parse_models_response": "não foi possível analisar a resposta de modelos; resposta bruta: %s",
"openai_unexpected_status_code_read_error": "código de status inesperado: %d do provedor %s (falha ao ler corpo da resposta: %v)",
@ -400,6 +426,7 @@
"output_video_metadata": "Exibir metadados do vídeo",
"path_to_yaml_config": "Caminho para arquivo de configuração YAML",
"pattern_not_found_list_available": "padrão '%s' não encontrado. Execute 'fabric -l' para ver os padrões disponíveis",
"pattern_invalid_name": "nome de padrão inválido: %q",
"pattern_not_found_no_patterns": "padrão '%s' não encontrado.\n\nNenhum padrão instalado! Para resolver:\n • Execute 'fabric --setup' para configurar e baixar padrões\n • Ou execute 'fabric -U' para baixar/atualizar padrões diretamente",
"pattern_variables_help": "Valores para variáveis do padrão, ex. -v=#role:expert -v=#points:30",
"patterns_cloning_repository": "Clonando repositório %s (caminho: %s)...\\n",
@ -473,6 +500,8 @@
"prefer_playlist_over_video": "Preferir playlist ao vídeo se ambos os IDs estiverem presentes na URL",
"print_context": "Imprimir contexto",
"print_current_version": "Imprimir versão atual",
"print_metadata_to_stderr": "Imprimir metadados (tokens de entrada/saída) no stderr",
"print_pattern_contents": "Imprimir o conteúdo do padrão indicado no terminal",
"print_session": "Imprimir sessão",
"register_new_extension": "Registrar uma nova extensão do caminho do arquivo de configuração",
"remove_registered_extension": "Remover uma extensão registrada por nome",
@ -486,10 +515,12 @@
"send_desktop_notification": "Enviar notificação desktop quando o comando for concluído",
"serve_fabric_api_ollama_endpoints": "Servir a API REST do Fabric com endpoints ollama",
"serve_fabric_rest_api": "Servir a API REST do Fabric",
"server_api_key_required": "recusando servir no endereço não loopback %s sem chave de API: defina --api-key ou FABRIC_API_KEY, ou vincule um endereço loopback como 127.0.0.1:8080",
"server_chat_error": "Erro: %v",
"server_error_marshaling_response": "erro ao serializar resposta: %v",
"server_error_writing_response": "erro ao escrever resposta: %v",
"server_invalid_request_format": "formato de solicitação inválido: %v",
"server_no_api_key_warning": "Iniciando o servidor da API REST sem autenticação por chave de API. Isso pode representar riscos de segurança.",
"sessions_creating_new": "Criando nova sessão: %s\n",
"set_debug_level": "Definir nível de debug (0=desligado, 1=básico, 2=detalhado, 3=rastreamento, 4=wire)",
"set_frequency_penalty": "Definir penalidade de frequência",
@ -581,6 +612,7 @@
"spotify_show_name_label": "**Programa**: %s",
"spotify_title_label": "**Título**: %s",
"spotify_total_episodes_label": "**Total de episódios**: %d",
"spotify_url_help": "URL de podcast ou episódio do Spotify para obter metadados e enviar ao chat",
"spotify_url_label": "**URL**: %s",
"start_tag_thinking_sections": "Tag inicial para seções de pensamento",
"storage_error_delete": "Não foi possível excluir %s: %v",
@ -592,6 +624,7 @@
"storage_error_save": "Não foi possível salvar %s: %v",
"storage_error_stat_entry": "Não foi possível obter informações da entrada %s: %v",
"storage_error_unmarshal": "Não foi possível desserializar %s: %s",
"storage_invalid_name": "nome inválido: %q",
"strategies_available_header": "Estratégias disponíveis:",
"strategies_cloning_repository": "Clonando repositório %s (caminho: %s)...\\n",
"strategies_download_success": "✅ Estratégias baixadas e instaladas com sucesso em %s\\n",
@ -610,11 +643,11 @@
"strategy_path_traversal": "o nome da estratégia %q resolve fora do diretório de estratégias",
"stream_help": "Streaming",
"suppress_thinking_tags": "Suprimir texto contido em tags de pensamento",
"template_datetime_error_invalid_number": "invalid number in relative time: %q",
"template_datetime_error_invalid_relative_format": "invalid relative time format",
"template_datetime_error_invalid_unit": "invalid time unit: %q",
"template_datetime_error_relative_requires_value": "relative time requires a value",
"template_datetime_error_unknown_operation": "datetime: unknown operation %q",
"template_datetime_error_invalid_number": "número inválido no tempo relativo: %q",
"template_datetime_error_invalid_relative_format": "formato de tempo relativo inválido",
"template_datetime_error_invalid_unit": "unidade de tempo inválida: %q",
"template_datetime_error_relative_requires_value": "o tempo relativo requer um valor",
"template_datetime_error_unknown_operation": "datetime: operação desconhecida %q",
"template_extension_error": "Erro na extensão %s: %v",
"template_file_error_expand_home_dir": "arquivo: não foi possível expandir o diretório home: %v",
"template_file_error_invalid_line_count": "arquivo: contagem de linhas inválida %q",
@ -643,12 +676,12 @@
"template_missing_required_variable": "Variável obrigatória ausente: %s",
"template_plugin_error": "Erro no plugin %s: %v",
"template_processing_stuck": "Processamento do modelo travado - possível loop infinito",
"template_sys_error_env_requires_var": "env: variable name required",
"template_sys_error_home": "failed to get home directory: %v",
"template_sys_error_hostname": "failed to get hostname: %v",
"template_sys_error_pwd": "failed to get working directory: %v",
"template_sys_error_unknown_operation": "sys: unknown operation %q",
"template_sys_error_user": "failed to get current user: %v",
"template_sys_error_env_requires_var": "env: nome da variável obrigatório",
"template_sys_error_home": "falha ao obter o diretório home: %v",
"template_sys_error_hostname": "falha ao obter o nome do host: %v",
"template_sys_error_pwd": "falha ao obter o diretório de trabalho: %v",
"template_sys_error_unknown_operation": "sys: operação desconhecida %q",
"template_sys_error_user": "falha ao obter o usuário atual: %v",
"template_text_empty_input": "Texto: entrada vazia para a operação %q",
"template_text_unknown_operation": "Texto: operação de texto desconhecida %q (suportadas: upper, lower, title, trim)",
"template_unknown_plugin_namespace": "Namespace de plugin desconhecido: %s",
@ -698,15 +731,21 @@
"youtube_error_getting_video_details": "erro ao obter detalhes do vídeo: %v",
"youtube_error_parsing_duration": "erro ao analisar a duração do vídeo: %v",
"youtube_error_saving_csv": "erro ao salvar vídeos em CSV: %v",
"youtube_extract_visual_data_help": "Extrair dados visuais do vídeo usando OCR e FFmpeg",
"youtube_failed_create_temp_dir": "falha ao criar diretório temporário: %v",
"youtube_failed_fetch_comments": "Falha ao buscar comentários: %v",
"youtube_failed_get_stream_url": "falha ao obter a URL do stream via yt-dlp: %v",
"youtube_failed_parse_http_stream_url": "falha ao analisar uma URL HTTP válida de stream da saída do yt-dlp",
"youtube_failed_walk_directory": "falha ao percorrer o diretório: %v",
"youtube_ffmpeg_frame_extraction_failed": "extração de quadros com ffmpeg falhou: %v, saída: %s",
"youtube_ffmpeg_required_visual_extraction": "ffmpeg é necessário para extração visual, mas não foi encontrado no PATH",
"youtube_invalid_duration_string": "string de duração inválida: %s",
"youtube_invalid_seconds_format": "formato de segundos inválido %q: %w",
"youtube_invalid_timestamp_format": "formato de timestamp inválido: %s",
"youtube_invalid_url": "URL do YouTube inválida, não é possível obter o ID do vídeo ou da playlist: '%s'",
"youtube_invalid_ytdlp_arguments": "argumentos do yt-dlp inválidos: %v",
"youtube_label": "YouTube",
"youtube_no_clear_text_visual_frames": "nenhum texto legível encontrado nos quadros visuais do vídeo",
"youtube_no_transcript_content": "nenhum conteúdo de transcrição encontrado no arquivo VTT",
"youtube_no_url_provided": "Nenhuma URL do YouTube fornecida",
"youtube_no_video_found_with_id": "nenhum vídeo encontrado com o ID: %s",
@ -717,9 +756,15 @@
"youtube_playlist_saved_to": "Lista de reprodução salva em %s",
"youtube_rate_limit_exceeded": "Limite de taxa do YouTube excedido. Tente novamente mais tarde ou use argumentos diferentes do yt-dlp como '--sleep-requests 1' para desacelerar as requisições.",
"youtube_setup_description": "YouTube - para obter transcrições de vídeo (via yt-dlp) e comentários/metadados (via API do YouTube)",
"youtube_tesseract_frame_failed": "tesseract falhou no quadro %d: %v, stderr: %s",
"youtube_tesseract_required_visual_extraction": "tesseract é necessário para extração visual, mas não foi encontrado no PATH",
"youtube_url_help": "Vídeo do YouTube ou URL da playlist para obter transcrição, comentários e enviar ao chat ou imprimir no console e armazenar no arquivo de saída",
"youtube_url_is_playlist_not_video": "a URL é uma playlist, não um vídeo",
"youtube_video_id_title_header": "VideoID: Título",
"youtube_visual_fps_help": "Extrair um número específico de quadros por segundo em vez de usar detecção de cenas",
"youtube_visual_frame_cue": "[MARCADOR DE QUADRO VISUAL]",
"youtube_visual_sensitivity_help": "Tolerância para detecção de cenas do FFmpeg (0.0 - 1.0)",
"youtube_ytdlp_not_found": "yt-dlp não encontrado no PATH. Por favor instale o yt-dlp para usar a funcionalidade de transcrição do YouTube",
"youtube_ytdlp_required_visual_extraction": "yt-dlp é necessário para extração visual, mas não foi encontrado no PATH",
"youtube_ytdlp_stderr_error": "erro ao ler stderr do yt-dlp"
}

View file

@ -54,7 +54,7 @@
"bedrock_aws_access_key_label": "Digite o seu AWS Access Key ID (deixe vazio para usar a cadeia de credenciais AWS)",
"bedrock_aws_region_label": "Regiao AWS",
"bedrock_aws_secret_key_label": "Digite o seu AWS Secret Access Key (deixe vazio para usar a cadeia de credenciais AWS)",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"bedrock_client_not_initialized": "cliente Bedrock não inicializado — execute 'fabric --setup' para configurar",
"bedrock_converse_failed": "bedrock converse falhou para o modelo %s: %w",
"bedrock_conversestream_failed": "bedrock conversestream falhou para o modelo %s: %w",
"bedrock_empty_response_content": "conteudo de resposta vazio",
@ -67,21 +67,21 @@
"bedrock_listmodels_fallback": "API ListModels do Bedrock falhou, usando lista estática de recurso",
"bedrock_panic_sendstream": "panico no SendStream: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",
"bedrock_setup_auth_prompt": "Enter 1 or 2",
"bedrock_setup_choose_auth_method": " Choose authentication method:",
"bedrock_setup_choose_model": " Choose default model (unprefixed IDs work in any region; us./eu./ap. are region-specific):",
"bedrock_setup_choose_region": " Choose AWS Region:",
"bedrock_setup_auth_option_apikey": " [1] Chave API Bedrock / token ABSK (recomendado — a mesma usada pelo Claude Code)",
"bedrock_setup_auth_prompt": "Introduza 1 ou 2",
"bedrock_setup_choose_auth_method": " Escolha o método de autenticação:",
"bedrock_setup_choose_model": " Escolha o modelo predefinido (IDs sem prefixo funcionam em qualquer região; us./eu./ap. são específicos de região):",
"bedrock_setup_choose_region": " Escolha a região AWS:",
"bedrock_setup_header": "[Bedrock]",
"bedrock_setup_invalid_auth_selection": "invalid selection: %s (enter 1 or 2)",
"bedrock_setup_model_custom_prompt": "Enter model ID (e.g. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Enter a different model ID",
"bedrock_setup_model_prompt": "Enter model number or 0 to type your own",
"bedrock_setup_region_custom_prompt": "Enter custom AWS region (e.g. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Enter a different region",
"bedrock_setup_region_prompt": "Enter region number or 0 for custom",
"bedrock_setup_selected_model": " ✓ Selected model: %s",
"bedrock_setup_use_with": " Use with: fabric -m %s -V Bedrock",
"bedrock_setup_invalid_auth_selection": "seleção inválida: %s (introduza 1 ou 2)",
"bedrock_setup_model_custom_prompt": "Introduza o ID do modelo (ex. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Introduzir um ID de modelo diferente",
"bedrock_setup_model_prompt": "Introduza o número do modelo ou 0 para indicar o seu",
"bedrock_setup_region_custom_prompt": "Introduza uma região AWS personalizada (ex. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Introduzir uma região diferente",
"bedrock_setup_region_prompt": "Introduza o número da região ou 0 para personalizar",
"bedrock_setup_selected_model": " ✓ Modelo selecionado: %s",
"bedrock_setup_use_with": " Uso: fabric -m %s -V Bedrock",
"bedrock_unable_load_aws_config": "nao foi possivel carregar a configuracao AWS: %w",
"bedrock_unable_load_aws_config_with_region": "nao foi possivel carregar a configuracao AWS com regiao %s: %w",
"bedrock_unexpected_content_block_type": "tipo de bloco de conteudo inesperado: %T",
@ -110,21 +110,43 @@
"choose_pattern_from_available": "Escolha um padrão dos padrões disponíveis",
"choose_session_from_available": "Escolha uma sessão das sessões disponíveis",
"choose_strategy_from_available": "Escolher uma estratégia das estratégias disponíveis",
"codex_api_base_url_question": "Introduza o URL base da API do Codex",
"codex_auth_base_url_invalid": "URL base de autenticação do Codex inválido: %w",
"codex_auth_base_url_question": "Introduza o URL base de OAuth do Codex",
"codex_browser_open_fallback": "Se o navegador não abriu, navegue até este URL para se autenticar:",
"codex_decode_models_response_failed": "Falha ao descodificar a resposta de modelos do Codex: %w",
"codex_decode_refresh_response_failed": "Falha ao descodificar a resposta do token atualizado do Codex: %w",
"codex_decode_token_response_failed": "Falha ao descodificar a resposta de troca de token do Codex: %w",
"codex_image_file_not_supported": "O fornecedor Codex não suporta --image-file. Utilize um anexo de imagem.",
"codex_login_account_changed": "O início de sessão do Codex está associado a uma conta ChatGPT diferente da configuração guardada. Execute 'fabric --setup' novamente.",
"codex_login_completed": "Início de sessão do Codex concluído",
"codex_login_failed": "Falha no início de sessão do Codex: %s",
"codex_login_invalid": "O seu início de sessão do Codex já não é válido. Execute 'fabric --setup' novamente.",
"codex_login_missing_account_claim": "O início de sessão do Codex não incluiu um ID de conta ChatGPT. Este estado de início de sessão não é suportado.",
"codex_login_missing_auth_code": "O início de sessão do Codex não devolveu um código de autorização.",
"codex_login_missing_tokens": "O início de sessão do Codex não devolveu os tokens de acesso e atualização necessários.",
"codex_login_refresh_failed": "Não foi possível atualizar o início de sessão do Codex. Execute 'fabric --setup' novamente.",
"codex_login_return_to_fabric": "Voltar ao Fabric.",
"codex_login_revoked": "O início de sessão do Codex expirou ou foi revogado. Execute 'fabric --setup' novamente.",
"codex_login_server_stopped": "O servidor de retorno do início de sessão do Codex parou antes da autenticação ser concluída.",
"codex_login_state_mismatch": "O início de sessão do Codex não pôde ser verificado porque o estado OAuth não correspondeu.",
"codex_login_timed_out": "O início de sessão do Codex expirou antes da autenticação ser concluída.",
"codex_oauth_missing_auth_code": "Código de autorização em falta",
"codex_oauth_random_state_failed": "Falha ao gerar estado OAuth aleatório seguro: %w",
"codex_oauth_server_start_failed": "Falha ao iniciar o servidor local de retorno OAuth: %w",
"codex_oauth_state_mismatch": "O estado não corresponde",
"codex_provider_error": "erro do fornecedor Codex (estado %d): %s",
"codex_refresh_failed_status": "Falha ao atualizar o início de sessão do Codex (estado %d)",
"codex_refresh_login_failed": "Falha ao atualizar o início de sessão do Codex: %w",
"codex_refresh_token_required": "O token de atualização do Codex é obrigatório. Execute 'fabric --setup' novamente.",
"codex_replay_body_unavailable": "O corpo do pedido não pode ser reproduzido para a tentativa de reautenticação do Codex",
"codex_request_failed": "O pedido do Codex falhou: %w",
"codex_request_failed_status": "O pedido do Codex falhou com estado %d",
"codex_starting_browser_login": "A iniciar início de sessão OpenAI baseado no navegador para o Codex.",
"codex_token_exchange_failed": "A troca de token do Codex falhou: %w",
"codex_token_persist_failed": "falha ao persistir o início de sessão do Codex: %w",
"codex_token_refresh_missing_access_token": "A atualização do token do Codex não devolveu um token de acesso.",
"codex_usage_limit_reached": "Limite de utilização do Codex atingido",
"command_completed_successfully": "Comando concluído com sucesso",
"compression_level_jpeg_webp": "Nível de compressão 0-100 para formatos JPEG/WebP (por omissão: não definido)",
"config_file_not_found": "ficheiro de configuração não encontrado: %s",
@ -151,6 +173,7 @@
"custom_patterns_setup_description": "Padrões personalizados - Definir diretório para os seus padrões personalizados",
"custom_patterns_warning_create_directory": "Aviso: Não foi possível criar o diretório de padrões personalizados %s: %v\n",
"db_error_loading_env_file": "erro ao carregar o ficheiro .env: %w",
"db_error_updating_env_file": "erro ao atualizar o ficheiro .env: %w",
"defaults_model_context_length_question": "Indique o comprimento do contexto do modelo",
"defaults_model_question": "Indique o índice ou o nome do seu modelo padrão",
"defaults_setup_description": "Fornecedor e modelo de IA padrão",
@ -161,7 +184,7 @@
"digitalocean_models_request_failed_with_status": "pedido de modelos do DigitalOcean falhou com estado %d: %s",
"disable_openai_responses_api": "Desabilitar API OpenAI Responses (por omissão: false)",
"disable_pattern_variable_replacement": "Desabilitar substituição de variáveis de padrão",
"enable_web_search_tool": "Habilitar ferramenta de pesquisa web para modelos suportados (Anthropic, OpenAI, Gemini)",
"enable_web_search_tool": "Habilitar ferramenta de pesquisa web para modelos suportados (Anthropic, OpenAI, Gemini, Grok)",
"end_tag_thinking_sections": "Tag final para secções de pensamento",
"error_creating_audio_file": "erro ao criar ficheiro de áudio: %v",
"error_creating_file": "erro ao criar ficheiro: %v",
@ -352,6 +375,7 @@
"ollama_invalid_data_url_format": "formato de URL de dados inválido",
"ollama_invalid_http_timeout_using_default": "Tempo limite HTTP inválido '%s': %v, a utilizar o padrão",
"ollama_invalid_num_ctx_in_request": "num_ctx inválido no pedido: %v",
"ollama_invalid_request_body": "corpo de pedido inválido",
"ollama_no_content_from_upstream": "nenhum conteúdo recebido do servidor Fabric upstream",
"ollama_num_ctx_exceeds_maximum": "num_ctx excede o valor máximo permitido de %d",
"ollama_num_ctx_invalid_type": "num_ctx deve ser um número, recebeu tipo inválido",
@ -365,6 +389,7 @@
"ollama_sse_buffer_limit": "Linha SSE excede limite de buffer de 1MB - linha de dados demasiado grande",
"ollama_upstream_non_2xx": "Servidor Fabric upstream retornou estado não-2xx %d: %s",
"ollama_upstream_non_2xx_body_unreadable": "Servidor Fabric upstream retornou estado não-2xx %d e o corpo não pôde ser lido: %v",
"ollama_upstream_request_failed": "falha ao contactar o servidor Fabric a montante",
"ollama_upstream_returned_status": "servidor Fabric upstream retornou estado %d",
"ollama_warning_no_content": "Aviso: nenhum conteúdo recebido do servidor Fabric upstream",
"ollama_warning_parse_variables": "Aviso: falha ao analisar options.variables como JSON: %v",
@ -385,6 +410,7 @@
"openai_image_failed_to_save_image": "falha ao guardar a imagem em %s: %w",
"openai_image_saved_to": "Imagem guardada em: %s",
"openai_model_no_image_generation": "o modelo '%s' não suporta geração de imagens. Modelos suportados: %s",
"openai_models_rate_limited": "limite de taxa excedido ao obter modelos do fornecedor %s; tente novamente após %s segundos",
"openai_models_response_too_large": "resposta de modelos demasiado grande do fornecedor %s (>%d bytes)",
"openai_unable_to_parse_models_response": "não foi possível analisar a resposta de modelos; resposta bruta: %s",
"openai_unexpected_status_code_read_error": "código de estado inesperado: %d do fornecedor %s (falha ao ler corpo da resposta: %v)",
@ -400,6 +426,7 @@
"output_video_metadata": "Mostrar metadados do vídeo",
"path_to_yaml_config": "Caminho para ficheiro de configuração YAML",
"pattern_not_found_list_available": "padrão '%s' não encontrado. Execute 'fabric -l' para ver os padrões disponíveis",
"pattern_invalid_name": "nome de padrão inválido: %q",
"pattern_not_found_no_patterns": "padrão '%s' não encontrado.\n\nNenhum padrão instalado! Para resolver:\n • Execute 'fabric --setup' para configurar e descarregar padrões\n • Ou execute 'fabric -U' para descarregar/atualizar padrões diretamente",
"pattern_variables_help": "Valores para variáveis de padrão, ex. -v=#role:expert -v=#points:30",
"patterns_cloning_repository": "A clonar repositório %s (caminho: %s)...\\n",
@ -473,6 +500,8 @@
"prefer_playlist_over_video": "Preferir playlist ao vídeo se ambos os IDs estiverem presentes na URL",
"print_context": "Imprimir contexto",
"print_current_version": "Imprimir versão atual",
"print_metadata_to_stderr": "Imprimir metadados (tokens de entrada/saída) no stderr",
"print_pattern_contents": "Imprimir o conteúdo do padrão indicado no terminal",
"print_session": "Imprimir sessão",
"register_new_extension": "Registar uma nova extensão do caminho do ficheiro de configuração",
"remove_registered_extension": "Remover uma extensão registada por nome",
@ -486,10 +515,12 @@
"send_desktop_notification": "Enviar notificação no ambiente de trabalho quando o comando for concluído",
"serve_fabric_api_ollama_endpoints": "Servir a API REST do Fabric com endpoints ollama",
"serve_fabric_rest_api": "Servir a API REST do Fabric",
"server_api_key_required": "recusa de servir no endereço não loopback %s sem chave de API: defina --api-key ou FABRIC_API_KEY, ou vincule um endereço loopback como 127.0.0.1:8080",
"server_chat_error": "Erro: %v",
"server_error_marshaling_response": "erro ao serializar resposta: %v",
"server_error_writing_response": "erro ao escrever resposta: %v",
"server_invalid_request_format": "formato de pedido inválido: %v",
"server_no_api_key_warning": "A iniciar o servidor da API REST sem autenticação por chave de API. Isto pode representar riscos de segurança.",
"sessions_creating_new": "A criar nova sessão: %s\n",
"set_debug_level": "Definir nível de debug (0=desligado, 1=básico, 2=detalhado, 3=rastreio, 4=wire)",
"set_frequency_penalty": "Definir penalidade de frequência",
@ -581,6 +612,7 @@
"spotify_show_name_label": "**Programa**: %s",
"spotify_title_label": "**Título**: %s",
"spotify_total_episodes_label": "**Total de episódios**: %d",
"spotify_url_help": "URL de podcast ou episódio do Spotify para obter metadados e enviar ao chat",
"spotify_url_label": "**URL**: %s",
"start_tag_thinking_sections": "Tag inicial para secções de pensamento",
"storage_error_delete": "Não foi possível eliminar %s: %v",
@ -592,6 +624,7 @@
"storage_error_save": "Não foi possível guardar %s: %v",
"storage_error_stat_entry": "Não foi possível obter informações da entrada %s: %v",
"storage_error_unmarshal": "Não foi possível desserializar %s: %s",
"storage_invalid_name": "nome inválido: %q",
"strategies_available_header": "Estratégias disponíveis:",
"strategies_cloning_repository": "A clonar repositório %s (caminho: %s)...\\n",
"strategies_download_success": "✅ Estratégias transferidas e instaladas com sucesso em %s\\n",
@ -610,11 +643,11 @@
"strategy_path_traversal": "o nome da estratégia %q resolve fora do diretório de estratégias",
"stream_help": "Streaming",
"suppress_thinking_tags": "Suprimir texto contido em tags de pensamento",
"template_datetime_error_invalid_number": "invalid number in relative time: %q",
"template_datetime_error_invalid_relative_format": "invalid relative time format",
"template_datetime_error_invalid_unit": "invalid time unit: %q",
"template_datetime_error_relative_requires_value": "relative time requires a value",
"template_datetime_error_unknown_operation": "datetime: unknown operation %q",
"template_datetime_error_invalid_number": "número inválido no tempo relativo: %q",
"template_datetime_error_invalid_relative_format": "formato de tempo relativo inválido",
"template_datetime_error_invalid_unit": "unidade de tempo inválida: %q",
"template_datetime_error_relative_requires_value": "o tempo relativo requer um valor",
"template_datetime_error_unknown_operation": "datetime: operação desconhecida %q",
"template_extension_error": "Erro na extensão %s: %v",
"template_file_error_expand_home_dir": "ficheiro: não foi possível expandir a diretoria home: %v",
"template_file_error_invalid_line_count": "ficheiro: contagem de linhas inválida %q",
@ -643,12 +676,12 @@
"template_missing_required_variable": "Variável obrigatória em falta: %s",
"template_plugin_error": "Erro no plugin %s: %v",
"template_processing_stuck": "Processamento do modelo bloqueado - possível ciclo infinito",
"template_sys_error_env_requires_var": "env: variable name required",
"template_sys_error_home": "failed to get home directory: %v",
"template_sys_error_hostname": "failed to get hostname: %v",
"template_sys_error_pwd": "failed to get working directory: %v",
"template_sys_error_unknown_operation": "sys: unknown operation %q",
"template_sys_error_user": "failed to get current user: %v",
"template_sys_error_env_requires_var": "env: nome da variável obrigatório",
"template_sys_error_home": "falha ao obter o diretório pessoal: %v",
"template_sys_error_hostname": "falha ao obter o nome do host: %v",
"template_sys_error_pwd": "falha ao obter o diretório de trabalho: %v",
"template_sys_error_unknown_operation": "sys: operação desconhecida %q",
"template_sys_error_user": "falha ao obter o utilizador atual: %v",
"template_text_empty_input": "Texto: entrada vazia para a operação %q",
"template_text_unknown_operation": "Texto: operação de texto desconhecida %q (suportadas: upper, lower, title, trim)",
"template_unknown_plugin_namespace": "Espaço de nomes do plugin desconhecido: %s",
@ -698,15 +731,21 @@
"youtube_error_getting_video_details": "erro ao obter detalhes do vídeo: %v",
"youtube_error_parsing_duration": "erro ao analisar a duração do vídeo: %v",
"youtube_error_saving_csv": "erro ao guardar vídeos em CSV: %v",
"youtube_extract_visual_data_help": "Extrair dados visuais do vídeo usando OCR e FFmpeg",
"youtube_failed_create_temp_dir": "falha ao criar diretório temporário: %v",
"youtube_failed_fetch_comments": "Falha ao obter comentários: %v",
"youtube_failed_get_stream_url": "falha ao obter o URL do stream via yt-dlp: %v",
"youtube_failed_parse_http_stream_url": "falha ao analisar um URL HTTP válido de stream a partir da saída do yt-dlp",
"youtube_failed_walk_directory": "falha ao percorrer o diretório: %v",
"youtube_ffmpeg_frame_extraction_failed": "a extração de fotogramas com ffmpeg falhou: %v, saída: %s",
"youtube_ffmpeg_required_visual_extraction": "ffmpeg é necessário para extração visual, mas não foi encontrado no PATH",
"youtube_invalid_duration_string": "cadeia de duração inválida: %s",
"youtube_invalid_seconds_format": "formato de segundos inválido %q: %w",
"youtube_invalid_timestamp_format": "formato de timestamp inválido: %s",
"youtube_invalid_url": "URL do YouTube inválido, não é possível obter o ID do vídeo ou da lista de reprodução: '%s'",
"youtube_invalid_ytdlp_arguments": "argumentos do yt-dlp inválidos: %v",
"youtube_label": "YouTube",
"youtube_no_clear_text_visual_frames": "nenhum texto legível encontrado nos fotogramas visuais do vídeo",
"youtube_no_transcript_content": "nenhum conteúdo de transcrição encontrado no ficheiro VTT",
"youtube_no_url_provided": "Nenhum URL do YouTube fornecido",
"youtube_no_video_found_with_id": "nenhum vídeo encontrado com o ID: %s",
@ -717,9 +756,15 @@
"youtube_playlist_saved_to": "Lista de reprodução guardada em %s",
"youtube_rate_limit_exceeded": "Limite de taxa do YouTube excedido. Tente novamente mais tarde ou utilize argumentos diferentes do yt-dlp como '--sleep-requests 1' para desacelerar os pedidos.",
"youtube_setup_description": "YouTube - para obter transcrições de vídeo (via yt-dlp) e comentários/metadados (via API do YouTube)",
"youtube_tesseract_frame_failed": "tesseract falhou no fotograma %d: %v, stderr: %s",
"youtube_tesseract_required_visual_extraction": "tesseract é necessário para extração visual, mas não foi encontrado no PATH",
"youtube_url_help": "Vídeo do YouTube ou \"URL\" de playlist para obter transcrição, comentários e enviar ao chat ou imprimir na consola e armazenar no ficheiro de saída",
"youtube_url_is_playlist_not_video": "o URL é uma lista de reprodução, não um vídeo",
"youtube_video_id_title_header": "VideoID: Título",
"youtube_visual_fps_help": "Extrair um número específico de fotogramas por segundo em vez de usar deteção de cenas",
"youtube_visual_frame_cue": "[MARCADOR DE FOTOGRAMA VISUAL]",
"youtube_visual_sensitivity_help": "Tolerância para deteção de cenas do FFmpeg (0.0 - 1.0)",
"youtube_ytdlp_not_found": "yt-dlp não encontrado no PATH. Por favor instale o yt-dlp para usar a funcionalidade de transcrição do YouTube",
"youtube_ytdlp_required_visual_extraction": "yt-dlp é necessário para extração visual, mas não foi encontrado no PATH",
"youtube_ytdlp_stderr_error": "erro ao ler stderr do yt-dlp"
}

View file

@ -0,0 +1,770 @@
{
"abacus_models_endpoint_status": "abacus modelleri uç noktası %d durumunu döndürdü",
"additional_yt_dlp_args": "yt-dlp'ye geçirilecek ek argümanlar (örn. '--cookies-from-browser brave')",
"address_to_bind_rest_api": "REST API'sini bağlamak için adres",
"anthropic_stream_error": "Akış hatası: %v",
"api_key_secure_server_routes": "Sunucu rotalarını güvence altına almak için kullanılan API anahtarı",
"application_options_header": "Uygulama Seçenekleri:",
"apply_variables_to_input": "Değişkenleri kullanıcı girişine uygula",
"attachment_could_not_determine_mimetype": "URL'nin mimetype'ı belirlenemedi",
"attachment_file_not_exist": "dosya %s mevcut değil",
"attachment_no_content_available": "içerik mevcut değil",
"attachment_no_type_no_content": "eklentiye ait tür veya türetilecek içerik mevcut değil",
"attachment_path_or_url_help": "Eklenti yolu veya URL'si (örn. OpenAI görsel tanıma mesajları için)",
"audio_output_file_specified_but_not_tts_model": "ses çıkış dosyası '%s' belirtildi ancak '%s' modeli bir TTS modeli değil. Lütfen gemini-2.5-flash-preview-tts gibi bir TTS modeli kullanın",
"audio_video_file_transcribe": "Ses veya video dosyasını yazıya dönüştür",
"available_models_header": "Mevcut modeller",
"available_transcription_models": "Mevcut yazıya dönüştürme modelleri:",
"available_vendors_header": "Mevcut Sağlayıcılar:",
"azure_api_key_required": "Azure API anahtarı gerekli",
"azure_api_version_question": "Azure API sürümünü girin (varsayılan için boş bırakın)",
"azure_base_url_question": "API Temel URL'si",
"azure_base_url_required": "Azure temel URL'si gerekli",
"azure_credential_failure": "Azure kimlik bilgisi oluşturulamadı",
"azure_deployments_question": "Azure dağıtım adlarınızı girin (virgülle ayrılmış)",
"azure_deployments_required": "en az bir Azure dağıtım adı gerekli",
"azure_failed_extract_deployment": "istekten dağıtım adı alınamadı",
"azure_model_field_empty": "istek gövdesinde model alanı boş",
"azure_request_body_nil": "istek gövdesi boş",
"azureaigateway_aoai_no_choices": "Azure OpenAI yanıtında seçenek yok",
"azureaigateway_aoai_parse_response_failed": "Azure OpenAI yanıtı ayrıştırılamadı: %w",
"azureaigateway_api_version_question": "Azure OpenAI API sürümü (varsayılan: 2025-04-01-preview, varsayılan için boş bırakın)",
"azureaigateway_backend_not_initialized": "arka uç başlatılmadı - yapılandırmak için 'fabric --setup' komutunu çalıştırın",
"azureaigateway_backend_type_question": "Arka uç türünü seçin (bedrock, azure-openai, vertex-ai)",
"azureaigateway_bedrock_no_text_blocks": "Bedrock yanıtında metin içerik bloğu yok",
"azureaigateway_bedrock_parse_response_failed": "Bedrock yanıtı ayrıştırılamadı: %w",
"azureaigateway_failed_create_request": "AzureAIGateway: istek oluşturulamadı: %w",
"azureaigateway_failed_read_response": "AzureAIGateway: yanıt okunamadı: %w",
"azureaigateway_gateway_url_https_required": "ağ geçidi URL'si HTTPS şeması kullanmalıdır",
"azureaigateway_gateway_url_question": "Azure APIM Ağ Geçidi temel URL'nizi girin (örn. https://gateway.company.com)",
"azureaigateway_gateway_url_required": "Azure APIM ağ geçidi URL'si gerekli",
"azureaigateway_http_error": "AzureAIGateway: HTTP %d: %s",
"azureaigateway_http_request_failed": "AzureAIGateway: HTTP isteği başarısız oldu: %w",
"azureaigateway_invalid_gateway_url": "geçersiz ağ geçidi URL'si: %w",
"azureaigateway_no_valid_messages": "boş içerik filtrelendikten sonra geçerli mesaj yok",
"azureaigateway_prepare_request_failed": "AzureAIGateway: %w",
"azureaigateway_response_too_large": "AzureAIGateway: yanıt çok büyük (>%d bayt)",
"azureaigateway_subscription_key_question": "Azure APIM abonelik anahtarınızı girin",
"azureaigateway_subscription_key_required": "Azure APIM abonelik anahtarı gerekli",
"azureaigateway_unsupported_backend": "desteklenmeyen arka uç: %s (geçerli seçenekler: bedrock, azure-openai, vertex-ai)",
"azureaigateway_vertexai_no_content": "Vertex AI yanıtında içerik yok",
"azureaigateway_vertexai_parse_response_failed": "Vertex AI yanıtı ayrıştırılamadı: %w",
"background_type_help": "Arka plan türü: opak, şeffaf (varsayılan: opak, yalnızca PNG/WebP için)",
"bedrock_api_key_label": "Bedrock API Anahtarınızı / ABSK belirtecinizi girin (önerilir — Claude Code tarafından kullanılan anahtarla aynı)",
"bedrock_aws_access_key_label": "AWS Erişim Anahtarı Kimliğinizi girin (yalnızca yukarıdaki API Anahtarını kullanmıyorsanız)",
"bedrock_aws_region_label": "AWS Bölgenizi girin (örn. us-east-1, us-west-2, eu-west-1, ap-southeast-1)",
"bedrock_aws_secret_key_label": "AWS Gizli Erişim Anahtarınızı girin (yalnızca yukarıdaki API Anahtarını kullanmıyorsanız)",
"bedrock_client_not_initialized": "Bedrock istemcisi başlatılmadı — yapılandırmak için 'fabric --setup' komutunu çalıştırın",
"bedrock_converse_failed": "bedrock converse, %s modeli için başarısız oldu: %w",
"bedrock_conversestream_failed": "bedrock conversestream, %s modeli için başarısız oldu: %w",
"bedrock_empty_response_content": "boş yanıt içeriği",
"bedrock_failed_list_foundation_models": "temel modeller listelenemedi: %w",
"bedrock_failed_list_inference_profiles": ıkarım profilleri listelenemedi: %w",
"bedrock_fetch_regions_bad_status": "botocore uç noktaları beklenmeyen bir durum döndürdü",
"bedrock_fetch_regions_failed": "botocore'dan Bedrock bölgeleri alınamadı",
"bedrock_fetch_regions_parse_failed": "botocore endpoints.json ayrıştırılamadı",
"bedrock_invalid_aws_region": "geçersiz AWS bölgesi: %s",
"bedrock_listmodels_fallback": "Bedrock ListModels API başarısız oldu, statik yedek kullanılıyor",
"bedrock_panic_sendstream": "SendStream'de panik: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Erişim Anahtarı + Gizli Anahtar",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Anahtarı / ABSK belirteci (önerilir — Claude Code ile aynı)",
"bedrock_setup_auth_prompt": "1 veya 2 girin",
"bedrock_setup_choose_auth_method": " Kimlik doğrulama yöntemini seçin:",
"bedrock_setup_choose_model": " Varsayılan modeli seçin (ön ek olmayan kimlikler herhangi bir bölgede çalışır; us./eu./ap. bölgeye özeldir):",
"bedrock_setup_choose_region": " AWS Bölgesini seçin:",
"bedrock_setup_header": "[Bedrock]",
"bedrock_setup_invalid_auth_selection": "geçersiz seçim: %s (1 veya 2 girin)",
"bedrock_setup_model_custom_prompt": "Model kimliğini girin (örn. anthropic.claude-opus-4-6-v1)",
"bedrock_setup_model_option_custom": " [0] Farklı bir model kimliği girin",
"bedrock_setup_model_prompt": "Model numarasını veya kendinizinkini yazmak için 0 girin",
"bedrock_setup_region_custom_prompt": "Özel AWS bölgesini girin (örn. us-east-1)",
"bedrock_setup_region_option_custom": " [0] Farklı bir bölge girin",
"bedrock_setup_region_prompt": "Bölge numarasını veya özel için 0 girin",
"bedrock_setup_selected_model": " ✓ Seçilen model: %s",
"bedrock_setup_use_with": " Şununla kullanın: fabric -m %s -V Bedrock",
"bedrock_unable_load_aws_config": "AWS Yapılandırması yüklenemiyor: %w",
"bedrock_unable_load_aws_config_with_region": "AWS Yapılandırması %s bölgesiyle yüklenemiyor: %w",
"bedrock_unexpected_content_block_type": "beklenmeyen içerik bloğu türü: %T",
"bedrock_unexpected_response_type": "beklenmeyen yanıt türü: %T",
"bedrock_unknown_stream_event_type": "bilinmeyen akış olayı türü: %T",
"cannot_convert_string": "%q dizgisi %v öğesine dönüştürülemiyor",
"change_default_model": "Varsayılan modeli değiştir",
"chat_error_content_fields_misused": "Content ve MultiContent özelliklerini aynı anda kullanamazsınız",
"chatter_error_empty_response": "boş yanıt",
"chatter_error_find_context": "%s bağlamı bulunamadı: %v",
"chatter_error_find_session": "%s oturumu bulunamadı: %v",
"chatter_error_get_pattern": "%s deseni alınamadı: %v",
"chatter_error_load_strategy": "%s stratejisi yüklenemedi: %v",
"chatter_error_no_messages_provided": "mesaj sağlanmadı",
"chatter_error_no_session_pattern_user_messages": "oturum, desen veya kullanıcı mesajı sağlanmadı",
"chatter_error_stream_update": "Hata: %s",
"chatter_help_review_changes_with_git_diff": "git kullanıyorsanız değişiklikleri 'git diff' ile inceleyebilirsiniz.",
"chatter_info_file_changes_applied_successfully": "Dosya değişiklikleri başarıyla uygulandı.",
"chatter_log_stream_usage_metadata": "[Meta Veri] Giriş: %d | Çıkış: %d | Toplam: %d",
"chatter_prompt_enforce_response_language": "%s\n\nÖNEMLİ: Öncelikle, bu istemde verilen talimatları kullanıcının girdisini kullanarak yürütün. İkinci olarak, talimatların yürütülmesinin bir parçası olarak oluşturulan herhangi bir bölüm başlığı veya başlık dahil olmak üzere tüm nihai yanıtınızın YALNIZCA %s dilinde yazıldığından emin olun.",
"chatter_warning_apply_file_changes_failed": "Uyarı: Dosya değişiklikleri uygulanamadı: %v",
"chatter_warning_get_current_directory_failed": "Uyarı: Geçerli dizin alınamadı: %v",
"chatter_warning_parse_file_changes_failed": "Uyarı: Dosya değişiklikleri ayrıştırılamadı: %v",
"choose_context_from_available": "Mevcut bağlamlar arasından bir bağlam seçin",
"choose_model": "Model seç",
"choose_pattern_from_available": "Mevcut desenler arasından bir desen seçin",
"choose_session_from_available": "Mevcut oturumlar arasından bir oturum seçin",
"choose_strategy_from_available": "Mevcut stratejiler arasından bir strateji seçin",
"codex_api_base_url_question": "Codex API temel URL'nizi girin",
"codex_auth_base_url_invalid": "geçersiz codex kimlik doğrulama temel URL'si: %w",
"codex_auth_base_url_question": "Codex OAuth temel URL'nizi girin",
"codex_browser_open_fallback": "Tarayıcınız açılmadıysa, kimlik doğrulaması için bu URL'ye gidin:",
"codex_decode_models_response_failed": "codex modelleri yanıtı çözülemedi: %w",
"codex_decode_refresh_response_failed": "yenilenmiş Codex belirteç yanıtı çözülemedi: %w",
"codex_decode_token_response_failed": "codex belirteç takas yanıtı çözülemedi: %w",
"codex_image_file_not_supported": "Codex sağlayıcısı --image-file özelliğini desteklemiyor. Bunun yerine bir görsel eki kullanın.",
"codex_login_account_changed": "Codex girişi, depolanan yapılandırmadan farklı bir ChatGPT hesabına bağlı. Lütfen 'fabric --setup' komutunu tekrar çalıştırın.",
"codex_login_completed": "Codex girişi tamamlandı",
"codex_login_failed": "Codex girişi başarısız oldu: %s",
"codex_login_invalid": "Codex girişi artık geçerli değil. Lütfen 'fabric --setup' komutunu tekrar çalıştırın.",
"codex_login_missing_account_claim": "Codex girişi bir ChatGPT hesap kimliği içermiyordu. Bu giriş durumu desteklenmiyor.",
"codex_login_missing_auth_code": "Codex girişi bir yetkilendirme kodu döndürmedi.",
"codex_login_missing_tokens": "Codex girişi gerekli erişim ve yenileme belirteçlerini döndürmedi.",
"codex_login_refresh_failed": "Codex girişi yenilenemedi. Lütfen 'fabric --setup' komutunu tekrar çalıştırın.",
"codex_login_return_to_fabric": "Fabric'e dönün.",
"codex_login_revoked": "Codex girişi süresi doldu veya iptal edildi. Lütfen 'fabric --setup' komutunu tekrar çalıştırın.",
"codex_login_server_stopped": "Codex giriş geri çağırma sunucusu, kimlik doğrulama tamamlanmadan önce durdu.",
"codex_login_state_mismatch": "OAuth durumu eşleşmediği için Codex girişi doğrulanamadı.",
"codex_login_timed_out": "Codex girişi, kimlik doğrulama tamamlanmadan önce zaman aşımına uğradı.",
"codex_oauth_missing_auth_code": "Yetkilendirme kodu eksik",
"codex_oauth_random_state_failed": "güvenli rastgele oauth durumu oluşturulamadı: %w",
"codex_oauth_server_start_failed": "yerel oauth geri çağırma sunucusu başlatılamadı: %w",
"codex_oauth_state_mismatch": "Durum eşleşmiyor",
"codex_provider_error": "codex sağlayıcı hatası (durum %d): %s",
"codex_refresh_failed_status": "codex girişi yenilenemedi (durum %d)",
"codex_refresh_login_failed": "Codex girişi yenilenemedi: %w",
"codex_refresh_token_required": "Codex yenileme belirteci gerekli. Lütfen 'fabric --setup' komutunu tekrar çalıştırın.",
"codex_replay_body_unavailable": "Codex yeniden kimlik doğrulama denemesi için istek gövdesi tekrar oynatılamaz",
"codex_request_failed": "codex isteği başarısız oldu: %w",
"codex_request_failed_status": "codex isteği %d durumuyla başarısız oldu",
"codex_starting_browser_login": "Codex için tarayıcı tabanlı OpenAI girişi başlatılıyor.",
"codex_token_exchange_failed": "codex belirteç takası başarısız oldu: %w",
"codex_token_persist_failed": "Codex girişi kalıcı hale getirilemedi: %w",
"codex_token_refresh_missing_access_token": "Codex belirteç yenilemesi bir erişim belirteci döndürmedi.",
"codex_usage_limit_reached": "codex kullanım limitine ulaşıldı",
"command_completed_successfully": "Komut başarıyla tamamlandı",
"compression_level_jpeg_webp": "JPEG/WebP biçimleri için sıkıştırma seviyesi 0-100 (varsayılan: ayarlanmadı)",
"config_file_not_found": "yapılandırma dosyası bulunamadı: %s",
"convert_html_readability": "HTML girdisini temiz, okunabilir bir görünüme dönüştür",
"copilot_debug_created_conversation": "Copilot konuşması oluşturuldu: %s",
"copilot_debug_failed_parse_sse_event": "SSE olayı ayrıştırılamadı: %v",
"copilot_error_chat_request": "sohbet isteği başarısız oldu: %s - %s",
"copilot_error_create_conversation": "konuşma oluşturulamadı: %s - %s",
"copilot_error_reading_stream": "akış okunurken hata oluştu: %w",
"copilot_error_stream_request": "akış isteği başarısız oldu: %s - %s",
"copilot_failed_create_conversation": "konuşma oluşturulamadı: %w",
"copilot_failed_send_message": "mesaj gönderilemedi: %w",
"copilot_failed_stream_message": "mesaj akışı sağlanamadı: %w",
"copilot_tenant_client_id_required": "kiracı kimliği ve istemci kimliği gereklidir",
"copy_to_clipboard": "Panoya Kopyala",
"could_not_copy_to_clipboard": "panoya kopyalanamadı: %v",
"could_not_create_config_dir": "yapılandırma dizini oluşturulamadı: %w",
"could_not_create_env_file": ".env dosyası oluşturulamadı: %w",
"could_not_determine_home_dir": "kullanıcı ana dizini belirlenemedi: %w",
"could_not_stat_env_file": ".env dosyası bilgileri alınamadı (stat): %w",
"custom_notification_command": "Bildirimler için çalıştırılacak özel komut (yerleşik bildirimleri geçersiz kılar)",
"custom_patterns_directory_question": "Özel desenler dizininizin yolunu girin",
"custom_patterns_label": "Özel Desenler",
"custom_patterns_setup_description": "Özel Desenler - Özel desenleriniz için dizini belirleyin",
"custom_patterns_warning_create_directory": "Uyarı: Özel desenler dizini %s oluşturulamadı: %v\n",
"db_error_loading_env_file": ".env dosyası yüklenirken hata oluştu: %w",
"db_error_updating_env_file": ".env dosyası güncellenirken hata oluştu: %w",
"defaults_model_context_length_question": "Model bağlam uzunluğunu girin",
"defaults_model_question": "Varsayılan modelinizin dizinini veya adını girin",
"defaults_setup_description": "Varsayılan Yapay Zeka Sağlayıcısı ve Modeli",
"digitalocean_failed_parse_control_plane_url": "DigitalOcean kontrol düzlemi URL'si ayrıştırılamadı: %w",
"digitalocean_model_list_unavailable": "DigitalOcean model listesi kullanılamıyor. Modelleri kontrol düzleminden almak için DIGITALOCEAN_TOKEN değerini ayarlayın",
"digitalocean_model_list_unavailable_with_error": "DigitalOcean model listesi kullanılamıyor: %w. Modelleri kontrol düzleminden almak için DIGITALOCEAN_TOKEN değerini ayarlayın",
"digitalocean_models_request_failed_read_error": "DigitalOcean modelleri isteği %d durumuyla başarısız oldu: %w",
"digitalocean_models_request_failed_with_status": "DigitalOcean modelleri isteği %d durumuyla başarısız oldu: %s",
"disable_openai_responses_api": "OpenAI Yanıt API'sini devre dışı bırak (varsayılan: false)",
"disable_pattern_variable_replacement": "Desen değişkeni değiştirmeyi devre dışı bırak",
"enable_web_search_tool": "Desteklenen modeller için web arama aracını etkinleştir (Anthropic, OpenAI, Gemini, Grok)",
"end_tag_thinking_sections": "Düşünme bölümleri için bitiş etiketi",
"error_creating_audio_file": "ses dosyası oluşturulurken hata oluştu: %v",
"error_creating_file": "dosya oluşturulurken hata oluştu: %v",
"error_fetching_playlist_videos": "çalma listesi videoları alınırken hata oluştu: %w",
"error_parsing_config_file": "yapılandırma dosyası ayrıştırılırken hata oluştu: %w",
"error_reading_config_file": "yapılandırma dosyası okunurken hata oluştu: %w",
"error_reading_piped_message": "stdin'den yönlendirilen mesaj okunurken hata oluştu: %w",
"error_writing_audio_data": "ses verileri dosyaya yazılırken hata oluştu: %v",
"error_writing_to_file": "dosyaya yazılırken hata oluştu: %v",
"extension_cmd_template_required": "işlem %s için komut şablonu gereklidir",
"extension_command_template_label": " Komut Şablonu: %s\n",
"extension_config_hash_mismatch": "%s için yapılandırma dosyası hash uyuşmazlığı",
"extension_config_path_label": " Yapılandırma Yolu: %s\n\n",
"extension_description_label": " Açıklama: %s\n",
"extension_empty_command": "biçimlendirme sonrası boş komut",
"extension_executable_hash_mismatch": "%s için çalıştırılabilir dosya hash uyuşmazlığı",
"extension_executable_label": " Çalıştırılabilir: %s\n",
"extension_executable_not_found": "çalıştırılabilir dosya bulunamadı: %w",
"extension_executable_required": "çalıştırılabilir dosya yolu gereklidir",
"extension_executing_command": "Komut yürütülüyor: %s\n",
"extension_execution_failed_err": "yürütme başarısız oldu: %w\nhata: %s",
"extension_execution_failed_stderr": "yürütme başarısız oldu: %w\nstderr: %s",
"extension_execution_timed_out": "%v sonra yürütme zaman aşımına uğradı",
"extension_failed_format_command": "komut biçimlendirilemedi: %w",
"extension_failed_get_absolute_path": "mutlak yol alınamadı: %w",
"extension_failed_get_extension": "uzantı alınamadı: %w",
"extension_failed_get_output_path": ıktı yolu alınamadı: %w\nhata: %s",
"extension_failed_hash_executable": "çalıştırılabilir dosya hash'i alınamadı: %w",
"extension_failed_marshal_registry": "uzantı kaydı sıralanamadı: %w",
"extension_failed_parse_config": "yapılandırma dosyası ayrıştırılamadı: %w",
"extension_failed_parse_registry": "uzantı kaydı ayrıştırılamadı: %w",
"extension_failed_read_config": "yapılandırma dosyası okunamadı: %w",
"extension_failed_read_output_file": ıktı dosyası okunamadı: %w",
"extension_failed_read_registry": "uzantı kaydı okunamadı: %w",
"extension_failed_register": "uzantı kaydedilemedi: %w",
"extension_failed_remove": "uzantı kaldırılamadı: %w",
"extension_failed_verify_executable": "çalıştırılabilir dosya doğrulanamadı: %w",
"extension_file_config_label": " Dosya Yapılandırması:\n",
"extension_invalid_config_path": "geçersiz yapılandırma yolu: %w",
"extension_invalid_definition": "geçersiz uzantı tanımı: %w",
"extension_invalid_timeout": "geçersiz zaman aşımı değeri '%s': '30s' veya '1m' gibi bir süre olmalıdır: %w",
"extension_invalid_timeout_format": "geçersiz zaman aşımı biçimi: %w",
"extension_name_contains_spaces": "uzantı adı '%s' boşluk içeriyor - adlar boşluk içermemeli",
"extension_name_detail_label": "Adı: %s\n",
"extension_name_empty": "uzantı adı boş olamaz",
"extension_name_label": "Uzantı: %s\n",
"extension_name_required": "uzantı adı gereklidir",
"extension_no_file_config": "dosya yapılandırması bulunamadı",
"extension_no_output_file": "yapılandırmada çıktı dosyası belirtilmemiş",
"extension_not_found": "uzantı %s bulunamadı",
"extension_operation_not_found": "uzantı %s için işlem %s bulunamadı",
"extension_operation_required": "en az bir işlem tanımlanmalıdır",
"extension_operations_label": " İşlemler:\n",
"extension_registered_success": "Uzantı başarıyla kaydedildi:\n",
"extension_registry_not_initialized": "uzantı kaydı başlatılmadı",
"extension_status_disabled": " Durum: DEVRE DIŞI - Karma doğrulama başarısız oldu: %v\n",
"extension_status_enabled": " Durum: ETKİN\n",
"extension_timeout_label": " Zaman Aşımı: %s\n",
"extension_type_label": " Tür: %s\n",
"extension_type_required": "uzantı türü gerekli",
"extension_version_label": " Sürüm: %s\n",
"extension_warning_load_registry": "Uyarı: uzantı kaydı yüklenemedi: %v\n",
"fabric_command_complete": "Fabric Komutu Tamamlandı",
"fabric_command_complete_with_pattern": "Fabric: %s Tamamlandı",
"fetch_content_exceeds_limit": "getir: içerik çok büyük: %d baytııyor",
"fetch_content_not_utf8": "getir: içerik geçerli UTF-8 metni değil",
"fetch_content_null_bytes": "getir: içerik boş baytlar içeriyor",
"fetch_content_too_large": "getir: içerik çok büyük: %d bayt (maks %d bayt)",
"fetch_error_create_request": "getir: istek oluşturulurken hata: %v",
"fetch_error_fetching_url": "getir: URL getirilirken hata: %v",
"fetch_error_reading_response": "getir: yanıt okunurken hata: %v",
"fetch_http_error": "getir: HTTP hatası: %d - %s",
"fetch_unknown_operation": "getir: bilinmeyen işlem %q (desteklenen: get)",
"fetch_unsupported_content_type": "getir: desteklenmeyen içerik türü %q - yalnızca metin içeriğine izin verilir",
"file_already_exists_choose_different": "dosya %s zaten mevcut. Lütfen farklı bir dosya adı seçin veya mevcut dosyayı kaldırın",
"file_already_exists_not_overwriting": "dosya %s zaten mevcut, üzerine yazılmıyor. Mevcut dosyayı yeniden adlandırın veya farklı bir ad seçin",
"file_manager_applied_operation": "%s işlemi %s üzerine uygulandı",
"file_manager_empty_path": "dosya değişikliği %d için boş yol",
"file_manager_failed_create_directory": "failed to create directory %s for file change %d: %w",
"file_manager_failed_parse_json": "%s JSON ayrıştırılamadı: %w",
"file_manager_failed_write_file": "failed to write file %s for file change %d: %w",
"file_manager_file_content_too_large": "dosya değişikliği %d için dosya içeriği çok büyük: %d bayt",
"file_manager_invalid_format_no_json_array": "geçersiz %s formatı: JSON dizisi bulunamadı",
"file_manager_invalid_format_unbalanced_brackets": "geçersiz %s formatı: dengesiz parantezler",
"file_manager_invalid_operation": "dosya değişikliği %d için geçersiz işlem: %s",
"file_manager_suspicious_path": "dosya değişikliği %d için şüpheli yol: %s",
"gemini_audio_data_too_small": "ses verisi çok küçük: %d bayt, minimum gerekli: %d",
"gemini_empty_pcm_data": "boş PCM verisi sağlandı",
"gemini_invalid_location_format": "geçersiz arama konumu formatı %q: saat dilimi (örn., 'America/Los_Angeles') veya dil kodu (örn., 'en-US') olmalı",
"gemini_invalid_voice": "geçersiz ses '%s'. Geçerli sesler: %v",
"gemini_no_audio_data": "TTS modelinden ses verisi alınmadı",
"gemini_no_text_for_tts": "TTS oluşturma için metin içeriği bulunamadı",
"gemini_pcm_data_too_large": "PCM verisi çok büyük: %d bayt, izin verilen maksimum: %d",
"gemini_stream_error": "Hata: %v",
"gemini_tts_failed": "TTS oluşturma başarısız oldu: %w",
"gemini_unexpected_data_type": "beklenmeyen veri türü: %s, beklenen ses verisi",
"gemini_voice_not_found": "ses '%s' bulunamadı",
"gemini_wav_data_invalid": "oluşturulan WAV verisi geçersiz: %d bayt, minimum gerekli: %d",
"gemini_wav_generation_failed": "WAV dosyası oluşturulamadı: %w",
"githelper_failed_clone_repository": "depo klonlanamadı: %w",
"githelper_failed_create_dest_directory": "hedef dizin oluşturulamadı: %w",
"githelper_failed_create_temp_directory": "geçici dizin oluşturulamadı: %w",
"githelper_failed_get_commit": "commit alınamadı: %w",
"githelper_failed_get_head": "depo HEAD'i alınamadı: %w",
"githelper_failed_get_tree": "ağaç alınamadı: %w",
"githelper_failed_git_cli_clone": "git klonlama başarısız oldu: %w: %s",
"githelper_failed_git_cli_fallback": "%w; git CLI geri dönüşü de başarısız oldu: %v",
"grab_comments_from_youtube": "YouTube videosundaki yorumları al ve sohbete gönder",
"grab_transcript_from_youtube": "YouTube videosundaki dökümü al ve sohbete gönder (varsayılan olarak kullanılır).",
"grab_transcript_with_timestamps": "Zaman damgalı YouTube videosu dökümünü al ve sohbete gönder",
"groups_items_number_out_of_range": "sayı %d aralık dışında",
"help_message": "Bu yardım mesajını göster",
"help_options_header": "Yardım Seçenekleri:",
"html_readability_error": "html okunabilirliği uygulanamadığından orijinal girdiyi kullanın",
"i18n_download_failed": "'%s' dili için çeviri indirilemedi: %v",
"i18n_load_failed": "çeviri dosyası yüklenemedi: %v",
"image_compression_jpeg_webp_only": "görüntü sıkıştırma yalnızca JPEG ve WebP formatlarıyla kullanılabilir, %s ile değil",
"image_compression_range_error": "görüntü sıkıştırma 0 ile 100 arasında olmalı, %d alındı",
"image_dimensions_help": "Görüntü boyutları: 1024x1024, 1536x1024, 1024x1536, otomatik (varsayılan: otomatik)",
"image_file_already_exists": "görüntü dosyası zaten mevcut: %s",
"image_parameters_require_image_file": "görüntü parametreleri (--image-size, --image-quality, --image-background, --image-compression) yalnızca --image-file ile kullanılabilir",
"image_quality_help": "Görüntü kalitesi: düşük, orta, yüksek, otomatik (varsayılan: otomatik)",
"invalid_config_path": "geçersiz yapılandırma yolu: %w",
"invalid_image_background": "geçersiz görüntü arka planı '%s'. Desteklenen arka planlar: opak, şeffaf",
"invalid_image_file_extension": "geçersiz görüntü dosyası uzantısı '%s'. Desteklenen formatlar: .png, .jpeg, .jpg, .webp",
"invalid_image_quality": "geçersiz görüntü kalitesi '%s'. Desteklenen kaliteler: düşük, orta, yüksek, otomatik",
"invalid_image_size": "geçersiz görüntü boyutu '%s'. Desteklenen boyutlar: 1024x1024, 1536x1024, 1024x1536, otomatik",
"jina_error_creating_request": "istek oluşturulurken hata: %v",
"jina_error_reading_response_body": "yanıt gövdesi okunurken hata: %v",
"jina_error_sending_request": "istek gönderilirken hata: %v",
"jina_label": "Jina AI",
"jina_setup_description": "Jina AI Servisi - bir web sayfasını temiz, LLM dostu metin olarak almak için",
"language_label": "Dil",
"language_output_question": "Varsayılan çıktı dilinizi girin (örneğin: zh_CN)",
"language_setup_description": "Dil - Varsayılan Yapay Zeka Sağlayıcısı Çıktı Dili",
"list_all_available_models": "Mevcut tüm modelleri listele",
"list_all_contexts": "Tüm bağlamları listele",
"list_all_patterns": "Tüm desenleri listele",
"list_all_registered_extensions": "Tüm kayıtlı uzantıları listele",
"list_all_sessions": "Tüm oturumları listele",
"list_all_strategies": "Tüm stratejileri listele",
"list_all_vendors": "Tüm sağlayıcıları listele",
"list_gemini_tts_voices": "Mevcut tüm Gemini TTS seslerini listele",
"list_transcription_models": "Mevcut tüm transkripsiyon modellerini listele",
"lmstudio_api_url_question": "%v URL'nizi girin (hatırlatıcı olarak, genellikle %v'dir)",
"lmstudio_error_reading_response": "yanıt okunurken hata: %w",
"lmstudio_failed_create_request": "istek oluşturulamadı: %w",
"lmstudio_failed_decode_response": "yanıt kodu çözülemedi: %w",
"lmstudio_failed_marshal_payload": "yük ayrıştırılamadı: %w",
"lmstudio_failed_send_request": "istek gönderilemedi: %w",
"lmstudio_invalid_response_missing_choices": "geçersiz yanıt biçimi: eksik veya boş seçenekler",
"lmstudio_invalid_response_missing_content": "geçersiz yanıt biçimi: mesajda eksik veya dize olmayan içerik",
"lmstudio_invalid_response_missing_message": "geçersiz yanıt biçimi: ilk seçenekte mesaj eksik",
"lmstudio_invalid_response_missing_text": "geçersiz yanıt biçimi: ilk seçenekte eksik veya dize olmayan metin",
"lmstudio_no_embeddings_returned": "gömülü ögeler döndürülmedi",
"lmstudio_unexpected_status_code": "beklenmeyen durum kodu: %d",
"model_context_length_ollama": "Model bağlam uzunluğu (yalnızca ollama'yı etkiler)",
"model_for_transcription": "Transkripsiyon için kullanılacak model (sohbet modelinden ayrı)",
"no_description_available": "Açıklama mevcut değil",
"no_items_found": "Hiçbir %s bulunamadı",
"no_notification_system_available": "bildirim sistemi mevcut değil",
"notifications_no_provider_available": "bildirim sağlayıcısı mevcut değil",
"number_of_latest_patterns": "Listelenecek en son desen sayısı",
"ollama_cannot_parse_url": "URL '%s' ayrıştırılamıyor: %v",
"ollama_chat_request_failed": "Sohbet isteği başarısız oldu: %v",
"ollama_empty_address": "boş adres",
"ollama_error_building_chat_url": "/chat URL'si oluşturulurken hata: %v",
"ollama_error_creating_chat_request": "/chat isteği oluşturulurken hata: %v",
"ollama_error_endpoint": "uç nokta test ediliyor",
"ollama_error_getting_chat_body": "/chat gövdesi alınırken hata: %v",
"ollama_error_marshalling_body": "gövde ayrıştırılırken hata: %v",
"ollama_error_parse_upstream_response": "Hata: yukarı akış yanıtı ayrıştırılamadı",
"ollama_error_prefix": "Hata: %s",
"ollama_error_reading_body": "gövde okunurken hata: %v",
"ollama_error_scanning_body": "gövde taranırken hata: %v",
"ollama_error_unmarshalling_body": "gövde ayrıştırılamadı: %v",
"ollama_error_writing_response": "yanıt yazılırken hata: %v",
"ollama_failed_create_request": "istek oluşturulamadı",
"ollama_failed_decode_data_url": "veri URL'si kodu çözülemedi: %v",
"ollama_failed_fetch_image": "%s adresinden görsel getirilemedi: %s",
"ollama_failed_scan_sse_stream": "SSE yanıt akışı taranamadı: %v",
"ollama_failed_unmarshal_fabric_response": "Fabric yanıtı ayrıştırılamadı",
"ollama_http_timeout_question": "HTTP zaman aşımını girin (örn. 20d, 60s)",
"ollama_invalid_address": "geçersiz adres: %w",
"ollama_invalid_address_missing_host": "geçersiz adres: ana bilgisayar eksik",
"ollama_invalid_address_missing_hostname": "geçersiz adres: ana bilgisayar adı eksik",
"ollama_invalid_address_path_not_allowed": "geçersiz adres: yol bileşeni yalın adreste izin verilmiyor",
"ollama_invalid_data_url_format": "geçersiz veri URL biçimi",
"ollama_invalid_http_timeout_using_default": "geçersiz HTTP zaman aşımı '%s': %v, varsayılan kullanılıyor",
"ollama_invalid_num_ctx_in_request": "istekte geçersiz num_ctx: %v",
"ollama_invalid_request_body": "geçersiz istek gövdesi",
"ollama_no_content_from_upstream": "yukarı akış Fabric sunucusundan içerik alınamadı",
"ollama_num_ctx_exceeds_maximum": "num_ctx, izin verilen maksimum değer olan %d'yi aşıyor",
"ollama_num_ctx_invalid_type": "num_ctx bir sayı olmalı, geçersiz tür alındı",
"ollama_num_ctx_must_be_finite": "num_ctx sonlu bir sayı olmalı",
"ollama_num_ctx_must_be_integer": "num_ctx bir tam sayı olmalı, kesirli kısmı olan ondalık sayı alındı",
"ollama_num_ctx_must_be_positive": "num_ctx pozitif olmalı, alındı: %d",
"ollama_num_ctx_must_be_valid_number": "num_ctx geçerli bir sayı olmalı",
"ollama_num_ctx_must_be_valid_number_got": "num_ctx geçerli bir sayı olmalı, alındı: %s",
"ollama_num_ctx_value_out_of_range": "num_ctx değeri aralık dışında",
"ollama_num_ctx_value_too_large": "num_ctx değeri çok büyük: %d",
"ollama_sse_buffer_limit": "SSE satırı 1MB arabellek sınırınııyor - veri satırı çok büyük",
"ollama_upstream_non_2xx": "Yukarı akış Fabric sunucusu 2xx olmayan durum kodu %d döndürdü: %s",
"ollama_upstream_non_2xx_body_unreadable": "Yukarı akış Fabric sunucusu 2xx olmayan durum kodu %d döndürdü ve gövde okunamadı: %v",
"ollama_upstream_request_failed": "yukarı akış Fabric sunucusuna ulaşılamadı",
"ollama_upstream_returned_status": "yukarı akış Fabric sunucusu %d durum kodunu döndürdü",
"ollama_warning_no_content": "Uyarı: yukarı akış Fabric sunucusundan içerik alınamadı",
"ollama_warning_parse_variables": "Uyarı: options.variables JSON olarak ayrıştırılamadı: %v",
"openai_api_base_url_not_configured": "API temel URL'si %s sağlayıcısı için yapılandırılmamış",
"openai_audio_ffmpeg_failed": "ffmpeg başarısız oldu: %v: %s",
"openai_audio_ffmpeg_not_found_install": "ffmpeg bulunamadı: lütfen yükleyin",
"openai_audio_file_exceeds_limit_enable_split": "Dosya %s 25MB sınırınııyor; otomatik bölmeyi etkinleştirmek için --split-media-file kullanın",
"openai_audio_file_exceeds_limit_splitting": "Dosya %s boyut sınırından daha büyük... parçalara ayrılıyor...",
"openai_audio_model_not_supported_for_transcription": "model '%s' transkripsiyon için desteklenmiyor",
"openai_audio_running_ffmpeg_split_chunks": "Sesi %d saniyelik parçalara ayırmak için ffmpeg çalıştırılıyor...",
"openai_audio_unable_to_split_acceptable_size_chunks": "dosyayı kabul edilebilir boyutta parçalara ayıramıyor",
"openai_audio_unsupported_audio_format": "desteklenmeyen ses formatı '%s'",
"openai_audio_using_model_to_transcribe_part": "%s modeli kullanılarak %d. parça yazıya dökülüyor (dosya adı: %s)...",
"openai_compatible_unknown_static_model_list": "bilinmeyen statik model listesi: %s",
"openai_failed_to_create_models_url": "modeller URL'si oluşturulamadı: %w",
"openai_image_failed_to_create_directory": "%s dizini oluşturulamadı: %w",
"openai_image_failed_to_decode_image_data": "görsel verisi çözülemedi: %w",
"openai_image_failed_to_save_image": "görsel %s konumuna kaydedilemedi: %w",
"openai_image_saved_to": "Görsel kaydedildi: %s",
"openai_model_no_image_generation": "'%s' modeli görsel oluşturmayı desteklemiyor. Desteklenen modeller: %s",
"openai_models_rate_limited": "%s sağlayıcısından modeller alınırken hız sınırııldı; %s saniye sonra tekrar deneyin",
"openai_models_response_too_large": "%s sağlayıcısından gelen modeller yanıtı çok büyük (>%d bayt)",
"openai_unable_to_parse_models_response": "modeller yanıtı ayrıştırılamadı; ham yanıt: %s",
"openai_unexpected_status_code_read_error": "unexpected status code: %d from provider %s (failed to read response body: %v)",
"openai_unexpected_status_code_with_body": "unexpected status code: %d from provider %s, response body: %s",
"openai_warning_model_no_image_generation": "Uyarı: '%s' modeli görsel oluşturmayı desteklemiyor. Desteklenen modeller: %s. Görsel oluşturmak için -m gpt-5.2 kullanmayı düşünebilirsiniz.\n",
"optional_marker": "(isteğe bağlı)",
"options_placeholder": "[SEÇENEKLER]",
"output_entire_session": "Tüm oturumu (geçici olanı da dahil) çıktı dosyasına yaz",
"output_full": ıktı: %s",
"output_raw_list_shell_completion": "Başlıklar/biçimlendirme olmadan ham listeyi çıkar (kabuk tamamlama için)",
"output_to_file": "Dosyaya çıktı al",
"output_truncated": ıktı: %s...",
"output_video_metadata": "Video meta verilerini çıkar",
"path_to_yaml_config": "YAML yapılandırma dosyası yolu",
"pattern_not_found_list_available": "'%s' deseni bulunamadı. Mevcut desenleri görmek için 'fabric -l' komutunu çalıştırın",
"pattern_invalid_name": "geçersiz desen adı: %q",
"pattern_not_found_no_patterns": "'%s' deseni bulunamadı.\n\nHiçbir desen yüklü değil! Bunu düzeltmek için:\n • Desenleri yapılandırmak ve indirmek için 'fabric --setup' komutunu çalıştırın\n • Veya desenleri doğrudan indirip güncellemek için 'fabric -U' komutunu çalıştırın",
"pattern_variables_help": "Desen değişkenleri için değerler, örn. -v=#role:expert -v=#points:30",
"patterns_cloning_repository": "Depo kopyalanıyor %s (yol: %s)...\n",
"patterns_debug_included_custom_directory": "📂 Özel dizinden desenler de dahil edildi: %s\n",
"patterns_detected_old_path": "🔄 Eski desen yolu 'patterns' algılandı, 'data/patterns' konumuna geçiş deneniyor...",
"patterns_download_success": "✅ Desenler başarıyla indirildi ve %s konumuna yüklendi\n",
"patterns_downloaded_temp": "%d desen geçici dizine indirildi\n",
"patterns_downloading": "Desenler indiriliyor ve %s dolduruluyor...\n",
"patterns_error_create_directory": "desen dizini oluşturulamadı: %v",
"patterns_error_get_home_directory": "ana dizin alınamadı: %v",
"patterns_error_load_from_file": "%s dosyasından desen yüklenemedi: %w",
"patterns_error_read_pattern_file": "%s desen dosyası okunamadı: %v",
"patterns_error_read_unique_file": "benzersiz desenler dosyası okunamadı. Lütfen --updatepatterns (%s) komutunu çalıştırın",
"patterns_error_resolve_file_path": "dosya yolu çözümlenemedi: %v",
"patterns_error_save_pattern": "desen kaydedilemedi: %v",
"patterns_failed_access_directory": "desenler dizini '%s' erişilemedi: %w",
"patterns_failed_create_temp_dir": "geçici dizin oluşturulamadı: %w",
"patterns_failed_create_temp_folder": "geçici desenler klasörü oluşturulamadı: %w",
"patterns_failed_download_from_git": "desenler git deposundan indirilemedi: %w",
"patterns_failed_download_from_repo": "%s konumundan desenler indirilemedi: %w",
"patterns_failed_loaded_marker": "yüklü işaretleyici dosyası '%s' oluşturulamadı: %w",
"patterns_failed_move_patterns": "desenler yapılandırma dizinine taşınamadı: %w",
"patterns_failed_move_test_patterns": "test desenleri geçici klasöre taşınamadı: %w",
"patterns_failed_read_directory": "desenler dizini okunamadı: %w",
"patterns_failed_read_temp_directory": "geçici desenler dizini okunamadı: %w",
"patterns_failed_unique_file": "benzersiz desenler dosyası oluşturulamadı: %w",
"patterns_failed_write_unique_file": "benzersiz desenler dosyası yazılamadı: %w",
"patterns_found_new_path": "✅ Found %d patterns at new path '%s', updating configuration...\n",
"patterns_git_repo_folder_question": "Desenlerin saklandığı Git deposundaki varsayılan klasörü girin",
"patterns_git_repo_url_question": "Desenler için varsayılan Git deposu URL'sini girin",
"patterns_loader_label": "Desen Yükleyici",
"patterns_no_patterns_copied": "hiçbir desen %s konumuna başarıyla kopyalanamadı",
"patterns_no_patterns_found_in_directories": "%s ve %s dizinlerinde desen bulunamadı",
"patterns_no_patterns_found_in_directory": "%s dizininde desen bulunamadı",
"patterns_no_patterns_migration_failed": "%s yolundaki depoda desen bulunamadı ve geçiş başarısız oldu: %w",
"patterns_not_found_header": "⚠️ Desen bulunamadı!",
"patterns_option_run_setup": "Seçenek 1 (Önerilen): Desenleri indirmek için kurulumu çalıştırın",
"patterns_option_run_setup_command": "fabric --setup",
"patterns_option_run_update": "Seçenek 2: Desenleri doğrudan indir/güncelle",
"patterns_option_run_update_command": "fabric -U",
"patterns_preserve_warning": "Uyarı: özel desen '%s' korunamadı: %v\n",
"patterns_preserved_custom_pattern": "Korunan özel desen: %s\n",
"patterns_required_to_work": "Fabric'in çalışması için desenler gereklidir. Bunu düzeltmek için:",
"patterns_saving_updated_configuration": "💾 Güncellenmiş yapılandırma kaydediliyor (yol '%s' konumundan '%s' konumuna değişti)...\n",
"patterns_setup_description": "Desenler - Desenleri indirir",
"patterns_unable_to_find_or_migrate": "mevcut '%s' yolunda desenler bulunamadı veya yeni yapıya geçilemedi",
"patterns_unique_file_created": "📝 %d desen içeren benzersiz desenler dosyası oluşturuldu\n",
"patterns_warning_custom_directory": "Uyarı: Özel desenler dizini %s okunamadı: %v\n",
"patterns_warning_remove_test_folder": "Uyarı: test geçici klasörü '%s' kaldırılamadı: %v\n",
"perplexity_api_key_not_configured": "%s için API anahtarı yapılandırılmadı. %s ortam değişkenini ayarlayın veya %s yapılandırmak için 'fabric --setup' komutunu çalıştırın",
"perplexity_api_request_failed": "Perplexity API isteği başarısız oldu: %w",
"perplexity_citations_header": "\n\n**Alıntılar:**\n",
"perplexity_failed_configure": "Perplexity yapılandırılamadı: %w",
"perplexity_streaming_error": "Perplexity akış hatası: %v",
"plugin_configured": " ✓",
"plugin_enable_bool_question": "%v %v etkinleştirilsin mi (true/false)",
"plugin_enter_value": "%v %v değerinizi girin",
"plugin_invalid_bool": "geçersiz boolean: %q",
"plugin_invalid_boolean_value": "geçersiz boolean değeri: %v",
"plugin_not_configured": " ⚠️ YAPILANDIRILMADI",
"plugin_question_bool": "%v%v (doğru/yanlış, '%s' için boş bırakın veya değeri kaldırmak için '%v' yazın):",
"plugin_question_optional": "%v%v (atlamak için boş bırakın):",
"plugin_question_with_default": "%v%v ('%s' için boş bırakın veya değeri kaldırmak için '%v' yazın):",
"plugin_registry_could_not_find_vendor": "sağlayıcı bulunamadı",
"plugin_registry_error_configuring_custom_patterns": "Özel Desenler yapılandırılırken hata oluştu: %w",
"plugin_registry_model_not_available_for_vendor": "%s modeli %s sağlayıcısı için mevcut değil",
"plugin_registry_run_setup_select_defaults": "lütfen 'fabric --setup' komutunu çalıştırın ve varsayılan modeli ve sağlayıcıyı seçin",
"plugin_setting_not_valid": "%v=%v, geçerli değil",
"plugin_setup_configured": "[%v] yapılandırıldı",
"plugin_setup_skipped": "[%v] atlandı\n",
"prefer_playlist_over_video": "URL'de her iki kimlik de varsa oynatma listesini videoya tercih et",
"print_context": "Bağlamı yazdır",
"print_current_version": "Mevcut sürümü yazdır",
"print_metadata_to_stderr": "Meta verileri (girdi/çıktı jetonları) stderr'e yazdır",
"print_pattern_contents": "Adlandırılmış desenin içeriğini terminale yazdır",
"print_session": "Oturumu yazdır",
"register_new_extension": "Yapılandırma dosyası yolundan yeni bir uzantı kaydet",
"remove_registered_extension": "Kayıtlı bir uzantıyı adına göre kaldır",
"required_marker": "[gerekli]",
"run_setup_for_reconfigurable_parts": "Fabric'in yeniden yapılandırılabilir tüm bölümleri için kurulumu çalıştır",
"save_generated_image_to_file": "Oluşturulan görüntüyü belirtilen dosya yoluna kaydet (örn. 'output.png')",
"scrape_website_url": "Jina AI kullanarak web sitesi URL'sini Markdown'a dönüştür",
"scraping_not_configured": "Kazıma işlevi yapılandırılmadı. Kazımayı etkinleştirmek için lütfen Jina'yı kurun",
"search_question_jina": "Jina AI kullanarak soru ara",
"seed_for_lmm_generation": "LMM üretimi için kullanılacak başlangıç değeri",
"send_desktop_notification": "Komut tamamlandığında masaüstü bildirimi gönder",
"serve_fabric_api_ollama_endpoints": "Fabric REST API'sini Ollama uç noktalarıyla sun",
"serve_fabric_rest_api": "Fabric REST API'sini sun",
"server_api_key_required": "bir API anahtarı olmadan döngüsel olmayan bir adres olan %s üzerinde hizmet vermeyi reddediyor: --api-key veya FABRIC_API_KEY ayarlayın ya da 127.0.0.1:8080 gibi bir döngüsel adres bağlayın",
"server_chat_error": "Hata: %v",
"server_error_marshaling_response": "yanıt sıralanırken hata oluştu: %v",
"server_error_writing_response": "yanıt yazılırken hata oluştu: %v",
"server_invalid_request_format": "geçersiz istek formatı: %v",
"server_no_api_key_warning": "REST API sunucusu API anahtarı doğrulaması olmadan başlatılıyor. Bu durum güvenlik riskleri oluşturabilir.",
"sessions_creating_new": "Yeni oturum oluşturuluyor: %s\n",
"set_debug_level": "Hata ayıklama düzeyini ayarla (0=kapalı, 1=temel, 2=ayrıntılı, 3=izleme, 4=tel)",
"set_frequency_penalty": "Frekans cezasını ayarla",
"set_location_web_search": "Web arama sonuçları için konumu ayarla (örn. 'America/Los_Angeles')",
"set_presence_penalty": "Mevcudiyet cezasını ayarla",
"set_reasoning_thinking_level": "Akıl yürütme/düşünme düzeyini ayarla (örn. kapalı, düşük, orta, yüksek veya Anthropic veya Google Gemini için sayısal jetonlar)",
"set_temperature": "Sıcaklığı ayarla",
"set_top_p": "En üst P'yi ayarla",
"setup_add_more_providers_later": "'fabric --setup' ile daha sonra ek sağlayıcılar ekleyebileceksiniz",
"setup_ai_provider_required": "Fabric'in çalışması için en az bir yapay zeka sağlayıcısına ihtiyacı var.",
"setup_available_ai_providers": "Mevcut Yapay Zeka Sağlayıcıları:",
"setup_available_plugins": "Mevcut eklentiler:",
"setup_complete_header": "✅ Kurulum tamamlandı! Artık Fabric'i kullanabilirsiniz.",
"setup_configure_more": "• Daha fazla ayar yapılandır: fabric --setup",
"setup_enter_ai_provider_number": "Yapay Zeka Sağlayıcı Numarası",
"setup_failed_download_patterns": "desenler indirilemedi: %w",
"setup_failed_download_strategies": "stratejiler indirilemedi: %w",
"setup_failed_set_defaults": "varsayılan sağlayıcı ve model ayarlanamadı: %w",
"setup_invalid_selection": "geçersiz seçim: %s",
"setup_list_patterns": "• Mevcut desenleri listele: fabric -l",
"setup_next_steps": "Sonraki adımlar:",
"setup_no_ai_provider_selected": "hiçbir yapay zeka sağlayıcısı seçilmedi - en az biri gerekli",
"setup_optional_configuration_header": "━━━ İSTEĞE BAĞLI YAPILANDIRMA ━━━\n\nİsteğe Bağlı Araçlar",
"setup_plugin_number": "Eklenti Numarası",
"setup_plugin_prompt": "Kurulacak eklentinin numarasını girin",
"setup_required_configuration_header": "━━━ GEREKLİ YAPILANDIRMA ━━━\n\nYapay Zeka Sağlayıcıları [en az biri gerekli]",
"setup_required_tools": "Gerekli Araçlar",
"setup_step_configure_ai_provider": "🤖 Adım 3: Bir yapay zeka sağlayıcısı yapılandır",
"setup_step_downloading_patterns": "📥 Adım 1: Desenler indiriliyor (Fabric'in çalışması için gerekli)...",
"setup_step_downloading_strategies": "📥 Adım 2: Stratejiler indiriliyor (Fabric'in çalışması için gerekli)...",
"setup_step_setting_defaults": "⚙️ Adım 4: Varsayılan sağlayıcı ve model ayarlanıyor...",
"setup_try_pattern": "• Bir desen dene: echo 'metniniz' | fabric --pattern summarize",
"setup_validation_ai_provider_configured": "✓ Yapay Zeka Sağlayıcısı yapılandırıldı",
"setup_validation_ai_provider_missing": "✗ Yapay Zeka Sağlayıcısı yapılandırılmadı - Fabric'in çalışması için gerekli",
"setup_validation_complete": "✓ Tüm gerekli bileşenler yapılandırıldı!",
"setup_validation_defaults_configured": "✓ Varsayılan sağlayıcı/model ayarlandı: %s/%s",
"setup_validation_defaults_missing": "✗ Varsayılan sağlayıcı/model ayarlanmadı - Fabric'in çalışması için gerekli",
"setup_validation_header": "Yapılandırma Durumu:",
"setup_validation_incomplete_help": "Eksik öğeleri yapılandırmak için 'fabric --setup' komutunu tekrar çalıştırın,\nveya desenleri ve stratejileri indirmek için 'fabric -U' komutunu çalıştırın.",
"setup_validation_incomplete_warning": "⚠️ Kurulum tamamlanmadı! Gerekli bileşenler eksik.",
"setup_validation_patterns_configured": "✓ Desenler indirildi",
"setup_validation_patterns_missing": "✗ Desenler bulunamadı - Fabric'in çalışması için gerekli",
"setup_validation_strategies_configured": "✓ Stratejiler indirildi",
"setup_validation_strategies_missing": "✗ Stratejiler bulunamadı - Fabric'in çalışması için gerekli",
"setup_welcome_header": "🎉 Fabric'e hoş geldiniz! Kurulumunuzu yapalım.",
"show_dry_run": "Modele gerçekten göndermeden, ne gönderileceğini göster.",
"specify_language_code": "Sohbet için Dil Kodunu belirtin, örn. -g=en -g=zh -g=pt-BR -g=pt-PT",
"specify_vendor_for_model": "Seçilen model için sağlayıcıyı belirtin (örn., -V \"LM Studio\" -m openai/gpt-oss-20b)",
"split_media_files_ffmpeg": "25MB'tan büyük ses/video dosyalarını ffmpeg kullanarak ayırın",
"spotify_api_request_failed": "API isteği başarısız oldu: durum %d, gövde: %s",
"spotify_audio_preview_label": "**Ses Önizlemesi**: %s",
"spotify_client_id_question": "Spotify İSTEMCİ KİMLİĞİNİZİ girin",
"spotify_client_secret_question": "Spotify İSTEMCİ SIRRINIZI girin",
"spotify_description_header": "## Açıklama",
"spotify_duration_label": "**Süre**: %d dakika",
"spotify_episode_header": "# Spotify Bölümü",
"spotify_error_getting_metadata": "Spotify meta verileri alınırken hata oluştu: %v",
"spotify_explicit_label": "**Açık İçerik**: %v",
"spotify_failed_create_request": "istek oluşturulamadı: %w",
"spotify_failed_create_token_request": "jeton isteği oluşturulamadı: %w",
"spotify_failed_decode_token_response": "jeton yanıtı çözümlenemedi: %w",
"spotify_failed_execute_request": "istek yürütülemedi: %w",
"spotify_failed_get_access_token": "erişim jetonu alınamadı: durum %d, gövde: %s",
"spotify_failed_get_show_episodes": "program bölümleri alınamadı: %w",
"spotify_failed_parse_episode_metadata": "bölüm meta verileri ayrıştırılamadı: %w",
"spotify_failed_parse_episodes": "bölümler ayrıştırılamadı: %w",
"spotify_failed_parse_search_results": "arama sonuçları ayrıştırılamadı: %w",
"spotify_failed_parse_show_metadata": "program meta verileri ayrıştırılamadı: %w",
"spotify_failed_read_response_body": "yanıt gövdesi okunamadı: %w",
"spotify_failed_request_access_token": "erişim jetonu isteği başarısız oldu: %w",
"spotify_invalid_url": "geçersiz Spotify URL'si, program veya bölüm kimliği alınamıyor: '%s'",
"spotify_label": "Spotify",
"spotify_language_field_label": "**Dil**: %s",
"spotify_languages_label": "**Diller**: %s",
"spotify_media_type_label": "**Medya Türü**: %s",
"spotify_no_episode_found": "bu kimliğe sahip bölüm bulunamadı: %s",
"spotify_no_show_found": "bu kimliğe sahip program bulunamadı: %s",
"spotify_not_configured": "Spotify yapılandırılmamış, lütfen kurulum prosedürünü çalıştırın",
"spotify_publisher_label": "**Yayıncı**: %s",
"spotify_release_date_label": "**Yayın Tarihi**: %s",
"spotify_search_description_label": "- **Açıklama**: %s",
"spotify_search_episodes_label": "- **Bölümler**: %d",
"spotify_search_failed": "arama başarısız oldu: %w",
"spotify_search_publisher_label": "- **Yayıncı**: %s",
"spotify_search_results_header": "# Spotify Arama Sonuçları",
"spotify_search_url_label": "- **URL**: %s",
"spotify_setup_description": "Spotify - Spotify'dan podcast/program meta verilerini almak için",
"spotify_show_header": "# Spotify Podcast/Programı",
"spotify_show_name_label": "**Program**: %s",
"spotify_title_label": "**Başlık**: %s",
"spotify_total_episodes_label": "**Toplam Bölüm Sayısı**: %d",
"spotify_url_help": "Meta verileri almak ve sohbete göndermek için Spotify podcast veya bölüm URL'si",
"spotify_url_label": "**URL**: %s",
"start_tag_thinking_sections": "Düşünme bölümleri için başlangıç etiketi",
"storage_error_delete": "%s silinemedi: %v",
"storage_error_load": "%s yüklenemedi: %v",
"storage_error_marshal": "%s serileştirilemedi: %s",
"storage_error_read_directory": "dizinden öğeler okunamadı: %v",
"storage_error_rename": "%s, %s olarak yeniden adlandırılamadı: %v",
"storage_error_resolve_directory": "dizin yolu çözümlenemedi: %v",
"storage_error_save": "%s kaydedilemedi: %v",
"storage_error_stat_entry": "%s girdisi hakkında bilgi alınamadı: %v",
"storage_error_unmarshal": "%s seri durumdan çıkarılamadı: %s",
"storage_invalid_name": "geçersiz ad: %q",
"strategies_available_header": "Mevcut Stratejiler:",
"strategies_cloning_repository": "Depo klonlanıyor %s (yol: %s)...\n",
"strategies_download_success": "✅ Stratejiler başarıyla indirildi ve %s konumuna kuruldu\n",
"strategies_downloaded_count": "%d strateji indirildi\n",
"strategies_downloading": "Stratejiler indiriliyor ve %s dolduruluyor...\n",
"strategies_failed_create_directory": "stratejiler dizini oluşturulamadı: %w",
"strategies_failed_download": "stratejiler indirilemedi: %w",
"strategies_git_repo_folder_question": "Stratejilerin saklandığı Git deposundaki varsayılan klasörü girin",
"strategies_git_repo_url_question": "Stratejiler için varsayılan Git deposu URL'sini girin",
"strategies_home_dir_error": "ana dizin alınamadı: %v",
"strategies_home_dir_fallback": "ana dizin alınamadı: %v, bunun yerine mevcut dizin kullanılıyor",
"strategies_label": "İstem Stratejileri",
"strategies_none_found": "strateji bulunamadı. Stratejileri indirmek için lütfen 'fabric --setup' komutunu çalıştırın",
"strategies_setup_description": "Stratejiler - İstem Stratejilerini (düşünce zinciri gibi) indirir",
"strategy_not_found": "desen %s bulunamadı. Lütfen listeyi görmek için 'fabric --liststrategies' komutunu çalıştırın.",
"strategy_path_traversal": "desen adı %q desen dizininin dışına çözümleniyor",
"stream_help": "Akış",
"suppress_thinking_tags": "Düşünme etiketleri arasına alınmış metni gizle",
"template_datetime_error_invalid_number": "göreceli zamanda geçersiz sayı: %q",
"template_datetime_error_invalid_relative_format": "geçersiz göreceli zaman biçimi",
"template_datetime_error_invalid_unit": "geçersiz zaman birimi: %q",
"template_datetime_error_relative_requires_value": "göreceli zaman bir değer gerektirir",
"template_datetime_error_unknown_operation": "tarihsaat: bilinmeyen işlem %q",
"template_extension_error": "uzantı %s hatası: %v",
"template_file_error_expand_home_dir": "dosya: ana dizin genişletilemedi: %v",
"template_file_error_invalid_line_count": "dosya: geçersiz satır sayısı %q",
"template_file_error_line_count_positive": "dosya: satır sayısı pozitif olmalıdır",
"template_file_error_open_file": "dosya: açılamadı: %v",
"template_file_error_path_contains_parent_ref": "dosya: yol '..' içeremez",
"template_file_error_read_file": "dosya: okunamadı: %v",
"template_file_error_scanner_read": "dosya: okuma hatası: %v",
"template_file_error_size_exceeds_limit": "dosya: boyut %d, %d baytlık sınırııyor",
"template_file_error_stat_file": "dosya: dosya durumu alınamadı: %v",
"template_file_error_stat_open_file": "dosya: durum alınamadı: %v",
"template_file_error_tail_requires_path_lines": "dosya: tail, yol|satır biçimini gerektirir",
"template_file_error_unknown_operation": "dosya: bilinmeyen işlem %q (desteklenenler: read, tail, exists, size, modified)",
"template_file_log_cleaned_path": "Dosya: temizlenmiş yol %q",
"template_file_log_exists_for_path": "Dosya: var=%v, yol %q için",
"template_file_log_modified_for_path": "Dosya: değiştirildi=%q, yol %q için",
"template_file_log_operation_value": "Dosya: işlem=%q değer=%q",
"template_file_log_read_bytes": "Dosya: %d bayt okundu",
"template_file_log_read_total_return_last": "Dosya: toplam %d satır okundu, son %d döndürülüyor",
"template_file_log_reading_last_lines": "File: reading last %d lines from %q",
"template_file_log_size_for_path": "Dosya: boyut=%d, yol %q için",
"template_file_log_tail_returning_lines": "Dosya: tail %d satır döndürüyor",
"template_file_log_validating_path": "Dosya: yol doğrulanıyor %q",
"template_hash_open_file": "dosya açılamadı: %w",
"template_hash_read_file": "dosya okunamadı: %w",
"template_missing_required_variable": "gerekli değişken eksik: %s",
"template_plugin_error": "eklenti %s hatası: %v",
"template_processing_stuck": "şablon işleme takıldı - potansiyel sonsuz döngü",
"template_sys_error_env_requires_var": "ortam: değişken adı gerekli",
"template_sys_error_home": "ana dizin alınamadı: %v",
"template_sys_error_hostname": "ana bilgisayar adı alınamadı: %v",
"template_sys_error_pwd": "çalışma dizini alınamadı: %v",
"template_sys_error_unknown_operation": "sistem: bilinmeyen işlem %q",
"template_sys_error_user": "mevcut kullanıcı alınamadı: %v",
"template_text_empty_input": "metin: %q işlemi için boş giriş",
"template_text_unknown_operation": "metin: bilinmeyen metin işlemi %q (desteklenenler: upper, lower, title, trim)",
"template_unknown_plugin_namespace": "bilinmeyen eklenti ad alanı: %s",
"template_utils_failed_get_absolute_path": "mutlak yol alınamadı: %w",
"template_utils_failed_get_home_dir": "kullanıcı ana dizini alınamadı: %w",
"template_utils_path_not_exist": "yol mevcut değil: %w",
"transcription_model_required": "transkripsiyon modeli gereklidir (--transcribe-model kullanın)",
"transparent_background_png_webp_only": "şeffaf arka plan yalnızca PNG ve WebP biçimleriyle kullanılabilir, %s ile değil",
"tts_audio_generated_successfully": "TTS sesi başarıyla oluşturuldu ve şuraya kaydedildi: %s\n",
"tts_model_requires_audio_output": "TTS modeli '%s' ses çıkışı gerektirir. Lütfen -o bayrağıyla bir ses çıkış dosyası belirtin (örn: -o output.wav)",
"tts_voice_name": "Desteklenen modeller için TTS ses adı (örn: Kore, Charon, Puck)",
"unsupported_conversion": "desteklenmeyen dönüştürme: %v öğesinden %v öğesine",
"update_patterns": "Desenleri Güncelle",
"usage_header": "Kullanım:",
"use_model_defaults_raw_help": "Modelin varsayılanlarını sohbet seçeneklerini (temperature, top_p vb.) göndermeden kullanın. Yalnızca OpenAI uyumlu sağlayıcıları etkiler. Anthropic modelleri, modele özgü gereksinimlere uymak için her zaman akıllı parametre seçimi kullanır.",
"util_error_accessing_config_path": "varsayılan yapılandırma yoluna erişimde hata: %w",
"util_error_determine_home_directory": "kullanıcı ana dizini belirlenemedi: %w",
"util_error_get_absolute_path": "mutlak yol alınamadı",
"util_error_path_is_empty": "yol boş",
"util_error_resolve_home_directory": "ana dizin çözümlenemedi",
"util_error_resolve_symlinks": "sembolik bağlantılar çözümlenemedi: %w",
"vendor_no_transcription_support": "sağlayıcı %s ses transkripsiyonunu desteklemiyor",
"vendor_not_configured": "sağlayıcı %s yapılandırılmadı",
"vendor_not_found": "sağlayıcı %s bulunamadı",
"vendors_no_ai_vendors_configured_read_models": "modelleri okumak için yapılandırılmış hiçbir yapay zeka sağlayıcısı yok",
"vertexai_client_not_initialized": "VertexAI istemcisi başlatılmadı",
"vertexai_error_api_status": "API %d durumunu döndürdü: %s",
"vertexai_error_create_request": "istek oluşturulamadı: %w",
"vertexai_error_parse_response": "yanıt ayrıştırılamadı: %w",
"vertexai_error_read_response": "yanıt okunamadı: %w",
"vertexai_error_request_failed": "istek başarısız oldu: %w",
"vertexai_error_response_too_large": "yanıt çok büyük (>%d bayt)",
"vertexai_failed_gemini_client": "Gemini istemcisi oluşturulamadı: %w",
"vertexai_failed_google_credentials": "Google kimlik bilgileri alınamadı (ADC'nin yapılandırıldığından emin olun): %w",
"vertexai_no_content_in_response": "yanıtta içerik yok",
"vertexai_no_conversational_models": "konuşma modeli bulunamadı",
"vertexai_no_models_found": "hiçbir yayıncıdan model bulunamadı",
"vertexai_no_valid_messages": "gönderilecek geçerli mesaj yok",
"vertexai_stream_error": "Hata: %v",
"wipe_context": "Bağlamı sil",
"wipe_session": "Oturumu sil",
"youtube_api_key_required": "Yorumlar ve meta veriler için YouTube API anahtarı gerekli. Yapılandırmak için 'fabric --setup' komutunu çalıştırın.",
"youtube_auth_required_bot_detection": "YouTube kimlik doğrulama gerektiriyor (bot algılama). BROWSER'ın chrome, firefox, brave vb. olduğu durumlarda --yt-dlp-args='--cookies-from-browser BROWSER' kullanın.",
"youtube_empty_seconds_string": "boş saniye dizesi",
"youtube_error_getting_comments": "yorumlar alınırken hata oluştu: %v",
"youtube_error_getting_metadata": "video meta verileri alınırken hata oluştu: %v",
"youtube_error_getting_video_details": "video detayları alınırken hata oluştu: %v",
"youtube_error_parsing_duration": "video süresi ayrıştırılırken hata oluştu: %v",
"youtube_error_saving_csv": "videolar CSV'ye kaydedilirken hata oluştu: %v",
"youtube_extract_visual_data_help": "OCR ve FFmpeg kullanarak videodan görsel veri çıkarın",
"youtube_failed_create_temp_dir": "geçici dizin oluşturulamadı: %v",
"youtube_failed_fetch_comments": "yorumlar alınamadı: %v",
"youtube_failed_get_stream_url": "yt-dlp aracılığıyla akış URL'si alınamadı: %v",
"youtube_failed_parse_http_stream_url": "yt-dlp çıktısından geçerli HTTP akış URL'si ayrıştırılamadı",
"youtube_failed_walk_directory": "dizin gezilemedi: %v",
"youtube_ffmpeg_frame_extraction_failed": "ffmpeg kare çıkarma başarısız oldu: %v, çıktı: %s",
"youtube_ffmpeg_required_visual_extraction": "görsel çıkarma için ffmpeg gerekli ancak PATH'te bulunamadı",
"youtube_invalid_duration_string": "geçersiz süre dizesi: %s",
"youtube_invalid_seconds_format": "geçersiz saniye biçimi %q: %w",
"youtube_invalid_timestamp_format": "geçersiz zaman damgası biçimi: %s",
"youtube_invalid_url": "geçersiz YouTube URL'si, video veya çalma listesi kimliği alınamıyor: '%s'",
"youtube_invalid_ytdlp_arguments": "geçersiz yt-dlp argümanları: %v",
"youtube_label": "YouTube",
"youtube_no_clear_text_visual_frames": "video görsel karelerinde net metin bulunamadı",
"youtube_no_transcript_content": "VTT dosyasında transkript içeriği bulunamadı",
"youtube_no_url_provided": "YouTube URL'si sağlanmadı",
"youtube_no_video_found_with_id": "ID'si %s olan video bulunamadı",
"youtube_no_video_id_found": "URL'de video kimliği bulunamadı",
"youtube_no_vtt_files_found": "dizinde VTT dosyası bulunamadı",
"youtube_not_configured": "YouTube yapılandırılmamış, lütfen kurulum prosedürünü çalıştırın",
"youtube_playlist_header": "Çalma Listesi: %s",
"youtube_playlist_saved_to": "Çalma listesi %s konumuna kaydedildi",
"youtube_rate_limit_exceeded": "YouTube hız sınırııldı. Daha sonra tekrar deneyin veya istekleri yavaşlatmak için '--sleep-requests 1' gibi farklı yt-dlp argümanları kullanın.",
"youtube_setup_description": "YouTube - video transkriptlerini (yt-dlp aracılığıyla) ve yorumları/meta verilerini (YouTube API aracılığıyla) almak için",
"youtube_tesseract_frame_failed": "tesseract %d. karede başarısız oldu: %v, stderr: %s",
"youtube_tesseract_required_visual_extraction": "görsel çıkarma için tesseract gerekli ancak PATH'te bulunamadı",
"youtube_url_help": "Transkripti ve yorumları almak, sohbete göndermek veya konsola yazdırmak ve çıktı dosyasına kaydetmek için YouTube video veya çalma listesi \"URL\"si",
"youtube_url_is_playlist_not_video": "URL bir çalma listesi, video değil",
"youtube_video_id_title_header": "Video Kimliği: Başlık",
"youtube_visual_fps_help": "Sahne algılama kullanmak yerine saniye başına belirli sayıda kare çıkarın",
"youtube_visual_frame_cue": "[GÖRSEL KARE İPUCU]",
"youtube_visual_sensitivity_help": "FFmpeg sahne algılama toleransı (0.0 - 1.0)",
"youtube_ytdlp_not_found": "yt-dlp PATH'te bulunamadı. YouTube transkript işlevselliğini kullanmak için lütfen yt-dlp'yi kurun",
"youtube_ytdlp_required_visual_extraction": "görsel çıkarma için yt-dlp gerekli ancak PATH'te bulunamadı",
"youtube_ytdlp_stderr_error": "yt-dlp stderr okunurken hata oluştu"
}

View file

@ -110,21 +110,43 @@
"choose_pattern_from_available": "从可用模式中选择一个模式",
"choose_session_from_available": "从可用会话中选择一个会话",
"choose_strategy_from_available": "从可用策略中选择一个策略",
"codex_api_base_url_question": "请输入您的 Codex API 基础 URL",
"codex_auth_base_url_invalid": "Codex 认证基础 URL 无效:%w",
"codex_auth_base_url_question": "请输入您的 Codex OAuth 基础 URL",
"codex_browser_open_fallback": "如果浏览器未打开,请导航到此 URL 进行身份验证:",
"codex_decode_models_response_failed": "解码 Codex 模型响应失败:%w",
"codex_decode_refresh_response_failed": "解码刷新的 Codex 令牌响应失败:%w",
"codex_decode_token_response_failed": "解码 Codex 令牌交换响应失败:%w",
"codex_image_file_not_supported": "Codex 供应商不支持 --image-file。请改用图片附件。",
"codex_login_account_changed": "Codex 登录关联的 ChatGPT 账户与保存的配置不同。请重新运行 'fabric --setup'。",
"codex_login_completed": "Codex 登录完成",
"codex_login_failed": "Codex 登录失败:%s",
"codex_login_invalid": "您的 Codex 登录已失效。请重新运行 'fabric --setup'。",
"codex_login_missing_account_claim": "Codex 登录未包含 ChatGPT 账户 ID。此登录状态不受支持。",
"codex_login_missing_auth_code": "Codex 登录未返回授权码。",
"codex_login_missing_tokens": "Codex 登录未返回所需的访问令牌和刷新令牌。",
"codex_login_refresh_failed": "无法刷新 Codex 登录。请重新运行 'fabric --setup'。",
"codex_login_return_to_fabric": "返回 Fabric。",
"codex_login_revoked": "Codex 登录已过期或被撤销。请重新运行 'fabric --setup'。",
"codex_login_server_stopped": "Codex 登录回调服务器在身份验证完成前停止。",
"codex_login_state_mismatch": "由于 OAuth 状态不匹配,无法验证 Codex 登录。",
"codex_login_timed_out": "Codex 登录在身份验证完成前超时。",
"codex_oauth_missing_auth_code": "缺少授权码",
"codex_oauth_random_state_failed": "生成安全随机 OAuth 状态失败:%w",
"codex_oauth_server_start_failed": "启动本地 OAuth 回调服务器失败:%w",
"codex_oauth_state_mismatch": "状态不匹配",
"codex_provider_error": "Codex 供应商错误(状态 %d%s",
"codex_refresh_failed_status": "刷新 Codex 登录失败(状态 %d",
"codex_refresh_login_failed": "刷新 Codex 登录失败:%w",
"codex_refresh_token_required": "需要 Codex 刷新令牌。请重新运行 'fabric --setup'。",
"codex_replay_body_unavailable": "请求正文无法重放用于 Codex 重新认证重试",
"codex_request_failed": "Codex 请求失败:%w",
"codex_request_failed_status": "Codex 请求失败,状态 %d",
"codex_starting_browser_login": "正在启动基于浏览器的 OpenAI 登录以连接 Codex。",
"codex_token_exchange_failed": "Codex 令牌交换失败:%w",
"codex_token_persist_failed": "无法保存 Codex 登录:%w",
"codex_token_refresh_missing_access_token": "Codex 令牌刷新未返回访问令牌。",
"codex_usage_limit_reached": "已达到 Codex 使用限制",
"command_completed_successfully": "命令执行成功",
"compression_level_jpeg_webp": "JPEG/WebP 格式的压缩级别 0-100默认未设置",
"config_file_not_found": "找不到配置文件:%s",
@ -151,6 +173,7 @@
"custom_patterns_setup_description": "自定义模式 - 设置您的自定义模式目录",
"custom_patterns_warning_create_directory": "警告:无法创建自定义模式目录 %s%v\n",
"db_error_loading_env_file": "加载 .env 文件错误:%w",
"db_error_updating_env_file": "更新 .env 文件错误:%w",
"defaults_model_context_length_question": "请输入模型上下文长度",
"defaults_model_question": "请输入您的默认模型的索引或名称",
"defaults_setup_description": "默认 AI 提供商和模型",
@ -161,7 +184,7 @@
"digitalocean_models_request_failed_with_status": "DigitalOcean 模型请求失败,状态码 %d%s",
"disable_openai_responses_api": "禁用 OpenAI 响应 API默认false",
"disable_pattern_variable_replacement": "禁用模式变量替换",
"enable_web_search_tool": "为支持的模型启用网络搜索工具Anthropic、OpenAI、Gemini",
"enable_web_search_tool": "为支持的模型启用网络搜索工具Anthropic、OpenAI、Gemini、Grok",
"end_tag_thinking_sections": "思考部分的结束标签",
"error_creating_audio_file": "创建音频文件时出错:%v",
"error_creating_file": "创建文件时出错:%v",
@ -352,6 +375,7 @@
"ollama_invalid_data_url_format": "无效的数据 URL 格式",
"ollama_invalid_http_timeout_using_default": "无效的 HTTP 超时时间 '%s'%v使用默认值",
"ollama_invalid_num_ctx_in_request": "请求中的 num_ctx 无效:%v",
"ollama_invalid_request_body": "无效的请求正文",
"ollama_no_content_from_upstream": "未从上游 Fabric 服务器收到内容",
"ollama_num_ctx_exceeds_maximum": "num_ctx 超过允许的最大值 %d",
"ollama_num_ctx_invalid_type": "num_ctx 必须是数字,收到无效类型",
@ -365,6 +389,7 @@
"ollama_sse_buffer_limit": "SSE 行超过 1MB 缓冲区限制 - 数据行过大",
"ollama_upstream_non_2xx": "上游 Fabric 服务器返回非 2xx 状态 %d%s",
"ollama_upstream_non_2xx_body_unreadable": "上游 Fabric 服务器返回非 2xx 状态 %d 且无法读取正文:%v",
"ollama_upstream_request_failed": "无法连接上游 Fabric 服务器",
"ollama_upstream_returned_status": "上游 Fabric 服务器返回状态 %d",
"ollama_warning_no_content": "警告:未从上游 Fabric 服务器收到内容",
"ollama_warning_parse_variables": "警告:无法将 options.variables 解析为 JSON%v",
@ -385,6 +410,7 @@
"openai_image_failed_to_save_image": "保存图像到 %s 失败:%w",
"openai_image_saved_to": "图像已保存到:%s",
"openai_model_no_image_generation": "模型 '%s' 不支持图像生成。支持的模型:%s",
"openai_models_rate_limited": "从提供商 %s 获取模型时超出速率限制;请在 %s 秒后重试",
"openai_models_response_too_large": "来自提供商 %s 的模型响应过大(>%d 字节)",
"openai_unable_to_parse_models_response": "无法解析模型响应;原始响应:%s",
"openai_unexpected_status_code_read_error": "意外的状态码:来自提供商 %s 的 %d读取响应主体失败%v)",
@ -400,6 +426,7 @@
"output_video_metadata": "输出视频元数据",
"path_to_yaml_config": "YAML 配置文件路径",
"pattern_not_found_list_available": "未找到模式 '%s'。运行 'fabric -l' 查看可用模式",
"pattern_invalid_name": "无效的模式名称:%q",
"pattern_not_found_no_patterns": "未找到模式 '%s'。\n\n未安装任何模式要解决此问题\n • 运行 'fabric --setup' 配置并下载模式\n • 或运行 'fabric -U' 直接下载/更新模式",
"pattern_variables_help": "模式变量的值,例如 -v=#role:expert -v=#points:30",
"patterns_cloning_repository": "正在克隆仓库 %s至路径%s...\\n",
@ -473,6 +500,8 @@
"prefer_playlist_over_video": "如果 URL 中同时存在两个 ID则优先选择播放列表而不是视频",
"print_context": "打印上下文",
"print_current_version": "打印当前版本",
"print_metadata_to_stderr": "将元数据(输入/输出令牌)打印到 stderr",
"print_pattern_contents": "将指定模式的内容打印到终端",
"print_session": "打印会话",
"register_new_extension": "从配置文件路径注册新扩展",
"remove_registered_extension": "按名称删除已注册的扩展",
@ -486,10 +515,12 @@
"send_desktop_notification": "命令完成时发送桌面通知",
"serve_fabric_api_ollama_endpoints": "提供带有 ollama 端点的 Fabric REST API 服务",
"serve_fabric_rest_api": "提供 Fabric REST API 服务",
"server_api_key_required": "拒绝在非回环地址 %s 上提供服务(未配置 API 密钥):请设置 --api-key 或 FABRIC_API_KEY或绑定回环地址如 127.0.0.1:8080",
"server_chat_error": "错误:%v",
"server_error_marshaling_response": "序列化响应错误:%v",
"server_error_writing_response": "写入响应错误:%v",
"server_invalid_request_format": "无效的请求格式:%v",
"server_no_api_key_warning": "正在启动 REST API 服务器,未启用 API 密钥身份验证。这可能带来安全风险。",
"sessions_creating_new": "正在创建新会话:%s\n",
"set_debug_level": "设置调试级别0=关闭1=基本2=详细3=跟踪4=wire",
"set_frequency_penalty": "设置频率惩罚",
@ -581,6 +612,7 @@
"spotify_show_name_label": "**节目**%s",
"spotify_title_label": "**标题**%s",
"spotify_total_episodes_label": "**总剧集数**%d",
"spotify_url_help": "Spotify 播客或单集 URL用于获取元数据并发送到聊天",
"spotify_url_label": "**URL**%s",
"start_tag_thinking_sections": "思考部分的开始标签",
"storage_error_delete": "无法删除 %s%v",
@ -592,6 +624,7 @@
"storage_error_save": "无法保存 %s%v",
"storage_error_stat_entry": "无法获取条目 %s 的信息:%v",
"storage_error_unmarshal": "无法反序列化 %s%s",
"storage_invalid_name": "无效的名称:%q",
"strategies_available_header": "可用的策略:",
"strategies_cloning_repository": "正在克隆仓库 %s至路径%s...\\n",
"strategies_download_success": "✅ 已成功下载并安装策略到 %s\\n",
@ -698,15 +731,21 @@
"youtube_error_getting_video_details": "获取视频详情时出错:%v",
"youtube_error_parsing_duration": "解析视频时长时出错:%v",
"youtube_error_saving_csv": "将视频保存为 CSV 时出错:%v",
"youtube_extract_visual_data_help": "使用 OCR 和 FFmpeg 从视频中提取视觉数据",
"youtube_failed_create_temp_dir": "创建临时目录失败:%v",
"youtube_failed_fetch_comments": "获取评论失败:%v",
"youtube_failed_get_stream_url": "无法通过 yt-dlp 获取流 URL%v",
"youtube_failed_parse_http_stream_url": "无法从 yt-dlp 输出中解析出有效的 HTTP 流 URL",
"youtube_failed_walk_directory": "遍历目录失败:%v",
"youtube_ffmpeg_frame_extraction_failed": "ffmpeg 提取帧失败:%v输出%s",
"youtube_ffmpeg_required_visual_extraction": "视觉提取需要 ffmpeg但在 PATH 中未找到",
"youtube_invalid_duration_string": "无效的时长字符串:%s",
"youtube_invalid_seconds_format": "无效的秒数格式 %q%w",
"youtube_invalid_timestamp_format": "无效的时间戳格式:%s",
"youtube_invalid_url": "无效的 YouTube URL无法获取视频或播放列表 ID'%s'",
"youtube_invalid_ytdlp_arguments": "无效的 yt-dlp 参数:%v",
"youtube_label": "YouTube",
"youtube_no_clear_text_visual_frames": "在视频视觉帧中未找到清晰文本",
"youtube_no_transcript_content": "在 VTT 文件中未找到转录内容",
"youtube_no_url_provided": "未提供 YouTube URL",
"youtube_no_video_found_with_id": "未找到 ID 为 %s 的视频",
@ -717,9 +756,15 @@
"youtube_playlist_saved_to": "播放列表已保存到 %s",
"youtube_rate_limit_exceeded": "超过 YouTube 速率限制。请稍后重试,或使用不同的 yt-dlp 参数(如 '--sleep-requests 1')来减慢请求速度。",
"youtube_setup_description": "YouTube - 获取视频转录(通过 yt-dlp和评论/元数据(通过 YouTube API",
"youtube_tesseract_frame_failed": "tesseract 在第 %d 帧上失败:%vstderr%s",
"youtube_tesseract_required_visual_extraction": "视觉提取需要 tesseract但在 PATH 中未找到",
"youtube_url_help": "YouTube 视频或播放列表 \"URL\",用于获取转录、评论并发送到聊天或打印到控制台并存储到输出文件",
"youtube_url_is_playlist_not_video": "URL 是播放列表,而不是视频",
"youtube_video_id_title_header": "视频 ID标题",
"youtube_visual_fps_help": "按指定的每秒帧数提取画面,而不是使用场景检测",
"youtube_visual_frame_cue": "[视觉帧提示]",
"youtube_visual_sensitivity_help": "FFmpeg 场景检测的容差0.0 - 1.0",
"youtube_ytdlp_not_found": "在 PATH 中未找到 yt-dlp。请安装 yt-dlp 以使用 YouTube 转录功能",
"youtube_ytdlp_required_visual_extraction": "视觉提取需要 yt-dlp但在 PATH 中未找到",
"youtube_ytdlp_stderr_error": "读取 yt-dlp stderr 时出错"
}

View file

@ -6,6 +6,7 @@ import (
neturl "net/url"
"os"
"path"
"slices"
"strconv"
"strings"
@ -24,6 +25,22 @@ const webSearchToolName = "web_search"
const webSearchToolType = "web_search_20250305"
const sourcesHeader = "## Sources"
// These models reject non-default sampling parameters.
// Omit these params entirely for safest compatibility.
var samplingParamsDisallowedPrefixes = []string{
"claude-opus-4-7",
"claude-opus-4-8",
"claude-opus-5",
"claude-sonnet-5",
"claude-fable-5",
}
func modelDisallowsSamplingParams(model string) bool {
return slices.ContainsFunc(samplingParamsDisallowedPrefixes, func(prefix string) bool {
return strings.HasPrefix(model, prefix)
})
}
func NewClient() (ret *Client) {
vendorName := "Anthropic"
ret = &Client{}
@ -38,43 +55,47 @@ func NewClient() (ret *Client) {
ret.defaultRequiredUserMessage = "Hi"
ret.models = []string{
// The following are the current supported models
string(anthropic.ModelClaudeFable5),
string(anthropic.ModelClaudeSonnet5),
string(anthropic.ModelClaudeOpus5),
string(anthropic.ModelClaudeOpus4_8),
string(anthropic.ModelClaudeOpus4_7),
string(anthropic.ModelClaudeSonnet4_6),
string(anthropic.ModelClaudeOpus4_6),
string(anthropic.ModelClaudeOpus4_5_20251101),
string(anthropic.ModelClaudeOpus4_5),
string(anthropic.ModelClaudeHaiku4_5),
string(anthropic.ModelClaudeHaiku4_5_20251001),
string(anthropic.ModelClaudeSonnet4_20250514),
string(anthropic.ModelClaudeSonnet4_0),
string(anthropic.ModelClaudeSonnet4_5),
string(anthropic.ModelClaudeSonnet4_5_20250929),
string(anthropic.ModelClaudeOpus4_0),
string(anthropic.ModelClaudeOpus4_20250514),
string(anthropic.ModelClaudeOpus4_1_20250805),
}
// context1M is the beta header historically required to opt into the
// 1-million token context window. On current models 1M is the DEFAULT and
// no header is needed; we still send it defensively (Send/SendStream retry
// without it if a model rejects it), so only models with a genuine 1M
// window belong here.
//
// Verified against
// https://platform.claude.com/docs/en/build-with-claude/context-windows#context-window-sizes-by-model
// Excluded because they are 200K-context models: Sonnet 4.5, Opus 4.5,
// Opus 4.1, and Haiku 4.5.
//
// Kept separate from the main model list for easier updates.
const context1M = "context-1m-2025-08-07"
ret.modelBetas = map[string][]string{
// See https://platform.claude.com/docs/en/build-with-claude/context-windows#1-m-token-context-window
// Claude Opus 4.6, Sonnet 4.6, Sonnet 4.5, and Sonnet 4 support a 1-million token context window.
// Claude 5 family
string(anthropic.ModelClaudeFable5): {context1M},
string(anthropic.ModelClaudeOpus5): {context1M},
string(anthropic.ModelClaudeSonnet5): {context1M},
// This list can change over time as Anthropic updates their models and beta features, so we maintain it separately from the main model list
// for easier updates.
// Claude Opus 4.x (1M-capable)
string(anthropic.ModelClaudeOpus4_8): {context1M},
string(anthropic.ModelClaudeOpus4_7): {context1M},
string(anthropic.ModelClaudeOpus4_6): {context1M},
// Claude Sonnet 4 variants (1M context support)
string(anthropic.ModelClaudeSonnet4_20250514): {"context-1m-2025-08-07"},
string(anthropic.ModelClaudeSonnet4_0): {"context-1m-2025-08-07"},
// Claude Sonnet 4.5 variants (1M context support)
string(anthropic.ModelClaudeSonnet4_5): {"context-1m-2025-08-07"},
string(anthropic.ModelClaudeSonnet4_5_20250929): {"context-1m-2025-08-07"},
// Claude Sonnet 4.6 (1M context support)
string(anthropic.ModelClaudeSonnet4_6): {"context-1m-2025-08-07"},
// Claude Opus 4.5 and 4.6 variants (1M context support)
string(anthropic.ModelClaudeOpus4_5): {"context-1m-2025-08-07"},
string(anthropic.ModelClaudeOpus4_6): {"context-1m-2025-08-07"},
string(anthropic.ModelClaudeOpus4_5_20251101): {"context-1m-2025-08-07"},
// Claude Sonnet 4.x (1M-capable)
string(anthropic.ModelClaudeSonnet4_6): {context1M},
}
return
@ -125,7 +146,7 @@ func (an *Client) configure() (err error) {
return
}
func (an *Client) ListModels() (ret []string, err error) {
func (an *Client) ListModels(context.Context) (ret []string, err error) {
return an.models, nil
}
@ -150,7 +171,7 @@ func parseThinking(level domain.ThinkingLevel) (anthropic.ThinkingConfigParamUni
}
func (an *Client) SendStream(
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
) (err error) {
messages := an.toMessages(msgs)
if len(messages) == 0 {
@ -159,8 +180,6 @@ func (an *Client) SendStream(
return
}
ctx := context.Background()
params := an.buildMessageParams(messages, opts)
betas := an.modelBetas[opts.Model]
var reqOpts []option.RequestOption
@ -216,15 +235,21 @@ func (an *Client) SendStream(
func (an *Client) buildMessageParams(msgs []anthropic.MessageParam, opts *domain.ChatOptions) (
params anthropic.MessageNewParams) {
maxTokens := an.maxTokens
if opts.MaxTokens > 0 {
maxTokens = opts.MaxTokens
}
params = anthropic.MessageNewParams{
Model: anthropic.Model(opts.Model),
MaxTokens: int64(an.maxTokens),
MaxTokens: int64(maxTokens),
Messages: msgs,
}
// Only set one of Temperature or TopP as some models don't allow both
// Always set temperature to ensure consistent behavior (Anthropic default is 1.0, Fabric default is 0.7)
if opts.TopP != domain.DefaultTopP {
// Claude Opus 4.7 disallows sampling params; omit both temperature and top_p.
if modelDisallowsSamplingParams(opts.Model) {
// Intentionally omit both fields.
} else if opts.TopP != domain.DefaultTopP {
// User explicitly set TopP, so use that instead of temperature
params.TopP = anthropic.Opt(opts.TopP)
} else {

View file

@ -1,6 +1,7 @@
package anthropic
import (
"context"
"strings"
"testing"
@ -34,7 +35,7 @@ func TestNewClient_DefaultInitialization(t *testing.T) {
func TestClientListModels(t *testing.T) {
client := NewClient()
models, err := client.ListModels()
models, err := client.ListModels(context.Background())
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
@ -52,7 +53,7 @@ func TestClientListModels(t *testing.T) {
func TestClient_ListModels_ReturnsCorrectModels(t *testing.T) {
client := NewClient()
models, err := client.ListModels()
models, err := client.ListModels(context.Background())
if err != nil {
t.Fatalf("Expected no error, got %v", err)
@ -98,6 +99,43 @@ func TestBuildMessageParams_WithoutSearch(t *testing.T) {
}
}
func TestBuildMessageParams_UsesConfiguredMaxTokensByDefault(t *testing.T) {
client := NewClient()
opts := &domain.ChatOptions{
Model: "claude-3-5-sonnet-latest",
Temperature: domain.DefaultTemperature,
TopP: domain.DefaultTopP,
}
messages := []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello")),
}
params := client.buildMessageParams(messages, opts)
if params.MaxTokens != int64(client.maxTokens) {
t.Errorf("Expected default max_tokens %d, got %d", client.maxTokens, params.MaxTokens)
}
}
func TestBuildMessageParams_UsesChatOptionsMaxTokens(t *testing.T) {
client := NewClient()
opts := &domain.ChatOptions{
Model: "claude-3-5-sonnet-latest",
Temperature: domain.DefaultTemperature,
TopP: domain.DefaultTopP,
MaxTokens: 8192,
}
messages := []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello")),
}
params := client.buildMessageParams(messages, opts)
if params.MaxTokens != int64(opts.MaxTokens) {
t.Errorf("Expected max_tokens %d, got %d", opts.MaxTokens, params.MaxTokens)
}
}
func TestBuildMessageParams_WithSearch(t *testing.T) {
client := NewClient()
opts := &domain.ChatOptions{
@ -169,9 +207,32 @@ func TestBuildMessageParams_WithSearchAndLocation(t *testing.T) {
}
}
func TestBuildMessageParams_Opus47OmitsSamplingParams(t *testing.T) {
client := NewClient()
opts := &domain.ChatOptions{
Model: string(anthropic.ModelClaudeOpus4_7),
Temperature: 0.8,
TopP: 0.8,
Search: false,
}
messages := []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello")),
}
params := client.buildMessageParams(messages, opts)
if params.Temperature.Value != 0 {
t.Errorf("expected temperature to be omitted for %s, got %f", opts.Model, params.Temperature.Value)
}
if params.TopP.Value != 0 {
t.Errorf("expected top_p to be omitted for %s, got %f", opts.Model, params.TopP.Value)
}
}
func TestModelBetasConfiguration(t *testing.T) {
client := NewClient()
model := string(anthropic.ModelClaudeSonnet4_20250514)
model := string(anthropic.ModelClaudeSonnet5)
betas, ok := client.modelBetas[model]
if !ok || len(betas) != 1 || betas[0] != "context-1m-2025-08-07" {
t.Errorf("expected beta mapping for %s", model)

View file

@ -1,6 +1,7 @@
package azure
import (
"context"
"errors"
"strings"
@ -66,7 +67,7 @@ func (oi *Client) configure() error {
return nil
}
func (oi *Client) ListModels() (ret []string, err error) {
func (oi *Client) ListModels(context.Context) (ret []string, err error) {
ret = oi.apiDeployments
return
}

View file

@ -2,6 +2,7 @@ package azure
import (
"bytes"
"context"
"io"
"net/http"
"testing"
@ -78,7 +79,7 @@ func TestListModels(t *testing.T) {
client := NewClient()
client.apiDeployments = []string{"deployment1", "deployment2"}
models, err := client.ListModels()
models, err := client.ListModels(context.Background())
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}

View file

@ -1,6 +1,7 @@
package azure_entra
import (
"context"
"errors"
"fmt"
"strings"
@ -71,7 +72,7 @@ func (c *Client) configure() error {
return nil
}
func (c *Client) ListModels() (ret []string, err error) {
func (c *Client) ListModels(context.Context) (ret []string, err error) {
ret = c.apiDeployments
return
}

View file

@ -1,6 +1,7 @@
package azure_entra
import (
"context"
"testing"
)
@ -47,7 +48,7 @@ func TestListModels(t *testing.T) {
client := NewClient()
client.apiDeployments = []string{"gpt-4o", "gpt-5"}
models, err := client.ListModels()
models, err := client.ListModels(context.Background())
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}

View file

@ -35,7 +35,7 @@ var _ ai.Vendor = (*Client)(nil)
// are handled by the Client.
type Backend interface {
// ListModels returns the list of models available for this backend
ListModels() ([]string, error)
ListModels(context.Context) ([]string, error)
// BuildEndpoint constructs the full API endpoint URL for the given model
BuildEndpoint(baseURL, model string) string
@ -132,11 +132,11 @@ func (c *Client) IsConfigured() bool {
}
// ListModels delegates to the active backend
func (c *Client) ListModels() ([]string, error) {
func (c *Client) ListModels(ctx context.Context) ([]string, error) {
if c.backend == nil {
return nil, errors.New(i18n.T("azureaigateway_backend_not_initialized"))
}
return c.backend.ListModels()
return c.backend.ListModels(ctx)
}
// Send sends a non-streaming request through the APIM gateway.
@ -199,18 +199,13 @@ func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
}
// SendStream falls back to non-streaming (APIM gateway doesn't support SSE pass-through).
//
// NOTE: This method uses context.Background() because the ai.Vendor interface does not
// accept a context parameter for SendStream. If the caller disconnects, this request will
// continue until the gateway timeout (300s). A future update to the ai.Vendor interface
// should add context propagation to SendStream.
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
func (c *Client) SendStream(ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
defer close(channel)
if c.backend == nil {
return errors.New(i18n.T("azureaigateway_backend_not_initialized"))
}
ctx, cancel := context.WithTimeout(context.Background(), gatewayTimeout)
ctx, cancel := context.WithTimeout(ctx, gatewayTimeout)
defer cancel()
result, err := c.Send(ctx, msgs, opts)

View file

@ -60,7 +60,7 @@ func TestBedrockAuthHeader(t *testing.T) {
func TestBedrockListModels(t *testing.T) {
b := NewBedrockBackend("key")
models, err := b.ListModels()
models, err := b.ListModels(context.Background())
if err != nil {
t.Fatalf("ListModels() error = %v", err)
}
@ -253,7 +253,7 @@ func TestAzureOpenAIAuthHeader(t *testing.T) {
func TestAzureOpenAIListModels(t *testing.T) {
b := NewAzureOpenAIBackend("key", "")
models, err := b.ListModels()
models, err := b.ListModels(context.Background())
if err != nil {
t.Fatalf("ListModels() error = %v", err)
}
@ -336,7 +336,7 @@ func TestVertexAIAuthHeader(t *testing.T) {
func TestVertexAIListModels(t *testing.T) {
b := NewVertexAIBackend("key")
models, err := b.ListModels()
models, err := b.ListModels(context.Background())
if err != nil {
t.Fatalf("ListModels() error = %v", err)
}
@ -549,7 +549,7 @@ func TestConfigureInvalidBackend(t *testing.T) {
func TestListModelsWithoutInit(t *testing.T) {
c := NewClient()
_, err := c.ListModels()
_, err := c.ListModels(context.Background())
if err == nil {
t.Error("ListModels() expected error when backend not initialized")
}
@ -848,7 +848,7 @@ func TestSendStreamWithoutBackendInit(t *testing.T) {
}
channel := make(chan domain.StreamUpdate, 1)
err := c.SendStream(msgs, opts, channel)
err := c.SendStream(context.Background(), msgs, opts, channel)
if err == nil {
t.Fatal("SendStream() expected error when backend not initialized")
}
@ -901,7 +901,7 @@ func TestSendStreamFallback(t *testing.T) {
}
channel := make(chan domain.StreamUpdate, 10)
err := c.SendStream(msgs, opts, channel)
err := c.SendStream(context.Background(), msgs, opts, channel)
if err != nil {
t.Fatalf("SendStream() error = %v", err)
}

View file

@ -2,6 +2,7 @@
package azureaigateway
import (
"context"
"encoding/json"
"errors"
"fmt"
@ -34,7 +35,7 @@ func NewAzureOpenAIBackend(subscriptionKey, apiVersion string) *AzureOpenAIBacke
// ListModels returns the list of models available through Azure OpenAI.
// These are deployment names that must exist in your Azure OpenAI resource.
func (b *AzureOpenAIBackend) ListModels() ([]string, error) {
func (b *AzureOpenAIBackend) ListModels(_ context.Context) ([]string, error) {
return []string{
"DeepSeek-R1",
"gpt-4o",

View file

@ -2,6 +2,7 @@
package azureaigateway
import (
"context"
"encoding/json"
"errors"
"fmt"
@ -27,7 +28,7 @@ func NewBedrockBackend(subscriptionKey string) *BedrockBackend {
}
// ListModels returns the list of available Bedrock inference profiles
func (b *BedrockBackend) ListModels() ([]string, error) {
func (b *BedrockBackend) ListModels(_ context.Context) ([]string, error) {
return []string{
"us.anthropic.claude-3-haiku-20240307-v1:0",
"us.anthropic.claude-3-opus-20240229-v1:0",

View file

@ -2,6 +2,7 @@
package azureaigateway
import (
"context"
"encoding/json"
"errors"
"fmt"
@ -26,7 +27,7 @@ func NewVertexAIBackend(subscriptionKey string) *VertexAIBackend {
}
// ListModels returns the list of Gemini models available through Vertex AI
func (b *VertexAIBackend) ListModels() ([]string, error) {
func (b *VertexAIBackend) ListModels(_ context.Context) ([]string, error) {
return []string{
"gemini-3-pro-preview",
"gemini-2.5-pro",

View file

@ -443,7 +443,7 @@ func (c *BedrockClient) configure() error {
// from AWS Bedrock that can be used with this plugin.
// When using bearer token auth, the API may not be accessible, so a static
// fallback list of common models is returned instead.
func (c *BedrockClient) ListModels() ([]string, error) {
func (c *BedrockClient) ListModels(_ context.Context) ([]string, error) {
models, err := c.listModelsFromAPI()
if err != nil && c.bedrockAPIKey.Value != "" {
// Bearer token auth may lack ListFoundationModels permissions;
@ -488,7 +488,7 @@ func (c *BedrockClient) listModelsFromAPI() ([]string, error) {
}
// SendStream sends the messages to the Bedrock ConverseStream API
func (c *BedrockClient) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
func (c *BedrockClient) SendStream(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
// Ensure channel is closed on all exit paths to prevent goroutine leaks
defer func() {
if r := recover(); r != nil {

View file

@ -207,7 +207,7 @@ func TestListModels_NilClient_WithApiKey_ReturnsFallback(t *testing.T) {
client.bedrockAPIKey.Value = "test-absk-token"
// Don't call configure() — clients are nil
models, err := client.ListModels()
models, err := client.ListModels(context.Background())
assert.NoError(t, err, "ListModels should not error when falling back to static list")
assert.Equal(t, defaultBedrockModels, models, "should return default models as fallback")
}
@ -216,7 +216,7 @@ func TestListModels_NilClient_NoApiKey_ReturnsError(t *testing.T) {
client := NewClient()
// Don't call configure() and no API key — should propagate error
_, err := client.ListModels()
_, err := client.ListModels(context.Background())
assert.Error(t, err, "ListModels should error when client is nil and no API key for fallback")
}
@ -227,7 +227,7 @@ func TestSendStream_NilClient_ReturnsError(t *testing.T) {
ch := make(chan domain.StreamUpdate, 10)
opts := &domain.ChatOptions{Model: "test-model", Temperature: 0.7, TopP: 0.9}
err := client.SendStream(nil, opts, ch)
err := client.SendStream(context.Background(), nil, opts, ch)
assert.Error(t, err, "SendStream should return error when client is nil")
assert.Contains(t, err.Error(), i18n.T("bedrock_client_not_initialized"))
}

View file

@ -0,0 +1,248 @@
package codex
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/danielmiessler/fabric/internal/i18n"
debuglog "github.com/danielmiessler/fabric/internal/log"
plugins "github.com/danielmiessler/fabric/internal/plugins"
)
var errReplayBodyUnavailable = errors.New(i18n.T("codex_replay_body_unavailable"))
type authTransport struct {
client *Client
wrapped http.RoundTripper
}
func (c *Client) ensureAccessToken(ctx context.Context, forceRefresh bool) (string, string, error) {
// Fast path: a valid in-memory token needs no refresh, so avoid the store
// lock and disk reload. This also keeps Setup's fresh OAuth tokens from
// being overwritten by the stale values still on disk before SaveEnvFile.
if !forceRefresh {
if access, account, ok := c.currentToken(); ok {
return access, account, nil
}
}
if c.WithStoreLock == nil {
return c.ensureAccessTokenLocked(ctx, forceRefresh)
}
var access, account string
err := c.WithStoreLock(func() error {
var inner error
access, account, inner = c.ensureAccessTokenLocked(ctx, forceRefresh)
return inner
})
if err != nil {
return "", "", err
}
return access, account, nil
}
// currentToken returns the in-memory token when it is present and unexpired.
// A missing account ID falls through to the locked path, which parses it from
// the JWT.
func (c *Client) currentToken() (access string, account string, ok bool) {
c.tokenMu.Lock()
defer c.tokenMu.Unlock()
access = strings.TrimSpace(c.AccessToken.Value)
account = strings.TrimSpace(c.AccountID.Value)
if access == "" || account == "" || tokenNeedsRefresh(access, time.Now()) {
return "", "", false
}
return access, account, true
}
func (c *Client) ensureAccessTokenLocked(ctx context.Context, forceRefresh bool) (string, string, error) {
c.tokenMu.Lock()
defer c.tokenMu.Unlock()
accessToken := strings.TrimSpace(c.AccessToken.Value)
accountID := strings.TrimSpace(c.AccountID.Value)
if !forceRefresh && accessToken != "" && !tokenNeedsRefresh(accessToken, time.Now()) {
if accountID == "" {
parsedAccountID, err := extractAccountIDFromJWT(accessToken)
if err == nil && parsedAccountID != "" {
accountID = parsedAccountID
c.setSettingValue(c.AccountID, accountID)
}
}
if accountID != "" {
return accessToken, accountID, nil
}
}
refreshed, err := c.refreshAccessToken(ctx)
if err != nil {
return "", "", err
}
refreshedAccountID, err := c.extractAccountID(refreshed.IDToken, refreshed.AccessToken)
if err != nil {
return "", "", err
}
if accountID != "" && refreshedAccountID != "" && !strings.EqualFold(accountID, refreshedAccountID) {
return "", "", errors.New(i18n.T("codex_login_account_changed"))
}
c.setSettingValue(c.AccessToken, refreshed.AccessToken)
if strings.TrimSpace(refreshed.RefreshToken) != "" {
c.setSettingValue(c.RefreshToken, refreshed.RefreshToken)
}
c.setSettingValue(c.AccountID, refreshedAccountID)
if c.TokenPersist != nil {
if err := c.TokenPersist(); err != nil {
debuglog.Log("Codex token persist failed: %v\n", err)
return "", "", fmt.Errorf(i18n.T("codex_token_persist_failed"), err)
}
}
debuglog.Debug(debuglog.Detailed, "Codex access token refreshed account_present=%t\n", refreshedAccountID != "")
return c.AccessToken.Value, c.AccountID.Value, nil
}
func (c *Client) refreshAccessToken(ctx context.Context) (oauthTokens, error) {
payload := refreshRequest{
ClientID: oauthClientID,
GrantType: "refresh_token",
RefreshToken: strings.TrimSpace(c.RefreshToken.Value),
}
body, err := json.Marshal(payload)
if err != nil {
return oauthTokens{}, err
}
tokenURL := strings.TrimRight(c.AuthBaseURL.Value, "/") + "/oauth/token"
req, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenURL, strings.NewReader(string(body)))
if err != nil {
return oauthTokens{}, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.authHTTPClient.Do(req)
if err != nil {
return oauthTokens{}, fmt.Errorf(i18n.T("codex_refresh_login_failed"), err)
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return oauthTokens{}, err
}
if resp.StatusCode != http.StatusOK {
return oauthTokens{}, c.refreshErrorFromResponse(resp.StatusCode, responseBody)
}
var refreshed refreshResponse
if err := json.Unmarshal(responseBody, &refreshed); err != nil {
return oauthTokens{}, fmt.Errorf(i18n.T("codex_decode_refresh_response_failed"), err)
}
if strings.TrimSpace(refreshed.AccessToken) == "" {
return oauthTokens{}, errors.New(i18n.T("codex_token_refresh_missing_access_token"))
}
return oauthTokens{
IDToken: strings.TrimSpace(refreshed.IDToken),
AccessToken: strings.TrimSpace(refreshed.AccessToken),
RefreshToken: strings.TrimSpace(refreshed.RefreshToken),
}, nil
}
func (c *Client) extractAccountID(idToken string, accessToken string) (string, error) {
if accountID, err := extractAccountIDFromJWT(idToken); err == nil && accountID != "" {
return accountID, nil
}
if accountID, err := extractAccountIDFromJWT(accessToken); err == nil && accountID != "" {
return accountID, nil
}
return "", errors.New(i18n.T("codex_login_missing_account_claim"))
}
func (c *Client) setSettingValue(setting *plugins.Setting, value string) {
setting.Value = value
if setting.EnvVariable != "" {
_ = os.Setenv(setting.EnvVariable, value)
}
}
// RoundTrip adds Codex authentication headers and retries once after a 401.
func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return t.roundTrip(req, false)
}
func (t *authTransport) roundTrip(req *http.Request, retried bool) (*http.Response, error) {
token, accountID, err := t.client.ensureAccessToken(req.Context(), false)
if err != nil {
return nil, err
}
clone, err := cloneRequest(req)
if err != nil {
return nil, err
}
clone.Header.Set(http.CanonicalHeaderKey("originator"), defaultOriginator)
clone.Header.Set("User-Agent", defaultUserAgent)
clone.Header.Set("Authorization", "Bearer "+token)
clone.Header.Set("ChatGPT-Account-ID", accountID)
resp, err := t.roundTripper().RoundTrip(clone)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusUnauthorized || retried {
return resp, nil
}
drainAndClose(resp.Body)
debuglog.Debug(debuglog.Detailed, "Codex request returned 401; attempting token refresh and one retry\n")
if _, _, err := t.client.ensureAccessToken(req.Context(), true); err != nil {
return nil, err
}
return t.roundTrip(req, true)
}
func (t *authTransport) roundTripper() http.RoundTripper {
if t.wrapped != nil {
return t.wrapped
}
return http.DefaultTransport
}
func cloneRequest(req *http.Request) (*http.Request, error) {
clone := req.Clone(req.Context())
if req.Body == nil || req.Body == http.NoBody {
return clone, nil
}
// Codex retry logic assumes GetBody is available so the request can be replayed after refresh.
if req.GetBody == nil {
return nil, errReplayBodyUnavailable
}
body, err := req.GetBody()
if err != nil {
return nil, err
}
clone.Body = body
return clone, nil
}
func drainAndClose(body io.ReadCloser) {
if body == nil {
return
}
_, _ = io.Copy(io.Discard, io.LimitReader(body, defaultRoundTripLimit))
_ = body.Close()
}

View file

@ -4,21 +4,11 @@ package codex
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"runtime"
"runtime/debug"
"slices"
"strings"
"sync"
"time"
@ -55,8 +45,7 @@ const (
const oauthScope = "openid profile email offline_access api.connectors.read api.connectors.invoke"
var errReplayBodyUnavailable = errors.New("request body cannot be replayed for Codex re-authentication retry")
// Client implements the Codex-backed AI vendor.
type Client struct {
*openaivendor.Client
@ -69,29 +58,10 @@ type Client struct {
apiHTTPClient *http.Client
tokenMu sync.Mutex
}
type oauthTokens struct {
IDToken string `json:"id_token"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
type refreshRequest struct {
ClientID string `json:"client_id"`
GrantType string `json:"grant_type"`
RefreshToken string `json:"refresh_token"`
}
type refreshResponse struct {
IDToken string `json:"id_token"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
type oauthResult struct {
tokens oauthTokens
err error
// TokenPersist writes current tokens after refresh. Nil skips. Error fails the refresh.
TokenPersist func() error
// WithStoreLock, when set, wraps refresh+persist (reload tokens inside the lock).
WithStoreLock func(func() error) error
}
type modelInfo struct {
@ -104,29 +74,6 @@ type modelsResponse struct {
Models []modelInfo `json:"models"`
}
type tokenClaims struct {
Exp int64 `json:"exp"`
Auth tokenAuthClaims `json:"https://api.openai.com/auth"`
Profile tokenProfile `json:"https://api.openai.com/profile"`
Email string `json:"email"`
}
type tokenAuthClaims struct {
ChatGPTAccountID string `json:"chatgpt_account_id"`
ChatGPTPlanType string `json:"chatgpt_plan_type"`
UserID string `json:"user_id"`
ChatGPTUserID string `json:"chatgpt_user_id"`
}
type tokenProfile struct {
Email string `json:"email"`
}
type authTransport struct {
client *Client
wrapped http.RoundTripper
}
// NewClient creates a new Codex vendor client.
func NewClient() *Client {
client := &Client{}
@ -138,11 +85,11 @@ func NewClient() *Client {
client.AccountID = client.AddSetting("Account ID", true)
client.ApiBaseURL = client.AddSetupQuestionWithEnvName("Base URL", false,
"Enter your Codex API base URL")
i18n.T("codex_api_base_url_question"))
client.ApiBaseURL.Value = defaultBaseURL
client.AuthBaseURL = client.AddSetupQuestionWithEnvName("Auth Base URL", false,
"Enter your Codex OAuth base URL")
i18n.T("codex_auth_base_url_question"))
client.AuthBaseURL.Value = defaultAuthBaseURL
client.authHTTPClient = &http.Client{Timeout: modelsRequestTimeout}
@ -158,6 +105,14 @@ func (c *Client) Setup() error {
return err
}
if strings.TrimSpace(c.RefreshToken.Value) != "" {
if err := c.configure(); err == nil {
return nil
} else {
debuglog.Log("Codex configure failed; starting browser login: %v\n", err)
}
}
ctx, cancel := context.WithTimeout(context.Background(), oauthTimeout)
defer cancel()
@ -210,20 +165,35 @@ func (c *Client) configure() error {
option.WithHTTPClient(c.apiHTTPClient),
)
c.ApiClient = &apiClient
debuglog.Debug(debuglog.Detailed, "Codex configure: authenticated account=%s base_url=%s\n", c.AccountID.Value, c.ApiBaseURL.Value)
debuglog.Debug(debuglog.Detailed, "Codex configure: authenticated account_present=%t base_url=%s\n", strings.TrimSpace(c.AccountID.Value) != "", c.ApiBaseURL.Value)
return nil
}
// LoadEnvSettings copies non-empty Codex token values from env.
func (c *Client) LoadEnvSettings(env map[string]string) {
apply := func(setting *plugins.Setting) {
if setting == nil || setting.EnvVariable == "" {
return
}
if value := strings.TrimSpace(env[setting.EnvVariable]); value != "" {
c.setSettingValue(setting, value)
}
}
apply(c.AccessToken)
apply(c.RefreshToken)
apply(c.AccountID)
}
// ListModels returns the Codex models available to the configured account.
func (c *Client) ListModels() ([]string, error) {
func (c *Client) ListModels(ctx context.Context) ([]string, error) {
if c.apiHTTPClient == nil {
if err := c.configure(); err != nil {
return nil, err
}
}
ctx, cancel := context.WithTimeout(context.Background(), modelsRequestTimeout)
ctx, cancel := context.WithTimeout(ctx, modelsRequestTimeout)
defer cancel()
modelsURL := strings.TrimRight(c.ApiBaseURL.Value, "/") + "/models"
@ -252,7 +222,7 @@ func (c *Client) ListModels() ([]string, error) {
var decoded modelsResponse
if err := json.Unmarshal(body, &decoded); err != nil {
return nil, fmt.Errorf("failed to decode Codex models response: %w", err)
return nil, fmt.Errorf(i18n.T("codex_decode_models_response_failed"), err)
}
models := make([]string, 0, len(decoded.Models))
@ -299,16 +269,19 @@ func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
if err := c.mapRequestError(stream.Err()); err != nil {
return "", err
}
streamedText := builder.String()
if completedResp != nil {
return c.ExtractText(completedResp), nil
if extractedText := c.ExtractText(completedResp); strings.TrimSpace(extractedText) != "" {
return extractedText, nil
}
}
return builder.String(), nil
return streamedText, nil
}
// SendStream sends a request to Codex and streams the response text updates.
func (c *Client) SendStream(
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
) error {
defer close(channel)
@ -322,15 +295,17 @@ func (c *Client) SendStream(
}
req := c.buildCodexResponseParams(msgs, opts)
stream := c.ApiClient.Responses.NewStreaming(context.Background(), req)
stream := c.ApiClient.Responses.NewStreaming(ctx, req)
defer stream.Close()
for stream.Next() {
event := stream.Current()
switch event.Type {
case string(constant.ResponseOutputTextDelta("").Default()):
channel <- domain.StreamUpdate{
if err := sendStreamUpdate(ctx, channel, domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: event.AsResponseOutputTextDelta().Delta,
}); err != nil {
return err
}
case string(constant.ResponseOutputTextDone("").Default()):
continue
@ -338,15 +313,26 @@ func (c *Client) SendStream(
}
if stream.Err() == nil {
channel <- domain.StreamUpdate{
if err := sendStreamUpdate(ctx, channel, domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: "\n",
}); err != nil {
return err
}
}
return c.mapRequestError(stream.Err())
}
func sendStreamUpdate(ctx context.Context, channel chan domain.StreamUpdate, update domain.StreamUpdate) error {
select {
case <-ctx.Done():
return ctx.Err()
case channel <- update:
return nil
}
}
func (c *Client) buildCodexResponseParams(
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions,
) responses.ResponseNewParams {
@ -404,669 +390,3 @@ func codexMessageText(msg chat.ChatCompletionMessage) string {
return strings.Join(parts, "\n")
}
func (c *Client) runOAuthFlow(
ctx context.Context,
openBrowserFn func(string) error,
) (oauthTokens, error) {
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", defaultCallbackPort))
if err != nil {
return oauthTokens{}, fmt.Errorf("failed to start local OAuth callback server: %w", err)
}
defer listener.Close()
debuglog.Debug(debuglog.Detailed, "Codex OAuth callback listener started on 127.0.0.1:%d\n", defaultCallbackPort)
pkce, err := generatePKCECodes()
if err != nil {
return oauthTokens{}, err
}
state, err := randomBase64URL(oauthStateBytes)
if err != nil {
return oauthTokens{}, err
}
callbackURL := (&url.URL{
Scheme: "http",
Host: fmt.Sprintf("localhost:%d", defaultCallbackPort),
Path: oauthCallbackPath,
}).String()
authURL, err := buildAuthorizeURL(c.AuthBaseURL.Value, callbackURL, pkce, state)
if err != nil {
return oauthTokens{}, err
}
results := make(chan oauthResult, 1)
server := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.handleOAuthCallback(w, r, callbackURL, pkce, state, results)
}),
}
serveDone := make(chan error, 1)
go func() {
err := server.Serve(listener)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
serveDone <- err
return
}
serveDone <- nil
}()
if err := openBrowserFn(authURL); err != nil {
fmt.Printf("If your browser did not open, navigate to this URL to authenticate:\n%s\n", authURL)
}
select {
case result := <-results:
shutdownCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
_ = server.Shutdown(shutdownCtx)
<-serveDone
return result.tokens, result.err
case err := <-serveDone:
if err != nil {
return oauthTokens{}, err
}
return oauthTokens{}, errors.New(i18n.T("codex_login_server_stopped"))
case <-ctx.Done():
shutdownCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
_ = server.Shutdown(shutdownCtx)
<-serveDone
return oauthTokens{}, errors.New(i18n.T("codex_login_timed_out"))
}
}
func (c *Client) handleOAuthCallback(
w http.ResponseWriter,
r *http.Request,
callbackURL string,
pkce pkceCodes,
expectedState string,
results chan<- oauthResult,
) {
if r.URL.Path != oauthCallbackPath {
http.NotFound(w, r)
return
}
if r.URL.Query().Get("state") != expectedState {
http.Error(w, "State mismatch", http.StatusBadRequest)
c.publishOAuthResult(results, oauthResult{
err: errors.New(i18n.T("codex_login_state_mismatch")),
})
return
}
if callbackError := strings.TrimSpace(r.URL.Query().Get("error")); callbackError != "" {
description := strings.TrimSpace(r.URL.Query().Get("error_description"))
if description != "" {
http.Error(w, description, http.StatusForbidden)
c.publishOAuthResult(results, oauthResult{
err: fmt.Errorf(i18n.T("codex_login_failed"), description),
})
return
}
http.Error(w, callbackError, http.StatusForbidden)
c.publishOAuthResult(results, oauthResult{
err: fmt.Errorf(i18n.T("codex_login_failed"), callbackError),
})
return
}
code := strings.TrimSpace(r.URL.Query().Get("code"))
if code == "" {
http.Error(w, "Missing authorization code", http.StatusBadRequest)
c.publishOAuthResult(results, oauthResult{
err: errors.New(i18n.T("codex_login_missing_auth_code")),
})
return
}
tokens, err := c.exchangeCodeForTokens(r.Context(), callbackURL, pkce, code)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
c.publishOAuthResult(results, oauthResult{err: err})
return
}
if _, err := c.extractAccountID(tokens.IDToken, tokens.AccessToken); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
c.publishOAuthResult(results, oauthResult{err: err})
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write([]byte("<html><body><h1>Codex login completed</h1><p>Return to Fabric.</p></body></html>"))
c.publishOAuthResult(results, oauthResult{tokens: tokens})
}
func (c *Client) publishOAuthResult(results chan<- oauthResult, result oauthResult) {
select {
case results <- result:
default:
}
}
func (c *Client) exchangeCodeForTokens(
ctx context.Context,
callbackURL string,
pkce pkceCodes,
code string,
) (oauthTokens, error) {
form := url.Values{}
form.Set("grant_type", "authorization_code")
form.Set("code", code)
form.Set("redirect_uri", callbackURL)
form.Set("client_id", oauthClientID)
form.Set("code_verifier", pkce.CodeVerifier)
tokenURL := strings.TrimRight(c.AuthBaseURL.Value, "/") + "/oauth/token"
req, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenURL, strings.NewReader(form.Encode()))
if err != nil {
return oauthTokens{}, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := c.authHTTPClient.Do(req)
if err != nil {
return oauthTokens{}, fmt.Errorf("Codex token exchange failed: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return oauthTokens{}, err
}
if resp.StatusCode != http.StatusOK {
return oauthTokens{}, c.errorFromHTTPResponse(resp.StatusCode, body)
}
var tokens oauthTokens
if err := json.Unmarshal(body, &tokens); err != nil {
return oauthTokens{}, fmt.Errorf("failed to decode Codex token exchange response: %w", err)
}
if strings.TrimSpace(tokens.AccessToken) == "" || strings.TrimSpace(tokens.RefreshToken) == "" {
return oauthTokens{}, errors.New(i18n.T("codex_login_missing_tokens"))
}
return tokens, nil
}
func (c *Client) ensureAccessToken(ctx context.Context, forceRefresh bool) (string, string, error) {
c.tokenMu.Lock()
defer c.tokenMu.Unlock()
accessToken := strings.TrimSpace(c.AccessToken.Value)
accountID := strings.TrimSpace(c.AccountID.Value)
if !forceRefresh && accessToken != "" && !tokenNeedsRefresh(accessToken, time.Now()) {
if accountID == "" {
parsedAccountID, err := extractAccountIDFromJWT(accessToken)
if err == nil && parsedAccountID != "" {
accountID = parsedAccountID
c.setSettingValue(c.AccountID, accountID)
}
}
if accountID != "" {
return accessToken, accountID, nil
}
}
refreshed, err := c.refreshAccessToken(ctx)
if err != nil {
return "", "", err
}
refreshedAccountID, err := c.extractAccountID(refreshed.IDToken, refreshed.AccessToken)
if err != nil {
return "", "", err
}
if accountID != "" && refreshedAccountID != "" && !strings.EqualFold(accountID, refreshedAccountID) {
return "", "", errors.New(i18n.T("codex_login_account_changed"))
}
c.setSettingValue(c.AccessToken, refreshed.AccessToken)
if strings.TrimSpace(refreshed.RefreshToken) != "" {
c.setSettingValue(c.RefreshToken, refreshed.RefreshToken)
}
c.setSettingValue(c.AccountID, refreshedAccountID)
debuglog.Debug(debuglog.Detailed, "Codex access token refreshed for account=%s\n", refreshedAccountID)
return c.AccessToken.Value, c.AccountID.Value, nil
}
func (c *Client) refreshAccessToken(ctx context.Context) (oauthTokens, error) {
payload := refreshRequest{
ClientID: oauthClientID,
GrantType: "refresh_token",
RefreshToken: strings.TrimSpace(c.RefreshToken.Value),
}
body, err := json.Marshal(payload)
if err != nil {
return oauthTokens{}, err
}
tokenURL := strings.TrimRight(c.AuthBaseURL.Value, "/") + "/oauth/token"
req, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenURL, strings.NewReader(string(body)))
if err != nil {
return oauthTokens{}, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.authHTTPClient.Do(req)
if err != nil {
return oauthTokens{}, fmt.Errorf("failed to refresh Codex login: %w", err)
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return oauthTokens{}, err
}
if resp.StatusCode != http.StatusOK {
return oauthTokens{}, c.refreshErrorFromResponse(resp.StatusCode, responseBody)
}
var refreshed refreshResponse
if err := json.Unmarshal(responseBody, &refreshed); err != nil {
return oauthTokens{}, fmt.Errorf("failed to decode refreshed Codex token response: %w", err)
}
if strings.TrimSpace(refreshed.AccessToken) == "" {
return oauthTokens{}, errors.New(i18n.T("codex_token_refresh_missing_access_token"))
}
return oauthTokens{
IDToken: strings.TrimSpace(refreshed.IDToken),
AccessToken: strings.TrimSpace(refreshed.AccessToken),
RefreshToken: strings.TrimSpace(refreshed.RefreshToken),
}, nil
}
func (c *Client) extractAccountID(idToken string, accessToken string) (string, error) {
if accountID, err := extractAccountIDFromJWT(idToken); err == nil && accountID != "" {
return accountID, nil
}
if accountID, err := extractAccountIDFromJWT(accessToken); err == nil && accountID != "" {
return accountID, nil
}
return "", errors.New(i18n.T("codex_login_missing_account_claim"))
}
func (c *Client) setSettingValue(setting *plugins.Setting, value string) {
setting.Value = value
if setting.EnvVariable != "" {
_ = os.Setenv(setting.EnvVariable, value)
}
}
func (c *Client) errorFromHTTPResponse(statusCode int, body []byte) error {
message := extractErrorMessage(body)
if statusCode == http.StatusUnauthorized {
return errors.New(i18n.T("codex_login_invalid"))
}
if isUsageLimitMessage(message) {
return errors.New(message)
}
if message == "" {
message = fmt.Sprintf("Codex request failed with status %d", statusCode)
}
return errors.New(message)
}
func (c *Client) refreshErrorFromResponse(statusCode int, body []byte) error {
message := extractErrorMessage(body)
code := strings.ToLower(extractErrorCode(body))
if statusCode == http.StatusUnauthorized {
switch code {
case "refresh_token_expired", "refresh_token_reused", "refresh_token_invalidated":
return errors.New(i18n.T("codex_login_revoked"))
default:
return errors.New(i18n.T("codex_login_refresh_failed"))
}
}
if message == "" {
message = fmt.Sprintf("failed to refresh Codex login (status %d)", statusCode)
}
return errors.New(message)
}
func (c *Client) mapRequestError(err error) error {
if err == nil {
return nil
}
var apiErr *openaiapi.Error
if errors.As(err, &apiErr) {
body := []byte(apiErr.RawJSON())
if len(body) == 0 {
body = readAPIErrorBody(apiErr)
}
return c.errorFromHTTPResponse(apiErr.StatusCode, body)
}
message := err.Error()
lower := strings.ToLower(message)
switch {
case strings.Contains(lower, "status code 401"),
strings.Contains(lower, "401 unauthorized"),
strings.Contains(lower, "refresh token"),
strings.Contains(lower, "chatgpt login"):
return errors.New(i18n.T("codex_login_invalid"))
case isUsageLimitMessage(message):
return errors.New(message)
default:
return err
}
}
func readAPIErrorBody(apiErr *openaiapi.Error) []byte {
if apiErr == nil || apiErr.Response == nil || apiErr.Response.Body == nil {
return nil
}
body, err := io.ReadAll(apiErr.Response.Body)
if err != nil {
return nil
}
apiErr.Response.Body = io.NopCloser(strings.NewReader(string(body)))
return body
}
// RoundTrip adds Codex authentication headers and retries once after a 401.
func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return t.roundTrip(req, false)
}
func (t *authTransport) roundTrip(req *http.Request, retried bool) (*http.Response, error) {
token, accountID, err := t.client.ensureAccessToken(req.Context(), false)
if err != nil {
return nil, err
}
clone, err := cloneRequest(req)
if err != nil {
return nil, err
}
clone.Header.Set(http.CanonicalHeaderKey("originator"), defaultOriginator)
clone.Header.Set("User-Agent", defaultUserAgent)
clone.Header.Set("Authorization", "Bearer "+token)
clone.Header.Set("ChatGPT-Account-ID", accountID)
resp, err := t.roundTripper().RoundTrip(clone)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusUnauthorized || retried {
return resp, nil
}
drainAndClose(resp.Body)
debuglog.Debug(debuglog.Detailed, "Codex request returned 401; attempting token refresh and one retry\n")
if _, _, err := t.client.ensureAccessToken(req.Context(), true); err != nil {
return nil, err
}
return t.roundTrip(req, true)
}
func (t *authTransport) roundTripper() http.RoundTripper {
if t.wrapped != nil {
return t.wrapped
}
return http.DefaultTransport
}
func cloneRequest(req *http.Request) (*http.Request, error) {
clone := req.Clone(req.Context())
if req.Body == nil || req.Body == http.NoBody {
return clone, nil
}
// Codex retry logic assumes GetBody is available so the request can be replayed after refresh.
if req.GetBody == nil {
return nil, errReplayBodyUnavailable
}
body, err := req.GetBody()
if err != nil {
return nil, err
}
clone.Body = body
return clone, nil
}
func drainAndClose(body io.ReadCloser) {
if body == nil {
return
}
_, _ = io.Copy(io.Discard, io.LimitReader(body, defaultRoundTripLimit))
_ = body.Close()
}
func buildAuthorizeURL(authBaseURL string, callbackURL string, pkce pkceCodes, state string) (string, error) {
issuer, err := url.Parse(strings.TrimRight(authBaseURL, "/"))
if err != nil {
return "", fmt.Errorf("invalid Codex auth base URL: %w", err)
}
issuer.Path = strings.TrimRight(issuer.Path, "/") + "/oauth/authorize"
query := issuer.Query()
query.Set("response_type", "code")
query.Set("client_id", oauthClientID)
query.Set("redirect_uri", callbackURL)
query.Set("scope", oauthScope)
query.Set("code_challenge", pkce.CodeChallenge)
query.Set("code_challenge_method", "S256")
query.Set("id_token_add_organizations", "true")
query.Set("codex_cli_simplified_flow", "true")
query.Set("state", state)
query.Set("originator", defaultOriginator)
issuer.RawQuery = query.Encode()
return issuer.String(), nil
}
type pkceCodes struct {
CodeVerifier string
CodeChallenge string
}
func generatePKCECodes() (pkceCodes, error) {
verifier, err := randomBase64URL(oauthVerifierBytes)
if err != nil {
return pkceCodes{}, err
}
sum := sha256.Sum256([]byte(verifier))
return pkceCodes{
CodeVerifier: verifier,
CodeChallenge: base64.RawURLEncoding.EncodeToString(sum[:]),
}, nil
}
func randomBase64URL(size int) (string, error) {
buf := make([]byte, size)
if _, err := rand.Read(buf); err != nil {
return "", fmt.Errorf("failed to generate secure random OAuth state: %w", err)
}
return base64.RawURLEncoding.EncodeToString(buf), nil
}
func tokenNeedsRefresh(jwt string, now time.Time) bool {
expiry, err := extractExpiryFromJWT(jwt)
if err != nil {
return true
}
return now.Add(tokenRefreshLeeway).After(expiry)
}
func extractExpiryFromJWT(jwt string) (time.Time, error) {
claims, err := parseTokenClaims(jwt)
if err != nil {
return time.Time{}, err
}
if claims.Exp == 0 {
return time.Time{}, errors.New("JWT did not include an exp claim")
}
return time.Unix(claims.Exp, 0), nil
}
func extractAccountIDFromJWT(jwt string) (string, error) {
claims, err := parseTokenClaims(jwt)
if err != nil {
return "", err
}
return strings.TrimSpace(claims.Auth.ChatGPTAccountID), nil
}
func parseTokenClaims(jwt string) (tokenClaims, error) {
parts := strings.Split(jwt, ".")
if len(parts) < 2 {
return tokenClaims{}, errors.New("invalid JWT format")
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return tokenClaims{}, err
}
var claims tokenClaims
if err := json.Unmarshal(payload, &claims); err != nil {
return tokenClaims{}, err
}
return claims, nil
}
func extractErrorMessage(body []byte) string {
if len(body) == 0 {
return ""
}
var payload map[string]any
if err := json.Unmarshal(body, &payload); err != nil {
return strings.TrimSpace(string(body))
}
if errorValue, ok := payload["error"]; ok {
switch typed := errorValue.(type) {
case string:
return strings.TrimSpace(typed)
case map[string]any:
if message, ok := typed["message"].(string); ok && strings.TrimSpace(message) != "" {
return strings.TrimSpace(message)
}
if code, ok := typed["code"].(string); ok && strings.TrimSpace(code) != "" {
return strings.TrimSpace(code)
}
}
}
if message, ok := payload["message"].(string); ok {
return strings.TrimSpace(message)
}
if detail, ok := payload["detail"].(string); ok {
return strings.TrimSpace(detail)
}
return strings.TrimSpace(string(body))
}
func extractErrorCode(body []byte) string {
var payload map[string]any
if err := json.Unmarshal(body, &payload); err != nil {
return ""
}
if code, ok := payload["code"].(string); ok {
return strings.TrimSpace(code)
}
errorValue, ok := payload["error"]
if !ok {
return ""
}
switch typed := errorValue.(type) {
case string:
return strings.TrimSpace(typed)
case map[string]any:
if code, ok := typed["code"].(string); ok {
return strings.TrimSpace(code)
}
}
return ""
}
func codexClientVersion() string {
if info, ok := debug.ReadBuildInfo(); ok {
if version := normalizeSemverLikeVersion(info.Main.Version); version != "" {
return version
}
}
return defaultClientVersion
}
func normalizeSemverLikeVersion(version string) string {
version = strings.TrimSpace(version)
version = strings.TrimPrefix(version, "v")
if version == "" || version == "(devel)" {
return ""
}
end := len(version)
for i, r := range version {
if (r < '0' || r > '9') && r != '.' {
end = i
break
}
}
version = strings.Trim(version[:end], ".")
if version == "" {
return ""
}
parts := strings.Split(version, ".")
if len(parts) < 3 {
return ""
}
if slices.Contains(parts[:3], "") {
return ""
}
return strings.Join(parts[:3], ".")
}
func isUsageLimitMessage(message string) bool {
lower := strings.ToLower(strings.TrimSpace(message))
if lower == "" {
return false
}
return strings.Contains(lower, "usage limit") ||
strings.Contains(lower, "purchase more credits") ||
strings.Contains(lower, "upgrade to plus") ||
strings.Contains(lower, "upgrade to pro") ||
strings.Contains(lower, "plan and billing")
}
func openBrowser(targetURL string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "darwin":
cmd = exec.Command("open", targetURL)
case "windows":
cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", targetURL)
default:
cmd = exec.Command("xdg-open", targetURL)
}
return cmd.Start()
}

View file

@ -4,6 +4,7 @@ import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net"
@ -18,6 +19,7 @@ import (
"github.com/danielmiessler/fabric/internal/chat"
"github.com/danielmiessler/fabric/internal/domain"
"github.com/danielmiessler/fabric/internal/i18n"
openaiapi "github.com/openai/openai-go"
"github.com/openai/openai-go/shared/constant"
)
@ -176,7 +178,7 @@ func TestListModelsFiltersSupportedVisibleModels(t *testing.T) {
client := newConfiguredTestClient(t, modelsServer.URL, "acct_models", testJWT("acct_models", time.Now().Add(time.Hour)))
models, err := client.ListModels()
models, err := client.ListModels(context.Background())
if err != nil {
t.Fatalf("ListModels() error = %v", err)
}
@ -210,6 +212,10 @@ func TestNormalizeSemverLikeVersion(t *testing.T) {
}
func TestMapRequestErrorPreservesCodexAPIErrorMessage(t *testing.T) {
if _, err := i18n.Init("en"); err != nil {
t.Fatalf("i18n.Init() error = %v", err)
}
client := NewClient()
apiErr := &openaiapi.Error{StatusCode: http.StatusBadRequest}
if err := apiErr.UnmarshalJSON([]byte(`{"message":"The requested model is not supported.","type":"invalid_request_error","param":"model","code":"invalid_value"}`)); err != nil {
@ -220,12 +226,20 @@ func TestMapRequestErrorPreservesCodexAPIErrorMessage(t *testing.T) {
if err == nil {
t.Fatal("mapRequestError() returned nil")
}
if got := err.Error(); got != "The requested model is not supported." {
t.Fatalf("mapRequestError() = %q, want %q", got, "The requested model is not supported.")
want := fmt.Sprintf(i18n.T("codex_request_failed_status"), http.StatusBadRequest)
if got := err.Error(); got != want {
t.Fatalf("mapRequestError() = %q, want %q", got, want)
}
if unwrapped := errors.Unwrap(err); unwrapped == nil || !strings.Contains(unwrapped.Error(), "The requested model is not supported.") {
t.Fatalf("wrapped error = %v, want provider detail", unwrapped)
}
}
func TestMapRequestErrorReadsAPIErrorResponseBodyWhenRawJSONMissing(t *testing.T) {
if _, err := i18n.Init("en"); err != nil {
t.Fatalf("i18n.Init() error = %v", err)
}
client := NewClient()
apiErr := &openaiapi.Error{
StatusCode: http.StatusBadRequest,
@ -238,8 +252,12 @@ func TestMapRequestErrorReadsAPIErrorResponseBodyWhenRawJSONMissing(t *testing.T
if err == nil {
t.Fatal("mapRequestError() returned nil")
}
if got := err.Error(); got != "The requested model is not supported for Codex." {
t.Fatalf("mapRequestError() = %q, want %q", got, "The requested model is not supported for Codex.")
want := fmt.Sprintf(i18n.T("codex_request_failed_status"), http.StatusBadRequest)
if got := err.Error(); got != want {
t.Fatalf("mapRequestError() = %q, want %q", got, want)
}
if unwrapped := errors.Unwrap(err); unwrapped == nil || !strings.Contains(unwrapped.Error(), "The requested model is not supported for Codex.") {
t.Fatalf("wrapped error = %v, want provider detail", unwrapped)
}
}
@ -422,6 +440,61 @@ func TestSendIncludesSourcesFromAnnotatedResponse(t *testing.T) {
}
}
func TestSendFallsBackToDeltaWhenCompletedResponseHasNoText(t *testing.T) {
apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/responses" {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/event-stream")
flusher, ok := w.(http.Flusher)
if !ok {
t.Fatalf("response writer does not implement http.Flusher")
}
fmt.Fprintf(w, "data: %s\n\n", marshalJSON(t, map[string]any{
"type": string(constant.ResponseOutputTextDelta("").Default()),
"delta": "hello from delta",
}))
flusher.Flush()
fmt.Fprintf(w, "data: %s\n\n", marshalJSON(t, map[string]any{
"type": "response.completed",
"response": map[string]any{
"output": []any{
map[string]any{
"type": "message",
"content": []any{
map[string]any{
"type": "output_text",
"text": "",
},
},
},
},
},
}))
flusher.Flush()
fmt.Fprint(w, "data: [DONE]\n\n")
}))
defer apiServer.Close()
client := newConfiguredTestClient(t, apiServer.URL, "acct_delta_fallback", testJWT("acct_delta_fallback", time.Now().Add(time.Hour)))
message, err := client.Send(context.Background(), []*chat.ChatCompletionMessage{
{Role: chat.ChatMessageRoleUser, Content: "Hello"},
}, &domain.ChatOptions{
Model: "gpt-5.4",
Temperature: 0.7,
})
if err != nil {
t.Fatalf("Send() error = %v", err)
}
if message != "hello from delta" {
t.Fatalf("Send() = %q, want %q", message, "hello from delta")
}
}
func TestSendStreamReadsCodexSSE(t *testing.T) {
apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/responses" {
@ -469,7 +542,7 @@ func TestSendStreamReadsCodexSSE(t *testing.T) {
client := newConfiguredTestClient(t, apiServer.URL, "acct_stream", testJWT("acct_stream", time.Now().Add(time.Hour)))
updates := make(chan domain.StreamUpdate, 8)
err := client.SendStream([]*chat.ChatCompletionMessage{
err := client.SendStream(context.Background(), []*chat.ChatCompletionMessage{
{Role: chat.ChatMessageRoleSystem, Content: "Follow the system prompt"},
{Role: "user", Content: "Hello"},
}, &domain.ChatOptions{
@ -492,6 +565,190 @@ func TestSendStreamReadsCodexSSE(t *testing.T) {
}
}
func TestSendStreamClosesChannelAndMapsHTTPError(t *testing.T) {
apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/responses" {
http.NotFound(w, r)
return
}
http.Error(w, `{"error":{"message":"usage limit reached"}}`, http.StatusTooManyRequests)
}))
defer apiServer.Close()
client := newConfiguredTestClient(t, apiServer.URL, "acct_stream_error", testJWT("acct_stream_error", time.Now().Add(time.Hour)))
updates := make(chan domain.StreamUpdate, 1)
err := client.SendStream(context.Background(), []*chat.ChatCompletionMessage{
{Role: chat.ChatMessageRoleUser, Content: "Hello"},
}, &domain.ChatOptions{
Model: "gpt-5.4",
}, updates)
if err == nil {
t.Fatal("SendStream() error = nil, want mapped HTTP error")
}
if got := err.Error(); got != i18n.T("codex_usage_limit_reached") {
t.Fatalf("SendStream() error = %q, want %q", got, i18n.T("codex_usage_limit_reached"))
}
update, ok := <-updates
if ok {
t.Fatalf("expected closed channel after stream error, got update %#v", update)
}
}
func TestEnsureAccessTokenPersistsRotatedRefreshToken(t *testing.T) {
authServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/oauth/token" {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(refreshResponse{
IDToken: testJWT("acct_persist", time.Now().Add(2*time.Hour)),
AccessToken: testJWT("acct_persist", time.Now().Add(2*time.Hour)),
RefreshToken: "refresh-rotated",
})
}))
defer authServer.Close()
var persisted map[string]string
client := NewClient()
client.ApiBaseURL.Value = "https://example.invalid"
client.AuthBaseURL.Value = authServer.URL
client.RefreshToken.Value = "refresh-old"
client.AccessToken.Value = testJWT("acct_persist", time.Now().Add(-time.Hour))
client.AccountID.Value = "acct_persist"
client.TokenPersist = func() error {
persisted = map[string]string{
"access": client.AccessToken.Value,
"refresh": client.RefreshToken.Value,
"account": client.AccountID.Value,
}
return nil
}
access, account, err := client.ensureAccessToken(context.Background(), false)
if err != nil {
t.Fatalf("ensureAccessToken() error = %v", err)
}
if access == "" {
t.Fatal("ensureAccessToken() returned empty access token")
}
if account != "acct_persist" {
t.Fatalf("account = %q, want acct_persist", account)
}
if persisted["refresh"] != "refresh-rotated" {
t.Fatalf("persisted refresh = %q, want refresh-rotated", persisted["refresh"])
}
}
func TestEnsureAccessTokenPersistError(t *testing.T) {
if _, err := i18n.Init("en"); err != nil {
t.Fatalf("i18n.Init() error = %v", err)
}
authServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/oauth/token" {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(refreshResponse{
IDToken: testJWT("acct_persist", time.Now().Add(2*time.Hour)),
AccessToken: testJWT("acct_persist", time.Now().Add(2*time.Hour)),
RefreshToken: "refresh-rotated",
})
}))
defer authServer.Close()
persistErr := errors.New("disk full")
client := NewClient()
client.ApiBaseURL.Value = "https://example.invalid"
client.AuthBaseURL.Value = authServer.URL
client.RefreshToken.Value = "refresh-old"
client.AccessToken.Value = testJWT("acct_persist", time.Now().Add(-time.Hour))
client.AccountID.Value = "acct_persist"
client.TokenPersist = func() error {
return persistErr
}
_, _, err := client.ensureAccessToken(context.Background(), false)
if err == nil {
t.Fatal("ensureAccessToken() error = nil, want persist error")
}
if !errors.Is(err, persistErr) {
t.Fatalf("ensureAccessToken() error = %v, want persist error %v", err, persistErr)
}
}
func TestEnsureAccessTokenReloadsStoreBeforeRefresh(t *testing.T) {
authHits := 0
authServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHits++
http.Error(w, "unexpected refresh", http.StatusInternalServerError)
}))
defer authServer.Close()
fresh := testJWT("acct_reload", time.Now().Add(2*time.Hour))
client := NewClient()
client.ApiBaseURL.Value = "https://example.invalid"
client.AuthBaseURL.Value = authServer.URL
client.RefreshToken.Value = "refresh-old"
client.AccessToken.Value = testJWT("acct_reload", time.Now().Add(-time.Hour))
client.AccountID.Value = "acct_reload"
client.WithStoreLock = func(fn func() error) error {
client.AccessToken.Value = fresh
client.AccountID.Value = "acct_reload"
return fn()
}
access, account, err := client.ensureAccessToken(context.Background(), false)
if err != nil {
t.Fatalf("ensureAccessToken() error = %v", err)
}
if access != fresh {
t.Fatal("ensureAccessToken() did not use reloaded access token")
}
if account != "acct_reload" {
t.Fatalf("account = %q, want acct_reload", account)
}
if authHits != 0 {
t.Fatalf("auth server hits = %d, want 0", authHits)
}
}
// Regression: a valid in-memory token (e.g. just obtained via Setup's OAuth)
// must not be reloaded from the store, which still holds the stale token until
// SaveEnvFile runs. The fast path returns before WithStoreLock can clobber it.
func TestEnsureAccessTokenKeepsFreshTokenOverStore(t *testing.T) {
fresh := testJWT("acct_fresh", time.Now().Add(2*time.Hour))
client := NewClient()
client.ApiBaseURL.Value = "https://example.invalid"
client.AuthBaseURL.Value = "https://auth.invalid"
client.RefreshToken.Value = "refresh-new"
client.AccessToken.Value = fresh
client.AccountID.Value = "acct_fresh"
storeLocked := false
client.WithStoreLock = func(fn func() error) error {
storeLocked = true
client.AccessToken.Value = "stale-from-disk"
client.AccountID.Value = "acct_stale"
return fn()
}
access, account, err := client.ensureAccessToken(context.Background(), false)
if err != nil {
t.Fatalf("ensureAccessToken() error = %v", err)
}
if storeLocked {
t.Fatal("store lock ran and clobbered a valid in-memory token")
}
if access != fresh || account != "acct_fresh" {
t.Fatalf("got access=%q account=%q, want fresh token for acct_fresh", access, account)
}
}
func newConfiguredTestClient(t *testing.T, apiBaseURL string, accountID string, accessToken string) *Client {
t.Helper()

View file

@ -0,0 +1,183 @@
package codex
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"github.com/danielmiessler/fabric/internal/i18n"
openaiapi "github.com/openai/openai-go"
)
type publicError struct {
message string
cause error
}
func (e *publicError) Error() string {
return e.message
}
func (e *publicError) Unwrap() error {
return e.cause
}
func (c *Client) errorFromHTTPResponse(statusCode int, body []byte) error {
message := extractErrorMessage(body)
if statusCode == http.StatusUnauthorized {
return errors.New(i18n.T("codex_login_invalid"))
}
if isUsageLimitMessage(message) {
return wrapPublicError(i18n.T("codex_usage_limit_reached"), statusCode, message)
}
return wrapPublicError(fmt.Sprintf(i18n.T("codex_request_failed_status"), statusCode), statusCode, message)
}
func (c *Client) refreshErrorFromResponse(statusCode int, body []byte) error {
message := extractErrorMessage(body)
code := strings.ToLower(extractErrorCode(body))
if statusCode == http.StatusUnauthorized {
switch code {
case "refresh_token_expired", "refresh_token_reused", "refresh_token_invalidated":
return errors.New(i18n.T("codex_login_revoked"))
default:
return errors.New(i18n.T("codex_login_refresh_failed"))
}
}
return wrapPublicError(fmt.Sprintf(i18n.T("codex_refresh_failed_status"), statusCode), statusCode, message)
}
func (c *Client) mapRequestError(err error) error {
if err == nil {
return nil
}
if apiErr, ok := errors.AsType[*openaiapi.Error](err); ok {
body := []byte(apiErr.RawJSON())
if len(body) == 0 {
body = readAPIErrorBody(apiErr)
}
return c.errorFromHTTPResponse(apiErr.StatusCode, body)
}
message := err.Error()
lower := strings.ToLower(message)
switch {
case strings.Contains(lower, "status code 401"),
strings.Contains(lower, "401 unauthorized"),
strings.Contains(lower, "refresh token"),
strings.Contains(lower, "chatgpt login"):
return errors.New(i18n.T("codex_login_invalid"))
case isUsageLimitMessage(message):
return &publicError{
message: i18n.T("codex_usage_limit_reached"),
cause: fmt.Errorf(i18n.T("codex_request_failed"), err),
}
default:
return err
}
}
func wrapPublicError(message string, statusCode int, providerMessage string) error {
if providerMessage == "" {
return errors.New(message)
}
return &publicError{
message: message,
cause: fmt.Errorf(i18n.T("codex_provider_error"), statusCode, providerMessage),
}
}
func readAPIErrorBody(apiErr *openaiapi.Error) []byte {
if apiErr == nil || apiErr.Response == nil || apiErr.Response.Body == nil {
return nil
}
body, err := io.ReadAll(apiErr.Response.Body)
if err != nil {
return nil
}
apiErr.Response.Body = io.NopCloser(strings.NewReader(string(body)))
return body
}
func extractErrorMessage(body []byte) string {
if len(body) == 0 {
return ""
}
var payload map[string]any
if err := json.Unmarshal(body, &payload); err != nil {
return strings.TrimSpace(string(body))
}
if errorValue, ok := payload["error"]; ok {
switch typed := errorValue.(type) {
case string:
return strings.TrimSpace(typed)
case map[string]any:
if message, ok := typed["message"].(string); ok && strings.TrimSpace(message) != "" {
return strings.TrimSpace(message)
}
if code, ok := typed["code"].(string); ok && strings.TrimSpace(code) != "" {
return strings.TrimSpace(code)
}
}
}
if message, ok := payload["message"].(string); ok {
return strings.TrimSpace(message)
}
if detail, ok := payload["detail"].(string); ok {
return strings.TrimSpace(detail)
}
return strings.TrimSpace(string(body))
}
func extractErrorCode(body []byte) string {
var payload map[string]any
if err := json.Unmarshal(body, &payload); err != nil {
return ""
}
if code, ok := payload["code"].(string); ok {
return strings.TrimSpace(code)
}
errorValue, ok := payload["error"]
if !ok {
return ""
}
switch typed := errorValue.(type) {
case string:
return strings.TrimSpace(typed)
case map[string]any:
if code, ok := typed["code"].(string); ok {
return strings.TrimSpace(code)
}
}
return ""
}
func isUsageLimitMessage(message string) bool {
lower := strings.ToLower(strings.TrimSpace(message))
if lower == "" {
return false
}
return strings.Contains(lower, "usage limit") ||
strings.Contains(lower, "purchase more credits") ||
strings.Contains(lower, "upgrade to plus") ||
strings.Contains(lower, "upgrade to pro") ||
strings.Contains(lower, "plan and billing")
}

View file

@ -0,0 +1,304 @@
package codex
import (
"context"
"crypto/rand"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os/exec"
"runtime"
"strings"
"time"
"github.com/danielmiessler/fabric/internal/i18n"
debuglog "github.com/danielmiessler/fabric/internal/log"
)
type oauthTokens struct {
IDToken string `json:"id_token"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
type refreshRequest struct {
ClientID string `json:"client_id"`
GrantType string `json:"grant_type"`
RefreshToken string `json:"refresh_token"`
}
type refreshResponse struct {
IDToken string `json:"id_token"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
type oauthResult struct {
tokens oauthTokens
err error
}
type pkceCodes struct {
CodeVerifier string
CodeChallenge string
}
func (c *Client) runOAuthFlow(
ctx context.Context,
openBrowserFn func(string) error,
) (oauthTokens, error) {
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", defaultCallbackPort))
if err != nil {
return oauthTokens{}, fmt.Errorf(i18n.T("codex_oauth_server_start_failed"), err)
}
defer listener.Close()
debuglog.Debug(debuglog.Detailed, "Codex OAuth callback listener started on 127.0.0.1:%d\n", defaultCallbackPort)
pkce, err := generatePKCECodes()
if err != nil {
return oauthTokens{}, err
}
state, err := randomBase64URL(oauthStateBytes)
if err != nil {
return oauthTokens{}, err
}
callbackURL := (&url.URL{
Scheme: "http",
Host: fmt.Sprintf("localhost:%d", defaultCallbackPort),
Path: oauthCallbackPath,
}).String()
authURL, err := buildAuthorizeURL(c.AuthBaseURL.Value, callbackURL, pkce, state)
if err != nil {
return oauthTokens{}, err
}
results := make(chan oauthResult, 1)
server := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.handleOAuthCallback(w, r, callbackURL, pkce, state, results)
}),
}
serveDone := make(chan error, 1)
go func() {
err := server.Serve(listener)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
serveDone <- err
return
}
serveDone <- nil
}()
if err := openBrowserFn(authURL); err != nil {
fmt.Printf("%s\n%s\n", i18n.T("codex_browser_open_fallback"), authURL)
}
select {
case result := <-results:
shutdownCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
_ = server.Shutdown(shutdownCtx)
<-serveDone
return result.tokens, result.err
case err := <-serveDone:
if err != nil {
return oauthTokens{}, err
}
return oauthTokens{}, errors.New(i18n.T("codex_login_server_stopped"))
case <-ctx.Done():
shutdownCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
_ = server.Shutdown(shutdownCtx)
<-serveDone
return oauthTokens{}, errors.New(i18n.T("codex_login_timed_out"))
}
}
func (c *Client) handleOAuthCallback(
w http.ResponseWriter,
r *http.Request,
callbackURL string,
pkce pkceCodes,
expectedState string,
results chan<- oauthResult,
) {
if r.URL.Path != oauthCallbackPath {
http.NotFound(w, r)
return
}
if !oauthStatesMatch(expectedState, r.URL.Query().Get("state")) {
http.Error(w, i18n.T("codex_oauth_state_mismatch"), http.StatusBadRequest)
c.publishOAuthResult(results, oauthResult{
err: errors.New(i18n.T("codex_login_state_mismatch")),
})
return
}
if callbackError := strings.TrimSpace(r.URL.Query().Get("error")); callbackError != "" {
description := strings.TrimSpace(r.URL.Query().Get("error_description"))
if description != "" {
http.Error(w, description, http.StatusForbidden)
c.publishOAuthResult(results, oauthResult{
err: fmt.Errorf(i18n.T("codex_login_failed"), description),
})
return
}
http.Error(w, callbackError, http.StatusForbidden)
c.publishOAuthResult(results, oauthResult{
err: fmt.Errorf(i18n.T("codex_login_failed"), callbackError),
})
return
}
code := strings.TrimSpace(r.URL.Query().Get("code"))
if code == "" {
http.Error(w, i18n.T("codex_oauth_missing_auth_code"), http.StatusBadRequest)
c.publishOAuthResult(results, oauthResult{
err: errors.New(i18n.T("codex_login_missing_auth_code")),
})
return
}
tokens, err := c.exchangeCodeForTokens(r.Context(), callbackURL, pkce, code)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
c.publishOAuthResult(results, oauthResult{err: err})
return
}
if _, err := c.extractAccountID(tokens.IDToken, tokens.AccessToken); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
c.publishOAuthResult(results, oauthResult{err: err})
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write([]byte("<html><body><h1>" + i18n.T("codex_login_completed") + "</h1><p>" + i18n.T("codex_login_return_to_fabric") + "</p></body></html>"))
c.publishOAuthResult(results, oauthResult{tokens: tokens})
}
func (c *Client) publishOAuthResult(results chan<- oauthResult, result oauthResult) {
select {
case results <- result:
default:
}
}
func (c *Client) exchangeCodeForTokens(
ctx context.Context,
callbackURL string,
pkce pkceCodes,
code string,
) (oauthTokens, error) {
form := url.Values{}
form.Set("grant_type", "authorization_code")
form.Set("code", code)
form.Set("redirect_uri", callbackURL)
form.Set("client_id", oauthClientID)
form.Set("code_verifier", pkce.CodeVerifier)
tokenURL := strings.TrimRight(c.AuthBaseURL.Value, "/") + "/oauth/token"
req, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenURL, strings.NewReader(form.Encode()))
if err != nil {
return oauthTokens{}, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := c.authHTTPClient.Do(req)
if err != nil {
return oauthTokens{}, fmt.Errorf(i18n.T("codex_token_exchange_failed"), err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return oauthTokens{}, err
}
if resp.StatusCode != http.StatusOK {
return oauthTokens{}, c.errorFromHTTPResponse(resp.StatusCode, body)
}
var tokens oauthTokens
if err := json.Unmarshal(body, &tokens); err != nil {
return oauthTokens{}, fmt.Errorf(i18n.T("codex_decode_token_response_failed"), err)
}
if strings.TrimSpace(tokens.AccessToken) == "" || strings.TrimSpace(tokens.RefreshToken) == "" {
return oauthTokens{}, errors.New(i18n.T("codex_login_missing_tokens"))
}
return tokens, nil
}
func buildAuthorizeURL(authBaseURL string, callbackURL string, pkce pkceCodes, state string) (string, error) {
issuer, err := url.Parse(strings.TrimRight(authBaseURL, "/"))
if err != nil {
return "", fmt.Errorf(i18n.T("codex_auth_base_url_invalid"), err)
}
issuer.Path = strings.TrimRight(issuer.Path, "/") + "/oauth/authorize"
query := issuer.Query()
query.Set("response_type", "code")
query.Set("client_id", oauthClientID)
query.Set("redirect_uri", callbackURL)
query.Set("scope", oauthScope)
query.Set("code_challenge", pkce.CodeChallenge)
query.Set("code_challenge_method", "S256")
query.Set("id_token_add_organizations", "true")
query.Set("codex_cli_simplified_flow", "true")
query.Set("state", state)
query.Set("originator", defaultOriginator)
issuer.RawQuery = query.Encode()
return issuer.String(), nil
}
func generatePKCECodes() (pkceCodes, error) {
verifier, err := randomBase64URL(oauthVerifierBytes)
if err != nil {
return pkceCodes{}, err
}
sum := sha256.Sum256([]byte(verifier))
return pkceCodes{
CodeVerifier: verifier,
CodeChallenge: base64.RawURLEncoding.EncodeToString(sum[:]),
}, nil
}
func randomBase64URL(size int) (string, error) {
buf := make([]byte, size)
if _, err := rand.Read(buf); err != nil {
return "", fmt.Errorf(i18n.T("codex_oauth_random_state_failed"), err)
}
return base64.RawURLEncoding.EncodeToString(buf), nil
}
func oauthStatesMatch(expected string, actual string) bool {
if len(expected) != len(actual) {
return false
}
return subtle.ConstantTimeCompare([]byte(expected), []byte(actual)) == 1
}
func openBrowser(targetURL string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "darwin":
cmd = exec.Command("open", targetURL)
case "windows":
cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", targetURL)
default:
cmd = exec.Command("xdg-open", targetURL)
}
return cmd.Start()
}

View file

@ -0,0 +1,115 @@
package codex
import (
"encoding/base64"
"encoding/json"
"errors"
"runtime/debug"
"slices"
"strings"
"time"
)
type tokenClaims struct {
Exp int64 `json:"exp"`
Auth tokenAuthClaims `json:"https://api.openai.com/auth"`
Profile tokenProfile `json:"https://api.openai.com/profile"`
Email string `json:"email"`
}
type tokenAuthClaims struct {
ChatGPTAccountID string `json:"chatgpt_account_id"`
ChatGPTPlanType string `json:"chatgpt_plan_type"`
UserID string `json:"user_id"`
ChatGPTUserID string `json:"chatgpt_user_id"`
}
type tokenProfile struct {
Email string `json:"email"`
}
func tokenNeedsRefresh(jwt string, now time.Time) bool {
expiry, err := extractExpiryFromJWT(jwt)
if err != nil {
return true
}
return now.Add(tokenRefreshLeeway).After(expiry)
}
func extractExpiryFromJWT(jwt string) (time.Time, error) {
claims, err := parseTokenClaims(jwt)
if err != nil {
return time.Time{}, err
}
if claims.Exp == 0 {
return time.Time{}, errors.New("jwt did not include an exp claim")
}
return time.Unix(claims.Exp, 0), nil
}
func extractAccountIDFromJWT(jwt string) (string, error) {
claims, err := parseTokenClaims(jwt)
if err != nil {
return "", err
}
return strings.TrimSpace(claims.Auth.ChatGPTAccountID), nil
}
func parseTokenClaims(jwt string) (tokenClaims, error) {
parts := strings.Split(jwt, ".")
if len(parts) < 2 {
return tokenClaims{}, errors.New("invalid jwt format")
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return tokenClaims{}, err
}
var claims tokenClaims
if err := json.Unmarshal(payload, &claims); err != nil {
return tokenClaims{}, err
}
return claims, nil
}
func codexClientVersion() string {
if info, ok := debug.ReadBuildInfo(); ok {
if version := normalizeSemverLikeVersion(info.Main.Version); version != "" {
return version
}
}
return defaultClientVersion
}
func normalizeSemverLikeVersion(version string) string {
version = strings.TrimSpace(version)
version = strings.TrimPrefix(version, "v")
if version == "" || version == "(devel)" {
return ""
}
end := len(version)
for i, r := range version {
if (r < '0' || r > '9') && r != '.' {
end = i
break
}
}
version = strings.Trim(version[:end], ".")
if version == "" {
return ""
}
parts := strings.Split(version, ".")
if len(parts) < 3 {
return ""
}
if slices.Contains(parts[:3], "") {
return ""
}
return strings.Join(parts[:3], ".")
}

View file

@ -20,6 +20,7 @@ import (
"fmt"
"io"
"net/http"
"slices"
"strings"
"time"
@ -159,7 +160,7 @@ func (c *Client) IsConfigured() bool {
// ListModels returns the available models.
// Microsoft 365 Copilot exposes a single model - the Copilot service itself.
func (c *Client) ListModels() ([]string, error) {
func (c *Client) ListModels(_ context.Context) ([]string, error) {
// Copilot doesn't expose multiple models - it's a unified service
// We expose it as a single "model" for consistency with Fabric's architecture
return []string{copilotModelName}, nil
@ -186,7 +187,7 @@ func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
}
// SendStream sends a message to Copilot and streams the response.
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
func (c *Client) SendStream(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
defer close(channel)
ctx := context.Background()
@ -401,9 +402,7 @@ func (c *Client) parseSSEStream(reader io.Reader, channel chan domain.StreamUpda
// extractResponseText extracts the assistant's response from messages.
func (c *Client) extractResponseText(messages []responseMessage) string {
// Find the last assistant message (Copilot's response)
for i := len(messages) - 1; i >= 0; i-- {
msg := messages[i]
// Response messages from Copilot have the copilotConversationResponseMessage type
for _, msg := range slices.Backward(messages) {
if msg.ODataType == "#microsoft.graph.copilotConversationResponseMessage" {
if msg.Text != "" {
return msg.Text

View file

@ -52,9 +52,9 @@ func NewClient() *Client {
return client
}
func (c *Client) ListModels() ([]string, error) {
func (c *Client) ListModels(ctx context.Context) ([]string, error) {
if c.ControlPlaneToken.Value == "" {
models, err := c.Client.ListModels()
models, err := c.Client.ListModels(ctx)
if err == nil && len(models) > 0 {
return models, nil
}

View file

@ -22,7 +22,7 @@ func NewClient() *Client {
return &Client{PluginBase: &plugins.PluginBase{Name: "DryRun"}}
}
func (c *Client) ListModels() ([]string, error) {
func (c *Client) ListModels(_ context.Context) ([]string, error) {
return []string{"dry-run-model"}, nil
}
@ -108,7 +108,7 @@ func (c *Client) constructRequest(msgs []*chat.ChatCompletionMessage, opts *doma
return builder.String()
}
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
func (c *Client) SendStream(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
defer close(channel)
request := c.constructRequest(msgs, opts)
channel <- domain.StreamUpdate{

View file

@ -1,6 +1,7 @@
package dryrun
import (
"context"
"reflect"
"testing"
@ -11,7 +12,7 @@ import (
// Test generated using Keploy
func TestListModels_ReturnsExpectedModel(t *testing.T) {
client := NewClient()
models, err := client.ListModels()
models, err := client.ListModels(context.Background())
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
@ -41,7 +42,7 @@ func TestSendStream_SendsMessages(t *testing.T) {
}
channel := make(chan domain.StreamUpdate)
go func() {
err := client.SendStream(msgs, opts, channel)
err := client.SendStream(context.Background(), msgs, opts, channel)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}

View file

@ -1,6 +1,7 @@
package exolab
import (
"context"
"strings"
"github.com/danielmiessler/fabric/internal/plugins"
@ -42,7 +43,7 @@ func (oi *Client) configure() (err error) {
return
}
func (oi *Client) ListModels() (ret []string, err error) {
func (oi *Client) ListModels(context.Context) (ret []string, err error) {
ret = oi.apiModels
return
}

View file

@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"regexp"
"slices"
"strconv"
"strings"
@ -60,7 +61,7 @@ type Client struct {
ApiKey *plugins.SetupQuestion
}
func (o *Client) ListModels() (ret []string, err error) {
func (o *Client) ListModels(_ context.Context) (ret []string, err error) {
ctx := context.Background()
var client *genai.Client
if client, err = genai.NewClient(ctx, &genai.ClientConfig{
@ -124,7 +125,7 @@ func (o *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
return
}
func (o *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
func (o *Client) SendStream(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
ctx := context.Background()
defer close(channel)
@ -290,9 +291,9 @@ func (o *Client) isTTSModel(modelName string) bool {
// extractTextForTTS extracts text content from chat messages for TTS generation
func (o *Client) extractTextForTTS(msgs []*chat.ChatCompletionMessage) (string, error) {
for i := len(msgs) - 1; i >= 0; i-- {
if msgs[i].Role == chat.ChatMessageRoleUser && msgs[i].Content != "" {
return msgs[i].Content, nil
for _, msg := range slices.Backward(msgs) {
if msg.Role == chat.ChatMessageRoleUser && msg.Content != "" {
return msg.Content, nil
}
}
return "", errors.New(i18n.T("gemini_no_text_for_tts"))

View file

@ -52,7 +52,7 @@ func (c *Client) configure() error {
}
// ListModels returns a list of available models.
func (c *Client) ListModels() ([]string, error) {
func (c *Client) ListModels(_ context.Context) ([]string, error) {
url := fmt.Sprintf("%s/models", c.ApiUrl.Value)
req, err := http.NewRequest("GET", url, nil)
@ -89,7 +89,7 @@ func (c *Client) ListModels() ([]string, error) {
return models, nil
}
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
func (c *Client) SendStream(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
url := fmt.Sprintf("%s/chat/completions", c.ApiUrl.Value)
payload := map[string]any{

View file

@ -27,7 +27,7 @@ func TestListModelsUsesBearerTokenWhenConfigured(t *testing.T) {
client.ApiKey.Value = "secret"
client.HttpClient = server.Client()
models, err := client.ListModels()
models, err := client.ListModels(context.Background())
require.NoError(t, err)
require.Equal(t, []string{"model-1"}, models)
}
@ -86,7 +86,7 @@ func TestListModelsDoesNotSendBearerForWhitespaceOnlyKey(t *testing.T) {
client.ApiKey.Value = " "
client.HttpClient = server.Client()
models, err := client.ListModels()
models, err := client.ListModels(context.Background())
require.NoError(t, err)
require.Equal(t, []string{"model-1"}, models)
}

View file

@ -90,7 +90,7 @@ func (o *Client) configure() (err error) {
return
}
func (o *Client) ListModels() (ret []string, err error) {
func (o *Client) ListModels(_ context.Context) (ret []string, err error) {
ctx := context.Background()
var listResp *ollamaapi.ListResponse
@ -104,8 +104,9 @@ func (o *Client) ListModels() (ret []string, err error) {
return
}
func (o *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
func (o *Client) SendStream(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
ctx := context.Background()
defer close(channel)
var req ollamaapi.ChatRequest
if req, err = o.createChatRequest(ctx, msgs, opts); err != nil {
@ -135,7 +136,6 @@ func (o *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
return
}
close(channel)
return
}

View file

@ -6,10 +6,14 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/danielmiessler/fabric/internal/chat"
"github.com/danielmiessler/fabric/internal/domain"
"github.com/danielmiessler/fabric/internal/i18n"
ollamaapi "github.com/ollama/ollama/api"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -60,3 +64,28 @@ func TestLoadImageBytes_DataURLSuccess(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, expected, got)
}
func TestSendStreamClosesChannelOnChatError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(`{"error":"ollama failed"}` + "\n"))
}))
t.Cleanup(server.Close)
baseURL, err := url.Parse(server.URL)
require.NoError(t, err)
client := &Client{client: ollamaapi.NewClient(baseURL, server.Client())}
channel := make(chan domain.StreamUpdate)
err = client.SendStream(
context.Background(),
[]*chat.ChatCompletionMessage{{Role: chat.ChatMessageRoleUser, Content: "hello"}},
&domain.ChatOptions{Model: "missing-model"},
channel,
)
require.Error(t, err)
_, ok := <-channel
assert.False(t, ok, "stream channel should be closed when Ollama chat returns an error")
}

View file

@ -30,7 +30,7 @@ func (o *Client) sendChatCompletions(ctx context.Context, msgs []*chat.ChatCompl
// sendStreamChatCompletions sends a streaming request using the Chat Completions API
func (o *Client) sendStreamChatCompletions(
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
) (err error) {
defer close(channel)
@ -39,7 +39,7 @@ func (o *Client) sendStreamChatCompletions(
req.StreamOptions = openai.ChatCompletionStreamOptionsParam{
IncludeUsage: openai.Bool(true),
}
stream := o.ApiClient.Chat.Completions.NewStreaming(context.Background(), req)
stream := o.ApiClient.Chat.Completions.NewStreaming(ctx, req)
for stream.Next() {
chunk := stream.Current()
if len(chunk.Choices) > 0 && chunk.Choices[0].Delta.Content != "" {

View file

@ -7,6 +7,7 @@ import (
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/danielmiessler/fabric/internal/i18n"
@ -45,6 +46,13 @@ func FetchModelsDirectly(ctx context.Context, baseURL, apiKey, providerName stri
return nil, fmt.Errorf(i18n.T("openai_failed_to_create_models_url"), err)
}
// Serve a fresh cached list when available to avoid re-hitting discovery
// endpoints that aggressively rate-limit (e.g. GitHub Models' catalog).
if models, ok := readModelsCache(providerName, fullURL, modelsCacheTTL); ok {
debuglog.Debug(debuglog.Detailed, "Using cached models list for %s (%d models)\n", providerName, len(models))
return models, nil
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fullURL, nil)
if err != nil {
return nil, err
@ -53,6 +61,14 @@ func FetchModelsDirectly(ctx context.Context, baseURL, apiKey, providerName stri
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
req.Header.Set("Accept", "application/json")
// GitHub Models' catalog endpoint sits behind GitHub's edge layer, which
// throttles requests that omit the documented API version header (returning
// HTTP 429 with an HTML body). Send it so the catalog fetch matches GitHub's
// API contract and avoids the edge-level rate limiter.
if strings.EqualFold(req.URL.Host, "models.github.ai") {
req.Header.Set("X-GitHub-Api-Version", "2022-11-28")
}
// Reuse provided HTTP client, or create a new one if not provided
client := httpClient
if client == nil {
@ -62,17 +78,40 @@ func FetchModelsDirectly(ctx context.Context, baseURL, apiKey, providerName stri
}
resp, err := client.Do(req)
if err != nil {
// On a network error, fall back to a stale cached list if we have one.
if models, ok := readModelsCache(providerName, fullURL, 0); ok {
debuglog.Debug(debuglog.Basic, "Fetch failed for %s (%v); serving stale cached models\n", providerName, err)
return models, nil
}
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
// A failing discovery call (commonly HTTP 429 rate limiting) should not
// hard-fail when we already have a list cached from a prior success.
if models, ok := readModelsCache(providerName, fullURL, 0); ok {
debuglog.Debug(debuglog.Basic, "Status %d from %s; serving stale cached models\n", resp.StatusCode, providerName)
return models, nil
}
// Read the response body for debugging, but limit the number of bytes read
bodyBytes, readErr := io.ReadAll(io.LimitReader(resp.Body, errorResponseLimit))
if readErr != nil {
return nil, fmt.Errorf(i18n.T("openai_unexpected_status_code_read_error"),
resp.StatusCode, providerName, readErr)
}
// Rate limiting often returns an HTML body; surface a concise, actionable
// message instead of dumping the raw page.
if resp.StatusCode == http.StatusTooManyRequests {
retryAfter := strings.TrimSpace(resp.Header.Get("Retry-After"))
if retryAfter == "" {
retryAfter = "60"
}
return nil, fmt.Errorf(i18n.T("openai_models_rate_limited"), providerName, retryAfter)
}
bodyString := string(bodyBytes)
return nil, fmt.Errorf(i18n.T("openai_unexpected_status_code_with_body"),
resp.StatusCode, providerName, bodyString)
@ -97,12 +136,16 @@ func FetchModelsDirectly(ctx context.Context, baseURL, apiKey, providerName stri
if err := json.Unmarshal(bodyBytes, &openAIFormat); err == nil {
debuglog.Debug(debuglog.Detailed, "Successfully parsed models response from %s using OpenAI format (found %d models)\n", providerName, len(openAIFormat.Data))
return extractModelIDs(openAIFormat.Data), nil
ids := extractModelIDs(openAIFormat.Data)
_ = writeModelsCache(providerName, fullURL, ids)
return ids, nil
}
if err := json.Unmarshal(bodyBytes, &directArray); err == nil {
debuglog.Debug(debuglog.Detailed, "Successfully parsed models response from %s using direct array format (found %d models)\n", providerName, len(directArray))
return extractModelIDs(directArray), nil
ids := extractModelIDs(directArray)
_ = writeModelsCache(providerName, fullURL, ids)
return ids, nil
}
var truncatedBody string

View file

@ -0,0 +1,95 @@
package openai
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"time"
)
// modelsCacheTTL is how long a successfully fetched model list is considered
// fresh. Model catalogs change rarely, so a long TTL avoids re-hitting
// discovery endpoints that aggressively rate-limit (e.g. GitHub Models'
// catalog, which returns HTTP 429 with a Retry-After header).
const modelsCacheTTL = 24 * time.Hour
// modelsCacheDir returns the directory used to cache provider model lists. It
// is a package variable so tests can redirect it to a temporary location.
var modelsCacheDir = defaultModelsCacheDir
func defaultModelsCacheDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".config", "fabric", "cache", "models"), nil
}
type modelsCacheEntry struct {
URL string `json:"url"`
FetchedAt time.Time `json:"fetched_at"`
Models []string `json:"models"`
}
var cacheNameSanitizer = regexp.MustCompile(`[^A-Za-z0-9_-]+`)
// modelsCacheFile derives a stable, collision-resistant cache path from the
// provider name and the full request URL. The URL is hashed so a provider that
// changes its endpoint does not read a stale entry from the old one.
func modelsCacheFile(dir, providerName, fullURL string) string {
sum := sha256.Sum256([]byte(fullURL))
slug := cacheNameSanitizer.ReplaceAllString(providerName, "_")
name := fmt.Sprintf("%s-%s.json", slug, hex.EncodeToString(sum[:])[:12])
return filepath.Join(dir, name)
}
// readModelsCache returns the cached model list for (providerName, fullURL).
// When maxAge > 0 the entry must be younger than maxAge; maxAge <= 0 accepts an
// entry of any age (used to fall back to a stale list when a fetch fails).
// Empty cached lists are never returned.
func readModelsCache(providerName, fullURL string, maxAge time.Duration) ([]string, bool) {
dir, err := modelsCacheDir()
if err != nil {
return nil, false
}
data, err := os.ReadFile(modelsCacheFile(dir, providerName, fullURL))
if err != nil {
return nil, false
}
var entry modelsCacheEntry
if err := json.Unmarshal(data, &entry); err != nil {
return nil, false
}
if entry.URL != fullURL || len(entry.Models) == 0 {
return nil, false
}
if maxAge > 0 && time.Since(entry.FetchedAt) > maxAge {
return nil, false
}
return entry.Models, true
}
// writeModelsCache persists a successfully fetched model list. Empty lists are
// not cached so a transient empty response does not stick for the whole TTL.
func writeModelsCache(providerName, fullURL string, models []string) error {
if len(models) == 0 {
return nil
}
dir, err := modelsCacheDir()
if err != nil {
return err
}
if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}
entry := modelsCacheEntry{URL: fullURL, FetchedAt: time.Now(), Models: models}
data, err := json.Marshal(entry)
if err != nil {
return err
}
return os.WriteFile(modelsCacheFile(dir, providerName, fullURL), data, 0o600)
}

View file

@ -0,0 +1,165 @@
package openai
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// withTempModelsCache redirects the models cache to a temporary directory for
// the duration of a test and restores the original afterwards.
func withTempModelsCache(t *testing.T) string {
t.Helper()
dir := t.TempDir()
orig := modelsCacheDir
modelsCacheDir = func() (string, error) { return dir, nil }
t.Cleanup(func() { modelsCacheDir = orig })
return dir
}
func modelsURLFor(t *testing.T, baseURL string) string {
t.Helper()
full, err := url.JoinPath(baseURL, "models")
require.NoError(t, err)
return full
}
// writeAgedCache writes a cache entry with an explicit age so freshness and
// stale-fallback paths can be exercised deterministically.
func writeAgedCache(t *testing.T, dir, provider, fullURL string, models []string, age time.Duration) {
t.Helper()
entry := modelsCacheEntry{URL: fullURL, FetchedAt: time.Now().Add(-age), Models: models}
data, err := json.Marshal(entry)
require.NoError(t, err)
require.NoError(t, os.MkdirAll(dir, 0o755))
require.NoError(t, os.WriteFile(modelsCacheFile(dir, provider, fullURL), data, 0o600))
}
func TestModelsCache_WriteThenReadFresh(t *testing.T) {
withTempModelsCache(t)
const provider, fullURL = "GitHub", "https://models.github.ai/catalog/models"
require.NoError(t, writeModelsCache(provider, fullURL, []string{"a", "b"}))
models, ok := readModelsCache(provider, fullURL, modelsCacheTTL)
assert.True(t, ok)
assert.Equal(t, []string{"a", "b"}, models)
}
func TestModelsCache_EmptyListNotCached(t *testing.T) {
withTempModelsCache(t)
const provider, fullURL = "GitHub", "https://models.github.ai/catalog/models"
require.NoError(t, writeModelsCache(provider, fullURL, nil))
_, ok := readModelsCache(provider, fullURL, 0)
assert.False(t, ok)
}
func TestModelsCache_ExpiredMissesWithTTLButHitsWithoutAgeLimit(t *testing.T) {
dir := withTempModelsCache(t)
const provider, fullURL = "GitHub", "https://models.github.ai/catalog/models"
writeAgedCache(t, dir, provider, fullURL, []string{"old"}, 48*time.Hour)
_, ok := readModelsCache(provider, fullURL, modelsCacheTTL)
assert.False(t, ok, "entry older than TTL should be a miss")
models, ok := readModelsCache(provider, fullURL, 0)
assert.True(t, ok, "maxAge<=0 should accept any age")
assert.Equal(t, []string{"old"}, models)
}
func TestModelsCache_DifferentURLDoesNotCollide(t *testing.T) {
withTempModelsCache(t)
require.NoError(t, writeModelsCache("GitHub", "https://a/models", []string{"a"}))
_, ok := readModelsCache("GitHub", "https://b/models", 0)
assert.False(t, ok)
}
// A fresh cache short-circuits before any network call.
func TestFetchModelsDirectly_ServesFreshCacheWithoutRequest(t *testing.T) {
dir := withTempModelsCache(t)
called := false
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
called = true
w.WriteHeader(http.StatusInternalServerError)
}))
defer srv.Close()
writeAgedCache(t, dir, "GitHub", modelsURLFor(t, srv.URL), []string{"cached-model"}, time.Minute)
models, err := FetchModelsDirectly(context.Background(), srv.URL, "key", "GitHub", nil)
assert.NoError(t, err)
assert.Equal(t, []string{"cached-model"}, models)
assert.False(t, called, "fresh cache should prevent the network call")
}
// A 429 with a stale cache present returns the stale list rather than erroring.
func TestFetchModelsDirectly_429ServesStaleCache(t *testing.T) {
dir := withTempModelsCache(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Retry-After", "60")
w.WriteHeader(http.StatusTooManyRequests)
_, _ = w.Write([]byte("<html>Whoa there!</html>"))
}))
defer srv.Close()
// Stale so the TTL check misses and the request is actually made.
writeAgedCache(t, dir, "GitHub", modelsURLFor(t, srv.URL), []string{"stale-model"}, 48*time.Hour)
models, err := FetchModelsDirectly(context.Background(), srv.URL, "key", "GitHub", nil)
assert.NoError(t, err)
assert.Equal(t, []string{"stale-model"}, models)
}
// A 429 with no cache yields a concise message, not the raw HTML body.
func TestFetchModelsDirectly_429NoCacheCleanError(t *testing.T) {
withTempModelsCache(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Retry-After", "60")
w.WriteHeader(http.StatusTooManyRequests)
_, _ = w.Write([]byte("<html><title>Rate limit</title>Whoa there!</html>"))
}))
defer srv.Close()
_, err := FetchModelsDirectly(context.Background(), srv.URL, "key", "GitHub", nil)
require.Error(t, err)
assert.Contains(t, err.Error(), "rate limit")
assert.Contains(t, err.Error(), "60")
assert.NotContains(t, err.Error(), "<html>")
assert.NotContains(t, err.Error(), "Whoa there")
}
// A successful fetch is cached, so a later failure is served from cache.
func TestFetchModelsDirectly_WritesCacheOnSuccess(t *testing.T) {
withTempModelsCache(t)
fail := false
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if fail {
w.WriteHeader(http.StatusTooManyRequests)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`[{"id":"m1"}]`))
}))
defer srv.Close()
models, err := FetchModelsDirectly(context.Background(), srv.URL, "key", "GitHub", nil)
require.NoError(t, err)
assert.Equal(t, []string{"m1"}, models)
// Cache is fresh now, so even with the server failing we get the cached list.
fail = true
models, err = FetchModelsDirectly(context.Background(), srv.URL, "key", "GitHub", nil)
require.NoError(t, err)
assert.Equal(t, []string{"m1"}, models)
}

View file

@ -66,6 +66,15 @@ type Client struct {
ApiClient *openai.Client
ImplementsResponses bool // Whether this provider supports the Responses API
httpClient *http.Client
// webSearchToolName, when non-empty, overrides the default
// "web_search_preview" tool name emitted on the Responses API.
// Used by OpenAI-compatible providers whose upstream API expects a
// different tool type string (xAI expects "web_search").
webSearchToolName string
// enableXSearch, when true, appends an additional "x_search" tool
// entry alongside the web search tool when Search is enabled.
// This is an xAI-specific live search grounding tool.
enableXSearch bool
}
// SetResponsesAPIEnabled configures whether to use the Responses API
@ -73,6 +82,21 @@ func (o *Client) SetResponsesAPIEnabled(enabled bool) {
o.ImplementsResponses = enabled
}
// SetWebSearchToolName overrides the default "web_search_preview" tool
// name emitted on the Responses API when Search is enabled. Pass an empty
// string to keep the OpenAI default. Non-OpenAI providers (for example,
// xAI) may require "web_search" instead.
func (o *Client) SetWebSearchToolName(name string) {
o.webSearchToolName = name
}
// SetEnableXSearch toggles whether an additional xAI "x_search" tool
// entry is appended when Search is enabled. Non-xAI providers should
// leave this false.
func (o *Client) SetEnableXSearch(enabled bool) {
o.enableXSearch = enabled
}
// checkImageGenerationCompatibility warns if the model doesn't support image generation
func checkImageGenerationCompatibility(model string) {
if !supportsImageGeneration(model) {
@ -96,9 +120,9 @@ func (o *Client) configure() (ret error) {
return
}
func (o *Client) ListModels() (ret []string, err error) {
func (o *Client) ListModels(ctx context.Context) (ret []string, err error) {
var page *pagination.Page[openai.Model]
if page, err = o.ApiClient.Models.List(context.Background()); err == nil {
if page, err = o.ApiClient.Models.List(ctx); err == nil {
for _, mod := range page.Data {
ret = append(ret, mod.ID)
}
@ -110,26 +134,26 @@ func (o *Client) ListModels() (ret []string, err error) {
// Some providers (e.g., GitHub Models) return non-standard response formats
// that the SDK fails to parse.
debuglog.Debug(debuglog.Basic, "SDK Models.List failed for %s: %v, falling back to direct API fetch\n", o.GetName(), err)
return FetchModelsDirectly(context.Background(), o.ApiBaseURL.Value, o.ApiKey.Value, o.GetName(), o.httpClient)
return FetchModelsDirectly(ctx, o.ApiBaseURL.Value, o.ApiKey.Value, o.GetName(), o.httpClient)
}
func (o *Client) SendStream(
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
) (err error) {
// Use Responses API for OpenAI, Chat Completions API for other providers
if o.supportsResponsesAPI() {
return o.sendStreamResponses(msgs, opts, channel)
return o.sendStreamResponses(ctx, msgs, opts, channel)
}
return o.sendStreamChatCompletions(msgs, opts, channel)
return o.sendStreamChatCompletions(ctx, msgs, opts, channel)
}
func (o *Client) sendStreamResponses(
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
) (err error) {
defer close(channel)
req := o.buildResponseParams(msgs, opts)
stream := o.ApiClient.Responses.NewStreaming(context.Background(), req)
stream := o.ApiClient.Responses.NewStreaming(ctx, req)
for stream.Next() {
event := stream.Current()
switch event.Type {
@ -199,6 +223,7 @@ func (o *Client) NeedsRawMode(modelName string) bool {
openaiModelsPrefixes := []string{
"glm",
"gpt-5",
"gpt-6",
"o1",
"o3",
"o4",
@ -253,11 +278,18 @@ func (o *Client) buildResponseParams(
// Add tools if enabled
var tools []responses.ToolUnionParam
// Add web search tool if enabled
// Add web search tool if enabled. The default tool name is OpenAI's
// "web_search_preview", but providers may override it (for example,
// xAI's Responses API requires "web_search").
if opts.Search {
webSearchTool := responses.ToolParamOfWebSearchPreview("web_search_preview")
searchToolName := responses.WebSearchToolType("web_search_preview")
if o.webSearchToolName != "" {
searchToolName = responses.WebSearchToolType(o.webSearchToolName)
}
webSearchTool := responses.ToolParamOfWebSearchPreview(searchToolName)
// Add user location if provided
// Add user location if provided. Only attach when the caller
// asked for it; xAI rejects unexpected location payloads.
if opts.SearchLocation != "" {
webSearchTool.OfWebSearchPreview.UserLocation = responses.WebSearchToolUserLocationParam{
Type: "approximate",
@ -266,6 +298,20 @@ func (o *Client) buildResponseParams(
}
tools = append(tools, webSearchTool)
// Append xAI's live "x_search" tool when the provider opts in.
// The xAI Responses API accepts a bare {"type":"x_search"}
// entry with no other required fields. We reuse the SDK's
// WebSearchToolParam as a minimal container since every other
// field is omitzero and will be elided during JSON marshalling.
if o.enableXSearch {
xSearchTool := responses.ToolUnionParam{
OfWebSearchPreview: &responses.WebSearchToolParam{
Type: responses.WebSearchToolType("x_search"),
},
}
tools = append(tools, xSearchTool)
}
}
// Add image generation tool if needed

View file

@ -2,8 +2,10 @@ package openai
import (
"context"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@ -12,6 +14,7 @@ import (
// Ensures we can fetch models directly when a provider returns a direct array of models
// instead of the standard OpenAI list response structure.
func TestFetchModelsDirectly_DirectArray(t *testing.T) {
withTempModelsCache(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/models", r.URL.Path)
w.Header().Set("Content-Type", "application/json")
@ -28,6 +31,7 @@ func TestFetchModelsDirectly_DirectArray(t *testing.T) {
// Ensures we can fetch models when a provider returns the standard OpenAI format
func TestFetchModelsDirectly_OpenAIFormat(t *testing.T) {
withTempModelsCache(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/models", r.URL.Path)
w.Header().Set("Content-Type", "application/json")
@ -42,8 +46,54 @@ func TestFetchModelsDirectly_OpenAIFormat(t *testing.T) {
assert.Equal(t, "openai-model", models[0])
}
// Ensures non-GitHub hosts do not receive the GitHub-specific API version header.
func TestFetchModelsDirectly_NoGitHubHeaderForOtherHosts(t *testing.T) {
withTempModelsCache(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Empty(t, r.Header.Get("X-GitHub-Api-Version"))
w.Header().Set("Content-Type", "application/json")
_, err := w.Write([]byte(`[{"id":"some-model"}]`))
assert.NoError(t, err)
}))
defer srv.Close()
_, err := FetchModelsDirectly(context.Background(), srv.URL, "test-key", "TestProvider", nil)
assert.NoError(t, err)
}
// captureRoundTripper records the outgoing request and returns a canned response,
// allowing assertions against requests sent to real hosts without networking.
type captureRoundTripper struct {
req *http.Request
body string
}
func (c *captureRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
c.req = req
return &http.Response{
StatusCode: http.StatusOK,
Header: http.Header{"Content-Type": []string{"application/json"}},
Body: io.NopCloser(strings.NewReader(c.body)),
Request: req,
}, nil
}
// Ensures the GitHub Models catalog host receives the documented API version
// header so it is not throttled by GitHub's edge rate limiter.
func TestFetchModelsDirectly_GitHubHostSendsAPIVersionHeader(t *testing.T) {
withTempModelsCache(t)
rt := &captureRoundTripper{body: `[{"id":"github-model"}]`}
client := &http.Client{Transport: rt}
models, err := FetchModelsDirectly(context.Background(), "https://models.github.ai/catalog", "test-key", "GitHub", client)
assert.NoError(t, err)
assert.Equal(t, []string{"github-model"}, models)
assert.Equal(t, "2022-11-28", rt.req.Header.Get("X-GitHub-Api-Version"))
}
// Ensures we handle empty model lists correctly
func TestFetchModelsDirectly_EmptyArray(t *testing.T) {
withTempModelsCache(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/models", r.URL.Path)
w.Header().Set("Content-Type", "application/json")

View file

@ -12,6 +12,12 @@ import (
"github.com/stretchr/testify/assert"
)
func TestNeedsRawModeGPT6(t *testing.T) {
if !NewClient().NeedsRawMode("gpt-6-astra") {
t.Fatal("gpt-6 models should use raw mode")
}
}
func TestBuildResponseRequestWithMaxTokens(t *testing.T) {
var msgs []*chat.ChatCompletionMessage
@ -129,6 +135,88 @@ func TestBuildResponseParams_WithSearchAndLocation(t *testing.T) {
assert.Equal(t, opts.SearchLocation, userLocation.Timezone.Value)
}
// TestBuildResponseParams_GrokAI_WithSearch verifies that a client
// configured with a custom web search tool name and x_search enabled
// emits both tool entries with the xAI-expected type strings.
func TestBuildResponseParams_GrokAI_WithSearch(t *testing.T) {
client := NewClient()
client.SetWebSearchToolName("web_search")
client.SetEnableXSearch(true)
opts := &domain.ChatOptions{
Model: "grok-4-fast-reasoning",
Temperature: 0.7,
Search: true,
}
msgs := []*chat.ChatCompletionMessage{
{Role: "user", Content: "What happened in the news today?"},
}
params := client.buildResponseParams(msgs, opts)
assert.NotNil(t, params.Tools, "Expected tools when search is enabled")
assert.Len(t, params.Tools, 2, "Expected web_search plus x_search tools")
webSearchTool := params.Tools[0]
assert.NotNil(t, webSearchTool.OfWebSearchPreview, "Expected web search tool slot")
assert.Equal(t, responses.WebSearchToolType("web_search"), webSearchTool.OfWebSearchPreview.Type)
xSearchTool := params.Tools[1]
assert.NotNil(t, xSearchTool.OfWebSearchPreview, "Expected x_search tool slot")
assert.Equal(t, responses.WebSearchToolType("x_search"), xSearchTool.OfWebSearchPreview.Type)
}
// TestBuildResponseParams_DefaultProvider_Unchanged guards backwards
// compatibility. A client that does not set the new override fields
// must continue emitting a single web_search_preview tool entry.
func TestBuildResponseParams_DefaultProvider_Unchanged(t *testing.T) {
client := NewClient()
opts := &domain.ChatOptions{
Model: "gpt-4o",
Temperature: 0.7,
Search: true,
}
msgs := []*chat.ChatCompletionMessage{
{Role: "user", Content: "What is the capital of France?"},
}
params := client.buildResponseParams(msgs, opts)
assert.NotNil(t, params.Tools, "Expected tools when search is enabled")
assert.Len(t, params.Tools, 1, "Expected exactly one tool for default provider")
tool := params.Tools[0]
assert.NotNil(t, tool.OfWebSearchPreview, "Expected web search tool slot")
assert.Equal(t, responses.WebSearchToolType("web_search_preview"), tool.OfWebSearchPreview.Type)
}
// TestBuildResponseParams_GrokAI_WithoutSearch confirms that a GrokAI
// style client without Search enabled does not append any tools.
// This protects the no-search path from regressions introduced by the
// new override logic.
func TestBuildResponseParams_GrokAI_WithoutSearch(t *testing.T) {
client := NewClient()
client.SetWebSearchToolName("web_search")
client.SetEnableXSearch(true)
opts := &domain.ChatOptions{
Model: "grok-4-fast-reasoning",
Temperature: 0.7,
Search: false,
}
msgs := []*chat.ChatCompletionMessage{
{Role: "user", Content: "Hello"},
}
params := client.buildResponseParams(msgs, opts)
assert.Nil(t, params.Tools, "Expected no tools when search is disabled")
}
func TestCitationFormatting(t *testing.T) {
// Test the citation formatting logic by simulating the citation extraction
var textParts []string

View file

@ -21,6 +21,14 @@ type ProviderConfig struct {
BaseURL string
ModelsURL string // Optional: Custom endpoint for listing models (if different from BaseURL/models)
ImplementsResponses bool // Whether the provider supports OpenAI's new Responses API
// WebSearchToolName overrides the default "web_search_preview" tool name
// emitted on the Responses API when Search is enabled. Leave empty to keep
// the OpenAI default. xAI, for example, requires "web_search".
WebSearchToolName string
// EnableXSearch, when true, also appends an xAI "x_search" tool entry
// alongside the web search tool when Search is enabled. Non-xAI
// providers should leave this false.
EnableXSearch bool
}
// Client is the common structure for all OpenAI-compatible providers
@ -40,11 +48,15 @@ func NewClient(providerConfig ProviderConfig) *Client {
providerConfig.ImplementsResponses,
nil,
)
// Apply optional Responses API tool overrides. Zero values preserve
// existing behavior for providers that do not set these fields.
client.Client.SetWebSearchToolName(providerConfig.WebSearchToolName)
client.Client.SetEnableXSearch(providerConfig.EnableXSearch)
return client
}
// ListModels overrides the default ListModels to handle different response formats
func (c *Client) ListModels() ([]string, error) {
func (c *Client) ListModels(ctx context.Context) ([]string, error) {
// If a custom models URL is provided, handle it
if c.modelsURL != "" {
if c.modelsURL == "static:abacus" {
@ -65,13 +77,13 @@ func (c *Client) ListModels() ([]string, error) {
}
// First try the standard OpenAI SDK approach
models, err := c.Client.ListModels()
models, err := c.Client.ListModels(ctx)
if err == nil && len(models) > 0 { // only return if OpenAI SDK returns models
return models, nil
}
// Fall back to direct API fetch
return c.DirectlyGetModels(context.Background())
return c.DirectlyGetModels(ctx)
}
func (c *Client) fetchAbacusModels() ([]string, error) {
@ -194,14 +206,9 @@ func (c *Client) getStaticModels(modelsKey string) ([]string, error) {
}, nil
case "static:minimax":
return []string{
"MiniMax-M3",
"MiniMax-M2.7",
"MiniMax-M2.7-highspeed",
"MiniMax-M2.5",
"MiniMax-M2.5-highspeed",
"MiniMax-M2.5-lightning",
"MiniMax-M2",
"MiniMax-M2.1",
"MiniMax-M2.1-lightning",
}, nil
default:
return nil, fmt.Errorf(i18n.T("openai_compatible_unknown_static_model_list"), modelsKey)
@ -240,6 +247,11 @@ var ProviderMap = map[string]ProviderConfig{
Name: "GrokAI",
BaseURL: "https://api.x.ai/v1",
ImplementsResponses: true,
// xAI's Responses API expects the "web_search" tool type, not
// OpenAI's "web_search_preview", and additionally accepts an
// "x_search" tool entry for live search grounding.
WebSearchToolName: "web_search",
EnableXSearch: true,
},
"Groq": {
Name: "Groq",
@ -277,11 +289,21 @@ var ProviderMap = map[string]ProviderConfig{
BaseURL: "https://openrouter.ai/api/v1",
ImplementsResponses: false,
},
"Pzero": {
Name: "Pzero",
BaseURL: "https://api.pzero.studio/v1",
ImplementsResponses: false,
},
"SiliconCloud": {
Name: "SiliconCloud",
BaseURL: "https://api.siliconflow.cn/v1",
ImplementsResponses: false,
},
"Synthorai": {
Name: "Synthorai",
BaseURL: "https://synthorai.io/v1",
ImplementsResponses: false,
},
"Together": {
Name: "Together",
BaseURL: "https://api.together.xyz/v1",

View file

@ -53,7 +53,7 @@ func (c *Client) Configure() error {
return nil
}
func (c *Client) ListModels() ([]string, error) {
func (c *Client) ListModels(_ context.Context) ([]string, error) {
// Perplexity API does not have a ListModels endpoint.
// We return a predefined list.
return models, nil
@ -119,7 +119,7 @@ func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
return content.String(), nil
}
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
func (c *Client) SendStream(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
if c.client == nil {
if err := c.Configure(); err != nil {
close(channel) // Ensure channel is closed on error

View file

@ -11,8 +11,8 @@ import (
type Vendor interface {
plugins.Plugin
ListModels() ([]string, error)
SendStream([]*chat.ChatCompletionMessage, *domain.ChatOptions, chan domain.StreamUpdate) error
ListModels(context.Context) ([]string, error)
SendStream(context.Context, []*chat.ChatCompletionMessage, *domain.ChatOptions, chan domain.StreamUpdate) error
Send(context.Context, []*chat.ChatCompletionMessage, *domain.ChatOptions) (string, error)
NeedsRawMode(modelName string) bool
}

View file

@ -117,7 +117,7 @@ func (o *VendorsManager) fetchVendorModels(
defer wg.Done()
models, err := vendor.ListModels()
models, err := vendor.ListModels(ctx)
select {
case <-ctx.Done():
// Context canceled, don't send the result

View file

@ -13,14 +13,14 @@ type stubVendor struct {
name string
}
func (v *stubVendor) GetName() string { return v.name }
func (v *stubVendor) GetSetupDescription() string { return "" }
func (v *stubVendor) IsConfigured() bool { return true }
func (v *stubVendor) Configure() error { return nil }
func (v *stubVendor) Setup() error { return nil }
func (v *stubVendor) SetupFillEnvFileContent(*bytes.Buffer) {}
func (v *stubVendor) ListModels() ([]string, error) { return nil, nil }
func (v *stubVendor) SendStream([]*chat.ChatCompletionMessage, *domain.ChatOptions, chan domain.StreamUpdate) error {
func (v *stubVendor) GetName() string { return v.name }
func (v *stubVendor) GetSetupDescription() string { return "" }
func (v *stubVendor) IsConfigured() bool { return true }
func (v *stubVendor) Configure() error { return nil }
func (v *stubVendor) Setup() error { return nil }
func (v *stubVendor) SetupFillEnvFileContent(*bytes.Buffer) {}
func (v *stubVendor) ListModels(context.Context) ([]string, error) { return nil, nil }
func (v *stubVendor) SendStream(context.Context, []*chat.ChatCompletionMessage, *domain.ChatOptions, chan domain.StreamUpdate) error {
return nil
}
func (v *stubVendor) Send(context.Context, []*chat.ChatCompletionMessage, *domain.ChatOptions) (string, error) {

View file

@ -61,7 +61,7 @@ func (c *Client) configure() error {
return nil
}
func (c *Client) ListModels() ([]string, error) {
func (c *Client) ListModels(_ context.Context) ([]string, error) {
ctx := context.Background()
// Get ADC credentials for API authentication
@ -179,7 +179,7 @@ func (c *Client) sendClaude(ctx context.Context, msgs []*chat.ChatCompletionMess
return strings.Join(textParts, ""), nil
}
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
func (c *Client) SendStream(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
if isGeminiModel(opts.Model) {
return c.sendStreamGemini(msgs, opts, channel)
}

View file

@ -1,10 +1,15 @@
package db
// Storage is the contract for a named-entity store. Each implementation
// specifies the names that are valid. It rejects an invalid name with a
// typed error. Callers can map this error to a client error.
type Storage[T any] interface {
Configure() (err error)
Get(name string) (ret *T, err error)
GetNames() (ret []string, err error)
Delete(name string) (err error)
// Exists reports false for an invalid name. It cannot show the
// difference between a rejected name and an absent entry.
Exists(name string) (ret bool)
Rename(oldName, newName string) (err error)
Save(name string, content []byte) (err error)

View file

@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/danielmiessler/fabric/internal/i18n"
@ -41,6 +42,8 @@ type Db struct {
Contexts *ContextsEntity
EnvFilePath string
envMu sync.Mutex
}
func (o *Db) Configure() (err error) {
@ -92,9 +95,100 @@ func (o *Db) IsEnvFileExists() (ret bool) {
return
}
func (o *Db) SaveEnv(content string) (err error) {
err = os.WriteFile(o.EnvFilePath, []byte(content), 0644)
return
func (o *Db) SaveEnv(content string) error {
return o.WithEnvLock(func() error {
if err := writeFileAtomic(o.EnvFilePath, []byte(content)); err != nil {
return fmt.Errorf(i18n.T("db_error_updating_env_file"), err)
}
return nil
})
}
func (o *Db) ReadEnvFile() (map[string]string, error) {
env, err := godotenv.Read(o.EnvFilePath)
if err != nil {
if o.IsEnvFileExists() {
return nil, fmt.Errorf(i18n.T("db_error_loading_env_file"), err)
}
return map[string]string{}, nil
}
return env, nil
}
func (o *Db) WithEnvLock(fn func() error) error {
o.envMu.Lock()
defer o.envMu.Unlock()
lockFile, err := os.OpenFile(o.EnvFilePath+".lock", os.O_CREATE|os.O_RDWR, 0600)
if err != nil {
return fmt.Errorf(i18n.T("db_error_updating_env_file"), err)
}
defer lockFile.Close()
if err := lockExclusive(lockFile); err != nil {
return fmt.Errorf(i18n.T("db_error_updating_env_file"), err)
}
defer unlockExclusive(lockFile)
return fn()
}
// UpdateEnvVars merges non-empty values into .env under a file lock.
// Comments and key order are not preserved.
func (o *Db) UpdateEnvVars(updates map[string]string) error {
return o.WithEnvLock(func() error {
return o.ApplyEnvUpdates(updates)
})
}
// ApplyEnvUpdates writes non-empty updates atomically. Callers holding WithEnvLock use this.
func (o *Db) ApplyEnvUpdates(updates map[string]string) error {
env, err := o.ReadEnvFile()
if err != nil {
return err
}
for key, value := range updates {
if strings.TrimSpace(value) == "" {
continue
}
env[key] = value
}
if err := writeEnvFileAtomic(o.EnvFilePath, env); err != nil {
return fmt.Errorf(i18n.T("db_error_updating_env_file"), err)
}
return nil
}
func writeEnvFileAtomic(path string, env map[string]string) error {
content, err := godotenv.Marshal(env)
if err != nil {
return err
}
return writeFileAtomic(path, []byte(content+"\n"))
}
func writeFileAtomic(path string, content []byte) error {
tmp, err := os.CreateTemp(filepath.Dir(path), ".env.tmp-")
if err != nil {
return err
}
tmpName := tmp.Name()
defer os.Remove(tmpName)
if err := tmp.Chmod(0600); err != nil {
_ = tmp.Close()
return err
}
_, err = tmp.Write(content)
if err == nil {
err = tmp.Sync()
}
if cerr := tmp.Close(); err == nil {
err = cerr
}
if err != nil {
return err
}
return os.Rename(tmpName, path)
}
func (o *Db) FilePath(fileName string) (ret string) {

View file

@ -2,7 +2,12 @@ package fsdb
import (
"os"
"path/filepath"
"runtime"
"sync"
"testing"
"github.com/joho/godotenv"
)
func TestDb_Configure(t *testing.T) {
@ -52,4 +57,150 @@ func TestDb_SaveEnv(t *testing.T) {
if _, err := os.Stat(db.EnvFilePath); os.IsNotExist(err) {
t.Errorf("expected .env file to be saved")
}
assertEnvMode(t, db.EnvFilePath)
}
func TestDb_UpdateEnvVars(t *testing.T) {
dir := t.TempDir()
db := NewDb(dir)
if err := db.SaveEnv("KEEP=old\nCODEX_REFRESH_TOKEN=stale\n"); err != nil {
t.Fatalf("SaveEnv() error = %v", err)
}
if err := db.UpdateEnvVars(map[string]string{
"CODEX_REFRESH_TOKEN": "rotated",
"CODEX_ACCESS_TOKEN": "fresh",
}); err != nil {
t.Fatalf("UpdateEnvVars() error = %v", err)
}
parsed, err := godotenv.Read(db.EnvFilePath)
if err != nil {
t.Fatalf("godotenv.Read() error = %v", err)
}
if parsed["KEEP"] != "old" {
t.Fatalf("KEEP = %q, want old", parsed["KEEP"])
}
if parsed["CODEX_REFRESH_TOKEN"] != "rotated" {
t.Fatalf("CODEX_REFRESH_TOKEN = %q, want rotated", parsed["CODEX_REFRESH_TOKEN"])
}
if parsed["CODEX_ACCESS_TOKEN"] != "fresh" {
t.Fatalf("CODEX_ACCESS_TOKEN = %q, want fresh", parsed["CODEX_ACCESS_TOKEN"])
}
assertEnvMode(t, db.EnvFilePath)
}
func TestDb_UpdateEnvVars_MissingFile(t *testing.T) {
dir := t.TempDir()
db := NewDb(dir)
if err := db.UpdateEnvVars(map[string]string{"CODEX_ACCESS_TOKEN": "fresh"}); err != nil {
t.Fatalf("UpdateEnvVars() error = %v", err)
}
parsed, err := godotenv.Read(db.EnvFilePath)
if err != nil {
t.Fatalf("godotenv.Read() error = %v", err)
}
if parsed["CODEX_ACCESS_TOKEN"] != "fresh" {
t.Fatalf("CODEX_ACCESS_TOKEN = %q, want fresh", parsed["CODEX_ACCESS_TOKEN"])
}
assertEnvMode(t, db.EnvFilePath)
}
func TestDb_UpdateEnvVars_CorruptFile(t *testing.T) {
dir := t.TempDir()
db := NewDb(dir)
if err := os.Mkdir(db.EnvFilePath, 0700); err != nil {
t.Fatalf("Mkdir() error = %v", err)
}
err := db.UpdateEnvVars(map[string]string{"CODEX_ACCESS_TOKEN": "fresh"})
if err == nil {
t.Fatal("UpdateEnvVars() error = nil, want corrupt-file error")
}
}
func TestDb_UpdateEnvVars_SkipEmpty(t *testing.T) {
dir := t.TempDir()
db := NewDb(dir)
if err := db.SaveEnv("CODEX_REFRESH_TOKEN=live\nKEEP=old\n"); err != nil {
t.Fatalf("SaveEnv() error = %v", err)
}
if err := db.UpdateEnvVars(map[string]string{
"CODEX_REFRESH_TOKEN": "",
"CODEX_ACCESS_TOKEN": "fresh",
}); err != nil {
t.Fatalf("UpdateEnvVars() error = %v", err)
}
parsed, err := godotenv.Read(db.EnvFilePath)
if err != nil {
t.Fatalf("godotenv.Read() error = %v", err)
}
if parsed["CODEX_REFRESH_TOKEN"] != "live" {
t.Fatalf("CODEX_REFRESH_TOKEN = %q, want live", parsed["CODEX_REFRESH_TOKEN"])
}
if parsed["CODEX_ACCESS_TOKEN"] != "fresh" {
t.Fatalf("CODEX_ACCESS_TOKEN = %q, want fresh", parsed["CODEX_ACCESS_TOKEN"])
}
if parsed["KEEP"] != "old" {
t.Fatalf("KEEP = %q, want old", parsed["KEEP"])
}
}
func TestDb_UpdateEnvVars_Concurrent(t *testing.T) {
dir := t.TempDir()
db := NewDb(dir)
if err := db.SaveEnv("KEEP=old\n"); err != nil {
t.Fatalf("SaveEnv() error = %v", err)
}
var wg sync.WaitGroup
errs := make(chan error, 2)
wg.Add(2)
go func() {
defer wg.Done()
errs <- db.UpdateEnvVars(map[string]string{"CODEX_ACCESS_TOKEN": "one"})
}()
go func() {
defer wg.Done()
errs <- db.UpdateEnvVars(map[string]string{"CODEX_REFRESH_TOKEN": "two"})
}()
wg.Wait()
close(errs)
for err := range errs {
if err != nil {
t.Fatalf("UpdateEnvVars() error = %v", err)
}
}
parsed, err := godotenv.Read(db.EnvFilePath)
if err != nil {
t.Fatalf("godotenv.Read() error = %v", err)
}
if parsed["KEEP"] != "old" {
t.Fatalf("KEEP = %q, want old", parsed["KEEP"])
}
if parsed["CODEX_ACCESS_TOKEN"] != "one" {
t.Fatalf("CODEX_ACCESS_TOKEN = %q, want one", parsed["CODEX_ACCESS_TOKEN"])
}
if parsed["CODEX_REFRESH_TOKEN"] != "two" {
t.Fatalf("CODEX_REFRESH_TOKEN = %q, want two", parsed["CODEX_REFRESH_TOKEN"])
}
}
func assertEnvMode(t *testing.T, path string) {
t.Helper()
if runtime.GOOS == "windows" {
return
}
info, err := os.Stat(path)
if err != nil {
t.Fatalf("Stat(%s) error = %v", path, err)
}
if perm := info.Mode().Perm(); perm != 0600 {
t.Fatalf("%s mode = %o, want 0600", filepath.Base(path), perm)
}
}

Some files were not shown because too many files have changed in this diff Show more