@cloudgrid-io/mcp 0.6.0 → 0.7.0

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.
@@ -4,8 +4,9 @@ name: drop
4
4
  description: |
5
5
  Share an artifact and get a public URL. Use when the user wants to share,
6
6
  publish, send, or deploy an HTML page or file, or wants a link for a friend.
7
- Works with no login (anonymous, 7-day, claimable later) or signed in (published
8
- into the user's org, owned, 30-day). Inspirations only.
7
+ Works with no login (anonymous, claimable later) or signed in (published
8
+ into the user's org, owned). Re-plugging updates the same URL in place.
9
+ Inspirations only.
9
10
  argument-hint: "[file]"
10
11
  allowed-tools: Bash
11
12
  ---
@@ -50,18 +51,19 @@ curl -sS -X POST https://api.cloudgrid.io/api/v2/plug \
50
51
 
51
52
  The response is JSON (HTTP 201 for an anonymous drop). Read these fields:
52
53
 
53
- - `slug` — the entity's public slug. An anonymous inspiration's live link is
54
- `https://guest.cloudgrid.io/<slug>`. The response has no ready-made `url`;
55
- compose it from the slug. (Apps/agents would be `https://<slug>.<grid>.cloudgrid.io`,
56
- but anonymous drops are always inspirations.)
57
- - `claim_url` — the link to sign in and keep it past the 7-day expiry.
54
+ - `url` — the live link, composed by the server. Use it verbatim; never compose a
55
+ URL from the slug yourself.
56
+ - `entity_id` — the durable handle for updating this drop later (see re-plug below).
57
+ - `owner_token` — a JWT proving you made this drop. It lets you re-plug the drop
58
+ anonymously and doubles as the claim token. Its lifetime matches the drop's
59
+ expiry (default 7 days). Persist it together with `entity_id` and `url`.
60
+ - `claim_url` — the link to sign in and keep the drop past its expiry.
58
61
  - `claim_message` — a ready-to-show nudge to sign in and claim.
59
62
 
60
63
  ## If the user is signed in
61
64
 
62
65
  A signed-in user can publish into their own org instead of anonymously — the drop
63
- is owned, lasts 30 days, and needs no claim. Add their token and org to the same
64
- request:
66
+ is owned and needs no claim. Add their token and org to the same request:
65
67
 
66
68
  ```
67
69
  JWT=$(jq -r .jwt ~/.cloudgrid/credentials 2>/dev/null)
@@ -71,30 +73,63 @@ curl -sS -X POST https://api.cloudgrid.io/api/v2/plug \
71
73
  -F "artifact=@/path/to/index.html;type=text/html"
72
74
  ```
73
75
 
74
- A signed-in drop returns HTTP 202 with `slug` + `grid` and no `claim_url` (it is
75
- already owned). Its live link is `https://<grid>.cloudgrid.io/<slug>` for an
76
- inspiration. The org is taken from the `X-CloudGrid-Org` header — there is no
76
+ A signed-in drop returns HTTP 202 with `entity_id`, `url`, `slug` + `grid` and no
77
+ `claim_url` (it is already owned). Use the returned `url` as the live link — do
78
+ not compose it. The org is taken from the `X-CloudGrid-Org` header — there is no
77
79
  `org_slug` form field. If the user is a member of several orgs and the header is
78
80
  missing, the API replies with the list of orgs to choose from. The MCP
79
81
  `cloudgrid_drop` tool does this automatically when credentials are present.
80
82
 
81
- ## One drop = one entity (no in-place re-drop)
83
+ ## Re-plug: update the same URL in place
82
84
 
83
- The unified `/api/v2/plug` endpoint is create-only for this flow: every drop mints
84
- a NEW entity with a NEW URL. There is no `previous_id` / in-place-update path and no
85
- `202 unchanged` no-op — those belonged to the retired `/drop/auto` endpoint. To
86
- publish an update, drop again; you get a fresh link. (To update an existing app in
87
- place, sign in and use `cloudgrid:plug`, which targets the entity by name.)
85
+ `/api/v2/plug` is a unified create-or-update endpoint. The switch is the
86
+ `target_entity_id` form field:
88
87
 
89
- The MCP `cloudgrid_drop` tool accepts a `fresh` flag for backward compatibility,
90
- but it is now a no-op — each drop is already a fresh create.
88
+ - **Absent** → create: a new entity, a new URL.
89
+ - **Present** → re-plug: the SAME entity is updated — same slug, same URL, new
90
+ content. This is how you iterate on one shareable link.
91
+
92
+ Signed in, re-plug with the entity id and your normal auth headers:
93
+
94
+ ```
95
+ curl -sS -X POST https://api.cloudgrid.io/api/v2/plug \
96
+ -H "Authorization: Bearer $JWT" -H "X-CloudGrid-Org: $ORG" \
97
+ -F "target_entity_id=$ENTITY_ID" \
98
+ -F "artifact=@/path/to/index.html;type=text/html"
99
+ ```
100
+
101
+ Anonymously, prove ownership with the `owner_token` the drop returned:
102
+
103
+ ```
104
+ curl -sS -X POST https://api.cloudgrid.io/api/v2/plug \
105
+ -F "target_entity_id=$ENTITY_ID" \
106
+ -F "owner_token=$OWNER_TOKEN" \
107
+ -F "artifact=@/path/to/index.html;type=text/html"
108
+ ```
109
+
110
+ Rules that matter:
111
+
112
+ - `entity_id` + `url` (+ `owner_token` when anonymous) are the durable re-plug
113
+ handle. Persist them after every drop.
114
+ - Every anonymous edit REFRESHES the `owner_token` — store the one from the
115
+ latest response; the old one is superseded. Each edit also resets the drop's
116
+ expiry window.
117
+ - Editing an archived or expired drop returns **409 `EDIT_REJECTED`** — it never
118
+ silently creates a new entity. Drop again (without `target_entity_id`) if the
119
+ user wants a new link.
120
+ - An anonymous edit without a valid `owner_token` returns **401**.
121
+ - Anonymous edits count against the same daily anonymous cap as creates.
122
+ - Only re-plug when the user is iterating on an existing artifact. If they want a
123
+ separate new link, omit `target_entity_id` (the MCP `cloudgrid_drop` tool's
124
+ `fresh` flag does the same: it forces a new entity instead of updating).
91
125
 
92
126
  ## After the drop
93
127
 
94
- Print the composed `url` (e.g. `https://guest.cloudgrid.io/<slug>`) on its own line,
95
- by itself, so it can be copied in one go. Then add one line of context: an anonymous
96
- drop lasts 7 days and can be claimed to keep it; a signed-in drop is already owned.
97
- Do not bury the URL in prose.
128
+ Print the `url` from the response on its own line, by itself, so it can be copied
129
+ in one go. Then add one line of context: an anonymous drop expires (default 7 days,
130
+ reset on every edit) and can be claimed to keep it; a signed-in drop is already
131
+ owned. Do not bury the URL in prose. Keep `entity_id` (and `owner_token` if
132
+ anonymous) so a follow-up "change X" can re-plug the same URL.
98
133
 
99
134
  ## Limits and errors
100
135
 
@@ -25,16 +25,21 @@ behalf: scaffold, deploy, tail logs, share, read feedback. Everything here is fr
25
25
  cloudgrid login
26
26
  ```
27
27
 
28
- 3. Add the skills. In Claude Code (recommended):
28
+ 3. Add the agent-core. In Claude Code (recommended):
29
29
 
30
30
  ```
31
31
  /plugin marketplace add cloudgrid-io/skills
32
- /plugin install cloudgrid
32
+ /plugin install cloudgrid@cloudgrid-skills
33
33
  ```
34
34
 
35
- They show under `/plugin` and `/skills` as `cloudgrid:drop`, `cloudgrid:login`,
36
- … — or just say what you want ("drop this HTML to CloudGrid") and the right one
37
- triggers.
35
+ One install wires up everything: the skills, the SessionStart orientation
36
+ hook, and the CloudGrid MCP server (`gridctl_start`, `gridctl_fetch`, and the
37
+ CLI-wrapping tools). The MCP auto-starts when the plugin is enabled — no
38
+ separate `claude mcp add` needed.
39
+
40
+ The skills show under `/plugin` and `/skills` as `cloudgrid:drop`,
41
+ `cloudgrid:login`, … — or just say what you want ("drop this HTML to
42
+ CloudGrid") and the right one triggers.
38
43
 
39
44
  Or install with the `gh skill` extension:
40
45
 
@@ -82,9 +87,11 @@ walks through it end to end.
82
87
 
83
88
  ## MCP server
84
89
 
85
- The MCP server is published separately as
90
+ The Claude Code plugin above already bundles the MCP server — installing
91
+ `cloudgrid@cloudgrid-skills` starts it automatically. For every other client, the
92
+ MCP server is published separately as
86
93
  [`@cloudgrid-io/mcp`](https://github.com/cloudgrid-io/mcp). It exposes the same
87
- nine actions as MCP tools, for agents that speak the Model Context Protocol.
94
+ actions as MCP tools, for agents that speak the Model Context Protocol.
88
95
  Install it with `npx -y @cloudgrid-io/mcp`, or point a remote-capable client at
89
96
  `https://mcp.cloudgrid.io/mcp`. See [USAGE.md](USAGE.md) for per-client setup.
90
97
 
@@ -0,0 +1,118 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{DASHBOARD_TITLE}}</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0b0f17;
10
+ --panel: #121826;
11
+ --fg: #e8edf5;
12
+ --muted: #9aa7bd;
13
+ --accent: #5b8cff;
14
+ --good: #46d19e;
15
+ --border: #223049;
16
+ }
17
+ * { box-sizing: border-box; margin: 0; padding: 0; }
18
+ body {
19
+ background: var(--bg);
20
+ color: var(--fg);
21
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
22
+ -webkit-font-smoothing: antialiased;
23
+ padding: clamp(1.25rem, 4vw, 2.5rem);
24
+ }
25
+ header { margin-bottom: 1.75rem; }
26
+ h1 { font-size: clamp(1.6rem, 4vw, 2.3rem); letter-spacing: -0.02em; }
27
+ .status { color: var(--muted); margin-top: 0.35rem; }
28
+ .grid { display: grid; gap: 1.1rem; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
29
+ .card {
30
+ background: var(--panel); border: 1px solid var(--border);
31
+ border-radius: 14px; padding: 1.25rem 1.4rem;
32
+ }
33
+ .card .label { color: var(--muted); font-size: 0.85rem; }
34
+ .card .value { font-size: 2rem; font-weight: 600; margin: 0.35rem 0; font-variant-numeric: tabular-nums; }
35
+ .card .trend { font-size: 0.85rem; color: var(--good); }
36
+ .charts { display: grid; gap: 1.1rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); margin-top: 1.1rem; }
37
+ .chart-card { background: var(--panel); border: 1px solid var(--border); border-radius: 14px; padding: 1.25rem 1.4rem; }
38
+ .chart-card h2 { font-size: 1rem; color: var(--muted); font-weight: 600; margin-bottom: 0.9rem; }
39
+ svg { width: 100%; height: auto; display: block; }
40
+ </style>
41
+ </head>
42
+ <body>
43
+ <header>
44
+ <h1>{{DASHBOARD_TITLE}}</h1>
45
+ <div class="status">{{STATUS_LINE}}</div>
46
+ </header>
47
+
48
+ <!-- One card per KPI. -->
49
+ <section class="grid">
50
+ <div class="card">
51
+ <div class="label">{{KPI_1_LABEL}}</div>
52
+ <div class="value">{{KPI_1_VALUE}}</div>
53
+ <div class="trend">{{KPI_1_TREND}}</div>
54
+ </div>
55
+ <div class="card">
56
+ <div class="label">{{KPI_2_LABEL}}</div>
57
+ <div class="value">{{KPI_2_VALUE}}</div>
58
+ <div class="trend">{{KPI_2_TREND}}</div>
59
+ </div>
60
+ <div class="card">
61
+ <div class="label">{{KPI_3_LABEL}}</div>
62
+ <div class="value">{{KPI_3_VALUE}}</div>
63
+ <div class="trend">{{KPI_3_TREND}}</div>
64
+ </div>
65
+ </section>
66
+
67
+ <section class="charts">
68
+ <div class="chart-card">
69
+ <h2>{{BAR_CHART_TITLE}}</h2>
70
+ <svg id="bar" viewBox="0 0 320 160" role="img" aria-label="Bar chart"></svg>
71
+ </div>
72
+ <div class="chart-card">
73
+ <h2>{{LINE_CHART_TITLE}}</h2>
74
+ <svg id="line" viewBox="0 0 320 160" role="img" aria-label="Line chart"></svg>
75
+ </div>
76
+ </section>
77
+
78
+ <script>
79
+ // Charts are hand-rolled inline SVG — no external library. Edit these arrays.
80
+ var barData = [{{BAR_DATA}}]; // e.g. 12, 19, 9, 22, 17
81
+ var lineData = [{{LINE_DATA}}]; // e.g. 4, 8, 6, 11, 9, 14
82
+
83
+ var NS = "http://www.w3.org/2000/svg";
84
+ function el(name, attrs) {
85
+ var n = document.createElementNS(NS, name);
86
+ for (var k in attrs) n.setAttribute(k, attrs[k]);
87
+ return n;
88
+ }
89
+
90
+ (function drawBars() {
91
+ var svg = document.getElementById("bar");
92
+ var W = 320, H = 160, pad = 20;
93
+ var max = Math.max.apply(null, barData.concat([1]));
94
+ var bw = (W - pad * 2) / barData.length;
95
+ barData.forEach(function (v, i) {
96
+ var h = (v / max) * (H - pad * 2);
97
+ svg.appendChild(el("rect", {
98
+ x: pad + i * bw + bw * 0.15, y: H - pad - h,
99
+ width: bw * 0.7, height: h, rx: 3, fill: "var(--accent)"
100
+ }));
101
+ });
102
+ svg.appendChild(el("line", { x1: pad, y1: H - pad, x2: W - pad, y2: H - pad, stroke: "var(--border)" }));
103
+ })();
104
+
105
+ (function drawLine() {
106
+ var svg = document.getElementById("line");
107
+ var W = 320, H = 160, pad = 20;
108
+ var max = Math.max.apply(null, lineData.concat([1]));
109
+ var step = (W - pad * 2) / Math.max(1, lineData.length - 1);
110
+ var pts = lineData.map(function (v, i) {
111
+ return (pad + i * step) + "," + (H - pad - (v / max) * (H - pad * 2));
112
+ }).join(" ");
113
+ svg.appendChild(el("polyline", { points: pts, fill: "none", stroke: "var(--good)", "stroke-width": 2.5 }));
114
+ svg.appendChild(el("line", { x1: pad, y1: H - pad, x2: W - pad, y2: H - pad, stroke: "var(--border)" }));
115
+ })();
116
+ </script>
117
+ </body>
118
+ </html>
@@ -0,0 +1,97 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{PRODUCT_NAME}}</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0b0f17;
10
+ --panel: #121826;
11
+ --fg: #e8edf5;
12
+ --muted: #9aa7bd;
13
+ --accent: #5b8cff;
14
+ --border: #223049;
15
+ }
16
+ * { box-sizing: border-box; margin: 0; padding: 0; }
17
+ body {
18
+ background: var(--bg);
19
+ color: var(--fg);
20
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
21
+ -webkit-font-smoothing: antialiased;
22
+ line-height: 1.5;
23
+ }
24
+ header, section, footer { padding: clamp(2rem, 6vw, 5rem) clamp(1.25rem, 6vw, 6rem); }
25
+ nav {
26
+ display: flex; align-items: center; justify-content: space-between;
27
+ padding: 1.1rem clamp(1.25rem, 6vw, 6rem);
28
+ border-bottom: 1px solid var(--border);
29
+ }
30
+ nav .brand { font-weight: 600; letter-spacing: -0.01em; }
31
+ nav a { color: var(--muted); text-decoration: none; }
32
+ .hero { text-align: center; display: grid; gap: 1.25rem; place-items: center; }
33
+ .hero h1 {
34
+ font-size: clamp(2.4rem, 6vw, 4.5rem);
35
+ line-height: 1.05; letter-spacing: -0.02em; max-width: 20ch;
36
+ }
37
+ .hero p { font-size: clamp(1.05rem, 2.4vw, 1.5rem); color: var(--muted); max-width: 48ch; }
38
+ .cta {
39
+ display: inline-block; margin-top: 0.5rem;
40
+ background: var(--accent); color: #fff; text-decoration: none;
41
+ padding: 0.85rem 1.6rem; border-radius: 10px; font-weight: 600;
42
+ }
43
+ .cta:hover { filter: brightness(1.08); }
44
+ .features {
45
+ display: grid; gap: 1.25rem;
46
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
47
+ border-top: 1px solid var(--border);
48
+ }
49
+ .feature {
50
+ background: var(--panel); border: 1px solid var(--border);
51
+ border-radius: 14px; padding: 1.5rem;
52
+ }
53
+ .feature h3 { font-size: 1.15rem; margin-bottom: 0.5rem; letter-spacing: -0.01em; }
54
+ .feature p { color: var(--muted); }
55
+ .closing { text-align: center; border-top: 1px solid var(--border); display: grid; gap: 1.25rem; place-items: center; }
56
+ .closing h2 { font-size: clamp(1.8rem, 4.5vw, 3rem); letter-spacing: -0.01em; max-width: 24ch; }
57
+ footer { border-top: 1px solid var(--border); color: var(--muted); font-size: 0.9rem; text-align: center; }
58
+ </style>
59
+ </head>
60
+ <body>
61
+ <nav>
62
+ <span class="brand">{{PRODUCT_NAME}}</span>
63
+ <a class="cta" href="{{CTA_HREF}}">{{CTA_LABEL}}</a>
64
+ </nav>
65
+
66
+ <header class="hero">
67
+ <h1>{{HERO_HEADLINE}}</h1>
68
+ <p>{{HERO_SUBHEAD}}</p>
69
+ <a class="cta" href="{{CTA_HREF}}">{{CTA_LABEL}}</a>
70
+ </header>
71
+
72
+ <section class="features">
73
+ <!-- FEATURES_START -->
74
+ <div class="feature">
75
+ <h3>{{FEATURE_1_TITLE}}</h3>
76
+ <p>{{FEATURE_1_BODY}}</p>
77
+ </div>
78
+ <div class="feature">
79
+ <h3>{{FEATURE_2_TITLE}}</h3>
80
+ <p>{{FEATURE_2_BODY}}</p>
81
+ </div>
82
+ <div class="feature">
83
+ <h3>{{FEATURE_3_TITLE}}</h3>
84
+ <p>{{FEATURE_3_BODY}}</p>
85
+ </div>
86
+ <!-- Add more <div class="feature">...</div> blocks as needed. -->
87
+ <!-- FEATURES_END -->
88
+ </section>
89
+
90
+ <section class="closing">
91
+ <h2>{{CLOSING_HEADLINE}}</h2>
92
+ <a class="cta" href="{{CTA_HREF}}">{{CTA_LABEL}}</a>
93
+ </section>
94
+
95
+ <footer>{{FOOTER_TEXT}}</footer>
96
+ </body>
97
+ </html>
@@ -0,0 +1,93 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{REPORT_TITLE}}</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0b0f17;
10
+ --panel: #121826;
11
+ --fg: #e8edf5;
12
+ --muted: #9aa7bd;
13
+ --accent: #5b8cff;
14
+ --border: #223049;
15
+ }
16
+ * { box-sizing: border-box; margin: 0; padding: 0; }
17
+ body {
18
+ background: var(--bg);
19
+ color: var(--fg);
20
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
21
+ -webkit-font-smoothing: antialiased;
22
+ line-height: 1.6;
23
+ }
24
+ .page {
25
+ max-width: 820px; margin: 0 auto;
26
+ padding: clamp(2rem, 6vw, 4.5rem) clamp(1.25rem, 5vw, 3.5rem);
27
+ }
28
+ header { border-bottom: 1px solid var(--border); padding-bottom: 1.5rem; margin-bottom: 2rem; }
29
+ .accent-bar { width: 64px; height: 5px; border-radius: 99px; background: var(--accent); margin-bottom: 1.1rem; }
30
+ h1 { font-size: clamp(1.9rem, 4.5vw, 2.8rem); line-height: 1.1; letter-spacing: -0.02em; }
31
+ .subtitle { color: var(--muted); font-size: 1.1rem; margin-top: 0.5rem; }
32
+ .meta { color: var(--muted); font-size: 0.9rem; margin-top: 0.75rem; }
33
+ .summary {
34
+ background: var(--panel); border: 1px solid var(--border);
35
+ border-radius: 12px; padding: 1.25rem 1.4rem; margin-bottom: 2rem; color: var(--muted);
36
+ }
37
+ section { margin-bottom: 2rem; }
38
+ section h2 { font-size: 1.35rem; letter-spacing: -0.01em; margin-bottom: 0.6rem; }
39
+ section p { color: var(--muted); }
40
+ table { width: 100%; border-collapse: collapse; margin-top: 0.5rem; }
41
+ th, td { text-align: left; padding: 0.6rem 0.75rem; border-bottom: 1px solid var(--border); }
42
+ th { color: var(--muted); font-size: 0.85rem; font-weight: 600; }
43
+ td { font-variant-numeric: tabular-nums; }
44
+ footer { border-top: 1px solid var(--border); padding-top: 1.25rem; color: var(--muted); font-size: 0.85rem; }
45
+ @media print {
46
+ :root { --bg: #ffffff; --panel: #f4f6fa; --fg: #10151f; --muted: #4a5568; --border: #d5dbe6; }
47
+ body { background: #fff; }
48
+ .page { padding: 0; }
49
+ }
50
+ </style>
51
+ </head>
52
+ <body>
53
+ <div class="page">
54
+ <header>
55
+ <div class="accent-bar"></div>
56
+ <h1>{{REPORT_TITLE}}</h1>
57
+ <div class="subtitle">{{REPORT_SUBTITLE}}</div>
58
+ <div class="meta">{{REPORT_DATE_LINE}}</div>
59
+ </header>
60
+
61
+ <div class="summary">{{REPORT_SUMMARY}}</div>
62
+
63
+ <!-- SECTIONS_START -->
64
+ <section>
65
+ <h2>{{SECTION_1_HEADING}}</h2>
66
+ <p>{{SECTION_1_BODY}}</p>
67
+ </section>
68
+ <section>
69
+ <h2>{{SECTION_2_HEADING}}</h2>
70
+ <p>{{SECTION_2_BODY}}</p>
71
+ </section>
72
+ <!-- Add more <section>...</section> blocks as needed. -->
73
+ <!-- SECTIONS_END -->
74
+
75
+ <section>
76
+ <h2>{{DATA_HEADING}}</h2>
77
+ <!-- Fill these rows with the data, or remove this whole section if there is none. -->
78
+ <table>
79
+ <thead>
80
+ <tr><th>{{COL_1}}</th><th>{{COL_2}}</th><th>{{COL_3}}</th></tr>
81
+ </thead>
82
+ <tbody>
83
+ <tr><td>{{R1C1}}</td><td>{{R1C2}}</td><td>{{R1C3}}</td></tr>
84
+ <tr><td>{{R2C1}}</td><td>{{R2C2}}</td><td>{{R2C3}}</td></tr>
85
+ <tr><td>{{R3C1}}</td><td>{{R3C2}}</td><td>{{R3C3}}</td></tr>
86
+ </tbody>
87
+ </table>
88
+ </section>
89
+
90
+ <footer>{{FOOTER_TEXT}}</footer>
91
+ </div>
92
+ </body>
93
+ </html>
@@ -0,0 +1,99 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{APP_TITLE}}</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0b0f17;
10
+ --panel: #121826;
11
+ --fg: #e8edf5;
12
+ --muted: #9aa7bd;
13
+ --accent: #5b8cff;
14
+ --border: #223049;
15
+ }
16
+ * { box-sizing: border-box; margin: 0; padding: 0; }
17
+ body {
18
+ background: var(--bg);
19
+ color: var(--fg);
20
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
21
+ -webkit-font-smoothing: antialiased;
22
+ min-height: 100vh;
23
+ display: grid; place-items: center;
24
+ padding: 1.5rem;
25
+ }
26
+ .app {
27
+ width: 100%; max-width: 420px;
28
+ background: var(--panel); border: 1px solid var(--border);
29
+ border-radius: 16px; padding: 1.75rem;
30
+ }
31
+ h1 { font-size: 1.5rem; letter-spacing: -0.01em; margin-bottom: 0.35rem; }
32
+ .subtitle { color: var(--muted); font-size: 0.95rem; margin-bottom: 1.4rem; }
33
+ label { display: block; font-size: 0.85rem; color: var(--muted); margin: 0.9rem 0 0.35rem; }
34
+ input, select {
35
+ width: 100%; font: inherit; color: var(--fg);
36
+ background: var(--bg); border: 1px solid var(--border);
37
+ border-radius: 10px; padding: 0.65rem 0.75rem;
38
+ }
39
+ input:focus, select:focus { outline: none; border-color: var(--accent); }
40
+ .row { display: flex; gap: 0.75rem; }
41
+ .row > * { flex: 1; }
42
+ .result {
43
+ margin-top: 1.5rem; padding: 1.1rem 1.25rem;
44
+ background: var(--bg); border: 1px solid var(--border); border-radius: 12px;
45
+ display: flex; align-items: baseline; justify-content: space-between;
46
+ }
47
+ .result .label { color: var(--muted); font-size: 0.85rem; }
48
+ .result .value { font-size: 1.6rem; font-weight: 600; font-variant-numeric: tabular-nums; }
49
+ </style>
50
+ </head>
51
+ <body>
52
+ <!-- Adapt this shell into the tool you need: rename the title, replace the
53
+ inputs, and rewrite compute() to produce the output. Keep all logic
54
+ inline. This starter reads two numeric inputs and shows their result. -->
55
+ <main class="app">
56
+ <h1>{{APP_TITLE}}</h1>
57
+ <p class="subtitle">{{APP_SUBTITLE}}</p>
58
+
59
+ <div class="row">
60
+ <div>
61
+ <label for="a">{{INPUT_A_LABEL}}</label>
62
+ <input id="a" type="number" value="0" inputmode="decimal" />
63
+ </div>
64
+ <div>
65
+ <label for="b">{{INPUT_B_LABEL}}</label>
66
+ <input id="b" type="number" value="0" inputmode="decimal" />
67
+ </div>
68
+ </div>
69
+
70
+ <div class="result">
71
+ <span class="label">{{RESULT_LABEL}}</span>
72
+ <span class="value" id="out">0</span>
73
+ </div>
74
+ </main>
75
+
76
+ <script>
77
+ (function () {
78
+ var a = document.getElementById("a");
79
+ var b = document.getElementById("b");
80
+ var out = document.getElementById("out");
81
+
82
+ // Replace this with the tool's real calculation.
83
+ function compute() {
84
+ var x = parseFloat(a.value) || 0;
85
+ var y = parseFloat(b.value) || 0;
86
+ return x + y;
87
+ }
88
+
89
+ function render() {
90
+ out.textContent = String(compute());
91
+ }
92
+
93
+ a.addEventListener("input", render);
94
+ b.addEventListener("input", render);
95
+ render();
96
+ })();
97
+ </script>
98
+ </body>
99
+ </html>
@@ -128,7 +128,7 @@ You can install both; they do not conflict.
128
128
 
