Skip to content

Commit 1218fa7

Browse files
committed
feat: expose editable NeoCLI TOML config
1 parent 2169c42 commit 1218fa7

6 files changed

Lines changed: 55 additions & 27 deletions

File tree

INSTALL-WINDOWS.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
Install `NeoCLI-Setup-<version>-x64.exe`. The setup wizard requires an API key and places the compiled NeoCLI application in the selected application folder. The installed executable embeds the Bun runtime, so Bun, Node.js, npm, Python, and other development runtimes are not required on the target machine.
44

5-
The installer stores the API key for the current Windows account at `%APPDATA%\NeoCLI\installer.json`. This file is intentionally outside the installation directory so an upgrade or uninstall does not delete the key. Do not share this file.
5+
The installer stores the API key and model settings for the current Windows account at `~\.NeoCLI\config.toml`. This file is intentionally outside the installation directory so an upgrade or uninstall does not delete the settings. Edit it to change `api_key`, `base_url`, or model names; do not share it.
66

77
NeoCLI starts in dangerous mode after a new installation. It skips permission prompts; the lower-left status area renders a red warning and permanently shows `Shift+Tab` as the mode-switch shortcut. Press `Shift+Tab` to switch back to a confirmation-based mode.
88

99
At launch, `ANTHROPIC_AUTH_TOKEN` and `ANTHROPIC_API_KEY` take precedence over the installer value. This supports CI and managed deployments without changing the installer configuration.
1010

11+
The Windows installer configures `deepseek-v4-pro` as the main, Opus, and Sonnet model, and `deepseek-v4-flash` as the Haiku and subagent model.
12+
1113
To build the installer from source on Windows, install Bun 1.3.11 and Inno Setup 6, then run:
1214

