Compare commits

..

10 Commits

Author SHA1 Message Date
github-actions[bot]
74250bbcbd chore(release): Update version to v1.4.377 2026-01-12 17:39:02 +00:00
Kayvan Sylvan
a593e83d9f Merge pull request #1929 from ksylvan/kayvan/add-mammouth-ai-provider
Add Mammouth as new OpenAI-compatible AI provider
2026-01-12 09:36:05 -08:00
Kayvan Sylvan
62e6812c7f chore: incoming 1929 changelog entry 2026-01-12 09:33:38 -08:00
Kayvan Sylvan
7e7ab9e5f2 feat: add Mammouth as new OpenAI-compatible AI provider
## CHANGES

- Add Mammouth provider configuration with API base URL
- Configure Mammouth to use standard OpenAI-compatible interface
- Disable Responses API implementation for Mammouth provider
- Add "Mammouth" to VSCode spell check dictionary
2026-01-12 09:27:28 -08:00
github-actions[bot]
df2938a7ee chore(release): Update version to v1.4.376 2026-01-12 05:22:38 +00:00
Kayvan Sylvan
014985c407 Merge pull request #1928 from ksylvan/kayvan/refactor-new-plugin-base
Eliminate repetitive boilerplate across eight vendor implementations
2026-01-11 21:20:07 -08:00
Kayvan Sylvan
a3d9bec537 chore: incoming 1928 changelog entry 2026-01-11 21:18:06 -08:00
Kayvan Sylvan
febae215f3 chore: exempt json files from VSCode format-on-save 2026-01-11 20:56:13 -08:00
Kayvan Sylvan
cf55be784f refactor: add NewVendorPluginBase factory function to reduce duplication
Add centralized factory function for AI vendor plugin initialization:
- Add NewVendorPluginBase(name, configure) to internal/plugins/plugin.go
- Update 8 vendor files (anthropic, bedrock, gemini, lmstudio, ollama,
  openai, perplexity, vertexai) to use the factory function
- Add 3 test cases for the new factory function

This removes ~40 lines of duplicated boilerplate code and ensures
consistent plugin initialization across all vendors.

MAESTRO: Loop 00001 refactoring implementation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 20:12:58 -08:00
Daniel Miessler
6d2180e69a docs: Add GitHub sponsor section to README
I spend hundreds of hours a year on open source. If you'd like to help support this project, you can sponsor me here.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 12:40:13 -08:00
17 changed files with 105 additions and 48 deletions

View File

@@ -117,6 +117,7 @@
"listvendors",
"lmstudio",
"Makefiles",
"Mammouth",
"markmap",
"matplotlib",
"mattn",
@@ -247,5 +248,8 @@
]
},
"MD041": false
},
"[json]": {
"editor.formatOnSave": false
}
}

View File

@@ -1,5 +1,31 @@
# Changelog
## v1.4.377 (2026-01-12)
### PR [#1929](https://github.com/danielmiessler/Fabric/pull/1929) by [ksylvan](https://github.com/ksylvan): Add Mammouth as new OpenAI-compatible AI provider
- Feat: add Mammouth as new OpenAI-compatible AI provider
- Add Mammouth provider configuration with API base URL
- Configure Mammouth to use standard OpenAI-compatible interface
- Disable Responses API implementation for Mammouth provider
- Add "Mammouth" to VSCode spell check dictionary
## v1.4.376 (2026-01-12)
### PR [#1928](https://github.com/danielmiessler/Fabric/pull/1928) by [ksylvan](https://github.com/ksylvan): Eliminate repetitive boilerplate across eight vendor implementations
- Refactor: add NewVendorPluginBase factory function to reduce duplication
- Update 8 vendor files (anthropic, bedrock, gemini, lmstudio, ollama, openai, perplexity, vertexai) to use the factory function
- Add 3 test cases for the new factory function
- Add centralized factory function for AI vendor plugin initialization
- Chore: exempt json files from VSCode format-on-save
### Direct commits
- Docs: Add GitHub sponsor section to README
I spend hundreds of hours a year on open source. If you'd like to help support this project, you can sponsor me here.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
## v1.4.375 (2026-01-08)
### PR [#1925](https://github.com/danielmiessler/Fabric/pull/1925) by [ksylvan](https://github.com/ksylvan): docs: update README to document new AI providers and features

