Compare commits

..

8 Commits

Author SHA1 Message Date
Noah Zweben
d53f6ca4cd Remove telegram, discord, and fakechat plugins (#741)
Remove the three chat bridge plugins from external_plugins/ and their
corresponding entries in marketplace.json.

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-18 21:31:12 -07:00
Kenneth Lien
55de7f6d1a Merge pull request #740 from anthropics/marketplace-sorted
Enforce alphabetical sort on marketplace.json plugins
2026-03-18 21:21:36 -07:00
Kenneth Lien
f0fdb72a02 Enforce alphabetical sort on marketplace.json plugins
Adds a sort check as a second step in the existing validate-marketplace
workflow. The script supports --fix to sort in place.

Sorts the existing 86 entries — pure reorder, no content change.
Previously grouped loosely by kind (LSPs first, then internal, then
external); now strictly alphabetical so insertion point is unambiguous.
2026-03-18 16:56:11 -07:00
Noah Zweben
158ef95c6f Add marketplace.json validation CI workflow (#347)
* Add CI workflow to validate marketplace.json on PRs

Add a GitHub Actions workflow that validates marketplace.json is
well-formed JSON with a plugins array whenever PRs modify it. Includes:
- validate-marketplace.ts: Bun script that parses and validates the JSON
- validate-marketplace.yml: GH Actions workflow triggered on PR changes
- test-marketplace-check.js: Unit tests for the validation logic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Strengthen marketplace validator and remove orphaned test file

- validate-marketplace.ts: check duplicate names and required fields
  (name, description, source) per entry, not just valid JSON
- remove .github/workflows/test-marketplace-check.js: tested a
  checkMarketplaceViolations function that doesn't exist in the PR,
  and was in workflows/ instead of scripts/

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tobin South <tobin.south@gmail.com>
2026-03-18 23:49:26 +00:00
Kenneth Lien
3de6a94eb9 Register telegram, discord, fakechat channel plugins (#739)
Adds marketplace entries for the three channel plugins landing in
external_plugins/ via #735, #736, #738. iMessage (#737) is intentionally
held back from registration.

telegram and discord go under productivity; fakechat goes under
development since it's a localhost testing harness, not an end-user
messaging bridge.
2026-03-18 23:49:01 +00:00
Kenneth Lien
1b33c1d9f9 Add telegram channel plugin (#735)
Telegram messaging bridge for Claude Code. Runs a local MCP server that
connects to the Telegram Bot API via a user-created bot token.

Built-in access control: inbound messages are gated by an allowlist
(default: pairing mode), outbound sends are scoped to the same allowlist.
The /telegram:access skill manages pairing, allowlists, and policy.

Ships full source — server.ts runs locally via bun, started by the
.mcp.json command. First external_plugins entry to bundle source rather
than point at a hosted MCP endpoint.
2026-03-18 23:46:59 +00:00
Kenneth Lien
4796148ace Add discord channel plugin (#736)
Discord messaging bridge for Claude Code. Runs a local MCP server that
connects to Discord's Gateway via a user-created bot token.

Built-in access control: inbound messages are gated by an allowlist
(default: pairing mode), outbound sends are scoped to the same allowlist.
Guild channels require opt-in and @mention. The /discord:access skill
manages pairing, allowlists, and policy.

Ships full source — server.ts runs locally via bun, started by the
.mcp.json command.
2026-03-18 23:46:41 +00:00
Kenneth Lien
75e1c5d437 Add fakechat channel plugin (#738)
Localhost web chat UI for testing the channel notification flow.
No tokens, no access control, no third-party service. Serves an
iMessage-style UI on localhost; messages posted there arrive as
channel notifications, replies render in the UI.

Useful for developing against the channel protocol without a live
messaging account.

Ships full source — server.ts runs locally via bun, started by the
.mcp.json command.
2026-03-18 23:46:21 +00:00
4 changed files with 954 additions and 833 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env bun
/**
* Checks that marketplace.json plugins are alphabetically sorted by name.
*
* Usage:
* bun check-marketplace-sorted.ts # check, exit 1 if unsorted
* bun check-marketplace-sorted.ts --fix # sort in place
*/
import { readFileSync, writeFileSync } from "fs";
import { join } from "path";
const MARKETPLACE = join(import.meta.dir, "../../.claude-plugin/marketplace.json");
type Plugin = { name: string; [k: string]: unknown };
type Marketplace = { plugins: Plugin[]; [k: string]: unknown };
const raw = readFileSync(MARKETPLACE, "utf8");
const mp: Marketplace = JSON.parse(raw);
const cmp = (a: Plugin, b: Plugin) =>
a.name.toLowerCase().localeCompare(b.name.toLowerCase());
if (process.argv.includes("--fix")) {
mp.plugins.sort(cmp);
writeFileSync(MARKETPLACE, JSON.stringify(mp, null, 2) + "\n");
console.log(`sorted ${mp.plugins.length} plugins`);
process.exit(0);
}
for (let i = 1; i < mp.plugins.length; i++) {
if (cmp(mp.plugins[i - 1], mp.plugins[i]) > 0) {
console.error(
`marketplace.json plugins are not sorted: ` +
`'${mp.plugins[i - 1].name}' should come after '${mp.plugins[i].name}' (index ${i})`,
);
console.error(` run: bun .github/scripts/check-marketplace-sorted.ts --fix`);
process.exit(1);
}
}
console.log(`ok: ${mp.plugins.length} plugins sorted`);

77
.github/scripts/validate-marketplace.ts vendored Normal file
View File

@@ -0,0 +1,77 @@
#!/usr/bin/env bun
/**
* Validates marketplace.json: well-formed JSON, plugins array present,
* each entry has required fields, and no duplicate plugin names.
*
* Usage:
* bun validate-marketplace.ts <path-to-marketplace.json>
*/
import { readFile } from "fs/promises";
async function main() {
const filePath = process.argv[2];
if (!filePath) {
console.error("Usage: validate-marketplace.ts <path-to-marketplace.json>");
process.exit(2);
}
const content = await readFile(filePath, "utf-8");
let parsed: unknown;
try {
parsed = JSON.parse(content);
} catch (err) {
console.error(
`ERROR: ${filePath} is not valid JSON: ${err instanceof Error ? err.message : err}`
);
process.exit(1);
}
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
console.error(`ERROR: ${filePath} must be a JSON object`);
process.exit(1);
}
const marketplace = parsed as Record<string, unknown>;
if (!Array.isArray(marketplace.plugins)) {
console.error(`ERROR: ${filePath} missing "plugins" array`);
process.exit(1);
}
const errors: string[] = [];
const seen = new Set<string>();
const required = ["name", "description", "source"] as const;
marketplace.plugins.forEach((p, i) => {
if (!p || typeof p !== "object") {
errors.push(`plugins[${i}]: must be an object`);
return;
}
const entry = p as Record<string, unknown>;
for (const field of required) {
if (!entry[field]) {
errors.push(`plugins[${i}] (${entry.name ?? "?"}): missing required field "${field}"`);
}
}
if (typeof entry.name === "string") {
if (seen.has(entry.name)) {
errors.push(`plugins[${i}]: duplicate plugin name "${entry.name}"`);
}
seen.add(entry.name);
}
});
if (errors.length) {
console.error(`ERROR: ${filePath} has ${errors.length} validation error(s):`);
for (const e of errors) console.error(` - ${e}`);
process.exit(1);
}
console.log(`OK: ${marketplace.plugins.length} plugins, no duplicates, all required fields present`);
}
main().catch((err) => {
console.error("Fatal error:", err);
process.exit(2);
});

View File

@@ -0,0 +1,20 @@
name: Validate Marketplace JSON
on:
pull_request:
paths:
- '.claude-plugin/marketplace.json'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- name: Validate marketplace.json
run: bun .github/scripts/validate-marketplace.ts .claude-plugin/marketplace.json
- name: Check plugins sorted
run: bun .github/scripts/check-marketplace-sorted.ts