@bolloon/bolloon-agent 0.5.0 → 0.5.2
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 +39 -0
- package/dist/agents/agent-lang.js +101 -0
- package/dist/agents/agent-manifest-protocol.js +4 -0
- package/dist/agents/execution-supervisor.js +87 -5
- package/dist/agents/gateway-group.js +44 -3
- package/dist/agents/model-policy.js +651 -0
- package/dist/agents/pi-sdk-tools.js +84 -33
- package/dist/agents/pi-sdk.js +184 -31
- package/dist/agents/run-store.js +53 -0
- package/dist/agents/task/task-runner.js +8 -1
- package/dist/agents/workflow-pivot-loop.js +22 -13
- package/dist/cli/clipboard.js +65 -0
- package/dist/cli/ink-app.js +145 -17
- package/dist/cli/log-gate.js +386 -0
- package/dist/cli/mention-data.js +2 -0
- package/dist/cli/model-selector.js +538 -0
- package/dist/cli/reply-hygiene.js +54 -0
- package/dist/cli/setup-wizard.js +686 -69
- package/dist/cli/startup-notice.js +160 -0
- package/dist/cli/stores.js +22 -1
- package/dist/cli/theme.js +49 -0
- package/dist/cli/tui-select.js +970 -0
- package/dist/cli-entry.js +125 -18
- package/dist/efficode/compress.js +78 -0
- package/dist/efficode/crc32.js +38 -0
- package/dist/efficode/did.js +66 -0
- package/dist/efficode/index.js +24 -0
- package/dist/efficode/lz77.js +170 -0
- package/dist/efficode/negotiate.js +187 -0
- package/dist/efficode/ops.js +87 -0
- package/dist/efficode/packet.js +271 -0
- package/dist/efficode/types.js +45 -0
- package/dist/electron-build/agents/agent-manifest-protocol.js +4 -0
- package/dist/electron-build/agents/agent-manifest-protocol.js.map +1 -1
- package/dist/electron-build/agents/execution-supervisor.js +86 -4
- package/dist/electron-build/agents/execution-supervisor.js.map +1 -1
- package/dist/electron-build/agents/goal-store.js.map +1 -1
- package/dist/electron-build/agents/model-policy.js +706 -0
- package/dist/electron-build/agents/model-policy.js.map +1 -0
- package/dist/electron-build/agents/pi-sdk-tools.js +84 -33
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-types.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +185 -31
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/run-store.js +55 -1
- package/dist/electron-build/agents/run-store.js.map +1 -1
- package/dist/electron-build/agents/task/task-runner.js +8 -1
- package/dist/electron-build/agents/task/task-runner.js.map +1 -1
- package/dist/electron-build/agents/workflow-pivot-loop.js +22 -13
- package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
- package/dist/electron-build/llm/config-store.js +146 -6
- package/dist/electron-build/llm/config-store.js.map +1 -1
- package/dist/electron-build/llm/connection-probe.js +764 -0
- package/dist/electron-build/llm/connection-probe.js.map +1 -0
- package/dist/electron-build/llm/custom-provider-store.js +272 -0
- package/dist/electron-build/llm/custom-provider-store.js.map +1 -0
- package/dist/electron-build/llm/data/provider-catalog-baked.js +23 -0
- package/dist/electron-build/llm/data/provider-catalog-baked.js.map +1 -0
- package/dist/electron-build/llm/model-catalog.js +778 -0
- package/dist/electron-build/llm/model-catalog.js.map +1 -0
- package/dist/electron-build/llm/model-discovery.js +1202 -0
- package/dist/electron-build/llm/model-discovery.js.map +1 -0
- package/dist/electron-build/llm/model-selection.js +1315 -0
- package/dist/electron-build/llm/model-selection.js.map +1 -0
- package/dist/electron-build/llm/pi-ai.js +751 -55
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/electron-build/llm/provider-catalog.js +969 -0
- package/dist/electron-build/llm/provider-catalog.js.map +1 -0
- package/dist/electron-build/llm/provider-registry.js +950 -0
- package/dist/electron-build/llm/provider-registry.js.map +1 -0
- package/dist/electron-build/llm/tool-name.js +266 -0
- package/dist/electron-build/llm/tool-name.js.map +1 -0
- package/dist/electron-build/utils/dual-source-facts.js +236 -0
- package/dist/electron-build/utils/dual-source-facts.js.map +1 -0
- package/dist/electron-build/utils/dual-source.js +32 -156
- package/dist/electron-build/utils/dual-source.js.map +1 -1
- package/dist/electron-build/utils/update-manager.js +22 -72
- package/dist/electron-build/utils/update-manager.js.map +1 -1
- package/dist/electron-build/utils/update-state.js +2 -19
- package/dist/electron-build/utils/update-state.js.map +1 -1
- package/dist/electron-build/utils/version-identity.js +193 -0
- package/dist/electron-build/utils/version-identity.js.map +1 -0
- package/dist/electron-build/utils/version-info.js +47 -101
- package/dist/electron-build/utils/version-info.js.map +1 -1
- package/dist/index.js +282 -96
- package/dist/ios/bolloon-web.json +10 -0
- package/dist/ios/mobile-contacts.js +44 -11
- package/dist/ios/mobile-core.js +1723 -84
- package/dist/ios/mobile-tasks.js +339 -0
- package/dist/ios/mobile-update.js +1442 -0
- package/dist/ios/mobile.js +308 -35
- package/dist/ios/routes-llm-config.js +344 -52
- package/dist/ios/routes-mobile-tasks.js +353 -0
- package/dist/ios/server.js +163 -4
- package/dist/ios/sw.js +1 -1
- package/dist/llm/config-store.js +146 -6
- package/dist/llm/connection-probe.js +744 -0
- package/dist/llm/custom-provider-store.js +224 -0
- package/dist/llm/data/provider-catalog-baked.js +19 -0
- package/dist/llm/model-catalog.js +717 -0
- package/dist/llm/model-discovery.js +1137 -0
- package/dist/llm/model-selection.js +1241 -0
- package/dist/llm/pi-ai.js +732 -54
- package/dist/llm/provider-catalog.js +898 -0
- package/dist/llm/provider-registry.js +917 -0
- package/dist/llm/tool-name.js +220 -0
- package/dist/setup/onboard.js +34 -8
- package/dist/utils/dual-source-facts.js +223 -0
- package/dist/utils/dual-source.js +12 -141
- package/dist/utils/update-manager.js +16 -59
- package/dist/utils/update-state.js +6 -18
- package/dist/utils/version-identity.js +176 -0
- package/dist/utils/version-info.js +9 -78
- package/dist/web/bolloon-web.json +10 -0
- package/dist/web/mobile-core.js +1721 -84
- package/dist/web/mobile-update.js +1442 -0
- package/dist/web/mobile.js +372 -35
- package/dist/web/routes-llm-config.js +344 -52
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/scripts/build-web.ts +36 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* startup-notice.ts — 启动**前言 / 就绪度报告**的上屏闸门 (2026-09-27)
|
|
3
|
+
*
|
|
4
|
+
* ## 这一层解决什么
|
|
5
|
+
*
|
|
6
|
+
* `src/cli/log-gate.ts` 盖的是**通用加载日志**(按行形状判: module tag / ISO 时间戳 / P2P 自述)。
|
|
7
|
+
* 但 `bolloon --cli` 启动前还有**另一条**刷屏路径, 它不带任何 tag, 所以上一轮没盖到:
|
|
8
|
+
*
|
|
9
|
+
* ① `src/index.ts` 交互启动前的门禁检查 → `describeSetup(ev)` 整段
|
|
10
|
+
* (`初始化状态: … 门禁: …` / `就绪度: basic=✗ …` / `已完成: …` / `配置来源: …` /
|
|
11
|
+
* `已存输入: …` / `缺 (basic): …` / `下一步: …`);
|
|
12
|
+
* ② `runSetupWizard` 的续跑前言 (那个 `╭─ Bolloon 初始化 …╮` 框 + `Onboard 模式: …` +
|
|
13
|
+
* 每步 `✓/✗` + `✅ 初始化完成 …` + 收尾再报一遍 `describeSetup`)。
|
|
14
|
+
*
|
|
15
|
+
* leo 口径 (2026-09-27): **打完 `bolloon --cli` 就该直接看到面板** —— 上面那些属于
|
|
16
|
+
* 「启动前的日志」, 默认一个字节都不该上屏; 但**没做好/要人下手的事不许被吞**。
|
|
17
|
+
*
|
|
18
|
+
* ## 三条出口 (与 log-gate 同构, 但对象是"前言"而不是"加载日志")
|
|
19
|
+
*
|
|
20
|
+
* · 默认: 收进内存缓冲 → **不上屏**, 但**照抄进启动日志文件** (诊断不丢);
|
|
21
|
+
* · `--verbose` / `BOLLOON_VERBOSE=1` / `BOLLOON_STARTUP_PREAMBLE=1`:
|
|
22
|
+
* 一行不改地写回 stderr (与修前逐字一致, 也是验收门的"修前对照");
|
|
23
|
+
* · **失败 / 需人介入**: 由 `alertsFromSetup()` 折成**面板内一行提示**, 交给 Ink 渲染
|
|
24
|
+
* (不是启动前大段刷屏)。判据复用 `log-gate.ts` 的 `carriesHumanSignal()` —— 只有一处。
|
|
25
|
+
*
|
|
26
|
+
* ## 什么时候必须**当场**把缓冲吐出来
|
|
27
|
+
*
|
|
28
|
+
* 前言被静默的前提是"后面有面板接着显示"。一旦流程变成**要人当场回答**(向导问问题)或者
|
|
29
|
+
* **根本走不到面板**(初始化中断/非零退出), 缓冲就必须先落屏 —— 否则就变成"信息被吞"。
|
|
30
|
+
* 这两条出口分别在 `runSetupWizard`(问问题前 `flushStartupNotices()`) 与
|
|
31
|
+
* `src/index.ts`(退出前) 显式调用。
|
|
32
|
+
*/
|
|
33
|
+
import { carriesHumanSignal, isStartupVerbose, logStartupLine, VERBOSE_ENV } from './log-gate.js';
|
|
34
|
+
/** 显式要求"把启动前言打出来"(排查用; 语义 = 修前的默认行为) */
|
|
35
|
+
export const PREAMBLE_ENV = 'BOLLOON_STARTUP_PREAMBLE';
|
|
36
|
+
const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
|
|
37
|
+
/** 前言要不要上屏 */
|
|
38
|
+
export function startupPreambleVisible(env = process.env, args = process.argv.slice(2)) {
|
|
39
|
+
const v = (env[PREAMBLE_ENV] ?? '').trim().toLowerCase();
|
|
40
|
+
if (TRUTHY.has(v))
|
|
41
|
+
return true;
|
|
42
|
+
return isStartupVerbose(args, env);
|
|
43
|
+
}
|
|
44
|
+
/** 缓冲里攒下的前言行 (默认模式的"没上屏但没丢"的那一份) */
|
|
45
|
+
const buffer = [];
|
|
46
|
+
/** 面板里要显示的就绪度行 (一行, 来自 `readinessLine()`) */
|
|
47
|
+
let readiness = null;
|
|
48
|
+
/** 面板里要显示的告警行 (失败/需人介入; 一条一行) */
|
|
49
|
+
const alerts = [];
|
|
50
|
+
/** 复位 (单测 / 同进程多次启动用) */
|
|
51
|
+
export function resetStartupNotices() {
|
|
52
|
+
buffer.length = 0;
|
|
53
|
+
alerts.length = 0;
|
|
54
|
+
readiness = null;
|
|
55
|
+
}
|
|
56
|
+
/** 收/发一行前言: 可见模式写 stderr (与修前逐字一致), 默认模式进缓冲 + 落日志文件 */
|
|
57
|
+
export function noticeLine(line, opts = {}) {
|
|
58
|
+
const visible = opts.visible ?? startupPreambleVisible();
|
|
59
|
+
for (const l of String(line).split('\n')) {
|
|
60
|
+
if (l === '' && line === '') { /* 空行也照收: 前言里有排版空行 */ }
|
|
61
|
+
logStartupLine(l); // 诊断不丢: 无论上不上屏都落盘
|
|
62
|
+
if (visible) {
|
|
63
|
+
process.stderr.write(l + '\n');
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
buffer.push(l);
|
|
67
|
+
// 失败/需人介入的行不许只躺在缓冲里 —— 同时折成面板告警 (去重)
|
|
68
|
+
if (carriesHumanSignal(l) && !alerts.includes(l))
|
|
69
|
+
alerts.push(l);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** 缓冲里的前言行数 (验收门读它做对照) */
|
|
73
|
+
export function bufferedNoticeLines() {
|
|
74
|
+
return [...buffer];
|
|
75
|
+
}
|
|
76
|
+
/** 把缓冲**当场**写到 stderr (要人回答 / 走不到面板时的兜底出口) */
|
|
77
|
+
export function flushStartupNotices() {
|
|
78
|
+
if (buffer.length === 0)
|
|
79
|
+
return;
|
|
80
|
+
const payload = buffer.join('\n') + '\n';
|
|
81
|
+
buffer.length = 0;
|
|
82
|
+
process.stderr.write(payload);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 丢掉缓冲 (2026-09-27): **已经走到面板**时用 —— 剩下的纯前言不上屏 (它在 startup.log 里),
|
|
86
|
+
* 但它也就没必要继续留在内存里等下一次启动重放。
|
|
87
|
+
*/
|
|
88
|
+
export function clearBufferedNotices() {
|
|
89
|
+
buffer.length = 0;
|
|
90
|
+
}
|
|
91
|
+
/** 设置面板里的就绪度行 (一行) */
|
|
92
|
+
export function setStartupReadiness(line) {
|
|
93
|
+
readiness = line;
|
|
94
|
+
}
|
|
95
|
+
/** 追加一条面板告警 (失败/需人介入) */
|
|
96
|
+
export function pushStartupAlert(line) {
|
|
97
|
+
if (!line)
|
|
98
|
+
return;
|
|
99
|
+
if (!alerts.includes(line))
|
|
100
|
+
alerts.push(line);
|
|
101
|
+
}
|
|
102
|
+
/** 取走面板要显示的内容 (取走即清 —— 免得下次启动重复显示) */
|
|
103
|
+
export function takeStartupPanelNotes() {
|
|
104
|
+
const notes = { readiness, alerts: [...alerts] };
|
|
105
|
+
readiness = null;
|
|
106
|
+
alerts.length = 0;
|
|
107
|
+
return notes;
|
|
108
|
+
}
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// 格式化 (纯函数 —— 单测与验收门直接喂它)
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
/** 详情查询命令 (面板提示里指的那个显式出口; 与 `cli-entry.ts` 的实现同一句) */
|
|
113
|
+
export const SETUP_STATUS_CMD = 'bolloon setup status';
|
|
114
|
+
const mark = (v) => (v ? '✓' : '✗');
|
|
115
|
+
/**
|
|
116
|
+
* 一行就绪度: `就绪: basic ✓ · agent ✓ · durable ✗ · network ✓ · 详情: bolloon setup status`
|
|
117
|
+
*
|
|
118
|
+
* 为什么是"一行"而不是原来那 4 行明细: 面板里放得下, 一眼看得出来, 细节按需查
|
|
119
|
+
* (`bolloon setup status` 打全量 / `--verbose` 打修前那一段)。
|
|
120
|
+
*/
|
|
121
|
+
export function readinessLine(readiness, cmd = SETUP_STATUS_CMD) {
|
|
122
|
+
return `就绪: basic ${mark(!!readiness.basic)} · agent ${mark(!!readiness.agent)}`
|
|
123
|
+
+ ` · durable ${mark(!!readiness.durable)} · network ${mark(!!readiness.network)}`
|
|
124
|
+
+ ` · 详情: ${cmd}`;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 从门禁结论折出**面板告警行** (失败/需人介入才给, 就绪时给空数组)。
|
|
128
|
+
*
|
|
129
|
+
* 三条规矩:
|
|
130
|
+
* ① 只在"真有事"时出声 (gate !== ready / 有 lastError) —— 就绪时面板不加噪音;
|
|
131
|
+
* ② 每条都是**一行**, 且带上"下一步怎么办"或"去哪看全量";
|
|
132
|
+
* ③ 不许只说"未就绪"三个字 —— 必须点出第一条具体原因 (`readinessWhy` 优先, 再退 `reasons`)。
|
|
133
|
+
*/
|
|
134
|
+
export function alertsFromSetup(ev) {
|
|
135
|
+
if (!ev)
|
|
136
|
+
return [];
|
|
137
|
+
const state = ev.state || {};
|
|
138
|
+
const out = [];
|
|
139
|
+
const why = state.readinessWhy || {};
|
|
140
|
+
const firstWhy = [why.basic, why.agent, why.durable].flatMap((a) => a || []).find((x) => !!x);
|
|
141
|
+
const firstReason = (ev.reasons || []).find((x) => !!x);
|
|
142
|
+
if (ev.gate !== 'ready') {
|
|
143
|
+
const detail = firstWhy || firstReason || '未说明 (跑 `' + SETUP_STATUS_CMD + '` 看全量)';
|
|
144
|
+
out.push(`⚠ 未就绪 (门禁: ${ev.gate}): ${detail}`);
|
|
145
|
+
const next = (ev.nextActions || [])[0];
|
|
146
|
+
if (next)
|
|
147
|
+
out.push(` 下一步: ${next} · 全量: ${SETUP_STATUS_CMD}`);
|
|
148
|
+
}
|
|
149
|
+
const le = state.lastError;
|
|
150
|
+
if (le && (le.message || le.stage)) {
|
|
151
|
+
out.push(`⛔ 上次初始化失败 [${le.errorClass || 'unknown'}] ${le.stage || ''}: ${String(le.message || '').slice(0, 160)}`);
|
|
152
|
+
out.push(` 处置: \`bolloon setup\` 会从失败阶段继续 · 全量: ${SETUP_STATUS_CMD}`);
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
/** verbose 模式下那句"现在是全量"的提示 (默认不出现) */
|
|
157
|
+
export function preambleHintLine(env = process.env) {
|
|
158
|
+
return `[startup] 诊断模式 (--verbose / ${VERBOSE_ENV}=1 / ${PREAMBLE_ENV}=1): 启动前言全量输出`
|
|
159
|
+
+ ' (默认模式下这部分只在面板里给一行就绪度 + 失败提示)';
|
|
160
|
+
}
|
package/dist/cli/stores.js
CHANGED
|
@@ -54,11 +54,32 @@ export function replaceMarkerMsg(marker, line) {
|
|
|
54
54
|
transcriptStore.set(n);
|
|
55
55
|
}
|
|
56
56
|
export function setUiStatus(status) {
|
|
57
|
+
// 2026-09-27: **同值不通知** —— 状态栏每秒 tick 一次 (时钟/上下文), 但值没变时重渲染 = Ink 整帧重画,
|
|
58
|
+
// 会把"用户正在框选复制"和"用户上滚暂停跟随"这两件事一起打断 (见 ink-app 的跟随冻结)。
|
|
59
|
+
if (uiStore.get().status === status)
|
|
60
|
+
return;
|
|
57
61
|
uiStore.set({ ...uiStore.get(), status });
|
|
58
62
|
}
|
|
59
63
|
export function setUiThinking(thinking) {
|
|
64
|
+
if (uiStore.get().thinking === thinking)
|
|
65
|
+
return;
|
|
60
66
|
uiStore.set({ ...uiStore.get(), thinking });
|
|
61
67
|
}
|
|
62
68
|
export function setUiTransient(v) {
|
|
63
|
-
|
|
69
|
+
const next = v === undefined ? null : v;
|
|
70
|
+
if (uiStore.get().transient === next)
|
|
71
|
+
return;
|
|
72
|
+
uiStore.set({ ...uiStore.get(), transient: next });
|
|
73
|
+
}
|
|
74
|
+
/** transcript 里最后一条**非空**消息 (面板/`/copy` 都要"最近一条回复") */
|
|
75
|
+
export function lastMsg() {
|
|
76
|
+
const c = transcriptStore.get();
|
|
77
|
+
for (let i = c.length - 1; i >= 0; i--)
|
|
78
|
+
if (String(c[i] ?? '').trim())
|
|
79
|
+
return c[i];
|
|
80
|
+
return '';
|
|
81
|
+
}
|
|
82
|
+
/** 整段会话文本 (`/copy all`) —— 内存里**完整**历史, 不裁剪 */
|
|
83
|
+
export function allMsgsText() {
|
|
84
|
+
return transcriptStore.get().join('\n');
|
|
64
85
|
}
|
package/dist/cli/theme.js
CHANGED
|
@@ -13,6 +13,15 @@ export const THEME = {
|
|
|
13
13
|
warn: '#f59e0b', // 警告
|
|
14
14
|
border: '#3a3a36', // 暗描边
|
|
15
15
|
borderBright: '#8a8a7e', // 对话框边框提亮
|
|
16
|
+
/**
|
|
17
|
+
* 光标行的**字色** (2026-09-27 三改: 压在 accent 底上的近黑, 对比度 ~11:1)。
|
|
18
|
+
*
|
|
19
|
+
* 原来压在 accent 底上是 `muted` (#606058) —— 对比只有 ~3.9:1, 在真终端里看着"灰糊糊一片",
|
|
20
|
+
* 而且分组标题行当时**压根没进高亮分支** (leo 亲测: 光标停在标题上看不出选中)。
|
|
21
|
+
* 光标是"你在哪儿"的唯一指示, 必须一眼看到: 底色 = `accent` (主色块), 字色 = 本 token (近黑)。
|
|
22
|
+
* 仍然只有这一个色源 —— 组件里不许写第二份 RGB。
|
|
23
|
+
*/
|
|
24
|
+
cursor: '#141410', // 光标行字色 (accent 底上的近黑)
|
|
16
25
|
};
|
|
17
26
|
/** '#c4d640' → '\x1b[38;2;196;214;64m' ANSI 前景色码 */
|
|
18
27
|
export function fg(hex) {
|
|
@@ -21,3 +30,43 @@ export function fg(hex) {
|
|
|
21
30
|
const b = parseInt(hex.slice(5, 7), 16);
|
|
22
31
|
return `\x1b[38;2;${r};${g};${b}m`;
|
|
23
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* '#c4d640' → '\x1b[48;2;196;214;64m' ANSI **背景**色码 (2026-09-27)。
|
|
35
|
+
*
|
|
36
|
+
* 与 `fg()` 同一个调色板, 只换通道 —— 光标行要"accent 底色 + 深色字"就靠它,
|
|
37
|
+
* 不许在组件里再写一份 RGB (散落 hex 是这条线的病根)。
|
|
38
|
+
*/
|
|
39
|
+
export function bg(hex) {
|
|
40
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
41
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
42
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
43
|
+
return `\x1b[48;2;${r};${g};${b}m`;
|
|
44
|
+
}
|
|
45
|
+
/** 调子 → 调色板 token (唯一映射) */
|
|
46
|
+
export const TONE_TOKEN = {
|
|
47
|
+
accent: 'accent', // 主色: 光标底色 / 标题 / ← 当前
|
|
48
|
+
ok: 'ok', // 成功 / ● 可用
|
|
49
|
+
warn: 'warn', // 警告: special (未支持) / 无基址 / 探测异常
|
|
50
|
+
error: 'error', // 错误: 输入非法 / 切换失败
|
|
51
|
+
muted: 'muted', // 提示 / 头行 / 分组标题
|
|
52
|
+
dim: 'dim', // 更暗: ○ 未配置凭据 / 次要脚注
|
|
53
|
+
text: 'text', // 正文
|
|
54
|
+
plain: 'text', // 无调子 = 正文
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* 要不要上色 —— **唯一判据** (TUI 与主屏共用):
|
|
58
|
+
* ① 输出得是真终端; ② 没设 `NO_COLOR`; ③ `TERM` 不是 `dumb`。
|
|
59
|
+
* 关掉颜色时**信息一点都不丢**: 所有语义都另有符号/文字 (●/○/→/›/▾/← 当前)。
|
|
60
|
+
*/
|
|
61
|
+
export function colorEnabled(isTTY, env = process.env) {
|
|
62
|
+
return !!isTTY && !env.NO_COLOR && env.TERM !== 'dumb';
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 给**一整行**套上调子的前景色 (主屏/选择器都走这里)。
|
|
66
|
+
* `color === false` → 原样返回 (一个字节的 SGR 都不加)。
|
|
67
|
+
*/
|
|
68
|
+
export function tint(line, tone, color = true) {
|
|
69
|
+
if (!color)
|
|
70
|
+
return line;
|
|
71
|
+
return `${fg(THEME[TONE_TOKEN[tone] ?? 'text'])}${line}\x1b[0m`;
|
|
72
|
+
}
|