# FrankenClaw Changelog

## 0.3.3 — 2026-05-28

Security: the Shopify Admin access token is no longer written world-readable.

- **Problem:** `tools/shopify.py` cached the OAuth access token to
  `/tmp/frankenclaw-shopify-token-<entry>.json` with default permissions.
  Under a typical `umask 0002` that file lands `0664` in a world-readable
  `/tmp`, so any local user could read a live Shopify Admin token (full
  read/write on the store). `tools/gdrive.py` already wrote its token
  `0o600`; shopify was the inconsistent outlier.
- **Fix:** `_save_token()` now writes via a temp file, `chmod 0o600`, then
  atomic `os.replace()` — matching the gdrive token-persist pattern. No
  window where the token is world-readable.

## 0.3.2 — 2026-05-23

Fix a real installer/runtime mismatch that left fresh installs unable to
find their keys. The `robot-install.sh` installer (and the public
`robot.install` / `robot.info` / `robot.install.md` docs) all said keys
live at `~/.frankenclaw/keys.json`. The runtime — `config.py` line 19 —
defaulted to `~/.rockys-switch/keys.json`. A user running
`./robot-install.sh` blind ended up with their keys in a place the tools
never read unless they also set `FRANKENCLAW_KEYS_PATH` by hand.

- **`config.py`**: default keys path moved to `~/.frankenclaw/keys.json`,
  matching the installer + the documented default. Resolution
  precedence: `FRANKENCLAW_KEYS_PATH` env → new default → legacy
  `~/.rockys-switch/keys.json` (with a one-shot stderr deprecation
  warning) → FileNotFoundError pointing at the new default.
- **Legacy users keep working.** Anyone whose keys still live at
  `~/.rockys-switch/keys.json` sees a warning on first call but the file
  is still read. Move the file (or set the env var) to silence.
- **`load_keys()` and `get_key()`** now re-resolve the path on each call
  rather than freezing at import time, so a keys file that appears after
  the process starts is picked up without restart. `KEYS_PATH` is kept as
  a module-level name for back-compat with any external importer.
- **Docstring + error messages** in `config.py`, `tools/web_scrape.py`,
  `tools/shopify.py`, and `README.md` updated to reference the new
  default.

Verified with five local tests covering env override, new-path-present,
legacy fallback with warning, both-missing error path, and
warning-emits-once-per-process semantics.

## 0.3.1 — 2026-05-04

Per-call store routing for the Shopify tools. The 0.3.0 generalization
made the keys-file entry name configurable via `FRANKENCLAW_SHOPIFY_KEY`,
but it was still process-wide — one Hermes/MCP session could only target
one store. This release adds an optional `store` argument to all three
shopify tools so a single agent session can talk to multiple stores
without restart or env-var swaps.

- **`shopify_products`, `shopify_inventory`, `shopify_collections`** now
  accept `store: str = ""` as the last argument. Pass the keys.json entry
  name (e.g. `store="shopify_secondary"`) to target that store for this
  call only. Empty string keeps the prior behavior — falls back to
  `FRANKENCLAW_SHOPIFY_KEY` env, then to the literal entry name `"shopify"`.
- **Per-store token cache.** Was a single `/tmp/frankenclaw-shopify-token.json`
  that two-store usage would oscillate over and re-fetch on every flip.
  Now `/tmp/frankenclaw-shopify-token-<entry>.json` so multiple stores'
  access tokens coexist without invalidating each other.
- **No breaking changes.** Existing single-store deployments behave
  identically — `store=""` (the default) reads the env var as before.

## 0.3.0 — 2026-05-04

Public-release scrub. FrankenClaw was developed inside Project Sparks
against a specific Shopify customer; the source carried that history in
docstrings, defaults, and narrative. This release strips all of it so a
fresh user gets a stock/default toolbox they can configure for their own
stack.

### Generalized

