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 🙏

© 2026 – Pkg Stats / Ryan Hefner

gemini-reverse

v1.0.12

Published

Unofficial Node.js client for gemini.google.com — inspired by Gemini-API (Python). Supports streaming, chat sessions, gems, file uploads, and TypeScript.

Readme

Banner

Gemini-Reverse

An unofficial Node.js client for Google Gemini, inspired by Gemini-API — a Python reverse engineering project by @HanaokaYuzu.

Language / Bahasa: English · Indonesia  |  Changelog: EN · IDN

Features

  • Persistent Cookies — Automatically refreshes cookies in the background with jitter to prevent synchronized requests. Optimized for always-on services.
  • Image Generation — Natively supports generating and editing images with natural language.
  • Video & Audio Generation — Supports generating videos and audio/music content natively.
  • Deep Research — Full deep research workflow with plan creation, status polling, and result retrieval.
  • Extended Thinking — Enables deeper reasoning mode on supported models.
  • System Prompt — Supports customizing the model's system prompt with Gemini Gems.
  • Extension Support — Supports generating content with Gemini extensions such as YouTube and Gmail.
  • Classified Outputs — Categorizes text, thoughts, images, videos, and audio in the response.
  • Streaming Mode — Supports stream generation with an incremental stateful frame parser, yielding partial outputs as they are generated.
  • Dynamic Model Discovery — Automatically discovers available models from your account at initialization.
  • Quota & Usage Info — Exposes account quota, compute usage, and abuse status after initialization.
  • Activity Watchdog — Background heartbeat task that keeps the session alive automatically.
  • TypeScript Support — Full TypeScript type declarations included out of the box.

Table of Contents

Installation

npm install gemini-reverse

Authentication

  • Go to gemini.google.com and log in with your Google account
  • Press F12 to open DevTools, go to the Application tab → Cookieshttps://gemini.google.com
  • Copy the value of __Secure-1PSID (and optionally __Secure-1PSIDTS)

__Secure-1PSIDTS is optional — the client will attempt to refresh and cache it automatically after the first successful initialization.

Usage

Initialization

Import the package and initialize a client with your cookies. After a successful initialization, the client will automatically refresh __Secure-1PSIDTS in the background with random jitter, and start a heartbeat watchdog to keep the session alive.

const { GeminiClient } = require('gemini-reverse');

const client = new GeminiClient({
    secure_1psid: 'YOUR_SECURE_1PSID',
    secure_1psidts: 'YOUR_SECURE_1PSIDTS', // optional
    proxy: null,                            // optional, e.g. 'http://host:port'
});

await client.init({
    timeout: 300000,        // request timeout in ms, default 300000
    autoClose: false,       // auto-close client after inactivity
    closeDelay: 300000,     // inactivity delay before closing in ms
    autoRefresh: true,      // auto-refresh cookies + start activity watchdog
    refreshInterval: 540000 // cookie refresh interval in ms
});

autoClose and closeDelay are optional arguments for automatically closing the client after a period of inactivity. In an always-on service like a chatbot, it is recommended to set autoClose to true with a reasonable closeDelay value for better resource management.

Generate Content

Ask a single-turn question by calling generateContent, which returns a ModelOutput object containing the generated text, images, thoughts, and conversation metadata.

const response = await client.generateContent({ prompt: 'Hello World!' });
console.log(response.text);

Generate Content with Files

Gemini supports file input, including images and documents. Pass an array of file paths or Buffer objects alongside your text prompt.

const response = await client.generateContent({
    prompt: 'Describe the contents of these files.',
    files: ['./document.pdf', './photo.png'],
});
console.log(response.text);

Conversations Across Multiple Turns

Use startChat to create a ChatSession object and send messages through it. The conversation history is handled automatically and updated after each turn.

const chat = client.startChat();

const res1 = await chat.sendMessage({ prompt: 'My name is Alice.' });
console.log(res1.text);

const res2 = await chat.sendMessage({ prompt: 'What is my name?' });
console.log(res2.text); // remembers context

