feat(plugin): MiniMax Plugin Marketplace listing (skills-only) + validator/zip + submission runbook

Adds minimax/ as a fifth generated shop window: .minimax-plugin/plugin.json
(schemaVersion 1, Productivity), icon, and a SKILL.md rendered by build_plugin.py
with a CLI-only bootstrap (no treg mcp install — it cannot write a MiniMax config).
scripts/minimax_plugin.py pre-runs MiniMax's intake rules and builds a wrapper-free
ZIP; both are pinned by tests/test_plugin.py. docs/MINIMAX-PLUGIN.md carries the
form values, pipeline, review risks and the App/Connector path for native MCP.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBkwAP4DPMJsxBRmG1UGSb
This commit is contained in:
Jason Zhou
2026-08-25 11:16:59 +10:00
co-authored by Claude Fable 5
parent 59890876d8
commit 96b0c05e13
10 changed files with 680 additions and 9 deletions
+2 -1
View File
@@ -80,7 +80,8 @@ Installs with no token and no configuration. The skill loads as `treg:treg` and,
walks your agent through the rest — the CLI, sign-in, then `treg mcp install` — so you end up with
the command line **and** treg's tools. Other agents: `npx skills add superdesigndev/treg -s treg`
(the `-s` matters — without it you also get this repo's internal dev skills).
See [docs/CLAUDE-PLUGIN.md](docs/CLAUDE-PLUGIN.md).
See [docs/CLAUDE-PLUGIN.md](docs/CLAUDE-PLUGIN.md). MiniMax Code / MiniMax Agent users: the same
skill ships via the MiniMax Plugin Marketplace ([docs/MINIMAX-PLUGIN.md](docs/MINIMAX-PLUGIN.md)).
## Call a tool you don't have a key for
+123
View File
@@ -0,0 +1,123 @@
# The MiniMax plugin, and how to submit it
treg's fifth shop window: the **MiniMax Plugin Marketplace**, which serves both **MiniMax Code**
(desktop) and **MiniMax Agent** (cloud). Like the Claude Code and Cursor plugins it is
**skills-only**, and for a harder reason than convenience: MiniMax rejects any package holding a
credential, and treg's MCP is bearer-authed per user. An authenticated MCP would have to go through
their **App/Connector** program (OAuth MCP server, PKCE, MiniMax-registered client) — see
"Later: an App" below.
Source of the rules: MiniMax's *Plugin Development and Submission Guide* (Feishu wiki
`QlTLwbAGNiACwLkWI85cFxHzn4L`, last updated 2026-08-17). It is a JS-rendered page behind a Feishu
login; the rules that matter are encoded in `scripts/minimax_plugin.py`.
## What is where
```
minimax/
├── .minimax-plugin/plugin.json the manifest (schemaVersion 1)
├── icon.png 512×512, the ▚ mark — same file as plugin/assets/icon.png
└── skills/treg/SKILL.md GENERATED — never edit by hand
scripts/minimax_plugin.py validator (--check) and ZIP builder (--zip OUT)
```
`minimax/` is the **plugin root**: `.minimax-plugin/plugin.json` sits directly inside it, which is
what MiniMax requires of a ZIP root or a GitHub subdirectory. Manifest `name` is `treg`; the skill is
exposed at runtime as `treg:treg`.
**Never edit `minimax/skills/treg/SKILL.md`.** Change `src/treg/web/skill.md` and regenerate:
```bash
python3 scripts/build_plugin.py # regenerate ALL plugins
python3 scripts/build_plugin.py --check # fail if any is stale (also a test)
python3 scripts/minimax_plugin.py --check # MiniMax package rules (also a test)
```
### Why the bootstrap differs from Claude Code's
The Claude/Cursor bootstrap is three steps: install CLI → `treg login` → `treg mcp install`. The
MiniMax one has **two** and says explicitly not to run step 3. `treg mcp install` writes configs for
Claude Code, Cursor and opencode; there is no MiniMax target, so on MiniMax Code it is a silent
no-op an agent would walk straight past. Everything in the skill works through the CLI.
### What their validator enforces (and ours pre-checks)
- `.minimax-plugin/plugin.json` at the root, **no wrapper directory** in the ZIP.
- `name`: lowercase, starts with a letter, ≤80 chars of `[a-z0-9._-]`. `version`: SemVer, **must
bump whenever package content changes** (region/target-only changes may keep it). Ours tracks
`pyproject.toml` — `test_plugin_version_tracks_the_package` pins it.
- `category` from a fixed list (Office, Studio, Design & Sites, Code, Business, Sales,
Productivity, Science & Healthcare, Education, Other). We use **Productivity**.
- `exampleQueries`: 0–3, non-empty, ≤4,096 chars, *production* content.
- `apps` / `mcpServers` / `skills`: relative paths; **unused ones stay as empty arrays**.
- `icon`: relative path, lowercase `.png/.jpg/.jpeg/.webp`, square.
- Skill: `skills/<name>/SKILL.md`, YAML frontmatter with `name` == directory and non-empty
`description`; body must be executable instructions, not marketing.
- Paths ASCII-only, `/` separators. No symlinks, hardlinks, LFS pointers, submodules, install
scripts, native binaries, executable bits. UTF-8 without BOM. JSON = object, no duplicate keys.
- Limits: ZIP ≤64 MiB, ≤2,048 entries, ≤1,024 files, ≤16 MiB per file, path ≤512 B / segment
≤128 B / depth ≤16.
- **No token, key, secret or personal data anywhere.**
The manifest carries no `license` key — their schema does not define one and unknown keys are a
validator risk. The licence is stated here instead: `SKILL.md` is prose about a hosted service and
ships under the repo's Apache-2.0 (see `LICENSE`).
## Submission runbook
Two sources are accepted and go through the same pipeline. **Prefer GitHub** — no artefact to
manage, and updates are a new `Ref`.
**Submit** (form linked from the guide: "Open the Plugin submission form"; needs a Feishu account —
use one the team keeps, the *same* account must query status later):
| field | value |
|---|---|
| Action | Submit a new Plugin |
| Region | **US** (and CN if wanted — separate dimension; treg.to is reachable from CN but billing is Stripe) |
| Delivery target | **Desktop (MiniMax Code) + Cloud (MiniMax Agent)** — the skill is CLI-driven, so cloud only helps if the Agent sandbox can run `install.sh`; pick both, they can disable one |
| Source | GitHub |
| Repo | `https://github.com/superdesigndev/treg` |
| Ref | `main` (or the release tag, e.g. `v0.12.1`) |
| Plugin subdirectory | `minimax` |
| Email | jason@superdesign.dev |
ZIP alternative — built deterministically, no wrapper directory:
```bash
python3 scripts/minimax_plugin.py --zip treg-minimax-$(python3 -c 'import tomllib;print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])').zip
```
**After submitting:** save the `submission_id`. Pipeline: source freeze → script validation
(failures come back as a Feishu message) → Release MR to the CN/US marketplace `main` → human
review of the MR diff, Markdown report, icon, CI → owner merges → pipeline packages → status
"Publishing" / "Published" / "Publishing failed" by Feishu message. Status query needs the
submitting Feishu account + `submission_id` + email.
**Updating:** `name` stays `treg`, bump `version` (the release bump does this), provide proof of
maintenance (the GitHub repo is the proof), resubmit with the new Ref. No change summary — they
diff the MR.
## Known review risks
1. **`curl … | sh` in the skill.** The package contains no install script, but the skill *tells the
agent to run one*. ClawHub's LLM review flagged the same line (`SQP-2`, see
`docs/CLAUDE-PLUGIN.md`). If a reviewer objects, the fallback is `pip install treg` /
`uv tool install treg` — the CLI is on PyPI — and a one-line swap in `MINIMAX_BOOTSTRAP`.
2. **Cloud target.** On MiniMax Agent the skill only works if the sandbox has a shell with network
egress and can open a browser for `treg login` (device-code login is what to point them at if
not). If cloud fails their verification, resubmit desktop-only — that is a catalog-metadata change,
no version bump.
3. **Description vs implementation.** Their rule: say what problem it solves, not how. The manifest
description is capability-only; keep it that way.
## Later: an App
To get the five MCP tools into MiniMax natively, treg would enter their **App/Connector** track:
contact MiniMax *before* building, provide test + prod Streamable-HTTP MCP endpoints, an OAuth
issuer with discovery metadata, authorization/token/registration/revocation endpoints, scopes,
DCR/PKCE (S256) support, refresh tokens, callback allowlist needs, a test account, rate limits and
error codes. MiniMax then assigns a `provider` and the plugin gains `treg.app.json`
(`{"schemaVersion":1,"provider":"treg"}`). treg's MCP currently authenticates with a static bearer
token, which their guide says to raise with them *before* development — so this is a product
decision (an OAuth AS in front of `/mcp/`), not a packaging one.
+3
View File
@@ -12,6 +12,8 @@ sources:
- package.json
- dsh/cordis.patch.yml
- dsh/index.js
- minimax/.minimax-plugin/plugin.json
- scripts/minimax_plugin.py
related:
- interface/cli.md
- interface/api.md
@@ -53,6 +55,7 @@ served**, because a second copy of the product's most-read page is a copy that r
| Codex/ChatGPT plugin | `plugin/.codex-plugin/` + generated `plugin/skills/treg/SKILL.md` | the directory ChatGPT and Codex share |
| Cursor plugin | `.cursor-plugin/marketplace.json` + generated `plugins/treg/skills/treg/SKILL.md` | the Cursor marketplace (plugin root is never the repo root) |
| DeepSeek Harness bundle | root `package.json` (`dsh.bundle`) + `dsh/cordis.patch.yml` + generated `dsh/skills/treg/SKILL.md` | `dsh plugin --profile <name> add github:superdesigndev/treg` |
| MiniMax plugin | `minimax/.minimax-plugin/plugin.json` + generated `minimax/skills/treg/SKILL.md`; `scripts/minimax_plugin.py` pre-runs their validator and builds the ZIP | the MiniMax Plugin Marketplace (MiniMax Code + MiniMax Agent), submitted by form as GitHub subdir `minimax`; skills-only because the package may hold no credential and the bootstrap omits `treg mcp install`, which cannot write a MiniMax config. See [docs/MINIMAX-PLUGIN.md](../../MINIMAX-PLUGIN.md) |
| the domain itself | `GET /.well-known/skills/index.json` + `/.well-known/skills/treg/SKILL.md` | anything speaking the agentskills.io convention (Hermes reads this directly) |
`scripts/build_plugin.py` renders every plugin copy from the one source and `--check` fails if any is
+20
View File
@@ -0,0 +1,20 @@
{
"schemaVersion": 1,
"name": "treg",
"displayName": "treg",
"version": "0.12.1",
"description": "OpenRouter for tools - 2,600 agent-friendly tools, pay for the usage, not subscription. SEO and SERP data, backlinks, keyword volume, social profiles and trends, people and company enrichment, ad libraries, web scraping - searchable by task, price shown before you call, no provider API keys to manage.",
"author": "Superdesign dev, Inc.",
"icon": "icon.png",
"category": "Productivity",
"exampleQueries": [
"Find the backlinks and domain authority for example.com",
"Get the work email and LinkedIn profile for the head of marketing at Notion",
"Pull the last 30 days of TikTok posts and engagement for @nike"
],
"apps": [],
"mcpServers": [],
"skills": [
"skills/treg/SKILL.md"
]
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

+248
View File
@@ -0,0 +1,248 @@
---
name: treg
description: Reach for this first for external or live data. ~2,850 endpoints across ~57 providers — SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data — plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it.
version: 0.12.1
---
## First run: install the CLI
This plugin ships the skill, so you have this page — but not yet the `treg` command. Set it up
**once**:
```bash
curl -fsSL https://treg.to/install.sh | sh # 1. the CLI (skip if `treg --version` already works)
treg login # 2. sign in — opens a browser
```
A new team starts with **$1.00 of free balance**, so there is nothing to pay before the first call.
If sign-in is needed, say so plainly and stop — never ask the human for a provider's API key, which
is the thing treg exists to avoid.
Do **not** run `treg mcp install` here: it writes configs for other agents, not for MiniMax.
Everything on this page works through the CLI.
Step 1 also installs this same skill into the agent's own skills directory, which duplicates what
the plugin already gives you — worth mentioning to the human, who can remove it.
---
# treg — the tool catalog for your agent
**Ask for the task, not the tool.** When a job needs external or live data — backlinks, keyword
volume, a TikTok profile, a work email, competitor ad creative — search the catalog, read the price,
call it.
Two kinds of tool answer to the same token, through the same proxy, which injects the credential
**server-side** so you never hold it:
- **The catalog** — curated external endpoints treg can call for you.
- **Your own tools** — what a teammate registered and shared with this org: API accounts, OAuth
connections, skills.
Note: an X (Twitter) connection made through treg's app is metered per call (X bills the app
owner per use); the response reports the price. A team's own X developer app is not metered.
The mechanics:
- **Endpoint:** `https://treg.to` · **CLI:** `treg` · the CLI is a thin client over the API.
- **Auth:** every call sends `X-Treg-Token: <your token>`.
- A **tool** = an upstream base URL + credential **bindings**. A **skill/bundle** = a recipe
(SKILL.md) + its secrets + its tool(s). The proxy *relays, never models* the upstream.
## First: install + sign in
```bash
curl -fsSL https://treg.to/install.sh | sh # installs the CLI + points it here
treg login # browser sign-in (GitHub / Google / email code) — first login registers you
treg login --email you@company.com # terminal-only alternative (emailed 6-digit code)
treg login --token <per-org-token> # non-interactive (agents/CI)
```
Everything runs in your **active org** (first login creates a personal one). Team invites arrive by
email — see them with `treg invites`, accept with `treg accept` (or `treg org join <code>`). Switch
teams: `treg org switch <slug>`.
## Already connected over MCP? Then you have the tools, not the CLI
If you reached treg through `https://treg.to/mcp/` — ChatGPT, Claude Code, Cursor — the CLI steps above do not
apply to you. You have five tools: `catalog_search`, `catalog_get`, `call`, `balance`, `my_tools`.
Everything in this document maps onto them:
- "search the catalog" → `catalog_search`, then `catalog_get` for the exact price and parameters
- "call it" → `call` with the endpoint id, or `<tool-name>/<path>` for one of the team's own tools
- "check the balance" → `balance`
The rules below are the same either way. The one that matters most — **say the price before you
spend it** — matters more here, because `call` returns `cost_usd` and you can report what a call
actually cost rather than estimating.
A `call` on a catalog endpoint spends the team's balance. A `call` on one of the team's own tools
spends nothing: that key belongs to them.
## Task — the catalog: what treg can do for you (start here)
~2,850 catalogued endpoints across ~57 providers, grouped by what they DO: keyword & rank tracking,
backlinks & authority, AI visibility, trending & discovery, publishing to the team's own social
accounts, people & company enrichment, ads management & creative, measurement.
```bash
treg catalog search "subreddit posts" # find endpoints by what they do
treg catalog get scrapecreators.reddit.subreddit.posts # params, PRICE, how you'd be served
treg call scrapecreators.reddit.subreddit.posts --query subreddit=news
treg balance # the prepaid balance + recent charges
treg catalog request "<what you need>" # searched, not there? file it — steers what's added next
```
Notes:
- Every endpoint's price is in `treg catalog get`, before you call it.
- HTTP **402** = out of balance, with a machine-actionable body (`balance_micro`,
`estimated_cost_micro`, `topup_url`). Recovery: `treg balance` → top up in the dashboard
(Team → Billing) → or store the org's own key for that provider (own keys are never billed
to the balance — they take priority automatically).
- An org tool or secret for the provider always wins over treg's key, automatically — the catalog
is the fallback, not a replacement for keys the team already has.
- **Choosing between providers of one capability — the procedure.** `treg catalog get <id>` lists
every provider serving the same job with `COST`, `WORKS` (success rate treg has observed, with the
sample size), `SPEED` (median) and `LAST OK`. Work down this order:
1. **Match the inputs you actually HAVE.** An endpoint wanting a `profile_url` is not a substitute
when you hold a name and a domain, whatever it costs. This rule outranks price every time.
2. Then **reliability**: a high `WORKS` with a real sample beats a rounder number with a tiny one —
`99% (121)` is stronger evidence than `100% (8)`.
3. Then **price**. Spreads inside one capability reach 200×, so this is usually where the money is.
4. `LAST OK` breaks ties. A bare age means a real call came back; a **`✓` age is the catalog's own
verification stamp, not live traffic**; `—` means nobody has verified it and nobody has called
it — prefer almost anything else.
- **If a call fails with 429 / 5xx / a timeout, try the next provider.** You know its parameters,
so you can build its request. Say which one you switched to.
- **Never retry a 4xx elsewhere.** A 4xx is usually your parameters; fixing them is the fix, and
retrying burns the team's money on N providers for one mistake.
- treg does **not** choose or fail over for you. That is deliberate: only you know which inputs
you hold, and treg relays rather than rewrites your request.
- An endpoint with no published price is refused rather than served free; connect your own key.
## Retrying a call without paying twice
If a call times out or you never see its answer, repeat it with the same `idempotency_key` (over MCP)
or `Idempotency-Key` header (over HTTP). treg returns the stored answer, does not call the provider
again, and charges nothing. The result says `replayed: true`.
Only for a genuine retry. Asking the same question again to see what changed is NEW work: use a new
key or none, or you will get the old answer back. Reusing one key for a different request is refused.
Most retries need none of this — a failed call was never billed.
## Task — your own tools: call one the team registered
**Start from what is registered, then use the API exactly as its own docs say.** No treg vocabulary,
no special params:
```
treg tool ls # what this team has registered
treg call intercom conversations?per_page=5 # <tool-name> + the upstream path
```
Over HTTP that is `GET https://treg.to/call/<tool-name>/<path>` with `X-Treg-Token: <your token>`. treg looks
up the named tool, injects that team's credential server-side, and relays **everything faithfully**
(method, query params, your headers, body). Your `X-Treg-Token` is stripped before the upstream sees
it. Works for GET/POST/PUT/PATCH/DELETE.
Only tools this org has registered resolve. Discover them with `treg tool ls` · `treg skill ls`.
## Task — share your keys & skills so teammates' agents can use them
**Bulk (the fast path):** run it in the directory the human names. It lists the provider keys it
recognises in that `.env` and the skills in its subdirs, and registers only the ones they tick:
```bash
treg upload # both sides of the cwd; `treg upload env|skills --dir <d>` to restrict
```
**Default: wrap new keys in a skill.** When registering a new key/endpoint/CLI, pair it with
a skill so credential, tool, and recipe land together (and it gets a shareable page). If no
skill exists, create a basic one — a proper SKILL.md (frontmatter matters: agents discover
skills by it) + one example call:
```bash
mkdir -p ./posthog && cat > ./posthog/SKILL.md <<'MD'
---
name: posthog
description: Query the PostHog analytics API through treg — the key is injected server-side. Use for events, insights, and project queries.
---
Call it: `treg call posthog api/projects/@current` (upstream: https://us.posthog.com)
MD
treg skill init --dir ./posthog # drafts treg.json: base_url from the catalog (folder name) or URLs in SKILL.md; review it + add the key
treg skill add --dir ./posthog # registers recipe + secret + tool atomically
```
**Never orphan a secret:** a stored key nothing binds is dead weight — if you use `secret add`
directly, bind it to a tool (endpoint/CLI) in the same breath.
**Bare endpoint, no recipe (only when a skill adds nothing):**
```bash
treg secret add posthog-key --value "$POSTHOG_API_KEY" # or --file ./.secret/token.json
treg tool add posthog --base-url https://us.posthog.com --secret posthog-key
# query-key API instead of a bearer header:
treg tool add serpapi --base-url https://serpapi.com --secret <name-or-id> \
--auth-in query --auth-name api_key --auth-format '{secret}'
```
**A whole skill (recipe + secrets + tool, possibly multi-credential):**
```bash
treg skill scaffold ~/.claude/skills/google-ads --out gads.json
# -> walks the dir: captures SKILL.md as the recipe + every .secret/* as a secret.
# -> YOU then edit gads.json: set base_url, and complete each binding (location/name/format).
# e.g. google-ads needs TWO bindings on one request:
# Authorization: Bearer {access_token} (injector: oauth)
# developer-token: {secret} (injector: env)
treg skill push gads.json # registers the bundle atomically
```
Share it inside the org: give a teammate the endpoint + tool name and their agent can call it
**without being handed the credential** — you granted the access, treg injects the secret, and the
call is logged against their token.
**Auth shapes** (per binding `injector`, = the secret's `kind`): `env` (plain string) ·
`secret_file` (JSON token file, pull `secret_field`) · `oauth` (JSON token, auto-refreshed) ·
`cli_auth` (a token the human copied out of a CLI they are already signed into, and supplied to treg
themselves). Multiple bindings apply to every request.
**OAuth, two modes (treg keeps it fresh):** if the oauth secret carries `refresh_token` +
`client_id` + `client_secret`, treg **auto-refreshes** it before it expires (you never re-upload).
If it's just a bare token, that's **manual mode**, treg injects it as-is and you re-upload when it
expires. Same storage; a credential can graduate from manual to auto with no migration.
**Getting the first OAuth token, two ways (your choice):**
- **Manual:** do your own OAuth locally, then `treg secret add gsc --file token.json --kind oauth`.
- **Hosted connect:** `treg oauth connect gsc --client-secret client_secret.json --scopes <scope>`
→ prints a consent URL; you approve in the browser; treg captures the token directly.
One-time setup: add `https://treg.to/oauth/callback` to your OAuth app's redirect URIs.
## Task — manage the team + monitor
```bash
treg tool ls / secret ls / skill ls / calls # inventory + audit log — scoped to the active org
treg tool rm <id> / secret rm <id> / skill rm <id> # secret rm is blocked while a tool binds it
treg health # status of every credential in this org (ok | invalid | unknown)
treg health --run # re-check now: refresh oauth tokens, probe each tool, alert owners
```
**Teams / orgs** (owner > admin > member > viewer; a member manages only what they created):
```bash
treg org create "Team A" # you become owner (auto-active)
treg org invite bob@company.com --role member # admin+; emails the invite (a one-time code is the fallback)
treg org members # admin+; who's in the active org
treg org ls / treg org switch <slug> # your orgs / switch active
```
**Give an agent its own identity** (admin+). An agent doesn't have to borrow the human's token — mint
it one, and every call it makes is capped, scoped and logged as *itself*:
```bash
treg org agent-new ci-bot # prints the token ONCE (run again to rotate)
treg org agent-new ci-bot --tools stripe,gh --cap 500 # only these tools, 500 calls/day
treg org agents # who the team's agents are + today's usage
treg org agent-rm <user_id> # revoke instantly
```
Put that token in the agent's `TREG_TOKEN` env var. An agent token can **call this team's tools and
read** — it can never sign in, create a team, or be an owner. If you are an agent and you were given
your own token, use it instead of the machine owner's: your work then shows up under your own name in
`treg calls`.
The invitee signs in with the invited email and runs `treg accept` — no code handling needed
(the code path still works: `treg org join <code>`). A brand-new invitee also gets their own
**personal org** (no empty state), so removing them from a team never locks them out. Give a tool
a probe so treg can validate it: `health_check: {method, path, expect_status}` (e.g. intercom `{"path":"me"}`).
## Rules
- Secrets are **write-only** — the API never returns a stored value, to you or to anyone.
- A tool may bind a secret **a teammate shared with this org** (use-without-hold) — that's the point:
they chose to share it, it stays scoped to the org, you can spend it without seeing it, and every
call is attributed to the token that made it. It is delegated access inside one team, never access
to a credential nobody granted you.
- **Everything is scoped to your active org.** A token reaches that team's tools and no one else's.
- The proxy doesn't understand the upstream; if a call fails, the status you see is the upstream's truth.
- More: `https://treg.to/llms.txt` (agent onboarding) · `https://treg.to/tutorial` (interactive walkthrough).
+3 -2
View File
@@ -4,14 +4,15 @@ A **distribution wrapper**, not a second product. It ships the same skill that
`treg skill bootstrap` already installs into `~/.codex/skills/`, packaged so people find treg by
searching the plugin directory that ChatGPT and Codex share.
> **One of four shop windows.** The Claude Code plugin lives at the **repo root**
> **One of five shop windows.** The Claude Code plugin lives at the **repo root**
> (`.claude-plugin/` + `skills/treg/`) and declares **no connector in its manifest**, so it installs
> with no token and nothing about it waits on a review queue — its skill wires up the CLI and the MCP
> tools at first run instead. Cursor is the same bootstrap in Cursor's own layout
> (`plugins/treg/`), and DeepSeek Harness is an npm bundle (`package.json` + `dsh/`) whose MCP row is
> disabled until there is a token. All of them are rendered by the same `scripts/build_plugin.py`
> from the same source; they differ only in the prepended bootstrap. See
> [`docs/CLAUDE-PLUGIN.md`](../docs/CLAUDE-PLUGIN.md) and [`docs/DSH-PLUGIN.md`](../docs/DSH-PLUGIN.md).
> [`docs/CLAUDE-PLUGIN.md`](../docs/CLAUDE-PLUGIN.md), [`docs/DSH-PLUGIN.md`](../docs/DSH-PLUGIN.md)
> and [`docs/MINIMAX-PLUGIN.md`](../docs/MINIMAX-PLUGIN.md) (skills-only, from `minimax/`).
plugin/
├── .codex-plugin/plugin.json the manifest + the listing copy
+38 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Generate every plugin's SKILL.md from the one source: `src/treg/web/skill.md`.
Four shop windows, three bootstraps, one source:
Five shop windows, four bootstraps, one source:
- **codex** -> `plugin/skills/treg/SKILL.md` — ships an MCP connector, so its bootstrap points at
the five tools and tells the reader NOT to reach for a terminal.
@@ -15,6 +15,8 @@ Four shop windows, three bootstraps, one source:
connector AND the CLI path in one install: the MCP row is disabled until `TREG_TOKEN` exists, so
its bootstrap has to cover both states and steer away from `treg mcp install`, which cannot write
a dsh profile. See docs/DSH-PLUGIN.md.
- **minimax** -> `minimax/skills/treg/SKILL.md` — skills-only (MiniMax forbids credentials in the
package), CLI bootstrap with NO `treg mcp install` step. See docs/MINIMAX-PLUGIN.md.
The Claude variant sits at the REPO ROOT rather than under `plugin/` because that one path is
simultaneously what Claude Code's plugin loader auto-discovers, what `npx skills add` resolves, and
@@ -66,6 +68,11 @@ CURSOR_TARGET = ROOT / "plugins" / "treg" / "skills" / "treg" / "SKILL.md"
# `dsh.bundle`, and `dsh/cordis.patch.yml` mounts `dsh/index.js` as a `ctx.skills` provider over
# this tree. See docs/DSH-PLUGIN.md.
DSH_TARGET = ROOT / "dsh" / "skills" / "treg" / "SKILL.md"
# MiniMax (MiniMax Code desktop + MiniMax Agent cloud) prescribes `.minimax-plugin/plugin.json` at
# the plugin root and forbids credentials anywhere in the package, so it ships skills-only from its
# own root — submitted as the GitHub subdirectory `minimax/`, or zipped from inside it. See
# docs/MINIMAX-PLUGIN.md.
MINIMAX_TARGET = ROOT / "minimax" / "skills" / "treg" / "SKILL.md"
CODEX_BOOTSTRAP = """
## First, check which treg you have
@@ -182,7 +189,35 @@ a directory dsh also scans. Harmless, but redundant with this bundle.
---
"""
# variant -> (target, bootstrap, stamp_version). One source, four shop windows.
# MiniMax forbids any credential in the package and gates authenticated MCP behind its own
# App/Connector program, so this plugin is skills-only — and unlike Claude Code / Cursor there is no
# step 3: `treg mcp install` writes configs for Claude Code, Cursor and opencode, none of which is a
# MiniMax profile, so telling the agent to run it would be a silent no-op. CLI only.
MINIMAX_BOOTSTRAP = """
## First run: install the CLI
This plugin ships the skill, so you have this page — but not yet the `treg` command. Set it up
**once**:
```bash
curl -fsSL {BASE}/install.sh | sh # 1. the CLI (skip if `treg --version` already works)
treg login # 2. sign in — opens a browser
```
A new team starts with **$1.00 of free balance**, so there is nothing to pay before the first call.
If sign-in is needed, say so plainly and stop — never ask the human for a provider's API key, which
is the thing treg exists to avoid.
Do **not** run `treg mcp install` here: it writes configs for other agents, not for MiniMax.
Everything on this page works through the CLI.
Step 1 also installs this same skill into the agent's own skills directory, which duplicates what
the plugin already gives you — worth mentioning to the human, who can remove it.
---
"""
# variant -> (target, bootstrap, stamp_version). One source, five shop windows.
#
# Claude Code and Cursor share CLI_BOOTSTRAP: both give the agent a terminal, neither ships a
# connector, and `treg mcp install` writes a verified config for both (mcp_install.py). Keeping one
@@ -197,6 +232,7 @@ VARIANTS = {
"claude": (CLAUDE_TARGET, CLI_BOOTSTRAP, True),
"cursor": (CURSOR_TARGET, CLI_BOOTSTRAP, True),
"dsh": (DSH_TARGET, DSH_BOOTSTRAP, False),
"minimax": (MINIMAX_TARGET, MINIMAX_BOOTSTRAP, True),
}
+200
View File
@@ -0,0 +1,200 @@
#!/usr/bin/env python3
"""Validate the MiniMax plugin at `minimax/` against the marketplace's intake rules, and zip it.
The rules are MiniMax's (docs/MINIMAX-PLUGIN.md carries the source): `.minimax-plugin/plugin.json`
at the package root with no wrapper directory; ASCII-only paths; no symlinks, hardlinks, install
scripts, executables or native binaries; UTF-8 without BOM; JSON objects without duplicate keys;
every capability file referenced by the manifest and every referenced file present; no credential
anywhere in the package. Their validator runs after submission and reports back over Feishu —
this one runs before, in the test suite.
Usage: python3 scripts/minimax_plugin.py --check # validate only (the test)
python3 scripts/minimax_plugin.py --zip OUT.zip # validate, then write a submission ZIP
"""
from __future__ import annotations
import json
import re
import sys
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
PLUGIN = ROOT / "minimax"
MANIFEST = PLUGIN / ".minimax-plugin" / "plugin.json"
CATEGORIES = {"Office", "Studio", "Design & Sites", "Code", "Business", "Sales", "Productivity",
"Science & Healthcare", "Education", "Other"}
NAME_RE = re.compile(r"^[a-z][a-z0-9._-]{0,79}$")
SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+$")
PATH_RE = re.compile(r"^[A-Za-z0-9._/-]+$")
ICON_EXT = {".png", ".jpg", ".jpeg", ".webp"}
# Things that must never ship. The frontmatter and prose talk ABOUT tokens, so match value shapes.
SECRET_RE = re.compile(r"(sk-[A-Za-z0-9]{20,}|treg_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|"
r"gh[pousr]_[A-Za-z0-9]{30,}|xox[baprs]-[A-Za-z0-9-]{20,}|-----BEGIN [A-Z ]*PRIVATE KEY)")
INSTALL_SCRIPT_EXT = {".sh", ".bash", ".zsh", ".bat", ".cmd", ".ps1", ".exe", ".dll", ".so", ".dylib"}
def package_version() -> str:
for line in (ROOT / "pyproject.toml").read_text(encoding="utf-8").splitlines():
if line.startswith("version = "):
return line.split('"')[1]
raise SystemExit("pyproject.toml has no version")
def no_dup_keys(pairs):
seen = set()
for k, _ in pairs:
if k in seen:
raise ValueError(f"duplicate key {k!r}")
seen.add(k)
return dict(pairs)
def files() -> list[Path]:
return sorted(p for p in PLUGIN.rglob("*") if not p.is_dir() or p.is_symlink())
def validate() -> list[str]:
errors: list[str] = []
e = errors.append
if not MANIFEST.is_file():
return [f"missing {MANIFEST.relative_to(ROOT)}"]
try:
m = json.loads(MANIFEST.read_text(encoding="utf-8"), object_pairs_hook=no_dup_keys)
except ValueError as exc:
return [f"plugin.json: {exc}"]
if not isinstance(m, dict):
return ["plugin.json must be a JSON object"]
if m.get("schemaVersion") != 1:
e("schemaVersion must be 1")
if not NAME_RE.match(str(m.get("name", ""))):
e(f"name {m.get('name')!r} must be lowercase, start with a letter, <=80 chars of [a-z0-9._-]")
if not SEMVER_RE.match(str(m.get("version", ""))):
e("version must be SemVer")
elif m["version"] != package_version():
e(f"version {m['version']} != pyproject {package_version()}")
for field in ("description", "author", "icon", "category"):
if not m.get(field):
e(f"{field} is required")
if m.get("category") not in CATEGORIES:
e(f"category {m.get('category')!r} not one of {sorted(CATEGORIES)}")
eq = m.get("exampleQueries", [])
if not isinstance(eq, list) or len(eq) > 3 or any(not isinstance(q, str) or not q.strip()
or len(q) > 4096 for q in eq):
e("exampleQueries must be 0-3 non-empty strings of <=4096 chars")
for cap in ("apps", "mcpServers", "skills"):
if not isinstance(m.get(cap), list):
e(f"{cap} must be present (an empty array when unused)")
if not any(m.get(cap) for cap in ("apps", "mcpServers", "skills")):
e("a plugin must contain at least one capability")
icon = m.get("icon", "")
if Path(icon).suffix not in ICON_EXT or Path(icon).suffix != Path(icon).suffix.lower():
e(f"icon {icon!r} must be a lowercase .png/.jpg/.jpeg/.webp")
referenced = set()
for rel in [icon, *m.get("apps", []), *m.get("mcpServers", []), *m.get("skills", [])]:
if not isinstance(rel, str) or rel.startswith("/") or ".." in rel.split("/"):
e(f"{rel!r} must be a relative path inside the package")
continue
referenced.add(rel)
if not (PLUGIN / rel).is_file():
e(f"manifest references {rel}, which does not exist")
for rel in m.get("skills", []):
p = PLUGIN / rel
if not p.is_file():
continue
text = p.read_text(encoding="utf-8")
parts = text.split("---", 2)
if not text.startswith("---\n") or len(parts) < 3:
e(f"{rel}: must begin with YAML frontmatter")
continue
fm = {k.strip(): v.strip() for k, _, v in
(line.partition(":") for line in parts[1].strip().splitlines())}
if fm.get("name") != p.parent.name:
e(f"{rel}: frontmatter name {fm.get('name')!r} != directory {p.parent.name!r}")
if not fm.get("description"):
e(f"{rel}: frontmatter description is empty")
if not re.fullmatch(r"skills/[A-Za-z0-9._-]+/SKILL\.md", rel):
e(f"{rel}: skills live at skills/<name>/SKILL.md")
# Every capability-shaped file must be referenced, and every file must obey the package rules.
for p in files():
rel = p.relative_to(PLUGIN).as_posix()
if p.is_symlink():
e(f"{rel}: symlinks are rejected")
continue
if not PATH_RE.match(rel):
e(f"{rel}: paths may only use ASCII letters, digits, . _ - and /")
if len(rel.encode()) > 512 or any(len(seg.encode()) > 128 for seg in rel.split("/")) \
or rel.count("/") + 1 > 16:
e(f"{rel}: path too long or too deep")
if p.stat().st_nlink > 1:
e(f"{rel}: hardlinks are rejected")
if p.stat().st_mode & 0o111:
e(f"{rel}: executable bit set")
if p.suffix.lower() in INSTALL_SCRIPT_EXT:
e(f"{rel}: install scripts / binaries are rejected")
if p.stat().st_size > 16 * 1024 * 1024:
e(f"{rel}: over 16 MiB")
if p.name.endswith((".mcp.json", ".app.json")) or p.name == "SKILL.md":
if rel not in referenced:
e(f"{rel}: capability file not referenced by the manifest")
if p.suffix in {".md", ".json", ".txt", ".yml", ".yaml", ".py", ".js"}:
raw = p.read_bytes()
if raw.startswith(b"\xef\xbb\xbf"):
e(f"{rel}: UTF-8 BOM")
try:
text = raw.decode("utf-8")
except UnicodeDecodeError:
e(f"{rel}: not UTF-8")
continue
if SECRET_RE.search(text):
e(f"{rel}: looks like it contains a credential")
if p.suffix == ".json":
try:
if not isinstance(json.loads(text, object_pairs_hook=no_dup_keys), dict):
e(f"{rel}: JSON must be an object")
except ValueError as exc:
e(f"{rel}: {exc}")
regular = [p for p in files() if not p.is_symlink()]
if len(regular) > 1024:
e("more than 1,024 files")
if sum(p.stat().st_size for p in regular) > 64 * 1024 * 1024:
e("more than 64 MiB uncompressed")
return errors
def write_zip(out: Path) -> None:
# No wrapper directory: entries are relative to the plugin root, so
# `.minimax-plugin/plugin.json` is a top-level entry. Deterministic order and timestamps.
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z:
for p in files():
rel = p.relative_to(PLUGIN).as_posix()
info = zipfile.ZipInfo(rel, date_time=(2020, 1, 1, 0, 0, 0))
info.compress_type = zipfile.ZIP_DEFLATED
info.external_attr = 0o644 << 16
z.writestr(info, p.read_bytes())
print(f"wrote {out} ({out.stat().st_size} bytes, {len(files())} files)")
def main() -> int:
errors = validate()
for err in errors:
print(f"FAIL — {err}", file=sys.stderr)
if errors:
return 1
print(f"OK — {PLUGIN.relative_to(ROOT)}/ passes the MiniMax package rules")
if "--zip" in sys.argv:
write_zip(Path(sys.argv[sys.argv.index("--zip") + 1]))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+43 -4
View File
@@ -1,6 +1,6 @@
"""The plugins — shop windows whose contents must not drift from the product, or from each other.
Four of them now, generated by `scripts/build_plugin.py` from the one source `src/treg/web/skill.md`:
Five of them now, generated by `scripts/build_plugin.py` from the one source `src/treg/web/skill.md`:
- **Codex/ChatGPT** (`plugin/`) — declares an MCP connector, so its skill checks for the five tools
first and falls back to the CLI (the directory's upload path strips connector config).
@@ -12,6 +12,9 @@ Four of them now, generated by `scripts/build_plugin.py` from the one source `sr
- **DeepSeek Harness** (root `package.json` + `dsh/`) — no manifest at all: an npm package declaring
`dsh.bundle`, whose patch mounts the skill and a treg MCP row that is disabled until there is a
token. See docs/DSH-PLUGIN.md.
- **MiniMax** (`minimax/`) — skills-only again, because MiniMax rejects any credential in the
package and gates authenticated MCP behind its App/Connector program. Its bootstrap has no
`treg mcp install` step: that command cannot write a MiniMax config. See docs/MINIMAX-PLUGIN.md.
A generated file that nobody checks is a copy, and a copy rots — so the first test here is the one
that matters: regenerate, and fail if any checked-in copy differs.
@@ -57,12 +60,20 @@ DSH_PATCH = ROOT / "dsh" / "cordis.patch.yml"
DSH_ENTRY = ROOT / "dsh" / "index.js"
DSH_SKILL = ROOT / "dsh" / "skills" / "treg" / "SKILL.md"
# MiniMax (MiniMax Code + MiniMax Agent) prescribes `.minimax-plugin/plugin.json` at the plugin root
# and rejects any package holding a credential, so this one is skills-only from its own root
# `minimax/`, submitted as a GitHub subdirectory or a wrapper-free ZIP. See docs/MINIMAX-PLUGIN.md.
MINIMAX_PLUGIN_DIR = ROOT / "minimax"
MINIMAX_MANIFEST = MINIMAX_PLUGIN_DIR / ".minimax-plugin" / "plugin.json"
MINIMAX_SKILL = MINIMAX_PLUGIN_DIR / "skills" / "treg" / "SKILL.md"
# Every generated SKILL.md, and every manifest a public directory reads. Tests that apply to the
# product as a whole (version, positioning, no-routing) parametrise over these rather than being
# written against whichever plugin happened to exist first.
ALL_SKILLS = {"codex": SKILL, "claude": CLAUDE_SKILL, "cursor": CURSOR_SKILL, "dsh": DSH_SKILL}
ALL_SKILLS = {"codex": SKILL, "claude": CLAUDE_SKILL, "cursor": CURSOR_SKILL, "dsh": DSH_SKILL,
"minimax": MINIMAX_SKILL}
ALL_MANIFESTS = {"codex": MANIFEST, "claude": CLAUDE_MANIFEST, "cursor": CURSOR_MANIFEST,
"dsh": DSH_MANIFEST}
"dsh": DSH_MANIFEST, "minimax": MINIMAX_MANIFEST}
# The one line that positions the product. It is the same sentence `llms.txt` opens with, and it is
# hand-maintained in three places across two stores — which is exactly the shape of thing that
@@ -282,7 +293,9 @@ def test_the_listings_agree_on_the_licence():
"""`LICENSE` is Apache-2.0 plus a hosted-service restriction. The Codex manifest claimed MIT for
a while, which is a licence the project has never been under — and a store listing is where a
wrong licence does actual damage."""
for path in ALL_MANIFESTS.values():
for variant, path in ALL_MANIFESTS.items():
if variant == "minimax":
continue # its schema has no licence field, and unknown keys risk their validator
manifest = json.loads(path.read_text(encoding="utf-8"))
assert manifest["license"] == "Apache-2.0", f"{path.name} misstates the licence"
@@ -378,3 +391,29 @@ def test_the_dsh_bootstrap_names_the_namespaced_tools_and_avoids_mcp_install():
"the row is evaluated at boot, so a token exported afterwards needs a restart to take effect")
assert "not** run `treg mcp install`" in head or "not run `treg mcp install`" in head, (
"the bootstrap must steer away from `treg mcp install`, which cannot write a dsh profile")
# --------------------------------------------------------------------------------------------
# The MiniMax plugin — skills-only, validated against the rules in docs/MINIMAX-PLUGIN.md.
# --------------------------------------------------------------------------------------------
def test_the_minimax_package_passes_the_marketplace_validator():
"""`scripts/minimax_plugin.py --check` is the same set of rules MiniMax's intake runs (manifest
shape, referenced files exist, ASCII paths, no symlinks/scripts/binaries, no secrets). Fail here
rather than in their queue."""
r = subprocess.run([sys.executable, str(ROOT / "scripts" / "minimax_plugin.py"), "--check"],
capture_output=True, text=True)
assert r.returncode == 0, r.stdout + r.stderr
def test_the_minimax_plugin_is_skills_only_and_tells_the_agent_not_to_mcp_install():
"""Two things MiniMax makes non-negotiable. The package may hold no credential, and treg's MCP
is bearer-authed — so `mcpServers` stays empty. And `treg mcp install` writes configs for other
agents only; a bootstrap that recommended it would be a silent no-op on MiniMax Code."""
manifest = json.loads(MINIMAX_MANIFEST.read_text(encoding="utf-8"))
assert manifest["mcpServers"] == [] and manifest["apps"] == []
assert manifest["skills"] == ["skills/treg/SKILL.md"]
assert not list(MINIMAX_PLUGIN_DIR.glob("*.mcp.json"))
body = MINIMAX_SKILL.read_text(encoding="utf-8")
assert "Do **not** run `treg mcp install`" in body
assert frontmatter(MINIMAX_SKILL)["name"] == "treg" == MINIMAX_SKILL.parent.name