Skip to content

Repository files navigation

πŸͺ™ Tokenmaxxing

A black macOS desktop + CLI portal for agentic handoffs: plan on ChatGPT Web β†’ approve locally β†’ execute with your local CLI agent.

Tokenmaxxing_demo.mp4

Tokenmaxxing runs a local MCP server, exposes it over a stable Tailscale Funnel URL so ChatGPT Web can read your workspace and write an implementation plan, then β€” after you approve that exact plan on this machine β€” hands it to a local CLI agent (Antigravity / Claude Code / Codex) for execution.


πŸ’‘ The "Rate-Limit Arbitrage" (Why this exists)

ChatGPT Web usage limits are separate from developer API limits (the ones Codex and local CLI agents spend). Most developers don't code inside a browser, so the workflow splits:

  1. Design & planning on the web (flat-rate). Analyzing a codebase and architecting a change is token-heavy. Exposing your workspace to ChatGPT Web lets the planner read it and write .tokenmaxxing/plan.md on your web subscription, consuming zero developer-API credits.
  2. Local execution (cheap & fast). Your local agent only processes the finished plan and implements it.
  3. Local control. The web planner can read the granted workspace and write one file: the plan. It cannot run anything. Execution requires your explicit approval on this Mac.

πŸ— Architecture

ChatGPT Web  (planner β€” reads workspace, writes plan.md, cannot execute)
  |
  | Tailscale Funnel, HTTPS, exact-hostname Host/Origin allowlist
  v
tokenmaxxing/server.py   FastMCP daemon β€” re-checks the grant on every tool call
  |                      writes .tokenmaxxing/plan.md atomically, hashes it
  v
YOU  β€” read the plan in the app, approve that exact hash
  |
  v
tokenmaxxing/execution.py β†’ local CLI agent  (UNSANDBOXED, full user permissions)
Module Role
server.py FastMCP server. The only internet-facing surface.
state.py Grants, approvals, execution records. Atomic 0600 writes under a file lock.
plans.py Plan validation, atomic writes, symlink refusal, content hashing, bounded history.
execution.py Authorization gate + hardened subprocess runner.
events.py Append-only structured event log (identifiers only, never content).
preflight.py Tailscale/Funnel checks, shared by the CLI and the GUI.
runner.py CLI and local execution layer.
gui.py Desktop UI.
config.py Port / host / LaunchAgent label resolution.

The pywebview UI

The desktop app is a native macOS window rendering an embedded HTML/CSS/JS document through WebKit (pywebview), with a Python bridge at window.pywebview.api. It is not Electron and not a browser tab.

The UI is not a security boundary. Every disabled button has a matching refusal in Python: Api.run_agent re-runs execution.authorize from scratch regardless of what the front end believed, and every value arriving from JavaScript (workspace path, agent name, TTL, URL) is re-validated before it reaches the filesystem or a subprocess.

The window shows, at all times: workspace, grant status and expiry countdown, grant mode, Tailscale / Funnel / daemon / authentication status, plan ID and hash, approval state, selected agent, whether execution is sandboxed (it is not), current execution state, last result, and whether recovery is required. When Run is disabled it names the reason.


πŸ“¦ Installation

macOS required. Tokenmaxxing assumes Terminal.app, Homebrew, LaunchAgents, and .icns app icons β€” it will not run on Linux or Windows. You also need Python 3.10+ and a (free) Tailscale account.

git clone https://github.com/jerrylin-23/tokenmaxxing.git
cd tokenmaxxing
python3 -m venv .venv
source .venv/bin/activate
pip install -e .

tokenmaxxing doctor       # check every precondition β€” PASS/FAIL with the exact fix per item
tokenmaxxing setup        # first-run wizard: fixes what it can, opens the right admin pages,
                          # and verifies ChatGPT can really reach your machine
tokenmaxxing gui          # launch the desktop app

doctor checks each precondition independently and prints the exact command or admin-console URL for anything that fails, then summarizes the runtime state (grant, plan, approval, last execution). It exits non-zero if setup is incomplete, so you can script against it.

setup walks the same checks interactively, runs what it safely can (e.g. tailscale up), stops at the steps that need you in the Tailscale admin console, and finishes by POSTing a real MCP initialize through the public Funnel URL.

Packaged .app: build with pyinstaller Tokenmaxxing.spec, then ./make_dmg.sh to produce dist/Tokenmaxxing.dmg.


🌐 Local-only mode vs. Tailscale Funnel mode

The daemon binds to 127.0.0.1 by default and serves no public hostname unless you name one.

