Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
238290b
v1
clarkd May 8, 2026
370d271
v2
clarkd May 8, 2026
a42ce7c
v3
clarkd May 8, 2026
860d8b0
v4
clarkd May 8, 2026
67ed6c5
v5
clarkd May 8, 2026
a7cef2e
v6
clarkd May 8, 2026
8cf3ac5
Ensure Claude skill uses password field for api keys
andrewmumblebee May 12, 2026
b4a25f4
Update importFrequencyMinutes to frequencyMinutes
andrewmumblebee May 12, 2026
a46c6cc
deduplicate help property in skill for ui.json
andrewmumblebee May 12, 2026
69afcb5
Remove suffix as can cause Claude to update or create a new plugin
andrewmumblebee May 12, 2026
0dbc190
move allowEncryption skill tip into key-value
andrewmumblebee May 13, 2026
f36246c
add version metadata to skill to track changes
andrewmumblebee May 13, 2026
2a7f06a
Add editorconfig to preserve indentation from product
andrewmumblebee May 13, 2026
d32594f
Ask for author handle before writing metadata.json
andrewmumblebee May 13, 2026
cd40e40
Always create docs/README.md when scaffolding
andrewmumblebee May 13, 2026
a847ab6
Ensure Claude uses id property for built in datastream
andrewmumblebee May 13, 2026
74243e4
Ensure user is prompted for user handle
andrewmumblebee May 13, 2026
96bcfa9
remove built-in properties warning
andrewmumblebee May 13, 2026
7aa1968
Ensure dashboard titles are unique
andrewmumblebee May 13, 2026
78159e3
post process svg icons to provide contrast between application ui
andrewmumblebee May 13, 2026
af88cf4
Fix ui json in dataStreams using incorrect displayName property
andrewmumblebee May 14, 2026
cf0f3f2
Apply suggestions from code review
clarkd May 14, 2026
4b36853
Merge pull request #47 from squaredup/work/ah/skill-password
andrewmumblebee May 15, 2026
e222e79
Load build-plugin reference content on demand
andrewmumblebee May 14, 2026
9e9d465
Revert user input change to previous text
andrewmumblebee May 15, 2026
3f96579
Add visibility section to dataStreams
andrewmumblebee May 15, 2026
3394156
Add gen-uuids script for oob content
andrewmumblebee May 18, 2026
ba4bedc
Teach build-plugin to prefer column expressions
andrewmumblebee May 18, 2026
d3e651f
Default build-plugin streams to no script
andrewmumblebee May 18, 2026
24e2d86
Remove postRequestScript from dataStream examples
andrewmumblebee May 18, 2026
f13efa0
Add notes on when to use postRequestScript over expressions on columns
andrewmumblebee May 18, 2026
875a281
Remove schema references from skill
andrewmumblebee May 18, 2026
f651254
Note that object mapping properties may need to be iterated
andrewmumblebee May 21, 2026
ebba258
remove newline from json file ends
andrewmumblebee May 21, 2026
97a9a14
Add a plan format to keep skill consistent
andrewmumblebee May 21, 2026
95f43fe
Add note on using {{object.rawId} in object expressions
andrewmumblebee May 22, 2026
d8fd4d4
Merge pull request #49 from squaredup/work/ah/skill-progressive
andrewmumblebee May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions .claude/skills/build-plugin/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
---
name: build-plugin
description: Guides building a SquaredUp low-code plugin for HTTP/REST APIs, from API exploration through deployment. Use when the user wants to integrate a service with SquaredUp, add a new data source, connect to a third-party tool, "pull data from", or "monitor" any service in SquaredUp.
metadata:
author: SquaredUp
version: "0.0.3"
---

# Building a SquaredUp Low-Code Plugin

> **Scope:** Web API-based plugins only. If the target tool has no usable REST API, PowerShell may be a better fit — suggest it and stop.

**Announce at start:** "I'm using the build-plugin skill."

---

## Required user inputs

| Input | When to ask | Why |
| ------------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------- |
| **Author handle** (GitHub handle or display name) | Before writing `metadata.json` (Phase 4) | Goes into `author.name`. Guessing from git config frequently picks the wrong identity. |

If the user has already volunteered the answer earlier in the conversation or you're updating a plugin, use that and skip the prompt. Otherwise, ask — even in autonomous mode.

---

## When to Use

- Building a new plugin for an HTTP/REST API
- Adding data streams or dashboards to an existing plugin
- Any request to integrate a service, "pull data from", or "monitor" a service in SquaredUp
- Adding a new data source or integration to a SquaredUp workspace

---

## Checklist

Create a TaskCreate task for each phase:

- [ ] **Phase 1** — Explore the API
- [ ] **Phase 2** — Plan the plugin structure
- [ ] **Phase 3** — Scaffold files (icon, file structure, `docs/README.md`)
- [ ] **Phase 4** — Write `metadata.json` and `ui.json` → read [metadata.md](references/metadata.md) and [ui.md](references/ui.md)
- [ ] **Phase 5** — Write import definitions → read [index-defs.md](references/index-defs.md)
- [ ] **Phase 6** — Write data streams → read [data-streams.md](references/data-streams.md)
- [ ] **Phase 7** — Write OOB default content → read [oob-content.md](references/oob-content.md)
- [ ] **Phase 8** — Write `custom_types.json` → read [common-patterns.md](references/common-patterns.md)
- [ ] **Phase 9** — Validate and deploy → invoke the `deploy-plugin` skill

---

## Phase 1: Explore the API

Before writing a single file, understand the API. **Use `AskUserQuestion` to ask for API documentation URLs, OpenAPI/Swagger specs, Postman collections, or any other reference material.** You can also search online, but verify you're looking at docs for the exact product/version the user wants.

1. **Find the docs** — Gather URLs or spec files from the user, then fetch and read them.
2. **Identify the object model** — What are the core entities? (e.g. installations, devices, sites). These become the **indexed objects** in SquaredUp — available for drilldown, search, scoping dashboards, and use as variables.
3. **Find the list endpoints** — Used to import objects. Prefer fetching **50–250 records per page** across multiple requests — SquaredUp has a per-page timeout but supports as many paged requests as needed.
4. **Find the data endpoints** — These power data streams. Identify whether each is scoped to a single object, multiple objects, or global (no object context).
5. **Understand pagination** — Cursor/next-token, or offset/limit? Separate concern from response transformation.
6. **Note the auth pattern** — API key in header, Bearer token, OAuth2, Basic auth? Determine from the docs.

---

## Phase 2: Plan the Plugin Structure

This phase produces a written plan and a user-approval gate before any files are written. Object types, import shape, and sourceId format are expensive to change once Phase 3+ commits them to JSON — Phase 2 is where scope errors are cheap to fix.

### The plan must cover

1. **Object types** — Every type that should appear in the SquaredUp graph. These go in `objectTypes` in `metadata.json` and as `sourceType` throughout.
2. **Import steps** — Let the API shape dictate: one step returning many types, or separate steps per type.
3. **Data streams** — For each object type, plan:
- A **summary/current state** stream (`"timeframes": false`, returns current values)
- A **history/metrics** stream (supports timeframes, returns time-series rows)
- Any **cross-object** streams scoped to a parent (e.g. alarms for an installation)
- **Prefer configurable streams** over hardcoded ones — use a UI parameter rather than multiple streams for the same endpoint with different values.
4. **What's intentionally omitted** — API capabilities not being implemented, and why. Highest-value section for catching scope creep.
5. **Authentication** — Auth mechanism and any UX concerns (token expiry, rate limits, hard-to-obtain credentials).
6. **OOB dashboards** — A **top-level summary dashboard** plus **one perspective per object type** scoped via a dashboard variable.
7. **sourceId format** — Use the raw API ID wherever possible.

### Plan format

Post the plan as markdown with one `###` heading per item above. Short example:

```markdown
## Plan

### Object types
- `My Installation` — sites being monitored
- `My Device` — physical devices reporting telemetry

### Import steps
- `installations` — one step, returns both types

### Data streams
- `batterySummary` — per-device, current state
- `batteryHistory` — per-device, time-series
- `siteAlarms` — per-installation

### What's intentionally omitted
- Webhook ingestion (no v1 use case)

### Authentication
- API key in `X-API-Key` header

### OOB dashboards
- Overview, Installation perspective, Device perspective

### sourceId format
- Installation: raw API `id`
- Device: composite `{installationId}-{deviceId}` (API has no global device ID)
```

### Approval gate

**When to fire:** when `metadata.json` doesn't exist yet in the plugin folder, OR when the planned `objectTypes` differs from the current `metadata.json`. Otherwise skip — incremental work that doesn't introduce new entities doesn't need the gate.

**How:** post the plan, then call `AskUserQuestion` **in the same turn** with three options:

- `Approve as written` → proceed to Phase 3
- `Trim scope — start with less` → user wants a smaller MVP; ask what to cut
- `Adjust — different objects/streams/auth` → user wants changes; ask what specifically

If the user picks anything other than approve (including "Other"), revise the plan and re-fire the gate with the updated plan. Loop until approval — a revised plan can introduce new wrong assumptions, so the second pass is doing real work, not theatre. If the user explicitly waives further gating ("just proceed", "looks fine, go", "stop asking"), honor that for the rest of this conversation.

---

## Phase 3: Scaffold Files

**Icon:** Find the official brand/product logo (SVG or PNG). Never auto-generate a generic icon — ask the user to supply one if you can't find an official logo.

**Post-process SVG icons if needed.** SquaredUp shows icons on dark/white backgrounds. Fix if the SVG lacks a background or is not square:

1. Set `width="512" height="512" viewBox="0 0 512 512"`
2. Insert `<rect width="512" height="512" fill="BRAND_COLOR"/>` as the first child
3. Wrap paths in `<g transform="translate(X, Y) scale(S)">` for ~10% padding: `S = min(409.6/w, 409.6/h)`, `X = (512−w*S)/2`, `Y = (512−h*S)/2`

**File structure:**

```
my-plugin/
v1/
metadata.json
ui.json
icon.svg
custom_types.json
configValidation.json # preferred: validates config on setup
docs/
README.md # REQUIRED: shown in-product when users add the plugin
indexDefinitions/
default.json
dataStreams/
myStream.json
scripts/
myScript.js
defaultContent/
manifest.json
scopes.json
overviewDashboard.dash.json
deviceDashboard.dash.json # single perspective — no sub-folder needed
Installations/ # sub-folder only for multiple dashboards of the same type
manifest.json
dashboard1.dash.json

Check failure on line 165 in .claude/skills/build-plugin/SKILL.md

View check run for this annotation

Claude / Claude Code Review

Scaffold example uses lowercase folder; contradicts repo convention and metadata.md

The Phase 3 file-structure scaffold in build-plugin/SKILL.md uses lowercase `my-plugin/` as the top-level folder, but the same PR's references/metadata.md explicitly says the folder is PascalCase (e.g. `MyPlugin`), the documentation-link example a few lines below uses `plugins/MyPlugin/v1/`, and every one of the 19 existing plugin folders in this repo uses PascalCase (DigiCert, GoogleSheets, MetOffice, …). An LLM following the scaffold literally would create the plugin in a lowercase folder that
Comment on lines +142 to +165
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 The Phase 3 file-structure scaffold in build-plugin/SKILL.md uses lowercase my-plugin/ as the top-level folder, but the same PR's references/metadata.md explicitly says the folder is PascalCase (e.g. MyPlugin), the documentation-link example a few lines below uses plugins/MyPlugin/v1/, and every one of the 19 existing plugin folders in this repo uses PascalCase (DigiCert, GoogleSheets, MetOffice, …). An LLM following the scaffold literally would create the plugin in a lowercase folder that breaks repo convention — change the example to MyPlugin/v1/ (the kebab-case stays only in the metadata.json name field).

Extended reasoning...

The bug

In .claude/skills/build-plugin/SKILL.md, the Phase 3 "File structure" code block starts with:

my-plugin/
  v1/
    metadata.json
    ...

This lowercase kebab-case folder name contradicts three load-bearing sources in this same PR and repo:

  1. references/metadata.md in this PR — under the field notes it states verbatim: "name: lowercase kebab-case (e.g. my-plugin). Folder uses PascalCase (e.g. MyPlugin) — these are separate things." The skill is contradicting its own reference doc.
  2. The same SKILL.md file, ~20 lines below the scaffold — the documentation-link example is https://github.com/squaredup/plugins/blob/main/plugins/MyPlugin/v1/docs/README.md. So PascalCase is already used as the intended path inside SKILL.md itself.
  3. Existing repo convention — every plugin folder under plugins/ is PascalCase: DattoRMM, DigiCert, FantasyPremierLeague, GoogleSheets, MetOffice, OpenSearch, Phare, Postcoder, RDAP, RSS, Rootly, SendGrid, Snowflake, Spotify, Steam, SumoLogic, TransportForLondon, UniFi, UptimeRobot. There is no precedent for a lowercase folder.

How it manifests

The build-plugin skill is intended to be invoked by an AI agent that will scaffold files literally from the example. Phase 3 is the scaffolding phase; the agent reads the file-tree block and creates directories/files matching it. Because the example shows my-plugin/v1/..., the agent will run something like mkdir -p my-plugin/v1/dataStreams and write files there. By the time Phase 4 instructs the agent to read metadata.md (which contains the PascalCase rule), the folder has already been created with the wrong case — renaming a directory mid-build is a friction point the agent may simply skip, especially because the lowercase name field in metadata.json does match the folder name and looks superficially consistent.

Step-by-step proof

  1. User runs the skill: "I want to build a plugin for MyService."
  2. Agent works through the checklist; reaches Phase 3.
  3. Agent reads the scaffold tree and creates my-plugin/v1/metadata.json, my-plugin/v1/ui.json, etc.
  4. Agent moves to Phase 4, opens references/metadata.md, sees the PascalCase rule — but only as a field note about name vs. folder. The agent has already created the folder; no instruction tells it "go back and rename."
  5. PR opens with a new plugin at plugins/my-plugin/v1/ — inconsistent with all 19 existing plugins. Reviewer asks for a rename, which involves rewriting paths in the documentation link inside metadata.json too.

Why existing code does not prevent it

There is no validator step that checks folder casing — squaredup validate validates JSON contents, not directory names. The PascalCase rule exists only in prose inside metadata.md, which is read after the folder is created. Without correcting the scaffold itself, the rule is documented but unenforced at the moment it matters.

Impact

Low severity (it is a docs/instructional bug, easily fixed by a reviewer), but real — every plugin generated by this skill is at risk of being committed with the wrong folder case, requiring a manual fix before merging. Given this is the new official AI-skill workflow, it will be the dominant pattern going forward.

Fix

Change the scaffold tree to:

MyPlugin/
  v1/
    metadata.json
    ...

Leave the name field in metadata.json as my-plugin (kebab-case) — those are separate things, exactly as metadata.md states. No other lines in the scaffold need to change.

```

**docs/README.md (required)** — surfaced in-product when a user adds the plugin. Always create as part of scaffolding; the `documentation` link in `metadata.json` must point to it (e.g. `https://github.com/squaredup/plugins/blob/main/plugins/MyPlugin/v1/docs/README.md`).

The README must cover:

1. What the plugin monitors — objects imported, what dashboards show
2. Prerequisites / getting credentials — step-by-step, include required scopes/permissions
3. Configuration fields — table explaining every `ui.json` field: what it is, where to find the value, whether required
4. What gets indexed — list object types and what they represent
5. Known limitations — rate limits, permission requirements, API quirks

Write as if the user has never seen the API. They're reading it inside SquaredUp, not on the vendor's site.

**Other rules:**

- `scopes.json`: only include scopes used by OOB dashboards. Don't add speculatively.
- `configValidation.json`: optional but strongly preferred — see [common-patterns.md](references/common-patterns.md).
- **Single-dashboard rule:** Only create a sub-folder under `defaultContent/` when you have **multiple dashboards** for the same type.

---

## Phases 4–8: Writing Files

Read the corresponding reference file before writing each phase:

| Phase | Files | Reference |
| -------------------------- | --------------------------------------------------- | ---------------------------------------------------------------- |
| 4 — Plugin identity & auth | `metadata.json`, `ui.json`, `configValidation.json` | [metadata.md](references/metadata.md), [ui.md](references/ui.md) |
| 5 — Import definitions | `indexDefinitions/default.json` | [index-defs.md](references/index-defs.md) |
| 6 — Data streams | `dataStreams/*.json`, `scripts/*.js` | [data-streams.md](references/data-streams.md) |
| 7 — OOB default content | `defaultContent/`, `scopes.json` | [oob-content.md](references/oob-content.md) |
| 8 — Custom types | `custom_types.json` | [common-patterns.md](references/common-patterns.md) |

For reusable patterns (built-in properties stream, configValidation steps), read [common-patterns.md](references/common-patterns.md).

---

## Phase 9: Validate & Deploy

Invoke the `deploy-plugin` skill.
94 changes: 94 additions & 0 deletions .claude/skills/build-plugin/references/common-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Common Patterns and Custom Types

> **Note:** `$schema` is not a valid property in any SquaredUp plugin JSON file.

## Contents

- [custom_types.json](#custom_typesjson)
- [Built-in properties stream](#built-in-properties-stream)
- [Config validation steps](#config-validation-steps)

---

## custom_types.json

Adds friendly display names and FontAwesome icons per object type. The `sourceType` value must exactly match the type used in `objectMapping.type` in `indexDefinitions/default.json`.

```json
[
{
"name": "My Installation",
"sourceType": "My Installation",
"icon": "house",
"singular": "Installation",
"plural": "Installations"
},
{
"name": "My Device",
"sourceType": "My Device",
"icon": "microchip",
"singular": "Device",
"plural": "Devices"
}
]
```

Use **FontAwesome** icon names (`fontawesome.com/icons`), lowercase kebab-case. Common icons: `house`, `bolt`, `sun`, `battery-full`, `plug`, `thermometer`, `factory`, `gear`, `globe`, `wind`, `microchip`, `rotate`, `car`, `droplet`, `atom`, `gas-pump`, `wifi`, `camera`, `display`, `building`, `key`.

---

## Built-in properties stream

SquaredUp includes a built-in `datastream-properties` stream that automatically shows the indexed properties of any object. Use in OOB dashboards for a "Properties" or "Details" tile — no custom stream needed:

```json
"dataStream": {
"id": "datastream-properties"
}
```

---

## Config validation steps

`configValidation.json` is optional but strongly preferred. Use a **lightweight endpoint** (e.g. `/me`, `/user`). No extra flag needed in `metadata.json` — the presence of the file is sufficient.

```json
{
"steps": [
{
"displayName": "Authenticate",
"dataStream": { "name": "currentUser" },
"required": true,
"error": "Could not authenticate. Check your API key has the required scopes.",
"success": "Connected successfully."
},
{
"displayName": "Check data access",
"dataStream": { "name": "installations" },
"required": false,
"error": "Authenticated but no installations found.",
"success": "Installations accessible."
}
]
}
```

`required: true` — a failing step blocks the user from completing setup. Write error messages that name what to check, not just that something failed.

Steps can override stream parameters for validation-specific queries:

```json
{
"displayName": "Check warehouse access",
"dataStream": {
"name": "sqlQuery",
"config": { "query": "select 1", "errorOnEmptyResults": true }
},
"required": true,
"error": "No warehouse access.",
"success": "Warehouse accessible."
}
```

`errorOnEmptyResults: true` causes the step to fail if the stream returns no rows — useful when empty means access was denied.
Loading
Loading