pi-cc-extensions 0.9.10 → 0.9.12

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.en.md CHANGED
@@ -61,15 +61,14 @@ Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-exam
61
61
  "expandedOutputMaxLines": 10, // expanded tool Output lines; overflow shows a footer hint
62
62
  "expandedPreviewMaxLines": 40, // max lines for expanded TaskList bodies (expanded diffs always show all)
63
63
  "expandedCardBackground": "userMessageBg", // expanded card background slot: userMessageBg (same as user prompts) / toolPendingBg (neutral) etc.
64
- "inputClip": 0, // tool summary path/command clip length; 0 = fit width
64
+ "toolLabelClip": 0, // tool summary path/command clip length; 0 = fit width
65
65
  "showStartupHeader": true, // startup header (logo + tips) toggle
66
- "scrollStepLines": 3, // fullscreen wheel scroll step
67
66
 
68
67
  // diff
69
68
  "diffViewMode": "auto", // layout: auto / split / unified
70
69
  "diffIndicatorMode": "bars", // change indicators: bars / classic / none
71
70
  "diffSplitMinWidth": 120, // min terminal width for side-by-side columns
72
- "editDiffCollapsedLines": 24, // Edit collapse lines; beyond that shows the expand hint
71
+ "editDiffCollapsedLines": 24, // edit collapse lines; 0 = change-count summary only
73
72
  "writeDiffCollapsedLines": 0, // write collapse lines; 0 = creation summary only
74
73
  "diffWordWrap": true, // wrap long diff lines
75
74
 
@@ -81,7 +80,10 @@ Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-exam
81
80
 
82
81
  // footer
83
82
  "enableCustomFooter": true, // custom status bar
83
+ "footerCurrency": "USD", // display currency for the footer cost
84
+ "footerCurrencyRate": null, // display units per 1 cost unit; null changes the symbol only
84
85
  "footerNerdIcons": true, // Nerd Font glyphs for git/cache; false = plain text
86
+ "footerGitStatsMode": "working", // working (default): uncommitted; branch: cumulative net changes
85
87
  "footerHiddenKeys": [], // hidden plugin chip keys
86
88
  "footerLine1Keys": ["pi-usage"], // line1 plugin chip order; pi-usage is shown by default, data from @narumitw/pi-usage
87
89
  "footerLine2Keys": [], // line2 plugin chip order (after cwd/git)
@@ -98,11 +100,9 @@ Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-exam
98
100
  ## Local development
99
101
 
100
102
  ```bash
101
- npm test
102
- npm run typecheck
103
- ./test.sh # macOS/Linux: load the local checkout for one run, then restore
104
- test.bat # Windows: same
105
- # one-off run without touching settings: pi -e .
103
+ ./test.sh # macOS/Linux; needs node and pi
104
+ ./test.bat # Windows; runs test.sh through Git Bash
105
+ pi -e . # one run, does not edit settings.json
106
106
  ```
107
107
 
108
108
  ## Compatibility
package/README.md CHANGED
@@ -61,15 +61,14 @@ pi install git:github.com/minuque/pi-cc-extensions
61
61
  "expandedOutputMaxLines": 10, // 展开工具卡 Output 可见行数,超出在末行显示展开提示
62
62
  "expandedPreviewMaxLines": 40, // 展开 TaskList 正文最大行数(展开 diff 始终全量)
63
63
  "expandedCardBackground": "userMessageBg", // 展开卡背景槽位;userMessageBg 与用户消息同色,toolPendingBg 为中性灰
64
- "inputClip": 0, // 工具摘要 path/command 折叠字符数;0 = 按可用宽度
64
+ "toolLabelClip": 0, // 工具摘要 path/command 折叠字符数;0 = 按可用宽度
65
65
  "showStartupHeader": true, // 启动头(logo + tips)开关
66
- "scrollStepLines": 3, // fullscreen 滚轮步进
67
66
 
68
67
  // diff
69
68
  "diffViewMode": "auto", // 布局:auto / split / unified
70
69
  "diffIndicatorMode": "bars", // 变更指示:bars / classic / none
71
70
  "diffSplitMinWidth": 120, // 左右分栏的最小终端宽度
72
- "editDiffCollapsedLines": 24, // Edit 折叠行数,超出显示展开提示
71
+ "editDiffCollapsedLines": 24, // edit 折叠行数;0 仅显示变更统计摘要
73
72
  "writeDiffCollapsedLines": 0, // write 折叠行数,0 仅显示创建摘要
74
73
  "diffWordWrap": true, // 长 diff 行换行