Continue Previous Conversations

Pass a previous ChatSession's metadata to startChat to resume a conversation. You can persist the metadata to a file or database to restore it across process restarts.

const chat = client.startChat();
await chat.sendMessage({ prompt: 'Fine weather today.' });

// Save the session metadata
const savedMetadata = chat.metadata;
const savedCid = chat.cid;

// Resume in a new session
const previousChat = client.startChat({ metadata: savedMetadata });
const response = await previousChat.sendMessage({ prompt: 'What was my previous message?' });
console.log(response.text);

Read Conversation History

Fetch the full conversation history of a chat by calling readChat with the chat ID. It returns a ChatHistory object containing a list of ChatTurn objects ordered from newest to oldest.

const chat = client.startChat();
await chat.sendMessage({ prompt: 'What is the capital of France?' });

const history = await client.readChat(chat.cid);
if (history) {
    for (const turn of history.turns) {
        console.log(`[${turn.role.toUpperCase()}] ${turn.text}`);
    }
}

You can also read the history directly from the session:

const history = await chat.readHistory(10); // fetch last 10 turns

List Recent Chats

Use listChats to get a list of recent chat sessions cached at initialization.

const chats = client.listChats();
if (chats) {
    for (const info of chats) {
        console.log(`${info.cid}: ${info.title} (pinned: ${info.is_pinned})`);
    }
}

Delete a Conversation

Delete a specific chat from Gemini history on the server by calling deleteChat with the chat ID.

const chat = client.startChat();
await chat.sendMessage({ prompt: 'This is a temporary conversation.' });

await client.deleteChat(chat.cid);
console.log(`Chat deleted: ${chat.cid}`);

Temporary Mode

Pass temporary: true to generateContent or sendMessage to prevent the conversation from being saved to Gemini history.

const response = await client.generateContent({
    prompt: 'Hello World!',
    temporary: true,
});
console.log(response.text);

// Also works in chat sessions
const chat = client.startChat();
await chat.sendMessage({ prompt: 'Fine weather today.', temporary: false });
const res2 = await chat.sendMessage({ prompt: "What's my last message?", temporary: true });
console.log(res2.text);

Streaming Mode

For longer responses, use streaming mode to receive partial outputs as they are generated. The response uses a stateful StreamingFrameParser internally, so partial frames are buffered efficiently across chunks without rescanning. The text_delta attribute contains only the new characters received since the last yield.

for await (const chunk of client.generateContentStream({
    prompt: "What's the difference between 'await' and 'async for'?",
})) {
    process.stdout.write(chunk.text_delta);
}
console.log();

Streaming also works inside a chat session:

const chat = client.startChat();
for await (const chunk of chat.sendMessageStream({ prompt: 'Tell me a long story.' })) {
    process.stdout.write(chunk.text_delta);
}

Extended Thinking

Pass extended_thinking: true to enable deeper reasoning mode. This causes the model to spend more time planning before responding. Supported on Pro and Advanced tier models.

const { Model } = require('gemini-reverse');

const response = await client.generateContent({
    prompt: 'Solve this step by step: If a train travels at 120 km/h and needs to cover 450 km, how long does it take?',
    model: Model.ADVANCED_PRO,
    extended_thinking: true,
});

if (response.thoughts) {
    console.log('Thinking process:', response.thoughts);
}
console.log('Answer:', response.text);

Also works in streaming and chat:

const chat = client.startChat({ model: Model.ADVANCED_FLASH });
const res = await chat.sendMessage({
    prompt: 'Explain the P vs NP problem.',
    extended_thinking: true,
});
console.log(res.text);

Select Language Model

Specify which language model to use by passing a model argument. Available models are discovered dynamically at init time based on your account tier.

const { Model } = require('gemini-reverse');

// Using a built-in constant
const response1 = await client.generateContent({
    prompt: 'What is your model version?',
    model: Model.BASIC_FLASH,
});

// Using a model name string (case-insensitive)
const chat = client.startChat({ model: 'gemini-3-pro' });

