package analyze import ( "bytes" "context" "fmt" "os/exec" "strings" "github.com/ytshih/stonks/internal/config" ) func init() { Register("subscription", newSubscriptionAnalyzer) } // subscriptionAnalyzer shells out to the `claude` CLI in headless/print mode // (`claude -p`), using whatever session `claude login` established on this // host. Usage is billed against that Claude Pro/Max subscription's // rate limits rather than a separate per-token API bill. // // The CLI has no separate "system prompt" flag documented for scripting, so // the system and user prompts are concatenated and piped in over stdin — // the most robust interface regardless of CLI version. type subscriptionAnalyzer struct{} func newSubscriptionAnalyzer(cfg config.AnalyzerConfig) (Analyzer, error) { if _, err := exec.LookPath("claude"); err != nil { return nil, fmt.Errorf("analyze (subscription mode): `claude` CLI not found on PATH — install Claude Code and run `claude login`: %w", err) } return &subscriptionAnalyzer{}, nil } func (a *subscriptionAnalyzer) Analyze(ctx context.Context, prompt string) (string, error) { fullPrompt := SystemPrompt + "\n\n---\n\n" + prompt cmd := exec.CommandContext(ctx, "claude", "-p") cmd.Stdin = strings.NewReader(fullPrompt) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr if err := cmd.Run(); err != nil { return "", fmt.Errorf("analyze (subscription mode): claude CLI failed: %w (stderr: %s)", err, strings.TrimSpace(stderr.String())) } out := strings.TrimSpace(stdout.String()) if out == "" { return "", fmt.Errorf("analyze (subscription mode): claude CLI produced no output (stderr: %s)", strings.TrimSpace(stderr.String())) } return out, nil }