75
74
 
@@ -81,7 +80,10 @@ pi install git:github.com/minuque/pi-cc-extensions
81
80
 
82
81
  // footer
83
82
  "enableCustomFooter": true, // 自定义状态栏
83
+ "footerCurrency": "USD", // 底栏费用的显示币种
84
+ "footerCurrencyRate": null, // 1 单位费用换成多少显示币种;null 只换符号、不乘倍率
84
85
  "footerNerdIcons": true, // git/缓存用 Nerd Font 图标;false 为纯文本
86
+ "footerGitStatsMode": "working", // working(默认):未提交改动;branch:分支累计净改动
85
87
  "footerHiddenKeys": [], // 隐藏的插件芯片 key
86
88
  "footerLine1Keys": ["pi-usage"], // line1 插件芯片顺序;pi-usage 默认显示,数据来自 @narumitw/pi-usage
87
89
  "footerLine2Keys": [], // line2 插件芯片顺序(接在 cwd/git 后)
@@ -98,11 +100,9 @@ pi install git:github.com/minuque/pi-cc-extensions
98
100
  ## 本地开发
99
101
 
100
102
  ```bash
101
- npm test
102
- npm run typecheck
103
- ./test.sh # macOS/Linux:临时加载本地检出,退出后还原
104
- test.bat # Windows:同上
105
- # 只想跑一次、不改配置:pi -e .
103
+ ./test.sh # macOS/Linux,需要 node 和 pi
104
+ ./test.bat # Windows,经 Git Bash 运行 test.sh
105
+ pi -e . # 只跑一次,不改 settings.json
106
106
  ```
107
107
 
108
108
  ## 兼容性
@@ -13,11 +13,13 @@ export type CompactStyleMode = "on" | "compact" | "off";
13
13
 
14
14
  export type DiffViewMode = "auto" | "split" | "unified";
15
15
  export type DiffIndicatorMode = "bars" | "classic" | "none";
16
+ export type FooterGitStatsMode = "working" | "branch";
16
17
 