// Using a custom model header dict
const chat2 = client.startChat({
    model: {
        model_name: 'custom',
        model_header: {
            'x-goog-ext-525001261-jspb': '[1,null,null,null,"MODEL_ID",null,null,0,[4,5,6,8],null,null,1,null,null,1]',
            'x-goog-ext-73010989-jspb': '[0]',
            'x-goog-ext-73010990-jspb': '[0,0,0]',
        },
    },
});

Built-in model constants:

| Constant | model_name | Notes | |---|---|---| | Model.UNSPECIFIED | unspecified | Default, lets Gemini choose | | Model.BASIC_PRO | gemini-3-pro | Free tier | | Model.BASIC_FLASH | gemini-3-flash | Free tier, fastest | | Model.BASIC_LITE | gemini-3-lite | Free tier, lightweight | | Model.PLUS_PRO | gemini-3-pro-plus | Plus tier | | Model.PLUS_FLASH | gemini-3-flash-plus | Plus tier | | Model.PLUS_LITE | gemini-3-lite-plus | Plus tier | | Model.ADVANCED_PRO | gemini-3-pro-advanced | Advanced tier | | Model.ADVANCED_FLASH | gemini-3-flash-advanced | Advanced tier | | Model.ADVANCED_LITE | gemini-3-lite-advanced | Advanced tier |

List Available Models

The client dynamically discovers which models your account can access during initialization. Use listModels to inspect them.

await client.init();

const models = client.listModels();
if (models) {
    for (const model of models) {
        console.log(`${model.model_id} → ${model.model_name || model.display_name}`);
        console.log(`  capacity: ${model.capacity}, model_number: ${model.model_number}, advanced_only: ${model.advanced_only}`);
    }
}

Apply System Prompt with Gemini Gems

System prompts can be applied to conversations via Gemini Gems. Pass the gem argument to generateContent or startChat — it can be a Gem object or a gem ID string.

// Fetch all gems for the account
await client.fetchGems();
const gems = client.gems;

// Get a specific gem
const codingPartner = gems.get({ name: 'Coding partner' });

const response = await client.generateContent({
    prompt: "What's your system prompt?",
    gem: codingPartner,
});
console.log(response.text);

// Use a gem in a multi-turn chat
const chat = client.startChat({ gem: codingPartner });
const res2 = await chat.sendMessage({ prompt: 'Help me write a binary search.' });
console.log(res2.text);

There are some system predefined gems that are hidden by default. Use fetchGems({ includeHidden: true }) to include them.

Manage Custom Gems

You can create, update, and delete custom gems programmatically. Predefined system gems cannot be modified.

Create a Custom Gem

const newGem = await client.createGem({
    name: 'Python Tutor',
    prompt: 'You are a helpful Python programming tutor. Always provide runnable code examples.',
    description: 'A specialized gem for Python programming',
});

console.log(`Created: ${newGem.id}`);

// Use the new gem immediately
const response = await client.generateContent({
    prompt: 'Explain list comprehensions.',
    gem: newGem,
});
console.log(response.text);

Update an Existing Gem

When updating a gem, all parameters (name, prompt, description) must be provided even if only one is changing.

await client.fetchGems();
const pythonTutor = client.gems.get({ name: 'Python Tutor' });

const updatedGem = await client.updateGem({
    gem: pythonTutor,
    name: 'Advanced Python Tutor',
    prompt: 'You are an expert Python programming tutor. Focus on performance and best practices.',
    description: 'An advanced Python programming assistant',
});

console.log(`Updated: ${updatedGem.id}`);

Delete a Custom Gem

await client.fetchGems();
const gemToDelete = client.gems.get({ name: 'Advanced Python Tutor' });

await client.deleteGem(gemToDelete); // can also pass a gem ID string
console.log(`Deleted: ${gemToDelete.name}`);

Retrieve Model's Thought Process

When using thinking-capable models, the model's internal reasoning is exposed via response.thoughts.

const response = await client.generateContent({
    prompt: 'What is 17 × 23?',
    model: Model.BASIC_FLASH,
});

