- Default provider changed from Zhipu to Agnes AI (agnes-2.0-flash) - Add --provider flag: agnes (default), zhipu, custom - Add AGNES_API_KEY env var support (still compatible with ZHIPU_API_KEY) - Add provider presets system for easy switching between AI backends - Add .gitignore to prevent config.json with API keys from being committed - Update usage examples Co-Authored-By: Claude <noreply@anthropic.com>
125 lines
3.4 KiB
Go
125 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
func main() {
|
|
// Command-line flags
|
|
var (
|
|
apiKey string
|
|
configPath string
|
|
prompt string
|
|
quiet bool
|
|
noThink bool
|
|
saveConfig bool
|
|
)
|
|
|
|
flag.StringVar(&apiKey, "apikey", "", "API key (or set AGNES_API_KEY env var)")
|
|
flag.StringVar(&configPath, "config", "", "Path to config.json file")
|
|
flag.StringVar(&prompt, "prompt", "", "Custom prompt for image analysis")
|
|
flag.BoolVar(&quiet, "q", false, "Quiet mode — only output the model's response text")
|
|
flag.BoolVar(&noThink, "no-think", false, "Disable thinking mode (faster, lower server load)")
|
|
flag.BoolVar(&saveConfig, "save-config", false, "Save the API key to config.json and exit")
|
|
provider := flag.String("provider", "agnes", "AI provider: agnes (default), zhipu, or custom")
|
|
flag.Parse()
|
|
|
|
// Handle --save-config: persist the key and exit
|
|
if saveConfig {
|
|
if apiKey == "" {
|
|
fmt.Fprintln(os.Stderr, "Error: --apikey is required with --save-config")
|
|
os.Exit(1)
|
|
}
|
|
if configPath == "" {
|
|
configPath = filepath.Join(getToolsDir(), "config.json")
|
|
}
|
|
if err := SaveConfigFile(configPath, apiKey); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Failed to save config: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Printf("API key saved to %s\n", configPath)
|
|
return
|
|
}
|
|
|
|
// Load configuration
|
|
cfg, err := LoadConfig(apiKey, configPath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Apply provider preset (respects user's custom model/baseURL in config)
|
|
cfg.ApplyProvider(*provider)
|
|
|
|
if !quiet {
|
|
fmt.Fprintf(os.Stderr, "Provider: %s | Model: %s | API key: %s\n",
|
|
cfg.Provider, cfg.Model, MaskKey(cfg.APIKey))
|
|
}
|
|
|
|
// Collect image paths from remaining args
|
|
imagePaths := flag.Args()
|
|
if len(imagePaths) == 0 {
|
|
fmt.Fprintln(os.Stderr, "Usage: vision-tool [flags] <image-file> [image-file2 ...]")
|
|
fmt.Fprintln(os.Stderr, "")
|
|
fmt.Fprintln(os.Stderr, "Flags:")
|
|
flag.PrintDefaults()
|
|
fmt.Fprintln(os.Stderr, "")
|
|
fmt.Fprintln(os.Stderr, "Examples:")
|
|
fmt.Fprintln(os.Stderr, " vision-tool photo.jpg")
|
|
fmt.Fprintln(os.Stderr, ` vision-tool -prompt "这张图里有什么文字?" screenshot.png`)
|
|
fmt.Fprintln(os.Stderr, " vision-tool -q img1.png img2.png")
|
|
fmt.Fprintln(os.Stderr, " vision-tool --provider zhipu photo.jpg")
|
|
fmt.Fprintln(os.Stderr, " vision-tool --apikey YOUR_KEY photo.jpg")
|
|
fmt.Fprintln(os.Stderr, " vision-tool --save-config --apikey YOUR_KEY")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Validate image files exist
|
|
for _, p := range imagePaths {
|
|
if _, err := os.Stat(p); os.IsNotExist(err) {
|
|
fmt.Fprintf(os.Stderr, "Error: file not found: %s\n", p)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
client := NewVisionClient(cfg, noThink)
|
|
|
|
if !quiet {
|
|
fmt.Fprintf(os.Stderr, "Analyzing %d image(s)...\n\n", len(imagePaths))
|
|
}
|
|
|
|
// Call the API
|
|
var resp *ChatResponse
|
|
if len(imagePaths) == 1 {
|
|
resp, err = client.AnalyzeImage(imagePaths[0], prompt)
|
|
} else {
|
|
resp, err = client.AnalyzeImages(imagePaths, prompt)
|
|
}
|
|
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Output the result
|
|
if len(resp.Choices) > 0 {
|
|
fmt.Println(resp.Choices[0].Message.Content)
|
|
}
|
|
|
|
if resp.Usage != nil && !quiet {
|
|
fmt.Fprintf(os.Stderr, "\n---\nTokens: prompt=%d completion=%d total=%d\n",
|
|
resp.Usage.PromptTokens, resp.Usage.CompletionTokens, resp.Usage.TotalTokens)
|
|
}
|
|
}
|
|
|
|
func getToolsDir() string {
|
|
exe, err := os.Executable()
|
|
if err != nil {
|
|
return "."
|
|
}
|
|
return filepath.Dir(exe)
|
|
}
|