@sunr/dsh-reminder 0.0.0-stage → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +188 -2
- package/bundles/cordis.patch.yml +5 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +52 -3
- package/src/almanac.js +80 -0
- package/src/client.js +984 -0
- package/src/export.js +111 -0
- package/src/index.js +296 -0
- package/src/lunar.js +180 -0
- package/src/remind.js +40 -0
- package/src/repeat.js +171 -0
- package/src/retrograde.js +67 -0
- package/src/service.js +170 -0
- package/src/store.js +116 -0
- package/src/tools.js +512 -0
- package/src/view.js +107 -0
package/README.md
CHANGED
|
@@ -1,3 +1,189 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @sunr/dsh-reminder(记日)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> 把「按农历 / 按年 / 按周循环的日子」交给 agent 记住;页面上也能直接改。
|
|
4
|
+
> 领域逻辑移植自 Xcode 项目 **Reminder(记日)**,不是它的打包版。
|
|
5
|
+
|
|
6
|
+
## 现在有什么
|
|
7
|
+
|
|
8
|
+
| 模块 | 状态 | 说明 |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| `src/lunar.js` | ✅ | 公历 ⇄ 农历、闰月、腊月三十降级、反查下一次 |
|
|
11
|
+
| `src/repeat.js` | ✅ | 7 种重复规则展开(对齐 iOS 端 `RepeatKind`) |
|
|
12
|
+
| `src/store.js` | ✅ | JSON 存储,原子写 + 进程内串行化 |
|
|
13
|
+
| `src/tools.js` | ✅ | 6 个工具 |
|
|
14
|
+
| `src/index.js` | ✅ | 插件入口(`name` / `inject` / `apply`)+ 页面读写路由 |
|
|
15
|
+
| `src/client.js` | ✅ | 浏览器面:`main` 页面(增删改查)+ `sidebar.panellist` 图标,手写无构建 |
|
|
16
|
+
| 主动提醒 | ⬜ | 交给 DSH 内置 `schedule`,插件不自己造定时器 |
|
|
17
|
+
|
|
18
|
+
测试:**94 项**(农历 9 / 重复 15 / 工具与存储 12 / 服务 9 / 视图 6 / 黄历 5 / 导出 7 / 渲染 7)+ 2 个冒烟,全过。
|
|
19
|
+
渲染测试用真实 React 做 SSR(react 只装成 devDependency,不进发布包)。
|
|
20
|
+
|
|
21
|
+
**已端到端验证**(2026-11-07,桌面版 profile 热加载后由模型实际调用):
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
jiri_lunar(2026-11-07) → 2026-11-07 是 2026 年农历九月廿九
|
|
25
|
+
jiri_add(农历八月初八) → 已记下,下一次 2027-09-08(305 天后)
|
|
26
|
+
jiri_list(upcoming) → 表格里带「农历八月初八」
|
|
27
|
+
jiri_delete(id) → 已删除,落盘文件回到空列表
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 六个工具
|
|
31
|
+
|
|
32
|
+
| 工具 | 作用 |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `jiri_add` | 记一条(纪念日 / 待办 / 备忘),农历条目自动算下一次 |
|
|
35
|
+
| `jiri_list` | 查 today / upcoming / overdue / all |
|
|
36
|
+
| `jiri_done` | 标记某一次完成或跳过 |
|
|
37
|
+
| `jiri_update` | 改标题、日期、重复规则、标签、备注 |
|
|
38
|
+
| `jiri_delete` | 删 |
|
|
39
|
+
| `jiri_lunar` | 公历转农历,或农历反查公历 |
|
|
40
|
+
| `jiri_brief` | 今天到未来 N 天的简报 |
|
|
41
|
+
| `jiri_reminders` | 只返回今天该提醒的(提前点命中 + 今天到期),定时任务用 |
|
|
42
|
+
| `jiri_export` | 导出 json / markdown / ics 到指定路径 |
|
|
43
|
+
|
|
44
|
+
用起来就是一句话的事:
|
|
45
|
+
|
|
46
|
+
> 记一下我妈农历八月初八生日,提前三天提醒我。
|
|
47
|
+
|
|
48
|
+
## 数据
|
|
49
|
+
|
|
50
|
+
`$DSH_HOME/storages/@sunr/dsh-reminder/items.json`(默认 `~/.dsh/...`),人可以直接打开看。
|
|
51
|
+
|
|
52
|
+
```jsonc
|
|
53
|
+
{
|
|
54
|
+
"version": 1,
|
|
55
|
+
"items": [{
|
|
56
|
+
"id": "jiri_1a2b3c4d5e",
|
|
57
|
+
"title": "妈妈的生日",
|
|
58
|
+
"kind": "anniversary",
|
|
59
|
+
"calendar": "lunar",
|
|
60
|
+
"lunar": { "month": 8, "day": 8, "leap": false },
|
|
61
|
+
"date": "2027-09-08", // 公历锚点
|
|
62
|
+
"repeat": "yearly",
|
|
63
|
+
"done": [], "skipped": [],
|
|
64
|
+
"tags": ["家人"]
|
|
65
|
+
}]
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 跑测试
|
|
70
|
+
|
|
71
|
+
本机没有全局 node,用桌面版自带的 Electron 当 Node:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
./scripts/test.sh # 36 项单测
|
|
75
|
+
./scripts/smoke.mjs # 入口冒烟(用 DSH node 跑,见下)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
冒烟测试单独跑:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
ELECTRON_RUN_AS_NODE=1 \
|
|
82
|
+
"/Applications/DeepSeek Harness.app/Contents/MacOS/DeepSeek Harness" \
|
|
83
|
+
scripts/smoke.mjs
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 一个刻意的例外:lunar-javascript
|
|
87
|
+
|
|
88
|
+
插件本体仍是零构建、纯 ESM,但老黄历的干支/节气/宜忌交给 `lunar-javascript`(纯 JS、无原生依赖)。
|
|
89
|
+
理由:干支纪日和节气自己推很容易差一天,与其写错,不如依赖一个成熟实现。
|
|
90
|
+
|
|
91
|
+
## 原本的零依赖部分
|
|
92
|
+
|
|
93
|
+
农历换算走 Node 内置的 `Intl.DateTimeFormat('zh-CN-u-ca-chinese')`,
|
|
94
|
+
和 iOS 端用 `Calendar(identifier: .chinese)` 是同一个思路:不自带万年历表。
|
|
95
|
+
|
|
96
|
+
实测覆盖:
|
|
97
|
+
|
|
98
|
+
| 场景 | 结果 |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `2026-11-07` | 农历九月廿九 |
|
|
101
|
+
| `2025-08-01` | 农历**闰**六月初八 |
|
|
102
|
+
| `2027-02-06` | 腊月三十(属农历 2026 年) |
|
|
103
|
+
| `2027-02-07` | 正月初一(2027 春节) |
|
|
104
|
+
| 反查八月十五(从 2026-11-07 起) | `2027-09-15` |
|
|
105
|
+
|
|
106
|
+
## 三个必须记住的边界
|
|
107
|
+
|
|
108
|
+
1. `Intl` 只能「公历 → 农历」,**反查必须逐日扫描**(上限 400 天,毫秒级)。
|
|
109
|
+
2. 闰月以 `闰` 前缀出现(英文 locale 下是 `bis`),单独建模成 `leap: boolean`。
|
|
110
|
+
3. 农历腊月可能只有廿九;2 月 29 日生日在平年要降级 —— 由 `leapDayFallback` 决定。
|
|
111
|
+
|
|
112
|
+
## 安装
|
|
113
|
+
|
|
114
|
+
插件自带 `dsh.bundle`,装进 profile 后由 `dsh.profile.bundles` 自动挂载,**不用手改 cordis.patch.yml**:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
# 1. 打包(file: 依赖会被 pnpm 装成软链,软链拿不到宿主 SDK,见下)
|
|
118
|
+
ELECTRON_RUN_AS_NODE=1 "/Applications/DeepSeek Harness.app/Contents/MacOS/DeepSeek Harness" \
|
|
119
|
+
"/Applications/DeepSeek Harness.app/Contents/Resources/runtime/primary-runtime/dependencies/pnpm/bin/pnpm.cjs" \
|
|
120
|
+
pack --pack-destination ~/DSHwork
|
|
121
|
+
|
|
122
|
+
# 2. 装进目标 profile
|
|
123
|
+
dsh plugin --profile web add ~/DSHwork/@sunr/dsh-reminder-0.1.2.tgz
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
桌面版(profile `desktop`)装完需要重启 App 才会生效。
|
|
127
|
+
|
|
128
|
+
### 踩过的坑:软链插件拿不到宿主 SDK
|
|
129
|
+
|
|
130
|
+
`@deepseek-ai/*` 不是从插件自己的 `node_modules` 解析的。加载器按模块路径分层
|
|
131
|
+
(`findInterceptionLayer`):只有**位于 profile 目录树内**的模块才会被拦截解析到宿主包;
|
|
132
|
+
pnpm `file:` 依赖装出来是指向仓库的软链,落在 "linked" 层,走 Node 原生解析 → `ERR_MODULE_NOT_FOUND`。
|
|
133
|
+
|
|
134
|
+
所以本地开发要么用 tarball 安装,要么接受“改完要重新打包安装”。
|
|
135
|
+
|
|
136
|
+
另一个坑:`output.schema` 里的显式对象必须写 `additionalProperties`,否则 `defineTool` 直接抛
|
|
137
|
+
`JsonSchemaError`,插件在启动时被跳过(日志里是 `1 entry did not activate`)。
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
## 设计约束(来自方案文档)
|
|
141
|
+
|
|
142
|
+
- 插件跑在 Mac 上,**碰不到 iPhone**,所以不做推送。
|
|
143
|
+
- 第一版数据由插件独占,不读写 iOS App 的 SwiftData。
|
|
144
|
+
- 定位是「结构化长期数据 + 查询」,定时提醒交给 DSH 内置 `schedule`。
|
|
145
|
+
|
|
146
|
+
详见 [两项目转DSH插件-评估与方案.md](../两项目转DSH插件-评估与方案.md)。
|
|
147
|
+
|
|
148
|
+
## 页面(纪念日)
|
|
149
|
+
|
|
150
|
+
侧边栏面板里名为「纪念日」,点开是列表 + 表单:
|
|
151
|
+
|
|
152
|
+
| 能力 | 说明 |
|
|
153
|
+
|---|---|
|
|
154
|
+
| 新增 | 标题、类型(纪念日/待办/备忘)、公历/农历、农历月日与闰月、重复规则、标签 |
|
|
155
|
+
| 编辑 | 行内「编辑」回填表单,可改历法与农历分量(切到公历会丢掉 lunar) |
|
|
156
|
+
| 删除 | 带确认 |
|
|
157
|
+
| 打卡 | 标记这一次完成,列表里不再计入 |
|
|
158
|
+
| 重复规则 | 不重复 / 每年 / 每月 / 每周(选周几)/ 每天 / 单周 / 双周 / 每 N 天 |
|
|
159
|
+
| 月历 | 常驻在上方,与列表合并成一页;上下月、今天、点某天筛列表 |
|
|
160
|
+
| 彩色标记 | 每条记录按 id 哈希取调色板颜色,圆点与列表色条一致 |
|
|
161
|
+
| 老黄历(左栏) | 农历、干支、节气、建除十二神、宜/忌、彭祖百忌 |
|
|
162
|
+
| 黄历 · 续 | 冲煞、值神、**星宿、纳音、喜神/福神/财神方位**、吉时;展开看吉神宜趋与凶煞宜忌 |
|
|
163
|
+
| 星座·月相 | 星座、元素(火/土/风/水)、守护星、日期区间、传统月相 |
|
|
164
|
+
| 水星逆行 | 按水星地心视黄经算区间;月历上方横条显示当前/下次水逆,格子标「逆」 |
|
|
165
|
+
| 单双周 | 列表每行首列显示该日期所属 ISO 周的「单周/双周」 |
|
|
166
|
+
| 完成状态 | 打过卡的那一次显示为删除线 + 置灰,按钮变「撤销」 |
|
|
167
|
+
| 删除撤销 | 删除后右下角浮出「撤销」,可恢复被删记录 |
|
|
168
|
+
| 搜索筛选 | 按标题/标签搜索,标签一键筛选 |
|
|
169
|
+
| 表单定位 | 点编辑自动滚到表单 |
|
|
170
|
+
| 时区 | 页面右上选择,持久化到 settings.json,工具与页面共用 |
|
|
171
|
+
| 响应式 | 窗口 < 780px 自动紧凑:隐藏农历日、缩小格子、收起「距今」列 |
|
|
172
|
+
| 提醒时间 | 全局默认时刻 + 每条记录可覆盖;不同时刻各自对应一条定时任务,增删改后自动对齐 |
|
|
173
|
+
| 事件提醒 | 每条记录可设多个提前点(如 `7,1`)和**自己的提醒时刻**(如 20:30),按记录归属的时刻投递 |
|
|
174
|
+
|
|
175
|
+
数据链路:页面 → `POST /api/@sunr/dsh-reminder/items`(`action: list|add|update|delete|done`)→ `src/service.js`(与工具共用校验)→ JSON 存储。
|
|
176
|
+
|
|
177
|
+
## 每日提醒
|
|
178
|
+
|
|
179
|
+
页面右上角「每日提醒」开关。打开后插件通过宿主的 `schedule` 服务创建一条每天 09:00 的任务
|
|
180
|
+
(`new_once`:首次投递新建会话,之后复用),投递内容就是让 agent 调 `jiri_brief`。
|
|
181
|
+
关掉会删除该任务;任务 id 记在 `~/.dsh/storages/@sunr/dsh-reminder/reminder.json`。
|
|
182
|
+
|
|
183
|
+
## 导出 / 导入
|
|
184
|
+
|
|
185
|
+
- 页面右上「导出」:下载 JSON(备份)、Markdown、ICS 需走工具或路由
|
|
186
|
+
- `jiri_export` 工具可指定 `format` 与 `path`
|
|
187
|
+
- 「导入」接受本插件导出的 JSON,按 id 去重(重复的跳过),坏数据逐条报错但不中断
|
|
188
|
+
|
|
189
|
+
数据文件权限已改为 `0600`(此前是 0644,同机其他账户可读)。
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sunr/dsh-reminder",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"description": "记日:农历纪念日、倒数与重复待办的 DeepSeek Harness 插件",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"deepseek-harness",
|
|
7
|
+
"dsh",
|
|
8
|
+
"plugin",
|
|
9
|
+
"lunar",
|
|
10
|
+
"anniversary",
|
|
11
|
+
"reminder",
|
|
12
|
+
"almanac"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "src/index.js",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./src/index.js",
|
|
19
|
+
"./client": "./src/client.js",
|
|
20
|
+
"./package.json": "./package.json",
|
|
21
|
+
"./locale/*.json": "./locale/*.json"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"src",
|
|
25
|
+
"bundles",
|
|
26
|
+
"locale",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": "^22.19.0 || >=24.0.0",
|
|
31
|
+
"dsh": ">=0.2.2-rc.1"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"astronomy-engine": "^2.1.19",
|
|
35
|
+
"lunar-javascript": "^1.7.8"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@deepseek-ai/dsh-tools": ">=0.2.0-rc.2"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"react": "18.3.1",
|
|
42
|
+
"react-dom": "18.3.1"
|
|
43
|
+
},
|
|
44
|
+
"dsh": {
|
|
45
|
+
"bundle": {
|
|
46
|
+
"patch": "./bundles/cordis.patch.yml"
|
|
47
|
+
},
|
|
48
|
+
"client": {
|
|
49
|
+
"platform": "web"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"test": "node --test test/"
|
|
54
|
+
}
|
|
6
55
|
}
|
package/src/almanac.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 老黄历:农历、干支、节气、建除十二神、宜忌、彭祖百忌。
|
|
3
|
+
*
|
|
4
|
+
* 这部分交给 lunar-javascript —— 自己写干支和节气很容易错一天,
|
|
5
|
+
* 而这个库是纯 JS、无原生依赖,只在 host 侧用。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import lunarPackage from 'lunar-javascript'
|
|
9
|
+
|
|
10
|
+
const { Solar } = lunarPackage
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 十二星座:名称、元素(火/土/风/水)、守护星、日期区间。
|
|
14
|
+
* 只做客观映射,不生成“今日运势”。
|
|
15
|
+
*/
|
|
16
|
+
const ZODIAC = {
|
|
17
|
+
白羊: { element: '火象', ruler: '火星', range: '3/21 – 4/19' },
|
|
18
|
+
金牛: { element: '土象', ruler: '金星', range: '4/20 – 5/20' },
|
|
19
|
+
双子: { element: '风象', ruler: '水星', range: '5/21 – 6/21' },
|
|
20
|
+
巨蟹: { element: '水象', ruler: '月亮', range: '6/22 – 7/22' },
|
|
21
|
+
狮子: { element: '火象', ruler: '太阳', range: '7/23 – 8/22' },
|
|
22
|
+
处女: { element: '土象', ruler: '水星', range: '8/23 – 9/22' },
|
|
23
|
+
天秤: { element: '风象', ruler: '金星', range: '9/23 – 10/23' },
|
|
24
|
+
天蝎: { element: '水象', ruler: '冥王星', range: '10/24 – 11/22' },
|
|
25
|
+
射手: { element: '火象', ruler: '木星', range: '11/23 – 12/21' },
|
|
26
|
+
摩羯: { element: '土象', ruler: '土星', range: '12/22 – 1/19' },
|
|
27
|
+
水瓶: { element: '风象', ruler: '天王星', range: '1/20 – 2/18' },
|
|
28
|
+
双鱼: { element: '水象', ruler: '海王星', range: '2/19 – 3/20' },
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 星座信息:{ sign, element, ruler, range },sign 不带“座”字 */
|
|
32
|
+
export function zodiacOf(iso) {
|
|
33
|
+
const [year, month, day] = iso.split('-').map(Number)
|
|
34
|
+
const sign = Solar.fromYmd(year, month, day).getXingZuo()
|
|
35
|
+
const meta = ZODIAC[sign] ?? { element: '', ruler: '', range: '' }
|
|
36
|
+
return { sign, ...meta }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} iso YYYY-MM-DD
|
|
41
|
+
* @returns {{ lunarDay: string, lunarText: string, ganzhi: string, jieQi: string,
|
|
42
|
+
* zhiXing: string, yi: string[], ji: string[], pengZu: string, week: string }}
|
|
43
|
+
*/
|
|
44
|
+
export function almanacOf(iso) {
|
|
45
|
+
const [year, month, day] = iso.split('-').map(Number)
|
|
46
|
+
const solar = Solar.fromYmd(year, month, day)
|
|
47
|
+
const lunar = solar.getLunar()
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
lunarDay: lunar.getDayInChinese(),
|
|
51
|
+
lunarText: `${lunar.getMonthInChinese()}月${lunar.getDayInChinese()}`,
|
|
52
|
+
ganzhi: `${lunar.getYearInGanZhi()}年 ${lunar.getMonthInGanZhi()}月 ${lunar.getDayInGanZhi()}日`,
|
|
53
|
+
jieQi: lunar.getJieQi() || '',
|
|
54
|
+
zhiXing: lunar.getZhiXing(),
|
|
55
|
+
yi: lunar.getDayYi() || [],
|
|
56
|
+
ji: lunar.getDayJi() || [],
|
|
57
|
+
pengZu: `${lunar.getPengZuGan()} ${lunar.getPengZuZhi()}`,
|
|
58
|
+
week: solar.getWeekInChinese(),
|
|
59
|
+
yueXiang: lunar.getYueXiang(),
|
|
60
|
+
// 黄道吉时:时辰的天神类型为「黄道」
|
|
61
|
+
jiShi: lunar.getTimes()
|
|
62
|
+
.filter((time) => time.getTianShenType() === '黄道')
|
|
63
|
+
.map((time) => ({ zhi: time.getZhi(), from: time.getMinHm(), to: time.getMaxHm() })),
|
|
64
|
+
xiu: `${lunar.getXiu()}${lunar.getZheng()}${lunar.getAnimal()}`,
|
|
65
|
+
xiuLuck: lunar.getXiuLuck(),
|
|
66
|
+
naYin: lunar.getDayNaYin(),
|
|
67
|
+
positions: {
|
|
68
|
+
xi: lunar.getDayPositionXiDesc(),
|
|
69
|
+
fu: lunar.getDayPositionFuDesc(),
|
|
70
|
+
cai: lunar.getDayPositionCaiDesc(),
|
|
71
|
+
},
|
|
72
|
+
chong: lunar.getDayChongDesc(),
|
|
73
|
+
sha: lunar.getDaySha(),
|
|
74
|
+
tianShen: lunar.getDayTianShen(),
|
|
75
|
+
tianShenType: lunar.getDayTianShenType(),
|
|
76
|
+
jiShen: lunar.getDayJiShen() || [],
|
|
77
|
+
xiongSha: lunar.getDayXiongSha() || [],
|
|
78
|
+
zodiac: zodiacOf(iso),
|
|
79
|
+
}
|
|
80
|
+
}
|