@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 +5 -3
- package/package.json +2 -1
- package/src/corpus/cookbook.md +22 -4
- package/src/corpus/examples/dashboard/index.html +121 -0
- package/src/corpus/examples/landing-page/index.html +98 -0
- package/src/corpus/examples/report/index.html +112 -0
- package/src/corpus/examples/web-app/index.html +125 -0
- package/src/corpus/install.md +5 -1
- package/src/corpus/skill-claim.md +9 -3
- package/src/corpus/skill-drop.md +59 -24
- package/src/corpus/skills-readme.md +14 -7
- package/src/corpus/templates/dashboard/index.html +118 -0
- package/src/corpus/templates/landing-page/index.html +97 -0
- package/src/corpus/templates/report/index.html +93 -0
- package/src/corpus/templates/web-app/index.html +99 -0
- package/src/corpus/usage.md +1 -1
- package/src/corpus/workflows/dashboard.md +33 -0
- package/src/corpus/workflows/landing-page.md +31 -0
- package/src/corpus/workflows/presentation.md +2 -0
- package/src/corpus/workflows/report.md +33 -0
- package/src/corpus/workflows/web-app.md +33 -0
- package/src/tools.js +810 -81
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
|
-
| `
|
|
58
|
-
| `
|
|
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.
|
|
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",
|
package/src/corpus/cookbook.md
CHANGED
|
@@ -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 `
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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>
|
package/src/corpus/install.md
CHANGED
|
@@ -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
|
|
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
|
|
30
|
-
|
|
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
|