View File

@@ -1091,3 +1091,13 @@ Made with [contrib.rocks](https://contrib.rocks).
`fabric` was created by <a href="https://danielmiessler.com/subscribe" target="_blank">Daniel Miessler</a> in January of 2024.
<br /><br />
<a href="https://twitter.com/intent/user?screen_name=danielmiessler">![X (formerly Twitter) Follow](https://img.shields.io/twitter/follow/danielmiessler)</a>
## 💜 Support This Project
<div align="center">
<img src="https://img.shields.io/badge/Sponsor-❤️-EA4AAA?style=for-the-badge&logo=github-sponsors&logoColor=white" alt="Sponsor">
**I spend hundreds of hours a year on open source. If you'd like to help support this project, you can sponsor me [here](https://github.com/sponsors/danielmiessler). 🙏🏼**
</div>

View File

@@ -1,3 +1,3 @@
package main
var version = "v1.4.375"
var version = "v1.4.377"

Binary file not shown.

View File

@@ -29,11 +29,7 @@ func NewClient() (ret *Client) {
vendorName := "Anthropic"
ret = &Client{}
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
ConfigureCustom: ret.configure,
}
ret.PluginBase = plugins.NewVendorPluginBase(vendorName, ret.configure)
ret.ApiBaseURL = ret.AddSetupQuestion("API Base URL", false)
ret.ApiBaseURL.Value = defaultBaseUrl

View File

@@ -51,13 +51,9 @@ func NewClient() (ret *BedrockClient) {
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
// Create a minimal client that will fail gracefully during configuration
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
ConfigureCustom: func() error {
return fmt.Errorf("unable to load AWS Config: %w", err)
},
}
ret.PluginBase = plugins.NewVendorPluginBase(vendorName, func() error {
return fmt.Errorf("unable to load AWS Config: %w", err)
})
ret.bedrockRegion = ret.PluginBase.AddSetupQuestion("AWS Region", true)
return
}
@@ -67,11 +63,7 @@ func NewClient() (ret *BedrockClient) {
runtimeClient := bedrockruntime.NewFromConfig(cfg)
controlPlaneClient := bedrock.NewFromConfig(cfg)
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
ConfigureCustom: ret.configure,
}
ret.PluginBase = plugins.NewVendorPluginBase(vendorName, ret.configure)
ret.runtimeClient = runtimeClient
ret.controlPlaneClient = controlPlaneClient

View File

@@ -46,10 +46,7 @@ func NewClient() (ret *Client) {
vendorName := "Gemini"
ret = &Client{}
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
}
ret.PluginBase = plugins.NewVendorPluginBase(vendorName, nil)
ret.ApiKey = ret.PluginBase.AddSetupQuestion("API key", true)

View File

@@ -27,11 +27,7 @@ func NewClientCompatible(vendorName string, defaultBaseUrl string, configureCust
if configureCustom == nil {
configureCustom = ret.configure
}
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
ConfigureCustom: configureCustom,
}
ret.PluginBase = plugins.NewVendorPluginBase(vendorName, configureCustom)
ret.ApiUrl = ret.AddSetupQuestionCustom("API URL", true,
fmt.Sprintf("Enter your %v URL (as a reminder, it is usually %v')", vendorName, defaultBaseUrl))
return

View File

@@ -24,11 +24,7 @@ func NewClient() (ret *Client) {
vendorName := "Ollama"
ret = &Client{}
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
ConfigureCustom: ret.configure,
}
ret.PluginBase = plugins.NewVendorPluginBase(vendorName, ret.configure)
ret.ApiUrl = ret.AddSetupQuestionCustom("API URL", true,
"Enter your Ollama URL (as a reminder, it is usually http://localhost:11434')")

View File

@@ -52,11 +52,7 @@ func NewClientCompatibleNoSetupQuestions(vendorName string, configureCustom func
configureCustom = ret.configure
}
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
ConfigureCustom: configureCustom,
}
ret.PluginBase = plugins.NewVendorPluginBase(vendorName, configureCustom)
return
}