129
129
  | Action | What it does |
130
130
  |---|---|
131
- | drop | Share an artifact, get a public URL. No login needed (anonymous, 7-day) or owned if signed in (30-day). |
131
+ | drop | Share an artifact, get a public URL. No login needed (anonymous, claimable) or owned if signed in. Re-drop with the entity id to update the same URL. |
132
132
  | login | Sign in to CloudGrid, with or without the CLI. |
133
133
  | claim | Claim an anonymous drop into your account; the URL stays the same. |
134
134
  | init | Scaffold a new app or agent. |
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: dashboard
3
+ when: The user wants to build a dashboard showing metrics, status, or KPIs with cards and charts.
4
+ summary: Fetch the dashboard template, fill it with the user's metrics and inline charts, deploy it, and return the live share URL.
5
+ ---
6
+
7
+ # Workflow: dashboard
8
+
9
+ Detect the intent ("build me a dashboard for X metrics / status") and follow
10
+ this recipe. Do not ask setup questions first; use sensible defaults and build.
11
+
12
+ 1. **Fetch the template.** Call `gridctl_fetch("template", "dashboard")` to load
13
+ the self-contained HTML dashboard template.
14
+ 2. **(Optional) Fetch the example.** Call `gridctl_fetch("example", "dashboard")`
15
+ to see a filled dashboard as a reference for structure and layout.
16
+ 3. **Fill the template.** Replace the placeholders with the user's content:
17
+ - The dashboard title and (optional) status line.
18
+ - One metric card per KPI. Each card has a label, a value, and a trend note.
19
+ - The chart data: edit the inline data arrays that the hand-rolled SVG and
20
+ inline JS read from. Keep every chart inline — do not add an external chart
21
+ library or CDN.
22
+ - Keep it a single self-contained HTML file. Do not add external scripts,
23
+ stylesheets, fonts, or large embedded media.
24
+ 4. **Deploy.** Deploy the filled HTML:
25
+ - Hosted MCP edition: call the drop tool with the HTML.
26
+ - Local MCP / CLI edition: write the HTML to a file and run `gridctl plug`.
27
+ A dashboard is an inspiration and deploys synchronously, so you get a URL
28
+ right away.
29
+ 5. **Return the live share URL.** Give the user the URL that serves the dashboard.
30
+ For revisions, re-deploy with the entity id from the first deploy — it updates
31
+ the same share URL, so the user iterates on one link.
32
+
33
+ Keep the whole flow tight: fetch, fill, deploy, share.
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: landing-page
3
+ when: The user wants to build a landing page, marketing page, or product page for something.
4
+ summary: Fetch the landing-page template, fill it with the user's content, deploy it, and return the live share URL.
5
+ ---
6
+
7
+ # Workflow: landing-page
8
+
9
+ Detect the intent ("build me a landing page / marketing page for X") and follow
10
+ this recipe. Do not ask setup questions first; use sensible defaults and build.
11
+
12
+ 1. **Fetch the template.** Call `gridctl_fetch("template", "landing-page")` to
13
+ load the self-contained HTML landing-page template.
14
+ 2. **(Optional) Fetch the example.** Call `gridctl_fetch("example", "landing-page")`
15
+ to see a filled landing page as a reference for tone and structure.
16
+ 3. **Fill the template.** Replace the placeholders with the user's content:
17
+ - The product name, hero headline, and subheadline.
18
+ - The call-to-action label and (optional) target.
19
+ - One block per feature. Each feature has a title and a short description.
20
+ - Keep it a single self-contained HTML file. Do not add external scripts,
21
+ stylesheets, fonts, or large embedded media.
22
+ 4. **Deploy.** Deploy the filled HTML:
23
+ - Hosted MCP edition: call the drop tool with the HTML.
24
+ - Local MCP / CLI edition: write the HTML to a file and run `gridctl plug`.
25
+ A landing page is an inspiration and deploys synchronously, so you get a URL
26
+ right away.
27
+ 5. **Return the live share URL.** Give the user the URL that serves the page.
28
+ For revisions, re-deploy with the entity id from the first deploy — it updates
29
+ the same share URL, so the user iterates on one link.
30
+
31
+ Keep the whole flow tight: fetch, fill, deploy, share.
@@ -24,5 +24,7 @@ this recipe. Do not ask setup questions first; use sensible defaults and build.
24
24
  An HTML deck is an inspiration and deploys synchronously, so you get a URL
25
25
  right away.
26
26
  5. **Return the live share URL.** Give the user the URL that serves the deck.
27
+ For revisions, re-deploy with the entity id from the first deploy — it updates
28
+ the same share URL, so the user iterates on one link.
27
29
 
28
30
  Keep the whole flow tight: fetch, fill, deploy, share.