Add support for reading Claude Code settings from ~/.claude/settings.json
This commit is contained in:
+15
-2
@@ -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
@@ -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
@@ -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],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user