split out claude package, report weight breakdowns, add slog tracing

This commit is contained in:
Ian Gulliver
2026-05-31 14:33:26 -07:00
parent cb6b427990
commit e22d512be7
3 changed files with 415 additions and 332 deletions
+18 -3
View File
@@ -11,7 +11,7 @@ import (
"encoding/json"
"errors"
"fmt"
"log"
"log/slog"
"os"
"path/filepath"
"strconv"
@@ -21,6 +21,14 @@ import (
"github.com/chromedp/chromedp"
)
// trace logs the start of an operation and, via the returned func, its end and
// duration. It is a no-op above debug level, so it only appears with -v.
func trace(op string) func() {
t0 := time.Now()
slog.Debug("begin", "op", op)
return func() { slog.Debug("end", "op", op, "dur", time.Since(t0)) }
}
const (
baseURL = "https://3dfilamentprofiles.com"
challengeText = "Security Checkpoint"
@@ -79,6 +87,7 @@ func New(opts ...Option) (*Client, error) {
// Allocate the browser bound to the long-lived browserCtx. chromedp ties the
// Chrome process lifetime to the context of the first Run, so this must not
// be a short-lived per-call context (otherwise the browser dies after it).
defer trace("spooldb.launchBrowser")()
if err := chromedp.Run(browserCtx, chromedp.Navigate("about:blank")); err != nil {
browserCancel()
allocCancel()
@@ -98,7 +107,7 @@ func chromeErrorf(format string, args ...any) {
if strings.HasPrefix(format, "unhandled ") {
return
}
log.Printf("chromedp: "+format, args...)
slog.Warn("chromedp: " + fmt.Sprintf(format, args...))
}
// Close shuts down the browser.
@@ -121,13 +130,15 @@ func (c *Client) run(ctx context.Context, timeout time.Duration, actions ...chro
// awaitChallenge waits until the Vercel checkpoint clears (the headless browser
// solves it automatically by running the page's JavaScript).
func (c *Client) awaitChallenge(ctx context.Context) error {
defer trace("spooldb.awaitChallenge")()
deadline := time.Now().Add(45 * time.Second)
for time.Now().Before(deadline) {
for polls := 0; time.Now().Before(deadline); polls++ {
var title string
if err := c.run(ctx, 10*time.Second, chromedp.Title(&title)); err != nil {
return err
}
if !strings.Contains(title, challengeText) {
slog.Debug("challenge cleared", "polls", polls)
return nil
}
select {
@@ -141,6 +152,7 @@ func (c *Client) awaitChallenge(ctx context.Context) error {
// Login authenticates with email/password. Safe to call once per Client.
func (c *Client) Login(ctx context.Context, email, password string) error {
defer trace("spooldb.login")()
if err := c.run(ctx, 45*time.Second, chromedp.Navigate(baseURL+"/login")); err != nil {
return fmt.Errorf("navigate to login: %w", err)
}
@@ -188,6 +200,7 @@ type SpoolInfo struct {
// direct URL for the dialog; it is opened from the spool page by the pencil
// button, which sits between the QR-code and delete buttons in the card header.
func (c *Client) openEdit(ctx context.Context, spoolID string) error {
defer trace("spooldb.openEdit")()
if !c.loggedIn {
return errors.New("not logged in")
}
@@ -232,6 +245,7 @@ func (c *Client) openEdit(ctx context.Context, spoolID string) error {
// SpoolInfo opens a spool's edit dialog and reads its location and weights.
func (c *Client) SpoolInfo(ctx context.Context, spoolID string) (SpoolInfo, error) {
defer trace("spooldb.spoolInfo")()
if err := c.openEdit(ctx, spoolID); err != nil {
return SpoolInfo{}, err
}
@@ -265,6 +279,7 @@ func (c *Client) SpoolInfo(ctx context.Context, spoolID string) (SpoolInfo, erro
// — the site recomputes remaining filament from the empty-spool weight — and
// saves. grams is the weight read off the scale (filament plus spool).
func (c *Client) SetTotalWeight(ctx context.Context, spoolID string, grams float64) error {
defer trace("spooldb.setTotalWeight")()
if err := c.openEdit(ctx, spoolID); err != nil {
return err
}