Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fuzzy-canvases-create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sketchi/cli": minor
---

Add the noninteractive `sketchi canvas` command for typed Universal CanvasSpec creation, local persistence, and artifact export through the public production API.
27 changes: 24 additions & 3 deletions apps/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

Sketchi stores canonical diagram documents and authoritative Excalidraw artifacts
under `~/.sketchi/diagrams`. Its manual and recovery workflows stay local;
generation and encrypted snapshot exchange are explicit, credential-free network
generation, Universal Canvas compilation, and encrypted snapshot exchange are explicit, credential-free network
commands.

## Install
Expand Down Expand Up @@ -83,7 +83,27 @@ sketchi generate --prompt "Map release approval" --output json
`--format` accepts `png`, `excalidraw`, or `scene`; their default names are
`<id>.png`, `<id>.excalidraw`, and `<id>.scene.json`. With `--dest -`, stdout
contains artifact bytes only and the text or JSON status envelope moves to
stderr. Generation, sharing, and pulling are the CLI's three network commands.
stderr. Generation, Universal Canvas compilation, sharing, and pulling are the CLI's four network commands.

## Build a Universal CanvasSpec

`canvas` sends an already-authored, typed CanvasSpec to Sketchi's public
create-canvas API, validates the response, preserves the normalized scene and
editable Excalidraw artifact in the local store, and exports PNG by default:

```sh
sketchi canvas --file canvas.json --output json
printf '%s' "$CANVAS_SPEC" | sketchi canvas --file - --format excalidraw --dest canvas.excalidraw
```

The input is the CanvasSpec object itself—not a request wrapper and not raw
Excalidraw JSON. The command never prompts, so files and piped stdin are safe for
agents and scripts. `--format` and `--dest` have the same behavior as `generate`.
The endpoint defaults to
`https://playground.sketchi.app/api/v1/canvases/create`; use `--endpoint URL` or
`SKETCHI_CANVAS_ENDPOINT` only for preview and local testing. The existing
`create` command remains the separate, strictly offline command for accepted
flowchart, mindmap, and sequence documents.

## Create and work with a diagram offline

Expand Down Expand Up @@ -259,7 +279,7 @@ to human text TTYs and is never enabled by JSON output, pipes, redirects, or CI.
After exporting PNG to a file, agents can follow the returned hint to display
that path as an inline Markdown image for the user.