View File

@@ -206,6 +206,11 @@ var ProviderMap = map[string]ProviderConfig{
ModelsURL: "static:abacus", // Special marker for static model list
ImplementsResponses: false,
},
"Mammouth": {
Name: "Mammouth",
BaseURL: "https://api.mammouth.ai/v1",
ImplementsResponses: false,
},
}
// GetProviderByName returns the provider configuration for a given name with O(1) lookup

View File

@@ -31,11 +31,7 @@ type Client struct {
func NewClient() *Client {
c := &Client{}
c.PluginBase = &plugins.PluginBase{
Name: providerName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(providerName),
ConfigureCustom: c.Configure, // Assign the Configure method
}
c.PluginBase = plugins.NewVendorPluginBase(providerName, c.Configure)
c.APIKey = c.AddSetupQuestion("API_KEY", true)
return c
}

View File

@@ -28,11 +28,7 @@ func NewClient() (ret *Client) {
vendorName := "VertexAI"
ret = &Client{}
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
ConfigureCustom: ret.configure,
}
ret.PluginBase = plugins.NewVendorPluginBase(vendorName, ret.configure)
ret.ProjectID = ret.AddSetupQuestion("Project ID", true)
ret.Region = ret.AddSetupQuestion("Region", false)

View File

@@ -36,6 +36,16 @@ func (o *PluginBase) GetName() string {
return o.Name
}
// NewVendorPluginBase creates a standardized PluginBase for AI vendor plugins.
// This centralizes the common initialization pattern used by all vendors.
func NewVendorPluginBase(name string, configure func() error) *PluginBase {
return &PluginBase{
Name: name,
EnvNamePrefix: BuildEnvVariablePrefix(name),
ConfigureCustom: configure,
}
}
func (o *PluginBase) GetSetupDescription() (ret string) {
if ret = o.SetupDescription; ret == "" {
ret = o.GetName()

View File

@@ -8,6 +8,43 @@ import (
"github.com/stretchr/testify/assert"
)
func TestNewVendorPluginBase(t *testing.T) {
// Test with configure function
configureCalled := false
configureFunc := func() error {
configureCalled = true
return nil
}
plugin := NewVendorPluginBase("TestVendor", configureFunc)
assert.Equal(t, "TestVendor", plugin.Name)
assert.Equal(t, "TESTVENDOR_", plugin.EnvNamePrefix)
assert.NotNil(t, plugin.ConfigureCustom)
// Test that configure function is properly stored
err := plugin.ConfigureCustom()
assert.NoError(t, err)
assert.True(t, configureCalled)
}
func TestNewVendorPluginBase_NilConfigure(t *testing.T) {
// Test with nil configure function
plugin := NewVendorPluginBase("TestVendor", nil)
assert.Equal(t, "TestVendor", plugin.Name)
assert.Equal(t, "TESTVENDOR_", plugin.EnvNamePrefix)
assert.Nil(t, plugin.ConfigureCustom)
}
func TestNewVendorPluginBase_EnvPrefixWithSpaces(t *testing.T) {
// Test that spaces are converted to underscores
plugin := NewVendorPluginBase("LM Studio", nil)
assert.Equal(t, "LM Studio", plugin.Name)
assert.Equal(t, "LM_STUDIO_", plugin.EnvNamePrefix)
}
func TestConfigurable_AddSetting(t *testing.T) {
conf := &PluginBase{
Settings: Settings{},

View File

@@ -1 +1 @@
"1.4.375"
"1.4.377"