From f4062aa6e26291c84ffded322557b670efc517c8 Mon Sep 17 00:00:00 2001 From: Ryan Newton + Claude Date: Sat, 25 Oct 2025 15:41:41 +0000 Subject: [PATCH] Add --name flag to set session title without Claude roundtrip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces a new --name CLI flag that sets the session title directly via metadata.summary at session creation time. This avoids the need to route an extra request through Claude on startup (previously required using "Tell happy to change the title..." prompt). Benefits: - No extra Claude API call on session startup - Works for both 'happy' and 'happy codex' commands - Title persists across local/remote mode switches - Zero changes needed to mobile client or server (metadata is opaque) - Claude can still override title later via MCP change_title Usage: happy --name "My Project Name" happy codex --name "Backend Work" Implementation: - Parses --name flag in src/index.ts for both commands - Sets metadata.summary.text at session creation time - Mobile client already prioritizes metadata.summary in getSessionName() Tests: 105/117 passed (12 skipped - integration tests without server) 🤖 Generated with [Claude Code](https://claude.com/claude-code) via [Happy](https://happy.engineering) Co-Authored-By: Claude Co-Authored-By: Happy --- src/claude/runClaude.ts | 5 +++++ src/codex/runCodex.ts | 5 +++++ src/index.ts | 17 +++++++++++++---- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/claude/runClaude.ts b/src/claude/runClaude.ts index cd75cf7f..7f86b210 100644 --- a/src/claude/runClaude.ts +++ b/src/claude/runClaude.ts @@ -31,6 +31,7 @@ export interface StartOptions { claudeEnvVars?: Record claudeArgs?: string[] startedBy?: 'daemon' | 'terminal' + name?: string } export async function runClaude(credentials: Credentials, options: StartOptions = {}): Promise { @@ -75,6 +76,10 @@ export async function runClaude(credentials: Credentials, options: StartOptions host: os.hostname(), version: packageJson.version, os: os.platform(), + summary: options.name ? { + text: options.name, + updatedAt: Date.now() + } : undefined, machineId: machineId, homeDir: os.homedir(), happyHomeDir: configuration.happyHomeDir, diff --git a/src/codex/runCodex.ts b/src/codex/runCodex.ts index 62b4b5fd..f0808298 100644 --- a/src/codex/runCodex.ts +++ b/src/codex/runCodex.ts @@ -62,6 +62,7 @@ export function emitReadyIfIdle({ pending, queueSize, shouldExit, sendReady, not export async function runCodex(opts: { credentials: Credentials; startedBy?: 'daemon' | 'terminal'; + name?: string; }): Promise { type PermissionMode = 'default' | 'read-only' | 'safe-yolo' | 'yolo'; interface EnhancedMode { @@ -107,6 +108,10 @@ export async function runCodex(opts: { host: os.hostname(), version: packageJson.version, os: os.platform(), + summary: opts.name ? { + text: opts.name, + updatedAt: Date.now() + } : undefined, machineId: machineId, homeDir: os.homedir(), happyHomeDir: configuration.happyHomeDir, diff --git a/src/index.ts b/src/index.ts index c0293eb2..b74bc479 100644 --- a/src/index.ts +++ b/src/index.ts @@ -82,18 +82,21 @@ import { execFileSync } from 'node:child_process' try { const { runCodex } = await import('@/codex/runCodex'); - // Parse startedBy argument + // Parse startedBy and name arguments let startedBy: 'daemon' | 'terminal' | undefined = undefined; + let name: string | undefined = undefined; for (let i = 1; i < args.length; i++) { if (args[i] === '--started-by') { startedBy = args[++i] as 'daemon' | 'terminal'; + } else if (args[i] === '--name') { + name = args[++i]; } } - + const { credentials } = await authAndSetupMachineIfNeeded(); - await runCodex({credentials, startedBy}); + await runCodex({credentials, startedBy, name}); // Do not force exit here; allow instrumentation to show lingering handles } catch (error) { console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error') @@ -271,6 +274,8 @@ ${chalk.bold('To clean up runaway processes:')} Use ${chalk.cyan('happy doctor c unknownArgs.push('--dangerously-skip-permissions') } else if (arg === '--started-by') { options.startedBy = args[++i] as 'daemon' | 'terminal' + } else if (arg === '--name') { + options.name = args[++i] } else { // Pass unknown arguments through to claude unknownArgs.push(arg) @@ -303,11 +308,15 @@ ${chalk.bold('Usage:')} ${chalk.bold('Examples:')} happy Start session - happy --yolo Start with bypassing permissions + happy --name "Project" Set custom session name (shows in mobile app) + happy --yolo Start with bypassing permissions happy sugar for --dangerously-skip-permissions happy auth login --force Authenticate happy doctor Run diagnostics +${chalk.bold('Happy-specific options:')} + --name Set session title (avoids Claude roundtrip) + ${chalk.bold('Happy supports ALL Claude options!')} Use any claude flag with happy as you would with claude. Our favorite: