Compare commits

..

No commits in common. "main" and "v1.4.441" have entirely different histories.

186 changed files with 7291 additions and 15428 deletions

3
.gitignore vendored
View file

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

View file

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

View file

@ -1,387 +1,5 @@
# 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)](https://github.com/danielmiessler/fabric)
![Static Badge](https://img.shields.io/badge/mission-human_flourishing_via_AI_augmentation-purple)
<br />
[![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)
![GitHub top language](https://img.shields.io/github/languages/top/danielmiessler/fabric)
![GitHub last commit](https://img.shields.io/github/last-commit/danielmiessler/fabric)
[![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,7 +82,6 @@ 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.
@ -137,7 +136,6 @@ 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)
@ -264,10 +262,6 @@ 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.
@ -290,13 +284,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:/home/appuser/.config/fabric kayvan/fabric:latest --setup
docker run --rm -it -v $HOME/.fabric-config:/root/.config/fabric kayvan/fabric:latest --setup
# Use Fabric with your patterns
docker run --rm -it -v $HOME/.fabric-config:/home/appuser/.config/fabric kayvan/fabric:latest -p summarize
docker run --rm -it -v $HOME/.fabric-config:/root/.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:/home/appuser/.config/fabric kayvan/fabric:latest --serve
docker run --rm -it -p 8080:8080 -v $HOME/.fabric-config:/root/.config/fabric kayvan/fabric:latest --serve
```
**Images available at:**
@ -374,9 +368,7 @@ Fabric supports a wide range of AI providers:
- Mistral
- Novita AI
- OpenRouter
- Pzero
- SiliconCloud
- Synthorai
- Together
- Venice AI
- Z AI
@ -666,41 +658,34 @@ 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 the selected model (e.g., -V "LM Studio" -m
openai/gpt-oss-20b)
-V, --vendor= Specify vendor for chosen 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
-n, --latest= Number of latest patterns to list (default: 0)
-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
--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
-g, --language= Specify the Language Code for the chat, e.g. -g=en -g=zh
-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
@ -725,31 +710,29 @@ 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, Grok)
--search Enable web search tool for supported models (Anthropic, OpenAI, Gemini)
--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)
--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)
--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)
--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)
--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)
--show-metadata Print metadata (input/output tokens) to stderr
--debug= Set debug level (0=off, 1=basic, 2=detailed, 3=trace, 4=wire)
--debug= Set debug level (0: off, 1: basic, 2: detailed, 3: trace)
Help Options:
-h, --help Show this help message
```
@ -762,7 +745,6 @@ 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,7 +131,6 @@ Fabric 按照现实世界中的任务来组织 Prompt允许人们在一个地
- [macOS (Homebrew)](#macos-homebrew)
- [Arch Linux (AUR)](#arch-linux-aur)
- [Windows](#windows)
- [Windows (Scoop)](#windows-scoop)
- [从源码构建](#从源码构建)
- [Docker](#docker)
- [环境变量](#环境变量)
@ -261,12 +260,6 @@ yay -S fabric-ai
winget install danielmiessler.Fabric
```
#### Windows (Scoop)
```bash
scoop install fabric-ai
```
### 从源码构建
安装 Fabric 前,请先[确保已安装 Go](https://go.dev/doc/install),然后运行:
@ -288,13 +281,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:/home/appuser/.config/fabric kayvan/fabric:latest --setup
docker run --rm -it -v $HOME/.fabric-config:/root/.config/fabric kayvan/fabric:latest --setup
# 使用你的 patterns
docker run --rm -it -v $HOME/.fabric-config:/home/appuser/.config/fabric kayvan/fabric:latest -p summarize
docker run --rm -it -v $HOME/.fabric-config:/root/.config/fabric kayvan/fabric:latest -p summarize
# 运行 REST API 服务器
docker run --rm -it -p 8080:8080 -v $HOME/.fabric-config:/home/appuser/.config/fabric kayvan/fabric:latest --serve
docker run --rm -it -p 8080:8080 -v $HOME/.fabric-config:/root/.config/fabric kayvan/fabric:latest --serve
```
**镜像来源:**
@ -358,7 +351,7 @@ fabric --setup
**OpenAI 兼容供应商:**
- Abacus、AIML、Cerebras、DeepSeek、DigitalOcean、GitHub Models、GrokAI、Groq、Langdock、LiteLLM、MiniMax、Mistral、Novita AI、OpenRouter、SiliconCloud、Synthorai、Together、Venice AI、Z AI
- Abacus、AIML、Cerebras、DeepSeek、DigitalOcean、GitHub Models、GrokAI、Groq、Langdock、LiteLLM、MiniMax、Mistral、Novita AI、OpenRouter、SiliconCloud、Together、Venice AI、Z AI
运行 `fabric --setup` 配置首选供应商,或使用 `fabric --listvendors` 查看所有可用供应商。
@ -460,12 +453,6 @@ 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
@ -480,7 +467,6 @@ pbpaste | fabric --pattern extract_wisdom
- `1`:基本调试信息
- `2`:详细调试
- `3`:追踪级别
- `4`wire 级别(完整的请求和响应内容)
### 演习模式

View file

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

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=gpt-5.6-sol
FABRIC_CHANGELOG_SUMMARIZE_MODEL=claude-sonnet-4-20250514
```
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-opus-5)
FABRIC_CHANGELOG_SUMMARIZE_MODEL=gpt-5.6-sol generate_changelog --ai-summarize
# Custom model (default: claude-sonnet-4-20250514)
FABRIC_CHANGELOG_SUMMARIZE_MODEL=claude-opus-4 generate_changelog --ai-summarize
```
### AI Summary Features

Binary file not shown.

View file

@ -381,13 +381,11 @@ func (g *Generator) validatePRState(prNumber int) error {
return fmt.Errorf("failed to fetch PR %d: %w", prNumber, err)
}
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.State != "open" {
return fmt.Errorf("PR %d is not open (current state: %s)", prNumber, details.State)
}
// 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 {
if !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-opus-5"
const DefaultSummarizeModel = "claude-sonnet-4-5"
const MinContentLength = 256 // Minimum content length to consider for summarization
const prompt = `# ROLE

View file

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

View file

@ -45,7 +45,6 @@ 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,14 +84,13 @@ _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 the selected model (e.g., -V "LM Studio" -m openai/gpt-oss-20b)]:vendor:_fabric_vendors' \
'(-V --vendor)'{-V,--vendor}'[Specify vendor for chosen 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]' \
@ -101,9 +100,6 @@ _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:' \
@ -126,7 +122,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, Grok)]' \
'(--search)--search[Enable web search tool for supported models (Anthropic, OpenAI, Gemini)]' \
'(--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)' \
@ -141,7 +137,6 @@ _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:' \
@ -150,7 +145,6 @@ _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 --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"
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"
# 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 | --readpattern)
-p | --pattern)
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 | --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)
-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)
# No specific completion suggestions, user types the value
return 0
;;

View file

@ -53,69 +53,54 @@ 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
# 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)"
# 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)"
# Boolean flags (no arguments)
complete -c $cmd -s S -l setup -d "Run setup for all reconfigurable parts of fabric"
@ -132,14 +117,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, Grok)"
complete -c $cmd -l search -d "Enable web search tool for supported models (Anthropic, OpenAI, Gemini)"
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"
@ -147,14 +132,13 @@ 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

@ -1,62 +0,0 @@
# 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

@ -1,58 +0,0 @@
# 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

@ -1,62 +0,0 @@
# 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

@ -1,57 +0,0 @@
# 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,240 +20,236 @@
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_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.
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.

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_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
**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
**BILL**: analyze_bill, analyze_bill_short
**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
**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
**CLASSIFICATION**: apply_ul_tags
**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
**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
**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_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
**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
**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, 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
**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
## Workflow Suggestions

View file

@ -136,10 +136,6 @@ 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.
@ -446,10 +442,6 @@ 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.
@ -546,10 +538,6 @@ 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.
@ -702,10 +690,6 @@ 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,7 +55,6 @@ 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,18 +112,14 @@ For manual installation or troubleshooting, see the detailed instructions below.
The completions provide intelligent suggestions for:
- **Patterns**: Tab-complete available patterns with `-p`, `--pattern`, or `--readpattern`
- **Patterns**: Tab-complete available patterns with `-p` or `--pattern`
- **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`
- **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
- **File paths**: Smart file completion for attachment, output, and config options
- **Flag completion**: All available command-line flags and options
## Alternative Installation Method

View file

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

View file

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

View file

@ -50,6 +50,7 @@ definitions:
imageSize:
type: string
language:
description: Add Language field to bind from request
type: string
maxTokens:
type: integer
@ -94,6 +95,8 @@ definitions:
topP:
format: float64
type: number
updateChan:
type: object
voice:
type: string
type: object
@ -262,12 +265,6 @@ 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": 1785692966,
"narHash": "sha256-vUfIeBEfpbAfZ5zjgIkYk7eHBeVfCYVjLbWnMkseYnk=",
"lastModified": 1765472234,
"narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "643809054d65fdd466a63e3155b8c498cb483c04",
"rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b",
"type": "github"
},
"original": {

174
go.mod
View file

@ -1,125 +1,121 @@
module github.com/danielmiessler/fabric
go 1.26.0
go 1.25.1
require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0
github.com/anthropics/anthropic-sdk-go v1.67.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1
github.com/anthropics/anthropic-sdk-go v1.27.1
github.com/atotto/clipboard v0.1.4
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/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/gin-gonic/gin v1.12.0
github.com/go-git/go-git/v5 v5.19.2
github.com/go-git/go-git/v5 v5.17.0
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.16.0
github.com/hasura/go-graphql-client v0.15.1
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.50
github.com/mattn/go-sqlite3 v1.14.37
github.com/nicksnyder/go-i18n/v2 v2.6.1
github.com/ollama/ollama v0.33.1
github.com/ollama/ollama v0.18.2
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.16.1
github.com/sgaunet/perplexity-go/v2 v2.15.0
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.12.1
github.com/stretchr/testify v1.11.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/sys v0.47.0
golang.org/x/text v0.41.0
google.golang.org/api v0.294.0
golang.org/x/text v0.35.0
google.golang.org/api v0.272.0
gopkg.in/yaml.v3 v3.0.1
)
require (
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/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/KyleBanks/depth v1.2.1 // 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/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.6.1 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/bytedance/gopkg v0.1.4 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // 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/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/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.61.0 // indirect
github.com/standard-webhooks/standard-webhooks/libraries v0.0.1 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // 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
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
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.49.0 // indirect
golang.org/x/tools v0.43.0 // indirect
)
require (
cloud.google.com/go v0.123.0 // indirect
cloud.google.com/go/auth v0.23.2 // indirect
cloud.google.com/go/auth v0.18.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.4 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // 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/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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/felixge/httpsnoop v1.1.0 // indirect
github.com/gin-contrib/sse v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.9.1 // indirect
github.com/go-logr/logr v1.4.4 // indirect
github.com/go-git/go-billy/v5 v5.8.0 // indirect
github.com/go-logr/logr v1.4.3 // 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.3 // indirect
github.com/go-playground/validator/v10 v10.30.1 // 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
@ -127,43 +123,45 @@ 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.21 // indirect
github.com/googleapis/gax-go/v2 v2.24.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
github.com/googleapis/gax-go/v2 v2.19.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.4.0 // indirect
github.com/leodido/go-urn v1.5.0 // indirect
github.com/mattn/go-isatty v0.0.24 // 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/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.4.3 // indirect
github.com/pjbgf/sha1cd v0.6.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pjbgf/sha1cd v0.5.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.19.0 // indirect
github.com/tidwall/gjson v1.18.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.2 // indirect
github.com/ugorji/go/codec v1.3.1 // 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.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
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
gopkg.in/warnings.v0 v0.1.2 // indirect
)

683
go.sum
View file

@ -1,31 +1,29 @@
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.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 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/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.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/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/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.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/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/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=
@ -33,229 +31,235 @@ 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.4 h1:vM2lgh0Vru9Vwyfm4cQqWP2HHMW0u0+2PAW7Q38Qufg=
github.com/andybalholm/cascadia v1.3.4/go.mod h1:BLRmbRjpEtNKieZOCCvYj4RqN+KRA41GBe/5O+G93kM=
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/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.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/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/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.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/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/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.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/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/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.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/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/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.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/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/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
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/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
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.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/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/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.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
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-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.9.1 h1:8U73XiOTfINdItHVa6z4Gv7ToObcZ6grkqQbLryLCdA=
github.com/go-git/go-billy/v5 v5.9.1/go.mod h1:ExsU+jcGwXTBOnyilvAnEM1wug1IxHr4yP2ZXsNRtV0=
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-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.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-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-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
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/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/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 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/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/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
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-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-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.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8=
github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc=
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-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=
@ -274,22 +278,20 @@ 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.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/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/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.16.0 h1:DQLfp+djj4j5NPdJkGYym8J55hpm5etML1zqgco78Qc=
github.com/hasura/go-graphql-client v0.16.0/go.mod h1:z/sO2T0zI+HnPNIevQcs+7xA6/gDOc8hgHMrNBzfL2c=
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/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=
@ -300,12 +302,14 @@ 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.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
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/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=
@ -315,17 +319,19 @@ 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.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0=
github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE=
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/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.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
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-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
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/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/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=
@ -333,10 +339,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.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/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/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=
@ -345,12 +351,10 @@ 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/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/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/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=
@ -360,23 +364,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.61.0 h1:ui88A53s8MSVYLC56en0KQ17HARk+9986Dn0SBfKNvA=
github.com/quic-go/quic-go v0.61.0/go.mod h1:9So2anK4Tp22URSQq00k+Vo2PNkle96ycDPDHL4s9vs=
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/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.16.1 h1://Xa7P0F/eOIcJ/6SehTqgr2MRnzPfI8LdAwlAYTnTs=
github.com/sgaunet/perplexity-go/v2 v2.16.1/go.mod h1:5dckcaxoFKtJEfNRc9+OhL6p/W/TKxd0gbSzc1YkmkI=
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/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=
@ -385,8 +389,6 @@ 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=
@ -401,8 +403,6 @@ 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.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
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,86 +424,101 @@ 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.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.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.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.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.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.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=
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/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=
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.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/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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
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/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/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.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/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/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.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
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/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=
@ -515,60 +530,81 @@ 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.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
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/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.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
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/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.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/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/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.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/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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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=
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=
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=
@ -576,7 +612,6 @@ 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,7 +1,6 @@
package cli
import (
"context"
"errors"
"fmt"
"os"
@ -89,7 +88,7 @@ func handleChatProcessing(currentFlags *Flags, registry *core.PluginRegistry, me
chatOptions.AudioFormat = "wav" // Default to WAV format
}
if session, err = chatter.Send(context.Background(), chatReq, chatOptions); err != nil {
if session, err = chatter.Send(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.YouTubeVisual) || flags.YouTubeTranscript || flags.YouTubeTranscriptWithTimestamps {
if (!flags.YouTubeComments && !flags.YouTubeMetadata) || flags.YouTubeTranscript || flags.YouTubeTranscriptWithTimestamps {
var transcript string
var language = "en"
if flags.Language != "" || registry.Language.DefaultLanguage.Value != "" {
@ -139,20 +139,6 @@ 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,7 +39,6 @@ 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"`
@ -57,9 +56,6 @@ 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')"`
@ -78,8 +74,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:"127.0.0.1:8080"`
ServeAPIKey string `long:"api-key" env:"FABRIC_API_KEY" description:"API key used to secure server routes" default:""`
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:""`
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"`
@ -89,7 +85,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, Grok)"`
Search bool `long:"search" description:"Enable web search tool for supported models (Anthropic, OpenAI, Gemini)"`
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)"`
@ -109,7 +105,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 (input/output tokens) to stderr"`
ShowMetadata bool `long:"show-metadata" description:"Print metadata to stderr"`
Debug int `long:"debug" description:"Set debug level (0=off, 1=basic, 2=detailed, 3=trace, 4=wire)" default:"0"`
}
@ -123,7 +119,8 @@ 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 field := range t.Fields() {
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
yamlTag := field.Tag.Get("yaml")
if yamlTag != "" {
longTag := field.Tag.Get("long")

View file

@ -27,7 +27,6 @@ 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",
@ -44,13 +43,9 @@ 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",
@ -96,7 +91,6 @@ 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",
}
@ -145,7 +139,8 @@ func (h *TranslatedHelpWriter) getTranslatedDescription(flagName string) string
func (h *TranslatedHelpWriter) getOriginalDescription(flagName string) string {
flagsType := reflect.TypeFor[Flags]()
for field := range flagsType.Fields() {
for i := 0; i < flagsType.NumField(); i++ {
field := flagsType.Field(i)
longTag := field.Tag.Get("long")
if longTag == flagName {
@ -224,7 +219,9 @@ func (h *TranslatedHelpWriter) writeAllFlags() {
// Use direct reflection on the Flags struct to get all flag definitions
flagsType := reflect.TypeFor[Flags]()
for field := range flagsType.Fields() {
for i := 0; i < flagsType.NumField(); i++ {
field := flagsType.Field(i)
shortTag := field.Tag.Get("short")
longTag := field.Tag.Get("long")
defaultTag := field.Tag.Get("default")

View file

@ -29,11 +29,6 @@ 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,10 +5,6 @@ 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) {
@ -26,7 +22,7 @@ func handleSetupAndServerCommands(currentFlags *Flags, registry *core.PluginRegi
if currentFlags.ServeOllama {
registry.ConfigureVendors()
err = serveOllama(registry, currentFlags.ServeAddress, version, currentFlags.ServeAPIKey)
err = restapi.ServeOllama(registry, currentFlags.ServeAddress, version)
return true, err
}

View file

@ -1,37 +0,0 @@
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(ctx context.Context, request *domain.ChatRequest, opts *domain.ChatOptions) (session *fsdb.Session, err error) {
func (o *Chatter) Send(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(ctx context.Context, request *domain.ChatRequest, opts *d
go func() {
defer close(done)
if streamErr := o.vendor.SendStream(ctx, session.GetVendorMessages(), opts, responseChan); streamErr != nil {
if streamErr := o.vendor.SendStream(session.GetVendorMessages(), opts, responseChan); streamErr != nil {
recordFirstStreamError(errChan, streamErr)
}
}()
@ -168,7 +168,7 @@ func (o *Chatter) Send(ctx context.Context, request *domain.ChatRequest, opts *d
// No errors, continue
}
} else {
if message, err = o.vendor.Send(ctx, session.GetVendorMessages(), opts); err != nil {
if message, err = o.vendor.Send(context.Background(), 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(context.Context) ([]string, error) {
func (m *mockVendor) ListModels() ([]string, error) {
return []string{"test-model"}, nil
}
func (m *mockVendor) SendStream(_ context.Context, messages []*chat.ChatCompletionMessage, opts *domain.ChatOptions, responseChan chan domain.StreamUpdate) error {
func (m *mockVendor) SendStream(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(context.Background(), request, opts)
session, err := chatter.Send(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(context.Background(), request, opts)
session, err := chatter.Send(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(context.Background(), request, opts)
session, err := chatter.Send(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(context.Background(), request, opts)
session, err := chatter.Send(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(context.Background(), request, opts)
_, err := chatter.Send(request, opts)
if err != nil {
t.Fatalf("Expected no error, but got: %v", err)
}

View file

@ -10,7 +10,6 @@ import (
"sort"
"strconv"
"strings"
"sync"
"github.com/danielmiessler/fabric/internal/i18n"
debuglog "github.com/danielmiessler/fabric/internal/log"
@ -74,24 +73,6 @@ 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(),
@ -105,7 +86,7 @@ func NewPluginRegistry(db *fsdb.Db) (ret *PluginRegistry, err error) {
lmstudio.NewClient(),
exolab.NewClient(),
perplexity.NewClient(),
codexClient,
codex.NewClient(),
copilot.NewClient(), // Microsoft 365 Copilot
bedrock.NewClient(), // AWS Bedrock - credentials configured via setup or AWS credential chain
)
@ -142,8 +123,6 @@ 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
@ -309,12 +288,16 @@ 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
}
@ -366,11 +349,16 @@ 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
}
@ -437,32 +425,11 @@ 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() {
@ -471,34 +438,6 @@ 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()
@ -564,25 +503,13 @@ func (o *PluginRegistry) GetChatter(model string, modelContextLength int, vendor
ret.model = defaultModel
}
} else if model == "" {
name := vendorName
if name == "" {
name = defaultVendor
}
if ret.vendor, err = o.activateVendor(name); err != nil {
return
if vendorName != "" {
ret.vendor = vendorManager.FindByName(vendorName)
} else {
ret.vendor = vendorManager.FindByName(defaultVendor)
}
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
@ -612,44 +539,16 @@ func (o *PluginRegistry) GetChatter(model string, modelContextLength int, vendor
return
}
} else {
// 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))
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.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,7 +3,6 @@ package core
import (
"bytes"
"context"
"errors"
"io"
"os"
"strings"
@ -11,14 +10,11 @@ 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) {
@ -36,19 +32,18 @@ func TestSaveEnvFile(t *testing.T) {
// testVendor implements ai.Vendor for testing purposes
type testVendor struct {
name string
models []string
configureErr error
name string
models []string
}
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 {
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 {
return nil
}
func (m *testVendor) Send(context.Context, []*chat.ChatCompletionMessage, *domain.ChatOptions) (string, error) {
@ -155,176 +150,3 @@ 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,7 +218,8 @@ func getLocaleCandidates(locale string) []string {
candidates = append(candidates, locale)
// If it's a regional variant, add the base language as a candidate
if baseLang, _, found := strings.Cut(locale, "-"); found {
if strings.Contains(locale, "-") {
baseLang := strings.Split(locale, "-")[0]
candidates = append(candidates, baseLang)
// Also check if the base language has a default variant

View file

@ -20,7 +20,6 @@ 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 nicht initialisiert — führen Sie 'fabric --setup' zur Konfiguration aus",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"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-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_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_header": "[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_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_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,43 +110,21 @@
"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",
@ -173,7 +151,6 @@
"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",
@ -184,7 +161,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, Grok)",
"enable_web_search_tool": "Web-Such-Tool für unterstützte Modelle aktivieren (Anthropic, OpenAI, Gemini)",
"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",
@ -375,7 +352,6 @@
"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",
@ -389,7 +365,6 @@
"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",
@ -410,7 +385,6 @@
"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)",
@ -426,7 +400,6 @@
"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",
@ -500,8 +473,6 @@
"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",
@ -515,12 +486,10 @@
"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",
@ -612,7 +581,6 @@
"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",
@ -624,7 +592,6 @@
"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",
@ -643,11 +610,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": "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_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_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",
@ -676,12 +643,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: 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_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_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",
@ -731,21 +698,15 @@
"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",
@ -756,15 +717,9 @@
"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,43 +110,21 @@
"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",
@ -173,7 +151,6 @@
"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",
@ -184,7 +161,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, Grok)",
"enable_web_search_tool": "Enable web search tool for supported models (Anthropic, OpenAI, Gemini)",
"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",
@ -375,7 +352,6 @@
"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",
@ -389,7 +365,6 @@
"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",
@ -410,7 +385,6 @@
"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)",
@ -426,7 +400,6 @@
"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",
@ -500,8 +473,6 @@
"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",
@ -515,12 +486,10 @@
"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",
@ -612,7 +581,6 @@
"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",
@ -624,7 +592,6 @@
"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",
@ -731,21 +698,15 @@
"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",
@ -756,15 +717,9 @@
"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": "cliente de Bedrock no inicializado — ejecute 'fabric --setup' para configurar",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"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] 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_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_header": "[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_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_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,43 +110,21 @@
"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",
@ -173,7 +151,6 @@
"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",
@ -184,7 +161,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, Grok)",
"enable_web_search_tool": "Habilitar herramienta de búsqueda web para modelos soportados (Anthropic, OpenAI, Gemini)",
"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",
@ -375,7 +352,6 @@
"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",
@ -389,7 +365,6 @@
"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",
@ -410,7 +385,6 @@
"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)",
@ -426,7 +400,6 @@
"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",
@ -500,8 +473,6 @@
"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",
@ -515,12 +486,10 @@
"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",
@ -612,7 +581,6 @@
"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",
@ -624,7 +592,6 @@
"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",
@ -643,11 +610,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": "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_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_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",
@ -676,12 +643,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: 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_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_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",
@ -731,21 +698,15 @@
"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",
@ -756,15 +717,9 @@
"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 مقداردهی نشده است — 'fabric --setup' را برای پیکربندی اجرا کنید",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"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] کلید 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_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_header": "[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_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_unable_load_aws_config": "بارگذاری پیکربندی AWS ناموفق بود: %w",
"bedrock_unable_load_aws_config_with_region": "بارگذاری پیکربندی AWS با منطقه %s ناموفق بود: %w",
"bedrock_unexpected_content_block_type": "نوع بلوک محتوای غیرمنتظره: %T",
@ -110,43 +110,21 @@
"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",
@ -173,7 +151,6 @@
"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": "ارائه‌دهنده و مدل هوش مصنوعی پیش‌فرض",
@ -184,7 +161,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، Grok)",
"enable_web_search_tool": "فعال‌سازی ابزار جستجوی وب برای مدل‌های پشتیبانی شده (Anthropic، OpenAI، Gemini)",
"end_tag_thinking_sections": "تگ پایان برای بخش‌های تفکر",
"error_creating_audio_file": "خطا در ایجاد فایل صوتی: %v",
"error_creating_file": "خطا در ایجاد فایل: %v",
@ -375,7 +352,6 @@
"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 باید یک عدد باشد، نوع نامعتبر دریافت شد",
@ -389,7 +365,6 @@
"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",
@ -410,7 +385,6 @@
"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)",
@ -426,7 +400,6 @@
"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",
@ -500,8 +473,6 @@
"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": "حذف افزونه ثبت شده با نام",
@ -515,12 +486,10 @@
"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": "تنظیم جریمه فرکانس",
@ -577,7 +546,7 @@
"spotify_description_header": "## توضیحات",
"spotify_duration_label": "**مدت زمان**: %d دقیقه",
"spotify_episode_header": "# اپیزود اسپاتیفای",
"spotify_error_getting_metadata": "خطا در دریافت فراداده Spotify: %v",
"spotify_error_getting_metadata": "error getting Spotify metadata: %v",
"spotify_explicit_label": "**محتوای صریح**: %v",
"spotify_failed_create_request": "ایجاد درخواست ناموفق بود: %w",
"spotify_failed_create_token_request": "ایجاد درخواست توکن ناموفق بود: %w",
@ -591,14 +560,14 @@
"spotify_failed_parse_show_metadata": "تجزیه فراداده برنامه ناموفق بود: %w",
"spotify_failed_read_response_body": "خواندن متن پاسخ ناموفق بود: %w",
"spotify_failed_request_access_token": "درخواست توکن دسترسی ناموفق بود: %w",
"spotify_invalid_url": "نشانی Spotify نامعتبر است، دریافت شناسه برنامه یا قسمت ممکن نیست: '%s'",
"spotify_invalid_url": "invalid Spotify URL, can't get show or episode ID: '%s'",
"spotify_label": "Spotify",
"spotify_language_field_label": "**زبان**: %s",
"spotify_languages_label": "**زبان‌ها**: %s",
"spotify_media_type_label": "**نوع رسانه**: %s",
"spotify_no_episode_found": "هیچ قسمتی با این شناسه یافت نشد: %s",
"spotify_no_show_found": "هیچ برنامه‌ای با این شناسه یافت نشد: %s",
"spotify_not_configured": "Spotify پیکربندی نشده است، لطفاً روند راه‌اندازی را اجرا کنید",
"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_publisher_label": "**ناشر**: %s",
"spotify_release_date_label": "**تاریخ انتشار**: %s",
"spotify_search_description_label": "- **توضیحات**: %s",
@ -607,12 +576,11 @@
"spotify_search_publisher_label": "- **ناشر**: %s",
"spotify_search_results_header": "# نتایج جستجوی اسپاتیفای",
"spotify_search_url_label": "- **URL**: %s",
"spotify_setup_description": "Spotify - برای دریافت فراداده پادکست/برنامه از Spotify",
"spotify_setup_description": "Spotify - to grab podcast/show metadata from 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",
@ -624,7 +592,6 @@
"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",
@ -643,11 +610,11 @@
"strategy_path_traversal": "نام راهبرد %q خارج از دایرکتوری راهبردها حل می‌شود",
"stream_help": "پخش زنده",
"suppress_thinking_tags": "سرکوب متن محصور در تگ‌های تفکر",
"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_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_extension_error": "خطای افزونه %s: %v",
"template_file_error_expand_home_dir": "فایل: گسترش پوشه خانگی ممکن نشد: %v",
"template_file_error_invalid_line_count": "فایل: تعداد خط نامعتبر %q",
@ -676,12 +643,12 @@
"template_missing_required_variable": "متغیر الزامی موجود نیست: %s",
"template_plugin_error": "خطای پلاگین %s: %v",
"template_processing_stuck": "پردازش قالب متوقف شده - احتمال حلقه بی‌نهایت",
"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_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_text_empty_input": "متن: ورودی خالی برای عملیات %q",
"template_text_unknown_operation": "متن: عملیات متنی ناشناخته %q (پشتیبانی شده: upper, lower, title, trim)",
"template_unknown_plugin_namespace": "فضای نام پلاگین ناشناخته: %s",
@ -731,21 +698,15 @@
"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",
@ -756,15 +717,9 @@
"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": "client Bedrock non initialisé — exécutez 'fabric --setup' pour configurer",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"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] 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_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_header": "[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_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_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,43 +110,21 @@
"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",
@ -173,7 +151,6 @@
"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",
@ -184,7 +161,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, Grok)",
"enable_web_search_tool": "Activer l'outil de recherche web pour les modèles pris en charge (Anthropic, OpenAI, Gemini)",
"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",
@ -375,7 +352,6 @@
"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",
@ -389,7 +365,6 @@
"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",
@ -410,7 +385,6 @@
"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)",
@ -426,7 +400,6 @@
"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",
@ -500,8 +473,6 @@
"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",
@ -515,12 +486,10 @@
"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",
@ -612,7 +581,6 @@
"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",
@ -624,7 +592,6 @@
"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",
@ -643,11 +610,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": "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_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_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",
@ -676,12 +643,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: 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_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_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",
@ -731,21 +698,15 @@
"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",
@ -756,15 +717,9 @@
"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": "client Bedrock non inizializzato — eseguire 'fabric --setup' per configurare",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"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] 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_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_header": "[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_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_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,43 +110,21 @@
"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",
@ -173,7 +151,6 @@
"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",
@ -184,7 +161,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, Grok)",
"enable_web_search_tool": "Abilita strumento di ricerca web per modelli supportati (Anthropic, OpenAI, Gemini)",
"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",
@ -375,7 +352,6 @@
"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",
@ -389,7 +365,6 @@
"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",
@ -410,7 +385,6 @@
"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)",
@ -426,7 +400,6 @@
"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",
@ -500,8 +473,6 @@
"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",
@ -515,12 +486,10 @@
"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",
@ -612,7 +581,6 @@
"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",
@ -624,7 +592,6 @@
"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",
@ -643,11 +610,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": "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_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_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",
@ -676,12 +643,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: 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_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_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",
@ -731,21 +698,15 @@
"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",
@ -756,15 +717,9 @@
"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クライアントが初期化されていません — 設定するには 'fabric --setup' を実行してください",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"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キー / 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_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_header": "[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_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_unable_load_aws_config": "AWS設定を読み込めませんでした: %w",
"bedrock_unable_load_aws_config_with_region": "リージョン %s でAWS設定を読み込めませんでした: %w",
"bedrock_unexpected_content_block_type": "予期しないコンテンツブロックタイプ: %T",
@ -110,43 +110,21 @@
"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",
@ -173,7 +151,6 @@
"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プロバイダーとモデル",
@ -184,7 +161,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、Grok)でウェブ検索ツールを有効化",
"enable_web_search_tool": "サポートされているモデルAnthropic、OpenAI、Gemini)でウェブ検索ツールを有効化",
"end_tag_thinking_sections": "思考セクションの終了タグ",
"error_creating_audio_file": "音声ファイルの作成エラー: %v",
"error_creating_file": "ファイルの作成エラー: %v",
@ -375,7 +352,6 @@
"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 は数値である必要があります。無効な型を受け取りました",
@ -389,7 +365,6 @@
"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",
@ -410,7 +385,6 @@
"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)",
@ -426,7 +400,6 @@
"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",
@ -500,8 +473,6 @@
"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": "名前で登録済み拡張機能を削除",
@ -515,12 +486,10 @@
"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": "頻度ペナルティを設定",
@ -612,7 +581,6 @@
"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",
@ -624,7 +592,6 @@
"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",
@ -643,11 +610,11 @@
"strategy_path_traversal": "戦略名 %q が戦略ディレクトリの外部に解決されます",
"stream_help": "ストリーミング",
"suppress_thinking_tags": "思考タグで囲まれたテキストを抑制",
"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_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_extension_error": "拡張機能%sエラー: %v",
"template_file_error_expand_home_dir": "file: ホームディレクトリを展開できませんでした: %v",
"template_file_error_invalid_line_count": "file: 無効な行数 %q",
@ -664,7 +631,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": "ファイル: operation=%q value=%q",
"template_file_log_operation_value": "File: 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 行を読み取り中",
@ -676,12 +643,12 @@
"template_missing_required_variable": "必須変数が不足しています: %s",
"template_plugin_error": "プラグイン%sエラー: %v",
"template_processing_stuck": "テンプレート処理が停止 - 無限ループの可能性",
"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_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_text_empty_input": "テキスト: 操作%qに対する入力が空です",
"template_text_unknown_operation": "テキスト: 不明なテキスト操作%q (対応: upper, lower, title, trim)",
"template_unknown_plugin_namespace": "不明なプラグイン名前空間: %s",
@ -731,21 +698,15 @@
"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",
@ -756,15 +717,9 @@
"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": "klient Bedrock nie jest zainicjalizowany — uruchom 'fabric --setup', aby skonfigurować",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"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] 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_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_header": "[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_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_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,43 +110,21 @@
"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",
@ -173,7 +151,6 @@
"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",
@ -184,7 +161,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, Grok)",
"enable_web_search_tool": "Włącz narzędzie wyszukiwania internetowego dla obsługiwanych modeli (Anthropic, OpenAI, Gemini)",
"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",
@ -375,7 +352,6 @@
"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",
@ -389,7 +365,6 @@
"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",
@ -410,7 +385,6 @@
"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)",
@ -426,7 +400,6 @@
"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",
@ -500,8 +473,6 @@
"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",
@ -515,12 +486,10 @@
"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",
@ -612,7 +581,6 @@
"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",
@ -624,7 +592,6 @@
"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",
@ -664,7 +631,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": "Plik: operation=%q value=%q",
"template_file_log_operation_value": "File: 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",
@ -731,21 +698,15 @@
"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",
@ -756,15 +717,9 @@
"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": "cliente Bedrock não inicializado — execute 'fabric --setup' para configurar",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"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] 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_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_header": "[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_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_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,43 +110,21 @@
"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",
@ -173,7 +151,6 @@
"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",
@ -184,7 +161,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, Grok)",
"enable_web_search_tool": "Habilitar ferramenta de busca web para modelos suportados (Anthropic, OpenAI, Gemini)",
"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",
@ -375,7 +352,6 @@
"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",
@ -389,7 +365,6 @@
"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",
@ -410,7 +385,6 @@
"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)",
@ -426,7 +400,6 @@
"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",
@ -500,8 +473,6 @@
"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",
@ -515,12 +486,10 @@
"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",
@ -612,7 +581,6 @@
"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",
@ -624,7 +592,6 @@
"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",
@ -643,11 +610,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": "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_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_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",
@ -676,12 +643,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: 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_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_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",
@ -731,21 +698,15 @@
"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",
@ -756,15 +717,9 @@
"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": "cliente Bedrock não inicializado — execute 'fabric --setup' para configurar",
"bedrock_client_not_initialized": "Bedrock client not initialized — run 'fabric --setup' to configure",
"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] 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_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_header": "[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_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_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,43 +110,21 @@
"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",
@ -173,7 +151,6 @@
"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",
@ -184,7 +161,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, Grok)",
"enable_web_search_tool": "Habilitar ferramenta de pesquisa web para modelos suportados (Anthropic, OpenAI, Gemini)",
"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",
@ -375,7 +352,6 @@
"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",
@ -389,7 +365,6 @@
"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",
@ -410,7 +385,6 @@
"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)",
@ -426,7 +400,6 @@
"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",
@ -500,8 +473,6 @@
"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",
@ -515,12 +486,10 @@
"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",
@ -612,7 +581,6 @@
"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",
@ -624,7 +592,6 @@
"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",
@ -643,11 +610,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": "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_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_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",
@ -676,12 +643,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: 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_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_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",
@ -731,21 +698,15 @@
"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",
@ -756,15 +717,9 @@
"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

@ -1,770 +0,0 @@
{
"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,43 +110,21 @@
"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",
@ -173,7 +151,6 @@
"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 提供商和模型",
@ -184,7 +161,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、Grok",
"enable_web_search_tool": "为支持的模型启用网络搜索工具Anthropic、OpenAI、Gemini",
"end_tag_thinking_sections": "思考部分的结束标签",
"error_creating_audio_file": "创建音频文件时出错:%v",
"error_creating_file": "创建文件时出错:%v",
@ -375,7 +352,6 @@
"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 必须是数字,收到无效类型",
@ -389,7 +365,6 @@
"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",
@ -410,7 +385,6 @@
"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)",
@ -426,7 +400,6 @@
"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",
@ -500,8 +473,6 @@
"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": "按名称删除已注册的扩展",
@ -515,12 +486,10 @@
"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": "设置频率惩罚",
@ -612,7 +581,6 @@
"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",
@ -624,7 +592,6 @@
"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",
@ -731,21 +698,15 @@
"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 的视频",
@ -756,15 +717,9 @@
"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,7 +6,6 @@ import (
neturl "net/url"
"os"
"path"
"slices"
"strconv"
"strings"
@ -25,22 +24,6 @@ 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{}
@ -55,47 +38,43 @@ 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{
// Claude 5 family
string(anthropic.ModelClaudeFable5): {context1M},
string(anthropic.ModelClaudeOpus5): {context1M},
string(anthropic.ModelClaudeSonnet5): {context1M},
// 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 Opus 4.x (1M-capable)
string(anthropic.ModelClaudeOpus4_8): {context1M},
string(anthropic.ModelClaudeOpus4_7): {context1M},
string(anthropic.ModelClaudeOpus4_6): {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 Sonnet 4.x (1M-capable)
string(anthropic.ModelClaudeSonnet4_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"},
}
return
@ -146,7 +125,7 @@ func (an *Client) configure() (err error) {
return
}
func (an *Client) ListModels(context.Context) (ret []string, err error) {
func (an *Client) ListModels() (ret []string, err error) {
return an.models, nil
}
@ -171,7 +150,7 @@ func parseThinking(level domain.ThinkingLevel) (anthropic.ThinkingConfigParamUni
}
func (an *Client) SendStream(
ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
) (err error) {
messages := an.toMessages(msgs)
if len(messages) == 0 {
@ -180,6 +159,8 @@ func (an *Client) SendStream(
return
}
ctx := context.Background()
params := an.buildMessageParams(messages, opts)
betas := an.modelBetas[opts.Model]
var reqOpts []option.RequestOption
@ -235,21 +216,15 @@ 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(maxTokens),
MaxTokens: int64(an.maxTokens),
Messages: msgs,
}
// 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 {
// 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 {
// User explicitly set TopP, so use that instead of temperature
params.TopP = anthropic.Opt(opts.TopP)
} else {

View file

@ -1,7 +1,6 @@
package anthropic
import (
"context"
"strings"
"testing"
@ -35,7 +34,7 @@ func TestNewClient_DefaultInitialization(t *testing.T) {
func TestClientListModels(t *testing.T) {
client := NewClient()
models, err := client.ListModels(context.Background())
models, err := client.ListModels()
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
@ -53,7 +52,7 @@ func TestClientListModels(t *testing.T) {
func TestClient_ListModels_ReturnsCorrectModels(t *testing.T) {
client := NewClient()
models, err := client.ListModels(context.Background())
models, err := client.ListModels()
if err != nil {
t.Fatalf("Expected no error, got %v", err)
@ -99,43 +98,6 @@ 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{
@ -207,32 +169,9 @@ 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.ModelClaudeSonnet5)
model := string(anthropic.ModelClaudeSonnet4_20250514)
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,7 +1,6 @@
package azure
import (
"context"
"errors"
"strings"
@ -67,7 +66,7 @@ func (oi *Client) configure() error {
return nil
}
func (oi *Client) ListModels(context.Context) (ret []string, err error) {
func (oi *Client) ListModels() (ret []string, err error) {
ret = oi.apiDeployments
return
}

View file

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

View file

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

View file

@ -1,7 +1,6 @@
package azure_entra
import (
"context"
"testing"
)
@ -48,7 +47,7 @@ func TestListModels(t *testing.T) {
client := NewClient()
client.apiDeployments = []string{"gpt-4o", "gpt-5"}
models, err := client.ListModels(context.Background())
models, err := client.ListModels()
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(context.Context) ([]string, error)
ListModels() ([]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(ctx context.Context) ([]string, error) {
func (c *Client) ListModels() ([]string, error) {
if c.backend == nil {
return nil, errors.New(i18n.T("azureaigateway_backend_not_initialized"))
}
return c.backend.ListModels(ctx)
return c.backend.ListModels()
}
// Send sends a non-streaming request through the APIM gateway.
@ -199,13 +199,18 @@ 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).
func (c *Client) SendStream(ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
//
// 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 {
defer close(channel)
if c.backend == nil {
return errors.New(i18n.T("azureaigateway_backend_not_initialized"))
}
ctx, cancel := context.WithTimeout(ctx, gatewayTimeout)
ctx, cancel := context.WithTimeout(context.Background(), 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(context.Background())
models, err := b.ListModels()
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(context.Background())
models, err := b.ListModels()
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(context.Background())
models, err := b.ListModels()
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(context.Background())
_, err := c.ListModels()
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(context.Background(), msgs, opts, channel)
err := c.SendStream(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(context.Background(), msgs, opts, channel)
err := c.SendStream(msgs, opts, channel)
if err != nil {
t.Fatalf("SendStream() error = %v", err)
}

View file

@ -2,7 +2,6 @@
package azureaigateway
import (
"context"
"encoding/json"
"errors"
"fmt"
@ -35,7 +34,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(_ context.Context) ([]string, error) {
func (b *AzureOpenAIBackend) ListModels() ([]string, error) {
return []string{
"DeepSeek-R1",
"gpt-4o",

View file

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

View file

@ -2,7 +2,6 @@
package azureaigateway
import (
"context"
"encoding/json"
"errors"
"fmt"
@ -27,7 +26,7 @@ func NewVertexAIBackend(subscriptionKey string) *VertexAIBackend {
}
// ListModels returns the list of Gemini models available through Vertex AI
func (b *VertexAIBackend) ListModels(_ context.Context) ([]string, error) {
func (b *VertexAIBackend) ListModels() ([]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(_ context.Context) ([]string, error) {
func (c *BedrockClient) ListModels() ([]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(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
func (c *BedrockClient) SendStream(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(context.Background())
models, err := client.ListModels()
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(context.Background())
_, err := client.ListModels()
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(context.Background(), nil, opts, ch)
err := client.SendStream(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

@ -1,248 +0,0 @@
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,11 +4,21 @@ 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"
@ -45,7 +55,8 @@ const (
const oauthScope = "openid profile email offline_access api.connectors.read api.connectors.invoke"
// Client implements the Codex-backed AI vendor.
var errReplayBodyUnavailable = errors.New("request body cannot be replayed for Codex re-authentication retry")
type Client struct {
*openaivendor.Client
@ -58,10 +69,29 @@ type Client struct {
apiHTTPClient *http.Client
tokenMu sync.Mutex
// 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 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 modelInfo struct {
@ -74,6 +104,29 @@ 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{}
@ -85,11 +138,11 @@ func NewClient() *Client {
client.AccountID = client.AddSetting("Account ID", true)
client.ApiBaseURL = client.AddSetupQuestionWithEnvName("Base URL", false,
i18n.T("codex_api_base_url_question"))
"Enter your Codex API base URL")
client.ApiBaseURL.Value = defaultBaseURL
client.AuthBaseURL = client.AddSetupQuestionWithEnvName("Auth Base URL", false,
i18n.T("codex_auth_base_url_question"))
"Enter your Codex OAuth base URL")
client.AuthBaseURL.Value = defaultAuthBaseURL
client.authHTTPClient = &http.Client{Timeout: modelsRequestTimeout}
@ -105,14 +158,6 @@ 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()
@ -165,35 +210,20 @@ func (c *Client) configure() error {
option.WithHTTPClient(c.apiHTTPClient),
)
c.ApiClient = &apiClient
debuglog.Debug(debuglog.Detailed, "Codex configure: authenticated account_present=%t base_url=%s\n", strings.TrimSpace(c.AccountID.Value) != "", c.ApiBaseURL.Value)
debuglog.Debug(debuglog.Detailed, "Codex configure: authenticated account=%s base_url=%s\n", 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(ctx context.Context) ([]string, error) {
func (c *Client) ListModels() ([]string, error) {
if c.apiHTTPClient == nil {
if err := c.configure(); err != nil {
return nil, err
}
}
ctx, cancel := context.WithTimeout(ctx, modelsRequestTimeout)
ctx, cancel := context.WithTimeout(context.Background(), modelsRequestTimeout)
defer cancel()
modelsURL := strings.TrimRight(c.ApiBaseURL.Value, "/") + "/models"
@ -222,7 +252,7 @@ func (c *Client) ListModels(ctx context.Context) ([]string, error) {
var decoded modelsResponse
if err := json.Unmarshal(body, &decoded); err != nil {
return nil, fmt.Errorf(i18n.T("codex_decode_models_response_failed"), err)
return nil, fmt.Errorf("failed to decode Codex models response: %w", err)
}
models := make([]string, 0, len(decoded.Models))
@ -269,19 +299,16 @@ 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 {
if extractedText := c.ExtractText(completedResp); strings.TrimSpace(extractedText) != "" {
return extractedText, nil
}
return c.ExtractText(completedResp), nil
}
return streamedText, nil
return builder.String(), nil
}
// SendStream sends a request to Codex and streams the response text updates.
func (c *Client) SendStream(
ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
) error {
defer close(channel)
@ -295,17 +322,15 @@ func (c *Client) SendStream(
}
req := c.buildCodexResponseParams(msgs, opts)
stream := c.ApiClient.Responses.NewStreaming(ctx, req)
stream := c.ApiClient.Responses.NewStreaming(context.Background(), req)
defer stream.Close()
for stream.Next() {
event := stream.Current()
switch event.Type {
case string(constant.ResponseOutputTextDelta("").Default()):
if err := sendStreamUpdate(ctx, channel, domain.StreamUpdate{
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: event.AsResponseOutputTextDelta().Delta,
}); err != nil {
return err
}
case string(constant.ResponseOutputTextDone("").Default()):
continue
@ -313,26 +338,15 @@ func (c *Client) SendStream(
}
if stream.Err() == nil {
if err := sendStreamUpdate(ctx, channel, domain.StreamUpdate{
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 {
@ -390,3 +404,669 @@ 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,7 +4,6 @@ import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net"
@ -19,7 +18,6 @@ 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"
)
@ -178,7 +176,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(context.Background())
models, err := client.ListModels()
if err != nil {
t.Fatalf("ListModels() error = %v", err)
}
@ -212,10 +210,6 @@ 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 {
@ -226,20 +220,12 @@ func TestMapRequestErrorPreservesCodexAPIErrorMessage(t *testing.T) {
if err == nil {
t.Fatal("mapRequestError() returned nil")
}
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)
if got := err.Error(); got != "The requested model is not supported." {
t.Fatalf("mapRequestError() = %q, want %q", got, "The requested model is not supported.")
}
}
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,
@ -252,12 +238,8 @@ func TestMapRequestErrorReadsAPIErrorResponseBodyWhenRawJSONMissing(t *testing.T
if err == nil {
t.Fatal("mapRequestError() returned nil")
}
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)
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.")
}
}
@ -440,61 +422,6 @@ 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" {
@ -542,7 +469,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(context.Background(), []*chat.ChatCompletionMessage{
err := client.SendStream([]*chat.ChatCompletionMessage{
{Role: chat.ChatMessageRoleSystem, Content: "Follow the system prompt"},
{Role: "user", Content: "Hello"},
}, &domain.ChatOptions{
@ -565,190 +492,6 @@ 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

@ -1,183 +0,0 @@
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

@ -1,304 +0,0 @@
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

@ -1,115 +0,0 @@
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,7 +20,6 @@ import (
"fmt"
"io"
"net/http"
"slices"
"strings"
"time"
@ -160,7 +159,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(_ context.Context) ([]string, error) {
func (c *Client) ListModels() ([]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
@ -187,7 +186,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(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
defer close(channel)
ctx := context.Background()
@ -402,7 +401,9 @@ 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 _, msg := range slices.Backward(messages) {
for i := len(messages) - 1; i >= 0; i-- {
msg := messages[i]
// Response messages from Copilot have the copilotConversationResponseMessage type
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(ctx context.Context) ([]string, error) {
func (c *Client) ListModels() ([]string, error) {
if c.ControlPlaneToken.Value == "" {
models, err := c.Client.ListModels(ctx)
models, err := c.Client.ListModels()
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(_ context.Context) ([]string, error) {
func (c *Client) ListModels() ([]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(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
func (c *Client) SendStream(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,7 +1,6 @@
package dryrun
import (
"context"
"reflect"
"testing"
@ -12,7 +11,7 @@ import (
// Test generated using Keploy
func TestListModels_ReturnsExpectedModel(t *testing.T) {
client := NewClient()
models, err := client.ListModels(context.Background())
models, err := client.ListModels()
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
@ -42,7 +41,7 @@ func TestSendStream_SendsMessages(t *testing.T) {
}
channel := make(chan domain.StreamUpdate)
go func() {
err := client.SendStream(context.Background(), msgs, opts, channel)
err := client.SendStream(msgs, opts, channel)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}

View file

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

View file

@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"regexp"
"slices"
"strconv"
"strings"
@ -61,7 +60,7 @@ type Client struct {
ApiKey *plugins.SetupQuestion
}
func (o *Client) ListModels(_ context.Context) (ret []string, err error) {
func (o *Client) ListModels() (ret []string, err error) {
ctx := context.Background()
var client *genai.Client
if client, err = genai.NewClient(ctx, &genai.ClientConfig{
@ -125,7 +124,7 @@ func (o *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
return
}
func (o *Client) SendStream(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
func (o *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
ctx := context.Background()
defer close(channel)
@ -291,9 +290,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 _, msg := range slices.Backward(msgs) {
if msg.Role == chat.ChatMessageRoleUser && msg.Content != "" {
return msg.Content, nil
for i := len(msgs) - 1; i >= 0; i-- {
if msgs[i].Role == chat.ChatMessageRoleUser && msgs[i].Content != "" {
return msgs[i].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(_ context.Context) ([]string, error) {
func (c *Client) ListModels() ([]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(_ context.Context) ([]string, error) {
return models, nil
}
func (c *Client) SendStream(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
func (c *Client) SendStream(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(context.Background())
models, err := client.ListModels()
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(context.Background())
models, err := client.ListModels()
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(_ context.Context) (ret []string, err error) {
func (o *Client) ListModels() (ret []string, err error) {
ctx := context.Background()
var listResp *ollamaapi.ListResponse
@ -104,9 +104,8 @@ func (o *Client) ListModels(_ context.Context) (ret []string, err error) {
return
}
func (o *Client) SendStream(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
func (o *Client) SendStream(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 {
@ -136,6 +135,7 @@ func (o *Client) SendStream(_ context.Context, msgs []*chat.ChatCompletionMessag
return
}
close(channel)
return
}

View file

@ -6,14 +6,10 @@ 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"
)
@ -64,28 +60,3 @@ 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(
ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
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(ctx, req)
stream := o.ApiClient.Chat.Completions.NewStreaming(context.Background(), req)
for stream.Next() {
chunk := stream.Current()
if len(chunk.Choices) > 0 && chunk.Choices[0].Delta.Content != "" {

View file

@ -7,7 +7,6 @@ import (
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/danielmiessler/fabric/internal/i18n"
@ -46,13 +45,6 @@ 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
@ -61,14 +53,6 @@ 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 {
@ -78,40 +62,17 @@ 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)
@ -136,16 +97,12 @@ 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))
ids := extractModelIDs(openAIFormat.Data)
_ = writeModelsCache(providerName, fullURL, ids)
return ids, nil
return extractModelIDs(openAIFormat.Data), 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))
ids := extractModelIDs(directArray)
_ = writeModelsCache(providerName, fullURL, ids)
return ids, nil
return extractModelIDs(directArray), nil
}
var truncatedBody string

View file

@ -1,95 +0,0 @@
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

@ -1,165 +0,0 @@
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,15 +66,6 @@ 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
@ -82,21 +73,6 @@ 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) {
@ -120,9 +96,9 @@ func (o *Client) configure() (ret error) {
return
}
func (o *Client) ListModels(ctx context.Context) (ret []string, err error) {
func (o *Client) ListModels() (ret []string, err error) {
var page *pagination.Page[openai.Model]
if page, err = o.ApiClient.Models.List(ctx); err == nil {
if page, err = o.ApiClient.Models.List(context.Background()); err == nil {
for _, mod := range page.Data {
ret = append(ret, mod.ID)
}
@ -134,26 +110,26 @@ func (o *Client) ListModels(ctx context.Context) (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(ctx, o.ApiBaseURL.Value, o.ApiKey.Value, o.GetName(), o.httpClient)
return FetchModelsDirectly(context.Background(), o.ApiBaseURL.Value, o.ApiKey.Value, o.GetName(), o.httpClient)
}
func (o *Client) SendStream(
ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
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(ctx, msgs, opts, channel)
return o.sendStreamResponses(msgs, opts, channel)
}
return o.sendStreamChatCompletions(ctx, msgs, opts, channel)
return o.sendStreamChatCompletions(msgs, opts, channel)
}
func (o *Client) sendStreamResponses(
ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
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(ctx, req)
stream := o.ApiClient.Responses.NewStreaming(context.Background(), req)
for stream.Next() {
event := stream.Current()
switch event.Type {
@ -223,7 +199,6 @@ func (o *Client) NeedsRawMode(modelName string) bool {
openaiModelsPrefixes := []string{
"glm",
"gpt-5",
"gpt-6",
"o1",
"o3",
"o4",
@ -278,18 +253,11 @@ func (o *Client) buildResponseParams(
// Add tools if enabled
var tools []responses.ToolUnionParam
// 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").
// Add web search tool if enabled
if opts.Search {
searchToolName := responses.WebSearchToolType("web_search_preview")
if o.webSearchToolName != "" {
searchToolName = responses.WebSearchToolType(o.webSearchToolName)
}
webSearchTool := responses.ToolParamOfWebSearchPreview(searchToolName)
webSearchTool := responses.ToolParamOfWebSearchPreview("web_search_preview")
// Add user location if provided. Only attach when the caller
// asked for it; xAI rejects unexpected location payloads.
// Add user location if provided
if opts.SearchLocation != "" {
webSearchTool.OfWebSearchPreview.UserLocation = responses.WebSearchToolUserLocationParam{
Type: "approximate",
@ -298,20 +266,6 @@ 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,10 +2,8 @@ package openai
import (
"context"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@ -14,7 +12,6 @@ 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")
@ -31,7 +28,6 @@ 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")
@ -46,54 +42,8 @@ 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,12 +12,6 @@ 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
@ -135,88 +129,6 @@ 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,14 +21,6 @@ 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
@ -48,15 +40,11 @@ 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(ctx context.Context) ([]string, error) {
func (c *Client) ListModels() ([]string, error) {
// If a custom models URL is provided, handle it
if c.modelsURL != "" {
if c.modelsURL == "static:abacus" {
@ -77,13 +65,13 @@ func (c *Client) ListModels(ctx context.Context) ([]string, error) {
}
// First try the standard OpenAI SDK approach
models, err := c.Client.ListModels(ctx)
models, err := c.Client.ListModels()
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(ctx)
return c.DirectlyGetModels(context.Background())
}
func (c *Client) fetchAbacusModels() ([]string, error) {
@ -206,9 +194,14 @@ 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)
@ -247,11 +240,6 @@ 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",
@ -289,21 +277,11 @@ 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(_ context.Context) ([]string, error) {
func (c *Client) ListModels() ([]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(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
func (c *Client) SendStream(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(context.Context) ([]string, error)
SendStream(context.Context, []*chat.ChatCompletionMessage, *domain.ChatOptions, chan domain.StreamUpdate) error
ListModels() ([]string, error)
SendStream([]*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(ctx)
models, err := vendor.ListModels()
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(context.Context) ([]string, error) { return nil, nil }
func (v *stubVendor) SendStream(context.Context, []*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() ([]string, error) { return nil, nil }
func (v *stubVendor) SendStream([]*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(_ context.Context) ([]string, error) {
func (c *Client) ListModels() ([]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(_ context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
if isGeminiModel(opts.Model) {
return c.sendStreamGemini(msgs, opts, channel)
}

View file

@ -1,15 +1,10 @@
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,7 +5,6 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/danielmiessler/fabric/internal/i18n"
@ -42,8 +41,6 @@ type Db struct {
Contexts *ContextsEntity
EnvFilePath string
envMu sync.Mutex
}
func (o *Db) Configure() (err error) {
@ -95,100 +92,9 @@ func (o *Db) IsEnvFileExists() (ret bool) {
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) SaveEnv(content string) (err error) {
err = os.WriteFile(o.EnvFilePath, []byte(content), 0644)
return
}
func (o *Db) FilePath(fileName string) (ret string) {

View file

@ -2,12 +2,7 @@ package fsdb
import (
"os"
"path/filepath"
"runtime"
"sync"
"testing"
"github.com/joho/godotenv"
)
func TestDb_Configure(t *testing.T) {
@ -57,150 +52,4 @@ 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