Files

1 line
30 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "bob-bobo/llm-to-agent", "files": {"code/agent/bob_pet/src/renderer/settings/settings.js": {"679cfc2cf68761b4a9107082a259f1badec19097": {"t": 1785674642.657466, "n": 20904, "h": "c7d6ee48212ed2a4", "c": "let modelType = 'cloud';\nlet selectedPersona = 'neko';\nlet selectedTheme = 'blue';\nlet i18n = {};\nlet currentLang = 'zh-CN';\nlet modelProfiles = [];\nlet editingProfileId = null;\nlet editingPersonaName = null;\n\nconst BUILTIN_PERSONAS = ['neko', 'chatty_friend', 'smiling_sister', 'funny_bro', 'cautious_mentor'];\n\nconst CLOUD_PRESETS = {\n openai: { baseUrl: 'https://api.openai.com/v1', model: 'gpt-4o-mini', keyHint: 'sk-...' },\n deepseek: { baseUrl: 'https://api.deepseek.com/v1', model: 'deepseek-chat', keyHint: 'sk-...' },\n zhipu: { baseUrl: 'https://open.bigmodel.cn/api/paas/v4', model: 'glm-4-flash', keyHint: 'xxx.yyy' },\n moonshot: { baseUrl: 'https://api.moonshot.cn/v1', model: 'moonshot-v1-8k', keyHint: 'sk-...' },\n volcengine: { baseUrl: 'https://ark.cn-beijing.volces.com/api/v3', model: 'ep-xxxxxxxx', keyHint: 'ARK API Key' },\n volcengine_plan:{ baseUrl: 'https://ark.cn-beijing.volces.com/api/plan/v3', model: 'ep-xxxxxxxx', keyHint: 'ARK API Key' },\n aliyun: { baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-turbo', keyHint: 'sk-...' },\n yi: { baseUrl: 'https://api.lingyiwanwu.com/v1', model: 'yi-lightning', keyHint: 'sk-...' },\n minimax: { baseUrl: 'https://api.minimax.chat/v1', model: 'MiniMax-Text-01', keyHint: 'sk-...' }\n};\n\nconst THEMES = [\n { name: 'blue', labelKey: 'themeBlue', color: '#3b82f6' },\n { name: 'white', labelKey: 'themeWhite', color: '#6b7280' },\n { name: 'black', labelKey: 'themeBlack', color: '#262626' },\n { name: 'green', labelKey: 'themeGreen', color: '#22c55e' },\n { name: 'orange', labelKey: 'themeOrange', color: '#f97316' },\n { name: 'purple', labelKey: 'themePurple', color: '#a855f7' },\n { name: 'pink', labelKey: 'themePink', color: '#ec4899' }\n];\n\nfunction t(key) { return i18n[key] || key; }\n\nfunction applyI18n() {\n document.querySelectorAll('[data-i18n]').forEach((el) => {\n const key = el.dataset.i18n;\n if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') return;\n const firstText = el.childNodes[0];\n if (firstText && firstText.nodeType === 3) {\n firstText.textContent = t(key);\n } else if (!el.querySelector('input, textarea')) {\n el.textContent = t(key);\n }\n });\n const navTexts = ['tabModel', 'tabPersona', 'tabTheme', 'tabLanguage', 'tabShortcuts', 'tabGeneral'];\n document.querySelectorAll('.nav-item').forEach((btn, i) => {\n if (navTexts[i]) btn.textContent = t(navTexts[i]);\n });\n}\n\nasync function loadI18n() {\n const data = await window.bobpet.getI18n();\n i18n = data.strings;\n currentLang = data.lang;\n applyI18n();\n}\n\nasync function loadConfig() {\n const cfg = await window.bobpet.getConfig();\n modelType = cfg.model?.type || 'cloud';\n selectedPersona = cfg.persona || 'neko';\n selectedTheme = cfg.theme || 'blue';\n modelProfiles = cfg.modelProfiles || [];\n\n document.getElementById('local-url').value = cfg.model?.local?.baseUrl || 'http://127.0.0.1:11434';\n document.getElementById('local-model').value = cfg.model?.local?.model || 'llama3.2';\n document.getElementById('cloud-url').value = cfg.model?.cloud?.baseUrl || '';\n document.getElementById('cloud-key').value = cfg.model?.cloud?.apiKey || '';\n document.getElementById('cloud-model').value = cfg.model?.cloud?.model || '';\n document.getElementById('auto-start').checked = !!cfg.autoStart;\n document.getElementById('show-thinking').checked = !!cfg.showThinking;\n document.getElementById('deep-think').checked = !!cfg.deepThink;\n\n updateModelPanels();\n renderPersonas();\n renderThemes();\n renderLanguages();\n renderProfiles();\n loadMultiAgentConfig();\n}\n\nfunction updateModelPanels() {\n document.querySelectorAll('[data-model-type]').forEach((btn) => {\n btn.classList.toggle('active', btn.dataset.modelType === modelType);\n });\n document.getElementById('panel-local').classList.toggle('hidden', modelType !== 'local');\n document.getElementById('panel-cloud').classList.toggle('hidden', modelType !== 'cloud');\n}\n\n// --- Model Profiles ---\nfunction renderProfiles() {\n const el = document.getElementById('profile-list');\n el.innerHTML = '';\n if (modelProfiles.length === 0) {\n el.innerHTML = `<p class=\"hint\">${t('noProfiles') || '暂无保存的配置'}</p>`;\n return;\n }\n modelProfiles.forEach((profile) => {\n const card = document.createElement('div');\n card.className = 'profile-card';\n const typeLabel = profile.type === 'local' ? 'Ollama' : 'Cloud';\n const modelLabel = profile.type === 'local'\n ? (profile.local?.model || 'llama3.2')\n : (profile.cloud?.model || 'gpt-4o-mini');\n const roleLabel = profile.role ? ` · ${profile.role}` : '';\n card.innerHTML = `\n <div class=\"profile-info\">\n <strong>${profile.name}</strong>\n <span>${typeLabel} / ${modelLabel}${roleLabel}</span>\n </div>\n <div class=\"profile-actions\">\n <button type=\"button\" class=\"btn-sm btn-use\" title=\"${t('useProfile') || '使用'}\">✓</button>\n <button type=\"button\" class=\"btn-sm btn-edit\" title=\"${t('editProfile') || '编辑'}\">✎</button>\n <button type=\"button\" class=\"btn-sm btn-del\" title=\"${t('deleteProfile') || '删除'}\">×</button>\n </div>\n `;\n card.querySelector('.btn-use').addEventListener('click', async () => {\n await window.bobpet.saveConfig({\n model: {\n type: profile.type,\n local: { ...profile.local },\n cloud: { ...profile.cloud },\n _profileId: profile.id\n }\n });\n clearTestResult();\n await loadConfig();\n });\n card.querySelector('.btn-edit').addEventListener('click', () => openProfileModal(profile));\n card.querySelector('.btn-del').addEventListener('click', async () => {\n if (confirm(`${t('deleteProfile') || '删除'}: ${profile.name}?`)) {\n modelProfiles = modelProfiles.filter((p) => p.id !== profile.id);\n await window.bobpet.saveConfig({ modelProfiles });\n renderProfiles();\n }\n });\n el.appendChild(card);\n });\n}\n\nlet pfType = 'local';\nfunction openProfileModal(profile) {\n const modal = document.getElementById('profile-modal-overlay');\n modal.hidden = false;\n editingProfileId = profile?.id || null;\n document.getElementById('profile-modal-title').textContent =\n profile ? (t('editProfile') || '编辑配置') : (t('addProfile') || '添加配置');\n document.getElementById('pf-name').value = profile?.name || '';\n document.getElementById('pf-role').value = profile?.role || '';\n pfType = profile?.type || 'local';\n document.getElementById('pf-local-url').value = profile?.local?.baseUrl || 'http://127.0.0.1:11434';\n document.getElementById('pf-local-model').value = profile?.local?.model || 'llama3.2';\n document.getElementById('pf-cloud-url').value = profile?.cloud?.baseUrl || '';\n document.getElementById('pf-cloud-key').value = profile?.cloud?.apiKey || '';\n document.getElementById('pf-cloud-model').value = profile?.cloud?.model || 'gpt-4o-mini';\n updatePfPanels();\n}\n\nfunction updatePfPanels() {\n document.querySelectorAll('[data-pf-type]').forEach((btn) => {\n btn.classList.toggle('active', btn.dataset.pfType === pfType);\n });\n document.getElementById('pf-panel-local').classList.toggle('hidden', pfType !== 'local');\n document.getElementById('pf-panel-cloud').classList.toggle('hidden', pfType !== 'cloud');\n}\n\ndocument.querySelectorAll('[data-pf-type]').forEach((btn) => {\n btn.addEventListener('click', () => {\n pfType = btn.dataset.pfType;\n updatePfPanels();\n });\n});\n\ndocument.getElementById('btn-add-profile').addEventListener('click', () => openProfileModal(null));\n\ndocument.getElementById('btn-pf-cancel').addEventListener('click', () => {\n document.getElementById('profile-modal-overlay').hidden = true;\n});\n\ndocument.getElementById('btn-pf-save').addEventListener('click', async () => {\n const name = document.getElementById('pf-name').value.trim();\n if (!name) return;\n const profile = {\n id: editingProfileId || Date.now().toString(),\n name,\n role: document.getElementById('pf-role').value.trim(),\n type: pfType,\n local: {\n baseUrl: document.getElementById('pf-local-url').value.trim(),\n model: document.getElementById('pf-local-model').value.trim()\n },\n cloud: {\n baseUrl: document.getElementById('pf-cloud-url').value.trim(),\n apiKey: document.getElementById('pf-cloud-key').value,\n model: document.getElementById('pf-cloud-model').value.trim()\n }\n };\n if (editingProfileId) {\n modelProfiles = modelProfiles.map((p) => p.id === editingProfileId ? profile : p);\n } else {\n modelProfiles.push(profile);\n }\n await window.bobpet.saveConfig({ modelProfiles });\n document.getElementById('profile-modal-overlay').hidden = true;\n renderProfiles();\n});\n\n// --- Personas ---\nasync function renderPersonas() {\n const list = await window.bobpet.listPersonas();\n const el = document.getElementById('persona-list');\n el.innerHTML = '';\n list.forEach((p) => {\n const isBuiltin = BUILTIN_PERSONAS.includes(p.name);\n const card = document.createElement('div');\n card.className = 'persona-card' + (p.name === selectedPersona ? ' selected' : '');\n let html = `<h3>${p.name}</h3><p>${p.description || ''}</p>`;\n if (!isBuiltin) {\n html += `<div class=\"persona-actions\">`;\n html += `<button type=\"button\" class=\"btn-persona-edit\" title=\"${t('editPersona') || '编辑'}\">✎</button>`;\n html += `<button type=\"button\" class=\"btn-delete\" title=\"${t('deletePersona') || '删除'}\">&times;</button>`;\n html += `</div>`;\n }\n card.innerHTML = html;\n card.addEventListener('click', async (e) => {\n if (e.target.classList.contains('btn-delete')) {\n e.stopPropagation();\n if (confirm(`${t('deletePersona') || '删除'}: ${p.name}?`)) {\n await window.bobpet.deletePersona(p.name);\n if (selectedPersona === p.name) {\n selectedPersona = 'neko';\n await window.bobpet.saveConfig({ persona: 'neko' });\n }\n renderPersonas();\n }\n return;\n }\n if (e.target.classList.contains('btn-persona-edit')) {\n e.stopPropagation();\n await openPersonaEditModal(p.name);\n return;\n }\n selectedPersona = p.name;\n await window.bobpet.saveConfig({ persona: p.name });\n renderPersonas();\n });\n el.appendChild(card);\n });\n}\n\n// --- Themes ---\nfunction renderThemes() {\n const el = document.getElementById('theme-list');\n el.innerHTML = '';\n THEMES.forEach((th) => {\n const card = document.createElement('div');\n card.className = 'theme-card' + (th.name === selectedTheme ? ' selected' : '');\n card.innerHTML = `<div class=\"theme-swatch\" style=\"background:${th.color}\"></div><h3>${t(th.labelKey)}</h3>`;\n card.addEventListener('click', async () => {\n selectedTheme = th.name;\n await window.bobpet.saveConfig({ theme: th.name });\n renderThemes();\n });\n el.appendChild(card);\n });\n}\n\n// --- Languages ---\nasync function renderLanguages() {\n const langs = await window.bobpet.getLanguages();\n const el = document.getElementById('language-list');\n el.innerHTML = '';\n langs.forEach((l) => {\n const card = document.createElement('div');\n card.className = 'lang-card' + (l.code === currentLang ? ' selected' : '');\n card.innerHTML = `<h3>${l.label}</h3>`;\n card.addEventListener('click', async () => {\n currentLang = l.code;\n await window.bobpet.saveConfig({ language: l.code });\n await loadI18n();\n renderThemes();\n renderLanguages();\n renderPersonas();\n renderProfiles();\n });\n el.appendChild(card);\n });\n}\n\n// --- Tabs ---\ndocument.querySelectorAll('.nav-item').forEach((btn) => {\n btn.addEventListener('click', () => {\n document.querySelectorAll('.nav-item').forEach((b) => b.classList.remove('active'));\n document.querySelectorAll('.tab').forEach((t) => t.classList.remove('active'));\n btn.classList.add('active');\n document.getElementById(`tab-${btn.dataset.tab}`).classList.add('active');\n });\n});\n\n// --- Model type toggle ---\nfunction clearTestResult() {\n const el = document.getElementById('test-result');\n el.textContent = '';\n el.className = 'result';\n}\n\ndocument.querySelectorAll('[data-model-type]').forEach((btn) => {\n btn.addEventListener('click', () => {\n modelType = btn.dataset.modelType;\n updateModelPanels();\n clearTestResult();\n });\n});\n\n// Clear test result when any model config field changes\n['local-url', 'local-model', 'cloud-url', 'cloud-key', 'cloud-model'].forEach((id) => {\n document.getElementById(id).addEventListener('input', clearTestResult);\n});\n\n// --- Cloud preset provider ---\ndocument.getElementById('cloud-preset').addEventListener('change', (e) => {\n const preset = CLOUD_PRESETS[e.target.value];\n if (!preset) return;\n document.getElementById('cloud-url').value = preset.baseUrl;\n document.getElementById('cloud-model').value = preset.model;\n document.getElementById('cloud-key').placeholder = preset.keyHint;\n clearTestResult();\n});\n\n// --- Save model ---\ndocument.getElementById('btn-save-model').addEventListener('click', async () => {\n await window.bobpet.saveConfig({\n model: {\n type: modelType,\n local: {\n baseUrl: document.getElementById('local-url').value.trim(),\n model: document.getElementById('local-model').value.trim()\n },\n cloud: {\n baseUrl: document.getElementById('cloud-url').value.trim(),\n apiKey: document.getElementById('cloud-key').value,\n model: document.getElementById('cloud-model').value.trim()\n }\n }\n });\n alert(t('save') + ' ✓');\n});\n\n// --- Test connection ---\ndocument.getElementById('btn-test').addEventListener('click', async () => {\n await window.bobpet.saveConfig({\n model: {\n type: modelType,\n local: {\n baseUrl: document.getElementById('local-url').value.trim(),\n model: document.getElementById('local-model').value.trim()\n },\n cloud: {\n baseUrl: document.getElementById('cloud-url').value.trim(),\n apiKey: document.getElementById('cloud-key').value,\n model: document.getElementById('cloud-model').value.trim()\n }\n }\n });\n const res = await window.bobpet.testModel();\n const el = document.getElementById('test-result');\n el.textContent = res.message;\n el.className = 'result ' + (res.ok ? 'ok' : 'err');\n});\n\n// --- Save general ---\ndocument.getElementById('btn-save-general').addEventListener('click', async () => {\n await window.bobpet.saveConfig({\n autoStart: document.getElementById('auto-start').checked,\n showThinking: document.getElementById('show-thinking').checked,\n deepThink: document.getElementById('deep-think').checked\n });\n alert(t('save') + ' ✓');\n});\n\n// --- Ollama link ---\ndocument.getElementById('link-ollama').addEventListener('click', (e) => {\n e.preventDefault();\n window.bobpet.openExternal('https://ollama.com/download');\n});\n\n// --- Multi-Agent ---\nfunction renderMultiAgent() {\n const agentList = document.getElementById('ma-agent-list');\n const summarySelect = document.getElementById('ma-summary');\n const ma = (typeof window !== 'undefined' && window.__maConfig) || {};\n\n agentList.innerHTML = '';\n summarySelect.innerHTML = '<option value=\"\">-- 无总结 --</option>';\n\n if (modelProfiles.length === 0) {\n agentList.innerHTML = '<p class=\"hint\">请先在模型配置中添加配置</p>';\n return;\n }\n\n const selectedIds = ma.agentIds || [];\n modelProfiles.forEach((profile) => {\n const card = document.createElement('div');\n card.className = 'ma-agent-card' + (selectedIds.includes(profile.id) ? ' selected' : '');\n const typeLabel = profile.type === 'local' ? 'Ollama' : 'Cloud';\n const modelLabel = profile.type === 'local'\n ? (profile.local?.model || 'llama3.2')\n : (profile.cloud?.model || 'gpt-4o-mini');\n card.innerHTML = `\n <input type=\"checkbox\" class=\"ma-agent-check\" data-id=\"${profile.id}\" ${selectedIds.includes(profile.id) ? 'checked' : ''} />\n <div class=\"ma-agent-info\">\n <strong>${profile.name}</strong>\n <span>${typeLabel} / ${modelLabel}</span>\n </div>\n `;\n card.querySelector('.ma-agent-check').addEventListener('change', () => {\n card.classList.toggle('selected', card.querySelector('.ma-agent-check').checked);\n });\n agentList.appendChild(card);\n\n // Add to summary dropdown\n const opt = document.createElement('option');\n opt.value = profile.id;\n opt.textContent = profile.name;\n summarySelect.appendChild(opt);\n });\n\n if (ma.summaryId) {\n summarySelect.value = ma.summaryId;\n }\n}\n\nasync function loadMultiAgentConfig() {\n const cfg = await window.bobpet.getConfig();\n const ma = cfg.multiAgent || {};\n window.__maConfig = ma;\n document.getElementById('ma-enabled').checked = !!ma.enabled;\n document.getElementById('ma-mode').value = ma.mode || 'independent';\n document.getElementById('ma-rounds').value = ma.rounds || 1;\n document.getElementById('ma-summary-prompt').value = ma.summaryPrompt || '';\n renderMultiAgent();\n}\n\ndocument.getElementById('btn-save-ma').addEventListener('click', async () => {\n const agentIds = [];\n document.querySelectorAll('.ma-agent-check:checked').forEach((cb) => {\n agentIds.push(cb.dataset.id);\n });\n const summaryId = document.getElementById('ma-summary').value || null;\n await window.bobpet.saveConfig({\n multiAgent: {\n enabled: document.getElementById('ma-enabled').checked,\n agentIds,\n summaryId,\n mode: document.getElementById('ma-mode').value,\n rounds: parseInt(document.getElementById('ma-rounds').value, 10) || 1,\n summaryPrompt: document.getElementById('ma-summary-prompt').value.trim()\n }\n });\n alert(t('save') + ' ✓');\n});\n\nloadMultiAgentConfig();\n\n// --- Persona modal ---\nconst personaModal = document.getElementById('modal-overlay');\ndocument.getElementById('btn-add-persona').addEventListener('click', () => {\n editingPersonaName = null;\n personaModal.hidden = false;\n document.getElementById('modal-title').textContent = t('addPersona') || '添加人格';\n document.getElementById('m-name').value = '';\n document.getElementById('m-name').disabled = false;\n document.getElementById('m-desc').value = '';\n document.getElementById('m-greeting').value = '';\n document.getElementById('m-prompt').value = '';\n document.getElementById('m-keywords').value = '';\n});\n\ndocument.getElementById('btn-modal-cancel').addEventListener('click', () => {\n personaModal.hidden = true;\n editingPersonaName = null;\n document.getElementById('m-name').disabled = false;\n});\n\ndocument.getElementById('btn-modal-save').addEventListener('click', async () => {\n const name = document.getElementById('m-name').value.trim();\n if (!name) return;\n const desc = document.getElementById('m-desc').value.trim();\n const greeting = document.getElementById('m-greeting').value.trim();\n const prompt = document.getElementById('m-prompt').value.trim();\n const keywordsRaw = document.getElementById('m-keywords').value.trim();\n\n const keywords = {};\n if (keywordsRaw) {\n keywordsRaw.split('\\n').forEach((line) => {\n const eq = line.indexOf('=');\n if (eq > 0) {\n const kw = line.slice(0, eq).trim();\n const replies = line.slice(eq + 1).split('|').map((s) => s.trim()).filter(Boolean);\n if (kw && replies.length) keywords[kw] = replies;\n }\n });\n }\n\n await window.bobpet.savePersona({ name, description: desc, greeting, systemPrompt: prompt, keywords });\n if (!editingPersonaName) {\n selectedPersona = name;\n await window.bobpet.saveConfig({ persona: name });\n }\n personaModal.hidden = true;\n editingPersonaName = null;\n renderPersonas();\n});\n\nasync function openPersonaEditModal(name) {\n const persona = await window.bobpet.getPersona(name);\n if (!persona) return;\n editingPersonaName = name;\n personaModal.hidden = false;\n document.getElementById('modal-title').textContent = t('editPersona') || '编辑人格';\n document.getElementById('m-name').value = persona.name || name;\n document.getElementById('m-name').disabled = true;\n document.getElementById('m-desc').value = persona.description || '';\n document.getElementById('m-greeting').value = persona.dialogue_style?.greeting || '';\n document.getElementById('m-prompt').value = persona.dialogue_style?.system_prompt || '';\n const kw = persona.dialogue_style?.keywords || {};\n document.getElementById('m-keywords').value = Object.entries(kw)\n .map(([k, v]) => `${k}=${Array.isArray(v) ? v.join('|') : v}`)\n .join('\\n');\n}\n\n// --- Language change listener ---\nwindow.bobpet.onLanguageChange?.(async (data) => {\n i18n = data.strings;\n currentLang = data.lang;\n applyI18n();\n renderThemes();\n renderLanguages();\n renderProfiles();\n});\n\nloadI18n();\nloadConfig();\n"}}, "code/agent/claude/mini-claude/README.md": {"679cfc2cf68761b4a9107082a259f1badec19097": {"t": 1785674645.8334894, "n": 6848, "h": "ec564258ba70faaf", "c": "# Mini Claude Code\n\n> A minimal Claude Code clone — learn the agentic loop by doing\n\n基于 Claude Code 官方架构原理,提炼核心设计,构建的精简版 Agent CLI 工具。\n\n## 核心原理\n\n```\n用户输入 → 构建上下文 → LLM API 调用 → 解析工具调用 → 权限校验 → 执行工具 → 追加结果 → 循环/终止\n```\n\n这就是 **Agentic Loop(智能体循环)** 的全部。\n\n## 快速开始\n\n```bash\n# 安装依赖\nnpm install\n\n# 设置 API Key(Anthropic)\nexport ANTHROPIC_API_KEY=your-key\n\n# 交互模式\nnpm run dev\n\n# 管道模式\nnpm run dev -- -p \"list all TypeScript files in src/\"\n```\n\n## 🔥 多 Provider 支持\n\nMini Claude Code 支持 **Anthropic** 和所有 **OpenAI 兼容 API**(覆盖国内几乎所有厂商):\n\n### CLI 参数方式\n\n```bash\n# Anthropic Claude(默认)\nexport ANTHROPIC_API_KEY=sk-ant-xxx\nnpm run dev\n\n# DeepSeek\nexport DEEPSEEK_API_KEY=sk-xxx\nnpm run dev -- --provider deepseek --model deepseek-chat\n\n# 智谱 GLM\nexport GLM_API_KEY=xxx.xxx\nnpm run dev -- --provider glm --model glm-4-plus\n\n# Moonshot (Kimi)\nexport MOONSHOT_API_KEY=sk-xxx\nnpm run dev -- --provider moonshot --model moonshot-v1-8k\n\n# 通义千问 Qwen\nexport DASHSCOPE_API_KEY=sk-xxx\nnpm run dev -- --provider qwen --model qwen-plus\n\n# 百川\nexport BAICHUAN_API_KEY=sk-xxx\nnpm run dev -- --provider baichuan --model Baichuan4\n\n# 零一万物\nnpm run dev -- --provider yi --api-key sk-xxx --model yi-large\n\n# 硅基流动(聚合多模型)\nexport SILICONFLOW_API_KEY=sk-xxx\nnpm run dev -- --provider siliconflow --model Qwen/Qwen2.5-72B-Instruct\n\n# OpenAI\nexport OPENAI_API_KEY=sk-xxx\nnpm run dev -- --provider openai --model gpt-4o\n\n# 自定义 OpenAI 兼容 API\nnpm run dev -- --base-url https://your-api.com/v1 --api-key your-key --model your-model\n```\n\n### 环境变量方式\n\n```bash\n# 通用环境变量(优先级最高)\nexport LLM_PROVIDER=deepseek # 或 glm, moonshot, qwen, openai, anthropic\nexport LLM_BASE_URL=https://api.deepseek.com/v1\nexport LLM_API_KEY=sk-xxx\nexport LLM_MODEL=deepseek-chat\n\nnpm run dev\n```\n\n### 配置文件方式 `~/.mini-claude/settings.json`\n\n```json\n{\n \"model\": \"glm-4-plus\",\n \"provider\": {\n \"type\": \"openai\",\n \"baseUrl\": \"https://open.bigmodel.cn/api/paas/v4\",\n \"apiKey\": \"your-glm-api-key\",\n \"modelMap\": {\n \"claude-sonnet-4-6\": \"glm-4-plus\"\n }\n }\n}\n```\n\n### Provider 预设一览\n\n| 预设名 | 厂商 | Base URL | 环境变量 |\n|--------|------|----------|----------|\n| `anthropic` | Anthropic | https://api.anthropic.com | `ANTHROPIC_API_KEY` |\n| `openai` | OpenAI | https://api.openai.com/v1 | `OPENAI_API_KEY` |\n| `deepseek` | DeepSeek | https://api.deepseek.com/v1 | `DEEPSEEK_API_KEY` |\n| `glm` | 智谱AI | https://open.bigmodel.cn/api/paas/v4 | `GLM_API_KEY` |\n| `moonshot` | 月之暗面 | https://api.moonshot.cn/v1 | `MOONSHOT_API_KEY` |\n| `qwen` | 阿里通义 | https://dashscope.aliyuncs.com/compatible-mode/v1 | `DASHSCOPE_API_KEY` |\n| `baichuan` | 百川 | https://api.baichuan-ai.com/v1 | `BAICHUAN_API_KEY` |\n| `yi` | 零一万物 | https://api.lingyiwanwu.com/v1 | - |\n| `siliconflow` | 硅基流动 | https://api.siliconflow.cn/v1 | `SILICONFLOW_API_KEY` |\n\n### 模型映射\n\n如果内部代码中硬编码了 `claude-sonnet-4-6` 等模型名,可通过 `modelMap` 映射到实际模型:\n\n```json\n{\n \"provider\": {\n \"type\": \"openai\",\n \"baseUrl\": \"https://open.bigmodel.cn/api/paas/v4\",\n \"modelMap\": {\n \"claude-sonnet-4-6\": \"glm-4-plus\",\n \"claude-opus-4-6\": \"glm-4-long\"\n }\n }\n}\n```\n\n## 内置工具\n\n| 工具 | 用途 |\n|------|------|\n| **Read** | 读取文件(支持分段、目录列表) |\n| **Write** | 创建/覆盖文件 |\n| **Edit** | 精确字符串替换 |\n| **Bash** | 执行 shell 命令 |\n| **Glob** | 文件模式匹配搜索 |\n| **Grep** | 文件内容正则搜索 |\n\n## 斜杠命令\n\n| 命令 | 说明 |\n|------|------|\n| `/help` | 显示帮助 |\n| `/compact` | 压缩上下文 |\n| `/clear` | 清除历史 |\n| `/model` | 查看/切换模型 |\n| `/provider` | 查看 Provider 信息 |\n| `/config` | 查看配置 |\n| `/context` | 查看上下文使用量 |\n| `/quit` | 退出 |\n\n## 配置\n\n### 用户级配置 `~/.mini-claude/settings.json`\n\n```json\n{\n \"model\": \"claude-sonnet-4-6\",\n \"maxTokens\": 16384,\n \"maxIterations\": 50,\n \"provider\": {\n \"type\": \"anthropic\"\n },\n \"permissions\": {\n \"allow\": [\n { \"tool\": \"Read\", \"pattern\": \"*\" },\n { \"tool\": \"Glob\", pattern: \"*\" },\n { \"tool\": \"Grep\", \"pattern\": \"*\" }\n ],\n \"deny\": [\n { \"tool\": \"Bash\", \"pattern\": \"rm -rf *\" }\n ]\n }\n}\n```\n\n### 项目级配置 `.mini-claude/settings.json`\n\n项目配置覆盖用户配置。\n\n### CLAUDE.md 指令\n\n在项目根目录创建 `CLAUDE.md`,Mini Claude Code 会在每次会话开始时加载:\n\n```markdown\n# Project Instructions\n\n- Use TypeScript strict mode\n- Run `npm test` before committing\n- API handlers live in src/api/\n```\n\n## 项目结构\n\n```\nmini-claude/\n├── src/\n│ ├── index.ts # CLI 入口\n│ ├── types.ts # 核心类型定义(含 ProviderConfig)\n│ ├── agent/\n│ │ ├── loop.ts # Agent Loop 核心循环\n│ │ └── context.ts # 上下文管理器\n│ ├── api/\n│ │ ├── provider.ts # 🔥 LLM Provider 抽象接口\n│ │ ├── factory.ts # Provider 工厂 + 预设\n│ │ ├── anthropic-provider.ts # Anthropic Provider\n│ │ ├── openai-provider.ts # OpenAI-compatible Provider\n│ │ ├── client.ts # 兼容层\n│ │ └── tokens.ts # Token 估算\n│ ├── tools/\n│ │ ├── registry.ts # 工具注册中心\n│ │ ├── dispatcher.ts # 工具调度器\n│ │ ├── read.ts / write.ts / edit.ts / bash.ts / glob.ts / grep.ts\n│ ├── permissions/\n│ │ └── checker.ts # 权限校验器\n│ ├── memory/\n│ │ └── loader.ts # CLAUDE.md 加载器\n│ ├── hooks/\n│ │ └── runner.ts # 钩子运行器\n│ ├── config/\n│ │ └── manager.ts # 配置管理器\n│ └── cli/\n│ ├── repl.ts # 交互式 REPL\n│ ├── render.ts # 输出渲染\n│ └── commands.ts # 斜杠命令\n├── package.json\n├── tsconfig.json\n└── README.md\n```\n\n## 架构:Provider 抽象层\n\n```\n ┌──────────────────┐\n │ LLMProvider │ ← 抽象接口\n │ (provider.ts) │\n │──────────────────│\n │ + call(params) │\n │ + resolveModel() │\n └────────┬─────────┘\n │\n ┌────────────┼────────────┐\n │ │\n ┌──────────▼──────────┐ ┌──────────▼──────────┐\n │ AnthropicProvider │ │ OpenAIProvider │\n │ (anthropic- │ │ (openai- │\n │ provider.ts) │ │ provider.ts) │\n │─────────────────────│ │─────────────────────│\n │ @anthropic-ai/sdk │ │ openai sdk │\n │ │ │ │\n │ Claude 系列: │ │ DeepSeek / GLM / │\n │ sonnet, opus, haiku │ │ Qwen / Moonshot / │\n │ │ │ Baichuan / Yi / │\n │ │ │ SiliconFlow / ... │\n └─────────────────────┘ └─────────────────────┘\n```\n\nAgent Loop 只依赖 `LLMProvider` 接口,不关心底层是哪家 API。\n\n## 与 Claude Code 对比\n\n| 维度 | Claude Code | Mini Claude Code |\n|------|-------------|------------------|\n| 代码量 | ~100K+ 行 | ~3K 行 |\n| 工具数量 | 11+ | 6 |\n| 权限模式 | allow/ask/deny + auto | allow/deny |\n| 配置层数 | 5 层 | 2 层 |\n| Hook 类型 | 5 种 × 20+ 事件 | 1 种 × 3 事件 |\n| 子代理 | 完整 | 无 |\n| MCP | 完整 | 无 |\n| **Provider** | **仅 Anthropic** | **Anthropic + 国内全厂商** |\n\n## License\n\nMIT\n"}}}}