1315
```bash

installer/NeoCLI.iss

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ var
3737
ApiKeyPage: TInputQueryWizardPage;
3838
function GetInstallerConfigPath(): String;
3939
begin
40-
Result := ExpandConstant('{userappdata}\NeoCLI\installer.json');
40+
Result := ExpandConstant('{userprofile}\.NeoCLI\config.toml');
4141
end;
4242
function JsonEscape(Value: String): String;
4343
begin
@@ -52,6 +52,10 @@ begin
5252
ApiKeyPage := CreateInputQueryPage(wpSelectDir, 'API key', 'Configure NeoCLI', 'Enter the API key used by NeoCLI. It is stored only in your Windows user profile and can be replaced by setting ANTHROPIC_AUTH_TOKEN before launch.');
5353
ApiKeyPage.Add('API key:', True);
5454
end;
55+
function ShouldSkipPage(PageID: Integer): Boolean;
56+
begin
57+
Result := (PageID = ApiKeyPage.ID) and FileExists(GetInstallerConfigPath());
58+
end;
5559
function NextButtonClick(CurPageID: Integer): Boolean;
5660
begin
5761
Result := True;
@@ -62,14 +66,24 @@ begin
6266
end;
6367
procedure CurStepChanged(CurStep: TSetupStep);
6468
var
65-
ConfigPath, ConfigDir, Json: String;
69+
ConfigPath, ConfigDir, Toml: String;
6670
begin
6771
if CurStep = ssPostInstall then begin
6872
ConfigPath := GetInstallerConfigPath();
6973
ConfigDir := ExtractFileDir(ConfigPath);
7074
ForceDirectories(ConfigDir);
71-
Json := '{' + #13#10 + ' "apiKey": "' + JsonEscape(ApiKeyPage.Values[0]) + '",' + #13#10 + ' "baseUrl": "https://api.deepseek.com/anthropic"' + #13#10 + '}' + #13#10;
72-
SaveStringToFile(ConfigPath, Json, False);
75+
if not FileExists(ConfigPath) then begin
76+
Toml := '# NeoCLI user configuration' + #13#10 +
77+
'# Edit this file to change the API endpoint or model names.' + #13#10 + #13#10 +
78+
'api_key = "' + JsonEscape(ApiKeyPage.Values[0]) + '"' + #13#10 +
79+
'base_url = "https://api.deepseek.com/anthropic"' + #13#10 +
80+
'model = "deepseek-v4-pro"' + #13#10 +
81+
'default_opus_model = "deepseek-v4-pro"' + #13#10 +
82+
'default_sonnet_model = "deepseek-v4-pro"' + #13#10 +
83+
'default_haiku_model = "deepseek-v4-flash"' + #13#10 +
84+
'subagent_model = "deepseek-v4-flash"' + #13#10;
85+
SaveStringToFile(ConfigPath, Toml, False);
86+
end;
7387
if not FileExists(ExpandConstant('{userappdata}\NeoCLI\settings.json')) then
7488
SaveStringToFile(ExpandConstant('{userappdata}\NeoCLI\settings.json'), '{' + #13#10 + ' "permissions": {' + #13#10 + ' "defaultMode": "bypassPermissions"' + #13#10 + ' }' + #13#10 + '}' + #13#10, False);
7589
end;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "NeoCLI",
3-
"version": "0.1.3",
3+
"version": "0.1.6",
44
"private": true,
55
"description": "NeoCLI — standalone AI coding agent, powered by DeepSeek.",
66
"type": "module",

src/bootstrap/installedConfig.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,43 @@ import { existsSync, readFileSync } from 'fs'
22
import { homedir } from 'os'
33
import { join } from 'path'
44

5-
type InstallerConfig = {
6-
apiKey?: unknown
7-
baseUrl?: unknown
5+
type UserConfig = {
6+
api_key?: unknown
7+
base_url?: unknown
88
model?: unknown
9+
default_opus_model?: unknown
10+
default_sonnet_model?: unknown
11+
default_haiku_model?: unknown
12+
subagent_model?: unknown
913
}
1014

11-
function getInstallerConfigPath(): string | undefined {
12-
if (process.env.NEOCLI_INSTALLER_CONFIG_PATH) {
13-
return process.env.NEOCLI_INSTALLER_CONFIG_PATH
14-
}
15-
if (process.platform !== 'win32') {
16-
return undefined
17-
}
18-
return join(process.env.APPDATA ?? join(homedir(), 'AppData', 'Roaming'), 'NeoCLI', 'installer.json')
15+
function getConfigPath(): string {
16+
return process.env.NEOCLI_CONFIG_PATH ?? join(
17+
process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.NeoCLI'),
18+
'config.toml',
19+
)
1920
}
2021

2122
function setIfMissing(name: string, value: unknown): void {
2223
if (process.env[name] || typeof value !== 'string' || !value.trim()) return
2324
process.env[name] = value.trim()
2425
}
2526

26-
/** Applies installer credentials before API modules inspect process.env. */
27+
/** Applies user-editable config.toml values before API modules inspect process.env. */
2728
export function loadInstalledConfig(): void {
28-
const configPath = getInstallerConfigPath()
29-
if (!configPath || !existsSync(configPath)) return
29+
const configPath = getConfigPath()
30+
if (!existsSync(configPath)) return
3031
try {
31-
const config = JSON.parse(readFileSync(configPath, 'utf8')) as InstallerConfig
32+
const config = Bun.TOML.parse(readFileSync(configPath, 'utf8')) as UserConfig
3233
if (!process.env.ANTHROPIC_AUTH_TOKEN && !process.env.ANTHROPIC_API_KEY) {
33-
setIfMissing('ANTHROPIC_AUTH_TOKEN', config.apiKey)
34+
setIfMissing('ANTHROPIC_AUTH_TOKEN', config.api_key)
3435
}
35-
setIfMissing('ANTHROPIC_BASE_URL', config.baseUrl)
36+
setIfMissing('ANTHROPIC_BASE_URL', config.base_url)
3637
setIfMissing('ANTHROPIC_MODEL', config.model)
38+
setIfMissing('ANTHROPIC_DEFAULT_OPUS_MODEL', config.default_opus_model)
39+
setIfMissing('ANTHROPIC_DEFAULT_SONNET_MODEL', config.default_sonnet_model)
40+
setIfMissing('ANTHROPIC_DEFAULT_HAIKU_MODEL', config.default_haiku_model)
41+
setIfMissing('CLAUDE_CODE_SUBAGENT_MODEL', config.subagent_model)
3742
} catch {
3843
// A damaged user-local installer config must not prevent the CLI starting.
3944
}

src/components/PromptInput/PromptInputFooterLeftSide.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -354,11 +354,12 @@ function ModeIndicator({
354354
? '计划模式'
355355
: currentMode === 'auto'
356356
? '自动模式'
357-
: permissionModeTitle(currentMode ?? 'default').toLowerCase();
358-
const modePart = currentMode && hasActiveMode && !getIsRemoteMode() ? <Text color={getModeColor(currentMode)} key="mode">
359-
{permissionModeSymbol(currentMode)}{' '}
357+
: '默认模式';
358+
const displayedPermissionMode = currentMode ?? 'default';
359+
const modePart = !getIsRemoteMode() ? <Text color={getModeColor(displayedPermissionMode)} key="mode">
360+
{permissionModeSymbol(displayedPermissionMode)}{' '}
360361
{permissionModeLabel}
361-
{(currentMode === 'bypassPermissions' || currentMode === 'dontAsk') && '(所有权限检查已跳过)'}
362+
{(displayedPermissionMode === 'bypassPermissions' || displayedPermissionMode === 'dontAsk') && '(所有权限检查已跳过)'}
362363
{shouldShowModeHint && <Text dimColor>
363364
{' 按 '}
364365
<KeyboardShortcutHint shortcut={modeCycleShortcut} action="切换模式" parens />

src/utils/permissions/getNextPermissionMode.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ export function getNextPermissionMode(
5353
return 'plan'
5454

5555
case 'plan':
56+
// NeoCLI's public Windows build exposes dangerous mode explicitly in the
57+
// local Shift+Tab cycle. This lets users return to it after selecting a
58+
// confirmation-based mode, without relying on internal feature gates.
59+
if (process.env.USER_TYPE !== 'ant') {
60+
return 'bypassPermissions'
61+
}
5662
if (toolPermissionContext.isBypassPermissionsModeAvailable) {
5763
return 'bypassPermissions'
5864
}

0 commit comments

Comments
 (0)