if (response.thoughts) {
    console.log('Thoughts:', response.thoughts);
}
console.log('Answer:', response.text);

Retrieve Images in Response

Images in the response are stored as a list of Image objects accessible via response.images. Each image has a url, title, and alt description.

const response = await client.generateContent({ prompt: 'Send me some pictures of cats.' });

for (const image of response.images) {
    console.log(`${image.title}: ${image.url}`);
    console.log(`Alt: ${image.alt}`);
}

Generate and Edit Images

Ask Gemini to generate or edit images using natural language. Generated images are returned as GeneratedImage objects and can be saved to disk.

Google has limitations on image generation availability that vary by region and account type. Users under 18 cannot use this feature.

const response = await client.generateContent({
    prompt: 'Generate a photo-realistic image of a cat in a space suit.',
});

for (let i = 0; i < response.images.length; i++) {
    const image = response.images[i];
    const savedPath = await image.save({
        path: './temp',
        filename: `cat_space_${i}.png`,
        verbose: true,
    });
    console.log(`Saved to: ${savedPath}`);
}

When asking Gemini to "send" images, it returns web images (WebImage). When asking to "generate" images, it returns AI-generated images (GeneratedImage). Both are automatically categorized in response.images.

Retrieve Videos and Audio

Gemini can generate short videos and audio/music. These are returned as GeneratedVideo and GeneratedMedia objects in response.videos and response.media respectively.

You may need an active Gemini subscription to access video and audio generation.

// Generate a video
const videoResponse = await client.generateContent({
    prompt: 'Generate a short video of waves on a beach.',
});

for (const video of videoResponse.videos) {
    const result = await video.save({ savePath: './temp', verbose: true });
    console.log('Video:', result.video);
    console.log('Thumbnail:', result.video_thumbnail);
}

// Generate audio/music
const mediaResponse = await client.generateContent({
    prompt: 'Compose a short calming piano melody.',
});

for (const media of mediaResponse.media) {
    // downloadType: 'audio' | 'video' | 'both' (default)
    const result = await media.save({ savePath: './temp', downloadType: 'both', verbose: true });
    console.log('MP3:', result.audio);
    console.log('MP4:', result.video);
}

GeneratedMedia.save() accepts a downloadType parameter: "audio", "video", or "both" (default). The save method polls automatically if content is still generating (HTTP 206).

Generate Content with Gemini Extensions

To use Gemini extensions (Gmail, YouTube, etc.), you must first activate them on the Gemini website. Reference them in prompts with the @ prefix or in natural language.

You must have Gemini Apps Activity enabled in your account to use extensions.

const gmailResponse = await client.generateContent({
    prompt: "@Gmail What's the latest message in my mailbox?",
});
console.log(gmailResponse.text);

const youtubeResponse = await client.generateContent({
    prompt: "@YouTube What's the latest video from Fireship?",
});
console.log(youtubeResponse.text);

Check and Switch to Other Reply Candidates

A Gemini response sometimes contains multiple reply candidates with different generated content. You can inspect all candidates and choose one to continue the conversation flow.

const chat = client.startChat();
const response = await chat.sendMessage({ prompt: 'Recommend a science fiction book.' });

// List all candidates
response.candidates.forEach((candidate, i) => {
    console.log(`[${i}] ${candidate.text.slice(0, 80)}...`);
});

if (response.candidates.length > 1) {
    // Choose the second candidate to continue from
    chat.chooseCandidate(1);

    const followup = await chat.sendMessage({ prompt: 'Tell me more about it.' });
    console.log(followup.text);
} else {
    console.log('Only one candidate available.');
}

Deep Research

Gemini's deep research feature is an autonomous agent that browses the web, analyzes sources, and produces a comprehensive report.

You may need an active Gemini subscription to access deep research.

Quick one-call method:

const result = await client.deepResearch(
    'Compare the top 3 cloud providers and their AI offerings',
    10000,   // poll interval in ms
    600000,  // timeout in ms
    (status) => console.log(`Status: ${status.state} — ${status.notes.slice(0, 1).join(', ')}`),
);

