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 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
- | **自动跟随背景** | 启动时序 | 检测终端/系统背景色(OSC 11,与宿主同阈值),在昼樱/夜樱间自动切换——pink 版的 auto |
9
- | **花符状态行** | `tuiStatus` | 输入框上方一行小装饰:✿ · 时钟 · 实时轮数(默认仅粉主题下显示) |
10
- | **设置面板** | `tuiSettingsSections` | `/settings` 里一个可编辑区块,改完即时生效 |
11
-
12
- **明确不做的事**:不注册快捷键、不注册/修改任何命令、不拦截输入、不追加会话事件、不注入 system prompt。卸载即无痕(可选删除主题文件)。
13
-
14
- ## 主题预览
15
-
16
- | 主题 | 基底 | 风格 |
17
- | --- | --- | --- |
18
- | `pink-night` 夜樱 | dark | 深梅紫底、玫瑰粉强调、粉鲸鱼吉祥物,96 键全覆盖 |
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.0
29
-
30
- | 昼樱 `pink-day` | 夜樱 `pink-night` |
31
- | :---: | :---: |
32
- | ![pink-day 主题界面](docs/screenshots/pink-day.png) | ![pink-night 主题界面](docs/screenshots/pink-night.png) |
33
-
34
- `/settings` 里的 pink-theme 区块(跟随终端背景 / 花符 / 时钟 / 轮数,保存即时生效):
35
-
36
- ![pink-theme 设置区块](docs/screenshots/settings.png)
37
-
38
- > 图中底栏上下文进度条的蓝色分段与 ❯ 提示符是宿主硬编码的,见下文[宿主限制](#受宿主限制目前无法定制的部分)。
39
-
40
- ## 自动跟随终端背景
41
-
42
- 设置里的“跟随终端背景”开启后(本部署默认开启):
43
-
44
- - 每次启动检测终端背景(跟随系统主题的终端会随系统深浅变化),亮 → `pink-day`,暗 → `pink-night`,写入 `~/.dsh-tui/theme.json`,与宿主 `auto` 伪主题同款行为、同一亮度阈值;
45
- - 检测结果缓存在 `~/.dsh-tui/theme-follow.json`,因此切换在**启动瞬间**就生效;首次开启或系统刚翻转后的那次启动可能滞后一拍(与宿主 auto “重新选择或重启后跟上”一致);
46
- - 开启此功能后 `/theme` 的选择由插件接管(每次启动都会按背景覆写);关闭它即可恢复手动选择;
47
- - `DSH_TUI_THEME` 环境变量仍然最优先(宿主行为,插件不覆盖环境变量)。
48
-
49
- ## 安装
50
-
51
- ```sh
52
- # 方式一:从 npm(已发布)
53
- dsh plugin --profile dsh-tui add -w dsh-tui-theme
54
-
55
- # 方式二:从 GitHub
56
- dsh plugin --profile dsh-tui add -w github:xiaoxiaohaigui/dsh-tui-theme
57
-
58
- # 方式三:本地路径(开发/自用)
59
- dsh plugin --profile dsh-tui add -w /path/to/dsh-tui-theme
60
- ```
61
-
62
- 重启 dsh-TUI 后插件自动把三套主题复制进 `~/.dsh-tui/themes/`(**仅缺失时复制,绝不覆盖你已有的同名文件**),然后:
63
-
64
- ```sh
65
- # dsh-TUI
66
- /theme # 选择器:夜樱 / 昼樱 / 樱·ANSI
67
- /theme pink-night # 或直接切换(开启跟随后由插件接管)
68
- ```
69
-
70
- ## 配置
71
-
72
- 配置有三层,优先级:`/settings` 用户层 > `cordis.yml` 配置层 > 内置默认值。
73
-
74
- `/settings` 里找到 **pink-theme** 区块即可编辑:
75
-
76
- | 字段 | 默认 | 说明 |
77
- | --- | --- | --- |
78
- | `followSystem` | 部署默认开 | 跟随终端背景:昼樱 ↔ 夜樱自动切换 |
79
- | `showGlyph` | `true` | 花符:开 = ✿ 开头,关 = 不显示 |
80
- | `showClock` | `true` | 显示 HH:MM 时钟 |
81
- | `showTurns` | `true` | 显示当前会话轮数(`N✦`,自本次启动起计) |
82
- | `statusScope` | `pink-only` | 状态行展示:`pink-only` 仅樱花粉主题 / `all-themes` 所有主题 |
83
-
84
- 三项装饰全关时状态行整体消失。另有仅 profile 层的开关(`cordis.patch.yml`,不出现在 /settings):`autoInstallThemes`、`statusEnabled`。
85
-
86
- ## 受宿主限制、目前无法定制的部分
87
-
88
- 以下元素的颜色/形态由 dsh-TUI 宿主**硬编码**,不读取任何主题键,主题 JSON 与插件接缝都覆盖不到(dsh-TUI 0.9.0 实测):
89
-
90
- | 元素 | 现状 | 位置(宿主源码) |
91
- | --- | --- | --- |
92
- | 输入框 ❯ 提示符 | 默认态无颜色参数(终端默认前景色,模型工作时变暗);最高推理档充能动画用**写死的蓝色 ramp**(深色端 `#82B9FF` / 浅色端 `#1E5FEB`) | `EffortChargeGlyph.tsx`、`trajectory/effortIgnition.ts` |
93
- | 底栏上下文进度条分段色 | system / prompt / assistant / thinking / tools 五段为**写死的藏青→品牌蓝系**(`#22305F`→`#5A7CFF`),永远不随主题变化 | `screens/StatusMetrics.ts` |
94
- | 进度条空余段配色 | 宿主按 `themeName === 'light'` **字符串比较**取浅色配色——自定义浅色主题(如 pink-day)不等于 `'light'`,会拿到深色空余段,在浅色终端上偏深 | `screens/StatusLine.tsx` |
95
- | 状态行文字颜色 | 插件状态行(tuiStatus)由宿主统一以**无色 + 终端 dim** 渲染,插件无法指定颜色(✿ 行因此继承终端默认前景色) | `screens/Chat.tsx` |
96
- | 主界面组件与布局 | 顶栏像素鲸鱼、工具卡、输入框等宿主组件不可被插件替换或改布局——平台规则(内建优先,无组件替换接缝);主题能碰的只有颜色层 | 宿主架构约定 |
97
-
98
- 这些都需要上游 dsh-TUI 修改(例如:把充能色/进度条分段色接入主题键、空余段判断改用 `isLightThemeActive()`)。上游修复前,任何社区主题包都受同样约束。
99
-
100
- ## 卸载
101
-
102
- ```sh
103
- dsh plugin --profile dsh-tui remove -w dsh-tui-theme # 移除插件
104
- rm ~/.dsh-tui/themes/pink-{night,day,ansi}.json # 可选:删除主题文件
105
- rm ~/.dsh-tui/theme-follow.json # 可选:删除跟随缓存
106
- ```
107
-
108
- ## 开发
109
-
110
- ```sh
111
- pnpm install && pnpm build # tsc -> lib/types/
112
- npm run verify # 沙盒测试(临时 HOME + 伪造 TTY,不碰真实 ~/.dsh-tui)
113
- ```
114
-
115
- 主题调色板改起来最直接:编辑 `themes/*.json` 后重新 `npm run verify`,再删掉 `~/.dsh-tui/themes/` 下对应文件让插件重装。
116
-
117
- ## 兼容性
118
-
119
- - **dsh-TUI 版本下限:0.8.8**(全功能,含状态行与设置面板;0.9.0 实测)。更旧的宿主缺 `dsh-tui-extensions` 扩展面时,插件自动降级为“仅安装三套主题 + 背景跟随”,不报错。
120
- - Node `^22.19 || >=24`,纯 ESM,MIT。
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
+ | ![pink-day 主题界面](docs/screenshots/pink-day.png) | ![pink-night 主题界面](docs/screenshots/pink-night.png) |
33
+
34
+ `/settings` 里的 pink-theme 区块(已缓存背景 / 花符 / 时钟 / 轮数,保存即时生效):
35
+
36
+ ![pink-theme 设置区块](docs/screenshots/settings.png)
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 opts this deployment into background-follow (the schema
5
- # default is off for generic installs).
6
- - insert:
7
- - id: dsh-tui-theme
8
- name: 'dsh-tui-theme'
9
- config:
10
- autoInstallThemes: true
11
- statusEnabled: true
12
- followSystem: true
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
@@ -1,21 +1,14 @@
1
1
  /**
2
- * Terminal-background follow: the pink pair's `auto`.
2
+ * Cached terminal-background follow for the pink theme pair.
3
3
  *
4
- * The host's `auto` pseudo-theme resolves builtin light/dark via an OSC 11
5
- * query at mount and keeps the result in memory only user themes cannot
6
- * ride it. This module gives pink-day/pink-night the same behavior from the
7
- * plugin side:
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) and the light test mirrors the host's
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 detection, if one exists. */
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 from cache: writes the resolved
38
- * theme name into the pref when it differs. Safe before mount pure fs.
39
- * @param dataDir - The host data directory (~/.dsh-tui).
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
- * Query the terminal background (OSC 11) and refresh the cache + pref.
45
- * Best effort: no TTY, an unresponsive terminal, or a parse failure just
46
- * leaves the previous state intact. Runs before the host's own stdin
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;;;;;;;;;;;;;;;;;;;GAmBG;AAeH,UAAU,WAAW;IACnB,KAAK,EAAE,OAAO,CAAA;IACd,EAAE,EAAE,MAAM,CAAA;CACX;AA4BD,+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,2CAA2C;AAC3C,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAGxE;AAMD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEzD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQrE;AAcD;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAM,CAAC,WAA4B,EAC3C,KAAK,GAAE,MAAM,CAAC,UAA0B,EACxC,YAAY,GAAE,OAAO,UAAuB,GAC3C,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAmD9B;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC7B,IAAI,CAYN"}
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"}
@@ -1,33 +1,21 @@
1
1
  /**
2
- * Terminal-background follow: the pink pair's `auto`.
2
+ * Cached terminal-background follow for the pink theme pair.
3
3
  *
4
- * The host's `auto` pseudo-theme resolves builtin light/dark via an OSC 11
5
- * query at mount and keeps the result in memory only user themes cannot
6
- * ride it. This module gives pink-day/pink-night the same behavior from the
7
- * plugin side:
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) and the light test mirrors the host's
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 { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
22
- import { join } from 'node:path';
23
- /** The pair this feature switches between (light terminal day). */
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(join(path, '..'), { recursive: true });
48
- writeFileSync(path, JSON.stringify(value, null, 2));
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 detection, if one exists. */
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
- function writeFollowCache(light, dataDir) {
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 from cache: writes the resolved
81
- * theme name into the pref when it differs. Safe before mount pure fs.
82
- * @param dataDir - The host data directory (~/.dsh-tui).
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) !== target) {
91
- writeThemePref(target, dataDir);
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
- * Query the terminal background (OSC 11) and refresh the cache + pref.
108
- * Best effort: no TTY, an unresponsive terminal, or a parse failure just
109
- * leaves the previous state intact. Runs before the host's own stdin
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 refreshDetectedBackground(dataDir, stdout = process.stdout, stdin = process.stdin, setTimeoutFn = setTimeout) {
118
- return new Promise(resolve => {
119
- if (stdout.isTTY !== true || stdin.isTTY !== true) {
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 (applied !== undefined && existsSync(prefPath(dataDir))) {
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
  }
@@ -4,8 +4,8 @@
4
4
  * 个性化全部走既有接缝,不注册快捷键、不注册命令、不拦截任何输入:
5
5
  * - 接缝四(主题):内置三套粉色 JSON(pink-night / pink-day / pink-ansi),
6
6
  * 启动时复制进 ~/.dsh-tui/themes/(仅缺失时,绝不覆盖用户已有文件);
7
- * - 自动跟随:检测终端/系统背景色(OSC 11,与宿主同阈值),在昼樱/夜樱
8
- * 间自动切换——pink 版的 auto
7
+ * - 缓存背景跟随:安全应用已有的终端背景缓存,在昼樱/夜樱间切换;
8
+ * dsh-TUI 未提供插件终端查询接缝时,不直接读写 stdin、raw mode 或 OSC 11
9
9
  * - 接缝十一(状态行):输入框上方一行小装饰(✿ · 时钟 · 本轮轮数);
10
10
  * - 接缝六(设置区块):/settings 里一个可编辑面板,即时生效。
11
11
  *
@@ -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,CAiE7D"}
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"}