Reach outside its package越出自己的包
Reading an undeclared user file fails. The executable path must be package-relative with no parent traversal. 读取未声明的用户文件会失败。可执行文件路径必须是包内相对路径,不允许向上穿越。
Widget pluginWidget 插件
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 步那四个坑,是真正会浪费你时间的地方。
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
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
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_clock,now_ms 也是 null。用不到的就别申请。
// 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}}}
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
Each of these produced a real failure while this page was being written. 这四条都是写这一页时真撞出来的。
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 失败。
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。
manifest_file_sha256, sha256 and preview_sha256. Only the last one is accepted by import.
preview 会打印三个摘要,只有 preview_sha256 会被 import 接受。
These are boundaries of the sandbox, not conventions you are asked to respect. 这些是沙箱的硬边界,不是靠自觉遵守的约定。
Reading an undeclared user file fails. The executable path must be package-relative with no parent traversal. 读取未声明的用户文件会失败。可执行文件路径必须是包内相对路径,不允许向上穿越。
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. 插件只返回结构化数据。它不写任何转义序列、不拥有按键,原生客户端占屏时它一个字都不画。
timeout_ms and max_output_bytes are enforced by the host. A crash, a hang or a cancellation stays local to the plugin.
timeout_ms 与 max_output_bytes 由宿主强制执行。崩溃、卡死或取消都只影响这个插件自己。
Both files are served from this server and are the exact ones verified above. 两个文件由本服务器提供,就是上面验证通过的那两个。