pi-one-ui 0.1.1 → 0.2.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 +66 -39
- package/extensions/{shell → app/commands}/settings-previews.ts +7 -7
- package/extensions/app/config/renderer.ts +13 -63
- package/extensions/app/config/shell.ts +24 -138
- package/extensions/app/config/store.ts +290 -0
- package/extensions/app/host/pi-extension-port.ts +25 -0
- package/extensions/app/host/pi-ui-port.ts +52 -0
- package/extensions/app/host/tui-capabilities.ts +27 -0
- package/extensions/app/overlay/input-router.ts +63 -0
- package/extensions/app/overlay/overlay-manager.ts +39 -0
- package/extensions/{shell → app/overlay}/selector-border.ts +3 -3
- package/extensions/app/overlay/selector-controller.ts +104 -0
- package/extensions/app/ownership/layout-registry.ts +65 -0
- package/extensions/app/ownership.ts +21 -7
- package/extensions/app/panel.ts +261 -140
- package/extensions/app/runtime/event-coordinator.ts +84 -0
- package/extensions/app/runtime/render-scheduler.ts +56 -0
- package/extensions/app/runtime/runtime-state.ts +83 -0
- package/extensions/app/runtime/tui-runtime.ts +546 -0
- package/extensions/features/{context.ts → context-inspector/index.ts} +30 -23
- package/extensions/features/{shell/flush-docked-bash.ts → flush-docked-bash.ts} +1 -4
- package/extensions/index.ts +2 -48
- package/extensions/layouts/context/index.ts +188 -0
- package/extensions/{shell → layouts/context/message}/user-message-styles.ts +2 -2
- package/extensions/{shell → layouts/context/message}/user-message.ts +2 -2
- package/extensions/{transcript → layouts/context}/renderer/compact-mode.ts +17 -13
- package/extensions/layouts/context/renderer/context-refresh.ts +79 -0
- package/extensions/{transcript → layouts/context}/renderer/default-mode.ts +3 -3
- package/extensions/{transcript → layouts/context}/renderer/index.ts +14 -14
- package/extensions/{transcript → layouts/context}/renderer/mouse/hover.ts +2 -2
- package/extensions/{transcript → layouts/context}/renderer/mouse/interaction.ts +26 -7
- package/extensions/{transcript → layouts/context}/renderer/mouse/layout.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/mouse/packets.ts +3 -3
- package/extensions/{transcript → layouts/context}/renderer/mouse/scroll.ts +12 -7
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-component.ts +2 -2
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-edit-render.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-highlight.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-layout.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-limits.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-palette.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-presentation.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-renderer.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-write-render.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/index.ts +2 -2
- package/extensions/{transcript → layouts/context}/renderer/tool/grouping.ts +4 -4
- package/extensions/{transcript → layouts/context}/renderer/tool/message-display.ts +6 -4
- package/extensions/{transcript → layouts/context}/renderer/tool/names.ts +2 -2
- package/extensions/{transcript → layouts/context}/renderer/tool/result.ts +3 -3
- package/extensions/{features/agent-summary → layouts/context/summary}/core.ts +1 -1
- package/extensions/{features → layouts/context/thinking}/compact-thinking.ts +7 -7
- package/extensions/{shell → layouts/editor}/accent-rail-editor.ts +8 -8
- package/extensions/{shell → layouts/editor}/completion-menu.ts +3 -6
- package/extensions/layouts/editor/controller.ts +613 -0
- package/extensions/{shell → layouts/editor}/editor-metadata-format.ts +6 -3
- package/extensions/layouts/editor/factory.ts +159 -0
- package/extensions/layouts/editor/index.ts +16 -0
- package/extensions/{shell → layouts/editor}/minimalist-editor.ts +4 -4
- package/extensions/layouts/editor/ownership.ts +103 -0
- package/extensions/{shell → layouts/editor}/ui.ts +2 -2
- package/extensions/layouts/footer/controller.ts +392 -0
- package/extensions/layouts/footer/data.ts +41 -0
- package/extensions/{shell → layouts/footer}/extension-status.ts +2 -2
- package/extensions/{shell → layouts/footer}/footer-layout.ts +1 -1
- package/extensions/{shell → layouts/footer}/footer.ts +11 -8
- package/extensions/layouts/footer/index.ts +24 -0
- package/extensions/layouts/header/index.ts +5 -0
- package/extensions/{features/shell → layouts/header}/startup-header.ts +105 -19
- package/extensions/layouts/working-line/controller.ts +291 -0
- package/extensions/{shell → layouts/working-line}/interaction-summary.ts +3 -3
- package/extensions/{shell → layouts/working-line}/working-line-spinners.ts +1 -1
- package/extensions/{shell → layouts/working-line}/working-line.ts +4 -4
- package/extensions/{shell → services}/live-context.ts +29 -1
- package/extensions/services/project-refresh.ts +374 -0
- package/extensions/{shell → services}/project-state.ts +5 -5
- package/extensions/{shell/state.ts → services/session-state.ts} +75 -5
- package/extensions/{shell → shared}/format.ts +4 -4
- package/extensions/tools/component-tree.ts +6 -6
- package/package.json +4 -4
- package/extensions/shell/index.ts +0 -1659
- package/extensions/shell/project-refresh.ts +0 -130
- package/extensions/shell/settings-command.ts +0 -2144
- package/extensions/transcript/index.ts +0 -65
- package/extensions/transcript/renderer/transcript-refresh.ts +0 -79
- /package/extensions/{shell → app/ownership}/prototype-patch-registry.ts +0 -0
- /package/extensions/{shell → app/runtime}/session-lifecycle.ts +0 -0
- /package/extensions/features/{shell/aliases.ts → aliases.ts} +0 -0
- /package/extensions/features/{shell → legacy}/working-message.ts +0 -0
- /package/extensions/features/{reference → session-reference}/index.ts +0 -0
- /package/extensions/features/{reference → session-reference}/session.ts +0 -0
- /package/extensions/features/{reference/subagent.ts → subagent-autocomplete.ts} +0 -0
- /package/extensions/{shell → layouts/context/message}/user-message-osc.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/markdown-enhance.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/ATTRIBUTION.md +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/ansi-utils.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-header.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-inline.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-parse.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-text.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/line-width-safety.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/shiki-highlight.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/write-execution.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/show-more-hint.ts +0 -0
- /package/extensions/{features/agent-summary → layouts/context/summary}/index.ts +0 -0
- /package/extensions/{shell → layouts/editor}/accent-rail-layout-patch.ts +0 -0
- /package/extensions/{shell → layouts/editor}/editor-transfer.ts +0 -0
- /package/extensions/{shell → layouts/footer}/footer-format.ts +0 -0
- /package/extensions/{shell → layouts/working-line}/working-line-messages.ts +0 -0
- /package/extensions/{shell/git.ts → services/git-data.ts} +0 -0
- /package/extensions/{shell/package-version.ts → services/package-data.ts} +0 -0
- /package/extensions/{shell/runtime.ts → services/runtime-data.ts} +0 -0
- /package/extensions/{shell → services}/telemetry.ts +0 -0
- /package/extensions/{shell → shared}/icons.ts +0 -0
- /package/extensions/{shell → shared}/style.ts +0 -0
package/README.md
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
# pi-one-ui
|
|
2
2
|
|
|
3
|
-
`pi-one-ui`
|
|
4
|
-
|
|
5
|
-
- **Zentui**:Editor、User message、Working line、Selector border、Footer
|
|
6
|
-
- **CC Style**:Tool renderer、Rich diff、Thinking、Compact transcript、鼠标交互和辅助功能
|
|
7
|
-
|
|
8
|
-
安装后只需要启用这个包,不需要再单独安装 `pi-zentui` 或 `pi-cc-extensions`。
|
|
3
|
+
`pi-one-ui` 是一个统一的 Pi TUI 插件包,将 Zentui 的终端外壳能力与 Claude Code 风格的对话内容渲染合并到一个插件中。
|
|
9
4
|
|
|
10
5
|
## 安装
|
|
11
6
|
|
|
@@ -23,23 +18,37 @@ pi install git:github.com/<your-account>/pi-one-ui
|
|
|
23
18
|
/oneui
|
|
24
19
|
```
|
|
25
20
|
|
|
26
|
-
`/oneui`
|
|
21
|
+
`/oneui` 是唯一的公开管理命令。设置面板按照 TUI 的视觉 Layout 划分:
|
|
27
22
|
|
|
28
23
|
```text
|
|
29
|
-
|
|
24
|
+
Header → Context → WorkingLine → Editor → Footer → Features → Presets
|
|
30
25
|
```
|
|
31
26
|
|
|
32
|
-
|
|
27
|
+
其中:
|
|
28
|
+
|
|
29
|
+
- **Header**:启动信息、Logo、快捷键提示。
|
|
30
|
+
- **Context**:对话内容区,包含 User Message、Assistant Message、Thinking、Tool、Diff、Summary 和鼠标交互。
|
|
31
|
+
- **WorkingLine**:Pi 的工作状态行、spinner、token/thought/elapsed 信息。
|
|
32
|
+
- **Editor**:输入编辑器、completion、metadata、Accent Rail 和 Minimalist 样式。
|
|
33
|
+
- **Footer**:目录、Git、runtime、token、cost、extension status 等信息。
|
|
34
|
+
- **Features**:Context Inspector、Session Reference、Subagent Autocomplete、Aliases 等行为能力。
|
|
35
|
+
- **Overlay**:设置面板和 Context Inspector 等临时浮层由统一 OverlayManager 跟踪。
|
|
36
|
+
|
|
37
|
+
## 配置
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
配置文件:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
~/.pi/agent/pi-one-ui.json
|
|
43
|
+
```
|
|
35
44
|
|
|
36
|
-
|
|
45
|
+
当前仍兼容 v1 配置结构:
|
|
37
46
|
|
|
38
47
|
```json
|
|
39
48
|
{
|
|
40
49
|
"version": 1,
|
|
41
50
|
"components": {
|
|
42
|
-
"editor": { "enabled": true, "style": "
|
|
51
|
+
"editor": { "enabled": true, "style": "opencode" },
|
|
43
52
|
"userMessages": { "enabled": true, "style": "framed" },
|
|
44
53
|
"workingLine": { "enabled": true },
|
|
45
54
|
"footer": { "style": "starship" }
|
|
@@ -52,34 +61,44 @@ Zentui 的配置保留在 JSON 根级,CC Style 的配置放在 `renderer` 下
|
|
|
52
61
|
}
|
|
53
62
|
```
|
|
54
63
|
|
|
55
|
-
|
|
64
|
+
所有读写已经经过统一 ConfigStore;旧的 `zentui.json`、`claude-code-style.json` 和 `pi-mine-ui.json` 会被兼容读取并保留。`enableWorkingMessage` 仍可被旧配置读取,但统一插件不会注册第二套 working-message 实现,WorkingLine 的唯一 owner 是本插件的 WorkingLine layout。
|
|
56
65
|
|
|
57
|
-
|
|
58
|
-
- `vendor/pi-cc-extensions/README.md`
|
|
59
|
-
|
|
60
|
-
Working line 的 owner 固定为 Zentui,因此默认关闭 CC Style 的 `enableWorkingMessage`,避免两个扩展争抢 Pi 的无 key working row。
|
|
61
|
-
|
|
62
|
-
## 来源与目录结构
|
|
63
|
-
|
|
64
|
-
本项目基于以下上游项目进行融合开发:
|
|
65
|
-
|
|
66
|
-
- `pi-zentui`:提供 Shell chrome、Editor、Footer、Working line 等能力
|
|
67
|
-
- `pi-cc-extensions`:提供 Tool renderer、Diff、Thinking 和 Transcript 能力
|
|
68
|
-
|
|
69
|
-
上游源码快照保存在 `vendor/` 中,仅用于追踪和同步。`extensions/` 按 `pi-one-ui` 的产品能力组织,不保留上游项目的目录结构:
|
|
66
|
+
## 源码结构
|
|
70
67
|
|
|
71
68
|
```text
|
|
72
69
|
extensions/
|
|
73
|
-
index.ts
|
|
74
|
-
app/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
index.ts # 唯一插件入口,创建 TuiRuntime
|
|
71
|
+
app/
|
|
72
|
+
runtime/ # TuiRuntime、state、事件和调度
|
|
73
|
+
host/ # Pi API 的窄化 host ports
|
|
74
|
+
config/ # ConfigStore 与配置领域解析
|
|
75
|
+
ownership/ # Layout ownership
|
|
76
|
+
overlay/ # OverlayManager、InputRouter、selector
|
|
77
|
+
commands/ # /oneui and settings panel previews
|
|
78
|
+
layouts/
|
|
79
|
+
header/ # Header layout
|
|
80
|
+
context/ # 原 Transcript:对话内容区
|
|
81
|
+
message/ # User Message
|
|
82
|
+
thinking/ # Thinking
|
|
83
|
+
renderer/ # Tool、Diff、Markdown、Mouse
|
|
84
|
+
summary/ # Agent summary
|
|
85
|
+
working-line/ # WorkingLine 与 interaction summary
|
|
86
|
+
editor/ # Editor 及其样式实现
|
|
87
|
+
footer/ # Footer 及其布局/format/status
|
|
88
|
+
features/
|
|
89
|
+
aliases.ts
|
|
90
|
+
context-inspector/ # /context,不是 Context 对话区
|
|
91
|
+
flush-docked-bash.ts
|
|
92
|
+
legacy/ # standalone compatibility implementation
|
|
93
|
+
session-reference/
|
|
94
|
+
subagent-autocomplete.ts
|
|
95
|
+
services/ # Git、runtime、package、project、session 数据
|
|
96
|
+
shared/ # ANSI、format、style、icons 等共享实现
|
|
97
|
+
tools/ # 底层组件树、patch 和 terminal helpers
|
|
98
|
+
vendor/ # 只读上游参考快照
|
|
80
99
|
```
|
|
81
100
|
|
|
82
|
-
|
|
101
|
+
`TuiRuntime` 是唯一 composition root,并直接装配 Layout controllers、services 和 shared lifecycle effects。standalone compatibility harness 只位于 `tests/support/`,不进入安装包。
|
|
83
102
|
|
|
84
103
|
## 开发
|
|
85
104
|
|
|
@@ -90,12 +109,20 @@ npm test
|
|
|
90
109
|
npm run pack:check
|
|
91
110
|
```
|
|
92
111
|
|
|
93
|
-
|
|
112
|
+
完整检查:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npm run verify
|
|
116
|
+
```
|
|
94
117
|
|
|
95
|
-
|
|
96
|
-
- `Vitest`:Shell 及其生命周期、布局和配置测试
|
|
118
|
+
测试按领域划分:
|
|
97
119
|
|
|
98
|
-
|
|
120
|
+
- `tests/context-*`:Context 内容区、Tool、Diff、Thinking 和鼠标行为。
|
|
121
|
+
- `tests/working-line-*`:WorkingLine 和 turn summary。
|
|
122
|
+
- `tests/editor-*`:Editor、completion、metadata、transfer 和 Accent Rail。
|
|
123
|
+
- `tests/footer-*`:Footer、Footer format/layout/status。
|
|
124
|
+
- `tests/services-*`:Git、runtime、project、session 和 telemetry。
|
|
125
|
+
- `tests/shell-*`:standalone compatibility tests for the remaining layout lifecycle glue。
|
|
99
126
|
|
|
100
127
|
本地启动:
|
|
101
128
|
|
|
@@ -103,4 +130,4 @@ npm run pack:check
|
|
|
103
130
|
npm run pi:dev
|
|
104
131
|
```
|
|
105
132
|
|
|
106
|
-
|
|
133
|
+
上游快照保存在 `vendor/`,仅用于追踪和同步。不要直接修改 `vendor/` 或 `node_modules/`。
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import { renderAccentRailEditorFrame } from "
|
|
4
|
-
import type { PolishedTuiConfig } from "../
|
|
5
|
-
import { sanitizeEditorMetadataText } from "
|
|
6
|
-
import { renderMinimalistFrame } from "
|
|
7
|
-
import { safeThemeFg } from "
|
|
8
|
-
import { renderPolishedEditorFrame } from "
|
|
9
|
-
import { renderUserMessageStyle } from "
|
|
3
|
+
import { renderAccentRailEditorFrame } from "../../layouts/editor/accent-rail-editor.ts";
|
|
4
|
+
import type { PolishedTuiConfig } from "../config/shell.ts";
|
|
5
|
+
import { sanitizeEditorMetadataText } from "../../layouts/editor/editor-metadata-format.ts";
|
|
6
|
+
import { renderMinimalistFrame } from "../../layouts/editor/minimalist-editor.ts";
|
|
7
|
+
import { safeThemeFg } from "../../shared/style.ts";
|
|
8
|
+
import { renderPolishedEditorFrame } from "../../layouts/editor/ui.ts";
|
|
9
|
+
import { renderUserMessageStyle } from "../../layouts/context/message/user-message-styles.ts";
|
|
10
10
|
|
|
11
11
|
export const SETTINGS_PREVIEW_MAX_WIDTH = 72;
|
|
12
12
|
export const SETTINGS_PREVIEW_MAX_ROWS = 10;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import type { CompactThinkingConfig } from "../../
|
|
2
|
-
import {
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
1
|
+
import type { CompactThinkingConfig } from "../../layouts/context/thinking/compact-thinking.ts";
|
|
2
|
+
import { configStore, type ConfigRecord } from "./store.ts";
|
|
5
3
|
|
|
6
4
|
export type CompactStyleMode = "on" | "compact" | "off";
|
|
7
5
|
|
|
@@ -67,58 +65,6 @@ export type Config = {
|
|
|
67
65
|
enableAliases: boolean;
|
|
68
66
|
};
|
|
69
67
|
|
|
70
|
-
const AGENT_DIR =
|
|
71
|
-
process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
|
|
72
|
-
const CONFIG_PATH = join(AGENT_DIR, "pi-one-ui.json");
|
|
73
|
-
const LEGACY_UNIFIED_CONFIG_PATH = join(AGENT_DIR, "pi-mine-ui.json");
|
|
74
|
-
const LEGACY_CONFIG_PATH = join(AGENT_DIR, "claude-code-style.json");
|
|
75
|
-
const LEGACY_ZENTUI_CONFIG_PATH = join(AGENT_DIR, "zentui.json");
|
|
76
|
-
|
|
77
|
-
type ConfigRecord = Record<string, unknown>;
|
|
78
|
-
|
|
79
|
-
function readJson(path: string): ConfigRecord | undefined {
|
|
80
|
-
try {
|
|
81
|
-
if (!existsSync(path)) return undefined;
|
|
82
|
-
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
83
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
84
|
-
? (value as ConfigRecord)
|
|
85
|
-
: undefined;
|
|
86
|
-
} catch {
|
|
87
|
-
return undefined;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Keep the two upstream configuration schemas under one portable file. Zentui
|
|
93
|
-
* owns the root fields; the CC renderer owns `renderer`. Existing installations
|
|
94
|
-
* are imported once without deleting their legacy files.
|
|
95
|
-
*/
|
|
96
|
-
function readUnifiedConfig(): ConfigRecord {
|
|
97
|
-
const current = readJson(CONFIG_PATH) ?? readJson(LEGACY_UNIFIED_CONFIG_PATH);
|
|
98
|
-
if (current) {
|
|
99
|
-
if (!existsSync(CONFIG_PATH)) {
|
|
100
|
-
try {
|
|
101
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(current, null, 2));
|
|
102
|
-
} catch {
|
|
103
|
-
// Keep using the legacy file in memory if migration cannot be written.
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return current;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const shell = readJson(LEGACY_ZENTUI_CONFIG_PATH) ?? {};
|
|
110
|
-
const renderer = readJson(LEGACY_CONFIG_PATH);
|
|
111
|
-
if (!renderer && Object.keys(shell).length === 0) return {};
|
|
112
|
-
|
|
113
|
-
const migrated = { ...shell, version: 1, renderer: renderer ?? {} };
|
|
114
|
-
try {
|
|
115
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(migrated, null, 2));
|
|
116
|
-
} catch {
|
|
117
|
-
// A read-only agent directory still uses the migrated in-memory value.
|
|
118
|
-
}
|
|
119
|
-
return migrated;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
68
|
function rendererConfigFrom(record: ConfigRecord): ConfigRecord {
|
|
123
69
|
const renderer = record.renderer;
|
|
124
70
|
return renderer && typeof renderer === "object" && !Array.isArray(renderer)
|
|
@@ -422,22 +368,26 @@ export function formatConfigStatus(source: Config = config): string {
|
|
|
422
368
|
/** 进程内唯一的活动配置对象。读取直接用 `config.xxx`;写入必须走 `updateConfig`。 */
|
|
423
369
|
export const config: Config = loadConfig();
|
|
424
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Loads and normalizes the renderer projection from the shared store.
|
|
373
|
+
*/
|
|
425
374
|
function loadConfig(): Config {
|
|
426
375
|
try {
|
|
427
|
-
|
|
428
|
-
return normalizeConfig(rendererConfigFrom(unified));
|
|
376
|
+
return normalizeConfig(rendererConfigFrom(configStore.read()));
|
|
429
377
|
} catch {
|
|
430
378
|
// Ignore bad config and fall back to defaults.
|
|
431
379
|
}
|
|
432
380
|
return { ...DEFAULT_CONFIG };
|
|
433
381
|
}
|
|
434
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Persists the in-memory renderer projection through the shared store.
|
|
385
|
+
*/
|
|
435
386
|
export function saveConfig() {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
);
|
|
387
|
+
configStore.update((record) => {
|
|
388
|
+
record.version = 1;
|
|
389
|
+
record.renderer = config;
|
|
390
|
+
});
|
|
441
391
|
}
|
|
442
392
|
|
|
443
393
|
/** 运行时配置写入的唯一入口:合并 + 规范化 + 持久化。 */
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
closeSync,
|
|
4
|
-
existsSync,
|
|
5
|
-
fchmodSync,
|
|
6
|
-
fsyncSync,
|
|
7
|
-
lstatSync,
|
|
8
|
-
openSync,
|
|
9
|
-
readFileSync,
|
|
10
|
-
realpathSync,
|
|
11
|
-
renameSync,
|
|
12
|
-
statSync,
|
|
13
|
-
unlinkSync,
|
|
14
|
-
writeFileSync,
|
|
15
|
-
} from "node:fs";
|
|
16
|
-
import { basename, dirname, join } from "node:path";
|
|
17
|
-
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
18
1
|
import {
|
|
19
2
|
ICON_GLYPH_KEYS,
|
|
20
3
|
type IconGlyphs,
|
|
@@ -23,14 +6,20 @@ import {
|
|
|
23
6
|
normalizeIconMode,
|
|
24
7
|
type ResolvedIcons,
|
|
25
8
|
resolveConfiguredIcons,
|
|
26
|
-
} from "../../
|
|
27
|
-
import { isSupportedColorSpec } from "../../
|
|
28
|
-
import { normalizeWorkingLineMessages } from "../../
|
|
29
|
-
import { PI_WORKING_LINE_MESSAGES } from "../../
|
|
9
|
+
} from "../../shared/icons.ts";
|
|
10
|
+
import { isSupportedColorSpec } from "../../shared/style.ts";
|
|
11
|
+
import { normalizeWorkingLineMessages } from "../../layouts/working-line/working-line.ts";
|
|
12
|
+
import { PI_WORKING_LINE_MESSAGES } from "../../layouts/working-line/working-line-messages.ts";
|
|
13
|
+
import {
|
|
14
|
+
configPath as defaultConfigPath,
|
|
15
|
+
configStore,
|
|
16
|
+
mutateConfigFile,
|
|
17
|
+
type ConfigRecord,
|
|
18
|
+
} from "./store.ts";
|
|
30
19
|
|
|
31
20
|
export type ColorSpec = string;
|
|
32
21
|
export type ColorSource = "theme" | "terminal";
|
|
33
|
-
export type { IconMode } from "../../
|
|
22
|
+
export type { IconMode } from "../../shared/icons.ts";
|
|
34
23
|
|
|
35
24
|
export type ContextStyle = "text" | "gauge" | "text+gauge";
|
|
36
25
|
export type SeparatorStyle = "pipe" | "dot" | "chevron" | "none";
|
|
@@ -429,9 +418,7 @@ export const FOOTER_FORMAT_ALIASES: Record<string, string> = {
|
|
|
429
418
|
};
|
|
430
419
|
|
|
431
420
|
/** Shared configuration file used by the pi-one-ui package. */
|
|
432
|
-
export const configPath =
|
|
433
|
-
const legacyConfigPath = join(getAgentDir(), "pi-mine-ui.json");
|
|
434
|
-
const legacyZentuiConfigPath = join(getAgentDir(), "zentui.json");
|
|
421
|
+
export const configPath = defaultConfigPath;
|
|
435
422
|
|
|
436
423
|
const defaultFooterSegments: FooterSegmentsConfig = {
|
|
437
424
|
cwd: true,
|
|
@@ -597,8 +584,6 @@ export const defaultConfig: PolishedTuiConfig = {
|
|
|
597
584
|
extensionStatuses: defaultStarshipStyle.extensionStatuses,
|
|
598
585
|
};
|
|
599
586
|
|
|
600
|
-
type ConfigRecord = Record<string, unknown>;
|
|
601
|
-
|
|
602
587
|
function isRecord(value: unknown): value is ConfigRecord {
|
|
603
588
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
604
589
|
}
|
|
@@ -977,102 +962,18 @@ function validFooterSegmentEntries(
|
|
|
977
962
|
) as Partial<FooterSegmentsConfig>;
|
|
978
963
|
}
|
|
979
964
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
| { kind: "corrupt"; error: unknown };
|
|
984
|
-
|
|
985
|
-
function errorCode(error: unknown): string | undefined {
|
|
986
|
-
return typeof error === "object" && error !== null && "code" in error
|
|
987
|
-
? String(error.code)
|
|
988
|
-
: undefined;
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
function readConfigFileState(path: string): ConfigFileState {
|
|
992
|
-
let writePath = path;
|
|
993
|
-
try {
|
|
994
|
-
const pathStat = lstatSync(path);
|
|
995
|
-
if (pathStat.isSymbolicLink()) writePath = realpathSync(path);
|
|
996
|
-
const targetStat = statSync(writePath);
|
|
997
|
-
const parsed = JSON.parse(readFileSync(writePath, "utf8"));
|
|
998
|
-
return isRecord(parsed)
|
|
999
|
-
? {
|
|
1000
|
-
kind: "valid",
|
|
1001
|
-
record: parsed,
|
|
1002
|
-
writePath,
|
|
1003
|
-
mode: targetStat.mode & 0o7777,
|
|
1004
|
-
}
|
|
1005
|
-
: {
|
|
1006
|
-
kind: "corrupt",
|
|
1007
|
-
error: new Error("top-level value must be a JSON object"),
|
|
1008
|
-
};
|
|
1009
|
-
} catch (error) {
|
|
1010
|
-
if (errorCode(error) === "ENOENT") {
|
|
1011
|
-
try {
|
|
1012
|
-
lstatSync(path);
|
|
1013
|
-
} catch (pathError) {
|
|
1014
|
-
if (errorCode(pathError) === "ENOENT")
|
|
1015
|
-
return { kind: "missing", record: {}, writePath: path };
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
return { kind: "corrupt", error };
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
function writeConfigAtomically(
|
|
1023
|
-
path: string,
|
|
1024
|
-
record: ConfigRecord,
|
|
1025
|
-
mode?: number,
|
|
1026
|
-
): void {
|
|
1027
|
-
const tempPath = join(
|
|
1028
|
-
dirname(path),
|
|
1029
|
-
`.${basename(path)}.${process.pid}.${randomUUID()}.tmp`,
|
|
1030
|
-
);
|
|
1031
|
-
let file: number | undefined;
|
|
1032
|
-
try {
|
|
1033
|
-
file = openSync(tempPath, "wx", mode ?? 0o666);
|
|
1034
|
-
if (mode !== undefined) fchmodSync(file, mode);
|
|
1035
|
-
writeFileSync(file, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
1036
|
-
fsyncSync(file);
|
|
1037
|
-
closeSync(file);
|
|
1038
|
-
file = undefined;
|
|
1039
|
-
renameSync(tempPath, path);
|
|
1040
|
-
} catch (error) {
|
|
1041
|
-
if (file !== undefined) {
|
|
1042
|
-
try {
|
|
1043
|
-
closeSync(file);
|
|
1044
|
-
} catch {}
|
|
1045
|
-
}
|
|
1046
|
-
try {
|
|
1047
|
-
unlinkSync(tempPath);
|
|
1048
|
-
} catch (cleanupError) {
|
|
1049
|
-
if (errorCode(cleanupError) !== "ENOENT") {
|
|
1050
|
-
// Preserve the persistence failure; the best-effort cleanup error is secondary.
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
throw error;
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
|
|
965
|
+
/**
|
|
966
|
+
* Applies one compatibility mutation through the shared config writer.
|
|
967
|
+
*/
|
|
1057
968
|
function mutateConfig(
|
|
1058
969
|
path: string,
|
|
1059
970
|
mutate: (record: ConfigRecord) => void,
|
|
1060
971
|
): PolishedTuiConfig {
|
|
1061
|
-
const
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
`Refusing to save Zentui config because ${path} is corrupt or unreadable; fix or remove it first.${detail}`,
|
|
1067
|
-
);
|
|
1068
|
-
}
|
|
1069
|
-
mutate(state.record);
|
|
1070
|
-
writeConfigAtomically(
|
|
1071
|
-
state.writePath,
|
|
1072
|
-
state.record,
|
|
1073
|
-
state.kind === "valid" ? state.mode : undefined,
|
|
1074
|
-
);
|
|
1075
|
-
return mergeConfig(state.record);
|
|
972
|
+
const record =
|
|
973
|
+
path === configPath
|
|
974
|
+
? configStore.update(mutate)
|
|
975
|
+
: mutateConfigFile(path, mutate, "Zentui");
|
|
976
|
+
return mergeConfig(record);
|
|
1076
977
|
}
|
|
1077
978
|
|
|
1078
979
|
export function ensureConfigExists(_path = configPath): void {
|
|
@@ -1807,27 +1708,12 @@ export function getExtensionStatusColorMode(
|
|
|
1807
1708
|
return DEFAULT_EXTENSION_STATUS_COLOR_MODE;
|
|
1808
1709
|
}
|
|
1809
1710
|
|
|
1711
|
+
/**
|
|
1712
|
+
* Loads and normalizes the shared raw configuration for shell consumers.
|
|
1713
|
+
*/
|
|
1810
1714
|
export function loadConfig(): PolishedTuiConfig {
|
|
1811
1715
|
try {
|
|
1812
|
-
|
|
1813
|
-
if (existsSync(configPath)) {
|
|
1814
|
-
raw = readFileSync(configPath, "utf8");
|
|
1815
|
-
} else if (existsSync(legacyConfigPath)) {
|
|
1816
|
-
raw = readFileSync(legacyConfigPath, "utf8");
|
|
1817
|
-
try {
|
|
1818
|
-
writeFileSync(configPath, raw);
|
|
1819
|
-
} catch {
|
|
1820
|
-
// Keep using the legacy file in memory if migration cannot be written.
|
|
1821
|
-
}
|
|
1822
|
-
} else if (existsSync(legacyZentuiConfigPath)) {
|
|
1823
|
-
raw = readFileSync(legacyZentuiConfigPath, "utf8");
|
|
1824
|
-
try {
|
|
1825
|
-
writeFileSync(configPath, raw);
|
|
1826
|
-
} catch {
|
|
1827
|
-
// Keep using the legacy file in memory if migration cannot be written.
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
return mergeConfig(raw ? JSON.parse(raw) : {});
|
|
1716
|
+
return mergeConfig(configStore.read());
|
|
1831
1717
|
} catch {
|
|
1832
1718
|
return mergeConfig({});
|
|
1833
1719
|
}
|