- 双文件系统 - config.json (基础配置) + settings.json (行为配置)
- 扁平化配置 - config.json 采用扁平结构,避免过度嵌套
- 统一对象 - 运行时合并为单一 BladeConfig 对象
- 无 CLI 命令 - 仅通过文件编辑和 REPL /config 命令
- 参考 Claude Code - 配置结构和使用方式保持一致
config.json (2层): 环境变量 > 命令行参数 > 项目配置 > 用户配置 > 默认配置
settings.json (3层): 命令行参数 > 项目本地配置 > 项目共享配置 > 用户配置 > 默认配置
项目根目录:
项目根目录/
├── .blade/
│ ├── config.json # 项目基础配置
│ ├── settings.json # 项目行为配置 (提交到 git)
│ ├── settings.local.json # 本地配置 (不提交,自动忽略)
│ ├── BLADE.md # 项目上下文说明
│ ├── commands/ # 自定义斜杠命令
│ └── agents/ # 子代理定义
│
├── .gitignore # 自动添加 .blade/settings.local.json
用户主目录:
用户主目录/
└── ~/.blade/
├── config.json # 用户基础配置
├── settings.json # 用户行为配置
├── BLADE.md # 全局上下文说明
├── commands/ # 全局自定义命令
└── agents/ # 全局子代理
# .gitignore (自动添加)
.blade/settings.local.json// src/config/types.ts
/**
* Blade 统一配置接口
* 合并了 config.json 和 settings.json 的所有配置项
*/
export interface BladeConfig {
// =====================================
// 基础配置 (来自 config.json)
// =====================================
// 认证
apiKey: string;
apiSecret?: string;
baseURL: string;
// 模型
model: string;
temperature: number;
maxContextTokens: number; // 上下文窗口大小
maxOutputTokens: number; // 输出 token 限制
stream: boolean;
topP: number;
topK: number;
// UI
theme: string;
language: string;
fontSize: number;
showStatusBar: boolean;
// 核心
debug: boolean;
autoUpdate: boolean;
workingDirectory: string;
// 日志
logLevel: 'debug' | 'info' | 'warn' | 'error';
logFormat: 'json' | 'text';
// MCP
mcpEnabled: boolean;
// =====================================
// 行为配置 (来自 settings.json)
// =====================================
// 权限
permissions: PermissionConfig;
// Hooks
hooks: HookConfig;
// 环境变量
env: Record<string, string>;
// 其他
disableAllHooks: boolean;
cleanupPeriodDays: number;
includeCoAuthoredBy: boolean;
apiKeyHelper?: string;
}
/**
* 权限配置
*/
export interface PermissionConfig {
allow: string[];
ask: string[];
deny: string[];
}
/**
* Hooks 配置
*/
export interface HookConfig {
PreToolUse?: Record<string, string>;
PostToolUse?: Record<string, string>;
}// src/config/defaults.ts
export const DEFAULT_CONFIG: BladeConfig = {
// 基础配置 (config.json)
apiKey: '',
baseURL: 'https://apis.iflow.cn/v1',
model: 'qwen3-coder-plus',
temperature: 0.0,
maxContextTokens: 128000,
maxOutputTokens: 32768,
stream: true,
topP: 0.9,
topK: 50,
theme: 'GitHub',
language: 'zh-CN',
fontSize: 14,
showStatusBar: true,
debug: false,
telemetry: true,
autoUpdate: true,
workingDirectory: process.cwd(),
logLevel: 'info',
logFormat: 'text',
mcpEnabled: false,
// 行为配置 (settings.json)
permissions: {
allow: [],
ask: [],
deny: [
'Read(./.env)',
'Read(./.env.*)',
],
},
hooks: {},
env: {},
disableAllHooks: false,
cleanupPeriodDays: 30,
includeCoAuthoredBy: true,
};~/.blade/config.json (用户级):
{
"apiKey": "$BLADE_API_KEY",
"baseURL": "https://apis.iflow.cn/v1",
"model": "qwen3-coder-plus",
"temperature": 0.0,
"maxContextTokens": 128000,
"maxOutputTokens": 32768,
"stream": true,
"topP": 0.9,
"topK": 50,
"theme": "GitHub",
"language": "zh-CN",
"fontSize": 14,
"showStatusBar": true,
"debug": false,
"autoUpdate": true,
"logLevel": "info",
"logFormat": "text",
"mcpEnabled": false
}.blade/config.json (项目级):
{
"model": "qwen-max",
"temperature": 0.2,
"theme": "dark",
"debug": true,
"mcpEnabled": true,
"workingDirectory": "."
}~/.blade/settings.json (用户级):
{
"permissions": {
"allow": [
"Bash(git status)",
"Bash(git diff)",
"Bash(npm run lint)",
"Read(~/.zshrc)"
],
"ask": [
"Bash(git push:*)",
"WebFetch(*)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Bash(rm -rf:*)",
"Bash(curl:*)"
]
},
"cleanupPeriodDays": 30,
"includeCoAuthoredBy": true
}.blade/settings.json (项目共享,提交到 git):
{
"permissions": {
"allow": [
"Bash(npm run:*)",
"Bash(npx:*)",
"Bash(pnpm:*)"
],
"deny": [
"Read(./.env.production)",
"Write(./dist/**)",
"Write(./build/**)"
]
},
"hooks": {
"PostToolUse": {
"Write": "npx prettier --write {file_path}",
"Edit": "npx prettier --write {file_path}"
}
},
"env": {
"NODE_ENV": "development",
"NPM_CONFIG_LOGLEVEL": "warn"
}
}.blade/settings.local.json (本地,不提交):
{
"permissions": {
"allow": [
"Bash(rm:*)"
]
},
"env": {
"BLADE_DEBUG": "1"
},
"disableAllHooks": false
}// src/config/ConfigManager.ts
import { promises as fs } from 'fs';
import os from 'os';
import path from 'path';
import { DEFAULT_CONFIG } from './defaults.js';
import { BladeConfig, PermissionConfig, HookConfig } from './types.js';
export class ConfigManager {
private config: BladeConfig | null = null;
private configLoaded = false;
/**
* 初始化配置系统
*/
async initialize(): Promise<BladeConfig> {
if (this.configLoaded && this.config) {
return this.config;
}
try {
// 1. 加载基础配置 (config.json)
const baseConfig = await this.loadConfigFiles();
// 2. 加载行为配置 (settings.json)
const settingsConfig = await this.loadSettingsFiles();
// 3. 合并为统一配置
this.config = {
...DEFAULT_CONFIG,
...baseConfig,
...settingsConfig,
};
// 4. 解析环境变量插值
this.resolveEnvInterpolation(this.config);
// 5. 确保 Git 忽略 settings.local.json
await this.ensureGitIgnore();
this.configLoaded = true;
if (this.config.debug) {
console.log('[ConfigManager] Configuration loaded successfully');
}
return this.config;
} catch (error) {
console.error('[ConfigManager] Failed to initialize:', error);
this.config = DEFAULT_CONFIG;
this.configLoaded = true;
return this.config;
}
}
/**
* 加载 config.json 文件 (2层优先级)
*/
private async loadConfigFiles(): Promise<Partial<BladeConfig>> {
const userConfigPath = path.join(os.homedir(), '.blade', 'config.json');
const projectConfigPath = path.join(process.cwd(), '.blade', 'config.json');
let config: Partial<BladeConfig> = {};
// 1. 用户配置
const userConfig = await this.loadJsonFile(userConfigPath);
if (userConfig) {
config = { ...config, ...userConfig };
}
// 2. 项目配置
const projectConfig = await this.loadJsonFile(projectConfigPath);
if (projectConfig) {
config = { ...config, ...projectConfig };
}
// 3. 应用环境变量
config = this.applyEnvToConfig(config);
return config;
}
/**
* 加载 settings.json 文件 (3层优先级)
*/
private async loadSettingsFiles(): Promise<Partial<BladeConfig>> {
const userSettingsPath = path.join(os.homedir(), '.blade', 'settings.json');
const projectSettingsPath = path.join(process.cwd(), '.blade', 'settings.json');
const localSettingsPath = path.join(process.cwd(), '.blade', 'settings.local.json');
let settings: Partial<BladeConfig> = {};
// 1. 用户配置
const userSettings = await this.loadJsonFile(userSettingsPath);
if (userSettings) {
settings = this.mergeSettings(settings, userSettings);
}
// 2. 项目共享配置
const projectSettings = await this.loadJsonFile(projectSettingsPath);
if (projectSettings) {
settings = this.mergeSettings(settings, projectSettings);
}
// 3. 项目本地配置
const localSettings = await this.loadJsonFile(localSettingsPath);
if (localSettings) {
settings = this.mergeSettings(settings, localSettings);
}
return settings;
}
/**
* 应用环境变量到 config
*/
private applyEnvToConfig(config: Partial<BladeConfig>): Partial<BladeConfig> {
const result = { ...config };
// 认证
if (process.env.BLADE_API_KEY) result.apiKey = process.env.BLADE_API_KEY;
if (process.env.BLADE_API_SECRET) result.apiSecret = process.env.BLADE_API_SECRET;
if (process.env.BLADE_BASE_URL) result.baseURL = process.env.BLADE_BASE_URL;
// 模型
if (process.env.BLADE_MODEL) result.model = process.env.BLADE_MODEL;
if (process.env.BLADE_TEMPERATURE) result.temperature = parseFloat(process.env.BLADE_TEMPERATURE);
if (process.env.BLADE_MAX_CONTEXT_TOKENS) result.maxContextTokens = parseInt(process.env.BLADE_MAX_CONTEXT_TOKENS);
if (process.env.BLADE_MAX_OUTPUT_TOKENS) result.maxOutputTokens = parseInt(process.env.BLADE_MAX_OUTPUT_TOKENS);
// UI
if (process.env.BLADE_THEME) result.theme = process.env.BLADE_THEME;
if (process.env.BLADE_LANGUAGE) result.language = process.env.BLADE_LANGUAGE;
// 核心
if (process.env.BLADE_DEBUG) {
result.debug = process.env.BLADE_DEBUG === '1' || process.env.BLADE_DEBUG === 'true';
}
if (process.env.BLADE_TELEMETRY) {
result.telemetry = process.env.BLADE_TELEMETRY === '1' || process.env.BLADE_TELEMETRY === 'true';
}
return result;
}
/**
* 合并 settings 配置 (数组追加,对象覆盖)
*/
private mergeSettings(
base: Partial<BladeConfig>,
override: Partial<BladeConfig>
): Partial<BladeConfig> {
const result = JSON.parse(JSON.stringify(base));
// 合并 permissions (数组追加去重)
if (override.permissions) {
result.permissions = result.permissions || { allow: [], ask: [], deny: [] };
if (override.permissions.allow) {
const combined = [...(result.permissions.allow || []), ...override.permissions.allow];
result.permissions.allow = Array.from(new Set(combined));
}
if (override.permissions.ask) {
const combined = [...(result.permissions.ask || []), ...override.permissions.ask];
result.permissions.ask = Array.from(new Set(combined));
}
if (override.permissions.deny) {
const combined = [...(result.permissions.deny || []), ...override.permissions.deny];
result.permissions.deny = Array.from(new Set(combined));
}
}
// 合并 hooks (对象覆盖)
if (override.hooks) {
result.hooks = { ...result.hooks, ...override.hooks };
}
// 合并 env (对象覆盖)
if (override.env) {
result.env = { ...result.env, ...override.env };
}
// 其他字段直接覆盖
if (override.disableAllHooks !== undefined) {
result.disableAllHooks = override.disableAllHooks;
}
if (override.cleanupPeriodDays !== undefined) {
result.cleanupPeriodDays = override.cleanupPeriodDays;
}
if (override.includeCoAuthoredBy !== undefined) {
result.includeCoAuthoredBy = override.includeCoAuthoredBy;
}
if (override.apiKeyHelper !== undefined) {
result.apiKeyHelper = override.apiKeyHelper;
}
return result;
}
/**
* 解析配置中的环境变量插值
* 支持 $VAR 和 ${VAR} 以及 ${VAR:-default}
*/
private resolveEnvInterpolation(config: BladeConfig): void {
const envPattern = /\$\{?([A-Z_][A-Z0-9_]*)(:-([^}]+))?\}?/g;
const resolve = (value: any): any => {
if (typeof value === 'string') {
return value.replace(envPattern, (match, varName, _, defaultValue) => {
return process.env[varName] || defaultValue || match;
});
}
return value;
};
// 只解析字符串字段
for (const [key, value] of Object.entries(config)) {
if (typeof value === 'string') {
(config as any)[key] = resolve(value);
}
}
}
/**
* 确保 .gitignore 包含 settings.local.json
*/
private async ensureGitIgnore(): Promise<void> {
const gitignorePath = path.join(process.cwd(), '.gitignore');
const pattern = '.blade/settings.local.json';
try {
let content = '';
if (await this.fileExists(gitignorePath)) {
content = await fs.readFile(gitignorePath, 'utf-8');
}
if (!content.includes(pattern)) {
const newContent = content.trim() + '\n\n# Blade local settings\n' + pattern + '\n';
await fs.writeFile(gitignorePath, newContent, 'utf-8');
if (this.config?.debug) {
console.log('[ConfigManager] Added .blade/settings.local.json to .gitignore');
}
}
} catch (error) {
// 忽略错误,不影响主流程
}
}
/**
* 加载 JSON 文件
*/
private async loadJsonFile(filePath: string): Promise<any> {
try {
if (await this.fileExists(filePath)) {
const content = await fs.readFile(filePath, 'utf-8');
return JSON.parse(content);
}
} catch (error) {
console.warn(`[ConfigManager] Failed to load ${filePath}:`, error);
}
return null;
}
/**
* 检查文件是否存在
*/
private async fileExists(filePath: string): Promise<boolean> {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
/**
* 获取配置
*/
getConfig(): BladeConfig {
if (!this.config) {
throw new Error('Config not initialized. Call initialize() first.');
}
return this.config;
}
/**
* 更新配置
*/
async updateConfig(updates: Partial<BladeConfig>): Promise<void> {
if (!this.config) {
throw new Error('Config not initialized');
}
this.config = {
...this.config,
...updates,
};
// 可选:保存到文件
// await this.saveConfig();
}
}// src/config/PermissionChecker.ts
import { PermissionConfig } from './types.js';
export class PermissionChecker {
constructor(private permissions: PermissionConfig) {}
/**
* 检查工具调用权限
* @returns 'allow' | 'ask' | 'deny'
*/
checkPermission(tool: string, args: Record<string, any>): 'allow' | 'ask' | 'deny' {
const rule = this.formatRule(tool, args);
// 1. 检查 deny 规则 (最高优先级)
if (this.matchesAny(rule, this.permissions.deny)) {
return 'deny';
}
// 2. 检查 allow 规则
if (this.matchesAny(rule, this.permissions.allow)) {
return 'allow';
}
// 3. 检查 ask 规则
if (this.matchesAny(rule, this.permissions.ask)) {
return 'ask';
}
// 4. 默认 ask
return 'ask';
}
/**
* 格式化工具调用为规则字符串
* 例如: Bash(npm run test) 或 Read(./.env)
*/
private formatRule(tool: string, args: Record<string, any>): string {
const mainArg = args.command || args.file_path || args.pattern || '';
return `${tool}(${mainArg})`;
}
/**
* 检查是否匹配任意规则
*/
private matchesAny(rule: string, patterns: string[]): boolean {
return patterns.some(pattern => this.matchPattern(rule, pattern));
}
/**
* 匹配单个规则
*/
private matchPattern(rule: string, pattern: string): boolean {
// 1. 精确匹配
if (rule === pattern) {
return true;
}
// 2. Bash 前缀匹配: Bash(git push:*) 匹配 Bash(git push origin main)
if (pattern.includes(':*')) {
const prefix = pattern.replace(':*', '');
return rule.startsWith(prefix);
}
// 3. 工具通配符: WebFetch(*) 匹配所有 WebFetch 调用
if (pattern.endsWith('(*)')) {
const toolName = pattern.slice(0, -3);
return rule.startsWith(toolName + '(');
}
// 4. Glob 模式匹配 (文件路径)
if (pattern.includes('*') || pattern.includes('**')) {
return this.globMatch(rule, pattern);
}
return false;
}
/**
* Glob 模式匹配
*/
private globMatch(str: string, pattern: string): boolean {
// 简单的 glob 匹配实现
// 支持 * 和 **
const regexPattern = pattern
.replace(/\./g, '\\.')
.replace(/\*\*/g, '.*')
.replace(/\*/g, '[^/]*');
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(str);
}
}// src/config/HookExecutor.ts
import { exec } from 'child_process';
import { promisify } from 'util';
import { HookConfig } from './types.js';
const execAsync = promisify(exec);
export class HookExecutor {
constructor(
private hooks: HookConfig,
private disableAllHooks: boolean = false
) {}
/**
* 执行 Hook
*/
async executeHook(
hookType: 'PreToolUse' | 'PostToolUse',
tool: string,
context: Record<string, any>
): Promise<void> {
// 如果禁用所有 hooks,直接返回
if (this.disableAllHooks) {
return;
}
const hooks = this.hooks[hookType];
if (!hooks) {
return;
}
const command = hooks[tool];
if (!command) {
return;
}
// 替换变量
const resolvedCommand = this.resolveVariables(command, context);
try {
await execAsync(resolvedCommand, {
cwd: process.cwd(),
timeout: 30000, // 30秒超时
});
} catch (error) {
console.warn(`[Hook] Failed to execute ${hookType} hook for ${tool}:`, error);
// Hook 失败不应中断主流程
}
}
/**
* 解析命令中的变量
* 支持: {file_path}, {command}, {pattern} 等
*/
private resolveVariables(command: string, context: Record<string, any>): string {
return command.replace(/\{(\w+)\}/g, (match, key) => {
return context[key] || match;
});
}
}// src/index.ts
import { ConfigManager } from './config/ConfigManager.js';
async function main() {
// 初始化配置管理器
const configManager = new ConfigManager();
await configManager.initialize();
// 获取统一配置
const config = configManager.getConfig();
console.log('API Key:', config.apiKey);
console.log('Model:', config.model);
console.log('Theme:', config.theme);
console.log('Permissions:', config.permissions);
}// src/agent/Agent.ts
import { BladeConfig } from '../config/types.js';
import { PermissionChecker } from '../config/PermissionChecker.js';
import { HookExecutor } from '../config/HookExecutor.js';
export class Agent {
private permissionChecker: PermissionChecker;
private hookExecutor: HookExecutor;
constructor(private config: BladeConfig) {
this.permissionChecker = new PermissionChecker(config.permissions);
this.hookExecutor = new HookExecutor(config.hooks, config.disableAllHooks);
}
async executeTool(tool: string, args: Record<string, any>): Promise<any> {
// 1. 检查权限
const permission = this.permissionChecker.checkPermission(tool, args);
if (permission === 'deny') {
throw new Error(`Permission denied for ${tool}`);
}
if (permission === 'ask') {
// 询问用户
const allowed = await this.askUser(`Allow ${tool}?`);
if (!allowed) {
throw new Error(`User denied ${tool}`);
}
}
// 2. 执行 PreToolUse Hook
await this.hookExecutor.executeHook('PreToolUse', tool, args);
// 3. 执行工具
const result = await this.toolRegistry.execute(tool, args);
// 4. 执行 PostToolUse Hook
await this.hookExecutor.executeHook('PostToolUse', tool, {
...args,
result,
});
return result;
}
}# 设置环境变量
export BLADE_API_KEY="sk-xxx"
export BLADE_BASE_URL="https://api.example.com"
export BLADE_MODEL="qwen-max"
export BLADE_THEME="dark"
export BLADE_DEBUG="1"
# 在配置文件中引用
# config.json
{
"apiKey": "$BLADE_API_KEY",
"baseURL": "${BLADE_BASE_URL:-https://apis.iflow.cn/v1}",
"modelName": "$BLADE_MODEL"
}| 环境变量 | 配置字段 | 说明 |
|---|---|---|
| BLADE_API_KEY | apiKey | API 密钥 |
| BLADE_API_SECRET | apiSecret | API 密钥 |
| BLADE_BASE_URL | baseURL | API 基础 URL |
| BLADE_MODEL | modelName | 模型名称 |
| BLADE_TEMPERATURE | temperature | 温度参数 |
| BLADE_MAX_CONTEXT_TOKENS | maxContextTokens | 上下文窗口大小 |
| BLADE_MAX_OUTPUT_TOKENS | maxOutputTokens | 输出 token 限制 |
| BLADE_THEME | theme | UI 主题 |
| BLADE_LANGUAGE | language | 界面语言 |
| BLADE_DEBUG | debug | 调试模式 |
| BLADE_TELEMETRY | telemetry | 遥测开关 |
目标: 实现双配置文件系统和统一配置对象
任务:
- 定义 BladeConfig 类型 (src/config/types.ts)
- 实现默认配置 (src/config/defaults.ts)
- 实现 ConfigManager 配置加载逻辑 (src/config/ConfigManager.ts)
- 实现环境变量插值功能
- 实现 Git 自动忽略配置
- 编写配置加载测试
交付物:
- ✅ 支持 config.json (2层) + settings.json (3层)
- ✅ 统一的 BladeConfig 对象
- ✅ 环境变量支持
$VAR 和 $ {VAR:-default} 语法 - ✅ 自动忽略 .blade/settings.local.json
目标: 实现完整的权限检查机制
任务:
- 实现 PermissionChecker (src/config/PermissionChecker.ts)
- 实现权限规则匹配算法 (精确/前缀/通配符/glob)
- 集成到工具执行流程
- 编写权限测试用例
- 添加权限配置文档
交付物:
- ✅ allow/ask/deny 三级权限控制
- ✅ 支持多种匹配模式
- ✅ 工具执行前自动权限检查
目标: 实现工具执行前后的自动化
任务:
- 实现 HookExecutor (src/config/HookExecutor.ts)
- 实现变量替换功能 ({file_path}, {command} 等)
- 集成到工具执行流程
- 编写 Hooks 测试用例
- 添加常用 Hooks 示例
交付物:
- ✅ PreToolUse / PostToolUse Hooks
- ✅ 命令变量替换
- ✅ Hook 示例库 (格式化、日志等)
目标: 完善环境管理和项目上下文
任务:
- 实现 env 配置注入到会话
- 实现 BLADE.md 上下文文件加载
- 支持分层上下文 (全局/项目)
- 实现 /memory 命令显示上下文
- 编写上下文测试
交付物:
- ✅ 会话环境变量注入
- ✅ BLADE.md 上下文系统
- ✅ 上下文查看命令
目标: 实现 REPL 中的配置管理
任务:
- 实现 /config 斜杠命令
- 使用 Ink 构建配置查看界面
- 显示配置来源和优先级
- 实现配置追踪功能 (/config trace)
- 添加配置验证功能
交付物:
- ✅ /config 交互式界面
- ✅ 配置来源追踪
- ✅ 配置验证和提示
| 配置项 | config.json | settings.json | 说明 |
|---|---|---|---|
| API Key | ✅ | ❌ | 认证凭证 |
| Base URL | ✅ | ❌ | API 端点 |
| 模型配置 | ✅ | ❌ | 模型名称、参数 |
| UI 配置 | ✅ | ❌ | 主题、语言、字体 |
| 核心配置 | ✅ | ❌ | debug、telemetry |
| 权限控制 | ❌ | ✅ | allow/ask/deny |
| Hooks | ❌ | ✅ | Pre/Post 工具执行 |
| 环境变量 | ❌ | ✅ | 会话级环境变量 |
| 配置层级 | 2层 | 3层 | 用户/项目 vs 用户/项目/本地 |
| 数据结构 | 扁平化 | 部分嵌套 | 简单直接 vs 分组管理 |
| Git 提交 | 可选 | 部分提交 | config.json 可选,settings.json 提交,settings.local.json 不提交 |
- 双配置文件 - config.json + settings.json 职责分离
- 扁平化 config - 避免过度嵌套,简单直接
- 统一运行时对象 - 内存中合并为 BladeConfig
- 无 CLI 命令 - 仅通过文件编辑和 REPL
- 3层 settings - 支持团队共享和本地覆盖
- 参考 Claude Code - 权限系统、Hooks、配置方式
- 企业配置层级 - 暂不考虑企业场景
- 版本字段 - 配置文件不需要版本号
- 复杂 CLI 命令 - 不提供 config set/get 等命令
- 向后兼容 - 全新设计,不考虑旧版本迁移
- 分离的配置对象 - 运行时统一为一个对象 Update Todos
- 完善权限系统的 glob 模式支持
- 添加更多 Hooks 示例
- 实现配置验证和错误提示
- 添加配置迁移工具
- 完善文档和示例
基于 Claude Code 设计理念,实现 config.json (基础配置) + settings.json (行为配置) 的双文件系统,运行时合并为统一的 BladeConfig 对象。
- 双文件分离 - config.json (扁平化) + settings.json (嵌套)
- 统一对象流转 - 内存中合并为单一 BladeConfig
- 3+2 层级 - config 两层,settings 三层(含 local)
- 权限系统 - allow/ask/deny 三级工具访问控制
- Hooks 系统 - Pre/Post 工具执行自动化
-
环境变量 - 支持插值 (
$VAR, $ {VAR:-default}) 和注入 - Git 友好 - 自动忽略 settings.local.json
Sprint 1 (Week 1): 配置基础架构
- 定义 BladeConfig 类型和默认配置
- 实现 ConfigManager 双配置加载
- 实现环境变量插值
- 自动配置 Git 忽略
Sprint 2 (Week 2): 权限系统
- 实现 PermissionChecker
- 支持多种匹配模式(精确/前缀/通配/glob)
- 集成到工具执行流程
Sprint 3 (Week 3): Hooks 系统
- 实现 HookExecutor
- 支持变量替换
- 集成到工具执行流程
Sprint 4 (Week 4): 环境和上下文
- 环境变量注入
- BLADE.md 上下文系统
- /memory 命令
Sprint 5 (Week 5): 交互式界面
- /config 斜杠命令
- Ink UI 配置界面
- 配置追踪和验证
src/config/types.ts- 类型定义src/config/defaults.ts- 默认配置src/config/ConfigManager.ts- 配置管理器src/config/PermissionChecker.ts- 权限检查器src/config/HookExecutor.ts- Hooks 执行器
- 编辑
~/.blade/config.json和.blade/config.json - 编辑
~/.blade/settings.json、.blade/settings.json和.blade/settings.local.json - REPL 中使用
/config查看和管理配置 - 无需 CLI 子命令