Compare commits

...

9 Commits

Author SHA1 Message Date
github-actions[bot]
efadc81974 chore(release): Update version to v1.4.296 2025-08-26 03:15:57 +00:00
Kayvan Sylvan
ea54f60dcc Merge pull request #1728 from ksylvan/0825-debug-logging-cleanup
Refactor Logging System to Use Centralized Debug Logger
2025-08-25 20:13:26 -07:00
Kayvan Sylvan
4008125e37 refactor: replace stderr prints with centralized debuglog.Log and improve auth messaging
- Replace fmt.Fprintf/os.Stderr with centralized debuglog.Log across CLI
- Add unconditional Log function to debuglog for important messages
- Improve OAuth flow messaging and token refresh diagnostics
- Update tests to capture debuglog output via SetOutput
- Convert Perplexity streaming errors to unified debug logging
- Emit file write notifications through debuglog instead of stderr
- Warn on ambiguous model selection using centralized logger
- Announce large audio processing steps via debuglog progress messages
- Standardize extension registry and patterns warnings through debuglog
2025-08-25 20:09:55 -07:00
github-actions[bot]
da94411bf3 chore(release): Update version to v1.4.295 2025-08-24 20:22:53 +00:00
Kayvan Sylvan
ab7b37be10 Merge pull request #1727 from ksylvan/0824-anthropic-beta-logs
Standardize Anthropic Beta Failure Logging
2025-08-24 13:20:19 -07:00
Kayvan Sylvan
772337bf0d refactor: route Anthropic beta failure logs through internal debug logger
CHANGES
- Replace fmt.Fprintf stderr with debuglog.Debug for beta failures
- Import internal log package and remove os dependency
- Standardize logging level to debuglog.Basic for beta errors
- Preserve fallback stream behavior when beta features fail
- Maintain message send fallback when beta options fail
2025-08-24 13:10:57 -07:00
github-actions[bot]
1e30c4e136 chore(release): Update version to v1.4.294 2025-08-20 16:37:50 +00:00
Kayvan Sylvan
e12a40ad4f Merge pull request #1723 from ksylvan/0820-venice-ai-provider
docs: update README with Venice AI provider and Windows install script
2025-08-20 09:35:18 -07:00
Kayvan Sylvan
97beaecbeb docs: update README with Venice AI provider and Windows install script
- Add Venice AI provider configuration with API endpoint
- Document Venice AI as privacy-first open-source provider
- Include PowerShell installation script for Windows users
- Add debug levels section to table of contents
- Update recent major features with v1.4.294 release notes
- Configure Venice AI base URL and response settings
2025-08-20 09:30:29 -07:00
19 changed files with 101 additions and 30 deletions

View File

@@ -25,6 +25,7 @@
"danielmiessler",
"davidanson",
"Debugf",
"debuglog",
"dedup",
"deepseek",
"Despina",
@@ -131,7 +132,9 @@
"seaborn",
"semgrep",
"sess",
"sgaunet",
"shellquote",
"SSEHTTP",
"storer",
"Streamlit",
"stretchr",

View File

@@ -1,5 +1,35 @@
# Changelog
## v1.4.296 (2025-08-26)
### PR [#1728](https://github.com/danielmiessler/Fabric/pull/1728) by [ksylvan](https://github.com/ksylvan): Refactor Logging System to Use Centralized Debug Logger
- Replace fmt.Fprintf/os.Stderr with centralized debuglog.Log across CLI and add unconditional Log function for important messages
- Improve OAuth flow messaging and token refresh diagnostics with better error handling
- Update tests to capture debuglog output via SetOutput for better test coverage
- Convert Perplexity streaming errors to unified debug logging and emit file write notifications through debuglog
- Standardize extension registry warnings and announce large audio processing steps via centralized logger
## v1.4.295 (2025-08-24)
### PR [#1727](https://github.com/danielmiessler/Fabric/pull/1727) by [ksylvan](https://github.com/ksylvan): Standardize Anthropic Beta Failure Logging
- Refactor: route Anthropic beta failure logs through internal debug logger
- Replace fmt.Fprintf stderr with debuglog.Debug for beta failures
- Import internal log package and remove os dependency
- Standardize logging level to debuglog.Basic for beta errors
- Preserve fallback stream behavior when beta features fail
## v1.4.294 (2025-08-20)
### PR [#1723](https://github.com/danielmiessler/Fabric/pull/1723) by [ksylvan](https://github.com/ksylvan): docs: update README with Venice AI provider and Windows install script
- Add Venice AI provider configuration with API endpoint
- Document Venice AI as privacy-first open-source provider
- Include PowerShell installation script for Windows users
- Add debug levels section to table of contents
- Update recent major features with v1.4.294 release notes
## v1.4.293 (2025-08-19)
### PR [#1718](https://github.com/danielmiessler/Fabric/pull/1718) by [ksylvan](https://github.com/ksylvan): Implement Configurable Debug Logging Levels

