Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ The MiniMax Code Agent communicates through the host MCP client. Business reques
| [Anatomy Gallery](plugins/zch0227/anatomy-gallery/) | A gallery for interactive isometric figures made with the anatomy skill: hang them in rooms you organize, view light and dark editions full screen, keep every version, and have an Agent create or edit figures from the gallery with a per-run process log. The anatomy skill is bundled. Requires MiniMax Code 3.1.2 or later | [zch0227](https://github.com/zch0227) |
| [仓库透视 / Repo X-Ray](plugins/yujiachen-y/repo-xray/) | Understand a codebase from its Git history: a code map you can drill into, hotspots and bug magnets, high-risk files, owners and knowledge loss, commit rhythm and firefighting, and the tech stack. Works with GitHub, GitLab, and local repositories; analysis runs on your machine | [yujiachen-y](https://github.com/yujiachen-y) |
| [Disk Insight](plugins/zch0227/disk-insight/) | See where disk space went on macOS and Windows with a sunburst space map, find junk files sorted into three safety tiers, large and duplicate files, and how much code repositories and Git worktrees take; ask an Agent about any item, and permanently delete what you select after a confirmation that lists every item. Requires MiniMax Code 3.1.2 or later | [zch0227](https://github.com/zch0227) |
| [蹬王之王 · Expiry Guardian](plugins/hylouis233/expiry-guardian/) | Track credit and reset-card expiry from a local ledger you maintain, and get a "spend the card or let the credits burn first" recommendation with the reasoning spelled out | [Hylouis](https://github.com/Hylouis233) |

<details>
<summary>Preview: Token Usage Board</summary>
Expand Down
1 change: 1 addition & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ MiniMax Code Agent 通过宿主 MCP 客户端与 MiniApp 协作。业务请求
| [Anatomy 画廊](plugins/zch0227/anatomy-gallery/README.zh-CN.md) | 集中展示 anatomy 技能做出的交互式等距图解:按自建展区挂墙,全屏查看明暗两版,保留全部历史版本;在画廊里让 Agent 新建或修改作品,每次运行都有过程记录。自带 anatomy 技能。需要 MiniMax Code 3.1.2 或更高版本 | [zch0227](https://github.com/zch0227) |
| [仓库透视](plugins/yujiachen-y/repo-xray/README.zh-CN.md) | 从 Git 历史看懂一个代码仓库:可逐层下钻的代码地图、热点与 Bug 磁铁、高风险文件、负责人与知识流失、提交节奏与救火记录、技术栈;支持 GitHub、GitLab 和本机仓库,全部在本机分析 | [yujiachen-y](https://github.com/yujiachen-y) |
| [磁盘透视](plugins/zch0227/disk-insight/README.zh-CN.md) | 查看 macOS 和 Windows 的磁盘空间去向:旭日图空间地图,按三档风险分类的垃圾文件,大文件和重复文件,代码仓库和 Git worktree 的占用;任何条目都可以问 Agent,选中的条目在确认框里逐一列出,确认后直接永久删除。需要 MiniMax Code 3.1.2 或更高版本 | [zch0227](https://github.com/zch0227) |
| [蹬王之王](plugins/hylouis233/expiry-guardian/README.md) | 看你自己维护的本地账本,算出「该用卡还是该留积分」,并把理由说清楚 | [Hylouis](https://github.com/Hylouis233) |

<details>
<summary>预览:Token 用量看板</summary>
Expand Down
3 changes: 3 additions & 0 deletions plugins/hylouis233/expiry-guardian/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Python 字节码缓存(跑测试时会重新生成,不该进版本库)
__pycache__/
*.pyc
21 changes: 21 additions & 0 deletions plugins/hylouis233/expiry-guardian/.minimax-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"schemaVersion": 1,
"name": "expiry-guardian",
"displayName": "蹬王之王",
"version": "1.6.0",
"description": "实时追踪积分与重置卡的到期时间,额度打满时自动暂停所有进行中的目标、额度回落后自动恢复并叫醒;无人值守后台守护进程配合 Mini App 看板,按「先过期先用」自动使用重置卡,遇到 500/429 等可重试错误会一直重试。v1.6 修掉一个让 Bot 守护完全静默的真 bug(minimax-bot 状态文件里一个被截断的代理对转义导致严格 JSON 解析失败),并新增唤醒链路自检:宿主重启后运行时没起来时如实告警、投递空转时也能发现,绝不再「看起来一切正常、实际一个 bot 都没叫醒」。",
"author": "User",
"icon": "icon.png",
"category": "Productivity",
"exampleQueries": [
"打开蹬王之王,看我的积分和重置卡什么时候过期。",
"我的额度快用完了,该用重置卡还是留着?",
"看看我有多少积分要过期了。"
],
"apps": [],
"mcpServers": [
"servers.mcp.json"
],
"skills": [],
"darkIcon": "icon-dark.png"
}
21 changes: 21 additions & 0 deletions plugins/hylouis233/expiry-guardian/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Hylouis

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
210 changes: 210 additions & 0 deletions plugins/hylouis233/expiry-guardian/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# 蹬王之王 · Local Expiry Ledger & Card Decision

Track when your credits and reset cards expire, and decide — from a **ledger you keep
yourself** — whether to spend a card or let the credits burn first.

**v2.0 (trimmed) — 2026-10-20.**

## What it does

| | |
| --- | --- |
| **Local ledger** | The only data source: `~/.config/mcode-reset-card/ledger.json`, which *you* maintain. Edit it in the panel or in any editor. |
| **"Expiring first" decision** | When the 5-hour quota crosses the trigger line, it compares expiry dates and says whether to spend a card or keep the credits — with the reasoning spelled out. |
| **Honest failures** | If the ledger can't be read, it says so plainly instead of quietly concluding "you have nothing". |
| **Backend daemon** | A Rust daemon under `launchd` re-runs the same decision on a timer and writes the conclusion to a status file. |

## What it deliberately does *not* do

Community plugins must not touch the host's private data. So this version:

| Boundary | v1.x (removed) | v2.0 |
| --- | --- | --- |
| **No reading MiniMax Code's login credentials** | Read the lease socket / `.cap` for an access token and called the account APIs | **Doesn't.** The ledger is the only data source |
| **No reading/writing the host's internal database** | `UPDATE`/`DELETE`/`INSERT` on goals and the execution queue, and read turn records | **Doesn't touch it.** Only reads its own directory |
| **No reading other plugins' data directories** | Read `minimax-bot`'s `state.json` for bot and session ids, then messaged those sessions | **Doesn't.** The Bot watchdog is gone |

⇒ **v2.0 never spends a card, never pauses a goal, never wakes a Bot.** It computes the
conclusion and explains it; **you** do the action.

⇒ The upside: v2.0 is code that can live in a community repository. It touches no
private host data and its behaviour is fully predictable.

## The ledger

```json
{
"schemaVersion": 1,
"updatedAtMs": 1792480000000,
"quota": {
"fiveHour": { "usedPercent": 3, "totalPercent": 100, "unlimited": false, "resetTimeMs": 1792497600000 },
"weekly": { "usedPercent": 0, "totalPercent": 100, "unlimited": true, "resetTimeMs": 1792944000000 }
},
"cards": [
{ "cardId": "card_1", "label": "Card A", "sourceType": "code",
"outBizNo": "biz_1", "expireTimeMs": 1795000000000 }
],
"credits": [
{ "label": "Granted", "source": "code", "granted": 400, "used": 0,
"balance": 400, "expireTimeMs": 1795017600000 }
]
}
```

A card counts as usable only if it has **both** `outBizNo` and `sourceType`.
A credit counts only if `balance > 0` and it has not expired.

> ⚠️ **A missing `credits` field does not mean "no credits".**
> A missing field is treated as *incomplete data* ⇒ the conclusion stays "hold off".
> It will **never** recommend burning a real card because it couldn't read the data.

The panel has a ledger editor at the bottom — edit the JSON there and save, or edit
the file in any editor and hit refresh.

## The decision

| Situation | Conclusion |
| --- | --- |
| Quota below the trigger line | Hold — nothing to do |
| At the line, no usable credits, card available | **Spend a card** |
| At the line, no usable credits, no card | Hold |
| At the line, credits available, no card | **Keep the credits** |
| At the line, both, card expires first | **Spend a card** |
| At the line, both, credits expire first | **Keep the credits** |
| At the line, but the quota refreshes within N minutes | Hold — spending a card would be pure waste |
| Data incomplete | Hold — better no advice than wrong advice |

## Three-way consistency

The decision logic has **three isomorphic implementations**:

| Implementation | Where | Role |
| --- | --- | --- |
| Rust | `daemon/rust/src/decision.rs` | The backend daemon |
| Mini App | `miniapp/node/server.mjs` (`decide()`) | The dashboard |
| Python | `daemon/reset_card_autouse.py` (`decide_background()`) | The **reference** (pure, no network) |

`daemon/verify-parity.mjs` feeds all three the *same* cases and compares the
action **and the reason text, character for character**:

```
$ node daemon/verify-parity.mjs
19/19 三方完全一致(动作 + 理由文本逐字)
```

The reason text has to match character for character — change one side and you must
change all three, or the checker goes red. That check is the most valuable guard in
this project: it stops the three implementations from slowly drifting apart until
they contradict each other.

## Backend daemon

The Rust daemon runs under `launchd` and is **read-only**: it reads the ledger, runs
the decision, and writes the conclusion to the log and to
`~/.config/mcode-reset-card/daemon-status.json`. It performs **no write operations**.

If the ledger can't be read it says so explicitly ("no conclusion this round") and
escalates the warning as consecutive failures pile up — it never pretends the answer
is "you have no cards and no credits".

```bash
cd daemon/rust && cargo test --release # 25/25
./reset-card-daemon once # one round, prints the conclusion
./reset-card-daemon status # print the current conclusion only
```

## Local MCP endpoint

The plugin declares one MCP endpoint (`server=expiry-guardian`,
`path=/mcp/expiry-guardian`) so other plugins can ask it directly:

| Tool | What it does |
| --- | --- |
| `watchdog_status` | Quota, cards, credits and the current conclusion from the ledger |
| `watchdog_decide` | Decide right now — **decides only, never spends a card** |

⚠️ **Spending a card is deliberately not exposed**: it is a real write operation, and
letting another plugin spend your card on a whim is too dangerous.

## Configuration

`~/.config/mcode-reset-card/config.json` (read by Rust, the Mini App and Python):

```json
{
"schemaVersion": 1,
"cardTriggerPercent": 99,
"cardSkipBeforeResetMinutes": 60,
"quotaLevels": { "tight": 80, "low": 95 }
}
```

Config fields are **rebuilt from a whitelist** (`normalizeConfig`): a missing field is
a silently dead channel, so the config shape has to be changed in **three** places
(Rust config, the Mini App's `normalizeConfig`, and the real `config.json`).

## Tests

| Suite | Result |
| --- | --- |
| Rust (`daemon/rust`) | **25/25** |
| Python decision unit tests | **20/20** |
| Mini App (`tests/`) | **102/102** |
| Three-way parity | **19/19** |
| Upstream `npm run validate` | **OK** for this plugin (0 errors, 0 warnings); `repository: OK` |
| Upstream `npm test` | 69/69 |

One Mini App test is a **static guard** (`tests/server-lint.test.mjs`): it catches
identifiers that are used but never declared. It exists because of a real incident —
a typo in the dashboard's data layer made the whole snapshot throw, so the panel
(quota, decision, cards) was **completely broken**, while the unit tests kept passing
because they only cover the pure `decide()` function. The data layer itself was never
executed. The guard makes that class of error fail loudly.

## What v1.x used to do

The full history is in git (the v1.1–v1.6 commits). v1.x spent cards, paused goals,
and revived crashed Bots by reading the account API and other plugins' data — all of
which a community plugin must not do.

The hardening v1.x added — tolerant JSON parsing, judging "is it broken?" by the
latest *turn* rather than a stale error code, per-instance request ids, failure
counters that reset only on success, the static guard — **survived into v2.0**; it
just guards the local ledger now instead of the host's private state.

## Tested environment

- **MiniMax Code 3.1.2 or later** — the Mini App declares `agentAccess` and `agentConcurrency`.
- Verified on macOS; the upstream checks run on **Node 20**.
- The Rust daemon needs a Rust toolchain to build (`cargo build --release`).

## Data & access

This plugin reads and writes **only its own directory**:

| Path | What |
| --- | --- |
| `~/.config/mcode-reset-card/ledger.json` | The ledger **you** maintain (read by the panel, writable from the panel's editor) |
| `~/.config/mcode-reset-card/config.json` | Shared configuration (trigger lines) |
| `~/.config/mcode-reset-card/daemon-status.json` | The backend daemon's latest conclusion |

It deliberately does **not**:

- read MiniMax Code's **login credentials** (no lease socket, no `.cap` file, no token on disk)
- open or write **`~/.minimax/v2/sqlite/runtime-state.sqlite`** (the host's internal database)
- read **another plugin's data directory** (e.g. `~/.minimax/v2/plugin-data/liveboards/*/state.json`)

It performs **no write operations** outside its own directory: it never spends a
reset card, never pauses a goal, and never writes into another plugin's sessions.
The backend daemon is optional — the dashboard works on its own.

## Boundaries

- **No host credentials, no host database, no other plugins' data** (hard constraints)
- **No write operations** — spending a card and pausing a goal are yours to click
- The conclusion is advice only: it is based on a ledger *you* wrote
- Incomplete data yields *no* conclusion rather than a guess

## Documentation

- [docs/README.zh-CN.md](docs/README.zh-CN.md) — Chinese documentation
51 changes: 51 additions & 0 deletions plugins/hylouis233/expiry-guardian/check-test-parity.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# 校验「工作区源码」与「插件包」**测试文件**保持一致。
#
# 2026-10-20 反复踩到的坑:工作区(miniapps/expiry-guardian/)与插件包
# (plugins/hylouis233/reset-card-autouse/)各有一份测试,路径布局还不同
# (工作区是 `./x.mjs`,插件包是 `../miniapp/node/x.mjs`)。
# 改了一边忘了另一边 ⇒ 插件包跑的是旧测试,出现「17/18」这种看不懂的结果。
#
# ⇒ 这个脚本只比**测试文件的内容**(忽略 import 路径差异),
# 不一致就报错并指出是哪几个文件。
set -euo pipefail

WORKSPACE="${WORKSPACE:-$HOME/Desktop/mmx/miniapps/expiry-guardian}"
PLUGIN="${PLUGIN:-$(cd "$(dirname "$0")" && pwd)}"

# 插件包里测试在 tests/,工作区在 miniapp/node/。
PAIRS=(
"config-fidelity.test.mjs"
"decision.test.mjs"
"mcp.test.mjs"
"server-lint.test.mjs"
)

fail=0
for f in "${PAIRS[@]}"; do
a="$WORKSPACE/miniapp/node/$f"
b="$PLUGIN/tests/$f"
if [ ! -f "$a" ] || [ ! -f "$b" ]; then
echo " SKIP $f(缺文件)"
continue
fi
# 归一化:去掉 import 路径差异与空白差异。
na=$(sed -E "s#\.\./miniapp/node/##g; s#'\./#'#g" "$a" | tr -d ' \t\r\n')
nb=$(sed -E "s#\.\./miniapp/node/##g; s#'\./#'#g" "$b" | tr -d ' \t\r\n')
if [ "$na" = "$nb" ]; then
echo " OK $f"
else
echo " DIFF $f ← 工作区与插件包不一致"
diff <(sed -E "s#\.\./miniapp/node/##g; s#'\./#'#g" "$a") \
<(sed -E "s#\.\./miniapp/node/##g; s#'\./#'#g" "$b") | head -12 || true
fail=1
fi
done

if [ "$fail" -ne 0 ]; then
echo
echo "❌ 工作区与插件包的测试文件不一致 —— 改完记得两边都同步。"
exit 1
fi
echo
echo "✅ 工作区与插件包的测试文件一致"
Loading