Skip to content
Open
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
32 changes: 26 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# Happy

Code on the go controlling claude code from your mobile device.
Code on the go controlling AI coding assistants from your mobile device.

Free. Open source. Code anywhere.

## Supported AI Assistants

- **Claude Code** (Anthropic) - `happy` or `happy claude`
- **Codex** (OpenAI) - `happy codex`
- **Gemini CLI** (Google) - `happy gemini` ✨ NEW

## Installation

```bash
Expand All @@ -12,19 +18,31 @@ npm install -g happy-coder

## Usage

### Claude Code (Default)
```bash
happy
```

### OpenAI Codex
```bash
happy codex
```

### Google Gemini CLI
```bash
happy gemini
```

This will:
1. Start a Claude Code session
1. Start an AI coding session
2. Display a QR code to connect from your mobile device
3. Allow real-time session sharing between Claude Code and your mobile app
3. Allow real-time session sharing between the AI and your mobile app

## Commands

- `happy auth` – Manage authentication
- `happy codex` – Start Codex mode
- `happy codex` – Start Codex mode (OpenAI)
- `happy gemini` – Start Gemini mode (Google)
- `happy connect` – Store AI vendor API keys in Happy cloud
- `happy notify` – Send a push notification to your devices
- `happy daemon` – Manage background service
Expand All @@ -34,7 +52,7 @@ This will:

- `-h, --help` - Show help
- `-v, --version` - Show version
- `-m, --model <model>` - Claude model to use (default: sonnet)
- `-m, --model <model>` - Model to use (e.g., sonnet, gemini-2.5-pro)
- `-p, --permission-mode <mode>` - Permission mode: auto, default, or plan
- `--claude-env KEY=VALUE` - Set environment variable for Claude Code (e.g., for [claude-code-router](https://github.com/musistudio/claude-code-router))
- `--claude-arg ARG` - Pass additional argument to Claude CLI
Expand All @@ -53,7 +71,9 @@ This will:
- Required by `eventsource-parser@3.0.5`, which is required by
`@modelcontextprotocol/sdk`, which we used to implement permission forwarding
to mobile app
- Claude CLI installed & logged in (`claude` command available in PATH)
- For Claude: Claude CLI installed & logged in (`claude` command available in PATH)
- For Codex: OpenAI Codex CLI installed
- For Gemini: Gemini CLI installed (`npm install -g @google/gemini-cli`) & authenticated with Google account

## License

Expand Down
37 changes: 34 additions & 3 deletions src/claude/utils/startHappyServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export async function startHappyServer(client: ApiSessionClient) {
summary: title,
leafUuid: randomUUID()
});

return { success: true };
} catch (error) {
return { success: false, error: String(error) };
Expand All @@ -48,7 +48,7 @@ export async function startHappyServer(client: ApiSessionClient) {
}, async (args) => {
const response = await handler(args.title);
logger.debug('[happyMCP] Response:', response);

if (response.success) {
return {
content: [
Expand All @@ -72,6 +72,34 @@ export async function startHappyServer(client: ApiSessionClient) {
}
});

// Add notify_user tool
mcp.registerTool('notify_user', {
description: 'Send a notification message to the user on their mobile device',
title: 'Notify User',
inputSchema: {
message: z.string().describe('The message to send to the user'),
level: z.enum(['info', 'warning', 'error']).optional().describe('Notification level'),
},
}, async (args) => {
try {
// Send as a session event/message
client.sendSessionEvent({
type: 'message',
message: `📱 [IDE Notification] ${args.level ? `[${args.level.toUpperCase()}] ` : ''}${args.message}`
});

return {
content: [{ type: 'text', text: `Notification sent to user device: "${args.message}"` }],
isError: false,
};
} catch (error) {
return {
content: [{ type: 'text', text: `Failed to send notification: ${String(error)}` }],
isError: true,
};
}
});

const transport = new StreamableHTTPServerTransport({
// NOTE: Returning session id here will result in claude
// sdk spawn to fail with `Invalid Request: Server already initialized`
Expand Down Expand Up @@ -101,9 +129,12 @@ export async function startHappyServer(client: ApiSessionClient) {
});
});

// Write URL to a file so other tools can find it easily if needed, or just log it
// For now, we rely on the main process logging it

return {
url: baseUrl.toString(),
toolNames: ['change_title'],
toolNames: ['change_title', 'notify_user'],
stop: () => {
logger.debug('[happyMCP] Stopping server');
mcp.close();
Expand Down
7 changes: 7 additions & 0 deletions src/gemini/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Gemini module exports
*/

export { runGemini, type GeminiStartOptions } from './runGemini';
export * from './types';
export * from './sdk';
Loading