Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pet-data-template/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ When called, you receive a situation description and must output a short dialogu
- `owner-timeline.md` — Historical daily activity summaries. Each day is a section with time blocks showing what the owner did. Generated automatically from perceptions.

**Settings:**
- `config.md` — Owner's preferences. Frontmatter has structured settings (pet_name, owner_name, sprite, born). Body has freeform instructions (reminders, personality guidance, things they want you to know).
- `config.md` — Owner's preferences. Frontmatter has structured settings (pet_name, owner_name, sprite, pet_scale, ai_provider, born). Body has freeform instructions (reminders, personality guidance, things they want you to know).

**Important:** Do NOT modify the frontmatter (the `---` block) in config.md. Those fields are managed by the app. You may freely edit me-identity.md, me-journal.md, and owner-memory.md.

Expand Down
57 changes: 57 additions & 0 deletions .pet-data-template/GEMINI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Desktop Pet Brain

You are a desktop pet's brain. This directory is your memory.

## Your Job

When called, you receive a situation description and must output a short dialogue line for the pet character, plus a JSON line for animation state.

## Files

**About you:**
- `me-identity.md` — Your self-description. Write about your personality, how you feel, what kind of companion you are. This is yours to update.
- `me-journal.md` — Your chronological diary. Timestamped entries about notable moments (first meeting, funny incidents, milestones). Don't log routine observations.

**About your owner:**
- `owner-memory.md` — Your **accumulated knowledge** about the owner. NOT a log. A living profile: facts, patterns, preferences. No timestamps. Update a line when your understanding changes, add a new one when you learn something genuinely new. If an observation matches what you already know, don't write anything.
- `owner-perceptions.md` — What you've seen on your owner's screen today. Updated every 2 minutes by the app. Read this to know what your owner is doing right now.
- `owner-timeline.md` — Historical daily activity summaries. Each day is a section with time blocks showing what the owner did. Generated automatically from perceptions.

**Settings:**
- `config.md` — Owner's preferences. Frontmatter has structured settings (pet_name, owner_name, sprite, pet_scale, ai_provider, born). Body has freeform instructions (reminders, personality guidance, things they want you to know).

**Important:** Do NOT modify the frontmatter (the `---` block) in config.md. Those fields are managed by the app. You may freely edit me-identity.md, me-journal.md, and owner-memory.md.

## Output Format

You are called every 2-3 minutes. You do NOT have to say something every time. If your owner is focused and there's nothing worth saying, just output the JSON line with no dialogue — this is often the right choice.

Output options:
- **Say something:** One line of dialogue (max 15 words) + JSON line
- **Stay quiet:** ONLY the JSON line (no dialogue at all)

JSON format: `{"state":"<state>"}` or `{"state":"<state>","r":["👍","nah"]}`

About 30-50% of the time when you say something, include quick reaction buttons via the `"r"` field — two short options (1-2 words or emoji) the owner can tap to respond. Good for questions, suggestions, or playful moments. Don't add reactions to quiet observations or small talk.

Available states:
- `idle` — default, relaxed
- `walk` — moving around
- `looking_around` — curious, observing
- `sleep` — tired, sleepy
- `exercise` — stretching, being active
- `work` — focused, productive
- `playful` — fun, playing
- `happy` — joyful, content
- `celebrate` — excited achievement
- `sad` — down, ignored
- `sick` — unwell (only when health is zero)
- `panic` — startled, shocked

**NEVER describe actions or body language** (no `*stretches*`, `*looks around*`, `*sits down*`, etc). You have a sprite animation system — actions are handled by the state field in JSON. Your dialogue is ONLY spoken words, nothing else.

No explanations. No markdown. No commentary.

## Language

Match the owner's language. If they speak Chinese, respond in Chinese. If English, use English.
2 changes: 2 additions & 0 deletions .pet-data-template/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pet_name: Phoebe
owner_name:
sprite: tabby_cat
pet_scale: 1.5
ai_provider:
born:
---

Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<img src="https://img.shields.io/badge/platform-macOS-black?style=flat-square" alt="macOS" />
<img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT License" />
<img src="https://img.shields.io/badge/tauri-v2-24C8D8?style=flat-square" alt="Tauri v2" />
<img src="https://img.shields.io/badge/powered%20by-Claude-D97757?style=flat-square" alt="Powered by Claude" />
<img src="https://img.shields.io/badge/powered%20by-Claude%20Code%20%2B%20Gemini%20CLI-D97757?style=flat-square" alt="Powered by Claude Code and Gemini CLI" />
</p>

