Claude Bridge

A local HTTP eval server for live SketchUp model inspection

SketchUp extension · version 1.0.0 · Boris Beaulant · developer tool, not for distribution

Claude Bridge is a development-only SketchUp extension. It exposes a tiny HTTP server on 127.0.0.1 that accepts Ruby source code, evaluates it inside the running SketchUp process, and returns the result as JSON. Its purpose is to let a local coding agent (Claude Code) or any shell script read and manipulate the model you currently have open, without the copy-paste round trip through the Ruby console.

Read this first

This is an arbitrary code execution server. Any process running under your user account can reach it while it is started, and evaluated code has the full power of the SketchUp API — including saving, deleting and overwriting your work. Start it when you need it, stop it when you are done, and never ship it to anyone as part of a product.

Installation

  1. In SketchUp, open Window › Extension Manager.
  2. Click Install Extension and pick ladb_claude_bridge-1.0.0.rbz.
  3. Confirm the unsigned-extension warning. SketchUp's loading policy must allow unsigned extensions (Extension Manager › Manage › Loading policy: Unrestricted).
  4. Restart SketchUp.

A Claude Bridge toolbar appears, and a Claude Bridge item is added to the Extensions menu. The extension does not auto-start the server on load.

Alternative: load from source

For day-to-day development, use the Extension Sources extension by ThomThom to add this project's src/ folder as an extra load path, so the extension runs straight from the working tree. Failing that, copy or symlink ladb_claude_bridge.rb and the ladb_claude_bridge/ folder into SketchUp's Plugins directory.

Starting and stopping the server

Three equivalent ways:

Ladb::ClaudeBridge.start          # default port 7857
Ladb::ClaudeBridge.start(port: 7900)
Ladb::ClaudeBridge.stop
Ladb::ClaudeBridge.running?       # => true / false
Ladb::ClaudeBridge.toggle

Starting the server opens the Ruby console and logs the listening address. Requests are logged there too, which is the quickest way to confirm the bridge sees your calls.

HTTP API

The server listens on http://127.0.0.1:7857 and answers two routes. Every request must carry the X-Claude-Bridge: 1 header — requests without it get 403.

RoutePurpose
GET /ping Liveness check. Returns the SketchUp version, the Ruby version, and the title and path of the active model.
POST /eval The request body is Ruby source code, evaluated in the SketchUp process. Returns the value of the last expression plus anything the script printed to stdout.

Checking the bridge is up

curl -s -H 'X-Claude-Bridge: 1' http://127.0.0.1:7857/ping
{
  "ok": true,
  "sketchup": "26.0.108",
  "ruby": "3.2.2",
  "model_title": "cabinet",
  "model_path": "/Users/me/models/cabinet.skp"
}

Evaluating Ruby

Send the script as the raw request body. Writing it to a file first and using --data-binary @file avoids all shell-quoting problems:

cat > /tmp/script.rb <<'RUBY'
model = Sketchup.active_model
model.selection.map { |e| [ e.class.name, e.entityID ] }
RUBY

curl -s -X POST -H 'X-Claude-Bridge: 1' \
     --data-binary @/tmp/script.rb \
     http://127.0.0.1:7857/eval

Response format

Always JSON, always HTTP 200 for /eval — success and script failure are distinguished by the ok field:

{ "ok": true,  "result": [ [ "Sketchup::Group", 1234 ] ], "stdout": "" }

{ "ok": false, "error": "NoMethodError: undefined method `foo' for nil",
  "backtrace": [ "claude-bridge-eval:2:in `<main>'" ], "stdout": "partial output\n" }
FieldMeaning
oktrue if the script ran to completion.
resultValue of the last expression, round-tripped through JSON. Values that cannot be serialized fall back to their inspect string.
stdoutEverything the script wrote to $stdout (puts, print, …), captured separately from the result.
errorClass and message of the raised exception. SyntaxError and SystemStackError are reported too, not just StandardError.
backtraceFirst ten backtrace lines. The eval frame is named claude-bridge-eval.

Writing scripts for the bridge

Return JSON-friendly values

result goes through JSON.generate. Arrays, hashes, strings, numbers and booleans travel intact; SketchUp entities do not. Map what you need explicitly:

Sketchup.active_model.definitions.map { |d|
  { name: d.name, count: d.count_used_instances, entities: d.entities.length }
}
Length values serialize as strings

Coordinates of a Point3d are Length objects, and they serialize as "12\""-style strings, not numbers. Call .to_f on anything you intend to compare or feed to a native library.

Wrap every mutation in an operation

Evaluated code runs with full write access to the model. Grouping changes into a single undoable operation keeps the model recoverable:

model = Sketchup.active_model
model.start_operation('Claude Bridge tweak', true)
begin
  model.selection.grep(Sketchup::ComponentInstance).each { |i| i.material = 'red' }
  model.commit_operation
rescue
  model.abort_operation
  raise
end

Keep evaluations short

The server is driven by a UI.start_timer tick and handles requests on SketchUp's main thread. That is what makes the SketchUp API safe to call — and it also means a slow script freezes the SketchUp window until it returns. The client-side read timeout is 3 seconds for receiving a request; there is no cap on evaluation time, so a runaway loop needs a force quit. Prefer many small probes over one long batch.

Never hot-reload a native library

Do not use the bridge to dlclose and re-load a Fiddle native library (for example Fiddle::Meshy.unload followed by another call after replacing the .dylib on disk). It kills the SketchUp process instantly and takes the unsaved model with it. After rebuilding a native library, restart SketchUp; test C++ changes in a standalone headless harness instead.

Security model

Troubleshooting

SymptomCause and fix
Message box:
port 7857 already in use
Another bridge instance is running — often a second SketchUp window, or a previous session whose socket was not closed. Stop the other instance, or start on another port with Ladb::ClaudeBridge.start(port: 7900).
curl reports
connection refused
The server is stopped. Click the toolbar button and check it becomes checked, or evaluate Ladb::ClaudeBridge.running? in the Ruby console.
403 Missing x-claude-bridge header The -H 'X-Claude-Bridge: 1' argument is missing from the request.
Toolbar button check state looks stale SketchUp re-runs validation procs only on certain UI events. On SketchUp 2017 (no UI.refresh_toolbars) the state can lag one action behind; the console log is authoritative.
No toolbar after install The loading policy blocked the unsigned extension. Set Extension Manager › Manage › Loading policy to Unrestricted and restart SketchUp.
SketchUp is frozen An evaluated script is still running on the main thread. Wait for it, or force quit if it is an infinite loop.

Package contents

ladb_claude_bridge.rb                 extension registrar (Ladb::ClaudeBridge)
ladb_claude_bridge/
  main.rb                             HTTP server, eval, toolbar and menu
  img/icon.svg                        toolbar icon
  doc/ladb_claude_bridge.pdf          this document