1 line
30 KiB
JSON
1 line
30 KiB
JSON
{"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') || '删除'}\">×</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"}}}} |