Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ff84679
docs: add codebase understanding guide
Samee24 Feb 26, 2026
760054e
docs: expand codebase understanding guide with technical depth
Samee24 Feb 26, 2026
f5b60c7
docs: finalize codebase understanding guide with advanced technical d…
Samee24 Feb 26, 2026
0f59d48
docs: add codebase understanding from antigravity
Samee24 Feb 26, 2026
bc6c644
Merge branch 'main' of https://github.com/google-gemini/gemini-cli
Samee24 Feb 27, 2026
99f2499
Merge branch 'main' of https://github.com/google-gemini/gemini-cli
Samee24 Mar 23, 2026
e930084
Merge branch 'main' of https://github.com/google-gemini/gemini-cli
Samee24 Apr 2, 2026
3dc0bb2
Merge branch 'main' of https://github.com/google-gemini/gemini-cli
Samee24 Apr 9, 2026
be2db23
Merge branch 'main' of https://github.com/google-gemini/gemini-cli
Samee24 Apr 10, 2026
7aec00d
Merge branch 'main' of https://github.com/google-gemini/gemini-cli
Samee24 Apr 10, 2026
dc30bd1
Merge branch 'main' of https://github.com/google-gemini/gemini-cli
Samee24 Apr 10, 2026
f7d4307
feat(cli): add `gemini gemma` command for streamlined local model setup
Samee24 Apr 10, 2026
8f023b5
feat(cli): add `gemini gemma logs` command to view LiteRT server logs
Samee24 Apr 10, 2026
82e87c4
docs: add gemma setup quick-start guide
Samee24 Apr 10, 2026
e0f043a
fix(core): set apiVersion to empty string for LiteRT-LM client
Samee24 Apr 13, 2026
ab8dc2d
Revert "fix(core): set apiVersion to empty string for LiteRT-LM client"
Samee24 Apr 13, 2026
d0dd169
feat(cli): show gemma router settings in /settings dialog
Samee24 Apr 13, 2026
8d3ac52
fix(gemma): resolve 404 errors and improve port resolution (#25340)
Abhijit-2592 Apr 13, 2026
c0117b4
chore(docs): regenerate settings schema and docs
Apr 15, 2026
509060e
rm docs
Apr 15, 2026
3b2243d
fix(cli): correctness and cross-platform fixes for gemma commands
Samee24 Apr 17, 2026
5223295
Merge branch 'main' into sameez/gemma-auto-setup
Samee24 Apr 17, 2026
85a5a97
chore: remove gemma setup guide doc for now
Samee24 Apr 17, 2026
eb5a3b9
chore: remove unnecessary comments across gemma commands
Samee24 Apr 17, 2026
c83376c
fix(cli): harden gemma router setup and server handling
Samee24 Apr 17, 2026
637af65
fix(cli): keep gemma logs attached to tail
Samee24 Apr 17, 2026
21bd3bd
Update packages/cli/src/commands/gemma/platform.ts
Samee24 Apr 17, 2026
6fed15f
Update packages/cli/src/gemini.tsx
Samee24 Apr 17, 2026
403a4c0
fix(cli): harden gemma setup and stop safety
Samee24 Apr 17, 2026
078aeb3
fix(cli): repair gemma platform config parsing
Samee24 Apr 17, 2026
dec7329
fix(cli): prettier
Samee24 Apr 17, 2026
c79ade9
Merge branch 'main' into sameez/gemma-auto-setup
Samee24 Apr 17, 2026
8f2a331
fix(cli): default autostart to false
Samee24 Apr 20, 2026
364eb1c
fix(cli): make server check more robust
Samee24 Apr 20, 2026
dac00da
fix(cli): scope gemma settings for security and project isolation
Samee24 Apr 20, 2026
e17478b
fix(cli): handle missing tail command and log stale PID cleanup
Samee24 Apr 20, 2026
6bad211
fix(cli): use generateContent endpoint for server health check
Samee24 Apr 20, 2026
e1a2899
Merge branch 'main' into sameez/gemma-auto-setup
Samee24 Apr 20, 2026
d3576a2
fix(cli): default autoStartServer to false in schema
Samee24 Apr 20, 2026
2d3e3ab
fix(cli): fix unsafe type assertion in logs error handler
Samee24 Apr 20, 2026
14e60b3
format
Samee24 Apr 20, 2026
b894f16
fix(chore): update docs
Apr 20, 2026
8ff6330
fix(cli): update tests for autoStartServer default and cross-platform…
Samee24 Apr 20, 2026
c065641
Merge branch 'main' into sameez/gemma-auto-setup
Samee24 Apr 20, 2026
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
12 changes: 12 additions & 0 deletions docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1706,6 +1706,18 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `false`
- **Requires restart:** Yes

- **`experimental.gemmaModelRouter.autoStartServer`** (boolean):
- **Description:** Automatically start the LiteRT-LM server when Gemini CLI
starts and the Gemma router is enabled.
- **Default:** `true`
Comment thread
Samee24 marked this conversation as resolved.
Resolved
- **Requires restart:** Yes

- **`experimental.gemmaModelRouter.binaryPath`** (string):
- **Description:** Custom path to the LiteRT-LM binary. Leave empty to use the
default location (~/.gemini/bin/litert/).
- **Default:** `""`
- **Requires restart:** Yes

- **`experimental.gemmaModelRouter.classifier.host`** (string):
- **Description:** The host of the classifier.
- **Default:** `"http://localhost:9379"`
Expand Down
33 changes: 33 additions & 0 deletions packages/cli/src/commands/gemma.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import type { CommandModule, Argv } from 'yargs';
import { initializeOutputListenersAndFlush } from '../gemini.js';
import { defer } from '../deferred.js';
import { setupCommand } from './gemma/setup.js';
import { startCommand } from './gemma/start.js';
import { stopCommand } from './gemma/stop.js';
import { statusCommand } from './gemma/status.js';
import { logsCommand } from './gemma/logs.js';

export const gemmaCommand: CommandModule = {
command: 'gemma',
describe: 'Manage local Gemma model routing',
builder: (yargs: Argv) =>
yargs
.middleware((argv) => {
initializeOutputListenersAndFlush();
argv['isCommand'] = true;
})
.command(defer(setupCommand, 'gemma'))
.command(defer(startCommand, 'gemma'))
.command(defer(stopCommand, 'gemma'))
.command(defer(statusCommand, 'gemma'))
.command(defer(logsCommand, 'gemma'))
.demandCommand(1, 'You need at least one command before continuing.')
.version(false),
handler: () => {},
};
34 changes: 34 additions & 0 deletions packages/cli/src/commands/gemma/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import path from 'node:path';
import { Storage } from '@google/gemini-cli-core';

export const LITERT_RELEASE_VERSION = 'v0.9.0-alpha03';
export const LITERT_RELEASE_BASE_URL =
'https://github.com/google-ai-edge/LiteRT-LM/releases/download';
export const GEMMA_MODEL_NAME = 'gemma3-1b-gpu-custom';
export const DEFAULT_PORT = 9379;
export const HEALTH_CHECK_TIMEOUT_MS = 5000;
export const SERVER_START_WAIT_MS = 3000;

export const PLATFORM_BINARY_MAP: Record<string, string> = {
'darwin-arm64': 'lit.macos_arm64',
'linux-x64': 'lit.linux_x86_64',
'win32-x64': 'lit.windows_x86_64.exe',
};

export function getLiteRtBinDir(): string {
return path.join(Storage.getGlobalGeminiDir(), 'bin', 'litert');
}

export function getPidFilePath(): string {
return path.join(Storage.getGlobalTempDir(), 'litert-server.pid');
}

export function getLogFilePath(): string {
return path.join(Storage.getGlobalTempDir(), 'litert-server.log');
}
51 changes: 51 additions & 0 deletions packages/cli/src/commands/gemma/logs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, describe, expect, it } from 'vitest';
import { readLastLines } from './logs.js';