Local-only (default). tokenmaxxing daemon accepts only 127.0.0.1, localhost, and [::1] as Host. Useful for a local MCP client; ChatGPT Web cannot reach it.

Funnel mode. Pass the exact hostname the tunnel will use:

tokenmaxxing daemon --transport streamable-http --public-host box.tailnet.ts.net

--public-host takes one exact hostname, optionally with a port. Wildcards (*, *.ts.net) are rejected outright. The GUI and tokenmaxxing setup derive this automatically from your node's MagicDNS name.

This matters: the previous version allowed *.ts.net, which accepted every Tailscale Funnel host on the internet as a valid Host header. Now box.tailnet.ts.net is allowed and evil.tailnet.ts.net gets 421 Misdirected Request.

Host and Origin are separate allowlists. https://chatgpt.com is an allowed page origin but never an allowed host; your public hostname is an allowed host, and only its https:// form is an allowed origin.


πŸ”‘ Grant lifecycle

A grant is a time-boxed authorization for one workspace, stored in ~/.tokenmaxxing/state.json (mode 0600, schema-versioned, written atomically under a file lock).

create  β†’  active  β†’  expired          (TTL elapses)
                   β†’  revoked          (you revoke it)
                   β†’  workspace_missing (the directory is gone)
                   β†’  corrupt          (unreadable state β†’ recovery required)

Each grant carries a schema version, a unique grant ID, the canonical workspace path, creation and expiry timestamps, its mode, an execution-permission flag, a revocation flag, a capability secret (with a non-secret capability_id for display and logs), and the last known execution state.

  • Every MCP tool call re-reads and re-classifies the grant. There is no cached authorization, so revocation takes effect on the next call.
  • Revocation marks the record rather than deleting it, so a partial write can never resurrect a live grant.
  • The capability secret is never printed by any CLI command or written to any log. Only capability_id appears.
tokenmaxxing grant ~/code/myproject --ttl 4h
tokenmaxxing status
tokenmaxxing revoke

πŸ“„ Plan lifecycle

