2025-07-16 18:36:14 -04:00
|
|
|
/**
|
|
|
|
|
* @license
|
|
|
|
|
* Copyright 2025 Google LLC
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { fileURLToPath } from 'url';
|
2025-07-25 17:46:55 +00:00
|
|
|
import { Config, IDEConnectionStatus } from '@google/gemini-cli-core';
|
2025-07-16 18:36:14 -04:00
|
|
|
import {
|
|
|
|
|
CommandContext,
|
|
|
|
|
SlashCommand,
|
|
|
|
|
SlashCommandActionReturn,
|
2025-07-20 16:57:34 -04:00
|
|
|
CommandKind,
|
2025-07-16 18:36:14 -04:00
|
|
|
} from './types.js';
|
|
|
|
|
import * as child_process from 'child_process';
|
|
|
|
|
import * as process from 'process';
|
|
|
|
|
import { glob } from 'glob';
|
|
|
|
|
import * as path from 'path';
|
|
|
|
|
|
|
|
|
|
const VSCODE_COMMAND = process.platform === 'win32' ? 'code.cmd' : 'code';
|
|
|
|
|
const VSCODE_COMPANION_EXTENSION_FOLDER = 'vscode-ide-companion';
|
|
|
|
|
|
|
|
|
|
function isVSCodeInstalled(): boolean {
|
|
|
|
|
try {
|
|
|
|
|
child_process.execSync(
|
|
|
|
|
process.platform === 'win32'
|
|
|
|
|
? `where.exe ${VSCODE_COMMAND}`
|
|
|
|
|
: `command -v ${VSCODE_COMMAND}`,
|
|
|
|
|
{ stdio: 'ignore' },
|
|
|
|
|
);
|
|
|
|
|
return true;
|
|
|
|
|
} catch {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const ideCommand = (config: Config | null): SlashCommand | null => {
|
|
|
|
|
if (!config?.getIdeMode()) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
name: 'ide',
|
|
|
|
|
description: 'manage IDE integration',
|
2025-07-20 16:57:34 -04:00
|
|
|
kind: CommandKind.BUILT_IN,
|
2025-07-16 18:36:14 -04:00
|
|
|
subCommands: [
|
|
|
|
|
{
|
|
|
|
|
name: 'status',
|
|
|
|
|
description: 'check status of IDE integration',
|
2025-07-20 16:57:34 -04:00
|
|
|
kind: CommandKind.BUILT_IN,
|
2025-07-16 18:36:14 -04:00
|
|
|
action: (_context: CommandContext): SlashCommandActionReturn => {
|
2025-07-25 17:46:55 +00:00
|
|
|
const connection = config.getIdeClient()?.getConnectionStatus();
|
|
|
|
|
switch (connection?.status) {
|
|
|
|
|
case IDEConnectionStatus.Connected:
|
2025-07-16 18:36:14 -04:00
|
|
|
return {
|
|
|
|
|
type: 'message',
|
|
|
|
|
messageType: 'info',
|
|
|
|
|
content: `🟢 Connected`,
|
2025-07-25 17:46:55 +00:00
|
|
|
} as const;
|
|
|
|
|
case IDEConnectionStatus.Connecting:
|
2025-07-16 18:36:14 -04:00
|
|
|
return {
|
|
|
|
|
type: 'message',
|
|
|
|
|
messageType: 'info',
|
2025-07-25 17:46:55 +00:00
|
|
|
content: `🟡 Connecting...`,
|
|
|
|
|
} as const;
|
|
|
|
|
default: {
|
|
|
|
|
let content = `🔴 Disconnected`;
|
|
|
|
|
if (connection?.details) {
|
|
|
|
|
content += `: ${connection.details}`;
|
2025-07-16 18:36:14 -04:00
|
|
|
}
|
2025-07-25 17:46:55 +00:00
|
|
|
return {
|
|
|
|
|
type: 'message',
|
|
|
|
|
messageType: 'error',
|
|
|
|
|
content,
|
|
|
|
|
} as const;
|
|
|
|
|
}
|
2025-07-16 18:36:14 -04:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'install',
|
|
|
|
|
description: 'install required VS Code companion extension',
|
2025-07-20 16:57:34 -04:00
|
|
|
kind: CommandKind.BUILT_IN,
|
2025-07-16 18:36:14 -04:00
|
|
|
action: async (context) => {
|
|
|
|
|
if (!isVSCodeInstalled()) {
|
|
|
|
|
context.ui.addItem(
|
|
|
|
|
{
|
|
|
|
|
type: 'error',
|
|
|
|
|
text: `VS Code command-line tool "${VSCODE_COMMAND}" not found in your PATH.`,
|
|
|
|
|
},
|
|
|
|
|
Date.now(),
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bundleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
|
// The VSIX file is copied to the bundle directory as part of the build.
|
|
|
|
|
let vsixFiles = glob.sync(path.join(bundleDir, '*.vsix'));
|
|
|
|
|
if (vsixFiles.length === 0) {
|
|
|
|
|
// If the VSIX file is not in the bundle, it might be a dev
|
|
|
|
|
// environment running with `npm start`. Look for it in the original
|
|
|
|
|
// package location, relative to the bundle dir.
|
|
|
|
|
const devPath = path.join(
|
|
|
|
|
bundleDir,
|
|
|
|
|
'..',
|
|
|
|
|
'..',
|
|
|
|
|
'..',
|
|
|
|
|
'..',
|
|
|
|
|
'..',
|
|
|
|
|
VSCODE_COMPANION_EXTENSION_FOLDER,
|
|
|
|
|
'*.vsix',
|
|
|
|
|
);
|
|
|
|
|
vsixFiles = glob.sync(devPath);
|
|
|
|
|
}
|
|
|
|
|
if (vsixFiles.length === 0) {
|
|
|
|
|
context.ui.addItem(
|
|
|
|
|
{
|
|
|
|
|
type: 'error',
|
|
|
|
|
text: 'Could not find the required VS Code companion extension. Please file a bug via /bug.',
|
|
|
|
|
},
|
|
|
|
|
Date.now(),
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const vsixPath = vsixFiles[0];
|
|
|
|
|
const command = `${VSCODE_COMMAND} --install-extension ${vsixPath} --force`;
|
|
|
|
|
context.ui.addItem(
|
|
|
|
|
{
|
|
|
|
|
type: 'info',
|
|
|
|
|
text: `Installing VS Code companion extension...`,
|
|
|
|
|
},
|
|
|
|
|
Date.now(),
|
|
|
|
|
);
|
|
|
|
|
try {
|
|
|
|
|
child_process.execSync(command, { stdio: 'pipe' });
|
|
|
|
|
context.ui.addItem(
|
|
|
|
|
{
|
|
|
|
|
type: 'info',
|
|
|
|
|
text: 'VS Code companion extension installed successfully. Restart gemini-cli in a fresh terminal window.',
|
|
|
|
|
},
|
|
|
|
|
Date.now(),
|
|
|
|
|
);
|
|
|
|
|
} catch (_error) {
|
|
|
|
|
context.ui.addItem(
|
|
|
|
|
{
|
|
|
|
|
type: 'error',
|
|
|
|
|
text: `Failed to install VS Code companion extension.`,
|
|
|
|
|
},
|
|
|
|
|
Date.now(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
};
|