From 4664b072fa00190f7d74d05a2e67f23fb543fe13 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 18 Jul 2026 10:17:31 -0400 Subject: [PATCH 1/2] docs: CHANGELOGs + freshness riders conformance wave (10 plugins) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wave #316 of epic #313 (matrix dims 7 + 14). CHANGELOGs (dim 7): firecrawl 0.2.2, miro 0.1.2, playwright 0.2.1 now ship Keep-a-Changelog files seeded with the current version entry — every versioned plugin in the fleet now has one. Miro's version drift is resolved: package.json, the MCP server Implementation version, the lockfile, and the rebuilt committed bundle all report 0.1.2 (drift check, typecheck, and the 32-test suite green). Freshness riders (dim 14), facts re-verified this session before stamping — corrections where verification failed: - context7 0.3.1: CLI facts corrected against live ctx7 0.5.5 (--base-url default, lowercase -v, optional library query, upstream skills-surface deprecation, new remove/upgrade commands); the Claude Code unset-env MCP claim rewritten per current docs (config loads with a warning and sends the literal ${VAR} — not a parse failure). - songwriting 0.4.1: lyrics limit corrected to a 5,000-char hard cap on v4.5/v5/v5.5 with ~3,000 kept as the quality sweet spot (the May consensus position flipped), title ~100 chars, Studio row Premier-exclusive; unverifiable rows keep hedges undated; conflicting upload-length figures cross-flagged; all help.suno.com links fixed to the working /en/articles/ form. - claude-ops 0.11.3: scope-semantics doc dated (version gates re-verified: --force >=2.1.163, renames >=2.1.193, prune >=2.1.121, userConfig type set); monitor-restart claim now cites plugins-reference; setup pluginConfigs claim dated + pinned >=2.1.207. - desktop-notification 0.3.2: terminalSequence v2.1.141 claim verified against the Claude Code changelog and dated (plus a backfilled 0.3.1 entry for the #298 hook-utils resync). - skill-quality 0.4.1 and verification 0.2.1: pluginConfigs and bundled /verify + /run >=2.1.145 claims verified, dated, linked. - ai-briefing 0.5.2: README Playwright matrix dated against the official system-requirements page; the setup skill defers to that link instead of restating the matrix. ## Related - Closes #316 Co-Authored-By: Claude Fable 5 --- .../ai-briefing/.claude-plugin/plugin.json | 2 +- plugins/ai-briefing/CHANGELOG.md | 10 +++++++++ plugins/ai-briefing/README.md | 6 +++-- plugins/ai-briefing/skills/setup/SKILL.md | 8 +++---- plugins/claude-ops/.claude-plugin/plugin.json | 2 +- plugins/claude-ops/CHANGELOG.md | 11 ++++++++++ .../skills/plugins/context/scope-semantics.md | 12 +++++----- plugins/claude-ops/skills/setup/SKILL.md | 5 +++-- plugins/context7/.claude-plugin/plugin.json | 2 +- plugins/context7/CHANGELOG.md | 15 +++++++++++++ plugins/context7/skills/lookup/context/cli.md | 22 ++++++++++++++----- plugins/context7/skills/lookup/context/mcp.md | 6 ++++- .../.claude-plugin/plugin.json | 2 +- plugins/desktop-notification/CHANGELOG.md | 16 ++++++++++++++ plugins/desktop-notification/README.md | 4 ++++ plugins/firecrawl/.claude-plugin/plugin.json | 2 +- plugins/firecrawl/CHANGELOG.md | 16 ++++++++++++++ plugins/miro/.claude-plugin/plugin.json | 2 +- plugins/miro/CHANGELOG.md | 22 +++++++++++++++++++ plugins/miro/dist/index.min.js | 2 +- plugins/miro/package-lock.json | 4 ++-- plugins/miro/package.json | 2 +- plugins/miro/src/index.ts | 2 +- plugins/playwright/.claude-plugin/plugin.json | 2 +- plugins/playwright/CHANGELOG.md | 16 ++++++++++++++ .../skill-quality/.claude-plugin/plugin.json | 2 +- plugins/skill-quality/CHANGELOG.md | 8 +++++++ plugins/skill-quality/skills/setup/SKILL.md | 5 +++-- .../songwriting/.claude-plugin/plugin.json | 2 +- plugins/songwriting/CHANGELOG.md | 15 +++++++++++++ plugins/songwriting/skills/suno/SKILL.md | 16 ++++++++------ .../songwriting/skills/suno/context/lyrics.md | 2 +- .../songwriting/skills/suno/context/studio.md | 6 ++--- .../songwriting/skills/suno/context/style.md | 6 ++--- .../skills/suno/context/troubleshoot.md | 4 ++-- .../skills/suno/context/v55-features.md | 18 +++++++++++---- .../songwriting/skills/suno/context/voices.md | 4 +++- .../skills/suno/context/workflow-recipes.md | 4 ++-- .../verification/.claude-plugin/plugin.json | 2 +- plugins/verification/CHANGELOG.md | 8 +++++++ plugins/verification/skills/confirm/SKILL.md | 2 +- 41 files changed, 236 insertions(+), 61 deletions(-) create mode 100644 plugins/firecrawl/CHANGELOG.md create mode 100644 plugins/miro/CHANGELOG.md create mode 100644 plugins/playwright/CHANGELOG.md diff --git a/plugins/ai-briefing/.claude-plugin/plugin.json b/plugins/ai-briefing/.claude-plugin/plugin.json index f7c5ff39f..130bff06e 100644 --- a/plugins/ai-briefing/.claude-plugin/plugin.json +++ b/plugins/ai-briefing/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "ai-briefing", - "version": "0.5.1", + "version": "0.5.2", "description": "Build source-backed AI-industry briefings from official vendor publications, configured RSS/Atom feeds, GitHub releases, reputable secondary reporting, and user-supplied URLs. Deduplicate, rank, and present results as markdown or optional HTML/PPTX decks, with repository-owned profile, audience, and brand configuration. Automated X/Twitter collection is disabled; Playwright is used only for deterministic local rendering.", "author": { "name": "Melodic Software", diff --git a/plugins/ai-briefing/CHANGELOG.md b/plugins/ai-briefing/CHANGELOG.md index ef437d7fd..c9a6bc262 100644 --- a/plugins/ai-briefing/CHANGELOG.md +++ b/plugins/ai-briefing/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to the `ai-briefing` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.5.2] + +### Changed + +- **Freshness rider on the Playwright environment matrix** (fleet conformance + wave: volatile platform facts carry a verified-date + official link). The + README matrix is dated and re-verified against Playwright's system + requirements; the setup skill no longer restates the version matrix and + defers to the linked page as authoritative. + ## [0.5.1] ### Changed diff --git a/plugins/ai-briefing/README.md b/plugins/ai-briefing/README.md index 8cb4047f7..59f857288 100644 --- a/plugins/ai-briefing/README.md +++ b/plugins/ai-briefing/README.md @@ -23,7 +23,10 @@ and presents the result as markdown or an optional HTML/PPTX deck. ## Optional build prerequisites -The presentation pipeline follows Playwright's current supported environment matrix: +The presentation pipeline follows Playwright's current supported environment matrix +(verified 2026-07-18 against +[Playwright system requirements](https://playwright.dev/docs/intro#system-requirements) — +re-check that page before installing, the matrix moves with Playwright releases): - the latest Node.js 22.x, 24.x, or 26.x release, with npm; - Windows 11+ or Windows Server 2019+; @@ -33,7 +36,6 @@ The presentation pipeline follows Playwright's current supported environment mat Setup preflights Node, npm, and the OS family. On Linux, Playwright's documented `install --with-deps` flow may invoke the system package manager and require elevation. Unsupported or missing prerequisites are reported before the existing runtime is changed. -See [Playwright system requirements](https://playwright.dev/docs/intro#system-requirements). The `setup --with-build-deps` install step is a POSIX-shell script: it requires Bash — on native Windows that is Git Bash (its platform gate accepts diff --git a/plugins/ai-briefing/skills/setup/SKILL.md b/plugins/ai-briefing/skills/setup/SKILL.md index be4d4603b..8423dfa0d 100644 --- a/plugins/ai-briefing/skills/setup/SKILL.md +++ b/plugins/ai-briefing/skills/setup/SKILL.md @@ -127,10 +127,10 @@ Tracked profile configuration belongs in the consuming repository. Never write i only to render and inspect generated local HTML/PDF artifacts; it must not be used as a collection browser. On Linux, Playwright's documented `--with-deps` path installs required operating-system packages. Other supported platforms install the browser shell and rely - on their platform prerequisites. Current supported targets are Windows 11+/Server 2019+, - macOS 14+, Debian 12/13, and Ubuntu 22.04/24.04/26.04 on x86-64 or arm64, using the latest - Node.js 22.x, 24.x, or 26.x with npm. The launch probe runs before the runtime swap. See - . + on their platform prerequisites. Supported OS and Node targets are whatever + [Playwright's system requirements](https://playwright.dev/docs/intro#system-requirements) + currently list (the README's matrix is a dated snapshot of that page — the link is + authoritative). The launch probe runs before the runtime swap. 5. **Confirm.** Report the profile path, whether `sources.md` was created or preserved, which optional overlays were created, and whether build dependencies were installed or diff --git a/plugins/claude-ops/.claude-plugin/plugin.json b/plugins/claude-ops/.claude-plugin/plugin.json index 0528f0307..43357ce69 100644 --- a/plugins/claude-ops/.claude-plugin/plugin.json +++ b/plugins/claude-ops/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "claude-ops", - "version": "0.11.2", + "version": "0.11.3", "description": "Claude Code operations toolkit. Five skills: observability (read locally captured telemetry — OTEL store, collector, hook-event JSONL, ccusage — with trend reports and store pruning), known-issues (search known Claude product GitHub bugs, check service health, maintain a persistent tracked-issue registry), changelog (ingest Claude Code changelog entries and integrate them into the current repo), plugins (bring a machine's plugin fleet current on demand — marketplace refresh, effective-scope updates including in-repo project/local installs, new-plugin install per policy, scope-divergence detection and explicit convergence), and a re-runnable setup action that settles where the known-issues registry lives. Plus a family of seven advisory *-audit telemetry-emitter hooks (API errors, config changes, instruction loads, permission denials, pre-compaction, skill usage, tool failures) that emit the shared hook-telemetry envelope, and a reference sink that maps envelopes into the hook-events.jsonl the observability skill reads.", "author": { "name": "Melodic Software", diff --git a/plugins/claude-ops/CHANGELOG.md b/plugins/claude-ops/CHANGELOG.md index 5c131fc33..d8523597b 100644 --- a/plugins/claude-ops/CHANGELOG.md +++ b/plugins/claude-ops/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to the `claude-ops` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.11.3] + +### Changed + +- **Freshness riders on platform-fact docs** (fleet conformance wave). The + `plugins` skill's scope-semantics doc now carries a verified-date header + (all version-gated claims re-verified: `/reload-plugins --force` ≥ 2.1.163, + renames ≥ 2.1.193, `plugin prune` ≥ 2.1.121, `userConfig` type set); the + monitor-restart claim now cites its actual source (plugins-reference). The + setup skill's `pluginConfigs` claim is dated and pinned to ≥ 2.1.207. + ## [0.11.2] ### Changed diff --git a/plugins/claude-ops/skills/plugins/context/scope-semantics.md b/plugins/claude-ops/skills/plugins/context/scope-semantics.md index 7fbd49a86..b2389dfd9 100644 --- a/plugins/claude-ops/skills/plugins/context/scope-semantics.md +++ b/plugins/claude-ops/skills/plugins/context/scope-semantics.md @@ -1,9 +1,11 @@ # Scope semantics — verified facts this skill depends on Every claim below was verified against a fetched official-docs page or an empirical test on a real -machine during this skill's implementation, not assumed from training data. Re-verify against -`code.claude.com/docs/en/plugins-reference` and `discover-plugins` if Claude Code's plugin CLI -changes shape. +machine, not assumed from training data. Last re-verified 2026-07-18 against +[plugins-reference](https://code.claude.com/docs/en/plugins-reference), +[discover-plugins](https://code.claude.com/docs/en/discover-plugins), the published plugin-manifest +JSON Schema, and the Claude Code changelog (version gates). Re-verify against those pages if Claude +Code's plugin CLI changes shape. ## Scope-by-cwd loading @@ -55,8 +57,8 @@ remove an `enabledPlugins` entry, and only that action surfaces a settings diff. ## `/reload-plugins` — bare by default, `--force` for the MCP-cache-invalidation case **Verified against `code.claude.com/docs/en/discover-plugins`**: `/reload-plugins` refreshes skills, -agents, hooks, MCP, and LSP servers in-process. It does **not** cover monitors — a monitor requires a -full session restart. Recommend bare `/reload-plugins` by default; call out the restart requirement +agents, hooks, MCP, and LSP servers in-process. It does **not** cover monitors — per +`code.claude.com/docs/en/plugins-reference`, "monitors require a session restart". Recommend bare `/reload-plugins` by default; call out the restart requirement only when an updated plugin ships a monitor. `--force` is real (Claude Code ≥ 2.1.163), but scoped to one specific case: a plugin that provides an diff --git a/plugins/claude-ops/skills/setup/SKILL.md b/plugins/claude-ops/skills/setup/SKILL.md index c9008858b..ae1055022 100644 --- a/plugins/claude-ops/skills/setup/SKILL.md +++ b/plugins/claude-ops/skills/setup/SKILL.md @@ -11,9 +11,10 @@ disable-model-invocation: true Confirm the known-issues registry and skill-usage-log locations without editing Claude Code settings. `registry_dir` and `skill_usage_dir` are personal `userConfig` options. Claude Code prompts for them when the plugin is enabled, stores non-sensitive options in user settings, and -ignores `pluginConfigs` entries in project and local settings on current releases. +ignores `pluginConfigs` entries in project and local settings on current releases (≥ 2.1.207). -Official contract: . +Official contract (verified 2026-07-18): +. ## Task diff --git a/plugins/context7/.claude-plugin/plugin.json b/plugins/context7/.claude-plugin/plugin.json index a68f069af..18c605dda 100644 --- a/plugins/context7/.claude-plugin/plugin.json +++ b/plugins/context7/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "context7", - "version": "0.3.0", + "version": "0.3.1", "description": "Looks up current library documentation, API references, and code examples via Context7 (ctx7 CLI or the Context7 MCP server) with a two-step resolve-then-query workflow: a lookup skill (default lookup plus an upstream drift-check action) and a setup skill for CLI install, auth, and MCP configuration.", "author": { "name": "Melodic Software", diff --git a/plugins/context7/CHANGELOG.md b/plugins/context7/CHANGELOG.md index 406816b0c..5b6ec0d95 100644 --- a/plugins/context7/CHANGELOG.md +++ b/plugins/context7/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to the `context7` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.3.1] + +### Changed + +- **CLI/platform facts re-verified against `ctx7` 0.5.5 and corrected** + (fleet conformance wave: freshness riders). `--base-url` default is + `https://context7.com` (not `/api`), version flag is lowercase `-v`, + `library`'s query argument is optional, the `skills` surface is deprecated + upstream (this plugin never invokes it), and new top-level + `remove`/`uninstall` + `upgrade` commands are listed. +- **Claude Code unset-env-var MCP behavior corrected**: the config loads with + a missing-variable warning and the literal `${VAR}` text is sent as-is + (silently broken auth) — it is not a parse failure. Both context docs now + carry verified-date + official-link riders. + ## [0.3.0] ### Changed diff --git a/plugins/context7/skills/lookup/context/cli.md b/plugins/context7/skills/lookup/context/cli.md index edd80a712..1db7e279e 100644 --- a/plugins/context7/skills/lookup/context/cli.md +++ b/plugins/context7/skills/lookup/context/cli.md @@ -2,11 +2,15 @@ Install, configure, command reference, flags, env vars, and Windows-specific gotchas. +> Verified 2026-07-18 against `ctx7` 0.5.5 (live `--help`/`--version` output) and +> [Context7's CLI docs](https://context7.com/docs/clients/cli). +> The CLI moves fast — re-check against a current install before acting on a row. + ## Install ```bash npm install -g ctx7@latest -ctx7 --version # 0.3.13 or later +ctx7 --version # 0.5.5 or later ``` Fallback (no global install): `npx ctx7@latest ` — slower per-invocation, no PATH ceremony. @@ -27,10 +31,12 @@ Set it wherever your project manages local environment variables (shell profile, | Command | Purpose | |---|---| -| `ctx7 library ` | Resolve library name → Context7 library ID | +| `ctx7 library [query]` | Resolve library name → Context7 library ID (query optional since 0.5.x; still pass one for ranking) | | `ctx7 docs ` | Fetch documentation for a resolved library | | `ctx7 setup [flags]` | Configure Context7 for an IDE (this plugin does NOT use it — see below) | | `ctx7 login` / `logout` / `whoami` | OAuth flow (this plugin does NOT use it — env var is enough) | +| `ctx7 remove` / `uninstall` | Remove a `setup`-installed agent configuration (this plugin does NOT use it) | +| `ctx7 upgrade` | Self-upgrade the CLI | | `ctx7 skills install [skill]` | Install skills from a GitHub repo (this plugin does NOT use it — see below) | | `ctx7 skills search ` | Search the Context7 skills registry | | `ctx7 skills suggest` | Auto-suggest skills based on project dependencies | @@ -39,7 +45,11 @@ Set it wherever your project manages local environment variables (shell profile, | `ctx7 skills list` | List installed skills in the current directory | | `ctx7 skills remove ` | Uninstall a skill | -Short aliases: `ctx7 skills` ↔ `ctx7 skill`, `install` ↔ `i`, `list` ↔ `ls`, `search` ↔ `s`, `generate` ↔ `gen/g`. +**The whole `ctx7 skills` surface is deprecated upstream as of 0.5.5** — hidden from `--help`, still +runnable, with an in-tool warning that it "will stop working in the next major release". This plugin +never invokes it (see below), so no behavior here depends on it. + +Short aliases: `ctx7 skills` ↔ `ctx7 skill`, `install` ↔ `i`, `list` ↔ `ls`, `search` ↔ `s`, `generate` ↔ `gen`, `remove` ↔ `rm`. ## Flags @@ -47,8 +57,8 @@ Short aliases: `ctx7 skills` ↔ `ctx7 skill`, `install` ↔ `i`, `list` ↔ `ls | Flag | Default | Purpose | |---|---|---| -| `--base-url ` | `https://context7.com/api` | Point at a custom Context7 backend | -| `-V, --version` | | Print version | +| `--base-url ` | `https://context7.com` | Point at a custom Context7 backend (API paths are appended) | +| `-v, --version` | | Print version | | `-h, --help` | | Help for the current command | ### Per-command (`library`, `docs`) @@ -57,7 +67,7 @@ Short aliases: `ctx7 skills` ↔ `ctx7 skill`, `install` ↔ `i`, `list` ↔ `ls |---|---| | `--json` | Structured JSON output (vs. formatted text). Useful for scripts / `jq` extraction | -**No `--tokens`, `--limit`, `--format`, or `-v/--verbose` flag exists.** Default content depth is server-controlled. For more content per call, prefer MCP (`mcp__context7__query-docs`) — returns ~1.8× more content by default. +**No `--tokens`, `--limit`, `--format`, or `--verbose` flag exists.** Default content depth is server-controlled. For more content per call, prefer MCP (`mcp__context7__query-docs`) — returns ~1.8× more content by default. ### Setup / install flags (why this plugin doesn't use them) diff --git a/plugins/context7/skills/lookup/context/mcp.md b/plugins/context7/skills/lookup/context/mcp.md index f1fdbf0b2..07a11612c 100644 --- a/plugins/context7/skills/lookup/context/mcp.md +++ b/plugins/context7/skills/lookup/context/mcp.md @@ -2,6 +2,10 @@ Context7 HTTP MCP server — reads the same backend as the `ctx7` CLI. This plugin does NOT ship or auto-start an MCP server; the consuming project opts in by declaring it in its own MCP configuration. +> Server URL and header facts verified 2026-07-18 against `ctx7` 0.5.5 source and +> [Context7's docs](https://context7.com/docs); Claude Code config behavior verified against +> [the MCP docs](https://code.claude.com/docs/en/mcp). Re-check both before acting on a row. + ## Configuration (consumer-side, optional) Add to the consuming project's `.mcp.json` (or user-scope MCP config) — server entries live under the top-level `mcpServers` key. Anonymous (low-rate) usage needs no headers: @@ -17,7 +21,7 @@ Add to the consuming project's `.mcp.json` (or user-scope MCP config) — server } ``` -With an API key (higher limits), add the `CONTEXT7_API_KEY` request header (the header name Context7's server expects) — but note Claude Code **fails to parse the config** when a referenced env var is unset with no default, so only use this form once `CONTEXT7_API_KEY` is actually set in your environment: +With an API key (higher limits), add the `CONTEXT7_API_KEY` request header (the header name Context7's server expects). When a referenced env var is unset with no default, Claude Code still loads the config: it reports a missing-variable warning in `claude mcp list` and sends the **literal `${CONTEXT7_API_KEY}` text as-is** — silently broken auth, not a parse failure. Only use this form once `CONTEXT7_API_KEY` is actually set in your environment (or add a `${VAR:-default}` fallback): ```json { diff --git a/plugins/desktop-notification/.claude-plugin/plugin.json b/plugins/desktop-notification/.claude-plugin/plugin.json index 7ba99fe0b..7ad6e2b98 100644 --- a/plugins/desktop-notification/.claude-plugin/plugin.json +++ b/plugins/desktop-notification/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "desktop-notification", - "version": "0.3.1", + "version": "0.3.2", "description": "Alert you when Claude Code needs input — an audible terminal bell, an OSC 9 terminal notification, and an OS-native toast (macOS/Linux) on permission and idle prompts.", "author": { "name": "Melodic Software", diff --git a/plugins/desktop-notification/CHANGELOG.md b/plugins/desktop-notification/CHANGELOG.md index 9e32b47c5..6b4e6c530 100644 --- a/plugins/desktop-notification/CHANGELOG.md +++ b/plugins/desktop-notification/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to the `desktop-notification` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.3.2] + +### Changed + +- **Freshness rider on the channel table** (fleet conformance wave). The + `terminalSequence` / Claude Code v2.1.141+ claim is re-verified against the + Claude Code changelog and dated, with links to the changelog and the hooks + reference. + +## [0.3.1] + +### Changed + +- Shared `hook-utils.sh` resynced from the repository library (no behavior + change in this plugin's hook). + ## [0.3.0] ### Changed diff --git a/plugins/desktop-notification/README.md b/plugins/desktop-notification/README.md index 3cc129029..a2f9565ca 100644 --- a/plugins/desktop-notification/README.md +++ b/plugins/desktop-notification/README.md @@ -19,6 +19,10 @@ notification, and — on macOS and Linux — an OS-native desktop toast. | `terminal_notify` | `desktop_notification_terminal_notify_enabled` | OSC 9 desktop notification, emitted via the hook's `terminalSequence` output (Claude Code v2.1.141+ writes it through its own terminal path). | | `os_toast` | `desktop_notification_os_toast_enabled` | OS-native toast (see per-OS table). | +Platform facts verified 2026-07-18: hook `terminalSequence` output landed in Claude Code +v2.1.141 per the [Claude Code changelog](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md); +channel semantics per the [hooks reference](https://code.claude.com/docs/en/hooks). + ### Per-OS `os_toast` behavior | OS | Tool | Requirement | diff --git a/plugins/firecrawl/.claude-plugin/plugin.json b/plugins/firecrawl/.claude-plugin/plugin.json index d3ad730a9..fb7cc205d 100644 --- a/plugins/firecrawl/.claude-plugin/plugin.json +++ b/plugins/firecrawl/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "firecrawl", - "version": "0.2.1", + "version": "0.2.2", "description": "Web scraping, search, crawling, and file parsing through the firecrawl-cli binary with a write-to-disk-then-Read pattern that keeps large results out of context — plus a gated maintainer update flow tracking the upstream CLI and skill source.", "author": { "name": "Melodic Software", diff --git a/plugins/firecrawl/CHANGELOG.md b/plugins/firecrawl/CHANGELOG.md new file mode 100644 index 000000000..accf96d1d --- /dev/null +++ b/plugins/firecrawl/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +All notable changes to the `firecrawl` plugin are documented here. Format follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. + +## [0.2.2] + +### Added + +- This changelog (fleet conformance wave: every versioned plugin ships a + Keep-a-Changelog file). + +## [0.2.1] + +First versioned release covered by this changelog; see the git history of +`plugins/firecrawl/` for earlier changes. diff --git a/plugins/miro/.claude-plugin/plugin.json b/plugins/miro/.claude-plugin/plugin.json index 9fa26bfae..2e5d6e63a 100644 --- a/plugins/miro/.claude-plugin/plugin.json +++ b/plugins/miro/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "miro", - "version": "0.1.1", + "version": "0.1.2", "description": "Miro board management over the Model Context Protocol: create and manage boards, sticky notes, shapes, frames, connectors, and tags for EventStorming, brainstorming, and diagramming. Bundles a local stdio MCP server (single self-contained Node artifact); installs disabled — opt in and supply a Miro API token.", "author": { "name": "Melodic Software", diff --git a/plugins/miro/CHANGELOG.md b/plugins/miro/CHANGELOG.md new file mode 100644 index 000000000..03f6bfd1b --- /dev/null +++ b/plugins/miro/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +All notable changes to the `miro` plugin are documented here. Format follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. + +## [0.1.2] + +### Added + +- This changelog (fleet conformance wave: every versioned plugin ships a + Keep-a-Changelog file). + +### Changed + +- MCP server version aligned with the plugin: `package.json`, the server's + MCP `Implementation` version, and the committed bundle now all report + `0.1.2` (they had drifted to `0.1.0` while `plugin.json` was `0.1.1`). + +## [0.1.1] + +First versioned release covered by this changelog; see the git history of +`plugins/miro/` for earlier changes. diff --git a/plugins/miro/dist/index.min.js b/plugins/miro/dist/index.min.js index edb2a4ec2..a094f79cc 100644 --- a/plugins/miro/dist/index.min.js +++ b/plugins/miro/dist/index.min.js @@ -79,4 +79,4 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs. ]`;continue}r+=i[p],i[p]==="\\"?n=!0:o&&i[p]==="]"?o=!1:!o&&i[p]==="["&&(o=!0)}try{new RegExp(r)}catch{return console.warn(`Could not convert regex pattern at ${t.currentPath.join("/")} to a flag-independent form! Falling back to the flag-ignorant source`),e.source}return r}function t4(e,t){if(t.target==="openAi"&&console.warn("Warning: OpenAI may not support records in schemas! Try an array of key-value pairs instead."),t.target==="openApi3"&&e.keyType?._def.typeName===I.ZodEnum)return{type:"object",required:e.keyType._def.values,properties:e.keyType._def.values.reduce((i,r)=>({...i,[r]:H(e.valueType._def,{...t,currentPath:[...t.currentPath,"properties",r]})??Ee(t)}),{}),additionalProperties:t.rejectedAdditionalProperties};let a={type:"object",additionalProperties:H(e.valueType._def,{...t,currentPath:[...t.currentPath,"additionalProperties"]})??t.allowedAdditionalProperties};if(t.target==="openApi3")return a;if(e.keyType?._def.typeName===I.ZodString&&e.keyType._def.checks?.length){let{type:i,...r}=e4(e.keyType._def,t);return{...a,propertyNames:r}}else{if(e.keyType?._def.typeName===I.ZodEnum)return{...a,propertyNames:{enum:e.keyType._def.values}};if(e.keyType?._def.typeName===I.ZodBranded&&e.keyType._def.type._def.typeName===I.ZodString&&e.keyType._def.type._def.checks?.length){let{type:i,...r}=Yh(e.keyType._def,t);return{...a,propertyNames:r}}}return a}function vR(e,t){if(t.mapStrategy==="record")return t4(e,t);let a=H(e.keyType._def,{...t,currentPath:[...t.currentPath,"items","items","0"]})||Ee(t),i=H(e.valueType._def,{...t,currentPath:[...t.currentPath,"items","items","1"]})||Ee(t);return{type:"array",maxItems:125,items:{type:"array",items:[a,i],minItems:2,maxItems:2}}}function bR(e){let t=e.values,i=Object.keys(e.values).filter(n=>typeof t[t[n]]!="number").map(n=>t[n]),r=Array.from(new Set(i.map(n=>typeof n)));return{type:r.length===1?r[0]==="string"?"string":"number":["string","number"],enum:i}}function _R(e){return e.target==="openAi"?void 0:{not:Ee({...e,currentPath:[...e.currentPath,"not"]})}}function wR(e){return e.target==="openApi3"?{enum:["null"],nullable:!0}:{type:"null"}}var Wd={ZodString:"string",ZodNumber:"number",ZodBigInt:"integer",ZodBoolean:"boolean",ZodNull:"null"};function SR(e,t){if(t.target==="openApi3")return xR(e,t);let a=e.options instanceof Map?Array.from(e.options.values()):e.options;if(a.every(i=>i._def.typeName in Wd&&(!i._def.checks||!i._def.checks.length))){let i=a.reduce((r,n)=>{let o=Wd[n._def.typeName];return o&&!r.includes(o)?[...r,o]:r},[]);return{type:i.length>1?i:i[0]}}else if(a.every(i=>i._def.typeName==="ZodLiteral"&&!i.description)){let i=a.reduce((r,n)=>{let o=typeof n._def.value;switch(o){case"string":case"number":case"boolean":return[...r,o];case"bigint":return[...r,"integer"];case"object":if(n._def.value===null)return[...r,"null"];default:return r}},[]);if(i.length===a.length){let r=i.filter((n,o,s)=>s.indexOf(n)===o);return{type:r.length>1?r:r[0],enum:a.reduce((n,o)=>n.includes(o._def.value)?n:[...n,o._def.value],[])}}}else if(a.every(i=>i._def.typeName==="ZodEnum"))return{type:"string",enum:a.reduce((i,r)=>[...i,...r._def.values.filter(n=>!i.includes(n))],[])};return xR(e,t)}var xR=(e,t)=>{let a=(e.options instanceof Map?Array.from(e.options.values()):e.options).map((i,r)=>H(i._def,{...t,currentPath:[...t.currentPath,"anyOf",`${r}`]})).filter(i=>!!i&&(!t.strictUnions||typeof i=="object"&&Object.keys(i).length>0));return a.length?{anyOf:a}:void 0};function TR(e,t){if(["ZodString","ZodNumber","ZodBigInt","ZodBoolean","ZodNull"].includes(e.innerType._def.typeName)&&(!e.innerType._def.checks||!e.innerType._def.checks.length))return t.target==="openApi3"?{type:Wd[e.innerType._def.typeName],nullable:!0}:{type:[Wd[e.innerType._def.typeName],"null"]};if(t.target==="openApi3"){let i=H(e.innerType._def,{...t,currentPath:[...t.currentPath]});return i&&"$ref"in i?{allOf:[i],nullable:!0}:i&&{...i,nullable:!0}}let a=H(e.innerType._def,{...t,currentPath:[...t.currentPath,"anyOf","0"]});return a&&{anyOf:[a,{type:"null"}]}}function kR(e,t){let a={type:"number"};if(!e.checks)return a;for(let i of e.checks)switch(i.kind){case"int":a.type="integer",T7(a,"type",i.message,t);break;case"min":t.target==="jsonSchema7"?i.inclusive?ce(a,"minimum",i.value,i.message,t):ce(a,"exclusiveMinimum",i.value,i.message,t):(i.inclusive||(a.exclusiveMinimum=!0),ce(a,"minimum",i.value,i.message,t));break;case"max":t.target==="jsonSchema7"?i.inclusive?ce(a,"maximum",i.value,i.message,t):ce(a,"exclusiveMaximum",i.value,i.message,t):(i.inclusive||(a.exclusiveMaximum=!0),ce(a,"maximum",i.value,i.message,t));break;case"multipleOf":ce(a,"multipleOf",i.value,i.message,t);break}return a}function PR(e,t){let a=t.target==="openAi",i={type:"object",properties:{}},r=[],n=e.shape();for(let s in n){let p=n[s];if(p===void 0||p._def===void 0)continue;let d=GB(p);d&&a&&(p._def.typeName==="ZodOptional"&&(p=p._def.innerType),p.isNullable()||(p=p.nullable()),d=!1);let c=H(p._def,{...t,currentPath:[...t.currentPath,"properties",s],propertyPath:[...t.currentPath,"properties",s]});c!==void 0&&(i.properties[s]=c,d||r.push(s))}r.length&&(i.required=r);let o=ZB(e,t);return o!==void 0&&(i.additionalProperties=o),i}function ZB(e,t){if(e.catchall._def.typeName!=="ZodNever")return H(e.catchall._def,{...t,currentPath:[...t.currentPath,"additionalProperties"]});switch(e.unknownKeys){case"passthrough":return t.allowedAdditionalProperties;case"strict":return t.rejectedAdditionalProperties;case"strip":return t.removeAdditionalStrategy==="strict"?t.allowedAdditionalProperties:t.rejectedAdditionalProperties}}function GB(e){try{return e.isOptional()}catch{return!0}}var $R=(e,t)=>{if(t.currentPath.toString()===t.propertyPath?.toString())return H(e.innerType._def,t);let a=H(e.innerType._def,{...t,currentPath:[...t.currentPath,"anyOf","1"]});return a?{anyOf:[{not:Ee(t)},a]}:Ee(t)};var NR=(e,t)=>{if(t.pipeStrategy==="input")return H(e.in._def,t);if(t.pipeStrategy==="output")return H(e.out._def,t);let a=H(e.in._def,{...t,currentPath:[...t.currentPath,"allOf","0"]}),i=H(e.out._def,{...t,currentPath:[...t.currentPath,"allOf",a?"1":"0"]});return{allOf:[a,i].filter(r=>r!==void 0)}};function IR(e,t){return H(e.type._def,t)}function ER(e,t){let i={type:"array",uniqueItems:!0,items:H(e.valueType._def,{...t,currentPath:[...t.currentPath,"items"]})};return e.minSize&&ce(i,"minItems",e.minSize.value,e.minSize.message,t),e.maxSize&&ce(i,"maxItems",e.maxSize.value,e.maxSize.message,t),i}function zR(e,t){return e.rest?{type:"array",minItems:e.items.length,items:e.items.map((a,i)=>H(a._def,{...t,currentPath:[...t.currentPath,"items",`${i}`]})).reduce((a,i)=>i===void 0?a:[...a,i],[]),additionalItems:H(e.rest._def,{...t,currentPath:[...t.currentPath,"additionalItems"]})}:{type:"array",minItems:e.items.length,maxItems:e.items.length,items:e.items.map((a,i)=>H(a._def,{...t,currentPath:[...t.currentPath,"items",`${i}`]})).reduce((a,i)=>i===void 0?a:[...a,i],[])}}function RR(e){return{not:Ee(e)}}function CR(e){return Ee(e)}var AR=(e,t)=>H(e.innerType._def,t);var jR=(e,t,a)=>{switch(t){case I.ZodString:return e4(e,a);case I.ZodNumber:return kR(e,a);case I.ZodObject:return PR(e,a);case I.ZodBigInt:return dR(e,a);case I.ZodBoolean:return cR();case I.ZodDate:return k7(e,a);case I.ZodUndefined:return RR(a);case I.ZodNull:return wR(a);case I.ZodArray:return pR(e,a);case I.ZodUnion:case I.ZodDiscriminatedUnion:return SR(e,a);case I.ZodIntersection:return hR(e,a);case I.ZodTuple:return zR(e,a);case I.ZodRecord:return t4(e,a);case I.ZodLiteral:return gR(e,a);case I.ZodEnum:return fR(e);case I.ZodNativeEnum:return bR(e);case I.ZodNullable:return TR(e,a);case I.ZodOptional:return $R(e,a);case I.ZodMap:return vR(e,a);case I.ZodSet:return ER(e,a);case I.ZodLazy:return()=>e.getter()._def;case I.ZodPromise:return IR(e,a);case I.ZodNaN:case I.ZodNever:return _R(a);case I.ZodEffects:return mR(e,a);case I.ZodAny:return Ee(a);case I.ZodUnknown:return CR(a);case I.ZodDefault:return uR(e,a);case I.ZodBranded:return Yh(e,a);case I.ZodReadonly:return AR(e,a);case I.ZodCatch:return lR(e,a);case I.ZodPipeline:return NR(e,a);case I.ZodFunction:case I.ZodVoid:case I.ZodSymbol:return;default:return(i=>{})(t)}};function H(e,t,a=!1){let i=t.seen.get(e);if(t.override){let s=t.override?.(e,t,i,a);if(s!==nR)return s}if(i&&!a){let s=JB(i,t);if(s!==void 0)return s}let r={def:e,path:t.currentPath,jsonSchema:void 0};t.seen.set(e,r);let n=jR(e,e.typeName,t),o=typeof n=="function"?H(n(),t):n;if(o&&HB(e,t,o),t.postProcess){let s=t.postProcess(o,e,t);return r.jsonSchema=o,s}return r.jsonSchema=o,o}var JB=(e,t)=>{switch(t.$refStrategy){case"root":return{$ref:e.path.join("/")};case"relative":return{$ref:Xh(t.currentPath,e.path)};case"none":case"seen":return e.path.lengtht.currentPath[i]===a)?(console.warn(`Recursive reference detected at ${t.currentPath.join("/")}! Defaulting to any`),Ee(t)):t.$refStrategy==="seen"?Ee(t):void 0}},HB=(e,t,a)=>(e.description&&(a.description=e.description,t.markdownDescription&&(a.markdownDescription=e.description)),a);var N7=(e,t)=>{let a=sR(t),i=typeof t=="object"&&t.definitions?Object.entries(t.definitions).reduce((p,[d,c])=>({...p,[d]:H(c._def,{...a,currentPath:[...a.basePath,a.definitionPath,d]},!0)??Ee(a)}),{}):void 0,r=typeof t=="string"?t:t?.nameStrategy==="title"?void 0:t?.name,n=H(e._def,r===void 0?a:{...a,currentPath:[...a.basePath,a.definitionPath,r]},!1)??Ee(a),o=typeof t=="object"&&t.name!==void 0&&t.nameStrategy==="title"?t.name:void 0;o!==void 0&&(n.title=o),a.flags.hasReferencedOpenAiAnyType&&(i||(i={}),i[a.openAiAnyTypeName]||(i[a.openAiAnyTypeName]={type:["string","number","integer","boolean","array","null"],items:{$ref:a.$refStrategy==="relative"?"1":[...a.basePath,a.definitionPath,a.openAiAnyTypeName].join("/")}}));let s=r===void 0?i?{...n,[a.definitionPath]:i}:n:{$ref:[...a.$refStrategy==="relative"?[]:a.basePath,a.definitionPath,r].join("/"),[a.definitionPath]:{...i,[r]:n}};return a.target==="jsonSchema7"?s.$schema="http://json-schema.org/draft-07/schema#":(a.target==="jsonSchema2019-09"||a.target==="openAi")&&(s.$schema="https://json-schema.org/draft/2019-09/schema#"),a.target==="openAi"&&("anyOf"in s||"oneOf"in s||"allOf"in s||"type"in s&&Array.isArray(s.type))&&console.warn("Warning: OpenAI may not support schemas with unions as roots! Try wrapping it in an object property."),s};function WB(e){return!e||e==="jsonSchema7"||e==="draft-7"?"draft-7":e==="jsonSchema2019-09"||e==="draft-2020-12"?"draft-2020-12":"draft-7"}function I7(e,t){return Rt(e)?Cr(e,{target:WB(t?.target),io:t?.pipeStrategy??"input"}):N7(e,{strictUnions:t?.strictUnions??!0,pipeStrategy:t?.pipeStrategy??"input"})}function E7(e){let a=Xa(e)?.method;if(!a)throw new Error("Schema is missing a method literal");let i=th(a);if(typeof i!="string")throw new Error("Schema method literal must be a string");return i}function z7(e,t){let a=Qa(e,t);if(!a.success)throw a.error;return a.data}var KB=6e4,a4=class{constructor(t){this._options=t,this._requestMessageId=0,this._requestHandlers=new Map,this._requestHandlerAbortControllers=new Map,this._notificationHandlers=new Map,this._responseHandlers=new Map,this._progressHandlers=new Map,this._timeoutInfo=new Map,this._pendingDebouncedNotifications=new Set,this._taskProgressTokens=new Map,this._requestResolvers=new Map,this.setNotificationHandler(jh,a=>{this._oncancel(a)}),this.setNotificationHandler(Mh,a=>{this._onprogress(a)}),this.setRequestHandler(Oh,a=>({})),this._taskStore=t?.taskStore,this._taskMessageQueue=t?.taskMessageQueue,this._taskStore&&(this.setRequestHandler(Dh,async(a,i)=>{let r=await this._taskStore.getTask(a.params.taskId,i.sessionId);if(!r)throw new F(Z.InvalidParams,"Failed to retrieve task: Task not found");return{...r}}),this.setRequestHandler(qh,async(a,i)=>{let r=async()=>{let n=a.params.taskId;if(this._taskMessageQueue){let s;for(;s=await this._taskMessageQueue.dequeue(n,i.sessionId);){if(s.type==="response"||s.type==="error"){let p=s.message,d=p.id,c=this._requestResolvers.get(d);if(c)if(this._requestResolvers.delete(d),s.type==="response")c(p);else{let u=p,g=new F(u.error.code,u.error.message,u.error.data);c(g)}else{let u=s.type==="response"?"Response":"Error";this._onerror(new Error(`${u} handler missing for request ${d}`))}continue}await this._transport?.send(s.message,{relatedRequestId:i.requestId})}}let o=await this._taskStore.getTask(n,i.sessionId);if(!o)throw new F(Z.InvalidParams,`Task not found: ${n}`);if(!tr(o.status))return await this._waitForTaskUpdate(n,i.signal),await r();if(tr(o.status)){let s=await this._taskStore.getTaskResult(n,i.sessionId);return this._clearTaskQueue(n),{...s,_meta:{...s._meta,[er]:{taskId:n}}}}return await r()};return await r()}),this.setRequestHandler(Lh,async(a,i)=>{try{let{tasks:r,nextCursor:n}=await this._taskStore.listTasks(a.params?.cursor,i.sessionId);return{tasks:r,nextCursor:n,_meta:{}}}catch(r){throw new F(Z.InvalidParams,`Failed to list tasks: ${r instanceof Error?r.message:String(r)}`)}}),this.setRequestHandler(Fh,async(a,i)=>{try{let r=await this._taskStore.getTask(a.params.taskId,i.sessionId);if(!r)throw new F(Z.InvalidParams,`Task not found: ${a.params.taskId}`);if(tr(r.status))throw new F(Z.InvalidParams,`Cannot cancel task in terminal status: ${r.status}`);await this._taskStore.updateTaskStatus(a.params.taskId,"cancelled","Client cancelled task execution.",i.sessionId),this._clearTaskQueue(a.params.taskId);let n=await this._taskStore.getTask(a.params.taskId,i.sessionId);if(!n)throw new F(Z.InvalidParams,`Task not found after cancellation: ${a.params.taskId}`);return{_meta:{},...n}}catch(r){throw r instanceof F?r:new F(Z.InvalidRequest,`Failed to cancel task: ${r instanceof Error?r.message:String(r)}`)}}))}async _oncancel(t){if(!t.params.requestId)return;this._requestHandlerAbortControllers.get(t.params.requestId)?.abort(t.params.reason)}_setupTimeout(t,a,i,r,n=!1){this._timeoutInfo.set(t,{timeoutId:setTimeout(r,a),startTime:Date.now(),timeout:a,maxTotalTimeout:i,resetTimeoutOnProgress:n,onTimeout:r})}_resetTimeout(t){let a=this._timeoutInfo.get(t);if(!a)return!1;let i=Date.now()-a.startTime;if(a.maxTotalTimeout&&i>=a.maxTotalTimeout)throw this._timeoutInfo.delete(t),F.fromError(Z.RequestTimeout,"Maximum total timeout exceeded",{maxTotalTimeout:a.maxTotalTimeout,totalElapsed:i});return clearTimeout(a.timeoutId),a.timeoutId=setTimeout(a.onTimeout,a.timeout),!0}_cleanupTimeout(t){let a=this._timeoutInfo.get(t);a&&(clearTimeout(a.timeoutId),this._timeoutInfo.delete(t))}async connect(t){if(this._transport)throw new Error("Already connected to a transport. Call close() before connecting to a new transport, or use a separate Protocol instance per connection.");this._transport=t;let a=this.transport?.onclose;this._transport.onclose=()=>{a?.(),this._onclose()};let i=this.transport?.onerror;this._transport.onerror=n=>{i?.(n),this._onerror(n)};let r=this._transport?.onmessage;this._transport.onmessage=(n,o)=>{r?.(n,o),qd(n)||Jz(n)?this._onresponse(n):c7(n)?this._onrequest(n,o):Gz(n)?this._onnotification(n):this._onerror(new Error(`Unknown message type: ${JSON.stringify(n)}`))},await this._transport.start()}_onclose(){let t=this._responseHandlers;this._responseHandlers=new Map,this._progressHandlers.clear(),this._taskProgressTokens.clear(),this._pendingDebouncedNotifications.clear();for(let i of this._timeoutInfo.values())clearTimeout(i.timeoutId);this._timeoutInfo.clear();for(let i of this._requestHandlerAbortControllers.values())i.abort();this._requestHandlerAbortControllers.clear();let a=F.fromError(Z.ConnectionClosed,"Connection closed");this._transport=void 0,this.onclose?.();for(let i of t.values())i(a)}_onerror(t){this.onerror?.(t)}_onnotification(t){let a=this._notificationHandlers.get(t.method)??this.fallbackNotificationHandler;a!==void 0&&Promise.resolve().then(()=>a(t)).catch(i=>this._onerror(new Error(`Uncaught error in notification handler: ${i}`)))}_onrequest(t,a){let i=this._requestHandlers.get(t.method)??this.fallbackRequestHandler,r=this._transport,n=t.params?._meta?.[er]?.taskId;if(i===void 0){let c={jsonrpc:"2.0",id:t.id,error:{code:Z.MethodNotFound,message:"Method not found"}};n&&this._taskMessageQueue?this._enqueueTaskMessage(n,{type:"error",message:c,timestamp:Date.now()},r?.sessionId).catch(u=>this._onerror(new Error(`Failed to enqueue error response: ${u}`))):r?.send(c).catch(u=>this._onerror(new Error(`Failed to send an error response: ${u}`)));return}let o=new AbortController;this._requestHandlerAbortControllers.set(t.id,o);let s=Fz(t.params)?t.params.task:void 0,p=this._taskStore?this.requestTaskStore(t,r?.sessionId):void 0,d={signal:o.signal,sessionId:r?.sessionId,_meta:t.params?._meta,sendNotification:async c=>{if(o.signal.aborted)return;let u={relatedRequestId:t.id};n&&(u.relatedTask={taskId:n}),await this.notification(c,u)},sendRequest:async(c,u,g)=>{if(o.signal.aborted)throw new F(Z.ConnectionClosed,"Request was cancelled");let y={...g,relatedRequestId:t.id};n&&!y.relatedTask&&(y.relatedTask={taskId:n});let v=y.relatedTask?.taskId??n;return v&&p&&await p.updateTaskStatus(v,"input_required"),await this.request(c,u,y)},authInfo:a?.authInfo,requestId:t.id,requestInfo:a?.requestInfo,taskId:n,taskStore:p,taskRequestedTtl:s?.ttl,closeSSEStream:a?.closeSSEStream,closeStandaloneSSEStream:a?.closeStandaloneSSEStream};Promise.resolve().then(()=>{s&&this.assertTaskHandlerCapability(t.method)}).then(()=>i(t,d)).then(async c=>{if(o.signal.aborted)return;let u={result:c,jsonrpc:"2.0",id:t.id};n&&this._taskMessageQueue?await this._enqueueTaskMessage(n,{type:"response",message:u,timestamp:Date.now()},r?.sessionId):await r?.send(u)},async c=>{if(o.signal.aborted)return;let u={jsonrpc:"2.0",id:t.id,error:{code:Number.isSafeInteger(c.code)?c.code:Z.InternalError,message:c.message??"Internal error",...c.data!==void 0&&{data:c.data}}};n&&this._taskMessageQueue?await this._enqueueTaskMessage(n,{type:"error",message:u,timestamp:Date.now()},r?.sessionId):await r?.send(u)}).catch(c=>this._onerror(new Error(`Failed to send response: ${c}`))).finally(()=>{this._requestHandlerAbortControllers.get(t.id)===o&&this._requestHandlerAbortControllers.delete(t.id)})}_onprogress(t){let{progressToken:a,...i}=t.params,r=Number(a),n=this._progressHandlers.get(r);if(!n){this._onerror(new Error(`Received a progress notification for an unknown token: ${JSON.stringify(t)}`));return}let o=this._responseHandlers.get(r),s=this._timeoutInfo.get(r);if(s&&o&&s.resetTimeoutOnProgress)try{this._resetTimeout(r)}catch(p){this._responseHandlers.delete(r),this._progressHandlers.delete(r),this._cleanupTimeout(r),o(p);return}n(i)}_onresponse(t){let a=Number(t.id),i=this._requestResolvers.get(a);if(i){if(this._requestResolvers.delete(a),qd(t))i(t);else{let o=new F(t.error.code,t.error.message,t.error.data);i(o)}return}let r=this._responseHandlers.get(a);if(r===void 0){this._onerror(new Error(`Received a response for an unknown message ID: ${JSON.stringify(t)}`));return}this._responseHandlers.delete(a),this._cleanupTimeout(a);let n=!1;if(qd(t)&&t.result&&typeof t.result=="object"){let o=t.result;if(o.task&&typeof o.task=="object"){let s=o.task;typeof s.taskId=="string"&&(n=!0,this._taskProgressTokens.set(s.taskId,a))}}if(n||this._progressHandlers.delete(a),qd(t))r(t);else{let o=F.fromError(t.error.code,t.error.message,t.error.data);r(o)}}get transport(){return this._transport}async close(){await this._transport?.close()}async*requestStream(t,a,i){let{task:r}=i??{};if(!r){try{yield{type:"result",result:await this.request(t,a,i)}}catch(o){yield{type:"error",error:o instanceof F?o:new F(Z.InternalError,String(o))}}return}let n;try{let o=await this.request(t,Ki,i);if(o.task)n=o.task.taskId,yield{type:"taskCreated",task:o.task};else throw new F(Z.InternalError,"Task creation did not return a task");for(;;){let s=await this.getTask({taskId:n},i);if(yield{type:"taskStatus",task:s},tr(s.status)){s.status==="completed"?yield{type:"result",result:await this.getTaskResult({taskId:n},a,i)}:s.status==="failed"?yield{type:"error",error:new F(Z.InternalError,`Task ${n} failed`)}:s.status==="cancelled"&&(yield{type:"error",error:new F(Z.InternalError,`Task ${n} was cancelled`)});return}if(s.status==="input_required"){yield{type:"result",result:await this.getTaskResult({taskId:n},a,i)};return}let p=s.pollInterval??this._options?.defaultTaskPollInterval??1e3;await new Promise(d=>setTimeout(d,p)),i?.signal?.throwIfAborted()}}catch(o){yield{type:"error",error:o instanceof F?o:new F(Z.InternalError,String(o))}}}request(t,a,i){let{relatedRequestId:r,resumptionToken:n,onresumptiontoken:o,task:s,relatedTask:p}=i??{};return new Promise((d,c)=>{let u=L=>{c(L)};if(!this._transport){u(new Error("Not connected"));return}if(this._options?.enforceStrictCapabilities===!0)try{this.assertCapabilityForMethod(t.method),s&&this.assertTaskCapability(t.method)}catch(L){u(L);return}i?.signal?.throwIfAborted();let g=this._requestMessageId++,y={...t,jsonrpc:"2.0",id:g};i?.onprogress&&(this._progressHandlers.set(g,i.onprogress),y.params={...t.params,_meta:{...t.params?._meta||{},progressToken:g}}),s&&(y.params={...y.params,task:s}),p&&(y.params={...y.params,_meta:{...y.params?._meta||{},[er]:p}});let v=L=>{this._responseHandlers.delete(g),this._progressHandlers.delete(g),this._cleanupTimeout(g),this._transport?.send({jsonrpc:"2.0",method:"notifications/cancelled",params:{requestId:g,reason:String(L)}},{relatedRequestId:r,resumptionToken:n,onresumptiontoken:o}).catch(U=>this._onerror(new Error(`Failed to send cancellation: ${U}`)));let D=L instanceof F?L:new F(Z.RequestTimeout,String(L));c(D)};this._responseHandlers.set(g,L=>{if(!i?.signal?.aborted){if(L instanceof Error)return c(L);try{let D=Qa(a,L.result);D.success?d(D.data):c(D.error)}catch(D){c(D)}}}),i?.signal?.addEventListener("abort",()=>{v(i?.signal?.reason)});let S=i?.timeout??KB,T=()=>v(F.fromError(Z.RequestTimeout,"Request timed out",{timeout:S}));this._setupTimeout(g,S,i?.maxTotalTimeout,T,i?.resetTimeoutOnProgress??!1);let z=p?.taskId;if(z){let L=D=>{let U=this._responseHandlers.get(g);U?U(D):this._onerror(new Error(`Response handler missing for side-channeled request ${g}`))};this._requestResolvers.set(g,L),this._enqueueTaskMessage(z,{type:"request",message:y,timestamp:Date.now()}).catch(D=>{this._cleanupTimeout(g),c(D)})}else this._transport.send(y,{relatedRequestId:r,resumptionToken:n,onresumptiontoken:o}).catch(L=>{this._cleanupTimeout(g),c(L)})})}async getTask(t,a){return this.request({method:"tasks/get",params:t},Uh,a)}async getTaskResult(t,a,i){return this.request({method:"tasks/result",params:t},a,i)}async listTasks(t,a){return this.request({method:"tasks/list",params:t},Vh,a)}async cancelTask(t,a){return this.request({method:"tasks/cancel",params:t},Kz,a)}async notification(t,a){if(!this._transport)throw new Error("Not connected");this.assertNotificationCapability(t.method);let i=a?.relatedTask?.taskId;if(i){let s={...t,jsonrpc:"2.0",params:{...t.params,_meta:{...t.params?._meta||{},[er]:a.relatedTask}}};await this._enqueueTaskMessage(i,{type:"notification",message:s,timestamp:Date.now()});return}if((this._options?.debouncedNotificationMethods??[]).includes(t.method)&&!t.params&&!a?.relatedRequestId&&!a?.relatedTask){if(this._pendingDebouncedNotifications.has(t.method))return;this._pendingDebouncedNotifications.add(t.method),Promise.resolve().then(()=>{if(this._pendingDebouncedNotifications.delete(t.method),!this._transport)return;let s={...t,jsonrpc:"2.0"};a?.relatedTask&&(s={...s,params:{...s.params,_meta:{...s.params?._meta||{},[er]:a.relatedTask}}}),this._transport?.send(s,a).catch(p=>this._onerror(p))});return}let o={...t,jsonrpc:"2.0"};a?.relatedTask&&(o={...o,params:{...o.params,_meta:{...o.params?._meta||{},[er]:a.relatedTask}}}),await this._transport.send(o,a)}setRequestHandler(t,a){let i=E7(t);this.assertRequestHandlerCapability(i),this._requestHandlers.set(i,(r,n)=>{let o=z7(t,r);return Promise.resolve(a(o,n))})}removeRequestHandler(t){this._requestHandlers.delete(t)}assertCanSetRequestHandler(t){if(this._requestHandlers.has(t))throw new Error(`A request handler for ${t} already exists, which would be overridden`)}setNotificationHandler(t,a){let i=E7(t);this._notificationHandlers.set(i,r=>{let n=z7(t,r);return Promise.resolve(a(n))})}removeNotificationHandler(t){this._notificationHandlers.delete(t)}_cleanupTaskProgressHandler(t){let a=this._taskProgressTokens.get(t);a!==void 0&&(this._progressHandlers.delete(a),this._taskProgressTokens.delete(t))}async _enqueueTaskMessage(t,a,i){if(!this._taskStore||!this._taskMessageQueue)throw new Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");let r=this._options?.maxTaskQueueSize;await this._taskMessageQueue.enqueue(t,a,i,r)}async _clearTaskQueue(t,a){if(this._taskMessageQueue){let i=await this._taskMessageQueue.dequeueAll(t,a);for(let r of i)if(r.type==="request"&&c7(r.message)){let n=r.message.id,o=this._requestResolvers.get(n);o?(o(new F(Z.InternalError,"Task cancelled or completed")),this._requestResolvers.delete(n)):this._onerror(new Error(`Resolver missing for request ${n} during task ${t} cleanup`))}}}async _waitForTaskUpdate(t,a){let i=this._options?.defaultTaskPollInterval??1e3;try{let r=await this._taskStore?.getTask(t);r?.pollInterval&&(i=r.pollInterval)}catch{}return new Promise((r,n)=>{if(a.aborted){n(new F(Z.InvalidRequest,"Request cancelled"));return}let o=setTimeout(r,i);a.addEventListener("abort",()=>{clearTimeout(o),n(new F(Z.InvalidRequest,"Request cancelled"))},{once:!0})})}requestTaskStore(t,a){let i=this._taskStore;if(!i)throw new Error("No task store configured");return{createTask:async r=>{if(!t)throw new Error("No request provided");return await i.createTask(r,t.id,{method:t.method,params:t.params},a)},getTask:async r=>{let n=await i.getTask(r,a);if(!n)throw new F(Z.InvalidParams,"Failed to retrieve task: Task not found");return n},storeTaskResult:async(r,n,o)=>{await i.storeTaskResult(r,n,o,a);let s=await i.getTask(r,a);if(s){let p=Zd.parse({method:"notifications/tasks/status",params:s});await this.notification(p),tr(s.status)&&this._cleanupTaskProgressHandler(r)}},getTaskResult:r=>i.getTaskResult(r,a),updateTaskStatus:async(r,n,o)=>{let s=await i.getTask(r,a);if(!s)throw new F(Z.InvalidParams,`Task "${r}" not found - it may have been cleaned up`);if(tr(s.status))throw new F(Z.InvalidParams,`Cannot update task "${r}" from terminal status "${s.status}" to "${n}". Terminal states (completed, failed, cancelled) cannot transition to other states.`);await i.updateTaskStatus(r,n,o,a);let p=await i.getTask(r,a);if(p){let d=Zd.parse({method:"notifications/tasks/status",params:p});await this.notification(d),tr(p.status)&&this._cleanupTaskProgressHandler(r)}},listTasks:r=>i.listTasks(r,a)}}};function OR(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function MR(e,t){let a={...e};for(let i in t){let r=i,n=t[r];if(n===void 0)continue;let o=a[r];OR(o)&&OR(n)?a[r]={...o,...n}:a[r]=n}return a}var Nj=A9(yw(),1),Ij=A9($j(),1);function YK(){let e=new Nj.default({strict:!1,validateFormats:!0,validateSchema:!1,allErrors:!0});return(0,Ij.default)(e),e}var M4=class{constructor(t){this._ajv=t??YK()}getValidator(t){let a="$id"in t&&typeof t.$id=="string"?this._ajv.getSchema(t.$id)??this._ajv.compile(t):this._ajv.compile(t);return i=>a(i)?{valid:!0,data:i,errorMessage:void 0}:{valid:!1,data:void 0,errorMessage:this._ajv.errorsText(a.errors)}}};var D4=class{constructor(t){this._server=t}requestStream(t,a,i){return this._server.requestStream(t,a,i)}createMessageStream(t,a){let i=this._server.getClientCapabilities();if((t.tools||t.toolChoice)&&!i?.sampling?.tools)throw new Error("Client does not support sampling tools capability.");if(t.messages.length>0){let r=t.messages[t.messages.length-1],n=Array.isArray(r.content)?r.content:[r.content],o=n.some(c=>c.type==="tool_result"),s=t.messages.length>1?t.messages[t.messages.length-2]:void 0,p=s?Array.isArray(s.content)?s.content:[s.content]:[],d=p.some(c=>c.type==="tool_use");if(o){if(n.some(c=>c.type!=="tool_result"))throw new Error("The last message must contain only tool_result content if any is present");if(!d)throw new Error("tool_result blocks are not matching any tool_use from the previous message")}if(d){let c=new Set(p.filter(g=>g.type==="tool_use").map(g=>g.id)),u=new Set(n.filter(g=>g.type==="tool_result").map(g=>g.toolUseId));if(c.size!==u.size||![...c].every(g=>u.has(g)))throw new Error("ids of tool_result blocks and tool_use blocks from previous message do not match")}}return this.requestStream({method:"sampling/createMessage",params:t},Hd,a)}elicitInputStream(t,a){let i=this._server.getClientCapabilities(),r=t.mode??"form";switch(r){case"url":{if(!i?.elicitation?.url)throw new Error("Client does not support url elicitation.");break}case"form":{if(!i?.elicitation?.form)throw new Error("Client does not support form elicitation.");break}}let n=r==="form"&&t.mode===void 0?{...t,mode:"form"}:t;return this.requestStream({method:"elicitation/create",params:n},Yi,a)}async getTask(t,a){return this._server.getTask({taskId:t},a)}async getTaskResult(t,a,i){return this._server.getTaskResult({taskId:t},a,i)}async listTasks(t,a){return this._server.listTasks(t?{cursor:t}:void 0,a)}async cancelTask(t,a){return this._server.cancelTask({taskId:t},a)}};function Ej(e,t,a){if(!e)throw new Error(`${a} does not support task creation (required for ${t})`);switch(t){case"tools/call":if(!e.tools?.call)throw new Error(`${a} does not support task creation for tools/call (required for ${t})`);break;default:break}}function zj(e,t,a){if(!e)throw new Error(`${a} does not support task creation (required for ${t})`);switch(t){case"sampling/createMessage":if(!e.sampling?.createMessage)throw new Error(`${a} does not support task creation for sampling/createMessage (required for ${t})`);break;case"elicitation/create":if(!e.elicitation?.create)throw new Error(`${a} does not support task creation for elicitation/create (required for ${t})`);break;default:break}}var U4=class extends a4{constructor(t,a){super(a),this._serverInfo=t,this._loggingLevels=new Map,this.LOG_LEVEL_SEVERITY=new Map(Jd.options.map((i,r)=>[i,r])),this.isMessageIgnored=(i,r)=>{let n=this._loggingLevels.get(r);return n?this.LOG_LEVEL_SEVERITY.get(i)this._oninitialize(i)),this.setNotificationHandler(f7,()=>this.oninitialized?.()),this._capabilities.logging&&this.setRequestHandler(w7,async(i,r)=>{let n=r.sessionId||r.requestInfo?.headers["mcp-session-id"]||void 0,{level:o}=i.params,s=Jd.safeParse(o);return s.success&&this._loggingLevels.set(n,s.data),{}})}get experimental(){return this._experimental||(this._experimental={tasks:new D4(this)}),this._experimental}registerCapabilities(t){if(this.transport)throw new Error("Cannot register capabilities after connecting to transport");this._capabilities=MR(this._capabilities,t)}setRequestHandler(t,a){let r=Xa(t)?.method;if(!r)throw new Error("Schema is missing a method literal");let n;if(Rt(r)){let s=r;n=s._zod?.def?.value??s.value}else{let s=r;n=s._def?.value??s.value}if(typeof n!="string")throw new Error("Schema method literal must be a string");if(n==="tools/call"){let s=async(p,d)=>{let c=Qa(Xi,p);if(!c.success){let v=c.error instanceof Error?c.error.message:String(c.error);throw new F(Z.InvalidParams,`Invalid tools/call request: ${v}`)}let{params:u}=c.data,g=await Promise.resolve(a(p,d));if(u.task){let v=Qa(Ki,g);if(!v.success){let S=v.error instanceof Error?v.error.message:String(v.error);throw new F(Z.InvalidParams,`Invalid task creation result: ${S}`)}return v.data}let y=Qa(Kh,g);if(!y.success){let v=y.error instanceof Error?y.error.message:String(y.error);throw new F(Z.InvalidParams,`Invalid tools/call result: ${v}`)}return y.data};return super.setRequestHandler(t,s)}return super.setRequestHandler(t,a)}assertCapabilityForMethod(t){switch(t){case"sampling/createMessage":if(!this._clientCapabilities?.sampling)throw new Error(`Client does not support sampling (required for ${t})`);break;case"elicitation/create":if(!this._clientCapabilities?.elicitation)throw new Error(`Client does not support elicitation (required for ${t})`);break;case"roots/list":if(!this._clientCapabilities?.roots)throw new Error(`Client does not support listing roots (required for ${t})`);break;case"ping":break}}assertNotificationCapability(t){switch(t){case"notifications/message":if(!this._capabilities.logging)throw new Error(`Server does not support logging (required for ${t})`);break;case"notifications/resources/updated":case"notifications/resources/list_changed":if(!this._capabilities.resources)throw new Error(`Server does not support notifying about resources (required for ${t})`);break;case"notifications/tools/list_changed":if(!this._capabilities.tools)throw new Error(`Server does not support notifying of tool list changes (required for ${t})`);break;case"notifications/prompts/list_changed":if(!this._capabilities.prompts)throw new Error(`Server does not support notifying of prompt list changes (required for ${t})`);break;case"notifications/elicitation/complete":if(!this._clientCapabilities?.elicitation?.url)throw new Error(`Client does not support URL elicitation (required for ${t})`);break;case"notifications/cancelled":break;case"notifications/progress":break}}assertRequestHandlerCapability(t){if(this._capabilities)switch(t){case"completion/complete":if(!this._capabilities.completions)throw new Error(`Server does not support completions (required for ${t})`);break;case"logging/setLevel":if(!this._capabilities.logging)throw new Error(`Server does not support logging (required for ${t})`);break;case"prompts/get":case"prompts/list":if(!this._capabilities.prompts)throw new Error(`Server does not support prompts (required for ${t})`);break;case"resources/list":case"resources/templates/list":case"resources/read":if(!this._capabilities.resources)throw new Error(`Server does not support resources (required for ${t})`);break;case"tools/call":case"tools/list":if(!this._capabilities.tools)throw new Error(`Server does not support tools (required for ${t})`);break;case"tasks/get":case"tasks/list":case"tasks/result":case"tasks/cancel":if(!this._capabilities.tasks)throw new Error(`Server does not support tasks capability (required for ${t})`);break;case"ping":case"initialize":break}}assertTaskCapability(t){zj(this._clientCapabilities?.tasks?.requests,t,"Client")}assertTaskHandlerCapability(t){this._capabilities&&Ej(this._capabilities.tasks?.requests,t,"Server")}async _oninitialize(t){let a=t.params.protocolVersion;return this._clientCapabilities=t.params.capabilities,this._clientVersion=t.params.clientInfo,{protocolVersion:qz.includes(a)?a:p7,capabilities:this.getCapabilities(),serverInfo:this._serverInfo,...this._instructions&&{instructions:this._instructions}}}getClientCapabilities(){return this._clientCapabilities}getClientVersion(){return this._clientVersion}getCapabilities(){return this._capabilities}async ping(){return this.request({method:"ping"},Ah)}async createMessage(t,a){if((t.tools||t.toolChoice)&&!this._clientCapabilities?.sampling?.tools)throw new Error("Client does not support sampling tools capability.");if(t.messages.length>0){let i=t.messages[t.messages.length-1],r=Array.isArray(i.content)?i.content:[i.content],n=r.some(d=>d.type==="tool_result"),o=t.messages.length>1?t.messages[t.messages.length-2]:void 0,s=o?Array.isArray(o.content)?o.content:[o.content]:[],p=s.some(d=>d.type==="tool_use");if(n){if(r.some(d=>d.type!=="tool_result"))throw new Error("The last message must contain only tool_result content if any is present");if(!p)throw new Error("tool_result blocks are not matching any tool_use from the previous message")}if(p){let d=new Set(s.filter(u=>u.type==="tool_use").map(u=>u.id)),c=new Set(r.filter(u=>u.type==="tool_result").map(u=>u.toolUseId));if(d.size!==c.size||![...d].every(u=>c.has(u)))throw new Error("ids of tool_result blocks and tool_use blocks from previous message do not match")}}return t.tools?this.request({method:"sampling/createMessage",params:t},x7,a):this.request({method:"sampling/createMessage",params:t},Hd,a)}async elicitInput(t,a){switch(t.mode??"form"){case"url":{if(!this._clientCapabilities?.elicitation?.url)throw new Error("Client does not support url elicitation.");let r=t;return this.request({method:"elicitation/create",params:r},Yi,a)}case"form":{if(!this._clientCapabilities?.elicitation?.form)throw new Error("Client does not support form elicitation.");let r=t.mode==="form"?t:{...t,mode:"form"},n=await this.request({method:"elicitation/create",params:r},Yi,a);if(n.action==="accept"&&n.content&&r.requestedSchema)try{let s=this._jsonSchemaValidator.getValidator(r.requestedSchema)(n.content);if(!s.valid)throw new F(Z.InvalidParams,`Elicitation response content does not match requested schema: ${s.errorMessage}`)}catch(o){throw o instanceof F?o:new F(Z.InternalError,`Error validating elicitation response: ${o instanceof Error?o.message:String(o)}`)}return n}}}createElicitationCompletionNotifier(t,a){if(!this._clientCapabilities?.elicitation?.url)throw new Error("Client does not support URL elicitation (required for notifications/elicitation/complete)");return()=>this.notification({method:"notifications/elicitation/complete",params:{elicitationId:t}},a)}async listRoots(t,a){return this.request({method:"roots/list",params:t},S7,a)}async sendLoggingMessage(t,a){if(this._capabilities.logging&&!this.isMessageIgnored(t.level,a))return this.notification({method:"notifications/message",params:t})}async sendResourceUpdated(t){return this.notification({method:"notifications/resources/updated",params:t})}async sendResourceListChanged(){return this.notification({method:"notifications/resources/list_changed"})}async sendToolListChanged(){return this.notification({method:"notifications/tools/list_changed"})}async sendPromptListChanged(){return this.notification({method:"notifications/prompts/list_changed"})}};var Cj=Symbol.for("mcp.completable");function kw(e){return!!e&&typeof e=="object"&&Cj in e}function Aj(e){return e[Cj]?.complete}var Rj;(function(e){e.Completable="McpCompletable"})(Rj||(Rj={}));var eQ=/^[A-Za-z0-9._-]{1,128}$/;function tQ(e){let t=[];if(e.length===0)return{isValid:!1,warnings:["Tool name cannot be empty"]};if(e.length>128)return{isValid:!1,warnings:[`Tool name exceeds maximum length of 128 characters (current: ${e.length})`]};if(e.includes(" ")&&t.push("Tool name contains spaces, which may cause parsing issues"),e.includes(",")&&t.push("Tool name contains commas, which may cause parsing issues"),(e.startsWith("-")||e.endsWith("-"))&&t.push("Tool name starts or ends with a dash, which may cause parsing issues in some contexts"),(e.startsWith(".")||e.endsWith("."))&&t.push("Tool name starts or ends with a dot, which may cause parsing issues in some contexts"),!eQ.test(e)){let a=e.split("").filter(i=>!/[A-Za-z0-9._-]/.test(i)).filter((i,r,n)=>n.indexOf(i)===r);return t.push(`Tool name contains invalid characters: ${a.map(i=>`"${i}"`).join(", ")}`,"Allowed characters are: A-Z, a-z, 0-9, underscore (_), dash (-), and dot (.)"),{isValid:!1,warnings:t}}return{isValid:!0,warnings:t}}function aQ(e,t){if(t.length>0){console.warn(`Tool name validation warning for "${e}":`);for(let a of t)console.warn(` - ${a}`);console.warn("Tool registration will proceed, but this may cause compatibility issues."),console.warn("Consider updating the tool name to conform to the MCP tool naming standard."),console.warn("See SEP: Specify Format for Tool Names (https://github.com/modelcontextprotocol/modelcontextprotocol/issues/986) for more details.")}}function Pw(e){let t=tQ(e);return aQ(e,t.warnings),t.isValid}var q4=class{constructor(t){this._mcpServer=t}registerToolTask(t,a,i){let r={taskSupport:"required",...a.execution};if(r.taskSupport==="forbidden")throw new Error(`Cannot register task-based tool '${t}' with taskSupport 'forbidden'. Use registerTool() instead.`);return this._mcpServer._createRegisteredTool(t,a.title,a.description,a.inputSchema,a.outputSchema,a.annotations,r,a._meta,i)}};var L4=class{constructor(t,a){this._registeredResources={},this._registeredResourceTemplates={},this._registeredTools={},this._registeredPrompts={},this._toolHandlersInitialized=!1,this._completionHandlerInitialized=!1,this._resourceHandlersInitialized=!1,this._promptHandlersInitialized=!1,this.server=new U4(t,a)}get experimental(){return this._experimental||(this._experimental={tasks:new q4(this)}),this._experimental}async connect(t){return await this.server.connect(t)}async close(){await this.server.close()}setToolRequestHandlers(){this._toolHandlersInitialized||(this.server.assertCanSetRequestHandler(cr(Wh)),this.server.assertCanSetRequestHandler(cr(Xi)),this.server.registerCapabilities({tools:{listChanged:!0}}),this.server.setRequestHandler(Wh,()=>({tools:Object.entries(this._registeredTools).filter(([,t])=>t.enabled).map(([t,a])=>{let i={name:t,title:a.title,description:a.description,inputSchema:(()=>{let r=Ui(a.inputSchema);return r?I7(r,{strictUnions:!0,pipeStrategy:"input"}):rQ})(),annotations:a.annotations,execution:a.execution,_meta:a._meta};if(a.outputSchema){let r=Ui(a.outputSchema);r&&(i.outputSchema=I7(r,{strictUnions:!0,pipeStrategy:"output"}))}return i})})),this.server.setRequestHandler(Xi,async(t,a)=>{try{let i=this._registeredTools[t.params.name];if(!i)throw new F(Z.InvalidParams,`Tool ${t.params.name} not found`);if(!i.enabled)throw new F(Z.InvalidParams,`Tool ${t.params.name} disabled`);let r=!!t.params.task,n=i.execution?.taskSupport,o="createTask"in i.handler;if((n==="required"||n==="optional")&&!o)throw new F(Z.InternalError,`Tool ${t.params.name} has taskSupport '${n}' but was not registered with registerToolTask`);if(n==="required"&&!r)throw new F(Z.MethodNotFound,`Tool ${t.params.name} requires task augmentation (taskSupport: 'required')`);if(n==="optional"&&!r&&o)return await this.handleAutomaticTaskPolling(i,t,a);let s=await this.validateToolInput(i,t.params.arguments,t.params.name),p=await this.executeToolHandler(i,s,a);return r||await this.validateToolOutput(i,p,t.params.name),p}catch(i){if(i instanceof F&&i.code===Z.UrlElicitationRequired)throw i;return this.createToolError(i instanceof Error?i.message:String(i))}}),this._toolHandlersInitialized=!0)}createToolError(t){return{content:[{type:"text",text:t}],isError:!0}}async validateToolInput(t,a,i){if(!t.inputSchema)return;let n=Ui(t.inputSchema)??t.inputSchema,o=await Y0(n,a);if(!o.success){let s="error"in o?o.error:"Unknown error",p=eh(s);throw new F(Z.InvalidParams,`Input validation error: Invalid arguments for tool ${i}: ${p}`)}return o.data}async validateToolOutput(t,a,i){if(!t.outputSchema||!("content"in a)||a.isError)return;if(!a.structuredContent)throw new F(Z.InvalidParams,`Output validation error: Tool ${i} has an output schema but no structured content was provided`);let r=Ui(t.outputSchema),n=await Y0(r,a.structuredContent);if(!n.success){let o="error"in n?n.error:"Unknown error",s=eh(o);throw new F(Z.InvalidParams,`Output validation error: Invalid structured content for tool ${i}: ${s}`)}}async executeToolHandler(t,a,i){let r=t.handler;if("createTask"in r){if(!i.taskStore)throw new Error("No task store provided.");let o={...i,taskStore:i.taskStore};if(t.inputSchema){let s=r;return await Promise.resolve(s.createTask(a,o))}else{let s=r;return await Promise.resolve(s.createTask(o))}}if(t.inputSchema){let o=r;return await Promise.resolve(o(a,i))}else{let o=r;return await Promise.resolve(o(i))}}async handleAutomaticTaskPolling(t,a,i){if(!i.taskStore)throw new Error("No task store provided for task-capable tool.");let r=await this.validateToolInput(t,a.params.arguments,a.params.name),n=t.handler,o={...i,taskStore:i.taskStore},s=r?await Promise.resolve(n.createTask(r,o)):await Promise.resolve(n.createTask(o)),p=s.task.taskId,d=s.task,c=d.pollInterval??5e3;for(;d.status!=="completed"&&d.status!=="failed"&&d.status!=="cancelled";){await new Promise(g=>setTimeout(g,c));let u=await i.taskStore.getTask(p);if(!u)throw new F(Z.InternalError,`Task ${p} not found during polling`);d=u}return await i.taskStore.getTaskResult(p)}setCompletionRequestHandler(){this._completionHandlerInitialized||(this.server.assertCanSetRequestHandler(cr(Qh)),this.server.registerCapabilities({completions:{}}),this.server.setRequestHandler(Qh,async t=>{switch(t.params.ref.type){case"ref/prompt":return aR(t),this.handlePromptCompletion(t,t.params.ref);case"ref/resource":return rR(t),this.handleResourceCompletion(t,t.params.ref);default:throw new F(Z.InvalidParams,`Invalid completion reference: ${t.params.ref}`)}}),this._completionHandlerInitialized=!0)}async handlePromptCompletion(t,a){let i=this._registeredPrompts[a.name];if(!i)throw new F(Z.InvalidParams,`Prompt ${a.name} not found`);if(!i.enabled)throw new F(Z.InvalidParams,`Prompt ${a.name} disabled`);if(!i.argsSchema)return kc;let n=Xa(i.argsSchema)?.[t.params.argument.name];if(!kw(n))return kc;let o=Aj(n);if(!o)return kc;let s=await o(t.params.argument.value,t.params.context);return Oj(s)}async handleResourceCompletion(t,a){let i=Object.values(this._registeredResourceTemplates).find(o=>o.resourceTemplate.uriTemplate.toString()===a.uri);if(!i){if(this._registeredResources[a.uri])return kc;throw new F(Z.InvalidParams,`Resource template ${t.params.ref.uri} not found`)}let r=i.resourceTemplate.completeCallback(t.params.argument.name);if(!r)return kc;let n=await r(t.params.argument.value,t.params.context);return Oj(n)}setResourceRequestHandlers(){this._resourceHandlersInitialized||(this.server.assertCanSetRequestHandler(cr(Bh)),this.server.assertCanSetRequestHandler(cr(Zh)),this.server.assertCanSetRequestHandler(cr(Gh)),this.server.registerCapabilities({resources:{listChanged:!0}}),this.server.setRequestHandler(Bh,async(t,a)=>{let i=Object.entries(this._registeredResources).filter(([n,o])=>o.enabled).map(([n,o])=>({uri:n,name:o.name,...o.metadata})),r=[];for(let n of Object.values(this._registeredResourceTemplates)){if(!n.resourceTemplate.listCallback)continue;let o=await n.resourceTemplate.listCallback(a);for(let s of o.resources)r.push({...n.metadata,...s})}return{resources:[...i,...r]}}),this.server.setRequestHandler(Zh,async()=>({resourceTemplates:Object.entries(this._registeredResourceTemplates).map(([a,i])=>({name:a,uriTemplate:i.resourceTemplate.uriTemplate.toString(),...i.metadata}))})),this.server.setRequestHandler(Gh,async(t,a)=>{let i=new URL(t.params.uri),r=this._registeredResources[i.toString()];if(r){if(!r.enabled)throw new F(Z.InvalidParams,`Resource ${i} disabled`);return r.readCallback(i,a)}for(let n of Object.values(this._registeredResourceTemplates)){let o=n.resourceTemplate.uriTemplate.match(i.toString());if(o)return n.readCallback(i,o,a)}throw new F(Z.InvalidParams,`Resource ${i} not found`)}),this._resourceHandlersInitialized=!0)}setPromptRequestHandlers(){this._promptHandlersInitialized||(this.server.assertCanSetRequestHandler(cr(Jh)),this.server.assertCanSetRequestHandler(cr(Hh)),this.server.registerCapabilities({prompts:{listChanged:!0}}),this.server.setRequestHandler(Jh,()=>({prompts:Object.entries(this._registeredPrompts).filter(([,t])=>t.enabled).map(([t,a])=>({name:t,title:a.title,description:a.description,arguments:a.argsSchema?iQ(a.argsSchema):void 0}))})),this.server.setRequestHandler(Hh,async(t,a)=>{let i=this._registeredPrompts[t.params.name];if(!i)throw new F(Z.InvalidParams,`Prompt ${t.params.name} not found`);if(!i.enabled)throw new F(Z.InvalidParams,`Prompt ${t.params.name} disabled`);if(i.argsSchema){let r=Ui(i.argsSchema),n=await Y0(r,t.params.arguments);if(!n.success){let p="error"in n?n.error:"Unknown error",d=eh(p);throw new F(Z.InvalidParams,`Invalid arguments for prompt ${t.params.name}: ${d}`)}let o=n.data,s=i.callback;return await Promise.resolve(s(o,a))}else{let r=i.callback;return await Promise.resolve(r(a))}}),this._promptHandlersInitialized=!0)}resource(t,a,...i){let r;typeof i[0]=="object"&&(r=i.shift());let n=i[0];if(typeof a=="string"){if(this._registeredResources[a])throw new Error(`Resource ${a} is already registered`);let o=this._createRegisteredResource(t,void 0,a,r,n);return this.setResourceRequestHandlers(),this.sendResourceListChanged(),o}else{if(this._registeredResourceTemplates[t])throw new Error(`Resource template ${t} is already registered`);let o=this._createRegisteredResourceTemplate(t,void 0,a,r,n);return this.setResourceRequestHandlers(),this.sendResourceListChanged(),o}}registerResource(t,a,i,r){if(typeof a=="string"){if(this._registeredResources[a])throw new Error(`Resource ${a} is already registered`);let n=this._createRegisteredResource(t,i.title,a,i,r);return this.setResourceRequestHandlers(),this.sendResourceListChanged(),n}else{if(this._registeredResourceTemplates[t])throw new Error(`Resource template ${t} is already registered`);let n=this._createRegisteredResourceTemplate(t,i.title,a,i,r);return this.setResourceRequestHandlers(),this.sendResourceListChanged(),n}}_createRegisteredResource(t,a,i,r,n){let o={name:t,title:a,metadata:r,readCallback:n,enabled:!0,disable:()=>o.update({enabled:!1}),enable:()=>o.update({enabled:!0}),remove:()=>o.update({uri:null}),update:s=>{typeof s.uri<"u"&&s.uri!==i&&(delete this._registeredResources[i],s.uri&&(this._registeredResources[s.uri]=o)),typeof s.name<"u"&&(o.name=s.name),typeof s.title<"u"&&(o.title=s.title),typeof s.metadata<"u"&&(o.metadata=s.metadata),typeof s.callback<"u"&&(o.readCallback=s.callback),typeof s.enabled<"u"&&(o.enabled=s.enabled),this.sendResourceListChanged()}};return this._registeredResources[i]=o,o}_createRegisteredResourceTemplate(t,a,i,r,n){let o={resourceTemplate:i,title:a,metadata:r,readCallback:n,enabled:!0,disable:()=>o.update({enabled:!1}),enable:()=>o.update({enabled:!0}),remove:()=>o.update({name:null}),update:d=>{typeof d.name<"u"&&d.name!==t&&(delete this._registeredResourceTemplates[t],d.name&&(this._registeredResourceTemplates[d.name]=o)),typeof d.title<"u"&&(o.title=d.title),typeof d.template<"u"&&(o.resourceTemplate=d.template),typeof d.metadata<"u"&&(o.metadata=d.metadata),typeof d.callback<"u"&&(o.readCallback=d.callback),typeof d.enabled<"u"&&(o.enabled=d.enabled),this.sendResourceListChanged()}};this._registeredResourceTemplates[t]=o;let s=i.uriTemplate.variableNames;return Array.isArray(s)&&s.some(d=>!!i.completeCallback(d))&&this.setCompletionRequestHandler(),o}_createRegisteredPrompt(t,a,i,r,n){let o={title:a,description:i,argsSchema:r===void 0?void 0:Ar(r),callback:n,enabled:!0,disable:()=>o.update({enabled:!1}),enable:()=>o.update({enabled:!0}),remove:()=>o.update({name:null}),update:s=>{typeof s.name<"u"&&s.name!==t&&(delete this._registeredPrompts[t],s.name&&(this._registeredPrompts[s.name]=o)),typeof s.title<"u"&&(o.title=s.title),typeof s.description<"u"&&(o.description=s.description),typeof s.argsSchema<"u"&&(o.argsSchema=Ar(s.argsSchema)),typeof s.callback<"u"&&(o.callback=s.callback),typeof s.enabled<"u"&&(o.enabled=s.enabled),this.sendPromptListChanged()}};return this._registeredPrompts[t]=o,r&&Object.values(r).some(p=>{let d=p instanceof Hi?p._def?.innerType:p;return kw(d)})&&this.setCompletionRequestHandler(),o}_createRegisteredTool(t,a,i,r,n,o,s,p,d){Pw(t);let c={title:a,description:i,inputSchema:jj(r),outputSchema:jj(n),annotations:o,execution:s,_meta:p,handler:d,enabled:!0,disable:()=>c.update({enabled:!1}),enable:()=>c.update({enabled:!0}),remove:()=>c.update({name:null}),update:u=>{typeof u.name<"u"&&u.name!==t&&(typeof u.name=="string"&&Pw(u.name),delete this._registeredTools[t],u.name&&(this._registeredTools[u.name]=c)),typeof u.title<"u"&&(c.title=u.title),typeof u.description<"u"&&(c.description=u.description),typeof u.paramsSchema<"u"&&(c.inputSchema=Ar(u.paramsSchema)),typeof u.outputSchema<"u"&&(c.outputSchema=Ar(u.outputSchema)),typeof u.callback<"u"&&(c.handler=u.callback),typeof u.annotations<"u"&&(c.annotations=u.annotations),typeof u._meta<"u"&&(c._meta=u._meta),typeof u.enabled<"u"&&(c.enabled=u.enabled),this.sendToolListChanged()}};return this._registeredTools[t]=c,this.setToolRequestHandlers(),this.sendToolListChanged(),c}tool(t,...a){if(this._registeredTools[t])throw new Error(`Tool ${t} is already registered`);let i,r,n,o;if(typeof a[0]=="string"&&(i=a.shift()),a.length>1){let p=a[0];if($w(p))r=a.shift(),a.length>1&&typeof a[0]=="object"&&a[0]!==null&&!$w(a[0])&&(o=a.shift());else if(typeof p=="object"&&p!==null){if(Object.values(p).some(d=>typeof d=="object"&&d!==null))throw new Error(`Tool ${t} expected a Zod schema or ToolAnnotations, but received an unrecognized object`);o=a.shift()}}let s=a[0];return this._createRegisteredTool(t,void 0,i,r,n,o,{taskSupport:"forbidden"},void 0,s)}registerTool(t,a,i){if(this._registeredTools[t])throw new Error(`Tool ${t} is already registered`);let{title:r,description:n,inputSchema:o,outputSchema:s,annotations:p,_meta:d}=a;return this._createRegisteredTool(t,r,n,o,s,p,{taskSupport:"forbidden"},d,i)}prompt(t,...a){if(this._registeredPrompts[t])throw new Error(`Prompt ${t} is already registered`);let i;typeof a[0]=="string"&&(i=a.shift());let r;a.length>1&&(r=a.shift());let n=a[0],o=this._createRegisteredPrompt(t,void 0,i,r,n);return this.setPromptRequestHandlers(),this.sendPromptListChanged(),o}registerPrompt(t,a,i){if(this._registeredPrompts[t])throw new Error(`Prompt ${t} is already registered`);let{title:r,description:n,argsSchema:o}=a,s=this._createRegisteredPrompt(t,r,n,o,i);return this.setPromptRequestHandlers(),this.sendPromptListChanged(),s}isConnected(){return this.server.transport!==void 0}async sendLoggingMessage(t,a){return this.server.sendLoggingMessage(t,a)}sendResourceListChanged(){this.isConnected()&&this.server.sendResourceListChanged()}sendToolListChanged(){this.isConnected()&&this.server.sendToolListChanged()}sendPromptListChanged(){this.isConnected()&&this.server.sendPromptListChanged()}};var rQ={type:"object",properties:{}};function Mj(e){return e!==null&&typeof e=="object"&&"parse"in e&&typeof e.parse=="function"&&"safeParse"in e&&typeof e.safeParse=="function"}function Dj(e){return"_def"in e||"_zod"in e||Mj(e)}function $w(e){return typeof e!="object"||e===null||Dj(e)?!1:Object.keys(e).length===0?!0:Object.values(e).some(Mj)}function jj(e){if(e){if($w(e))return Ar(e);if(!Dj(e))throw new Error("inputSchema must be a Zod schema or raw shape, received an unrecognized object");return e}}function iQ(e){let t=Xa(e);return t?Object.entries(t).map(([a,i])=>{let r=kE(i),n=PE(i);return{name:a,description:r,required:!n}}):[]}function cr(e){let a=Xa(e)?.method;if(!a)throw new Error("Schema is missing a method literal");let i=th(a);if(typeof i=="string")return i;throw new Error("Schema method literal must be a string")}function Oj(e){return{completion:{values:e.slice(0,100),total:e.length,hasMore:e.length>100}}}var kc={completion:{values:[],hasMore:!1}};import qj from"node:process";var V4=class{append(t){this._buffer=this._buffer?Buffer.concat([this._buffer,t]):t}readMessage(){if(!this._buffer)return null;let t=this._buffer.indexOf(` `);if(t===-1)return null;let a=this._buffer.toString("utf8",0,t).replace(/\r$/,"");return this._buffer=this._buffer.subarray(t+1),nQ(a)}clear(){this._buffer=void 0}};function nQ(e){return Hz.parse(JSON.parse(e))}function Uj(e){return JSON.stringify(e)+` `}var F4=class{constructor(t=qj.stdin,a=qj.stdout){this._stdin=t,this._stdout=a,this._readBuffer=new V4,this._started=!1,this._ondata=i=>{this._readBuffer.append(i),this.processReadBuffer()},this._onerror=i=>{this.onerror?.(i)}}async start(){if(this._started)throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");this._started=!0,this._stdin.on("data",this._ondata),this._stdin.on("error",this._onerror)}processReadBuffer(){for(;;)try{let t=this._readBuffer.readMessage();if(t===null)break;this.onmessage?.(t)}catch(t){this.onerror?.(t)}}async close(){this._stdin.off("data",this._ondata),this._stdin.off("error",this._onerror),this._stdin.listenerCount("data")===0&&this._stdin.pause(),this._readBuffer.clear(),this.onclose?.()}send(t){return new Promise(a=>{let i=Uj(t);this._stdout.write(i)?a():this._stdout.once("drain",a)})}};var N9=A9(BU(),1);function ZU(e){process.stderr.write(`${e} -`)}function GU(){let e=process.env.MIRO_API_TOKEN;return e||(ZU("MIRO_API_TOKEN environment variable is required. Get one from https://miro.com/app/settings/user-profile/apps"),process.exit(1)),{api:new N9.MiroApi(e),lowLevel:new N9.MiroLowlevelApi(e)}}function ze(e){return{content:[{type:"text",text:JSON.stringify(e)}]}}function I9(e){return{content:[{type:"text",text:e}],isError:!0}}var zne=["private","view","comment","edit"],JU=O.enum(zne).optional().describe("Board link access level. 'view' makes the board public (anyone with link can view)"),Rne={private:"no_access",view:"viewer",comment:"commenter",edit:"editor"};function HU(e){return{sharingPolicy:{access:e,inviteToAccountAndBoardLinkAccess:Rne[e]}}}function WU(e,t,a){e.tool("miro_create_board","Create a new Miro board. Use this to start a new visual collaboration space. Returns the board ID and view URL.",{name:O.string().describe("Board name"),description:O.string().optional().describe("Board description"),sharing_access:JU},{destructiveHint:!1,openWorldHint:!0},async({name:i,description:r,sharing_access:n})=>{let o={name:i,description:r??""};n&&(o.policy=HU(n));let{body:s}=await a.createBoard(o);return ze({id:s.id,name:s.name,viewLink:s.viewLink})}),e.tool("miro_list_boards","List accessible Miro boards. Use this to find existing boards or verify a board exists. Returns board IDs, names, and view URLs.",{query:O.string().optional().describe("Search query to filter boards by name"),limit:O.number().min(1).max(50).default(10).describe("Max boards to return")},{readOnlyHint:!0,openWorldHint:!0},async({query:i,limit:r})=>{let n=[];for await(let o of t.getAllBoards({query:i}))if(n.push({id:o.id??"",name:o.name??"",viewLink:o.viewLink??""}),n.length>=r)break;return ze(n)}),e.tool("miro_get_board","Get details for a specific Miro board. Use this to check board metadata, sharing status, or last modified time. Returns name, description, view URL, and timestamps.",{board_id:O.string().describe("The board ID")},{readOnlyHint:!0,openWorldHint:!0},async({board_id:i})=>{let r=await t.getBoard(i);return ze({id:r.id,name:r.name,description:r.description,viewLink:r.viewLink,createdAt:r.createdAt,modifiedAt:r.modifiedAt})}),e.tool("miro_update_board","Update a Miro board's name, description, or sharing policy. Use this to rename boards, change descriptions, or adjust access levels. Returns the updated board details.",{board_id:O.string().describe("The board ID"),name:O.string().optional().describe("New board name"),description:O.string().optional().describe("New board description"),sharing_access:JU},{idempotentHint:!0,destructiveHint:!1,openWorldHint:!0},async({board_id:i,name:r,description:n,sharing_access:o})=>{let s={};r!==void 0&&(s.name=r),n!==void 0&&(s.description=n),o&&(s.policy=HU(o));let{body:p}=await a.updateBoard(i,s);return ze({id:p.id,name:p.name,viewLink:p.viewLink,sharingPolicy:p.sharingPolicy})}),e.tool("miro_delete_board","Permanently delete a Miro board. On paid plans, boards go to Trash (restorable via UI within 90 days). Use miro_list_boards first to verify the board ID.",{board_id:O.string().describe("The board ID to delete")},{destructiveHint:!0,openWorldHint:!0},async({board_id:i})=>(await a.deleteBoard(i),ze({id:i,status:"deleted"})))}var E9=["gray","light_yellow","yellow","orange","light_green","green","dark_green","cyan","light_pink","pink","violet","red","light_blue","blue","dark_blue","black"],xN=["square","rectangle"],KU=["sticky_note","shape","frame","text","card","connector","image","document","app_card"];function SN(e){return{data:{content:e.content,shape:e.shape},style:{fillColor:e.color,textAlign:"center",textAlignVertical:"middle"},position:{x:e.x,y:e.y},...e.parentId?{parent:{id:e.parentId}}:{}}}function QU(e,t,a){e.tool("miro_create_sticky_note","Create a sticky note on a Miro board. Use this for individual notes \u2014 for batch creation (up to 20), use miro_bulk_create_sticky_notes instead. Returns the item ID.",{board_id:O.string().describe("The board ID"),content:O.string().describe("Text content for the sticky note"),color:O.enum(E9).default("light_yellow").describe("Fill color"),x:O.number().default(0).describe("X position. Board-center-relative (0 = center) by default; relative to the parent frame's top-left corner when parent_id is set"),y:O.number().default(0).describe("Y position. Board-center-relative (0 = center) by default; relative to the parent frame's top-left corner when parent_id is set"),shape:O.enum(xN).default("square").describe("Sticky note shape"),parent_id:O.string().optional().describe("Parent frame ID to place the sticky inside. When set, x/y are interpreted relative to the frame's top-left corner, not the board center")},{destructiveHint:!1,openWorldHint:!0},async({board_id:i,content:r,color:n,x:o,y:s,shape:p,parent_id:d})=>{let u=await(await t.getBoard(i)).createStickyNoteItem(SN({content:r,shape:p,color:n,x:o,y:s,parentId:d}));return ze({id:u.id,type:u.type,content:r,color:n,position:{x:o,y:s}})}),e.tool("miro_update_sticky_note","Update a sticky note's content, color, or position. Use this to modify existing notes without recreating them. Only specified fields are changed \u2014 omitted fields keep their current values.",{board_id:O.string().describe("The board ID"),item_id:O.string().describe("The sticky note item ID"),content:O.string().optional().describe("New text content"),color:O.enum(E9).optional().describe("New fill color"),x:O.number().optional().describe("New X position"),y:O.number().optional().describe("New Y position")},{idempotentHint:!0,destructiveHint:!1,openWorldHint:!0},async({board_id:i,item_id:r,content:n,color:o,x:s,y:p})=>{let d={};return n!==void 0&&(d.data={content:n}),o!==void 0&&(d.style={fillColor:o}),(s!==void 0||p!==void 0)&&(d.position={...s!==void 0?{x:s}:{},...p!==void 0?{y:p}:{}}),await a.updateStickyNoteItem(i,r,d),ze({id:r,status:"updated"})}),e.tool("miro_list_board_items","List items on a Miro board. Use this to discover what's on a board before modifying it. Can filter by type (sticky_note, shape, frame, text, connector, etc.). Connectors are retrieved via Miro's separate connectors endpoint \u2014 pass type 'connector' to list them (each result carries startItemId/endItemId, the string IDs of the connected items). Returns item IDs, types, data, and positions.",{board_id:O.string().describe("The board ID"),type:O.enum(KU).optional().describe("Filter by item type"),limit:O.number().min(1).max(1e3).default(20).describe("Max items to return (the SDK auto-paginates beyond a single API page)")},{readOnlyHint:!0,openWorldHint:!0},async({board_id:i,type:r,limit:n})=>{let o=await t.getBoard(i),s=[];if(r==="connector"){for await(let p of o.getAllConnectors())if(s.push({id:p.id,type:p.type??"connector",...p.startItem?.id?{startItemId:p.startItem.id}:{},...p.endItem?.id?{endItemId:p.endItem.id}:{}}),s.length>=n)break}else for await(let p of o.getAllItems({type:r}))if(s.push({id:p.id,type:p.type,...p.data&&typeof p.data=="object"?{data:p.data}:{},...p.position?{position:p.position}:{}}),s.length>=n)break;return ze(s)}),e.tool("miro_delete_item","Delete an item from a Miro board. Use miro_list_board_items first to verify the item ID. Pass the item's type (as returned by miro_list_board_items) so connectors route to Miro's separate connector endpoint; any other type \u2014 or omitting it \u2014 deletes via the item endpoint.",{board_id:O.string().describe("The board ID"),item_id:O.string().describe("The item ID to delete"),type:O.enum(KU).optional().describe("Item type, as returned by miro_list_board_items. 'connector' routes to Miro's separate connector-delete endpoint; every other type (sticky_note, shape, frame, \u2026) uses the item-delete endpoint")},{destructiveHint:!0,openWorldHint:!0},async({board_id:i,item_id:r,type:n})=>(n==="connector"?await a.deleteConnector(i,r):await a.deleteItem(i,r),ze({id:r,status:"deleted"})))}var Cne=O.object({content:O.string().describe("Text content"),color:O.enum(E9).default("light_yellow").describe("Fill color name"),shape:O.enum(xN).default("square").describe("Sticky note shape. Use rectangle for Read Models, External Systems, Pivotal Events"),x:O.number().describe("X position. Board-center-relative (0 = center) by default; relative to the parent frame's top-left corner when parent_id is set"),y:O.number().describe("Y position. Board-center-relative (0 = center) by default; relative to the parent frame's top-left corner when parent_id is set")});function XU(e,t){e.tool("miro_bulk_create_sticky_notes","Create multiple sticky notes at once (max 20 per call). Use this instead of miro_create_sticky_note when placing many notes \u2014 faster and rate-limit aware. Notes are created sequentially with no rollback: if a later note fails, the earlier ones remain on the board. Returns the count and IDs of created items.",{board_id:O.string().describe("The board ID"),sticky_notes:O.array(Cne).min(1).max(20).describe("Array of sticky notes to create (max 20)"),parent_id:O.string().optional().describe("Parent frame ID to place all stickies inside. When set, each note's x/y is interpreted relative to the frame's top-left corner, not the board center")},{destructiveHint:!1,openWorldHint:!0},async({board_id:a,sticky_notes:i,parent_id:r})=>{let n=await t.getBoard(a),o=[];for(let s of i){let p=await n.createStickyNoteItem(SN({content:s.content,shape:s.shape,color:s.color,x:s.x,y:s.y,parentId:r}));o.push({id:p.id??"",content:s.content,color:s.color})}return ze({count:o.length,items:o})})}var Ane=O.string().regex(/^#[0-9a-fA-F]{6}$/,"stroke_color must be a 6-digit hex color, e.g. #000000").default("#000000").describe("Line color as 6-digit hex (e.g., #000000)");function YU(e,t){e.tool("miro_create_connector","Create a connector (line) between two items on a Miro board. Use this to show relationships, flow direction, or dependencies between sticky notes, shapes, cards, or other items. Frames are not supported as connector endpoints. Returns the connector ID.",{board_id:O.string().describe("The board ID"),start_item_id:O.string().describe("ID of the item where the connector starts (frames are not supported)"),end_item_id:O.string().describe("ID of the item where the connector ends (frames are not supported)"),caption:O.string().optional().describe("Text label on the connector"),stroke_color:Ane,stroke_style:O.enum(["normal","dashed","dotted"]).default("normal").describe("Line style")},{destructiveHint:!1,openWorldHint:!0},async({board_id:a,start_item_id:i,end_item_id:r,caption:n,stroke_color:o,stroke_style:s})=>{let{body:p}=await t.createConnector(a,{startItem:{id:i},endItem:{id:r},...n?{captions:[{content:n,position:"50%"}]}:{},style:{strokeColor:o,strokeStyle:s}});return ze({id:p.id,type:p.type,startItem:i,endItem:r,caption:n??null})})}function eq(e,t){e.tool("miro_create_frame","Create a frame on a Miro board. Use this to define layout zones that group child items (sticky notes, shapes). Use the returned frame ID as parent_id when creating sticky notes inside it.",{board_id:O.string().describe("The board ID"),title:O.string().describe("Frame title"),x:O.number().default(0).describe("X position on the board"),y:O.number().default(0).describe("Y position on the board"),width:O.number().default(800).describe("Frame width in pixels"),height:O.number().default(600).describe("Frame height in pixels")},{destructiveHint:!1,openWorldHint:!0},async({board_id:a,title:i,x:r,y:n,width:o,height:s})=>{let d=await(await t.getBoard(a)).createFrameItem({data:{title:i,format:"custom"},position:{x:r,y:n},geometry:{width:o,height:s}});return ze({id:d.id,type:d.type,title:i,position:{x:r,y:n},geometry:{width:o,height:s}})}),e.tool("miro_get_frame_items","List all items inside a specific frame. Use this to see what's grouped within a frame. Returns item IDs, types, and positions relative to the frame.",{board_id:O.string().describe("The board ID"),frame_id:O.string().describe("The frame item ID"),limit:O.number().min(1).max(1e3).default(50).describe("Max items to return (the SDK auto-paginates beyond a single API page)")},{readOnlyHint:!0,openWorldHint:!0},async({board_id:a,frame_id:i,limit:r})=>{let o=await(await t.getBoard(a)).getItem(i);if(!("getAllItems"in o)||typeof o.getAllItems!="function")return I9(`Item ${i} is not a frame. Only frames contain child items.`);let s=[];for await(let p of o.getAllItems())if(s.push({id:p.id,type:p.type,...p.position?{position:p.position}:{}}),s.length>=r)break;return ze(s)})}var jne=195,tq=60,aq="...";function One(e,t){let a=[];for(let i=0;i{let n=await t.getBoard(a),o=[];for await(let d of n.getAllItems({type:"sticky_note"})){let c=d.position,u=d.data;if(c?.x!=null&&c?.y!=null&&o.push({id:d.id??"",content:u?.content??"",x:c.x,y:c.y}),o.length>=r)break}let s=One(o,i),p=d=>({id:d.id,content:Mne(d.content),x:d.x,y:d.y});return ze({total_items:o.length,overlap_count:s.length,overlaps:s.map(d=>({a:p(d.a),b:p(d.b),dx:Math.round(d.dx),dy:Math.round(d.dy)}))})})}var Dne=["red","light_green","cyan","yellow","magenta","green","blue","gray","violet","dark_green","dark_blue","black"];function iq(e,t){e.tool("miro_create_tag","Create a tag on a Miro board for categorizing items. Use this before miro_attach_tag. Tags are board-scoped \u2014 create once, attach to many items. Returns the tag ID.",{board_id:O.string().describe("The board ID"),title:O.string().describe("Tag title text"),color:O.enum(Dne).default("blue").describe("Tag color")},{destructiveHint:!1,openWorldHint:!0},async({board_id:a,title:i,color:r})=>{let o=await(await t.getBoard(a)).createTag({title:i,fillColor:r});return ze({id:o.id,title:o.title,color:r})}),e.tool("miro_attach_tag","Attach an existing tag to a board item. Use miro_create_tag first to create the tag, then attach it to sticky notes, cards, or other taggable items. Idempotent \u2014 attaching the same tag twice is safe.",{board_id:O.string().describe("The board ID"),item_id:O.string().describe("The item ID to tag"),tag_id:O.string().describe("The tag ID to attach")},{idempotentHint:!0,destructiveHint:!1,openWorldHint:!0},async({board_id:a,item_id:i,tag_id:r})=>{let o=await(await t.getBoard(a)).getItem(i);return!o||!("attachTag"in o)||typeof o.attachTag!="function"?I9(`Item ${i} does not support tags. Only sticky notes, cards, and similar items can have tags attached.`):(await o.attachTag(r),ze({item_id:i,tag_id:r,status:"attached"}))})}var wr=new L4({name:"miro-mcp",version:"0.1.0"},{instructions:"Miro board management for visual collaboration. Use these tools to create and manage boards, sticky notes, frames, tags, and connectors for EventStorming, brainstorming, and diagramming."}),{api:Pp,lowLevel:TN}=GU();WU(wr,Pp,TN);QU(wr,Pp,TN);eq(wr,Pp);iq(wr,Pp);YU(wr,TN);XU(wr,Pp);rq(wr,Pp);var Une=new F4;await wr.connect(Une); +`)}function GU(){let e=process.env.MIRO_API_TOKEN;return e||(ZU("MIRO_API_TOKEN environment variable is required. Get one from https://miro.com/app/settings/user-profile/apps"),process.exit(1)),{api:new N9.MiroApi(e),lowLevel:new N9.MiroLowlevelApi(e)}}function ze(e){return{content:[{type:"text",text:JSON.stringify(e)}]}}function I9(e){return{content:[{type:"text",text:e}],isError:!0}}var zne=["private","view","comment","edit"],JU=O.enum(zne).optional().describe("Board link access level. 'view' makes the board public (anyone with link can view)"),Rne={private:"no_access",view:"viewer",comment:"commenter",edit:"editor"};function HU(e){return{sharingPolicy:{access:e,inviteToAccountAndBoardLinkAccess:Rne[e]}}}function WU(e,t,a){e.tool("miro_create_board","Create a new Miro board. Use this to start a new visual collaboration space. Returns the board ID and view URL.",{name:O.string().describe("Board name"),description:O.string().optional().describe("Board description"),sharing_access:JU},{destructiveHint:!1,openWorldHint:!0},async({name:i,description:r,sharing_access:n})=>{let o={name:i,description:r??""};n&&(o.policy=HU(n));let{body:s}=await a.createBoard(o);return ze({id:s.id,name:s.name,viewLink:s.viewLink})}),e.tool("miro_list_boards","List accessible Miro boards. Use this to find existing boards or verify a board exists. Returns board IDs, names, and view URLs.",{query:O.string().optional().describe("Search query to filter boards by name"),limit:O.number().min(1).max(50).default(10).describe("Max boards to return")},{readOnlyHint:!0,openWorldHint:!0},async({query:i,limit:r})=>{let n=[];for await(let o of t.getAllBoards({query:i}))if(n.push({id:o.id??"",name:o.name??"",viewLink:o.viewLink??""}),n.length>=r)break;return ze(n)}),e.tool("miro_get_board","Get details for a specific Miro board. Use this to check board metadata, sharing status, or last modified time. Returns name, description, view URL, and timestamps.",{board_id:O.string().describe("The board ID")},{readOnlyHint:!0,openWorldHint:!0},async({board_id:i})=>{let r=await t.getBoard(i);return ze({id:r.id,name:r.name,description:r.description,viewLink:r.viewLink,createdAt:r.createdAt,modifiedAt:r.modifiedAt})}),e.tool("miro_update_board","Update a Miro board's name, description, or sharing policy. Use this to rename boards, change descriptions, or adjust access levels. Returns the updated board details.",{board_id:O.string().describe("The board ID"),name:O.string().optional().describe("New board name"),description:O.string().optional().describe("New board description"),sharing_access:JU},{idempotentHint:!0,destructiveHint:!1,openWorldHint:!0},async({board_id:i,name:r,description:n,sharing_access:o})=>{let s={};r!==void 0&&(s.name=r),n!==void 0&&(s.description=n),o&&(s.policy=HU(o));let{body:p}=await a.updateBoard(i,s);return ze({id:p.id,name:p.name,viewLink:p.viewLink,sharingPolicy:p.sharingPolicy})}),e.tool("miro_delete_board","Permanently delete a Miro board. On paid plans, boards go to Trash (restorable via UI within 90 days). Use miro_list_boards first to verify the board ID.",{board_id:O.string().describe("The board ID to delete")},{destructiveHint:!0,openWorldHint:!0},async({board_id:i})=>(await a.deleteBoard(i),ze({id:i,status:"deleted"})))}var E9=["gray","light_yellow","yellow","orange","light_green","green","dark_green","cyan","light_pink","pink","violet","red","light_blue","blue","dark_blue","black"],xN=["square","rectangle"],KU=["sticky_note","shape","frame","text","card","connector","image","document","app_card"];function SN(e){return{data:{content:e.content,shape:e.shape},style:{fillColor:e.color,textAlign:"center",textAlignVertical:"middle"},position:{x:e.x,y:e.y},...e.parentId?{parent:{id:e.parentId}}:{}}}function QU(e,t,a){e.tool("miro_create_sticky_note","Create a sticky note on a Miro board. Use this for individual notes \u2014 for batch creation (up to 20), use miro_bulk_create_sticky_notes instead. Returns the item ID.",{board_id:O.string().describe("The board ID"),content:O.string().describe("Text content for the sticky note"),color:O.enum(E9).default("light_yellow").describe("Fill color"),x:O.number().default(0).describe("X position. Board-center-relative (0 = center) by default; relative to the parent frame's top-left corner when parent_id is set"),y:O.number().default(0).describe("Y position. Board-center-relative (0 = center) by default; relative to the parent frame's top-left corner when parent_id is set"),shape:O.enum(xN).default("square").describe("Sticky note shape"),parent_id:O.string().optional().describe("Parent frame ID to place the sticky inside. When set, x/y are interpreted relative to the frame's top-left corner, not the board center")},{destructiveHint:!1,openWorldHint:!0},async({board_id:i,content:r,color:n,x:o,y:s,shape:p,parent_id:d})=>{let u=await(await t.getBoard(i)).createStickyNoteItem(SN({content:r,shape:p,color:n,x:o,y:s,parentId:d}));return ze({id:u.id,type:u.type,content:r,color:n,position:{x:o,y:s}})}),e.tool("miro_update_sticky_note","Update a sticky note's content, color, or position. Use this to modify existing notes without recreating them. Only specified fields are changed \u2014 omitted fields keep their current values.",{board_id:O.string().describe("The board ID"),item_id:O.string().describe("The sticky note item ID"),content:O.string().optional().describe("New text content"),color:O.enum(E9).optional().describe("New fill color"),x:O.number().optional().describe("New X position"),y:O.number().optional().describe("New Y position")},{idempotentHint:!0,destructiveHint:!1,openWorldHint:!0},async({board_id:i,item_id:r,content:n,color:o,x:s,y:p})=>{let d={};return n!==void 0&&(d.data={content:n}),o!==void 0&&(d.style={fillColor:o}),(s!==void 0||p!==void 0)&&(d.position={...s!==void 0?{x:s}:{},...p!==void 0?{y:p}:{}}),await a.updateStickyNoteItem(i,r,d),ze({id:r,status:"updated"})}),e.tool("miro_list_board_items","List items on a Miro board. Use this to discover what's on a board before modifying it. Can filter by type (sticky_note, shape, frame, text, connector, etc.). Connectors are retrieved via Miro's separate connectors endpoint \u2014 pass type 'connector' to list them (each result carries startItemId/endItemId, the string IDs of the connected items). Returns item IDs, types, data, and positions.",{board_id:O.string().describe("The board ID"),type:O.enum(KU).optional().describe("Filter by item type"),limit:O.number().min(1).max(1e3).default(20).describe("Max items to return (the SDK auto-paginates beyond a single API page)")},{readOnlyHint:!0,openWorldHint:!0},async({board_id:i,type:r,limit:n})=>{let o=await t.getBoard(i),s=[];if(r==="connector"){for await(let p of o.getAllConnectors())if(s.push({id:p.id,type:p.type??"connector",...p.startItem?.id?{startItemId:p.startItem.id}:{},...p.endItem?.id?{endItemId:p.endItem.id}:{}}),s.length>=n)break}else for await(let p of o.getAllItems({type:r}))if(s.push({id:p.id,type:p.type,...p.data&&typeof p.data=="object"?{data:p.data}:{},...p.position?{position:p.position}:{}}),s.length>=n)break;return ze(s)}),e.tool("miro_delete_item","Delete an item from a Miro board. Use miro_list_board_items first to verify the item ID. Pass the item's type (as returned by miro_list_board_items) so connectors route to Miro's separate connector endpoint; any other type \u2014 or omitting it \u2014 deletes via the item endpoint.",{board_id:O.string().describe("The board ID"),item_id:O.string().describe("The item ID to delete"),type:O.enum(KU).optional().describe("Item type, as returned by miro_list_board_items. 'connector' routes to Miro's separate connector-delete endpoint; every other type (sticky_note, shape, frame, \u2026) uses the item-delete endpoint")},{destructiveHint:!0,openWorldHint:!0},async({board_id:i,item_id:r,type:n})=>(n==="connector"?await a.deleteConnector(i,r):await a.deleteItem(i,r),ze({id:r,status:"deleted"})))}var Cne=O.object({content:O.string().describe("Text content"),color:O.enum(E9).default("light_yellow").describe("Fill color name"),shape:O.enum(xN).default("square").describe("Sticky note shape. Use rectangle for Read Models, External Systems, Pivotal Events"),x:O.number().describe("X position. Board-center-relative (0 = center) by default; relative to the parent frame's top-left corner when parent_id is set"),y:O.number().describe("Y position. Board-center-relative (0 = center) by default; relative to the parent frame's top-left corner when parent_id is set")});function XU(e,t){e.tool("miro_bulk_create_sticky_notes","Create multiple sticky notes at once (max 20 per call). Use this instead of miro_create_sticky_note when placing many notes \u2014 faster and rate-limit aware. Notes are created sequentially with no rollback: if a later note fails, the earlier ones remain on the board. Returns the count and IDs of created items.",{board_id:O.string().describe("The board ID"),sticky_notes:O.array(Cne).min(1).max(20).describe("Array of sticky notes to create (max 20)"),parent_id:O.string().optional().describe("Parent frame ID to place all stickies inside. When set, each note's x/y is interpreted relative to the frame's top-left corner, not the board center")},{destructiveHint:!1,openWorldHint:!0},async({board_id:a,sticky_notes:i,parent_id:r})=>{let n=await t.getBoard(a),o=[];for(let s of i){let p=await n.createStickyNoteItem(SN({content:s.content,shape:s.shape,color:s.color,x:s.x,y:s.y,parentId:r}));o.push({id:p.id??"",content:s.content,color:s.color})}return ze({count:o.length,items:o})})}var Ane=O.string().regex(/^#[0-9a-fA-F]{6}$/,"stroke_color must be a 6-digit hex color, e.g. #000000").default("#000000").describe("Line color as 6-digit hex (e.g., #000000)");function YU(e,t){e.tool("miro_create_connector","Create a connector (line) between two items on a Miro board. Use this to show relationships, flow direction, or dependencies between sticky notes, shapes, cards, or other items. Frames are not supported as connector endpoints. Returns the connector ID.",{board_id:O.string().describe("The board ID"),start_item_id:O.string().describe("ID of the item where the connector starts (frames are not supported)"),end_item_id:O.string().describe("ID of the item where the connector ends (frames are not supported)"),caption:O.string().optional().describe("Text label on the connector"),stroke_color:Ane,stroke_style:O.enum(["normal","dashed","dotted"]).default("normal").describe("Line style")},{destructiveHint:!1,openWorldHint:!0},async({board_id:a,start_item_id:i,end_item_id:r,caption:n,stroke_color:o,stroke_style:s})=>{let{body:p}=await t.createConnector(a,{startItem:{id:i},endItem:{id:r},...n?{captions:[{content:n,position:"50%"}]}:{},style:{strokeColor:o,strokeStyle:s}});return ze({id:p.id,type:p.type,startItem:i,endItem:r,caption:n??null})})}function eq(e,t){e.tool("miro_create_frame","Create a frame on a Miro board. Use this to define layout zones that group child items (sticky notes, shapes). Use the returned frame ID as parent_id when creating sticky notes inside it.",{board_id:O.string().describe("The board ID"),title:O.string().describe("Frame title"),x:O.number().default(0).describe("X position on the board"),y:O.number().default(0).describe("Y position on the board"),width:O.number().default(800).describe("Frame width in pixels"),height:O.number().default(600).describe("Frame height in pixels")},{destructiveHint:!1,openWorldHint:!0},async({board_id:a,title:i,x:r,y:n,width:o,height:s})=>{let d=await(await t.getBoard(a)).createFrameItem({data:{title:i,format:"custom"},position:{x:r,y:n},geometry:{width:o,height:s}});return ze({id:d.id,type:d.type,title:i,position:{x:r,y:n},geometry:{width:o,height:s}})}),e.tool("miro_get_frame_items","List all items inside a specific frame. Use this to see what's grouped within a frame. Returns item IDs, types, and positions relative to the frame.",{board_id:O.string().describe("The board ID"),frame_id:O.string().describe("The frame item ID"),limit:O.number().min(1).max(1e3).default(50).describe("Max items to return (the SDK auto-paginates beyond a single API page)")},{readOnlyHint:!0,openWorldHint:!0},async({board_id:a,frame_id:i,limit:r})=>{let o=await(await t.getBoard(a)).getItem(i);if(!("getAllItems"in o)||typeof o.getAllItems!="function")return I9(`Item ${i} is not a frame. Only frames contain child items.`);let s=[];for await(let p of o.getAllItems())if(s.push({id:p.id,type:p.type,...p.position?{position:p.position}:{}}),s.length>=r)break;return ze(s)})}var jne=195,tq=60,aq="...";function One(e,t){let a=[];for(let i=0;i{let n=await t.getBoard(a),o=[];for await(let d of n.getAllItems({type:"sticky_note"})){let c=d.position,u=d.data;if(c?.x!=null&&c?.y!=null&&o.push({id:d.id??"",content:u?.content??"",x:c.x,y:c.y}),o.length>=r)break}let s=One(o,i),p=d=>({id:d.id,content:Mne(d.content),x:d.x,y:d.y});return ze({total_items:o.length,overlap_count:s.length,overlaps:s.map(d=>({a:p(d.a),b:p(d.b),dx:Math.round(d.dx),dy:Math.round(d.dy)}))})})}var Dne=["red","light_green","cyan","yellow","magenta","green","blue","gray","violet","dark_green","dark_blue","black"];function iq(e,t){e.tool("miro_create_tag","Create a tag on a Miro board for categorizing items. Use this before miro_attach_tag. Tags are board-scoped \u2014 create once, attach to many items. Returns the tag ID.",{board_id:O.string().describe("The board ID"),title:O.string().describe("Tag title text"),color:O.enum(Dne).default("blue").describe("Tag color")},{destructiveHint:!1,openWorldHint:!0},async({board_id:a,title:i,color:r})=>{let o=await(await t.getBoard(a)).createTag({title:i,fillColor:r});return ze({id:o.id,title:o.title,color:r})}),e.tool("miro_attach_tag","Attach an existing tag to a board item. Use miro_create_tag first to create the tag, then attach it to sticky notes, cards, or other taggable items. Idempotent \u2014 attaching the same tag twice is safe.",{board_id:O.string().describe("The board ID"),item_id:O.string().describe("The item ID to tag"),tag_id:O.string().describe("The tag ID to attach")},{idempotentHint:!0,destructiveHint:!1,openWorldHint:!0},async({board_id:a,item_id:i,tag_id:r})=>{let o=await(await t.getBoard(a)).getItem(i);return!o||!("attachTag"in o)||typeof o.attachTag!="function"?I9(`Item ${i} does not support tags. Only sticky notes, cards, and similar items can have tags attached.`):(await o.attachTag(r),ze({item_id:i,tag_id:r,status:"attached"}))})}var wr=new L4({name:"miro-mcp",version:"0.1.2"},{instructions:"Miro board management for visual collaboration. Use these tools to create and manage boards, sticky notes, frames, tags, and connectors for EventStorming, brainstorming, and diagramming."}),{api:Pp,lowLevel:TN}=GU();WU(wr,Pp,TN);QU(wr,Pp,TN);eq(wr,Pp);iq(wr,Pp);YU(wr,TN);XU(wr,Pp);rq(wr,Pp);var Une=new F4;await wr.connect(Une); diff --git a/plugins/miro/package-lock.json b/plugins/miro/package-lock.json index 30c71eb87..519e15fed 100644 --- a/plugins/miro/package-lock.json +++ b/plugins/miro/package-lock.json @@ -1,12 +1,12 @@ { "name": "@melodic-software/miro-mcp", - "version": "0.1.0", + "version": "0.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@melodic-software/miro-mcp", - "version": "0.1.0", + "version": "0.1.2", "dependencies": { "@mirohq/miro-api": "^2.2.4", "@modelcontextprotocol/sdk": "^1.29.0", diff --git a/plugins/miro/package.json b/plugins/miro/package.json index a06e4e4f4..89a33cf02 100644 --- a/plugins/miro/package.json +++ b/plugins/miro/package.json @@ -1,6 +1,6 @@ { "name": "@melodic-software/miro-mcp", - "version": "0.1.0", + "version": "0.1.2", "description": "General-purpose Miro MCP server. Creates and manages sticky notes, shapes, frames, connectors, and tags on Miro boards via the Model Context Protocol.", "type": "module", "private": true, diff --git a/plugins/miro/src/index.ts b/plugins/miro/src/index.ts index 2f9b5a5af..3a1f43684 100644 --- a/plugins/miro/src/index.ts +++ b/plugins/miro/src/index.ts @@ -13,7 +13,7 @@ import { registerStickyNoteTools } from "./tools/sticky-notes.js"; import { registerTagTools } from "./tools/tags.js"; const server = new McpServer( - { name: "miro-mcp", version: "0.1.0" }, + { name: "miro-mcp", version: "0.1.2" }, { instructions: "Miro board management for visual collaboration. " + diff --git a/plugins/playwright/.claude-plugin/plugin.json b/plugins/playwright/.claude-plugin/plugin.json index f03a5e265..56fb07f27 100644 --- a/plugins/playwright/.claude-plugin/plugin.json +++ b/plugins/playwright/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "playwright", - "version": "0.2.0", + "version": "0.2.1", "description": "Live E2E browser automation via Microsoft's @playwright/cli — named sessions, accessibility-ref snapshots, click/fill by ref, screenshots, console and network capture, mocking, tracing, video, and auth state, with artifacts written to disk so only paths enter context, plus a vendored upstream baseline and maintainer drift-check update flow.", "author": { "name": "Melodic Software", diff --git a/plugins/playwright/CHANGELOG.md b/plugins/playwright/CHANGELOG.md new file mode 100644 index 000000000..a14b18294 --- /dev/null +++ b/plugins/playwright/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +All notable changes to the `playwright` plugin are documented here. Format follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. + +## [0.2.1] + +### Added + +- This changelog (fleet conformance wave: every versioned plugin ships a + Keep-a-Changelog file). + +## [0.2.0] + +First versioned release covered by this changelog; see the git history of +`plugins/playwright/` for earlier changes. diff --git a/plugins/skill-quality/.claude-plugin/plugin.json b/plugins/skill-quality/.claude-plugin/plugin.json index 1120eb094..708219975 100644 --- a/plugins/skill-quality/.claude-plugin/plugin.json +++ b/plugins/skill-quality/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "skill-quality", - "version": "0.4.0", + "version": "0.4.1", "description": "Skill-authoring QA tooling: a static contract checker that runs seventeen deterministic checks over a Claude Code skill (frontmatter, listing-budget cap, trigger-keyword preservation, line caps, broken internal refs, markdownlint, gotchas surface, evals presence) and a bundled evals.json schema for validation. Runs against any repo's skills directory via the convention-resolution ladder — no baked layout.", "author": { "name": "Melodic Software", diff --git a/plugins/skill-quality/CHANGELOG.md b/plugins/skill-quality/CHANGELOG.md index 5c25983cf..9bb054ce6 100644 --- a/plugins/skill-quality/CHANGELOG.md +++ b/plugins/skill-quality/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to the `skill-quality` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.4.1] + +### Changed + +- **Freshness rider on the setup skill's `pluginConfigs` claim** (fleet + conformance wave). The claim is re-verified, dated, and pinned to the + release that introduced the behavior (≥ 2.1.207). + ## [0.4.0] ### Changed diff --git a/plugins/skill-quality/skills/setup/SKILL.md b/plugins/skill-quality/skills/setup/SKILL.md index 3cfa1cc34..6956740dd 100644 --- a/plugins/skill-quality/skills/setup/SKILL.md +++ b/plugins/skill-quality/skills/setup/SKILL.md @@ -11,9 +11,10 @@ disable-model-invocation: true Resolve and verify the skills root without editing Claude Code settings. `skills_root` is a personal `userConfig` option. Claude Code prompts for it when the plugin is enabled, stores non-sensitive options in user settings, and ignores project/local `pluginConfigs` entries on -current releases. +current releases (≥ 2.1.207). -Official contract: . +Official contract (verified 2026-07-18): +. ## Task diff --git a/plugins/songwriting/.claude-plugin/plugin.json b/plugins/songwriting/.claude-plugin/plugin.json index e4020dfe7..56c5cb367 100644 --- a/plugins/songwriting/.claude-plugin/plugin.json +++ b/plugins/songwriting/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "songwriting", - "version": "0.4.0", + "version": "0.4.1", "description": "Songwriting craft companion — eight concern-scoped lyric-craft skills (workflow router, rhyme, object-writing, meter-prosody, song-form, co-write, diagnosis, daily-practice) applying Pat Pattison's methods, plus Suno v5.5 prompt engineering (style prompts, tagged lyrics, genre templates, troubleshooting).", "author": { "name": "Melodic Software", diff --git a/plugins/songwriting/CHANGELOG.md b/plugins/songwriting/CHANGELOG.md index 3f5e51efa..a62e98136 100644 --- a/plugins/songwriting/CHANGELOG.md +++ b/plugins/songwriting/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to the `songwriting` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.4.1] + +### Changed + +- **Suno fact tables re-verified 2026-07-18 and corrected** (fleet conformance + wave: freshness riders). Lyrics limit corrected to a 5,000-character hard cap + on v4.5/v5/v5.5 (~3,000 stays as the quality sweet spot; 3,000 was the + v4-era cap — the May consensus position flipped), title to ~100 characters, + and the tier matrix's Suno Studio row to Premier-exclusive. Confirmed tables + (Voices, release dates) carry dated riders with official links; unverifiable + rows keep their hedges undated; conflicting upload-length figures are + cross-flagged instead of resolved. All `help.suno.com` source links fixed to + the working `/en/articles/` form. Character limits remain third-party-tester + sourced — Suno publishes no official field limits — and the riders say so. + ## [0.4.0] ### Added diff --git a/plugins/songwriting/skills/suno/SKILL.md b/plugins/songwriting/skills/suno/SKILL.md index 49fb99b5e..be4446dac 100644 --- a/plugins/songwriting/skills/suno/SKILL.md +++ b/plugins/songwriting/skills/suno/SKILL.md @@ -16,7 +16,7 @@ Suno is an AI music platform; the prompt is the instrument. This skill helps the The skill is a **prompt-craft assistant**, not an audio generator. Suno produces audio; this skill makes sure the prompt going INTO Suno is sharp, on-format, free of common pitfalls. -Suno v5.5 (released March 26, 2026) preserved v5's prompt syntax but improved adherence to nuanced descriptors and added three personalization layers (Voices, Custom Models, My Taste). Everything below targets v5.5 unless noted; legacy v4 (~200-char style prompt) is out of scope. +Suno v5.5 (released March 26, 2026; verified 2026-07-18 against ) preserved v5's prompt syntax but improved adherence to nuanced descriptors and added three personalization layers (Voices, Custom Models, My Taste). Everything below targets v5.5 unless noted; legacy v4 (~200-char style prompt) is out of scope. ## Action Router @@ -61,15 +61,17 @@ Order matters — early tags carry more weight. Front-load genre. Sweet spot: **5–8 distinct tags**. Fewer than 4 → generic. More than 10 → conflicting signals. -### Character budgets (silent truncation past these) +### Character budgets | Field | Limit | Notes | |-------|-------|-------| | Style prompt (v5/v5.5) | **~1,000 chars** | Up from ~200 in v4. Truncates silently. Front-load critical content. | -| Lyrics | **~3,000 chars** | ~40-60 lines / 200-300 words. Past 3K → Suno rushes, skips sections, or cuts output short. | -| Title | **~80 chars** | No effect on musical output | +| Lyrics | **5,000-char hard cap** (v4.5/v5/v5.5) | 3,000 was the v4-era cap. Quality sweet spot stays **~3,000** (~40-60 lines / 200-300 words) — past that Suno rushes, skips sections, or cuts output short. | +| Title | **~100 chars** | Up from ~80 in v4. No effect on musical output | | Exclude field | Free-text in Advanced Options (Custom mode) | Same vocabulary as inline negatives | +**Verified 2026-07-18** — no official Suno page states field limits; figures are third-party tester consensus ([hookgenius character limits](https://hookgenius.app/learn/suno-character-limits/), [aimusicapi cheat sheet, 2026-07-03](https://aimusicapi.ai/en/blog/suno-ai-prompt-character-limits)). + ### The lyrics field is a SECOND style channel Power users treat the lyrics field as more than words to sing. **Per-section style overrides** in lyrics control instrumentation, dynamics, and production section-by-section — something the global style prompt can't do. @@ -169,7 +171,7 @@ Simple mode = unified prompt + auto-lyrics. **Custom mode** = separate fields (s **`prompt `** — build both style and lyrics. Read [context/style.md](context/style.md) for layer detail, [context/lyrics.md](context/lyrics.md) for tag taxonomy. Produce two fenced blocks (style, lyrics), each labeled, within budget. End with 2-3 tweak suggestions. -**`lyrics `** — load [context/lyrics.md](context/lyrics.md). Output a tagged lyrics block with section structure (`[Verse 1]`, `[Chorus]`, etc.). Include 1-2 performance cues (parentheticals, ellipses) where they fit naturally. Stay ≤3000 chars unless user asked for max. +**`lyrics `** — load [context/lyrics.md](context/lyrics.md). Output a tagged lyrics block with section structure (`[Verse 1]`, `[Chorus]`, etc.). Include 1-2 performance cues (parentheticals, ellipses) where they fit naturally. Stay ≤3,000 chars — the quality sweet spot; the hard cap is 5,000 — unless user asked for max. **`style `** — load [context/style.md](context/style.md). Walk the 6-layer formula. Output one fenced style-prompt block + character count. Suggest 2-3 alternatives (different mood, different production). @@ -210,9 +212,9 @@ If `` is ambiguous (could be template OR research OR suggest), ask user wh ## Confidence flags (be honest about source quality) -- **HIGH confidence**: claims confirmed by Suno's official help center (`help.suno.com`) or `suno.com/blog`. The 6-layer formula, character budgets, structural and vocal tag names, Custom mode requirements, Voices/Custom Models/My Taste mechanics, Creative Slider behavior — all HIGH. +- **HIGH confidence**: claims confirmed by Suno's official help center (`help.suno.com`) or `suno.com/blog`. The 6-layer formula, structural and vocal tag names, Custom mode requirements, Voices/Custom Models/My Taste mechanics, Creative Slider behavior — all HIGH. Character budgets are NOT officially published — third-party tester consensus only (MEDIUM-HIGH). - **MEDIUM confidence**: community-validated techniques across multiple guides + Reddit consensus, but no official Suno doc. Capitalization weighting magnitude, vowel-stretching letter counts, hyphenation staccato, `(x2)` failure mode — all MEDIUM. Effects are real (multi-source agreement); exact magnitudes are folk wisdom. -- **Resolved 2026-05-10**: lyrics char limit is **~3,000** (UI field). The earlier "5,000" figure was a single-source outlier from one hookgenius v5.5 page; cross-checking HookGenius char-limits, roo.beehiiv 2026, musicsmith.ai, and 2025 community guides yields consensus on 3,000. The 5,000 number may reflect API/programmatic context (chirp-crow), not the UI. +- **Re-verified 2026-07-18 — position flipped since the 2026-05-10 pass**: current third-party testers agree the lyrics **hard cap is 5,000 chars on v4.5/v5/v5.5**; 3,000 was the v4-and-earlier cap. The earlier "3,000 consensus" conflated the old cap with the quality threshold. **~3,000 remains the practical budget** — past it Suno rushes, skips sections, or shortens output. Sources: [hookgenius character limits](https://hookgenius.app/learn/suno-character-limits/), [aimusicapi cheat sheet, 2026-07-03](https://aimusicapi.ai/en/blog/suno-ai-prompt-character-limits). No official Suno page states field limits. When generating prompts, default to HIGH-confidence techniques. Surface MEDIUM-confidence tricks as opt-in suggestions, not commands. diff --git a/plugins/songwriting/skills/suno/context/lyrics.md b/plugins/songwriting/skills/suno/context/lyrics.md index 0fbcc204b..7b427779b 100644 --- a/plugins/songwriting/skills/suno/context/lyrics.md +++ b/plugins/songwriting/skills/suno/context/lyrics.md @@ -182,7 +182,7 @@ Take me home (ad-lib: home, home) ### Confidence note -Parameterized syntax is documented across multiple community guides (hookgenius, blakecrosley, jackrighteous) and confirmed by Suno's own "Song Editor" article (`help.suno.com/articles/6141505`). The full **per-section instrumentation control** as a primary technique (vs just vocal/mood modifiers) is community-validated through extensive empirical testing — HIGH confidence on syntax, HIGH-MEDIUM on the broader "treat lyrics as second style channel" framing. +Parameterized syntax is documented across multiple community guides (hookgenius, blakecrosley, jackrighteous) and confirmed by Suno's own "Song Editor" article (`help.suno.com/en/articles/6141505`). The full **per-section instrumentation control** as a primary technique (vs just vocal/mood modifiers) is community-validated through extensive empirical testing — HIGH confidence on syntax, HIGH-MEDIUM on the broader "treat lyrics as second style channel" framing. ## Performance shaping (community-validated, MEDIUM confidence) diff --git a/plugins/songwriting/skills/suno/context/studio.md b/plugins/songwriting/skills/suno/context/studio.md index 7038fc353..dde81b5a3 100644 --- a/plugins/songwriting/skills/suno/context/studio.md +++ b/plugins/songwriting/skills/suno/context/studio.md @@ -1,6 +1,6 @@ # Suno Studio (1.2) — full guide -Suno Studio is a **Generative Audio Workstation (GAW)** — multitrack DAW in the browser, with AI generation built into the timeline. **Premier tier** (verify against current pricing — tiers drift). +Suno Studio is a **Generative Audio Workstation (GAW)** — multitrack DAW in the browser, with AI generation built into the timeline. **Premier tier** — verified 2026-07-18 against (Pro has no Studio access; tiers drift, re-check before relying). Where to go AFTER initial generation when you want to: rearrange sections, comp across multiple takes, isolate/replace instruments, fix timing, strip reverb, export stems, or build a song from scratch using AI-generated parts on individual tracks. @@ -91,7 +91,7 @@ Studio's export menu has **3 scopes**: - An external instrumental for vocals to be generated over - Reference material for timing comparison -Upload limits (verify current — these drift): +Upload limits (unverified — conflicts with the 120-second Pro/Premier figure in the tier matrix elsewhere in this skill; re-verify before relying on either): | Tier | Max upload | |------|-----------| @@ -143,4 +143,4 @@ Upload limits (verify current — these drift): ## Sources -`help.suno.com/articles/7940161` (Introduction to Studio), `help.suno.com/articles/10625089` (Studio 1.2 release notes), `help.suno.com/articles/8128193` (Exporting from Studio), `help.suno.com/articles/6141505` (Song Editor). +`help.suno.com/en/articles/7940161` (Introduction to Studio), `help.suno.com/en/articles/10625089` (Studio 1.2 release notes), `help.suno.com/en/articles/8128193` (Exporting from Studio), `help.suno.com/en/articles/6141505` (Song Editor). diff --git a/plugins/songwriting/skills/suno/context/style.md b/plugins/songwriting/skills/suno/context/style.md index 2c3fc766d..dfc38827b 100644 --- a/plugins/songwriting/skills/suno/context/style.md +++ b/plugins/songwriting/skills/suno/context/style.md @@ -8,11 +8,11 @@ The style/genre prompt tells Suno **what kind of song to make**. v5/v5.5 expande |-------|-------|-------| | Style prompt (v5/v5.5) | **~1,000 chars** | Truncates silently — front-load critical content | | Style prompt (v4 legacy) | ~200 chars | Out of scope for this skill, noted for orientation | -| Lyrics | **~3,000 chars** | ~40-60 lines / 200-300 words. Past 3K → Suno rushes, skips sections, or cuts output short | -| Title | **~80 chars** | No effect on musical output | +| Lyrics | **5,000-char hard cap** (v4.5/v5/v5.5) | 3,000 was the v4-era cap. Quality sweet spot stays **~3,000** (~40-60 lines / 200-300 words) — past that Suno rushes, skips sections, or cuts output short | +| Title | **~100 chars** | Up from ~80 in v4. No effect on musical output | | Exclude (Custom mode Advanced Options) | Free-text box | Same vocabulary as inline negatives | -**Consensus verified 2026-05-10** across HookGenius, roo.beehiiv, musicsmith.ai — the earlier "5,000 lyrics" figure was a single-source outlier on one hookgenius v5.5 page (other hookgenius pages and the rest of the ecosystem agree on 3,000). The 5,000 figure may reflect API/programmatic context (chirp-crow model in ePhoneAI docs), not the UI lyrics field. +**Re-verified 2026-07-18 — position flipped since the 2026-05-10 pass.** Current third-party testers agree the lyrics hard cap is **5,000 chars on v4.5/v5/v5.5**; 3,000 was the v4-and-earlier cap, and the earlier "3,000 consensus" conflated that old cap with the quality threshold. ~3,000 remains the practical budget. No official Suno page states field limits — sources: [hookgenius character limits](https://hookgenius.app/learn/suno-character-limits/), [aimusicapi cheat sheet, 2026-07-03](https://aimusicapi.ai/en/blog/suno-ai-prompt-character-limits). ## The 6-layer formula diff --git a/plugins/songwriting/skills/suno/context/troubleshoot.md b/plugins/songwriting/skills/suno/context/troubleshoot.md index 99a6921a5..ada738b7d 100644 --- a/plugins/songwriting/skills/suno/context/troubleshoot.md +++ b/plugins/songwriting/skills/suno/context/troubleshoot.md @@ -38,7 +38,7 @@ Most Suno issues are **prompt-side preventable**. The model can't fix audio post ### "Lyrics are garbled / don't match what I wrote" -**Why:** Complex rhyme schemes, run-on sentences, missing punctuation, or lyrics field exceeding 3000 chars (silent truncation past that). +**Why:** Complex rhyme schemes, run-on sentences, missing punctuation, or lyrics exceeding ~3,000 chars — the quality threshold past which Suno rushes, skips, or garbles (hard cap is 5,000 on v4.5/v5/v5.5; verified 2026-07-18, third-party testers — no official limit published). **Fix:** @@ -46,7 +46,7 @@ Most Suno issues are **prompt-side preventable**. The model can't fix audio post 2. Add periods/commas where natural breath happens 3. Break long sentences across lines 4. Simplify rhyme scheme — internal multi-syllable rhymes confuse the model -5. Verify total char count — paste into a counter; if > 3000 chars, trim +5. Verify total char count — paste into a counter; if > 3,000 chars, trim (quality threshold; the 5,000 hard cap is not the problem) ### "BPM is off by 20+" diff --git a/plugins/songwriting/skills/suno/context/v55-features.md b/plugins/songwriting/skills/suno/context/v55-features.md index 18efec5d9..e9f57dab0 100644 --- a/plugins/songwriting/skills/suno/context/v55-features.md +++ b/plugins/songwriting/skills/suno/context/v55-features.md @@ -1,6 +1,6 @@ # What's new in v5.5 -**Released March 26, 2026.** v5.5 is a personalization-focused upgrade over v5 (Sep 2025). Core prompt syntax unchanged from v5; what changed is **adherence quality** plus three new identity layers. +**Released March 26, 2026** (verified 2026-07-18 against ; still the current model). v5.5 is a personalization-focused upgrade over v5 (Sep 2025). Core prompt syntax unchanged from v5; what changed is **adherence quality** plus three new identity layers. ## Three new layers @@ -19,6 +19,8 @@ | Privacy | Private by default, account-locked, non-shareable | | Activation | Select voice from dropdown in Custom mode + raise Audio Influence | +Clip length, 2-minute auto-selection, verification, and privacy rows verified 2026-07-18 against ; Pro/Premier tier per . + **Critical prompting rule when a Voice is active:** > **Drop gender/tone descriptors from the style prompt.** They conflict with the cloned voice and degrade output quality. @@ -38,6 +40,8 @@ For best clone quality: record 3 acapella clips across emotional range (gentle, | Result | Fine-tuned v5.5 reflecting YOUR production patterns, instrumentation, harmonic preferences | | Privacy | Private, non-shareable | +Partially verified 2026-07-18: max-3-models and Pro/Premier rows confirmed against ; min-tracks and training-time figures are not in official docs — treat as unverified. + **Key behavior:** style tags now operate **relative to your baseline**, not generic averages. If your catalog is heavy on lo-fi tape saturation, "polished mix" might still come out warmer than generic Suno polished mix. **Best practice:** **train separate models for separate sounds.** Don't mix genres in one training set — model averages across them and loses the per-style signal. @@ -60,7 +64,7 @@ For best clone quality: record 3 acapella clips across emotional range (gentle, | Feature | v4 | v5 (Sep 2025) | v5.5 (Mar 2026) | |---------|----|----|------| | Style prompt limit | ~200 chars | ~1,000 chars | ~1,000 chars | -| Lyrics limit | smaller (varied) | ~3,000 chars | ~3,000 chars | +| Lyrics limit | 3,000 chars | 5,000 chars (quality sweet spot ~3,000) | 5,000 chars (quality sweet spot ~3,000) | | Numeric BPM accuracy | ~70% | ~85% | ~90% | | Adherence to nuanced descriptors | low | medium | high | | Voices | — | — | ✓ | @@ -69,6 +73,8 @@ For best clone quality: record 3 acapella clips across emotional range (gentle, | Tag syntax | basic | full | full | | Multilingual | limited | ~50 langs | ~50 langs | +Char-limit rows verified 2026-07-18 against third-party testers ([hookgenius character limits](https://hookgenius.app/learn/suno-character-limits/), [aimusicapi cheat sheet, 2026-07-03](https://aimusicapi.ai/en/blog/suno-ai-prompt-character-limits)) — no official Suno page states field limits. Other rows unverified community figures. + **This skill targets v5.5 only.** Legacy v4 prompting (200-char era, fewer tags) is out of scope. ## Multilingual @@ -96,10 +102,14 @@ For best clone quality: record 3 acapella clips across emotional range (gentle, | Voices | — | ✓ | ✓ | | Custom Models (up to 3) | — | ✓ | ✓ | | Replace Section | — | ✓ | ✓ | -| Suno Studio 1.2 | partial | full | full | +| Suno Studio | — | — | ✓ | (Verify against current Suno pricing page — tier feature lists drift.) +- Studio row corrected 2026-07-18: **Premier-exclusive** per — Pro has no Studio access. +- Audio-upload row conflicts with the 8-minute Pro/Premier figure stated elsewhere in this skill — unresolved; re-verify before relying on either. +- Free-tier generation runs on **v4.5-all**, not v5.5 (third-party report: TechRadar). + ## Sources -Primary: `help.suno.com/articles/11362305` (v5.5 release), `help.suno.com/articles/11362369` (Voices), `help.suno.com/articles/11362497` (Custom Models), `suno.com/blog/v5-5`. +Primary: `help.suno.com/en/articles/11362305` (v5.5 release), `help.suno.com/en/articles/11362369` (Voices), `help.suno.com/en/articles/11362497` (Custom Models), `suno.com/blog/v5-5`. diff --git a/plugins/songwriting/skills/suno/context/voices.md b/plugins/songwriting/skills/suno/context/voices.md index 6f5395635..d6697b9d4 100644 --- a/plugins/songwriting/skills/suno/context/voices.md +++ b/plugins/songwriting/skills/suno/context/voices.md @@ -21,6 +21,8 @@ Voices clones YOUR singing identity. v5.5 only. **Pro / Premier tier.** 18+, geo | Room | Acoustically neutral; no echoey bathroom takes | | Model gate | Must select v5.5 in Custom mode | +Clip length, 2-minute auto-selection, and acapella-preferred rows verified 2026-07-18 against (which also confirms the 18+ and geographic restrictions above); mic/room/model-gate rows are community best practice. + ## Best-practice recording session **Single 90-120 second clip with intentional vocal variety** (per community empirical testing). Tonal and emotional variety within ONE clip produces stronger clones than multiple separate flat-dynamic clips. @@ -137,4 +139,4 @@ If any matter for your workflow, contact Suno support directly. ## Sources -`help.suno.com/articles/11362369` (Voices: Use Your Voice in Suno), `help.suno.com/articles/11362433` (Voices FAQ), `help.suno.com/categories/2327233-v-5-5-voices-custom-models-my-taste`. +`help.suno.com/en/articles/11362369` (Voices: Use Your Voice in Suno), `help.suno.com/en/articles/11362433` (Voices FAQ), `help.suno.com/en/categories/2327233-v-5-5-voices-custom-models-my-taste`. diff --git a/plugins/songwriting/skills/suno/context/workflow-recipes.md b/plugins/songwriting/skills/suno/context/workflow-recipes.md index b5122f4a6..1655de4bb 100644 --- a/plugins/songwriting/skills/suno/context/workflow-recipes.md +++ b/plugins/songwriting/skills/suno/context/workflow-recipes.md @@ -36,7 +36,7 @@ You have a generated song or uploaded demo. Now what? 8. (Optional) Stem export → external DAW for final mix 9. Export full mix -**Upload limits** (verify current): +**Upload limits** (unverified — conflicts with the 120-second Pro/Premier figure in the tier matrix elsewhere in this skill; re-verify before relying on either): | Tier | Max upload | |------|-----------| @@ -169,4 +169,4 @@ Note: covers chain. Cover-of-cover-of-cover is allowed; each version traces back ## Sources -`help.suno.com/articles/6141569` (Audio Uploads), `help.suno.com/articles/6141377` (Creative Sliders), `help.suno.com/articles/5663873` (Remix FAQs / Cover), `help.suno.com/articles/2409601` (Extend), `help.suno.com/articles/3271873` (Replace Section), `help.suno.com/articles/6141505` (Song Editor). +`help.suno.com/en/articles/6141569` (Audio Uploads), `help.suno.com/en/articles/6141377` (Creative Sliders), `help.suno.com/en/articles/5663873` (Remix FAQs / Cover), `help.suno.com/en/articles/2409601` (Extend), `help.suno.com/en/articles/3271873` (Replace Section), `help.suno.com/en/articles/6141505` (Song Editor). diff --git a/plugins/verification/.claude-plugin/plugin.json b/plugins/verification/.claude-plugin/plugin.json index 68acf919e..047bd5510 100644 --- a/plugins/verification/.claude-plugin/plugin.json +++ b/plugins/verification/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "verification", - "version": "0.2.0", + "version": "0.2.1", "description": "Outcome-verification stage: prove a change achieved its intended outcome (`/verification:confirm` — a mechanical build/test/lint prerequisite gate, then intent-match + evidence + verdict with the criterion auto-detected by change type), and verify measurable-improvement claims against a planning-time baseline (`/verification:measure`), never fabricating numbers.", "author": { "name": "Melodic Software", diff --git a/plugins/verification/CHANGELOG.md b/plugins/verification/CHANGELOG.md index b480f0e6a..317c46136 100644 --- a/plugins/verification/CHANGELOG.md +++ b/plugins/verification/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to the `verification` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.2.1] + +### Changed + +- **Freshness rider on the bundled `/verify` + `/run` availability claim** + (fleet conformance wave). The ≥ 2.1.145 floor is re-verified against the + official bundled-skills docs and now carries a verified-date + link. + ## [0.2.0] ### Changed diff --git a/plugins/verification/skills/confirm/SKILL.md b/plugins/verification/skills/confirm/SKILL.md index d9ff7c9b5..577f3d6d3 100644 --- a/plugins/verification/skills/confirm/SKILL.md +++ b/plugins/verification/skills/confirm/SKILL.md @@ -105,7 +105,7 @@ When `/testing:e2e` ran, persist an assertion-only evidence manifest (what was a For "run the live app and watch it behave" — beyond automated `/testing:e2e` — `/verification:confirm` delegates rather than reimplementing app-launch: - **Primary: `/testing:e2e`** (when the `testing` plugin is installed) — the reliable path for orchestrated apps (Aspire, docker-compose, tilt) via the project's orchestrator tooling + Playwright CLI. -- **Supplementary: Claude Code's bundled `/verify` + `/run`** — when a quick interactive run is enough and the orchestrated harness is overkill (available on CC ≥2.1.145). +- **Supplementary: Claude Code's bundled `/verify` + `/run`** — when a quick interactive run is enough and the orchestrated harness is overkill (requires Claude Code ≥2.1.145 — verified 2026-07-18 against [bundled skills](https://code.claude.com/docs/en/skills#bundled-skills)). - **Graceful fallback** — if the bundled skills cannot infer the project's launch (or the CC version lacks them), fall back to `/testing:e2e` when the `testing` plugin is installed, or a manual orchestrator launch otherwise. Never silently downgrade live-app verification to a static check — surface the gap. ## Edge cases From 8c1f58e61e8a01c5bb47275d8233f83acaefd6cd Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 18 Jul 2026 11:36:26 -0400 Subject: [PATCH 2/2] docs(songwriting): resolve upload-limit conflict against current pricing page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex review on #337: suno.com/pricing now states Upload Audio Free up to 8 minutes and Pro/Premier up to 30 minutes — both prior figures (60s/120s tier matrix and the 8-minute Pro cap) were stale. All three upload-limit tables corrected and dated; cross-flag hedges removed. Co-Authored-By: Claude Fable 5 --- plugins/songwriting/CHANGELOG.md | 5 +++-- plugins/songwriting/skills/suno/context/studio.md | 6 +++--- plugins/songwriting/skills/suno/context/v55-features.md | 4 ++-- plugins/songwriting/skills/suno/context/workflow-recipes.md | 6 +++--- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/plugins/songwriting/CHANGELOG.md b/plugins/songwriting/CHANGELOG.md index a62e98136..ec25dc0f2 100644 --- a/plugins/songwriting/CHANGELOG.md +++ b/plugins/songwriting/CHANGELOG.md @@ -13,8 +13,9 @@ All notable changes to the `songwriting` plugin are documented here. Format foll v4-era cap — the May consensus position flipped), title to ~100 characters, and the tier matrix's Suno Studio row to Premier-exclusive. Confirmed tables (Voices, release dates) carry dated riders with official links; unverifiable - rows keep their hedges undated; conflicting upload-length figures are - cross-flagged instead of resolved. All `help.suno.com` source links fixed to + rows keep their hedges undated; upload limits corrected to the current + pricing page (Free up to 8 min, Pro/Premier up to 30 min — the 60s/120s and + 8-min figures were both stale). All `help.suno.com` source links fixed to the working `/en/articles/` form. Character limits remain third-party-tester sourced — Suno publishes no official field limits — and the riders say so. diff --git a/plugins/songwriting/skills/suno/context/studio.md b/plugins/songwriting/skills/suno/context/studio.md index dde81b5a3..b55e900c4 100644 --- a/plugins/songwriting/skills/suno/context/studio.md +++ b/plugins/songwriting/skills/suno/context/studio.md @@ -91,12 +91,12 @@ Studio's export menu has **3 scopes**: - An external instrumental for vocals to be generated over - Reference material for timing comparison -Upload limits (unverified — conflicts with the 120-second Pro/Premier figure in the tier matrix elsewhere in this skill; re-verify before relying on either): +Upload limits (verified 2026-07-18 against ): | Tier | Max upload | |------|-----------| -| Basic | 60 seconds | -| Pro / Premier | 8 minutes | +| Free | up to 8 minutes | +| Pro / Premier | up to 30 minutes | ## What Studio does NOT do (current 1.2) diff --git a/plugins/songwriting/skills/suno/context/v55-features.md b/plugins/songwriting/skills/suno/context/v55-features.md index e9f57dab0..f64ff5d56 100644 --- a/plugins/songwriting/skills/suno/context/v55-features.md +++ b/plugins/songwriting/skills/suno/context/v55-features.md @@ -96,7 +96,7 @@ Char-limit rows verified 2026-07-18 against third-party testers ([hookgenius cha | Personas | ✓ | ✓ | ✓ | | Cover | ✓ | ✓ | ✓ | | Extend | ✓ | ✓ | ✓ | -| Audio upload | 60s | 120s | 120s | +| Audio upload | up to 8 min | up to 30 min | up to 30 min | | 2-track stems | ✓ | ✓ | ✓ | | 12-track stems | — | ✓ | ✓ | | Voices | — | ✓ | ✓ | @@ -107,7 +107,7 @@ Char-limit rows verified 2026-07-18 against third-party testers ([hookgenius cha (Verify against current Suno pricing page — tier feature lists drift.) - Studio row corrected 2026-07-18: **Premier-exclusive** per — Pro has no Studio access. -- Audio-upload row conflicts with the 8-minute Pro/Premier figure stated elsewhere in this skill — unresolved; re-verify before relying on either. +- Audio-upload row verified 2026-07-18 against : Free up to 8 minutes, Pro/Premier up to 30 minutes (the earlier 60s/120s figures were stale). - Free-tier generation runs on **v4.5-all**, not v5.5 (third-party report: TechRadar). ## Sources diff --git a/plugins/songwriting/skills/suno/context/workflow-recipes.md b/plugins/songwriting/skills/suno/context/workflow-recipes.md index 1655de4bb..c0214d085 100644 --- a/plugins/songwriting/skills/suno/context/workflow-recipes.md +++ b/plugins/songwriting/skills/suno/context/workflow-recipes.md @@ -36,12 +36,12 @@ You have a generated song or uploaded demo. Now what? 8. (Optional) Stem export → external DAW for final mix 9. Export full mix -**Upload limits** (unverified — conflicts with the 120-second Pro/Premier figure in the tier matrix elsewhere in this skill; re-verify before relying on either): +**Upload limits** (verified 2026-07-18 against ): | Tier | Max upload | |------|-----------| -| Basic | 60 seconds | -| Pro / Premier | 8 minutes | +| Free | up to 8 minutes | +| Pro / Premier | up to 30 minutes | ## Recipe 2: Edit / rearrange an existing song