try download claude
This commit is contained in:
+100
-1
@@ -1,16 +1,113 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import { createWriteStream, existsSync, rmSync } from "node:fs";
|
||||
import { mkdtemp } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { pipeline } from "node:stream/promises";
|
||||
import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
import packageJson from "../package.json" with { type: "json" };
|
||||
import { log } from "../utils/cli.ts";
|
||||
import { type Agent, instructions } from "./shared.ts";
|
||||
|
||||
let cachedCliPath: string | undefined;
|
||||
|
||||
export const claude: Agent = {
|
||||
install: async (): Promise<string> => {
|
||||
if (cachedCliPath) {
|
||||
log.info(`Using cached Claude Code CLI at ${cachedCliPath}`);
|
||||
return cachedCliPath;
|
||||
}
|
||||
|
||||
// Get the SDK version from package.json and resolve to actual version
|
||||
const versionRange = packageJson.dependencies["@anthropic-ai/claude-agent-sdk"] || "latest";
|
||||
let sdkVersion: string;
|
||||
|
||||
// If it's a range (starts with ^ or ~), query npm registry for the latest matching version
|
||||
if (versionRange.startsWith("^") || versionRange.startsWith("~")) {
|
||||
const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
|
||||
log.info(`Resolving version for range ${versionRange}...`);
|
||||
try {
|
||||
const registryResponse = await fetch(`${npmRegistry}/@anthropic-ai/claude-agent-sdk`);
|
||||
if (!registryResponse.ok) {
|
||||
throw new Error(`Failed to query registry: ${registryResponse.status}`);
|
||||
}
|
||||
const registryData = (await registryResponse.json()) as {
|
||||
"dist-tags": { latest: string };
|
||||
versions: Record<string, unknown>;
|
||||
};
|
||||
// Get the latest version that matches the range (simplified: just use latest)
|
||||
sdkVersion = registryData["dist-tags"].latest;
|
||||
log.info(`Resolved to version ${sdkVersion}`);
|
||||
} catch (error) {
|
||||
log.warning(
|
||||
`Failed to resolve version from registry, using latest: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
sdkVersion = "latest";
|
||||
}
|
||||
} else {
|
||||
sdkVersion = versionRange;
|
||||
}
|
||||
|
||||
log.info(`📦 Installing Claude Code CLI from @anthropic-ai/claude-agent-sdk@${sdkVersion}...`);
|
||||
|
||||
// Create temp directory
|
||||
const tempDir = await mkdtemp(join(tmpdir(), "claude-cli-"));
|
||||
const tarballPath = join(tempDir, "package.tgz");
|
||||
|
||||
try {
|
||||
// Download tarball from npm
|
||||
const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
|
||||
const tarballUrl = `${npmRegistry}/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-${sdkVersion}.tgz`;
|
||||
|
||||
log.info(`Downloading from ${tarballUrl}...`);
|
||||
const response = await fetch(tarballUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download tarball: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
// Write tarball to file
|
||||
const fileStream = createWriteStream(tarballPath);
|
||||
await pipeline(response.body as any, fileStream);
|
||||
log.info(`Downloaded tarball to ${tarballPath}`);
|
||||
|
||||
// Extract tarball
|
||||
log.info(`Extracting tarball...`);
|
||||
execSync(`tar -xzf "${tarballPath}" -C "${tempDir}"`, { stdio: "pipe" });
|
||||
|
||||
// Find cli.js in the extracted package
|
||||
const extractedDir = join(tempDir, "package");
|
||||
const cliPath = join(extractedDir, "cli.js");
|
||||
|
||||
if (!existsSync(cliPath)) {
|
||||
throw new Error(`cli.js not found in extracted package at ${cliPath}`);
|
||||
}
|
||||
|
||||
cachedCliPath = cliPath;
|
||||
log.info(`✓ Claude Code CLI installed at ${cliPath}`);
|
||||
return cliPath;
|
||||
} catch (error) {
|
||||
// Cleanup on error
|
||||
try {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
run: async ({ prompt, mcpServers, apiKey }) => {
|
||||
process.env.ANTHROPIC_API_KEY = apiKey;
|
||||
|
||||
if (!cachedCliPath) {
|
||||
throw new Error("Claude CLI not installed. Call install() before run().");
|
||||
}
|
||||
|
||||
const queryInstance = query({
|
||||
prompt: `${instructions}\n\n${prompt}`,
|
||||
prompt: `${instructions}\n\n****** USER PROMPT ******\n${prompt}`,
|
||||
options: {
|
||||
permissionMode: "bypassPermissions",
|
||||
mcpServers,
|
||||
pathToClaudeCodeExecutable: cachedCliPath,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -142,4 +239,6 @@ const messageHandlers: SDKMessageHandlers = {
|
||||
},
|
||||
system: () => {},
|
||||
stream_event: () => {},
|
||||
tool_progress: () => {},
|
||||
auth_status: () => {},
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface AgentConfig {
|
||||
}
|
||||
|
||||
export type Agent = {
|
||||
install: () => Promise<string>;
|
||||
run: (config: AgentConfig) => Promise<AgentResult>;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,11 +7,20 @@
|
||||
import * as core from "@actions/core";
|
||||
import { type Inputs, main } from "./main.ts";
|
||||
import { createMcpServer } from "./mcp/server.ts";
|
||||
import { log } from "./utils/cli.ts";
|
||||
|
||||
// Export createMcpServer so it can be called from the spawned MCP process
|
||||
export { createMcpServer };
|
||||
|
||||
async function run(): Promise<void> {
|
||||
// Change to GITHUB_WORKSPACE if set (this is where actions/checkout puts the repo)
|
||||
// JavaScript actions run from the action's directory, not the checked-out repo
|
||||
if (process.env.GITHUB_WORKSPACE && process.cwd() !== process.env.GITHUB_WORKSPACE) {
|
||||
log.debug(`Changing to GITHUB_WORKSPACE: ${process.env.GITHUB_WORKSPACE}`);
|
||||
process.chdir(process.env.GITHUB_WORKSPACE);
|
||||
log.debug(`New working directory: ${process.cwd()}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const inputs: Inputs = {
|
||||
prompt: core.getInput("prompt", { required: true }),
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ await build({
|
||||
format: "esm",
|
||||
platform: "node",
|
||||
target: "node20",
|
||||
minify: true,
|
||||
minify: false,
|
||||
sourcemap: false,
|
||||
// Bundle all dependencies - GitHub Actions doesn't have node_modules
|
||||
// Only mark optional peer dependencies as external
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { readdir } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { type } from "arktype";
|
||||
import { claude } from "./agents/claude.ts";
|
||||
import { createMcpConfigs } from "./mcp/config.ts";
|
||||
@@ -21,18 +23,44 @@ export interface MainResult {
|
||||
|
||||
export type PromptJSON = {};
|
||||
|
||||
async function printDirectoryTree(dir: string, prefix = "", rootDir = dir): Promise<string> {
|
||||
const entries = await readdir(dir, { withFileTypes: true });
|
||||
const lines: string[] = [];
|
||||
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const entry = entries[i];
|
||||
const isLast = i === entries.length - 1;
|
||||
const currentPrefix = isLast ? "└── " : "├── ";
|
||||
const nextPrefix = isLast ? " " : "│ ";
|
||||
|
||||
const fullPath = join(dir, entry.name);
|
||||
lines.push(`${prefix}${currentPrefix}${entry.name}`);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
const subTree = await printDirectoryTree(fullPath, `${prefix}${nextPrefix}`, rootDir);
|
||||
lines.push(subTree);
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
export async function main(inputs: Inputs): Promise<MainResult> {
|
||||
try {
|
||||
log.info(`🐸 Running pullfrog/action@${packageJson.version}...`);
|
||||
|
||||
// Change to GITHUB_WORKSPACE if set (this is where actions/checkout puts the repo)
|
||||
// JavaScript actions run from the action's directory, not the checked-out repo
|
||||
if (process.env.GITHUB_WORKSPACE && process.cwd() !== process.env.GITHUB_WORKSPACE) {
|
||||
log.debug(`Changing to GITHUB_WORKSPACE: ${process.env.GITHUB_WORKSPACE}`);
|
||||
process.chdir(process.env.GITHUB_WORKSPACE);
|
||||
log.debug(`New working directory: ${process.cwd()}`);
|
||||
// Debug: Print current directory tree before anything runs
|
||||
const cwd = process.cwd();
|
||||
log.info(`Current working directory: ${cwd}`);
|
||||
try {
|
||||
const tree = await printDirectoryTree(cwd);
|
||||
log.info(`Directory tree:\n${tree}`);
|
||||
} catch (error) {
|
||||
log.warning(
|
||||
`Failed to print directory tree: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
|
||||
log.info(`🐸 Running pullfrog/action@${packageJson.version}...`);
|
||||
|
||||
setupGitConfig();
|
||||
|
||||
const githubInstallationToken = await setupGitHubInstallationToken();
|
||||
@@ -44,6 +72,9 @@ export async function main(inputs: Inputs): Promise<MainResult> {
|
||||
|
||||
log.debug(`📋 MCP Config: ${JSON.stringify(mcpServers, null, 2)}`);
|
||||
|
||||
// Install Claude CLI before running
|
||||
await claude.install();
|
||||
|
||||
log.info("Running Claude Agent SDK...");
|
||||
log.box(inputs.prompt, { title: "Prompt" });
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pullfrog/action",
|
||||
"version": "0.0.80",
|
||||
"version": "0.0.81",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"index.js",
|
||||
|
||||
@@ -47,7 +47,6 @@ export function setupGitConfig(): void {
|
||||
// Only set up git config in GitHub Actions environment
|
||||
// In local development, use the user's existing git config
|
||||
if (!process.env.GITHUB_ACTIONS) {
|
||||
log.warning("Skipping git configuration setup (not in GitHub Actions)");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -73,7 +72,6 @@ export function setupGitAuth(githubToken: string, repoContext: RepoContext): voi
|
||||
// Only set up git auth in GitHub Actions environment
|
||||
// In local testing, this would overwrite the real git remote with fake credentials
|
||||
if (!process.env.GITHUB_ACTIONS) {
|
||||
log.warning("Skipping git authentication setup (not in GitHub Actions)");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user