npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

cyrus-gemini-runner

v0.2.5

Published

Gemini CLI process spawning and management for Cyrus

Readme

@cyrus-ai/gemini-runner

A TypeScript wrapper around the Gemini CLI that provides a provider-agnostic interface for running Gemini AI sessions. This package mirrors the architecture of @cyrus-ai/claude-runner while adapting to Gemini CLI's streaming format and session management.

Overview

The gemini-runner package provides two main classes:

  • GeminiRunner: Full-featured wrapper around Gemini CLI with streaming support, event handling, and message format conversion
  • SimpleGeminiRunner: Simplified interface for enumerated response use cases (yes/no, multiple choice, etc.)

Both classes implement standard interfaces from cyrus-core, making them interchangeable with other AI runner implementations like ClaudeRunner.

Installation

pnpm add @cyrus-ai/gemini-runner

Prerequisites

  1. Gemini CLI: Install the Gemini CLI and ensure it's in your PATH:

    # Installation instructions depend on your platform
    # Verify installation:
    gemini --version
  2. Gemini API Credentials: Configure your Gemini API credentials according to the Gemini CLI documentation.

Quick Start

Basic Usage with GeminiRunner

import { GeminiRunner } from '@cyrus-ai/gemini-runner';

const runner = new GeminiRunner({
  cyrusHome: '/path/to/.cyrus',
  workingDirectory: '/path/to/project',
  model: 'gemini-2.5-flash',
  autoApprove: true  // Enable --yolo flag
});

// Listen for events
runner.on('message', (message) => {
  console.log('New message:', message);
});

runner.on('complete', (messages) => {
  console.log('Session complete! Total messages:', messages.length);
});

// Start a session
await runner.start("Analyze this codebase and suggest improvements");

// Get all messages
const messages = runner.getMessages();

Simple Enumerated Responses with SimpleGeminiRunner

import { SimpleGeminiRunner } from '@cyrus-ai/gemini-runner';

const runner = new SimpleGeminiRunner({
  validResponses: ['yes', 'no', 'maybe'],
  cyrusHome: '/path/to/.cyrus',
  workingDirectory: '/path/to/project',
  model: 'gemini-2.5-flash',
  maxTurns: 5,
  onProgress: (event) => {
    console.log(`Progress: ${event.type}`);
  }
});

const result = await runner.query('Is this code correct?');
console.log(`Response: ${result.response}`); // Guaranteed to be 'yes', 'no', or 'maybe'
console.log(`Cost: $${result.cost.toFixed(4)}`);

Core Concepts

GeminiRunner

GeminiRunner is the primary class for interacting with the Gemini CLI. It:

  • Spawns the Gemini CLI process in headless mode (--output-format stream-json)
  • Translates between Gemini CLI's JSON streaming format and Claude SDK message types
  • Manages session lifecycle (start, stop, status)
  • Provides event-based communication
  • Creates detailed logs in both NDJSON and human-readable formats
  • Supports both string prompts and streaming prompts

Key Features:

  • Provider-agnostic interface: Implements IAgentRunner for compatibility with other runners
  • Event system: Subscribe to message, error, complete, and streamEvent events
  • Streaming support: Add messages incrementally with addStreamMessage()
  • Session management: Track session state with isRunning() and getMessages()
  • MCP server support: Configure Model Context Protocol servers (inherited from IAgentRunner)

SimpleGeminiRunner

SimpleGeminiRunner extends SimpleAgentRunner to provide a streamlined interface for enumerated response scenarios:

  • Validates responses against a predefined set of valid answers
  • Handles timeout management automatically
  • Cleans responses (removes markdown, code blocks, quotes)
  • Emits progress events (thinking, tool-use, validating)
  • Builds system prompts with valid response constraints
  • Extracts cost information from result messages

Use Cases:

  • Yes/No questions
  • Multiple choice selection
  • Status confirmations
  • Classification tasks

API Reference

GeminiRunner

Constructor Options

