Imported from liketrek/Plugin-Skill (
skills/trek-plugin-dev/SKILL.md). Install upstream withnpx skills add liketrek/Plugin-Skill --skill trek-plugin-dev. Copyright stays with the author.
TREK Plugin Development
Build plugins for TREK, a self-hosted
trip-planning app. A plugin is a directory with a manifest (trek-plugin.json),
a built CommonJS server entry (server/index.js), and — for page/widget/
trip-page types — a static client bundle (client/). TREK runs the server part in an
isolated child process reached only over RPC, and the UI in a sandboxed,
opaque-origin iframe. Distribution happens through the
TREK-Plugins registry: a static
index updated by pull request; plugin code and releases stay in the author's
own public GitHub repo.
Everything is driven by the npm package trek-plugin-sdk (Node >= 18):
definePlugin + types, a permission-enforcing mock host for tests, and the
trek-plugin CLI (npx trek-plugin-sdk <command>).
Reference files — read before working on that area
| Task at hand | Read |
|---|---|
Writing/editing trek-plugin.json, permissions, egress, settings |
references/manifest.md |
Server code: definePlugin, routes, jobs, ctx.*, error codes |
references/server-api.md |
| Widget/page UI: iframe sandbox, postMessage bridge, CSP | references/client-bridge.md |
Local dev server, fixtures, unit tests with createMockHost |
references/testing.md |
Any trek-plugin CLI command and its flags |
references/cli.md |
| Releases, registry entries, CI gates, signing, updates | references/publishing.md |
Golden path
create → dev → status → shot → publish. When you don't know what to do next,
run status: it grades every registry gate that can be answered offline and names
exactly one next command.
# 1. Scaffold (id must be a lowercase slug, 3–40 chars)
npx trek-plugin-sdk create my-widget --type widget # or: page | trip-page | integration
# The scaffold RUNS and PACKS immediately — but it does NOT pass `validate`
# (stub README, no screenshot). That's deliberate; `status` says what's missing.
# 2. Develop: edit trek-plugin.json, server/index.js, client/index.html
cd my-widget
npx trek-plugin-sdk dev # http://localhost:4317 — hot reload, no TREK needed
# Enforces your permissions for real: ctx, hooks, events,
# jobs AND egress. Read the banner — it warns about any
# entry point TREK would silently never call (rule 4).
# 3. Where am I? (never fails — orientation, not a gate)
npx trek-plugin-sdk status # checklist: Manifest / Code / Docs / Release / Repo
# + "next → <one command>"
# 4. The store image the registry requires (needs Playwright — see below)
npx trek-plugin-sdk shot # → docs/screenshot.png, 1600×900, in the themed frame
# 5. Write the README (4 sections, ≥400 chars of prose, a row per permission),
# commit + push, then re-run status until it's green. `validate` is the same
# checks with an exit code, for CI.
# 6. Publish: public GitHub repo (convention: trek-plugin-<id>), then ONE command:
npx trek-plugin-sdk publish --repo you/trek-plugin-my-widget --tag v1.0.0 --sign
# = ① check (every offline registry gate) → ② pack → ③ git tag + GitHub release
# → ④ preflight (the gates that need the release to exist) → ⑤ registry PR.
# If a step-① gate fails, NOTHING is packed, tagged, pushed or released — and if a
# LATER step fails, publish ROLLS BACK the release/tags it created, so either way
# you fix and re-run against the SAME version (--keep-release opts out;
# `unrelease <tag>` cleans up an already-stranded state). Requires git + gh (authed).
# In a TERMINAL, publish OFFERS to sign and creates the key for you — --sign is
# only needed in scripts/CI, which are never prompted. (keygen makes the key by
# hand: ~/.trek-plugin/signing.key — ONCE, ever, for all your plugins. BACK IT UP.)
validate is a real gate now, and pack is not. validate errors on
everything the registry would reject offline — a lucide icon that doesn't exist, a
README missing a section / under 400 chars of prose / with placeholders left / with
an unexplained permission, a screenshot that doesn't resolve to a file on disk, an
egress[] host with no matching http:outbound:<host> permission, name/description/
author outside the registry's length limits. pack refuses only what makes a plugin
unloadable (broken manifest, no server/index.js, a native binary), because packing
is how you install into a local TREK to try it — so a green pack is not a green
validate. See references/cli.md.
Say yes when publish offers to sign. A signature proves the artifact came
from you, not merely that the registry vouched for some bytes — so a compromised
registry can't ship code under your name. In a terminal publish proposes it and
makes the key for you; in scripts/CI, which are never prompted, pass --sign.
Signing late is fine — unsigned → signed at v1.4.0 breaks nobody, because
nothing is pinned until a signed version installs; the SDK retro-signs your
older versions automatically on the first signed update (the registry requires
every version signed once a key is present). The thing you cannot do is
stop (see rule 12), so the only decision that ever really binds is whether
you'll keep the key safe. Back it up and sign.
Update flow: bump version in the manifest, re-pack, new vX.Y.Z tag/release,
then entry --merge onto the existing registry file (newest version first) and
PR it. entry hashes your local plugin.zip — make sure it's the same file
you uploaded as the release asset. There's also a hand-edit path — see "Updating
a published plugin" in references/publishing.md.
Build the UI / store shot with the user, not for them
For a page/widget plugin the look is subjective — don't silently pick it,
and don't just describe it in words. Two rules:
1. Propose choices interactively, with suggestions. Before and while building the UI and the store image, offer the user concrete options tailored to the plugin and let them choose (use an interactive prompt — e.g. Claude Code's question UI — not an assumption). Good dimensions to ask about, each with 2–4 suggestions derived from what the plugin does:
- Accent colour(s) — hues that match the subject (weather → sky blue + sunset orange; a Japanese-phrase plugin → warm coral).
- Store-shot background — dark & atmospheric (an accent glow) vs. light (a colourful accent mesh).
- Pattern/texture — waves / dots / grid / none (some texture so it isn't flat and boring).
- Kicker, tagline, and which feature pills to show.
- Layout — light + dark side by side (shows theme support) vs. a single hero.
2. Show the draft as a screenshot for sign-off — don't ship on a description:
- The plain shot is now a CLI command:
npx trek-plugin-sdk shotbootsdev, renders your UI in the themed/previewframe and writes a 1600×900docs/screenshot.png(--darkfor the dark theme). It needs Playwright, which is deliberately not an SDK dependency —npm i -D playwright && npx playwright install chromium. Anintegrationhas no UI, soshotrefuses: screenshot the TREK surface your plugin changes instead (the notification it sends, the badge it adds to a place, its settings page). - For a composed store image — light + dark cards side by side, title, kicker,
feature pills, an accent-driven background — the SDK has no equivalent, so use the
skill's own kit:
assets/store-shot.html(set its CONFIG from the choices above:glow/mesh·waves/dots/grid) driven byassets/shot.mjs, which also has--previewfordocs/preview-light.png+docs/preview-dark.png(both themes in one run — what you show for UI sign-off). Set it up once withassets/setup.sh(--web-hookfor Claude Code web) →npm run preview-shot/npm run shot. See references/testing.md. - Either way, open
dev's themed/preview(light/dark/accent toggles) while you iterate, present the image(s), ask "does this look right?", and iterate. The approved shot doubles as the storedocs/screenshot.png— which is a hard registry gate on exactly that path (the store card loads it; a README image under any other name doesn't count), andvalidate/statusfail when the file is missing.
Choosing the plugin type
type |
Surfaces | Use for |
|---|---|---|
widget |
Dashboard card (sidebar slot — glassy auto-height) or a non-interactive boarding-pass hero strip (hero slot, ~110px, desktop-only); plus the scoped planner slots place-detail (gets placeId), day-detail (dayId), and reservation-detail (reservationId) |
At-a-glance info (flight status, weather, mascot); a per-place/day/reservation add-on |
page |
Own entry in the top navigation → full-page iframe (you own the layout) | A self-contained tool |
trip-page |
A tab inside every trip planner, scoped to the open trip (tripId always set); full-frame like page, no dashboard nav. capabilities.tripPage can replace core tabs / set tab position (tab-takeover) |
A per-trip tool |
integration |
No UI; background routes, jobs, events, plus wired provider hooks (place-detail / trip-warning / table / map-marker / map-layer / route / day-schedule / day-tint / pdf-section / atlas-layer / journal-entry / trip-card / photo / calendar / notification-channel) | Feeding/syncing data; enriching core UI natively |
Note: jobs[] need the jobs:run grant — with it, declared cron jobs run
on their cron schedule (userless); without it they never fire. The persistent
ctx.scheduler (at/in/every/cancel, same grant) adds
restart-surviving one-shot/recurring callbacks into a scheduled handler. To
react to core activity, declare events: events: [{ on, handler }] +
events:subscribe; the handler gets { event, tripId, entity?, entityId?, snapshot? } (snapshot only when you also hold the family's db:read:*), runs
with no user, fire-and-forget (the SDK exports the family catalog as
EVENT_FAMILIES / EVENT_SNAPSHOT_GRANT). All fifteen provider hooks are
wired (place-detail / trip-warning / table / map-marker / map-layer / route /
day-schedule / day-tint / pdf-section / atlas-layer / journal-entry / trip-card /
photo / calendar / notification-channel), plus the GDPR hook:user-data
(deleteUserData/exportUserData, userless, own-db) — so an integration can
inject native UI or honour data-rights with no iframe. A plugin of any type
can also publish MCP tools on TREK's own MCP server —
capabilities.mcpTools (≤ 8, declared + consented) + the mcp:tools
permission + a hooks.mcpToolProvider whose tools array names the same
tools: each is advertised to connected assistants as plugin_<id>_<name>
behind the opt-in plugins:use OAuth scope, and callTool runs as the
requesting user (route-like ctx, 15 s timeout). See
references/server-api.md.
Critical rules (violating any of these breaks install or CI)
- Never vendor
trek-plugin-sdk. The host makesrequire('trek-plugin-sdk')resolve inside the child at runtime. Keep it a devDependency. Any other runtime dependency must be vendored/bundled — TREK never runsnpm installon a plugin. - Ship built CommonJS.
package.jsoncarries"type": "commonjs";server/index.jsis plain built JS (.tsand.mapfiles are stripped bypack). Client files are pre-built static assets. - Egress trap: the runtime network guard and the iframe CSP are built from
the
http:outbound:<host>permissions, not fromegress[](which TREK never reads at runtime — it is the consent-screen declaration). A host listed inegress[]but not granted ashttp:outbound:<host>used to install, activate, consent and then be silently blocked at runtime;validate/statusnow error on it (code.egress-reachable), and the reverse — a reachable host you forgot to declare inegress[], i.e. understating your own network reach — is a warning. Keep both lists identical. Barehttp:outboundalone reaches nothing — unless you setoperatorEgress: true, which waives the non-empty-egress[]rule so you ship an emptyegress[]and the admin configures the real hosts at runtime (for plugins whose egress hosts aren't known up front).trek-plugin devruns the same guard (SDK ≥ 1.5.0), so an undeclared host fails locally instead of after install; private/loopback targets are blocked there too unless you setTREK_PLUGIN_ALLOW_PRIVATE_EGRESS=on, exactly as on the real host. - Silent-no-op trap: a hook / event / job you implement but never GRANT is never
called.
hooks.*needs its matchinghook:*permission,events: []needsevents:subscribe,jobs: []andscheduledneedjobs:run, anddeleteUserData/exportUserDataneedhook:user-data. Miss one and TREK does not error — it installs, activates, and simply never invokes that entry point. No log, no warning; all you see is "my plugin does nothing."validate/packcannot catch it (yourhooks: {}live inserver/index.js, which the manifest validator never loads); the only automatic check istrek-plugin dev, which warns at load and 403s if you fire one (SDK ≥ 1.5.0). Rundevonce before you publish and read the banner. Two exceptions live in the manifest and so are caught byvalidate: the notification channel (capabilities.notificationChannelwithouthook:notification-channel) and MCP tools (capabilities.mcpToolswithoutmcp:tools). MCP tools add a second silent trap all their own: only the intersection ofcapabilities.mcpTools[].nameand the code'shooks.mcpToolProvider.toolsarray is advertised — a name mismatch drops the tool silently, and no tool checks it anywhere (notvalidate, notdev, not the mock host). Keep the two lists identical by eye. ctx.trips,ctx.users,ctx.costs,ctx.ws.*— andctx.packing/ctx.files/ctx.places/ctx.days/ctx.itinerary/ctx.trips.update/ctx.meta— work only inside route handlers (they need the acting user the host binds from the request; fromonLoad/jobs/events →RESOURCE_FORBIDDEN).asUserIdis ignored;ctx.usersreturns only self or a trip co-member;ctx.ws.broadcastToUsertargets only the acting user. Your ownctx.ws.broadcastToTripdoes come back to your iframe — but only as a name-onlytrek:eventping (never the payload), and only on a frame that has atripId(trip-page+ the scoped detail widgets). Treat it as a refresh signal and re-fetch viatrek:invoke. A dashboardsidebar/herowidget has notripIdand gets nothing — it must poll. Severalctx.*paths write core TREK data (places/days/itinerary/trips.update, pluscosts.create): each is route-only and gated on the acting user's matching edit permission (place_edit/day_edit/trip_edit/budget_edit), exactly like the web UI.ctx.metastores the plugin's own namespaced data on a trip/place/day (reads need trip access, writes the entity's edit permission). Heads-up: these enrichment namespaces (meta/places/days/itinerary/costs/packing/files/trips.update) can beundefinedon a host that predates them. Your"trek"range is what prevents that, and since TREK 3.4.0 it is enforced: an instance outside the range refuses to install or activate your plugin. So declare it honestly — the scaffold writes"trek": ">=4.0.0 <5.0.0"— and the namespaces you need are guaranteed present on every host that can run you. Still guard anyway, for the one hole the gate leaves: a host whoseAPP_VERSIONis not a semver version (the Docker default is the literaldev) cannot be compared to a range, so the check is skipped and an unversioned build installs anything. Keepdb:ownas the source of truth, treatctx.metaas a best-effort mirror, and put every optional call behind a thunked guard (attempt(() => ctx.meta.set(…))— the thunk also catches the synchronous property throw). See server-api.md and testing.md. Budget amount key istotal_price, notamount(unknown keys are silently dropped → saves 0).ctx.trips.getPlacesreturns the place pool (created_at DESC), not the itinerary — usectx.trips.getDaysfor day order. Thectx.*surface is broad — booking/roster/personal-data DB namespaces (reservations/accommodations/packingwrites+bags/collab/journal/atlas/vacay/collections/daynotes/todos/tags/categories/trips.members+addMember+create/files.getContent+writes),ctx.metanow also onreservation/accommodation,ctx.settings.getfor per-user settings,ctx.db.txatomic batches, andctx.plugins.call/ctx.events.emitfor inter-plugin calls — each behind its own new permission (see manifest.md). Host brokers are a distinct, non-DB family —ctx.notify(notify:send),ctx.ai(ai:invoke),ctx.oauth(oauth:client),ctx.weather(weather:read),ctx.rates(rates:read):notify/oauthare acting-user-scoped (route-only),ai/weather/ratesare tenant-free (work userless). AI output is data-only — never treat it as instructions. If your plugin stores personal data, implement the GDPRhook:user-data(deleteUserData/exportUserData, userless, own-db).- No native modules —
.node,binding.gyp,prebuilds/are refused at pack, CI, and install time.nativeModulesmust befalse/absent. - Git tag == manifest
version(v1.2.3↔"version": "1.2.3"), and the registry pins the release asset's exact sha256 — never re-upload or mutate a releasedplugin.zip; cut a new version instead. Re-packs on other machines/SDK versions can produce different bytes (CRLF, walk order), so always takesha256/sizefrom the uploaded release asset, never from a local re-pack — see references/publishing.md. - README quality gate is a hard CI gate: sections What it does /
Screenshots / Permissions / Setup (substring-matched, any heading level),
≥ 400 chars of real prose (headings, tables, code and links are stripped
before counting — it has to be sentences), at least one screenshot whose URL
returns
Content-Type: image/*(a committed file —data:URIs don't count), no leftover placeholders ({{…}},REPLACE_ME,Describe what/the …,your-name/trek-plugin), and every declared permission string must appear in the README. All five now failvalidate/statuslocally — the SDK ports the registry'scheck-readme.mjsline for line — so you find out before you cut a release, not after.preflightre-grades the README at the pinned commit, which is what catches the classic green-tree/red-tag: you wrote it and never committed it. See references/publishing.md. docs/is not shipped inplugin.zip(by design). Commitdocs/screenshot.pngto the repo — the store fetches it from GitHub at the pinned commit.- Reserved ids:
registry,install,rescan. Everything else matching^[a-z][a-z0-9-]{2,39}$is allowed and bound to your GitHub owner on first registration (nobody can repoint it later). - Registry PR = exactly one file,
registry/plugins/<id>.json. Never touchdist/(generated on merge) or setreviewedAt/boundOwner(CI-maintained). - Sign your plugin — and then never stop. Signing is technically optional
(unsigned installs on the sha256 pin alone — one fewer guarantee, not
"unsafe"), but sign anyway: in a terminal
publishoffers it and creates the key; in scripts,--signon every publish. The pin only proves the bytes are what the registry served; the signature proves they are what you built. It is the difference between trusting the registry and trusting the author, and it is the one security property only you can supply. It is a one-way door you may walk through late: unsigned → signed at any version breaks nobody (nothing is pinned until a signed version installs), so "sign from v1.0.0 or never" is simply false — and the first signed update retro-signs the older versions for you (each pinned artifact is downloaded, verified against its sha256, and signed with the same key; the registry requires every version signed once a key appears). But once a plugin has shipped signed, TREK refuses — on every instance that already has it — an update that drops the key, changes the key, or ships an unsigned version, and registry CI blocks all three before merge.publishalso refuses an unsigned release — or one signed with a different key — of an already-signed plugin at step 1, before anything is tagged or released. Only a key rotation is recoverable, and the SDK now drives it (≥ 1.7.0):rotate-keyrotates without shipping a version,publish --sign --allow-key-changerotates as part of a release — both re-sign every published version with the new key and open a PR flagged as a rotation. The human half stays human: a maintainer must applyallow-key-change, and every admin must re-trust the new key (see publishing.md). Dropping the key or shipping an unsigned version has no override at all. → Back up~/.trek-plugin/signing.key. Losing it no longer strands you —rotate-keyexists — but it still strands every existing install until each admin re-trusts the new key by hand. - Manifest
routes[]andcapabilities.navare declarative only. The host reads real routes off the loadeddefinePluginobject; a page's nav entry is built from top-levelname(label) and top-levelicon(glyph) — nothing undercapabilities.navis consumed. Youriconis any lucide name and TREK draws it everywhere it renders you (nav, trip tab, widget header, settings card, Admin row, store tile). An unknown name silently falls back toBlocks, so a typo just makes you look generic — which is whyvalidatenow errors on it (it used to only warn) and registry CI rejects it.createprompts for one, validates it against lucide as you type, and writes a sensible default for the type if you skip it. - The UI frame loads only its own bundled assets — never external ones.
It runs at an opaque origin under a strict CSP where
'self'matches nothing; an explicit own-path source allows yourclient/files by relative path (./logo.png, a bundled.woff2, a multi-file Vite/React build — no inlining needed), while external CDNs/fonts stay blocked anddata:/blob:/inline SVG always work. The own-path allow depends on a well-formedHostheader — for load-bearing artwork, inline SVG is the most robust (koffi's mascot is inline SVG).trek-plugin devapplies no CSP/sandbox, so an external asset that works indevstill fails in the real host — verify against the real frame. See references/client-bridge.md.
Isolation model (what plugin code can rely on)
- Own OS process under Node's permission model; filesystem reads scoped to the
plugin's own code. No
JWT_SECRET, notrek.db, no file writes, no child processes, no worker threads. - All host access via the
ctxobject; an ungranted capability throwsPERMISSION_DENIED, an unknown methodUNKNOWN_METHOD. - Own data only through
ctx.db(a private SQLite file, requiresdb:own). - UI iframe: opaque origin (sandbox without
allow-same-origin), no cookies, no parent DOM; talks to TREK only viapostMessagewith target origin'*'; CSPdefault-src 'none',connect-srclimited to granted hosts. - The raw child↔host IPC channel is sealed before your code loads —
process.send/process.on('message')/disconnectare revoked;ctxis the only channel in. - Crash/hang/OOM kills only the plugin's process; TREK keeps running. Watchdog:
RSS 300 MB, 192 MB heap, 30 s
onLoad/route timeouts, 5 crashes/5 min → auto-disabled (see references/server-api.md). - Per-plugin RPC rate limit: a token bucket at the
ctxdispatch boundary (defaults burst 60, 20/s, 16 in-flight; envTREK_PLUGIN_RPC_BURST/_PER_SEC/_INFLIGHT) throttles a runaway plugin instead of freezing the single-threaded host.
Instance & ops facts
- Plugin system is on by default; kill switch
TREK_PLUGINS_ENABLED=false(also accepts0/off/no). Admin UI: Admin → Plugins (Installed / Discover). Rescan re-reads the plugins directory and force-refreshes the remote registry (bypasses the 30-min + GitHub CDN cache, so a just-merged plugin shows up immediately). - Sideloading: admins can upload a plugin
.zip/.tar.gzvia Admin → Plugins (drag-drop / Upload). It installs inactive, is flagged Sideloaded (local:upload, unsigned, unreviewed, no auto-update), and still needs activation + permission consent; same extract/manifest/native guards as a registry install; ≤ 50 MB. - Plugin code lives in
TREK_PLUGINS_DIR(default<data>/plugins), plugin SQLite data inTREK_PLUGINS_DATA_DIR(default<data>/plugins-data). Behavior-affecting operator vars:TREK_PLUGIN_MAX_RSS_MB(default 300),TREK_PLUGIN_ALLOW_PRIVATE_EGRESS=on(lifts the SSRF block on internal addresses),TREK_PLUGIN_PERMISSIONS=off(weakens the OS fs/child sandbox),TREK_PLUGIN_REGISTRY_URL(override registry source);TREK_PLUGINS_DEV_LINK=1enables the DEV-ONLY dev-link workflow (link/reload a local build against real data — off by default, never set in production; see references/cli.md), the RPC-limit knobsTREK_PLUGIN_RPC_BURST/_PER_SEC/_INFLIGHT, and the log rate-limit knobsTREK_PLUGIN_LOG_BURST/_PER_SEC(defaults 50/10). - Per-plugin activity log: every user can audit what plugins did in their
name at
GET /api/plugin-activity(hash-chained audit trail) — design write paths knowing each call is user-visible. - Backups include plugins: TREK backup/restore archives each plugin's per-plugin SQLite data tree and installed code (staged and swapped in on next boot), so a restore no longer loses plugin state. Older archives without them are a no-op.
- Installed plugins must be activated one by one; a version bump that requests more permissions requires the admin to re-approve.
- The
trekrange is enforced (TREK ≥ 3.4.0), at install AND at activation. Install is refused on every path (registry, pinned version, update, sideload, dev-link) when the running TREK is outside your range, and activation re-checks it — so a plugin installed on 3.3 stops starting once the operator upgrades past the range's upper bound. It stays installed and visible, switched off, with the reason shown. There is no admin override: the range is your own statement that the plugin won't work there."install latest"resolves to the newest version this TREK can run, and an update that would drag a working plugin out of compatibility is refused rather than performed. - Instance-scoped settings actions need
trek>=4.2.0. A host older than that ignores an action'sscopeand renders the button on every user's settings page instead (running it as each user) — see references/manifest.md. - Current plugin API:
apiVersion: 1(PLUGIN_API_VERSION) — enforced: it must be a positive integer, and a manifest declaring a version newer than the host supports is refused at install and won't activate (API_VERSION_INCOMPATIBLE, no admin override). Thetrekrange remains the day-to-day compatibility gate. Artifact limits: 25 MB/file, 50 MB total, 4000 zip entries.
Canonical examples
Two official examples in the TREK repo (plugin-sdk/examples/) — pick the one
that matches your plugin's shape:
koffi— the mascot hero-widget: manifest, membership-checked trip read, the iframe bridge, hand-built inline SVG UI, and a README that passes the CI gate. The reference for anything with its own sandboxed UI. Registry entry:registry/plugins/koffi.jsonin TREK-Plugins.trip-doctor— a hooks-only, no-UIintegration: it feeds TREK's own planner surfaces throughwarningProvider.getWarningsandplaceDetailProvider.getDetails, and pins private notes viactx.metabehind aPOST /pinroute. The reference for the provider-hook +db:metapattern — TREK renders everything natively, so there's no CSP/iframe to fight. (Its README'snpx @trek/plugin-sdk …and "signed.trekpluginbundle" are example prose, not the real CLI — the package istrek-plugin-sdkandpackemitsplugin.zip; signing is a separatesignstep. Use the commands in cli.md.)
The Plugin Cookbook (wiki/Plugin-Cookbook.md) collects copy-paste
recipes for the above plus itinerary writes, meta tagging, ws broadcast, and
the trek:ui design kit — a good first stop when you know the capability but not
the exact call.
Reporting errors in this skill
This skill is documentation verified against TREK's source, but TREK evolves and gaps remain. If, while using this skill, you hit a claim here that contradicts what the TREK source or a real TREK instance actually does — or a gap that costs real time — do NOT just move on. Fill in the block below and hand it to the user, ready to paste, so it can be fixed for everyone.
Emit it verbatim in a fenced code block, every field filled from what you actually did, then tell the user: "Copy this and paste it into a new issue at https://github.com/liketrek/Plugin-Skill/issues/new/choose (pick 📋 Paste an agent-generated report) — it's already filled in."
## Skill feedback
**Type:** discrepancy | missing-guidance
**Skill file + section:** references/<file>.md → <section>
**What the skill says:** <quote the exact wording, or "n/a — not covered">
**What actually happens / what's missing:** <the correct fact or the gap>
**Evidence:** source-read | real TREK instance | trek-plugin dev | custom harness (no real CSP/sandbox) | inferred (
unconfirmed)
**Citation / repro:** <TREK repo path @ commit/tag, or exact steps>
**TREK version:** <x.y.z or unknown>
**trek-plugin-sdk version:** <x.y.z or unknown>
**OS (if tooling-related):** <or n/a>
**Suggested fix (optional):** <proposed wording>
_Generated by the trek-plugin-dev skill._
Be truthful in the Evidence field — it is the most important line. An
inference is not a confirmed discrepancy; label it inferred (unconfirmed)
and say so out loud to the user. This is load-bearing: several reported "bugs"
have turned out to be test-method artifacts (e.g. an image failing in a bare
harness that has none of TREK's real CSP), not skill errors. Prefer to verify
against the TREK source or a real instance before claiming a discrepancy.
Primary sources
- Wiki: Plugin-Development · Plugin-Permissions · Plugin-Publishing · Plugins
- Registry: liketrek/TREK-Plugins
(
schema/plugin-entry.schema.json,schema/example-entry.json) - SDK:
trek-plugin-sdkon npm (source:plugin-sdk/in the TREK repo)