Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
99 changes: 91 additions & 8 deletions packages/cli/src/ui/commands/helpCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ import { MessageType } from '../types.js';

describe('helpCommand', () => {
let mockContext: CommandContext;
const originalEnv = { ...process.env };
const originalPlatform = process.platform;
const action = helpCommand.action;

if (!action) {
throw new Error('Help command has no action');
}

beforeEach(() => {
mockContext = createMockCommandContext({
Expand All @@ -23,16 +28,13 @@ describe('helpCommand', () => {
});

afterEach(() => {
process.env = { ...originalEnv };
Object.defineProperty(process, 'platform', { value: originalPlatform });
vi.unstubAllEnvs();
vi.clearAllMocks();
});

it('should add a help message to the UI history', async () => {
if (!helpCommand.action) {
throw new Error('Help command has no action');
}

await helpCommand.action(mockContext, '');
it('should add a help message to the UI history by default', async () => {
await action(mockContext, '');

expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
Expand All @@ -47,4 +49,85 @@ describe('helpCommand', () => {
expect(helpCommand.kind).toBe(CommandKind.BUILT_IN);
expect(helpCommand.description).toBe('For help on gemini-cli');
});

describe('Antigravity installer commands help', () => {
it('should output macOS installation command on darwin platform', async () => {
Object.defineProperty(process, 'platform', { value: 'darwin' });

await action(mockContext, 'install antigravity cli');

expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: MessageType.INFO,
text: `To install the Antigravity CLI on macOS, run the following command:\n\n'curl -fsSL https://antigravity.google/cli/install.sh | bash'`,
}),
);
});

it('should output Linux installation command on linux platform', async () => {
Object.defineProperty(process, 'platform', { value: 'linux' });

await action(mockContext, 'how do I install antigravity CLI');

expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: MessageType.INFO,
text: `To install the Antigravity CLI on Linux, run the following command:\n\n'curl -fsSL https://antigravity.google/cli/install.sh | bash'`,
}),
);
});

it('should output Windows PowerShell installation command on win32 when PSModulePath is set', async () => {
Object.defineProperty(process, 'platform', { value: 'win32' });
vi.stubEnv('PSModulePath', 'C:\\some\\path');

await action(mockContext, 'how do I migrate to antigravity CLI');

expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: MessageType.INFO,
text: `To install the Antigravity CLI on Windows (PowerShell), run the following command:\n\n'irm https://antigravity.google/cli/install.ps1 | iex'`,
}),
);
});

it('should output Windows CMD installation command on win32 when PSModulePath is not set', async () => {
Object.defineProperty(process, 'platform', { value: 'win32' });
vi.stubEnv('PSModulePath', '');

await action(mockContext, 'install antigravity cli');

expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: MessageType.INFO,
text: `To install the Antigravity CLI on Windows (Command Prompt), run the following command:\n\n'curl -fsSL https://antigravity.google/cli/install.cmd -o install.cmd && install.cmd && del install.cmd'`,
}),
);
});

it('should learn more message on unsupported platform', async () => {
Object.defineProperty(process, 'platform', { value: 'freebsd' });

await action(mockContext, 'install antigravity cli');

expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: MessageType.INFO,
text: 'Learn more about Antigravity CLI at https://antigravity.google/docs/cli-getting-started',
}),
);
});

it('should fall back to default help if query does not contain install or migrate', async () => {
Object.defineProperty(process, 'platform', { value: 'darwin' });

await action(mockContext, 'antigravity cli');

expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: MessageType.HELP,
}),
);
});
});
});
25 changes: 24 additions & 1 deletion packages/cli/src/ui/commands/helpCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,36 @@

import { CommandKind, type SlashCommand } from './types.js';
import { MessageType, type HistoryItemHelp } from '../types.js';
import { getAntigravityInstallInfo } from '../utils/antigravityUtils.js';