interface GeminiRunnerConfig extends IAgentRunnerConfig {
  // Gemini-specific options
  geminiPath?: string;        // Path to gemini CLI (default: 'gemini')
  autoApprove?: boolean;      // Enable --yolo flag (default: false)
  approvalMode?: 'auto_edit' | 'auto' | 'manual';  // Approval mode
  debug?: boolean;            // Enable debug output

  // Inherited from IAgentRunnerConfig
  model?: string;             // Model to use (e.g., 'gemini-2.5-flash')
  cyrusHome: string;          // Home directory for logs
  workingDirectory: string;   // Working directory for Gemini
  systemPrompt?: string;      // System prompt for session
  mcpServers?: MCPServerConfig[];  // MCP server configurations
  env?: Record<string, string>;    // Environment variables
}

Methods

start(prompt: string): Promise

  • Start a new session with a string prompt
  • Throws if a session is already running

startStreaming(initialPrompt?: string): Promise

  • Start a new session with streaming input capability
  • Optionally provide an initial prompt
  • Use addStreamMessage() to add more messages
  • Call completeStream() when done

addStreamMessage(content: string): void

  • Add a message to an active streaming session
  • Must be called after startStreaming()

completeStream(): void

  • Signal the end of streaming input
  • Session will continue processing

stop(): Promise

  • Terminate the current session
  • Kills the Gemini CLI process

getMessages(): SDKMessage[]

  • Retrieve all messages from the current session
  • Returns both user and assistant messages

isRunning(): boolean

  • Check if a session is currently active

Events

'message': (message: SDKMessage) => void

  • Emitted when a new message is received from Gemini

'error': (error: Error) => void

  • Emitted when an error occurs

'complete': (messages: SDKMessage[]) => void

  • Emitted when the session completes successfully

'streamEvent': (event: GeminiStreamEvent) => void

  • Emitted for raw Gemini CLI events (for debugging)

SimpleGeminiRunner

Constructor Options

interface SimpleGeminiRunnerConfig {
  validResponses: string[];   // REQUIRED: Valid response options
  cyrusHome: string;          // REQUIRED: Home directory for logs
  workingDirectory?: string;  // Working directory (default: cwd)
  model?: string;             // Model to use (default: 'gemini-2.5-flash')
  systemPrompt?: string;      // Additional system prompt
  maxTurns?: number;          // Max conversation turns (default: 10)
  timeout?: number;           // Timeout in ms (default: 300000)
  onProgress?: (event: SimpleAgentProgressEvent) => void;
  mcpServers?: MCPServerConfig[];
  env?: Record<string, string>;
}

Methods

query(prompt: string, options?: SimpleAgentQueryOptions): Promise

  • Execute a query and get a validated response
  • Returns an object with response, cost, and messages
interface SimpleAgentResult {
  response: string;           // Validated response (one of validResponses)
  cost: number;               // Total cost in dollars
  messages: SDKMessage[];     // All messages from session
}

Adapter Pattern

The package uses an adapter pattern to translate between Gemini CLI's JSON streaming format and Claude SDK message types. This allows the runner to integrate seamlessly with other parts of the Cyrus ecosystem.

Message Format Conversion

Gemini Stream Events → SDK Messages:

  • GeminiInitEvent → Session ID extraction
  • GeminiMessageEvent (user) → SDKUserMessage
  • GeminiMessageEvent (assistant) → SDKAssistantMessage
  • GeminiToolUseEventSDKAssistantMessage with tool_use content block
  • GeminiToolResultEventSDKUserMessage with tool_result content block
  • GeminiResultEvent (success) → SDKAssistantMessage with final response

Key Differences from Claude:

  • Gemini uses simple {type, role, content} for messages
  • SDK uses {type, message: {role, content}, session_id, ...} structure
  • Tool use IDs are generated client-side for Gemini (${tool_name}_${timestamp})
  • Session ID is "pending" until init event is received from Gemini CLI

Differences from ClaudeRunner

While GeminiRunner mirrors the architecture of ClaudeRunner, there are some key differences:

