Build a Minimal Gemini‑CLI Clone with TypeScript

A beginner‑friendly, free‑tier AI‑agent in your terminal

Welcome to another official UVF IT build guide! In this tutorial we’ll craft a tiny command‑line AI agent inspired by Google’s Gemini‑CLI, using only Node.js, TypeScript and a free‑tier LLM endpoint.

You’ll end up with a runnable gemini‑clone that accepts a prompt, calls a public AI API, and prints the response – all in under 45 minutes.

Before you start…

Minimal Gemini‑CLI Architecture
LOCAL STACK EXTERNAL SERVICE TypeScript CLI .env (API key) OpenAI Chat API
1

Phase 1: Scaffold the project

First we create a fresh folder and turn it into an npm package. Adding TypeScript and ts-node lets us write modern code and run it directly without a separate compile step, which keeps the clone lightweight.

We also install dotenv for secret management and node-fetch to talk to the OpenAI endpoint. All dependencies are saved as regular (non‑dev) packages because the final script will run with them at runtime.

mkdir gemini-clone && cd gemini-clone
npm init -y
npm install typescript ts-node @types/node dotenv node-fetch@2
npx tsc --init --rootDir src --outDir dist --esModuleInterop true --resolveJsonModule true
mkdir src
2

Phase 2: Add config and API wrapper

Now we create a single TypeScript module that reads the OpenAI key from a .env file and exports an async function to call the chat completion endpoint. Keeping this logic isolated makes the CLI file tiny and focused on user interaction.

The function builds the request payload, sends it with fetch, checks for errors, and returns the generated message text. If the environment variable is missing we throw a clear error so the user knows what to fix.

src/gemini.ts

import { config } from 'dotenv';
import fetch from 'node-fetch';

config(); // loads .env into process.env

const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
if (!OPENAI_API_KEY) {
  throw new Error('Missing OPENAI_API_KEY in .env');
}

export interface ChatMessage { role: 'user' | 'assistant' | 'system'; content: string; }

export async function askGemini(prompt: string): Promise<string> {
  const payload = {
    model: 'gpt-3.5-turbo', // free‑tier model
    messages: [{ role: 'user', content: prompt }],
    temperature: 0.7,
  };

  const response = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${OPENAI_API_KEY}`,
    },
    body: JSON.stringify(payload),
  });

  if (!response.ok) {
    const err = await response.text();
    throw new Error(`OpenAI API error: ${response.status} ${err}`);
  }

  const data = (await response.json()) as any;
  return data.choices[0].message.content.trim();
}
3

Phase 3: Build the CLI entry point

In this step we create the tiny executable that users will run from their terminal. The file reads the prompt argument, hands it to the Gemini wrapper we wrote earlier, and prints the AI's reply. Keeping the logic simple makes it easy to extend later with flags or config files.

src/index.ts

#!/usr/bin/env node
import { askGemini } from './gemini';

// Grab the prompt from the command line (skip node and script name)
const args = process.argv.slice(2);
if (args.length === 0) {
  console.error('Usage: gemini-clone <prompt>');
  process.exit(1);
}
const prompt = args.join(' ');

(async () => {
  try {
    const response = await askGemini(prompt);
    console.log(response);
  } catch (err) {
    console.error('Error calling Gemini:', err);
    process.exit(1);
  }
})();
4

Phase 4: Run locally

Now we compile the TypeScript sources to JavaScript and execute the newly built CLI with a test prompt. The npm scripts added in earlier phases handle the build step, so we just invoke them and run the output file.

Running the tool locally confirms that the whole pipeline – from argument parsing to API call – works end‑to‑end.

npm run build   # compiles src/**/*.ts into dist/
node dist/index.js "Hello, Gemini!"
5

Phase 5: Verify & extend

Check that the CLI prints a helpful usage message when no prompt is supplied and that a real response appears for a valid prompt. Once the basics are solid, you can experiment by adding flags (e.g., --model or --temp) or swapping the endpoint URL in src/gemini.ts to point at another free LLM provider.

# Verify usage message
node dist/index.js
# Verify AI response
node dist/index.js "Tell me a joke about cats"

Verifying the Stack

CheckCommand / Why it matters
Project builds without TypeScript errorsnpm run build finishes without any compilation warnings or errors
CLI shows usage when no prompt is suppliednode dist/index.js returns exit code 1 and prints 'Usage: gemini-clone <prompt>'
AI response is printed for a valid promptnode dist/index.js "Hello" outputs a textual reply from the Gemini endpoint

You did it!

Your minimal Gemini‑CLI clone is now live. Swap the OpenAI endpoint for any free LLM, add streaming, or publish it globally with npm. Happy hacking!