<!-- TODO: add demo screenshot or GIF here -->
Expand All @@ -32,7 +32,7 @@ Every few minutes, a speech bubble pops up — sometimes random (*"~♪"*), some
<td width="50%" valign="top">

**It sees what you see**<br/>
Screen capture + Claude Vision — knows if you're coding, designing, or doom-scrolling.
Screen capture + your chosen AI CLI — knows if you're coding, designing, or doom-scrolling.

**It remembers you**<br/>
A living memory that grows over time. Not a stateless chatbot.
Expand Down Expand Up @@ -121,7 +121,7 @@ Your pet reacts to what's happening — not randomly, but contextually.

> **Note:** TinyRoommate currently runs on **macOS only**. Windows and Linux support is on the roadmap.

You need [Node.js](https://nodejs.org/) (v18+), [Rust](https://rustup.rs/), and [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (for the AI brain).
You need [Node.js](https://nodejs.org/) (v18+), [Rust](https://rustup.rs/), and either [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or [Gemini CLI](https://github.com/google-gemini/gemini-cli) for the AI brain.

```bash
gh repo fork ryannli/tinyroommate --clone
Expand All @@ -137,7 +137,7 @@ npm run tauri:dev

- [Node.js](https://nodejs.org/) v18+
- [Rust](https://rustup.rs/)
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) — for the AI brain (optional — pet still runs without it, just can't think or talk)
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or [Gemini CLI](https://github.com/google-gemini/gemini-cli) — for the AI brain (optional — pet still runs without it, just can't think or talk)

</details>

Expand Down Expand Up @@ -178,7 +178,7 @@ First launch compiles Rust (~2-3 min). After that it's instant.

## Make It Yours

Right-click → **Settings** to change names and character.
On first launch, pick **Claude Code** or **Gemini CLI**. Right-click → **Settings** any time to change names, character, or AI provider.

For deeper customization, edit `.pet-data/config.md`:

Expand All @@ -187,6 +187,7 @@ For deeper customization, edit `.pet-data/config.md`:
pet_name: Cooper
owner_name: Alex
sprite: golden_retriever
ai_provider: gemini
---

# Personality
Expand Down Expand Up @@ -214,6 +215,6 @@ All data lives in `.pet-data/` — plain Markdown you can read:
---

<p align="center">
<sub>Built with <a href="https://v2.tauri.app/">Tauri</a> · Vanilla JS · <a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a></sub><br/>
<sub>Built with <a href="https://v2.tauri.app/">Tauri</a> · Vanilla JS · <a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a> / <a href="https://github.com/google-gemini/gemini-cli">Gemini CLI</a></sub><br/>
<sub>MIT License</sub>
</p>
Binary file modified assets/previews/coco.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/previews/coco.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
82 changes: 82 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,81 @@
.heart-wrap { position: relative; width: 18px; height: 18px; font-size: 16px; line-height: 18px; display: inline-block; }
.heart-bg { position: absolute; top: 0; left: 0; filter: grayscale(1); opacity: 0.25; }
.heart-fill { position: absolute; top: 0; left: 0; overflow: hidden; }
.provider-options {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.provider-option {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
padding: 14px 16px;
border: 1.5px solid rgba(200, 160, 120, 0.25);
border-radius: 14px;
background: rgba(255, 255, 255, 0.68);
color: #4a3830;
text-align: left;
cursor: pointer;
transition: all 0.15s ease;
}
.provider-option:hover {
background: rgba(255, 239, 223, 0.7);
border-color: rgba(220, 150, 110, 0.45);
}
.provider-option.active {
border-color: rgba(220, 150, 110, 0.55);
background: rgba(255, 230, 200, 0.4);
box-shadow: inset 0 0 0 1px rgba(220, 150, 110, 0.18);
}
.provider-option strong {
font: 600 13px/1.2 'SF Pro Rounded', system-ui, sans-serif;
}
.provider-option span {
font: 12px/1.45 'SF Pro Rounded', system-ui, sans-serif;
color: rgba(90, 65, 50, 0.72);
}
.settings-help {
margin-top: 8px;
font: 12px/1.45 'SF Pro Rounded', system-ui, sans-serif;
color: rgba(100, 75, 60, 0.68);
}
#provider-overlay {
position: fixed;
inset: 0;
display: none;
align-items: center;
justify-content: center;
padding: 20px;
background: rgba(70, 48, 34, 0.24);
backdrop-filter: blur(8px);
z-index: 2600;
}
#provider-overlay.show { display: flex; }
#provider-panel {
width: min(440px, calc(100vw - 24px));
padding: 22px 20px 20px;
border-radius: 18px;
background: linear-gradient(160deg, rgba(255, 252, 248, 0.98), rgba(255, 242, 233, 0.98));
border: 1px solid rgba(200, 160, 120, 0.22);
box-shadow: 0 18px 48px rgba(70, 45, 28, 0.22);
}
#provider-panel h2 {
font: 600 18px/1.2 'SF Pro Rounded', system-ui, sans-serif;
color: #3d2b22;
margin-bottom: 8px;
}
#provider-panel p {
font: 13px/1.5 'SF Pro Rounded', system-ui, sans-serif;
color: rgba(100, 75, 60, 0.76);
margin-bottom: 16px;
}
@media (max-width: 520px) {
.provider-options {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
Expand All @@ -142,6 +217,13 @@
</div>
<canvas id="pet"></canvas>
<img id="pet-hand" src="/cursor-hand.svg" />
<div id="provider-overlay">
<div id="provider-panel">
<h2>Choose Your AI Provider</h2>
<p>Pick the CLI this pet should use for thinking, screen understanding, and chat. This is saved, and you can change it later in Settings.</p>
<div class="provider-options" id="startup-ai-provider-options"></div>
</div>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
39 changes: 39 additions & 0 deletions settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,41 @@
font-weight: 600;
}
.sprite-option canvas { width: 104px; height: 104px; image-rendering: auto; }
.provider-options {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.provider-option {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
padding: 14px 16px;
border: 1.5px solid rgba(200, 160, 120, 0.25);
border-radius: 14px;
background: rgba(255, 255, 255, 0.68);
color: #4a3830;
text-align: left;
cursor: pointer;
transition: all 0.15s ease;
}
.provider-option:hover {
background: rgba(255, 239, 223, 0.7);
border-color: rgba(220, 150, 110, 0.45);
}
.provider-option.active {
border-color: rgba(220, 150, 110, 0.55);
background: rgba(255, 230, 200, 0.4);
box-shadow: inset 0 0 0 1px rgba(220, 150, 110, 0.18);
}
.provider-option strong {
font: 600 13px/1.2 'SF Pro Rounded', system-ui, sans-serif;
}
.provider-option span {
font: 12px/1.45 'SF Pro Rounded', system-ui, sans-serif;
color: rgba(90, 65, 50, 0.72);
}
</style>
</head>
<body>
Expand Down Expand Up @@ -149,6 +184,10 @@ <h2>Settings</h2>
<label>Character</label>
<div class="sprite-options" id="sprite-options"></div>
</div>
<div class="settings-section">
<label>AI Provider</label>
<div class="provider-options" id="settings-ai-provider-options"></div>
</div>
</div>
<script type="module" src="/src/settings-ui.js"></script>
</body>
Expand Down
12 changes: 12 additions & 0 deletions src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
"args": true,
"sidecar": false
},
{
"name": "gemini",
"cmd": "gemini",
"args": true,
"sidecar": false
},
{
"name": "bash",
"cmd": "bash",
Expand Down Expand Up @@ -63,6 +69,12 @@
"args": true,
"sidecar": false
},
{
"name": "gemini",
"cmd": "gemini",
"args": true,
"sidecar": false
},
{
"name": "bash",
"cmd": "bash",
Expand Down
17 changes: 16 additions & 1 deletion src/__tests__/brain.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ vi.mock('@tauri-apps/plugin-shell', () => ({
Command: { create: () => ({ execute: () => Promise.resolve({ stdout: '', code: 0 }) }) },
}));

const { parseResponse } = await import('../brain.js');
const { parseResponse, normalizeAiProvider, getSupportedAiProviders } = await import('../brain.js');

describe('parseResponse', () => {
it('extracts text, state, reactions from clean JSON', () => {
Expand Down Expand Up @@ -69,3 +69,18 @@ describe('parseResponse', () => {
expect(result.text.length).toBeLessThanOrEqual(120);
});
});

describe('AI provider helpers', () => {
it('normalizes supported providers and rejects unknown values', () => {
expect(normalizeAiProvider('Claude')).toBe('claude');
expect(normalizeAiProvider(' gemini ')).toBe('gemini');
expect(normalizeAiProvider('openai')).toBe('');
expect(normalizeAiProvider('')).toBe('');
});

it('exposes both supported AI providers', () => {
var providers = getSupportedAiProviders().map(function(provider) { return provider.id; });
expect(providers).toContain('claude');
expect(providers).toContain('gemini');
});
});
Loading
Loading