download gemini from Github
This commit is contained in:
+1
-9
@@ -21,21 +21,16 @@ export const cursor = agent({
|
|||||||
configureCursorMcpServers({ mcpServers, cliPath });
|
configureCursorMcpServers({ mcpServers, cliPath });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Run cursor-agent in non-interactive mode with the prompt
|
|
||||||
// Using -p flag for prompt, --output-format text for plain text output
|
|
||||||
// and --approve-mcps to automatically approve all MCP servers
|
|
||||||
const fullPrompt = addInstructions(payload);
|
const fullPrompt = addInstructions(payload);
|
||||||
|
|
||||||
log.info("Running Cursor CLI...");
|
log.info("Running Cursor CLI...");
|
||||||
|
|
||||||
// Use spawn to handle streaming output
|
|
||||||
// Use --print flag explicitly for non-interactive mode
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const child = spawn(
|
const child = spawn(
|
||||||
cliPath,
|
cliPath,
|
||||||
["--print", fullPrompt, "--output-format", "text", "--approve-mcps", "--force"],
|
["--print", fullPrompt, "--output-format", "text", "--approve-mcps", "--force"],
|
||||||
{
|
{
|
||||||
cwd: process.cwd(), // Run in current working directory
|
cwd: process.cwd(),
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
CURSOR_API_KEY: apiKey,
|
CURSOR_API_KEY: apiKey,
|
||||||
@@ -50,7 +45,6 @@ export const cursor = agent({
|
|||||||
let stdout = "";
|
let stdout = "";
|
||||||
let stderr = "";
|
let stderr = "";
|
||||||
|
|
||||||
// Log when process starts
|
|
||||||
child.on("spawn", () => {
|
child.on("spawn", () => {
|
||||||
log.debug("Cursor CLI process spawned");
|
log.debug("Cursor CLI process spawned");
|
||||||
});
|
});
|
||||||
@@ -65,12 +59,10 @@ export const cursor = agent({
|
|||||||
child.stderr?.on("data", (data) => {
|
child.stderr?.on("data", (data) => {
|
||||||
const text = data.toString();
|
const text = data.toString();
|
||||||
stderr += text;
|
stderr += text;
|
||||||
// Log errors as they come - but also write to stdout so we can see it
|
|
||||||
process.stderr.write(text);
|
process.stderr.write(text);
|
||||||
log.warning(text);
|
log.warning(text);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle process exit
|
|
||||||
child.on("close", (code, signal) => {
|
child.on("close", (code, signal) => {
|
||||||
if (signal) {
|
if (signal) {
|
||||||
log.warning(`Cursor CLI terminated by signal: ${signal}`);
|
log.warning(`Cursor CLI terminated by signal: ${signal}`);
|
||||||
|
|||||||
+7
-8
@@ -2,16 +2,16 @@ import { spawnSync } from "node:child_process";
|
|||||||
import { log } from "../utils/cli.ts";
|
import { log } from "../utils/cli.ts";
|
||||||
import { spawn } from "../utils/subprocess.ts";
|
import { spawn } from "../utils/subprocess.ts";
|
||||||
import { addInstructions } from "./instructions.ts";
|
import { addInstructions } from "./instructions.ts";
|
||||||
import { agent, type ConfigureMcpServersParams, installFromNpmTarball } from "./shared.ts";
|
import { agent, type ConfigureMcpServersParams, installFromGithub } from "./shared.ts";
|
||||||
|
|
||||||
export const gemini = agent({
|
export const gemini = agent({
|
||||||
name: "gemini",
|
name: "gemini",
|
||||||
install: async () => {
|
install: async () => {
|
||||||
return await installFromNpmTarball({
|
return await installFromGithub({
|
||||||
packageName: "@google/gemini-cli",
|
owner: "google-gemini",
|
||||||
version: "latest",
|
repo: "gemini-cli",
|
||||||
executablePath: "dist/index.js",
|
tag: "v0.16.0",
|
||||||
installDependencies: true,
|
assetName: "gemini.js",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, apiKey, mcpServers, githubInstallationToken, cliPath }) => {
|
run: async ({ payload, apiKey, mcpServers, githubInstallationToken, cliPath }) => {
|
||||||
@@ -31,7 +31,7 @@ export const gemini = agent({
|
|||||||
try {
|
try {
|
||||||
const result = await spawn({
|
const result = await spawn({
|
||||||
cmd: "node",
|
cmd: "node",
|
||||||
args: [cliPath, "--yolo", "--output-format", "text", sessionPrompt],
|
args: [cliPath, "--yolo", "--output-format=text", "-p", sessionPrompt],
|
||||||
env: {
|
env: {
|
||||||
GEMINI_API_KEY: apiKey,
|
GEMINI_API_KEY: apiKey,
|
||||||
GITHUB_INSTALLATION_TOKEN: githubInstallationToken,
|
GITHUB_INSTALLATION_TOKEN: githubInstallationToken,
|
||||||
@@ -93,7 +93,6 @@ function configureGeminiMcpServers({ mcpServers, cliPath }: ConfigureMcpServersP
|
|||||||
|
|
||||||
const addArgs = ["mcp", "add", serverName, command, ...args];
|
const addArgs = ["mcp", "add", serverName, command, ...args];
|
||||||
|
|
||||||
// Add environment variables as --env flags
|
|
||||||
for (const [key, value] of Object.entries(envVars)) {
|
for (const [key, value] of Object.entries(envVars)) {
|
||||||
addArgs.push("--env", `${key}=${value}`);
|
addArgs.push("--env", `${key}=${value}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
import { chmodSync, createWriteStream, existsSync } from "node:fs";
|
import { chmodSync, createWriteStream, existsSync } from "node:fs";
|
||||||
|
import { mkdtemp } from "node:fs/promises";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { pipeline } from "node:stream/promises";
|
import { pipeline } from "node:stream/promises";
|
||||||
import type { McpStdioServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
import type { McpStdioServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
||||||
@@ -54,6 +56,17 @@ export interface InstallFromCurlParams {
|
|||||||
executableName: string;
|
executableName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters for installing from GitHub releases
|
||||||
|
*/
|
||||||
|
export interface InstallFromGithubParams {
|
||||||
|
owner: string;
|
||||||
|
repo: string;
|
||||||
|
tag?: string;
|
||||||
|
assetName?: string;
|
||||||
|
executablePath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NPM registry response data structure
|
* NPM registry response data structure
|
||||||
*/
|
*/
|
||||||
@@ -167,6 +180,91 @@ export async function installFromNpmTarball({
|
|||||||
return cliPath;
|
return cliPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install a CLI tool from GitHub releases
|
||||||
|
* Downloads the latest release asset from GitHub and returns the path to the executable
|
||||||
|
* The temp directory will be cleaned up by the OS automatically
|
||||||
|
*/
|
||||||
|
export async function installFromGithub({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
tag,
|
||||||
|
assetName,
|
||||||
|
executablePath,
|
||||||
|
}: InstallFromGithubParams): Promise<string> {
|
||||||
|
log.info(`📦 Installing ${owner}/${repo} from GitHub releases...`);
|
||||||
|
|
||||||
|
// fetch release from GitHub API (specific tag or latest)
|
||||||
|
const releaseUrl = tag
|
||||||
|
? `https://api.github.com/repos/${owner}/${repo}/releases/tags/${tag}`
|
||||||
|
: `https://api.github.com/repos/${owner}/${repo}/releases/latest`;
|
||||||
|
log.info(`Fetching release from ${releaseUrl}...`);
|
||||||
|
const releaseResponse = await fetch(releaseUrl);
|
||||||
|
if (!releaseResponse.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to fetch release: ${releaseResponse.status} ${releaseResponse.statusText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const releaseData = (await releaseResponse.json()) as {
|
||||||
|
tag_name: string;
|
||||||
|
assets: Array<{
|
||||||
|
name: string;
|
||||||
|
browser_download_url: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
log.info(`Found release: ${releaseData.tag_name}`);
|
||||||
|
|
||||||
|
const asset = releaseData.assets.find((a) => a.name === assetName);
|
||||||
|
if (!asset) {
|
||||||
|
throw new Error(`Asset '${assetName}' not found in release ${releaseData.tag_name}`);
|
||||||
|
}
|
||||||
|
const assetUrl = asset.browser_download_url;
|
||||||
|
|
||||||
|
log.info(`Downloading asset from ${assetUrl}...`);
|
||||||
|
|
||||||
|
// create temp directory
|
||||||
|
const tempDirPrefix = `${owner}-${repo}-github-`;
|
||||||
|
const tempDir = await mkdtemp(join(tmpdir(), tempDirPrefix));
|
||||||
|
|
||||||
|
// determine file extension and download path
|
||||||
|
const urlPath = new URL(assetUrl).pathname;
|
||||||
|
const fileName = urlPath.split("/").pop() || "asset";
|
||||||
|
const downloadPath = join(tempDir, fileName);
|
||||||
|
|
||||||
|
// download the asset
|
||||||
|
const assetResponse = await fetch(assetUrl);
|
||||||
|
if (!assetResponse.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to download asset: ${assetResponse.status} ${assetResponse.statusText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!assetResponse.body) throw new Error("Response body is null");
|
||||||
|
const fileStream = createWriteStream(downloadPath);
|
||||||
|
await pipeline(assetResponse.body, fileStream);
|
||||||
|
log.info(`Downloaded asset to ${downloadPath}`);
|
||||||
|
|
||||||
|
// determine the executable path
|
||||||
|
let cliPath: string;
|
||||||
|
if (executablePath) {
|
||||||
|
cliPath = join(tempDir, executablePath);
|
||||||
|
} else {
|
||||||
|
// no executablePath, assume the downloaded file is the executable
|
||||||
|
cliPath = downloadPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!existsSync(cliPath)) {
|
||||||
|
throw new Error(`Executable not found at ${cliPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
chmodSync(cliPath, 0o755);
|
||||||
|
log.info(`✓ Installed from GitHub release at ${cliPath}`);
|
||||||
|
|
||||||
|
return cliPath;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install a CLI tool from a curl-based install script
|
* Install a CLI tool from a curl-based install script
|
||||||
* Downloads the install script, runs it with HOME set to temp directory, and returns the path to the CLI executable
|
* Downloads the install script, runs it with HOME set to temp directory, and returns the path to the CLI executable
|
||||||
|
|||||||
+1
-3
@@ -1,3 +1 @@
|
|||||||
Print the MCP tools available to you.
|
write a comment to https://github.com/pullfrogai/scratch/pull/29 that tells a joke
|
||||||
Try to call select_mode with the name "Plan".
|
|
||||||
Then tell me a joke
|
|
||||||
@@ -135,7 +135,6 @@ To fix this, add the required secret to your GitHub repository:
|
|||||||
interface MainContext {
|
interface MainContext {
|
||||||
inputs: Inputs;
|
inputs: Inputs;
|
||||||
githubInstallationToken: string;
|
githubInstallationToken: string;
|
||||||
tokenToRevoke: string | null;
|
|
||||||
repoContext: RepoContext;
|
repoContext: RepoContext;
|
||||||
agentName: AgentNameType;
|
agentName: AgentNameType;
|
||||||
agent: (typeof agents)[AgentNameType];
|
agent: (typeof agents)[AgentNameType];
|
||||||
@@ -155,16 +154,14 @@ async function initializeContext(
|
|||||||
Inputs.assert(inputs);
|
Inputs.assert(inputs);
|
||||||
setupGitConfig();
|
setupGitConfig();
|
||||||
|
|
||||||
const { githubInstallationToken, wasAcquired } = await setupGitHubInstallationToken();
|
const githubInstallationToken = await setupGitHubInstallationToken();
|
||||||
const tokenToRevoke = wasAcquired ? githubInstallationToken : null;
|
|
||||||
const repoContext = parseRepoContext();
|
const repoContext = parseRepoContext();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputs,
|
inputs,
|
||||||
githubInstallationToken,
|
githubInstallationToken,
|
||||||
tokenToRevoke,
|
|
||||||
repoContext,
|
repoContext,
|
||||||
agentName: "claude" as AgentNameType,
|
agentName: "claude",
|
||||||
agent: agents.claude,
|
agent: agents.claude,
|
||||||
sharedTempDir: "",
|
sharedTempDir: "",
|
||||||
mcpLogPath: "",
|
mcpLogPath: "",
|
||||||
@@ -292,7 +289,5 @@ async function cleanup(ctx: Omit<MainContext, "mcpServers" | "cliPath" | "apiKey
|
|||||||
if (ctx.pollInterval) {
|
if (ctx.pollInterval) {
|
||||||
clearInterval(ctx.pollInterval);
|
clearInterval(ctx.pollInterval);
|
||||||
}
|
}
|
||||||
if (ctx.tokenToRevoke) {
|
await revokeInstallationToken(ctx.githubInstallationToken);
|
||||||
await revokeInstallationToken(ctx.tokenToRevoke);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -2,6 +2,6 @@ import { configure } from "arktype/config";
|
|||||||
|
|
||||||
configure({
|
configure({
|
||||||
toJsonSchema: {
|
toJsonSchema: {
|
||||||
// dialect: null,
|
dialect: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export async function run(prompt: string): Promise<AgentResult> {
|
|||||||
// we don't need to extract it here since main() will parse the payload
|
// we don't need to extract it here since main() will parse the payload
|
||||||
const inputs: Required<Inputs> = {
|
const inputs: Required<Inputs> = {
|
||||||
prompt,
|
prompt,
|
||||||
defaultAgent: "cursor",
|
defaultAgent: "gemini",
|
||||||
...flatMorph(agents, (_, agent) =>
|
...flatMorph(agents, (_, agent) =>
|
||||||
agent.apiKeyNames.map((inputKey) => [inputKey, process.env[inputKey.toUpperCase()]])
|
agent.apiKeyNames.map((inputKey) => [inputKey, process.env[inputKey.toUpperCase()]])
|
||||||
),
|
),
|
||||||
|
|||||||
+2
-19
@@ -43,12 +43,6 @@ interface RepositoriesResponse {
|
|||||||
repositories: Repository[];
|
repositories: Repository[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkExistingToken(): string | null {
|
|
||||||
const inputToken = core.getInput("github_installation_token");
|
|
||||||
const envToken = process.env.GITHUB_INSTALLATION_TOKEN;
|
|
||||||
return inputToken || envToken || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isGitHubActionsEnvironment(): boolean {
|
function isGitHubActionsEnvironment(): boolean {
|
||||||
return Boolean(process.env.GITHUB_ACTIONS);
|
return Boolean(process.env.GITHUB_ACTIONS);
|
||||||
}
|
}
|
||||||
@@ -249,24 +243,13 @@ async function acquireNewToken(): Promise<string> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup GitHub installation token for the action
|
* Setup GitHub installation token for the action
|
||||||
* Returns the token and whether it was acquired (needs revocation)
|
|
||||||
*/
|
*/
|
||||||
export async function setupGitHubInstallationToken(): Promise<{
|
export async function setupGitHubInstallationToken(): Promise<string> {
|
||||||
githubInstallationToken: string;
|
|
||||||
wasAcquired: boolean;
|
|
||||||
}> {
|
|
||||||
const existingToken = checkExistingToken();
|
|
||||||
if (existingToken) {
|
|
||||||
core.setSecret(existingToken);
|
|
||||||
log.info("Using provided GitHub installation token");
|
|
||||||
return { githubInstallationToken: existingToken, wasAcquired: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
const acquiredToken = await acquireNewToken();
|
const acquiredToken = await acquireNewToken();
|
||||||
core.setSecret(acquiredToken);
|
core.setSecret(acquiredToken);
|
||||||
process.env.GITHUB_INSTALLATION_TOKEN = acquiredToken;
|
process.env.GITHUB_INSTALLATION_TOKEN = acquiredToken;
|
||||||
|
|
||||||
return { githubInstallationToken: acquiredToken, wasAcquired: true };
|
return acquiredToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user