Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
64 changes: 50 additions & 14 deletions .claude/skills/build-plugin/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
---
name: build-plugin
description: Build a SquaredUp low-code plugin for any HTTP/REST API β€” from exploring the API through writing data streams, dashboards, and deploying. Use when building or extending a SquaredUp plugin, data source, or integration. Trigger phrases include "build a plugin", "create a plugin", "new plugin", "add a data source", "integrate with", "build an integration", "connect to [service]", "I want to pull data from", "monitor [service] in SquaredUp".
metadata:
author: SquaredUp
version: "0.0.1"
---

# Building a SquaredUp Low-Code Plugin
Expand All @@ -13,6 +16,18 @@ This skill guides you through building a complete SquaredUp low-code plugin for

---

## Required user inputs (always ask)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So this works, but if you use auto mode in Claude it still bypasses this, may remove

Similar to other AskUserQuestions, does use roughly my handle


The following inputs **cannot be inferred from the environment** and must be collected via `AskUserQuestion` before the corresponding file is written. **Ask these even when the user has requested autonomous / "no clarifying questions" mode** β€” they are not clarifying questions, they are required data that ends up baked into the plugin (and into git history).

| Input | When to ask | Why |
| --- | --- | --- |
| **Author handle** (GitHub handle or display name) | Before writing `metadata.json` (Phase 4) | Goes into `author.name` in `metadata.json` and shows in the UI. Guessing from git config or environment frequently picks the wrong identity (employer email vs personal handle, etc.). |

If the user has already volunteered the answer earlier in the conversation, use that and skip the prompt. Otherwise, ask β€” even in autonomous mode.

---

## When to Use

- Building a new plugin for an HTTP/REST API
Expand All @@ -30,7 +45,7 @@ Create a TodoWrite task for each phase before starting:

- [ ] **Phase 1** β€” Explore the API
- [ ] **Phase 2** β€” Plan the plugin structure
- [ ] **Phase 3** β€” Scaffold files (`metadata.json`, `ui.json`, `icon.png`)
- [ ] **Phase 3** β€” Scaffold files (`metadata.json`, `ui.json`, `icon.png`, `docs/README.md`)
- [ ] **Phase 4** β€” Write import definitions (`indexDefinitions/default.json`)
- [ ] **Phase 5** β€” Write data streams
- [ ] **Phase 6** β€” Write OOB default content (dashboards, scopes)
Expand Down Expand Up @@ -72,7 +87,13 @@ Decide before writing code. **Write this plan down and share it with the user be

## Phase 3: File Structure

**Icon:** Do not create or generate the icon yourself. Find the official brand/product logo online (SVG or PNG accepted by the validator), or ask the user to supply one. A square icon works best. Never auto-generate a generic icon.
**Icon:** Do not create or generate the icon yourself. Find the official brand/product logo online (SVG or PNG accepted by the validator), or ask the user to supply one. Never auto-generate a generic icon.

**Post-process SVG icons only if needed.** SquaredUp displays icons on a dark background in dark mode and a white background in light mode. If the SVG lacks a background or is not square, fix it:

1. **Make it square** β€” Set `width="512" height="512" viewBox="0 0 512 512"`.
2. **Add a background** β€” Insert `<rect width="512" height="512" fill="BRAND_COLOR"/>` as the first child. Pick a colour that contrasts with the logo paths.
3. **Add padding** β€” Wrap paths in `<g transform="translate(X, Y) scale(S)">` targeting ~10% padding (inner area 409.6Γ—409.6): `S = min(409.6/w, 409.6/h)`, `X = (512βˆ’w*S)/2`, `Y = (512βˆ’h*S)/2`.

**configValidation.json:** Optional but strongly preferred. Wrap a simple API call (e.g. `/me`, `/user`, or any lightweight authenticated endpoint) to verify the config works on setup. For complex APIs with distinct permission scopes (e.g. AWS CloudWatch, Cost Explorer, EC2), include multiple validation steps β€” one per capability β€” so users know exactly what's working.

Expand All @@ -90,6 +111,8 @@ my-plugin/
icon.svg # Square SVG β€” use official brand logo, ask user if unsure
custom_types.json # Friendly names + FontAwesome icons per type
configValidation.json # Preferred: validate config on setup
docs/
README.md # REQUIRED: shown in-product when users add the plugin
indexDefinitions/
default.json # Import steps
dataStreams/
Expand All @@ -107,10 +130,25 @@ my-plugin/
dashboard2.dash.json
```

### docs/README.md (required)

This file is surfaced in-product when a user adds the plugin β€” it is the primary place to tell users how to configure it. Always create it as part of scaffolding, before moving to later phases. 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 should cover:

1. **What the plugin monitors** β€” one short paragraph: what the service is, what objects are imported, and what the dashboards show.
2. **Prerequisites / getting credentials** β€” step-by-step instructions to obtain an API key, token, or OAuth credentials. Include any required scopes or permissions. Link to the service's own credential pages where helpful.
3. **Configuration fields** β€” a table or short list explaining every field in `ui.json`: what it is, where to find the value, and whether it's required.
4. **What gets indexed** β€” list the object types and what they represent.
5. **Known limitations** β€” rate limits, permission requirements, or API behaviours the user should know about.

Write this as if the user has never seen the API before. They're reading it inside SquaredUp, not on the vendor's site, so don't assume they'll follow external links for basic setup steps.

---

## Phase 4: metadata.json


```json
{
"name": "my-plugin",
Expand Down Expand Up @@ -251,11 +289,9 @@ For Web API plugins, always use `"hybrid"` unless the user specifically requests
Defines the config form shown when a user adds the plugin. One entry per config field. All field types share these common properties:
- `name` β€” the field's key, referenced as `{{fieldName}}` in expressions
- `label` β€” displayed in the form
- `help` β€” tooltip text shown as a `(?)` icon
- `defaultValue` β€” pre-populated value
- `validation` β€” e.g. `{ "required": true }`
- `allowEncryption: true` β€” marks the field as a secret (encrypted at rest); use on any token, password, or key field
- `help` β€” tooltip text; **supports markdown** (links, bold, etc.)
- `help` β€” tooltip text shown as a (?) icon; **supports markdown** (links, bold, etc.))
- `tileEditorStep` β€” controls which tile editor step the field appears in; defaults to `["Parameters"]`. Set to `["Timeframe"]` to place a field on the Timeframe step. **JSON-only** β€” cannot be set via the Save as data stream modal; must be added directly to the data stream JSON file after export.

