Add support for exposing Claude's terminal interface via browser using ttyd and cloudflared tunnel. - Add cloudflare_tunnel_token input parameter to both action.yml files - Spawn ttyd process on port 7681 to serve Claude's CLI interface - Start cloudflared tunnel process with provided token to expose via web - Implement proper process cleanup in finally block - Add 3-second startup delay for process initialization When cloudflare_tunnel_token is provided, users can access Claude's interactive terminal through their browser via the cloudflared tunnel. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
96 lines
2.9 KiB
TypeScript
96 lines
2.9 KiB
TypeScript
#!/usr/bin/env bun
|
|
|
|
import * as core from "@actions/core";
|
|
import { preparePrompt } from "./prepare-prompt";
|
|
import { runClaude } from "./run-claude";
|
|
import { setupClaudeCodeSettings } from "./setup-claude-code-settings";
|
|
import { validateEnvironmentVariables } from "./validate-env";
|
|
import { spawn } from "child_process";
|
|
|
|
async function run() {
|
|
try {
|
|
validateEnvironmentVariables();
|
|
|
|
await setupClaudeCodeSettings(
|
|
process.env.INPUT_SETTINGS,
|
|
undefined, // homeDir
|
|
process.env.INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR,
|
|
);
|
|
|
|
const promptConfig = await preparePrompt({
|
|
prompt: process.env.INPUT_PROMPT || "",
|
|
promptFile: process.env.INPUT_PROMPT_FILE || "",
|
|
});
|
|
|
|
// Setup ttyd and cloudflared tunnel if token provided
|
|
let ttydProcess: any = null;
|
|
let cloudflaredProcess: any = null;
|
|
|
|
if (process.env.INPUT_CLOUDFLARE_TUNNEL_TOKEN) {
|
|
console.log("Setting up ttyd and cloudflared tunnel...");
|
|
|
|
// Start ttyd process in background
|
|
ttydProcess = spawn("ttyd", ["-p", "7681", "-i", "0.0.0.0", "claude"], {
|
|
stdio: "inherit",
|
|
detached: true,
|
|
});
|
|
|
|
ttydProcess.on("error", (error: Error) => {
|
|
console.warn(`ttyd process error: ${error.message}`);
|
|
});
|
|
|
|
// Start cloudflared tunnel
|
|
cloudflaredProcess = spawn("cloudflared", ["tunnel", "run", "--token", process.env.INPUT_CLOUDFLARE_TUNNEL_TOKEN], {
|
|
stdio: "inherit",
|
|
detached: true,
|
|
});
|
|
|
|
cloudflaredProcess.on("error", (error: Error) => {
|
|
console.warn(`cloudflared process error: ${error.message}`);
|
|
});
|
|
|
|
// Give processes time to start up
|
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
console.log("ttyd and cloudflared tunnel started");
|
|
}
|
|
|
|
try {
|
|
await runClaude(promptConfig.path, {
|
|
allowedTools: process.env.INPUT_ALLOWED_TOOLS,
|
|
disallowedTools: process.env.INPUT_DISALLOWED_TOOLS,
|
|
maxTurns: process.env.INPUT_MAX_TURNS,
|
|
mcpConfig: process.env.INPUT_MCP_CONFIG,
|
|
systemPrompt: process.env.INPUT_SYSTEM_PROMPT,
|
|
appendSystemPrompt: process.env.INPUT_APPEND_SYSTEM_PROMPT,
|
|
claudeEnv: process.env.INPUT_CLAUDE_ENV,
|
|
fallbackModel: process.env.INPUT_FALLBACK_MODEL,
|
|
model: process.env.ANTHROPIC_MODEL,
|
|
});
|
|
} finally {
|
|
// Clean up processes
|
|
if (ttydProcess) {
|
|
try {
|
|
ttydProcess.kill("SIGTERM");
|
|
} catch (e) {
|
|
console.warn("Failed to terminate ttyd process");
|
|
}
|
|
}
|
|
if (cloudflaredProcess) {
|
|
try {
|
|
cloudflaredProcess.kill("SIGTERM");
|
|
} catch (e) {
|
|
console.warn("Failed to terminate cloudflared process");
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
core.setFailed(`Action failed with error: ${error}`);
|
|
core.setOutput("conclusion", "failure");
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
run();
|
|
}
|