mirror of
https://github.com/superdesigndev/treg.git
synced 2026-10-02 03:24:35 +08:00
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:
co-authored by
Claude Fable 5
parent
59890876d8
commit
96b0c05e13
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
@@ -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
@@ -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
@@ -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),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user