-
Notifications
You must be signed in to change notification settings - Fork 1
Experimental: AI Skill for building plugins #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+1,894
−0
Merged
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
238290b
v1
clarkd 370d271
v2
clarkd a42ce7c
v3
clarkd 860d8b0
v4
clarkd 67ed6c5
v5
clarkd a7cef2e
v6
clarkd 8cf3ac5
Ensure Claude skill uses password field for api keys
andrewmumblebee b4a25f4
Update importFrequencyMinutes to frequencyMinutes
andrewmumblebee a46c6cc
deduplicate help property in skill for ui.json
andrewmumblebee 69afcb5
Remove suffix as can cause Claude to update or create a new plugin
andrewmumblebee 0dbc190
move allowEncryption skill tip into key-value
andrewmumblebee f36246c
add version metadata to skill to track changes
andrewmumblebee 2a7f06a
Add editorconfig to preserve indentation from product
andrewmumblebee d32594f
Ask for author handle before writing metadata.json
andrewmumblebee cd40e40
Always create docs/README.md when scaffolding
andrewmumblebee a847ab6
Ensure Claude uses id property for built in datastream
andrewmumblebee 74243e4
Ensure user is prompted for user handle
andrewmumblebee 96bcfa9
remove built-in properties warning
andrewmumblebee 7aa1968
Ensure dashboard titles are unique
andrewmumblebee 78159e3
post process svg icons to provide contrast between application ui
andrewmumblebee af88cf4
Fix ui json in dataStreams using incorrect displayName property
andrewmumblebee cf0f3f2
Apply suggestions from code review
clarkd 4b36853
Merge pull request #47 from squaredup/work/ah/skill-password
andrewmumblebee e222e79
Load build-plugin reference content on demand
andrewmumblebee 9e9d465
Revert user input change to previous text
andrewmumblebee 3f96579
Add visibility section to dataStreams
andrewmumblebee 3394156
Add gen-uuids script for oob content
andrewmumblebee ba4bedc
Teach build-plugin to prefer column expressions
andrewmumblebee d3e651f
Default build-plugin streams to no script
andrewmumblebee 24e2d86
Remove postRequestScript from dataStream examples
andrewmumblebee f13efa0
Add notes on when to use postRequestScript over expressions on columns
andrewmumblebee 875a281
Remove schema references from skill
andrewmumblebee f651254
Note that object mapping properties may need to be iterated
andrewmumblebee ebba258
remove newline from json file ends
andrewmumblebee 97a9a14
Add a plan format to keep skill consistent
andrewmumblebee 95f43fe
Add note on using {{object.rawId} in object expressions
andrewmumblebee d8fd4d4
Merge pull request #49 from squaredup/work/ah/skill-progressive
andrewmumblebee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
|
||
| ``` | ||
|
|
||
| **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. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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 usesplugins/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 toMyPlugin/v1/(the kebab-case stays only in themetadata.jsonnamefield).Extended reasoning...
The bug
In
.claude/skills/build-plugin/SKILL.md, the Phase 3 "File structure" code block starts with:This lowercase kebab-case folder name contradicts three load-bearing sources in this same PR and repo:
references/metadata.mdin 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.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.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 likemkdir -p my-plugin/v1/dataStreamsand write files there. By the time Phase 4 instructs the agent to readmetadata.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 lowercasenamefield inmetadata.jsondoes match the folder name and looks superficially consistent.Step-by-step proof
my-plugin/v1/metadata.json,my-plugin/v1/ui.json, etc.references/metadata.md, sees the PascalCase rule — but only as a field note aboutnamevs. folder. The agent has already created the folder; no instruction tells it "go back and rename."plugins/my-plugin/v1/— inconsistent with all 19 existing plugins. Reviewer asks for a rename, which involves rewriting paths in the documentation link insidemetadata.jsontoo.Why existing code does not prevent it
There is no validator step that checks folder casing —
squaredup validatevalidates JSON contents, not directory names. The PascalCase rule exists only in prose insidemetadata.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:
Leave the
namefield inmetadata.jsonasmy-plugin(kebab-case) — those are separate things, exactly asmetadata.mdstates. No other lines in the scaffold need to change.