describe('readLastLines', () => {
const tempFiles: string[] = [];

afterEach(async () => {
await Promise.all(
tempFiles
.splice(0)
.map((filePath) => fs.promises.rm(filePath, { force: true })),
);
});

it('returns only the requested tail lines without reading the whole file eagerly', async () => {
const filePath = path.join(
os.tmpdir(),
`gemma-logs-${Date.now()}-${Math.random().toString(36).slice(2)}.log`,
);
tempFiles.push(filePath);

const content = Array.from({ length: 2000 }, (_, i) => `line-${i + 1}`)
.join('\n')
.concat('\n');
await fs.promises.writeFile(filePath, content, 'utf-8');

await expect(readLastLines(filePath, 3)).resolves.toBe(
'line-1998\nline-1999\nline-2000\n',
);
});

it('returns an empty string when zero lines are requested', async () => {
const filePath = path.join(
os.tmpdir(),
`gemma-logs-${Date.now()}-${Math.random().toString(36).slice(2)}.log`,
);
tempFiles.push(filePath);
await fs.promises.writeFile(filePath, 'line-1\nline-2\n', 'utf-8');

await expect(readLastLines(filePath, 0)).resolves.toBe('');
});
});
158 changes: 158 additions & 0 deletions packages/cli/src/commands/gemma/logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import type { CommandModule } from 'yargs';
import fs from 'node:fs';
import { spawn } from 'node:child_process';
import { debugLogger } from '@google/gemini-cli-core';
import { exitCli } from '../utils.js';
import { getLogFilePath } from './constants.js';