export const helpCommand: SlashCommand = {
name: 'help',
kind: CommandKind.BUILT_IN,
description: 'For help on gemini-cli',
autoExecute: true,
action: async (context) => {
action: async (context, args) => {
const lowerArgs = args?.toLowerCase() || '';
const hasAntigravity = lowerArgs.includes('antigravity');
const hasInstallOrMigrate =
lowerArgs.includes('install') || lowerArgs.includes('migrate');

if (hasAntigravity && hasInstallOrMigrate) {
const info = getAntigravityInstallInfo();

if (info) {
context.ui.addItem({
type: MessageType.INFO,
text: `To install the Antigravity CLI on ${info.platformName}, run the following command:\n\n'${info.installCmd}'`,
});
} else {
context.ui.addItem({
type: MessageType.INFO,
text: `Learn more about Antigravity CLI at https://antigravity.google/docs/cli-getting-started`,
});
}
return;
}

const helpItem: Omit<HistoryItemHelp, 'id'> = {
type: MessageType.HELP,
timestamp: new Date(),
Expand Down
79 changes: 78 additions & 1 deletion packages/cli/src/ui/hooks/useBanner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import {
expect,
vi,
beforeEach,
afterEach,
type MockedFunction,
} from 'vitest';
import { renderHook } from '../../test-utils/render.js';
import { useBanner, _clearSessionBannersForTest } from './useBanner.js';
import { persistentState } from '../../utils/persistentState.js';
import crypto from 'node:crypto';
import chalk from 'chalk';

vi.mock('../../utils/persistentState.js', () => ({
persistentState: {
Expand Down Expand Up @@ -94,7 +96,9 @@ describe('useBanner', () => {

const { result } = await renderHook(() => useBanner(antigravityBannerData));

expect(result.current.bannerText).toBe('Antigravity is coming to town!');
expect(result.current.bannerText).toContain(
'Antigravity is coming to town!',
);
});

it('should increment the persistent count when banner is shown', async () => {
Expand Down Expand Up @@ -137,4 +141,77 @@ describe('useBanner', () => {

expect(result.current.bannerText).toBe('Line1\nLine2');
});

describe('Antigravity installation commands', () => {
const originalPlatform = process.platform;

afterEach(() => {
Object.defineProperty(process, 'platform', { value: originalPlatform });
vi.unstubAllEnvs();
});

it('should append macOS & Linux install command when on darwin', async () => {
Object.defineProperty(process, 'platform', { value: 'darwin' });
const data = { defaultText: 'Welcome to Antigravity!', warningText: '' };

const { result } = await renderHook(() => useBanner(data));

expect(result.current.bannerText).toBe(
`Welcome to Antigravity!\n \nTo install run "${chalk.bold('curl -fsSL https://antigravity.google/cli/install.sh | bash')}"`,
);
});

it('should append macOS & Linux install command when on linux', async () => {
Object.defineProperty(process, 'platform', { value: 'linux' });
const data = { defaultText: 'Welcome to Antigravity!', warningText: '' };

const { result } = await renderHook(() => useBanner(data));

expect(result.current.bannerText).toBe(
`Welcome to Antigravity!\n \nTo install run "${chalk.bold('curl -fsSL https://antigravity.google/cli/install.sh | bash')}"`,
);
});

it('should append Windows PowerShell install command when on win32 and PSModulePath is set', async () => {
Object.defineProperty(process, 'platform', { value: 'win32' });
vi.stubEnv('PSModulePath', 'C:\\some\\path');
const data = { defaultText: 'Welcome to Antigravity!', warningText: '' };

const { result } = await renderHook(() => useBanner(data));

expect(result.current.bannerText).toBe(
`Welcome to Antigravity!\n \nTo install run "${chalk.bold('irm https://antigravity.google/cli/install.ps1 | iex')}"`,
);
});

it('should append Windows CMD install command when on win32 and PSModulePath is not set', async () => {
Object.defineProperty(process, 'platform', { value: 'win32' });
vi.stubEnv('PSModulePath', '');
const data = { defaultText: 'Welcome to Antigravity!', warningText: '' };

const { result } = await renderHook(() => useBanner(data));

expect(result.current.bannerText).toBe(
`Welcome to Antigravity!\n \nTo install run "${chalk.bold('curl -fsSL https://antigravity.google/cli/install.cmd -o install.cmd && install.cmd && del install.cmd')}"`,
);
});

it('should not append install command if banner text does not contain Antigravity', async () => {
Object.defineProperty(process, 'platform', { value: 'darwin' });
const data = { defaultText: 'Regular Banner', warningText: '' };

const { result } = await renderHook(() => useBanner(data));

expect(result.current.bannerText).toBe('Regular Banner');
});

it('should not append install command if process.platform is an unsupported platform', async () => {
Object.defineProperty(process, 'platform', { value: 'freebsd' });
const data = { defaultText: 'Welcome to Antigravity!', warningText: '' };

const { result } = await renderHook(() => useBanner(data));

expect(result.current.bannerText).toBe('Welcome to Antigravity!');
});
});
});
11 changes: 10 additions & 1 deletion packages/cli/src/ui/hooks/useBanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import { useState, useEffect } from 'react';
import { persistentState } from '../../utils/persistentState.js';
import crypto from 'node:crypto';
import chalk from 'chalk';
import { getAntigravityInstallInfo } from '../utils/antigravityUtils.js';

const DEFAULT_MAX_BANNER_SHOWN_COUNT = 5;

Expand Down Expand Up @@ -46,7 +48,14 @@ export function useBanner(bannerData: BannerData) {
activeText.includes('Antigravity'));

const rawBannerText = showBanner ? activeText : '';
const bannerText = rawBannerText.replace(/\\n/g, '\n');
let bannerText = rawBannerText.replace(/\\n/g, '\n');

if (showBanner && activeText.includes('Antigravity')) {
const info = getAntigravityInstallInfo();
if (info) {
bannerText += `\n \nTo install run "${chalk.bold(info.installCmd)}"`;
}
}

useEffect(() => {
if (showBanner && activeText) {
Expand Down
72 changes: 72 additions & 0 deletions packages/cli/src/ui/utils/antigravityUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { describe, it, expect, vi, afterEach } from 'vitest';
import { getAntigravityInstallInfo } from './antigravityUtils.js';

describe('antigravityUtils', () => {
const originalPlatform = process.platform;

afterEach(() => {
Object.defineProperty(process, 'platform', { value: originalPlatform });
vi.unstubAllEnvs();
});

it('should return macOS installation info on darwin platform', () => {
Object.defineProperty(process, 'platform', { value: 'darwin' });

const info = getAntigravityInstallInfo();

expect(info).toEqual({
platformName: 'macOS',
installCmd: 'curl -fsSL https://antigravity.google/cli/install.sh | bash',
});
});

it('should return Linux installation info on linux platform', () => {
Object.defineProperty(process, 'platform', { value: 'linux' });

const info = getAntigravityInstallInfo();

expect(info).toEqual({
platformName: 'Linux',
installCmd: 'curl -fsSL https://antigravity.google/cli/install.sh | bash',
});
});

it('should return Windows PowerShell installation info on win32 when PSModulePath is set', () => {
Object.defineProperty(process, 'platform', { value: 'win32' });
vi.stubEnv('PSModulePath', 'C:\\some\\path');

const info = getAntigravityInstallInfo();

expect(info).toEqual({
platformName: 'Windows (PowerShell)',
installCmd: 'irm https://antigravity.google/cli/install.ps1 | iex',
});
});

it('should return Windows CMD installation info on win32 when PSModulePath is not set', () => {
Object.defineProperty(process, 'platform', { value: 'win32' });
vi.stubEnv('PSModulePath', '');

const info = getAntigravityInstallInfo();

expect(info).toEqual({
platformName: 'Windows (Command Prompt)',
installCmd:
'curl -fsSL https://antigravity.google/cli/install.cmd -o install.cmd && install.cmd && del install.cmd',
});
});

it('should return null on unsupported platform', () => {
Object.defineProperty(process, 'platform', { value: 'freebsd' });

const info = getAntigravityInstallInfo();

expect(info).toBeNull();
});
});
Loading
Loading