`generate`, `share`, and `pull` are deliberately separate, explicit network
`generate`, `canvas`, `share`, and `pull` are deliberately separate, explicit network
commands. They remain credential-free and each makes one HTTPS request. Share
is randomized by its key and IV; pull depends on remote availability and
untrusted input, so neither belongs to determinism claims.
Expand All @@ -279,6 +299,7 @@ sketchi docs
sketchi create --help
sketchi patch --help
sketchi generate --help
sketchi canvas --help
sketchi share --help
sketchi pull --help
sketchi restore --help
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/scripts/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ const packageManifest = {
name: "sketchi",
version,
description:
"Local Sketchi authoring CLI with six offline authoring and recovery commands plus three credential-free network commands for generation and encrypted Excalidraw sharing.",
"Local Sketchi authoring CLI with offline workflows and four credential-free network commands for generation, Universal Canvas compilation, and encrypted Excalidraw sharing.",
keywords: ["sketchi", "diagram", "flowchart", "mindmap", "excalidraw", "cli"],
homepage: "https://sketchi.app",
repository: {
Expand Down
24 changes: 17 additions & 7 deletions apps/cli/src/__fixtures__/help/agent-docs.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
Sketchi CLI contracts for agents and automation.

Command map:
Start with generate for prompt-to-PNG. Use show, edit, patch, list, restore, and export for
local records. Use create for accepted canonical JSON. Share and pull are explicit network
Start with generate for prompt-to-PNG or canvas for an authored CanvasSpec. Use show, list,
and export for every local record. Edit and patch support canonical flowchart, mindmap, and
sequence records. Use create for accepted canonical JSON.
Share and pull are explicit network
boundaries for Excalidraw links. Run sketchi COMMAND --help for every flag and example.

Manual JSON workflow (strictly offline):
Expand All @@ -19,11 +21,15 @@ Canonical mindmap example:
Canonical sequence example:
{"type":"sequence","spec":{"id":"checkout-sequence","title":"Checkout sequence","participants":[{"id":"browser","label":"Browser"},{"id":"api","label":"API"}],"messages":[{"id":"request","source":"browser","target":"api","label":"Submit checkout"}]}}

Universal CanvasSpec example:
{"kind":"canvas","version":1,"diagramId":"release-board","title":"Release board","width":640,"height":360,"accentColor":"#2563eb","backgroundColor":"#ffffff","elements":[{"type":"node","id":"ready","nodeId":"ready","shape":"rectangle","x":40,"y":40,"width":240,"height":120,"label":"Ready to release"}],"layers":[],"layouts":[],"zOrder":["ready"]}

Semantic color patch example:
sketchi patch release-flow --json '{"operations":[{"op":"setStyle","selector":{"nodeIds":["review","approve"]},"style":{"fillColor":"#dbeafe","strokeColor":"#2563eb","textColor":"#1e3a8a"}}]}'

Explicit network commands (one credential-free HTTPS request each):
sketchi generate [--prompt TEXT] [--type flowchart|mindmap|sequence|er|architecture|swimlane|state-machine] [--model MODEL]
sketchi canvas --file PATH|- [--format png|excalidraw|scene] [--dest PATH|-]
sketchi share DIAGRAM_ID [--open]
sketchi pull DIAGRAM_ID --link URL|-
generate makes one unauthenticated HTTPS POST to the public Sketchi generate API at https://playground.sketchi.app/api/v1/generate
Expand All @@ -32,9 +38,12 @@ Explicit network commands (one credential-free HTTPS request each):
through the same local store as create. Without --type, the model selects a supported native type; the default model is
gemini-3.1-flash-lite. Override the endpoint for preview or local testing with
SKETCHI_GENERATE_ENDPOINT or --endpoint URL.
canvas submits the CanvasSpec object itself to https://playground.sketchi.app/api/v1/canvases/create, validates the typed
response, stores the normalized scene and Excalidraw artifact, and exports locally. Override its
endpoint with SKETCHI_CANVAS_ENDPOINT or --endpoint URL.

Input and output contracts:
create/edit/patch require exactly one of --file PATH|- or --json VALUE. --file - reads one
create/edit/patch/canvas require exactly one of --file PATH|- or --json VALUE. --file - reads one
noninteractive UTF-8 JSON document from stdin and exits with usage code 2 on a TTY.
--json is inline input only. --prompt is always direct and noninteractive. With no --prompt,
generate opens a short prompt/type/PNG-destination wizard only when stdin and stdout are human
Expand Down Expand Up @@ -66,7 +75,7 @@ Offline boundary and storage:
A pulled record is detached: diagram.excalidraw is authoritative while document.json and
scene.json remain non-authoritative provenance. A patched record makes scene.json authoritative
while retaining document.json as provenance. show/list expose these states; edit refuses both.
Formats are scene, excalidraw, and png. generate persists the canonical record before exporting
Formats are scene, excalidraw, and png. generate and canvas persist the local record before exporting
the requested artifact; a later destination failure leaves that record recoverable. If no PNG is
stored, export deterministically renders one on demand from the local scene and Excalidraw artifacts,
without a browser, network, or record write.
Expand All @@ -75,13 +84,14 @@ Exit codes and errors:
0 success; 1 internal failure; 2 usage or interactive stdin; 3 invalid input/document;
4 build/export construction failure; 5 diagram not found; 6 conflict/busy;
7 filesystem/storage failure; 8 unavailable format, render, destination, or write failure;
10 generate network/endpoint failure; 11 generate timeout; 12 malformed generate output;
10 generate/canvas network or endpoint failure; 11 generate timeout; 12 malformed remote output;
13 Excalidraw transport, timeout, HTTP, or API-shape failure.
Text errors start with "error: CODE". JSON errors use {"ok":false,"command":...,"error":...}.
Errors never include stacks.

Revision recovery and next steps:
edit, pull, and restore archive the complete prior authority state before atomic replacement.
Use restore --revision N to recover through the CLI without consuming the selected snapshot.
Start with generate for a prompt or create for accepted JSON. Use the returned id with show/list,
edit with a complete replacement document, then export another stored artifact when needed.
Start with generate for a prompt, canvas for CanvasSpec, or create for accepted canonical JSON.
Every returned id supports show, list, and export. Edit and patch are only for flowchart,
mindmap, and sequence records; create a new canvas to replace a Universal CanvasSpec.
42 changes: 42 additions & 0 deletions apps/cli/src/__fixtures__/help/canvas.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
DESCRIPTION
Build a complete Universal CanvasSpec through Sketchi's public create-canvas API, preserve the validated scene and Excalidraw result in the local store, and export PNG by default. This command is direct and noninteractive: pass exactly one CanvasSpec with --file PATH, --file - for piped stdin, or --json VALUE.

Network and options:
Endpoint: https://playground.sketchi.app/api/v1/canvases/create
Override it for preview or local testing with SKETCHI_CANVAS_ENDPOINT or --endpoint URL.
The request asks the server for scene and Excalidraw artifacts, validates the typed response,
stores the normalized CanvasSpec plus Excalidraw output, then exports locally. --format defaults
to png; --dest defaults to <diagramId>.png, <diagramId>.excalidraw, or
<diagramId>.scene.json. Use --dest - for artifact-only stdout and --output json for a stable
status envelope on stderr.

Input and failures:
Input must be the CanvasSpec object itself, not a create-canvas request wrapper and not raw
Excalidraw JSON. Interactive stdin is rejected with exit 2. Invalid JSON or CanvasSpec exits 3;
a typed server rejection exits 4; network/endpoint failure exits 10; malformed response exits 12.
The local record is committed before export, so a destination failure reports a concrete
sketchi export recovery command. The existing sketchi create command remains the strictly
offline path for accepted flowchart, mindmap, and sequence documents.

USAGE
sketchi canvas [flags]

FLAGS
--output text|json Result presentation format. (choices: text, json)
--file PATH|- Read one CanvasSpec document from PATH, or stdin with -.
--json VALUE Read one CanvasSpec document from inline JSON.
--endpoint URL Unauthenticated create-canvas API URL; defaults to production.
--format png|excalidraw|scene Artifact exported after the canvas is created. (choices: png, excalidraw, scene)
--dest PATH|- Artifact destination; defaults from diagramId, or - for stdout.

GLOBAL FLAGS
--help, -h Show help information
--version, -v Show version information
--completions <bash|zsh|fish|sh> Print shell completion script (choices: bash, zsh, fish, sh)

EXAMPLES
# Build a CanvasSpec, store it locally, and write its PNG.
sketchi canvas --file canvas.json --output json

# Read CanvasSpec from stdin and export editable Excalidraw.
sketchi canvas --file - --format excalidraw --dest canvas.excalidraw
2 changes: 1 addition & 1 deletion apps/cli/src/__fixtures__/help/generate.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
DESCRIPTION
Create one persisted diagram and export its PNG by default. With no --prompt, Sketchi opens a short wizard only when stdin and stdout are human TTYs, output is text, and CI is absent. Pipes, redirects, CI, and --output json never prompt or block; pass --prompt for every script and automation path. This is one of Sketchi's three explicit network commands (generate, share, pull). It makes one unauthenticated HTTPS POST to the public Sketchi generate API and needs no token, key, account, or login.
Create one persisted diagram and export its PNG by default. With no --prompt, Sketchi opens a short wizard only when stdin and stdout are human TTYs, output is text, and CI is absent. Pipes, redirects, CI, and --output json never prompt or block; pass --prompt for every script and automation path. This is one of Sketchi's four explicit network commands (generate, canvas, share, pull). It makes one unauthenticated HTTPS POST to the public Sketchi generate API and needs no token, key, account, or login.

Everyday wizard:
The wizard asks only for prompt text, flowchart (default), mind map, or sequence diagram, and a PNG destination.
Expand Down
1 change: 1 addition & 0 deletions apps/cli/src/__fixtures__/help/root.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ START HERE
sketchi generate interactive
sketchi generate --prompt "Map release approval with pass and revise branches"
Writes <generated-id>.png in this directory. No account or API key needed.
canvas Build and export a typed Universal CanvasSpec.

WORK WITH A DIAGRAM
show Inspect a local diagram.
Expand Down
19 changes: 17 additions & 2 deletions apps/cli/src/audit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe("CLI dependency and public-surface audit", () => {
assert.notInclude(source, "NormalizedMindmapSchema");
});

it("confines network boundaries to the three credential-free HTTPS commands", async () => {
it("confines network boundaries to the four credential-free HTTPS commands", async () => {
const files = await sourceFiles(join(workspaceRoot, "apps/cli/src"));
const fetchFiles: string[] = [];
let cliSource = "";
Expand All @@ -93,7 +93,11 @@ describe("CLI dependency and public-surface audit", () => {
}
assert.deepStrictEqual(
fetchFiles.map((file) => file.slice(workspaceRoot.length + 1)),
["apps/cli/src/generation.ts", "apps/cli/src/share.ts"],
[
"apps/cli/src/canvas.ts",
"apps/cli/src/generation.ts",
"apps/cli/src/share.ts",
],
);
assert.notInclude(cliSource, "GOOGLE_GENERATIVE_AI_API_KEY");
assert.notInclude(cliSource, "CF_AIG_TOKEN");
Expand All @@ -110,6 +114,15 @@ describe("CLI dependency and public-surface audit", () => {
);
assert.notInclude(generationSource, "@sketchi/diagram-generation");

const canvasSource = await readFile(
join(workspaceRoot, "apps/cli/src/canvas.ts"),
"utf8",
);
assert.include(
canvasSource,
"https://playground.sketchi.app/api/v1/canvases/create",
);

const shareProtocolSource = await readFile(
join(workspaceRoot, "apps/cli/src/share-protocol.ts"),
"utf8",
Expand Down Expand Up @@ -152,6 +165,7 @@ describe("CLI dependency and public-surface audit", () => {
);
assert.deepStrictEqual(commands, [
"generate",
"canvas",
"docs",
"create",
"show",
Expand Down Expand Up @@ -206,6 +220,7 @@ describe("CLI dependency and public-surface audit", () => {
assert.notInclude(bundle, "sourceMappingURL");
assert.notInclude(bundle, "CF_AIG_TOKEN");
assert.include(bundle, "playground.sketchi.app/api/v1/generate");
assert.include(bundle, "playground.sketchi.app/api/v1/canvases/create");
assert.include(readme, "Typed, deterministic diagrams from your terminal.");
assert.include(readme, "npm install -g sketchi");
});
Expand Down
Loading
Loading