Skip to content

feat(computer-use): add Claude draws guide - #25

Merged
mislavivanda merged 8 commits into
mainfrom
feat/claude-draws-guide
Oct 7, 2026
Merged

mislavivanda merged 8 commits into
mainfrom
feat/claude-draws-guide

Conversation

@mislavivanda

@mislavivanda mislavivanda commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds python/computer-use/claude-draws: Claude draws on a sketchpad in a Daytona sandbox desktop, using the Anthropic SDK's computer toolset through the daytona-toolsets driver.

  • draw.py: creates a sandbox, uploads paint.html and opens it in Chromium on the sandbox desktop. It then runs the Anthropic tool runner with a fixed drawing task and prints the token totals at the end.
  • paint.html: a self-contained canvas sketchpad with no external assets. It has a horizontally scrolling, labelled palette, brush sizes, a caption field, and shift-click straight lines.
  • A README, pyproject.toml, .env.example, and a root README entry.

The docs page for this guide is already live and links to this directory: https://www.daytona.io/docs/en/guides/claude/claude-draws-daytona-sandbox/

Dependencies

Testing

  • A real run against Daytona production completed. Claude scrolled the palette to the labelled swatches and drew the requested scene.
  • The sketchpad passes a browser QA run covering stroke drawing, palette scrolling, the shift-click straight line, and caption select-all.

Summary by cubic

Adds the Claude Draws guide: Claude draws on a canvas sketchpad opened in a Daytona sandbox desktop, driven through the Anthropic SDK's computer toolset via daytona-toolsets.

  • draw.py creates the sandbox, uploads and opens paint.html in Chromium, runs the drawing loop, and prints token totals; it fails fast when paint.html is missing and inlines the Chromium log tail into the startup timeout error.
  • paint.html is a self-contained sketchpad with a labelled, horizontally scrolling palette, brush sizes, a caption field, and shift-click straight lines.
  • The sketchpad guards the active stroke against a second pointer, ends the stroke before Clear wipes the canvas, and hands out a deep-copied window.paint snapshot.
  • The drawing task targets colors by visible label, never by position, because the palette scrolls sideways.
  • Requires daytona-toolsets; it resolves from PyPI once feat(daytona-claude-toolsets): add computer and browser toolset drivers integrations#84 is released. daytona is now declared directly, pinned to the same range.
  • The README run step covers PowerShell $env: variables, not just source .env.
  • The docs page for this guide is already live and links to this directory.

Written for commit e8a15e4. Summary will update on new commits.

View guided diff Turn on auto-fix

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 6 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread python/computer-use/claude-draws/README.md Outdated
Comment thread python/computer-use/claude-draws/pyproject.toml
Comment thread python/computer-use/claude-draws/pyproject.toml
Comment thread python/computer-use/claude-draws/draw.py Outdated
Comment thread python/computer-use/claude-draws/paint.html Outdated
Comment thread python/computer-use/claude-draws/paint.html
Comment thread python/computer-use/claude-draws/paint.html
The setup section offers PowerShell alternatives for venv activation and copying
.env, but the workflow step then told everyone to `source .env`, which PowerShell
has no command for and could not read anyway: .env is a POSIX shell file of
`export` lines. Mark the sourcing step as macOS/Linux and add the $env:
equivalents beside it, so the Windows path stays unbroken end to end.

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>
draw.py does `from daytona import Sandbox, SessionExecuteRequest`, but the project
only listed daytona-toolsets, so the install worked purely because the toolset
happens to pull daytona in. Declare it directly, pinned to the same
>=0.223.0,<0.224.0 range daytona-toolsets requires so the two cannot disagree.

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>
…html

py-modules ships draw.py alone, so a non-editable `pip install .` puts the module
in site-packages without the paint.html it uploads at runtime, and the run died
on a bare FileNotFoundError from read_bytes deep inside a live sandbox.

The example is meant to run from its checkout — the README's setup is clone, cd,
`pip install -e .`, `python draw.py` — so keep the module-only layout and make
that contract explicit instead of inventing package data for a single module:
say it in pyproject.toml, and check for the file at the top of main() so the
non-editable case exits with a sentence that names the fix, before a sandbox is
created for a run that cannot work.

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>
DaytonaComputer deletes the sandbox as this exception unwinds, so the log the
message pointed at was gone before anyone could open it — the error named a path
that no longer existed and told the reader nothing about why Chromium never came
up.

Read the tail of chromium.log while the sandbox is still alive and inline it in
the RuntimeError. The read is best effort and bounded: a sandbox too broken to
run tail reports that in place of the log rather than replacing the timeout with
a second failure.

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>
…roke

A second non-shift pointerdown overwrote state.pointerId and state.lastPoint, so
the stroke already in progress was abandoned mid-gesture: its anchor jumped to
the new pointer, and the pointer that still held the capture could no longer end
anything, because endStroke only releases against the recorded pointerId.

Reproduced with two real CDP touch pointers: the anchor moved from (300,250) to
(900,550) and a second stroke op was opened. Return early when a stroke is
already live, before any capture is taken, so the extra down owns nothing.
Verified the anchor and the op log now hold steady and the first pointer stays
the owner.

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>
Clear nulled lastPoint but left drawing true, so a Clear reached with the pointer
still down — a second touch on the button, or the keyboard activating it
mid-drag — left a live stroke with no anchor, and the next pointermove called
segment(null, point) and threw "Cannot read properties of null". Truncating ops
in the same handler also pulled out the entry endStroke later closes.

Call endStroke() first so the stroke is closed and the capture released before
the state is reset. Confirmed against the pre-fix page: two TypeErrors on the
next move, none after.

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>
The getter froze only the object it returned, so lastPoint was handed out as the
live anchor and ops.slice() copied the array but not the records in it. The
documented read-only surface was neither read-only — writing
state.lastPoint.x reached straight into the page's anchor — nor a snapshot,
since a held copy drifted as drawing continued.

Copy points and op records explicitly and freeze every level. Verified against
the pre-fix page: poking a snapshot moved the real anchor to -12345; it is now
inert, and a held snapshot stays put while the live state moves on.

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>
@mislavivanda
mislavivanda merged commit 2ae329b into main Oct 7, 2026
8 checks passed
@mislavivanda
mislavivanda deleted the feat/claude-draws-guide branch October 7, 2026 20:30
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 7, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant