ubume 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -0
- package/bin/codexa-local-harness-bridge.js +3 -0
- package/bin/codexa.js +28 -0
- package/bin/ubume-local-harness-bridge.js +325 -0
- package/bin/ubume.js +398 -0
- package/package.json +66 -0
- package/src/app.tsx +5759 -0
- package/src/commands/handler.ts +889 -0
- package/src/config/appVersion.ts +69 -0
- package/src/config/buildInfo.ts +3 -0
- package/src/config/launchArgs.ts +196 -0
- package/src/config/layeredConfig.ts +853 -0
- package/src/config/legacyEnv.ts +16 -0
- package/src/config/persistence.ts +377 -0
- package/src/config/runtimeConfig.ts +558 -0
- package/src/config/settings.ts +405 -0
- package/src/config/toml-serialize.ts +109 -0
- package/src/config/trustStore.ts +84 -0
- package/src/config/updateCheckCache.ts +85 -0
- package/src/core/README.md +55 -0
- package/src/core/agent/loop.ts +464 -0
- package/src/core/agent/protocol.ts +345 -0
- package/src/core/agent/tools.ts +423 -0
- package/src/core/auth/codexAuth.ts +359 -0
- package/src/core/codex/codexExecArgs.ts +166 -0
- package/src/core/codex/codexLaunch.ts +163 -0
- package/src/core/codex/codexPrompt.ts +429 -0
- package/src/core/debug/inputDebug.ts +51 -0
- package/src/core/debug/localStreamDebug.ts +50 -0
- package/src/core/debug/modelStateDebug.ts +35 -0
- package/src/core/executables/antigravityExecutable.ts +48 -0
- package/src/core/executables/claudeExecutable.ts +63 -0
- package/src/core/executables/codexExecutable.ts +160 -0
- package/src/core/executables/executableResolver.ts +164 -0
- package/src/core/executables/geminiExecutable.ts +78 -0
- package/src/core/models/codexCapabilities.ts +97 -0
- package/src/core/models/codexModelCapabilities.ts +624 -0
- package/src/core/models/codexModelsCacheSeed.ts +153 -0
- package/src/core/models/modelSpecs.ts +303 -0
- package/src/core/models/providerModelCache.ts +94 -0
- package/src/core/perf/profiler.ts +125 -0
- package/src/core/perf/renderDebug.ts +398 -0
- package/src/core/process/CommandRunner.ts +280 -0
- package/src/core/process/processValidation.ts +111 -0
- package/src/core/providerLauncher/launcher.ts +220 -0
- package/src/core/providerLauncher/registry.ts +354 -0
- package/src/core/providerLauncher/types.ts +95 -0
- package/src/core/providerLauncher/workspaceConfig.ts +487 -0
- package/src/core/providerRuntime/anthropic.ts +580 -0
- package/src/core/providerRuntime/antigravity.ts +500 -0
- package/src/core/providerRuntime/capabilityProfile.ts +383 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -0
- package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
- package/src/core/providerRuntime/codexaCupy.ts +97 -0
- package/src/core/providerRuntime/codexaNative.ts +425 -0
- package/src/core/providerRuntime/contextMetadata.ts +397 -0
- package/src/core/providerRuntime/gemini.ts +789 -0
- package/src/core/providerRuntime/lmstudio.ts +118 -0
- package/src/core/providerRuntime/local.ts +770 -0
- package/src/core/providerRuntime/localHarness/runtime.ts +1090 -0
- package/src/core/providerRuntime/localOutputBudget.ts +17 -0
- package/src/core/providerRuntime/mistralVibe.ts +667 -0
- package/src/core/providerRuntime/models.ts +175 -0
- package/src/core/providerRuntime/reasoning.ts +20 -0
- package/src/core/providerRuntime/registry.ts +286 -0
- package/src/core/providerRuntime/types.ts +142 -0
- package/src/core/providerRuntime/unsloth.ts +216 -0
- package/src/core/providers/codexJsonStream.ts +305 -0
- package/src/core/providers/codexSubprocess.ts +378 -0
- package/src/core/providers/codexTranscript.ts +695 -0
- package/src/core/providers/openaiNative.ts +13 -0
- package/src/core/providers/registry.ts +21 -0
- package/src/core/providers/types.ts +91 -0
- package/src/core/shared/attachments.ts +101 -0
- package/src/core/shared/cleanupFastFail.ts +67 -0
- package/src/core/shared/clipboard.ts +24 -0
- package/src/core/shared/clipboardImage.ts +111 -0
- package/src/core/shared/githubDiagnostics.ts +222 -0
- package/src/core/shared/hollowResponseFormat.ts +39 -0
- package/src/core/terminal/clearFrameBoundary.ts +852 -0
- package/src/core/terminal/frameLock.ts +110 -0
- package/src/core/terminal/inkRenderReset.ts +123 -0
- package/src/core/terminal/startupClear.ts +20 -0
- package/src/core/terminal/terminalCapabilities.ts +100 -0
- package/src/core/terminal/terminalControl.ts +169 -0
- package/src/core/terminal/terminalSanitize.ts +147 -0
- package/src/core/terminal/terminalTitle.ts +400 -0
- package/src/core/version/channel.ts +27 -0
- package/src/core/version/packageManager.ts +119 -0
- package/src/core/version/updateCheck.ts +203 -0
- package/src/core/workspace/appData.ts +107 -0
- package/src/core/workspace/conversationStore.ts +335 -0
- package/src/core/workspace/launchContext.ts +259 -0
- package/src/core/workspace/planStorage.ts +135 -0
- package/src/core/workspace/projectInstructions.ts +54 -0
- package/src/core/workspace/scratchDir.ts +64 -0
- package/src/core/workspace/workspaceActivity.ts +384 -0
- package/src/core/workspace/workspaceGuard.ts +377 -0
- package/src/core/workspace/workspaceRoot.ts +47 -0
- package/src/exec.ts +73 -0
- package/src/headless/execArgs.ts +296 -0
- package/src/headless/execRunner.ts +304 -0
- package/src/index.tsx +270 -0
- package/src/legacyEnvBootstrap.ts +5 -0
- package/src/session/appSession.ts +771 -0
- package/src/session/chatLifecycle.ts +994 -0
- package/src/session/conversation.ts +107 -0
- package/src/session/liveRenderScheduler.ts +214 -0
- package/src/session/persistedResponse.ts +93 -0
- package/src/session/planFlow.ts +159 -0
- package/src/session/planTranscript.ts +19 -0
- package/src/session/promptRunSchedule.ts +26 -0
- package/src/session/types.ts +234 -0
- package/src/test/runtimeTestUtils.ts +14 -0
- package/src/types/react-dom.d.ts +3 -0
- package/src/ui/chrome/ActivityBars.tsx +68 -0
- package/src/ui/chrome/ActivityIndicator.tsx +58 -0
- package/src/ui/chrome/AnimatedStatusText.tsx +69 -0
- package/src/ui/chrome/AppShell.tsx +474 -0
- package/src/ui/chrome/BottomComposer.tsx +1135 -0
- package/src/ui/chrome/DashCard.tsx +82 -0
- package/src/ui/chrome/RunFooter.tsx +65 -0
- package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
- package/src/ui/chrome/Spinner.tsx +25 -0
- package/src/ui/chrome/TopHeader.tsx +439 -0
- package/src/ui/chrome/UpdateAvailableCard.tsx +42 -0
- package/src/ui/chrome/busyStatusAnimation.ts +11 -0
- package/src/ui/input/commandNormalize.ts +66 -0
- package/src/ui/input/focus.ts +73 -0
- package/src/ui/input/imageAttachments.ts +18 -0
- package/src/ui/input/inputBuffer.ts +203 -0
- package/src/ui/input/pastedContent.ts +75 -0
- package/src/ui/input/rawArrowKeys.ts +28 -0
- package/src/ui/input/slashCommands.ts +43 -0
- package/src/ui/input/useStdinRawModeLease.ts +23 -0
- package/src/ui/layout.ts +560 -0
- package/src/ui/panels/AttachmentImportPanel.tsx +131 -0
- package/src/ui/panels/AuthPanel.tsx +149 -0
- package/src/ui/panels/BackendPicker.tsx +28 -0
- package/src/ui/panels/ModePicker.tsx +31 -0
- package/src/ui/panels/ModelPicker.tsx +31 -0
- package/src/ui/panels/ModelPickerScreen.tsx +761 -0
- package/src/ui/panels/ModelReasoningPicker.tsx +458 -0
- package/src/ui/panels/Panel.tsx +51 -0
- package/src/ui/panels/PermissionsPanel.tsx +78 -0
- package/src/ui/panels/PlanActionPicker.tsx +187 -0
- package/src/ui/panels/ProviderPicker.tsx +753 -0
- package/src/ui/panels/ProviderSetupPrompt.tsx +52 -0
- package/src/ui/panels/ReasoningPicker.tsx +46 -0
- package/src/ui/panels/ResumePicker.tsx +90 -0
- package/src/ui/panels/SelectionPanel.tsx +138 -0
- package/src/ui/panels/SettingsPanel.tsx +156 -0
- package/src/ui/panels/TextEntryPanel.tsx +139 -0
- package/src/ui/panels/ThemePicker.tsx +32 -0
- package/src/ui/panels/ToolApprovalPanel.tsx +46 -0
- package/src/ui/panels/UpdatePromptPanel.tsx +236 -0
- package/src/ui/panels/responsivePickerViewport.ts +64 -0
- package/src/ui/render/Markdown.tsx +331 -0
- package/src/ui/render/diffRenderer.ts +116 -0
- package/src/ui/render/logoVariants.ts +113 -0
- package/src/ui/render/modeDisplay.ts +52 -0
- package/src/ui/render/outputPipeline.ts +64 -0
- package/src/ui/render/runtimeDisplay.ts +128 -0
- package/src/ui/render/terminalAnswerFormat.ts +128 -0
- package/src/ui/render/textLayout.ts +392 -0
- package/src/ui/theme.tsx +274 -0
- package/src/ui/themeFlow.ts +41 -0
- package/src/ui/timeline/ActionRequiredBlock.tsx +38 -0
- package/src/ui/timeline/AgentBlock.tsx +130 -0
- package/src/ui/timeline/StaticIntroItem.tsx +54 -0
- package/src/ui/timeline/ThinkingBlock.tsx +100 -0
- package/src/ui/timeline/Timeline.tsx +1410 -0
- package/src/ui/timeline/TranscriptShell.tsx +302 -0
- package/src/ui/timeline/TurnGroup.tsx +673 -0
- package/src/ui/timeline/layoutListWindow.ts +145 -0
- package/src/ui/timeline/liveViewportWindow.ts +68 -0
- package/src/ui/timeline/progressEntries.ts +156 -0
- package/src/ui/timeline/runActivityView.ts +37 -0
- package/src/ui/timeline/staticTranscriptCache.ts +174 -0
- package/src/ui/timeline/streamCoalesce.ts +53 -0
- package/src/ui/timeline/timelineMeasure.ts +3273 -0
- package/src/ui/useThrottledValue.ts +31 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const LEGACY_ENV_PREFIX = "CODEXA_";
|
|
2
|
+
const ENV_PREFIX = "UBUME_";
|
|
3
|
+
// Codexa remains the name of the native model family, so its variables are not aliased.
|
|
4
|
+
const MODEL_FAMILY_ENV = /^CODEXA_(NATIVE|CUPY|NUMPY)_/;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Pre-rename `CODEXA_*` variables keep working: copy each one into its
|
|
8
|
+
* `UBUME_*` name unless that name is already set. Mutates `env` in place.
|
|
9
|
+
*/
|
|
10
|
+
export function applyLegacyEnvAliases(env: NodeJS.ProcessEnv = process.env): void {
|
|
11
|
+
for (const [key, value] of Object.entries(env)) {
|
|
12
|
+
if (!key.startsWith(LEGACY_ENV_PREFIX) || MODEL_FAMILY_ENV.test(key)) continue;
|
|
13
|
+
const ubumeKey = `${ENV_PREFIX}${key.slice(LEGACY_ENV_PREFIX.length)}`;
|
|
14
|
+
if (env[ubumeKey] === undefined) env[ubumeKey] = value;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
2
|
+
import { dirname } from "path";
|
|
3
|
+
import { Theme } from "../ui/theme.js";
|
|
4
|
+
import {
|
|
5
|
+
AUTH_PREFERENCES,
|
|
6
|
+
DEFAULT_AUTH_PREFERENCE,
|
|
7
|
+
DEFAULT_LAYOUT_STYLE,
|
|
8
|
+
DEFAULT_SHOW_BUSY_LOADER,
|
|
9
|
+
DEFAULT_TERMINAL_TITLE_MODE,
|
|
10
|
+
DEFAULT_THEME,
|
|
11
|
+
DEFAULT_WORKSPACE_DISPLAY_MODE,
|
|
12
|
+
HEADER_CONFIG_DEFAULTS,
|
|
13
|
+
LEGACY_DIRECTORY_DISPLAY_MODES,
|
|
14
|
+
WORKSPACE_DISPLAY_MODES,
|
|
15
|
+
getCodexConfigFile,
|
|
16
|
+
normalizeLegacyDirectoryDisplayMode,
|
|
17
|
+
SETTINGS_FILE,
|
|
18
|
+
LEGACY_SETTINGS_FILE,
|
|
19
|
+
type AuthPreference,
|
|
20
|
+
type HeaderConfig,
|
|
21
|
+
type TerminalTitleMode,
|
|
22
|
+
type WorkspaceDisplayMode,
|
|
23
|
+
} from "./settings.js";
|
|
24
|
+
import {
|
|
25
|
+
mergeRuntimeIntoTomlConfig,
|
|
26
|
+
parseTomlDocument,
|
|
27
|
+
} from "./layeredConfig.js";
|
|
28
|
+
import { serializeTomlDocument } from "./toml-serialize.js";
|
|
29
|
+
import {
|
|
30
|
+
normalizeRuntimeConfig,
|
|
31
|
+
type RuntimeConfig,
|
|
32
|
+
} from "./runtimeConfig.js";
|
|
33
|
+
import type { AvailableMode } from "./settings.js";
|
|
34
|
+
|
|
35
|
+
export interface UiSettings {
|
|
36
|
+
layoutStyle: string;
|
|
37
|
+
theme: string;
|
|
38
|
+
workspaceDisplayMode: WorkspaceDisplayMode;
|
|
39
|
+
terminalTitleMode: TerminalTitleMode;
|
|
40
|
+
showBusyLoader: boolean;
|
|
41
|
+
customTheme?: Partial<Theme>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface AuthSettings {
|
|
45
|
+
preference: AuthPreference;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface UpdateCheckSettings {
|
|
49
|
+
enabled: boolean;
|
|
50
|
+
intervalHours: number;
|
|
51
|
+
skippedUpdateVersion?: string | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const DEFAULT_UPDATE_CHECK_SETTINGS: UpdateCheckSettings = {
|
|
55
|
+
enabled: true,
|
|
56
|
+
intervalHours: 6,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export interface AppSettings {
|
|
60
|
+
ui: UiSettings;
|
|
61
|
+
auth: AuthSettings;
|
|
62
|
+
header: HeaderConfig;
|
|
63
|
+
updateCheck: UpdateCheckSettings;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Pick the first string value found under the camelCase or snake_case key.
|
|
67
|
+
// Falls through to the snake_case key only when the camelCase value is absent or not a string.
|
|
68
|
+
function pickStr(src: Record<string, unknown>, camel: string, snake: string): string | undefined {
|
|
69
|
+
const camelVal = src[camel];
|
|
70
|
+
if (typeof camelVal === "string") return camelVal;
|
|
71
|
+
const snakeVal = src[snake];
|
|
72
|
+
if (typeof snakeVal === "string") return snakeVal;
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Same as pickStr but for boolean values.
|
|
77
|
+
function pickBool(src: Record<string, unknown>, camel: string, snake: string): boolean | undefined {
|
|
78
|
+
const camelVal = src[camel];
|
|
79
|
+
if (typeof camelVal === "boolean") return camelVal;
|
|
80
|
+
const snakeVal = src[snake];
|
|
81
|
+
if (typeof snakeVal === "boolean") return snakeVal;
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeAuthPreference(value: unknown): AuthPreference {
|
|
86
|
+
return typeof value === "string" && AUTH_PREFERENCES.some((item) => item.id === value)
|
|
87
|
+
? (value as AuthPreference)
|
|
88
|
+
: DEFAULT_AUTH_PREFERENCE;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalizeUiSettings(input: Partial<UiSettings> | null | undefined): UiSettings {
|
|
92
|
+
return {
|
|
93
|
+
layoutStyle: input?.layoutStyle ?? DEFAULT_LAYOUT_STYLE,
|
|
94
|
+
theme: input?.theme ?? DEFAULT_THEME,
|
|
95
|
+
workspaceDisplayMode: input?.workspaceDisplayMode ?? DEFAULT_WORKSPACE_DISPLAY_MODE,
|
|
96
|
+
terminalTitleMode: input?.terminalTitleMode ?? DEFAULT_TERMINAL_TITLE_MODE,
|
|
97
|
+
showBusyLoader: typeof input?.showBusyLoader === "boolean"
|
|
98
|
+
? input.showBusyLoader
|
|
99
|
+
: DEFAULT_SHOW_BUSY_LOADER,
|
|
100
|
+
customTheme: input?.customTheme,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function normalizeHeaderConfig(input: Partial<HeaderConfig> | null | undefined): HeaderConfig {
|
|
105
|
+
return {
|
|
106
|
+
showBrand: typeof input?.showBrand === "boolean" ? input.showBrand : HEADER_CONFIG_DEFAULTS.showBrand,
|
|
107
|
+
showWorkspace: typeof input?.showWorkspace === "boolean" ? input.showWorkspace : HEADER_CONFIG_DEFAULTS.showWorkspace,
|
|
108
|
+
showProvider: typeof input?.showProvider === "boolean" ? input.showProvider : HEADER_CONFIG_DEFAULTS.showProvider,
|
|
109
|
+
showModel: typeof input?.showModel === "boolean" ? input.showModel : HEADER_CONFIG_DEFAULTS.showModel,
|
|
110
|
+
showReasoning: typeof input?.showReasoning === "boolean" ? input.showReasoning : HEADER_CONFIG_DEFAULTS.showReasoning,
|
|
111
|
+
showContext: typeof input?.showContext === "boolean" ? input.showContext : HEADER_CONFIG_DEFAULTS.showContext,
|
|
112
|
+
showAuthStatus: typeof input?.showAuthStatus === "boolean" ? input.showAuthStatus : HEADER_CONFIG_DEFAULTS.showAuthStatus,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function normalizeUpdateCheckSettings(
|
|
117
|
+
input: Partial<UpdateCheckSettings> | null | undefined,
|
|
118
|
+
): UpdateCheckSettings {
|
|
119
|
+
return {
|
|
120
|
+
enabled: typeof input?.enabled === "boolean" ? input.enabled : DEFAULT_UPDATE_CHECK_SETTINGS.enabled,
|
|
121
|
+
intervalHours: typeof input?.intervalHours === "number" && input.intervalHours > 0
|
|
122
|
+
? input.intervalHours
|
|
123
|
+
: DEFAULT_UPDATE_CHECK_SETTINGS.intervalHours,
|
|
124
|
+
skippedUpdateVersion: input?.skippedUpdateVersion ?? null,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function getDefaultSettings(): AppSettings {
|
|
129
|
+
return {
|
|
130
|
+
ui: normalizeUiSettings(null),
|
|
131
|
+
auth: {
|
|
132
|
+
preference: DEFAULT_AUTH_PREFERENCE,
|
|
133
|
+
},
|
|
134
|
+
header: normalizeHeaderConfig(null),
|
|
135
|
+
updateCheck: normalizeUpdateCheckSettings(null),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function parseLegacyRuntime(data: Record<string, unknown>): RuntimeConfig {
|
|
140
|
+
return normalizeRuntimeConfig({
|
|
141
|
+
provider: typeof data.backend === "string" ? data.backend as RuntimeConfig["provider"] : undefined,
|
|
142
|
+
model: typeof data.model === "string" ? data.model as RuntimeConfig["model"] : undefined,
|
|
143
|
+
mode: typeof data.mode === "string" ? data.mode as RuntimeConfig["mode"] : undefined,
|
|
144
|
+
reasoningLevel: typeof data.reasoning_level === "string" ? data.reasoning_level as RuntimeConfig["reasoningLevel"] : undefined,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function extractLegacyRuntime(data: unknown): RuntimeConfig | null {
|
|
149
|
+
if (!data || typeof data !== "object") {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const record = data as Record<string, unknown>;
|
|
154
|
+
if (typeof record.runtime === "object" && record.runtime !== null) {
|
|
155
|
+
return normalizeRuntimeConfig(record.runtime as Partial<RuntimeConfig>);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const hasFlatRuntimeKeys = ["backend", "model", "mode", "reasoning_level"].some((key) => key in record);
|
|
159
|
+
return hasFlatRuntimeKeys ? parseLegacyRuntime(record) : null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function stripLegacyRuntime(data: unknown): Record<string, unknown> {
|
|
163
|
+
if (!data || typeof data !== "object") {
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const record = { ...(data as Record<string, unknown>) };
|
|
168
|
+
delete record.runtime;
|
|
169
|
+
delete record.backend;
|
|
170
|
+
delete record.model;
|
|
171
|
+
delete record.mode;
|
|
172
|
+
delete record.reasoning_level;
|
|
173
|
+
return record;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function parseTerminalTitleMode(uiSource: Record<string, unknown>, fallback: TerminalTitleMode): TerminalTitleMode {
|
|
177
|
+
const direct = uiSource.terminalTitleMode ?? uiSource.terminal_title_mode;
|
|
178
|
+
if (typeof direct === "string" && WORKSPACE_DISPLAY_MODES.includes(direct as WorkspaceDisplayMode)) {
|
|
179
|
+
return direct as TerminalTitleMode;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return fallback;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function parseWorkspaceDisplayMode(uiSource: Record<string, unknown>, fallback: WorkspaceDisplayMode): WorkspaceDisplayMode {
|
|
186
|
+
const direct = uiSource.workspaceDisplayMode ?? uiSource.workspace_display_mode;
|
|
187
|
+
if (typeof direct === "string" && WORKSPACE_DISPLAY_MODES.includes(direct as WorkspaceDisplayMode)) {
|
|
188
|
+
return direct as WorkspaceDisplayMode;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const legacy = uiSource.directoryDisplayMode ?? uiSource.directory_display_mode;
|
|
192
|
+
if (typeof legacy === "string") {
|
|
193
|
+
if (WORKSPACE_DISPLAY_MODES.includes(legacy as WorkspaceDisplayMode)) {
|
|
194
|
+
return legacy as WorkspaceDisplayMode;
|
|
195
|
+
}
|
|
196
|
+
if (LEGACY_DIRECTORY_DISPLAY_MODES.includes(legacy as typeof LEGACY_DIRECTORY_DISPLAY_MODES[number])) {
|
|
197
|
+
return normalizeLegacyDirectoryDisplayMode(legacy as typeof LEGACY_DIRECTORY_DISPLAY_MODES[number]);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return fallback;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function parseSettingsData(data: unknown): AppSettings {
|
|
205
|
+
const defaults = getDefaultSettings();
|
|
206
|
+
if (!data || typeof data !== "object") {
|
|
207
|
+
return defaults;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const record = data as Record<string, unknown>;
|
|
211
|
+
const uiSource = typeof record.ui === "object" && record.ui !== null
|
|
212
|
+
? record.ui as Record<string, unknown>
|
|
213
|
+
: record;
|
|
214
|
+
const authSource = typeof record.auth === "object" && record.auth !== null
|
|
215
|
+
? record.auth as Record<string, unknown>
|
|
216
|
+
: record;
|
|
217
|
+
|
|
218
|
+
const headerSource = typeof record.header === "object" && record.header !== null
|
|
219
|
+
? record.header as Record<string, unknown>
|
|
220
|
+
: {};
|
|
221
|
+
|
|
222
|
+
const updateCheckSource = typeof record.updateCheck === "object" && record.updateCheck !== null
|
|
223
|
+
? record.updateCheck as Record<string, unknown>
|
|
224
|
+
: typeof record.update_check === "object" && record.update_check !== null
|
|
225
|
+
? record.update_check as Record<string, unknown>
|
|
226
|
+
: {};
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
ui: normalizeUiSettings({
|
|
230
|
+
layoutStyle: pickStr(uiSource, "layoutStyle", "layout_style") ?? defaults.ui.layoutStyle,
|
|
231
|
+
theme: pickStr(uiSource, "theme", "theme") ?? defaults.ui.theme,
|
|
232
|
+
workspaceDisplayMode: parseWorkspaceDisplayMode(uiSource, defaults.ui.workspaceDisplayMode),
|
|
233
|
+
terminalTitleMode: parseTerminalTitleMode(uiSource, defaults.ui.terminalTitleMode),
|
|
234
|
+
showBusyLoader: pickBool(uiSource, "showBusyLoader", "show_busy_loader") ?? defaults.ui.showBusyLoader,
|
|
235
|
+
customTheme: (uiSource.customTheme ?? uiSource.custom_theme) as Partial<Theme> | undefined,
|
|
236
|
+
}),
|
|
237
|
+
auth: {
|
|
238
|
+
preference: normalizeAuthPreference(authSource.preference ?? authSource.auth_preference),
|
|
239
|
+
},
|
|
240
|
+
header: normalizeHeaderConfig({
|
|
241
|
+
showBrand: pickBool(headerSource, "showBrand", "show_brand"),
|
|
242
|
+
showWorkspace: pickBool(headerSource, "showWorkspace", "show_workspace"),
|
|
243
|
+
showProvider: pickBool(headerSource, "showProvider", "show_provider"),
|
|
244
|
+
showModel: pickBool(headerSource, "showModel", "show_model"),
|
|
245
|
+
showReasoning: pickBool(headerSource, "showReasoning", "show_reasoning"),
|
|
246
|
+
showContext: pickBool(headerSource, "showContext", "show_context"),
|
|
247
|
+
showAuthStatus: pickBool(headerSource, "showAuthStatus", "show_auth_status"),
|
|
248
|
+
}),
|
|
249
|
+
updateCheck: normalizeUpdateCheckSettings({
|
|
250
|
+
enabled: pickBool(updateCheckSource, "enabled", "enabled"),
|
|
251
|
+
intervalHours: typeof updateCheckSource.intervalHours === "number"
|
|
252
|
+
? updateCheckSource.intervalHours
|
|
253
|
+
: typeof updateCheckSource.interval_hours === "number"
|
|
254
|
+
? updateCheckSource.interval_hours
|
|
255
|
+
: undefined,
|
|
256
|
+
skippedUpdateVersion: typeof updateCheckSource.skippedUpdateVersion === "string"
|
|
257
|
+
? updateCheckSource.skippedUpdateVersion
|
|
258
|
+
: typeof updateCheckSource.skipped_update_version === "string"
|
|
259
|
+
? updateCheckSource.skipped_update_version
|
|
260
|
+
: null,
|
|
261
|
+
}),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function serializeSettings(settings: AppSettings): Record<string, unknown> {
|
|
266
|
+
return {
|
|
267
|
+
ui: {
|
|
268
|
+
layout_style: settings.ui.layoutStyle,
|
|
269
|
+
theme: settings.ui.theme,
|
|
270
|
+
workspace_display_mode: settings.ui.workspaceDisplayMode,
|
|
271
|
+
terminal_title_mode: settings.ui.terminalTitleMode,
|
|
272
|
+
show_busy_loader: settings.ui.showBusyLoader,
|
|
273
|
+
custom_theme: settings.ui.customTheme,
|
|
274
|
+
},
|
|
275
|
+
auth: {
|
|
276
|
+
preference: settings.auth.preference,
|
|
277
|
+
},
|
|
278
|
+
header: {
|
|
279
|
+
show_brand: settings.header.showBrand,
|
|
280
|
+
show_workspace: settings.header.showWorkspace,
|
|
281
|
+
show_provider: settings.header.showProvider,
|
|
282
|
+
show_model: settings.header.showModel,
|
|
283
|
+
show_reasoning: settings.header.showReasoning,
|
|
284
|
+
show_context: settings.header.showContext,
|
|
285
|
+
show_auth_status: settings.header.showAuthStatus,
|
|
286
|
+
},
|
|
287
|
+
update_check: {
|
|
288
|
+
enabled: settings.updateCheck.enabled,
|
|
289
|
+
interval_hours: settings.updateCheck.intervalHours,
|
|
290
|
+
...(settings.updateCheck.skippedUpdateVersion != null
|
|
291
|
+
? { skipped_update_version: settings.updateCheck.skippedUpdateVersion }
|
|
292
|
+
: {}),
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function writeJsonFile(filePath: string, data: Record<string, unknown>): void {
|
|
298
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
299
|
+
const tmpFile = `${filePath}.tmp`;
|
|
300
|
+
writeFileSync(tmpFile, JSON.stringify(data, null, 2), "utf-8");
|
|
301
|
+
renameSync(tmpFile, filePath);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function maybeMigrateLegacyRuntime(rawData: unknown): void {
|
|
305
|
+
const legacyRuntime = extractLegacyRuntime(rawData);
|
|
306
|
+
if (!legacyRuntime) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const codexConfigFile = getCodexConfigFile();
|
|
312
|
+
const existingConfig = existsSync(codexConfigFile)
|
|
313
|
+
? parseTomlDocument(readFileSync(codexConfigFile, "utf-8"))
|
|
314
|
+
: {};
|
|
315
|
+
const mergedConfig = mergeRuntimeIntoTomlConfig(existingConfig, legacyRuntime);
|
|
316
|
+
mkdirSync(dirname(codexConfigFile), { recursive: true });
|
|
317
|
+
const tmpTomlFile = `${codexConfigFile}.tmp`;
|
|
318
|
+
writeFileSync(tmpTomlFile, serializeTomlDocument(mergedConfig), "utf-8");
|
|
319
|
+
renameSync(tmpTomlFile, codexConfigFile);
|
|
320
|
+
writeJsonFile(SETTINGS_FILE, stripLegacyRuntime(rawData));
|
|
321
|
+
} catch {
|
|
322
|
+
// Best-effort migration only; keep legacy JSON intact if anything fails.
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function loadSettings(): AppSettings {
|
|
327
|
+
try {
|
|
328
|
+
let text = "";
|
|
329
|
+
if (existsSync(SETTINGS_FILE)) {
|
|
330
|
+
text = readFileSync(SETTINGS_FILE, "utf-8");
|
|
331
|
+
} else if (existsSync(LEGACY_SETTINGS_FILE)) {
|
|
332
|
+
text = readFileSync(LEGACY_SETTINGS_FILE, "utf-8");
|
|
333
|
+
try {
|
|
334
|
+
writeJsonFile(SETTINGS_FILE, JSON.parse(text));
|
|
335
|
+
} catch {
|
|
336
|
+
// Best-effort migration only
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
return getDefaultSettings();
|
|
340
|
+
}
|
|
341
|
+
const rawData = JSON.parse(text);
|
|
342
|
+
maybeMigrateLegacyRuntime(rawData);
|
|
343
|
+
return parseSettingsData(rawData);
|
|
344
|
+
} catch {
|
|
345
|
+
return getDefaultSettings();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function saveSettings(settings: AppSettings): void {
|
|
350
|
+
try {
|
|
351
|
+
writeJsonFile(SETTINGS_FILE, serializeSettings(settings));
|
|
352
|
+
} catch {
|
|
353
|
+
// Silently ignore — settings are best-effort
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** Persist the interactive execution choice without replacing unrelated Codex config. */
|
|
358
|
+
export function saveRuntimeModePreference(mode: AvailableMode, planMode: boolean): void {
|
|
359
|
+
try {
|
|
360
|
+
const codexConfigFile = getCodexConfigFile();
|
|
361
|
+
const current = existsSync(codexConfigFile)
|
|
362
|
+
? parseTomlDocument(readFileSync(codexConfigFile, "utf-8"))
|
|
363
|
+
: {};
|
|
364
|
+
const useCodexa = !current.ubume && current.codexa && typeof current.codexa === "object" && !Array.isArray(current.codexa);
|
|
365
|
+
const tableKey = useCodexa ? "codexa" : "ubume";
|
|
366
|
+
const existing = (current[tableKey] && typeof current[tableKey] === "object" && !Array.isArray(current[tableKey]))
|
|
367
|
+
? { ...(current[tableKey] as Record<string, unknown>) }
|
|
368
|
+
: {};
|
|
369
|
+
current[tableKey] = { ...existing, mode, plan_mode: planMode };
|
|
370
|
+
mkdirSync(dirname(codexConfigFile), { recursive: true });
|
|
371
|
+
const tmpFile = `${codexConfigFile}.tmp`;
|
|
372
|
+
writeFileSync(tmpFile, serializeTomlDocument(current), "utf-8");
|
|
373
|
+
renameSync(tmpFile, codexConfigFile);
|
|
374
|
+
} catch {
|
|
375
|
+
// Runtime preference persistence is best-effort and must not crash the TUI.
|
|
376
|
+
}
|
|
377
|
+
}
|