SLOPSHOPPER

phantra-spend-guard

Refuses the Bash and Edit calls that would spend render credit, post, or edit .env by accident. The click is the approval; this keeps a session from clicking…

newguardtoaststatus
A shopper browsing a rack in a slop shop
README

Phantra

An AI production studio. Bring an idea and your own photos; get stills and video made on current image and video models, held to those photos, and pay in credits only for what you approve. Live at phantra.ai. (Named Zero Page until 2026-10-13.)

What it does

  • Create. One chat box. The assistant talks an idea through, proposes a plan, and makes a still or writes a scene when asked. Every step that costs credits waits on a card that shows the price.
  • Scenes held to your photos. A scene is written as timed shots and rendered shot by shot against the photos of your characters, products and places. A scene with no photographs does not render.
  • Projects. A brief, a look and a memory per piece of work.
  • Library and effects. Everything you made, with image edits, camera moves, upscaling, sound, reframing, draft-then-finish and real downloads.
  • Editor. A timeline over your rendered clips, with an agent that proposes edits you keep or undo, and export to MP4, audio, a still or OpenTimelineIO.
  • Connector. An MCP server so Claude or ChatGPT can work in your workspace: https://api.phantra.ai/mcp and https://api.phantra.ai/mcp/claude. See phantra.ai/connector.

How it is built

PartWhere
Site and studioNext.js in web/, on Vercel
APIFastAPI in app/ over the library in src/, on Fly
Data and sign-inPostgres and Auth on Supabase
MediaCloudflare R2, served at media.phantra.ai
Video and image modelsfal.ai (Kling, Veo, Seedance, LTX, Wan, FLUX and others) and Gemini
BillingStripe plans that grant credits; a ledger hold before every provider call

Running it

venv/bin/pip install -r requirements.txt
venv/bin/pip install -e .
venv/bin/uvicorn app.main:app --reload      # the API on 127.0.0.1:8000
cd web && npm install && npm run dev        # the studio on localhost:3000

.env.example and web/.env.example list the settings. START_SERVER.md covers the local server and the MCP mount; CLAUDE.md is the working guide to the code, commands and conventions; docs/ holds the task files, the billing design and the runbook.

Checks

venv/bin/python -m pytest tests/ -q
venv/bin/ruff check .
cd web && node --test tests/*.test.mjs && npm run typecheck && npm run build

The test suite blocks all network access, so a test cannot make a billed call.

The README this replaced is kept at docs/retired/README-before-phantra.md.

Source 1 files
hooks/register.ts 70 lines
1import type { Register } from 'claude-code'
2
3// Every switch in this repo that turns a dry run into money or a post. CLAUDE.md:
4// "the click is the approval" -- a person presses Approve in the Queue, or exports
5// a *_SPEND_OK on purpose. A session arming one of these in a Bash command is the
6// exact "approval that's always on" the gates were written to prevent.
7const ARMS: readonly [RegExp, string][] = [
8  [/\b[A-Z0-9_]*_SPEND_OK=1\b/, 'arms a *_SPEND_OK render approval'],
9  [/\bPHANTRA_RENDER=1\b/, 'turns the graph render from a dry stub into real spend'],
10  [/\bPHANTRA_AUTOPILOT=1\b/, 'arms the L4 autopilot'],
11  [/\bPHANTRA_POST_OK=1\b/, 'arms posting'],
12  [/\bsrc\.autopilot\s+run\b[^\n]*--approve/, 'runs the autopilot live'],
13  [/\bsrc\.scheduling\s+run\b[^\n]*--live/, 'publishes the scheduled queue live'],
14  [/\b(ops\.stripe_setup|ops\/stripe_setup\.py)\b[^\n]*--live/, 'switches Stripe to live mode'],
15]
16
17// 2026-09-28 "Stop local tests from reaching the live database": the pattern that
18// did it was `set -a && source .env && set +a` followed by pytest in one line.
19const LIVE_DB_TESTS = /source\s+\.env\b[\s\S]*\bpytest\b|\bpytest\b[\s\S]*source\s+\.env\b/
20
21// .env, .env.local, .env.bak.* -- never .env.example, which is checked in.
22const ENV_FILE = /(^|\/)\.env(\.(?!example$)[^/]*)?$/
23
24// The override, for the one case the person DID ask for the spend this session:
25// `# spend-ok` at the end of the command. The deny text says when it is allowed.
26const OVERRIDE = /#\s*spend-ok\b/
27
28export const register: Register = on => {
29  on('tool.call', { tool: 'Bash' }, ($, e, next) => {
30    const cmd = e.command
31    if (OVERRIDE.test(cmd)) return next(e)
32
33    const hit = ARMS.find(([re]) => re.test(cmd))
34    if (hit) {
35      $.ui.toast(`spend-guard refused a command that ${hit[1]}`)
36      $.ui.status(`spend-guard: refused (${hit[1]})`)
37      return {
38        deny:
39          `phantra-spend-guard: this command ${hit[1]}. The click in the Queue is the approval; ` +
40          `a session must not arm it. If Mike asked for exactly this spend in his own words ` +
41          `this session, re-run with \`# spend-ok\` at the end of the command and say so.`,
42      }
43    }
44
45    if (LIVE_DB_TESTS.test(cmd)) {
46      $.ui.toast('spend-guard: pytest with .env sourced -- refused (live DATABASE_URL)')
47      return {
48        deny:
49          'phantra-spend-guard: `source .env` and `pytest` in one command points the test suite at the ' +
50          'LIVE DATABASE_URL (2026-09-28, docs/RUNBOOK.md). Run the tests without sourcing .env.',
51      }
52    }
53
54    return next(e)
55  })
56
57  const protectEnv = (file: string) =>
58    ENV_FILE.test(file)
59      ? {
60          deny:
61            `phantra-spend-guard: ${file} holds the live keys and tokens and is written only by the ` +
62            `scripts that back it up first (ops.ig_tokens, ops.stripe_setup). Edit .env.example, ` +
63            `or tell Mike the exact line to change.`,
64        }
65      : null
66
67  on('tool.call', { tool: 'Edit' }, ($, e, next) => protectEnv(e.file_path) ?? next(e))
68  on('tool.call', { tool: 'Write' }, ($, e, next) => protectEnv(e.file_path) ?? next(e))
69}
70