| Aspect | ClaudeRunner | GeminiRunner | |--------|--------------|--------------| | CLI Command | claude --output-format ndjson | gemini --output-format stream-json | | Session ID | Generated client-side or via --continue | Assigned by CLI in init event | | Stream Format | NDJSON with SDK-compatible messages | Custom JSON format requiring adapter | | Tool Use IDs | Native SDK format | Generated client-side | | Auto-Approval | Approval callbacks or flags | --yolo flag and --approval-mode | | System Prompts | Native CLI support | Accepted in config (CLI handling TBD) |

Supported Models

The package supports Gemini 2.5 and later models. Common model identifiers:

  • gemini-2.5-flash - Fast, cost-effective model
  • gemini-2.5-pro - Advanced reasoning and complex tasks
  • Additional models as supported by the Gemini CLI

Specify the model in the configuration:

const runner = new GeminiRunner({
  model: 'gemini-2.5-flash',
  // ... other options
});

Configuration

Environment Variables

The package respects standard Gemini CLI environment variables for API credentials. Refer to the Gemini CLI documentation for setup instructions.

Log Files

Both runners create detailed logs in the cyrusHome directory:

  • {cyrusHome}/logs/{workspaceName}/{sessionId}.ndjson - NDJSON event log
  • {cyrusHome}/logs/{workspaceName}/{sessionId}.log - Human-readable log

MCP Server Configuration

Configure Model Context Protocol servers for enhanced capabilities:

const runner = new GeminiRunner({
  cyrusHome: '/path/to/.cyrus',
  workingDirectory: '/path/to/project',
  mcpServers: [
    {
      name: 'linear',
      type: 'http',
      url: 'https://mcp.linear.app',
      headers: {
        'Authorization': 'Bearer YOUR_TOKEN'
      }
    }
  ]
});

Examples

See the examples/ directory for complete working examples:

  • basic-usage.ts - Basic GeminiRunner usage with event handling
  • simple-agent-example.ts - SimpleGeminiRunner for enumerated responses

To run the examples:

cd packages/gemini-runner
pnpm build
node examples/basic-usage.js  # Or .ts with ts-node

Troubleshooting

Common Issues

"gemini: command not found"

  • Ensure the Gemini CLI is installed and in your PATH
  • Or specify the full path in the config: geminiPath: '/path/to/gemini'

"Session already running"

  • You can only have one active session per GeminiRunner instance
  • Call stop() before starting a new session, or create a new instance

"Invalid response from Gemini"

  • Check that your validResponses match the expected output format
  • Use the onProgress callback to see raw responses during validation
  • Verify the system prompt is being respected by the model

Empty messages array

  • The session may have errored before producing any messages
  • Check the error event for details
  • Review the log files in {cyrusHome}/logs/

Response validation fails with SimpleGeminiRunner

  • Increase maxTurns to allow more attempts
  • Simplify your validResponses list
  • Check if the model is producing markdown or code blocks (automatically cleaned)
  • Use onProgress to debug what responses are being received

Timeout errors

  • Increase the timeout option (default: 300000ms / 5 minutes)
  • Check if the Gemini CLI is hanging on approval prompts (use autoApprove: true)
  • Verify your working directory is accessible and doesn't have permission issues

Debug Mode

Enable debug mode for detailed logging:

const runner = new GeminiRunner({
  debug: true,
  // ... other options
});

Log Analysis

Check the NDJSON log files for detailed event information:

cat ~/.cyrus/logs/my-workspace/{session-id}.ndjson | jq .

Or the human-readable log:

cat ~/.cyrus/logs/my-workspace/{session-id}.log

Development

Building

pnpm build

Running Tests

pnpm test        # Watch mode
pnpm test:run    # Run once

Type Checking

pnpm typecheck

Related Packages

  • @cyrus-ai/core - Core types and interfaces
  • @cyrus-ai/claude-runner - Claude CLI wrapper (similar architecture)
  • @cyrus-ai/simple-agent-runner - Abstract base for simple enumerated responses

License

MIT

Support

For issues and feature requests, please use the project's issue tracker.