dsh-tui-theme 0.3.1 → 0.4.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 +146 -120
- package/cordis.patch.yml +16 -16
- package/lib/types/autoTheme.d.ts +16 -40
- package/lib/types/autoTheme.d.ts.map +1 -1
- package/lib/types/autoTheme.js +40 -127
- package/lib/types/index.d.ts +2 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +13 -23
- package/lib/types/settingsSection.d.ts +1 -1
- package/lib/types/settingsSection.d.ts.map +1 -1
- package/lib/types/settingsSection.js +34 -27
- package/lib/types/statusLine.d.ts.map +1 -1
- package/lib/types/statusLine.js +6 -2
- package/lib/types/themeAssets.d.ts.map +1 -1
- package/lib/types/themeAssets.js +14 -8
- package/package.json +12 -7
- package/scripts/headless-order-test.mjs +67 -0
- package/scripts/validate-themes-against-host.mjs +115 -0
- package/scripts/verify-package.mjs +79 -0
- package/scripts/verify.mjs +452 -0
package/README.md
CHANGED
|
@@ -1,120 +1,146 @@
|
|
|
1
|
-
# dsh-tui-theme 🌸
|
|
2
|
-
|
|
3
|
-
[dsh-TUI](https://github.com/ccch1mneyyy/dsh-TUI) 的樱花粉主题插件。一个包带来四种个性化,全部走官方接缝:
|
|
4
|
-
|
|
5
|
-
| 个性化 | 接缝 | 说明 |
|
|
6
|
-
| --- | --- | --- |
|
|
7
|
-
| **三套粉色主题** | 主题(静态资产) | `pink-night` 夜樱 / `pink-day` 昼樱 / `pink-ansi` 樱·ANSI,启动时自动装进 `~/.dsh-tui/themes/` |
|
|
8
|
-
|
|
|
9
|
-
| **花符状态行** | `tuiStatus` | 输入框上方一行小装饰:✿ · 时钟 · 实时轮数(默认仅粉主题下显示) |
|
|
10
|
-
| **设置面板** | `tuiSettingsSections` | `/settings` 里一个可编辑区块,改完即时生效 |
|
|
11
|
-
|
|
12
|
-
**明确不做的事**:不注册快捷键、不注册/修改任何命令、不拦截输入、不追加会话事件、不注入 system prompt。卸载即无痕(可选删除主题文件)。
|
|
13
|
-
|
|
14
|
-
## 主题预览
|
|
15
|
-
|
|
16
|
-
| 主题 | 基底 | 风格 |
|
|
17
|
-
| --- | --- | --- |
|
|
18
|
-
| `pink-night` 夜樱 | dark | 深梅紫底、玫瑰粉强调、粉鲸鱼吉祥物,
|
|
19
|
-
| `pink-day` 昼樱 | light | 象牙粉底、墨梅正文、柔和玫瑰强调(已通过宿主浅色身份判定) |
|
|
20
|
-
| `pink-ansi` 樱·ANSI | dark-ansi | 16 色 ANSI 回退,品牌色映射到 magenta 系 |
|
|
21
|
-
|
|
22
|
-
三套均通过 dsh-TUI 官方校验器(零警告、全键覆盖)与 WCAG 对比度检查(正文 ≥ 11:1)。
|
|
23
|
-
|
|
24
|
-
> 小知识:`pink-day` 的 `text` 写成 `rgb(61,43,51)` 而非 hex——宿主按 `text` 墨色亮度自动判定主题深浅,且只认 `rgb()` 格式。
|
|
25
|
-
|
|
26
|
-
## 截图
|
|
27
|
-
|
|
28
|
-
实测于 dsh-tui 0.9.
|
|
29
|
-
|
|
30
|
-
| 昼樱 `pink-day` | 夜樱 `pink-night` |
|
|
31
|
-
| :---: | :---: |
|
|
32
|
-
|  |  |
|
|
33
|
-
|
|
34
|
-
`/settings` 里的 pink-theme
|
|
35
|
-
|
|
36
|
-

|
|
37
|
-
|
|
38
|
-
> 图中底栏上下文进度条的蓝色分段与 ❯ 提示符是宿主硬编码的,见下文[宿主限制](#受宿主限制目前无法定制的部分)。
|
|
39
|
-
|
|
40
|
-
##
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
-
|
|
47
|
-
- `
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
#
|
|
56
|
-
dsh plugin --profile dsh-tui add -w
|
|
57
|
-
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
|
91
|
-
| --- | --- | --- |
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
##
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
1
|
+
# dsh-tui-theme 🌸
|
|
2
|
+
|
|
3
|
+
[dsh-TUI](https://github.com/ccch1mneyyy/dsh-TUI) 的樱花粉主题插件。一个包带来四种个性化,全部走官方接缝:
|
|
4
|
+
|
|
5
|
+
| 个性化 | 接缝 | 说明 |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| **三套粉色主题** | 主题(静态资产) | `pink-night` 夜樱 / `pink-day` 昼樱 / `pink-ansi` 樱·ANSI,启动时自动装进 `~/.dsh-tui/themes/` |
|
|
8
|
+
| **缓存背景跟随** | 设置 + 本地缓存 | 可选地应用已有 `theme-follow.json` 的昼樱/夜樱结果;不直接读取终端输入或发送 OSC 查询 |
|
|
9
|
+
| **花符状态行** | `tuiStatus` | 输入框上方一行小装饰:✿ · 时钟 · 实时轮数(默认仅粉主题下显示) |
|
|
10
|
+
| **设置面板** | `tuiSettingsSections` | `/settings` 里一个可编辑区块,改完即时生效 |
|
|
11
|
+
|
|
12
|
+
**明确不做的事**:不注册快捷键、不注册/修改任何命令、不拦截输入、不追加会话事件、不注入 system prompt。卸载即无痕(可选删除主题文件)。
|
|
13
|
+
|
|
14
|
+
## 主题预览
|
|
15
|
+
|
|
16
|
+
| 主题 | 基底 | 风格 |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| `pink-night` 夜樱 | dark | 深梅紫底、玫瑰粉强调、粉鲸鱼吉祥物,95 键全覆盖 |
|
|
19
|
+
| `pink-day` 昼樱 | light | 象牙粉底、墨梅正文、柔和玫瑰强调(已通过宿主浅色身份判定) |
|
|
20
|
+
| `pink-ansi` 樱·ANSI | dark-ansi | 16 色 ANSI 回退,品牌色映射到 magenta 系 |
|
|
21
|
+
|
|
22
|
+
三套均通过 dsh-TUI 官方校验器(零警告、全键覆盖)与 WCAG 对比度检查(正文 ≥ 11:1)。
|
|
23
|
+
|
|
24
|
+
> 小知识:`pink-day` 的 `text` 写成 `rgb(61,43,51)` 而非 hex——宿主按 `text` 墨色亮度自动判定主题深浅,且只认 `rgb()` 格式。
|
|
25
|
+
|
|
26
|
+
## 截图
|
|
27
|
+
|
|
28
|
+
实测于 dsh-tui 0.9.2:
|
|
29
|
+
|
|
30
|
+
| 昼樱 `pink-day` | 夜樱 `pink-night` |
|
|
31
|
+
| :---: | :---: |
|
|
32
|
+
|  |  |
|
|
33
|
+
|
|
34
|
+
`/settings` 里的 pink-theme 区块(已缓存背景 / 花符 / 时钟 / 轮数,保存即时生效):
|
|
35
|
+
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
> 图中底栏上下文进度条的蓝色分段与 ❯ 提示符是宿主硬编码的,见下文[宿主限制](#受宿主限制目前无法定制的部分)。
|
|
39
|
+
|
|
40
|
+
## 缓存背景跟随
|
|
41
|
+
|
|
42
|
+
`dsh-TUI 0.9.2` 没有向插件公开安全的终端查询接缝。为了不与宿主的 stdin/raw-mode 生命周期竞争,插件不会直接发送 OSC 11,也不会读取终端输入。
|
|
43
|
+
|
|
44
|
+
“使用已缓存的终端背景”开启后:
|
|
45
|
+
|
|
46
|
+
- 启动时读取 `~/.dsh-tui/theme-follow.json` 中已有的 `light` 结果,亮 → `pink-day`,暗 → `pink-night`,并将选择写入 `~/.dsh-tui/theme.json`;
|
|
47
|
+
- 不存在缓存时完全保留当前 `/theme` 选择;0.4.0 不会自行创建或刷新缓存;
|
|
48
|
+
- 默认关闭。该缓存可由之前的兼容版本留下;未来 dsh-TUI 提供宿主拥有的查询接缝后,插件才会安全地恢复刷新能力;
|
|
49
|
+
- 开启后该缓存会在启动时覆盖 `/theme` 的持久选择;关闭即可恢复手动选择;
|
|
50
|
+
- `DSH_TUI_THEME` 环境变量仍然最优先(宿主行为,插件不覆盖环境变量)。
|
|
51
|
+
|
|
52
|
+
## 安装
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
# 方式一:从 npm(已发布)
|
|
56
|
+
dsh plugin --profile dsh-tui add -w dsh-tui-theme@latest
|
|
57
|
+
|
|
58
|
+
# 方式二:本地 tarball(开发/自用;不要直接安装源码目录)
|
|
59
|
+
cd /path/to/dsh-tui-theme
|
|
60
|
+
npm run build
|
|
61
|
+
npm pack
|
|
62
|
+
dsh plugin --profile dsh-tui add -w ./dsh-tui-theme-0.4.0.tgz
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
不要以本地源码目录作为依赖安装:其开发 `node_modules` 可能与 dsh-TUI 宿主解析出不同的 Cordis/DSH framework instance,造成插件无法注册服务。
|
|
66
|
+
|
|
67
|
+
### 升级
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
# npm 已发布版本:请求最新版本并刷新 profile 依赖
|
|
71
|
+
dsh plugin --profile dsh-tui add -w dsh-tui-theme@latest
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
插件只在主题文件缺失时复制,绝不覆盖你编辑过的 `~/.dsh-tui/themes/pink-*.json`。升级若需要采用包内的新主题资产,请先备份自己的修改,再删除对应主题文件并重启 dsh-TUI 让插件重装。
|
|
75
|
+
|
|
76
|
+
重启 dsh-TUI 后插件自动把三套主题复制进 `~/.dsh-tui/themes/`(**仅缺失时复制,绝不覆盖你已有的同名文件**),然后:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
# 在 dsh-TUI 里
|
|
80
|
+
/theme # 选择器:夜樱 / 昼樱 / 樱·ANSI
|
|
81
|
+
/theme pink-night # 或直接切换(开启跟随后由插件接管)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 配置
|
|
85
|
+
|
|
86
|
+
配置有三层,优先级:`/settings` 用户层 > `cordis.yml` 配置层 > 内置默认值。
|
|
87
|
+
|
|
88
|
+
`/settings` 里找到 **pink-theme** 区块即可编辑:
|
|
89
|
+
|
|
90
|
+
| 字段 | 默认 | 说明 |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| `followSystem` | `false` | 启动时应用已缓存的终端背景结果(昼樱 ↔ 夜樱);0.4.0 不刷新缓存 |
|
|
93
|
+
| `showGlyph` | `true` | 花符:开 = ✿ 开头,关 = 不显示 |
|
|
94
|
+
| `showClock` | `true` | 显示 HH:MM 时钟 |
|
|
95
|
+
| `showTurns` | `true` | 显示当前会话轮数(`N✦`,自本次启动起计) |
|
|
96
|
+
| `statusScope` | `pink-only` | 状态行展示:`pink-only` 仅樱花粉主题 / `all-themes` 所有主题 |
|
|
97
|
+
|
|
98
|
+
三项装饰全关时状态行整体消失。另有仅 profile 层的开关(`cordis.patch.yml`,不出现在 /settings):`autoInstallThemes`、`statusEnabled`。
|
|
99
|
+
|
|
100
|
+
## 受宿主限制、目前无法定制的部分
|
|
101
|
+
|
|
102
|
+
以下元素的颜色/形态由 dsh-TUI 宿主**硬编码**,不读取任何主题键,主题 JSON 与插件接缝都覆盖不到(dsh-TUI 0.9.2 实测):
|
|
103
|
+
|
|
104
|
+
| 元素 | 现状 | 位置(宿主源码) |
|
|
105
|
+
| --- | --- | --- |
|
|
106
|
+
| 输入框 ❯ 提示符 | 默认态无颜色参数(终端默认前景色,模型工作时变暗);最高推理档充能动画用**写死的蓝色 ramp**(深色端 `#82B9FF` / 浅色端 `#1E5FEB`) | `EffortChargeGlyph.tsx`、`trajectory/effortIgnition.ts` |
|
|
107
|
+
| 底栏上下文进度条分段色 | system / prompt / assistant / thinking / tools 五段为**写死的藏青→品牌蓝系**(`#22305F`→`#5A7CFF`),永远不随主题变化 | `screens/StatusMetrics.ts` |
|
|
108
|
+
| 进度条空余段配色 | 宿主按 `themeName === 'light'` **字符串比较**取浅色配色——自定义浅色主题(如 pink-day)不等于 `'light'`,会拿到深色空余段,在浅色终端上偏深 | `screens/StatusLine.tsx` |
|
|
109
|
+
| 状态行文字颜色 | 插件状态行(tuiStatus)由宿主统一以**无色 + 终端 dim** 渲染,插件无法指定颜色(✿ 行因此继承终端默认前景色) | `screens/Chat.tsx` |
|
|
110
|
+
| 输入框块状光标 | 宿主挂载期间隐藏终端原生光标(`?25l`),输入框光标由应用以**反色字符**自绘(`<Text inverse>`),颜色即主题 text/background 的反色——OSC 12 光标色只能染到不可见的原生光标,插件无法给输入光标上色(辅助功能模式 `CLAUDE_CODE_ACCESSIBILITY=1` 下原生光标才可见) | `ink/components/App.tsx`、`components/PromptInput.tsx` |
|
|
111
|
+
| 主界面组件与布局 | 顶栏像素鲸鱼、工具卡、输入框等宿主组件不可被插件替换或改布局——平台规则(内建优先,无组件替换接缝);主题能碰的只有颜色层 | 宿主架构约定 |
|
|
112
|
+
|
|
113
|
+
这些都需要上游 dsh-TUI 修改(例如:把充能色/进度条分段色接入主题键、空余段判断改用 `isLightThemeActive()`、给输入光标增加主题键)。上游修复前,任何社区主题包都受同样约束。
|
|
114
|
+
|
|
115
|
+
## 卸载
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
# 先在 dsh-TUI 内切换到非 pink-* 主题,例如:
|
|
119
|
+
/theme auto
|
|
120
|
+
|
|
121
|
+
# 再移除插件和可选的本地主题资产
|
|
122
|
+
dsh plugin --profile dsh-tui remove -w dsh-tui-theme
|
|
123
|
+
rm ~/.dsh-tui/themes/pink-{night,day,ansi}.json
|
|
124
|
+
rm ~/.dsh-tui/theme-follow.json
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 开发
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
npm install
|
|
131
|
+
npm run build
|
|
132
|
+
npm run verify
|
|
133
|
+
npm run verify:package
|
|
134
|
+
DSH_TUI_ADAPTER_DIR=/path/to/dsh-TUI/lib/types/dsh-adapter \
|
|
135
|
+
DSH_TUI_SOURCE_ROOT=/path/to/dsh-TUI-source \
|
|
136
|
+
npm run verify:host
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`verify:host` 必须显式指向待兼容的宿主 adapter 与源码;缺少其中任一项会失败,避免把跳过误报为验证成功。
|
|
140
|
+
|
|
141
|
+
主题调色板改起来最直接:编辑 `themes/*.json` 后重新 `npm run verify`,再删掉 `~/.dsh-tui/themes/` 下对应文件让插件重装。
|
|
142
|
+
|
|
143
|
+
## 兼容性
|
|
144
|
+
|
|
145
|
+
- **dsh-TUI 版本下限:0.8.8**(状态行与设置面板;0.9.2 实测)。更旧的宿主缺 `dsh-tui-extensions` 扩展面时,插件自动降级为“仅安装三套主题”,不报错。
|
|
146
|
+
- Node `^22.19 || >=24`,纯 ESM,MIT。
|
package/cordis.patch.yml
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
# Bundle patch for dsh-tui-theme: insert the plugin as a profile row.
|
|
2
|
-
# The plugin only consumes optional seams (themes dir, tuiStatus,
|
|
3
|
-
# tuiSettingsSections, settings) and never registers shortcuts or commands.
|
|
4
|
-
# followSystem
|
|
5
|
-
#
|
|
6
|
-
- insert:
|
|
7
|
-
- id: dsh-tui-theme
|
|
8
|
-
name: 'dsh-tui-theme'
|
|
9
|
-
config:
|
|
10
|
-
autoInstallThemes: true
|
|
11
|
-
statusEnabled: true
|
|
12
|
-
followSystem:
|
|
13
|
-
showGlyph: true
|
|
14
|
-
showClock: true
|
|
15
|
-
showTurns: true
|
|
16
|
-
statusScope: pink-only
|
|
1
|
+
# Bundle patch for dsh-tui-theme: insert the plugin as a profile row.
|
|
2
|
+
# The plugin only consumes optional seams (themes dir, tuiStatus,
|
|
3
|
+
# tuiSettingsSections, settings) and never registers shortcuts or commands.
|
|
4
|
+
# followSystem stays off until dsh-TUI exposes a plugin-safe terminal query.
|
|
5
|
+
# Existing caches can still be applied when users explicitly enable it.
|
|
6
|
+
- insert:
|
|
7
|
+
- id: dsh-tui-theme
|
|
8
|
+
name: 'dsh-tui-theme'
|
|
9
|
+
config:
|
|
10
|
+
autoInstallThemes: true
|
|
11
|
+
statusEnabled: true
|
|
12
|
+
followSystem: false
|
|
13
|
+
showGlyph: true
|
|
14
|
+
showClock: true
|
|
15
|
+
showTurns: true
|
|
16
|
+
statusScope: pink-only
|
package/lib/types/autoTheme.d.ts
CHANGED
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Cached terminal-background follow for the pink theme pair.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - the caller wires this from the settings inject callback (see index.ts):
|
|
10
|
-
* that fires before the React tree mounts and reads ~/.dsh-tui/theme.json,
|
|
11
|
-
* so the cached detection still decides this boot;
|
|
12
|
-
* - a fresh OSC 11 query then refreshes the cache for the next boot. The
|
|
13
|
-
* very first enabling (or a system flip between boots) lands one boot
|
|
14
|
-
* late — the same cadence as the host's own "re-select auto or restart".
|
|
4
|
+
* dsh-TUI v0.9.2 does not expose a plugin terminal-query seam. Plugins must
|
|
5
|
+
* therefore not create their own stdin consumers or raw-mode leases: the host
|
|
6
|
+
* owns both through Ink. This module only applies a previously stored result
|
|
7
|
+
* before mount. A future host-owned query service can refresh that cache
|
|
8
|
+
* without changing the preference format used here.
|
|
15
9
|
*
|
|
16
10
|
* The pref write mirrors the host's writeThemePref byte-for-byte
|
|
17
|
-
* ({"theme": name}, 2-space indent)
|
|
18
|
-
* luminance threshold so both sides always agree on light/dark.
|
|
11
|
+
* ({"theme": name}, 2-space indent).
|
|
19
12
|
* @module dsh-tui-theme/autoTheme
|
|
20
13
|
*/
|
|
21
14
|
interface FollowCache {
|
|
@@ -26,38 +19,21 @@ interface FollowCache {
|
|
|
26
19
|
export declare function readThemePref(dataDir: string): string | undefined;
|
|
27
20
|
/** Persist the theme pref in the host's exact format. */
|
|
28
21
|
export declare function writeThemePref(name: string, dataDir: string): boolean;
|
|
29
|
-
/** The cached
|
|
22
|
+
/** The cached terminal-background result, if a prior compatible writer stored one. */
|
|
30
23
|
export declare function readFollowCache(dataDir: string): FollowCache | undefined;
|
|
31
|
-
/**
|
|
32
|
-
* Map a detected background to this pair's theme name.
|
|
33
|
-
* @param light - True for a light terminal background.
|
|
34
|
-
*/
|
|
24
|
+
/** Map a cached background to this pair's theme name. */
|
|
35
25
|
export declare function themeForBackground(light: boolean): string;
|
|
36
26
|
/**
|
|
37
|
-
* Apply the follow behavior synchronously
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* @returns The applied theme name, or undefined when no cache exists yet.
|
|
27
|
+
* Apply the cached follow behavior synchronously before mount. It returns the
|
|
28
|
+
* resolved name only when the existing preference already matched or the new
|
|
29
|
+
* preference committed successfully.
|
|
41
30
|
*/
|
|
42
31
|
export declare function applyCachedFollow(dataDir: string): string | undefined;
|
|
43
32
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* parsing is mounted; raw mode is restored to whatever it was.
|
|
48
|
-
* @param dataDir - The host data directory (~/.dsh-tui).
|
|
49
|
-
* @param stdout - Injectable for tests.
|
|
50
|
-
* @param stdin - Injectable for tests.
|
|
51
|
-
* @param setTimeoutFn - Injectable for tests.
|
|
52
|
-
* @returns The detected light-ness, or undefined when unavailable.
|
|
53
|
-
*/
|
|
54
|
-
export declare function refreshDetectedBackground(dataDir: string, stdout?: NodeJS.WriteStream, stdin?: NodeJS.ReadStream, setTimeoutFn?: typeof setTimeout): Promise<boolean | undefined>;
|
|
55
|
-
/**
|
|
56
|
-
* The whole follow sequence for apply(): cached value now (pre-mount),
|
|
57
|
-
* fresh detection for the next boot.
|
|
58
|
-
* @param dataDir - The host data directory (~/.dsh-tui).
|
|
59
|
-
* @param log - Info sink for the applied/refreshed outcomes.
|
|
33
|
+
* Apply a previously detected background without touching terminal I/O. A
|
|
34
|
+
* future host-owned terminal query service may refresh theme-follow.json; this
|
|
35
|
+
* plugin intentionally does not access stdin, stdout, or raw mode directly.
|
|
60
36
|
*/
|
|
61
|
-
export declare function runFollowSystem(dataDir: string, log: (message: string) => void): void;
|
|
37
|
+
export declare function runFollowSystem(dataDir: string, isCurrent: () => boolean, log: (message: string) => void): void;
|
|
62
38
|
export {};
|
|
63
39
|
//# sourceMappingURL=autoTheme.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autoTheme.d.ts","sourceRoot":"","sources":["../../src/autoTheme.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"autoTheme.d.ts","sourceRoot":"","sources":["../../src/autoTheme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH,UAAU,WAAW;IACnB,KAAK,EAAE,OAAO,CAAA;IACd,EAAE,EAAE,MAAM,CAAA;CACX;AAsCD,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGjE;AAED,yDAAyD;AACzD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAErE;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAGxE;AAED,yDAAyD;AACzD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEzD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMrE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,OAAO,EACxB,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC7B,IAAI,CASN"}
|
package/lib/types/autoTheme.js
CHANGED
|
@@ -1,33 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Cached terminal-background follow for the pink theme pair.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - the caller wires this from the settings inject callback (see index.ts):
|
|
10
|
-
* that fires before the React tree mounts and reads ~/.dsh-tui/theme.json,
|
|
11
|
-
* so the cached detection still decides this boot;
|
|
12
|
-
* - a fresh OSC 11 query then refreshes the cache for the next boot. The
|
|
13
|
-
* very first enabling (or a system flip between boots) lands one boot
|
|
14
|
-
* late — the same cadence as the host's own "re-select auto or restart".
|
|
4
|
+
* dsh-TUI v0.9.2 does not expose a plugin terminal-query seam. Plugins must
|
|
5
|
+
* therefore not create their own stdin consumers or raw-mode leases: the host
|
|
6
|
+
* owns both through Ink. This module only applies a previously stored result
|
|
7
|
+
* before mount. A future host-owned query service can refresh that cache
|
|
8
|
+
* without changing the preference format used here.
|
|
15
9
|
*
|
|
16
10
|
* The pref write mirrors the host's writeThemePref byte-for-byte
|
|
17
|
-
* ({"theme": name}, 2-space indent)
|
|
18
|
-
* luminance threshold so both sides always agree on light/dark.
|
|
11
|
+
* ({"theme": name}, 2-space indent).
|
|
19
12
|
* @module dsh-tui-theme/autoTheme
|
|
20
13
|
*/
|
|
21
|
-
import {
|
|
22
|
-
import { join } from 'node:path';
|
|
23
|
-
/** The pair this feature switches between (light terminal
|
|
14
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { basename, dirname, join } from 'node:path';
|
|
16
|
+
/** The pair this feature switches between (light terminal -> day). */
|
|
24
17
|
const LIGHT_THEME = 'pink-day';
|
|
25
18
|
const DARK_THEME = 'pink-night';
|
|
26
|
-
const QUERY = '\x1b]11;?\x07';
|
|
27
|
-
// OSC 11 reply: rgb:RRRR/GGGG/BBBB with 1-4 hex digits per channel, BEL or
|
|
28
|
-
// ST terminated.
|
|
29
|
-
const REPLY = /\x1b\]11;rgb:([0-9a-fA-F]{1,4})\/([0-9a-fA-F]{1,4})\/([0-9a-fA-F]{1,4})(?:\x07|\x1b\\)/;
|
|
30
|
-
const DETECT_TIMEOUT_MS = 400;
|
|
31
19
|
function prefPath(dir) {
|
|
32
20
|
return join(dir, 'theme.json');
|
|
33
21
|
}
|
|
@@ -43,12 +31,20 @@ function readJsonSync(path) {
|
|
|
43
31
|
}
|
|
44
32
|
}
|
|
45
33
|
function writeJsonSync(path, value) {
|
|
34
|
+
const temporary = join(dirname(path), `.${basename(path)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
|
|
46
35
|
try {
|
|
47
|
-
mkdirSync(
|
|
48
|
-
writeFileSync(
|
|
36
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
37
|
+
writeFileSync(temporary, JSON.stringify(value, null, 2), { flag: 'wx' });
|
|
38
|
+
renameSync(temporary, path);
|
|
49
39
|
return true;
|
|
50
40
|
}
|
|
51
41
|
catch {
|
|
42
|
+
try {
|
|
43
|
+
rmSync(temporary, { force: true });
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// The filesystem is already failing; a stale temp file is harmless.
|
|
47
|
+
}
|
|
52
48
|
return false;
|
|
53
49
|
}
|
|
54
50
|
}
|
|
@@ -61,127 +57,44 @@ export function readThemePref(dataDir) {
|
|
|
61
57
|
export function writeThemePref(name, dataDir) {
|
|
62
58
|
return writeJsonSync(prefPath(dataDir), { theme: name });
|
|
63
59
|
}
|
|
64
|
-
/** The cached
|
|
60
|
+
/** The cached terminal-background result, if a prior compatible writer stored one. */
|
|
65
61
|
export function readFollowCache(dataDir) {
|
|
66
62
|
const cached = readJsonSync(cachePath(dataDir));
|
|
67
63
|
return typeof cached?.light === 'boolean' ? cached : undefined;
|
|
68
64
|
}
|
|
69
|
-
|
|
70
|
-
writeJsonSync(cachePath(dataDir), { light, at: Date.now() });
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Map a detected background to this pair's theme name.
|
|
74
|
-
* @param light - True for a light terminal background.
|
|
75
|
-
*/
|
|
65
|
+
/** Map a cached background to this pair's theme name. */
|
|
76
66
|
export function themeForBackground(light) {
|
|
77
67
|
return light ? LIGHT_THEME : DARK_THEME;
|
|
78
68
|
}
|
|
79
69
|
/**
|
|
80
|
-
* Apply the follow behavior synchronously
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* @returns The applied theme name, or undefined when no cache exists yet.
|
|
70
|
+
* Apply the cached follow behavior synchronously before mount. It returns the
|
|
71
|
+
* resolved name only when the existing preference already matched or the new
|
|
72
|
+
* preference committed successfully.
|
|
84
73
|
*/
|
|
85
74
|
export function applyCachedFollow(dataDir) {
|
|
86
75
|
const cached = readFollowCache(dataDir);
|
|
87
76
|
if (cached === undefined)
|
|
88
77
|
return undefined;
|
|
89
78
|
const target = themeForBackground(cached.light);
|
|
90
|
-
if (readThemePref(dataDir)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return target;
|
|
94
|
-
}
|
|
95
|
-
/** Scale a 1-4 digit hex OSC channel to 8-bit. */
|
|
96
|
-
function channel8(hex) {
|
|
97
|
-
const value = parseInt(hex, 16);
|
|
98
|
-
if (hex.length >= 3)
|
|
99
|
-
return Math.round(value / (16 ** hex.length - 1) * 255);
|
|
100
|
-
return value * (hex.length === 2 ? 1 : 17);
|
|
101
|
-
}
|
|
102
|
-
/** The host's luminance test (ThemeProvider.isLightBackground). */
|
|
103
|
-
function isLightBackground(r, g, b) {
|
|
104
|
-
return 0.299 * r + 0.587 * g + 0.114 * b > 140;
|
|
79
|
+
if (readThemePref(dataDir) === target)
|
|
80
|
+
return target;
|
|
81
|
+
return writeThemePref(target, dataDir) ? target : undefined;
|
|
105
82
|
}
|
|
106
83
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* parsing is mounted; raw mode is restored to whatever it was.
|
|
111
|
-
* @param dataDir - The host data directory (~/.dsh-tui).
|
|
112
|
-
* @param stdout - Injectable for tests.
|
|
113
|
-
* @param stdin - Injectable for tests.
|
|
114
|
-
* @param setTimeoutFn - Injectable for tests.
|
|
115
|
-
* @returns The detected light-ness, or undefined when unavailable.
|
|
84
|
+
* Apply a previously detected background without touching terminal I/O. A
|
|
85
|
+
* future host-owned terminal query service may refresh theme-follow.json; this
|
|
86
|
+
* plugin intentionally does not access stdin, stdout, or raw mode directly.
|
|
116
87
|
*/
|
|
117
|
-
export function
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
resolve(undefined);
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
const wasRaw = stdin.isRaw === true;
|
|
124
|
-
try {
|
|
125
|
-
stdin.setRawMode?.(true);
|
|
126
|
-
}
|
|
127
|
-
catch {
|
|
128
|
-
resolve(undefined);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
let buffer = '';
|
|
132
|
-
let settled = false;
|
|
133
|
-
const finish = (light) => {
|
|
134
|
-
if (settled)
|
|
135
|
-
return;
|
|
136
|
-
settled = true;
|
|
137
|
-
clearTimeout(timer);
|
|
138
|
-
stdin.removeListener('data', onData);
|
|
139
|
-
try {
|
|
140
|
-
stdin.setRawMode?.(wasRaw);
|
|
141
|
-
}
|
|
142
|
-
catch {
|
|
143
|
-
// Stream already torn down — nothing to restore.
|
|
144
|
-
}
|
|
145
|
-
if (light !== undefined) {
|
|
146
|
-
writeFollowCache(light, dataDir);
|
|
147
|
-
const target = themeForBackground(light);
|
|
148
|
-
if (readThemePref(dataDir) !== target) {
|
|
149
|
-
writeThemePref(target, dataDir);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
resolve(light);
|
|
153
|
-
};
|
|
154
|
-
const onData = (chunk) => {
|
|
155
|
-
buffer += typeof chunk === 'string' ? chunk : chunk.toString('latin1');
|
|
156
|
-
const match = REPLY.exec(buffer);
|
|
157
|
-
if (match === null)
|
|
158
|
-
return;
|
|
159
|
-
finish(isLightBackground(channel8(match[1] ?? ''), channel8(match[2] ?? ''), channel8(match[3] ?? '')));
|
|
160
|
-
};
|
|
161
|
-
const timer = setTimeoutFn(() => finish(undefined), DETECT_TIMEOUT_MS);
|
|
162
|
-
if (stdin.isPaused())
|
|
163
|
-
stdin.resume();
|
|
164
|
-
stdin.on('data', onData);
|
|
165
|
-
stdout.write(QUERY);
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* The whole follow sequence for apply(): cached value now (pre-mount),
|
|
170
|
-
* fresh detection for the next boot.
|
|
171
|
-
* @param dataDir - The host data directory (~/.dsh-tui).
|
|
172
|
-
* @param log - Info sink for the applied/refreshed outcomes.
|
|
173
|
-
*/
|
|
174
|
-
export function runFollowSystem(dataDir, log) {
|
|
88
|
+
export function runFollowSystem(dataDir, isCurrent, log) {
|
|
89
|
+
if (!isCurrent())
|
|
90
|
+
return;
|
|
175
91
|
const applied = applyCachedFollow(dataDir);
|
|
176
|
-
if (
|
|
92
|
+
if (!isCurrent())
|
|
93
|
+
return;
|
|
94
|
+
if (applied === undefined) {
|
|
95
|
+
log('follow: cached background unavailable or preference write failed; keeping current choice');
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
177
98
|
log(`follow: applied cached background (${applied})`);
|
|
178
99
|
}
|
|
179
|
-
void refreshDetectedBackground(dataDir).then(light => {
|
|
180
|
-
if (light === undefined) {
|
|
181
|
-
log('follow: terminal background unavailable, keeping current choice');
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
log(`follow: detected ${light ? 'light' : 'dark'} terminal → ${themeForBackground(light)}`);
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
100
|
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* 个性化全部走既有接缝,不注册快捷键、不注册命令、不拦截任何输入:
|
|
5
5
|
* - 接缝四(主题):内置三套粉色 JSON(pink-night / pink-day / pink-ansi),
|
|
6
6
|
* 启动时复制进 ~/.dsh-tui/themes/(仅缺失时,绝不覆盖用户已有文件);
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
7
|
+
* - 缓存背景跟随:安全应用已有的终端背景缓存,在昼樱/夜樱间切换;
|
|
8
|
+
* dsh-TUI 未提供插件终端查询接缝时,不直接读写 stdin、raw mode 或 OSC 11;
|
|
9
9
|
* - 接缝十一(状态行):输入框上方一行小装饰(✿ · 时钟 · 本轮轮数);
|
|
10
10
|
* - 接缝六(设置区块):/settings 里一个可编辑面板,即时生效。
|
|
11
11
|
*
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAKlD,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEjF,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,wDAAwD;AACxD,MAAM,MAAM,MAAM,GAAG,eAAe,CAAA;AAUpC,eAAO,MAAM,MAAM,EAAE,WAAW,CAAC,MAAM,CAQrC,CAAA;AAyBF;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,MAAW,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAKlD,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEjF,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,wDAAwD;AACxD,MAAM,MAAM,MAAM,GAAG,eAAe,CAAA;AAUpC,eAAO,MAAM,MAAM,EAAE,WAAW,CAAC,MAAM,CAQrC,CAAA;AAyBF;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,MAAW,GAAG,IAAI,CA2D7D"}
|