@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.
package/README.md CHANGED
@@ -54,8 +54,11 @@ It speaks MCP over stdio. Point any MCP client at the `cloudgrid-mcp` command.
54
54
 
55
55
  | Tool | Wraps | Notes |
56
56
  |---|---|---|
57
- | `cloudgrid_drop` | `POST /api/v2/drop/auto` | Artifact drop. Anonymous, or owned if signed in. Direct API. |
58
- | `cloudgrid_claim` | `POST /api/v2/anon-claim` | Claim an anonymous drop into the signed-in account. Direct API. |
57
+ | `gridctl_plug` | `POST /api/v2/plug` | The unified create/re-plug verb: create a new entity, or update the SAME entity in place with `target_entity_id` (same URL). Source via local `path` (local edition) or inline `artifact_files` (both). |
58
+ | `gridctl_drop` | `POST /api/v2/plug` | Artifact drop. Anonymous, or owned if signed in. Re-drops in a session update the same drop in place; anonymous drops return an `entity_id` + `owner_token` re-plug/claim handle. |
59
+ | `gridctl_claim` | `POST /api/v2/entities/:id/pickup` | Claim an anonymous drop into the signed-in account (the claim token IS the drop's owner token). Direct API. |
60
+ | `gridctl_fork` | `POST /api/v2/runtimes/:id/fork` | Start a new entity from an existing runtime (lineage recorded). Needs sign-in. |
61
+ | `gridctl_download` | `GET /api/v2/runtimes/:id/source` | Signed 15-minute source-bundle URLs. Needs sign-in. |
59
62
  | `cloudgrid_login` | `GET /auth/login` | Start a CLI-free sign-in; returns a URL to open. Direct API. |
60
63
  | `cloudgrid_login_status` | `GET /auth/status` | Finish the sign-in; saves the token to the shared CLI credentials. |
61
64
  | `cloudgrid_visibility` | `PATCH /api/v2/inspirations/<id>` | Change who can see a drop (private, space, authenticated, org, link). Needs sign-in. Direct API; also in the web edition. |
@@ -70,7 +73,6 @@ also work in the web edition where no CLI exists. `cloudgrid_login` writes the s
70
73
  | Tool | Wraps | Notes |
71
74
  |---|---|---|
72
75
  | `cloudgrid_init` | `cloudgrid init` | Register an app or agent; optionally seed a web service. |
73
- | `cloudgrid_plug` | `cloudgrid plug` | Deploy a directory or URL. |
74
76
  | `cloudgrid_logs` | `cloudgrid logs` | Snapshot of recent logs. Does not stream. Read-only. |
75
77
  | `cloudgrid_share` | `cloudgrid visibility set` | Set visibility, default link. |
76
78
  | `cloudgrid_feedback` | `cloudgrid feedback list` | Read the org feedback feed. Read-only. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudgrid-io/mcp",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "MCP server for CloudGrid. Three editions: a local stdio server (full toolset), a hosted web server (light, CLI-free toolset), and a public docs server (search_cloudgrid_documentation) over MCP Streamable HTTP.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,6 +16,7 @@
16
16
  "start": "node src/index.js",
17
17
  "start:web": "node src/web.js",
18
18
  "smoke": "node test/smoke.mjs",
19
+ "test:plug-wire": "node test/plug-wire.test.mjs",
19
20
  "test:drift-guard": "node test/drift-guard.mjs",
20
21
  "smoke:drop": "node test/smoke-drop.mjs",
21
22
  "smoke:login": "node test/smoke-login.mjs",
@@ -71,10 +71,28 @@ curl -sS -X POST https://api.cloudgrid.io/api/v2/plug \
71
71
  -F "artifact=@./index.html;type=text/html"
72
72
  ```
73
73
 
74
- The response includes `slug` (the live link is `https://guest.cloudgrid.io/<slug>`)
75
- and `claim_url` (sign in within 7 days to keep it). This is what the `cloudgrid-drop`
76
- skill and the `cloudgrid_drop` MCP tool do for you. It is anonymous and serves
77
- inspirations only; a full app needs the signed-in `plug` flow above.
74
+ The response includes `url` (the live link — use it verbatim), `entity_id`,
75
+ `owner_token` (keep both to update the drop later), and `claim_url` (sign in
76
+ before it expires to keep it). This is what the `cloudgrid-drop` skill and the
77
+ `cloudgrid_drop` MCP tool do for you. It is anonymous and serves inspirations
78
+ only; a full app needs the signed-in `plug` flow above.
79
+
80
+ ## Update your drop in place
81
+
82
+ Re-plugging with `target_entity_id` updates the SAME entity — same slug, same URL,
83
+ new content. Iterate on one link instead of minting a new one per revision:
84
+
85
+ ```
86
+ curl -sS -X POST https://api.cloudgrid.io/api/v2/plug \
87
+ -F "target_entity_id=$ENTITY_ID" \
88
+ -F "owner_token=$OWNER_TOKEN" \
89
+ -F "artifact=@./index.html;type=text/html"
90
+ ```
91
+
92
+ Signed in, drop the `owner_token` field and send your auth headers instead. Each
93
+ anonymous edit returns a refreshed `owner_token` (store the new one) and resets
94
+ the drop's expiry. Editing an archived or expired drop returns 409
95
+ `EDIT_REJECTED`; omit `target_entity_id` when you actually want a fresh link.
78
96
 
79
97
  ## Shorter chains
80
98
 
@@ -0,0 +1,121 @@
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>Service Health Dashboard</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>Service Health Dashboard</h1>
45
+ <div class="status">All systems operational — updated a moment ago</div>
46
+ </header>
47
+
48
+ <section class="grid">
49
+ <div class="card">
50
+ <div class="label">Requests / min</div>
51
+ <div class="value">18.4k</div>
52
+ <div class="trend">+6.2% vs last hour</div>
53
+ </div>
54
+ <div class="card">
55
+ <div class="label">p95 latency</div>
56
+ <div class="value">142ms</div>
57
+ <div class="trend">-11ms vs last hour</div>
58
+ </div>
59
+ <div class="card">
60
+ <div class="label">Error rate</div>
61
+ <div class="value">0.08%</div>
62
+ <div class="trend">within target</div>
63
+ </div>
64
+ <div class="card">
65
+ <div class="label">Uptime (30d)</div>
66
+ <div class="value">99.98%</div>
67
+ <div class="trend">no incidents</div>
68
+ </div>
69
+ </section>
70
+
71
+ <section class="charts">
72
+ <div class="chart-card">
73
+ <h2>Requests by region</h2>
74
+ <svg id="bar" viewBox="0 0 320 160" role="img" aria-label="Bar chart of requests by region"></svg>
75
+ </div>
76
+ <div class="chart-card">
77
+ <h2>Latency over the last 12 hours</h2>
78
+ <svg id="line" viewBox="0 0 320 160" role="img" aria-label="Line chart of latency over time"></svg>
79
+ </div>
80
+ </section>
81
+
82
+ <script>
83
+ var barData = [42, 58, 31, 49, 66, 38];
84
+ var lineData = [160, 154, 148, 151, 139, 142, 137, 144, 140, 135, 142, 138];
85
+
86
+ var NS = "http://www.w3.org/2000/svg";
87
+ function el(name, attrs) {
88
+ var n = document.createElementNS(NS, name);
89
+ for (var k in attrs) n.setAttribute(k, attrs[k]);
90
+ return n;
91
+ }
92
+
93
+ (function drawBars() {
94
+ var svg = document.getElementById("bar");
95
+ var W = 320, H = 160, pad = 20;
96
+ var max = Math.max.apply(null, barData.concat([1]));
97
+ var bw = (W - pad * 2) / barData.length;
98
+ barData.forEach(function (v, i) {
99
+ var h = (v / max) * (H - pad * 2);
100
+ svg.appendChild(el("rect", {
101
+ x: pad + i * bw + bw * 0.15, y: H - pad - h,
102
+ width: bw * 0.7, height: h, rx: 3, fill: "var(--accent)"
103
+ }));
104
+ });
105
+ svg.appendChild(el("line", { x1: pad, y1: H - pad, x2: W - pad, y2: H - pad, stroke: "var(--border)" }));
106
+ })();
107
+
108
+ (function drawLine() {
109
+ var svg = document.getElementById("line");
110
+ var W = 320, H = 160, pad = 20;
111
+ var max = Math.max.apply(null, lineData.concat([1]));
112
+ var step = (W - pad * 2) / Math.max(1, lineData.length - 1);
113
+ var pts = lineData.map(function (v, i) {
114
+ return (pad + i * step) + "," + (H - pad - (v / max) * (H - pad * 2));
115
+ }).join(" ");
116
+ svg.appendChild(el("polyline", { points: pts, fill: "none", stroke: "var(--good)", "stroke-width": 2.5 }));
117
+ svg.appendChild(el("line", { x1: pad, y1: H - pad, x2: W - pad, y2: H - pad, stroke: "var(--border)" }));
118
+ })();
119
+ </script>
120
+ </body>
121
+ </html>
@@ -0,0 +1,98 @@
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>Cadence</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">Cadence</span>
63
+ <a class="cta" href="#start">Start free</a>
64
+ </nav>
65
+
66
+ <header class="hero">
67
+ <h1>Ship your week in a single view</h1>
68
+ <p>Cadence turns scattered tasks, notes, and deadlines into one calm weekly plan your whole team can trust.</p>
69
+ <a class="cta" href="#start">Start free</a>
70
+ </header>
71
+
72
+ <section class="features">
73
+ <div class="feature">
74
+ <h3>One weekly board</h3>
75
+ <p>Every task, meeting, and milestone lands on a single board that resets each Monday so nothing slips.</p>
76
+ </div>
77
+ <div class="feature">
78
+ <h3>Automatic rollover</h3>
79
+ <p>Unfinished work carries forward with its context intact, so you never rebuild a plan from scratch.</p>
80
+ </div>
81
+ <div class="feature">
82
+ <h3>Quiet by design</h3>
83
+ <p>Notifications batch into two calm digests a day, keeping focus time genuinely free of interruptions.</p>
84
+ </div>
85
+ <div class="feature">
86
+ <h3>Shared without noise</h3>
87
+ <p>Teammates see the same board and status without a stream of pings, comments, or status meetings.</p>
88
+ </div>
89
+ </section>
90
+
91
+ <section class="closing" id="start">
92
+ <h2>Plan less, finish more this week</h2>
93
+ <a class="cta" href="#start">Start free</a>
94
+ </section>
95
+
96
+ <footer>Cadence — a sample landing page built on CloudGrid.</footer>
97
+ </body>
98
+ </html>
@@ -0,0 +1,112 @@
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>Q2 Support Operations Review</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>Q2 Support Operations Review</h1>
57
+ <div class="subtitle">Ticket volume, response times, and satisfaction</div>
58
+ <div class="meta">Prepared by the Support team, quarter ending June 30</div>
59
+ </header>
60
+
61
+ <div class="summary">
62
+ Support handled 12% more tickets than Q1 while cutting median first-response
63
+ time by nearly a third. Satisfaction held steady at 94%. The main risk into
64
+ Q3 is billing-related volume, which grew faster than headcount.
65
+ </div>
66
+
67
+ <section>
68
+ <h2>Volume and staffing</h2>
69
+ <p>
70
+ The team resolved 8,420 tickets across the quarter, up from 7,510 in Q1.
71
+ Two agents joined in May, bringing the desk to nine. Even so, the
72
+ tickets-per-agent load rose slightly, absorbed mostly by faster tooling
73
+ rather than longer hours.
74
+ </p>
75
+ </section>
76
+
77
+ <section>
78
+ <h2>Response and resolution</h2>
79
+ <p>
80
+ Median first-response time fell from 4.6 hours to 3.1 hours after the
81
+ canned-reply library was rebuilt. Full resolution time was flat, since the
82
+ slowest tickets are billing disputes that depend on the finance team.
83
+ </p>
84
+ </section>
85
+
86
+ <section>
87
+ <h2>Outlook</h2>
88
+ <p>
89
+ Billing questions are the fastest-growing category and the least
90
+ self-serviceable. The recommendation for Q3 is a dedicated billing macro
91
+ set plus a short help-center article, before adding more headcount.
92
+ </p>
93
+ </section>
94
+
95
+ <section>
96
+ <h2>Key metrics</h2>
97
+ <table>
98
+ <thead>
99
+ <tr><th>Metric</th><th>Q1</th><th>Q2</th></tr>
100
+ </thead>
101
+ <tbody>
102
+ <tr><td>Tickets resolved</td><td>7,510</td><td>8,420</td></tr>
103
+ <tr><td>Median first response</td><td>4.6h</td><td>3.1h</td></tr>
104
+ <tr><td>Satisfaction</td><td>93%</td><td>94%</td></tr>
105
+ </tbody>
106
+ </table>
107
+ </section>
108
+
109
+ <footer>A sample report built on CloudGrid. Figures are illustrative.</footer>
110
+ </div>
111
+ </body>
112
+ </html>
@@ -0,0 +1,125 @@
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>Tip Splitter</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 {
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 { outline: none; border-color: var(--accent); }
40
+ .row { display: flex; gap: 0.75rem; }
41
+ .row > * { flex: 1; }
42
+ .tips { display: flex; gap: 0.5rem; margin-top: 0.35rem; }
43
+ .tips button {
44
+ flex: 1; font: inherit; cursor: pointer;
45
+ background: var(--bg); color: var(--fg);
46
+ border: 1px solid var(--border); border-radius: 10px; padding: 0.55rem 0;
47
+ }
48
+ .tips button.active { border-color: var(--accent); color: var(--accent); }
49
+ .result {
50
+ margin-top: 1.5rem; padding: 1.1rem 1.25rem;
51
+ background: var(--bg); border: 1px solid var(--border); border-radius: 12px;
52
+ display: grid; gap: 0.6rem;
53
+ }
54
+ .result .line { display: flex; align-items: baseline; justify-content: space-between; }
55
+ .result .label { color: var(--muted); font-size: 0.85rem; }
56
+ .result .value { font-size: 1.15rem; font-weight: 600; font-variant-numeric: tabular-nums; }
57
+ .result .line.total .value { font-size: 1.7rem; color: var(--accent); }
58
+ </style>
59
+ </head>
60
+ <body>
61
+ <main class="app">
62
+ <h1>Tip Splitter</h1>
63
+ <p class="subtitle">Split a bill and tip evenly across the table.</p>
64
+
65
+ <label for="bill">Bill amount</label>
66
+ <input id="bill" type="number" min="0" step="0.01" value="86.40" inputmode="decimal" />
67
+
68
+ <label>Tip percentage</label>
69
+ <div class="tips" id="tips">
70
+ <button data-tip="10">10%</button>
71
+ <button data-tip="15" class="active">15%</button>
72
+ <button data-tip="18">18%</button>
73
+ <button data-tip="20">20%</button>
74
+ </div>
75
+
76
+ <label for="people">Number of people</label>
77
+ <input id="people" type="number" min="1" step="1" value="4" inputmode="numeric" />
78
+
79
+ <div class="result">
80
+ <div class="line">
81
+ <span class="label">Tip amount</span>
82
+ <span class="value" id="tipOut">0.00</span>
83
+ </div>
84
+ <div class="line total">
85
+ <span class="label">Each person pays</span>
86
+ <span class="value" id="perOut">0.00</span>
87
+ </div>
88
+ </div>
89
+ </main>
90
+
91
+ <script>
92
+ (function () {
93
+ var bill = document.getElementById("bill");
94
+ var people = document.getElementById("people");
95
+ var tips = document.getElementById("tips");
96
+ var tipOut = document.getElementById("tipOut");
97
+ var perOut = document.getElementById("perOut");
98
+ var tipPct = 15;
99
+
100
+ function render() {
101
+ var amount = parseFloat(bill.value) || 0;
102
+ var n = Math.max(1, parseInt(people.value, 10) || 1);
103
+ var tip = amount * (tipPct / 100);
104
+ var perPerson = (amount + tip) / n;
105
+ tipOut.textContent = tip.toFixed(2);
106
+ perOut.textContent = perPerson.toFixed(2);
107
+ }
108
+
109
+ tips.addEventListener("click", function (e) {
110
+ var btn = e.target.closest("button");
111
+ if (!btn) return;
112
+ tipPct = parseFloat(btn.getAttribute("data-tip"));
113
+ Array.prototype.forEach.call(tips.children, function (b) {
114
+ b.classList.toggle("active", b === btn);
115
+ });
116
+ render();
117
+ });
118
+
119
+ bill.addEventListener("input", render);
120
+ people.addEventListener("input", render);
121
+ render();
122
+ })();
123
+ </script>
124
+ </body>
125
+ </html>
@@ -34,9 +34,13 @@ npx skills add cloudgrid-io/skills
34
34
 
35
35
  ```
36
36
  /plugin marketplace add cloudgrid-io/skills
37
+ /plugin install cloudgrid@cloudgrid-skills
37
38
  ```
38
39
 
39
- The marketplace manifest lives in `.claude-plugin/`.
40
+ The marketplace manifest lives in `.claude-plugin/` (marketplace name:
41
+ `cloudgrid-skills`, plugin name: `cloudgrid`). Installing the plugin bundles the
42
+ skills, the SessionStart orientation hook, and the CloudGrid MCP server — the MCP
43
+ auto-starts when the plugin is enabled, so no separate `claude mcp add` is needed.
40
44
 
41
45
  ### Codex
42
46
 
@@ -14,7 +14,7 @@ allowed-tools: Bash
14
14
 
15
15
  Turn an anonymous drop into an owned one. After an anonymous drop, the user has a
16
16
  `claim_url` (or token) plus the drop's `entity_id`. Once they sign in, claiming
17
- transfers ownership to them and extends the expiry from 7 days to 30. Claiming
17
+ transfers ownership to them and resets the expiry window. Claiming
18
18
  re-homes the drop into the user's grid, so its public URL changes from
19
19
  `guest.cloudgrid.io/<slug>` to `<grid>.cloudgrid.io/<slug>` — share the new link.
20
20
 
@@ -26,11 +26,17 @@ skill (or `cloudgrid login` with the CLI) first.
26
26
  ## What you need
27
27
 
28
28
  Two things: the **entity id** of the anonymous drop (the `entity_id` field the drop
29
- returned), and the **claim token** — the `token` query parameter inside the
30
- `claim_url` the drop returned, e.g. `https://console.cloudgrid.io/claim?token=<TOKEN>`.
29
+ returned), and the **claim token**. The claim token is the drop's `owner_token` —
30
+ the same JWT used for anonymous re-plugs. It also appears as the `token` query
31
+ parameter inside the `claim_url` the drop returned, e.g.
32
+ `https://console.cloudgrid.io/claim?token=<TOKEN>`.
31
33
  A browser that still holds the `cg_anon_session` cookie from the drop can claim
32
34
  without a token instead.
33
35
 
36
+ Note: every anonymous re-plug (edit) of the drop refreshes the `owner_token` and
37
+ resets the drop's expiry. Always claim with the token from the LATEST drop or
38
+ edit response — an older token may have been superseded.
39
+
34
40
  ## How to run it
35
41
 
36
42
  Claiming is now a "pickup" of the entity into your grid. The entity id goes in the