- **`tools/shopify.py`** is now store-agnostic. The keys.json entry name
  is read from `FRANKENCLAW_SHOPIFY_KEY` (default: `shopify`), not a
  hardcoded customer-specific key. Docstrings ("Manage Shopify products
  on the configured store") and error messages reference the env var,
  not a specific tenant. **Breaking** for anyone who was relying on the
  old hardcoded behavior — set the env var to keep your existing keys.json
  entry working.
- **`config.py`** now honors `FRANKENCLAW_KEYS_PATH` to point at any
  flat-JSON keys file (default still `~/.rockys-switch/keys.json`).
  Default `searxng_url` is now empty — bring your own SearXNG endpoint.
- **`tools/gdrive.py`** now honors `FRANKENCLAW_GDRIVE_TOKEN_PATH` and
  `FRANKENCLAW_GDRIVE_CLIENT_SECRET_PATH` for OAuth credential locations.
- **`tools/browser.py`** default session name is `default` (was a
  Sparks-internal agent name).

### Scrubbed

- All references to specific customers, agents, machines, or operators
  removed from tool docstrings, headers, comments, and error messages.
  Project-Sparks-specific narrative gone; technical content preserved.
- `frankenclaw.service` is now a generic template (`User=YOUR_USER`,
  `WorkingDirectory=/path/to/frankenclaw`).
- README updated: tool count corrected to 14 (gdrive_search and
  gdrive_read added to the table — they were always exposed but never
  listed); keys-file documentation now language-agnostic; per-tool env
  var overrides documented.

### Removed

- `tools/stats.py` (project-specific tool reading projectsparks.ai stats
  and a private widget — useful inside Sparks, useless outside).
  Auto-discovery still picks up local-only tools you drop in `tools/` —
  add to `.gitignore` to keep them off the public repo.

## 0.2.1 — 2026-05-01

Fix install-blocking ImportError caught while wiring Hermes Agent against
FrankenClaw. Same shape as the mnemo-cortex 2.6.5 fix shipped earlier today:
a feature was developed locally without updating the public deps, so any
fresh `pip install -r requirements.txt && python3 server.py` died at
import time.

- `tools/gdrive.py` (added April 22 with `gdrive_search` + `gdrive_read`)
  imports `google.oauth2.credentials`, `google.auth.transport.requests`,
  and `googleapiclient.discovery`. None of those packages were in
  `requirements.txt`. Added: `google-auth>=2.0`, `google-auth-oauthlib>=1.0`,
  `google-api-python-client>=2.0`. The bare-minimum install now boots the
  full 12-tool FrankenClaw cleanly.
- `server.py` auto-discovery hardened: a tool module that fails to import
  (missing optional deps, bad credential path, broken third-party API) is
  now logged to stderr and skipped, instead of crashing the whole server.
  The remaining tools still register. Same graceful-degradation pattern
  the mnemo-cortex bridge uses for optional `BRAIN_DIR` / `WIKI_DIR`.

## 0.2.0 — 2026-03-31

v0.1 had Playwright + Browser-Use — heavy, slow, full Chromium binary. That's gone.

### Phase 1: Lightpanda replaces Playwright
- Installed Lightpanda 0.2.7 at /usr/local/bin/lightpanda
- Fetch mode: `lightpanda fetch <url> --dump markdown` — one-shot, fast, clean markdown
- Serve mode: CDP server on port 9333 for interactive tasks (clicking, forms)
- browser_task auto-detects which mode to use based on task description
- Removed browser-use and playwright from requirements.txt

### Phase 2: web_scrape tool (Firecrawl)
- New tool: web_scrape — turns any URL into clean, LLM-ready markdown via Firecrawl API
- Strips nav, ads, boilerplate — returns just the content
- Supports markdown, html, and links output formats
- API key from Rocky's Switch keys.json under "firecrawl" key
- Graceful error if key not configured (tells Guy exactly where to add it)

### Phase 3: Auto-discovery
- server.py no longer manually imports each tool
- Scans tools/ directory, registers any async function with a docstring
- Drop a new .py file in tools/, restart FrankenClaw, new tool is live
- No edits to server.py needed for new tools

### Dependency changes
- Removed: browser-use, playwright
- Added: websockets (for CDP interactive mode)
- Kept: mcp[cli], httpx, Pillow

## 0.1.0 — 2026-03-31

Initial build. All three tools working end-to-end.

### Phase 1: Scaffold + Discovery
- FastMCP server boots clean on stdio transport
- Config loader reads API keys from Rocky's Switch (~/.rockys-switch/keys.json)
- Own config at ~/.frankenclaw/config.json with auto-created defaults
- Discovered SearXNG on port 55510 inside BW's Docker container (bridge network, no host port)

### Phase 2: search_web
- Queries SearXNG via `docker exec` (no container modifications)
- URL-encodes queries for safe transmission
- Returns structured JSON: title, url, snippet
- Tested: "Project Sparks 3D printing" returned real results

### Phase 3: vision_analyze
- Reads images from host filesystem, compresses if over limit
- Sends to Gemini via OpenRouter using Rocky's Switch key
- Tested: Gemini read "HELLO FRANK" from a test PNG

### Phase 4: browser_task
- Browser-Use + Playwright on host (not Docker)
- LLM calls routed through OpenRouter via ChatOpenRouter
- Tested: navigated to example.com, returned "Example Domain" page title
- Installed browser-use 0.12.5 and playwright 1.58.0 on IGOR
