stackcanvas 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -18
- package/dist/cli.js +22 -6
- package/package.json +22 -3
- package/ui-dist/assets/index-Cc5cRXYi.js +88 -0
- package/ui-dist/assets/{index-C-khjgwU.css → index-DY2i4Onu.css} +1 -1
- package/ui-dist/index.html +2 -2
- package/src/server.ts +0 -104
- package/ui-dist/assets/index-BLiLN0HR.js +0 -88
package/README.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# stackcanvas
|
|
2
2
|
|
|
3
|
-
[Telemetry: opt-in, anonymous, no infra data — see TELEMETRY.md](TELEMETRY.md)
|
|
4
|
-
|
|
5
3
|
Live infrastructure canvas for [Claude Code](https://claude.com/claude-code).
|
|
6
4
|
The agent writes and plans your Terraform — stackcanvas shows it as a living
|
|
7
5
|
diagram. Drag new resources onto the canvas; the agent turns them into
|
|
@@ -16,7 +14,7 @@ localhost, reading your local state and plan.
|
|
|
16
14
|
2. The graph re-renders live whenever `*.tfstate` or `.stackcanvas/plan.json`
|
|
17
15
|
change — you watch the agent work.
|
|
18
16
|
3. You drag resources from the palette (or right-click existing ones to
|
|
19
|
-
request changes / removal) and hit **
|
|
17
|
+
request changes / removal) and hit **Send to agent**. Connections between two
|
|
20
18
|
not-yet-created (draft) resources aren't included in the intent yet —
|
|
21
19
|
connect drafts to existing resources, or describe the relation in the
|
|
22
20
|
draft's wishes field.
|
|
@@ -30,10 +28,6 @@ localhost, reading your local state and plan.
|
|
|
30
28
|
claude plugin marketplace add pshenok/stackcanvas
|
|
31
29
|
claude plugin install stackcanvas@stackcanvas
|
|
32
30
|
|
|
33
|
-
or without the plugin system:
|
|
34
|
-
|
|
35
|
-
claude mcp add stackcanvas -- npx -y stackcanvas
|
|
36
|
-
|
|
37
31
|
Then, inside a repo with Terraform:
|
|
38
32
|
|
|
39
33
|
/stackcanvas
|
|
@@ -41,6 +35,13 @@ Then, inside a repo with Terraform:
|
|
|
41
35
|
This is the verified path — the CI `check-plugin` job validates the plugin
|
|
42
36
|
and marketplace manifests on every push.
|
|
43
37
|
|
|
38
|
+
Or without the plugin system:
|
|
39
|
+
|
|
40
|
+
claude mcp add stackcanvas -- npx -y stackcanvas
|
|
41
|
+
|
|
42
|
+
Then, inside a repo with Terraform, just ask: *open the stackcanvas canvas
|
|
43
|
+
for this repo*.
|
|
44
|
+
|
|
44
45
|
## Other MCP clients
|
|
45
46
|
|
|
46
47
|
stackcanvas is a standard [MCP](https://modelcontextprotocol.io) server, so
|
|
@@ -77,11 +78,14 @@ What's provider-specific is only the curation layer, shipped as **provider packs
|
|
|
77
78
|
|
|
78
79
|
- a palette pack (`packages/ui/src/resource-palette.ts`) — curated drag-and-drop types
|
|
79
80
|
- containment rules (`DEFAULT_CONTAINMENT_RULES` in `@stackcanvas/core`) — which
|
|
80
|
-
resources render as visual containers (AWS VPC/subnet
|
|
81
|
+
resources render as visual containers (AWS VPC/subnet, GCP network/subnetwork,
|
|
82
|
+
Azure subnet, Cloudflare zone today)
|
|
81
83
|
- icon patterns (`packages/ui/src/icons.tsx`)
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
Four packs ship today: **AWS** (complete — the reference pack) and **GCP /
|
|
86
|
+
Azure / Cloudflare starter packs** covering the common resource types per
|
|
87
|
+
provider. Rounding out a starter pack, or adding a new provider entirely, is
|
|
88
|
+
pure data and makes a great first PR.
|
|
85
89
|
|
|
86
90
|
## OpenTofu
|
|
87
91
|
|
|
@@ -98,7 +102,7 @@ to pin it — both take precedence over auto-detection.
|
|
|
98
102
|
| `open_canvas` | Start the canvas for a Terraform root, open the browser |
|
|
99
103
|
| `load_plan` | Register a plan (JSON or binary) for diff highlighting |
|
|
100
104
|
| `get_graph_summary` | Text summary of the graph for the agent |
|
|
101
|
-
| `await_canvas_intent` | Block until the user clicks
|
|
105
|
+
| `await_canvas_intent` | Block until the user clicks Send to agent; returns their edits |
|
|
102
106
|
|
|
103
107
|
## Demo
|
|
104
108
|
|
|
@@ -106,13 +110,14 @@ to pin it — both take precedence over auto-detection.
|
|
|
106
110
|
|
|
107
111
|
## Telemetry
|
|
108
112
|
|
|
109
|
-
stackcanvas can send a handful of anonymous
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
stackcanvas can send a handful of anonymous (pseudonymous install id — see
|
|
114
|
+
TELEMETRY.md) usage counters (installs, canvases opened, intents sent;
|
|
115
|
+
scan/drift counters reserved — five ever, see TELEMETRY.md) — **opt-in
|
|
116
|
+
only**, nothing is sent until you click **Allow** on the one-time canvas
|
|
117
|
+
banner, and `DO_NOT_TRACK=1` / `STACKCANVAS_TELEMETRY=0` always turn it off
|
|
118
|
+
regardless of that decision. No resource names, infrastructure data, or file
|
|
119
|
+
paths ever leave your machine. Full payload, consent model, and how to
|
|
120
|
+
verify it yourself: [TELEMETRY.md](TELEMETRY.md).
|
|
116
121
|
|
|
117
122
|
## Development
|
|
118
123
|
|
package/dist/cli.js
CHANGED
|
@@ -40,7 +40,15 @@ function deriveEdges(nodes) {
|
|
|
40
40
|
}
|
|
41
41
|
var DEFAULT_CONTAINMENT_RULES = [
|
|
42
42
|
{ containerType: "aws_vpc", memberAttr: "vpc_id", kind: "vpc" },
|
|
43
|
-
{ containerType: "aws_subnet", memberAttr: "subnet_id", kind: "subnet" }
|
|
43
|
+
{ containerType: "aws_subnet", memberAttr: "subnet_id", kind: "subnet" },
|
|
44
|
+
// GCP networks/subnetworks are referenced by `self_link`, not `id`.
|
|
45
|
+
{ containerType: "google_compute_network", memberAttr: "network", kind: "vpc", containerIdAttr: "self_link" },
|
|
46
|
+
{ containerType: "google_compute_subnetwork", memberAttr: "subnetwork", kind: "subnet", containerIdAttr: "self_link" },
|
|
47
|
+
{ containerType: "azurerm_subnet", memberAttr: "subnet_id", kind: "subnet" },
|
|
48
|
+
// Cloudflare zones aren't a network container, but they group the
|
|
49
|
+
// resources scoped to them the same way a VPC groups its members — reuse
|
|
50
|
+
// the 'vpc' kind rather than introducing a one-off styling kind.
|
|
51
|
+
{ containerType: "cloudflare_zone", memberAttr: "zone_id", kind: "vpc" }
|
|
44
52
|
];
|
|
45
53
|
function deriveContainment(g, rules = DEFAULT_CONTAINMENT_RULES) {
|
|
46
54
|
const groups = [...g.groups];
|
|
@@ -48,7 +56,7 @@ function deriveContainment(g, rules = DEFAULT_CONTAINMENT_RULES) {
|
|
|
48
56
|
for (const rule of rules) {
|
|
49
57
|
const groupByPhysicalId = /* @__PURE__ */ new Map();
|
|
50
58
|
for (const n of nodes.filter((n2) => n2.type === rule.containerType)) {
|
|
51
|
-
const pid = n.attributes["id"];
|
|
59
|
+
const pid = n.attributes[rule.containerIdAttr ?? "id"];
|
|
52
60
|
if (typeof pid !== "string") continue;
|
|
53
61
|
const gid = `${rule.kind}:${n.id}`;
|
|
54
62
|
groups.push({ id: gid, label: n.name, kind: rule.kind, parent: n.group });
|
|
@@ -926,7 +934,7 @@ function openBrowser(url) {
|
|
|
926
934
|
}
|
|
927
935
|
|
|
928
936
|
// src/version.ts
|
|
929
|
-
var VERSION = "0.2.
|
|
937
|
+
var VERSION = "0.2.2";
|
|
930
938
|
|
|
931
939
|
// src/server.ts
|
|
932
940
|
function looksLikeTerraformRoot(dir) {
|
|
@@ -966,7 +974,11 @@ function createMcpServer(deps = {}) {
|
|
|
966
974
|
canvas.setAgentStatus("idle");
|
|
967
975
|
open(url);
|
|
968
976
|
}
|
|
969
|
-
|
|
977
|
+
const stale = canvas.getStale();
|
|
978
|
+
const warning = stale === null ? "" : `
|
|
979
|
+
|
|
980
|
+
WARNING: the graph may be incomplete \u2014 ${stale}. Tell the user.`;
|
|
981
|
+
return ok(`Canvas running at ${url}. The graph live-updates as tfstate changes. Run \`terraform plan -out=tfplan && terraform show -json tfplan > .stackcanvas/plan.json\` (or \`tofu plan \u2026\` with OpenTofu) to show the plan diff, then call await_canvas_intent to receive user edits.` + warning);
|
|
970
982
|
});
|
|
971
983
|
mcp.registerTool("load_plan", {
|
|
972
984
|
description: "Register a Terraform plan for diff highlighting on the canvas. Accepts a JSON plan (terraform show -json output) or a binary plan file.",
|
|
@@ -988,10 +1000,14 @@ function createMcpServer(deps = {}) {
|
|
|
988
1000
|
inputSchema: {}
|
|
989
1001
|
}, async () => {
|
|
990
1002
|
if (!canvas) return fail("No canvas open. Call open_canvas first.");
|
|
991
|
-
|
|
1003
|
+
const stale = canvas.getStale();
|
|
1004
|
+
const warning = stale === null ? "" : `
|
|
1005
|
+
|
|
1006
|
+
WARNING: the graph may be incomplete \u2014 ${stale}. Tell the user.`;
|
|
1007
|
+
return ok(summarizeGraph(canvas.getGraph()) + warning);
|
|
992
1008
|
});
|
|
993
1009
|
mcp.registerTool("await_canvas_intent", {
|
|
994
|
-
description: "Block until the user clicks
|
|
1010
|
+
description: "Block until the user clicks Send-to-agent on the canvas, then return their requested changes as intent JSON: {intent: {add, modify, remove} | null}. null = timeout, call again in a loop to keep waiting. After receiving an intent, write the Terraform code for it. After implementing an intent, run terraform plan (write the JSON to .stackcanvas/plan.json) and call this tool again to keep the loop alive. Never run terraform apply unless the human explicitly asks.",
|
|
995
1011
|
inputSchema: {
|
|
996
1012
|
timeoutSeconds: z2.number().positive().max(3600).default(45).describe("How long to wait before returning {intent: null}. Default 45s: MCP clients commonly abort requests after 60s, so keep this under your client's request timeout and call the tool in a loop instead of passing large values.")
|
|
997
1013
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stackcanvas",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "dist/cli.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"stackcanvas": "dist/cli.js"
|
|
8
8
|
},
|
|
@@ -28,5 +28,24 @@
|
|
|
28
28
|
"@stackcanvas/server": "workspace:*",
|
|
29
29
|
"@types/node": "^22.0.0",
|
|
30
30
|
"tsup": "^8.0.0"
|
|
31
|
-
}
|
|
31
|
+
},
|
|
32
|
+
"description": "Live infrastructure canvas for Claude Code: see what your AI agent is about to do to your Terraform \u2014 and approve it \u2014 before it happens. Local-first, credentials never leave your machine.",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/pshenok/stackcanvas.git"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://stackcanvas.dev",
|
|
39
|
+
"bugs": "https://github.com/pshenok/stackcanvas/issues",
|
|
40
|
+
"author": "Kostyantyn Pshenychnyy",
|
|
41
|
+
"keywords": [
|
|
42
|
+
"terraform",
|
|
43
|
+
"opentofu",
|
|
44
|
+
"mcp",
|
|
45
|
+
"claude-code",
|
|
46
|
+
"infrastructure-as-code",
|
|
47
|
+
"devtools",
|
|
48
|
+
"visualization",
|
|
49
|
+
"ai-agent"
|
|
50
|
+
]
|
|
32
51
|
}
|