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
45 changes: 45 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Contributing to stackcanvas

## Dev setup

```
git clone https://github.com/pshenok/stackcanvas.git
cd stackcanvas
npx pnpm@9 install
npx pnpm@9 test # unit + integration
npx pnpm@9 e2e # playwright smoke (builds the UI first)
```

Node >=20. This is a pnpm workspace (`packages/core`, `packages/server`,
`packages/ui`, `packages/mcp`); `telemetry-collector/` is a standalone
Lambda deliberately outside the workspace — see its own README for `npm`
setup.

## Best first PR: a provider pack

Adding a cloud provider is pure data, no core changes required. See the
"Multi-cloud" section of the [README](README.md#multi-cloud) for the three
files a pack touches:

- `packages/ui/src/resource-palette.ts` — curated drag-and-drop types
- `DEFAULT_CONTAINMENT_RULES` in `@stackcanvas/core` — which resources
render as visual containers
- `packages/ui/src/icons.tsx` — icon patterns

AWS is the only pack that exists today; GCP or Azure are good starting
points.

## PR conventions

- Commit messages and PR titles follow [Conventional
Commits](https://www.conventionalcommits.org/) (`fix:`, `feat:`,
`chore:`, etc.).
- New behavior needs a test — unit/integration under the relevant
`packages/*`, or a Playwright case under `e2e/` for UI flows.
- CI must be green (`unit`, `it-tf`, `check-plugin`, and the other jobs in
`.github/workflows/ci.yml`) before merge.
- Keep PRs scoped to one change; large refactors are easier to review split
up.

Design context for anything non-obvious lives in [`docs/SPEC.md`](docs/SPEC.md),
[`docs/PROBLEM.md`](docs/PROBLEM.md), and [`docs/VISION.md`](docs/VISION.md).
32 changes: 17 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# stackcanvas

[Telemetry: opt-in, anonymous, no infra data — see TELEMETRY.md](TELEMETRY.md)

Live infrastructure canvas for [Claude Code](https://claude.com/claude-code).
The agent writes and plans your Terraform — stackcanvas shows it as a living
diagram. Drag new resources onto the canvas; the agent turns them into
Expand All @@ -16,7 +14,7 @@ localhost, reading your local state and plan.
2. The graph re-renders live whenever `*.tfstate` or `.stackcanvas/plan.json`
change — you watch the agent work.
3. You drag resources from the palette (or right-click existing ones to
request changes / removal) and hit **Apply**. Connections between two
request changes / removal) and hit **Send to agent**. Connections between two
not-yet-created (draft) resources aren't included in the intent yet —
connect drafts to existing resources, or describe the relation in the
draft's wishes field.
Expand All @@ -30,17 +28,20 @@ localhost, reading your local state and plan.
claude plugin marketplace add pshenok/stackcanvas
claude plugin install stackcanvas@stackcanvas

or without the plugin system:

claude mcp add stackcanvas -- npx -y stackcanvas

Then, inside a repo with Terraform:

/stackcanvas

This is the verified path — the CI `check-plugin` job validates the plugin
and marketplace manifests on every push.

Or without the plugin system:

claude mcp add stackcanvas -- npx -y stackcanvas

Then, inside a repo with Terraform, just ask: *open the stackcanvas canvas
for this repo*.

## Other MCP clients

stackcanvas is a standard [MCP](https://modelcontextprotocol.io) server, so
Expand Down Expand Up @@ -98,21 +99,22 @@ to pin it — both take precedence over auto-detection.
| `open_canvas` | Start the canvas for a Terraform root, open the browser |
| `load_plan` | Register a plan (JSON or binary) for diff highlighting |
| `get_graph_summary` | Text summary of the graph for the agent |
| `await_canvas_intent` | Block until the user clicks Apply; returns their edits |
| `await_canvas_intent` | Block until the user clicks Send to agent; returns their edits |

## Demo

`examples/demo` contains a small AWS config. Run `terraform init && terraform plan -out=tfplan && terraform show -json tfplan > .stackcanvas/plan.json` there and open the canvas to see create-highlighting. `plan` does not create or modify any resources — nothing is provisioned until `terraform apply` (note: the AWS provider still needs credentials and makes read-only API calls during plan).

## Telemetry

stackcanvas can send a handful of anonymous usage counters (installs,
canvases opened, edits sent, scans run) — **opt-in only**, nothing is sent
until you click **Allow** on the one-time canvas banner, and `DO_NOT_TRACK=1`
/ `STACKCANVAS_TELEMETRY=0` always turn it off regardless of that decision.
No resource names, infrastructure data, or file paths ever leave your
machine. Full payload, consent model, and how to verify it yourself:
[TELEMETRY.md](TELEMETRY.md).
stackcanvas can send a handful of anonymous (pseudonymous install id — see
TELEMETRY.md) usage counters (installs, canvases opened, intents sent;
scan/drift counters reserved — five ever, see TELEMETRY.md) — **opt-in
only**, nothing is sent until you click **Allow** on the one-time canvas
banner, and `DO_NOT_TRACK=1` / `STACKCANVAS_TELEMETRY=0` always turn it off
regardless of that decision. No resource names, infrastructure data, or file
paths ever leave your machine. Full payload, consent model, and how to
verify it yourself: [TELEMETRY.md](TELEMETRY.md).

## Development

Expand Down
19 changes: 19 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Security Policy

## Reporting a vulnerability

Please report security issues privately via [GitHub Security
Advisories](https://github.com/pshenok/stackcanvas/security/advisories/new)
for this repo — not a public issue or PR. We aim to respond within **72
hours**.

## Scope

- The local canvas server (`packages/server`) and its `/api/*` routes,
including the Host/Origin allowlist
- Terraform/OpenTofu state and plan parsing (`packages/core`)
- The telemetry pipeline: client (`packages/server/src/telemetry.ts`) and
collector (`telemetry-collector/`)

Out of scope: vulnerabilities in Terraform/OpenTofu themselves, or in cloud
provider APIs stackcanvas reads from.
13 changes: 8 additions & 5 deletions TELEMETRY.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ that already holds a `TelemetryClient` — they never touch the browser:
|---|---|---|
| `install` | The *first* time you ever click "Allow" (deduped forever via `installReportedAt`) | `TelemetryClient.setConsent(true)`, [`telemetry.ts`](packages/server/src/telemetry.ts) |
| `canvas_opened` | Every successful `CanvasServer.start()` — covers both `stackcanvas serve` and the MCP `open_canvas` tool (which calls `start()` once per new canvas, never again for a reused one) | [`canvas-server.ts`](packages/server/src/canvas-server.ts) |
| `intent_sent` | Every valid `POST /api/intent` (i.e. every **Apply** click on the canvas), counted by action kind *after* validation/normalization | [`canvas-server.ts`](packages/server/src/canvas-server.ts) |
| `intent_sent` | Every valid `POST /api/intent` (i.e. every **Send to agent** click on the canvas), counted by action kind *after* validation/normalization | [`canvas-server.ts`](packages/server/src/canvas-server.ts) |
| `scan_run` | Reserved for the live-scan feature; not emitted by any code yet | — |

`drift_opened` is the **one** browser-originated event (the drift lens is
Expand Down Expand Up @@ -171,7 +171,9 @@ never any resource identity.
- File paths, directory names, repo names
- IP addresses, hostnames, MAC addresses, or any device fingerprint
- Cloud account IDs, ARNs, or any other cloud-provider identifier
- Timestamps finer than a UTC **day**
- Client-sent timestamps finer than a UTC **day** (the collector records its
own arrival time server-side — see
[`telemetry-collector/schema.md`](telemetry-collector/schema.md))
- Anything not listed in the schema above — enforced by the "envelope
allowlist tripwire" test (see "How to verify" below)

Expand All @@ -187,9 +189,10 @@ validates every envelope against the same hard allowlist described above
which batches and delivers it to S3 as the system of record, see
[`telemetry-collector/schema.md`](telemetry-collector/schema.md) — and
responds `200 {ok: true}` on success / `400`/`413` otherwise. It stores no
cookies, reads no client IP, and has CORS closed (only this repo's own
server-side code calls it — never the browser directly, see "When events
fire" above).
cookies, the handler never reads the client IP (AWS edge/access logs may
record IPs at the infrastructure layer; we don't query them), and it has
CORS closed (only this repo's own server-side code calls it — never the
browser directly, see "When events fire" above).

**PostHog / third-party analytics were evaluated and rejected**: a
third-party processor breaks the "no cloud backend of ours, fully
Expand Down
Loading
Loading