console.log(`Done: ${result.done}`);
console.log(result.text);

Step-by-step workflow for more control:

// Step 1: Create a research plan
const plan = await client.createDeepResearchPlan(
    'What are the latest advancements in quantum computing?'
);

console.log(`Title: ${plan.title}`);
console.log(`ETA: ${plan.eta_text}`);
for (const step of plan.steps) {
    console.log(`  - ${step}`);
}

// Step 2: Start the research
const startOutput = await client.startDeepResearch(plan);
console.log('Research started:', startOutput.text.slice(0, 100));

// Step 3: Poll for completion
const result = await client.waitForDeepResearch(
    plan,
    10000,   // poll interval in ms
    600000,  // timeout in ms
    (status) => console.log(`[${status.state}] ${status.notes[0] || ''}`),
);

console.log(result.text);

Account Status

The client detects your account's capability tier at initialization and exposes it via client.accountStatus.

const { AccountStatus } = require('gemini-reverse');

await client.init();

if (client.accountStatus === AccountStatus.AVAILABLE) {
    console.log('Account is fully authorized.');
} else if (client.accountStatus === AccountStatus.UNAUTHENTICATED) {
    console.error('Cookies are expired or invalid.');
} else if (client.accountStatus === AccountStatus.LOCATION_REJECTED) {
    console.error('Gemini is not available in your region.');
} else {
    console.warn(`Account status: ${client.accountStatus.name} — ${client.accountStatus.description}`);
}

All account status values:

| Constant | Code | Description | |---|---|---| | AccountStatus.AVAILABLE | 1000 | Account is authorized and has normal access | | AccountStatus.ACCESS_TEMPORARILY_UNAVAILABLE | 1014 | Access restricted, possibly regional/temporary | | AccountStatus.UNAUTHENTICATED | 1016 | Cookies have expired or are invalid | | AccountStatus.ACCOUNT_REJECTED | 1021 | Account access rejected | | AccountStatus.ACCOUNT_UNTRUSTED | 1033 | Did not pass safety/trust checks | | AccountStatus.TOS_PENDING | 1040 | Must accept latest Terms of Service | | AccountStatus.TOS_OUT_OF_DATE | 1042 | Terms of Service are out of date | | AccountStatus.ACCOUNT_REJECTED_BY_GUARDIAN | 1054 | Blocked by parent or guardian | | AccountStatus.GUARDIAN_APPROVAL_REQUIRED | 1057 | Requires parental approval | | AccountStatus.LOCATION_REJECTED | 1060 | Not available in your country/region |

Quota and Usage Info

After initialization, the client fetches account quota limits and compute usage metrics automatically. Access them via client.quotas, client.usageInfo, and client.abuseStatus.

await client.init();

// Quota limits (Flash, Pro, extra features)
const quotas = client.quotas;
for (const [id, q] of Object.entries(quotas)) {
    if (q.label) {
        const remaining = q.remaining !== null ? `${q.remaining}/${q.total}` : 'unlimited';
        console.log(`${q.label}: ${remaining} credits (${q.usage_percentage?.toFixed(1) ?? '?'}% used)`);
    }
}

// Compute usage (5-hour and weekly windows)
const usage = client.usageInfo;
if (usage.tier) {
    console.log(`Account tier: ${usage.tier.label}`);
}
if (usage.current_5h) {
    console.log(`5h window: ${usage.current_5h.remaining_credits} credits remaining (${usage.current_5h.usage_percentage}% used)`);
}
if (usage.weekly) {
    console.log(`Weekly: ${usage.weekly.remaining_credits} credits remaining`);
}

// Abuse status
const abuse = client.abuseStatus;
if (abuse) {
    console.log(`Account clean: ${abuse.is_clean}`);
}

Error Handling

const {
    AuthError,
    APIError,
    GeminiError,
    TimeoutError,
    UsageLimitExceeded,
    ModelInvalid,
    TemporarilyBlocked,
} = require('gemini-reverse');

