diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 38513dd..0899c18 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -30,17 +30,23 @@ jobs: # One exact version, so that a new release can't change what passes. Raise it on purpose. - name: Install the Claude Code CLI run: npm install --global @anthropic-ai/claude-code@2.1.301 + # Each plugin with --strict, so that a warning fails the run too. - name: Validate the marketplace and each plugin run: | claude plugin validate . for plugin in plugins/*/; do - claude plugin validate "./${plugin%/}" + claude plugin validate --strict "./${plugin%/}" done - # The Codex marketplace, the plugins' names in all four places, the skills' frontmatter, the fee's wording, and no - # fee split's terms. jq is on GitHub's Ubuntu runners. + # The Codex marketplace, the plugins' names in all four places, the skills' frontmatter, the fee's wording in the + # skill, the exclip README and its OpenAI listing, and no fee split's terms. jq is on GitHub's Ubuntu runners. - name: Check the manifests run: bash scripts/check-manifests.sh # Exactly the six keys: ColdAI's address in EIP-55 form, its name, the rate 875, no fee on stock swaps, and a full # ExClip commit. - name: Check ColdAI's fee settings run: node scripts/check-platform.mjs + # Each plugin's listing for Anthropic's and OpenAI's directories: plugin.json against the portable schema and + # OpenAI's limits, the brand colour, the icons, READMEs and licences, the files under plugins/, and pinned package + # launchers. + - name: Check the directory listings + run: node scripts/check-listing.mjs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da0bfad --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# scripts/pack-openai.sh writes the directory ZIPs here. +dist/ diff --git a/CHANGELOG.md b/CHANGELOG.md index efd81cc..6e5372f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,18 @@ All notable changes to this project are recorded here. The format follows rate, the fee on stock swaps (0) and the ExClip commit the skill checks out, `46ff2be` (`46ff2beb0ca0c4500cff133c023e79411c0a52cc`). - The `clip-wallet` plugin, with the skill `launch-wallet`: make your own wallet with the Clip Wallet kit's - `create-clip-wallet`. Pre-release: test networks only, and no external audit. -- CI: `claude plugin validate` for the marketplace and each plugin, the Codex marketplace's fields and paths, the - plugins' names, and ColdAI's fee settings. + `create-clip-wallet`. Pre-release: test networks only, and no external audit. The skill runs `create-clip-wallet` + 0.2.1 and no other version: it first checks on the npm registry that the package is the kit's own, built and + published from Clip Wallet's repository with npm provenance, and stops if it isn't. After the build, the project's + `pnpm verify:provenance` checks every kit package's provenance attestation. +- Listings for Anthropic's and OpenAI's plugin directories. Each plugin folder has a README (what the plugin does and + needs, what it runs, fetches and sends, example prompts, support, privacy and licence), a copy of the licence, and + its icon. Each `.claude-plugin/plugin.json` adds `displayName`, `homepage`, `repository` and `author.url`. Each + `plugin.json` follows the portable Agent Plugins schema, with OpenAI's listing under + `extensions."com.openai".interface`. `PRIVACY.md` is the plugins' privacy policy: they send nothing to ColdAI and + collect no data. +- `scripts/pack-openai.sh`, which packs each committed plugin as one ZIP for OpenAI's plugin directory, in `dist/`, + and checks what each ZIP holds. +- CI: `claude plugin validate` for the marketplace, and with `--strict` for each plugin; the Codex marketplace's + fields and paths; the plugins' names; ColdAI's fee settings, and the fee's fixed words in the skill, the `exclip` + README and its OpenAI listing; and each plugin's directory listing (`scripts/check-listing.mjs`). diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..e4753e8 --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,57 @@ +# Privacy policy + +This policy covers the `exclip` and `clip-wallet` plugins for coding agents, published by ColdAI LLC from +https://github.com/ColdAI-org/coldai-plugins, wherever you install them from: this repository's marketplace, +Anthropic's plugin directory or OpenAI's. + +## What the plugins are + +Each plugin is a set of instructions, called a skill, that your coding agent reads and follows. The `exclip` plugin +also holds one settings file, `platform.json`, with ColdAI's fee settings. The plugins have no server, no code of +their own that runs, and no telemetry. When you use a skill, your coding agent runs the commands it describes on your +computer. + +## What ColdAI collects through them + +Nothing. The plugins send ColdAI no data: not your prompts, your files, your answers to the skills' questions, how you +use them, or anything about your computer. + +## What the commands contact + +The commands the skills describe contact these services. Each one's own privacy policy covers what it receives, such +as your IP address and what you download. + +- **Where you install the plugins from**: GitHub, or the plugin directory you use. +- **GitHub**: `launch-dex` clones ExClip from it. For a Clip Wallet desktop app, installing the project's packages + downloads Electron from Electron's releases on GitHub. +- **The npm registry**: both skills install packages from it, Corepack downloads pnpm from it, and `launch-wallet` + reads `create-clip-wallet`'s details and the kit's provenance attestations from it. +- **Playwright's servers**: if `launch-dex` downloads Chromium for ExClip's screenshots. +- **The services ExClip uses**: while `launch-dex` checks the fee, and once your DEX is live, ExClip's server may + contact the services in ExClip's + [architecture notes](https://github.com/ColdAI-org/exclip/blob/main/docs/architecture.md#how-the-parts-talk). On a + live DEX, ColdAI's fee is paid on-chain to ColdAI's address on Sonic, and like every on-chain payment, it's public. + Nothing else goes to ColdAI. +- **The services your wallet uses**: set in its `clip.config.ts` and described in the + [Clip Wallet kit's docs](https://github.com/ColdAI-org/clip-wallet). + +## Your conversation + +What you type to your AI app, and what your coding agent reads and writes while it follows a skill, is handled by the +app's maker under its own privacy policy. ColdAI doesn't receive it. + +## Retention + +ColdAI receives no data through the plugins, so it keeps none. + +## Your choices + +You can uninstall either plugin at any time, and decline or stop any command your coding agent runs. The `exclip` +plugin changes nothing unless you agree to ColdAI's fee. + +## Contact + +Ask ColdAI about this policy at https://coldai.org/contact. ColdAI's privacy policy for its website and services is +at https://coldai.org/privacy. + +Last changed: 2026-11-07. diff --git a/README.md b/README.md index b650fdf..2dffd3b 100644 --- a/README.md +++ b/README.md @@ -95,23 +95,54 @@ Wallet kit's starter: 1. It says that Clip Wallet is pre-release, runs on test networks only and has had no external audit. 2. It asks for the wallet's name, reverse-DNS id, logo, accent colour and platforms (browser extension, desktop app, phone app), the network families you pick, the languages and the homepage. -3. It runs the kit's documented `npx create-clip-wallet … --yes`, then the new project's checks. -4. It points to each platform's dev command, and to what's left before shipping. +3. It checks on the npm registry that `create-clip-wallet` 0.2.1 is the kit's own package, built and published from + Clip Wallet's repository with npm provenance, then runs the kit's documented + `npx create-clip-wallet@0.2.1 … --yes`. It runs no other version. +4. It runs the new project's checks, then `pnpm verify:provenance`, which checks every kit package's provenance + attestation. +5. It points to each platform's dev command, and to what's left before shipping. + +## Directory listings + +Each plugin folder holds what Anthropic's and OpenAI's plugin directories ask for, next to its skills: + +- a README for the plugin's page: what it does and needs, what it runs, fetches and sends, example prompts, support, + privacy and licence ([exclip](plugins/exclip/README.md), [clip-wallet](plugins/clip-wallet/README.md)); +- `LICENSE`, a copy of this repository's licence, and the plugin's icon, in `assets/`; +- `.claude-plugin/plugin.json`, the manifest for Claude Code and Anthropic's directory, and `plugin.json`, the portable + Agent Plugins manifest for Codex and OpenAI's directory, with OpenAI's listing under + `extensions."com.openai".interface`. + +Both plugins send nothing to ColdAI and collect no data; [PRIVACY.md](PRIVACY.md) is their privacy policy. + +For Anthropic's directory (claude.ai/directory/manage), submit a plugin bundle from this repository with the paths +`plugins/exclip` and `plugins/clip-wallet`. + +For OpenAI's directory, which ChatGPT and Codex share, commit first, then run +[`bash scripts/pack-openai.sh`](scripts/pack-openai.sh). It packs each committed plugin folder as +`dist/-.zip`, with `plugin.json` at the ZIP's root, lists each ZIP, and checks that it holds +`plugin.json`, `skills/`, the icon, the README and the licence. Submit one ZIP per plugin. ## Maintaining ```text .claude-plugin/marketplace.json the marketplace, for Claude Code .agents/plugins/marketplace.json the marketplace, for Codex -plugins//.claude-plugin/plugin.json each plugin's manifest, for Claude Code -plugins//plugin.json each plugin's manifest, for Codex +plugins//.claude-plugin/plugin.json each plugin's manifest, for Claude Code and Anthropic's plugin directory +plugins//plugin.json each plugin's manifest, for Codex and OpenAI's plugin directory plugins//skills//SKILL.md the skills, for both +plugins//README.md each plugin's page in the directories +plugins//LICENSE a copy of LICENSE, for each plugin on its own +plugins//assets/ each plugin's icon plugins/exclip/platform.json ColdAI's fee settings and the ExClip commit -scripts/ the checks CI runs +PRIVACY.md the plugins' privacy policy +scripts/ the checks CI runs, and the OpenAI packer ``` -A plugin's name is the same in all four places: both marketplaces and both of its manifests. Its version is in both -manifests. +A plugin's name is the same in all four places: both marketplaces and both of its manifests. Both manifests have the +same version, description, author, homepage, repository, licence and keywords, and the same display name: +`displayName` in `.claude-plugin/plugin.json`, and in OpenAI's listing in `plugin.json`. A plugin's `LICENSE` is a +copy of the root `LICENSE`, byte for byte. ### Bump the ExClip commit @@ -128,16 +159,26 @@ manifests. [`.github/workflows/validate.yml`](.github/workflows/validate.yml) runs on every push to `main` and every pull request: -- `claude plugin validate` on the marketplace and on each plugin, with the Claude Code CLI pinned to one version; +- `claude plugin validate` on the marketplace, and `claude plugin validate --strict` on each plugin, with the Claude + Code CLI pinned to one version; - [`scripts/check-manifests.sh`](scripts/check-manifests.sh): the Codex marketplace's fields and paths, that each plugin's name agrees in both marketplaces and both manifests, each skill's frontmatter, that the `exclip` plugin - states ColdAI's fee wherever it describes itself and in the skill's fixed words, and that no description, - `launch-dex`'s skill or this README describes a fee split; + states ColdAI's fee wherever it describes itself and in the fee's fixed words in its skill, its README and its + OpenAI listing, and that no description, listing text, `launch-dex`'s skill, README or `PRIVACY.md` describes a fee + split; - [`scripts/check-platform.mjs`](scripts/check-platform.mjs): that `platform.json` has exactly its six keys, with - ColdAI's address in EIP-55 form, its name, the rate 875, no fee on stock swaps, and a full ExClip commit. - -Locally: `claude plugin validate .`, `bash scripts/check-manifests.sh` (needs `jq`) and -`node scripts/check-platform.mjs`. + ColdAI's address in EIP-55 form, its name, the rate 875, no fee on stock swaps, and a full ExClip commit; +- [`scripts/check-listing.mjs`](scripts/check-listing.mjs): each plugin's directory listing. Its `plugin.json` has the + portable schema's keys, OpenAI's listing within its limits, three different example prompts, `https://` links and + a brand colour with at least 2:1 contrast against white, and the same shared fields and display name as its + `.claude-plugin/plugin.json`. Its icon is a square SVG of at least 48 px. Its README has at least 40 words, the + listing's prompts first under "Example prompts", the support and privacy links, and the icon only as a Markdown + image. Its `LICENSE` is the root's. No file under `plugins/` is a link or a system file, images are SVG or PNG, + other files are under 256 KiB, and every package launcher names one exact version. + +Locally: `claude plugin validate .`, `claude plugin validate --strict ./plugins/`, +`bash scripts/check-manifests.sh` (needs `jq`), `node scripts/check-platform.mjs` and +`node scripts/check-listing.mjs`. ## Security @@ -145,4 +186,5 @@ Report a vulnerability privately, as [SECURITY.md](SECURITY.md) says. ## Licence -These plugins are licensed under the [Apache License 2.0](LICENSE), © 2026 ColdAI LLC. +These plugins are licensed under the [Apache License 2.0](LICENSE), © 2026 ColdAI LLC. "ExClip", "Clip Wallet" and +their marks are ColdAI trademarks and aren't licensed under it: section 6 of the licence grants no trademark rights. diff --git a/plugins/clip-wallet/.claude-plugin/plugin.json b/plugins/clip-wallet/.claude-plugin/plugin.json index 781f00c..61b0ecf 100644 --- a/plugins/clip-wallet/.claude-plugin/plugin.json +++ b/plugins/clip-wallet/.claude-plugin/plugin.json @@ -1,10 +1,14 @@ { "name": "clip-wallet", + "displayName": "Clip Wallet", "version": "0.1.0", "description": "Launch your own non-custodial wallet with the Clip Wallet kit: a browser extension, a desktop app and a phone app under your own name, ids and logo, made with create-clip-wallet. Pre-release: test networks only, and no external audit.", "author": { - "name": "ColdAI LLC" + "name": "ColdAI LLC", + "url": "https://coldai.org" }, + "homepage": "https://github.com/ColdAI-org/clip-wallet", + "repository": "https://github.com/ColdAI-org/coldai-plugins", "license": "Apache-2.0", "keywords": ["wallet", "clip-wallet", "create-clip-wallet", "browser-extension", "desktop", "mobile"] } diff --git a/plugins/clip-wallet/LICENSE b/plugins/clip-wallet/LICENSE new file mode 100644 index 0000000..f4ed428 --- /dev/null +++ b/plugins/clip-wallet/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 ColdAI LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/clip-wallet/README.md b/plugins/clip-wallet/README.md new file mode 100644 index 0000000..24297f6 --- /dev/null +++ b/plugins/clip-wallet/README.md @@ -0,0 +1,88 @@ +![Clip Wallet mark](assets/icon.svg) + +# Clip Wallet + +> **Pre-release: Clip Wallet runs on test networks only and has had no external audit.** + +Launch your own non-custodial wallet with the [Clip Wallet](https://github.com/ColdAI-org/clip-wallet) kit: a browser +extension, a desktop app for macOS, Windows and Linux, and a phone app for iOS and Android, under your own name, ids, +logo and accent colour, for the network families and languages you pick. The plugin has one skill, `launch-wallet`, +which makes the project with the kit's starter, `create-clip-wallet`, and checks it with the project's own checks. + +The wallet it makes runs on test networks, and the command has no switch for mainnet: that is your decision, as the +wallet's owner, after the project's `MAINNET.md`. The skill is instructions to a coding agent, and agents make +mistakes: review what yours made before you build. + +## What it needs + +- A coding agent that runs commands in a terminal, such as Claude Code, Codex or Cowork. In a chat without a terminal, + such as claude.ai, the skill can explain the steps but not run them. +- Node 22 or newer, with npm, and pnpm or Corepack. +- Git, if you want `create-clip-wallet` to make the project's first commit. It works without it. +- Your wallet's name, a reverse-DNS id for a domain you own (such as com.acme.wallet), a logo as a `.png` (1024×1024 + is best) or an `.svg`, an accent colour and your homepage. An `.svg` logo needs one of `@resvg/resvg-js`, + `rsvg-convert`, `sips` (built into macOS) or ImageMagick's `magick`. +- Each platform's own build tools, to build for it. The agent tells you which are missing. + +## Install + +In Claude Code, one line adds the marketplace and installs the plugin: + +```sh +/plugin install clip-wallet --marketplace ColdAI-org/coldai-plugins +``` + +From a terminal: `claude plugin install clip-wallet --marketplace ColdAI-org/coldai-plugins`. The +[repository's README](https://github.com/ColdAI-org/coldai-plugins#install-with-claude-code) covers Codex. + +## What it runs, fetches and sends + +The skill tells your coding agent what to do, and the agent runs the commands on your computer. In order: + +1. It says that Clip Wallet is pre-release, and asks for your wallet's details. It runs nothing before you answer. +2. `node --version`, to check for Node 22 or newer. +3. `npm view create-clip-wallet@0.2.1 name version repository.url dist.attestations.provenance.predicateType`, which + reads the starter's details from the npm registry. If they don't show the kit's own package, built and published + from Clip Wallet's repository with npm provenance, the skill stops there. +4. `npx create-clip-wallet@0.2.1 … --yes`, with your values. It downloads the starter from the npm registry + and makes the project in a new folder: `clip.config.ts`, `wallet.identity.json`, every platform's icons, listing + drafts, and the browser extension's private key in `.keys/extension.pem`, which the agent never opens. With Git + installed, the starter also runs `git init` and makes the first commit, on your computer. +5. `corepack enable`, if pnpm isn't installed, then `pnpm install`, which downloads the project's packages from the + npm registry, and for the desktop app, Electron from Electron's releases on GitHub. +6. `pnpm harness`, `pnpm check-types` and `pnpm build`: the project's checks and builds, on your computer. +7. `pnpm verify:provenance`, which reads each kit package's provenance attestation from the npm registry, and checks + that Clip Wallet's CI built the package from its public repository and that npm serves that build. +8. It tells you what was made and what each check said, and how to try each platform. + +It reads the new project's `AGENTS.md` and `llms.txt`, and follows them. It makes no other commits unless you ask, +never switches mainnet on, never publishes anything, and never asks for a recovery phrase or a private key. + +## What the plugin sends to ColdAI + +Nothing. The plugin is instructions for your coding agent. It has no server and no telemetry, and ColdAI collects no +data through it. What your wallet contacts when it runs is set in its `clip.config.ts` and described in the kit's +docs. + +## Example prompts + +- Launch my own wallet with Clip Wallet. +- Make a browser extension wallet called Acme Wallet for EVM networks and Solana, with a green accent. +- Start a wallet with the Clip Wallet kit for desktop and phone, in English and German, with the id com.acme.wallet. + +## Support + +Contact ColdAI at [coldai.org/contact](https://coldai.org/contact), or open an issue at +[github.com/ColdAI-org/coldai-plugins/issues](https://github.com/ColdAI-org/coldai-plugins/issues). Report a +vulnerability privately, as +[SECURITY.md](https://github.com/ColdAI-org/coldai-plugins/blob/main/SECURITY.md) says. + +## Privacy + +The plugin's privacy policy is +[PRIVACY.md](https://github.com/ColdAI-org/coldai-plugins/blob/main/PRIVACY.md). + +## Licence + +This plugin is licensed under the [Apache License 2.0](LICENSE), © 2026 ColdAI LLC. "Clip Wallet" and the Clip Wallet +mark are ColdAI trademarks and aren't licensed under it: section 6 of the licence grants no trademark rights. diff --git a/plugins/clip-wallet/assets/icon.svg b/plugins/clip-wallet/assets/icon.svg new file mode 100644 index 0000000..c6c348f --- /dev/null +++ b/plugins/clip-wallet/assets/icon.svg @@ -0,0 +1,5 @@ + + Clip Wallet + + + diff --git a/plugins/clip-wallet/plugin.json b/plugins/clip-wallet/plugin.json index fff4f27..3712bd8 100644 --- a/plugins/clip-wallet/plugin.json +++ b/plugins/clip-wallet/plugin.json @@ -1,5 +1,38 @@ { + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "clip-wallet", "version": "0.1.0", - "description": "Launch your own non-custodial wallet with the Clip Wallet kit: a browser extension, a desktop app and a phone app under your own name, ids and logo, made with create-clip-wallet. Pre-release: test networks only, and no external audit." + "description": "Launch your own non-custodial wallet with the Clip Wallet kit: a browser extension, a desktop app and a phone app under your own name, ids and logo, made with create-clip-wallet. Pre-release: test networks only, and no external audit.", + "author": { + "name": "ColdAI LLC", + "url": "https://coldai.org" + }, + "homepage": "https://github.com/ColdAI-org/clip-wallet", + "repository": "https://github.com/ColdAI-org/coldai-plugins", + "license": "Apache-2.0", + "keywords": ["wallet", "clip-wallet", "create-clip-wallet", "browser-extension", "desktop", "mobile"], + "extensions": { + "com.openai": { + "interface": { + "displayName": "Clip Wallet", + "shortDescription": "Launch your own wallet", + "longDescription": "Clip Wallet is a kit for non-custodial wallets. This plugin's skill, launch-wallet, makes a new wallet project with the kit's starter, create-clip-wallet: a browser extension, a desktop app for macOS, Windows and Linux, and a phone app for iOS and Android, under your own name, ids, logo and accent colour, for the network families and languages you pick.\n\nPre-release: Clip Wallet runs on test networks only and has had no external audit. The wallet it makes runs on test networks, and the command has no switch for mainnet: that is your decision, as the wallet's owner.\n\nYour coding agent first checks on the npm registry that the starter is the kit's own package, built and published from Clip Wallet's repository with npm provenance, and runs it only at the exact version the skill names. Then it installs the project's packages with pnpm, runs the project's checks, and checks every kit package's provenance. It never opens the wallet's extension key, and never asks for a recovery phrase or a private key. It needs an agent that runs commands in a terminal, such as Claude Code, Codex or Cowork. In a chat without one, the skill can explain the steps but not run them.\n\nThe plugin sends nothing to ColdAI and collects no data.", + "developerName": "ColdAI LLC", + "category": "Coding", + "capabilities": [], + "websiteURL": "https://github.com/ColdAI-org/clip-wallet", + "supportURL": "https://coldai.org/contact", + "privacyPolicyURL": "https://github.com/ColdAI-org/coldai-plugins/blob/main/PRIVACY.md", + "termsOfServiceURL": "https://coldai.org/terms", + "defaultPrompt": [ + "Launch my own wallet with Clip Wallet.", + "Make a browser extension wallet called Acme Wallet for EVM networks and Solana, with a green accent.", + "Start a wallet with the Clip Wallet kit for desktop and phone, in English and German, with the id com.acme.wallet." + ], + "brandColor": "#FF3C00", + "composerIcon": "./assets/icon.svg", + "logo": "./assets/icon.svg" + } + } + } } diff --git a/plugins/clip-wallet/skills/launch-wallet/SKILL.md b/plugins/clip-wallet/skills/launch-wallet/SKILL.md index d6fbdbf..4dec345 100644 --- a/plugins/clip-wallet/skills/launch-wallet/SKILL.md +++ b/plugins/clip-wallet/skills/launch-wallet/SKILL.md @@ -62,27 +62,32 @@ and languages above. An `.svg` logo needs one of these installed to be rendered: ## 3. Make the project -Check that Node is 22 or newer (`node --version`). Then check that the package is the kit's own, published from -Clip Wallet's repository: +Check that Node is 22 or newer (`node --version`). + +This skill runs `create-clip-wallet` 0.2.1, exactly. Check that this version is the kit's own, built and published +from Clip Wallet's repository with npm provenance: ```sh -npm view create-clip-wallet name version repository.url +npm view create-clip-wallet@0.2.1 name version repository.url dist.attestations.provenance.predicateType ``` -`repository.url` must be `git+https://github.com/ColdAI-org/clip-wallet.git`. If it isn't, or the package isn't -found, stop and say so: don't run it. Otherwise tell the person which version will run. +It must print `create-clip-wallet` as the name, `0.2.1` as the version, +`git+https://github.com/ColdAI-org/clip-wallet.git` as `repository.url`, and `https://slsa.dev/provenance/v1` as the +provenance's `predicateType`. If any of them is different or missing, or the package isn't found, stop and say so: +don't run it. Otherwise tell the person that `create-clip-wallet` 0.2.1 will run. -Then run the kit's documented command, with their values, from the directory the folder goes in: +Then run the kit's documented command at that version, with their values, from the directory the folder goes in: ```sh -npx create-clip-wallet --platforms --name "" --rdns \ +npx create-clip-wallet@0.2.1 --platforms --name "" --rdns \ --id --logo --accent "" --networks "" \ --languages --homepage --yes ``` `--platforms` takes `extension`, `desktop` and `mobile`, joined by commas, and `--networks` and `--languages` take comma-separated lists too. Leave out an option the person gave no answer for, so the kit's default applies. Add other -options only if the person asks for them (see the kit's documentation for `create-clip-wallet`). +options only if the person asks for them (see the kit's documentation for `create-clip-wallet`). Never run another +version of `create-clip-wallet`, or the command without its version. If the command refuses a value, it has written nothing. Tell the person which value and why, in the command's words, ask again, and run it again. @@ -90,18 +95,24 @@ ask again, and run it again. ## 4. Check the project Read the new project's `AGENTS.md` and `llms.txt`, and follow them from here on. Then run the kit's loop for a -kit-built wallet, from the project's folder (`corepack enable` first, if `pnpm` isn't installed): +kit-built wallet, and its provenance check, from the project's folder (`corepack enable` first, if `pnpm` isn't +installed): ```sh pnpm install pnpm harness pnpm check-types pnpm build +pnpm verify:provenance ``` -`pnpm harness` must pass before every commit. If a check fails, fix the cause if it's in what the person gave you, -such as the logo or the colour, and run it again. Otherwise report it with its message. If a build needs a toolchain -this computer doesn't have, say which. +`pnpm harness` must pass before every commit. `pnpm verify:provenance` reads each kit package's provenance +attestation from the npm registry, and checks that Clip Wallet's CI built the package from its public repository and +that npm serves that build. + +If a check fails, fix the cause if it's in what the person gave you, such as the logo or the colour, and run it +again. Otherwise report it with its message. If a build needs a toolchain this computer doesn't have, say which. If +`pnpm verify:provenance` fails, don't change the kit's versions to get past it. ## 5. Hand back @@ -131,5 +142,5 @@ Tell the person what was made and what each check said, then give them this, wit > > **Later changes**: `pnpm wallet:identity` sets the name, ids, accent, logo, homepage or WalletConnect project id > again, and keeps the extension key. `pnpm wallet:brand` renders the icons again from the logo, and -> `pnpm wallet:listings` regenerates the listing drafts. `pnpm verify:provenance` checks every kit package's -> provenance attestation. +> `pnpm wallet:listings` regenerates the listing drafts. After a kit upgrade, `pnpm verify:provenance` checks every +> kit package's provenance attestation again. diff --git a/plugins/exclip/.claude-plugin/plugin.json b/plugins/exclip/.claude-plugin/plugin.json index 9ff0809..5cfa740 100644 --- a/plugins/exclip/.claude-plugin/plugin.json +++ b/plugins/exclip/.claude-plugin/plugin.json @@ -1,10 +1,14 @@ { "name": "exclip", + "displayName": "ExClip", "version": "0.1.0", "description": "Launch your own DEX on ExClip, the open-source SODAX swap front end, with ColdAI's fee. ColdAI LLC takes 0.875 % of the amount sold on every swap, paid straight to its address, except swaps with a tokenised stock on either side, which carry no fee. Your DEX charges no fee of its own. The skill states the fee and waits for your yes before it changes anything.", "author": { - "name": "ColdAI LLC" + "name": "ColdAI LLC", + "url": "https://coldai.org" }, + "homepage": "https://coldai.org/exclip/", + "repository": "https://github.com/ColdAI-org/coldai-plugins", "license": "Apache-2.0", "keywords": ["dex", "exclip", "sodax", "swap", "fee"] } diff --git a/plugins/exclip/LICENSE b/plugins/exclip/LICENSE new file mode 100644 index 0000000..f4ed428 --- /dev/null +++ b/plugins/exclip/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 ColdAI LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/exclip/README.md b/plugins/exclip/README.md new file mode 100644 index 0000000..e007954 --- /dev/null +++ b/plugins/exclip/README.md @@ -0,0 +1,105 @@ +![ExClip mark](assets/icon.svg) + +# ExClip + +Launch your own DEX on [ExClip](https://github.com/ColdAI-org/exclip), the open-source front end for SODAX cross-chain +swaps, under your name, logo, colours and domains, with ColdAI's fee. The plugin has one skill, `launch-dex`. It builds +on the task "Make this my own DEX" in ExClip's `AGENTS.md`, and like that task it changes settings, not code. + +## The fee + +- **ColdAI's fee: 0.875 % of the amount sold on every swap**, paid straight to ColdAI LLC's address. Swaps with a + tokenised stock on either side carry no fee. +- **Your DEX charges no fee of its own.** This plugin sets ColdAI's fee and no other. +- **Your traders see the fee** on every swap's review, named with its rate, before they confirm. + +Before it changes anything, the skill states the fee and asks: + +> Do you agree to ColdAI's fee of 0.875 %? Please answer yes or no. + +Only a clear yes goes on. On a no, nothing is changed, and the skill points to the task "Make this my own DEX" in +ExClip's `AGENTS.md`, which makes a DEX without ColdAI's fee. + +The fee is ExClip's plain fee rate, 875 per 100,000 of the amount sold. It's paid in the token sold (for a swap from +another chain, its hub asset on Sonic) to ColdAI's address on Sonic, `0x90291104f2D1ef9Ffe2Bc1635A3a270A09C8f313`. +[`platform.json`](platform.json) holds these settings and the ExClip commit the skill checks out. + +## What it needs + +- A coding agent that runs commands in a terminal, such as Claude Code, Codex or Cowork. In a chat without a terminal, + such as claude.ai, the skill can explain the steps but not run them. +- Git, Node 24 (ExClip's `.nvmrc`), and pnpm 12.6.0 through Corepack. +- Your DEX's name, its operator's name, a logo file you own and a colour. If you have them, two domains (one for the + app, one for the wallet) and the addresses of your terms of use and privacy notice. + +## Install + +In Claude Code, one line adds the marketplace and installs the plugin: + +```sh +/plugin install exclip --marketplace ColdAI-org/coldai-plugins +``` + +From a terminal: `claude plugin install exclip --marketplace ColdAI-org/coldai-plugins`. The +[repository's README](https://github.com/ColdAI-org/coldai-plugins#install-with-claude-code) covers Codex. + +## What it runs, fetches and sends + +The skill tells your coding agent what to do, and the agent runs the commands on your computer. In order: + +1. It reads [`platform.json`](platform.json) in this plugin, states the fee and asks for a yes. Nothing else happens + before that yes. +2. It clones ExClip from GitHub and checks out the commit in `platform.json`: + `git clone https://github.com/ColdAI-org/exclip`, then `git checkout` and `git rev-parse HEAD`. +3. It runs `corepack enable` and `pnpm install`, which download pnpm and ExClip's packages from the npm registry. +4. It writes your settings in the clone: `exclip.config.ts`, your logo, ColdAI's address in + `apps/wallet/fee-receivers.json` (the passkey wallet's fee allow-list), and the two tests that expect that address. +5. It runs ExClip's checks: `pnpm run contrast`, `pnpm typecheck`, `pnpm lint`, `pnpm test`, `pnpm run docs:links` + (which checks links without fetching them), `NODE_OPTIONS=--max-old-space-size=6144 pnpm build` and + `pnpm run screenshots --brand`. The screenshots need Google Chrome or Chromium. If neither is installed, + `pnpm exec playwright install chromium` downloads Chromium from Playwright's servers. +6. It starts `pnpm dev` on your computer, reads `http://localhost:5173/api/exclip/config` to check the fee setting, + and stops it. While it runs, ExClip's server may contact the services in ExClip's + [architecture notes](https://github.com/ColdAI-org/exclip/blob/main/docs/architecture.md#how-the-parts-talk), such + as CoinGecko. +7. It tells you what it changed and what each check said, and lists what's left before your DEX goes live: running + it, legal pages and review, tokenised stocks, the wallet's release, and a first small swap. + +It never asks for, reads or stores a private key, a recovery phrase or an API key, and it makes no commits unless you +ask. Once your DEX is live, it uses those same services, and each fee is paid on-chain, in public, to ColdAI's +address on Sonic. + +## What the plugin sends to ColdAI + +Nothing. The plugin is instructions for your coding agent, plus one settings file. It has no server and no telemetry, +and ColdAI collects no data through it. The fee on your DEX's swaps is the only thing that goes to ColdAI. + +## Example prompts + +- Launch my own DEX on ExClip. +- Set up a swap site with ExClip called Acme Swap, run by Acme Ltd, with a teal accent. +- Start a DEX with ColdAI on my domains: https://swap.acme.example for the app, https://wallet.acme.example for the + wallet. + +## Not audited + +No outside party has audited ExClip or this plugin. ExClip has no test-network mode: every swap is real. The skill is +instructions to a coding agent, and agents make mistakes: review what yours changed before you build, and again +before anything goes live. + +## Support + +Contact ColdAI at [coldai.org/contact](https://coldai.org/contact), or open an issue at +[github.com/ColdAI-org/coldai-plugins/issues](https://github.com/ColdAI-org/coldai-plugins/issues). Report a +vulnerability privately, as +[SECURITY.md](https://github.com/ColdAI-org/coldai-plugins/blob/main/SECURITY.md) says. + +## Privacy + +The plugin's privacy policy is +[PRIVACY.md](https://github.com/ColdAI-org/coldai-plugins/blob/main/PRIVACY.md). + +## Licence + +This plugin is licensed under the [Apache License 2.0](LICENSE), © 2026 ColdAI LLC. "ExClip" and the ExClip mark are +ColdAI trademarks and aren't licensed under it: section 6 of the licence grants no trademark rights. diff --git a/plugins/exclip/assets/icon.svg b/plugins/exclip/assets/icon.svg new file mode 100644 index 0000000..cb0cec1 --- /dev/null +++ b/plugins/exclip/assets/icon.svg @@ -0,0 +1,19 @@ + + ExClip + + + + + + + + + + + + + + + + diff --git a/plugins/exclip/plugin.json b/plugins/exclip/plugin.json index 10388d4..dd2d2ac 100644 --- a/plugins/exclip/plugin.json +++ b/plugins/exclip/plugin.json @@ -1,5 +1,38 @@ { + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "exclip", "version": "0.1.0", - "description": "Launch your own DEX on ExClip, the open-source SODAX swap front end, with ColdAI's fee. ColdAI LLC takes 0.875 % of the amount sold on every swap, paid straight to its address, except swaps with a tokenised stock on either side, which carry no fee. Your DEX charges no fee of its own. The skill states the fee and waits for your yes before it changes anything." + "description": "Launch your own DEX on ExClip, the open-source SODAX swap front end, with ColdAI's fee. ColdAI LLC takes 0.875 % of the amount sold on every swap, paid straight to its address, except swaps with a tokenised stock on either side, which carry no fee. Your DEX charges no fee of its own. The skill states the fee and waits for your yes before it changes anything.", + "author": { + "name": "ColdAI LLC", + "url": "https://coldai.org" + }, + "homepage": "https://coldai.org/exclip/", + "repository": "https://github.com/ColdAI-org/coldai-plugins", + "license": "Apache-2.0", + "keywords": ["dex", "exclip", "sodax", "swap", "fee"], + "extensions": { + "com.openai": { + "interface": { + "displayName": "ExClip", + "shortDescription": "Launch your own DEX", + "longDescription": "ExClip is the open-source front end for SODAX cross-chain swaps. This plugin's skill, launch-dex, sets up a copy of ExClip as your own DEX, under your name, logo, colours and domains, with ColdAI's fee.\n\nColdAI's fee: 0.875 % of the amount sold on every swap, paid straight to ColdAI LLC's address. Swaps with a tokenised stock on either side carry no fee. Your DEX charges no fee of its own. This plugin sets ColdAI's fee and no other. Your traders see the fee on every swap's review, named with its rate, before they confirm.\n\nBefore it changes anything, the skill states the fee and asks: \"Do you agree to ColdAI's fee of 0.875 %? Please answer yes or no.\" Only a clear yes goes on. On a no, nothing is changed.\n\nAfter a yes, your coding agent clones ExClip from GitHub at the commit this plugin pins, installs its packages from the npm registry with pnpm, writes your settings, runs ExClip's checks, and starts ExClip on your computer to check the fee setting. Then it lists what's left before your DEX goes live. It needs an agent that runs commands in a terminal, such as Claude Code, Codex or Cowork. In a chat without one, the skill can explain the steps but not run them.\n\nNo outside party has audited ExClip or this plugin. ExClip has no test-network mode: every swap is real. The plugin sends nothing to ColdAI and collects no data.", + "developerName": "ColdAI LLC", + "category": "Coding", + "capabilities": [], + "websiteURL": "https://coldai.org/exclip/", + "supportURL": "https://coldai.org/contact", + "privacyPolicyURL": "https://github.com/ColdAI-org/coldai-plugins/blob/main/PRIVACY.md", + "termsOfServiceURL": "https://coldai.org/terms", + "defaultPrompt": [ + "Launch my own DEX on ExClip.", + "Set up a swap site with ExClip called Acme Swap, run by Acme Ltd, with a teal accent.", + "Start a DEX with ColdAI on my domains: https://swap.acme.example for the app, https://wallet.acme.example for the wallet." + ], + "brandColor": "#2563EB", + "composerIcon": "./assets/icon.svg", + "logo": "./assets/icon.svg" + } + } + } } diff --git a/scripts/check-listing.mjs b/scripts/check-listing.mjs new file mode 100644 index 0000000..ebc6ffc --- /dev/null +++ b/scripts/check-listing.mjs @@ -0,0 +1,385 @@ +// Checks each plugin's listing for Anthropic's and OpenAI's plugin directories: plugin.json against the portable Agent +// Plugins schema's keys and OpenAI's interface limits, and the same as .claude-plugin/plugin.json where they overlap; +// the brand colour's contrast with white; the SVG icons, README.md and LICENSE; no stray, linked or oversized files +// under plugins/; and one exact version in every package launcher. Node alone, no packages: +// node scripts/check-listing.mjs +import { lstatSync, readFileSync, readdirSync } from "node:fs"; +import { extname, join, relative, resolve, sep } from "node:path"; +import { fileURLToPath } from "node:url"; +import { isDeepStrictEqual } from "node:util"; + +const ROOT = fileURLToPath(new URL("..", import.meta.url)); +const PLUGINS = join(ROOT, "plugins"); +const SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json"; +// The portable manifest's root keys, every one of them required here, and its author's keys. +const KEYS = [ + "$schema", "name", "version", "description", "author", "homepage", "repository", "license", "keywords", "extensions", +]; +const AUTHOR_KEYS = ["name", "email", "url"]; +// What both of a plugin's manifests hold, which must be the same in both. +const SHARED = ["version", "description", "author", "homepage", "repository", "license", "keywords"]; +// OpenAI's interface: its text limits in characters, its links, its prompts, its brand colour's least contrast with +// white, and its icons' least size in px. +const TEXT_LIMITS = { displayName: 30, shortDescription: 30, longDescription: 4000 }; +const LINKS = ["websiteURL", "supportURL", "privacyPolicyURL", "termsOfServiceURL"]; +const PROMPTS = 3; +const PROMPT_LIMIT = 128; +const MIN_CONTRAST = 2; +const MIN_ICON = 48; +// Anthropic's directory: a README of at least 40 words outside code, images only as SVG or PNG, every other file under +// 256 KiB, and no system files. +const MIN_WORDS = 40; +const MAX_BYTES = 256 * 1024; +const IMAGES = [".svg", ".png"]; +const OTHER_IMAGES = [".jpg", ".jpeg", ".gif", ".webp", ".avif", ".bmp", ".ico", ".tif", ".tiff", ".heic"]; +const STRAY = [".DS_Store", "Thumbs.db"]; +// A package launcher and what it launches, after any flags; a package with one exact version; a Markdown image. +const LAUNCHER = /(?]+)/g; +const PINNED = /^((?:@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*)@(\d+\.\d+\.\d+(?:-[\w.]+)?)$/; +const IMAGE = /!\[[^\]]*\]\(\s*]+)>?(?:\s+"[^"]*")?\s*\)/g; +const ROOT_LICENSE = readFileSync(join(ROOT, "LICENSE")); + +const problems = []; +const colours = []; +const problem = (file, text) => problems.push(`${file}: ${text}`); +const rel = (path) => relative(ROOT, path).split(sep).join("/"); +const chars = (text) => [...text].length; +const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value); +const isText = (value, limit = Infinity) => typeof value === "string" && value.trim() !== "" && chars(value) <= limit; +const isTextList = (value) => Array.isArray(value) && value.every((item) => isText(item)); +const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + +function isHttps(value) { + if (typeof value !== "string") return false; + try { + return new URL(value).protocol === "https:"; + } catch { + return false; + } +} + +// A file's contents, or undefined, with a problem, if it can't be read. +function read(path, encoding) { + try { + return readFileSync(path, encoding); + } catch { + problem(rel(path), "is missing"); + return undefined; + } +} + +function readJson(path) { + const text = read(path, "utf8"); + if (text === undefined) return undefined; + try { + const value = JSON.parse(text); + if (isObject(value)) return value; + problem(rel(path), "must be a JSON object"); + } catch (error) { + problem(rel(path), `isn't valid JSON: ${error.message}`); + } + return undefined; +} + +// WCAG 2's relative luminance of a #RRGGBB colour, and its contrast ratio with white. +function luminance(hex) { + const [r, g, b] = [1, 3, 5].map((i) => { + const c = parseInt(hex.slice(i, i + 2), 16) / 255; + return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4; + }); + return 0.2126 * r + 0.7152 * g + 0.0722 * b; +} + +const contrastWithWhite = (hex) => 1.05 / (luminance(hex) + 0.05); + +// An SVG's size, from its root tag: its width and height in px, or else its viewBox. +function svgSize(svg) { + const tag = svg.match(/]*>/)?.[0]; + if (tag === undefined) return undefined; + const attribute = (name) => tag.match(new RegExp(`\\s${name}\\s*=\\s*["']([^"']*)["']`))?.[1]; + const length = (value) => (/^\s*\d+(\.\d+)?(px)?\s*$/.test(value ?? "") ? parseFloat(value) : undefined); + const width = length(attribute("width")); + const height = length(attribute("height")); + if (width !== undefined && height !== undefined) return { width, height }; + const box = (attribute("viewBox") ?? "").trim().split(/[\s,]+/).map(Number); + return box.length === 4 && box.every(Number.isFinite) ? { width: box[2], height: box[3] } : undefined; +} + +// An icon that OpenAI's listing names: an SVG inside the plugin's folder, square, and at least 48 px. +function checkIcon(dir, file, key, value) { + const path = typeof value === "string" && value.startsWith("./") ? resolve(dir, value) : undefined; + if (path === undefined || !path.startsWith(dir + sep)) { + problem(file, `interface.${key} must be a path inside the plugin's folder that starts with ./`); + return; + } + if (extname(path).toLowerCase() !== ".svg") { + problem(file, `interface.${key} must be an SVG`); + return; + } + const svg = read(path, "utf8"); + if (svg === undefined) return; + const size = svgSize(svg); + if (size === undefined) problem(rel(path), "needs a width and height, or a viewBox"); + else if (size.width !== size.height) problem(rel(path), `must be square, not ${size.width}×${size.height}`); + else if (size.width < MIN_ICON) problem(rel(path), `must be at least ${MIN_ICON} px, not ${size.width}`); +} + +// Each line of a Markdown file, with whether it's in a fenced code block, the fences included. +function* markdownLines(markdown) { + let fence; + for (const line of markdown.split(/\r?\n/)) { + const marker = line.match(/^ {0,3}(`{3,}|~{3,})/)?.[1]; + if (fence === undefined) { + if (marker !== undefined) fence = marker; + yield { line, code: fence !== undefined }; + } else { + if (marker !== undefined && marker[0] === fence[0] && marker.length >= fence.length && line.trim() === marker) { + fence = undefined; + } + yield { line, code: true }; + } + } +} + +const proseWords = (markdown) => + [...markdownLines(markdown)] + .filter(({ code }) => !code) + .flatMap(({ line }) => line.split(/\s+/)) + .filter((word) => /[\p{L}\p{N}]/u.test(word)).length; + +// The items of the list under "## Example prompts", with each item's wrapped lines joined by a space. +function examplePrompts(markdown) { + const prompts = []; + let section = false; + let open = false; + for (const { line, code } of markdownLines(markdown)) { + if (code) { + open = false; + continue; + } + if (/^#{1,6}\s/.test(line)) { + section = /^##\s+Example prompts\s*$/.test(line); + open = false; + continue; + } + if (!section) continue; + const item = line.match(/^(?:[-*+]|\d+[.)])\s+(\S.*)$/); + if (item) { + prompts.push(item[1].trim()); + open = true; + } else if (open && /^\s+\S/.test(line)) { + prompts[prompts.length - 1] += ` ${line.trim()}`; + } else { + open = false; + } + } + return prompts; +} + +// Every entry under a folder, without following links. +function* walk(dir) { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const path = join(dir, entry.name); + yield { path, entry }; + if (entry.isDirectory()) yield* walk(path); + } +} + +// Every file under plugins/ is a regular file, not a system file; an image is an SVG or a PNG; any other file is under +// 256 KiB. Returns the files, relative to the repository. +function checkFiles() { + const files = []; + for (const { path, entry } of walk(PLUGINS)) { + const file = rel(path); + const extension = extname(entry.name).toLowerCase(); + if (STRAY.includes(entry.name)) problem(file, "is a system file: delete it"); + else if (entry.isSymbolicLink()) problem(file, "is a symbolic link: the directories take regular files only"); + else if (entry.isDirectory()) continue; + else if (!entry.isFile()) problem(file, "isn't a regular file"); + else if (OTHER_IMAGES.includes(extension)) problem(file, "is an image other than SVG or PNG"); + else if (!IMAGES.includes(extension) && lstatSync(path).size >= MAX_BYTES) problem(file, "must be under 256 KiB"); + else files.push(file); + } + return files; +} + +// A plugin's plugin.json: the portable schema's keys, the same shared fields and display name as its +// .claude-plugin/plugin.json, and OpenAI's interface within its limits. Returns the interface. +function checkManifests(name, dir) { + const file = `plugins/${name}/plugin.json`; + const manifest = readJson(join(dir, "plugin.json")); + const claude = readJson(join(dir, ".claude-plugin", "plugin.json")); + if (manifest === undefined || claude === undefined) return undefined; + + for (const key of Object.keys(manifest)) { + if (!KEYS.includes(key)) problem(file, `"${key}" isn't a key of the portable schema`); + } + for (const key of KEYS) if (!(key in manifest)) problem(file, `"${key}" is required`); + if (manifest.$schema !== SCHEMA) problem(file, `$schema must be ${SCHEMA}`); + if (manifest.name !== name || !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name) || chars(name) > 64) { + problem(file, `name must be "${name}", its folder's name, in kebab-case and at most 64 characters`); + } + if (!isText(manifest.description, 4000)) problem(file, "description must be text of at most 4,000 characters"); + if (!isObject(manifest.author) || !isText(manifest.author.name, 120)) { + problem(file, "author.name must be text of at most 120 characters"); + } else { + for (const key of Object.keys(manifest.author)) { + if (!AUTHOR_KEYS.includes(key)) problem(file, `"author.${key}" isn't a key of the portable schema`); + } + } + if (!isTextList(manifest.keywords)) problem(file, "keywords must be a list of text"); + for (const key of SHARED) { + if (!isDeepStrictEqual(manifest[key], claude[key])) { + problem(file, `${key} must be the same as in plugins/${name}/.claude-plugin/plugin.json`); + } + } + + const ui = manifest.extensions?.["com.openai"]?.interface; + if (!isObject(ui)) { + problem(file, 'extensions."com.openai".interface is required'); + return undefined; + } + for (const [key, limit] of Object.entries(TEXT_LIMITS)) { + if (!isText(ui[key], limit)) problem(file, `interface.${key} must be text of at most ${limit} characters`); + } + if (ui.displayName !== claude.displayName) { + problem(file, `interface.displayName must be the same as displayName in plugins/${name}/.claude-plugin/plugin.json`); + } + for (const key of ["developerName", "category"]) if (!isText(ui[key])) problem(file, `interface.${key} is required`); + if (!isTextList(ui.capabilities)) problem(file, "interface.capabilities must be a list of text"); + + const links = [["homepage", manifest.homepage], ["repository", manifest.repository]]; + if (isObject(manifest.author) && "url" in manifest.author) links.push(["author.url", manifest.author.url]); + for (const key of LINKS) links.push([`interface.${key}`, ui[key]]); + for (const [key, value] of links) if (!isHttps(value)) problem(file, `${key} must be an https:// URL`); + if (ui.websiteURL !== manifest.homepage) problem(file, "interface.websiteURL must be the same as homepage"); + + const prompts = ui.defaultPrompt; + if ( + !Array.isArray(prompts) || + prompts.length !== PROMPTS || + new Set(prompts).size !== PROMPTS || + !prompts.every((prompt) => isText(prompt, PROMPT_LIMIT) && !prompt.includes("@")) + ) { + problem( + file, + `interface.defaultPrompt must be ${PROMPTS} different prompts of at most ${PROMPT_LIMIT} characters, with no @`, + ); + } + + if (typeof ui.brandColor !== "string" || !/^#[0-9A-Fa-f]{6}$/.test(ui.brandColor)) { + problem(file, "interface.brandColor must be a colour as #RRGGBB"); + } else { + const contrast = contrastWithWhite(ui.brandColor); + if (contrast < MIN_CONTRAST) { + problem(file, `interface.brandColor has ${contrast.toFixed(2)}:1 contrast with white, under ${MIN_CONTRAST}:1`); + } + colours.push(`${name} ${ui.brandColor} ${contrast.toFixed(2)}:1`); + } + checkIcon(dir, file, "composerIcon", ui.composerIcon); + checkIcon(dir, file, "logo", ui.logo); + return ui; +} + +// A plugin's README.md: at least 40 words outside code; no bundled image named anywhere but in Markdown image syntax; +// the icon shown; the listing's prompts first under "## Example prompts"; and its support and privacy links. +function checkReadme(name, dir, ui, files) { + const file = `plugins/${name}/README.md`; + const readme = read(join(dir, "README.md"), "utf8"); + if (readme === undefined) return; + const words = proseWords(readme); + if (words < MIN_WORDS) problem(file, `has ${words} words outside code blocks, under ${MIN_WORDS}`); + + const prefix = `plugins/${name}/`; + const images = files + .filter((path) => path.startsWith(prefix) && IMAGES.includes(extname(path).toLowerCase())) + .map((path) => path.slice(prefix.length)); + for (const { line, code } of markdownLines(readme)) { + const text = code ? line : line.replace(IMAGE, ""); + for (const image of images) { + if (text.includes(image)) problem(file, `names ${image} outside Markdown image syntax: ${line.trim()}`); + } + } + if (ui === undefined) return; + + const logo = typeof ui.logo === "string" ? ui.logo.replace(/^\.\//, "") : undefined; + const shown = [...readme.matchAll(IMAGE)].map((match) => match[1].replace(/^\.\//, "")); + if (logo !== undefined && !shown.includes(logo)) problem(file, `must show the icon as a Markdown image: ![…](${logo})`); + const prompts = examplePrompts(readme); + if (prompts.length < PROMPTS) { + problem(file, `needs at least ${PROMPTS} example prompts, in a list under "## Example prompts"`); + } else if (!isDeepStrictEqual(prompts.slice(0, PROMPTS), ui.defaultPrompt)) { + problem(file, `its first ${PROMPTS} example prompts must be plugin.json's interface.defaultPrompt, in order`); + } + for (const key of ["supportURL", "privacyPolicyURL"]) { + if (isText(ui[key]) && !readme.includes(ui[key])) problem(file, `must link interface.${key}, ${ui[key]}`); + } +} + +function checkLicense(name, dir) { + const license = read(join(dir, "LICENSE")); + if (license !== undefined && !license.equals(ROOT_LICENSE)) { + problem(`plugins/${name}/LICENSE`, "must be a copy of the root LICENSE, byte for byte"); + } +} + +// Every package that a skill or a README launches with npx, bunx, pnpm dlx or yarn dlx names one exact version, the +// same everywhere, and every other mention of that package with a version names the same one. Returns the pins. +function checkLaunchers(files) { + const texts = [ + ...files.filter((file) => /^plugins\/[^/]+\/(?:skills\/[^/]+\/SKILL\.md|README\.md)$/.test(file)), + "README.md", + ]; + const pins = new Map(); + for (const file of texts) { + const text = readFileSync(join(ROOT, file), "utf8"); + for (const match of text.matchAll(LAUNCHER)) { + const target = match[1].replace(/[.,;:!?]+$/, ""); + const pinned = target.match(PINNED); + if (pinned === null) { + problem(file, `launches ${target}: name one exact version, as @`); + continue; + } + const [, pkg, version] = pinned; + if (pins.has(pkg) && pins.get(pkg) !== version) { + problem(file, `launches ${pkg}@${version}, but another file launches ${pkg}@${pins.get(pkg)}`); + } else { + pins.set(pkg, version); + } + } + } + for (const file of texts) { + const text = readFileSync(join(ROOT, file), "utf8"); + for (const [pkg, version] of pins) { + const mention = new RegExp(`(? entry.isDirectory()) + .map((entry) => entry.name) + .sort(); +for (const name of names) { + const dir = join(PLUGINS, name); + const ui = checkManifests(name, dir); + checkReadme(name, dir, ui, files); + checkLicense(name, dir); +} +const pins = checkLaunchers(files); + +if (problems.length > 0) { + for (const text of problems) console.error(`error: ${text}`); + process.exit(1); +} +const launched = [...pins].map(([pkg, version]) => `${pkg}@${version}`).join(", ") || "none"; +console.log( + `Listings: ${names.join(", ")}: plugin.json within the portable schema and OpenAI's limits, and matching ` + + `.claude-plugin/plugin.json; brand colours ${colours.join(", ")} against white; icons, READMEs and licences in ` + + `place; launchers pinned: ${launched}.`, +); diff --git a/scripts/check-manifests.sh b/scripts/check-manifests.sh index ea286cf..57729ec 100644 --- a/scripts/check-manifests.sh +++ b/scripts/check-manifests.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash # Checks what `claude plugin validate` doesn't: the Codex marketplace's fields and paths, that each plugin's name # agrees in both marketplaces and both of its manifests, each skill's frontmatter, that the exclip plugin states -# ColdAI's fee wherever it describes itself, and that no description, launch-dex's skill or the README describes a fee -# split. Needs jq. Run from anywhere: bash scripts/check-manifests.sh +# ColdAI's fee wherever it describes itself, in the fee's fixed words in its skill, its README and its OpenAI listing, +# and that no description, listing text, launch-dex's skill, README or PRIVACY.md describes a fee split. Needs jq. Run +# from anywhere: bash scripts/check-manifests.sh set -euo pipefail cd "$(dirname "$0")/.." @@ -109,7 +110,8 @@ for skill in plugins/*/skills/*/SKILL.md; do if [ "${#description}" -gt 1024 ]; then fail "$skill" "description is longer than 1,024 characters"; fi done -# ColdAI's fee, wherever the exclip plugin describes itself, and the skill's fixed words for it. +# ColdAI's fee, wherever the exclip plugin describes itself, and the fixed words for it in the skill, the plugin's +# README and its OpenAI listing. descriptions=( "$CLAUDE_MARKETPLACE" "$(jq -r '.plugins[]? | select(.name == "exclip") | .description' "$CLAUDE_MARKETPLACE")" plugins/exclip/.claude-plugin/plugin.json "$(jq -r '.description' plugins/exclip/.claude-plugin/plugin.json)" @@ -120,20 +122,31 @@ for ((i = 0; i < ${#descriptions[@]}; i += 2)); do fail "${descriptions[i]}" "the exclip plugin's description must state ColdAI's fee, 0.875 %" fi done -for words in \ - "**ColdAI's fee: 0.875 % of the amount sold on every swap**" \ - "**Your DEX charges no fee of its own.**" \ - "**Your traders see the fee**" \ - "Do you agree to ColdAI's fee of 0.875 %? Please answer yes or no."; do - if ! grep -qF -- "$words" plugins/exclip/skills/launch-dex/SKILL.md; then - fail plugins/exclip/skills/launch-dex/SKILL.md "the fee's fixed words are missing: $words" +fee_words=( + "**ColdAI's fee: 0.875 % of the amount sold on every swap**" + "**Your DEX charges no fee of its own.**" + "**Your traders see the fee**" + "Do you agree to ColdAI's fee of 0.875 %? Please answer yes or no." +) +for file in plugins/exclip/skills/launch-dex/SKILL.md plugins/exclip/README.md; do + for words in "${fee_words[@]}"; do + if ! grep -qF -- "$words" "$file" 2> /dev/null; then + fail "$file" "the fee's fixed words are missing: $words" + fi + done +done +# OpenAI's listing is plain text, so it carries the same words without the bold. +listing=$(jq -r '.extensions."com.openai".interface.longDescription // ""' plugins/exclip/plugin.json) +for words in "${fee_words[@]}"; do + if [[ "$listing" != *"${words//\*\*/}"* ]]; then + fail plugins/exclip/plugin.json "interface.longDescription is missing the fee's fixed words: ${words//\*\*/}" fi done # launch-dex charges ColdAI's fee as a plain rate, so a fee split's terms appear nowhere that describes it: not in its -# skill, not in the README, and not in any plugin's or marketplace's description. +# skill, not in a README or PRIVACY.md, and not in any plugin's or marketplace's description or listing text. split_terms=("1.875" "operatorRate" "FeeSplitterFactory") -for file in plugins/exclip/skills/launch-dex/SKILL.md README.md; do +for file in plugins/exclip/skills/launch-dex/SKILL.md README.md plugins/*/README.md PRIVACY.md; do for words in "${split_terms[@]}"; do if grep -qF -- "$words" "$file"; then fail "$file" "mentions $words, a fee split's term: launch-dex charges ColdAI's fee as a plain rate" @@ -141,13 +154,14 @@ for file in plugins/exclip/skills/launch-dex/SKILL.md README.md; do done done for file in "$CLAUDE_MARKETPLACE" "$CODEX_MARKETPLACE" plugins/*/.claude-plugin/plugin.json plugins/*/plugin.json; do - text=$(jq -r '[.. | objects | .description? | strings] | join("\n")' "$file") + text=$(jq -r '[.. | objects | (.description?, .shortDescription?, .longDescription?, .defaultPrompt[]?) | strings] + | join("\n")' "$file") for words in "${split_terms[@]}"; do if [[ "$text" == *"$words"* ]]; then - fail "$file" "a description mentions $words, a fee split's term: launch-dex charges ColdAI's fee as a plain rate" + fail "$file" "a description or listing text mentions $words, a fee split's term: launch-dex charges ColdAI's fee as a plain rate" fi done done if [ "$failed" -ne 0 ]; then exit 1; fi -echo "Manifests: $(echo $folders) agree in both marketplaces and both manifests; skill frontmatter and fee wording in place; no fee split's terms." +echo "Manifests: $(echo $folders) agree in both marketplaces and both manifests; skill frontmatter and fee wording in place, in the skill, the exclip README and its OpenAI listing; no fee split's terms." diff --git a/scripts/pack-openai.sh b/scripts/pack-openai.sh new file mode 100644 index 0000000..a95ca17 --- /dev/null +++ b/scripts/pack-openai.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Packs each plugin for OpenAI's plugin directory: one ZIP per plugin, of the committed plugins/ folder, with +# plugin.json at the ZIP's root. Then it lists each ZIP and checks that it holds what the listing needs. It packs HEAD, +# so commit first. Needs git, jq and unzip. Run from anywhere: bash scripts/pack-openai.sh +set -euo pipefail + +cd "$(dirname "$0")/.." + +# What every ZIP must hold. A name that ends in / is a folder, which must hold at least one file. +REQUIRED=(plugin.json skills/ assets/icon.svg README.md LICENSE) +failed=0 + +fail() { + echo "error: $1: $2" >&2 + failed=1 +} + +if ! git diff --quiet HEAD -- plugins || [ -n "$(git ls-files --others --exclude-standard plugins)" ]; then + echo "warning: plugins/ has changes that aren't committed: the ZIPs hold HEAD, without them" >&2 +fi + +mkdir -p dist +zips=() +for name in $(git ls-tree -d --name-only HEAD:plugins); do + if ! manifest=$(git show "HEAD:plugins/$name/plugin.json" 2> /dev/null); then + fail "plugins/$name" "has no plugin.json in HEAD" + continue + fi + version=$(jq -r '.version // empty' <<< "$manifest") + if [ -z "$version" ]; then + fail "plugins/$name/plugin.json" "has no version" + continue + fi + zip=dist/$name-$version.zip + git archive --format=zip -o "$zip" "HEAD:plugins/$name" + contents=$(unzip -Z1 "$zip") + echo "$zip:" + sed 's/^/ /' <<< "$contents" + for entry in "${REQUIRED[@]}"; do + if [[ "$entry" == */ ]]; then + if ! grep -q "^${entry}.*[^/]$" <<< "$contents"; then fail "$zip" "has no file in $entry"; fi + elif ! grep -qxF -- "$entry" <<< "$contents"; then + fail "$zip" "has no $entry" + fi + done + zips+=("$zip") +done + +if [ "$failed" -ne 0 ]; then exit 1; fi +echo "Packed for OpenAI's plugin directory: ${zips[*]}, each with plugin.json, skills/, assets/icon.svg, README.md and LICENSE."