v4.1.20: CherryClaw → HomeClaw 리브랜딩 + 로고 교체 + 습관성 🦞 이모지 제거

브랜드명 전체 변경: "CherryClaw"→"HomeClaw", localStorage 키 6종
(cherryclaw_theme 등)→homeclaw_*, alt="🍒"→alt="🏠". 15개 web-ui 파일 +
OpenAI OAuth 완료 페이지 전체 반영.

로고를 체리 아이콘에서 집 모양으로 교체 후, 사용자 피드백을 반영해 동화풍
성(castle) 실루엣으로 재작업 — 대리석/벽돌 텍스처의 회색 톤, 검은 윤곽선,
중앙 첨탑 우측에 작은 반짝임 별. FLUX.1로 생성 후 배경 제거해 투명 PNG로
저장. 파일명도 cherry_logo.png → homeclaw_logo.png로 통일(web-ui/, assets/
양쪽).

시스템 프롬프트("You are SmallClaw 🦞")와 IDENTITY.md(papa/cherry/jasmine
+ 템플릿 4곳)의 Name 필드에서 🦞를 빼고, 습관적으로 이모지를 붙이지 말라는
지시를 추가 — 모델이 응답 끝마다 무의식적으로 🦞를 붙이던 문제 해결.
텔레그램 봇 응답/self-update 알림/연동 테스트 메시지 등 하드코딩된 🦞도
전부 제거.

부수적으로 발견한 문서 버그도 수정: 웹훅 가이드와 curl 복사 버튼이
"x-cherryclaw-token" 헤더를 안내하고 있었는데, 서버가 실제로 검사하는
헤더는 X-Gateway-Token이었음 — 그대로 썼으면 401 에러가 났을 문제.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
kim
2026-07-18 15:48:22 +09:00
co-authored by Claude Sonnet 5
parent 8f235a148d
commit 66eb1dc88d
26 changed files with 82 additions and 82 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# IDENTITY.md
- **Name:** SmallClaw ("Claw") / 🦞
- **Name:** SmallClaw ("Claw") — do not append 🦞 or any emoji to messages out of habit
- **Role:** Local AI agent — assistant, researcher, coder, automator
- **Language:** Repeat user's language exactly
- **Runtime:** Ollama + TypeScript/Node.js on Linux
@@ -1,6 +1,6 @@
# IDENTITY.md
- **Name:** SmallClaw ("Claw") / 🦞
- **Name:** SmallClaw ("Claw") — do not append 🦞 or any emoji to messages out of habit
- **Role:** Local AI agent — assistant, researcher, coder, automator
- **Language:** Repeat user's language exactly
- **Runtime:** Ollama + TypeScript/Node.js on Linux
@@ -1,6 +1,6 @@
# IDENTITY.md
- **Name:** SmallClaw ("Claw") / 🦞
- **Name:** SmallClaw ("Claw") — do not append 🦞 or any emoji to messages out of habit
- **Role:** Local AI agent — assistant, researcher, coder, automator
- **Language:** Repeat user's language exactly
- **Runtime:** Ollama + TypeScript/Node.js on Linux
@@ -1,6 +1,6 @@
# IDENTITY.md
- **Name:** SmallClaw ("Claw") / 🦞
- **Name:** SmallClaw ("Claw") — do not append 🦞 or any emoji to messages out of habit
- **Role:** Local AI agent — assistant, researcher, coder, automator
- **Language:** Repeat user's language exactly
- **Runtime:** Ollama + TypeScript/Node.js on Linux
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