write_handover_plan is the only write the connector performs.

  1. Validated β€” must be a non-empty string of at most 256 KB (measured in bytes, so multibyte content can't slip past).
  2. Symlink-refused β€” a symlink at .tokenmaxxing/plan.md or at .tokenmaxxing/ itself aborts the write, so nothing can redirect it to another file.
  3. Written atomically β€” temp file, fsync, os.replace. An interrupted write leaves the previous plan intact, never a truncated one.
  4. Archived β€” the replaced plan moves to .tokenmaxxing/plan-history/, capped at 5 entries.
  5. Identified β€” a unique plan ID and a SHA-256 content hash go into .tokenmaxxing/plan.json, along with the grant ID that produced it.

The hash is always recomputed from the bytes on disk. If the sidecar metadata disagrees with the file β€” because something edited the plan behind the plan writer's back β€” the file wins and the plan gets a derived ID. Approval binds to the hash, so an out-of-band edit invalidates it.


βœ… Local approval flow

1. Select a workspace
2. Start the handoff service      β†’ grant created, tunnel up
3. ChatGPT writes the plan        β†’ hashed, shown in the app
4. Read the plan
5. Approve it                     β†’ approval bound to (grant, plan, hash, workspace, agent)
6. Confirm the agent and mode
7. Run                            β†’ re-authorized from scratch, then started

An approval is single-use and expires after 30 minutes. Execution is refused if the plan changed, the grant changed, the grant expired or was revoked, the approval was already used or expired, the selected agent differs from the approved one, the agent CLI is missing, or another run is already active for that workspace (enforced by a file lock, not a flag).

tokenmaxxing approve --agent codex     # prints the plan ID, hash, size, and the warning
tokenmaxxing execute --agent codex
tokenmaxxing execute --agent codex --dry-run   # shows ALLOWED/DENIED and why, runs nothing

The Run button is disabled when any of those conditions hold, and it names the blocker β€” but the button is a convenience. execution.authorize is the boundary.


⚠️ Execution trust boundary β€” the agent runs unsandboxed

The scoping and blocklists apply to the web planner. The local executor is a different trust domain, and it is not confined:

  • codex is invoked with --sandbox danger-full-access and --skip-git-repo-check.
  • claude and antigravity run with --dangerously-skip-permissions.

The agent executing your plan can read and write any file your user account can, run arbitrary shell commands, and reach the network. It is not limited to the granted workspace. Tokenmaxxing does not sandbox it and does not claim to. The CLI prints this on every run, the approval dialog states it, and is_sandboxed() returns False for every agent.

This makes the plan part of your trust boundary. Anything that can write .tokenmaxxing/plan.md is authoring instructions for an unrestricted local agent β€” which is exactly why approval is local, bound to a content hash, and single-use.

What is hardened is how the process is run:

  • Explicit argument arrays, shell=False, executables resolved on PATH β€” never a shell string.
  • The agent leads its own process group, so a timeout stops its grandchildren too, not just the direct child.
  • Graceful SIGTERM, then SIGKILL after a 5-second grace period.
  • stdin closed, output capture bounded at 512 KB, invalid UTF-8 replaced rather than crashing.
  • Outcomes distinguished: succeeded / failed / timeout / cancelled / unknown, each with a unique execution ID and a structured record.

πŸ”’ Security posture

What is enforced

  • Only the granted workspace is reachable; paths are canonicalized before every check, so .., absolute paths, and symlinks resolving outside all fail the same containment test.
  • os.walk does not follow symlinked directories, and symlinked directories are skipped during listing.
  • Sensitive paths are blocked in all path forms: .env*, .ssh, .aws, .azure, .kube, .gnupg, .docker, .config/gcloud, .netrc, .pgpass, .npmrc, .pypirc, .git-credentials, id_rsa/id_ed25519/id_ecdsa/id_dsa, *.key/*.pem/*.p12/*.pfx/*.jks/*.ppk, credentials, secrets.{json,yaml,yml}, service-account.json, and .git/config.
  • File reads are capped at 512 KB; binary/non-UTF-8 files return a safe error rather than bytes. Listings are bounded.
  • Grants expire, revoke immediately, and are re-checked on every tool call.
  • Tool errors return stable error codes with no stack traces, no absolute paths, and no secrets.

What is NOT enforced β€” read this before exposing anything

  • The public endpoint is unauthenticated. ChatGPT Web custom connectors registered with "No Authentication" cannot present a credential, so Tokenmaxxing does not pretend the tunnel is authenticated. While the funnel is up, anyone who learns the URL can call the read tools and overwrite the plan for the life of the grant. The mitigations are that grants are short-lived, revocable, scoped to one directory, credential-filtered, and β€” critically β€” that the endpoint cannot start an agent. A capability secret exists in the state model for local components and future authenticated transports; it is not currently required by the public HTTP surface.
  • The local agent is not sandboxed. See above.
  • Treat the connector URL as a secret, keep TTLs short, and revoke when you're done.

πŸ“Š Logs, diagnostics, and recovery

Event log β€” ~/.tokenmaxxing/events.jsonl, one JSON object per line, mode 0600, rotated at 2 MB. Covers server start/stop, grant creation/expiry/revocation, every MCP tool call, plan writes, plan approvals, and execution start/completion/failure/timeout, with timestamp, event name, grant ID, plan ID and hash, execution ID, tool name, duration, and result status.

Only an allowlist of field names is ever written. Anything else β€” a token, an environment dict, plan text, command arguments β€” is dropped, not truncated, so a careless call site cannot become a leak. Workspaces are logged by basename.

Run logs β€” .tokenmaxxing/runs/<ts>-<agent>-<execution-id>.log inside the workspace: the agent's output plus the execution metadata. Plan content and full command arguments are not written.

Diagnostics

tokenmaxxing diagnose     # config, state path, daemon health, grant, tunnel, auth,
                          # plan + approval, last execution, recovery state

Also available from the app's Diagnostics button. It never prints secrets. It exits non-zero when recovery is required.

Recovery β€” if the state file is unreadable (corrupt or partially written), every tool call is refused and recovery_required is set:

tokenmaxxing recover      # quarantines the bad file, resets state
tokenmaxxing grant <path> --ttl 4h

Recovery is also flagged when an execution is recorded as running but its process is gone.


πŸ›  Workflow

1. Start the service

Open the app, pick your workspace, click Start public tunnel and confirm. Tokenmaxxing runs the Tailscale + Funnel preflight, starts the local MCP daemon pinned to your exact MagicDNS hostname, creates the grant, and verifies a real MCP handshake through the public URL.

2. Connect ChatGPT Web (Developer Mode)

  1. chatgpt.com β†’ Settings β†’ Apps.
  2. Turn Developer mode on.
  3. Add App β†’ Server URL = the connector URL from the app (ends with /mcp).
  4. Select No Authentication β†’ Create.
  5. In a new chat, enable the Tokenmaxxing connector.

3. Generate the plan (web)

"First call get_project_context, then read the source files relevant to [feature]. Write an implementation plan to .tokenmaxxing/plan.md."

The plan appears in the Handoff plan panel with its ID, hash, and size. get_project_context gives the planner the working-tree state, a safe file map, and root manifests so it isn't planning from a blank slate. The connector is a sealed workspace view β€” don't ask ChatGPT for arbitrary machine paths; it will refuse.

4. Review and approve (local)

Read the plan. Click Approve plan and confirm. The approval binds to that exact hash for that agent.

5. Execute (local)

Click Run. A Terminal opens with the agent running interactively on the approved plan, so you can watch and intervene.


🧩 CLI

tokenmaxxing doctor                       # every setup precondition + runtime summary
tokenmaxxing diagnose                     # config, grant, plan, tunnel, execution, recovery
tokenmaxxing setup                        # interactive first-run wizard + end-to-end check
tokenmaxxing gui                          # launch the desktop app
tokenmaxxing grant <path> --ttl 4h        # grant a workspace for a window of time
tokenmaxxing status                       # active grant, plan, approval
tokenmaxxing revoke                       # revoke the grant
tokenmaxxing approve --agent codex        # approve the current plan for one run
tokenmaxxing execute --agent codex        # run the approved plan
tokenmaxxing execute --agent codex --dry-run --interactive
tokenmaxxing recover                      # reset unreadable state
tokenmaxxing daemon --transport streamable-http --public-host <host>
tokenmaxxing install-launchagent          # always-on daemon at login (macOS)

--allow-execute on grant records execution intent for the grant. It does not let the web planner start an agent, and it does not substitute for local approval β€” that is tested explicitly.

Configuration

Precedence: environment variable, then ~/.tokenmaxxing/config.json, then the built-in default.

Setting Env var Config key Default
Daemon port TOKENMAXXING_PORT port 8422
Bind host TOKENMAXXING_HOST host 127.0.0.1
LaunchAgent label TOKENMAXXING_LAUNCHAGENT_LABEL launchagent_label com.tokenmaxxing.daemon
State file TOKENMAXXING_STATE β€” ~/.tokenmaxxing/state.json

If port 8422 is taken on your machine, export TOKENMAXXING_PORT=<port> (or write {"port": <port>} to ~/.tokenmaxxing/config.json) and every component β€” doctor, setup, daemon, GUI, Funnel β€” follows it. tokenmaxxing doctor tells you when the port is occupied by something that is not a Tokenmaxxing daemon.


πŸ“¦ Packaging

PyInstaller + hdiutil, unchanged:

pyinstaller Tokenmaxxing.spec --noconfirm   # β†’ dist/Tokenmaxxing.app
./make_dmg.sh                               # β†’ dist/Tokenmaxxing.dmg

The frozen executable doubles as the CLI (Tokenmaxxing <subcommand>); with no subcommand it opens the GUI. The UI is embedded in gui.py as a Python string, so there are no external asset files to bundle or lose. The packaged app runs the same state.py / execution.py code paths as the source install β€” the security behaviour does not change when frozen.


πŸ§ͺ Tests

pytest tests/ -q
ruff check .

Tests never contact Tailscale, ChatGPT, a real Funnel, or a real coding agent. Agent binaries are throwaway shell scripts on a temporary PATH, state is redirected into tmp_path, and MCP tools are invoked through the FastMCP registry rather than private helpers.


⚠️ Known limitations

  • The public endpoint is unauthenticated while the funnel is up. See the security section.
  • The local agent is unsandboxed. Tokenmaxxing gates when it starts, not what it can do once running.
  • macOS only. Terminal.app, LaunchAgents, osascript, and the Tailscale macOS app are assumed.
  • The plan is trusted input to an unrestricted agent. Read it before approving.
  • Approval is time-boxed but coarse β€” one approval authorizes one full run, not individual actions within it.
  • get_project_context truncates. Root manifests are capped at 6 KB each and 18 KB total; file listings are bounded.
  • Tunnel health is a probe, not a guarantee. A green indicator means the endpoint answered an MCP initialize recently.

πŸ“‹ Readiness

Ready for local personal use. The grant, plan-integrity, approval, and process-execution paths are enforced in Python and covered by tests.

Not ready for production or untrusted multi-user use, for one reason above all: the public MCP endpoint is unauthenticated. Until an authenticated transport is in place, treat the connector URL as a short-lived secret and keep TTLs short.


πŸ“„ License

MIT β€” see LICENSE.

About

macOS desktop + CLI portal for agentic handoffs: plan on ChatGPT Web, execute with your local CLI agent (Codex, Claude Code, Antigravity) via a local MCP server exposed over Tailscale Funnel.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages