Skip to content

Latest commit

 

History

History
607 lines (447 loc) · 21 KB

canvas.md

File metadata and controls

607 lines (447 loc) · 21 KB

Canvas

Code Canvas is what a chat's agent builds when it writes files instead of just replying — a small site or app, kept as part of that chat.

From the CLI you can list what it built, read or download the files, roll the canvas back to an earlier checkpoint, and publish it as a live site with its own URL and custom domain. The command is camy canvas.

camy canvas
camy canvas cat index.html
camy canvas pull ./site
camy canvas sites

Bare camy canvas is the same as camy canvas files.

Editing file contents is not something camy canvas does — that happens in the chat (ask the agent to change a file) or in the web editor. The CLI's job is getting files out of a canvas and sites out into the world, not editing what's in them.

See camy canvas for the full command tree.

Which chat's canvas

Most subcommands act on one chat's canvas, so they take a --chat flag:

camy canvas --chat abc12345 files

--chat accepts a full chat id, or a short id — anything four characters or longer that uniquely prefixes one of your chats, archived chats included.

Leave it out and the CLI falls back to the chat you last used, the same anchor camy chat -c continues. With no chat to fall back to either, it exits 2 asking which chat's canvas you meant and points you at camy chats.

A short id that matches nothing is passed straight through, and left to the server to reject. The exception is camy canvas open, which only builds a link: it refuses a short id that matches none of your chats (exit 2) instead of opening an empty canvas. One that matches more than one chat is refused client-side (exit 2) rather than guessed at. A ref under four characters is refused outright as too short to identify a chat.

--chat only matters for the commands that read a canvas's files or work with checkpoints: files, cat, pull, open, publish, snapshot, snapshots, restore, preview, and export.

The site-management commands (sites, domain, access, indexable, rollback, versions) act on an already-published site by name, and need no chat at all.

Files and contents

camy canvas files

camy canvas files

Lists the files currently in the canvas: a mark, the filename, its language, and a human-readable size. An empty canvas says so rather than printing nothing.

--json gives one object per file. This is a shape the CLI builds, not a passthrough of the server's file list, and it does not carry file contents:

[{"filename": "index.html", "language": "html", "bytes": 1834, "updated_at": "..."}]

See camy canvas files.

camy canvas cat

camy canvas cat FILE

Prints one file's contents. FILE must match a filename in the canvas exactly — no globbing, no prefix matching. A filename that isn't in the canvas exits 1 and points you at camy canvas files.

When stdout is piped, the bytes go out unsanitized, exactly as the server holds them, except that cat adds a trailing newline when the file doesn't already end in one. So a redirect is a faithful checkout of that one file, up to that final newline:

camy canvas cat app.js > app.js

When stdout is a real terminal instead, the content is sanitized first, the same as everywhere else server text reaches your screen.

See camy canvas cat.

Getting the files onto your machine

camy canvas pull

camy canvas pull [DIR]

Writes every file in the canvas to a local directory: DIR if you give one, else canvas-<shortid> using the chat's own short id. An empty canvas exits 1.

Before writing anything, pull checks every destination path across the whole file set, so a conflict found late in the list can't leave earlier files already written. Two safety rules apply per file:

  • A filename that is empty, starts with /, or has a .. path component (../x, a/../../b) is never trusted as a local path. pull skips it and names every skipped file on stderr (under --json, in skipped instead), for example skipped 1 file — a path that is empty, absolute or climbs out of ./site is never written: "../x". A name that only contains two dots, such as jquery..min.js, is an ordinary file and is written.
  • A destination that already exists as a symlink, live or dangling, is refused unconditionally, even with --force. Remove it first, or pull into a fresh directory.

If any destination already exists as an ordinary file, pull refuses the whole operation with exit 2 and writes no files; -f/--force overwrites instead. The destination directory itself is created up front, so a refused pull can leave it behind, empty.

--json:

{"ok": true, "chat_id": "<full chat id>", "dir": "./site", "files": 6, "skipped": []}

skipped lists the filenames left out by the first rule above, and is an empty array when there were none.

See camy canvas pull.

camy canvas preview

camy canvas preview

Writes the canvas to a fresh temporary directory and opens it in your own browser as a local file. Nothing is published. It picks index.html if present, otherwise the first .html file it finds; a canvas with no HTML file at all exits 1. It skips the same unsafe filenames pull does, and names them on stderr.

Because this runs model-generated HTML and JavaScript the moment it opens — code that hasn't been reviewed, executing locally on your machine — it asks for confirmation first, worded around exactly that risk. This is one place --force/--no-input genuinely need to mean it: headless without --force refuses rather than silently opening a browser.

The temporary directory is left on disk afterward, not cleaned up. It is written before the prompt, so declining still leaves that copy on disk.

See camy canvas preview.

camy canvas open

camy canvas open

Opens the chat's Code Canvas page at camy.ai in your browser — the real canvas in the web editor, not a local checkout. It prints the URL as well as opening it, so the link is still the output on a machine with no browser.

open refuses a short id (anything shorter than a full chat id) that doesn't resolve to one of your chats. It exits 2 with no chat matches <ref> and a hint pointing at camy chats. A full chat id, including the last-chat fallback, is opened as given without being checked.

See camy canvas open.

Checkpoints

A snapshot is a checkpoint of the canvas as it stands. Restoring one replaces the canvas's current files with that checkpoint's.

camy canvas snapshot

camy canvas snapshot
camy canvas snapshot "before the redesign"

Creates a snapshot, with an optional free-text label. See camy canvas snapshot.

camy canvas snapshots

camy canvas snapshots

Lists snapshots, newest first: a mark, the snapshot's short id, its label (or a dim placeholder when there isn't one), and roughly how long ago it was taken. See camy canvas snapshots.

camy canvas restore

camy canvas restore SNAPSHOT

Rolls the canvas back to a snapshot. SNAPSHOT is a short id or a full id, resolved the same way --chat is; an ambiguous or too-short prefix is refused client-side. It asks for confirmation first — see Confirmation and --force.

Human output reports how many files came back. See camy canvas restore.

Publishing a site

Sites and your workspace

Publishing writes a site's files onto your workspace, and a site's archived versions are kept there too. So publish, rollback, sites and versions all depend on the workspace.

sites and versions won't boot a stopped workspace just to list what's on it. If it is stopped or stopping, they exit 7 with, for example, your workspace is stopped — listing its sites would start it and a hint to run camy vm start first. With no workspace at all they still list, and nothing is created.

When camy.ai can list without starting the workspace, the listing goes ahead on a stopped workspace instead, and a note on stderr says why the list may be short, for example your workspace is stopped — the sites stored on it show up once it's running (camy vm start, then camy canvas sites). --json prints the rows alone, without the note.

publish starts a stopped workspace first, and creates one if you have none; rollback starts a stopped one. Each says so before its confirmation prompt (unless --quiet, or camy couldn't read the workspace's state), because the workspace is billed while it runs, and then waits up to 30 minutes for it:

! your workspace is stopped — this starts it first (a few minutes; billed while it runs)

rollback with no workspace at all is refused before the prompt, with exit 7: the site's archived versions lived on the old workspace, so there is nothing to roll back to. camy canvas publish ships the site again.

camy canvas publish

camy canvas publish

Publishes the canvas as a live site. Before claiming anything, publish looks up the exact host the site would get and shows it to you in the confirmation prompt — you see the real URL before agreeing, not after.