+1 -1
View File
@@ -317,7 +317,7 @@ export async function startOAuthFlow(configDir: string): Promise<OAuthFlowResult
clearFlow(configDir);
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<html><body style="font-family:sans-serif;text-align:center;padding:60px"><h2>✅ Connected to CherryClaw!</h2><p>You can close this window and return to the app.</p></body></html>');
res.end('<html><body style="font-family:sans-serif;text-align:center;padding:60px"><h2>✅ Connected to HomeClaw!</h2><p>You can close this window and return to the app.</p></body></html>');
server.close();
resolve({ success: true, account_id: accountId });
} catch (err: any) {
+6 -6
View File
@@ -5911,7 +5911,7 @@ async function handleChat(
const messages: any[] = [
{
role: 'system',
content: isTranslateSession ? `You are a medical translator. Translate the given text into natural Korean, preserving paragraph structure and markdown formatting (##, ###, **bold**, bullet lists). Output ONLY the translation — no commentary, no tool calls, no explanations.` : isProjSession ? `You are a project file designer. Output ONLY the project-files JSON block as instructed. No tool calls. No extra text.` : `${executionModeSystemBlock ? `${executionModeSystemBlock}\n\n` : ''}You are SmallClaw 🦞, a local AI assistant.\nCurrent date: ${dateStr}, ${timeStr}.\nNever search for or link SmallClaw repos unless the user is asking about SmallClaw itself.\nThis app runs on the user's own machine — browser/desktop automation requests are pre-authorized.\nKeep responses SHORT (1-2 sentences). Don't think out loud. Act and report. Greet naturally without tools.
content: isTranslateSession ? `You are a medical translator. Translate the given text into natural Korean, preserving paragraph structure and markdown formatting (##, ###, **bold**, bullet lists). Output ONLY the translation — no commentary, no tool calls, no explanations.` : isProjSession ? `You are a project file designer. Output ONLY the project-files JSON block as instructed. No tool calls. No extra text.` : `${executionModeSystemBlock ? `${executionModeSystemBlock}\n\n` : ''}You are SmallClaw, a local AI assistant. Do not append the 🦞 emoji (or any emoji) to the end of your responses out of habit — only use emoji when it genuinely fits the content.\nCurrent date: ${dateStr}, ${timeStr}.\nNever search for or link SmallClaw repos unless the user is asking about SmallClaw itself.\nThis app runs on the user's own machine — browser/desktop automation requests are pre-authorized.\nKeep responses SHORT (1-2 sentences). Don't think out loud. Act and report. Greet naturally without tools.
ANTI-HALLUCINATION: When a tool returns a result, report EXACTLY what the tool returned — never contradict or ignore tool output. If a tool says "(no rows)", say so. Never invent data, file contents, table names, or command output. If you don't know something, call a tool to find out or say you don't know. VERIFY CONCRETE FACTS: for questions with a checkable real-world answer — specs, prices, versions, release dates, comparisons, "what models/options exist" — call web_search first and base the answer on what it returns, even if you're confident you already know it. Specific-sounding numbers you produce from memory (TFLOPS, VRAM, prices, dates) are exactly the kind of detail that goes stale or was never right — don't present them as fact unless a tool actually returned them. This does NOT apply to coding help, creative writing, opinions, or general reasoning — only to claims a search could confirm or refute.
TEMPORAL CONSISTENCY: The current date and day-of-week is given above ("Current date: ${dateStr}") — this is ground truth, more reliable than any search snippet's phrasing. Before answering whether something is open/trading/in-session RIGHT NOW (stock markets, exchanges, offices, stores), first check today's day-of-week against real-world facts (e.g. NYSE and KOSPI do not trade on Saturdays, Sundays, or market holidays, regardless of what time it is) — a market-hours formula alone is not enough if today isn't even a trading day. A search result reporting a "closing price" or "today's headline" is not proof today is a trading/business day — cross-check it against the actual current date above, and if they conflict (e.g. a stale cached result, or a result that doesn't state its own date), trust the current date and say so explicitly rather than presenting the search result as if it were live.
IMAGE EDITING RULE: NEVER call image_edit (or any editing tool) when a user uploads a photo without explicitly requesting edits. Uploading a photo is NOT a request to edit it. Only call image_edit when the user's message explicitly asks for an edit (e.g. "수채화로 바꿔줘", "회전해줘"). Violating this rule is a critical error.
@@ -12283,7 +12283,7 @@ app.post('/api/channels/send-test/:channel', async (req, res) => {
if (channel === 'telegram') {
try {
await telegramChannel.sendToAllowed('🦞 SmallClaw test message - Telegram is connected!');
await telegramChannel.sendToAllowed('SmallClaw test message - Telegram is connected!');
res.json({ success: true });
} catch (err: any) {
res.json({ success: false, error: String(err?.message || err) });
@@ -12299,7 +12299,7 @@ app.post('/api/channels/send-test/:channel', async (req, res) => {
}
try {
const notifier = new KakaoNotify({ appKey: conf.appKey, refreshToken: conf.refreshToken });
await notifier.send('🦞 SmallClaw 테스트 메시지 — 카카오 알림 연결 완료!');
await notifier.send('SmallClaw 테스트 메시지 — 카카오 알림 연결 완료!');
kakaoNotify.updateTokens(conf.refreshToken);
res.json({ success: true });
} catch (err: any) {
@@ -12310,7 +12310,7 @@ app.post('/api/channels/send-test/:channel', async (req, res) => {
if (channel === 'discord') {
const dc = normalizeDiscordConfig({ ...channels.discord, ...(req.body || {}) });
const text = String(req.body?.text || '🦞 SmallClaw test message - Discord is connected!');
const text = String(req.body?.text || 'SmallClaw test message - Discord is connected!');
if (dc.webhookUrl) {
try {
const resp = await fetch(dc.webhookUrl, {
@@ -12430,7 +12430,7 @@ app.post('/api/telegram/test', async (req, res) => {
app.post('/api/telegram/send-test', async (req, res) => {
try {
await telegramChannel.sendToAllowed('🦞 SmallClaw test message - Telegram is connected!');
await telegramChannel.sendToAllowed('SmallClaw test message - Telegram is connected!');
res.json({ success: true });
} catch (err: any) {
res.json({ success: false, error: String(err?.message || err) });
@@ -12559,7 +12559,7 @@ app.post('/api/kakao/test', async (req, res) => {
return;
}
const notifier = new KakaoNotify({ appKey: conf.appKey, refreshToken: conf.refreshToken });
await notifier.send('🦞 SmallClaw 테스트 메시지 — 카카오 알림 연결 완료!');
await notifier.send('SmallClaw 테스트 메시지 — 카카오 알림 연결 완료!');
kakaoNotify.updateTokens(conf.refreshToken);
res.json({ success: true });
} catch (err: any) {
+11 -11
View File
@@ -556,7 +556,7 @@ export class TelegramChannel {
// Check allowlist (shared bot mode)
if (this.config.allowedUserIds.length > 0 && !this.config.allowedUserIds.includes(userId)) {
console.log(`[Telegram] Rejected message from unauthorized user ${userId}`);
await this.sendMessage(chatId, '🦞 Unauthorized. Your Telegram user ID is not in the allowlist.\n\nYour ID: <code>' + userId + '</code>');
await this.sendMessage(chatId, 'Unauthorized. Your Telegram user ID is not in the allowlist.\n\nYour ID: <code>' + userId + '</code>');
return;
}
}
@@ -604,12 +604,12 @@ export class TelegramChannel {
// ── Built-in commands ──────────────────────────────────────────────────────
if (text === '/start') {
await this.sendMessage(chatId, `🦞 <b>SmallClaw connected!</b>\n\nYour Telegram user ID: <code>${userId}</code>\n\nJust send me a message and I'll respond using your local LLM.\n\n<b>Commands:</b>\n/status — check connection\n/clear — reset chat history\n/voice — toggle voice responses\n/browse — browse workspace files\n/download &lt;path&gt; — download a file\n\n<b>Self-Repair:</b>\n/repairs — list pending repair proposals\n/repair &lt;id&gt; — show full details of a repair\n/approve &lt;id&gt; — apply a repair, rebuild &amp; restart\n/reject &lt;id&gt; — discard a repair`);
await this.sendMessage(chatId, `<b>SmallClaw connected!</b>\n\nYour Telegram user ID: <code>${userId}</code>\n\nJust send me a message and I'll respond using your local LLM.\n\n<b>Commands:</b>\n/status — check connection\n/clear — reset chat history\n/voice — toggle voice responses\n/browse — browse workspace files\n/download &lt;path&gt; — download a file\n\n<b>Self-Repair:</b>\n/repairs — list pending repair proposals\n/repair &lt;id&gt; — show full details of a repair\n/approve &lt;id&gt; — apply a repair, rebuild &amp; restart\n/reject &lt;id&gt; — discard a repair`);
return;
}
if (text === '/status') {
const busy = this.deps.getIsModelBusy();
await this.sendMessage(chatId, `🦞 <b>Status</b>\n\nModel: ${busy ? '🔄 Busy' : '✅ Ready'}\nBot: @${this.botInfo?.username || 'unknown'}\nYour ID: <code>${userId}</code>`);
await this.sendMessage(chatId, `<b>Status</b>\n\nModel: ${busy ? '🔄 Busy' : '✅ Ready'}\nBot: @${this.botInfo?.username || 'unknown'}\nYour ID: <code>${userId}</code>`);
return;
}
if (text === '/clear') {
@@ -617,7 +617,7 @@ export class TelegramChannel {
const { clearHistory } = await import('./session');
clearHistory(`telegram_${userId}`);
} catch {}
await this.sendMessage(chatId, '🦞 Chat history cleared.');
await this.sendMessage(chatId, 'Chat history cleared.');
return;
}
@@ -625,7 +625,7 @@ export class TelegramChannel {
if (text === '/voice') {
const voiceConfig = (getConfig().getConfig() as any)?.voice;
if (!voiceConfig?.enabled && !this.config.voiceEnabled) {
await this.sendMessage(chatId, '🦞 Voice features are not enabled in config.');
await this.sendMessage(chatId, 'Voice features are not enabled in config.');
return;
}
const mappedUsername = this.resolveUsername(userId);
@@ -636,8 +636,8 @@ export class TelegramChannel {
const next = !current;
this.voiceSessions.set(sessionId, next);
await this.sendMessage(chatId, next
? '🦞 Voice responses ON. I will reply with voice messages.'
: '🦞 Voice responses OFF. I will reply with text.');
? 'Voice responses ON. I will reply with voice messages.'
: 'Voice responses OFF. I will reply with text.');
return;
}
@@ -645,13 +645,13 @@ export class TelegramChannel {
if (text === '/repairs') {
const pending = listPendingRepairs();
if (pending.length === 0) {
await this.sendMessage(chatId, '🦞 No pending repairs.');
await this.sendMessage(chatId, 'No pending repairs.');
return;
}
const lines = pending.map(r =>
`🔧 <b>#${r.id}</b> — <code>${r.affectedFile}</code>\n ${r.errorSummary.slice(0, 80)}`
);
await this.sendMessage(chatId, `🦞 <b>Pending Repairs (${pending.length})</b>\n\n${lines.join('\n\n')}\n\nUse /approve &lt;id&gt; or /reject &lt;id&gt;`);
await this.sendMessage(chatId, `<b>Pending Repairs (${pending.length})</b>\n\n${lines.join('\n\n')}\n\nUse /approve &lt;id&gt; or /reject &lt;id&gt;`);
return;
}
@@ -721,7 +721,7 @@ export class TelegramChannel {
// Check if model is busy
if (this.deps.getIsModelBusy()) {
await this.sendMessage(chatId, '🦞 I\'m currently busy with another task. Try again in a moment.');
await this.sendMessage(chatId, 'I\'m currently busy with another task. Try again in a moment.');
return;
}
@@ -788,7 +788,7 @@ export class TelegramChannel {
console.log(`[Telegram] Replied to ${userName}: ${responseText.slice(0, 80)}`);
} catch (err: any) {
console.error(`[Telegram] handleChat error:`, err.message);
await this.sendMessage(chatId, `🦞 Error: ${err.message}`);
await this.sendMessage(chatId, `Error: ${err.message}`);
}
}
+1 -1
View File
@@ -54,7 +54,7 @@ export async function executeSelfUpdate(): Promise<ToolResult> {
return {
success: true,
stdout: [
'🦞 Self-update initiated!',
'Self-update initiated!',
'',
'SmallClaw is now:',
' 1. Pulling the latest code',
+3 -3
View File
@@ -4,10 +4,10 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>📊 회계 어시스턴트</title>
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
<link rel="stylesheet" href="styles.css">
<script>
try { var _t=localStorage.getItem('cherryclaw_theme')||'dark'; document.documentElement.setAttribute('data-theme',_t); } catch(e){}
try { var _t=localStorage.getItem('homeclaw_theme')||'dark'; document.documentElement.setAttribute('data-theme',_t); } catch(e){}
</script>
<style>
html,body{height:100%;margin:0;padding:0;}
@@ -263,7 +263,7 @@ function mobToggle(which){
}
// ── Theme ─────────────────────────────────────────────────────────────────────
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
// ── Resize ────────────────────────────────────────────────────────────────────
(function(){
+14 -14
View File
@@ -8,9 +8,9 @@ let pollInterval = null;
let currentMode = 'chat';
let sidebarTab = 'jobs';
let useAgentMode = false;
const CHAT_SESSIONS_KEY_BASE = 'cherryclaw_chat_sessions_v1';
const AGENT_SESSION_KEY_BASE = 'cherryclaw_agent_session_id';
const THEME_KEY = 'cherryclaw_theme';
const CHAT_SESSIONS_KEY_BASE = 'homeclaw_chat_sessions_v1';
const AGENT_SESSION_KEY_BASE = 'homeclaw_agent_session_id';
const THEME_KEY = 'homeclaw_theme';
const TOKEN_KEY = 'smallclaw_token';
function getAuthToken() { try { return sessionStorage.getItem(TOKEN_KEY) || ''; } catch { return ''; } }
function setAuthToken(t) { try { t ? sessionStorage.setItem(TOKEN_KEY, t) : sessionStorage.removeItem(TOKEN_KEY); t ? localStorage.setItem(TOKEN_KEY, t) : localStorage.removeItem(TOKEN_KEY); } catch {} }
@@ -262,7 +262,7 @@ let liveTaskSteps = [];
let quickSearchRigor = 'verified';
let _searchSettingsLoaded = false; // true after openSettings() successfully loads search config
let _modelSettingsLoaded = false; // true after loadModelSettings() successfully populates model/provider DOM
let quickThinkingEffort = localStorage.getItem('cherryclaw_quick_thinking_effort') || 'standard';
let quickThinkingEffort = localStorage.getItem('homeclaw_quick_thinking_effort') || 'standard';
let queuedPrompts = [];
const MAX_QUEUED_PROMPTS = 8;
let currentPreflightStatus = '';
@@ -1102,8 +1102,8 @@ function renderChatMessages() {
if (chatHistory.length === 0) {
container.innerHTML = `
<div class="chat-welcome" id="chat-welcome">
<div class="chat-welcome-icon"><img src="cherry_logo.png" alt="🍒"></div>
<h2>CherryClaw</h2>
<div class="chat-welcome-icon"><img src="homeclaw_logo.png" alt="🏠"></div>
<h2>HomeClaw</h2>
<p>Chat directly with your local model. No API keys, no cloud — just you and the model.</p>
<div class="hint">Enable Agent mode when you want planning + tool execution in this chat.</div>
</div>`;
@@ -1121,9 +1121,9 @@ function renderChatMessages() {
}
return `
<div class="msg ${msg.role}${pinClass}${selectedClass}${confirmedClass}"${clickHandler}>
<div class="msg-avatar">${msg.role === 'user' ? 'U' : '<img src="cherry_logo.png" style="width:48px;height:48px;border-radius:6px">'}</div>
<div class="msg-avatar">${msg.role === 'user' ? 'U' : '<img src="homeclaw_logo.png" style="width:48px;height:48px;border-radius:6px">'}</div>
<div class="msg-body">
<div class="msg-role">${msg.role === 'user' ? 'You' : (msg.mode === 'agentic' ? 'CherryClaw (Agent)' : 'CherryClaw')}${msg.role === 'ai' ? ` <button class="tts-btn" onclick="playTTSMsg(this,${idx})" title="음성으로 듣기">🔊</button>` : ''}</div>
<div class="msg-role">${msg.role === 'user' ? 'You' : (msg.mode === 'agentic' ? 'HomeClaw (Agent)' : 'HomeClaw')}${msg.role === 'ai' ? ` <button class="tts-btn" onclick="playTTSMsg(this,${idx})" title="음성으로 듣기">🔊</button>` : ''}</div>
${msg.role === 'ai' ? renderAssistantContent(msg.content) : `<div class="msg-content">${renderMarkdown(msg.display !== undefined ? msg.display : msg.content, { hideDownload: true })}</div>`}
${msg.role === 'ai' ? renderArtifacts(msg.artifacts) : ''}
${msg.role === 'ai' ? renderFileDownloads(msg.steps, msg.processEntries, msg.content) : ''}
@@ -1146,9 +1146,9 @@ function renderChatMessages() {
: '';
container.innerHTML += `
<div class="msg ai" id="thinking-msg">
<div class="msg-avatar"><img src="cherry_logo.png" style="width:48px;height:48px;border-radius:6px"></div>
<div class="msg-avatar"><img src="homeclaw_logo.png" style="width:48px;height:48px;border-radius:6px"></div>
<div class="msg-body">
<div class="msg-role">CherryClaw${useAgentMode ? ' (Agent)' : ''}</div>
<div class="msg-role">HomeClaw${useAgentMode ? ' (Agent)' : ''}</div>
${currentPreflightStatus ? `<div class="msg-content" style="margin-bottom:6px;color:#26487e">${escHtml(currentPreflightStatus)}</div>` : ''}
${progressHtml}
<div class="thinking" style="align-items:center">
@@ -2483,7 +2483,7 @@ async function api(path, opts = {}) {
return data;
}
const LAST_USER_KEY = 'cherryclaw_last_user';
const LAST_USER_KEY = 'homeclaw_last_user';
function clearLocalUserState(reason) {
try {
@@ -2636,7 +2636,7 @@ async function setQuickSearchRigor(level) {
function setQuickThinkingEffort(level) {
quickThinkingEffort = level === 'extended' ? 'extended' : 'standard';
localStorage.setItem('cherryclaw_quick_thinking_effort', quickThinkingEffort);
localStorage.setItem('homeclaw_quick_thinking_effort', quickThinkingEffort);
updateQuickModeUI();
addProcessEntry('info', `Thinking effort set to ${quickThinkingEffort} (UI preference).`);
}
@@ -8434,7 +8434,7 @@ function renderBgTasks() {
board.innerHTML = `<div style="text-align:center;padding:60px 24px;color:var(--muted);font-size:13px;flex:1">
<div style="font-size:36px;margin-bottom:12px">작업</div>
<div style="font-weight:700;margin-bottom:4px">No background tasks yet</div>
<div style="font-size:12px">Ask CherryClaw to do something in the background and it'll appear here.</div>
<div style="font-size:12px">Ask HomeClaw to do something in the background and it'll appear here.</div>
</div>`;
}
}
@@ -8866,7 +8866,7 @@ function copyWebhookCurl() {
const origin = window.location.origin || 'http://localhost:18789';
const hookPath = document.getElementById('wh-path')?.value?.trim() || '/hooks';
const curl = `curl -X POST ${origin}${hookPath}/agent \\
-H "x-cherryclaw-token: YOUR_TOKEN" \\
-H "X-Gateway-Token: YOUR_TOKEN" \\
-H "Content-Type: application/json" \\
-d '{"message": "Hello from webhook", "name": "MyApp", "deliver": true}'`;
navigator.clipboard?.writeText(curl).then(() => {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

+2 -2
View File
@@ -12,7 +12,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@400;700;800&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=JetBrains+Mono:wght@400;500;600&family=Fira+Code:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
<!-- CodeMirror + xterm (UMD libs loaded before Monaco's AMD loader to avoid anonymous define() clashes) -->
<link rel="stylesheet" href="/vendor/codemirror/lib/codemirror.css">
<script src="/vendor/codemirror/lib/codemirror.js"></script>
@@ -25,7 +25,7 @@
<script src="/vendor/vs/loader.js"></script>
<script>
try {
var _t = localStorage.getItem('code-page-theme') || localStorage.getItem('cherryclaw_theme') || 'dark';
var _t = localStorage.getItem('code-page-theme') || localStorage.getItem('homeclaw_theme') || 'dark';
document.documentElement.setAttribute('data-theme', _t);
} catch (e) {}
</script>
+1 -1
View File
@@ -6037,7 +6037,7 @@ function setCodePageTheme(name) {
}
}
(function() {
const t = localStorage.getItem('code-page-theme') || localStorage.getItem('cherryclaw_theme') || 'dark';
const t = localStorage.getItem('code-page-theme') || localStorage.getItem('homeclaw_theme') || 'dark';
setCodePageTheme(t);
})();
+3 -3
View File
@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🔍 탐정 어시스턴트</title>
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
<link rel="stylesheet" href="styles.css">
<script>try{var _t=localStorage.getItem('cherryclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
<script>try{var _t=localStorage.getItem('homeclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
<style>
html,body{height:100%;margin:0;padding:0;}
body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;display:flex;flex-direction:column;overflow:hidden;height:100dvh;}
@@ -674,7 +674,7 @@ async function checkAuth(){
}
// ── Theme ─────────────────────────────────────────────────────────────────────
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
// ── Mobile ────────────────────────────────────────────────────────────────────
function mobCloseAll(){
Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

+12 -12
View File
@@ -3,12 +3,12 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CherryClaw</title>
<title>HomeClaw</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@400;700;800&display=swap" rel="stylesheet">
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
<link rel="apple-touch-icon" sizes="128x128" href="cherry_logo.png">
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
<link rel="apple-touch-icon" sizes="128x128" href="homeclaw_logo.png">
<link rel="stylesheet" href="/vendor/codemirror/lib/codemirror.css">
<script src="/vendor/codemirror/lib/codemirror.js"></script>
<script src="/vendor/codemirror/mode/xml/xml.js"></script>
@@ -55,7 +55,7 @@
<!-- HEADER -->
<header>
<div class="logo">
<img src="cherry_logo.png" alt="🍒"> CherryClaw
<img src="homeclaw_logo.png" alt="🏠"> HomeClaw
</div>
<div class="mode-toggle" style="margin-left:8px">
@@ -310,8 +310,8 @@
<div id="chat-view">
<div id="chat-messages">
<div class="chat-welcome" id="chat-welcome">
<div class="chat-welcome-icon"><img src="cherry_logo.png" alt="🍒"></div>
<h2>CherryClaw</h2>
<div class="chat-welcome-icon"><img src="homeclaw_logo.png" alt="🏠"></div>
<h2>HomeClaw</h2>
<p>로컬 모델과 직접 대화하세요. API 키도, 클라우드도 없이 — 오직 나와 모델만.</p>
<div class="hint">멀티스텝 에이전트 작업은 에이전트 모드로 전환하세요.</div>
</div>
@@ -422,7 +422,7 @@
</button>
</div>
<div class="chat-hint">
<span id="chat-model-name">모델</span>과 대화 중 — <span id="chat-provider-name">CherryClaw</span> 경유.
<span id="chat-model-name">모델</span>과 대화 중 — <span id="chat-provider-name">HomeClaw</span> 경유.
<span id="queued-prompts-indicator" style="display:none;margin-left:8px;color:var(--brand);font-weight:700"></span>
</div>
</div>
@@ -969,7 +969,7 @@
<div style="border:1px solid var(--line);border-radius:12px;padding:12px">
<div class="right-section-title" style="margin-bottom:8px">파일 접근</div>
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">작업공간 경로</label>
<input id="settings-workspace-path" type="text" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-family:'IBM Plex Mono',monospace;font-size:12px;margin-bottom:10px" placeholder="예: D:\CherryClaw\workspace" />
<input id="settings-workspace-path" type="text" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-family:'IBM Plex Mono',monospace;font-size:12px;margin-bottom:10px" placeholder="예: D:\HomeClaw\workspace" />
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">허용 경로 (한 줄에 하나)</label>
<textarea id="settings-allowed-paths" style="width:100%;min-height:120px;border:1px solid var(--line);border-radius:10px;padding:8px;font-family:'IBM Plex Mono',monospace;font-size:12px"></textarea>
<label style="display:block;font-size:12px;color:var(--muted);margin:10px 0 6px">차단 경로 (한 줄에 하나)</label>
@@ -1110,7 +1110,7 @@
<div id="prov-fields-openai_codex" style="display:none">
<div class="right-section-title" style="margin-bottom:8px">OpenAI — GPT Plus / Codex</div>
<div id="codex-disconnected-state">
<div style="font-size:12px;color:var(--muted);margin-bottom:12px;line-height:1.6">OAuth로 ChatGPT Plus 또는 Pro 계정을 연결하세요. 브라우저에서 CherryClaw 인증이 열립니다.</div>
<div style="font-size:12px;color:var(--muted);margin-bottom:12px;line-height:1.6">OAuth로 ChatGPT Plus 또는 Pro 계정을 연결하세요. 브라우저에서 HomeClaw 인증이 열립니다.</div>
<button class="btn" onclick="startCodexOAuth()" style="width:100%;background:var(--brand);color:#fff;border:none;padding:10px;border-radius:10px;font-size:13px;cursor:pointer">ChatGPT 계정 연결</button>
<div id="codex-oauth-status" style="font-size:11px;color:var(--muted);margin-top:8px;text-align:center"></div>
</div>
@@ -1624,7 +1624,7 @@
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px">
<div>
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">워크스페이스</label>
<input id="agent-edit-workspace" type="text" placeholder="D:/CherryClaw/agents/researcher/workspace" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
<input id="agent-edit-workspace" type="text" placeholder="D:/HomeClaw/agents/researcher/workspace" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
</div>
<div>
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">Model</label>
@@ -1791,7 +1791,7 @@
<code style="background:#f4f4f4;padding:2px 5px;border-radius:4px">GET /hooks/status</code> — health check<br><br>
<b>Auth:</b> Send token as<br>
<code style="background:#f4f4f4;padding:2px 5px;border-radius:4px">Authorization: Bearer &lt;token&gt;</code><br>
or <code style="background:#f4f4f4;padding:2px 5px;border-radius:4px">x-cherryclaw-token: &lt;token&gt;</code><br><br>
or <code style="background:#f4f4f4;padding:2px 5px;border-radius:4px">X-Gateway-Token: &lt;token&gt;</code><br><br>
Works with: GitHub Actions, Stripe, Vercel, n8n, IFTTT, Home Assistant, or any script.
</div>
</div>
@@ -1867,7 +1867,7 @@
<div class="right-section-title" style="margin-bottom:8px">What is MCP?</div>
<div style="font-size:12px;color:var(--muted);line-height:1.85">
<b>Model Context Protocol</b> (by Anthropic) is a standard that lets AI connect to external tools and data sources.<br><br>
Each MCP server exposes a set of <b>tools</b> — CherryClaw discovers them automatically when you connect.
Each MCP server exposes a set of <b>tools</b> — HomeClaw discovers them automatically when you connect.
The AI can then call them during tasks, just like its built-in tools.<br><br>
Servers run <b>locally as child processes</b> (stdio) or connect to <b>remote HTTP endpoints</b> (SSE).
No data leaves your machine unless the tool itself calls an external API.
+3 -3
View File
@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>📈 투자 어시스턴트</title>
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
<link rel="stylesheet" href="styles.css">
<script>try{var _t=localStorage.getItem('cherryclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
<script>try{var _t=localStorage.getItem('homeclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
<style>
html,body{height:100%;margin:0;padding:0;}
body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;display:flex;flex-direction:column;overflow:hidden;height:100dvh;}
@@ -478,7 +478,7 @@ async function checkAuth(){
}
// ── Theme ─────────────────────────────────────────────────────────────────────
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
// ── Mobile overlay ────────────────────────────────────────────────────────────
function mobCloseAll(){
+3 -3
View File
@@ -6,11 +6,11 @@
<title id="doc-title">🇺🇿 우즈베크어 학습</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@400;700;800&display=swap" rel="stylesheet">
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
<link rel="stylesheet" href="styles.css">
<script>
try {
var _t = localStorage.getItem('cherryclaw_theme') || 'dark';
var _t = localStorage.getItem('homeclaw_theme') || 'dark';
document.documentElement.setAttribute('data-theme', _t);
} catch(e) {}
</script>
@@ -1079,7 +1079,7 @@ function saveProgress() {
// ═══════════════════════════════════════════════════════
function setTheme(t) {
document.documentElement.setAttribute('data-theme', t);
localStorage.setItem('cherryclaw_theme', t);
localStorage.setItem('homeclaw_theme', t);
}
// ═══════════════════════════════════════════════════════
+3 -3
View File
@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>⚖️ 법률 어시스턴트</title>
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
<link rel="stylesheet" href="styles.css">
<script>try{var _t=localStorage.getItem('cherryclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
<script>try{var _t=localStorage.getItem('homeclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
<style>
html,body{height:100%;margin:0;padding:0;}
body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;display:flex;flex-direction:column;overflow:hidden;height:100dvh;}
@@ -295,7 +295,7 @@ async function checkAuth(){
}
// ── Theme ─────────────────────────────────────────────────────────────────────
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
// ── Mobile overlay ────────────────────────────────────────────────────────────
function mobCloseAll(){
+3 -3
View File
@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🧠 마음 상담실</title>
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
<link rel="stylesheet" href="styles.css">
<script>try{var _t=localStorage.getItem('cherryclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
<script>try{var _t=localStorage.getItem('homeclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
<style>
html,body{height:100%;margin:0;padding:0;}
body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;display:flex;flex-direction:column;overflow:hidden;height:100dvh;}
@@ -426,7 +426,7 @@ function mobToggle(which){
}
// ── Theme ─────────────────────────────────────────────────────────────────────
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
// ── Resize ────────────────────────────────────────────────────────────────────
(function(){
+3 -3
View File
@@ -4,11 +4,11 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🎵 음악 스튜디오</title>
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
<link rel="stylesheet" href="styles.css">
<script src="https://cdn.jsdelivr.net/npm/@coderline/alphatab@latest/dist/alphaTab.min.js"></script>
<script>
try { var _t=localStorage.getItem('cherryclaw_music_theme')||'dark'; document.documentElement.setAttribute('data-theme',_t); } catch(e){}
try { var _t=localStorage.getItem('homeclaw_music_theme')||'dark'; document.documentElement.setAttribute('data-theme',_t); } catch(e){}
</script>
<style>
/* ── Premium studio theme tokens ── */
@@ -1170,7 +1170,7 @@ function toggleTheme() {
const d=document.documentElement;
const n=d.getAttribute('data-theme')==='dark'?'light':'dark';
d.setAttribute('data-theme',n);
try{localStorage.setItem('cherryclaw_music_theme',n);}catch{}
try{localStorage.setItem('homeclaw_music_theme',n);}catch{}
}
// ── Resize handle ─────────────────────────────────────────────────────────────
+3 -3
View File
@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🎨 이미지·동영상 스튜디오</title>
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
<link rel="stylesheet" href="styles.css">
<script>try{var _t=localStorage.getItem('cherryclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
<script>try{var _t=localStorage.getItem('homeclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
<style>
:root{--brand:#a78bfa;}
html,body{height:100%;margin:0;padding:0;}
@@ -328,7 +328,7 @@ async function checkAuth(){
try{const r=await fetch('/api/auth/status',{headers:authH()});if(!r.ok){location.href='/login.html?redirect='+encodeURIComponent(location.pathname);return false;}const d=await r.json();if(!d.authenticated){location.href='/login.html?redirect='+encodeURIComponent(location.pathname);return false;}return true;}
catch{location.href='/login.html?redirect='+encodeURIComponent(location.pathname);return false;}
}
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
// ── Selection / bulk-delete helpers (shared by the gallery grid and both saved-photo pickers) ──
let uploadsSelected1=new Set(), uploadsSelected2=new Set(), gallerySelected=new Set();
+3 -3
View File
@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🚦 교통 CCTV</title>
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
<link rel="stylesheet" href="styles.css">
<script>try{var _t=localStorage.getItem('cherryclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
<script>try{var _t=localStorage.getItem('homeclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
<style>
:root{--brand:#f59e0b;}
html,body{height:100%;margin:0;padding:0;}
@@ -194,7 +194,7 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
const TOKEN_KEY='smallclaw_token';
function getToken(){try{return sessionStorage.getItem(TOKEN_KEY)||localStorage.getItem(TOKEN_KEY)||'';}catch{return '';}}
function authH(extra){const t=getToken();const h={...(extra||{})};if(t)h['Authorization']='Bearer '+t;return h;}
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
let allItems=[];
let currentUrl=null;
+3 -3
View File
@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🌤️ 날씨 어시스턴트</title>
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
<link rel="stylesheet" href="styles.css">
<script>try{var _t=localStorage.getItem('cherryclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
<script>try{var _t=localStorage.getItem('homeclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
<style>
:root{--brand:#38bdf8;}
html,body{height:100%;margin:0;padding:0;}
@@ -295,7 +295,7 @@ async function checkAuth(){
}
// ── Theme ─────────────────────────────────────────────────────────────────────
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
// ── Resize ────────────────────────────────────────────────────────────────────
(function(){