17
18
  export interface ToolDisplayConfig {
18
19
  diffViewMode: DiffViewMode;
19
20
  diffIndicatorMode: DiffIndicatorMode;
20
21
  diffSplitMinWidth: number;
22
+ /** Edit-only collapsed body lines. 0 = change counts and an expand hint. */
21
23
  editDiffCollapsedLines: number;
22
24
  /** Write-only collapsed body lines. 0 = `↳ created · click to show more`. */
23
25
  writeDiffCollapsedLines: number;
@@ -57,13 +59,12 @@ export type Config = {
57
59
  expandedCardBackground: ThemeBg;
58
60
  expandedInputMaxLines: number;
59
61
  expandedOutputMaxLines: number;
60
- inputClip: number;
62
+ toolLabelClip: number;
61
63
  useSummaryTitlesAsThinkingTitle: boolean;
62
64
  previewLines: number;
63
65
  animationIntervalMs: number;
64
66
  dimThinkingText: boolean;
65
67
  showStartupHeader: boolean;
66
- scrollStepLines: number;
67
68
  enableSessionReference: boolean;
68
69
  enableSubagentAutocomplete: boolean;
69
70
  enableContextCommand: boolean;
@@ -71,7 +72,12 @@ export type Config = {
71
72
  enableWorkingMessage: boolean;
72
73
  enableAliases: boolean;
73
74
  enableCustomFooter: boolean;
75
+ /** ISO 4217 显示币种。USD 时不换算。 */
76
+ footerCurrency: string;
77
+ /** 1 单位费用对应的显示币种数量。null 只换符号、不乘倍率。 */
78
+ footerCurrencyRate: number | null;
74
79
  footerNerdIcons: boolean;
80
+ footerGitStatsMode: FooterGitStatsMode;
75
81
  footerHiddenKeys: string[];
76
82
  footerLine1Keys: string[];
77
83
  footerLine2Keys: string[];
@@ -84,8 +90,9 @@ const LEGACY_CONFIG_PATH = join(AGENT_DIR, "claude-code-style.json");
84
90
 
85
91
  export const DIFF_VIEW_MODES: DiffViewMode[] = ["auto", "split", "unified"];
86
92
  export const DIFF_INDICATOR_MODES: DiffIndicatorMode[] = ["bars", "classic", "none"];
93
+ export const FOOTER_GIT_STATS_MODES: FooterGitStatsMode[] = ["working", "branch"];
87
94
  export const DIFF_SPLIT_MIN_WIDTH_VALUES = ["80", "100", "120", "140", "160", "180"];
88
- export const DIFF_COLLAPSED_LINES_VALUES = ["12", "24", "36", "48", "80", "120"];
95
+ export const DIFF_COLLAPSED_LINES_VALUES = ["0", "12", "24", "36", "48", "80", "120"];
89
96
  /** Write collapsed presets. 0 = stats only (`+N -0` + expand hint). */
90
97
  export const WRITE_DIFF_COLLAPSED_LINES_VALUES = ["0", "4", "8", "12", "24", "36"];
91
98
  /** Presets for expanded body height — keep low options first so cycling stays TUI-friendly. */
@@ -109,11 +116,9 @@ export const EXPANDED_INPUT_MAX_LINES_VALUES = ["5", "10", "20", "40", "80"];
109
116
  /** 展开工具卡 Output 可见行数预设。 */
110
117
  export const EXPANDED_OUTPUT_MAX_LINES_VALUES = ["10", "20", "40", "80", "120"];
111
118
  /** 工具摘要里 path/command 等输入的折叠字符数;0 = 只按可用宽度截断。 */
112
- export const INPUT_CLIP_VALUES = ["0", "40", "60", "80", "100", "120", "160"];
119
+ export const TOOL_LABEL_CLIP_VALUES = ["0", "40", "60", "80", "100", "120", "160"];
113
120
  export const THINKING_PREVIEW_LINES_VALUES = ["0", "1", "3", "5", "10"];
114
121
  export const THINKING_ANIMATION_INTERVAL_VALUES = ["40", "60", "90", "120", "180"];
115
- /** fullscreen 滚轮步进行数预设。 */
116
- export const SCROLL_STEP_LINES_VALUES = ["1", "2", "3", "5", "10"];
117
122
  /** Tools commonly toggled in excludeRenderers via the settings panel. */
118
123
  export const EXCLUDE_RENDERER_CANDIDATES = [
119
124
  "bash",
@@ -142,13 +147,12 @@ export const DEFAULT_CONFIG: Config = {
142
147
  expandedCardBackground: "userMessageBg",
143
148
  expandedInputMaxLines: 5,
144
149
  expandedOutputMaxLines: 10,
145
- inputClip: 0,
150
+ toolLabelClip: 0,
146
151
  useSummaryTitlesAsThinkingTitle: true,
147
152
  previewLines: 3,
148
153
  animationIntervalMs: 90,
149
154
  dimThinkingText: false,
150
155
  showStartupHeader: true,
151
- scrollStepLines: 3,
152
156
  enableSessionReference: true,
153
157
  enableSubagentAutocomplete: true,
154
158
  enableContextCommand: true,
@@ -156,7 +160,10 @@ export const DEFAULT_CONFIG: Config = {
156
160
  enableWorkingMessage: true,
157
161
  enableAliases: true,
158
162
  enableCustomFooter: true,
163
+ footerCurrency: "USD",
164
+ footerCurrencyRate: null,
159
165
  footerNerdIcons: true,
166
+ footerGitStatsMode: "working",
160
167
  ...DEFAULT_FOOTER_CHIP_LAYOUT,
161
168
  };
162
169
 
@@ -172,10 +179,10 @@ export function pickPositiveInt(value: unknown, fallback: number, min = 1, max =
172
179
  return Math.min(max, Math.max(min, Math.floor(n)));
173
180
  }
174
181
 
175
- /** inputClip:0 表示按宽度自适应,其余夹到 8..500。 */
176
- export function pickInputClip(value: unknown): number {
182
+ /** toolLabelClip:0 表示按宽度自适应,其余夹到 8..500。 */
183
+ export function pickToolLabelClip(value: unknown): number {
177
184
  const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
178
- return n === 0 ? 0 : pickPositiveInt(value, DEFAULT_CONFIG.inputClip, 8, 500);
185
+ return n === 0 ? 0 : pickPositiveInt(value, DEFAULT_CONFIG.toolLabelClip, 8, 500);
179
186
  }
180
187
 
181
188
  export function pickPositiveNumber(value: unknown, fallback: number, min = 1): number {
@@ -183,6 +190,19 @@ export function pickPositiveNumber(value: unknown, fallback: number, min = 1): n
183
190
  return Number.isFinite(n) ? Math.max(min, n) : fallback;
184
191
  }
185
192
 
193
+ function normalizeCurrency(value: unknown): string {
194
+ return typeof value === "string" && /^[A-Za-z]{3}$/.test(value.trim())
195
+ ? value.trim().toUpperCase()
196
+ : "USD";
197
+ }
198
+
199
+ /** 缺失、空串、非正数都视为不换算。 */
200
+ function normalizeFooterCurrencyRate(value: unknown): number | null {
201
+ if (value === undefined || value === null || value === "") return null;
202
+ const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
203
+ return Number.isFinite(n) && n > 0 ? n : null;
204
+ }
205
+
186
206
  export function normalizeConfig(input: unknown): Config {
187
207
  const source = (input && typeof input === "object" ? input : {}) as Record<string, unknown>;
188
208
  const mode = pickEnum(source.mode, ["on", "compact", "off"], DEFAULT_CONFIG.mode);
@@ -213,7 +233,7 @@ export function normalizeConfig(input: unknown): Config {
213
233
  editDiffCollapsedLines: pickPositiveInt(
214
234
  source.editDiffCollapsedLines,
215
235
  DEFAULT_CONFIG.editDiffCollapsedLines,
216
- 1,
236
+ 0,
217
237
  500,
218
238
  ),
219
239
  writeDiffCollapsedLines: pickPositiveInt(
@@ -246,7 +266,9 @@ export function normalizeConfig(input: unknown): Config {
246
266
  1,
247
267
  5_000,
248
268
  ),
249
- inputClip: pickInputClip(source.inputClip),
269
+ toolLabelClip: pickToolLabelClip(
270
+ source.toolLabelClip ?? source.inputClip, // 兼容旧配置键
271
+ ),
250
272
  useSummaryTitlesAsThinkingTitle: source.useSummaryTitlesAsThinkingTitle !== false,
251
273
  previewLines: pickPositiveInt(
252
274
  source.previewLines,
@@ -260,7 +282,6 @@ export function normalizeConfig(input: unknown): Config {
260
282
  ),
261
283
  dimThinkingText: source.dimThinkingText === true,
262
284
  showStartupHeader: source.showStartupHeader !== false,
263
- scrollStepLines: pickPositiveInt(source.scrollStepLines, DEFAULT_CONFIG.scrollStepLines, 1, 50),
264
285
  enableSessionReference: source.enableSessionReference !== false,
265
286
  enableSubagentAutocomplete: source.enableSubagentAutocomplete !== false,
266
287
  enableContextCommand: source.enableContextCommand !== false,
@@ -268,7 +289,14 @@ export function normalizeConfig(input: unknown): Config {
268
289
  enableWorkingMessage: source.enableWorkingMessage !== false,
269
290
  enableAliases: source.enableAliases !== false,
270
291
  enableCustomFooter: source.enableCustomFooter !== false,
292
+ footerCurrency: normalizeCurrency(source.footerCurrency),
293
+ footerCurrencyRate: normalizeFooterCurrencyRate(source.footerCurrencyRate),
271
294
  footerNerdIcons: source.footerNerdIcons !== false,
295
+ footerGitStatsMode: pickEnum(
296
+ source.footerGitStatsMode,
297
+ FOOTER_GIT_STATS_MODES,
298
+ DEFAULT_CONFIG.footerGitStatsMode,
299
+ ),
272
300
  ...normalizeFooterChipLayout(source),
273
301
  };
274
302
  }
@@ -310,13 +338,12 @@ export function formatConfigStatus(source: Config = config): string {
310
338
  `expandedMax=${source.expandedPreviewMaxLines}`,
311
339
  `expandedInput=${source.expandedInputMaxLines}`,
312
340
  `expandedOutput=${source.expandedOutputMaxLines}`,
313
- `inputClip=${source.inputClip}`,
341
+ `toolLabelClip=${source.toolLabelClip}`,
314
342
  `thinkingTitle=${source.useSummaryTitlesAsThinkingTitle ? "summary" : "default"}`,
315
343
  `thinkingPreview=${source.previewLines}`,
316
344
  `thinkingAnimation=${source.animationIntervalMs}ms`,
317
345
  `thinkingDim=${source.dimThinkingText ? "on" : "off"}`,
318
346
  `startupHeader=${source.showStartupHeader ? "on" : "off"}`,
319
- `scrollStep=${source.scrollStepLines}`,
320
347
  `sessionRef=${source.enableSessionReference ? "on" : "off"}`,
321
348
  `subagentAuto=${source.enableSubagentAutocomplete ? "on" : "off"}`,
322
349
  `context=${source.enableContextCommand ? "on" : "off"}`,
@@ -324,7 +351,10 @@ export function formatConfigStatus(source: Config = config): string {
324
351
  `workingMsg=${source.enableWorkingMessage ? "on" : "off"}`,
325
352
  `aliases=${source.enableAliases ? "on" : "off"}`,
326
353
  `footer=${source.enableCustomFooter ? "on" : "off"}`,
354
+ `footerCurrency=${source.footerCurrency}`,
355
+ `footerRate=${source.footerCurrencyRate ?? "-"}`,
327
356
  `footerIcons=${source.footerNerdIcons ? "nerd" : "plain"}`,
357
+ `footerGitStats=${source.footerGitStatsMode}`,
328
358
  formatFooterChipSummary(source),
329
359
  ].join(" · ");
330
360
  }
@@ -11,7 +11,12 @@ import {
11
11
  applyCustomFooter,
12
12
  clearCustomFooter,
13
13
  getFooterStatusSnapshot,
14
+ refreshFooterGitStats,
14
15
  } from "../feature/shell/footer.ts";
16
+ import {
17
+ FOOTER_CURRENCY_PRESETS,
18
+ presetFooterCurrencyRate,
19
+ } from "../feature/shell/footer-currency.ts";
15
20
  import {
16
21
  footerChipDescription,
17
22
  footerLineOfKey,
@@ -37,21 +42,22 @@ import {
37
42
  EXPANDED_INPUT_MAX_LINES_VALUES,
38
43
  EXPANDED_OUTPUT_MAX_LINES_VALUES,
39
44
  EXPANDED_PREVIEW_MAX_LINES_VALUES,
45
+ FOOTER_GIT_STATS_MODES,
40
46
  formatExcludeRenderers,
41
47
  getCompactThinkingConfig,
42
- pickInputClip,
43
48
  pickPositiveInt,
44
49
  pickPositiveNumber,
45
- SCROLL_STEP_LINES_VALUES,
50
+ pickToolLabelClip,
46
51
  THINKING_ANIMATION_INTERVAL_VALUES,
47
52
  THINKING_PREVIEW_LINES_VALUES,
48
- INPUT_CLIP_VALUES,
53
+ TOOL_LABEL_CLIP_VALUES,
49
54
  WRITE_DIFF_COLLAPSED_LINES_VALUES,
50
55
  updateConfig,
51
56
  type CompactStyleMode,
52
57
  type Config,
53
58
  type DiffIndicatorMode,
54
59
  type DiffViewMode,
60
+ type FooterGitStatsMode,
55
61
  } from "./config.ts";
56
62
 
57
63
  /** renderer 注入的渲染副作用,面板自身不触碰渲染状态。 */
@@ -81,6 +87,12 @@ function customFooterDescription(enabled: boolean): string {
81
87
  return "Custom status bar with model, context, cache, cost, git, and plugin chips.";
82
88
  }
83
89
 
90
+ function footerGitStatsDescription(mode: FooterGitStatsMode): string {
91
+ return mode === "branch"
92
+ ? "Net changes since the merge base, including uncommitted edits. Base: origin/HEAD, then local main/master. Untracked files excluded; unavailable stats hidden."
93
+ : "Uncommitted net changes relative to HEAD (default). Includes staged and unstaged edits; excludes untracked files.";
94
+ }
95
+
84
96
  function pluginChipsDescription(): string {
85
97
  return `Arrange plugin chips: ${formatFooterChipSummary(config)}. Enter to edit.`;
86
98
  }
@@ -281,10 +293,22 @@ function buildExcludeRenderersSubmenu(
281
293
  };
282
294
  }
283
295
 
284
- /** 数值项手动输入子面板:预填当前值,Space 循环预设,输入数字自定义,Enter 应用,Esc 取消。 */
285
- function buildNumberInputSubmenu(
296
+ function footerCurrencyRateLabel(rate: number | null): string {
297
+ return rate === null ? "-" : String(rate);
298
+ }
299
+
300
+ /**
301
+ * 手动输入子面板。accept 返回字符串则提交,undefined 取消,null 用 invalid 提示。
302
+ */
303
+ function buildValueInputSubmenu(
286
304
  theme: any,
287
- setting: { label: string; values: readonly string[]; currentValue: string },
305
+ options: {
306
+ prompt: string;
307
+ initial: string;
308
+ hint: string;
309
+ accept: (raw: string) => string | null | undefined;
310
+ invalid?: (raw: string) => string;
311
+ },
288
312
  closeSubmenu: (selected?: string) => void,
289
313
  ): {
290
314
  render: (width: number) => string[];
@@ -293,27 +317,28 @@ function buildNumberInputSubmenu(
293
317
  } {
294
318
  const input = new Input();
295
319
  let error = "";
296
- input.setValue(setting.currentValue);
320
+ input.setValue(options.initial);
297
321
  input.onSubmit = (value: string) => {
298
322
  const raw = value.trim();
299
- if (raw === "") {
300
- closeSubmenu(); // 空输入 = 取消
323
+ const next = options.accept(raw);
324
+ if (next === undefined) {
325
+ closeSubmenu();
301
326
  return;
302
327
  }
303
- if (!Number.isFinite(Number(raw))) {
304
- error = `Invalid number: "${raw}"`;
328
+ if (next === null) {
329
+ error = options.invalid?.(raw) ?? "Invalid value";
305
330
  return;
306
331
  }
307
- closeSubmenu(raw);
332
+ closeSubmenu(next);
308
333
  };
309
334
  input.onEscape = () => closeSubmenu();
310
335
  return {
311
336
  render: (width: number) => {
312
337
  const safe = Math.max(0, Math.floor(width));
313
338
  const lines = [
314
- theme.fg("dim", ` ${setting.label} — custom value:`),
339
+ theme.fg("dim", options.prompt),
315
340
  ...input.render(safe),
316
- truncateToWidth(theme.fg("dim", " Enter to apply · Esc to go back"), safe),
341
+ truncateToWidth(theme.fg("dim", options.hint), safe),
317
342
  ];
318
343
  if (error !== "") lines.push(theme.fg("dim", ` ${error}`));
319
344
  return lines;
@@ -323,6 +348,25 @@ function buildNumberInputSubmenu(
323
348
  };
324
349
  }
325
350
 
351
+ /** 数值项:预填当前值,空输入取消,非数字留下错误。 */
352
+ function buildNumberInputSubmenu(
353
+ theme: any,
354
+ setting: { label: string; currentValue: string },
355
+ closeSubmenu: (selected?: string) => void,
356
+ ) {
357
+ return buildValueInputSubmenu(
358
+ theme,
359
+ {
360
+ prompt: ` ${setting.label} — custom value:`,
361
+ initial: setting.currentValue,
362
+ hint: " Enter to apply · Esc to go back",
363
+ accept: (raw) => (raw === "" ? undefined : Number.isFinite(Number(raw)) ? raw : null),
364
+ invalid: (raw) => `Invalid number: "${raw}"`,
365
+ },
366
+ closeSubmenu,
367
+ );
368
+ }
369
+
326
370
  /** Section tabs for /ccstyle — matches Zentui-style "A / B / C" headers. */
327
371
  type CcstyleSection = {
328
372
  id: "style" | "diff" | "thinking" | "ui" | "feature" | "footer";
@@ -409,15 +453,15 @@ export async function showCcstylePanel(
409
453
  );
410
454
  },
411
455
  };
412
- const inputClipSetting = {
413
- id: "inputClip",
414
- label: "Input clip",
456
+ const toolLabelClipSetting = {
457
+ id: "toolLabelClip",
458
+ label: "Tool label clip",
415
459
  description:
416
460
  "Max characters for path/command/name in single and grouped tool summaries; 0 fits available width. Enter to type a custom value.",
417
- currentValue: String(config.inputClip),
418
- values: [...INPUT_CLIP_VALUES],
461
+ currentValue: String(config.toolLabelClip),
462
+ values: [...TOOL_LABEL_CLIP_VALUES],
419
463
  submenu: (_current: string, closeSubmenu: (selected?: string) => void) =>
420
- buildNumberInputSubmenu(theme, inputClipSetting, closeSubmenu),
464
+ buildNumberInputSubmenu(theme, toolLabelClipSetting, closeSubmenu),
421
465
  };
422
466
  const diffViewSetting = {
423
467
  id: "diffViewMode",
@@ -447,7 +491,7 @@ export async function showCcstylePanel(
447
491
  id: "editDiffCollapsedLines",
448
492
  label: "Edit collapsed lines",
449
493
  description:
450
- "How many edit/diff body lines to show before the expand hint (Ctrl+O / click). Write uses its own setting below.",
494
+ "Edit diff body lines before the expand hint (Ctrl+O / click). 0 shows only change counts and the hint.",
451
495
  currentValue: String(config.editDiffCollapsedLines),
452
496
  values: [...DIFF_COLLAPSED_LINES_VALUES],
453
497
  submenu: (_current: string, closeSubmenu: (selected?: string) => void) =>
@@ -551,15 +595,6 @@ export async function showCcstylePanel(
551
595
  currentValue: config.showStartupHeader ? "on" : "off",
552
596
  values: ["on", "off"],
553
597
  };
554
- const scrollStepSetting = {
555
- id: "scrollStepLines",
556
- label: "Scroll step",
557
- description: "Mouse wheel scroll lines in fullscreen mode.",
558
- currentValue: String(config.scrollStepLines),
559
- values: [...SCROLL_STEP_LINES_VALUES],
560
- submenu: (_current: string, closeSubmenu: (selected?: string) => void) =>
561
- buildNumberInputSubmenu(theme, scrollStepSetting, closeSubmenu),
562
- };
563
598
 
564
599
  // 额外功能开关:注册于扩展加载期,切换后需重启(/reload)生效。
565
600
  const sessionReferenceToggle = featureToggleSetting(
@@ -604,6 +639,65 @@ export async function showCcstylePanel(
604
639
  "Aliases disabled.",
605
640
  config.enableAliases,
606
641
  );
642
+ const footerCurrencySetting = {
643
+ id: "footerCurrency",
644
+ label: "Cost currency",
645
+ description:
646
+ "ISO code for the status-bar cost. Switching currency fills its preset rate. USD clears the rate.",
647
+ currentValue: config.footerCurrency,
648
+ values: [...FOOTER_CURRENCY_PRESETS],
649
+ submenu: (_current: string, closeSubmenu: (selected?: string) => void) => {
650
+ nestedSubmenuOpen = true;
651
+ nestedHint = " Enter to apply · Esc back to Footer";
652
+ return buildValueInputSubmenu(
653
+ theme,
654
+ {
655
+ prompt: " Cost currency — ISO code:",
656
+ initial: footerCurrencySetting.currentValue,
657
+ hint: " Enter to apply · Esc to go back",
658
+ accept: (raw) => (raw === "" ? undefined : raw),
659
+ },
660
+ (selected) => {
661
+ nestedSubmenuOpen = false;
662
+ nestedHint = "";
663
+ closeSubmenu(selected);
664
+ },
665
+ );
666
+ },
667
+ };
668
+ const footerCurrencyRateSetting = {
669
+ id: "footerCurrencyRate",
670
+ label: "Cost rate",
671
+ description:
672
+ "Display units per 1 USD of cost. Switching currency replaces this. Empty keeps the number and only changes the symbol.",
673
+ currentValue: footerCurrencyRateLabel(config.footerCurrencyRate),
674
+ submenu: (_current: string, closeSubmenu: (selected?: string) => void) => {
675
+ nestedSubmenuOpen = true;
676
+ nestedHint = " Enter to apply · empty clears · Esc back to Footer";
677
+ return buildValueInputSubmenu(
678
+ theme,
679
+ {
680
+ prompt: " Cost rate — display units per 1 cost unit:",
681
+ initial:
682
+ footerCurrencyRateSetting.currentValue === "-"
683
+ ? ""
684
+ : footerCurrencyRateSetting.currentValue,
685
+ hint: " Enter to apply · empty clears conversion · Esc to go back",
686
+ accept: (raw) => {
687
+ if (raw === "" || raw === "-") return "-";
688
+ const n = Number(raw);
689
+ return Number.isFinite(n) && n > 0 ? raw : null;
690
+ },
691
+ invalid: () => "Rate must be a positive number",
692
+ },
693
+ (selected) => {
694
+ nestedSubmenuOpen = false;
695
+ nestedHint = "";
696
+ closeSubmenu(selected);
697
+ },
698
+ );
699
+ },
700
+ };
607
701
  const footerNerdIconsSetting = {
608
702
  id: "footerNerdIcons",
609
703
  label: "Nerd Font icons",
@@ -620,6 +714,13 @@ export async function showCcstylePanel(
620
714
  currentValue: config.enableCustomFooter ? "on" : "off",
621
715
  values: ["on", "off"],
622
716
  };
717
+ const footerGitStatsSetting = {
718
+ id: "footerGitStatsMode",
719
+ label: "Git changes",
720
+ description: footerGitStatsDescription(config.footerGitStatsMode),
721
+ currentValue: config.footerGitStatsMode,
722
+ values: FOOTER_GIT_STATS_MODES,
723
+ };
623
724
  const pluginChipsSetting = {
624
725
  id: "footerPluginChips",
625
726
  label: "Plugin chips",
@@ -663,6 +764,35 @@ export async function showCcstylePanel(
663
764
  ctx.ui.notify(`Updated ${id}: ${value}`, "info");
664
765
  return;
665
766
  }
767
+ if (id === "footerCurrency") {
768
+ updateConfig({
769
+ footerCurrency: value,
770
+ footerCurrencyRate: presetFooterCurrencyRate(value),
771
+ });
772
+ footerCurrencySetting.currentValue = config.footerCurrency;
773
+ footerCurrencyRateSetting.currentValue = footerCurrencyRateLabel(config.footerCurrencyRate);
774
+ if (config.enableCustomFooter) applyCustomFooter(ctx);
775
+ ctx.ui.notify(
776
+ `Updated ${id}: ${config.footerCurrency} · ${footerCurrencyRateSetting.currentValue}`,
777
+ "info",
778
+ );
779
+ return;
780
+ }
781
+ if (id === "footerCurrencyRate") {
782
+ updateConfig({ footerCurrencyRate: value === "-" ? null : Number(value) });
783
+ footerCurrencyRateSetting.currentValue = footerCurrencyRateLabel(config.footerCurrencyRate);
784
+ if (config.enableCustomFooter) applyCustomFooter(ctx);
785
+ ctx.ui.notify(`Updated ${id}: ${footerCurrencyRateSetting.currentValue}`, "info");
786
+ return;
787
+ }
788
+ if (id === "footerGitStatsMode") {
789
+ updateConfig({ footerGitStatsMode: value as FooterGitStatsMode });
790
+ footerGitStatsSetting.currentValue = config.footerGitStatsMode;
791
+ footerGitStatsSetting.description = footerGitStatsDescription(config.footerGitStatsMode);
792
+ refreshFooterGitStats();
793
+ ctx.ui.notify(`Updated ${id}: ${value}`, "info");
794
+ return;
795
+ }
666
796
  if (id === "footerNerdIcons") {
667
797
  const enabled = value === "on";
668
798
  updateConfig({ footerNerdIcons: enabled });
@@ -682,11 +812,11 @@ export async function showCcstylePanel(
682
812
  return;
683
813
  }
684
814
  switch (id) {
685
- case "inputClip":
815
+ case "toolLabelClip":
686
816
  updateConfig({
687
- inputClip: pickInputClip(value),
817
+ toolLabelClip: pickToolLabelClip(value),
688
818
  });
689
- inputClipSetting.currentValue = String(config.inputClip);
819
+ toolLabelClipSetting.currentValue = String(config.toolLabelClip);
690
820
  break;
691
821
  case "mode": {
692
822
  // 选项值带 Experimental 标记,选择后还原为真实 mode 值。
@@ -719,7 +849,7 @@ export async function showCcstylePanel(
719
849
  editDiffCollapsedLines: pickPositiveInt(
720
850
  value,
721
851
  DEFAULT_CONFIG.editDiffCollapsedLines,
722
- 1,
852
+ 0,
723
853
  500,
724
854
  ),
725
855
  });
@@ -810,12 +940,6 @@ export async function showCcstylePanel(
810
940
  else clearStartupHeader(ctx);
811
941
  break;
812
942
  }
813
- case "scrollStepLines":
814
- updateConfig({
815
- scrollStepLines: pickPositiveInt(value, DEFAULT_CONFIG.scrollStepLines, 1, 50),
816
- });
817
- scrollStepSetting.currentValue = String(config.scrollStepLines);
818
- break;
819
943
  default:
820
944
  return;
821
945
  }
@@ -850,9 +974,8 @@ export async function showCcstylePanel(
850
974
  expandedOutputSetting,
851
975
  expandedMaxSetting,
852
976
  expandedCardBgSetting,
853
- inputClipSetting,
977
+ toolLabelClipSetting,
854
978
  startupHeaderSetting,
855
- scrollStepSetting,
856
979
  ],
857
980
  },
858
981
  {
@@ -880,7 +1003,14 @@ export async function showCcstylePanel(
880
1003
  {
881
1004
  id: "footer",
882
1005
  label: "Footer",
883
- items: [customFooterSetting, footerNerdIconsSetting, pluginChipsSetting],
1006
+ items: [
1007
+ customFooterSetting,
1008
+ footerCurrencySetting,
1009
+ footerCurrencyRateSetting,
1010
+ footerNerdIconsSetting,
1011
+ footerGitStatsSetting,
1012
+ pluginChipsSetting,
1013
+ ],
884
1014
  },
885
1015
  ];
886
1016