If the host isn't claimable at all (already taken, for example), publish fails with the server's reason and never reaches the confirmation prompt. If camy.ai then refuses the publish itself (an invalid or reserved site name, or a file a site can't serve, for example), publish fails with camy.ai's own sentence and exit 1.

Human output links the new site's URL and reports how many files were published. See camy canvas publish.

camy canvas sites

camy canvas sites

Lists every site you've published: a mark, the site's name, a link to its custom domain if it has one (else its default URL), and which chat it came from. With no sites yet, it prints a plain note instead of an empty list. See camy canvas sites.

camy canvas sites rm

camy canvas sites rm NAME

Unpublishes a site: its URL stops serving. It asks for confirmation, worded around the fact that the URL goes dark.

--json:

{"ok": true, "site": "NAME", "deleted": true}

See camy canvas sites rm.

camy canvas versions

camy canvas versions SITE

Lists a site's archived publish versions, newest first: each version's name and, when the name is a date, how long ago it was archived. With no archived versions — only the current live publish exists — it prints a note rather than an empty list.

A version is usually named by the UTC time it was archived, such as 20260920T100000Z. That name is what camy canvas rollback takes, and the list ends by suggesting a rollback to the newest one.

--json gives one object per version. created_at is only present when the version's name is a date:

[{"version": "20260920T100000Z", "created_at": "2026-09-20T10:00:00Z"}]

See camy canvas versions.

camy canvas rollback

camy canvas rollback SITE
camy canvas rollback SITE VERSION

Rolls a published site back to an earlier version: one publish back if you leave VERSION off (the server picks "one back," not the CLI), or to a specific version if you name one from camy canvas versions. It asks for confirmation, worded around exactly what it's about to do. It starts a stopped workspace first, and refuses outright when you have no workspace; see Sites and your workspace.

Human output confirms the site is now serving the older version, and names it when the service reports which one it restored: rolled SITE back to VERSION — the site serves that version. See camy canvas rollback.

Custom domain

camy canvas domain

camy canvas domain SITE

Shows a site's domain state:

  • No custom domain attached: it says so, and tells you how to set one.
  • A domain attached: it prints the domain.
  • A domain still pending verification: it prints the pending domain, the exact DNS TXT record name and value you need to add, and a reminder to run camy canvas domain verify once the record is in place.

Anything else the server has to say about the domain — a note about certificate issuance, or DNS instructions — is printed too. See camy canvas domain.

camy canvas domain set

camy canvas domain set SITE DOMAIN
camy canvas domain set SITE --clear

Attaches a custom domain to a site, or removes one with --clear (which takes no DOMAIN). Human output reports the new status, then renders the domain state exactly as plain camy canvas domain does — including the TXT record, if one is now pending.

See camy canvas domain set.

camy canvas domain verify

camy canvas domain verify SITE

Checks the pending DNS TXT challenge. The server can answer with an ordinary success response that still says "not verified" — a miss doesn't have to look like a failure.

So read the result, not just whether the command exited cleanly: a genuine pass prints an "ok" line naming the verified domain, anything else prints a "not yet" line with the reason. Under it come the TXT record name and value the service still expects, and a found line with any TXT records DNS did return, so you can tell a record that hasn't appeared yet from one with the wrong value. See camy canvas domain verify.

Access control

camy canvas access

camy canvas access SITE

Shows a site's access mode, whether a passcode is set, and whether the site is allowed to be indexed by search engines.

If the mode isn't public but the server hasn't switched enforcement on for that mode yet, the CLI adds a loud NOT ENFORCED warning: the site is still reachable by anyone, despite the mode you set. The mode: line is still printed, but never on its own over a site the server is still serving publicly.

See camy canvas access.

camy canvas access set

camy canvas access set SITE public
camy canvas access set SITE passcode --passcode-stdin

Sets a site's access mode. MODE must be public, passcode, or unlisted — but the command's own help text is explicit that only public is available today. passcode and unlisted aren't, even though the CLI accepts the value and sends it on.

A passcode is never taken as a command-line argument or flag value — that would land in your shell history. For MODE=passcode, give it one of two ways:

  • --passcode-stdin — read up to 4096 bytes from stdin, trimmed.
  • Otherwise, an interactive prompt on your terminal (not stdin). Running headless without --passcode-stdin exits 2 asking for one.

A passcode must be at least 6 characters, however it arrives — typed at the prompt or piped in with --passcode-stdin. Leaving it blank at the prompt, or piping nothing, means "keep the current one." --clear-passcode removes a stored passcode, but only takes effect when MODE is public.

The NOT ENFORCED warning above applies after access set too, when it is relevant. See camy canvas access set.

camy canvas indexable

camy canvas indexable SITE on
camy canvas indexable SITE off

Turns search-engine indexing for a published site on or off. The second argument must be literally on or off. See camy canvas indexable.

Exporting the canvas

camy canvas export

camy canvas export zip
camy canvas export pwa -o my-app-pwa.zip

Downloads the canvas as a packaged app instead of individual files. FORMAT must be pwa, capacitor, or zip.

-o/--output sets the destination path. Leave it out and the CLI uses the filename the server suggests, sanitized and stripped of any path components first. If the server suggests nothing usable, the CLI falls back to FORMAT.zip in the current directory: zip.zip, pwa.zip, capacitor.zip.

The destination is protected the same way camy download protects a download. A target that already exists as a symlink is refused outright, and --force doesn't override that; an ordinary existing file is refused unless you pass --force. With -o, that check runs before anything is exported, so a refused destination costs no export; a name the server suggests can only be checked once the download is in.

--json:

{"ok": true, "format": "zip", "chat_id": "<full chat id>", "path": "camy-canvas.zip", "bytes": 48213}

See camy canvas export.

Examples

Look at a canvas, then pull it down and open one file:

camy canvas --chat abc12345 files
camy canvas --chat abc12345 pull ./my-app
cat ./my-app/index.html

Snapshot before letting the agent make a risky change, then roll back if it goes wrong:

camy canvas snapshot "before the redesign"
camy canvas restore <snapshot-id> --force

Publish, check what's live, then roll back to the previous version:

camy canvas publish
camy canvas sites
camy canvas rollback my-app --force

Attach a custom domain and verify it once DNS is in place:

camy canvas domain set my-app example.com
camy canvas domain my-app
camy canvas domain verify my-app

Export a zip in CI, overwriting any previous build (the output directory must already exist):

camy canvas --chat abc12345 export zip -o build/app.zip --force

Confirmation and --force

A handful of camy canvas commands are destructive enough to ask first — current files replaced, a live URL going dark, unreviewed code about to run.

All of them follow the same rule: on a real terminal, they prompt y/N; -f/--force skips the prompt; running headless (--no-input, or no controlling terminal) without --force fails closed with exit 2 rather than guessing.

Command What you're confirming
camy canvas restore current canvas files are replaced by the snapshot
camy canvas preview unreviewed, model-generated code runs locally, immediately
camy canvas publish the exact host it's about to claim (shown to you first)
camy canvas sites rm the site's URL goes dark
camy canvas rollback the site starts serving an older version — one publish back, or the one you named

Every other camy canvas command — files, cat, pull, open, sites, snapshot, snapshots, versions, domain, domain set, domain verify, access, access set, indexable, export — runs without asking, though pull and export each still refuse to silently overwrite a file unless you pass --force.

--json output

snapshots and sites emit the raw row array the server returned. files, pull, sites rm, versions, and export emit a shape the CLI builds, shown with each of those commands above.

Everything else that takes --json emits the server's response object as-is: snapshot, restore, publish, rollback, domain, domain set, domain verify, access, access set, and indexable. Three fields in those objects are worth naming:

Command Field
publish site_url and files_published
access enforcement_ready, which the human view's NOT ENFORCED warning reads
domain verify verified — the one to check, since the command can exit cleanly and still report a domain as unverified

cat and preview have no --json form: cat always prints the file itself, and preview is an interactive action, not a data command.

Exit codes

camy canvas commands use the CLI's normal exit-code table — see Exit codes. The cases specific to this area:

Situation Exit
No --chat, and no last chat to fall back to 2
A --chat or snapshot ref under four characters 2
An ambiguous chat or snapshot prefix 2
open given a short chat id that matches none of your chats 2
A bad MODE or FORMAT 2
A missing passcode, or one under six characters 2
domain set given both --clear and a DOMAIN, or neither 2
A symlink destination refused by pull or export 2
A filename cat can't find 1
An empty canvas 1
No HTML entry point for preview 1
sites or versions on a stopped or stopping workspace (when listing would start it) 7
rollback with no workspace 7

See also