mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 22:44:13 +08:00
feat: add ttyd and cloudflared tunnel integration
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>
This commit is contained in:
@@ -114,6 +114,10 @@ inputs:
|
||||
description: "Restrict network access to these domains only (newline-separated). If not set, no restrictions are applied. Provider domains are auto-detected."
|
||||
required: false
|
||||
default: ""
|
||||
cloudflare_tunnel_token:
|
||||
description: "Cloudflare tunnel token to expose Claude UI via browser (optional)"
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
outputs:
|
||||
execution_file:
|
||||
@@ -206,6 +210,7 @@ runs:
|
||||
INPUT_CLAUDE_ENV: ${{ inputs.claude_env }}
|
||||
INPUT_FALLBACK_MODEL: ${{ inputs.fallback_model }}
|
||||
INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands
|
||||
INPUT_CLOUDFLARE_TUNNEL_TOKEN: ${{ inputs.cloudflare_tunnel_token }}
|
||||
|
||||
# Model configuration
|
||||
ANTHROPIC_MODEL: ${{ inputs.model || inputs.anthropic_model }}
|
||||
|
||||
@@ -87,6 +87,10 @@ inputs:
|
||||
description: "Whether to use Node.js dependency caching (set to true only for Node.js projects with lock files)"
|
||||
required: false
|
||||
default: "false"
|
||||
cloudflare_tunnel_token:
|
||||
description: "Cloudflare tunnel token to expose Claude UI via browser (optional)"
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
outputs:
|
||||
conclusion:
|
||||
@@ -147,6 +151,7 @@ runs:
|
||||
INPUT_CLAUDE_ENV: ${{ inputs.claude_env }}
|
||||
INPUT_FALLBACK_MODEL: ${{ inputs.fallback_model }}
|
||||
INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ inputs.experimental_slash_commands_dir }}
|
||||
INPUT_CLOUDFLARE_TUNNEL_TOKEN: ${{ inputs.cloudflare_tunnel_token }}
|
||||
|
||||
# Provider configuration
|
||||
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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 {
|
||||
@@ -21,6 +22,39 @@ async function run() {
|
||||
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,
|
||||
@@ -32,6 +66,23 @@ async function run() {
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user