Add support for reading Claude Code settings from ~/.claude/settings.json
ci / check (macos-latest) (push) Canceled after 0s
ci / check (ubuntu-latest) (push) Canceled after 0s
ci / check (windows-latest) (push) Canceled after 0s

This commit is contained in:
asepharyana
2026-09-03 17:39:34 +07:00
parent 6452299a45
commit 3090e477e3
3 changed files with 61 additions and 7 deletions
+15 -2
View File
@@ -7,6 +7,7 @@ import { join } from 'node:path';
import { withFallback, type FallbackEvent } from './fallback';
import type { McpServerConfig } from './mcp';
import { parsePermissions, type PermissionConfig } from './permission';
import { readClaudeCodeSettings } from './providers';
import { isToolSetName, type ToolSetName } from './tools';
export type ProviderName = 'anthropic' | 'openai';
@@ -83,11 +84,23 @@ export async function loadConfig(): Promise<Config> {
const envProvider = process.env['SHIRO_PROVIDER'];
const provider = isProvider(envProvider) ? envProvider : isProvider(file.provider) ? file.provider : 'anthropic';
// When using the claude-code preset, read from ~/.claude/settings.json
const isClaudeCode = file.presetId === 'claude-code';
const claudeSettings = isClaudeCode ? await readClaudeCodeSettings() : {};
return {
provider,
model: process.env['SHIRO_MODEL'] ?? file.model ?? DEFAULT_MODEL[provider],
baseURL: process.env['SHIRO_BASE_URL'] ?? file.baseURL ?? DEFAULT_BASE_URL[provider],
apiKey: process.env['SHIRO_API_KEY'] ?? file.apiKey ?? process.env[ENV_KEY[provider]],
baseURL:
process.env['SHIRO_BASE_URL'] ??
file.baseURL ??
claudeSettings.baseURL ??
DEFAULT_BASE_URL[provider],
apiKey:
process.env['SHIRO_API_KEY'] ??
file.apiKey ??
claudeSettings.apiKey ??
process.env[ENV_KEY[provider]],
...(file.presetId ? { presetId: file.presetId } : {}),
...(file.maxRetries !== undefined ? { maxRetries: file.maxRetries } : {}),
...(file.agent ? { agent: file.agent } : {}),
+35 -1
View File
@@ -1,5 +1,31 @@
import { homedir } from 'node:os';
import { join } from 'node:path';
import type { ProviderName } from './config';
/**
* Reads ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY from ~/.claude/settings.json
* so the claude-code preset reuses the endpoint and key Claude Code already has
* configured, rather than asking the user to type them again.
*/
export async function readClaudeCodeSettings(): Promise<{ baseURL?: string; apiKey?: string }> {
try {
const settingsPath = join(homedir(), '.claude', 'settings.json');
const f = Bun.file(settingsPath);
if (!(await f.exists())) return {};
const parsed: unknown = await f.json();
if (!parsed || typeof parsed !== 'object') return {};
const env = (parsed as { env?: unknown }).env;
if (!env || typeof env !== 'object') return {};
const envObj = env as Record<string, unknown>;
return {
baseURL: typeof envObj['ANTHROPIC_BASE_URL'] === 'string' ? envObj['ANTHROPIC_BASE_URL'] : undefined,
apiKey: typeof envObj['ANTHROPIC_API_KEY'] === 'string' ? envObj['ANTHROPIC_API_KEY'] : undefined,
};
} catch {
return {};
}
}
export type ProviderPreset = {
id: string;
label: string;
@@ -22,7 +48,15 @@ export const PRESETS: ProviderPreset[] = [
baseURL: 'https://api.anthropic.com/v1',
envKey: 'ANTHROPIC_API_KEY',
keyHint: 'sk-ant-...',
fallbackModels: ['claude-sonnet-4-5', 'claude-opus-4-1', 'claude-haiku-4-5'],
fallbackModels: ['claude-sonnet-5', 'claude-opus-5', 'claude-haiku-4-5-20251001'],
},
{
id: 'claude-code',
label: 'Claude Code (via ~/.claude/settings.json)',
kind: 'anthropic',
baseURL: '', // Read from ~/.claude/settings.json
envKey: 'ANTHROPIC_API_KEY',
fallbackModels: ['claude-sonnet-5', 'claude-opus-5', 'claude-haiku-4-5-20251001'],
},
{
id: 'openai',
+11 -4
View File
@@ -4,7 +4,7 @@ import TextInput from 'ink-text-input';
import Spinner from 'ink-spinner';
import React, { useCallback, useState } from 'react';
import type { Config, ProviderName } from '../config';
import { fetchModels, PRESETS, type ProviderPreset } from '../providers';
import { fetchModels, PRESETS, readClaudeCodeSettings, type ProviderPreset } from '../providers';
export type OnboardResult = {
presetId: string;
@@ -65,9 +65,9 @@ export function Onboard({
);
const afterBaseUrl = useCallback(
(preset: ProviderPreset, baseURL: string) => {
(preset: ProviderPreset, baseURL: string, settingsKey?: string) => {
const fromEnv = preset.envKey ? process.env[preset.envKey] : undefined;
const key = preset.keyless ? 'local' : (fromEnv ?? '');
const key = preset.keyless ? 'local' : (settingsKey ?? fromEnv ?? '');
if (key) return void loadModels(preset, baseURL, key);
setDraft('');
setStep({ name: 'api-key', preset, baseURL });
@@ -77,9 +77,16 @@ export function Onboard({
const pickProvider = useCallback(
(preset: ProviderPreset) => {
// The claude-code preset has no fixed endpoint: read the base URL and key
// from ~/.claude/settings.json, falling back to asking either if the file
// is absent or lacks the variable.
if (preset.baseURL) return afterBaseUrl(preset, preset.baseURL);
void readClaudeCodeSettings().then(({ baseURL, apiKey }) =>
baseURL
? afterBaseUrl(preset, baseURL, apiKey)
: setStep({ name: 'base-url', preset }),
);
setDraft('');
setStep({ name: 'base-url', preset });
},
[afterBaseUrl],
);