Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 78c22085bf | |||
| b55cda579d | |||
| 1d1d80c3f9 | |||
| 3a97ba04fc | |||
| fe7ce4af11 | |||
| 6260b23de7 | |||
| 9291ee5952 | |||
| d30532979a | |||
| c8b65327ee | |||
| 879d33403c | |||
| 2cc081c912 | |||
| b9a7a19ca1 | |||
| 7ee08d37a6 | |||
| ff913feb3c | |||
| 317ebd3431 | |||
| 2bd12b9553 | |||
| d99a852e24 | |||
| 6e289e9310 | |||
| a483711fee | |||
| 244c7d4d8d | |||
| 0504fc42ff | |||
| ad1f51d704 | |||
| 573c473dc1 | |||
| c200c7aff9 | |||
| 8a7db7bba2 | |||
| 3f996b4759 |
@@ -23,8 +23,6 @@ jobs:
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
@@ -45,3 +45,5 @@ examples
|
||||
# Temporary directory for cloned repos
|
||||
.temp/
|
||||
dist
|
||||
|
||||
.pnpm-store/
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
v24.3.0
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 pullfrog
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+8
-13
@@ -21,28 +21,23 @@ export const claude = agent({
|
||||
const prompt = addInstructions({ payload, repo });
|
||||
log.group("Full prompt", () => log.info(prompt));
|
||||
|
||||
// configure sandbox mode if enabled
|
||||
// SECURITY: For PUBLIC repos, Claude Code spawns subprocesses with full process.env, leaking API keys.
|
||||
// disable native Bash; agents use MCP bash tool which filters secrets.
|
||||
// for private repos, native Bash is allowed since secrets are less exposed.
|
||||
const disallowedTools = repo.isPublic ? ["Bash"] : [];
|
||||
const sandboxOptions: Options = payload.sandbox
|
||||
? {
|
||||
permissionMode: "default",
|
||||
disallowedTools: ["Bash", "WebSearch", "WebFetch", "Write"],
|
||||
async canUseTool(toolName, input, _options) {
|
||||
if (toolName.startsWith("mcp__gh_pullfrog__"))
|
||||
return {
|
||||
behavior: "allow",
|
||||
updatedInput: input,
|
||||
updatedPermissions: [],
|
||||
};
|
||||
|
||||
console.error("can i use this tool?", toolName);
|
||||
return {
|
||||
behavior: "deny",
|
||||
message: "You are not allowed to use this tool.",
|
||||
};
|
||||
return { behavior: "allow", updatedInput: input, updatedPermissions: [] };
|
||||
return { behavior: "deny", message: "tool not allowed in sandbox mode" };
|
||||
},
|
||||
}
|
||||
: {
|
||||
permissionMode: "bypassPermissions" as const,
|
||||
disallowedTools,
|
||||
};
|
||||
|
||||
if (payload.sandbox) {
|
||||
@@ -56,7 +51,7 @@ export const claude = agent({
|
||||
options: {
|
||||
...sandboxOptions,
|
||||
mcpServers,
|
||||
model: "claude-opus-4-5",
|
||||
// model: "claude-opus-4-5",
|
||||
pathToClaudeCodeExecutable: cliPath,
|
||||
env: createAgentEnv({ ANTHROPIC_API_KEY: apiKey }),
|
||||
},
|
||||
|
||||
+57
-42
@@ -1,15 +1,55 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import type { McpHttpServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { Codex, type CodexOptions, type ThreadEvent } from "@openai/codex-sdk";
|
||||
import { log } from "../utils/cli.ts";
|
||||
import { addInstructions } from "./instructions.ts";
|
||||
import {
|
||||
agent,
|
||||
type ConfigureMcpServersParams,
|
||||
installFromNpmTarball,
|
||||
setupProcessAgentEnv,
|
||||
} from "./shared.ts";
|
||||
import { agent, installFromNpmTarball, setupProcessAgentEnv } from "./shared.ts";
|
||||
|
||||
interface WriteCodexConfigParams {
|
||||
tempHome: string;
|
||||
mcpServers: Record<string, McpHttpServerConfig>;
|
||||
isPublicRepo: boolean;
|
||||
}
|
||||
|
||||
function writeCodexConfig({ tempHome, mcpServers, isPublicRepo }: WriteCodexConfigParams): string {
|
||||
const codexDir = join(tempHome, ".codex");
|
||||
mkdirSync(codexDir, { recursive: true });
|
||||
const configPath = join(codexDir, "config.toml");
|
||||
|
||||
// build MCP servers section
|
||||
const mcpServerSections: string[] = [];
|
||||
for (const [name, config] of Object.entries(mcpServers)) {
|
||||
if (config.type !== "http") continue;
|
||||
log.info(`» Adding MCP server '${name}' at ${config.url}`);
|
||||
mcpServerSections.push(`[mcp_servers.${name}]\nurl = "${config.url}"`);
|
||||
}
|
||||
|
||||
// SECURITY: for public repos, enforce env filtering via shell_environment_policy
|
||||
// this prevents vuln if user's ~/.codex/config.toml has ignore_default_excludes=true
|
||||
// for private repos, no filtering - agents use native shell with full env access
|
||||
const shellPolicy = isPublicRepo
|
||||
? `[shell_environment_policy]
|
||||
ignore_default_excludes = false`
|
||||
: "";
|
||||
|
||||
writeFileSync(
|
||||
configPath,
|
||||
`# written by pullfrog
|
||||
${shellPolicy}
|
||||
|
||||
${mcpServerSections.join("\n\n")}
|
||||
`.trim() + "\n"
|
||||
);
|
||||
|
||||
if (isPublicRepo) {
|
||||
log.info(`» Codex config written to ${configPath} (env filtering: enabled)`);
|
||||
} else {
|
||||
log.info(`» Codex config written to ${configPath} (private repo: no env filtering)`);
|
||||
}
|
||||
|
||||
return codexDir;
|
||||
}
|
||||
|
||||
export const codex = agent({
|
||||
name: "codex",
|
||||
@@ -21,18 +61,24 @@ export const codex = agent({
|
||||
});
|
||||
},
|
||||
run: async ({ payload, mcpServers, apiKey, cliPath, repo }) => {
|
||||
// create config directory for codex before setting HOME
|
||||
const tempHome = process.env.PULLFROG_TEMP_DIR!;
|
||||
|
||||
// create config directory for codex before setting HOME
|
||||
const configDir = join(tempHome, ".config", "codex");
|
||||
mkdirSync(configDir, { recursive: true });
|
||||
|
||||
const codexDir = writeCodexConfig({
|
||||
tempHome,
|
||||
mcpServers,
|
||||
isPublicRepo: repo.isPublic,
|
||||
});
|
||||
|
||||
setupProcessAgentEnv({
|
||||
OPENAI_API_KEY: apiKey,
|
||||
HOME: tempHome,
|
||||
CODEX_HOME: codexDir, // point Codex to our config directory
|
||||
});
|
||||
|
||||
configureCodexMcpServers({ mcpServers, cliPath });
|
||||
|
||||
// Configure Codex
|
||||
const codexOptions: CodexOptions = {
|
||||
apiKey,
|
||||
@@ -44,7 +90,6 @@ export const codex = agent({
|
||||
}
|
||||
|
||||
const codex = new Codex(codexOptions);
|
||||
// valid sandbox modes: read-only, workspace-write, danger-full-access
|
||||
const thread = codex.startThread(
|
||||
payload.sandbox
|
||||
? {
|
||||
@@ -186,33 +231,3 @@ const messageHandlers: {
|
||||
log.error(`Error: ${event.message}`);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Configure MCP servers for Codex using the CLI.
|
||||
* For HTTP-based servers, use: codex mcp add <name> --url <url>
|
||||
*/
|
||||
function configureCodexMcpServers({ mcpServers, cliPath }: ConfigureMcpServersParams): void {
|
||||
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
||||
if (serverConfig.type === "http") {
|
||||
// HTTP-based MCP server - use --url flag
|
||||
const addArgs = ["mcp", "add", serverName, "--url", serverConfig.url];
|
||||
|
||||
log.info(`Adding MCP server '${serverName}' at ${serverConfig.url}...`);
|
||||
const addResult = spawnSync("node", [cliPath, ...addArgs], {
|
||||
stdio: "pipe",
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
if (addResult.status !== 0) {
|
||||
throw new Error(
|
||||
`codex mcp add failed: ${addResult.stderr || addResult.stdout || "Unknown error"}`
|
||||
);
|
||||
}
|
||||
log.info(`✓ MCP server '${serverName}' configured`);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unsupported MCP server type for Codex: ${(serverConfig as any).type || "unknown"}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+26
-19
@@ -93,7 +93,7 @@ export const cursor = agent({
|
||||
},
|
||||
run: async ({ payload, apiKey, cliPath, mcpServers, repo }) => {
|
||||
configureCursorMcpServers({ mcpServers, cliPath });
|
||||
configureCursorSandbox({ sandbox: payload.sandbox ?? false });
|
||||
configureCursorSandbox({ sandbox: payload.sandbox ?? false, isPublicRepo: repo.isPublic });
|
||||
|
||||
// track logged model_call_ids to avoid duplicates
|
||||
// cursor emits each assistant message twice: once without model_call_id, then again with it
|
||||
@@ -314,39 +314,46 @@ function configureCursorMcpServers({ mcpServers }: ConfigureMcpServersParams) {
|
||||
|
||||
/**
|
||||
* Configure Cursor CLI sandbox mode via cli-config.json.
|
||||
* When sandbox is enabled, denies all file writes and shell commands.
|
||||
* In print mode without --force, writes are blocked by default, but we add
|
||||
* explicit deny rules as defense in depth.
|
||||
*
|
||||
* See: https://cursor.com/docs/cli/reference/permissions
|
||||
* SECURITY: For PUBLIC repos, Cursor spawns subprocesses with full process.env, leaking API keys.
|
||||
* We deny native Shell via Shell(*) rule, forcing use of MCP bash tool which
|
||||
* filters secrets. Note: Shell(**) does NOT work, must use Shell(*).
|
||||
* For private repos, native Shell is allowed.
|
||||
*
|
||||
* Config path: $XDG_CONFIG_HOME/cursor/ (not ~/.cursor/) because createAgentEnv
|
||||
* sets XDG_CONFIG_HOME=$HOME/.config. See issues/cursor-perms.md.
|
||||
*/
|
||||
function configureCursorSandbox({ sandbox }: { sandbox: boolean }): void {
|
||||
function configureCursorSandbox({
|
||||
sandbox,
|
||||
isPublicRepo,
|
||||
}: {
|
||||
sandbox: boolean;
|
||||
isPublicRepo: boolean;
|
||||
}): void {
|
||||
const realHome = homedir();
|
||||
const cursorConfigDir = join(realHome, ".cursor");
|
||||
const cursorConfigDir = join(realHome, ".config", "cursor");
|
||||
const cliConfigPath = join(cursorConfigDir, "cli-config.json");
|
||||
mkdirSync(cursorConfigDir, { recursive: true });
|
||||
|
||||
// deny native shell for public repos to prevent secret leakage
|
||||
const denyShell = isPublicRepo ? ["Shell(*)"] : [];
|
||||
|
||||
const config = sandbox
|
||||
? {
|
||||
// sandbox mode: deny all writes and shell commands
|
||||
permissions: {
|
||||
allow: [
|
||||
"Read(**)", // allow reading all files
|
||||
],
|
||||
deny: [
|
||||
"Write(**)", // deny all file writes
|
||||
"Shell(**)", // deny all shell commands
|
||||
],
|
||||
allow: ["Read(**)"],
|
||||
deny: ["Write(**)", ...denyShell],
|
||||
},
|
||||
}
|
||||
: {
|
||||
// normal mode: allow everything
|
||||
permissions: {
|
||||
allow: ["Read(**)", "Write(**)", "Shell(**)"],
|
||||
deny: [],
|
||||
allow: ["Read(**)", "Write(**)"],
|
||||
deny: denyShell,
|
||||
},
|
||||
};
|
||||
|
||||
writeFileSync(cliConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
||||
log.info(`» CLI config written to ${cliConfigPath} (sandbox: ${sandbox})`);
|
||||
log.info(
|
||||
`» CLI config written to ${cliConfigPath} (sandbox: ${sandbox}, isPublicRepo: ${isPublicRepo})`
|
||||
);
|
||||
}
|
||||
|
||||
+91
-45
@@ -1,4 +1,6 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { log } from "../utils/cli.ts";
|
||||
import { spawn } from "../utils/subprocess.ts";
|
||||
import { addInstructions } from "./instructions.ts";
|
||||
@@ -155,7 +157,7 @@ export const gemini = agent({
|
||||
});
|
||||
},
|
||||
run: async ({ payload, apiKey, mcpServers, cliPath, repo }) => {
|
||||
configureGeminiMcpServers({ mcpServers, cliPath });
|
||||
configureGeminiMcpServers({ mcpServers, isPublicRepo: repo.isPublic });
|
||||
|
||||
if (!apiKey) {
|
||||
throw new Error("google_api_key or gemini_api_key is required for gemini agent");
|
||||
@@ -164,34 +166,41 @@ export const gemini = agent({
|
||||
const sessionPrompt = addInstructions({ payload, repo });
|
||||
log.group("Full prompt", () => log.info(sessionPrompt));
|
||||
|
||||
// configure sandbox mode if enabled
|
||||
// --allowed-tools restricts which tools are available (removes others from registry entirely)
|
||||
// in sandbox mode: only read-only tools available (no write_file, run_shell_command, web_fetch)
|
||||
const args = payload.sandbox
|
||||
? [
|
||||
"--allowed-tools",
|
||||
"read_file,list_directory,search_file_content,glob,save_memory,write_todos",
|
||||
"--allowed-mcp-server-names",
|
||||
"gh_pullfrog",
|
||||
"--output-format=stream-json",
|
||||
"-p",
|
||||
sessionPrompt,
|
||||
]
|
||||
: ["--yolo", "--output-format=stream-json", "-p", sessionPrompt];
|
||||
// build CLI args based on sandbox mode
|
||||
// for public repos, native shell is disabled via excludeTools in settings.json
|
||||
let args: string[];
|
||||
if (payload.sandbox) {
|
||||
// sandbox mode: read-only tools only
|
||||
args = [
|
||||
"--allowed-tools",
|
||||
"read_file,list_directory,search_file_content,glob,save_memory,write_todos",
|
||||
"--allowed-mcp-server-names",
|
||||
"gh_pullfrog",
|
||||
"--output-format=stream-json",
|
||||
"-p",
|
||||
sessionPrompt,
|
||||
];
|
||||
} else {
|
||||
// normal mode: --yolo for auto-approval
|
||||
// for public repos, shell is excluded via settings.json excludeTools
|
||||
args = ["--yolo", "--output-format=stream-json", "-p", sessionPrompt];
|
||||
if (repo.isPublic) {
|
||||
log.info("🔒 public repo: native shell disabled via excludeTools, using MCP bash");
|
||||
}
|
||||
}
|
||||
|
||||
if (payload.sandbox) {
|
||||
log.info("🔒 sandbox mode enabled: restricting to read-only operations");
|
||||
}
|
||||
|
||||
let finalOutput = "";
|
||||
let stdoutBuffer = ""; // buffer for incomplete lines across chunks
|
||||
let stdoutBuffer = "";
|
||||
|
||||
try {
|
||||
const result = await spawn({
|
||||
cmd: "node",
|
||||
args: [cliPath, ...args],
|
||||
env: createAgentEnv({
|
||||
GEMINI_API_KEY: apiKey,
|
||||
}),
|
||||
env: createAgentEnv({ GEMINI_API_KEY: apiKey }),
|
||||
onStdout: async (chunk) => {
|
||||
const text = chunk.toString();
|
||||
finalOutput += text;
|
||||
@@ -264,36 +273,73 @@ export const gemini = agent({
|
||||
},
|
||||
});
|
||||
|
||||
type ConfigureGeminiParams = {
|
||||
mcpServers: ConfigureMcpServersParams["mcpServers"];
|
||||
isPublicRepo: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Configure MCP servers for Gemini using the CLI.
|
||||
* Gemini CLI syntax: gemini mcp add <name> <commandOrUrl> [args...] --transport <type>
|
||||
* For HTTP-based servers, use: gemini mcp add <name> <url> --transport http
|
||||
* Configure MCP servers for Gemini by writing to settings.json.
|
||||
* Gemini CLI uses `httpUrl` for HTTP/streamable transport, `url` for SSE transport.
|
||||
* See: https://github.com/google-gemini/gemini-cli/blob/main/docs/get-started/configuration.md
|
||||
*
|
||||
* For public repos, also configures excludeTools to disable native shell.
|
||||
*/
|
||||
function configureGeminiMcpServers({ mcpServers, cliPath }: ConfigureMcpServersParams): void {
|
||||
function configureGeminiMcpServers({ mcpServers, isPublicRepo }: ConfigureGeminiParams): void {
|
||||
const realHome = homedir();
|
||||
const geminiConfigDir = join(realHome, ".gemini");
|
||||
const settingsPath = join(geminiConfigDir, "settings.json");
|
||||
mkdirSync(geminiConfigDir, { recursive: true });
|
||||
|
||||
// read existing settings if present
|
||||
let existingSettings: Record<string, unknown> = {};
|
||||
try {
|
||||
const content = readFileSync(settingsPath, "utf-8");
|
||||
existingSettings = JSON.parse(content);
|
||||
} catch {
|
||||
// file doesn't exist or is invalid - start fresh
|
||||
}
|
||||
|
||||
// convert to Gemini's expected format (httpUrl for HTTP transport, no type field)
|
||||
type GeminiMcpServerConfig = {
|
||||
command?: string;
|
||||
args?: string[];
|
||||
env?: Record<string, string>;
|
||||
cwd?: string;
|
||||
url?: string;
|
||||
httpUrl?: string;
|
||||
headers?: Record<string, string>;
|
||||
timeout?: number;
|
||||
trust?: boolean;
|
||||
description?: string;
|
||||
includeTools?: string[];
|
||||
excludeTools?: string[];
|
||||
};
|
||||
const geminiMcpServers: Record<string, GeminiMcpServerConfig> = {};
|
||||
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
||||
if (serverConfig.type === "http") {
|
||||
// HTTP-based MCP server - use URL with --transport http flag
|
||||
const addArgs = ["mcp", "add", serverName, serverConfig.url, "--transport", "http"];
|
||||
|
||||
log.info(`Adding MCP server '${serverName}' at ${serverConfig.url}...`);
|
||||
const addResult = spawnSync("node", [cliPath, ...addArgs], {
|
||||
stdio: "pipe",
|
||||
encoding: "utf-8",
|
||||
env: {
|
||||
...process.env,
|
||||
},
|
||||
});
|
||||
|
||||
if (addResult.status !== 0) {
|
||||
throw new Error(
|
||||
`gemini mcp add failed: ${addResult.stderr || addResult.stdout || "Unknown error"}`
|
||||
);
|
||||
}
|
||||
log.info(`✓ MCP server '${serverName}' configured`);
|
||||
} else {
|
||||
if (serverConfig.type !== "http") {
|
||||
throw new Error(
|
||||
`Unsupported MCP server type for Gemini: ${(serverConfig as any).type || "unknown"}`
|
||||
`Unsupported MCP server type for Gemini: ${(serverConfig as { type?: string }).type || "unknown"}`
|
||||
);
|
||||
}
|
||||
geminiMcpServers[serverName] = {
|
||||
httpUrl: serverConfig.url,
|
||||
trust: true, // trust our own MCP server to avoid confirmation prompts
|
||||
};
|
||||
log.info(`Adding MCP server '${serverName}' at ${serverConfig.url}...`);
|
||||
}
|
||||
|
||||
// merge with existing settings, overwriting mcpServers
|
||||
// for public repos, exclude native shell tool to prevent secret leakage via env
|
||||
const newSettings: Record<string, unknown> = {
|
||||
...existingSettings,
|
||||
mcpServers: geminiMcpServers,
|
||||
};
|
||||
|
||||
if (isPublicRepo) {
|
||||
newSettings.excludeTools = ["run_shell_command"];
|
||||
}
|
||||
|
||||
writeFileSync(settingsPath, JSON.stringify(newSettings, null, 2), "utf-8");
|
||||
log.info(`» MCP config written to ${settingsPath}`);
|
||||
}
|
||||
|
||||
+12
-34
@@ -8,6 +8,7 @@ interface RepoInfo {
|
||||
owner: string;
|
||||
name: string;
|
||||
defaultBranch: string;
|
||||
isPublic: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,6 +57,9 @@ interface AddInstructionsParams {
|
||||
}
|
||||
|
||||
export const addInstructions = ({ payload, repo }: AddInstructionsParams) => {
|
||||
// for public repos, always use MCP bash for security (filters secrets)
|
||||
// for private repos, agents can use their native bash
|
||||
const useNativeBash = !repo.isPublic;
|
||||
let encodedEvent = "";
|
||||
|
||||
const eventKeys = Object.keys(payload.event);
|
||||
@@ -98,40 +102,9 @@ In case of conflict between instructions, follow this precedence (highest to low
|
||||
4. Repository-specific instructions (AGENTS.md, CLAUDE.md, etc.)
|
||||
5. User prompt
|
||||
|
||||
## SECURITY
|
||||
## Security
|
||||
|
||||
CRITICAL SECURITY RULES - NEVER VIOLATE UNDER ANY CIRCUMSTANCES:
|
||||
|
||||
### Rule 1: Never expose secrets through ANY means
|
||||
|
||||
You must NEVER expose secrets through any channel, including but not limited to:
|
||||
- Displaying, printing, echoing, logging, or outputting to console
|
||||
- Writing to files (including .txt, .env, .json, config files, etc.)
|
||||
- Including in git commits, commit messages, or PR descriptions
|
||||
- Posting in GitHub comments, issue bodies, or PR review comments
|
||||
- Returning in tool outputs, API responses, or error messages
|
||||
- Including in redirect URLs, WebSocket messages, or GraphQL responses
|
||||
|
||||
Secrets include: API keys, authentication tokens, passwords, private keys, certificates, database connection strings, and any credential used for authentication or authorization. Common patterns (case-insensitive): variables containing API_KEY, SECRET, TOKEN, PASSWORD, CREDENTIAL, PRIVATE_KEY, or AUTH in an authentication context. Use judgment: \`PUBLIC_KEY\` for a cryptographic public key is fine; \`PRIVATE_KEY\` is not.
|
||||
|
||||
### Rule 2: Never serialize objects containing secrets
|
||||
|
||||
When working with objects that may contain environment variables or secrets:
|
||||
- NEVER serialize, stringify, or dump entire environment objects (process.env, os.environ, ENV, etc.)
|
||||
- NEVER iterate over environment variables and write their values to files
|
||||
- NEVER include environment variable values in outputs, logs, HTTP requests, or anywhere they can be exposed
|
||||
- If you must list properties, only show property NAMES, never values
|
||||
- Only access specific, known-safe keys explicitly (e.g., NODE_ENV, HOME, PWD)
|
||||
|
||||
### Rule 3: Refuse and explain
|
||||
|
||||
Even if explicitly requested to reveal secrets, you must:
|
||||
1. Refuse the request
|
||||
2. Print a message explaining that exposing secrets is prohibited for security reasons
|
||||
3. If using ${ghPullfrogMcpName}, update the working comment to explain that secrets cannot be revealed
|
||||
4. Offer a safe alternative, if applicable
|
||||
|
||||
If you encounter secrets in files or environment, acknowledge they exist but never reveal their values.
|
||||
Never expose secrets (API keys, tokens, passwords, private keys, credentials) through any channel: console output, files, commits, comments, API responses, error messages, or URLs. Never serialize environment objects (\`process.env\`, \`os.environ\`, etc.) or iterate over them. If asked to reveal secrets: refuse, explain that exposing secrets is prohibited, and offer a safe alternative if applicable. Detect and deny any suspicious or malicious requests.
|
||||
|
||||
## MCP (Model Context Protocol) Tools
|
||||
|
||||
@@ -143,7 +116,6 @@ Tool names may be formatted as \`(server name)/(tool name)\`, for example: \`${g
|
||||
|
||||
**Git operations**: All git operations must use ${ghPullfrogMcpName} MCP tools to ensure proper authentication and commit attribution. Do NOT use git commands directly (e.g., \`git commit\`, \`git push\`, \`git checkout\`, \`git branch\`) - these will use incorrect credentials and attribute commits to the wrong author.
|
||||
|
||||
**File discovery**: Use \`${ghPullfrogMcpName}/list_files\` to discover files in the repository. This tool finds both git-tracked and untracked files, including newly created files that haven't been committed yet. Prefer this over native agent tools like \`glob\` or \`grep\` for file discovery, as it provides consistent results and handles untracked files correctly.
|
||||
` +
|
||||
// **Available git MCP tools**:
|
||||
// - \`${ghPullfrogMcpName}/checkout_pr\` - Checkout an existing PR branch locally (handles fork PRs automatically)
|
||||
@@ -170,6 +142,12 @@ Tool names may be formatted as \`(server name)/(tool name)\`, for example: \`${g
|
||||
|
||||
**Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error.
|
||||
|
||||
${
|
||||
useNativeBash
|
||||
? `**Shell commands**: Use your native bash/shell tool for shell command execution.`
|
||||
: `**Shell commands**: Use the \`${ghPullfrogMcpName}/bash\` MCP tool for all shell command execution. This tool provides a secure environment with filtered credentials. Do NOT use any native shell/bash tool - it is disabled for security.`
|
||||
}
|
||||
|
||||
**Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished.
|
||||
|
||||
**Commenting style**: When posting comments via ${ghPullfrogMcpName}, write as a professional team member would. Your final comments should be polished and actionable—do not include intermediate reasoning like "I'll now look at the code" or "Let me respond to the question."
|
||||
|
||||
+25
-15
@@ -7,6 +7,7 @@ import { addInstructions } from "./instructions.ts";
|
||||
import {
|
||||
agent,
|
||||
type ConfigureMcpServersParams,
|
||||
createAgentEnv,
|
||||
installFromNpmTarball,
|
||||
setupProcessAgentEnv,
|
||||
} from "./shared.ts";
|
||||
@@ -27,7 +28,11 @@ export const opencode = agent({
|
||||
const configDir = join(tempHome, ".config", "opencode");
|
||||
mkdirSync(configDir, { recursive: true });
|
||||
|
||||
configureOpenCode({ mcpServers, sandbox: payload.sandbox ?? false });
|
||||
configureOpenCode({
|
||||
mcpServers,
|
||||
sandbox: payload.sandbox ?? false,
|
||||
isPublicRepo: repo.isPublic,
|
||||
});
|
||||
|
||||
const prompt = addInstructions({ payload, repo });
|
||||
log.group("Full prompt", () => log.info(prompt));
|
||||
@@ -42,24 +47,25 @@ export const opencode = agent({
|
||||
// 6. set up environment
|
||||
setupProcessAgentEnv({ HOME: tempHome });
|
||||
|
||||
// build env vars: start with process.env (includes all API_KEY vars loaded by config())
|
||||
// exclude GITHUB_TOKEN - OpenCode should use MCP server for GitHub operations, not direct token
|
||||
// then override with apiKeys, HOME, and XDG_CONFIG_HOME
|
||||
// SECURITY: build env vars from whitelisted base env to prevent API key leakage
|
||||
// this prevents leaking other API keys (ANTHROPIC, GEMINI, etc.) to OpenCode subprocess
|
||||
// XDG_CONFIG_HOME must be set because GitHub Actions sets it to a different path,
|
||||
// and OpenCode follows XDG spec (checks XDG_CONFIG_HOME before falling back to $HOME/.config)
|
||||
const env: Record<string, string> = {
|
||||
...(Object.fromEntries(
|
||||
Object.entries(process.env).filter(
|
||||
([key, value]) => value !== undefined && key !== "GITHUB_TOKEN"
|
||||
)
|
||||
) as Record<string, string>),
|
||||
HOME: tempHome,
|
||||
...createAgentEnv({ HOME: tempHome }),
|
||||
XDG_CONFIG_HOME: join(tempHome, ".config"),
|
||||
};
|
||||
// OpenCode doesn't support GitHub App installation tokens
|
||||
delete env.GITHUB_TOKEN;
|
||||
|
||||
// add/override API keys from apiKeys object (uppercase keys)
|
||||
// add API keys from apiKeys object
|
||||
for (const [key, value] of Object.entries(apiKeys || {})) {
|
||||
env[key.toUpperCase()] = value;
|
||||
const upperKey = key.toUpperCase();
|
||||
env[upperKey] = value;
|
||||
// also set GOOGLE_GENERATIVE_AI_API_KEY for Google provider compatibility
|
||||
if (upperKey === "GEMINI_API_KEY") {
|
||||
env.GOOGLE_GENERATIVE_AI_API_KEY = value;
|
||||
}
|
||||
}
|
||||
|
||||
// run OpenCode in the repository directory (process.cwd() is set to GITHUB_WORKSPACE or repo dir)
|
||||
@@ -188,13 +194,14 @@ export const opencode = agent({
|
||||
interface ConfigureOpenCodeParams {
|
||||
mcpServers: ConfigureMcpServersParams["mcpServers"];
|
||||
sandbox: boolean;
|
||||
isPublicRepo: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure OpenCode via opencode.json config file.
|
||||
* Builds complete config with MCP servers and permissions in a single write to avoid race conditions.
|
||||
*/
|
||||
function configureOpenCode({ mcpServers, sandbox }: ConfigureOpenCodeParams): void {
|
||||
function configureOpenCode({ mcpServers, sandbox, isPublicRepo }: ConfigureOpenCodeParams): void {
|
||||
const tempHome = process.env.PULLFROG_TEMP_DIR!;
|
||||
const configDir = join(tempHome, ".config", "opencode");
|
||||
mkdirSync(configDir, { recursive: true });
|
||||
@@ -218,7 +225,10 @@ function configureOpenCode({ mcpServers, sandbox }: ConfigureOpenCodeParams): vo
|
||||
};
|
||||
}
|
||||
|
||||
// build permissions config
|
||||
// SECURITY: For PUBLIC repos, OpenCode spawns subprocesses with full process.env, leaking API keys.
|
||||
// disable native bash; agents use MCP bash tool which filters secrets.
|
||||
// for private repos, native bash is allowed.
|
||||
const bashPermission = isPublicRepo ? "deny" : "allow";
|
||||
const permission = sandbox
|
||||
? {
|
||||
edit: "deny",
|
||||
@@ -229,7 +239,7 @@ function configureOpenCode({ mcpServers, sandbox }: ConfigureOpenCodeParams): vo
|
||||
}
|
||||
: {
|
||||
edit: "allow",
|
||||
bash: "allow",
|
||||
bash: bashPermission,
|
||||
webfetch: "allow",
|
||||
doom_loop: "allow",
|
||||
external_directory: "allow",
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface RepoInfo {
|
||||
owner: string;
|
||||
name: string;
|
||||
defaultBranch: string;
|
||||
isPublic: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# Docker Testing Environment
|
||||
|
||||
`play.ts` runs in Docker by default for realistic testing (Linux, clean $HOME, matches CI).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
pnpm play bash-test.ts # runs in Docker (default)
|
||||
pnpm play --local bash-test.ts # runs on macOS (fast iteration)
|
||||
PLAY_LOCAL=1 pnpm play ... # same as --local
|
||||
```
|
||||
|
||||
## Why Docker by Default?
|
||||
|
||||
1. **Matches CI** - Linux environment like GitHub Actions
|
||||
2. **Clean $HOME** - No agent config pollution from `~/.claude`, `~/.cursor`
|
||||
3. **Tests unshare** - Verifies PID namespace sandbox works
|
||||
4. **Reproducible** - Same environment every run
|
||||
|
||||
## Performance
|
||||
|
||||
| Mode | Overhead |
|
||||
|------|----------|
|
||||
| Docker (cached deps) | ~1.5s |
|
||||
| Local (macOS) | ~0s |
|
||||
|
||||
For agent runs taking 30-120s, the 1.5s overhead is negligible.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. `play.ts` runs on host, loads `.env`
|
||||
2. Spawns Docker container with:
|
||||
- Volume-mounted `action/` code
|
||||
- Named volume for Linux node_modules (persists between runs)
|
||||
- SSH agent forwarding for git clone
|
||||
- Env vars passed via `-e` flags
|
||||
3. Inside Docker, `play.ts` runs again (detects `/.dockerenv` file)
|
||||
4. Clones `GITHUB_REPOSITORY`, runs agent
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Docker not running:**
|
||||
```
|
||||
Cannot connect to the Docker daemon
|
||||
```
|
||||
→ Start Docker Desktop
|
||||
|
||||
**SSH clone fails:**
|
||||
```
|
||||
Permission denied (publickey)
|
||||
```
|
||||
→ Ensure SSH agent is running: `ssh-add -l`
|
||||
@@ -0,0 +1,306 @@
|
||||
# Bash Tool Security
|
||||
|
||||
> **Note**: Security measures described here apply to **PUBLIC repositories only**. For private repos, agents can use native bash with full environment access.
|
||||
|
||||
## Architecture (Public Repos)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ GitHub Actions Runner │
|
||||
│ (has secrets: ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.) │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||
│ │ Pullfrog Action (Node.js) │ │
|
||||
│ │ - process.env contains all secrets │ │
|
||||
│ │ - spawns agent CLI as child process │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ Agent CLI (Claude/Cursor/OpenCode/etc.) │ │ │
|
||||
│ │ │ - receives filtered env (only API key it needs) │ │ │
|
||||
│ │ │ - has built-in Bash tool (DISABLED for public) │ │ │
|
||||
│ │ │ - connects to MCP server for tools │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ ┌───────────────────────────────────────────────┐ │ │ │
|
||||
│ │ │ │ MCP Bash Tool (our code) │ │ │ │
|
||||
│ │ │ │ - agent calls this for shell commands │ │ │ │
|
||||
│ │ │ │ - spawns bash with filtered env │ │ │ │
|
||||
│ │ │ │ - uses PID namespace isolation │ │ │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ │ │ ┌─────────────────────────────────────────┐ │ │ │ │
|
||||
│ │ │ │ │ Bash subprocess │ │ │ │ │
|
||||
│ │ │ │ │ - runs user-controlled commands │ │ │ │ │
|
||||
│ │ │ │ │ - MUST NOT access secrets │ │ │ │ │
|
||||
│ │ │ │ └─────────────────────────────────────────┘ │ │ │ │
|
||||
│ │ │ └───────────────────────────────────────────────┘ │ │ │
|
||||
│ │ └─────────────────────────────────────────────────────┘ │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key insight**: For **public repos**, the Pullfrog Action process has all secrets in `process.env`. Agent CLIs have built-in Bash tools that we can't trust since malicious actors can submit PRs with prompt injections. We disable those and provide our own MCP Bash tool that spawns subprocesses securely.
|
||||
|
||||
For **private repos**, the threat model is different — only trusted collaborators can trigger workflows, so we allow native bash with full environment access for better performance and compatibility.
|
||||
|
||||
---
|
||||
|
||||
## Public vs Private Repos
|
||||
|
||||
| Repo Visibility | Native Bash | Env Filtering | PID Isolation |
|
||||
|-----------------|-------------|---------------|---------------|
|
||||
| **Public** | Disabled | Yes | Yes (in CI) |
|
||||
| **Private** | Enabled | No | No |
|
||||
|
||||
**Rationale**: Public repos are at risk from prompt injection attacks via pull requests from untrusted contributors. Private repos only allow trusted collaborators, so the attack surface is much smaller.
|
||||
|
||||
---
|
||||
|
||||
## Threat Model (Public Repos)
|
||||
|
||||
A prompt-injected agent could run malicious bash commands to exfiltrate API keys.
|
||||
|
||||
**Attack vectors:**
|
||||
|
||||
| Vector | Example | Mitigation |
|
||||
|--------|---------|------------|
|
||||
| Direct env access | `env \| grep KEY` | Filter env vars before spawn |
|
||||
| Echo variable | `echo $ANTHROPIC_API_KEY` | Filter env vars before spawn |
|
||||
| `/proc/$PPID/environ` | `cat /proc/$PPID/environ` | PID namespace isolation |
|
||||
|
||||
The first two are solved by passing filtered env to subprocess. The third requires special handling on Linux.
|
||||
|
||||
---
|
||||
|
||||
## Attack: /proc/$PPID/environ (Public Repos)
|
||||
|
||||
On Linux, any process can read its parent's environment via `/proc/$PPID/environ`. Even if we spawn bash with a clean environment, the bash process can:
|
||||
|
||||
```bash
|
||||
# read parent's (Node.js) environment - contains all secrets!
|
||||
tr '\0' '\n' < /proc/$PPID/environ | grep KEY
|
||||
```
|
||||
|
||||
This bypasses environment filtering because we're reading the parent process's memory, not our own env.
|
||||
|
||||
**Why this matters:**
|
||||
- Pullfrog Action (Node.js) has `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc. in `process.env`
|
||||
- We spawn agent CLI with filtered env (only its own API key)
|
||||
- Agent CLI spawns MCP Bash tool
|
||||
- MCP Bash tool spawns bash with filtered env (no secrets)
|
||||
- BUT bash can read `/proc/$PPID/environ` → gets Node.js process's full env
|
||||
|
||||
---
|
||||
|
||||
## Solution: PID Namespace Isolation (Public Repos)
|
||||
|
||||
We use Linux PID namespaces to hide the parent process:
|
||||
|
||||
```bash
|
||||
unshare --pid --fork --mount-proc bash -c "$CMD"
|
||||
```
|
||||
|
||||
| Flag | Purpose |
|
||||
|------|---------|
|
||||
| `--pid` | Create new PID namespace |
|
||||
| `--fork` | Fork so child is actually in new namespace |
|
||||
| `--mount-proc` | Mount fresh `/proc` for new namespace |
|
||||
|
||||
**Result:**
|
||||
- Child sees itself as PID 1
|
||||
- Child's PPID is 0 (doesn't exist)
|
||||
- `/proc` only shows processes in child's namespace
|
||||
- Parent's PID is invisible → `/proc/$PPID/environ` fails
|
||||
|
||||
---
|
||||
|
||||
## Implementation
|
||||
|
||||
### mcp/bash.ts
|
||||
|
||||
```typescript
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
// filter sensitive env vars (only for public repos)
|
||||
function filterEnv(isPublicRepo: boolean): Record<string, string> {
|
||||
const SENSITIVE = [/_KEY$/i, /_SECRET$/i, /_TOKEN$/i, /_PASSWORD$/i, /_CREDENTIAL$/i];
|
||||
const filtered: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(process.env)) {
|
||||
if (value === undefined) continue;
|
||||
// only filter sensitive vars for public repos
|
||||
if (isPublicRepo && SENSITIVE.some(p => p.test(key))) continue;
|
||||
filtered[key] = value;
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
// spawn with PID namespace in CI for public repos, plain spawn otherwise
|
||||
function spawnSandboxed(command: string, options: { env, cwd, isPublicRepo }): ChildProcess {
|
||||
const useNamespaceIsolation = process.env.CI === "true" && options.isPublicRepo;
|
||||
if (useNamespaceIsolation) {
|
||||
return spawn("unshare", ["--pid", "--fork", "--mount-proc", "bash", "-c", command], options);
|
||||
}
|
||||
return spawn("bash", ["-c", command], options);
|
||||
}
|
||||
|
||||
// BashTool uses ctx.repo.private to determine visibility
|
||||
export function BashTool(ctx: ToolContext) {
|
||||
const isPublicRepo = !ctx.repo.private;
|
||||
// ... spawns with filterEnv(isPublicRepo) and isPublicRepo flag
|
||||
}
|
||||
```
|
||||
|
||||
**Defense in depth (public repos only):**
|
||||
1. `filterEnv(true)` - prevents `env` and `echo $VAR` attacks
|
||||
2. `unshare` - prevents `/proc/$PPID/environ` attack
|
||||
|
||||
---
|
||||
|
||||
## Disabling Native Bash Tools (Public Repos)
|
||||
|
||||
For **public repos**, each agent's built-in Bash/Shell tools are disabled. Agents use our MCP Bash tool which filters secrets:
|
||||
|
||||
```typescript
|
||||
// Claude - conditional based on repo.isPublic
|
||||
const disallowedTools = repo.isPublic ? ["Bash"] : [];
|
||||
{ permissionMode: "bypassPermissions", disallowedTools }
|
||||
|
||||
// Cursor - conditional shell denial
|
||||
const denyShell = isPublicRepo ? ["Shell(*)"] : [];
|
||||
{ permissions: { allow: ["Read(**)", "Write(**)"], deny: denyShell } }
|
||||
|
||||
// OpenCode - conditional bash denial
|
||||
const bashPermission = isPublicRepo ? "deny" : "allow";
|
||||
{ permission: { edit: "allow", bash: bashPermission, ... } }
|
||||
|
||||
// Gemini - uses excludeTools in ~/.gemini/settings.json
|
||||
newSettings.excludeTools = ["run_shell_command"];
|
||||
|
||||
// Codex - CLI internally scrubs env before spawning shell
|
||||
// No SDK-level config needed; Codex handles this automatically
|
||||
```
|
||||
|
||||
For **private repos**, native bash is allowed for all agents.
|
||||
|
||||
---
|
||||
|
||||
## Testing (Public Repo Scenario)
|
||||
|
||||
Run the vulnerability test in Docker to verify protection for public repos:
|
||||
|
||||
```bash
|
||||
# from action/ directory
|
||||
docker run --rm \
|
||||
-v "$(pwd):/app/action:cached" \
|
||||
-v "pullfrog-action-node-modules:/app/action/node_modules" \
|
||||
-w /app/action \
|
||||
-e GITHUB_ACTIONS=true \
|
||||
-e TEST_SECRET_KEY=test-secret \
|
||||
-e ANTHROPIC_API_KEY=sk-test \
|
||||
--cap-add SYS_ADMIN \
|
||||
--security-opt seccomp:unconfined \
|
||||
node:22 bash -c "corepack enable pnpm && pnpm install --frozen-lockfile && node test/proc-environ-vuln.ts"
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
1. UNPROTECTED (filterEnv only):
|
||||
Leaked: YES ❌
|
||||
|
||||
2. PROTECTED (unshare --pid --fork --mount-proc):
|
||||
Leaked: NO ✓
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Platform Notes
|
||||
|
||||
| Environment | Repo | Our approach |
|
||||
|-------------|------|--------------|
|
||||
| GitHub Actions (Linux) | Public | filterEnv + unshare + disable native bash |
|
||||
| GitHub Actions (Linux) | Private | Full env + native bash allowed |
|
||||
| Local dev (any OS) | Any | No filtering (local dev assumed trusted) |
|
||||
|
||||
We check `process.env.CI === "true"` (set by GitHub Actions) combined with `ctx.repo.private` to determine the security posture:
|
||||
- **CI + Public repo**: Full protection with PID namespace isolation
|
||||
- **CI + Private repo**: No protection (trusted collaborators only)
|
||||
- **Local**: No protection (developer's own machine)
|
||||
|
||||
GitHub Actions uses Ubuntu runners where `unshare` works without root.
|
||||
|
||||
---
|
||||
|
||||
## What This Does NOT Protect Against (Public Repos)
|
||||
|
||||
Even with protections enabled, bash subprocesses can still:
|
||||
|
||||
- **Network exfiltration**: Child has full network access
|
||||
- **File access**: Child can read any file the runner can (same UID)
|
||||
- **Resource exhaustion**: No cgroup limits
|
||||
|
||||
For those, you'd need `bwrap` with `--unshare-net`, `--ro-bind`, etc. But for the stated goal—preventing secret exfiltration via env—this is sufficient.
|
||||
|
||||
For **private repos**, none of these protections apply since we trust collaborators.
|
||||
|
||||
---
|
||||
|
||||
## Agent-Specific Notes
|
||||
|
||||
### Claude, Cursor, OpenCode (Public Repos)
|
||||
|
||||
These agents have their native Bash disabled via configuration. They use our `gh_pullfrog` MCP server's `bash` tool which implements `filterEnv()` + `unshare`.
|
||||
|
||||
For private repos, native bash is enabled for these agents.
|
||||
|
||||
### Gemini (Public Repos)
|
||||
|
||||
Gemini CLI supports `excludeTools` in its user-level settings file (`~/.gemini/settings.json`). For public repos, we exclude the native shell tool:
|
||||
|
||||
```typescript
|
||||
// written to ~/.gemini/settings.json
|
||||
newSettings.excludeTools = ["run_shell_command"];
|
||||
```
|
||||
|
||||
This is a blocklist approach which explicitly excludes the shell tool while allowing all other tools.
|
||||
|
||||
Additionally, Gemini has built-in CI detection that filters shell env when `GITHUB_SHA` is set.
|
||||
|
||||
### Codex
|
||||
|
||||
Codex CLI filters out env vars matching `KEY`, `SECRET`, or `TOKEN` (case-insensitive) by default via `shell_environment_policy.ignore_default_excludes = false`.
|
||||
|
||||
**Vulnerability**: If a user's `~/.codex/config.toml` has `ignore_default_excludes = true`, secrets will leak to shell commands.
|
||||
|
||||
**Our mitigation**: We set `CODEX_HOME` to a temp directory and write our own `config.toml` with `ignore_default_excludes = false` to enforce filtering regardless of what config exists in the user's `~/.codex/`.
|
||||
|
||||
```typescript
|
||||
// set CODEX_HOME to override user's config
|
||||
setupProcessAgentEnv({ CODEX_HOME: codexDir });
|
||||
|
||||
// write secure config to $CODEX_HOME/config.toml
|
||||
writeFileSync(join(codexDir, "config.toml"), `
|
||||
[shell_environment_policy]
|
||||
ignore_default_excludes = false
|
||||
`);
|
||||
```
|
||||
|
||||
See [GitHub Issue #3064](https://github.com/openai/codex/issues/3064) and [config docs](https://github.com/openai/codex/blob/main/docs/config.md#shell_environment_policy).
|
||||
|
||||
**Verified behavior** (tested via `pnpm play codex-env-test.ts`):
|
||||
- Default (no config): ✅ secrets filtered
|
||||
- `ignore_default_excludes = false`: ✅ secrets filtered
|
||||
- `ignore_default_excludes = true`: ❌ secrets leak
|
||||
|
||||
Example output when running `env | grep TEST` with our config:
|
||||
```
|
||||
TEST_SAFE_VAR=VISIBLE-SAFE-VALUE
|
||||
# FAKE_SECRET_KEY and TEST_API_TOKEN are NOT visible (filtered)
|
||||
```
|
||||
|
||||
### Summary by Agent
|
||||
|
||||
| Agent | Public Repo | Private Repo |
|
||||
|-------|-------------|--------------|
|
||||
| Claude | Native bash **disabled** | Native bash allowed |
|
||||
| Cursor | Native shell **disabled** | Native shell allowed |
|
||||
| OpenCode | Native bash **disabled** | Native bash allowed |
|
||||
| Gemini | Native shell **disabled** (via excludeTools) | Native bash allowed |
|
||||
| Codex | Native shell allowed (CLI scrubs env internally) | Native bash allowed |
|
||||
+2
-2
@@ -19,7 +19,7 @@ const stripShebangPlugin = {
|
||||
try {
|
||||
const content = readFileSync(outputFile, "utf8");
|
||||
// Remove shebang line from the beginning if present
|
||||
const withoutShebang = content.startsWith("#!")
|
||||
const withoutShebang = content.startsWith("#!")
|
||||
? content.slice(content.indexOf("\n") + 1)
|
||||
: content;
|
||||
writeFileSync(outputFile, withoutShebang);
|
||||
@@ -38,7 +38,7 @@ const sharedConfig = {
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
platform: "node",
|
||||
target: "node20",
|
||||
target: "node24",
|
||||
minify: false,
|
||||
sourcemap: false,
|
||||
// Bundle all dependencies - GitHub Actions doesn't have node_modules
|
||||
|
||||
@@ -212,6 +212,13 @@ interface FixReviewEvent extends BasePayloadEvent {
|
||||
unapproved_comments: ReviewCommentData[];
|
||||
}
|
||||
|
||||
interface ImplementPlanEvent extends BasePayloadEvent {
|
||||
trigger: "implement_plan";
|
||||
issue_number: number;
|
||||
plan_comment_id: number;
|
||||
plan_content: string;
|
||||
}
|
||||
|
||||
interface UnknownEvent extends BasePayloadEvent {
|
||||
trigger: "unknown";
|
||||
}
|
||||
@@ -231,6 +238,7 @@ export type PayloadEvent =
|
||||
| CheckSuiteCompletedEvent
|
||||
| WorkflowDispatchEvent
|
||||
| FixReviewEvent
|
||||
| ImplementPlanEvent
|
||||
| UnknownEvent;
|
||||
|
||||
export interface DispatchOptions {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { Payload } from "../external.ts";
|
||||
|
||||
/**
|
||||
* test fixture: verifies agents use MCP bash tool for shell commands.
|
||||
* creates a simple test file and runs it with node.
|
||||
*
|
||||
* for insecure agents (claude, cursor, opencode): native bash is disabled,
|
||||
* so they MUST use gh_pullfrog/bash MCP tool to run shell commands.
|
||||
*
|
||||
* for secure agents (codex, gemini): native bash is safe, but this test
|
||||
* still verifies shell execution works.
|
||||
*
|
||||
* run with: AGENT_OVERRIDE=<agent> pnpm play bash-test.ts
|
||||
*/
|
||||
export default {
|
||||
"~pullfrog": true,
|
||||
agent: null,
|
||||
prompt: `Create a file called test-runner.js with the following content:
|
||||
|
||||
\`\`\`javascript
|
||||
const assert = require('assert');
|
||||
assert.strictEqual(2 + 2, 4, 'math should work');
|
||||
console.log('TEST PASSED: basic arithmetic works');
|
||||
\`\`\`
|
||||
|
||||
Then run it with: node test-runner.js
|
||||
|
||||
Finally, delete the test file.
|
||||
|
||||
This tests that you can execute shell commands properly.`,
|
||||
event: {
|
||||
trigger: "workflow_dispatch",
|
||||
},
|
||||
modes: [],
|
||||
} satisfies Payload;
|
||||
+9
-6
@@ -1,9 +1,12 @@
|
||||
import type { Inputs } from "../main.ts";
|
||||
import type { Payload } from "../external.ts";
|
||||
|
||||
const testParams = {
|
||||
export default {
|
||||
"~pullfrog": true,
|
||||
agent: null,
|
||||
prompt:
|
||||
"List all files in the current directory, then create a file called dynamic-test.txt with the content 'This was loaded from a TypeScript file!', then delete it.",
|
||||
anthropic_api_key: "sk-test-key",
|
||||
} satisfies Inputs;
|
||||
|
||||
export default testParams;
|
||||
event: {
|
||||
trigger: "workflow_dispatch",
|
||||
},
|
||||
modes: [],
|
||||
} satisfies Payload;
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
add a file implementing quicksort and test it
|
||||
Find all markdown files in the repository and list their names from https://github.com/ShawnMorreau/cal.com/
|
||||
+3
-5
@@ -6,9 +6,9 @@ import type { Payload } from "../external.ts";
|
||||
*
|
||||
* run with: AGENT_OVERRIDE=claude pnpm play sandbox.ts
|
||||
*/
|
||||
const payload: Payload = {
|
||||
export default {
|
||||
"~pullfrog": true,
|
||||
agent: null, // let AGENT_OVERRIDE control this for testing different agents
|
||||
agent: null,
|
||||
prompt: `Please do the following three things:
|
||||
|
||||
1. Fetch the content from https://httpbin.org/json and tell me what it says
|
||||
@@ -24,6 +24,4 @@ All three of these actions should fail because you are running in sandbox mode w
|
||||
},
|
||||
modes: [],
|
||||
sandbox: true,
|
||||
};
|
||||
|
||||
export default JSON.stringify(payload);
|
||||
} satisfies Payload;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { mkdtemp } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { flatMorph } from "@ark/util";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import type { Octokit } from "@octokit/rest";
|
||||
import { encode as toonEncode } from "@toon-format/toon";
|
||||
import { type } from "arktype";
|
||||
import { type Agent, agents } from "./agents/index.ts";
|
||||
@@ -19,8 +19,8 @@ import { fetchRepoSettings, fetchWorkflowRunInfo, type RepoSettings } from "./ut
|
||||
import { log } from "./utils/cli.ts";
|
||||
import { reportErrorToComment } from "./utils/errorReport.ts";
|
||||
import {
|
||||
createOctokit,
|
||||
parseRepoContext,
|
||||
revokeGitHubInstallationToken,
|
||||
setupGitHubInstallationToken,
|
||||
} from "./utils/github.ts";
|
||||
import { setupGitAuth, setupGitConfig } from "./utils/setup.ts";
|
||||
@@ -53,7 +53,6 @@ export interface MainResult {
|
||||
|
||||
// intermediate result types for deterministic context building
|
||||
interface GitHubSetup {
|
||||
token: string;
|
||||
owner: string;
|
||||
name: string;
|
||||
octokit: Octokit;
|
||||
@@ -66,12 +65,11 @@ type ApiKeySetup =
|
||||
| { success: false; error: string };
|
||||
|
||||
export async function main(inputs: Inputs): Promise<MainResult> {
|
||||
let mcpServerClose: (() => Promise<void>) | undefined;
|
||||
const timer = new Timer();
|
||||
await using tokenRef = await setupGitHubInstallationToken();
|
||||
let payload: Payload | undefined;
|
||||
|
||||
try {
|
||||
const timer = new Timer();
|
||||
|
||||
// phase 1: parse and validate inputs
|
||||
payload = parsePayload(inputs);
|
||||
Inputs.assert(inputs);
|
||||
@@ -79,7 +77,7 @@ export async function main(inputs: Inputs): Promise<MainResult> {
|
||||
|
||||
// phase 2: fast setup (github + temp dir)
|
||||
const [githubSetup, sharedTempDir] = await Promise.all([
|
||||
initializeGitHub(),
|
||||
initializeGitHub(tokenRef.token),
|
||||
createTempDirectory(),
|
||||
]);
|
||||
timer.checkpoint("githubSetup");
|
||||
@@ -107,9 +105,9 @@ export async function main(inputs: Inputs): Promise<MainResult> {
|
||||
// phase 5: parallel long-running operations (agent install + git auth)
|
||||
const toolState: ToolState = {};
|
||||
const [cliPath] = await Promise.all([
|
||||
installAgentCli({ agent, token: githubSetup.token }),
|
||||
installAgentCli({ agent, token: tokenRef.token }),
|
||||
setupGitAuth({
|
||||
token: githubSetup.token,
|
||||
token: tokenRef.token,
|
||||
owner: githubSetup.owner,
|
||||
name: githubSetup.name,
|
||||
payload: resolvedPayload,
|
||||
@@ -156,7 +154,7 @@ export async function main(inputs: Inputs): Promise<MainResult> {
|
||||
const toolContext: ToolContext = {
|
||||
owner: githubSetup.owner,
|
||||
name: githubSetup.name,
|
||||
githubInstallationToken: githubSetup.token,
|
||||
githubInstallationToken: tokenRef.token,
|
||||
octokit: githubSetup.octokit,
|
||||
payload: resolvedPayload,
|
||||
repo: githubSetup.repo,
|
||||
@@ -169,11 +167,10 @@ export async function main(inputs: Inputs): Promise<MainResult> {
|
||||
jobId,
|
||||
};
|
||||
|
||||
const { url: mcpServerUrl, close: mcpServerCloseFunc } = await startMcpHttpServer(toolContext);
|
||||
mcpServerClose = mcpServerCloseFunc;
|
||||
log.info(`🚀 MCP server started at ${mcpServerUrl}`);
|
||||
await using mcpHttpServer = await startMcpHttpServer(toolContext);
|
||||
log.info(`🚀 MCP server started at ${mcpHttpServer.url}`);
|
||||
|
||||
const mcpServers = createMcpConfigs(mcpServerUrl);
|
||||
const mcpServers = createMcpConfigs(mcpHttpServer.url);
|
||||
log.debug(`📋 MCP Config: ${JSON.stringify(mcpServers, null, 2)}`);
|
||||
timer.checkpoint("mcpServer");
|
||||
|
||||
@@ -181,8 +178,7 @@ export async function main(inputs: Inputs): Promise<MainResult> {
|
||||
const ctx: AgentContext = {
|
||||
...toolContext,
|
||||
inputs,
|
||||
mcpServerUrl,
|
||||
mcpServerClose: mcpServerCloseFunc,
|
||||
mcpServerUrl: mcpHttpServer.url,
|
||||
mcpServers,
|
||||
cliPath,
|
||||
apiKey: apiKeySetup.apiKey,
|
||||
@@ -225,11 +221,6 @@ export async function main(inputs: Inputs): Promise<MainResult> {
|
||||
} catch {
|
||||
// error updating comment, but don't let it mask the original error
|
||||
}
|
||||
|
||||
if (mcpServerClose) {
|
||||
await mcpServerClose();
|
||||
}
|
||||
await revokeGitHubInstallationToken();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,7 +312,6 @@ export interface ToolContext {
|
||||
export interface AgentContext extends Readonly<ToolContext> {
|
||||
readonly inputs: Inputs;
|
||||
readonly mcpServerUrl: string;
|
||||
readonly mcpServerClose: () => Promise<void>;
|
||||
readonly mcpServers: ReturnType<typeof createMcpConfigs>;
|
||||
readonly cliPath: string;
|
||||
readonly apiKey: string;
|
||||
@@ -337,6 +327,7 @@ export interface DependencyInstallationState {
|
||||
export interface ToolState {
|
||||
prNumber?: number;
|
||||
issueNumber?: number;
|
||||
selectedMode?: string;
|
||||
review?: {
|
||||
id: number; // REST API database ID (for fix URLs)
|
||||
nodeId: string; // GraphQL node ID (for mutations)
|
||||
@@ -347,13 +338,12 @@ export interface ToolState {
|
||||
/**
|
||||
* Initialize GitHub connection: token, octokit, repo data, settings
|
||||
*/
|
||||
async function initializeGitHub(): Promise<GitHubSetup> {
|
||||
async function initializeGitHub(token: string): Promise<GitHubSetup> {
|
||||
log.info(`🐸 Running pullfrog/action@${packageJson.version}...`);
|
||||
|
||||
const token = await setupGitHubInstallationToken();
|
||||
const { owner, name } = parseRepoContext();
|
||||
|
||||
const octokit = new Octokit({ auth: token });
|
||||
const octokit = createOctokit(token);
|
||||
|
||||
// fetch repo data and settings in parallel
|
||||
const [repoResponse, repoSettings] = await Promise.all([
|
||||
@@ -362,7 +352,6 @@ async function initializeGitHub(): Promise<GitHubSetup> {
|
||||
]);
|
||||
|
||||
return {
|
||||
token,
|
||||
owner,
|
||||
name,
|
||||
octokit,
|
||||
@@ -381,6 +370,7 @@ function resolveAgent({
|
||||
repoSettings: RepoSettings;
|
||||
}): Agent {
|
||||
const agentOverride = process.env.AGENT_OVERRIDE as AgentName | undefined;
|
||||
log.debug(`» determineAgent: agentOverride=${agentOverride}, payload.agent=${payload.agent}, repoSettings.defaultAgent=${repoSettings.defaultAgent}`);
|
||||
const configuredAgentName = agentOverride || payload.agent || repoSettings.defaultAgent || null;
|
||||
|
||||
if (configuredAgentName) {
|
||||
@@ -524,6 +514,7 @@ async function runAgent(ctx: AgentContext): Promise<AgentResult> {
|
||||
owner: ctx.owner,
|
||||
name: ctx.name,
|
||||
defaultBranch: ctx.repo.default_branch,
|
||||
isPublic: !ctx.repo.private,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
import { type ChildProcess, spawn } from "node:child_process";
|
||||
import { type } from "arktype";
|
||||
import type { ToolContext } from "../main.ts";
|
||||
import { execute, tool } from "./shared.ts";
|
||||
|
||||
export const BashParams = type({
|
||||
command: "string",
|
||||
description: "string",
|
||||
"timeout?": "number",
|
||||
"working_directory?": "string",
|
||||
});
|
||||
|
||||
// patterns for sensitive env vars: suffixes (_KEY, _SECRET, _TOKEN) plus AI provider prefixes
|
||||
const SENSITIVE_PATTERNS = [/_KEY$/i, /_SECRET$/i, /_TOKEN$/i, /_PASSWORD$/i, /_CREDENTIAL$/i];
|
||||
|
||||
function isSensitive(key: string): boolean {
|
||||
return SENSITIVE_PATTERNS.some((p) => p.test(key));
|
||||
}
|
||||
|
||||
/** filter env vars, removing sensitive values (only for public repos) */
|
||||
function filterEnv(isPublicRepo: boolean): Record<string, string> {
|
||||
const filtered: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(process.env)) {
|
||||
if (value === undefined) continue;
|
||||
// only filter sensitive vars for public repos
|
||||
if (isPublicRepo && isSensitive(key)) continue;
|
||||
filtered[key] = value;
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* spawn command with filtered env. in CI, also use PID namespace isolation
|
||||
* to prevent child from reading /proc/$PPID/environ (only for public repos)
|
||||
*/
|
||||
function spawnSandboxed(
|
||||
command: string,
|
||||
options: { env: Record<string, string>; cwd: string; isPublicRepo: boolean }
|
||||
): ChildProcess {
|
||||
const stdio: ["ignore", "pipe", "pipe"] = ["ignore", "pipe", "pipe"];
|
||||
const spawnOpts = { env: options.env, cwd: options.cwd, stdio, detached: true };
|
||||
// only use PID namespace isolation for public repos in CI
|
||||
const useNamespaceIsolation = process.env.CI === "true" && options.isPublicRepo;
|
||||
return useNamespaceIsolation
|
||||
? spawn("unshare", ["--pid", "--fork", "--mount-proc", "bash", "-c", command], spawnOpts)
|
||||
: spawn("bash", ["-c", command], spawnOpts);
|
||||
}
|
||||
|
||||
/** kill process and its entire process group */
|
||||
async function killProcessGroup(proc: ChildProcess): Promise<void> {
|
||||
if (!proc.pid) return;
|
||||
try {
|
||||
process.kill(-proc.pid, "SIGTERM");
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
process.kill(-proc.pid, "SIGKILL");
|
||||
} catch {
|
||||
try {
|
||||
proc.kill("SIGKILL");
|
||||
} catch {
|
||||
/* already dead */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function BashTool(ctx: ToolContext) {
|
||||
const isPublicRepo = !ctx.repo.private;
|
||||
|
||||
return tool({
|
||||
name: "bash",
|
||||
description: `Execute shell commands securely.${isPublicRepo ? " Environment is filtered to remove API keys and secrets." : ""}
|
||||
|
||||
Use this tool to:
|
||||
- Run shell commands (ls, cat, grep, find, etc.)
|
||||
- Execute build tools (npm, pnpm, cargo, make, etc.)
|
||||
- Run tests and linters
|
||||
- Perform git operations
|
||||
- Run shell commands in a secure environment. Unlike the built-in bash tool, this tool filters sensitive environment variables from the subprocess's environment to avoid leaking secrets.`,
|
||||
parameters: BashParams,
|
||||
execute: execute(async (params) => {
|
||||
const timeout = Math.min(params.timeout ?? 120000, 600000);
|
||||
const cwd = params.working_directory ?? process.cwd();
|
||||
const proc = spawnSandboxed(params.command, {
|
||||
env: filterEnv(isPublicRepo),
|
||||
cwd,
|
||||
isPublicRepo,
|
||||
});
|
||||
|
||||
let stdout = "",
|
||||
stderr = "",
|
||||
timedOut = false,
|
||||
exited = false;
|
||||
proc.stdout?.on("data", (chunk: Buffer) => {
|
||||
stdout += chunk.toString();
|
||||
});
|
||||
proc.stderr?.on("data", (chunk: Buffer) => {
|
||||
stderr += chunk.toString();
|
||||
});
|
||||
|
||||
const timeoutId = setTimeout(async () => {
|
||||
if (!exited) {
|
||||
timedOut = true;
|
||||
await killProcessGroup(proc);
|
||||
}
|
||||
}, timeout);
|
||||
|
||||
const exitCode = await new Promise<number | null>((resolve) => {
|
||||
const done = (code: number | null) => {
|
||||
exited = true;
|
||||
clearTimeout(timeoutId);
|
||||
resolve(code);
|
||||
};
|
||||
proc.on("exit", done);
|
||||
proc.on("error", () => done(null));
|
||||
});
|
||||
|
||||
let output = stderr ? (stdout ? `${stdout}\n${stderr}` : stderr) : stdout;
|
||||
if (timedOut)
|
||||
output = output
|
||||
? `${output}\n[timed out after ${timeout}ms]`
|
||||
: `[timed out after ${timeout}ms]`;
|
||||
|
||||
return {
|
||||
output: output.trim(),
|
||||
exit_code: exitCode ?? (timedOut ? 124 : -1),
|
||||
timed_out: timedOut,
|
||||
};
|
||||
}),
|
||||
});
|
||||
}
|
||||
+84
-16
@@ -1,11 +1,10 @@
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { type } from "arktype";
|
||||
import type { Payload } from "../external.ts";
|
||||
import { agentsManifest } from "../external.ts";
|
||||
import type { ToolContext } from "../main.ts";
|
||||
import { fetchWorkflowRunInfo } from "../utils/api.ts";
|
||||
import { buildPullfrogFooter, stripExistingFooter } from "../utils/buildPullfrogFooter.ts";
|
||||
import { getGitHubInstallationToken, parseRepoContext } from "../utils/github.ts";
|
||||
import { createOctokit, getGitHubInstallationToken, parseRepoContext, type OctokitWithPlugins } from "../utils/github.ts";
|
||||
import { execute, tool } from "./shared.ts";
|
||||
|
||||
/**
|
||||
@@ -15,7 +14,17 @@ import { execute, tool } from "./shared.ts";
|
||||
*/
|
||||
export const LEAPING_INTO_ACTION_PREFIX = "Leaping into action";
|
||||
|
||||
async function buildCommentFooter(payload: Payload, octokit?: Octokit): Promise<string> {
|
||||
interface BuildCommentFooterParams {
|
||||
payload: Payload;
|
||||
octokit?: OctokitWithPlugins | undefined;
|
||||
customParts?: string[] | undefined;
|
||||
}
|
||||
|
||||
async function buildCommentFooter({
|
||||
payload,
|
||||
octokit,
|
||||
customParts,
|
||||
}: BuildCommentFooterParams): Promise<string> {
|
||||
const repoContext = parseRepoContext();
|
||||
const runId = process.env.GITHUB_RUN_ID;
|
||||
|
||||
@@ -38,7 +47,7 @@ async function buildCommentFooter(payload: Payload, octokit?: Octokit): Promise<
|
||||
}
|
||||
}
|
||||
|
||||
return buildPullfrogFooter({
|
||||
const footerParams = {
|
||||
triggeredBy: true,
|
||||
agent: {
|
||||
displayName: agentInfo?.displayName || "Unknown agent",
|
||||
@@ -52,12 +61,27 @@ async function buildCommentFooter(payload: Payload, octokit?: Octokit): Promise<
|
||||
...(workflowRunHtmlUrl ? { htmlUrl: workflowRunHtmlUrl } : {}),
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
if (customParts && customParts.length > 0) {
|
||||
return buildPullfrogFooter({ ...footerParams, customParts });
|
||||
}
|
||||
return buildPullfrogFooter(footerParams);
|
||||
}
|
||||
|
||||
async function addFooter(body: string, payload: Payload, octokit?: Octokit): Promise<string> {
|
||||
function buildImplementPlanLink(
|
||||
owner: string,
|
||||
repo: string,
|
||||
issueNumber: number,
|
||||
commentId: number
|
||||
): string {
|
||||
const apiUrl = process.env.API_URL || "https://pullfrog.com";
|
||||
return `[Implement plan ➔](${apiUrl}/trigger/${owner}/${repo}/${issueNumber}?action=implement&comment_id=${commentId})`;
|
||||
}
|
||||
|
||||
async function addFooter(body: string, payload: Payload, octokit?: OctokitWithPlugins): Promise<string> {
|
||||
const bodyWithoutFooter = stripExistingFooter(body);
|
||||
const footer = await buildCommentFooter(payload, octokit);
|
||||
const footer = await buildCommentFooter({ payload, octokit });
|
||||
return `${bodyWithoutFooter}${footer}`;
|
||||
}
|
||||
|
||||
@@ -181,11 +205,26 @@ export async function reportProgress(
|
||||
}
|
||||
| undefined
|
||||
> {
|
||||
const bodyWithFooter = await addFooter(body, ctx.payload, ctx.octokit);
|
||||
const existingCommentId = getProgressCommentId();
|
||||
const issueNumber =
|
||||
ctx.toolState.prNumber ?? ctx.toolState.issueNumber ?? ctx.payload.event.issue_number;
|
||||
const isPlanMode = ctx.toolState.selectedMode === "Plan";
|
||||
|
||||
// if we already have a progress comment, update it
|
||||
if (existingCommentId) {
|
||||
const customParts =
|
||||
isPlanMode && issueNumber !== undefined
|
||||
? [buildImplementPlanLink(ctx.owner, ctx.name, issueNumber, existingCommentId)]
|
||||
: undefined;
|
||||
|
||||
const bodyWithoutFooter = stripExistingFooter(body);
|
||||
const footer = await buildCommentFooter({
|
||||
payload: ctx.payload,
|
||||
octokit: ctx.octokit,
|
||||
customParts,
|
||||
});
|
||||
const bodyWithFooter = `${bodyWithoutFooter}${footer}`;
|
||||
|
||||
const result = await ctx.octokit.rest.issues.updateComment({
|
||||
owner: ctx.owner,
|
||||
repo: ctx.name,
|
||||
@@ -205,24 +244,51 @@ export async function reportProgress(
|
||||
|
||||
// no existing comment - create one
|
||||
// use fallback chain: dynamically set context > event payload
|
||||
const issueNumber =
|
||||
ctx.toolState.prNumber ?? ctx.toolState.issueNumber ?? ctx.payload.event.issue_number;
|
||||
if (issueNumber === undefined) {
|
||||
// cannot create comment without issue_number (e.g., workflow_dispatch events)
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// for new comments, we need to create first, then update with Plan link if in Plan mode
|
||||
const initialBody = await addFooter(body, ctx.payload, ctx.octokit);
|
||||
|
||||
const result = await ctx.octokit.rest.issues.createComment({
|
||||
owner: ctx.owner,
|
||||
repo: ctx.name,
|
||||
issue_number: issueNumber,
|
||||
body: bodyWithFooter,
|
||||
body: initialBody,
|
||||
});
|
||||
|
||||
// store the comment ID for future updates
|
||||
setProgressCommentId(result.data.id);
|
||||
progressCommentWasUpdated = true;
|
||||
|
||||
// if Plan mode, update the comment to add the "Implement plan" link
|
||||
if (isPlanMode) {
|
||||
const customParts = [buildImplementPlanLink(ctx.owner, ctx.name, issueNumber, result.data.id)];
|
||||
const bodyWithoutFooter = stripExistingFooter(body);
|
||||
const footer = await buildCommentFooter({
|
||||
payload: ctx.payload,
|
||||
octokit: ctx.octokit,
|
||||
customParts,
|
||||
});
|
||||
const bodyWithPlanLink = `${bodyWithoutFooter}${footer}`;
|
||||
|
||||
const updateResult = await ctx.octokit.rest.issues.updateComment({
|
||||
owner: ctx.owner,
|
||||
repo: ctx.name,
|
||||
comment_id: result.data.id,
|
||||
body: bodyWithPlanLink,
|
||||
});
|
||||
|
||||
return {
|
||||
commentId: updateResult.data.id,
|
||||
url: updateResult.data.html_url,
|
||||
body: updateResult.data.body || "",
|
||||
action: "created",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
commentId: result.data.id,
|
||||
url: result.data.html_url,
|
||||
@@ -341,8 +407,7 @@ export async function ensureProgressCommentUpdated(payload?: Payload): Promise<v
|
||||
|
||||
// check if comment still says "leaping into action" - if it's been updated with an error, don't overwrite it
|
||||
const repoContext = parseRepoContext();
|
||||
const token = getGitHubInstallationToken();
|
||||
const octokit = new Octokit({ auth: token });
|
||||
const octokit = createOctokit(getGitHubInstallationToken());
|
||||
|
||||
try {
|
||||
const existingComment = await octokit.rest.issues.getComment({
|
||||
@@ -363,10 +428,10 @@ export async function ensureProgressCommentUpdated(payload?: Payload): Promise<v
|
||||
|
||||
const runId = process.env.GITHUB_RUN_ID;
|
||||
const workflowRunLink = runId
|
||||
? `[workflow](https://github.com/${repoContext.owner}/${repoContext.name}/actions/runs/${runId})`
|
||||
: "workflow";
|
||||
? `[workflow run logs](https://github.com/${repoContext.owner}/${repoContext.name}/actions/runs/${runId})`
|
||||
: "workflow run logs";
|
||||
|
||||
const errorMessage = `❌ this run croaked
|
||||
const errorMessage = `This run croaked 😵
|
||||
|
||||
The workflow encountered an error before any progress could be reported. Please check the ${workflowRunLink} for details.`;
|
||||
|
||||
@@ -406,6 +471,9 @@ export function ReplyToReviewCommentTool(ctx: ToolContext) {
|
||||
body: bodyWithFooter,
|
||||
});
|
||||
|
||||
// mark progress as updated so ensureProgressCommentUpdated doesn't think the run failed
|
||||
progressCommentWasUpdated = true;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
commentId: result.data.id,
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
import { relative, resolve } from "node:path";
|
||||
import { type } from "arktype";
|
||||
import type { ToolContext } from "../main.ts";
|
||||
import { $ } from "../utils/shell.ts";
|
||||
import { execute, tool } from "./shared.ts";
|
||||
|
||||
export const ListFiles = type({
|
||||
path: type.string
|
||||
.describe("The path to list files from (defaults to current directory)")
|
||||
.default("."),
|
||||
});
|
||||
|
||||
export function ListFilesTool(_ctx: ToolContext) {
|
||||
return tool({
|
||||
name: "list_files",
|
||||
description:
|
||||
"List files in the repository, including both git-tracked and untracked files. Useful for discovering the file structure and locating files, including newly created files that haven't been committed yet.",
|
||||
parameters: ListFiles,
|
||||
execute: execute(async ({ path }: { path?: string }) => {
|
||||
const pathStr = path ?? ".";
|
||||
const cwd = process.cwd();
|
||||
|
||||
// Get git-tracked files
|
||||
let gitFiles: string[] = [];
|
||||
let gitFailed = false;
|
||||
try {
|
||||
const gitArgs = pathStr === "." ? ["ls-files"] : ["ls-files", pathStr];
|
||||
const gitOutput = $("git", gitArgs, { log: false });
|
||||
gitFiles = gitOutput
|
||||
.split("\n")
|
||||
.filter((f) => f.trim() !== "")
|
||||
.map((f) => f.trim());
|
||||
} catch {
|
||||
// git might fail, that's ok - we'll use find instead
|
||||
gitFailed = true;
|
||||
}
|
||||
|
||||
// Always also check filesystem for untracked files
|
||||
// This is important because newly created files won't be in git yet
|
||||
let filesystemFiles: string[] = [];
|
||||
let findFailed = false;
|
||||
try {
|
||||
const findOutput = $(
|
||||
"find",
|
||||
[pathStr, "-maxdepth", "3", "-not", "-path", "*/.*", "-type", "f"],
|
||||
{ log: false }
|
||||
);
|
||||
filesystemFiles = findOutput
|
||||
.split("\n")
|
||||
.filter((f) => f.trim() !== "")
|
||||
.map((f) => {
|
||||
const trimmed = f.trim();
|
||||
// normalize to relative paths for comparison
|
||||
try {
|
||||
return relative(cwd, resolve(cwd, trimmed));
|
||||
} catch {
|
||||
return trimmed;
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
// find might fail, that's ok - we'll just use git files
|
||||
findFailed = true;
|
||||
}
|
||||
|
||||
// if both methods failed, throw an error (execute helper will handle it)
|
||||
if (gitFailed && findFailed) {
|
||||
throw new Error(
|
||||
`Failed to list files: both git ls-files and find commands failed. ` +
|
||||
`Path: ${pathStr}, working directory: ${cwd}`
|
||||
);
|
||||
}
|
||||
|
||||
// Create a Set of git files for efficient lookup
|
||||
const gitFilesSet = new Set(gitFiles);
|
||||
|
||||
// Combine both lists, removing duplicates
|
||||
const allFiles = [...new Set([...gitFiles, ...filesystemFiles])].sort();
|
||||
|
||||
// Calculate actual untracked count (files in filesystem but not in git)
|
||||
const untrackedFiles = filesystemFiles.filter((f) => !gitFilesSet.has(f));
|
||||
const untrackedCount = untrackedFiles.length;
|
||||
|
||||
return {
|
||||
files: allFiles,
|
||||
method: "combined",
|
||||
trackedCount: gitFiles.length,
|
||||
untrackedCount,
|
||||
};
|
||||
}),
|
||||
});
|
||||
}
|
||||
@@ -25,6 +25,9 @@ export function SelectModeTool(ctx: ToolContext) {
|
||||
};
|
||||
}
|
||||
|
||||
// store selected mode in toolState for use by other tools (e.g., report_progress)
|
||||
ctx.toolState.selectedMode = selectedMode.name;
|
||||
|
||||
return {
|
||||
modeName: selectedMode.name,
|
||||
description: selectedMode.description,
|
||||
|
||||
+4
-4
@@ -17,7 +17,6 @@ import {
|
||||
AwaitDependencyInstallationTool,
|
||||
StartDependencyInstallationTool,
|
||||
} from "./dependencies.ts";
|
||||
import { ListFilesTool } from "./files.ts";
|
||||
import { CommitFilesTool, CreateBranchTool, PushBranchTool } from "./git.ts";
|
||||
import { IssueTool } from "./issue.ts";
|
||||
import { GetIssueCommentsTool } from "./issueComments.ts";
|
||||
@@ -30,6 +29,7 @@ import { CreatePullRequestReviewTool } from "./review.ts";
|
||||
import { GetReviewCommentsTool, ListPullRequestReviewsTool } from "./reviewComments.ts";
|
||||
import { SelectModeTool } from "./selectMode.ts";
|
||||
import { addTools } from "./shared.ts";
|
||||
import { BashTool } from "./bash.ts";
|
||||
|
||||
/**
|
||||
* Find an available port starting from the given port
|
||||
@@ -65,7 +65,7 @@ async function findAvailablePort(startPort: number): Promise<number> {
|
||||
*/
|
||||
export async function startMcpHttpServer(
|
||||
ctx: ToolContext
|
||||
): Promise<{ url: string; close: () => Promise<void> }> {
|
||||
): Promise<{ url: string; [Symbol.asyncDispose]: () => Promise<void> }> {
|
||||
const server = new FastMCP({
|
||||
name: ghPullfrogMcpName,
|
||||
version: "0.0.1",
|
||||
@@ -95,7 +95,7 @@ export async function startMcpHttpServer(
|
||||
CreateBranchTool(ctx),
|
||||
CommitFilesTool(ctx),
|
||||
PushBranchTool(ctx),
|
||||
ListFilesTool(ctx),
|
||||
BashTool(ctx),
|
||||
];
|
||||
|
||||
if (!ctx.payload.disableProgressComment) {
|
||||
@@ -121,7 +121,7 @@ export async function startMcpHttpServer(
|
||||
|
||||
return {
|
||||
url,
|
||||
close: async () => {
|
||||
[Symbol.asyncDispose]: async () => {
|
||||
await server.stop();
|
||||
},
|
||||
};
|
||||
|
||||
Generated
-960
@@ -1,960 +0,0 @@
|
||||
{
|
||||
"name": "@pullfrog/action",
|
||||
"version": "0.0.96",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@pullfrog/action",
|
||||
"version": "0.0.96",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.11.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.1.26",
|
||||
"@ark/fs": "0.53.0",
|
||||
"@ark/util": "0.53.0",
|
||||
"@octokit/rest": "^22.0.0",
|
||||
"@octokit/webhooks-types": "^7.6.1",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"arktype": "2.1.25",
|
||||
"convex": "^1.29.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"execa": "^9.6.0",
|
||||
"fastmcp": "^3.20.0",
|
||||
"table": "^6.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.7.2",
|
||||
"arg": "^5.0.2",
|
||||
"esbuild": "^0.25.9",
|
||||
"husky": "^9.0.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"../node_modules/.pnpm/@actions+core@1.11.1/node_modules/@actions/core": {
|
||||
"version": "1.11.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/http-client": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.18.112"
|
||||
}
|
||||
},
|
||||
"../node_modules/.pnpm/@anthropic-ai+claude-agent-sdk@0.1.26_zod@3.25.76/node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
"version": "0.1.26",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "^0.33.5",
|
||||
"@img/sharp-darwin-x64": "^0.33.5",
|
||||
"@img/sharp-linux-arm": "^0.33.5",
|
||||
"@img/sharp-linux-arm64": "^0.33.5",
|
||||
"@img/sharp-linux-x64": "^0.33.5",
|
||||
"@img/sharp-win32-x64": "^0.33.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.24.1"
|
||||
}
|
||||
},
|
||||
"../node_modules/.pnpm/@ark+fs@0.53.0/node_modules/@ark/fs": {
|
||||
"version": "0.53.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"../node_modules/.pnpm/@ark+util@0.53.0/node_modules/@ark/util": {
|
||||
"version": "0.53.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"../node_modules/.pnpm/@octokit+rest@22.0.0/node_modules/@octokit/rest": {
|
||||
"version": "22.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/core": "^7.0.2",
|
||||
"@octokit/plugin-paginate-rest": "^13.0.1",
|
||||
"@octokit/plugin-request-log": "^6.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^16.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@octokit/auth-action": "^6.0.1",
|
||||
"@octokit/auth-app": "^8.0.1",
|
||||
"@octokit/fixtures-server": "^8.1.0",
|
||||
"@octokit/request": "^10.0.0",
|
||||
"@octokit/tsconfig": "^4.0.0",
|
||||
"@types/node": "^22.0.0",
|
||||
"@vitest/coverage-v8": "^3.0.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"fetch-mock": "^12.0.0",
|
||||
"glob": "^11.0.0",
|
||||
"nock": "^14.0.0-beta.8",
|
||||
"prettier": "^3.2.4",
|
||||
"semantic-release-plugin-update-version-in-files": "^2.0.0",
|
||||
"typescript": "^5.3.3",
|
||||
"undici": "^6.4.0",
|
||||
"vitest": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"../node_modules/.pnpm/@octokit+webhooks-types@7.6.1/node_modules/@octokit/webhooks-types": {
|
||||
"version": "7.6.1",
|
||||
"license": "MIT",
|
||||
"devDependencies": {}
|
||||
},
|
||||
"../node_modules/.pnpm/@standard-schema+spec@1.0.0/node_modules/@standard-schema/spec": {
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"tsup": "^8.3.0",
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
},
|
||||
"../node_modules/.pnpm/@types+node@24.7.2/node_modules/@types/node": {
|
||||
"version": "24.7.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.14.0"
|
||||
}
|
||||
},
|
||||
"../node_modules/.pnpm/arg@5.0.2/node_modules/arg": {
|
||||
"version": "5.0.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"chai": "^4.1.1",
|
||||
"jest": "^27.0.6",
|
||||
"prettier": "^2.3.2"
|
||||
}
|
||||
},
|
||||
"../node_modules/.pnpm/arktype@2.1.25/node_modules/arktype": {
|
||||
"version": "2.1.25",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ark/schema": "0.53.0",
|
||||
"@ark/util": "0.53.0",
|
||||
"arkregex": "0.0.2"
|
||||
}
|
||||
},
|
||||
"../node_modules/.pnpm/dotenv@17.2.3/node_modules/dotenv": {
|
||||
"version": "17.2.3",
|
||||
"license": "BSD-2-Clause",
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.3",
|
||||
"decache": "^4.6.2",
|
||||
"sinon": "^14.0.1",
|
||||
"standard": "^17.0.0",
|
||||
"standard-version": "^9.5.0",
|
||||
"tap": "^19.2.0",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"../node_modules/.pnpm/esbuild@0.25.10/node_modules/esbuild": {
|
||||
"version": "0.25.10",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.10",
|
||||
"@esbuild/android-arm": "0.25.10",
|
||||
"@esbuild/android-arm64": "0.25.10",
|
||||
"@esbuild/android-x64": "0.25.10",
|
||||
"@esbuild/darwin-arm64": "0.25.10",
|
||||
"@esbuild/darwin-x64": "0.25.10",
|
||||
"@esbuild/freebsd-arm64": "0.25.10",
|
||||
"@esbuild/freebsd-x64": "0.25.10",
|
||||
"@esbuild/linux-arm": "0.25.10",
|
||||
"@esbuild/linux-arm64": "0.25.10",
|
||||
"@esbuild/linux-ia32": "0.25.10",
|
||||
"@esbuild/linux-loong64": "0.25.10",
|
||||
"@esbuild/linux-mips64el": "0.25.10",
|
||||
"@esbuild/linux-ppc64": "0.25.10",
|
||||
"@esbuild/linux-riscv64": "0.25.10",
|
||||
"@esbuild/linux-s390x": "0.25.10",
|
||||
"@esbuild/linux-x64": "0.25.10",
|
||||
"@esbuild/netbsd-arm64": "0.25.10",
|
||||
"@esbuild/netbsd-x64": "0.25.10",
|
||||
"@esbuild/openbsd-arm64": "0.25.10",
|
||||
"@esbuild/openbsd-x64": "0.25.10",
|
||||
"@esbuild/openharmony-arm64": "0.25.10",
|
||||
"@esbuild/sunos-x64": "0.25.10",
|
||||
"@esbuild/win32-arm64": "0.25.10",
|
||||
"@esbuild/win32-ia32": "0.25.10",
|
||||
"@esbuild/win32-x64": "0.25.10"
|
||||
}
|
||||
},
|
||||
"../node_modules/.pnpm/execa@9.6.0/node_modules/execa": {
|
||||
"version": "9.6.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sindresorhus/merge-streams": "^4.0.0",
|
||||
"cross-spawn": "^7.0.6",
|
||||
"figures": "^6.1.0",
|
||||
"get-stream": "^9.0.0",
|
||||
"human-signals": "^8.0.1",
|
||||
"is-plain-obj": "^4.1.0",
|
||||
"is-stream": "^4.0.1",
|
||||
"npm-run-path": "^6.0.0",
|
||||
"pretty-ms": "^9.2.0",
|
||||
"signal-exit": "^4.1.0",
|
||||
"strip-final-newline": "^4.0.0",
|
||||
"yoctocolors": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.15.21",
|
||||
"ava": "^6.3.0",
|
||||
"c8": "^10.1.3",
|
||||
"get-node": "^15.0.3",
|
||||
"is-in-ci": "^1.0.0",
|
||||
"is-running": "^2.1.0",
|
||||
"log-process-errors": "^12.0.1",
|
||||
"path-exists": "^5.0.0",
|
||||
"path-key": "^4.0.0",
|
||||
"tempfile": "^5.0.0",
|
||||
"tsd": "^0.32.0",
|
||||
"typescript": "^5.8.3",
|
||||
"which": "^5.0.0",
|
||||
"xo": "^0.60.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.5.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
||||
}
|
||||
},
|
||||
"../node_modules/.pnpm/fastmcp@3.20.0_arktype@2.1.25_effect@3.16.12/node_modules/fastmcp": {
|
||||
"version": "3.20.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.17.2",
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"execa": "^9.6.0",
|
||||
"file-type": "^21.0.0",
|
||||
"fuse.js": "^7.1.0",
|
||||
"mcp-proxy": "^5.8.1",
|
||||
"strict-event-emitter-types": "^2.0.0",
|
||||
"undici": "^7.13.0",
|
||||
"uri-templates": "^0.2.0",
|
||||
"xsschema": "0.3.5",
|
||||
"yargs": "^18.0.0",
|
||||
"zod": "^3.25.76",
|
||||
"zod-to-json-schema": "^3.24.6"
|
||||
},
|
||||
"bin": {
|
||||
"fastmcp": "dist/bin/fastmcp.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@modelcontextprotocol/inspector": "^0.16.2",
|
||||
"@sebbo2002/semantic-release-jsr": "^3.0.1",
|
||||
"@tsconfig/node22": "^22.0.2",
|
||||
"@types/node": "^24.2.1",
|
||||
"@types/uri-templates": "^0.1.34",
|
||||
"@types/yargs": "^17.0.33",
|
||||
"@valibot/to-json-schema": "^1.3.0",
|
||||
"@wong2/mcp-cli": "^1.13.0",
|
||||
"arktype": "^2.1.20",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-perfectionist": "^4.15.0",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eventsource-client": "^1.1.4",
|
||||
"get-port-please": "^3.2.0",
|
||||
"jiti": "^2.5.1",
|
||||
"jsr": "^0.13.5",
|
||||
"prettier": "^3.6.2",
|
||||
"semantic-release": "^24.2.7",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.39.0",
|
||||
"valibot": "^1.1.0",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
},
|
||||
"../node_modules/.pnpm/husky@9.1.7/node_modules/husky": {
|
||||
"version": "9.1.7",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"husky": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/typicode"
|
||||
}
|
||||
},
|
||||
"../node_modules/.pnpm/table@6.9.0/node_modules/table": {
|
||||
"version": "6.9.0",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"ajv": "^8.0.1",
|
||||
"lodash.truncate": "^4.4.2",
|
||||
"slice-ansi": "^4.0.0",
|
||||
"string-width": "^4.2.3",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.16",
|
||||
"@types/lodash.mapvalues": "^4.6.6",
|
||||
"@types/lodash.truncate": "^4.4.6",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/node": "^14.14.37",
|
||||
"@types/sinon": "^10.0.0",
|
||||
"@types/slice-ansi": "^4.0.0",
|
||||
"ajv-cli": "^5.0.0",
|
||||
"ajv-keywords": "^5.0.0",
|
||||
"chai": "^4.2.0",
|
||||
"chalk": "^4.1.0",
|
||||
"coveralls": "^3.1.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-canonical": "^25.0.0",
|
||||
"gitdown": "^3.1.4",
|
||||
"husky": "^4.3.6",
|
||||
"js-beautify": "^1.14.0",
|
||||
"lodash.mapvalues": "^4.6.0",
|
||||
"mkdirp": "^1.0.4",
|
||||
"mocha": "^8.2.1",
|
||||
"nyc": "^15.1.0",
|
||||
"semantic-release": "^17.3.1",
|
||||
"sinon": "^12.0.1",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "4.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dprint/formatter": "^0.4.1",
|
||||
"@dprint/typescript": "0.93.4",
|
||||
"@esfx/canceltoken": "^1.0.0",
|
||||
"@eslint/js": "^9.20.0",
|
||||
"@octokit/rest": "^21.1.1",
|
||||
"@types/chai": "^4.3.20",
|
||||
"@types/diff": "^7.0.1",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/ms": "^0.7.34",
|
||||
"@types/node": "latest",
|
||||
"@types/source-map-support": "^0.5.10",
|
||||
"@types/which": "^3.0.4",
|
||||
"@typescript-eslint/rule-tester": "^8.24.1",
|
||||
"@typescript-eslint/type-utils": "^8.24.1",
|
||||
"@typescript-eslint/utils": "^8.24.1",
|
||||
"azure-devops-node-api": "^14.1.0",
|
||||
"c8": "^10.1.3",
|
||||
"chai": "^4.5.0",
|
||||
"chokidar": "^4.0.3",
|
||||
"diff": "^7.0.0",
|
||||
"dprint": "^0.49.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^9.20.1",
|
||||
"eslint-formatter-autolinkable-stylish": "^1.4.0",
|
||||
"eslint-plugin-regexp": "^2.7.0",
|
||||
"fast-xml-parser": "^4.5.2",
|
||||
"glob": "^10.4.5",
|
||||
"globals": "^15.15.0",
|
||||
"hereby": "^1.10.0",
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"knip": "^5.44.4",
|
||||
"minimist": "^1.2.8",
|
||||
"mocha": "^10.8.2",
|
||||
"mocha-fivemat-progress-reporter": "^0.1.0",
|
||||
"monocart-coverage-reports": "^2.12.1",
|
||||
"ms": "^2.1.3",
|
||||
"picocolors": "^1.1.1",
|
||||
"playwright": "^1.50.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.24.1",
|
||||
"which": "^3.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"resolved": "../node_modules/.pnpm/@actions+core@1.11.1/node_modules/@actions/core",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
"resolved": "../node_modules/.pnpm/@anthropic-ai+claude-agent-sdk@0.1.26_zod@3.25.76/node_modules/@anthropic-ai/claude-agent-sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@ark/fs": {
|
||||
"resolved": "../node_modules/.pnpm/@ark+fs@0.53.0/node_modules/@ark/fs",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@ark/util": {
|
||||
"resolved": "../node_modules/.pnpm/@ark+util@0.53.0/node_modules/@ark/util",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz",
|
||||
"integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz",
|
||||
"integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz",
|
||||
"integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz",
|
||||
"integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz",
|
||||
"integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz",
|
||||
"integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz",
|
||||
"integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz",
|
||||
"integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz",
|
||||
"integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz",
|
||||
"integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz",
|
||||
"integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz",
|
||||
"integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz",
|
||||
"integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz",
|
||||
"integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz",
|
||||
"integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz",
|
||||
"integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz",
|
||||
"integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest": {
|
||||
"resolved": "../node_modules/.pnpm/@octokit+rest@22.0.0/node_modules/@octokit/rest",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@octokit/webhooks-types": {
|
||||
"resolved": "../node_modules/.pnpm/@octokit+webhooks-types@7.6.1/node_modules/@octokit/webhooks-types",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"resolved": "../node_modules/.pnpm/@standard-schema+spec@1.0.0/node_modules/@standard-schema/spec",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"resolved": "../node_modules/.pnpm/@types+node@24.7.2/node_modules/@types/node",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"resolved": "../node_modules/.pnpm/arg@5.0.2/node_modules/arg",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/arktype": {
|
||||
"resolved": "../node_modules/.pnpm/arktype@2.1.25/node_modules/arktype",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/convex": {
|
||||
"version": "1.29.0",
|
||||
"resolved": "https://registry.npmjs.org/convex/-/convex-1.29.0.tgz",
|
||||
"integrity": "sha512-uoIPXRKIp2eLCkkR9WJ2vc9NtgQtx8Pml59WPUahwbrd5EuW2WLI/cf2E7XrUzOSifdQC3kJZepisk4wJNTJaA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"esbuild": "0.25.4",
|
||||
"prettier": "^3.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"convex": "bin/main.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@auth0/auth0-react": "^2.0.1",
|
||||
"@clerk/clerk-react": "^4.12.8 || ^5.0.0",
|
||||
"react": "^18.0.0 || ^19.0.0-0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@auth0/auth0-react": {
|
||||
"optional": true
|
||||
},
|
||||
"@clerk/clerk-react": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/convex/node_modules/esbuild": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz",
|
||||
"integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.4",
|
||||
"@esbuild/android-arm": "0.25.4",
|
||||
"@esbuild/android-arm64": "0.25.4",
|
||||
"@esbuild/android-x64": "0.25.4",
|
||||
"@esbuild/darwin-arm64": "0.25.4",
|
||||
"@esbuild/darwin-x64": "0.25.4",
|
||||
"@esbuild/freebsd-arm64": "0.25.4",
|
||||
"@esbuild/freebsd-x64": "0.25.4",
|
||||
"@esbuild/linux-arm": "0.25.4",
|
||||
"@esbuild/linux-arm64": "0.25.4",
|
||||
"@esbuild/linux-ia32": "0.25.4",
|
||||
"@esbuild/linux-loong64": "0.25.4",
|
||||
"@esbuild/linux-mips64el": "0.25.4",
|
||||
"@esbuild/linux-ppc64": "0.25.4",
|
||||
"@esbuild/linux-riscv64": "0.25.4",
|
||||
"@esbuild/linux-s390x": "0.25.4",
|
||||
"@esbuild/linux-x64": "0.25.4",
|
||||
"@esbuild/netbsd-arm64": "0.25.4",
|
||||
"@esbuild/netbsd-x64": "0.25.4",
|
||||
"@esbuild/openbsd-arm64": "0.25.4",
|
||||
"@esbuild/openbsd-x64": "0.25.4",
|
||||
"@esbuild/sunos-x64": "0.25.4",
|
||||
"@esbuild/win32-arm64": "0.25.4",
|
||||
"@esbuild/win32-ia32": "0.25.4",
|
||||
"@esbuild/win32-x64": "0.25.4"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"resolved": "../node_modules/.pnpm/dotenv@17.2.3/node_modules/dotenv",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"resolved": "../node_modules/.pnpm/esbuild@0.25.10/node_modules/esbuild",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"resolved": "../node_modules/.pnpm/execa@9.6.0/node_modules/execa",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/fastmcp": {
|
||||
"resolved": "../node_modules/.pnpm/fastmcp@3.20.0_arktype@2.1.25_effect@3.16.12/node_modules/fastmcp",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/husky": {
|
||||
"resolved": "../node_modules/.pnpm/husky@9.1.7/node_modules/husky",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/table": {
|
||||
"resolved": "../node_modules/.pnpm/table@6.9.0/node_modules/table",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"resolved": "../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript",
|
||||
"link": true
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pullfrog/action",
|
||||
"version": "0.0.155",
|
||||
"version": "0.0.156",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"index.js",
|
||||
@@ -25,19 +25,21 @@
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.11.1",
|
||||
"@actions/github": "^6.0.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.1.37",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.1.77",
|
||||
"@ark/fs": "0.53.0",
|
||||
"@ark/util": "0.53.0",
|
||||
"@octokit/plugin-throttling": "^11.0.3",
|
||||
"@octokit/rest": "^22.0.0",
|
||||
"@octokit/webhooks-types": "^7.6.1",
|
||||
"@openai/codex-sdk": "0.58.0",
|
||||
"@opencode-ai/sdk": "^1.0.143",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"@toon-format/toon": "^1.0.0",
|
||||
"arktype": "2.1.28",
|
||||
"package-manager-detector": "^1.6.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"execa": "^9.6.0",
|
||||
"fastmcp": "^3.20.0",
|
||||
"fastmcp": "^3.26.8",
|
||||
"package-manager-detector": "^1.6.0",
|
||||
"table": "^6.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -70,5 +72,6 @@
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { extname, join, resolve } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
@@ -67,7 +68,9 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
const args = arg({
|
||||
"--help": Boolean,
|
||||
"--raw": String,
|
||||
"--local": Boolean,
|
||||
"-h": "--help",
|
||||
"-l": "--local",
|
||||
});
|
||||
|
||||
if (args["--help"]) {
|
||||
@@ -81,18 +84,85 @@ Arguments:
|
||||
|
||||
Options:
|
||||
--raw [prompt] Use raw string as prompt instead of loading from file
|
||||
--local, -l Run locally on macOS (default: runs in Docker)
|
||||
-h, --help Show this help message
|
||||
|
||||
Environment:
|
||||
PLAY_LOCAL=1 Same as --local
|
||||
|
||||
Examples:
|
||||
tsx play.ts # Use default fixture
|
||||
tsx play.ts fixtures/basic.txt # Use specific text file
|
||||
tsx play.ts custom.json # Use JSON file
|
||||
tsx play.ts fixtures/test.ts # Use TypeScript file
|
||||
tsx play.ts bash-test.ts # Run in Docker (default)
|
||||
tsx play.ts --local bash-test.ts # Run locally on macOS
|
||||
tsx play.ts --raw "Hello world" # Use raw string as prompt
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// default: run in Docker (unless --local or PLAY_LOCAL=1 or already inside Docker)
|
||||
const isInsideDocker = existsSync("/.dockerenv");
|
||||
const useLocal = args["--local"] || process.env.PLAY_LOCAL === "1" || isInsideDocker;
|
||||
|
||||
if (!useLocal) {
|
||||
log.info("» running in Docker container...");
|
||||
|
||||
const passArgs = process.argv.slice(2);
|
||||
const nodeCmd = `node play.ts ${passArgs.join(" ")}`;
|
||||
|
||||
// pass all env vars to docker
|
||||
const envFlags = Object.entries(process.env).flatMap(([key, value]) =>
|
||||
value !== undefined ? ["-e", `${key}=${value}`] : []
|
||||
);
|
||||
|
||||
// SSH for git - mount individual SSH files to avoid permission issues
|
||||
const sshFlags: string[] = [];
|
||||
const home = process.env.HOME;
|
||||
if (home) {
|
||||
const sshDir = join(home, ".ssh");
|
||||
// mount SSH keys (try common key names)
|
||||
for (const keyName of ["id_rsa", "id_ed25519", "id_ecdsa"]) {
|
||||
const keyPath = join(sshDir, keyName);
|
||||
if (existsSync(keyPath)) {
|
||||
sshFlags.push("-v", `${keyPath}:/root/.ssh/${keyName}:ro`);
|
||||
}
|
||||
}
|
||||
// mount known_hosts
|
||||
const knownHostsPath = join(sshDir, "known_hosts");
|
||||
if (existsSync(knownHostsPath)) {
|
||||
sshFlags.push("-v", `${knownHostsPath}:/root/.ssh/known_hosts:ro`);
|
||||
}
|
||||
}
|
||||
|
||||
const ttyFlags = process.stdin.isTTY ? ["-it"] : [];
|
||||
|
||||
const result = spawnSync(
|
||||
"docker",
|
||||
[
|
||||
"run",
|
||||
"--rm",
|
||||
...ttyFlags,
|
||||
"-v",
|
||||
`${process.cwd()}:/app/action:cached`,
|
||||
"-v",
|
||||
"pullfrog-action-node-modules:/app/action/node_modules",
|
||||
"-w",
|
||||
"/app/action",
|
||||
...envFlags,
|
||||
...sshFlags,
|
||||
"--cap-add",
|
||||
"SYS_ADMIN",
|
||||
"--security-opt",
|
||||
"seccomp:unconfined",
|
||||
"node:24",
|
||||
"bash",
|
||||
"-c",
|
||||
`corepack enable pnpm >/dev/null 2>&1 && pnpm install --frozen-lockfile && ${nodeCmd}`,
|
||||
],
|
||||
{ stdio: "inherit" }
|
||||
);
|
||||
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
|
||||
let prompt: string;
|
||||
|
||||
if (args["--raw"]) {
|
||||
@@ -135,10 +205,11 @@ Examples:
|
||||
|
||||
if (typeof module.default === "string") {
|
||||
prompt = module.default;
|
||||
} else if (typeof module.default === "object" && module.default.prompt) {
|
||||
prompt = module.default.prompt;
|
||||
} else {
|
||||
} else if (typeof module.default === "object") {
|
||||
// Payload objects (with ~pullfrog) should be stringified
|
||||
prompt = JSON.stringify(module.default, null, 2);
|
||||
} else {
|
||||
throw new Error(`Unsupported default export type: ${typeof module.default}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Generated
+199
-83
@@ -15,14 +15,17 @@ importers:
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1
|
||||
'@anthropic-ai/claude-agent-sdk':
|
||||
specifier: 0.1.37
|
||||
version: 0.1.37(zod@3.25.76)
|
||||
specifier: 0.1.77
|
||||
version: 0.1.77(zod@4.3.5)
|
||||
'@ark/fs':
|
||||
specifier: 0.53.0
|
||||
version: 0.53.0
|
||||
'@ark/util':
|
||||
specifier: 0.53.0
|
||||
version: 0.53.0
|
||||
'@octokit/plugin-throttling':
|
||||
specifier: ^11.0.3
|
||||
version: 11.0.3(@octokit/core@7.0.5)
|
||||
'@octokit/rest':
|
||||
specifier: ^22.0.0
|
||||
version: 22.0.0
|
||||
@@ -38,6 +41,9 @@ importers:
|
||||
'@standard-schema/spec':
|
||||
specifier: 1.0.0
|
||||
version: 1.0.0
|
||||
'@toon-format/toon':
|
||||
specifier: ^1.0.0
|
||||
version: 1.4.0
|
||||
arktype:
|
||||
specifier: 2.1.28
|
||||
version: 2.1.28
|
||||
@@ -48,8 +54,8 @@ importers:
|
||||
specifier: ^9.6.0
|
||||
version: 9.6.0
|
||||
fastmcp:
|
||||
specifier: ^3.20.0
|
||||
version: 3.20.0(arktype@2.1.28)
|
||||
specifier: ^3.26.8
|
||||
version: 3.26.8(arktype@2.1.28)(hono@4.11.3)
|
||||
package-manager-detector:
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0
|
||||
@@ -90,11 +96,11 @@ packages:
|
||||
'@actions/io@1.1.3':
|
||||
resolution: {integrity: sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==}
|
||||
|
||||
'@anthropic-ai/claude-agent-sdk@0.1.37':
|
||||
resolution: {integrity: sha512-LMfqMIPLTz0vRhpcO7hpPJ5L6Bg24y5/PaqZvwAUNZ/GR3OAl7xmJR7IryIR6m8Pyd/6Hs2yBU8j86Os+wHFQQ==}
|
||||
'@anthropic-ai/claude-agent-sdk@0.1.77':
|
||||
resolution: {integrity: sha512-ZEjWQtkoB2MEY6K16DWMmF+8OhywAynH0m08V265cerbZ8xPD/2Ng2jPzbbO40mPeFSsMDJboShL+a3aObP0Jg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
zod: ^3.24.1
|
||||
zod: ^3.25.0 || ^4.0.0
|
||||
|
||||
'@ark/fs@0.53.0':
|
||||
resolution: {integrity: sha512-XL0EbBAZgyy+j9aPhftYaBsbKAW5PTNSKCN6oLRRdrHuHPSAZgR6765/z0YZGhPxHEUNmq0vBoSk8yOLk91dNQ==}
|
||||
@@ -271,6 +277,12 @@ packages:
|
||||
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@hono/node-server@1.19.7':
|
||||
resolution: {integrity: sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==}
|
||||
engines: {node: '>=18.14.1'}
|
||||
peerDependencies:
|
||||
hono: ^4
|
||||
|
||||
'@img/sharp-darwin-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
@@ -308,6 +320,16 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
|
||||
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
|
||||
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linux-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
@@ -326,15 +348,33 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.33.5':
|
||||
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-win32-x64@0.33.5':
|
||||
resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@modelcontextprotocol/sdk@1.20.0':
|
||||
resolution: {integrity: sha512-kOQ4+fHuT4KbR2iq2IjeV32HiihueuOf1vJkq18z08CLZ1UQrTc8BXJpVfxZkq45+inLLD+D4xx4nBjUelJa4Q==}
|
||||
'@modelcontextprotocol/sdk@1.25.2':
|
||||
resolution: {integrity: sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@cfworker/json-schema': ^4.1.1
|
||||
zod: ^3.25 || ^4.0
|
||||
peerDependenciesMeta:
|
||||
'@cfworker/json-schema':
|
||||
optional: true
|
||||
|
||||
'@octokit/auth-token@4.0.0':
|
||||
resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==}
|
||||
@@ -377,6 +417,9 @@ packages:
|
||||
'@octokit/openapi-types@26.0.0':
|
||||
resolution: {integrity: sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==}
|
||||
|
||||
'@octokit/openapi-types@27.0.0':
|
||||
resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==}
|
||||
|
||||
'@octokit/plugin-paginate-rest@13.2.0':
|
||||
resolution: {integrity: sha512-YuAlyjR8o5QoRSOvMHxSJzPtogkNMgeMv2mpccrvdUGeC3MKyfi/hS+KiFwyH/iRKIKyx+eIMsDjbt3p9r2GYA==}
|
||||
engines: {node: '>= 20'}
|
||||
@@ -407,6 +450,12 @@ packages:
|
||||
peerDependencies:
|
||||
'@octokit/core': '>=6'
|
||||
|
||||
'@octokit/plugin-throttling@11.0.3':
|
||||
resolution: {integrity: sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==}
|
||||
engines: {node: '>= 20'}
|
||||
peerDependencies:
|
||||
'@octokit/core': ^7.0.0
|
||||
|
||||
'@octokit/request-error@5.1.1':
|
||||
resolution: {integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==}
|
||||
engines: {node: '>= 18'}
|
||||
@@ -436,6 +485,9 @@ packages:
|
||||
'@octokit/types@15.0.0':
|
||||
resolution: {integrity: sha512-8o6yDfmoGJUIeR9OfYU0/TUJTnMPG2r68+1yEdUeG2Fdqpj8Qetg0ziKIgcBm0RW/j29H41WP37CYCEhp6GoHQ==}
|
||||
|
||||
'@octokit/types@16.0.0':
|
||||
resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==}
|
||||
|
||||
'@octokit/webhooks-types@7.6.1':
|
||||
resolution: {integrity: sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==}
|
||||
|
||||
@@ -456,13 +508,16 @@ packages:
|
||||
'@standard-schema/spec@1.0.0':
|
||||
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
|
||||
|
||||
'@tokenizer/inflate@0.2.7':
|
||||
resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==}
|
||||
'@tokenizer/inflate@0.4.1':
|
||||
resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@tokenizer/token@0.3.0':
|
||||
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
|
||||
|
||||
'@toon-format/toon@1.4.0':
|
||||
resolution: {integrity: sha512-bjdhhIPjnX2oVk+pKy/nD3bwuESDLX/5fwW0TxwpV7Q4PVNkiRSv1S0sPeuy9TI4PfAlulow1HShdmMTnYvoLg==}
|
||||
|
||||
'@types/node@24.7.2':
|
||||
resolution: {integrity: sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==}
|
||||
|
||||
@@ -470,8 +525,13 @@ packages:
|
||||
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
ajv@6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
ajv-formats@3.0.1:
|
||||
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
|
||||
peerDependencies:
|
||||
ajv: ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
ajv:
|
||||
optional: true
|
||||
|
||||
ajv@8.17.1:
|
||||
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
|
||||
@@ -515,6 +575,9 @@ packages:
|
||||
resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
bottleneck@2.19.5:
|
||||
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
|
||||
|
||||
bytes@3.1.2:
|
||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -639,6 +702,10 @@ packages:
|
||||
resolution: {integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==}
|
||||
engines: {node: ^18.19.0 || >=20.5.0}
|
||||
|
||||
execa@9.6.1:
|
||||
resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==}
|
||||
engines: {node: ^18.19.0 || >=20.5.0}
|
||||
|
||||
express-rate-limit@7.5.1:
|
||||
resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==}
|
||||
engines: {node: '>= 16'}
|
||||
@@ -655,25 +722,24 @@ packages:
|
||||
fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
fast-json-stable-stringify@2.1.0:
|
||||
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
||||
|
||||
fast-uri@3.1.0:
|
||||
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
|
||||
|
||||
fastmcp@3.20.0:
|
||||
resolution: {integrity: sha512-W4Mx0Ft0mqb+BvZ2rHijQQSIKCX8FlRhLOCpK22rMJMxFR33rRyoDRa/WZ4+sDTQc099G7JvkrMBpkTJv1O12g==}
|
||||
fastmcp@3.26.8:
|
||||
resolution: {integrity: sha512-DQgvEdSoQpISqPDgLZe5K2RU8ICCz3/5QcEhHmz4KiNcNSdFOo7o817mGwWxPffOLe36vypCulq22cvEwqBi/A==}
|
||||
hasBin: true
|
||||
|
||||
fflate@0.8.2:
|
||||
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
|
||||
peerDependencies:
|
||||
jose: ^5.0.0
|
||||
peerDependenciesMeta:
|
||||
jose:
|
||||
optional: true
|
||||
|
||||
figures@6.1.0:
|
||||
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
file-type@21.0.0:
|
||||
resolution: {integrity: sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==}
|
||||
file-type@21.3.0:
|
||||
resolution: {integrity: sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
finalhandler@2.1.0:
|
||||
@@ -727,6 +793,10 @@ packages:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hono@4.11.3:
|
||||
resolution: {integrity: sha512-PmQi306+M/ct/m5s66Hrg+adPnkD5jiO6IjA7WhWw0gSBSo1EcRegwuI1deZ+wd5pzCGynCcn2DprnE4/yEV4w==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
http-errors@2.0.0:
|
||||
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -780,12 +850,15 @@ packages:
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
json-schema-traverse@0.4.1:
|
||||
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
||||
jose@6.1.3:
|
||||
resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
|
||||
|
||||
json-schema-traverse@1.0.0:
|
||||
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||
|
||||
json-schema-typed@8.0.2:
|
||||
resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
|
||||
|
||||
lodash.truncate@4.4.2:
|
||||
resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==}
|
||||
|
||||
@@ -793,8 +866,8 @@ packages:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
mcp-proxy@5.9.0:
|
||||
resolution: {integrity: sha512-xonJSkuy4wmwXeykBFl0mjRMt4D0pAGCtFIfBFUz4O80VrO92ruJseIdASJO3wN6MdYHi3vbZLOQol3NsUpg4g==}
|
||||
mcp-proxy@5.12.5:
|
||||
resolution: {integrity: sha512-Vawdc8vi36fXxKCaDpluRvbGcmrUXJdvXcDhkh30HYsws8XqX2rWPBflZpavzeS+6SwijRFV7g+9ypQRJZlrEQ==}
|
||||
hasBin: true
|
||||
|
||||
media-typer@1.1.0:
|
||||
@@ -873,10 +946,6 @@ packages:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
qs@6.14.0:
|
||||
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
||||
engines: {node: '>=0.6'}
|
||||
@@ -1035,9 +1104,6 @@ packages:
|
||||
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
|
||||
uri-templates@0.2.0:
|
||||
resolution: {integrity: sha512-EWkjYEN0L6KOfEoOH6Wj4ghQqU7eBZMJqRHQnxQAq+dSEzRPClkWjf8557HkWQXF6BrAUoLSAyy9i3RVTliaNg==}
|
||||
|
||||
@@ -1057,8 +1123,8 @@ packages:
|
||||
wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
xsschema@0.3.5:
|
||||
resolution: {integrity: sha512-f+dcy11dTv7aBEEfTbXWnrm/1b/Ds2k4taN0TpN6KCtPAD58kyE/R1znUdrHdBnhIFexj9k+pS1fm6FgtSISrQ==}
|
||||
xsschema@0.4.0-beta.5:
|
||||
resolution: {integrity: sha512-73pYwf1hMy++7SnOkghJdgdPaGi+Y5I0SaO6rIlxb1ouV6tEyDbEcXP82kyr32KQVTlUbFj6qewi9eUVEiXm+g==}
|
||||
peerDependencies:
|
||||
'@valibot/to-json-schema': ^1.0.0
|
||||
arktype: ^2.1.20
|
||||
@@ -1096,13 +1162,13 @@ packages:
|
||||
resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
zod-to-json-schema@3.24.6:
|
||||
resolution: {integrity: sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==}
|
||||
zod-to-json-schema@3.25.1:
|
||||
resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==}
|
||||
peerDependencies:
|
||||
zod: ^3.24.1
|
||||
zod: ^3.25 || ^4
|
||||
|
||||
zod@3.25.76:
|
||||
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
||||
zod@4.3.5:
|
||||
resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==}
|
||||
|
||||
snapshots:
|
||||
|
||||
@@ -1132,15 +1198,17 @@ snapshots:
|
||||
|
||||
'@actions/io@1.1.3': {}
|
||||
|
||||
'@anthropic-ai/claude-agent-sdk@0.1.37(zod@3.25.76)':
|
||||
'@anthropic-ai/claude-agent-sdk@0.1.77(zod@4.3.5)':
|
||||
dependencies:
|
||||
zod: 3.25.76
|
||||
zod: 4.3.5
|
||||
optionalDependencies:
|
||||
'@img/sharp-darwin-arm64': 0.33.5
|
||||
'@img/sharp-darwin-x64': 0.33.5
|
||||
'@img/sharp-linux-arm': 0.33.5
|
||||
'@img/sharp-linux-arm64': 0.33.5
|
||||
'@img/sharp-linux-x64': 0.33.5
|
||||
'@img/sharp-linuxmusl-arm64': 0.33.5
|
||||
'@img/sharp-linuxmusl-x64': 0.33.5
|
||||
'@img/sharp-win32-x64': 0.33.5
|
||||
|
||||
'@ark/fs@0.53.0': {}
|
||||
@@ -1235,6 +1303,10 @@ snapshots:
|
||||
|
||||
'@fastify/busboy@2.1.1': {}
|
||||
|
||||
'@hono/node-server@1.19.7(hono@4.11.3)':
|
||||
dependencies:
|
||||
hono: 4.11.3
|
||||
|
||||
'@img/sharp-darwin-arm64@0.33.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-arm64': 1.0.4
|
||||
@@ -1260,6 +1332,12 @@ snapshots:
|
||||
'@img/sharp-libvips-linux-x64@1.0.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-arm64@0.33.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-arm64': 1.0.4
|
||||
@@ -1275,12 +1353,24 @@ snapshots:
|
||||
'@img/sharp-libvips-linux-x64': 1.0.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.33.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linuxmusl-arm64': 1.0.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.33.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linuxmusl-x64': 1.0.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-x64@0.33.5':
|
||||
optional: true
|
||||
|
||||
'@modelcontextprotocol/sdk@1.20.0':
|
||||
'@modelcontextprotocol/sdk@1.25.2(hono@4.11.3)(zod@4.3.5)':
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
'@hono/node-server': 1.19.7(hono@4.11.3)
|
||||
ajv: 8.17.1
|
||||
ajv-formats: 3.0.1(ajv@8.17.1)
|
||||
content-type: 1.0.5
|
||||
cors: 2.8.5
|
||||
cross-spawn: 7.0.6
|
||||
@@ -1288,11 +1378,14 @@ snapshots:
|
||||
eventsource-parser: 3.0.6
|
||||
express: 5.1.0
|
||||
express-rate-limit: 7.5.1(express@5.1.0)
|
||||
jose: 6.1.3
|
||||
json-schema-typed: 8.0.2
|
||||
pkce-challenge: 5.0.0
|
||||
raw-body: 3.0.1
|
||||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.24.6(zod@3.25.76)
|
||||
zod: 4.3.5
|
||||
zod-to-json-schema: 3.25.1(zod@4.3.5)
|
||||
transitivePeerDependencies:
|
||||
- hono
|
||||
- supports-color
|
||||
|
||||
'@octokit/auth-token@4.0.0': {}
|
||||
@@ -1347,6 +1440,8 @@ snapshots:
|
||||
|
||||
'@octokit/openapi-types@26.0.0': {}
|
||||
|
||||
'@octokit/openapi-types@27.0.0': {}
|
||||
|
||||
'@octokit/plugin-paginate-rest@13.2.0(@octokit/core@7.0.5)':
|
||||
dependencies:
|
||||
'@octokit/core': 7.0.5
|
||||
@@ -1371,6 +1466,12 @@ snapshots:
|
||||
'@octokit/core': 7.0.5
|
||||
'@octokit/types': 15.0.0
|
||||
|
||||
'@octokit/plugin-throttling@11.0.3(@octokit/core@7.0.5)':
|
||||
dependencies:
|
||||
'@octokit/core': 7.0.5
|
||||
'@octokit/types': 16.0.0
|
||||
bottleneck: 2.19.5
|
||||
|
||||
'@octokit/request-error@5.1.1':
|
||||
dependencies:
|
||||
'@octokit/types': 13.10.0
|
||||
@@ -1415,6 +1516,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@octokit/openapi-types': 26.0.0
|
||||
|
||||
'@octokit/types@16.0.0':
|
||||
dependencies:
|
||||
'@octokit/openapi-types': 27.0.0
|
||||
|
||||
'@octokit/webhooks-types@7.6.1': {}
|
||||
|
||||
'@openai/codex-sdk@0.58.0': {}
|
||||
@@ -1427,16 +1532,17 @@ snapshots:
|
||||
|
||||
'@standard-schema/spec@1.0.0': {}
|
||||
|
||||
'@tokenizer/inflate@0.2.7':
|
||||
'@tokenizer/inflate@0.4.1':
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
fflate: 0.8.2
|
||||
token-types: 6.1.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@tokenizer/token@0.3.0': {}
|
||||
|
||||
'@toon-format/toon@1.4.0': {}
|
||||
|
||||
'@types/node@24.7.2':
|
||||
dependencies:
|
||||
undici-types: 7.14.0
|
||||
@@ -1446,12 +1552,9 @@ snapshots:
|
||||
mime-types: 3.0.1
|
||||
negotiator: 1.0.0
|
||||
|
||||
ajv@6.12.6:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
fast-json-stable-stringify: 2.1.0
|
||||
json-schema-traverse: 0.4.1
|
||||
uri-js: 4.4.1
|
||||
ajv-formats@3.0.1(ajv@8.17.1):
|
||||
optionalDependencies:
|
||||
ajv: 8.17.1
|
||||
|
||||
ajv@8.17.1:
|
||||
dependencies:
|
||||
@@ -1502,6 +1605,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
bottleneck@2.19.5: {}
|
||||
|
||||
bytes@3.1.2: {}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
@@ -1635,6 +1740,21 @@ snapshots:
|
||||
strip-final-newline: 4.0.0
|
||||
yoctocolors: 2.1.2
|
||||
|
||||
execa@9.6.1:
|
||||
dependencies:
|
||||
'@sindresorhus/merge-streams': 4.0.0
|
||||
cross-spawn: 7.0.6
|
||||
figures: 6.1.0
|
||||
get-stream: 9.0.1
|
||||
human-signals: 8.0.1
|
||||
is-plain-obj: 4.1.0
|
||||
is-stream: 4.0.1
|
||||
npm-run-path: 6.0.0
|
||||
pretty-ms: 9.3.0
|
||||
signal-exit: 4.1.0
|
||||
strip-final-newline: 4.0.0
|
||||
yoctocolors: 2.1.2
|
||||
|
||||
express-rate-limit@7.5.1(express@5.1.0):
|
||||
dependencies:
|
||||
express: 5.1.0
|
||||
@@ -1675,41 +1795,39 @@ snapshots:
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
fast-json-stable-stringify@2.1.0: {}
|
||||
|
||||
fast-uri@3.1.0: {}
|
||||
|
||||
fastmcp@3.20.0(arktype@2.1.28):
|
||||
fastmcp@3.26.8(arktype@2.1.28)(hono@4.11.3):
|
||||
dependencies:
|
||||
'@modelcontextprotocol/sdk': 1.20.0
|
||||
'@modelcontextprotocol/sdk': 1.25.2(hono@4.11.3)(zod@4.3.5)
|
||||
'@standard-schema/spec': 1.0.0
|
||||
execa: 9.6.0
|
||||
file-type: 21.0.0
|
||||
execa: 9.6.1
|
||||
file-type: 21.3.0
|
||||
fuse.js: 7.1.0
|
||||
mcp-proxy: 5.9.0
|
||||
mcp-proxy: 5.12.5
|
||||
strict-event-emitter-types: 2.0.0
|
||||
undici: 7.16.0
|
||||
uri-templates: 0.2.0
|
||||
xsschema: 0.3.5(arktype@2.1.28)(zod-to-json-schema@3.24.6(zod@3.25.76))(zod@3.25.76)
|
||||
xsschema: 0.4.0-beta.5(arktype@2.1.28)(zod-to-json-schema@3.25.1(zod@4.3.5))(zod@4.3.5)
|
||||
yargs: 18.0.0
|
||||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.24.6(zod@3.25.76)
|
||||
zod: 4.3.5
|
||||
zod-to-json-schema: 3.25.1(zod@4.3.5)
|
||||
transitivePeerDependencies:
|
||||
- '@cfworker/json-schema'
|
||||
- '@valibot/to-json-schema'
|
||||
- arktype
|
||||
- effect
|
||||
- hono
|
||||
- supports-color
|
||||
- sury
|
||||
|
||||
fflate@0.8.2: {}
|
||||
|
||||
figures@6.1.0:
|
||||
dependencies:
|
||||
is-unicode-supported: 2.1.0
|
||||
|
||||
file-type@21.0.0:
|
||||
file-type@21.3.0:
|
||||
dependencies:
|
||||
'@tokenizer/inflate': 0.2.7
|
||||
'@tokenizer/inflate': 0.4.1
|
||||
strtok3: 10.3.4
|
||||
token-types: 6.1.1
|
||||
uint8array-extras: 1.5.0
|
||||
@@ -1770,6 +1888,8 @@ snapshots:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hono@4.11.3: {}
|
||||
|
||||
http-errors@2.0.0:
|
||||
dependencies:
|
||||
depd: 2.0.0
|
||||
@@ -1808,15 +1928,17 @@ snapshots:
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
json-schema-traverse@0.4.1: {}
|
||||
jose@6.1.3: {}
|
||||
|
||||
json-schema-traverse@1.0.0: {}
|
||||
|
||||
json-schema-typed@8.0.2: {}
|
||||
|
||||
lodash.truncate@4.4.2: {}
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
mcp-proxy@5.9.0: {}
|
||||
mcp-proxy@5.12.5: {}
|
||||
|
||||
media-typer@1.1.0: {}
|
||||
|
||||
@@ -1872,8 +1994,6 @@ snapshots:
|
||||
forwarded: 0.2.0
|
||||
ipaddr.js: 1.9.1
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
qs@6.14.0:
|
||||
dependencies:
|
||||
side-channel: 1.1.0
|
||||
@@ -2048,10 +2168,6 @@ snapshots:
|
||||
|
||||
unpipe@1.0.0: {}
|
||||
|
||||
uri-js@4.4.1:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
uri-templates@0.2.0: {}
|
||||
|
||||
vary@1.1.2: {}
|
||||
@@ -2068,11 +2184,11 @@ snapshots:
|
||||
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
xsschema@0.3.5(arktype@2.1.28)(zod-to-json-schema@3.24.6(zod@3.25.76))(zod@3.25.76):
|
||||
xsschema@0.4.0-beta.5(arktype@2.1.28)(zod-to-json-schema@3.25.1(zod@4.3.5))(zod@4.3.5):
|
||||
optionalDependencies:
|
||||
arktype: 2.1.28
|
||||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.24.6(zod@3.25.76)
|
||||
zod: 4.3.5
|
||||
zod-to-json-schema: 3.25.1(zod@4.3.5)
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
||||
@@ -2089,8 +2205,8 @@ snapshots:
|
||||
|
||||
yoctocolors@2.1.2: {}
|
||||
|
||||
zod-to-json-schema@3.24.6(zod@3.25.76):
|
||||
zod-to-json-schema@3.25.1(zod@4.3.5):
|
||||
dependencies:
|
||||
zod: 3.25.76
|
||||
zod: 4.3.5
|
||||
|
||||
zod@3.25.76: {}
|
||||
zod@4.3.5: {}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { fetchWorkflowRunInfo } from "./api.ts";
|
||||
import { getGitHubInstallationToken, parseRepoContext } from "./github.ts";
|
||||
import { createOctokit, getGitHubInstallationToken, parseRepoContext } from "./github.ts";
|
||||
|
||||
/**
|
||||
* Get progress comment ID from environment variable or database.
|
||||
@@ -55,8 +54,7 @@ export async function reportErrorToComment({
|
||||
|
||||
// update comment directly using GitHub API
|
||||
const repoContext = parseRepoContext();
|
||||
const token = getGitHubInstallationToken();
|
||||
const octokit = new Octokit({ auth: token });
|
||||
const octokit = createOctokit(getGitHubInstallationToken());
|
||||
|
||||
await octokit.rest.issues.updateComment({
|
||||
owner: repoContext.owner,
|
||||
|
||||
+44
-16
@@ -1,5 +1,8 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createSign } from "node:crypto";
|
||||
import * as core from "@actions/core";
|
||||
import { throttling } from "@octokit/plugin-throttling";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { log } from "./cli.ts";
|
||||
import { retry } from "./retry.ts";
|
||||
|
||||
@@ -44,8 +47,11 @@ interface RepositoriesResponse {
|
||||
repositories: Repository[];
|
||||
}
|
||||
|
||||
function isGitHubActionsEnvironment(): boolean {
|
||||
return Boolean(process.env.GITHUB_ACTIONS);
|
||||
function isOIDCAvailable(): boolean {
|
||||
// OIDC requires both env vars to be set (only in real GitHub Actions with id-token permission)
|
||||
return Boolean(
|
||||
process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
||||
);
|
||||
}
|
||||
|
||||
async function acquireTokenViaOIDC(): Promise<string> {
|
||||
@@ -233,7 +239,7 @@ async function acquireTokenViaGitHubApp(): Promise<string> {
|
||||
}
|
||||
|
||||
async function acquireNewToken(): Promise<string> {
|
||||
if (isGitHubActionsEnvironment()) {
|
||||
if (isOIDCAvailable()) {
|
||||
return await retry(() => acquireTokenViaOIDC(), { label: "token exchange" });
|
||||
} else {
|
||||
return await acquireTokenViaGitHubApp();
|
||||
@@ -246,31 +252,32 @@ let githubInstallationToken: string | undefined;
|
||||
/**
|
||||
* Setup GitHub installation token for the action
|
||||
*/
|
||||
export async function setupGitHubInstallationToken(): Promise<string> {
|
||||
export async function setupGitHubInstallationToken() {
|
||||
assert(!githubInstallationToken, "GitHub installation token is already set.");
|
||||
const acquiredToken = await acquireNewToken();
|
||||
core.setSecret(acquiredToken);
|
||||
githubInstallationToken = acquiredToken;
|
||||
return acquiredToken;
|
||||
return {
|
||||
token: acquiredToken,
|
||||
[Symbol.asyncDispose]() {
|
||||
githubInstallationToken = undefined;
|
||||
return revokeGitHubInstallationToken(acquiredToken);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GitHub installation token from memory
|
||||
*/
|
||||
export function getGitHubInstallationToken(): string {
|
||||
if (!githubInstallationToken) {
|
||||
throw new Error("GitHub installation token not set. Call setupGitHubInstallationToken first.");
|
||||
}
|
||||
assert(
|
||||
githubInstallationToken,
|
||||
"GitHub installation token not set. Call setupGitHubInstallationToken first."
|
||||
);
|
||||
return githubInstallationToken;
|
||||
}
|
||||
|
||||
export async function revokeGitHubInstallationToken(): Promise<void> {
|
||||
if (!githubInstallationToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = githubInstallationToken;
|
||||
githubInstallationToken = undefined;
|
||||
|
||||
async function revokeGitHubInstallationToken(token: string): Promise<void> {
|
||||
const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com";
|
||||
|
||||
try {
|
||||
@@ -311,3 +318,24 @@ export function parseRepoContext(): RepoContext {
|
||||
|
||||
return { owner, name };
|
||||
}
|
||||
|
||||
export type OctokitWithPlugins = InstanceType<
|
||||
ReturnType<typeof Octokit.plugin<typeof Octokit, [typeof throttling]>>
|
||||
>;
|
||||
|
||||
export function createOctokit(token: string): OctokitWithPlugins {
|
||||
// `OctokitWithPlugins` initialization based on https://github.com/actions/toolkit/blob/2506e78e82fbd2f9e94d63e75f5309118c8de1b1/packages/github/src/github.ts#L15-L22
|
||||
// we can't use it directly because it's stuck on `@octokit/core@v5` and we use the hottest `@octokit/core@v7`
|
||||
const OctokitWithPlugins = Octokit.plugin(throttling);
|
||||
return new OctokitWithPlugins({
|
||||
auth: token,
|
||||
throttle: {
|
||||
onRateLimit: (retryAfter, options, octokit, retryCount) => {
|
||||
return retryCount <= 2;
|
||||
},
|
||||
onSecondaryRateLimit: (retryAfter, options, octokit, retryCount) => {
|
||||
return retryCount <= 2;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+3
-2
@@ -37,11 +37,12 @@ export async function retry<T>(fn: () => Promise<T>, options: RetryOptions = {})
|
||||
}
|
||||
|
||||
const delay = delayMs * attempt;
|
||||
log.warning(`» ${label} failed (attempt ${attempt}/${maxAttempts}), retrying in ${delay}ms...`);
|
||||
log.warning(
|
||||
`» ${label} failed (attempt ${attempt}/${maxAttempts}), retrying in ${delay}ms...`
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import { existsSync, rmSync } from "node:fs";
|
||||
import type { Octokit } from "@octokit/rest";
|
||||
import type { Payload } from "../external.ts";
|
||||
import type { ToolState } from "../main.ts";
|
||||
import { checkoutPrBranch } from "../mcp/checkout.ts";
|
||||
import { log } from "./cli.ts";
|
||||
import type { OctokitWithPlugins } from "./github.ts";
|
||||
import { $ } from "./shell.ts";
|
||||
|
||||
export interface SetupOptions {
|
||||
@@ -66,7 +66,7 @@ interface SetupGitAuthParams {
|
||||
owner: string;
|
||||
name: string;
|
||||
payload: Payload;
|
||||
octokit: Octokit;
|
||||
octokit: OctokitWithPlugins;
|
||||
toolState: ToolState;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user