CrazeCode疯码

Widget pluginWidget 插件

Write your first plugin in ten minutes. 十分钟写出你的第一个插件

A plugin is one executable file. CrazeCode writes two JSON lines to its standard input and reads two JSON lines back. That is the whole protocol — there is no SDK to install and no framework to learn. 一个插件就是一个可执行文件。疯码往它的标准输入写两行 JSON,再从标准输出读两行 JSON。整个协议就这么多 —— 不用装 SDK,也没有框架要学。

Every command and every warning below was run end to end before it was written down. The four traps in step 5 are the ones that actually cost time. 下面每一条命令和每一个提醒,都是先真跑通再写下来的。第 5 步那四个坑,是真正会浪费你时间的地方。

1 · Create a Dashboard to hold it1 · 先建一个面板放它

A plugin always targets a Dashboard. Import fails with “target Dashboard is missing” if you skip this. 插件必须挂在某个面板上。跳过这步,导入会报「target Dashboard is missing」。

$ craze dashboard create main
DASHBOARD CREATED · id=main · title=main · default=true

$ craze dashboard list
  main (default) · title=main · widgets=0

2 · Two files, one folder2 · 两个文件,一个目录

The manifest declares identity and limits; the script does the work. executable is resolved relative to the manifest, so both files sit together. manifest 声明身份与限额,脚本负责干活。executable 是相对 manifest 解析的,所以两个文件放在一起。

$ mkdir -p ~/my-widget && cd ~/my-widget
$ curl -fsSLo manifest.json https://crazecode.com/auth/assets/26347a1390ec/plugin-demo.json
$ curl -fsSLo plugin.sh    https://crazecode.com/auth/assets/26347a1390ec/plugin-demo.sh
$ chmod +x plugin.sh

3 · What the host sends you3 · 宿主发给你什么

Two lines arrive on stdin. Note the tag key: requests use method, your responses use type. stdin 上会来两行。注意标签字段不一样:请求用 method,你的回复用 type

// line 1 · 第一行
{"protocol_version":1,"request_id":"handshake",
 "request":{"method":"handshake","supported_versions":[1],
            "capabilities":["typed_value"],"permissions":["widget_identity"]}}

// line 2 · 第二行
{"protocol_version":1,"request_id":"refresh",
 "request":{"method":"refresh","attempt_revision":7,
            "widget_id":"w-demo","provider_id":"p-demo","now_ms":null}}

Permissions decide what you can see. Without widget_identity the ids are null; without attempt_clock so is now_ms. Ask for nothing you do not use. 权限决定你能看见什么。不申请 widget_identity,两个 id 就是 null;不申请 attempt_clocknow_ms 也是 null。用不到的就别申请。

And what you must send back而你必须回什么

// line 1 · 第一行:接受协议
{"protocol_version":1,"request_id":"handshake",
 "response":{"type":"handshake","selected_version":1,
             "capabilities":["typed_value"],"permissions":["widget_identity"]}}

// line 2 · 第二行:返回数据。revision 必须等于上面的 attempt_revision
{"protocol_version":1,"request_id":"refresh",
 "response":{"type":"value","value":{"schema_version":1,
   "widget_id":"w-demo","revision":7,"kind":"metric","title":"Demo",
   "updated_at_ms":0,"fresh_until_ms":0,"status":"ok",
   "summary":{"value":"42"},
   "data":{"value":"42","unit":null,"change":null},"actions":[],
   "provenance":{"provider_id":"p-demo","source_class":"plugin"},
   "truncated":false}}}

4 · Import it4 · 导入

Preview writes nothing. It returns a digest that binds the exact bytes it just read, and import only proceeds if you hand that digest back. preview 不写任何东西,它返回一个绑定「刚读到的确切字节」的摘要;只有把这个摘要交回去,import 才会执行。

$ craze dashboard plugin preview manifest.json
  "manifest_file_sha256": "677ee7a0…"   ← 不是这个 · not this one
  "sha256":               "bc323956…"   ← 也不是 · nor this one
  "preview_sha256":       "d3cbd7f0…"   ← 用这个 · use this one

$ craze dashboard plugin import manifest.json --yes --preview-sha256 d3cbd7f0…
$ craze dashboard plugin refresh demo-widget
WIDGET PLUGIN REFRESH · attempt=1 · disposition=Succeeded
cache-failure=none last-good=1

5 · The four traps5 · 四个坑

Each of these produced a real failure while this page was being written. 这四条都是写这一页时真撞出来的。

Echo the attempt number必须回显 attempt 号
Your value.revision must equal the request’s attempt_revision. A hard-coded 1 passes the first refresh and fails every one after it with InvalidValue. 返回值的 value.revision 必须等于请求里的 attempt_revision。写死 1 的话第一次会过,之后每次都以 InvalidValue 失败。
No fork, no pipes不能 fork,不能用管道
The sandbox denies fork(). A shell plugin cannot use $(…), pipes, or sed/awk/jq — only builtins. The demo parses JSON with parameter expansion alone. 沙箱禁止 fork()。shell 插件不能用 $(…)、管道,或 sed/awk/jq,只能用内建语法。示例仅用参数展开就解析出了 JSON。
Three digests, one right三个摘要,只有一个对
Preview prints manifest_file_sha256, sha256 and preview_sha256. Only the last one is accepted by import. preview 会打印三个摘要,只有 preview_sha256 会被 import 接受。
Failure means backoff失败后会退避
After a failed attempt the provider backs off for a few seconds and refuses to run. Fix the script, re-import, then wait for the retry window. 一次失败之后,provider 会退避几秒并拒绝执行。改好脚本、重新导入,然后等退避窗口过去。

What a plugin can never do插件永远做不到的事

These are boundaries of the sandbox, not conventions you are asked to respect. 这些是沙箱的硬边界,不是靠自觉遵守的约定。

Reach outside its package越出自己的包

Reading an undeclared user file fails. The executable path must be package-relative with no parent traversal. 读取未声明的用户文件会失败。可执行文件路径必须是包内相对路径,不允许向上穿越。

Own the terminal占用终端

A plugin returns structured values only. It never writes raw escape sequences, never owns a key binding, and draws nothing while a native client holds the screen. 插件只返回结构化数据。它不写任何转义序列、不拥有按键,原生客户端占屏时它一个字都不画。

Run unbounded无限制运行

timeout_ms and max_output_bytes are enforced by the host. A crash, a hang or a cancellation stays local to the plugin. timeout_msmax_output_bytes 由宿主强制执行。崩溃、卡死或取消都只影响这个插件自己。

The complete demo完整示例

Both files are served from this server and are the exact ones verified above. 两个文件由本服务器提供,就是上面验证通过的那两个。