Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ updates:
patterns:
- "*"

# Bun / npm dependencies
- package-ecosystem: "npm"
# Bun dependencies
- package-ecosystem: "bun"
directory: "/"
schedule:
interval: "monthly"
Expand Down
12 changes: 12 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
src/
test/
scripts/
.github/
.husky/
*.test.ts
tsconfig.json
eslint.config.*
.prettierrc*
.commitlintrc*
docker*
Dockerfile*
14 changes: 6 additions & 8 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 18 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
{
"name": "chrisleekr/github-app-playground",
"description": "A test GitHub App that listens for @chrisleekr-bot mentions on PRs and issues, then autonomously responds using the Claude Agent SDK (@anthropic-ai/claude-agent-sdk) with MCP servers",
"name": "@chrisleekr/github-app-playground",
"description": "A GitHub App that listens for @chrisleekr-bot mentions on PRs and issues, then autonomously responds using the Claude Agent SDK (@anthropic-ai/claude-agent-sdk) with MCP servers",
"author": "Chris Lee <git@chrislee.kr>",
"version": "1.0.0",
"private": true,
"main": "dist/app.js",
"bin": {
"github-app-playground": "dist/app.js"
},
"files": [
"dist/",
"README.md",
"LICENSE"
],
"publishConfig": {
"access": "public"
},
"keywords": [
"github",
"app",
Expand All @@ -28,6 +39,8 @@
},
"packageManager": "bun@1.3.8",
"scripts": {
"prepublishOnly": "bun run clean && NODE_ENV=production bun run build",
"clean": "rm -rf dist",
"build": "bun run scripts/build.ts",
"dev": "bun run build && bun run --watch src/app.ts",
"dev:ngrok": "ngrok http 3000",
Expand All @@ -47,12 +60,12 @@
"license": "MIT",
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.44",
"@modelcontextprotocol/sdk": "^1.11.0",
"@modelcontextprotocol/sdk": "^1.26.0",
"@octokit/webhooks": "13.9.1",
"@octokit/webhooks-types": "^7.6.1",
"octokit": "^4.1.2",
"pino": "^9.6.0",
"zod": "^3.24.4"
"zod": "^4.0.0"
},
"devDependencies": {
"@commitlint/cli": "^20.4.1",
Expand Down
6 changes: 3 additions & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const configSchema = z
// Direct Anthropic API requires a non-empty API key
if (data.anthropicApiKey === undefined || data.anthropicApiKey === "") {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: "ANTHROPIC_API_KEY is required when CLAUDE_PROVIDER=anthropic",
path: ["anthropicApiKey"],
});
Expand All @@ -77,7 +77,7 @@ const configSchema = z
// Bedrock always needs a region to construct the endpoint
if (data.awsRegion === undefined || data.awsRegion === "") {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: "AWS_REGION is required when CLAUDE_PROVIDER=bedrock",
path: ["awsRegion"],
});
Expand All @@ -87,7 +87,7 @@ const configSchema = z
// default that the Bedrock API will reject.
if (data.model === undefined) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message:
"CLAUDE_MODEL is required when CLAUDE_PROVIDER=bedrock (e.g. us.anthropic.claude-sonnet-4-6)",
path: ["model"],
Expand Down
27 changes: 16 additions & 11 deletions src/core/executor.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import { query } from "@anthropic-ai/claude-agent-sdk";
import { query, type SDKResultMessage } from "@anthropic-ai/claude-agent-sdk";

import { config } from "../config";
import type { BotContext, ExecutionResult, McpServerConfig } from "../types";

/** Narrows an unknown streamed SDK message to the final result shape. */
function isResultMessage(msg: unknown): msg is SDKResultMessage {
return (
typeof msg === "object" &&
msg !== null &&
"type" in msg &&
(msg as { type: unknown }).type === "result"
);
}

/**
* Build the subprocess environment for the Claude Code CLI.
*
Expand Down Expand Up @@ -44,14 +54,7 @@ export async function executeAgent(
);

const startTime = Date.now();
let result:
| {
subtype?: string;
total_cost_usd?: number;
duration_ms?: number;
num_turns?: number;
}
| undefined;
let result: SDKResultMessage | undefined;

// Build query options. model is only included when set (exactOptionalPropertyTypes
// forbids assigning undefined to optional properties).
Expand All @@ -77,8 +80,10 @@ export async function executeAgent(
// eventually exhaust MAX_CONCURRENT_REQUESTS for all subsequent requests.
const agentLoop = (async (): Promise<void> => {
for await (const message of query({ prompt, options: queryOptions })) {
// Capture the final result message for metadata
if (message.type === "result") {
// Capture the final result message for metadata.
// isResultMessage guards against the SDK message union resolving to
// `any` in ESLint's type graph — ensures safe property access.
if (isResultMessage(message)) {
result = message;
}
}
Expand Down