**Conditional visibility** β€” any field or fieldGroup can be conditionally shown using `visible`:
Expand Down Expand Up @@ -286,7 +322,7 @@ Defines the config form shown when a user adds the plugin. One entry per config
{ "type": "text", "name": "hostname", "label": "Hostname", "placeholder": "api.example.com" }
```

**`password`** β€” masked text input (alternative to `text` + `allowEncryption`):
**`password`** β€” masked text input; **use this for any API key, token, secret, or password field** (preferred over `text` + `allowEncryption`):
```json
{ "type": "password", "name": "apiKey", "label": "API Key" }
```
Expand Down Expand Up @@ -348,9 +384,9 @@ Defines the config form shown when a user adds the plugin. One entry per config

> ⚠️ When using a data stream as the autocomplete source, the backing stream must return rows with `label` (string) and `value` columns, and those columns must have `"role": "label"` and `"role": "value"` declared in the stream's metadata β€” otherwise the dropdown won't populate correctly.

**`key-value`** β€” list of key/value pairs (useful for custom headers, tags):
**`key-value`** β€” list of key/value pairs (useful for custom headers, tags).
```json
{ "type": "key-value", "name": "headers", "label": "Headers" }
{ "type": "key-value", "name": "headers", "label": "Headers", "allowEncryption": true }
```

**`expression`** β€” expression/template input:
Expand Down Expand Up @@ -441,7 +477,7 @@ Defines what gets imported into the SquaredUp graph.
- `properties` are extra fields stored on the graph node and accessible in data stream scripts as `object.propName`.
- Use `{ "targetProp": "sourceProp" }` syntax when the column name differs from the property name you want.
- The `sourceType` column value **must** match an entry in `objectTypes` β€” otherwise objects won't import.
- `importFrequencyMinutes` β€” controls how often SquaredUp re-runs the import. Defaults to `720` (12 hours).
- `frequencyMinutes` β€” controls how often SquaredUp re-runs the import. Defaults to `720` (12 hours).

**Import data stream pattern** β€” the stream called by an import step must return one flat row per object with at least `sourceId`, `name`, `sourceType`:

Expand Down Expand Up @@ -588,7 +624,7 @@ Expressions support **inline JavaScript**, so you can use any JS expression insi
"name": "deviceMetric",
"displayName": "Device Metric",
"ui": [
{ "name": "metric", "displayName": "Metric Name", "type": "text" }
{ "name": "metric", "label": "Metric Name", "type": "text" }
],
"config": {
"endpointPath": "devices/{{object.deviceId}}/metrics",
Expand Down Expand Up @@ -1045,6 +1081,7 @@ Single `.dash.json` files reference directly as `"type": "dashboard"`. Folders m

**Dashboard rules:**
- **Do not repeat the plugin name in dashboard names.** The name appears beneath the plugin name in the UI, so "Overview" reads as "MyPlugin / Overview" β€” adding the plugin name again produces "MyPlugin / MyPlugin Overview".
- **Give each dashboard a distinct, descriptive name.** Perspective tabs sit next to each other in the UI β€” identical names (e.g. every perspective called "Overview") are indistinguishable.
- `"variables"` array supports **only one variable** per dashboard. Design each dashboard around a single object type.
- Omit `"timeframe"` on tiles to inherit the dashboard timeframe β€” do not hardcode `"last24hours"` on tiles.
- All tile IDs (`"i"`) must be **genuinely random UUIDs** β€” generate them with `uuidgen` (macOS/Linux) or `python3 -c "import uuid; print(uuid.uuid4())"`. Never invent fake patterned UUIDs like `a1111111-1111-1111-1111-111111111111`.
Expand Down Expand Up @@ -1259,9 +1296,8 @@ squaredup validate --watch # re-validate on every file change (useful durin
squaredup validate --json # output JSON β€” use this flag when running validation as Claude/AI agent

# Deploy
squaredup deploy --suffix <yourname> # suffix namespaces your deployment (e.g. initials)
squaredup deploy --suffix <yourname> --force # overwrite without confirmation prompt
squaredup deploy --watch # re-deploy automatically on file changes
squaredup deploy --force # overwrite without confirmation prompt
squaredup deploy --watch # re-deploy automatically on file changes

# List and delete deployed plugins
squaredup list # list all plugins deployed to your tenant
Expand Down Expand Up @@ -1294,7 +1330,7 @@ SquaredUp includes a built-in `datastream-properties` data stream that automatic

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

Expand Down
14 changes: 14 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.{md,json,js}]
indent_style = space
indent_size = 4

[*.md]
trim_trailing_whitespace = false
Loading