本地推送 API
把脚本的进度、构建状态、提醒推到信息岛「动态」页。胶囊会显示最新卡片的标题(带进度百分比)并脉冲一次,卡片到期自动消失。接口只监听回环地址,不需要任何云端账号。
01概览#
信息岛在本地起一个极小的 HTTP 服务。脚本用一个 JSON 描述卡片,POST 过来即可;同一个 id 再次推送就是「更新并续期」,不会堆一屏卡片。
- 只监听
127.0.0.1,局域网与公网不可达。 - 除健康检查外,所有请求都要
Authorization: Bearer <token>。 dcx-island.exe --push在单实例检查之前运行:信息岛已经在跑也能用,而且不会打开或收起面板。- 一次请求只处理一个 JSON 卡片;不支持 chunked 编码。
02鉴权#
令牌在 %LOCALAPPDATA%\dcx\island_api_token.txt,信息岛首次启动时生成。设置页「桌面信息岛 → 推送 API」可以复制或重置;重置后立即生效,不需要重启信息岛。
# 读取令牌(注意 -Raw 与 Trim)
$token = (Get-Content "$env:LOCALAPPDATA\dcx\island_api_token.txt" -Raw).Trim()
为什么令牌在文件里
这是刻意的例外:脚本需要一行就能读到它,因此不放 Windows 凭据管理器。它只授予「往岛上推卡片」这一项能力,不能读剪贴板、不能读消息、不能执行命令。
从设置页「复制」令牌后,岛内剪贴板历史会跳过与当前令牌完全相同的文本,不会落盘。
令牌等价于「推卡片」权限。不要把它写进公开仓库、CI 日志或截图;泄露后到设置页重置即可,旧令牌立刻失效。
03快速开始#
两种方式:调用信息岛自带的 --push / --push-file,或者直接 POST HTTP。前者最省事,后者适合已经在写脚本的进程。
方式一:命令行推送
先确保信息岛在运行,并且设置里的推送 API 是开启状态(默认开启)。
PowerShell 5.1 在把带内嵌双引号的参数交给原生程序时会重新加引号,而且不同写法结果不一致。可靠做法是让 PowerShell 写文件、程序读文件:
$j = '{"id":"test","title":"测试推送","progress":0.4,"ttl_secs":30}'
[IO.File]::WriteAllText("$env:TEMP\dcx-island-push.json", $j, [Text.Encoding]::UTF8)
& "D:\dcx-dist\dcx-island.exe" --push-file "$env:TEMP\dcx-island-push.json" | Out-Host
| Out-Host 让 PowerShell 等待这个 GUI 子系统的程序并显示返回的 JSON;不加的话提示符会先回来,输出稍后才出现(卡片本身仍会正常推送)。
& "D:\dcx-dist\dcx-island.exe" --push '{"id":"test","title":"测试推送","progress":0.4,"ttl_secs":30}'
PowerShell 7 可以直接用单引号传 JSON。
echo {"id":"test","title":"测试推送","progress":0.4} > "%TEMP%\p.json"
"D:\dcx-dist\dcx-island.exe" --push-file "%TEMP%\p.json"
--push 也可以,但要把内部引号写成 \":--push "{\"id\":\"test\",\"title\":\"测试推送\"}"。
D:/dcx-dist/dcx-island.exe --push '{"id":"test","title":"测试推送","progress":0.4,"ttl_secs":30}'
退出码 0 表示 HTTP 200;失败时 stderr 给出原因。
方式二:直接 HTTP
Windows PowerShell 5.1 会用 ANSI 编码发送字符串 body,中文会变成 ?。显式发送 UTF-8 字节即可:
$token = (Get-Content "$env:LOCALAPPDATA\dcx\island_api_token.txt" -Raw).Trim()
$json = '{"id":"build-42","title":"构建中","progress":0.3,"ttl_secs":600}'
Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:9841/v1/island/push" `
-Headers @{ Authorization = "Bearer $token" } `
-ContentType "application/json; charset=utf-8" `
-Body ([Text.Encoding]::UTF8.GetBytes($json))
TOKEN=$(cat "$LOCALAPPDATA/dcx/island_api_token.txt")
curl -sS -X POST http://127.0.0.1:9841/v1/island/push \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data-binary '{"id":"build-42","title":"构建中","progress":0.3}'
用 --data-binary(或 --data-binary @file.json),不要用 -d 传中文——Git Bash 的 argv 编码会破坏多字节字符。
import json, pathlib, urllib.request
token = pathlib.Path.home().joinpath(
"AppData/Local/dcx/island_api_token.txt"
).read_text(encoding="utf-8").strip()
body = json.dumps(
{"id": "build-42", "title": "构建中", "progress": 0.3},
ensure_ascii=False,
).encode("utf-8")
req = urllib.request.Request(
"http://127.0.0.1:9841/v1/island/push", data=body,
headers={"Authorization": f"Bearer {token}",
"Content-Type": "application/json"},
)
print(urllib.request.urlopen(req, timeout=5).read().decode("utf-8"))
04端点#
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
GET | /v1/health | 否 | {"ok":true,"app":"dcx-island","version":"…"} |
POST | /v1/island/push | Bearer | 推送 / 更新卡片,返回 {"ok":true,"id":"…","expires_at":…} |
GET | /v1/island/active | Bearer | 当前未过期卡片,最新在前 |
DELETE | /v1/island/push/{id} | Bearer | 撤回卡片,幂等 |
请求体上限 64 KB,头部上限 8 KB,一次只处理一个 JSON 卡片。健康检查不带鉴权,可以拿来探测信息岛是否在跑:
GET http://127.0.0.1:9841/v1/health
200 OK
{"ok":true,"app":"dcx-island","version":"0.1.2"}
05卡片字段#
| 字段 | 必填 | 说明 |
|---|---|---|
id | 是 | 1–64 位 [A-Za-z0-9._-];同 id 再推 = 更新并续期 |
title | 是 | ≤200 字符;胶囊显示它(有进度时带百分比) |
body | 否 | ≤2000 字符;面板「动态」页的卡片正文 |
progress | 否 | 0–1 的小数;显示进度条与百分比(0.4 = 40%) |
ttl_secs | 否 | 存活秒数,默认 12,上限 3600;0 视为默认 |
action | 否 | 见下一节;没有动作就只有文字 |
完整示例
{
"id": "build-42",
"title": "构建中",
"body": "正在编译 dcx-island(12/40)",
"progress": 0.3,
"ttl_secs": 600,
"action": { "kind": "show_panel", "tab": "live" }
}
最小卡片是 {"id":"ping","title":"完成"},TTL 默认 12 秒。固定的 id 可以「同一张卡持续更新」,每次推送续期,不会堆一屏卡片。
06动作白名单#
卡片可以带一个动作,点击卡片上的按钮时执行。动作只有三种,没有执行命令一类动作:
| kind | 参数 | 行为 |
|---|---|---|
open_url | url(http / https) | 用默认浏览器打开 |
open_folder | path | 打开文件夹;不存在则报错提示 |
show_panel | tab:tasks / clip / inbox / perf / live / search | 打开信息岛并切到该页 |
open_url 只接受 http / https;show_panel 的页签 id 与面板内部一致,写错会被忽略。推送接口永远不能启动进程或读写文件。
07命令行推送#
dcx-island.exe 支持两个模式,参数都是一个 JSON 卡片的文本:
| 参数 | 说明 |
|---|---|
--push <json> | 直接把 JSON 作为参数 |
--push-file <path> | 从文件读取;UTF-8 BOM 会被自动跳过 |
- 退出码 0 = HTTP 200;失败时 stderr 给出原因。
- 两种模式都先于单实例检查运行:岛已经在跑也能用,且不会打开 / 收起面板。
- 推送 API 关闭时,
--push会直接报「推送 API 未启用」。
PowerShell 5.1 的引号问题不是偶发:--push '{"id":"test"}' 会被吃成 {id:test},改成 \" 转义后有时能过、有时又变成 EOF 错误。脚本里请一律用 --push-file。
08排错#
| 现象 | 原因 |
|---|---|
JSON 解析失败:key must be a string at line 1 column 2 | PowerShell 5.1 把 JSON 里的双引号吃掉了({id:test});改用 --push-file |
JSON 解析失败:EOF while parsing a string… | 同上,另一种引号变形;改用 --push-file |
| 401 | 没带 token,或 token 已重置(重置后立即生效) |
400 + id 需为… | id 字符不合法或超过 64 位 |
400 + open_url 仅支持… | 动作链接不是 http / https |
无法连接 127.0.0.1:9841 | 信息岛未运行,或设置里关掉了推送 API / 改了端口 |
| 卡片不出现 | TTL 太短(先被下一条顶掉);卡片最多 16 张,新的顶旧的 |
09边界与安全#
- 仅回环:服务只绑定
127.0.0.1,局域网与公网不可达,也没有跨域开放。 - 能力最小:令牌只允许推卡片;没有读剪贴板、读消息、执行命令、读写文件的端点。
- 令牌是文件:
island_api_token.txt刻意不放进凭据管理器,方便脚本读取;泄露后重置即可。 - 数据留在本地:剪贴板与通知内容全部本地保存;推送卡片只存在内存里,到期即消失,TTL 最长 1 小时。
- 卡片数量:最多 16 张,新的顶旧的。高频推送请复用固定
id。 - 默认开启:推送 API 默认开启;关掉后
--push直接报「推送 API 未启用」。
10扩展平台#
接入 DCX 不只有推送 API。平台本身是「宿主 + 独立 exe」的结构,新增一个应用不需要改设置窗口的导航结构,也不需要动任务栏引擎。
新增一个独立应用
三件事:新增一个 crate、在 dcx-host/src/apps.rs 的注册表加一条 AppEntry、在设置中心对应页面加一组卡片。可执行文件必须与 dcx-host.exe 同目录——宿主按 current_exe() 的父目录找它。
AppEntry {
id: "search",
name: "文件检索",
exe: "dcx-search.exe",
window_title: "文件检索", // 必须与应用的 WIN_TITLE 逐字一致
description: "全盘文件名与内容检索,由全局热键唤起",
default_on: false, // 缺省不随平台自启
}
default_on = false表示缺省关闭:宿主不会拉起它,也不预建索引。全局热键由常驻宿主监听,按下时才launch_on_demand。- 一个应用只能有一个主开关,同时写应用自己的 TOML 与
apps.toml,避免两处状态不一致。 - 不要给新应用单独写开机启动项。自启永远只有一条:
dcx-host.exe --no-settings,其余由apps.toml管理。
配置写入协议
五个 TOML 与两个 JSON 是多进程读写,所有保存都要走 dcx-win32::config_file,不要直接 fs::write + rename:
- FileLock:按文件全路径哈希生成命名互斥体,保护整个「读—改—写」;同线程可重入。
- write_atomic:临时文件带 PID 与序号并
sync_all,再ReplaceFileW/MoveFileExW换入。没有「rename 失败就直写」的回退。 - poisoned:解析失败的文件只读,save 返回错误,不许用默认值覆盖——否则会丢
overrides或library_root。
不要改的线协议
settings.toml、organizer.toml、apps.toml、box_slot.txt、box_host_alive、no_reserve 是引擎与收纳盒之间的稳定契约:文件名与语义都不改。新增应用不需要替换 dcx_engine.dll。
引擎 DLL 被 Explorer 映射时不能直接覆盖:先 mv 旧文件再 cp 新文件,随后用 grep -a "box_slot.txt" 确认产物带槽位代码,并重算 SHA256SUMS.txt。