View File

@@ -57,6 +57,7 @@ Below are the **new features and capabilities** we've added (newest first):
### Recent Major Features
- [v1.4.294](https://github.com/danielmiessler/fabric/releases/tag/v1.4.294) (Aug 20, 2025) — **Venice AI Support**: Added the Venice AI provider. Venice is a Privacy-First, Open-Source AI provider. See their ["About Venice"](https://docs.venice.ai/overview/about-venice) page for details.
- [v1.4.291](https://github.com/danielmiessler/fabric/releases/tag/v1.4.291) (Aug 18, 2025) — **Speech To Text**: Add OpenAI speech-to-text support with `--transcribe-file`, `--transcribe-model`, and `--split-media-file` flags.
- [v1.4.287](https://github.com/danielmiessler/fabric/releases/tag/v1.4.287) (Aug 16, 2025) — **AI Reasoning**: Add Thinking to Gemini models and introduce `readme_updates` python script
- [v1.4.286](https://github.com/danielmiessler/fabric/releases/tag/v1.4.286) (Aug 14, 2025) — **AI Reasoning**: Introduce Thinking Config Across Anthropic and OpenAI Providers
@@ -139,6 +140,7 @@ Keep in mind that many of these were recorded when Fabric was Python-based, so r
- [Bash Completion](#bash-completion)
- [Fish Completion](#fish-completion)
- [Usage](#usage)
- [Debug Levels](#debug-levels)
- [Our approach to prompting](#our-approach-to-prompting)
- [Examples](#examples)
- [Just use the Patterns](#just-use-the-patterns)
@@ -209,6 +211,17 @@ To install Fabric, you can use the latest release binaries or install it from th
`https://github.com/danielmiessler/fabric/releases/latest/download/fabric-windows-amd64.exe`
Or via PowerShell, just copy and paste and run the following snippet to install the binary into `{HOME}\.local\bin`. Please make sure that directory is included in your `PATH`.
```powershell
$ErrorActionPreference = "Stop"
$LATEST="https://github.com/danielmiessler/fabric/releases/latest/download/fabric-windows-amd64.exe"
$DIR="${HOME}\.local\bin"
New-Item -Path $DIR -ItemType Directory -Force
Invoke-WebRequest -URI "${LATEST}" -outfile "${DIR}\fabric.exe"
& "${DIR}\fabric.exe" /version
```
#### macOS (arm64)
`curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-darwin-arm64 > fabric && chmod +x fabric && ./fabric --version`

View File

@@ -1,3 +1,3 @@
package main
var version = "v1.4.293"
var version = "v1.4.296"

Binary file not shown.

View File

@@ -9,6 +9,7 @@ import (
"github.com/danielmiessler/fabric/internal/core"
"github.com/danielmiessler/fabric/internal/domain"
debuglog "github.com/danielmiessler/fabric/internal/log"
"github.com/danielmiessler/fabric/internal/plugins/db/fsdb"
"github.com/danielmiessler/fabric/internal/tools/notifications"
)
@@ -135,7 +136,7 @@ func handleChatProcessing(currentFlags *Flags, registry *core.PluginRegistry, me
if chatOptions.Notification {
if err = sendNotification(chatOptions, chatReq.PatternName, result); err != nil {
// Log notification error but don't fail the main command
fmt.Fprintf(os.Stderr, "Failed to send notification: %v\n", err)
debuglog.Log("Failed to send notification: %v\n", err)
}
}

View File

@@ -3,10 +3,10 @@ package cli
import (
"encoding/json"
"fmt"
"os"
"strings"
"github.com/danielmiessler/fabric/internal/core"
debuglog "github.com/danielmiessler/fabric/internal/log"
"github.com/danielmiessler/fabric/internal/plugins/ai/openai"
"github.com/danielmiessler/fabric/internal/tools/converter"
"github.com/danielmiessler/fabric/internal/tools/youtube"
@@ -34,7 +34,7 @@ func Cli(version string) (err error) {
var registry, err2 = initializeFabric()
if err2 != nil {
if !currentFlags.Setup {
fmt.Fprintln(os.Stderr, err2.Error())
debuglog.Log("%s\n", err2.Error())
currentFlags.Setup = true
}
// Return early if registry is nil to prevent panics in subsequent handlers

View File

@@ -7,6 +7,7 @@ import (
"strings"
"github.com/atotto/clipboard"
debuglog "github.com/danielmiessler/fabric/internal/log"
)
func CopyToClipboard(message string) (err error) {
@@ -30,7 +31,7 @@ func CreateOutputFile(message string, fileName string) (err error) {
if _, err = file.WriteString(message); err != nil {
err = fmt.Errorf("error writing to file: %v", err)
} else {
fmt.Fprintf(os.Stderr, "\n\n[Output also written to %s]\n", fileName)
debuglog.Log("\n\n[Output also written to %s]\n", fileName)
}
return
}

View File

@@ -10,6 +10,7 @@ import (
"strconv"
"strings"
debuglog "github.com/danielmiessler/fabric/internal/log"
"github.com/danielmiessler/fabric/internal/plugins/ai/anthropic"
"github.com/danielmiessler/fabric/internal/plugins/ai/azure"
"github.com/danielmiessler/fabric/internal/plugins/ai/bedrock"
@@ -20,7 +21,7 @@ import (
"github.com/danielmiessler/fabric/internal/plugins/ai/ollama"
"github.com/danielmiessler/fabric/internal/plugins/ai/openai"
"github.com/danielmiessler/fabric/internal/plugins/ai/openai_compatible"
"github.com/danielmiessler/fabric/internal/plugins/ai/perplexity" // Added Perplexity plugin
"github.com/danielmiessler/fabric/internal/plugins/ai/perplexity"
"github.com/danielmiessler/fabric/internal/plugins/strategy"
"github.com/samber/lo"
@@ -339,7 +340,7 @@ func (o *PluginRegistry) GetChatter(model string, modelContextLength int, vendor
} else {
availableVendors := models.FindGroupsByItem(model)
if len(availableVendors) > 1 {
fmt.Fprintf(os.Stderr, "Warning: multiple vendors provide model %s: %s. Using %s. Specify --vendor to select a vendor.\n", model, strings.Join(availableVendors, ", "), availableVendors[0])
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))
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/danielmiessler/fabric/internal/chat"
"github.com/danielmiessler/fabric/internal/domain"
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/db/fsdb"
@@ -72,7 +73,12 @@ func TestGetChatter_WarnsOnAmbiguousModel(t *testing.T) {
r, w, _ := os.Pipe()
oldStderr := os.Stderr
os.Stderr = w
defer func() { os.Stderr = oldStderr }()
// Redirect log output to our pipe to capture unconditional log messages
debuglog.SetOutput(w)
defer func() {
os.Stderr = oldStderr
debuglog.SetOutput(oldStderr)
}()
chatter, err := registry.GetChatter("shared-model", 0, "", "", false, false)
w.Close()

View File

@@ -61,6 +61,15 @@ func Debug(l Level, format string, a ...interface{}) {
}
}
// Log writes a message unconditionally to stderr.
// This is for important messages that should always be shown regardless of debug level.
func Log(format string, a ...interface{}) {
mu.RLock()
w := output
mu.RUnlock()
fmt.Fprintf(w, format, a...)
}
// SetOutput allows overriding the output destination for debug logs.
func SetOutput(w io.Writer) {
mu.Lock()

View File

@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"net/http"
"os"
"strconv"
"strings"
@@ -12,6 +11,7 @@ import (
"github.com/anthropics/anthropic-sdk-go/option"
"github.com/danielmiessler/fabric/internal/chat"
"github.com/danielmiessler/fabric/internal/domain"
debuglog "github.com/danielmiessler/fabric/internal/log"
"github.com/danielmiessler/fabric/internal/plugins"
"github.com/danielmiessler/fabric/internal/util"
)
@@ -195,7 +195,7 @@ func (an *Client) SendStream(
}
stream := an.client.Messages.NewStreaming(ctx, params, reqOpts...)
if stream.Err() != nil && len(betas) > 0 {
fmt.Fprintf(os.Stderr, "Anthropic beta feature %s failed: %v\n", strings.Join(betas, ","), stream.Err())
debuglog.Debug(debuglog.Basic, "Anthropic beta feature %s failed: %v\n", strings.Join(betas, ","), stream.Err())
stream = an.client.Messages.NewStreaming(ctx, params)
}
@@ -289,7 +289,7 @@ func (an *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage,
}
if message, err = an.client.Messages.New(ctx, params, reqOpts...); err != nil {
if len(betas) > 0 {
fmt.Fprintf(os.Stderr, "Anthropic beta feature %s failed: %v\n", strings.Join(betas, ","), err)
debuglog.Debug(debuglog.Basic, "Anthropic beta feature %s failed: %v\n", strings.Join(betas, ","), err)
if message, err = an.client.Messages.New(ctx, params); err != nil {
return
}

View File

@@ -9,11 +9,11 @@ import (
"fmt"
"io"
"net/http"
"os"
"os/exec"
"strings"
"time"
debuglog "github.com/danielmiessler/fabric/internal/log"
"github.com/danielmiessler/fabric/internal/util"
"golang.org/x/oauth2"
)
@@ -77,7 +77,7 @@ func (t *OAuthTransport) getValidToken(tokenIdentifier string) (string, error) {
}
// If no token exists, run OAuth flow
if token == nil {
fmt.Fprintln(os.Stderr, "No OAuth token found, initiating authentication...")
debuglog.Log("No OAuth token found, initiating authentication...\n")
newAccessToken, err := RunOAuthFlow(tokenIdentifier)
if err != nil {
return "", fmt.Errorf("failed to authenticate: %w", err)
@@ -87,11 +87,11 @@ func (t *OAuthTransport) getValidToken(tokenIdentifier string) (string, error) {
// Check if token needs refresh (5 minute buffer)
if token.IsExpired(5) {
fmt.Fprintln(os.Stderr, "OAuth token expired, refreshing...")
debuglog.Log("OAuth token expired, refreshing...\n")
newAccessToken, err := RefreshToken(tokenIdentifier)
if err != nil {
// If refresh fails, try re-authentication
fmt.Fprintln(os.Stderr, "Token refresh failed, re-authenticating...")
debuglog.Log("Token refresh failed, re-authenticating...\n")
newAccessToken, err = RunOAuthFlow(tokenIdentifier)
if err != nil {
return "", fmt.Errorf("failed to refresh or re-authenticate: %w", err)
@@ -143,13 +143,13 @@ func RunOAuthFlow(tokenIdentifier string) (token string, err error) {
if err == nil && existingToken != nil {
// If token exists but is expired, try refreshing first
if existingToken.IsExpired(5) {
fmt.Fprintln(os.Stderr, "Found expired OAuth token, attempting refresh...")
debuglog.Log("Found expired OAuth token, attempting refresh...\n")
refreshedToken, refreshErr := RefreshToken(tokenIdentifier)
if refreshErr == nil {
fmt.Fprintln(os.Stderr, "Token refresh successful")
debuglog.Log("Token refresh successful\n")
return refreshedToken, nil
}
fmt.Fprintf(os.Stderr, "Token refresh failed (%v), proceeding with full OAuth flow...\n", refreshErr)
debuglog.Log("Token refresh failed (%v), proceeding with full OAuth flow...\n", refreshErr)
} else {
// Token exists and is still valid
return existingToken.AccessToken, nil
@@ -176,10 +176,10 @@ func RunOAuthFlow(tokenIdentifier string) (token string, err error) {
oauth2.SetAuthURLParam("state", verifier),
)
fmt.Fprintln(os.Stderr, "Open the following URL in your browser. Fabric would like to authorize:")
fmt.Fprintln(os.Stderr, authURL)
debuglog.Log("Open the following URL in your browser. Fabric would like to authorize:\n")
debuglog.Log("%s\n", authURL)
openBrowser(authURL)
fmt.Fprint(os.Stderr, "Paste the authorization code here: ")
debuglog.Log("Paste the authorization code here: ")
var code string
fmt.Scanln(&code)
parts := strings.SplitN(code, "#", 2)

View File

@@ -64,7 +64,7 @@ func (o *Client) TranscribeFile(ctx context.Context, filePath, model string, spl
if !split {
return "", fmt.Errorf("file %s exceeds 25MB limit; use --split-media-file to enable automatic splitting", filePath)
}
debuglog.Debug(debuglog.Basic, "File %s is larger than the size limit... breaking it up into chunks...\n", filePath)
debuglog.Log("File %s is larger than the size limit... breaking it up into chunks...\n", filePath)
if files, cleanup, err = splitAudioFile(filePath, ext, MaxAudioFileSize); err != nil {
return "", err
}
@@ -75,7 +75,7 @@ func (o *Client) TranscribeFile(ctx context.Context, filePath, model string, spl
var builder strings.Builder
for i, f := range files {
debuglog.Debug(debuglog.Basic, "Using model %s to transcribe part %d (file name: %s)...\n", model, i+1, f)
debuglog.Log("Using model %s to transcribe part %d (file name: %s)...\n", model, i+1, f)
var chunk *os.File
if chunk, err = os.Open(f); err != nil {
return "", err
@@ -115,7 +115,7 @@ func splitAudioFile(src, ext string, maxSize int64) (files []string, cleanup fun
segmentTime := 600 // start with 10 minutes
for {
pattern := filepath.Join(dir, "chunk-%03d"+ext)
debuglog.Debug(debuglog.Basic, "Running ffmpeg to split audio into %d-second chunks...\n", segmentTime)
debuglog.Log("Running ffmpeg to split audio into %d-second chunks...\n", segmentTime)
cmd := exec.Command("ffmpeg", "-y", "-i", src, "-f", "segment", "-segment_time", fmt.Sprintf("%d", segmentTime), "-c", "copy", pattern)
var stderr bytes.Buffer
cmd.Stderr = &stderr

View File

@@ -102,6 +102,11 @@ var ProviderMap = map[string]ProviderConfig{
BaseURL: "https://api.together.xyz/v1",
ImplementsResponses: false,
},
"Venice AI": {
Name: "Venice AI",
BaseURL: "https://api.venice.ai/api/v1",
ImplementsResponses: false,
},
}
// GetProviderByName returns the provider configuration for a given name with O(1) lookup

View File

@@ -4,9 +4,10 @@ import (
"context"
"fmt"
"os"
"sync" // Added sync package
"sync"
"github.com/danielmiessler/fabric/internal/domain"
debuglog "github.com/danielmiessler/fabric/internal/log"
"github.com/danielmiessler/fabric/internal/plugins"
perplexity "github.com/sgaunet/perplexity-go/v2"
@@ -171,7 +172,7 @@ func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
if err != nil {
// Log error, can't send to string channel directly.
// Consider a mechanism to propagate this error if needed.
fmt.Fprintf(os.Stderr, "perplexity streaming error: %v\\n", err) // Corrected capitalization
debuglog.Log("perplexity streaming error: %v\n", err)
// If the error occurs during stream setup, the channel might not have been closed by the receiver loop.
// However, closing it here might cause a panic if the receiver loop also tries to close it.
// close(channel) // Caution: Uncommenting this may cause panic, as channel is closed in the receiver goroutine.

View File

@@ -88,7 +88,7 @@ func NewExtensionRegistry(configDir string) *ExtensionRegistry {
r.ensureConfigDir()
if err := r.loadRegistry(); err != nil {
debuglog.Debug(debuglog.Basic, "Warning: could not load extension registry: %v\n", err)
debuglog.Log("Warning: could not load extension registry: %v\n", err)
}
return r

View File

@@ -7,6 +7,7 @@ import (
"sort"
"strings"
debuglog "github.com/danielmiessler/fabric/internal/log"
"github.com/danielmiessler/fabric/internal/plugins"
"github.com/danielmiessler/fabric/internal/plugins/db/fsdb"
"github.com/danielmiessler/fabric/internal/tools/githelper"
@@ -335,9 +336,9 @@ func (o *PatternsLoader) createUniquePatternsFile() (err error) {
patternNamesMap[entry.Name()] = true
}
}
fmt.Fprintf(os.Stderr, "📂 Also included patterns from custom directory: %s\n", o.Patterns.CustomPatternsDir)
debuglog.Log("📂 Also included patterns from custom directory: %s\n", o.Patterns.CustomPatternsDir)
} else {
fmt.Fprintf(os.Stderr, "Warning: Could not read custom patterns directory %s: %v\n", o.Patterns.CustomPatternsDir, customErr)
debuglog.Log("Warning: Could not read custom patterns directory %s: %v\n", o.Patterns.CustomPatternsDir, customErr)
}
}

View File

@@ -1 +1 @@
"1.4.293"
"1.4.296"