@oh-my-pi/pi-coding-agent 9.3.1 → 9.6.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/CHANGELOG.md +98 -0
- package/examples/hooks/snake.ts +5 -5
- package/package.json +9 -8
- package/src/capability/index.ts +7 -9
- package/src/cli/config-cli.ts +86 -73
- package/src/cli/update-cli.ts +45 -3
- package/src/commit/agentic/agent.ts +4 -4
- package/src/commit/agentic/index.ts +6 -5
- package/src/commit/agentic/tools/analyze-file.ts +5 -7
- package/src/commit/agentic/tools/index.ts +3 -3
- package/src/commit/model-selection.ts +13 -17
- package/src/commit/pipeline.ts +5 -5
- package/src/config/model-registry.ts +7 -0
- package/src/config/settings-schema.ts +836 -0
- package/src/config/settings.ts +702 -0
- package/src/discovery/helpers.ts +55 -11
- package/src/exa/index.ts +1 -1
- package/src/exec/bash-executor.ts +13 -13
- package/src/exec/shell-session.ts +15 -3
- package/src/export/ttsr.ts +1 -1
- package/src/extensibility/skills.ts +40 -9
- package/src/index.ts +2 -10
- package/src/ipy/gateway-coordinator.ts +5 -159
- package/src/ipy/kernel.ts +6 -171
- package/src/ipy/runtime.ts +198 -0
- package/src/lsp/client.ts +14 -1
- package/src/lsp/defaults.json +0 -6
- package/src/lsp/index.ts +1 -1
- package/src/lsp/types.ts +2 -0
- package/src/main.ts +26 -48
- package/src/modes/components/armin.ts +7 -7
- package/src/modes/components/extensions/extension-dashboard.ts +33 -13
- package/src/modes/components/extensions/extension-list.ts +2 -2
- package/src/modes/components/footer.ts +5 -5
- package/src/modes/components/history-search.ts +2 -1
- package/src/modes/components/hook-selector.ts +2 -2
- package/src/modes/components/index.ts +1 -1
- package/src/modes/components/model-selector.ts +7 -7
- package/src/modes/components/session-selector.ts +2 -1
- package/src/modes/components/settings-defs.ts +210 -915
- package/src/modes/components/settings-selector.ts +80 -106
- package/src/modes/components/status-line/types.ts +2 -8
- package/src/modes/components/status-line-segment-editor.ts +4 -4
- package/src/modes/components/status-line.ts +28 -5
- package/src/modes/components/welcome.ts +3 -3
- package/src/modes/controllers/command-controller.ts +2 -2
- package/src/modes/controllers/event-controller.ts +9 -8
- package/src/modes/controllers/input-controller.ts +19 -15
- package/src/modes/controllers/selector-controller.ts +30 -14
- package/src/modes/interactive-mode.ts +10 -10
- package/src/modes/rpc/rpc-mode.ts +10 -0
- package/src/modes/rpc/rpc-types.ts +3 -0
- package/src/modes/types.ts +2 -2
- package/src/modes/utils/ui-helpers.ts +4 -3
- package/src/patch/index.ts +7 -7
- package/src/patch/normalize.ts +3 -1
- package/src/prompts/system/plan-mode-active.md +5 -4
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/bash.md +12 -2
- package/src/prompts/tools/task.md +180 -73
- package/src/sdk.ts +38 -61
- package/src/session/agent-session.ts +66 -55
- package/src/session/agent-storage.ts +1 -1
- package/src/session/session-manager.ts +10 -10
- package/src/system-prompt.ts +2 -2
- package/src/task/executor.ts +9 -9
- package/src/task/index.ts +2 -2
- package/src/tools/ask.ts +5 -6
- package/src/tools/bash-interceptor.ts +39 -1
- package/src/tools/bash-normalize.ts +126 -0
- package/src/tools/bash.ts +31 -5
- package/src/tools/find.ts +51 -33
- package/src/tools/gemini-image.ts +7 -8
- package/src/tools/index.ts +5 -23
- package/src/tools/plan-mode-guard.ts +1 -6
- package/src/tools/python.ts +29 -4
- package/src/tools/read.ts +2 -2
- package/src/tools/write.ts +2 -2
- package/src/tui/output-block.ts +2 -2
- package/src/tui/utils.ts +2 -2
- package/src/utils/ignore-files.ts +119 -0
- package/src/web/search/auth.ts +6 -58
- package/src/web/search/index.ts +2 -6
- package/src/web/search/providers/anthropic.ts +6 -6
- package/src/web/search/providers/exa.ts +2 -62
- package/src/web/search/providers/perplexity.ts +7 -53
- package/examples/sdk/10-settings.ts +0 -37
- package/src/config/settings-manager.ts +0 -2015
|
@@ -1,961 +1,256 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Declarative settings definitions.
|
|
2
|
+
* Declarative settings definitions for the UI.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* To add a new setting:
|
|
6
|
-
* 1. Add it to
|
|
7
|
-
* 2.
|
|
8
|
-
* 3. Add the handler in interactive-mode.ts settingsHandlers
|
|
4
|
+
* This file derives UI definitions from the schema - no duplicate get/set wrappers.
|
|
5
|
+
* To add a new setting to the UI:
|
|
6
|
+
* 1. Add it to settings-schema.ts with a `ui` field
|
|
7
|
+
* 2. That's it - it appears in the UI automatically
|
|
9
8
|
*/
|
|
10
|
-
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
11
9
|
import { TERMINAL_INFO } from "@oh-my-pi/pi-tui";
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
10
|
+
import {
|
|
11
|
+
getDefault,
|
|
12
|
+
getEnumValues,
|
|
13
|
+
getPathsForTab,
|
|
14
|
+
getType,
|
|
15
|
+
getUi,
|
|
16
|
+
type SettingPath,
|
|
17
|
+
type SettingTab,
|
|
18
|
+
} from "../../config/settings-schema";
|
|
19
|
+
|
|
20
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
21
|
+
// UI Definition Types
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
25
23
|
|
|
26
|
-
// Setting value types
|
|
27
24
|
export type SettingValue = boolean | string;
|
|
28
25
|
|
|
29
|
-
// Base definition for all settings
|
|
30
26
|
interface BaseSettingDef {
|
|
31
|
-
|
|
27
|
+
path: SettingPath;
|
|
32
28
|
label: string;
|
|
33
29
|
description: string;
|
|
34
|
-
tab:
|
|
30
|
+
tab: SettingTab;
|
|
35
31
|
}
|
|
36
32
|
|
|
37
|
-
// Boolean toggle setting
|
|
38
33
|
export interface BooleanSettingDef extends BaseSettingDef {
|
|
39
34
|
type: "boolean";
|
|
40
|
-
get: (sm: SettingsManager) => boolean;
|
|
41
|
-
set: (sm: SettingsManager, value: boolean) => void;
|
|
42
|
-
/** If provided, setting is only shown when this returns true */
|
|
43
35
|
condition?: () => boolean;
|
|
44
36
|
}
|
|
45
37
|
|
|
46
|
-
// Enum setting (inline toggle between values)
|
|
47
38
|
export interface EnumSettingDef extends BaseSettingDef {
|
|
48
39
|
type: "enum";
|
|
49
40
|
values: readonly string[];
|
|
50
|
-
get: (sm: SettingsManager) => string;
|
|
51
|
-
set: (sm: SettingsManager, value: string) => void;
|
|
52
41
|
}
|
|
53
42
|
|
|
54
|
-
// Submenu setting (opens a selection list)
|
|
55
43
|
export interface SubmenuSettingDef extends BaseSettingDef {
|
|
56
44
|
type: "submenu";
|
|
57
|
-
|
|
58
|
-
set: (sm: SettingsManager, value: string) => void;
|
|
59
|
-
/** Get available options dynamically */
|
|
60
|
-
getOptions: (sm: SettingsManager) => Array<{ value: string; label: string; description?: string }>;
|
|
61
|
-
/** Called when selection changes (for preview) */
|
|
45
|
+
getOptions: () => Array<{ value: string; label: string; description?: string }>;
|
|
62
46
|
onPreview?: (value: string) => void;
|
|
63
|
-
/** Called when submenu is cancelled (to restore preview) */
|
|
64
47
|
onPreviewCancel?: (originalValue: string) => void;
|
|
65
48
|
}
|
|
66
49
|
|
|
67
50
|
export type SettingDef = BooleanSettingDef | EnumSettingDef | SubmenuSettingDef;
|
|
68
51
|
|
|
69
|
-
|
|
52
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
53
|
+
// Condition Functions
|
|
54
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
55
|
+
|
|
56
|
+
const CONDITIONS: Record<string, () => boolean> = {
|
|
57
|
+
hasImageProtocol: () => !!TERMINAL_INFO.imageProtocol,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
61
|
+
// Submenu Option Providers
|
|
62
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
63
|
+
|
|
64
|
+
type OptionProvider = () => Array<{ value: string; label: string; description?: string }>;
|
|
65
|
+
|
|
66
|
+
const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
67
|
+
// Retry max retries
|
|
68
|
+
"retry.maxRetries": () => [
|
|
69
|
+
{ value: "1", label: "1 retry" },
|
|
70
|
+
{ value: "2", label: "2 retries" },
|
|
71
|
+
{ value: "3", label: "3 retries" },
|
|
72
|
+
{ value: "5", label: "5 retries" },
|
|
73
|
+
{ value: "10", label: "10 retries" },
|
|
74
|
+
],
|
|
75
|
+
// Todo max reminders
|
|
76
|
+
"todoCompletion.maxReminders": () => [
|
|
77
|
+
{ value: "1", label: "1 reminder" },
|
|
78
|
+
{ value: "2", label: "2 reminders" },
|
|
79
|
+
{ value: "3", label: "3 reminders" },
|
|
80
|
+
{ value: "5", label: "5 reminders" },
|
|
81
|
+
],
|
|
82
|
+
// Ask timeout
|
|
83
|
+
"ask.timeout": () => [
|
|
84
|
+
{ value: "0", label: "Disabled" },
|
|
85
|
+
{ value: "15", label: "15 seconds" },
|
|
86
|
+
{ value: "30", label: "30 seconds" },
|
|
87
|
+
{ value: "60", label: "60 seconds" },
|
|
88
|
+
{ value: "120", label: "120 seconds" },
|
|
89
|
+
],
|
|
90
|
+
// Edit fuzzy threshold
|
|
91
|
+
"edit.fuzzyThreshold": () => [
|
|
92
|
+
{ value: "0.85", label: "0.85", description: "Lenient" },
|
|
93
|
+
{ value: "0.90", label: "0.90", description: "Moderate" },
|
|
94
|
+
{ value: "0.95", label: "0.95", description: "Default" },
|
|
95
|
+
{ value: "0.98", label: "0.98", description: "Strict" },
|
|
96
|
+
],
|
|
97
|
+
// TTSR repeat gap
|
|
98
|
+
"ttsr.repeatGap": () => [
|
|
99
|
+
{ value: "5", label: "5 messages" },
|
|
100
|
+
{ value: "10", label: "10 messages" },
|
|
101
|
+
{ value: "15", label: "15 messages" },
|
|
102
|
+
{ value: "20", label: "20 messages" },
|
|
103
|
+
{ value: "30", label: "30 messages" },
|
|
104
|
+
],
|
|
105
|
+
// Provider options
|
|
106
|
+
"providers.webSearch": () => [
|
|
107
|
+
{ value: "auto", label: "Auto", description: "Priority: Exa > Perplexity > Anthropic" },
|
|
108
|
+
{ value: "exa", label: "Exa", description: "Requires EXA_API_KEY" },
|
|
109
|
+
{ value: "perplexity", label: "Perplexity", description: "Requires PERPLEXITY_API_KEY" },
|
|
110
|
+
{ value: "anthropic", label: "Anthropic", description: "Uses Anthropic web search" },
|
|
111
|
+
],
|
|
112
|
+
"providers.image": () => [
|
|
113
|
+
{ value: "auto", label: "Auto", description: "Priority: OpenRouter > Gemini" },
|
|
114
|
+
{ value: "gemini", label: "Gemini", description: "Requires GEMINI_API_KEY" },
|
|
115
|
+
{ value: "openrouter", label: "OpenRouter", description: "Requires OPENROUTER_API_KEY" },
|
|
116
|
+
],
|
|
117
|
+
"providers.kimiApiFormat": () => [
|
|
118
|
+
{ value: "openai", label: "OpenAI", description: "api.kimi.com" },
|
|
119
|
+
{ value: "anthropic", label: "Anthropic", description: "api.moonshot.ai" },
|
|
120
|
+
],
|
|
121
|
+
// Symbol preset
|
|
122
|
+
symbolPreset: () => [
|
|
123
|
+
{ value: "unicode", label: "Unicode", description: "Standard symbols (default)" },
|
|
124
|
+
{ value: "nerd", label: "Nerd Font", description: "Requires Nerd Font" },
|
|
125
|
+
{ value: "ascii", label: "ASCII", description: "Maximum compatibility" },
|
|
126
|
+
],
|
|
127
|
+
// Status line preset
|
|
128
|
+
"statusLine.preset": () => [
|
|
129
|
+
{ value: "default", label: "Default", description: "Model, path, git, context, tokens, cost" },
|
|
130
|
+
{ value: "minimal", label: "Minimal", description: "Path and git only" },
|
|
131
|
+
{ value: "compact", label: "Compact", description: "Model, git, cost, context" },
|
|
132
|
+
{ value: "full", label: "Full", description: "All segments including time" },
|
|
133
|
+
{ value: "nerd", label: "Nerd", description: "Maximum info with Nerd Font icons" },
|
|
134
|
+
{ value: "ascii", label: "ASCII", description: "No special characters" },
|
|
135
|
+
{ value: "custom", label: "Custom", description: "User-defined segments" },
|
|
136
|
+
],
|
|
137
|
+
// Status line separator
|
|
138
|
+
"statusLine.separator": () => [
|
|
139
|
+
{ value: "powerline", label: "Powerline", description: "Solid arrows (Nerd Font)" },
|
|
140
|
+
{ value: "powerline-thin", label: "Thin chevron", description: "Thin arrows (Nerd Font)" },
|
|
141
|
+
{ value: "slash", label: "Slash", description: "Forward slashes" },
|
|
142
|
+
{ value: "pipe", label: "Pipe", description: "Vertical pipes" },
|
|
143
|
+
{ value: "block", label: "Block", description: "Solid blocks" },
|
|
144
|
+
{ value: "none", label: "None", description: "Space only" },
|
|
145
|
+
{ value: "ascii", label: "ASCII", description: "Greater-than signs" },
|
|
146
|
+
],
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const THINKING_DESCRIPTIONS: Record<string, string> = {
|
|
70
150
|
off: "No reasoning",
|
|
71
|
-
minimal: "Very brief
|
|
72
|
-
low: "Light
|
|
73
|
-
medium: "Moderate
|
|
74
|
-
high: "Deep
|
|
75
|
-
xhigh: "Maximum
|
|
151
|
+
minimal: "Very brief (~1k tokens)",
|
|
152
|
+
low: "Light (~2k tokens)",
|
|
153
|
+
medium: "Moderate (~8k tokens)",
|
|
154
|
+
high: "Deep (~16k tokens)",
|
|
155
|
+
xhigh: "Maximum (~32k tokens)",
|
|
76
156
|
};
|
|
77
157
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
*
|
|
82
|
-
* Tabs:
|
|
83
|
-
* - behavior: Core agent behavior (compaction, modes, retries, notifications)
|
|
84
|
-
* - tools: Tool-specific settings (bash, git, python, edit, MCP, skills)
|
|
85
|
-
* - display: Visual/UI settings (theme, images, thinking)
|
|
86
|
-
* - ttsr: Time Traveling Stream Rules settings
|
|
87
|
-
* - status: Status line configuration
|
|
88
|
-
* - lsp: LSP integration settings
|
|
89
|
-
* - exa: Exa search tool settings
|
|
90
|
-
*/
|
|
91
|
-
export const SETTINGS_DEFS: SettingDef[] = [
|
|
92
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
93
|
-
// Behavior tab - Core agent behavior
|
|
94
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
95
|
-
{
|
|
96
|
-
id: "autoCompact",
|
|
97
|
-
tab: "behavior",
|
|
98
|
-
type: "boolean",
|
|
99
|
-
label: "Auto-compact",
|
|
100
|
-
description: "Automatically compact context when it gets too large",
|
|
101
|
-
get: sm => sm.getCompactionEnabled(),
|
|
102
|
-
set: (sm, v) => sm.setCompactionEnabled(v),
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
id: "branchSummaries",
|
|
106
|
-
tab: "behavior",
|
|
107
|
-
type: "boolean",
|
|
108
|
-
label: "Branch summaries",
|
|
109
|
-
description: "Prompt to summarize when leaving a branch",
|
|
110
|
-
get: sm => sm.getBranchSummaryEnabled(),
|
|
111
|
-
set: (sm, v) => sm.setBranchSummaryEnabled(v),
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
id: "todoCompletion",
|
|
115
|
-
tab: "behavior",
|
|
116
|
-
type: "boolean",
|
|
117
|
-
label: "Todo completion",
|
|
118
|
-
description: "Remind agent to complete todos before stopping",
|
|
119
|
-
get: sm => sm.getTodoCompletionEnabled(),
|
|
120
|
-
set: (sm, v) => sm.setTodoCompletionEnabled(v),
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
id: "todoCompletionMaxReminders",
|
|
124
|
-
tab: "behavior",
|
|
125
|
-
type: "submenu",
|
|
126
|
-
label: "Todo max reminders",
|
|
127
|
-
description: "Maximum reminders to complete todos before giving up",
|
|
128
|
-
get: sm => String(sm.getTodoCompletionMaxReminders()),
|
|
129
|
-
set: (sm, v) => sm.setTodoCompletionMaxReminders(Number.parseInt(v, 10)),
|
|
130
|
-
getOptions: () => [
|
|
131
|
-
{ value: "1", label: "1 reminder" },
|
|
132
|
-
{ value: "2", label: "2 reminders" },
|
|
133
|
-
{ value: "3", label: "3 reminders" },
|
|
134
|
-
{ value: "5", label: "5 reminders" },
|
|
135
|
-
],
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
id: "steeringMode",
|
|
139
|
-
tab: "behavior",
|
|
140
|
-
type: "enum",
|
|
141
|
-
label: "Steering mode",
|
|
142
|
-
description: "How to process queued messages while agent is working",
|
|
143
|
-
values: ["one-at-a-time", "all"],
|
|
144
|
-
get: sm => sm.getSteeringMode(),
|
|
145
|
-
set: (sm, v) => sm.setSteeringMode(v as "all" | "one-at-a-time"),
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
id: "followUpMode",
|
|
149
|
-
tab: "behavior",
|
|
150
|
-
type: "enum",
|
|
151
|
-
label: "Follow-up mode",
|
|
152
|
-
description: "How to drain follow-up messages after a turn completes",
|
|
153
|
-
values: ["one-at-a-time", "all"],
|
|
154
|
-
get: sm => sm.getFollowUpMode(),
|
|
155
|
-
set: (sm, v) => sm.setFollowUpMode(v as "one-at-a-time" | "all"),
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
id: "interruptMode",
|
|
159
|
-
tab: "behavior",
|
|
160
|
-
type: "enum",
|
|
161
|
-
label: "Interrupt mode",
|
|
162
|
-
description: "When steering messages interrupt tool execution",
|
|
163
|
-
values: ["immediate", "wait"],
|
|
164
|
-
get: sm => sm.getInterruptMode(),
|
|
165
|
-
set: (sm, v) => sm.setInterruptMode(v as "immediate" | "wait"),
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
id: "retryMaxRetries",
|
|
169
|
-
tab: "behavior",
|
|
170
|
-
type: "submenu",
|
|
171
|
-
label: "Retry max attempts",
|
|
172
|
-
description: "Maximum retry attempts on API errors",
|
|
173
|
-
get: sm => String(sm.getRetryMaxRetries()),
|
|
174
|
-
set: (sm, v) => sm.setRetryMaxRetries(Number.parseInt(v, 10)),
|
|
175
|
-
getOptions: () => [
|
|
176
|
-
{ value: "1", label: "1 retry" },
|
|
177
|
-
{ value: "2", label: "2 retries" },
|
|
178
|
-
{ value: "3", label: "3 retries" },
|
|
179
|
-
{ value: "5", label: "5 retries" },
|
|
180
|
-
{ value: "10", label: "10 retries" },
|
|
181
|
-
],
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
id: "completionNotification",
|
|
185
|
-
tab: "behavior",
|
|
186
|
-
type: "enum",
|
|
187
|
-
label: "Completion notification",
|
|
188
|
-
description: "Notify when the agent completes",
|
|
189
|
-
values: ["auto", "bell", "osc99", "osc9", "off"],
|
|
190
|
-
get: sm => sm.getNotificationOnComplete(),
|
|
191
|
-
set: (sm, v) => sm.setNotificationOnComplete(v as NotificationMethod),
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
id: "askTimeout",
|
|
195
|
-
tab: "behavior",
|
|
196
|
-
type: "enum",
|
|
197
|
-
label: "Ask tool timeout",
|
|
198
|
-
description: "Auto-select recommended option after timeout (disabled in plan mode)",
|
|
199
|
-
values: ["off", "15", "30", "60", "120"],
|
|
200
|
-
get: sm => {
|
|
201
|
-
const timeout = sm.getAskTimeout();
|
|
202
|
-
return timeout === null ? "off" : String(timeout / 1000);
|
|
203
|
-
},
|
|
204
|
-
set: (sm, v) => sm.setAskTimeout(v === "off" ? null : Number.parseInt(v, 10)),
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
id: "askNotification",
|
|
208
|
-
tab: "behavior",
|
|
209
|
-
type: "enum",
|
|
210
|
-
label: "Ask notification",
|
|
211
|
-
description: "Notify when ask tool is waiting for input",
|
|
212
|
-
values: ["auto", "bell", "osc99", "osc9", "off"],
|
|
213
|
-
get: sm => sm.getAskNotification(),
|
|
214
|
-
set: (sm, v) => sm.setAskNotification(v as NotificationMethod),
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
id: "startupQuiet",
|
|
218
|
-
tab: "behavior",
|
|
219
|
-
type: "boolean",
|
|
220
|
-
label: "Startup quiet",
|
|
221
|
-
description: "Skip welcome screen and startup status messages",
|
|
222
|
-
get: sm => sm.getStartupQuiet(),
|
|
223
|
-
set: (sm, v) => sm.setStartupQuiet(v),
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
id: "collapseChangelog",
|
|
227
|
-
tab: "behavior",
|
|
228
|
-
type: "boolean",
|
|
229
|
-
label: "Collapse changelog",
|
|
230
|
-
description: "Show condensed changelog after updates",
|
|
231
|
-
get: sm => sm.getCollapseChangelog(),
|
|
232
|
-
set: (sm, v) => sm.setCollapseChangelog(v),
|
|
233
|
-
},
|
|
234
|
-
{
|
|
235
|
-
id: "normativeRewrite",
|
|
236
|
-
tab: "behavior",
|
|
237
|
-
type: "boolean",
|
|
238
|
-
label: "Normative rewrite",
|
|
239
|
-
description: "Rewrite tool call arguments to normalized format in session history",
|
|
240
|
-
get: sm => sm.getNormativeRewrite(),
|
|
241
|
-
set: (sm, v) => sm.setNormativeRewrite(v),
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
id: "doubleEscapeAction",
|
|
245
|
-
tab: "behavior",
|
|
246
|
-
type: "enum",
|
|
247
|
-
label: "Double-escape action",
|
|
248
|
-
description: "Action when pressing Escape twice with empty editor",
|
|
249
|
-
values: ["tree", "branch"],
|
|
250
|
-
get: sm => sm.getDoubleEscapeAction(),
|
|
251
|
-
set: (sm, v) => sm.setDoubleEscapeAction(v as "branch" | "tree"),
|
|
252
|
-
},
|
|
158
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
159
|
+
// Schema to UI Conversion
|
|
160
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
253
161
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
{
|
|
258
|
-
id: "bashInterceptor",
|
|
259
|
-
tab: "tools",
|
|
260
|
-
type: "boolean",
|
|
261
|
-
label: "Bash interceptor",
|
|
262
|
-
description: "Block shell commands that have dedicated tools (grep, cat, etc.)",
|
|
263
|
-
get: sm => sm.getBashInterceptorEnabled(),
|
|
264
|
-
set: (sm, v) => sm.setBashInterceptorEnabled(v),
|
|
265
|
-
},
|
|
266
|
-
{
|
|
267
|
-
id: "shellForceBasic",
|
|
268
|
-
tab: "tools",
|
|
269
|
-
type: "boolean",
|
|
270
|
-
label: "Force basic shell",
|
|
271
|
-
description: "Use bash/sh even if your default shell is different",
|
|
272
|
-
get: sm => sm.getShellForceBasic(),
|
|
273
|
-
set: (sm, v) => sm.setShellForceBasic(v),
|
|
274
|
-
},
|
|
275
|
-
{
|
|
276
|
-
id: "bashInterceptorSimpleLs",
|
|
277
|
-
tab: "tools",
|
|
278
|
-
type: "boolean",
|
|
279
|
-
label: "Intercept simple ls",
|
|
280
|
-
description: "Intercept bare ls commands (when bash interceptor is enabled)",
|
|
281
|
-
get: sm => sm.getBashInterceptorSimpleLsEnabled(),
|
|
282
|
-
set: (sm, v) => sm.setBashInterceptorSimpleLsEnabled(v),
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
id: "pythonToolMode",
|
|
286
|
-
tab: "tools",
|
|
287
|
-
type: "enum",
|
|
288
|
-
label: "Python tool mode",
|
|
289
|
-
description: "How Python code is executed",
|
|
290
|
-
values: ["ipy-only", "bash-only", "both"],
|
|
291
|
-
get: sm => sm.getPythonToolMode(),
|
|
292
|
-
set: (sm, v) => sm.setPythonToolMode(v as PythonToolMode),
|
|
293
|
-
},
|
|
294
|
-
{
|
|
295
|
-
id: "pythonKernelMode",
|
|
296
|
-
tab: "tools",
|
|
297
|
-
type: "enum",
|
|
298
|
-
label: "Python kernel mode",
|
|
299
|
-
description: "Whether to keep IPython kernel alive across calls",
|
|
300
|
-
values: ["session", "per-call"],
|
|
301
|
-
get: sm => sm.getPythonKernelMode(),
|
|
302
|
-
set: (sm, v) => sm.setPythonKernelMode(v as PythonKernelMode),
|
|
303
|
-
},
|
|
304
|
-
{
|
|
305
|
-
id: "pythonSharedGateway",
|
|
306
|
-
tab: "tools",
|
|
307
|
-
type: "boolean",
|
|
308
|
-
label: "Python shared gateway",
|
|
309
|
-
description: "Share IPython kernel gateway across pi instances",
|
|
310
|
-
get: sm => sm.getPythonSharedGateway(),
|
|
311
|
-
set: (sm, v) => sm.setPythonSharedGateway(v),
|
|
312
|
-
},
|
|
313
|
-
{
|
|
314
|
-
id: "editFuzzyMatch",
|
|
315
|
-
tab: "tools",
|
|
316
|
-
type: "boolean",
|
|
317
|
-
label: "Edit fuzzy match",
|
|
318
|
-
description: "Accept high-confidence fuzzy matches for whitespace/indentation differences",
|
|
319
|
-
get: sm => sm.getEditFuzzyMatch(),
|
|
320
|
-
set: (sm, v) => sm.setEditFuzzyMatch(v),
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
id: "editFuzzyThreshold",
|
|
324
|
-
tab: "tools",
|
|
325
|
-
type: "submenu",
|
|
326
|
-
label: "Edit fuzzy threshold",
|
|
327
|
-
description: "Similarity threshold for fuzzy matches (higher = stricter)",
|
|
328
|
-
get: sm => sm.getEditFuzzyThreshold().toFixed(2),
|
|
329
|
-
set: (sm, v) => sm.setEditFuzzyThreshold(Number(v)),
|
|
330
|
-
getOptions: () => [
|
|
331
|
-
{ value: "0.85", label: "0.85", description: "Lenient" },
|
|
332
|
-
{ value: "0.90", label: "0.90", description: "Moderate" },
|
|
333
|
-
{ value: "0.95", label: "0.95", description: "Default" },
|
|
334
|
-
{ value: "0.98", label: "0.98", description: "Strict" },
|
|
335
|
-
],
|
|
336
|
-
},
|
|
337
|
-
{
|
|
338
|
-
id: "editPatchMode",
|
|
339
|
-
tab: "tools",
|
|
340
|
-
type: "boolean",
|
|
341
|
-
label: "Edit patch mode",
|
|
342
|
-
description: "Use codex-style apply-patch format instead of old_text/new_text for edits",
|
|
343
|
-
get: sm => sm.getEditPatchMode(),
|
|
344
|
-
set: (sm, v) => sm.setEditPatchMode(v),
|
|
345
|
-
},
|
|
346
|
-
{
|
|
347
|
-
id: "editStreamingAbort",
|
|
348
|
-
tab: "tools",
|
|
349
|
-
type: "boolean",
|
|
350
|
-
label: "Edit streaming abort",
|
|
351
|
-
description: "Abort streaming edit tool calls when patch preview fails",
|
|
352
|
-
get: sm => sm.getEditStreamingAbort(),
|
|
353
|
-
set: (sm, v) => sm.setEditStreamingAbort(v),
|
|
354
|
-
},
|
|
355
|
-
{
|
|
356
|
-
id: "readLineNumbers",
|
|
357
|
-
tab: "tools",
|
|
358
|
-
type: "boolean",
|
|
359
|
-
label: "Read line numbers",
|
|
360
|
-
description: "Prepend line numbers to read tool output by default",
|
|
361
|
-
get: sm => sm.getReadLineNumbers(),
|
|
362
|
-
set: (sm, v) => sm.setReadLineNumbers(v),
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
id: "mcpProjectConfig",
|
|
366
|
-
tab: "tools",
|
|
367
|
-
type: "boolean",
|
|
368
|
-
label: "MCP project config",
|
|
369
|
-
description: "Load .mcp.json/mcp.json from project root",
|
|
370
|
-
get: sm => sm.getMCPProjectConfigEnabled(),
|
|
371
|
-
set: (sm, v) => sm.setMCPProjectConfigEnabled(v),
|
|
372
|
-
},
|
|
373
|
-
{
|
|
374
|
-
id: "skillCommands",
|
|
375
|
-
tab: "tools",
|
|
376
|
-
type: "boolean",
|
|
377
|
-
label: "Skill commands",
|
|
378
|
-
description: "Register skills as /skill:name commands",
|
|
379
|
-
get: sm => sm.getEnableSkillCommands(),
|
|
380
|
-
set: (sm, v) => sm.setEnableSkillCommands(v),
|
|
381
|
-
},
|
|
382
|
-
{
|
|
383
|
-
id: "claudeUserCommands",
|
|
384
|
-
tab: "tools",
|
|
385
|
-
type: "boolean",
|
|
386
|
-
label: "Claude user commands",
|
|
387
|
-
description: "Load commands from ~/.claude/commands/",
|
|
388
|
-
get: sm => sm.getCommandsEnableClaudeUser(),
|
|
389
|
-
set: (sm, v) => sm.setCommandsEnableClaudeUser(v),
|
|
390
|
-
},
|
|
391
|
-
{
|
|
392
|
-
id: "claudeProjectCommands",
|
|
393
|
-
tab: "tools",
|
|
394
|
-
type: "boolean",
|
|
395
|
-
label: "Claude project commands",
|
|
396
|
-
description: "Load commands from .claude/commands/",
|
|
397
|
-
get: sm => sm.getCommandsEnableClaudeProject(),
|
|
398
|
-
set: (sm, v) => sm.setCommandsEnableClaudeProject(v),
|
|
399
|
-
},
|
|
400
|
-
{
|
|
401
|
-
id: "webSearchProvider",
|
|
402
|
-
tab: "tools",
|
|
403
|
-
type: "submenu",
|
|
404
|
-
label: "Web search provider",
|
|
405
|
-
description: "Provider for web search tool",
|
|
406
|
-
get: sm => sm.getWebSearchProvider(),
|
|
407
|
-
set: (sm, v) => sm.setWebSearchProvider(v as WebSearchProviderOption),
|
|
408
|
-
getOptions: () => [
|
|
409
|
-
{ value: "auto", label: "Auto", description: "Priority: Exa > Perplexity > Anthropic" },
|
|
410
|
-
{ value: "exa", label: "Exa", description: "Use Exa (requires EXA_API_KEY)" },
|
|
411
|
-
{ value: "perplexity", label: "Perplexity", description: "Use Perplexity (requires PERPLEXITY_API_KEY)" },
|
|
412
|
-
{ value: "anthropic", label: "Anthropic", description: "Use Anthropic web search" },
|
|
413
|
-
],
|
|
414
|
-
},
|
|
415
|
-
{
|
|
416
|
-
id: "imageProvider",
|
|
417
|
-
tab: "tools",
|
|
418
|
-
type: "submenu",
|
|
419
|
-
label: "Image provider",
|
|
420
|
-
description: "Provider for image generation tool",
|
|
421
|
-
get: sm => sm.getImageProvider(),
|
|
422
|
-
set: (sm, v) => sm.setImageProvider(v as ImageProviderOption),
|
|
423
|
-
getOptions: () => [
|
|
424
|
-
{ value: "auto", label: "Auto", description: "Priority: OpenRouter > Gemini" },
|
|
425
|
-
{ value: "gemini", label: "Gemini", description: "Use Gemini API directly (requires GEMINI_API_KEY)" },
|
|
426
|
-
{ value: "openrouter", label: "OpenRouter", description: "Use OpenRouter (requires OPENROUTER_API_KEY)" },
|
|
427
|
-
],
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
id: "kimiApiFormat",
|
|
431
|
-
tab: "tools",
|
|
432
|
-
type: "submenu",
|
|
433
|
-
label: "Kimi API format",
|
|
434
|
-
description: "API format for Kimi Code provider",
|
|
435
|
-
get: sm => sm.getKimiApiFormat(),
|
|
436
|
-
set: (sm, v) => sm.setKimiApiFormat(v as KimiApiFormatOption),
|
|
437
|
-
getOptions: () => [
|
|
438
|
-
{ value: "openai", label: "OpenAI", description: "Use OpenAI-compatible API (api.kimi.com)" },
|
|
439
|
-
{ value: "anthropic", label: "Anthropic", description: "Use Anthropic-compatible API (api.moonshot.ai)" },
|
|
440
|
-
],
|
|
441
|
-
},
|
|
162
|
+
function pathToSettingDef(path: SettingPath): SettingDef | null {
|
|
163
|
+
const ui = getUi(path);
|
|
164
|
+
if (!ui) return null;
|
|
442
165
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
446
|
-
{
|
|
447
|
-
id: "theme",
|
|
448
|
-
tab: "display",
|
|
449
|
-
type: "submenu",
|
|
450
|
-
label: "Theme",
|
|
451
|
-
description: "Color theme for the interface",
|
|
452
|
-
get: sm => sm.getTheme() ?? "dark",
|
|
453
|
-
set: (sm, v) => sm.setTheme(v),
|
|
454
|
-
getOptions: () => [], // Filled dynamically from context
|
|
455
|
-
},
|
|
456
|
-
{
|
|
457
|
-
id: "symbolPreset",
|
|
458
|
-
tab: "display",
|
|
459
|
-
type: "submenu",
|
|
460
|
-
label: "Symbol preset",
|
|
461
|
-
description: "Icon/symbol style (overrides theme default)",
|
|
462
|
-
get: sm => sm.getSymbolPreset() ?? "unicode",
|
|
463
|
-
set: (sm, v) => sm.setSymbolPreset(v as SymbolPreset),
|
|
464
|
-
getOptions: () => [
|
|
465
|
-
{ value: "unicode", label: "Unicode", description: "Standard Unicode symbols (default)" },
|
|
466
|
-
{ value: "nerd", label: "Nerd Font", description: "Nerd Font icons (requires Nerd Font)" },
|
|
467
|
-
{ value: "ascii", label: "ASCII", description: "ASCII-only characters (maximum compatibility)" },
|
|
468
|
-
],
|
|
469
|
-
},
|
|
470
|
-
{
|
|
471
|
-
id: "colorBlindMode",
|
|
472
|
-
tab: "display",
|
|
473
|
-
type: "boolean",
|
|
474
|
-
label: "Color blind mode",
|
|
475
|
-
description: "Use blue instead of green for diff additions (red-green color blindness)",
|
|
476
|
-
get: sm => sm.getColorBlindMode(),
|
|
477
|
-
set: (sm, v) => sm.setColorBlindMode(v),
|
|
478
|
-
},
|
|
479
|
-
{
|
|
480
|
-
id: "thinkingLevel",
|
|
481
|
-
tab: "display",
|
|
482
|
-
type: "submenu",
|
|
483
|
-
label: "Thinking level",
|
|
484
|
-
description: "Reasoning depth for thinking-capable models",
|
|
485
|
-
get: sm => sm.getDefaultThinkingLevel() ?? "off",
|
|
486
|
-
set: (sm, v) => sm.setDefaultThinkingLevel(v as ThinkingLevel),
|
|
487
|
-
getOptions: () =>
|
|
488
|
-
(["off", "minimal", "low", "medium", "high", "xhigh"] as ThinkingLevel[]).map(level => ({
|
|
489
|
-
value: level,
|
|
490
|
-
label: level,
|
|
491
|
-
description: THINKING_DESCRIPTIONS[level],
|
|
492
|
-
})),
|
|
493
|
-
},
|
|
494
|
-
{
|
|
495
|
-
id: "hideThinking",
|
|
496
|
-
tab: "display",
|
|
497
|
-
type: "boolean",
|
|
498
|
-
label: "Hide thinking",
|
|
499
|
-
description: "Hide thinking blocks in assistant responses",
|
|
500
|
-
get: sm => sm.getHideThinkingBlock(),
|
|
501
|
-
set: (sm, v) => sm.setHideThinkingBlock(v),
|
|
502
|
-
},
|
|
503
|
-
{
|
|
504
|
-
id: "showImages",
|
|
505
|
-
tab: "display",
|
|
506
|
-
type: "boolean",
|
|
507
|
-
label: "Show images",
|
|
508
|
-
description: "Render images inline in terminal",
|
|
509
|
-
get: sm => sm.getShowImages(),
|
|
510
|
-
set: (sm, v) => sm.setShowImages(v),
|
|
511
|
-
condition: () => !!TERMINAL_INFO.imageProtocol,
|
|
512
|
-
},
|
|
513
|
-
{
|
|
514
|
-
id: "autoResizeImages",
|
|
515
|
-
tab: "display",
|
|
516
|
-
type: "boolean",
|
|
517
|
-
label: "Auto-resize images",
|
|
518
|
-
description: "Resize large images to 2000x2000 max for better model compatibility",
|
|
519
|
-
get: sm => sm.getImageAutoResize(),
|
|
520
|
-
set: (sm, v) => sm.setImageAutoResize(v),
|
|
521
|
-
},
|
|
522
|
-
{
|
|
523
|
-
id: "blockImages",
|
|
524
|
-
tab: "display",
|
|
525
|
-
type: "boolean",
|
|
526
|
-
label: "Block images",
|
|
527
|
-
description: "Prevent images from being sent to LLM providers",
|
|
528
|
-
get: sm => sm.getBlockImages(),
|
|
529
|
-
set: (sm, v) => sm.setBlockImages(v),
|
|
530
|
-
},
|
|
531
|
-
{
|
|
532
|
-
id: "showHardwareCursor",
|
|
533
|
-
tab: "display",
|
|
534
|
-
type: "boolean",
|
|
535
|
-
label: "Hardware cursor",
|
|
536
|
-
description: "Show terminal cursor for IME support (default: on for Linux/macOS)",
|
|
537
|
-
get: sm => sm.getShowHardwareCursor(),
|
|
538
|
-
set: (sm, v) => sm.setShowHardwareCursor(v),
|
|
539
|
-
},
|
|
166
|
+
const schemaType = getType(path);
|
|
167
|
+
const base = { path, label: ui.label, description: ui.description, tab: ui.tab };
|
|
540
168
|
|
|
541
|
-
//
|
|
542
|
-
|
|
543
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
544
|
-
{
|
|
545
|
-
id: "ttsrEnabled",
|
|
546
|
-
tab: "ttsr",
|
|
547
|
-
type: "boolean",
|
|
548
|
-
label: "TTSR enabled",
|
|
549
|
-
description: "Time Traveling Stream Rules: interrupt agent when output matches patterns",
|
|
550
|
-
get: sm => sm.getTtsrEnabled(),
|
|
551
|
-
set: (sm, v) => sm.setTtsrEnabled(v),
|
|
552
|
-
},
|
|
553
|
-
{
|
|
554
|
-
id: "ttsrContextMode",
|
|
555
|
-
tab: "ttsr",
|
|
556
|
-
type: "enum",
|
|
557
|
-
label: "TTSR context mode",
|
|
558
|
-
description: "What to do with partial output when TTSR triggers",
|
|
559
|
-
values: ["discard", "keep"],
|
|
560
|
-
get: sm => sm.getTtsrContextMode(),
|
|
561
|
-
set: (sm, v) => sm.setTtsrContextMode(v as "keep" | "discard"),
|
|
562
|
-
},
|
|
563
|
-
{
|
|
564
|
-
id: "ttsrRepeatMode",
|
|
565
|
-
tab: "ttsr",
|
|
566
|
-
type: "enum",
|
|
567
|
-
label: "TTSR repeat mode",
|
|
568
|
-
description: "How rules can repeat: once per session or after a message gap",
|
|
569
|
-
values: ["once", "after-gap"],
|
|
570
|
-
get: sm => sm.getTtsrRepeatMode(),
|
|
571
|
-
set: (sm, v) => sm.setTtsrRepeatMode(v as "once" | "after-gap"),
|
|
572
|
-
},
|
|
573
|
-
{
|
|
574
|
-
id: "ttsrRepeatGap",
|
|
575
|
-
tab: "ttsr",
|
|
576
|
-
type: "submenu",
|
|
577
|
-
label: "TTSR repeat gap",
|
|
578
|
-
description: "Messages before a rule can trigger again (when repeat mode is after-gap)",
|
|
579
|
-
get: sm => String(sm.getTtsrRepeatGap()),
|
|
580
|
-
set: (sm, v) => sm.setTtsrRepeatGap(Number.parseInt(v, 10)),
|
|
581
|
-
getOptions: () => [
|
|
582
|
-
{ value: "5", label: "5 messages" },
|
|
583
|
-
{ value: "10", label: "10 messages" },
|
|
584
|
-
{ value: "15", label: "15 messages" },
|
|
585
|
-
{ value: "20", label: "20 messages" },
|
|
586
|
-
{ value: "30", label: "30 messages" },
|
|
587
|
-
],
|
|
588
|
-
},
|
|
169
|
+
// Check for condition
|
|
170
|
+
const condition = ui.condition ? CONDITIONS[ui.condition] : undefined;
|
|
589
171
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
{
|
|
594
|
-
id: "statusLinePreset",
|
|
595
|
-
tab: "status",
|
|
596
|
-
type: "submenu",
|
|
597
|
-
label: "Preset",
|
|
598
|
-
description: "Pre-built status line configurations",
|
|
599
|
-
get: sm => sm.getStatusLinePreset(),
|
|
600
|
-
set: (sm, v) => sm.setStatusLinePreset(v as StatusLinePreset),
|
|
601
|
-
getOptions: () => [
|
|
602
|
-
{ value: "default", label: "Default", description: "Model, path, git, context, tokens, cost" },
|
|
603
|
-
{ value: "minimal", label: "Minimal", description: "Path and git only" },
|
|
604
|
-
{ value: "compact", label: "Compact", description: "Model, git, cost, context" },
|
|
605
|
-
{ value: "full", label: "Full", description: "All segments including time" },
|
|
606
|
-
{ value: "nerd", label: "Nerd", description: "Maximum info with Nerd Font icons" },
|
|
607
|
-
{ value: "ascii", label: "ASCII", description: "No special characters" },
|
|
608
|
-
{ value: "custom", label: "Custom", description: "User-defined segments" },
|
|
609
|
-
],
|
|
610
|
-
},
|
|
611
|
-
{
|
|
612
|
-
id: "statusLineSeparator",
|
|
613
|
-
tab: "status",
|
|
614
|
-
type: "submenu",
|
|
615
|
-
label: "Separator style",
|
|
616
|
-
description: "Style of separators between segments",
|
|
617
|
-
get: sm => {
|
|
618
|
-
const settings = sm.getStatusLineSettings();
|
|
619
|
-
if (settings.separator) return settings.separator;
|
|
620
|
-
return getPreset(sm.getStatusLinePreset()).separator;
|
|
621
|
-
},
|
|
622
|
-
set: (sm, v) => sm.setStatusLineSeparator(v as StatusLineSeparatorStyle),
|
|
623
|
-
getOptions: () => [
|
|
624
|
-
{ value: "powerline", label: "Powerline", description: "Solid arrows (requires Nerd Font)" },
|
|
625
|
-
{ value: "powerline-thin", label: "Thin chevron", description: "Thin arrows (requires Nerd Font)" },
|
|
626
|
-
{ value: "slash", label: "Slash", description: "Forward slashes" },
|
|
627
|
-
{ value: "pipe", label: "Pipe", description: "Vertical pipes" },
|
|
628
|
-
{ value: "block", label: "Block", description: "Solid blocks" },
|
|
629
|
-
{ value: "none", label: "None", description: "Space only" },
|
|
630
|
-
{ value: "ascii", label: "ASCII", description: "Greater-than signs" },
|
|
631
|
-
],
|
|
632
|
-
},
|
|
633
|
-
{
|
|
634
|
-
id: "statusLineShowHooks",
|
|
635
|
-
tab: "status",
|
|
636
|
-
type: "boolean",
|
|
637
|
-
label: "Show extension status",
|
|
638
|
-
description: "Display hook status messages below status line",
|
|
639
|
-
get: sm => sm.getStatusLineShowHookStatus(),
|
|
640
|
-
set: (sm, v) => sm.setStatusLineShowHookStatus(v),
|
|
641
|
-
},
|
|
642
|
-
{
|
|
643
|
-
id: "statusLineSegments",
|
|
644
|
-
tab: "status",
|
|
645
|
-
type: "submenu",
|
|
646
|
-
label: "Configure segments",
|
|
647
|
-
description: "Choose and arrange status line segments",
|
|
648
|
-
get: () => "configure...",
|
|
649
|
-
set: () => {},
|
|
650
|
-
getOptions: () => [{ value: "open", label: "Open segment editor..." }],
|
|
651
|
-
},
|
|
652
|
-
{
|
|
653
|
-
id: "statusLineModelThinking",
|
|
654
|
-
tab: "status",
|
|
655
|
-
type: "enum",
|
|
656
|
-
label: "Model thinking level",
|
|
657
|
-
description: "Show thinking level in the model segment",
|
|
658
|
-
values: ["default", "on", "off"],
|
|
659
|
-
get: sm => {
|
|
660
|
-
const value = sm.getStatusLineSegmentOptions().model?.showThinkingLevel;
|
|
661
|
-
if (value === undefined) return "default";
|
|
662
|
-
return value ? "on" : "off";
|
|
663
|
-
},
|
|
664
|
-
set: (sm, v) => {
|
|
665
|
-
if (v === "default") {
|
|
666
|
-
sm.clearStatusLineSegmentOption("model", "showThinkingLevel");
|
|
667
|
-
} else {
|
|
668
|
-
sm.setStatusLineSegmentOption("model", "showThinkingLevel", v === "on");
|
|
669
|
-
}
|
|
670
|
-
},
|
|
671
|
-
},
|
|
672
|
-
{
|
|
673
|
-
id: "statusLinePathAbbreviate",
|
|
674
|
-
tab: "status",
|
|
675
|
-
type: "enum",
|
|
676
|
-
label: "Path abbreviate",
|
|
677
|
-
description: "Use ~ and strip home prefix in path segment",
|
|
678
|
-
values: ["default", "on", "off"],
|
|
679
|
-
get: sm => {
|
|
680
|
-
const value = sm.getStatusLineSegmentOptions().path?.abbreviate;
|
|
681
|
-
if (value === undefined) return "default";
|
|
682
|
-
return value ? "on" : "off";
|
|
683
|
-
},
|
|
684
|
-
set: (sm, v) => {
|
|
685
|
-
if (v === "default") {
|
|
686
|
-
sm.clearStatusLineSegmentOption("path", "abbreviate");
|
|
687
|
-
} else {
|
|
688
|
-
sm.setStatusLineSegmentOption("path", "abbreviate", v === "on");
|
|
689
|
-
}
|
|
690
|
-
},
|
|
691
|
-
},
|
|
692
|
-
{
|
|
693
|
-
id: "statusLinePathMaxLength",
|
|
694
|
-
tab: "status",
|
|
695
|
-
type: "submenu",
|
|
696
|
-
label: "Path max length",
|
|
697
|
-
description: "Maximum length for displayed path",
|
|
698
|
-
get: sm => {
|
|
699
|
-
const value = sm.getStatusLineSegmentOptions().path?.maxLength;
|
|
700
|
-
return typeof value === "number" ? String(value) : "default";
|
|
701
|
-
},
|
|
702
|
-
set: (sm, v) => {
|
|
703
|
-
if (v === "default") {
|
|
704
|
-
sm.clearStatusLineSegmentOption("path", "maxLength");
|
|
705
|
-
} else {
|
|
706
|
-
sm.setStatusLineSegmentOption("path", "maxLength", Number.parseInt(v, 10));
|
|
707
|
-
}
|
|
708
|
-
},
|
|
709
|
-
getOptions: () => [
|
|
710
|
-
{ value: "default", label: "Preset default" },
|
|
711
|
-
{ value: "20", label: "20" },
|
|
712
|
-
{ value: "30", label: "30" },
|
|
713
|
-
{ value: "40", label: "40" },
|
|
714
|
-
{ value: "50", label: "50" },
|
|
715
|
-
{ value: "60", label: "60" },
|
|
716
|
-
{ value: "80", label: "80" },
|
|
717
|
-
],
|
|
718
|
-
},
|
|
719
|
-
{
|
|
720
|
-
id: "statusLinePathStripWorkPrefix",
|
|
721
|
-
tab: "status",
|
|
722
|
-
type: "enum",
|
|
723
|
-
label: "Path strip /work",
|
|
724
|
-
description: "Strip /work prefix in path segment",
|
|
725
|
-
values: ["default", "on", "off"],
|
|
726
|
-
get: sm => {
|
|
727
|
-
const value = sm.getStatusLineSegmentOptions().path?.stripWorkPrefix;
|
|
728
|
-
if (value === undefined) return "default";
|
|
729
|
-
return value ? "on" : "off";
|
|
730
|
-
},
|
|
731
|
-
set: (sm, v) => {
|
|
732
|
-
if (v === "default") {
|
|
733
|
-
sm.clearStatusLineSegmentOption("path", "stripWorkPrefix");
|
|
734
|
-
} else {
|
|
735
|
-
sm.setStatusLineSegmentOption("path", "stripWorkPrefix", v === "on");
|
|
736
|
-
}
|
|
737
|
-
},
|
|
738
|
-
},
|
|
739
|
-
{
|
|
740
|
-
id: "statusLineGitShowBranch",
|
|
741
|
-
tab: "status",
|
|
742
|
-
type: "enum",
|
|
743
|
-
label: "Git show branch",
|
|
744
|
-
description: "Show branch name in git segment",
|
|
745
|
-
values: ["default", "on", "off"],
|
|
746
|
-
get: sm => {
|
|
747
|
-
const value = sm.getStatusLineSegmentOptions().git?.showBranch;
|
|
748
|
-
if (value === undefined) return "default";
|
|
749
|
-
return value ? "on" : "off";
|
|
750
|
-
},
|
|
751
|
-
set: (sm, v) => {
|
|
752
|
-
if (v === "default") {
|
|
753
|
-
sm.clearStatusLineSegmentOption("git", "showBranch");
|
|
754
|
-
} else {
|
|
755
|
-
sm.setStatusLineSegmentOption("git", "showBranch", v === "on");
|
|
756
|
-
}
|
|
757
|
-
},
|
|
758
|
-
},
|
|
759
|
-
{
|
|
760
|
-
id: "statusLineGitShowStaged",
|
|
761
|
-
tab: "status",
|
|
762
|
-
type: "enum",
|
|
763
|
-
label: "Git show staged",
|
|
764
|
-
description: "Show staged file count in git segment",
|
|
765
|
-
values: ["default", "on", "off"],
|
|
766
|
-
get: sm => {
|
|
767
|
-
const value = sm.getStatusLineSegmentOptions().git?.showStaged;
|
|
768
|
-
if (value === undefined) return "default";
|
|
769
|
-
return value ? "on" : "off";
|
|
770
|
-
},
|
|
771
|
-
set: (sm, v) => {
|
|
772
|
-
if (v === "default") {
|
|
773
|
-
sm.clearStatusLineSegmentOption("git", "showStaged");
|
|
774
|
-
} else {
|
|
775
|
-
sm.setStatusLineSegmentOption("git", "showStaged", v === "on");
|
|
776
|
-
}
|
|
777
|
-
},
|
|
778
|
-
},
|
|
779
|
-
{
|
|
780
|
-
id: "statusLineGitShowUnstaged",
|
|
781
|
-
tab: "status",
|
|
782
|
-
type: "enum",
|
|
783
|
-
label: "Git show unstaged",
|
|
784
|
-
description: "Show unstaged file count in git segment",
|
|
785
|
-
values: ["default", "on", "off"],
|
|
786
|
-
get: sm => {
|
|
787
|
-
const value = sm.getStatusLineSegmentOptions().git?.showUnstaged;
|
|
788
|
-
if (value === undefined) return "default";
|
|
789
|
-
return value ? "on" : "off";
|
|
790
|
-
},
|
|
791
|
-
set: (sm, v) => {
|
|
792
|
-
if (v === "default") {
|
|
793
|
-
sm.clearStatusLineSegmentOption("git", "showUnstaged");
|
|
794
|
-
} else {
|
|
795
|
-
sm.setStatusLineSegmentOption("git", "showUnstaged", v === "on");
|
|
796
|
-
}
|
|
797
|
-
},
|
|
798
|
-
},
|
|
799
|
-
{
|
|
800
|
-
id: "statusLineGitShowUntracked",
|
|
801
|
-
tab: "status",
|
|
802
|
-
type: "enum",
|
|
803
|
-
label: "Git show untracked",
|
|
804
|
-
description: "Show untracked file count in git segment",
|
|
805
|
-
values: ["default", "on", "off"],
|
|
806
|
-
get: sm => {
|
|
807
|
-
const value = sm.getStatusLineSegmentOptions().git?.showUntracked;
|
|
808
|
-
if (value === undefined) return "default";
|
|
809
|
-
return value ? "on" : "off";
|
|
810
|
-
},
|
|
811
|
-
set: (sm, v) => {
|
|
812
|
-
if (v === "default") {
|
|
813
|
-
sm.clearStatusLineSegmentOption("git", "showUntracked");
|
|
814
|
-
} else {
|
|
815
|
-
sm.setStatusLineSegmentOption("git", "showUntracked", v === "on");
|
|
816
|
-
}
|
|
817
|
-
},
|
|
818
|
-
},
|
|
819
|
-
{
|
|
820
|
-
id: "statusLineTimeFormat",
|
|
821
|
-
tab: "status",
|
|
822
|
-
type: "enum",
|
|
823
|
-
label: "Time format",
|
|
824
|
-
description: "Clock segment time format",
|
|
825
|
-
values: ["default", "12h", "24h"],
|
|
826
|
-
get: sm => sm.getStatusLineSegmentOptions().time?.format ?? "default",
|
|
827
|
-
set: (sm, v) => {
|
|
828
|
-
if (v === "default") {
|
|
829
|
-
sm.clearStatusLineSegmentOption("time", "format");
|
|
830
|
-
} else {
|
|
831
|
-
sm.setStatusLineSegmentOption("time", "format", v);
|
|
832
|
-
}
|
|
833
|
-
},
|
|
834
|
-
},
|
|
835
|
-
{
|
|
836
|
-
id: "statusLineTimeShowSeconds",
|
|
837
|
-
tab: "status",
|
|
838
|
-
type: "enum",
|
|
839
|
-
label: "Time show seconds",
|
|
840
|
-
description: "Include seconds in clock segment",
|
|
841
|
-
values: ["default", "on", "off"],
|
|
842
|
-
get: sm => {
|
|
843
|
-
const value = sm.getStatusLineSegmentOptions().time?.showSeconds;
|
|
844
|
-
if (value === undefined) return "default";
|
|
845
|
-
return value ? "on" : "off";
|
|
846
|
-
},
|
|
847
|
-
set: (sm, v) => {
|
|
848
|
-
if (v === "default") {
|
|
849
|
-
sm.clearStatusLineSegmentOption("time", "showSeconds");
|
|
850
|
-
} else {
|
|
851
|
-
sm.setStatusLineSegmentOption("time", "showSeconds", v === "on");
|
|
852
|
-
}
|
|
853
|
-
},
|
|
854
|
-
},
|
|
172
|
+
if (schemaType === "boolean") {
|
|
173
|
+
return { ...base, type: "boolean", condition };
|
|
174
|
+
}
|
|
855
175
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
859
|
-
{
|
|
860
|
-
id: "lspFormatOnWrite",
|
|
861
|
-
tab: "lsp",
|
|
862
|
-
type: "boolean",
|
|
863
|
-
label: "Format on write",
|
|
864
|
-
description: "Automatically format code files using LSP after writing",
|
|
865
|
-
get: sm => sm.getLspFormatOnWrite(),
|
|
866
|
-
set: (sm, v) => sm.setLspFormatOnWrite(v),
|
|
867
|
-
},
|
|
868
|
-
{
|
|
869
|
-
id: "lspDiagnosticsOnWrite",
|
|
870
|
-
tab: "lsp",
|
|
871
|
-
type: "boolean",
|
|
872
|
-
label: "Diagnostics on write",
|
|
873
|
-
description: "Return LSP diagnostics (errors/warnings) after writing code files",
|
|
874
|
-
get: sm => sm.getLspDiagnosticsOnWrite(),
|
|
875
|
-
set: (sm, v) => sm.setLspDiagnosticsOnWrite(v),
|
|
876
|
-
},
|
|
877
|
-
{
|
|
878
|
-
id: "lspDiagnosticsOnEdit",
|
|
879
|
-
tab: "lsp",
|
|
880
|
-
type: "boolean",
|
|
881
|
-
label: "Diagnostics on edit",
|
|
882
|
-
description: "Return LSP diagnostics (errors/warnings) after editing code files",
|
|
883
|
-
get: sm => sm.getLspDiagnosticsOnEdit(),
|
|
884
|
-
set: (sm, v) => sm.setLspDiagnosticsOnEdit(v),
|
|
885
|
-
},
|
|
176
|
+
if (schemaType === "enum") {
|
|
177
|
+
const values = getEnumValues(path) ?? [];
|
|
886
178
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
label: "Exa search",
|
|
904
|
-
description: "Basic search, deep search, code search, crawl",
|
|
905
|
-
get: sm => sm.getExaSettings().enableSearch,
|
|
906
|
-
set: (sm, v) => sm.setExaSearchEnabled(v),
|
|
907
|
-
},
|
|
908
|
-
{
|
|
909
|
-
id: "exaLinkedin",
|
|
910
|
-
tab: "exa",
|
|
911
|
-
type: "boolean",
|
|
912
|
-
label: "Exa LinkedIn",
|
|
913
|
-
description: "Search LinkedIn for people and companies",
|
|
914
|
-
get: sm => sm.getExaSettings().enableLinkedin,
|
|
915
|
-
set: (sm, v) => sm.setExaLinkedinEnabled(v),
|
|
916
|
-
},
|
|
917
|
-
{
|
|
918
|
-
id: "exaCompany",
|
|
919
|
-
tab: "exa",
|
|
920
|
-
type: "boolean",
|
|
921
|
-
label: "Exa company",
|
|
922
|
-
description: "Comprehensive company research tool",
|
|
923
|
-
get: sm => sm.getExaSettings().enableCompany,
|
|
924
|
-
set: (sm, v) => sm.setExaCompanyEnabled(v),
|
|
925
|
-
},
|
|
926
|
-
{
|
|
927
|
-
id: "exaResearcher",
|
|
928
|
-
tab: "exa",
|
|
929
|
-
type: "boolean",
|
|
930
|
-
label: "Exa researcher",
|
|
931
|
-
description: "AI-powered deep research tasks",
|
|
932
|
-
get: sm => sm.getExaSettings().enableResearcher,
|
|
933
|
-
set: (sm, v) => sm.setExaResearcherEnabled(v),
|
|
934
|
-
},
|
|
935
|
-
{
|
|
936
|
-
id: "exaWebsets",
|
|
937
|
-
tab: "exa",
|
|
938
|
-
type: "boolean",
|
|
939
|
-
label: "Exa websets",
|
|
940
|
-
description: "Webset management and enrichment tools",
|
|
941
|
-
get: sm => sm.getExaSettings().enableWebsets,
|
|
942
|
-
set: (sm, v) => sm.setExaWebsetsEnabled(v),
|
|
943
|
-
},
|
|
944
|
-
];
|
|
179
|
+
// If marked as submenu, use submenu type
|
|
180
|
+
if (ui.submenu) {
|
|
181
|
+
const provider = OPTION_PROVIDERS[path];
|
|
182
|
+
return {
|
|
183
|
+
...base,
|
|
184
|
+
type: "submenu",
|
|
185
|
+
getOptions:
|
|
186
|
+
provider ??
|
|
187
|
+
(() =>
|
|
188
|
+
values.map(v => ({
|
|
189
|
+
value: v,
|
|
190
|
+
label: v,
|
|
191
|
+
description: path === "defaultThinkingLevel" ? THINKING_DESCRIPTIONS[v] : undefined,
|
|
192
|
+
}))),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
945
195
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
196
|
+
return { ...base, type: "enum", values };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (schemaType === "number" && ui.submenu) {
|
|
200
|
+
const provider = OPTION_PROVIDERS[path];
|
|
201
|
+
if (provider) {
|
|
202
|
+
return { ...base, type: "submenu", getOptions: provider };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (schemaType === "string" && ui.submenu) {
|
|
207
|
+
const provider = OPTION_PROVIDERS[path];
|
|
208
|
+
if (provider) {
|
|
209
|
+
return { ...base, type: "submenu", getOptions: provider };
|
|
210
|
+
}
|
|
211
|
+
// For theme etc, options will be injected at runtime
|
|
212
|
+
return { ...base, type: "submenu", getOptions: () => [] };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
219
|
+
// Public API
|
|
220
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
221
|
+
|
|
222
|
+
/** Cache of generated definitions */
|
|
223
|
+
let cachedDefs: SettingDef[] | null = null;
|
|
224
|
+
|
|
225
|
+
/** Get all setting definitions with UI */
|
|
226
|
+
export function getAllSettingDefs(): SettingDef[] {
|
|
227
|
+
if (cachedDefs) return cachedDefs;
|
|
228
|
+
|
|
229
|
+
const defs: SettingDef[] = [];
|
|
230
|
+
for (const tab of ["behavior", "tools", "bash", "display", "ttsr", "status", "lsp", "exa"] as SettingTab[]) {
|
|
231
|
+
for (const path of getPathsForTab(tab)) {
|
|
232
|
+
const def = pathToSettingDef(path);
|
|
233
|
+
if (def) defs.push(def);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
cachedDefs = defs;
|
|
237
|
+
return defs;
|
|
951
238
|
}
|
|
952
239
|
|
|
953
240
|
/** Get settings for a specific tab */
|
|
954
|
-
export function getSettingsForTab(tab:
|
|
955
|
-
return
|
|
241
|
+
export function getSettingsForTab(tab: SettingTab): SettingDef[] {
|
|
242
|
+
return getAllSettingDefs().filter(def => def.tab === tab);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Get a setting definition by path */
|
|
246
|
+
export function getSettingDef(path: SettingPath): SettingDef | undefined {
|
|
247
|
+
return getAllSettingDefs().find(def => def.path === path);
|
|
956
248
|
}
|
|
957
249
|
|
|
958
|
-
/** Get
|
|
959
|
-
export function
|
|
960
|
-
|
|
250
|
+
/** Get default value for display */
|
|
251
|
+
export function getDisplayDefault(path: SettingPath): string {
|
|
252
|
+
const value = getDefault(path);
|
|
253
|
+
if (value === undefined) return "";
|
|
254
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
255
|
+
return String(value);
|
|
961
256
|
}
|