export async function readLastLines(
filePath: string,
count: number,
): Promise<string> {
if (count <= 0) {
return '';
}

const CHUNK_SIZE = 64 * 1024;
const fileHandle = await fs.promises.open(filePath, fs.constants.O_RDONLY);

try {
const stats = await fileHandle.stat();
if (stats.size === 0) {
return '';
}

const chunks: Buffer[] = [];
let totalBytes = 0;
let newlineCount = 0;
let position = stats.size;

while (position > 0 && newlineCount <= count) {
const readSize = Math.min(CHUNK_SIZE, position);
position -= readSize;

const buffer = Buffer.allocUnsafe(readSize);
const { bytesRead } = await fileHandle.read(
buffer,
0,
readSize,
position,
);

if (bytesRead === 0) {
break;
}

const chunk =
bytesRead === readSize ? buffer : buffer.subarray(0, bytesRead);
chunks.unshift(chunk);
totalBytes += chunk.length;

for (const byte of chunk) {
if (byte === 0x0a) {
newlineCount += 1;
}
}
}

const content = Buffer.concat(chunks, totalBytes).toString('utf-8');
const lines = content.split('\n');

if (position > 0 && lines.length > 0) {
const boundary = Buffer.allocUnsafe(1);
const { bytesRead } = await fileHandle.read(boundary, 0, 1, position - 1);
if (bytesRead === 1 && boundary[0] !== 0x0a) {
lines.shift();
}
}

if (lines.length > 0 && lines[lines.length - 1] === '') {
lines.pop();
}

if (lines.length === 0) {
return '';
}

return lines.slice(-count).join('\n') + '\n';
} finally {
await fileHandle.close();
}
}

const isWindows = process.platform === 'win32';

export const logsCommand: CommandModule = {
command: 'logs',
describe: 'View LiteRT-LM server logs',
builder: (yargs) =>
yargs
.option('lines', {
alias: 'n',
type: 'number',
description: 'Show the last N lines and exit (omit to follow live)',
})
.option('follow', {
alias: 'f',
type: 'boolean',
default: true,
description: 'Follow log output (default when --lines is not set)',
}),
handler: async (argv) => {
const logPath = getLogFilePath();

try {
await fs.promises.access(logPath, fs.constants.F_OK);
} catch {
debugLogger.log(`No log file found at ${logPath}`);
debugLogger.log(
'Is the LiteRT server running? Start it with: gemini gemma start',
);
await exitCli(1);
return;
}

const rawLines = argv['lines'];
const lines = Number.isFinite(rawLines) ? Number(rawLines) : undefined;

if (lines !== undefined) {
if (isWindows) {
process.stdout.write(await readLastLines(logPath, lines));
await exitCli(0);
return;
}
const tailArgs = ['-n', String(lines), logPath];
const child = spawn('tail', tailArgs, { stdio: 'inherit' });
Comment thread
Samee24 marked this conversation as resolved.
Resolved
child.on('close', async (code) => {
await exitCli(code ?? 0);
});
return;
}

if (isWindows) {
debugLogger.log(
'Live log following is not supported on Windows. Use --lines N to view recent logs.',
);
await exitCli(1);
return;
}

debugLogger.log(`Tailing ${logPath} (Ctrl+C to stop)\n`);
const tailArgs = ['-f', '-n', '20', logPath];
const child = spawn('tail', tailArgs, { stdio: 'inherit' });
Comment thread
Samee24 marked this conversation as resolved.
Resolved

process.on('SIGINT', () => {
child.kill('SIGTERM');
});

child.on('close', async (code) => {
await exitCli(code ?? 0);
});
},
};
Loading
Loading