try {
    const response = await chat.sendMessage({ prompt: 'Hello!' });
    console.log(response.text);
} catch (e) {
    if (e instanceof AuthError) {
        console.error('Cookie expired or invalid. Please refresh your cookies.');
    } else if (e instanceof UsageLimitExceeded) {
        console.error('Usage limit reached. Try again later or switch to a different model.');
    } else if (e instanceof TemporarilyBlocked) {
        console.error('IP temporarily blocked by Google. Try using a proxy or wait a while.');
    } else if (e instanceof TimeoutError) {
        console.error('Request timed out. Try increasing the timeout value in init().');
    } else if (e instanceof ModelInvalid) {
        console.error('Invalid or unavailable model. Try a different model.');
    } else if (e instanceof APIError) {
        console.error('API error:', e.message);
    } else {
        throw e;
    }
}

Cookie Persistence

If your application runs in a containerized environment (e.g. Docker), you can persist the auto-refreshed cookie cache to a volume by setting the GEMINI_COOKIE_PATH environment variable to a writable path.

# docker-compose.yml
services:
    app:
        environment:
            GEMINI_COOKIE_PATH: /tmp/gemini_cache
        volumes:
            - ./gemini_cookies:/tmp/gemini_cache

By default, the cache is stored in utils/temp/ relative to the package directory.

TypeScript

This package includes full TypeScript declarations.

import {
    GeminiClient,
    ChatSession,
    ModelOutput,
    ChatHistory,
    ChatTurn,
    ChatInfo,
    Gem,
    AvailableModel,
    Model,
    AccountStatus,
    DeepResearchPlan,
    DeepResearchResult,
    StreamingFrameParser,
} from 'gemini-reverse';

const client = new GeminiClient({ secure_1psid: '...' });
await client.init();

const chat: ChatSession = client.startChat({ model: 'gemini-3-flash' });
const response: ModelOutput = await chat.sendMessage({
    prompt: 'Hello!',
    extended_thinking: false,
});

console.log(response.text);

const history: ChatHistory | null = await chat.readHistory();
if (history) {
    history.turns.forEach((turn: ChatTurn) => console.log(turn.role, turn.text));
}

const models: AvailableModel[] | null = client.listModels();

// Access quota and usage after init
console.log(client.quotas);
console.log(client.usageInfo);
console.log(client.abuseStatus);

Project Structure

gemini-reverse/
├── index.js            # entry point & exports
├── index.d.ts          # TypeScript declarations
├── client.js           # GeminiClient + ChatSession
├── constants.js        # Endpoint, GRPC, Headers, Model, AccountStatus, ErrorCode
├── exceptions.js       # custom error classes
├── types/
│   ├── availablemodel.js   # AvailableModel (dynamic model from API)
│   ├── candidate.js        # Candidate
│   ├── chathistory.js      # ChatTurn, ChatHistory
│   ├── chatinfo.js         # ChatInfo
│   ├── gem.js              # Gem, GemJar
│   ├── grpc.js             # RPCData
│   ├── image.js            # Image, WebImage, GeneratedImage
│   ├── modeloutput.js      # ModelOutput
│   ├── research.js         # DeepResearchPlan, DeepResearchStatus
│   ├── researchresult.js   # DeepResearchResult
│   └── video.js            # Video, GeneratedVideo, GeneratedMedia
├── utils/
│   ├── accessToken.js      # cookie handling & init request
│   ├── parsing.js          # response parsing + StreamingFrameParser
│   ├── research.js         # deep research payload extractors
│   ├── rotate.js           # cookie rotation
│   └── upload.js           # file upload helpers
└── components/
    ├── chatMixin.js        # chat history methods
    ├── gemMixin.js         # gem management methods
    └── researchMixin.js    # deep research methods

References

Google AI Studio

Gemini-API (Python) by @HanaokaYuzu

acheong08/Bard


Disclaimer: This is an unofficial package and is not affiliated with or endorsed by Google. Cookie-based authentication may break if Google changes its internal API. Use at your own risk.

License: MIT