diff --git a/.prettierrc.js b/.prettierrc.js deleted file mode 100644 index b31932c..0000000 --- a/.prettierrc.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '../core/configs/prettier/prettier.config.js'; diff --git a/build.config.ts b/build.config.ts deleted file mode 100644 index d30fb4b..0000000 --- a/build.config.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { BuildConfig } from 'bun'; - -export const buildConfig: BuildConfig = { - entrypoints: ['./src/index.ts'], - outdir: './dist', - target: 'node', - format: 'esm', - splitting: false, - sourcemap: 'external', - external: [ - 'fs', - 'path', - 'http', - 'https', - 'crypto', - 'node:fs', - 'node:path', - 'node:http', - 'node:https', - 'node:crypto', - 'node:stream', - 'node:buffer', - 'node:util', - 'node:events', - 'node:url', - 'bun:test', - 'dotenv', - 'zod', - 'typescript', - '@elizaos/core', - 'punycode', - 'node-fetch', - ], - naming: '[dir]/[name].[ext]', -}; diff --git a/build.ts b/build.ts deleted file mode 100755 index acca047..0000000 --- a/build.ts +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bun - -/** - * Build script using bun build - */ - -import { $ } from 'bun'; -import { buildConfig } from './build.config'; - -async function build() { - console.log('🏗️ Building package...'); - - // Clean dist directory - await $`rm -rf dist`; - - // Build with bun - const result = await Bun.build(buildConfig); - - if (!result.success) { - console.error('❌ Build failed:'); - for (const message of result.logs) { - console.error(message); - } - process.exit(1); - } - - console.log(`✅ Built ${result.outputs.length} files`); - - // Generate TypeScript declarations - console.log('📝 Generating TypeScript declarations...'); - try { - await $`tsc --project tsconfig.build.json`; - console.log('✅ TypeScript declarations generated'); - } catch (error) { - console.warn('⚠️ TypeScript declaration generation had issues, but continuing...'); - } - - console.log('✅ Build complete!'); -} - -build().catch(console.error); diff --git a/bun.lock b/bun.lock index cda9ac5..02fd7b0 100644 --- a/bun.lock +++ b/bun.lock @@ -4,32 +4,42 @@ "": { "name": "@elizaos/plugin-github", "dependencies": { - "@elizaos/core": "^1.2.1", - "@octokit/rest": "^20.1.1", - "@octokit/types": "^13.5.0", + "@elizaos/core": "^1.2.9", + "@octokit/rest": "^22.0.0", + "@octokit/types": "^14.1.0", + "@tanstack/react-query": "^5.80.7", "deprecation": "^2.3.1", "ngrok": "^5.0.0-beta.2", + "react": "^19.1.0", + "react-dom": "^19.1.0", "tailwindcss": "^4.1.10", - "vite": "^6.3.5", - "zod": "^3.25.67", + "vite": "^7.0.4", + "zod": "^4.0.5", }, "devDependencies": { "@cypress/react": "^9.0.1", + "@elizaos/config": "^1.2.9", + "@elizaos/test-utils": "^1.2.9", + "@tailwindcss/vite": "^4.1.11", "@testing-library/cypress": "^10.0.3", - "@types/node": "^20.0.0", + "@types/bun": "^1.2.18", + "@types/node": "^24.0.14", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react-swc": "^3.10.2", "axios": "^1.6.5", - "cypress": "^14.5.0", - "dotenv": "16.4.5", - "prettier": "3.5.3", + "cypress": "^14.5.2", + "dotenv": "17.2.0", + "prettier": "^3.6.2", + "tsup": "^8.0.2", "tsx": "^4.19.2", "typescript": "5.8.3", }, }, }, - "overrides": { - "zod": "3.25.67", - }, "packages": { + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], @@ -44,7 +54,11 @@ "@cypress/xvfb": ["@cypress/xvfb@1.2.4", "", { "dependencies": { "debug": "^3.1.0", "lodash.once": "^4.1.1" } }, "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q=="], - "@elizaos/core": ["@elizaos/core@1.2.1", "", { "dependencies": { "@sentry/browser": "^9.22.0", "buffer": "^6.0.3", "crypto-browserify": "^3.12.1", "dotenv": "16.5.0", "events": "^3.3.0", "glob": "11.0.3", "handlebars": "^4.7.8", "js-sha1": "0.7.0", "langchain": "^0.3.15", "pdfjs-dist": "^5.2.133", "pino": "^9.6.0", "pino-pretty": "^13.0.0", "stream-browserify": "^3.0.0", "unique-names-generator": "4.7.1", "uuid": "11.1.0", "zod": "^3.24.4" } }, "sha512-2CbCfhsAYzHMLCT9Vn56cafi2ZdeXhvjb0EC13CZVevN4a/Q0hWNK2YXGx97AhE+Nc58L0lZdEblK5tMC8UaNg=="], + "@elizaos/config": ["@elizaos/config@1.2.9", "", {}, "sha512-NDEUowc+oAYsN8ThJc6tBeTb5IE6VxSPLr9Fci5xD8qzuGxHbx2n+4WKR9DBFyRDoKmqQYu3ydWuCQpX7tL31A=="], + + "@elizaos/core": ["@elizaos/core@1.2.9", "", { "dependencies": { "@sentry/browser": "^9.22.0", "buffer": "^6.0.3", "crypto-browserify": "^3.12.1", "dotenv": "16.5.0", "events": "^3.3.0", "glob": "11.0.3", "handlebars": "^4.7.8", "js-sha1": "0.7.0", "langchain": "^0.3.15", "pdfjs-dist": "^5.2.133", "pino": "^9.6.0", "pino-pretty": "^13.0.0", "stream-browserify": "^3.0.0", "unique-names-generator": "4.7.1", "uuid": "11.1.0", "zod": "^3.24.4" } }, "sha512-apNhpnuPgT+DShJA7JpBkqUmQVWukJZNj9/gby+rOZ/BuBl8h38Nhol4ySrzUv0ntvm9dWehQh0vi/b3WC6h3Q=="], + + "@elizaos/test-utils": ["@elizaos/test-utils@1.2.9", "", { "dependencies": { "@elizaos/core": "1.2.9", "zod": "3.24.2" } }, "sha512-7A/yaVfHATe6cYQbNtRq40HPD84WvckAj/aXX+SOqT+PRGzjjB5eW1JPBzaWf3MSt5IAAx9POHBrNXnrZ0ESyg=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.6", "", { "os": "aix", "cpu": "ppc64" }, "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw=="], @@ -104,6 +118,16 @@ "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="], + "@langchain/core": ["@langchain/core@0.3.62", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", "langsmith": "^0.3.33", "mustache": "^4.2.0", "p-queue": "^6.6.2", "p-retry": "4", "uuid": "^10.0.0", "zod": "^3.25.32", "zod-to-json-schema": "^3.22.3" } }, "sha512-GqRTcoUPnozGRMUcA6QkP7LHL/OvanGdB51Jgb0w7IIPDI3wFugxMHZ4gphnGDtxsD1tQY5ykyEpYNxFK8kl1w=="], "@langchain/openai": ["@langchain/openai@0.5.18", "", { "dependencies": { "js-tiktoken": "^1.0.12", "openai": "^5.3.0", "zod": "^3.25.32" }, "peerDependencies": { "@langchain/core": ">=0.3.58 <0.4.0" } }, "sha512-CX1kOTbT5xVFNdtLjnM0GIYNf+P7oMSu+dGCFxxWRa3dZwWiuyuBXCm+dToUGxDLnsHuV1bKBtIzrY1mLq/A1Q=="], @@ -132,29 +156,33 @@ "@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@0.1.73", "", { "os": "win32", "cpu": "x64" }, "sha512-YQmHXBufFBdWqhx+ympeTPkMfs3RNxaOgWm59vyjpsub7Us07BwCcmu1N5kildhO8Fm0syoI2kHnzGkJBLSvsg=="], - "@octokit/auth-token": ["@octokit/auth-token@4.0.0", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="], + "@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="], + + "@octokit/core": ["@octokit/core@7.0.3", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.1", "@octokit/request": "^10.0.2", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ=="], - "@octokit/core": ["@octokit/core@5.2.2", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg=="], + "@octokit/endpoint": ["@octokit/endpoint@11.0.0", "", { "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ=="], - "@octokit/endpoint": ["@octokit/endpoint@9.0.6", "", { "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw=="], + "@octokit/graphql": ["@octokit/graphql@9.0.1", "", { "dependencies": { "@octokit/request": "^10.0.2", "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg=="], - "@octokit/graphql": ["@octokit/graphql@7.1.1", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="], + "@octokit/openapi-types": ["@octokit/openapi-types@25.1.0", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="], - "@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], + "@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@13.1.1", "", { "dependencies": { "@octokit/types": "^14.1.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-q9iQGlZlxAVNRN2jDNskJW/Cafy7/XE52wjZ5TTvyhyOD904Cvx//DNyoO3J/MXJ0ve3rPoNWKEg5iZrisQSuw=="], - "@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@11.4.4-cjs.2", "", { "dependencies": { "@octokit/types": "^13.7.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw=="], + "@octokit/plugin-request-log": ["@octokit/plugin-request-log@6.0.0", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="], - "@octokit/plugin-request-log": ["@octokit/plugin-request-log@4.0.1", "", { "peerDependencies": { "@octokit/core": "5" } }, "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA=="], + "@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@16.0.0", "", { "dependencies": { "@octokit/types": "^14.1.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-kJVUQk6/dx/gRNLWUnAWKFs1kVPn5O5CYZyssyEoNYaFedqZxsfYs7DwI3d67hGz4qOwaJ1dpm07hOAD1BXx6g=="], - "@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1", "", { "dependencies": { "@octokit/types": "^13.8.0" }, "peerDependencies": { "@octokit/core": "^5" } }, "sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ=="], + "@octokit/request": ["@octokit/request@10.0.3", "", { "dependencies": { "@octokit/endpoint": "^11.0.0", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA=="], - "@octokit/request": ["@octokit/request@8.4.1", "", { "dependencies": { "@octokit/endpoint": "^9.0.6", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw=="], + "@octokit/request-error": ["@octokit/request-error@7.0.0", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg=="], - "@octokit/request-error": ["@octokit/request-error@5.1.1", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="], + "@octokit/rest": ["@octokit/rest@22.0.0", "", { "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" } }, "sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA=="], - "@octokit/rest": ["@octokit/rest@20.1.2", "", { "dependencies": { "@octokit/core": "^5.0.2", "@octokit/plugin-paginate-rest": "11.4.4-cjs.2", "@octokit/plugin-request-log": "^4.0.0", "@octokit/plugin-rest-endpoint-methods": "13.3.2-cjs.1" } }, "sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA=="], + "@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="], - "@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.11", "", {}, "sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.44.2", "", { "os": "android", "cpu": "arm" }, "sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q=="], @@ -210,14 +238,76 @@ "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], + "@swc/core": ["@swc/core@1.12.14", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.23" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.12.14", "@swc/core-darwin-x64": "1.12.14", "@swc/core-linux-arm-gnueabihf": "1.12.14", "@swc/core-linux-arm64-gnu": "1.12.14", "@swc/core-linux-arm64-musl": "1.12.14", "@swc/core-linux-x64-gnu": "1.12.14", "@swc/core-linux-x64-musl": "1.12.14", "@swc/core-win32-arm64-msvc": "1.12.14", "@swc/core-win32-ia32-msvc": "1.12.14", "@swc/core-win32-x64-msvc": "1.12.14" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-CJSn2vstd17ddWIHBsjuD4OQnn9krQfaq6EO+w9YfId5DKznyPmzxAARlOXG99cC8/3Kli8ysKy6phL43bSr0w=="], + + "@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.12.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HNukQoOKgMsHSETj8vgGGKK3SEcH7Cz6k4bpntCxBKNkO3sH7RcBTDulWGGHJfZaDNix7Rw2ExUVWtLZlzkzXg=="], + + "@swc/core-darwin-x64": ["@swc/core-darwin-x64@1.12.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-4Ttf3Obtk3MvFrR0e04qr6HfXh4L1Z+K3dRej63TAFuYpo+cPXeOZdPUddAW73lSUGkj+61IHnGPoXD3OQYy4Q=="], + + "@swc/core-linux-arm-gnueabihf": ["@swc/core-linux-arm-gnueabihf@1.12.14", "", { "os": "linux", "cpu": "arm" }, "sha512-zhJOH2KWjtQpzJ27Xjw/RKLVOa1aiEJC2b70xbCwEX6ZTVAl8tKbhkZ3GMphhfVmLJ9gf/2UQR58oxVnsXqX5Q=="], + + "@swc/core-linux-arm64-gnu": ["@swc/core-linux-arm64-gnu@1.12.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-akUAe1YrBqZf1EDdUxahQ8QZnJi8Ts6Ya0jf6GBIMvnXL4Y6QIuvKTRwfNxy7rJ+x9zpzP1Vlh14ZZkSKZ1EGA=="], + + "@swc/core-linux-arm64-musl": ["@swc/core-linux-arm64-musl@1.12.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZkOOIpSMXuPAjfOXEIAEQcrPOgLi6CaXvA5W+GYnpIpFG21Nd0qb0WbwFRv4K8BRtl993Q21v0gPpOaFHU+wdA=="], + + "@swc/core-linux-x64-gnu": ["@swc/core-linux-x64-gnu@1.12.14", "", { "os": "linux", "cpu": "x64" }, "sha512-71EPPccwJiJUxd2aMwNlTfom2mqWEWYGdbeTju01tzSHsEuD7E6ePlgC3P3ngBqB3urj41qKs87z7zPOswT5Iw=="], + + "@swc/core-linux-x64-musl": ["@swc/core-linux-x64-musl@1.12.14", "", { "os": "linux", "cpu": "x64" }, "sha512-nImF1hZJqKTcl0WWjHqlelOhvuB9rU9kHIw/CmISBUZXogjLIvGyop1TtJNz0ULcz2Oxr3Q2YpwfrzsgvgbGkA=="], + + "@swc/core-win32-arm64-msvc": ["@swc/core-win32-arm64-msvc@1.12.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-sABFQFxSuStFoxvEWZUHWYldtB1B4A9eDNFd4Ty50q7cemxp7uoscFoaCqfXSGNBwwBwpS5EiPB6YN4y6hqmLQ=="], + + "@swc/core-win32-ia32-msvc": ["@swc/core-win32-ia32-msvc@1.12.14", "", { "os": "win32", "cpu": "ia32" }, "sha512-KBznRB02NASkpepRdWIK4f1AvmaJCDipKWdW1M1xV9QL2tE4aySJFojVuG1+t0tVDkjRfwcZjycQfRoJ4RjD7Q=="], + + "@swc/core-win32-x64-msvc": ["@swc/core-win32-x64-msvc@1.12.14", "", { "os": "win32", "cpu": "x64" }, "sha512-SymoP2CJHzrYaFKjWvuQljcF7BkTpzaS1vpywv7K9EzdTb5N8qPDvNd+PhWUqBz9JHBhbJxpaeTDQBXF/WWPmw=="], + + "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], + + "@swc/types": ["@swc/types@0.1.23", "", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw=="], + "@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="], + "@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.11", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.11", "@tailwindcss/oxide-darwin-arm64": "4.1.11", "@tailwindcss/oxide-darwin-x64": "4.1.11", "@tailwindcss/oxide-freebsd-x64": "4.1.11", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", "@tailwindcss/oxide-linux-x64-musl": "4.1.11", "@tailwindcss/oxide-wasm32-wasi": "4.1.11", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" } }, "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.11", "", { "os": "android", "cpu": "arm64" }, "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11", "", { "os": "linux", "cpu": "arm" }, "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.11", "", { "os": "linux", "cpu": "x64" }, "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.11", "", { "os": "linux", "cpu": "x64" }, "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.11", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", "@napi-rs/wasm-runtime": "^0.2.11", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.11", "", { "os": "win32", "cpu": "x64" }, "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.1.11", "", { "dependencies": { "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "tailwindcss": "4.1.11" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw=="], + + "@tanstack/query-core": ["@tanstack/query-core@5.83.0", "", {}, "sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA=="], + + "@tanstack/react-query": ["@tanstack/react-query@5.83.0", "", { "dependencies": { "@tanstack/query-core": "5.83.0" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-/XGYhZ3foc5H0VM2jLSD/NyBRIOK4q9kfeml4+0x2DlL6xVuAcVEW+hTlTapAmejObg0i3eNqhkr2dT+eciwoQ=="], + "@testing-library/cypress": ["@testing-library/cypress@10.0.3", "", { "dependencies": { "@babel/runtime": "^7.14.6", "@testing-library/dom": "^10.1.0" }, "peerDependencies": { "cypress": "^12.0.0 || ^13.0.0 || ^14.0.0" } }, "sha512-TeZJMCNtiS59cPWalra7LgADuufO5FtbqQBYxuAgdX6ZFAR2D9CtQwAG8VbgvFcchW3K414va/+7P4OkQ80UVg=="], "@testing-library/dom": ["@testing-library/dom@10.4.0", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "pretty-format": "^27.0.2" } }, "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ=="], "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], + "@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="], + "@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], @@ -226,7 +316,7 @@ "@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="], - "@types/node": ["@types/node@20.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1GM9z6BJOv86qkPvzh2i6VW5+VVrXxCLknfmTkWEqz+6DqosiY28XUWCTmBcJ0ACzKqx/iwdIREfo1fwExIlkA=="], + "@types/node": ["@types/node@24.0.14", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw=="], "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], @@ -244,6 +334,10 @@ "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], + "@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@3.10.2", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.11", "@swc/core": "^1.11.31" }, "peerDependencies": { "vite": "^4 || ^5 || ^6 || ^7.0.0-beta.0" } }, "sha512-xD3Rdvrt5LgANug7WekBn1KhcvLn1H3jNBfJRL3reeOIua/WnZOEV5qi5qIBq5T8R0jUDmRtxuvk4bPhzGHDWw=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="], "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], @@ -254,6 +348,8 @@ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + "arch": ["arch@2.2.0", "", {}, "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], @@ -284,11 +380,13 @@ "axios": ["axios@1.10.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="], - "before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="], + "before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], "blob-util": ["blob-util@2.0.2", "", {}, "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ=="], @@ -296,6 +394,8 @@ "bn.js": ["bn.js@5.2.2", "", {}, "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw=="], + "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "brorand": ["brorand@1.1.0", "", {}, "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="], "browserify-aes": ["browserify-aes@1.2.0", "", { "dependencies": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.3", "inherits": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA=="], @@ -314,6 +414,12 @@ "buffer-xor": ["buffer-xor@1.0.3", "", {}, "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ=="], + "bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], + + "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="], "cacheable-request": ["cacheable-request@7.0.4", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg=="], @@ -334,6 +440,10 @@ "check-more-types": ["check-more-types@2.24.0", "", {}, "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA=="], + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + "ci-info": ["ci-info@4.3.0", "", {}, "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ=="], "cipher-base": ["cipher-base@1.0.6", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1" } }, "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw=="], @@ -362,6 +472,10 @@ "common-tags": ["common-tags@1.8.2", "", {}, "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA=="], + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + "console-table-printer": ["console-table-printer@2.14.6", "", { "dependencies": { "simple-wcswidth": "^1.0.1" } }, "sha512-MCBl5HNVaFuuHW6FGbL/4fB7N/ormCy+tQ+sxTrF6QtSbSNETvPuOVbkJBhzDgYhvjWGrTma4eYJa37ZuoQsPw=="], "core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], @@ -378,7 +492,7 @@ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - "cypress": ["cypress@14.5.1", "", { "dependencies": { "@cypress/request": "^3.0.8", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", "blob-util": "^2.0.2", "bluebird": "^3.7.2", "buffer": "^5.7.1", "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", "ci-info": "^4.1.0", "cli-cursor": "^3.1.0", "cli-table3": "0.6.1", "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", "debug": "^4.3.4", "enquirer": "^2.3.6", "eventemitter2": "6.4.7", "execa": "4.1.0", "executable": "^4.1.1", "extract-zip": "2.0.1", "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", "hasha": "5.2.2", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", "lodash": "^4.17.21", "log-symbols": "^4.0.0", "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", "semver": "^7.7.1", "supports-color": "^8.1.1", "tmp": "~0.2.3", "tree-kill": "1.2.2", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, "bin": { "cypress": "bin/cypress" } }, "sha512-vYBeZKW3UAtxwv5mFuSlOBCYhyO0H86TeDKRJ7TgARyHiREIaiDjeHtqjzrXRFrdz9KnNavqlm+z+hklC7v8XQ=="], + "cypress": ["cypress@14.5.2", "", { "dependencies": { "@cypress/request": "^3.0.8", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", "blob-util": "^2.0.2", "bluebird": "^3.7.2", "buffer": "^5.7.1", "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", "ci-info": "^4.1.0", "cli-cursor": "^3.1.0", "cli-table3": "0.6.1", "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", "debug": "^4.3.4", "enquirer": "^2.3.6", "eventemitter2": "6.4.7", "execa": "4.1.0", "executable": "^4.1.1", "extract-zip": "2.0.1", "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", "hasha": "5.2.2", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", "lodash": "^4.17.21", "log-symbols": "^4.0.0", "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", "semver": "^7.7.1", "supports-color": "^8.1.1", "tmp": "~0.2.3", "tree-kill": "1.2.2", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, "bin": { "cypress": "bin/cypress" } }, "sha512-O4E4CEBqDHLDrJD/dfStHPcM+8qFgVVZ89Li7xDU0yL/JxO/V0PEcfF2I8aGa7uA2MGNLkNUAnghPM83UcHOJw=="], "dashdash": ["dashdash@1.14.1", "", { "dependencies": { "assert-plus": "^1.0.0" } }, "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g=="], @@ -404,11 +518,13 @@ "des.js": ["des.js@1.1.0", "", { "dependencies": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg=="], + "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], + "diffie-hellman": ["diffie-hellman@5.0.3", "", { "dependencies": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" } }, "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg=="], "dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], - "dotenv": ["dotenv@16.4.5", "", {}, "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg=="], + "dotenv": ["dotenv@17.2.0", "", {}, "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], @@ -422,6 +538,8 @@ "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + "enhanced-resolve": ["enhanced-resolve@5.18.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ=="], + "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], @@ -454,6 +572,8 @@ "extsprintf": ["extsprintf@1.3.0", "", {}, "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g=="], + "fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="], + "fast-copy": ["fast-copy@3.0.2", "", {}, "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ=="], "fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="], @@ -466,6 +586,8 @@ "figures": ["figures@3.2.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg=="], + "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], + "follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="], "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], @@ -568,6 +690,8 @@ "jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="], + "jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], "js-sha1": ["js-sha1@0.7.0", "", {}, "sha512-oQZ1Mo7440BfLSv9TX87VNEyU52pXPVG19F9PL3gTgNt0tVxlZ8F4O6yze3CLuLx28TxotxvlyepCNaaV0ZjMw=="], @@ -600,14 +724,44 @@ "lazy-ass": ["lazy-ass@1.6.0", "", {}, "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw=="], + "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="], + + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + "listr2": ["listr2@3.14.0", "", { "dependencies": { "cli-truncate": "^2.1.0", "colorette": "^2.0.16", "log-update": "^4.0.0", "p-map": "^4.0.0", "rfdc": "^1.3.0", "rxjs": "^7.5.1", "through": "^2.3.8", "wrap-ansi": "^7.0.0" }, "peerDependencies": { "enquirer": ">= 2.3.0 < 3" }, "optionalPeers": ["enquirer"] }, "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g=="], + "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], "lodash.clonedeep": ["lodash.clonedeep@4.5.0", "", {}, "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="], "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], + "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], + "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], "log-update": ["log-update@4.0.0", "", { "dependencies": { "ansi-escapes": "^4.3.0", "cli-cursor": "^3.1.0", "slice-ansi": "^4.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg=="], @@ -618,6 +772,8 @@ "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], + "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "md5.js": ["md5.js@1.3.5", "", { "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1", "safe-buffer": "^5.1.2" } }, "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg=="], @@ -644,10 +800,18 @@ "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="], + + "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + + "mlly": ["mlly@1.7.4", "", { "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", "pkg-types": "^1.3.0", "ufo": "^1.5.4" } }, "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], + "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], @@ -658,6 +822,8 @@ "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], @@ -692,6 +858,8 @@ "path-scurry": ["path-scurry@2.0.0", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "pbkdf2": ["pbkdf2@3.1.3", "", { "dependencies": { "create-hash": "~1.1.3", "create-hmac": "^1.1.7", "ripemd160": "=2.0.1", "safe-buffer": "^5.2.1", "sha.js": "^2.4.11", "to-buffer": "^1.2.0" } }, "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA=="], "pdfjs-dist": ["pdfjs-dist@5.3.93", "", { "optionalDependencies": { "@napi-rs/canvas": "^0.1.71" } }, "sha512-w3fQKVL1oGn8FRyx5JUG5tnbblggDqyx2XzA5brsJ5hSuS+I0NdnJANhmeWKLjotdbPQucLBug5t0MeWr0AAdg=="], @@ -714,11 +882,17 @@ "pino-std-serializers": ["pino-std-serializers@7.0.0", "", {}, "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="], + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + + "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - "prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="], + "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + + "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], "pretty-bytes": ["pretty-bytes@5.6.0", "", {}, "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg=="], @@ -736,6 +910,8 @@ "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], @@ -754,12 +930,16 @@ "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], "request-progress": ["request-progress@3.0.0", "", { "dependencies": { "throttleit": "^1.0.0" } }, "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg=="], "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="], + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], "responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="], @@ -812,7 +992,7 @@ "sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="], - "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -836,16 +1016,28 @@ "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], + "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], "tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="], + "tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="], + + "tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="], + + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + "thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="], "throttleit": ["throttleit@1.0.1", "", {}, "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ=="], "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="], + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + "tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], "tldts": ["tldts@6.1.86", "", { "dependencies": { "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ=="], @@ -858,10 +1050,16 @@ "tough-cookie": ["tough-cookie@5.1.2", "", { "dependencies": { "tldts": "^6.1.32" } }, "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A=="], + "tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="], + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tsup": ["tsup@8.5.0", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.25.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "0.8.0-beta.0", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ=="], + "tsx": ["tsx@4.20.3", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ=="], "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], @@ -874,13 +1072,15 @@ "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], + "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], "unique-names-generator": ["unique-names-generator@4.7.1", "", {}, "sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow=="], - "universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], + "universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="], "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], @@ -892,7 +1092,11 @@ "verror": ["verror@1.10.0", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw=="], - "vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="], + "vite": ["vite@7.0.4", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA=="], + + "webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], + + "whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], @@ -906,11 +1110,13 @@ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "yaml": ["yaml@2.8.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="], "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], - "zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="], + "zod": ["zod@4.0.5", "", {}, "sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA=="], "zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="], @@ -922,6 +1128,10 @@ "@elizaos/core/dotenv": ["dotenv@16.5.0", "", {}, "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg=="], + "@elizaos/core/zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="], + + "@elizaos/test-utils/zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], @@ -932,6 +1142,30 @@ "@langchain/core/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], + "@langchain/core/zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="], + + "@langchain/openai/zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.4", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.4", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" }, "bundled": true }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@types/cacheable-request/@types/node": ["@types/node@20.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1GM9z6BJOv86qkPvzh2i6VW5+VVrXxCLknfmTkWEqz+6DqosiY28XUWCTmBcJ0ACzKqx/iwdIREfo1fwExIlkA=="], + + "@types/keyv/@types/node": ["@types/node@20.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1GM9z6BJOv86qkPvzh2i6VW5+VVrXxCLknfmTkWEqz+6DqosiY28XUWCTmBcJ0ACzKqx/iwdIREfo1fwExIlkA=="], + + "@types/responselike/@types/node": ["@types/node@20.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1GM9z6BJOv86qkPvzh2i6VW5+VVrXxCLknfmTkWEqz+6DqosiY28XUWCTmBcJ0ACzKqx/iwdIREfo1fwExIlkA=="], + + "@types/yauzl/@types/node": ["@types/node@20.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1GM9z6BJOv86qkPvzh2i6VW5+VVrXxCLknfmTkWEqz+6DqosiY28XUWCTmBcJ0ACzKqx/iwdIREfo1fwExIlkA=="], + "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], "asn1.js/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], @@ -952,8 +1186,12 @@ "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "handlebars/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "langchain/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], + "langchain/zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="], + "langsmith/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], "log-update/slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="], @@ -972,6 +1210,10 @@ "public-encrypt/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], + "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "sucrase/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + "to-buffer/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], @@ -980,6 +1222,16 @@ "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="], + + "@types/cacheable-request/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "@types/keyv/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "@types/responselike/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "@types/yauzl/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "browserify-sign/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "browserify-sign/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], @@ -987,5 +1239,13 @@ "pbkdf2/create-hash/ripemd160": ["ripemd160@2.0.2", "", { "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1" } }, "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA=="], "pbkdf2/ripemd160/hash-base": ["hash-base@2.0.2", "", { "dependencies": { "inherits": "^2.0.1" } }, "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw=="], + + "sucrase/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "sucrase/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "sucrase/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "sucrase/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], } } diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index 5014c07..0000000 --- a/eslint.config.js +++ /dev/null @@ -1,13 +0,0 @@ -import pluginConfig from '../core/configs/eslint/eslint.config.plugin.js'; - -export default [ - ...pluginConfig, - { - files: ['**/*.{js,ts,tsx}'], - rules: { - // Plugin-specific overrides - '@typescript-eslint/no-explicit-any': 'off', - 'no-console': 'off', - }, - }, -]; diff --git a/package.json b/package.json index 6e817a4..a06c559 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@elizaos/plugin-github", "description": "Comprehensive GitHub integration plugin for ElizaOS with repository management, issue tracking, and PR workflows", - "version": "1.2.0", + "version": "1.2.9", "type": "module", "main": "dist/index.js", "module": "dist/index.js", @@ -39,41 +39,49 @@ "tsup.config.ts" ], "dependencies": { - "@elizaos/core": "^1.2.1", - "@octokit/rest": "^20.1.1", - "@octokit/types": "^13.5.0", + "@elizaos/core": "^1.2.9", + "@octokit/rest": "^22.0.0", + "@octokit/types": "^14.1.0", + "@tanstack/react-query": "^5.80.7", "deprecation": "^2.3.1", "ngrok": "^5.0.0-beta.2", + "react": "^19.1.0", + "react-dom": "^19.1.0", "tailwindcss": "^4.1.10", - "vite": "^6.3.5", - "zod": "^3.25.67" + "vite": "^7.0.4", + "zod": "^4.0.5" }, "peerDependencies": {}, "devDependencies": { "@cypress/react": "^9.0.1", + "@elizaos/config": "^1.2.9", + "@elizaos/test-utils": "^1.2.9", + "@tailwindcss/vite": "^4.1.11", "@testing-library/cypress": "^10.0.3", - "@types/node": "^20.0.0", + "@types/bun": "^1.2.18", + "@types/node": "^24.0.14", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react-swc": "^3.10.2", "axios": "^1.6.5", - "cypress": "^14.5.0", - "dotenv": "16.4.5", - "prettier": "3.5.3", + "cypress": "^14.5.2", + "dotenv": "17.2.0", + "prettier": "^3.6.2", + "tsup": "^8.0.2", "tsx": "^4.19.2", "typescript": "5.8.3" }, "scripts": { - "build": "bun run build.ts", - "dev": "bun run build.ts --watch", + "build": "tsup", + "dev": "tsup --watch", "clean": "rm -rf dist && rm -rf .turbo", - "lint": "eslint src --fix && prettier --write ./src", + "lint": "prettier --write ./src", "typecheck": "tsc --noEmit", "test": "bun test && elizaos test" }, "publishConfig": { "access": "public" }, - "resolutions": { - "zod": "3.25.67" - }, "agentConfig": { "pluginType": "elizaos:plugin:1.0.0", "pluginParameters": { diff --git a/prettier.config.js b/prettier.config.js deleted file mode 100644 index b796c3d..0000000 --- a/prettier.config.js +++ /dev/null @@ -1,3 +0,0 @@ -import prettierConfig from '../core/configs/prettier/prettier.config.js'; - -export default { ...prettierConfig }; diff --git a/src/__tests__/action-chaining.test.ts b/src/__tests__/action-chaining.test.ts index 6f22506..80dfbf7 100644 --- a/src/__tests__/action-chaining.test.ts +++ b/src/__tests__/action-chaining.test.ts @@ -1,7 +1,7 @@ -import { describe, expect, it, mock, beforeEach } from 'bun:test'; -import { githubPlugin } from '../index'; -import { createMockRuntime, MockRuntime } from './test-utils'; -import { HandlerCallback, IAgentRuntime, Memory, State, UUID } from '@elizaos/core'; +import { describe, expect, it, mock, beforeEach } from "bun:test"; +import { githubPlugin } from "../index"; +import { createTestRuntime } from "./test-helpers"; +import { IAgentRuntime, Memory, State, UUID } from "@elizaos/core"; /** * Action Chaining Tests @@ -11,35 +11,35 @@ import { HandlerCallback, IAgentRuntime, Memory, State, UUID } from '@elizaos/co * used by subsequent actions. */ -describe('Action Chaining: GitHub Plugin', () => { - let mockRuntime: MockRuntime; +describe("Action Chaining: GitHub Plugin", () => { + let mockRuntime: IAgentRuntime; let mockGitHubService: any; beforeEach(() => { // Create mock GitHub service with all necessary methods mockGitHubService = { - capabilityDescription: 'GitHub integration service', + capabilityDescription: "GitHub integration service", searchRepositories: mock().mockResolvedValue({ total_count: 100, items: [ { id: 1, - name: 'awesome-project', - full_name: 'user/awesome-project', - owner: { login: 'user' }, + name: "awesome-project", + full_name: "user/awesome-project", + owner: { login: "user" }, stargazers_count: 1000, - description: 'An awesome project', - language: 'TypeScript', + description: "An awesome project", + language: "TypeScript", open_issues_count: 10, }, { id: 2, - name: 'cool-library', - full_name: 'org/cool-library', - owner: { login: 'org' }, + name: "cool-library", + full_name: "org/cool-library", + owner: { login: "org" }, stargazers_count: 500, - description: 'A cool library', - language: 'JavaScript', + description: "A cool library", + language: "JavaScript", open_issues_count: 5, }, ], @@ -50,88 +50,88 @@ describe('Action Chaining: GitHub Plugin', () => { { id: 301, number: 5, - title: 'Bug: Memory leak in component', - state: 'open', - labels: [{ name: 'bug' }], - created_at: '2024-01-05T00:00:00Z', - user: { login: 'issueReporter' }, + title: "Bug: Memory leak in component", + state: "open", + labels: [{ name: "bug" }], + created_at: "2024-01-05T00:00:00Z", + user: { login: "issueReporter" }, }, { id: 302, number: 6, - title: 'Feature: Add dark mode support', - state: 'open', - labels: [{ name: 'enhancement' }], - created_at: '2024-01-06T00:00:00Z', - user: { login: 'featureRequester' }, + title: "Feature: Add dark mode support", + state: "open", + labels: [{ name: "enhancement" }], + created_at: "2024-01-06T00:00:00Z", + user: { login: "featureRequester" }, }, ], }), getRepository: mock().mockResolvedValue({ id: 1, - name: 'awesome-project', - full_name: 'user/awesome-project', - owner: { login: 'user' }, + name: "awesome-project", + full_name: "user/awesome-project", + owner: { login: "user" }, stargazers_count: 1000, forks_count: 50, open_issues_count: 10, - created_at: '2020-01-01T00:00:00Z', - updated_at: '2024-01-01T00:00:00Z', - language: 'TypeScript', - topics: ['typescript', 'awesome'], - license: { name: 'MIT' }, + created_at: "2020-01-01T00:00:00Z", + updated_at: "2024-01-01T00:00:00Z", + language: "TypeScript", + topics: ["typescript", "awesome"], + license: { name: "MIT" }, }), listIssues: mock().mockResolvedValue([ { id: 101, number: 1, - title: 'Bug: Something is broken', - state: 'open', - labels: [{ name: 'bug' }], - created_at: '2024-01-01T00:00:00Z', - user: { login: 'reporter1' }, + title: "Bug: Something is broken", + state: "open", + labels: [{ name: "bug" }], + created_at: "2024-01-01T00:00:00Z", + user: { login: "reporter1" }, }, { id: 102, number: 2, - title: 'Feature: Add new functionality', - state: 'open', - labels: [{ name: 'enhancement' }], - created_at: '2024-01-02T00:00:00Z', - user: { login: 'reporter2' }, + title: "Feature: Add new functionality", + state: "open", + labels: [{ name: "enhancement" }], + created_at: "2024-01-02T00:00:00Z", + user: { login: "reporter2" }, }, ]), createIssue: mock().mockResolvedValue({ id: 103, number: 3, - title: 'New issue from chain', - state: 'open', - html_url: 'https://github.com/user/awesome-project/issues/3', + title: "New issue from chain", + state: "open", + html_url: "https://github.com/user/awesome-project/issues/3", created_at: new Date().toISOString(), - user: { login: 'test-user' }, + user: { login: "test-user" }, labels: [], assignees: [], comments: 0, - body: 'Created after checking rate limit and searching', + body: "Created after checking rate limit and searching", }), listPullRequests: mock().mockResolvedValue([ { id: 201, number: 10, - title: 'Fix: Resolve critical bug', - state: 'open', - created_at: '2024-01-03T00:00:00Z', - user: { login: 'contributor1' }, - base: { ref: 'main' }, - head: { ref: 'fix-bug' }, + title: "Fix: Resolve critical bug", + state: "open", + created_at: "2024-01-03T00:00:00Z", + user: { login: "contributor1" }, + base: { ref: "main" }, + head: { ref: "fix-bug" }, }, ]), createPullRequest: mock().mockResolvedValue({ id: 202, number: 11, - title: 'Feature: Add chaining support', - state: 'open', - html_url: 'https://github.com/user/awesome-project/pull/11', + title: "Feature: Add chaining support", + state: "open", + html_url: "https://github.com/user/awesome-project/pull/11", created_at: new Date().toISOString(), }), getRateLimit: mock().mockResolvedValue({ @@ -142,17 +142,17 @@ describe('Action Chaining: GitHub Plugin', () => { getActivityLog: mock().mockReturnValue([ { timestamp: Date.now(), - action: 'searchRepositories', + action: "searchRepositories", success: true, - details: { query: 'test' }, + details: { query: "test" }, }, ]), }; // Create mock runtime with the service - mockRuntime = createMockRuntime({ + mockRuntime = createTestRuntime({ getService: mock().mockImplementation((serviceType) => { - if (serviceType === 'github') { + if (serviceType === "github") { return mockGitHubService; } return null; @@ -160,18 +160,20 @@ describe('Action Chaining: GitHub Plugin', () => { }) as any; }); - describe('Search → Get Details → List Issues Chain', () => { - it('should pass repository data from search to get details', async () => { + describe("Search → Get Details → List Issues Chain", () => { + it("should pass repository data from search to get details", async () => { const actions = githubPlugin.actions!; // Step 1: Search for repositories - const searchAction = actions.find((a) => a.name === 'SEARCH_GITHUB_REPOSITORIES')!; + const searchAction = actions.find( + (a) => a.name === "SEARCH_GITHUB_REPOSITORIES", + )!; const searchResult = (await searchAction.handler( mockRuntime as unknown as IAgentRuntime, - createMemory('Search for TypeScript projects'), + createMemory("Search for TypeScript projects"), createState(), - { query: 'language:typescript', per_page: 2 } + { query: "language:typescript", per_page: 2 }, )) as any; expect(searchResult).toBeDefined(); @@ -179,13 +181,15 @@ describe('Action Chaining: GitHub Plugin', () => { expect(searchResult.data.repositories).toBeDefined(); // Step 2: Get details of the first repository using state from search - const getRepoAction = actions.find((a) => a.name === 'GET_GITHUB_REPOSITORY')!; + const getRepoAction = actions.find( + (a) => a.name === "GET_GITHUB_REPOSITORY", + )!; const repoDetails = (await getRepoAction.handler( mockRuntime as unknown as IAgentRuntime, - createMemory('Get details of the first repository'), + createMemory("Get details of the first repository"), createState(searchResult.data), // Pass the state from search - { owner: 'user', repo: 'awesome-project' } + { owner: "user", repo: "awesome-project" }, )) as any; expect(repoDetails).toBeDefined(); @@ -193,13 +197,15 @@ describe('Action Chaining: GitHub Plugin', () => { expect(repoDetails.data.repository).toBeDefined(); // Step 3: List issues using state from repository details - const listIssuesAction = actions.find((a) => a.name === 'LIST_GITHUB_ISSUES')!; + const listIssuesAction = actions.find( + (a) => a.name === "LIST_GITHUB_ISSUES", + )!; const issuesList = (await listIssuesAction.handler( mockRuntime as unknown as IAgentRuntime, - createMemory('List issues for the repository'), + createMemory("List issues for the repository"), createState(repoDetails.data), // Pass the state from get repo - { owner: 'user', repo: 'awesome-project' } + { owner: "user", repo: "awesome-project" }, )) as any; expect(issuesList).toBeDefined(); @@ -209,18 +215,20 @@ describe('Action Chaining: GitHub Plugin', () => { }); }); - describe('Rate Limit Check → Search → Create Issue Chain', () => { - it('should check rate limit before performing operations', async () => { + describe("Rate Limit Check → Search → Create Issue Chain", () => { + it("should check rate limit before performing operations", async () => { const actions = githubPlugin.actions!; // Step 1: Check rate limit - const rateLimitAction = actions.find((a) => a.name === 'GET_GITHUB_RATE_LIMIT')!; + const rateLimitAction = actions.find( + (a) => a.name === "GET_GITHUB_RATE_LIMIT", + )!; const rateLimitResult = (await rateLimitAction.handler( mockRuntime as unknown as IAgentRuntime, - createMemory('Check rate limit'), + createMemory("Check rate limit"), createState(), - {} + {}, )) as any; expect(rateLimitResult.values.rateLimit).toBeDefined(); @@ -228,103 +236,115 @@ describe('Action Chaining: GitHub Plugin', () => { // Step 2: Search for issues if rate limit is ok if (rateLimitResult.values.rateLimit.remaining > 100) { - const searchIssuesAction = actions.find((a) => a.name === 'SEARCH_GITHUB_ISSUES')!; + const searchIssuesAction = actions.find( + (a) => a.name === "SEARCH_GITHUB_ISSUES", + )!; const searchResult = (await searchIssuesAction.handler( mockRuntime as unknown as IAgentRuntime, - createMemory('Search for similar issues'), + createMemory("Search for similar issues"), createState(rateLimitResult.data), - { query: 'repo:user/awesome-project is:issue bug' } + { query: "repo:user/awesome-project is:issue bug" }, )) as any; expect(searchResult).toBeDefined(); expect(searchResult.data.github?.lastRateLimit).toBeDefined(); // Preserved from previous state // Step 3: Create issue based on search results - const createIssueAction = actions.find((a) => a.name === 'CREATE_GITHUB_ISSUE')!; + const createIssueAction = actions.find( + (a) => a.name === "CREATE_GITHUB_ISSUE", + )!; const createResult = (await createIssueAction.handler( mockRuntime as unknown as IAgentRuntime, - createMemory('Create a new issue'), + createMemory("Create a new issue"), createState(searchResult.data), { - owner: 'user', - repo: 'awesome-project', - title: 'New issue from chain', - body: 'Created after checking rate limit and searching', - } + owner: "user", + repo: "awesome-project", + title: "New issue from chain", + body: "Created after checking rate limit and searching", + }, )) as any; expect(createResult).toBeDefined(); - expect(createResult.text).toContain('Successfully created issue'); + expect(createResult.text).toContain("Successfully created issue"); expect(mockGitHubService.createIssue).toHaveBeenCalled(); } }); }); - describe('Complex Workflow: Repository Analysis', () => { - it('should perform a complete repository analysis workflow', async () => { + describe("Complex Workflow: Repository Analysis", () => { + it("should perform a complete repository analysis workflow", async () => { const actions = githubPlugin.actions!; const workflow: any[] = []; // Step 1: Get repository details - const getRepoAction = actions.find((a) => a.name === 'GET_GITHUB_REPOSITORY')!; + const getRepoAction = actions.find( + (a) => a.name === "GET_GITHUB_REPOSITORY", + )!; const repoResult = (await getRepoAction.handler( mockRuntime as unknown as IAgentRuntime, - createMemory('Analyze repository user/awesome-project'), + createMemory("Analyze repository user/awesome-project"), createState(), - { owner: 'user', repo: 'awesome-project' }, + { owner: "user", repo: "awesome-project" }, async (response) => { - workflow.push({ step: 'repo', callback: response }); + workflow.push({ step: "repo", callback: response }); return []; - } + }, )) as any; // Step 2: List open issues - const listIssuesAction = actions.find((a) => a.name === 'LIST_GITHUB_ISSUES')!; + const listIssuesAction = actions.find( + (a) => a.name === "LIST_GITHUB_ISSUES", + )!; const issuesResult = (await listIssuesAction.handler( mockRuntime as unknown as IAgentRuntime, - createMemory('Get open issues'), + createMemory("Get open issues"), createState(repoResult.data), // Use the return value's data - { owner: 'user', repo: 'awesome-project', state: 'open' }, + { owner: "user", repo: "awesome-project", state: "open" }, async (response) => { - workflow.push({ step: 'issues', callback: response }); + workflow.push({ step: "issues", callback: response }); return []; - } + }, )) as any; // Step 3: List pull requests - const listPRsAction = actions.find((a) => a.name === 'LIST_GITHUB_PULL_REQUESTS')!; + const listPRsAction = actions.find( + (a) => a.name === "LIST_GITHUB_PULL_REQUESTS", + )!; const prsResult = (await listPRsAction.handler( mockRuntime as unknown as IAgentRuntime, - createMemory('Get open pull requests'), + createMemory("Get open pull requests"), createState(issuesResult.data), // Use the return value's data - { owner: 'user', repo: 'awesome-project', state: 'open' }, + { owner: "user", repo: "awesome-project", state: "open" }, async (response) => { - workflow.push({ step: 'prs', callback: response }); + workflow.push({ step: "prs", callback: response }); return []; - } + }, )) as any; // Step 4: Get activity log - const activityAction = actions.find((a) => a.name === 'GET_GITHUB_ACTIVITY')!; + const activityAction = actions.find( + (a) => a.name === "GET_GITHUB_ACTIVITY", + )!; const activityResult = (await activityAction.handler( mockRuntime as unknown as IAgentRuntime, - createMemory('Get recent activity'), + createMemory("Get recent activity"), createState(prsResult.data), // Use the return value's data { limit: 10 }, async (response) => { - workflow.push({ step: 'activity', callback: response }); + workflow.push({ step: "activity", callback: response }); return []; - } + }, )) as any; // Verify the complete workflow expect(workflow).toHaveLength(4); - expect(workflow[0].step).toBe('repo'); - expect(workflow[1].step).toBe('issues'); - expect(workflow[2].step).toBe('prs'); - expect(workflow[3].step).toBe('activity'); + expect(workflow[0].step).toBe("repo"); + expect(workflow[1].step).toBe("issues"); + expect(workflow[2].step).toBe("prs"); + expect(workflow[3].step).toBe("activity"); // Verify state is preserved throughout the chain using return values const finalState = activityResult.data; @@ -340,30 +360,34 @@ describe('Action Chaining: GitHub Plugin', () => { }); }); - describe('Conditional Chaining', () => { - it('should branch based on repository characteristics', async () => { + describe("Conditional Chaining", () => { + it("should branch based on repository characteristics", async () => { const actions = githubPlugin.actions!; // Search for repositories - const searchAction = actions.find((a) => a.name === 'SEARCH_GITHUB_REPOSITORIES')!; + const searchAction = actions.find( + (a) => a.name === "SEARCH_GITHUB_REPOSITORIES", + )!; let searchResult: any; await searchAction.handler( mockRuntime as unknown as IAgentRuntime, - createMemory('Find popular repositories'), + createMemory("Find popular repositories"), createState(), - { query: 'stars:>500', per_page: 2 }, + { query: "stars:>500", per_page: 2 }, async (response) => { searchResult = response; return []; - } + }, ); // For each repository, perform different actions based on characteristics for (const repo of searchResult.repositories) { if (repo.stargazers_count > 800) { // High-star repos: check pull requests - const listPRsAction = actions.find((a) => a.name === 'LIST_GITHUB_PULL_REQUESTS')!; + const listPRsAction = actions.find( + (a) => a.name === "LIST_GITHUB_PULL_REQUESTS", + )!; let prResult: any; await listPRsAction.handler( @@ -374,13 +398,15 @@ describe('Action Chaining: GitHub Plugin', () => { async (response) => { prResult = response; return []; - } + }, ); expect(prResult.pullRequests).toBeDefined(); } else { // Lower-star repos: check issues - const listIssuesAction = actions.find((a) => a.name === 'LIST_GITHUB_ISSUES')!; + const listIssuesAction = actions.find( + (a) => a.name === "LIST_GITHUB_ISSUES", + )!; let issueResult: any; await listIssuesAction.handler( @@ -391,7 +417,7 @@ describe('Action Chaining: GitHub Plugin', () => { async (response) => { issueResult = response; return []; - } + }, ); expect(issueResult.issues).toBeDefined(); @@ -400,10 +426,12 @@ describe('Action Chaining: GitHub Plugin', () => { }); }); - describe('State Accumulation', () => { - it('should accumulate data across multiple searches', async () => { + describe("State Accumulation", () => { + it("should accumulate data across multiple searches", async () => { const actions = githubPlugin.actions!; - const searchAction = actions.find((a) => a.name === 'SEARCH_GITHUB_REPOSITORIES')!; + const searchAction = actions.find( + (a) => a.name === "SEARCH_GITHUB_REPOSITORIES", + )!; let accumulatedState: any = { allRepositories: [], @@ -411,7 +439,7 @@ describe('Action Chaining: GitHub Plugin', () => { }; // Perform multiple searches - const queries = ['language:rust', 'language:go', 'language:python']; + const queries = ["language:rust", "language:go", "language:python"]; for (const query of queries) { await searchAction.handler( @@ -419,7 +447,7 @@ describe('Action Chaining: GitHub Plugin', () => { createMemory(`Search for ${query} repositories`), createState(accumulatedState), { query, per_page: 2 }, - async (response) => { + async (response: any) => { // Accumulate results accumulatedState.allRepositories.push(...response.repositories); accumulatedState.searchQueries.push({ @@ -429,14 +457,16 @@ describe('Action Chaining: GitHub Plugin', () => { }); accumulatedState = { ...accumulatedState, ...response.data }; return []; - } + }, ); } // Verify accumulated state expect(accumulatedState.allRepositories).toHaveLength(6); // 2 per query expect(accumulatedState.searchQueries).toHaveLength(3); - expect(accumulatedState.searchQueries.every((sq) => sq.count === 2)).toBe(true); + expect( + accumulatedState.searchQueries.every((sq: any) => sq.count === 2), + ).toBe(true); }); }); }); @@ -445,10 +475,10 @@ describe('Action Chaining: GitHub Plugin', () => { function createMemory(text: string): Memory { return { id: `mem-${Date.now()}` as UUID, - roomId: 'test-room' as UUID, - entityId: 'test-entity' as UUID, - agentId: 'test-agent' as UUID, - content: { text, source: 'test' }, + roomId: "test-room" as UUID, + entityId: "test-entity" as UUID, + agentId: "test-agent" as UUID, + content: { text, source: "test" }, createdAt: Date.now(), }; } @@ -457,6 +487,6 @@ function createState(data: Record = {}): State { return { values: {}, data, - text: '', + text: "", }; } diff --git a/src/__tests__/autoCoder.test.ts b/src/__tests__/autoCoder.test.ts index b2c8e70..764dfbd 100644 --- a/src/__tests__/autoCoder.test.ts +++ b/src/__tests__/autoCoder.test.ts @@ -1,9 +1,12 @@ -import { describe, it, expect, mock, beforeEach } from 'bun:test'; -import { autoCodeIssueAction, respondToMentionAction } from '../actions/autoCoder'; -import type { IAgentRuntime, Memory, State, HandlerCallback } from '@elizaos/core'; -import { ModelType } from '@elizaos/core'; - -describe('Auto-Coder Action Tests', () => { +import { describe, it, expect, mock, beforeEach } from "bun:test"; +import { + autoCodeIssueAction, + respondToMentionAction, +} from "../actions/autoCoder"; +import type { Memory, State, HandlerCallback } from "@elizaos/core"; +import { ModelType } from "@elizaos/core"; + +describe("Auto-Coder Action Tests", () => { let mockRuntime: any; let mockGitHubService: any; let mockCallback: HandlerCallback; @@ -15,44 +18,44 @@ describe('Auto-Coder Action Tests', () => { // Create mock GitHub service mockGitHubService = { - getDefaultBranch: mock().mockResolvedValue('main'), + getDefaultBranch: mock().mockResolvedValue("main"), getRef: mock().mockResolvedValue({ - object: { sha: 'abc123' }, + object: { sha: "abc123" }, }), createBranch: mock().mockResolvedValue(undefined), getFileContent: mock().mockResolvedValue({ - content: '# Test Repository\n\nThis is a test.', - sha: 'file123', + content: "# Test Repository\n\nThis is a test.", + sha: "file123", }), createOrUpdateFile: mock().mockResolvedValue({ - commit: { sha: 'commit123' }, + commit: { sha: "commit123" }, }), createPullRequest: mock().mockResolvedValue({ number: 456, - html_url: 'https://github.com/owner/repo/pull/456', + html_url: "https://github.com/owner/repo/pull/456", }), createIssueComment: mock().mockResolvedValue(undefined), getRepositoryTree: mock().mockResolvedValue([ - { type: 'blob', path: 'README.md' }, - { type: 'blob', path: 'package.json' }, - { type: 'tree', path: 'src' }, + { type: "blob", path: "README.md" }, + { type: "blob", path: "package.json" }, + { type: "tree", path: "src" }, ]), }; // Create mock runtime mockRuntime = { - agentId: 'test-agent', + agentId: "test-agent", character: { - name: 'TestAgent', + name: "TestAgent", }, getSetting: mock((key: string) => { const settings: Record = { - GITHUB_TOKEN: 'ghp_test123', + GITHUB_TOKEN: "ghp_test123", }; return settings[key]; }), getService: mock((name: string) => { - if (name === 'github') { + if (name === "github") { return mockGitHubService; } return null; @@ -68,43 +71,48 @@ describe('Auto-Coder Action Tests', () => { if ( params && params.prompt && - (params.prompt.includes('generate the code') || - params.prompt.includes('Generate code') || - params.prompt.includes('code changes')) + (params.prompt.includes("generate the code") || + params.prompt.includes("Generate code") || + params.prompt.includes("code changes")) ) { return Promise.resolve( JSON.stringify({ canGenerate: true, confidence: 0.9, - reasoning: 'This is a simple documentation change that can be automated', + reasoning: + "This is a simple documentation change that can be automated", changes: [ { - file: 'README.md', - action: 'update', - change: 'Add documentation section', - reasoning: 'Adding a new section to the README file', + file: "README.md", + action: "update", + change: "Add documentation section", + reasoning: "Adding a new section to the README file", content: - '# Test Repository\n\nThis is a test.\n\n## New Section\n\nAdded by auto-coder.', + "# Test Repository\n\nThis is a test.\n\n## New Section\n\nAdded by auto-coder.", }, ], testingNeeded: false, dependencies: [], - }) + }), ); } // Check if this is a mention analysis prompt - if (params && params.prompt && params.prompt.includes('Analyze this GitHub mention')) { + if ( + params && + params.prompt && + params.prompt.includes("Analyze this GitHub mention") + ) { return Promise.resolve( JSON.stringify({ - requestType: 'code_fix', + requestType: "code_fix", confidence: 0.9, - reasoning: 'User is asking for a code fix', + reasoning: "User is asking for a code fix", shouldAutoCode: true, suggestedResponse: "I'll help you fix this issue!", - urgency: 'medium', + urgency: "medium", requiresHuman: false, - }) + }), ); } @@ -112,29 +120,32 @@ describe('Auto-Coder Action Tests', () => { return Promise.resolve( JSON.stringify({ canAutomate: true, - complexity: 'simple', + complexity: "simple", confidence: 0.8, - reasoning: 'This is a simple documentation issue that can be automated', - issueType: 'documentation', - summary: 'Add a new section to README', - requiredFiles: ['README.md'], + reasoning: + "This is a simple documentation issue that can be automated", + issueType: "documentation", + summary: "Add a new section to README", + requiredFiles: ["README.md"], estimatedChanges: 3, - riskLevel: 'low', + riskLevel: "low", dependencies: [], - }) + }), ); }), processAction: mock(), actions: [ { - name: 'AUTO_CODE_ISSUE', - handler: mock().mockImplementation(async (runtime, message, state, options, callback) => { - // Mock the auto-code action handler - await callback?.({ - text: 'Auto-coding triggered', - actions: ['AUTO_CODE_ISSUE'], - }); - }), + name: "AUTO_CODE_ISSUE", + handler: mock().mockImplementation( + async (runtime, message, state, options, callback) => { + // Mock the auto-code action handler + await callback?.({ + text: "Auto-coding triggered", + actions: ["AUTO_CODE_ISSUE"], + }); + }, + ), }, ], }; @@ -144,13 +155,13 @@ describe('Auto-Coder Action Tests', () => { // Create mock memory and state mockMemory = { - id: 'msg-12345678-1234-1234-1234-123456789012' as any, - entityId: 'user-12345678-1234-1234-1234-123456789012' as any, - roomId: 'room-12345678-1234-1234-1234-123456789012' as any, - agentId: 'test-12345678-1234-1234-1234-123456789012' as any, + id: "msg-12345678-1234-1234-1234-123456789012" as any, + entityId: "user-12345678-1234-1234-1234-123456789012" as any, + roomId: "room-12345678-1234-1234-1234-123456789012" as any, + agentId: "test-12345678-1234-1234-1234-123456789012" as any, content: { - text: 'Please fix this issue', - source: 'test', + text: "Please fix this issue", + source: "test", }, createdAt: Date.now(), } as Memory; @@ -158,132 +169,156 @@ describe('Auto-Coder Action Tests', () => { mockState = { values: {}, data: {}, - text: '', + text: "", } as State; }); - describe('autoCodeIssueAction', () => { - it('should validate when GitHub service is available', async () => { - const isValid = await autoCodeIssueAction.validate(mockRuntime, mockMemory, mockState); + describe("autoCodeIssueAction", () => { + it("should validate when GitHub service is available", async () => { + const isValid = await autoCodeIssueAction.validate( + mockRuntime, + mockMemory, + mockState, + ); expect(isValid).toBe(true); }); - it('should not validate when GitHub service is unavailable', async () => { + it("should not validate when GitHub service is unavailable", async () => { mockRuntime.getService = mock().mockReturnValue(null); - const isValid = await autoCodeIssueAction.validate(mockRuntime, mockMemory, mockState); + const isValid = await autoCodeIssueAction.validate( + mockRuntime, + mockMemory, + mockState, + ); expect(isValid).toBe(false); }); - it('should create a PR for a simple issue', async () => { + it("should create a PR for a simple issue", async () => { const options = { issue: { number: 123, - title: 'Add documentation', - body: 'Please add documentation for the new feature', + title: "Add documentation", + body: "Please add documentation for the new feature", assignee: null, labels: [], - user: { login: 'testuser' }, + user: { login: "testuser" }, }, repository: { - full_name: 'owner/repo', - owner: { login: 'owner' }, - name: 'repo', - language: 'JavaScript', - description: 'Test repository', + full_name: "owner/repo", + owner: { login: "owner" }, + name: "repo", + language: "JavaScript", + description: "Test repository", }, - action: 'opened', + action: "opened", }; - await autoCodeIssueAction.handler(mockRuntime, mockMemory, mockState, options, mockCallback); + await autoCodeIssueAction.handler( + mockRuntime, + mockMemory, + mockState, + options, + mockCallback, + ); // Verify AI analysis was performed expect(mockRuntime.useModel).toHaveBeenCalledWith( ModelType.TEXT_LARGE, expect.objectContaining({ - prompt: expect.stringContaining('Analyze this GitHub issue'), + prompt: expect.stringContaining("Analyze this GitHub issue"), temperature: 0.2, - }) + }), ); // Verify branch was created expect(mockGitHubService.createBranch).toHaveBeenCalledWith( - 'owner', - 'repo', - 'auto-fix/issue-123', - 'abc123' + "owner", + "repo", + "auto-fix/issue-123", + "abc123", ); // Verify file was updated expect(mockGitHubService.createOrUpdateFile).toHaveBeenCalledWith( - 'owner', - 'repo', - 'README.md', - '# Test Repository\n\nThis is a test.\n\n## New Section\n\nAdded by auto-coder.', - 'Auto-fix: Update README.md for issue #123', - 'auto-fix/issue-123', - 'file123' + "owner", + "repo", + "README.md", + "# Test Repository\n\nThis is a test.\n\n## New Section\n\nAdded by auto-coder.", + "Auto-fix: Update README.md for issue #123", + "auto-fix/issue-123", + "file123", ); // Verify PR was created - expect(mockGitHubService.createPullRequest).toHaveBeenCalledWith('owner', 'repo', { - title: expect.stringContaining('Auto-fix:'), - body: expect.stringContaining('Fixes #123'), - head: 'auto-fix/issue-123', - base: 'main', - }); + expect(mockGitHubService.createPullRequest).toHaveBeenCalledWith( + "owner", + "repo", + { + title: expect.stringContaining("Auto-fix:"), + body: expect.stringContaining("Fixes #123"), + head: "auto-fix/issue-123", + base: "main", + }, + ); // Verify issue comment was posted expect(mockGitHubService.createIssueComment).toHaveBeenCalledWith( - 'owner', - 'repo', + "owner", + "repo", 123, - expect.stringContaining('created') + expect.stringContaining("created"), ); // Verify success callback expect(mockCallback).toHaveBeenCalledWith( expect.objectContaining({ - text: expect.stringContaining('Automated fix created for issue #123'), - actions: ['AUTO_CODE_ISSUE'], - }) + text: expect.stringContaining("Automated fix created for issue #123"), + actions: ["AUTO_CODE_ISSUE"], + }), ); }); - it('should skip issues that are too complex', async () => { + it("should skip issues that are too complex", async () => { mockRuntime.useModel = mock().mockResolvedValue( JSON.stringify({ canAutomate: false, - complexity: 'complex', + complexity: "complex", confidence: 0.3, - reasoning: 'This requires major architectural changes', - issueType: 'refactor', - summary: 'This requires major architectural changes', + reasoning: "This requires major architectural changes", + issueType: "refactor", + summary: "This requires major architectural changes", requiredFiles: [], estimatedChanges: 100, - riskLevel: 'high', + riskLevel: "high", dependencies: [], - }) + }), ); const options = { issue: { number: 123, - title: 'Refactor entire codebase', - body: 'We need to refactor everything', + title: "Refactor entire codebase", + body: "We need to refactor everything", assignee: null, labels: [], - user: { login: 'testuser' }, + user: { login: "testuser" }, }, repository: { - full_name: 'owner/repo', - owner: { login: 'owner' }, - name: 'repo', - language: 'JavaScript', - description: 'Test repository', + full_name: "owner/repo", + owner: { login: "owner" }, + name: "repo", + language: "JavaScript", + description: "Test repository", }, }; - await autoCodeIssueAction.handler(mockRuntime, mockMemory, mockState, options, mockCallback); + await autoCodeIssueAction.handler( + mockRuntime, + mockMemory, + mockState, + options, + mockCallback, + ); // Should not create branch or PR expect(mockGitHubService.createBranch).not.toHaveBeenCalled(); @@ -292,31 +327,37 @@ describe('Auto-Coder Action Tests', () => { // Should inform about complexity expect(mockCallback).toHaveBeenCalledWith( expect.objectContaining({ - text: expect.stringContaining('requires human intervention'), - }) + text: expect.stringContaining("requires human intervention"), + }), ); }); - it('should skip assigned issues', async () => { + it("should skip assigned issues", async () => { const options = { issue: { number: 123, - title: 'Fix bug', - body: 'There is a bug', - assignee: { login: 'other-user' }, + title: "Fix bug", + body: "There is a bug", + assignee: { login: "other-user" }, labels: [], - user: { login: 'testuser' }, + user: { login: "testuser" }, }, repository: { - full_name: 'owner/repo', - owner: { login: 'owner' }, - name: 'repo', - language: 'JavaScript', - description: 'Test repository', + full_name: "owner/repo", + owner: { login: "owner" }, + name: "repo", + language: "JavaScript", + description: "Test repository", }, }; - await autoCodeIssueAction.handler(mockRuntime, mockMemory, mockState, options, mockCallback); + await autoCodeIssueAction.handler( + mockRuntime, + mockMemory, + mockState, + options, + mockCallback, + ); // Should not analyze or create PR expect(mockRuntime.useModel).not.toHaveBeenCalled(); @@ -325,99 +366,115 @@ describe('Auto-Coder Action Tests', () => { // Should inform about assignment expect(mockCallback).toHaveBeenCalledWith( expect.objectContaining({ - text: expect.stringContaining('already assigned to other-user'), - }) + text: expect.stringContaining("already assigned to other-user"), + }), ); }); - it('should handle missing repository info', async () => { + it("should handle missing repository info", async () => { const options = { issue: { number: 123, - title: 'Test issue', + title: "Test issue", }, // Missing repository }; - await autoCodeIssueAction.handler(mockRuntime, mockMemory, mockState, options, mockCallback); + await autoCodeIssueAction.handler( + mockRuntime, + mockMemory, + mockState, + options, + mockCallback, + ); expect(mockCallback).toHaveBeenCalledWith( expect.objectContaining({ - text: expect.stringContaining('requires issue and repository information'), - }) + text: expect.stringContaining( + "requires issue and repository information", + ), + }), ); }); - it('should handle file creation when README does not exist', async () => { - mockGitHubService.getFileContent = mock().mockRejectedValue(new Error('Not found')); + it("should handle file creation when README does not exist", async () => { + mockGitHubService.getFileContent = mock().mockRejectedValue( + new Error("Not found"), + ); // Also mock getRepositoryTree for the repository analysis mockGitHubService.getRepositoryTree = mock().mockResolvedValue([ - { type: 'blob', path: 'index.js' }, - { type: 'blob', path: 'package.json' }, + { type: "blob", path: "index.js" }, + { type: "blob", path: "package.json" }, ]); const options = { issue: { number: 123, - title: 'Create README', - body: 'Please create a README file', + title: "Create README", + body: "Please create a README file", assignee: null, labels: [], - user: { login: 'testuser' }, + user: { login: "testuser" }, }, repository: { - full_name: 'owner/repo', - owner: { login: 'owner' }, - name: 'repo', - description: 'A test repository', - language: 'JavaScript', + full_name: "owner/repo", + owner: { login: "owner" }, + name: "repo", + description: "A test repository", + language: "JavaScript", }, }; - await autoCodeIssueAction.handler(mockRuntime, mockMemory, mockState, options, mockCallback); + await autoCodeIssueAction.handler( + mockRuntime, + mockMemory, + mockState, + options, + mockCallback, + ); // Should create file without SHA expect(mockGitHubService.createOrUpdateFile).toHaveBeenCalledWith( - 'owner', - 'repo', - 'README.md', - '# Test Repository\n\nThis is a test.\n\n## New Section\n\nAdded by auto-coder.', - 'Auto-fix: Update README.md for issue #123', - 'auto-fix/issue-123', - undefined // No SHA for new file + "owner", + "repo", + "README.md", + "# Test Repository\n\nThis is a test.\n\n## New Section\n\nAdded by auto-coder.", + "Auto-fix: Update README.md for issue #123", + "auto-fix/issue-123", + undefined, // No SHA for new file ); }); }); - describe('respondToMentionAction', () => { - it('should respond to issue mentions', async () => { + describe("respondToMentionAction", () => { + it("should respond to issue mentions", async () => { mockRuntime.useModel = mock().mockResolvedValue( JSON.stringify({ - requestType: 'code_fix', + requestType: "code_fix", confidence: 0.9, - reasoning: 'User is asking for a code fix', + reasoning: "User is asking for a code fix", shouldAutoCode: true, suggestedResponse: "I'll help you fix this issue!", - urgency: 'medium', + urgency: "medium", requiresHuman: false, - }) + }), ); const options = { issue: { number: 123, - title: 'Bug in login', - body: '@TestAgent can you fix the login bug?', + title: "Bug in login", + body: "@TestAgent can you fix the login bug?", labels: [], - user: { login: 'testuser' }, + user: { login: "testuser" }, }, repository: { - full_name: 'owner/repo', - owner: { login: 'owner' }, - name: 'repo', + full_name: "owner/repo", + owner: { login: "owner" }, + name: "repo", }, - action: 'opened', + action: "opened", }; await respondToMentionAction.handler( @@ -425,27 +482,29 @@ describe('Auto-Coder Action Tests', () => { mockMemory, mockState, options, - mockCallback + mockCallback, ); // Should analyze the mention expect(mockRuntime.useModel).toHaveBeenCalledWith( ModelType.TEXT_LARGE, expect.objectContaining({ - prompt: expect.stringContaining('Analyze this GitHub mention'), - }) + prompt: expect.stringContaining("Analyze this GitHub mention"), + }), ); // Should post a comment expect(mockGitHubService.createIssueComment).toHaveBeenCalledWith( - 'owner', - 'repo', + "owner", + "repo", 123, - "I'll help you fix this issue!" + "I'll help you fix this issue!", ); // Should trigger auto-coding - const autoCodeAction = mockRuntime.actions.find((a: any) => a.name === 'AUTO_CODE_ISSUE'); + const autoCodeAction = mockRuntime.actions.find( + (a: any) => a.name === "AUTO_CODE_ISSUE", + ); expect(autoCodeAction).toBeDefined(); expect(autoCodeAction.handler).toHaveBeenCalled(); @@ -454,50 +513,52 @@ describe('Auto-Coder Action Tests', () => { // First call is from auto-code action expect(mockCallback).toHaveBeenNthCalledWith(1, { - text: 'Auto-coding triggered', - actions: ['AUTO_CODE_ISSUE'], + text: "Auto-coding triggered", + actions: ["AUTO_CODE_ISSUE"], }); // Second call is from respond to mention action expect(mockCallback).toHaveBeenNthCalledWith( 2, expect.objectContaining({ - text: expect.stringContaining('Intelligent mention response generated'), - actions: ['RESPOND_TO_GITHUB_MENTION'], - }) + text: expect.stringContaining( + "Intelligent mention response generated", + ), + actions: ["RESPOND_TO_GITHUB_MENTION"], + }), ); }); - it('should respond to comment mentions', async () => { + it("should respond to comment mentions", async () => { mockRuntime.useModel = mock().mockResolvedValue( JSON.stringify({ - requestType: 'question', + requestType: "question", confidence: 0.8, - reasoning: 'User is asking a question', + reasoning: "User is asking a question", shouldAutoCode: false, - suggestedResponse: 'Let me check that for you.', - urgency: 'low', + suggestedResponse: "Let me check that for you.", + urgency: "low", requiresHuman: false, - }) + }), ); const options = { issue: { number: 123, - title: 'Question about API', + title: "Question about API", labels: [], - user: { login: 'testuser' }, + user: { login: "testuser" }, }, comment: { id: 456, - body: '@TestAgent what is the API endpoint for users?', + body: "@TestAgent what is the API endpoint for users?", }, repository: { - full_name: 'owner/repo', - owner: { login: 'owner' }, - name: 'repo', + full_name: "owner/repo", + owner: { login: "owner" }, + name: "repo", }, - action: 'created', + action: "created", }; await respondToMentionAction.handler( @@ -505,15 +566,15 @@ describe('Auto-Coder Action Tests', () => { mockMemory, mockState, options, - mockCallback + mockCallback, ); // Should post a response expect(mockGitHubService.createIssueComment).toHaveBeenCalledWith( - 'owner', - 'repo', + "owner", + "repo", 123, - 'Let me check that for you.' + "Let me check that for you.", ); // Should not trigger auto-coding @@ -522,27 +583,29 @@ describe('Auto-Coder Action Tests', () => { // Should report success without auto-coding expect(mockCallback).toHaveBeenCalledWith( expect.objectContaining({ - text: expect.stringContaining('Intelligent mention response generated'), - actions: ['RESPOND_TO_GITHUB_MENTION'], - }) + text: expect.stringContaining( + "Intelligent mention response generated", + ), + actions: ["RESPOND_TO_GITHUB_MENTION"], + }), ); }); - it('should handle parsing errors gracefully', async () => { - mockRuntime.useModel = mock().mockResolvedValue('Invalid JSON response'); + it("should handle parsing errors gracefully", async () => { + mockRuntime.useModel = mock().mockResolvedValue("Invalid JSON response"); const options = { issue: { number: 123, - title: 'Test', - body: '@TestAgent help', + title: "Test", + body: "@TestAgent help", labels: [], - user: { login: 'testuser' }, + user: { login: "testuser" }, }, repository: { - full_name: 'owner/repo', - owner: { login: 'owner' }, - name: 'repo', + full_name: "owner/repo", + owner: { login: "owner" }, + name: "repo", }, }; @@ -551,19 +614,19 @@ describe('Auto-Coder Action Tests', () => { mockMemory, mockState, options, - mockCallback + mockCallback, ); // Should still post a generic response expect(mockGitHubService.createIssueComment).toHaveBeenCalledWith( - 'owner', - 'repo', + "owner", + "repo", 123, - expect.stringContaining("I see you've mentioned me") + expect.stringContaining("I see you've mentioned me"), ); }); - it('should handle missing repository info', async () => { + it("should handle missing repository info", async () => { const options = { issue: { number: 123 }, // Missing repository @@ -574,13 +637,15 @@ describe('Auto-Coder Action Tests', () => { mockMemory, mockState, options, - mockCallback + mockCallback, ); expect(mockCallback).toHaveBeenCalledWith( expect.objectContaining({ - text: expect.stringContaining('Failed to handle mention intelligently'), - }) + text: expect.stringContaining( + "Failed to handle mention intelligently", + ), + }), ); }); }); diff --git a/src/__tests__/e2e.test.ts b/src/__tests__/e2e.test.ts index 233547a..c1acf20 100644 --- a/src/__tests__/e2e.test.ts +++ b/src/__tests__/e2e.test.ts @@ -1,8 +1,15 @@ -import { describe, expect, it, mock, beforeEach, afterAll, beforeAll } from 'bun:test'; -import { githubPlugin, GitHubService } from '../index'; -import { createMockRuntime, setupLoggerSpies, MockRuntime } from './test-utils'; -import { HandlerCallback, IAgentRuntime, Memory, State, UUID, logger } from '@elizaos/core'; -import { config } from 'dotenv'; +import { describe, expect, it, mock, afterAll, beforeAll } from "bun:test"; +import { githubPlugin, GitHubService } from "../index"; +import { createTestRuntime } from "./test-helpers"; +import { + HandlerCallback, + IAgentRuntime, + Memory, + State, + UUID, + logger, +} from "@elizaos/core"; +import { config } from "dotenv"; // Load environment variables config(); @@ -25,19 +32,19 @@ const skipE2E = !GITHUB_TOKEN; const hasGitHubToken = !!process.env.GITHUB_TOKEN; const describeWithToken = hasGitHubToken ? describe : describe.skip; -describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { +describeWithToken("E2E: GitHub Plugin with Real GitHub API", () => { let runtime: IAgentRuntime; let githubService: GitHubService; const testRepoPrefix = `eliza-test-${Date.now()}`; const createdRepos: string[] = []; beforeAll(async () => { - setupLoggerSpies(); + // Logger spies not needed with new test utils // Create a real runtime with GitHub token - runtime = createMockRuntime({ + runtime = createTestRuntime({ getSetting: mock().mockImplementation((key: string) => { - if (key === 'GITHUB_TOKEN' || key === 'GITHUB_TOKEN') { + if (key === "GITHUB_TOKEN" || key === "GITHUB_TOKEN") { return GITHUB_TOKEN; } return null; @@ -49,20 +56,18 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { { GITHUB_TOKEN: GITHUB_TOKEN!, }, - runtime + runtime, ); // Start the GitHub service githubService = await GitHubService.start(runtime); - // Register the service with the runtime - runtime.registerService(githubService); - runtime.getService = mock().mockImplementation((serviceType: string) => { - if (serviceType === 'github') { - return githubService; - } - return null; - }); + // The runtime's getService is already mocked in createTestRuntime + // to return the github service when requested + + // Verify we can get the service + const registeredService = runtime.getService("github"); + expect(registeredService).toBeDefined(); }); afterAll(async () => { @@ -71,7 +76,9 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { for (const repoName of createdRepos) { try { const owner = await githubService.getAuthenticatedUser(); - logger.info(`Cleaning up test repository: ${owner.login}/${repoName}`); + logger.info( + `Cleaning up test repository: ${owner.login}/${repoName}`, + ); // Note: Repository deletion requires additional permissions // For now, log the repositories that need manual cleanup } catch (error) { @@ -88,20 +95,20 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { mock.restore(); }); - describe('Authentication and Service Initialization', () => { - it('should authenticate successfully with real GitHub token', async () => { + describe("Authentication and Service Initialization", () => { + it("should authenticate successfully with real GitHub token", async () => { const isAuthenticated = await githubService.validateAuthentication(); expect(isAuthenticated).toBe(true); }); - it('should get authenticated user information', async () => { + it("should get authenticated user information", async () => { const user = await githubService.getAuthenticatedUser(); expect(user).toBeDefined(); expect(user.login).toBeTruthy(); expect(user.id).toBeGreaterThan(0); }); - it('should check rate limit status', async () => { + it("should check rate limit status", async () => { const rateLimit = await githubService.getRateLimit(); expect(rateLimit).toBeDefined(); expect(rateLimit.remaining).toBeGreaterThanOrEqual(0); @@ -109,31 +116,37 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { }); }); - describe('Repository Operations', () => { - it('should search for popular repositories', async () => { - const results = await githubService.searchRepositories('javascript stars:>10000', { - per_page: 5, - }); + describe("Repository Operations", () => { + it("should search for popular repositories", async () => { + const results = await githubService.searchRepositories( + "javascript stars:>10000", + { + per_page: 5, + }, + ); expect(results.items).toBeDefined(); expect(results.items.length).toBeGreaterThan(0); expect(results.items[0].stargazers_count).toBeGreaterThan(10000); }); - it('should get a specific repository', async () => { - const repo = await githubService.getRepository('microsoft', 'TypeScript'); + it("should get a specific repository", async () => { + const repo = await githubService.getRepository("microsoft", "TypeScript"); expect(repo).toBeDefined(); - expect(repo.name).toBe('TypeScript'); - expect(repo.owner.login).toBe('microsoft'); + expect(repo.name).toBe("TypeScript"); + expect(repo.owner.login).toBe("microsoft"); expect(repo.stargazers_count).toBeGreaterThan(0); }, 120000); // 2 minute timeout for API calls - it('should list user repositories', async () => { - const user = await githubService.getAuthenticatedUser(); - const repos = await githubService.listRepositories(user.login, { + it("should list user repositories", async () => { + const user = await githubService.getCurrentUser(); + expect(user).toHaveProperty("login"); + + // List repositories + const repos = await githubService.listRepositories({ per_page: 10, - sort: 'updated', + sort: "updated", }); expect(repos).toBeDefined(); @@ -141,25 +154,25 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { }); }); - describe('Issue Operations', () => { - it('should search for issues in TypeScript repository', async () => { + describe("Issue Operations", () => { + it("should search for issues in TypeScript repository", async () => { const results = await githubService.searchIssues( - 'repo:microsoft/TypeScript is:issue is:open', + "repo:microsoft/TypeScript is:issue is:open", { per_page: 5, - } + }, ); expect(results.items).toBeDefined(); expect(results.items.length).toBeGreaterThan(0); - expect(results.items[0].repository_url).toContain('microsoft/TypeScript'); + expect(results.items[0].repository_url).toContain("microsoft/TypeScript"); }); - it('should list issues from a repository', async () => { - const issues = await githubService.listIssues('microsoft', 'vscode', { - state: 'open', + it("should list issues from a repository", async () => { + const issues = await githubService.listIssues("microsoft", "vscode", { + state: "open", per_page: 5, - labels: 'bug', + labels: "bug", }); expect(issues).toBeDefined(); @@ -167,27 +180,30 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { if (issues.length > 0) { expect( issues[0].labels.some((label: any) => - typeof label === 'string' ? label === 'bug' : label.name === 'bug' - ) + typeof label === "string" ? label === "bug" : label.name === "bug", + ), ).toBe(true); } }, 120000); // 2 minute timeout for API calls }); - describe('Pull Request Operations', () => { - it('should search for pull requests', async () => { - const results = await githubService.searchPullRequests('repo:facebook/react is:pr is:open', { - per_page: 5, - }); + describe("Pull Request Operations", () => { + it("should search for pull requests", async () => { + const results = await githubService.searchPullRequests( + "repo:facebook/react is:pr is:open", + { + per_page: 5, + }, + ); expect(results.items).toBeDefined(); expect(results.items.length).toBeGreaterThan(0); expect(results.items[0].pull_request).toBeDefined(); }); - it('should list pull requests from a repository', async () => { - const prs = await githubService.listPullRequests('facebook', 'react', { - state: 'open', + it("should list pull requests from a repository", async () => { + const prs = await githubService.listPullRequests("facebook", "react", { + state: "open", per_page: 5, }); @@ -196,26 +212,31 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { }); }); - describe('Action Chaining Scenarios', () => { - it('should chain actions: search repo -> get details -> list issues', async () => { + describe("Action Chaining Scenarios", () => { + it("should chain actions: search repo -> get details -> list issues", async () => { const results: any[] = []; // Step 1: Search for a repository const searchAction = githubPlugin.actions?.find( - (a) => a.name === 'SEARCH_GITHUB_REPOSITORIES' + (a) => a.name === "SEARCH_GITHUB_REPOSITORIES", ); expect(searchAction).toBeDefined(); - const searchCallback: HandlerCallback = mock((response) => { - results.push({ step: 'search', response }); + const searchCallback: HandlerCallback = mock(async (response) => { + results.push({ step: "search", response }); + return []; }); await searchAction!.handler( runtime, - createTestMemory('Search for popular TypeScript repositories'), + createTestMemory("Search for popular TypeScript repositories"), createTestState(), - { query: 'language:typescript stars:>5000', sort: 'stars', per_page: 1 }, - searchCallback + { + query: "language:typescript stars:>5000", + sort: "stars", + per_page: 1, + }, + searchCallback, ); expect(searchCallback).toHaveBeenCalled(); @@ -225,11 +246,14 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { // Step 2: Get repository details using the result from step 1 const firstRepo = searchResult.repositories[0]; - const getRepoAction = githubPlugin.actions?.find((a) => a.name === 'GET_GITHUB_REPOSITORY'); + const getRepoAction = githubPlugin.actions?.find( + (a) => a.name === "GET_GITHUB_REPOSITORY", + ); expect(getRepoAction).toBeDefined(); - const getRepoCallback: HandlerCallback = mock((response) => { - results.push({ step: 'getRepo', response }); + const getRepoCallback: HandlerCallback = mock(async (response) => { + results.push({ step: "get-repo", response }); + return []; }); await getRepoAction!.handler( @@ -240,7 +264,7 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { lastSearchQuery: searchResult.query, }), { owner: firstRepo.owner.login, repo: firstRepo.name }, - getRepoCallback + getRepoCallback, ); expect(getRepoCallback).toHaveBeenCalled(); @@ -248,11 +272,14 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { expect(repoDetails.repository).toBeDefined(); // Step 3: List issues from the repository - const listIssuesAction = githubPlugin.actions?.find((a) => a.name === 'LIST_GITHUB_ISSUES'); + const listIssuesAction = githubPlugin.actions?.find( + (a) => a.name === "LIST_GITHUB_ISSUES", + ); expect(listIssuesAction).toBeDefined(); - const listIssuesCallback: HandlerCallback = mock((response) => { - results.push({ step: 'listIssues', response }); + const listIssuesCallback: HandlerCallback = mock(async (response) => { + results.push({ step: "list-issues", response }); + return []; }); await listIssuesAction!.handler( @@ -265,10 +292,10 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { { owner: firstRepo.owner.login, repo: firstRepo.name, - state: 'open', + state: "open", per_page: 5, }, - listIssuesCallback + listIssuesCallback, ); expect(listIssuesCallback).toHaveBeenCalled(); @@ -278,28 +305,31 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { // Verify the chain completed successfully expect(results).toHaveLength(3); - expect(results[0].step).toBe('search'); - expect(results[1].step).toBe('getRepo'); - expect(results[2].step).toBe('listIssues'); + expect(results[0].step).toBe("search"); + expect(results[1].step).toBe("get-repo"); + expect(results[2].step).toBe("list-issues"); }, 120000); // 2 minute timeout for API calls - it('should chain actions: get rate limit -> search issues -> create issue (dry run)', async () => { + it("should chain actions: get rate limit -> search issues -> create issue (dry run)", async () => { const results: any[] = []; // Step 1: Check rate limit - const rateLimitAction = githubPlugin.actions?.find((a) => a.name === 'GET_GITHUB_RATE_LIMIT'); + const rateLimitAction = githubPlugin.actions?.find( + (a) => a.name === "GET_GITHUB_RATE_LIMIT", + ); expect(rateLimitAction).toBeDefined(); - const rateLimitCallback: HandlerCallback = mock((response) => { - results.push({ step: 'rateLimit', response }); + const rateLimitCallback: HandlerCallback = mock(async (response) => { + results.push({ step: "rate-limit", response }); + return []; }); await rateLimitAction!.handler( runtime, - createTestMemory('Check GitHub API rate limit'), + createTestMemory("Check GitHub API rate limit"), createTestState(), {}, - rateLimitCallback + rateLimitCallback, ); expect(rateLimitCallback).toHaveBeenCalled(); @@ -309,25 +339,28 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { // Step 2: Search for similar issues (to avoid duplicates) const searchIssuesAction = githubPlugin.actions?.find( - (a) => a.name === 'SEARCH_GITHUB_ISSUES' + (a) => a.name === "SEARCH_GITHUB_ISSUES", ); expect(searchIssuesAction).toBeDefined(); - const searchIssuesCallback: HandlerCallback = mock((response) => { - results.push({ step: 'searchIssues', response }); + const searchIssuesCallback: HandlerCallback = mock(async (response) => { + results.push({ step: "search-issues", response }); + return []; }); await searchIssuesAction!.handler( runtime, - createTestMemory('Search for test-related issues in octocat/Hello-World'), + createTestMemory( + "Search for test-related issues in octocat/Hello-World", + ), createTestState({ rateLimit: rateLimitResult.rateLimit, }), { - query: 'repo:octocat/Hello-World is:issue test in:title', + query: "repo:octocat/Hello-World is:issue test in:title", per_page: 5, }, - searchIssuesCallback + searchIssuesCallback, ); expect(searchIssuesCallback).toHaveBeenCalled(); @@ -337,8 +370,8 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { // Step 3: Prepare to create an issue (but don't actually create it in tests) // This demonstrates how the state from previous actions can be used const createIssueData = { - owner: 'octocat', - repo: 'Hello-World', + owner: "octocat", + repo: "Hello-World", title: `[TEST] Automated test issue - ${Date.now()}`, body: `This is a test issue created by ElizaOS GitHub plugin E2E tests. @@ -349,24 +382,24 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { **Similar Issues Found:** ${searchIssuesResult.total_count} *Note: This is a dry run - no issue will actually be created.*`, - labels: ['test', 'automated'], + labels: ["test", "automated"], }; // Log what would be created - logger.info('Would create issue with data:', createIssueData); - results.push({ step: 'prepareIssue', data: createIssueData }); + logger.info("Would create issue with data:", createIssueData); + results.push({ step: "prepareIssue", data: createIssueData }); // Verify the chain completed successfully expect(results).toHaveLength(3); - expect(results[0].step).toBe('rateLimit'); - expect(results[1].step).toBe('searchIssues'); - expect(results[2].step).toBe('prepareIssue'); + expect(results[0].step).toBe("rate-limit"); + expect(results[1].step).toBe("search-issues"); + expect(results[2].step).toBe("prepareIssue"); }); }); - describe('Complex Task Scenarios', () => { - it('should handle a complex workflow: analyze repository health', async () => { - const targetRepo = { owner: 'facebook', repo: 'react' }; + describe("Complex Task Scenarios", () => { + it("should handle a complex workflow: analyze repository health", async () => { + const targetRepo = { owner: "facebook", repo: "react" }; const analysis: any = { repository: null, issues: { open: 0, closed: 0 }, @@ -375,7 +408,10 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { }; // Get repository details - const repo = await githubService.getRepository(targetRepo.owner, targetRepo.repo); + const repo = await githubService.getRepository( + targetRepo.owner, + targetRepo.repo, + ); analysis.repository = { name: repo.name, stars: repo.stargazers_count, @@ -386,17 +422,25 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { }; // Get open issues count - const openIssues = await githubService.listIssues(targetRepo.owner, targetRepo.repo, { - state: 'open', - per_page: 1, - }); + const openIssues = await githubService.listIssues( + targetRepo.owner, + targetRepo.repo, + { + state: "open", + per_page: 1, + }, + ); analysis.issues.open = openIssues.length; // Get open pull requests - const openPRs = await githubService.listPullRequests(targetRepo.owner, targetRepo.repo, { - state: 'open', - per_page: 5, - }); + const openPRs = await githubService.listPullRequests( + targetRepo.owner, + targetRepo.repo, + { + state: "open", + per_page: 5, + }, + ); analysis.pullRequests.open = openPRs.length; // Get activity log @@ -407,75 +451,82 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { expect(analysis.repository.stars).toBeGreaterThan(0); expect(analysis.activity.length).toBeGreaterThan(0); - logger.info('Repository health analysis:', analysis); + logger.info("Repository health analysis:", analysis); }); - it('should demonstrate provider integration with actions', async () => { + it("should demonstrate provider integration with actions", async () => { // Get the repository provider const repoProvider = githubPlugin.providers?.find( - (p) => p.name === 'GITHUB_REPOSITORY_CONTEXT' + (p) => p.name === "GITHUB_REPOSITORY_CONTEXT", ); expect(repoProvider).toBeDefined(); // Generate context about popular repositories const context = await repoProvider!.get( runtime, - createTestMemory('Tell me about popular JavaScript frameworks'), - createTestState() + createTestMemory("Tell me about popular JavaScript frameworks"), + createTestState(), ); expect(context).toBeTruthy(); - expect(context.text).toContain('Repository'); + expect(context.text).toContain("Repository"); // Now use an action based on the provider context const searchAction = githubPlugin.actions?.find( - (a) => a.name === 'SEARCH_GITHUB_REPOSITORIES' + (a) => a.name === "SEARCH_GITHUB_REPOSITORIES", ); const callback: HandlerCallback = mock(); await searchAction!.handler( runtime, - createTestMemory('Find popular JavaScript frameworks on GitHub'), + createTestMemory("Find popular JavaScript frameworks on GitHub"), createTestState({ recentContext: context }), - { query: 'language:javascript framework stars:>50000', per_page: 3 }, - callback + { query: "language:javascript framework stars:>50000", per_page: 3 }, + callback, ); expect(callback).toHaveBeenCalledWith( expect.objectContaining({ - text: expect.stringContaining('repositories'), + text: expect.stringContaining("repositories"), repositories: expect.any(Array), - }) + }), ); }); }); - describe('Error Handling and Edge Cases', () => { - it('should handle non-existent repository gracefully', async () => { + describe("Error Handling and Edge Cases", () => { + it("should handle non-existent repository gracefully", async () => { await expect( - githubService.getRepository('this-user-definitely-does-not-exist-12345', 'fake-repo') + githubService.getRepository( + "this-user-definitely-does-not-exist-12345", + "fake-repo", + ), ).rejects.toThrow(); }, 120000); // 2 minute timeout for API calls - it('should handle rate limiting information', async () => { + it("should handle rate limiting information", async () => { const rateLimit = await githubService.getRateLimit(); expect(rateLimit).toBeDefined(); expect(rateLimit.remaining).toBeGreaterThanOrEqual(0); expect(rateLimit.limit).toBeGreaterThan(0); - expect(rateLimit.reset).toBeGreaterThanOrEqual(Math.floor(Date.now() / 1000)); + expect(rateLimit.reset).toBeGreaterThanOrEqual( + Math.floor(Date.now() / 1000), + ); // Check if we're close to rate limit if (rateLimit.remaining < 10) { - logger.warn(`Low rate limit remaining: ${rateLimit.remaining}/${rateLimit.limit}`); + logger.warn( + `Low rate limit remaining: ${rateLimit.remaining}/${rateLimit.limit}`, + ); } }); - it('should track all API activities', async () => { + it("should track all API activities", async () => { const initialActivityCount = githubService.getActivityLog(100).length; // Perform some operations - await githubService.searchRepositories('language:rust', { per_page: 1 }); + await githubService.searchRepositories("language:rust", { per_page: 1 }); await githubService.getRateLimit(); const newActivityCount = githubService.getActivityLog(100).length; @@ -483,9 +534,11 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { // Check activity log structure const activities = githubService.getActivityLog(10); - expect(activities.every((a) => a.timestamp && a.action && a.success !== undefined)).toBe( - true - ); + expect( + activities.every( + (a) => a.timestamp && a.action && a.success !== undefined, + ), + ).toBe(true); }); }); }); @@ -494,10 +547,10 @@ describeWithToken('E2E: GitHub Plugin with Real GitHub API', () => { function createTestMemory(text: string): Memory { return { id: `test-${Date.now()}` as UUID, - roomId: 'test-room' as UUID, - entityId: 'test-entity' as UUID, - agentId: 'test-agent' as UUID, - content: { text, source: 'test' }, + roomId: "test-room" as UUID, + entityId: "test-entity" as UUID, + agentId: "test-agent" as UUID, + content: { text, source: "test" }, createdAt: Date.now(), }; } @@ -506,6 +559,6 @@ function createTestState(additionalData: Record = {}): State { return { values: {}, data: additionalData, - text: '', + text: "", }; } diff --git a/src/__tests__/e2e/intelligent-analysis.test.ts b/src/__tests__/e2e/intelligent-analysis.test.ts index 2841ff9..46fca39 100644 --- a/src/__tests__/e2e/intelligent-analysis.test.ts +++ b/src/__tests__/e2e/intelligent-analysis.test.ts @@ -1,49 +1,52 @@ -import { describe, it, expect } from 'bun:test'; -import type { IAgentRuntime } from '@elizaos/core'; +import { describe, it, expect } from "bun:test"; +import type { IAgentRuntime } from "@elizaos/core"; /** * E2E tests for intelligent GitHub plugin analysis capabilities * Tests real AI-powered evaluation instead of string matching */ export class IntelligentAnalysisTestSuite { - name = 'github-intelligent-analysis'; - description = 'E2E tests for AI-powered GitHub analysis (no string matching)'; + name = "github-intelligent-analysis"; + description = "E2E tests for AI-powered GitHub analysis (no string matching)"; tests = [ { - name: 'should intelligently detect agent mentions in issue content', + name: "should intelligently detect agent mentions in issue content", fn: async (runtime: IAgentRuntime) => { - console.log('🧠 Testing intelligent mention detection...'); + console.log("🧠 Testing intelligent mention detection..."); // Test various mention scenarios const testCases = [ { - content: '@TestAgent please help fix this bug in the authentication system', - agentName: 'TestAgent', - context: 'Issue #123 in example/repo', + content: + "@TestAgent please help fix this bug in the authentication system", + agentName: "TestAgent", + context: "Issue #123 in example/repo", expectedMention: true, - description: 'Direct mention with specific request', + description: "Direct mention with specific request", }, { - content: 'We need an automated solution for this recurring issue. Can someone help?', - agentName: 'AutoAgent', - context: 'Issue #456 in automation/repo', + content: + "We need an automated solution for this recurring issue. Can someone help?", + agentName: "AutoAgent", + context: "Issue #456 in automation/repo", expectedMention: true, // Should detect implicit automation request - description: 'Implicit automation request', + description: "Implicit automation request", }, { - content: 'TestAgent is mentioned in the documentation but not requested here', - agentName: 'TestAgent', - context: 'Issue #789 in docs/repo', + content: + "TestAgent is mentioned in the documentation but not requested here", + agentName: "TestAgent", + context: "Issue #789 in docs/repo", expectedMention: false, // Should detect this is not a request - description: 'Mention without request intent', + description: "Mention without request intent", }, { - content: 'Please review this PR and let me know what you think', - agentName: 'ReviewBot', - context: 'Issue #101 in code/repo', + content: "Please review this PR and let me know what you think", + agentName: "ReviewBot", + context: "Issue #101 in code/repo", expectedMention: false, // No agent mentioned - description: 'No mention at all', + description: "No mention at all", }, ]; @@ -53,17 +56,17 @@ export class IntelligentAnalysisTestSuite { // This would call the analyzeWebhookMention function from index.ts // Since it's not exported, we test it through webhook simulation const mockPayload = { - action: 'opened', + action: "opened", issue: { number: 123, - title: 'Test Issue', + title: "Test Issue", body: testCase.content, - user: { login: 'testuser' }, + user: { login: "testuser" }, }, repository: { - full_name: 'test/repo', - owner: { login: 'test' }, - name: 'repo', + full_name: "test/repo", + owner: { login: "test" }, + name: "repo", }, }; @@ -78,66 +81,66 @@ export class IntelligentAnalysisTestSuite { } } - console.log('✅ Intelligent mention detection tests completed'); + console.log("✅ Intelligent mention detection tests completed"); }, }, { - name: 'should perform intelligent auto-coding analysis', + name: "should perform intelligent auto-coding analysis", fn: async (runtime: IAgentRuntime) => { - console.log('🧠 Testing intelligent auto-coding capabilities...'); + console.log("🧠 Testing intelligent auto-coding capabilities..."); const testCases = [ { issue: { number: 101, - title: 'Fix login button styling', - body: 'The login button on the homepage has incorrect CSS. It should be blue with white text.', - labels: [{ name: 'bug' }, { name: 'frontend' }], - user: { login: 'developer' }, + title: "Fix login button styling", + body: "The login button on the homepage has incorrect CSS. It should be blue with white text.", + labels: [{ name: "bug" }, { name: "frontend" }], + user: { login: "developer" }, }, repository: { - full_name: 'company/website', - language: 'JavaScript', - description: 'Company website frontend', + full_name: "company/website", + language: "JavaScript", + description: "Company website frontend", }, expectedAutomatable: true, - expectedComplexity: 'simple', - description: 'Simple CSS styling fix', + expectedComplexity: "simple", + description: "Simple CSS styling fix", }, { issue: { number: 102, - title: 'Implement machine learning recommendation engine', - body: 'We need a complete ML pipeline with data processing, model training, and real-time inference.', - labels: [{ name: 'enhancement' }, { name: 'ml' }], - user: { login: 'product-manager' }, + title: "Implement machine learning recommendation engine", + body: "We need a complete ML pipeline with data processing, model training, and real-time inference.", + labels: [{ name: "enhancement" }, { name: "ml" }], + user: { login: "product-manager" }, }, repository: { - full_name: 'company/backend', - language: 'Python', - description: 'Backend services and APIs', + full_name: "company/backend", + language: "Python", + description: "Backend services and APIs", }, expectedAutomatable: false, - expectedComplexity: 'complex', - description: 'Complex ML implementation', + expectedComplexity: "complex", + description: "Complex ML implementation", }, { issue: { number: 103, - title: 'Add unit test for validateEmail function', - body: 'Create unit tests for the validateEmail function in utils/validation.js', - labels: [{ name: 'testing' }], - user: { login: 'qa-engineer' }, + title: "Add unit test for validateEmail function", + body: "Create unit tests for the validateEmail function in utils/validation.js", + labels: [{ name: "testing" }], + user: { login: "qa-engineer" }, }, repository: { - full_name: 'company/utils', - language: 'JavaScript', - description: 'Utility functions library', + full_name: "company/utils", + language: "JavaScript", + description: "Utility functions library", }, expectedAutomatable: true, - expectedComplexity: 'medium', - description: 'Unit test creation', + expectedComplexity: "medium", + description: "Unit test creation", }, ]; @@ -147,79 +150,81 @@ export class IntelligentAnalysisTestSuite { try { // In a real implementation, this would call the actual AI analysis // The test validates that the issue has the expected automation potential - console.log(`✅ ${testCase.description}: Auto-coding analysis validated`); + console.log( + `✅ ${testCase.description}: Auto-coding analysis validated`, + ); } catch (error) { console.error(`❌ ${testCase.description}: ${error}`); throw error; } } - console.log('✅ Auto-coding analysis tests completed'); + console.log("✅ Auto-coding analysis tests completed"); }, }, { - name: 'should handle webhook events with intelligent processing', + name: "should handle webhook events with intelligent processing", fn: async (runtime: IAgentRuntime) => { - console.log('🧠 Testing intelligent webhook event processing...'); + console.log("🧠 Testing intelligent webhook event processing..."); // Test webhook event structures that would trigger intelligent analysis const webhookEvents = [ { - eventType: 'issues', + eventType: "issues", payload: { - action: 'opened', + action: "opened", issue: { number: 201, - title: '@AutoAgent please fix the responsive design', - body: 'The mobile layout breaks on tablets. Can you help automate a fix?', - user: { login: 'designer' }, - labels: [{ name: 'bug' }, { name: 'responsive' }], + title: "@AutoAgent please fix the responsive design", + body: "The mobile layout breaks on tablets. Can you help automate a fix?", + user: { login: "designer" }, + labels: [{ name: "bug" }, { name: "responsive" }], }, repository: { - full_name: 'company/frontend', - owner: { login: 'company' }, - name: 'frontend', + full_name: "company/frontend", + owner: { login: "company" }, + name: "frontend", }, }, - expectedProcessing: 'agent_mention_detected', - description: 'Issue with agent mention and automation request', + expectedProcessing: "agent_mention_detected", + description: "Issue with agent mention and automation request", }, { - eventType: 'issue_comment', + eventType: "issue_comment", payload: { - action: 'created', + action: "created", issue: { number: 202, - title: 'Database optimization needed', - user: { login: 'developer' }, + title: "Database optimization needed", + user: { login: "developer" }, }, comment: { - body: 'DevAgent can you analyze the slow queries and suggest optimizations?', + body: "DevAgent can you analyze the slow queries and suggest optimizations?", }, repository: { - full_name: 'company/backend', - owner: { login: 'company' }, + full_name: "company/backend", + owner: { login: "company" }, }, }, - expectedProcessing: 'comment_mention_detected', - description: 'Comment with agent mention', + expectedProcessing: "comment_mention_detected", + description: "Comment with agent mention", }, { - eventType: 'pull_request', + eventType: "pull_request", payload: { - action: 'opened', + action: "opened", pull_request: { number: 203, - title: 'Add new feature', - user: { login: 'contributor' }, + title: "Add new feature", + user: { login: "contributor" }, }, repository: { - full_name: 'company/features', + full_name: "company/features", }, }, - expectedProcessing: 'pr_event_logged', - description: 'PR opened without agent mention', + expectedProcessing: "pr_event_logged", + description: "PR opened without agent mention", }, ]; @@ -237,43 +242,43 @@ export class IntelligentAnalysisTestSuite { } } - console.log('✅ Webhook intelligent processing tests completed'); + console.log("✅ Webhook intelligent processing tests completed"); }, }, { - name: 'should validate secure webhook signature verification', + name: "should validate secure webhook signature verification", fn: async (runtime: IAgentRuntime) => { - console.log('🔒 Testing mandatory webhook signature verification...'); + console.log("🔒 Testing mandatory webhook signature verification..."); const securityTests = [ { - description: 'Valid signature with secret', + description: "Valid signature with secret", hasSecret: true, hasSignature: true, validSignature: true, - expectedResult: 'accepted', + expectedResult: "accepted", }, { - description: 'Missing secret (security violation)', + description: "Missing secret (security violation)", hasSecret: false, hasSignature: true, validSignature: true, - expectedResult: 'rejected', + expectedResult: "rejected", }, { - description: 'Missing signature', + description: "Missing signature", hasSecret: true, hasSignature: false, validSignature: false, - expectedResult: 'rejected', + expectedResult: "rejected", }, { - description: 'Invalid signature', + description: "Invalid signature", hasSecret: true, hasSignature: true, validSignature: false, - expectedResult: 'rejected', + expectedResult: "rejected", }, ]; @@ -282,8 +287,9 @@ export class IntelligentAnalysisTestSuite { try { // Security rule: anything without proper secret + signature should be rejected - const shouldBeAccepted = test.hasSecret && test.hasSignature && test.validSignature; - const expectedAccepted = test.expectedResult === 'accepted'; + const shouldBeAccepted = + test.hasSecret && test.hasSignature && test.validSignature; + const expectedAccepted = test.expectedResult === "accepted"; expect(shouldBeAccepted).toBe(expectedAccepted); @@ -294,44 +300,47 @@ export class IntelligentAnalysisTestSuite { } } - console.log('✅ Webhook security tests completed'); + console.log("✅ Webhook security tests completed"); }, }, { - name: 'should demonstrate no string matching fallbacks', + name: "should demonstrate no string matching fallbacks", fn: async (runtime: IAgentRuntime) => { - console.log('🚫 Testing elimination of string matching fallbacks...'); + console.log("🚫 Testing elimination of string matching fallbacks..."); // Test cases that would break string matching but should work with AI const edgeCases = [ { - content: 'Do NOT @TestAgent contact me about this issue', - agentName: 'TestAgent', + content: "Do NOT @TestAgent contact me about this issue", + agentName: "TestAgent", expectedMention: false, - reasoning: 'Negative context should prevent false positive', + reasoning: "Negative context should prevent false positive", }, { - content: 'TestAgent-like functionality is needed but not TestAgent specifically', - agentName: 'TestAgent', + content: + "TestAgent-like functionality is needed but not TestAgent specifically", + agentName: "TestAgent", expectedMention: false, - reasoning: 'Similar name but not actual mention', + reasoning: "Similar name but not actual mention", }, { - content: 'We discussed @TestAgent in the meeting. Please help with automation.', - agentName: 'TestAgent', + content: + "We discussed @TestAgent in the meeting. Please help with automation.", + agentName: "TestAgent", expectedMention: true, - reasoning: 'Past reference with current request', + reasoning: "Past reference with current request", }, { - content: 'github.com is down but this is not a repository issue', + content: "github.com is down but this is not a repository issue", expectedGitHubRelevant: false, - reasoning: 'Keyword present but not relevant to plugin', + reasoning: "Keyword present but not relevant to plugin", }, { - content: 'Can you check the repository owner/repo for recent commits?', + content: + "Can you check the repository owner/repo for recent commits?", expectedGitHubRelevant: true, - reasoning: 'Clear GitHub intent despite different phrasing', + reasoning: "Clear GitHub intent despite different phrasing", }, ]; @@ -343,12 +352,14 @@ export class IntelligentAnalysisTestSuite { // This demonstrates we need intelligent analysis, not string matching console.log(`✅ Edge case validated: ${edgeCase.reasoning}`); } catch (error) { - console.error(`❌ Edge case failed: ${edgeCase.reasoning}: ${error}`); + console.error( + `❌ Edge case failed: ${edgeCase.reasoning}: ${error}`, + ); throw error; } } - console.log('✅ String matching elimination tests completed'); + console.log("✅ String matching elimination tests completed"); }, }, ]; diff --git a/src/__tests__/e2e/starter-plugin.ts b/src/__tests__/e2e/starter-plugin.ts index 86828f8..d7170ce 100644 --- a/src/__tests__/e2e/starter-plugin.ts +++ b/src/__tests__/e2e/starter-plugin.ts @@ -1,4 +1,4 @@ -import { type Content, type HandlerCallback } from '@elizaos/core'; +import { type Content, type HandlerCallback } from "@elizaos/core"; /** * E2E (End-to-End) Test Suite for ElizaOS Plugins @@ -90,8 +90,8 @@ interface State { } export const StarterPluginTestSuite: TestSuite = { - name: 'plugin_starter_test_suite', - description: 'E2E tests for the starter plugin', + name: "plugin_starter_test_suite", + description: "E2E tests for the starter plugin", tests: [ /** @@ -101,33 +101,33 @@ export const StarterPluginTestSuite: TestSuite = { * within the runtime environment. */ { - name: 'example_test', + name: "example_test", fn: async (runtime) => { // Test the character name - if (runtime.character.name !== 'Eliza') { + if (runtime.character.name !== "Eliza") { throw new Error( - `Expected character name to be "Eliza" but got "${runtime.character.name}"` + `Expected character name to be "Eliza" but got "${runtime.character.name}"`, ); } // Debug: Check if getService exists if (!runtime.getService) { - throw new Error('Runtime does not have getService method'); + throw new Error("Runtime does not have getService method"); } // Verify the plugin is loaded properly // First try the expected service name patterns let service = - runtime.getService('starter') || - runtime.getService('_StarterService') || - runtime.getService('StarterService'); + runtime.getService("starter") || + runtime.getService("_StarterService") || + runtime.getService("StarterService"); // If not found, try to get all services and find it if (!service && runtime.getAllServices) { const allServices = runtime.getAllServices(); // Look for our service in the returned services for (const [key, svc] of Object.entries(allServices || {})) { - if (key.toLowerCase().includes('starter')) { + if (key.toLowerCase().includes("starter")) { service = svc; break; } @@ -139,7 +139,7 @@ export const StarterPluginTestSuite: TestSuite = { ? Object.keys(runtime.getAllServices() || {}) : []; throw new Error( - `Starter service not found. Available services: ${serviceList.join(', ') || 'none'}` + `Starter service not found. Available services: ${serviceList.join(", ") || "none"}`, ); } }, @@ -152,12 +152,14 @@ export const StarterPluginTestSuite: TestSuite = { * This is important to ensure actions are available for the agent to use. */ { - name: 'should_have_hello_world_action', + name: "should_have_hello_world_action", fn: async (runtime) => { // Access actions through runtime.actions instead of getPlugin - const actionExists = runtime.actions?.some((a: any) => a.name === 'HELLO_WORLD'); + const actionExists = runtime.actions?.some( + (a: any) => a.name === "HELLO_WORLD", + ); if (!actionExists) { - throw new Error('Hello world action not found in runtime actions'); + throw new Error("Hello world action not found in runtime actions"); } }, }, @@ -174,16 +176,16 @@ export const StarterPluginTestSuite: TestSuite = { * a user message and verify the agent's response. */ { - name: 'hello_world_action_test', + name: "hello_world_action_test", fn: async (runtime) => { // Create a test message asking the agent to say hello const testMessage: Memory = { - entityId: '12345678-1234-1234-1234-123456789012' as UUID, - roomId: '12345678-1234-1234-1234-123456789012' as UUID, + entityId: "12345678-1234-1234-1234-123456789012" as UUID, + roomId: "12345678-1234-1234-1234-123456789012" as UUID, content: { - text: 'Can you say hello?', - source: 'test', - actions: ['HELLO_WORLD'], // Specify which action we expect to trigger + text: "Can you say hello?", + source: "test", + actions: ["HELLO_WORLD"], // Specify which action we expect to trigger }, }; @@ -191,27 +193,29 @@ export const StarterPluginTestSuite: TestSuite = { const testState: State = { values: {}, data: {}, - text: '', + text: "", }; - let responseText = ''; + let responseText = ""; let responseReceived = false; // Find the hello world action in runtime.actions - const helloWorldAction = runtime.actions?.find((a: any) => a.name === 'HELLO_WORLD'); + const helloWorldAction = runtime.actions?.find( + (a: any) => a.name === "HELLO_WORLD", + ); if (!helloWorldAction) { - throw new Error('Hello world action not found in runtime actions'); + throw new Error("Hello world action not found in runtime actions"); } // Create a callback that captures the agent's response // This simulates how the runtime would handle the action's response const callback: HandlerCallback = async (response: Content) => { responseReceived = true; - responseText = response.text || ''; + responseText = response.text || ""; // Verify the response includes the expected action - if (!response.actions?.includes('HELLO_WORLD')) { - throw new Error('Response did not include HELLO_WORLD action'); + if (!response.actions?.includes("HELLO_WORLD")) { + throw new Error("Response did not include HELLO_WORLD action"); } // Return Promise as required by the HandlerCallback interface @@ -219,16 +223,24 @@ export const StarterPluginTestSuite: TestSuite = { }; // Execute the action - this simulates the runtime calling the action - await helloWorldAction.handler(runtime, testMessage, testState, {}, callback); + await helloWorldAction.handler( + runtime, + testMessage, + testState, + {}, + callback, + ); // Verify we received a response if (!responseReceived) { - throw new Error('Hello world action did not produce a response'); + throw new Error("Hello world action did not produce a response"); } // Verify the response contains "hello world" (case-insensitive) - if (!responseText.toLowerCase().includes('hello world')) { - throw new Error(`Expected response to contain "hello world" but got: "${responseText}"`); + if (!responseText.toLowerCase().includes("hello world")) { + throw new Error( + `Expected response to contain "hello world" but got: "${responseText}"`, + ); } // Success! The agent responded with "hello world" as expected @@ -242,15 +254,15 @@ export const StarterPluginTestSuite: TestSuite = { * Providers are used to fetch external data or compute values. */ { - name: 'hello_world_provider_test', + name: "hello_world_provider_test", fn: async (runtime) => { // Create a test message const testMessage: Memory = { - entityId: '12345678-1234-1234-1234-123456789012' as UUID, - roomId: '12345678-1234-1234-1234-123456789012' as UUID, + entityId: "12345678-1234-1234-1234-123456789012" as UUID, + roomId: "12345678-1234-1234-1234-123456789012" as UUID, content: { - text: 'What can you provide?', - source: 'test', + text: "What can you provide?", + source: "test", }, }; @@ -258,22 +270,30 @@ export const StarterPluginTestSuite: TestSuite = { const testState: State = { values: {}, data: {}, - text: '', + text: "", }; // Find the hello world provider in runtime.providers const helloWorldProvider = runtime.providers?.find( - (p: any) => p.name === 'HELLO_WORLD_PROVIDER' + (p: any) => p.name === "HELLO_WORLD_PROVIDER", ); if (!helloWorldProvider) { - throw new Error('Hello world provider not found in runtime providers'); + throw new Error( + "Hello world provider not found in runtime providers", + ); } // Test the provider - const result = await helloWorldProvider.get(runtime, testMessage, testState); + const result = await helloWorldProvider.get( + runtime, + testMessage, + testState, + ); - if (result.text !== 'I am a provider') { - throw new Error(`Expected provider to return "I am a provider", got "${result.text}"`); + if (result.text !== "I am a provider") { + throw new Error( + `Expected provider to return "I am a provider", got "${result.text}"`, + ); } }, }, @@ -285,25 +305,25 @@ export const StarterPluginTestSuite: TestSuite = { * Services run background tasks or manage long-lived resources. */ { - name: 'starter_service_test', + name: "starter_service_test", fn: async (runtime) => { // Check if getService exists if (!runtime.getService) { - throw new Error('Runtime does not have getService method'); + throw new Error("Runtime does not have getService method"); } // Get the service from the runtime let service = - runtime.getService('starter') || - runtime.getService('_StarterService') || - runtime.getService('StarterService'); + runtime.getService("starter") || + runtime.getService("_StarterService") || + runtime.getService("StarterService"); // If not found, try to get all services and find it if (!service && runtime.getAllServices) { const allServices = runtime.getAllServices(); // Look for our service in the returned services for (const [key, svc] of Object.entries(allServices || {})) { - if (key.toLowerCase().includes('starter')) { + if (key.toLowerCase().includes("starter")) { service = svc; break; } @@ -315,16 +335,16 @@ export const StarterPluginTestSuite: TestSuite = { ? Object.keys(runtime.getAllServices() || {}) : []; throw new Error( - `Starter service not found. Available services: ${serviceList.join(', ') || 'none'}` + `Starter service not found. Available services: ${serviceList.join(", ") || "none"}`, ); } // Check service capability description if ( service.capabilityDescription !== - 'This is a starter service which is attached to the agent through the starter plugin.' + "This is a starter service which is attached to the agent through the starter plugin." ) { - throw new Error('Incorrect service capability description'); + throw new Error("Incorrect service capability description"); } // Test service stop method diff --git a/src/__tests__/integration.test.ts b/src/__tests__/integration.test.ts index 5946de4..a746476 100644 --- a/src/__tests__/integration.test.ts +++ b/src/__tests__/integration.test.ts @@ -1,7 +1,21 @@ -import { describe, expect, it, mock, beforeEach, afterAll, beforeAll } from 'bun:test'; -import { githubPlugin, GitHubService } from '../index'; -import { createMockRuntime, setupLoggerSpies, MockRuntime } from './test-utils'; -import { HandlerCallback, IAgentRuntime, Memory, State, UUID, logger } from '@elizaos/core'; +import { + describe, + expect, + it, + mock, + beforeEach, + afterAll, + beforeAll, +} from "bun:test"; +import { githubPlugin } from "../index"; +import { createTestRuntime } from "./test-helpers"; +import { + HandlerCallback, + IAgentRuntime, + Memory, + State, + UUID, +} from "@elizaos/core"; /** * Integration tests demonstrate how multiple components of the plugin work together. @@ -14,40 +28,40 @@ import { HandlerCallback, IAgentRuntime, Memory, State, UUID, logger } from '@el // Set up spies on logger beforeAll(() => { - setupLoggerSpies(); + // Logger spies not needed with new test utils }); afterAll(() => { mock.restore(); }); -describe('Integration: GitHub Repository Action with GitHubService', () => { - let mockRuntime: MockRuntime; +describe("Integration: GitHub Repository Action with GitHubService", () => { + let mockRuntime: IAgentRuntime; let getServiceSpy: any; beforeEach(() => { // Create a service mock that will be returned by getService const mockService = { capabilityDescription: - 'Comprehensive GitHub integration with repository management, issue tracking, and PR workflows', + "Comprehensive GitHub integration with repository management, issue tracking, and PR workflows", getRepository: mock().mockResolvedValue({ id: 1, - name: 'Hello-World', - full_name: 'octocat/Hello-World', - description: 'This your first repo!', - html_url: 'https://github.com/octocat/Hello-World', + name: "Hello-World", + full_name: "octocat/Hello-World", + description: "This your first repo!", + html_url: "https://github.com/octocat/Hello-World", stargazers_count: 80, forks_count: 9, open_issues_count: 0, private: false, - created_at: '2011-01-26T19:01:12Z', - updated_at: '2011-01-26T19:14:43Z', - language: 'C', + created_at: "2011-01-26T19:01:12Z", + updated_at: "2011-01-26T19:14:43Z", + language: "C", owner: { - login: 'octocat', + login: "octocat", id: 1, - type: 'User', - avatar_url: 'https://github.com/images/error/octocat_happy.gif', + type: "User", + avatar_url: "https://github.com/images/error/octocat_happy.gif", }, }), stop: mock().mockResolvedValue(undefined), @@ -55,33 +69,33 @@ describe('Integration: GitHub Repository Action with GitHubService', () => { // Create a mock runtime with a spied getService method getServiceSpy = mock().mockImplementation((serviceType) => { - if (serviceType === 'github') { + if (serviceType === "github") { return mockService; } return null; }); - mockRuntime = createMockRuntime({ + mockRuntime = createTestRuntime({ getService: getServiceSpy, }); }); - it('should handle GET_GITHUB_REPOSITORY action with GitHubService available', async () => { + it("should handle GET_GITHUB_REPOSITORY action with GitHubService available", async () => { // Find the GET_GITHUB_REPOSITORY action const getRepoAction = githubPlugin.actions?.find( - (action) => action.name === 'GET_GITHUB_REPOSITORY' + (action) => action.name === "GET_GITHUB_REPOSITORY", ); expect(getRepoAction).toBeDefined(); // Create a mock message and state const mockMessage: Memory = { - id: '12345678-1234-1234-1234-123456789012' as UUID, - roomId: '12345678-1234-1234-1234-123456789012' as UUID, - entityId: '12345678-1234-1234-1234-123456789012' as UUID, - agentId: '12345678-1234-1234-1234-123456789012' as UUID, + id: "12345678-1234-1234-1234-123456789012" as UUID, + roomId: "12345678-1234-1234-1234-123456789012" as UUID, + entityId: "12345678-1234-1234-1234-123456789012" as UUID, + agentId: "12345678-1234-1234-1234-123456789012" as UUID, content: { - text: 'Get repository octocat/Hello-World', - source: 'test', + text: "Get repository octocat/Hello-World", + source: "test", }, createdAt: Date.now(), }; @@ -89,7 +103,7 @@ describe('Integration: GitHub Repository Action with GitHubService', () => { const mockState: State = { values: {}, data: {}, - text: '', + text: "", }; // Create a mock callback to capture the response @@ -100,29 +114,29 @@ describe('Integration: GitHub Repository Action with GitHubService', () => { mockRuntime as unknown as IAgentRuntime, mockMessage, mockState, - { owner: 'octocat', repo: 'Hello-World' }, - callbackFn as HandlerCallback + { owner: "octocat", repo: "Hello-World" }, + callbackFn as HandlerCallback, ); // Verify the callback was called with expected response expect(callbackFn).toHaveBeenCalledWith( expect.objectContaining({ - text: expect.stringContaining('Repository: octocat/Hello-World'), - actions: ['GET_GITHUB_REPOSITORY'], - }) + text: expect.stringContaining("Repository: octocat/Hello-World"), + actions: ["GET_GITHUB_REPOSITORY"], + }), ); // Get the service to ensure integration - const service = mockRuntime.getService('github'); + const service = mockRuntime.getService("github"); expect(service).toBeDefined(); - expect(service?.capabilityDescription).toContain('GitHub integration'); + expect(service?.capabilityDescription).toContain("GitHub integration"); }); }); -describe('Integration: Plugin initialization and service registration', () => { - it('should initialize the plugin and register the service', async () => { +describe("Integration: Plugin initialization and service registration", () => { + it("should initialize the plugin and register the service", async () => { // Create a fresh mock runtime with mocked registerService for testing initialization flow - const mockRuntime = createMockRuntime(); + const mockRuntime = createTestRuntime(); // Create and install a spy on registerService const registerServiceSpy = mock(); @@ -131,8 +145,8 @@ describe('Integration: Plugin initialization and service registration', () => { // Run a minimal simulation of the plugin initialization process if (githubPlugin.init) { await githubPlugin.init( - { GITHUB_TOKEN: 'ghp_test123456789012345678901234567890' }, - mockRuntime as unknown as IAgentRuntime + { GITHUB_TOKEN: "ghp_test123456789012345678901234567890" }, + mockRuntime as unknown as IAgentRuntime, ); // Directly mock the service registration that happens during initialization @@ -144,12 +158,12 @@ describe('Integration: Plugin initialization and service registration', () => { const originalStart = GitHubServiceClass.start; GitHubServiceClass.start = mock().mockResolvedValue({ capabilityDescription: - 'Comprehensive GitHub integration with repository management, issue tracking, and PR workflows', + "Comprehensive GitHub integration with repository management, issue tracking, and PR workflows", stop: mock(), } as any); const serviceInstance = await GitHubServiceClass.start( - mockRuntime as unknown as IAgentRuntime + mockRuntime as unknown as IAgentRuntime, ); // Register the Service class to match the core API diff --git a/src/__tests__/plugin.test.ts b/src/__tests__/plugin.test.ts index d0c8924..1082b69 100644 --- a/src/__tests__/plugin.test.ts +++ b/src/__tests__/plugin.test.ts @@ -1,30 +1,29 @@ -import { describe, it, expect, beforeEach, mock } from 'bun:test'; -import { githubPlugin } from '../index'; -import { GitHubService } from '../services/github'; -import { githubConfigSchema } from '../types'; -import { IAgentRuntime, UUID } from '@elizaos/core'; -import { createMockRuntime } from './test-utils'; - -describe('GitHub Plugin Initialization', () => { +import { describe, it, expect, beforeEach, mock } from "bun:test"; +import { githubPlugin } from "../index"; +import { githubConfigSchema } from "../types"; +import { UUID } from "@elizaos/core"; +import { createTestRuntime } from "./test-helpers"; + +describe("GitHub Plugin Initialization", () => { beforeEach(() => { mock.restore(); delete (global as any).GITHUB_CONFIG; }); - it('should initialize successfully with valid configuration', async () => { + it("should initialize successfully with valid configuration", async () => { const config = { - GITHUB_TOKEN: 'ghp_1234567890abcdef1234567890abcdef12345678', - GITHUB_OWNER: 'test-owner', - GITHUB_WEBHOOK_SECRET: 'test-webhook-secret', + GITHUB_TOKEN: "ghp_1234567890abcdef1234567890abcdef12345678", + GITHUB_OWNER: "test-owner", + GITHUB_WEBHOOK_SECRET: "test-webhook-secret", }; - const mockRuntime = createMockRuntime(); + const mockRuntime = createTestRuntime(); // Ensure character exists and has settings if (!mockRuntime.character) { mockRuntime.character = { - name: 'Test Character', - bio: 'Test bio', + name: "Test Character", + bio: "Test bio", settings: {}, }; } @@ -41,7 +40,7 @@ describe('GitHub Plugin Initialization', () => { expect(true).toBe(true); } catch (error) { // If we get here, the test fails - show us what error occurred - console.error('Plugin init failed with error:', error); + console.error("Plugin init failed with error:", error); throw new Error(`Plugin init should not have thrown, but got: ${error}`); } @@ -52,53 +51,57 @@ describe('GitHub Plugin Initialization', () => { // The important thing is that init doesn't throw }); - it('should handle invalid GitHub token in test environment', async () => { + it("should handle invalid GitHub token in test environment", async () => { const config = { - GITHUB_TOKEN: 'invalid-token-format', - GITHUB_OWNER: 'test-owner', + GITHUB_TOKEN: "invalid-token-format", + GITHUB_OWNER: "test-owner", }; // In test environment, it should not throw, just warn try { - await githubPlugin.init!(config, createMockRuntime()); + await githubPlugin.init!(config, createTestRuntime()); expect(true).toBe(true); } catch (error) { - console.error('Plugin init with invalid token failed:', error); - throw new Error(`Plugin init should not have thrown in test mode, but got: ${error}`); + console.error("Plugin init with invalid token failed:", error); + throw new Error( + `Plugin init should not have thrown in test mode, but got: ${error}`, + ); } }); - it('should succeed initialization without GitHub token in test environment', async () => { + it("should succeed initialization without GitHub token in test environment", async () => { const config = { - GITHUB_TOKEN: '', - GITHUB_OWNER: 'test-owner', + GITHUB_TOKEN: "", + GITHUB_OWNER: "test-owner", }; // Should not throw in test environment try { - await githubPlugin.init!(config, createMockRuntime()); + await githubPlugin.init!(config, createTestRuntime()); expect(true).toBe(true); } catch (error) { - console.error('Plugin init without token failed:', error); - throw new Error(`Plugin init should not have thrown in test mode, but got: ${error}`); + console.error("Plugin init without token failed:", error); + throw new Error( + `Plugin init should not have thrown in test mode, but got: ${error}`, + ); } }); - it('should accept fine-grained tokens', async () => { + it("should accept fine-grained tokens", async () => { const config = { GITHUB_TOKEN: - 'github_pat_1234567890abcdef12_1234567890abcdef1234567890abcdef1234567890abcdef123456789', - GITHUB_OWNER: 'test-owner', - GITHUB_WEBHOOK_SECRET: 'test-webhook-secret', + "github_pat_1234567890abcdef12_1234567890abcdef1234567890abcdef1234567890abcdef123456789", + GITHUB_OWNER: "test-owner", + GITHUB_WEBHOOK_SECRET: "test-webhook-secret", }; - const mockRuntime = createMockRuntime(); + const mockRuntime = createTestRuntime(); // Ensure character exists and has settings if (!mockRuntime.character) { mockRuntime.character = { - name: 'Test Character', - bio: 'Test bio', + name: "Test Character", + bio: "Test bio", settings: {}, }; } @@ -110,7 +113,7 @@ describe('GitHub Plugin Initialization', () => { await githubPlugin.init!(config, mockRuntime); expect(true).toBe(true); } catch (error) { - console.error('Plugin init with fine-grained token failed:', error); + console.error("Plugin init with fine-grained token failed:", error); throw new Error(`Plugin init should not have thrown, but got: ${error}`); } @@ -121,66 +124,66 @@ describe('GitHub Plugin Initialization', () => { }); }); -describe('GitHub Configuration Schema', () => { - it('should validate valid PAT token', () => { +describe("GitHub Configuration Schema", () => { + it("should validate valid PAT token", () => { const config = { - GITHUB_TOKEN: 'ghp_1234567890abcdef1234567890abcdef12345678', + GITHUB_TOKEN: "ghp_1234567890abcdef1234567890abcdef12345678", }; expect(() => githubConfigSchema.parse(config)).not.toThrow(); }); - it('should validate valid fine-grained token', () => { + it("should validate valid fine-grained token", () => { const config = { GITHUB_TOKEN: - 'github_pat_1234567890abcdef12_1234567890abcdef1234567890abcdef1234567890abcdef123456789', + "github_pat_1234567890abcdef12_1234567890abcdef1234567890abcdef1234567890abcdef123456789", }; expect(() => githubConfigSchema.parse(config)).not.toThrow(); }); - it('should reject invalid token format', () => { + it("should reject invalid token format", () => { const config = { - GITHUB_TOKEN: 'invalid-token', + GITHUB_TOKEN: "invalid-token", }; expect(() => githubConfigSchema.parse(config)).toThrow(); }); - it('should reject empty token', () => { + it("should reject empty token", () => { const config = { - GITHUB_TOKEN: '', + GITHUB_TOKEN: "", }; expect(() => githubConfigSchema.parse(config)).toThrow(); }); - it('should accept optional GITHUB_TOKEN and GITHUB_OWNER', () => { + it("should accept optional GITHUB_TOKEN and GITHUB_OWNER", () => { const config = { - GITHUB_TOKEN: 'ghp_1234567890abcdef1234567890abcdef12345678', - GITHUB_OWNER: 'test-user', + GITHUB_TOKEN: "ghp_1234567890abcdef1234567890abcdef12345678", + GITHUB_OWNER: "test-user", }; expect(() => githubConfigSchema.parse(config)).not.toThrow(); }); }); -describe('GitHub Plugin Events', () => { - it('should process GitHub-related messages', async () => { +describe("GitHub Plugin Events", () => { + it("should process GitHub-related messages", async () => { if (!githubPlugin.events?.MESSAGE_RECEIVED?.[0]) { - throw new Error('MESSAGE_RECEIVED event handler not found'); + throw new Error("MESSAGE_RECEIVED event handler not found"); } const messageHandler = githubPlugin.events.MESSAGE_RECEIVED[0]; const params = { - runtime: createMockRuntime(), - source: 'test', + runtime: createTestRuntime(), + source: "test", message: { - id: 'test-msg' as UUID, - entityId: 'test-entity' as UUID, - roomId: 'test-room' as UUID, + id: "test-msg" as UUID, + entityId: "test-entity" as UUID, + roomId: "test-room" as UUID, content: { - text: 'Check the issue on github.com/user/repo', + text: "Check the issue on github.com/user/repo", }, createdAt: Date.now(), }, @@ -191,8 +194,10 @@ describe('GitHub Plugin Events', () => { await messageHandler(params); expect(true).toBe(true); } catch (error) { - console.error('MESSAGE_RECEIVED handler failed:', error); - throw new Error(`MESSAGE_RECEIVED handler should not have thrown, but got: ${error}`); + console.error("MESSAGE_RECEIVED handler failed:", error); + throw new Error( + `MESSAGE_RECEIVED handler should not have thrown, but got: ${error}`, + ); } }); }); diff --git a/src/__tests__/prAndBranch.test.ts b/src/__tests__/prAndBranch.test.ts index a533cb2..7b25344 100644 --- a/src/__tests__/prAndBranch.test.ts +++ b/src/__tests__/prAndBranch.test.ts @@ -1,39 +1,44 @@ -import { describe, it, expect, mock, beforeEach } from 'bun:test'; -import { GitHubService } from '../services/github'; -import { Octokit } from '@octokit/rest'; -import type { IAgentRuntime } from '@elizaos/core'; +import { describe, it, expect, beforeEach, mock } from "bun:test"; +import { GitHubService } from "../services/github"; +import { createMockPullRequest } from "./test-helpers"; -// Mock Octokit -mock.module('@octokit/rest', () => ({ +mock.module("@octokit/rest", () => ({ Octokit: class MockOctokit { + repos: any; + git: any; + pulls: any; + constructor() { this.repos = { - getContent: mock(), - createOrUpdateFileContents: mock(), - deleteFile: mock(), - listBranches: mock(), - getBranch: mock(), - get: mock(), + get: mock().mockResolvedValue({ + data: { + id: 1, + name: "test-repo", + full_name: "test-owner/test-repo", + default_branch: "main", + }, + }), + createOrUpdateFile: mock().mockResolvedValue({ + data: { commit: { sha: "abc123" } }, + }), }; this.git = { - createRef: mock(), - deleteRef: mock(), - getRef: mock(), - compareCommits: mock(), + createRef: mock().mockResolvedValue({ + data: { ref: "refs/heads/test-branch", object: { sha: "def456" } }, + }), }; this.pulls = { - create: mock(), - list: mock(), - get: mock(), - merge: mock(), + create: mock().mockResolvedValue({ + data: createMockPullRequest(), + }), }; } }, })); -describe('PR and Branch Management Tests', () => { +describe("PR and Branch Management Tests", () => { let mockRuntime: any; - let githubService: GitHubService; + let githubService: any; let mockOctokit: any; beforeEach(() => { @@ -44,74 +49,74 @@ describe('PR and Branch Management Tests', () => { repos: { createOrUpdateFileContents: mock().mockResolvedValue({ data: { - commit: { sha: 'new-commit-sha' }, - content: { sha: 'new-file-sha' }, + commit: { sha: "new-commit-sha" }, + content: { sha: "new-file-sha" }, }, headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }), getContent: mock().mockResolvedValue({ data: { - type: 'file', - content: Buffer.from('# Test Content').toString('base64'), - sha: 'file-sha', + type: "file", + content: Buffer.from("# Test Content").toString("base64"), + sha: "file-sha", }, headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }), deleteFile: mock().mockResolvedValue({ - data: { commit: { sha: 'delete-commit-sha' } }, + data: { commit: { sha: "delete-commit-sha" } }, headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }), compareCommits: mock().mockResolvedValue({ data: { - status: 'ahead', + status: "ahead", ahead_by: 2, behind_by: 0, files: [ - { filename: 'file1.js', additions: 10, deletions: 5 }, - { filename: 'file2.js', additions: 20, deletions: 0 }, + { filename: "file1.js", additions: 10, deletions: 5 }, + { filename: "file2.js", additions: 20, deletions: 0 }, ], }, headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }), getBranch: mock().mockResolvedValue({ data: { - name: 'test-branch', - commit: { sha: 'branch-sha' }, + name: "test-branch", + commit: { sha: "branch-sha" }, protected: false, }, headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }), listBranches: mock().mockResolvedValue({ data: [ - { name: 'main', protected: true }, - { name: 'develop', protected: false }, - { name: 'feature/test', protected: false }, + { name: "main", protected: true }, + { name: "develop", protected: false }, + { name: "feature/test", protected: false }, ], headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }), get: mock().mockResolvedValue({ - data: { default_branch: 'main' }, + data: { default_branch: "main" }, headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }), }, @@ -119,79 +124,79 @@ describe('PR and Branch Management Tests', () => { create: mock().mockResolvedValue({ data: { number: 123, - title: 'Test PR', - html_url: 'https://github.com/owner/repo/pull/123', - state: 'open', + title: "Test PR", + html_url: "https://github.com/owner/repo/pull/123", + state: "open", }, headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }), list: mock().mockResolvedValue({ data: [ { number: 123, - title: 'Test PR', - state: 'open', - user: { login: 'testuser' }, + title: "Test PR", + state: "open", + user: { login: "testuser" }, }, ], headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }), get: mock().mockResolvedValue({ data: { number: 123, - title: 'Test PR', - state: 'open', + title: "Test PR", + state: "open", mergeable: true, merged: false, }, headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }), merge: mock().mockResolvedValue({ data: { - sha: 'merge-sha', + sha: "merge-sha", merged: true, - message: 'Pull Request successfully merged', + message: "Pull Request successfully merged", }, headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }), }, git: { createRef: mock().mockResolvedValue({ data: { - ref: 'refs/heads/new-branch', - object: { sha: 'new-branch-sha' }, + ref: "refs/heads/new-branch", + object: { sha: "new-branch-sha" }, }, headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }), getRef: mock().mockResolvedValue({ data: { - ref: 'refs/heads/main', - object: { sha: 'main-sha' }, + ref: "refs/heads/main", + object: { sha: "main-sha" }, }, headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }), deleteRef: mock().mockResolvedValue({ headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }), }, @@ -206,8 +211,8 @@ describe('PR and Branch Management Tests', () => { }, }, headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }), }, @@ -219,11 +224,11 @@ describe('PR and Branch Management Tests', () => { // Create mock runtime mockRuntime = { - agentId: 'test-agent', + agentId: "test-agent", getSetting: mock((key: string) => { const settings: Record = { - GITHUB_TOKEN: 'ghp_test123', - GITHUB_OWNER: 'testowner', + GITHUB_TOKEN: "ghp_test123", + GITHUB_OWNER: "testowner", }; return settings[key]; }), @@ -240,8 +245,8 @@ describe('PR and Branch Management Tests', () => { const tempRuntime = { ...mockRuntime, getSetting: mock((key: string) => { - if (key === 'GITHUB_TOKEN') { - return 'test-token'; + if (key === "GITHUB_TOKEN") { + return "test-token"; } return null; }), @@ -253,168 +258,187 @@ describe('PR and Branch Management Tests', () => { (githubService as any).octokit = mockOctokit; }); - describe('File Operations', () => { - it('should get file content', async () => { - const result = await githubService.getFileContent('owner', 'repo', 'README.md'); + describe("File Operations", () => { + it("should get file content", async () => { + const result = await githubService.getFileContent( + "owner", + "repo", + "README.md", + ); expect(mockOctokit.repos.getContent).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', - path: 'README.md', + owner: "owner", + repo: "repo", + path: "README.md", ref: undefined, }); expect(result).toEqual({ - content: '# Test Content', - sha: 'file-sha', + content: "# Test Content", + sha: "file-sha", }); }); - it('should handle directory paths', async () => { + it("should handle directory paths", async () => { mockOctokit.repos.getContent.mockResolvedValueOnce({ - data: [{ type: 'file', name: 'file1.js' }], // Array indicates directory + data: [{ type: "file", name: "file1.js" }], // Array indicates directory headers: { - 'x-ratelimit-remaining': '4999', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "4999", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }); - await expect(githubService.getFileContent('owner', 'repo', 'src')).rejects.toThrow( - 'Path is not a file or content not available' - ); + await expect( + githubService.getFileContent("owner", "repo", "src"), + ).rejects.toThrow("Path is not a file or content not available"); }); - it('should create or update files', async () => { - const content = '# Updated Content'; + it("should create or update files", async () => { + const content = "# Updated Content"; const result = await githubService.createOrUpdateFile( - 'owner', - 'repo', - 'README.md', + "owner", + "repo", + "README.md", content, - 'Update README', - 'main', - 'old-sha' + "Update README", + "main", + "old-sha", ); - expect(mockOctokit.repos.createOrUpdateFileContents).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', - path: 'README.md', - message: 'Update README', - content: Buffer.from(content).toString('base64'), - branch: 'main', - sha: 'old-sha', - }); + expect(mockOctokit.repos.createOrUpdateFileContents).toHaveBeenCalledWith( + { + owner: "owner", + repo: "repo", + path: "README.md", + message: "Update README", + content: Buffer.from(content).toString("base64"), + branch: "main", + sha: "old-sha", + }, + ); - expect(result.commit.sha).toBe('new-commit-sha'); + expect(result.commit.sha).toBe("new-commit-sha"); }); - it('should create new files without SHA', async () => { - const content = '# New File'; + it("should create new files without SHA", async () => { + const content = "# New File"; await githubService.createOrUpdateFile( - 'owner', - 'repo', - 'NEW.md', + "owner", + "repo", + "NEW.md", content, - 'Create new file', - 'main' + "Create new file", + "main", // No SHA for new files ); expect(mockOctokit.repos.createOrUpdateFileContents).toHaveBeenCalledWith( - expect.not.objectContaining({ sha: expect.any(String) }) + expect.not.objectContaining({ sha: expect.any(String) }), ); }); - it('should delete files', async () => { + it("should delete files", async () => { await githubService.deleteFile( - 'owner', - 'repo', - 'old-file.txt', - 'Remove old file', - 'file-sha', - 'main' + "owner", + "repo", + "old-file.txt", + "Remove old file", + "file-sha", + "main", ); expect(mockOctokit.repos.deleteFile).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', - path: 'old-file.txt', - message: 'Remove old file', - sha: 'file-sha', - branch: 'main', + owner: "owner", + repo: "repo", + path: "old-file.txt", + message: "Remove old file", + sha: "file-sha", + branch: "main", }); }); }); - describe('Branch Operations', () => { - it('should create branches', async () => { - const result = await githubService.createBranch('owner', 'repo', 'feature/new', 'base-sha'); + describe("Branch Operations", () => { + it("should create branches", async () => { + const result = await githubService.createBranch( + "owner", + "repo", + "feature/new", + "base-sha", + ); expect(mockOctokit.git.createRef).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', - ref: 'refs/heads/feature/new', - sha: 'base-sha', + owner: "owner", + repo: "repo", + ref: "refs/heads/feature/new", + sha: "base-sha", }); - expect(result.ref).toBe('refs/heads/new-branch'); + expect(result.ref).toBe("refs/heads/new-branch"); }); - it('should list branches', async () => { - const branches = await githubService.listBranches('owner', 'repo'); + it("should list branches", async () => { + const branches = await githubService.listBranches("owner", "repo"); expect(mockOctokit.repos.listBranches).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', + owner: "owner", + repo: "repo", }); expect(branches).toHaveLength(3); - expect(branches[0].name).toBe('main'); + expect(branches[0].name).toBe("main"); }); - it('should get branch details', async () => { - const branch = await githubService.getBranch('owner', 'repo', 'develop'); + it("should get branch details", async () => { + const branch = await githubService.getBranch("owner", "repo", "develop"); expect(mockOctokit.repos.getBranch).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', - branch: 'develop', + owner: "owner", + repo: "repo", + branch: "develop", }); - expect(branch.name).toBe('test-branch'); + expect(branch.name).toBe("test-branch"); expect(branch.protected).toBe(false); }); - it('should delete branches', async () => { - await githubService.deleteBranch('owner', 'repo', 'old-feature'); + it("should delete branches", async () => { + await githubService.deleteBranch("owner", "repo", "old-feature"); expect(mockOctokit.git.deleteRef).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', - ref: 'heads/old-feature', + owner: "owner", + repo: "repo", + ref: "heads/old-feature", }); }); - it('should get default branch', async () => { - const defaultBranch = await githubService.getDefaultBranch('owner', 'repo'); + it("should get default branch", async () => { + const defaultBranch = await githubService.getDefaultBranch( + "owner", + "repo", + ); expect(mockOctokit.repos.get).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', + owner: "owner", + repo: "repo", }); - expect(defaultBranch).toBe('main'); + expect(defaultBranch).toBe("main"); }); - it('should compare branches', async () => { - const comparison = await githubService.compareBranches('owner', 'repo', 'main', 'feature'); + it("should compare branches", async () => { + const comparison = await githubService.compareBranches( + "owner", + "repo", + "main", + "feature", + ); expect(mockOctokit.repos.compareCommits).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', - base: 'main', - head: 'feature', + owner: "owner", + repo: "repo", + base: "main", + head: "feature", }); expect(comparison.ahead_by).toBe(2); @@ -422,37 +446,37 @@ describe('PR and Branch Management Tests', () => { }); }); - describe('Pull Request Operations', () => { - it('should create pull requests', async () => { - const pr = await githubService.createPullRequest('owner', 'repo', { - title: 'New Feature', - body: 'This PR adds a new feature', - head: 'feature/new', - base: 'main', + describe("Pull Request Operations", () => { + it("should create pull requests", async () => { + const pr = await githubService.createPullRequest("owner", "repo", { + title: "New Feature", + body: "This PR adds a new feature", + head: "feature/new", + base: "main", }); expect(mockOctokit.pulls.create).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', - title: 'New Feature', - body: 'This PR adds a new feature', - head: 'feature/new', - base: 'main', + owner: "owner", + repo: "repo", + title: "New Feature", + body: "This PR adds a new feature", + head: "feature/new", + base: "main", }); expect(pr.number).toBe(123); - expect(pr.html_url).toContain('github.com'); + expect(pr.html_url).toContain("github.com"); }); - it('should list pull requests', async () => { - const prs = await githubService.listPullRequests('owner', 'repo'); + it("should list pull requests", async () => { + const prs = await githubService.listPullRequests("owner", "repo"); expect(mockOctokit.pulls.list).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', - state: 'open', - sort: 'created', - direction: 'desc', + owner: "owner", + repo: "repo", + state: "open", + sort: "created", + direction: "desc", per_page: 30, }); @@ -460,12 +484,12 @@ describe('PR and Branch Management Tests', () => { expect(prs[0].number).toBe(123); }); - it('should get pull request details', async () => { - const pr = await githubService.getPullRequest('owner', 'repo', 123); + it("should get pull request details", async () => { + const pr = await githubService.getPullRequest("owner", "repo", 123); expect(mockOctokit.pulls.get).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', + owner: "owner", + repo: "repo", pull_number: 123, }); @@ -473,86 +497,102 @@ describe('PR and Branch Management Tests', () => { expect(pr.mergeable).toBe(true); }); - it('should merge pull requests', async () => { - const result = await githubService.mergePullRequest('owner', 'repo', 123, { - merge_method: 'squash', - }); + it("should merge pull requests", async () => { + const result = await githubService.mergePullRequest( + "owner", + "repo", + 123, + { + merge_method: "squash", + }, + ); expect(mockOctokit.pulls.merge).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', + owner: "owner", + repo: "repo", pull_number: 123, - merge_method: 'squash', + merge_method: "squash", }); expect(result.merged).toBe(true); - expect(result.sha).toBe('merge-sha'); + expect(result.sha).toBe("merge-sha"); }); - it('should handle merge with default method', async () => { - await githubService.mergePullRequest('owner', 'repo', 456); + it("should handle merge with default method", async () => { + await githubService.mergePullRequest("owner", "repo", 456); expect(mockOctokit.pulls.merge).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', + owner: "owner", + repo: "repo", pull_number: 456, commit_title: undefined, commit_message: undefined, - merge_method: 'merge', + merge_method: "merge", }); }); }); - describe('Error Handling', () => { - it('should handle rate limit errors', async () => { + describe("Error Handling", () => { + it("should handle rate limit errors", async () => { mockOctokit.repos.getContent.mockRejectedValueOnce({ status: 403, response: { headers: { - 'x-ratelimit-remaining': '0', - 'x-ratelimit-reset': String(Date.now() / 1000 + 3600), + "x-ratelimit-remaining": "0", + "x-ratelimit-reset": String(Date.now() / 1000 + 3600), }, }, }); - await expect(githubService.getFileContent('owner', 'repo', 'file.txt')).rejects.toThrow(); + await expect( + githubService.getFileContent("owner", "repo", "file.txt"), + ).rejects.toThrow(); }); - it('should handle not found errors', async () => { + it("should handle not found errors", async () => { mockOctokit.repos.getContent.mockRejectedValueOnce({ status: 404, - message: 'Not Found', + message: "Not Found", }); - await expect(githubService.getFileContent('owner', 'repo', 'missing.txt')).rejects.toThrow(); + await expect( + githubService.getFileContent("owner", "repo", "missing.txt"), + ).rejects.toThrow(); }); - it('should validate repository names', async () => { + it("should validate repository names", async () => { await expect( - githubService.createBranch('invalid/owner', 'repo', 'branch', 'sha') + githubService.createBranch("invalid/owner", "repo", "branch", "sha"), ).rejects.toThrow(); await expect( - githubService.createBranch('owner', 'invalid/repo', 'branch', 'sha') + githubService.createBranch("owner", "invalid/repo", "branch", "sha"), ).rejects.toThrow(); }); }); - describe('Activity Logging', () => { - it('should log successful operations', async () => { - await githubService.createBranch('owner', 'repo', 'new-branch', 'sha'); + describe("Activity Logging", () => { + it("should log successful operations", async () => { + await githubService.createBranch("owner", "repo", "new-branch", "sha"); const activityLog = githubService.getActivityLog(); expect(activityLog).toHaveLength(1); - expect(activityLog[0].action).toBe('create_branch'); + expect(activityLog[0].action).toBe("create_branch"); expect(activityLog[0].success).toBe(true); - expect(activityLog[0].resource_id).toBe('owner/repo:new-branch'); + expect(activityLog[0].resource_id).toBe("owner/repo:new-branch"); }); - it('should log failed operations', async () => { - mockOctokit.git.createRef.mockRejectedValueOnce(new Error('Branch already exists')); + it("should log failed operations", async () => { + mockOctokit.git.createRef.mockRejectedValueOnce( + new Error("Branch already exists"), + ); try { - await githubService.createBranch('owner', 'repo', 'existing-branch', 'sha'); + await githubService.createBranch( + "owner", + "repo", + "existing-branch", + "sha", + ); } catch (error) { // Expected error } @@ -560,7 +600,7 @@ describe('PR and Branch Management Tests', () => { const activityLog = githubService.getActivityLog(); expect(activityLog).toHaveLength(1); expect(activityLog[0].success).toBe(false); - expect(activityLog[0].error).toContain('Branch already exists'); + expect(activityLog[0].error).toContain("Branch already exists"); }); }); }); diff --git a/src/__tests__/runtime-integration.test.ts b/src/__tests__/runtime-integration.test.ts index 969e56b..a449b7e 100644 --- a/src/__tests__/runtime-integration.test.ts +++ b/src/__tests__/runtime-integration.test.ts @@ -1,30 +1,30 @@ -import { describe, expect, it, mock, beforeEach } from 'bun:test'; -import { githubPlugin, GitHubService } from '../index'; -import { IAgentRuntime, UUID } from '@elizaos/core'; +import { describe, expect, it, mock, beforeEach } from "bun:test"; +import { githubPlugin, GitHubService } from "../index"; +import { IAgentRuntime, UUID } from "@elizaos/core"; -describe('Runtime Integration: GitHub Plugin with runtime.getSetting', () => { +describe("Runtime Integration: GitHub Plugin with runtime.getSetting", () => { let mockRuntime: any; beforeEach(() => { mockRuntime = { - agentId: 'test-agent' as UUID, + agentId: "test-agent" as UUID, getSetting: mock(), getService: mock(), registerService: mock(), services: new Map(), character: { - name: 'Test Character', - bio: 'Test bio', + name: "Test Character", + bio: "Test bio", settings: {}, }, }; }); - it('should initialize with runtime.getSetting for token', async () => { + it("should initialize with runtime.getSetting for token", async () => { // Mock getSetting to return a test token mockRuntime.getSetting.mockImplementation((key: string) => { - if (key === 'GITHUB_TOKEN') { - return 'ghp_test123456789'; + if (key === "GITHUB_TOKEN") { + return "ghp_test123456789"; } return null; }); @@ -33,20 +33,20 @@ describe('Runtime Integration: GitHub Plugin with runtime.getSetting', () => { await githubPlugin.init!({}, mockRuntime); // Verify getSetting was called - expect(mockRuntime.getSetting).toHaveBeenCalledWith('GITHUB_TOKEN'); + expect(mockRuntime.getSetting).toHaveBeenCalledWith("GITHUB_TOKEN"); // Verify the plugin initialized successfully expect(mockRuntime.character.settings).toBeDefined(); }); - it('should use runtime.getSetting in GitHubService and validate token', async () => { + it("should use runtime.getSetting in GitHubService and validate token", async () => { // Mock getSetting mockRuntime.getSetting.mockImplementation((key: string) => { - if (key === 'GITHUB_TOKEN') { - return 'ghp_runtime_token'; + if (key === "GITHUB_TOKEN") { + return "ghp_runtime_token"; } - if (key === 'GITHUB_OWNER') { - return 'runtime-owner'; + if (key === "GITHUB_OWNER") { + return "runtime-owner"; } return null; }); @@ -55,7 +55,7 @@ describe('Runtime Integration: GitHub Plugin with runtime.getSetting', () => { const service = new GitHubService(mockRuntime); // Verify getSetting was called for token during construction - expect(mockRuntime.getSetting).toHaveBeenCalledWith('GITHUB_TOKEN'); + expect(mockRuntime.getSetting).toHaveBeenCalledWith("GITHUB_TOKEN"); // Verify the service has the correct configuration const activityLog = service.getActivityLog(); @@ -63,20 +63,20 @@ describe('Runtime Integration: GitHub Plugin with runtime.getSetting', () => { expect(Array.isArray(activityLog)).toBe(true); }); - it('should throw when getSetting returns null', async () => { + it("should throw when getSetting returns null", async () => { // Mock getSetting to return null (no token available) mockRuntime.getSetting.mockReturnValue(null); // GitHubService constructor should throw when no token is available expect(() => { new GitHubService(mockRuntime); - }).toThrow('GitHub token is required'); + }).toThrow("GitHub token is required"); // Verify getSetting was called - expect(mockRuntime.getSetting).toHaveBeenCalledWith('GITHUB_TOKEN'); + expect(mockRuntime.getSetting).toHaveBeenCalledWith("GITHUB_TOKEN"); }); - it('should handle missing token gracefully in test mode', async () => { + it("should handle missing token gracefully in test mode", async () => { // Mock getSetting to return null mockRuntime.getSetting.mockReturnValue(null); @@ -87,11 +87,11 @@ describe('Runtime Integration: GitHub Plugin with runtime.getSetting', () => { expect(mockRuntime.character.settings).toBeDefined(); }); - it('should work with actions using runtime settings', async () => { + it("should work with actions using runtime settings", async () => { // Mock runtime with token mockRuntime.getSetting.mockImplementation((key: string) => { - if (key === 'GITHUB_TOKEN') { - return 'ghp_test123'; + if (key === "GITHUB_TOKEN") { + return "ghp_test123"; } return null; }); @@ -103,7 +103,7 @@ describe('Runtime Integration: GitHub Plugin with runtime.getSetting', () => { remaining: 4500, used: 500, reset: Date.now() / 1000 + 3600, - resource: 'core', + resource: "core", }), getActivityLog: mock().mockReturnValue([]), }; @@ -111,23 +111,25 @@ describe('Runtime Integration: GitHub Plugin with runtime.getSetting', () => { mockRuntime.getService.mockReturnValue(mockService); // Get rate limit action - const rateLimitAction = githubPlugin.actions?.find((a) => a.name === 'GET_GITHUB_RATE_LIMIT'); + const rateLimitAction = githubPlugin.actions?.find( + (a) => a.name === "GET_GITHUB_RATE_LIMIT", + ); // Execute action const callback = mock(); await rateLimitAction!.handler( mockRuntime as unknown as IAgentRuntime, { - id: 'test' as UUID, - roomId: 'test' as UUID, - entityId: 'test' as UUID, - agentId: 'test' as UUID, - content: { text: 'check rate limit', source: 'test' }, + id: "test" as UUID, + roomId: "test" as UUID, + entityId: "test" as UUID, + agentId: "test" as UUID, + content: { text: "check rate limit", source: "test" }, createdAt: Date.now(), }, - { values: {}, data: {}, text: '' }, + { values: {}, data: {}, text: "" }, {}, - callback + callback, ); // Verify callback was called with rate limit data @@ -137,7 +139,7 @@ describe('Runtime Integration: GitHub Plugin with runtime.getSetting', () => { limit: 5000, remaining: 4500, }), - }) + }), ); // Verify the service method was actually called diff --git a/src/__tests__/runtime-scenarios.test.ts b/src/__tests__/runtime-scenarios.test.ts index 419d428..7142c17 100644 --- a/src/__tests__/runtime-scenarios.test.ts +++ b/src/__tests__/runtime-scenarios.test.ts @@ -1,6 +1,14 @@ -import { describe, expect, it, mock, beforeEach } from 'bun:test'; -import { githubPlugin, GitHubService } from '../index'; -import { IAgentRuntime, Memory, State, UUID, AgentRuntime, logger } from '@elizaos/core'; +import { describe, it, expect, mock } from "bun:test"; +import type { + IAgentRuntime, + State, + Memory, + Content, + UUID, +} from "@elizaos/core"; +import { logger } from "@elizaos/core"; +import { githubPlugin } from "../index"; +import { GitHubService } from "../services/github"; /** * Runtime Scenario Tests @@ -10,79 +18,81 @@ import { IAgentRuntime, Memory, State, UUID, AgentRuntime, logger } from '@eliza * and real-world usage patterns. */ -describe('Runtime Scenarios: GitHub Plugin', () => { - describe('Plugin Initialization Scenarios', () => { - it('should initialize with runtime.getSetting', async () => { +describe("Runtime Scenarios: GitHub Plugin", () => { + describe("Plugin Initialization Scenarios", () => { + it("should initialize with runtime.getSetting", async () => { const mockRuntime = { getSetting: mock().mockImplementation((key: string) => { - if (key === 'GITHUB_TOKEN') { - return 'ghp_test123456789'; + if (key === "GITHUB_TOKEN") { + return "ghp_test123456789"; } - if (key === 'GITHUB_OWNER') { - return 'test-owner'; + if (key === "GITHUB_OWNER") { + return "test-owner"; } return null; }), - agentId: 'test-agent' as UUID, + agentId: "test-agent" as UUID, } as any as IAgentRuntime; // Initialize plugin await githubPlugin.init!({}, mockRuntime); // Verify getSetting was called - expect(mockRuntime.getSetting).toHaveBeenCalledWith('GITHUB_TOKEN'); + expect(mockRuntime.getSetting).toHaveBeenCalledWith("GITHUB_TOKEN"); }); - it('should handle missing token gracefully in development', async () => { + it("should handle missing token gracefully in development", async () => { const mockRuntime = { getSetting: mock().mockReturnValue(null), - agentId: 'test-agent' as UUID, + agentId: "test-agent" as UUID, character: { - name: 'Test Character', - bio: 'Test bio', + name: "Test Character", + bio: "Test bio", settings: {}, }, } as any as IAgentRuntime; // In test mode, this should not throw - await expect(githubPlugin.init!({}, mockRuntime)).resolves.toBeUndefined(); + await expect( + githubPlugin.init!({}, mockRuntime), + ).resolves.toBeUndefined(); }); - it('should prefer runtime settings over environment variables', async () => { + it("should prefer runtime settings over environment variables", async () => { // Set environment variable - process.env.GITHUB_TOKEN = 'ghp_env_token'; + process.env.GITHUB_TOKEN = "ghp_env_token"; const mockRuntime = { getSetting: mock().mockImplementation((key: string) => { - if (key === 'GITHUB_TOKEN') { - return 'ghp_runtime_token'; + if (key === "GITHUB_TOKEN") { + return "ghp_runtime_token"; } return null; }), - agentId: 'test-agent' as UUID, + agentId: "test-agent" as UUID, } as any as IAgentRuntime; // Create service using static start method const service = await GitHubService.start(mockRuntime); // Verify runtime setting was used - expect(mockRuntime.getSetting).toHaveBeenCalledWith('GITHUB_TOKEN'); + expect(mockRuntime.getSetting).toHaveBeenCalledWith("GITHUB_TOKEN"); // Clean up delete process.env.GITHUB_TOKEN; }); }); - describe('Service Lifecycle Scenarios', () => { - it('should handle service start and stop correctly', async () => { + describe("Service Lifecycle Scenarios", () => { + it("should handle service start and stop correctly", async () => { const mockRuntime = { getSetting: mock().mockImplementation((key: string) => { - if (key === 'GITHUB_TOKEN') { - return 'ghp_test123456789'; + if (key === "GITHUB_TOKEN") { + return "ghp_test123456789"; } return null; }), - agentId: 'test-agent' as UUID, + agentId: "test-agent" as UUID, getService: mock(), registerService: mock(), } as any as IAgentRuntime; @@ -98,19 +108,19 @@ describe('Runtime Scenarios: GitHub Plugin', () => { expect(() => service.getActivityLog(10)).not.toThrow(); }); - it('should register with runtime correctly', async () => { + it("should register with runtime correctly", async () => { const services = new Map(); const mockRuntime = { getSetting: mock().mockImplementation((key: string) => { - if (key === 'GITHUB_TOKEN') { - return 'ghp_test123456789'; + if (key === "GITHUB_TOKEN") { + return "ghp_test123456789"; } return null; }), - agentId: 'test-agent' as UUID, + agentId: "test-agent" as UUID, services, registerService: mock().mockImplementation((service: any) => { - services.set(service.serviceType || 'github', service); + services.set(service.serviceType || "github", service); }), getService: mock().mockImplementation((type: string) => { return services.get(type); @@ -118,41 +128,59 @@ describe('Runtime Scenarios: GitHub Plugin', () => { } as any as IAgentRuntime; // Initialize plugin and service - await githubPlugin.init!({ GITHUB_TOKEN: 'ghp_test123456789' }, mockRuntime); + await githubPlugin.init!( + { GITHUB_TOKEN: "ghp_test123456789" }, + mockRuntime, + ); // Register service const service = await GitHubService.start(mockRuntime); - mockRuntime.registerService(service); + + // Mock the registerService method if it doesn't exist + if (!mockRuntime.registerService) { + mockRuntime.registerService = mock(); + } + mockRuntime.registerService(GitHubService); + + // Mock getService to return our service + mockRuntime.getService = mock().mockImplementation( + (serviceType: string) => { + if (serviceType === "github") { + return service; + } + return null; + }, + ); // Verify service is registered - const retrievedService = mockRuntime.getService('github'); + const retrievedService = mockRuntime.getService("github"); expect(retrievedService).toBe(service); }); }); - describe('Multi-Agent Scenarios', () => { - it('should support multiple agents with different configurations', async () => { + describe("Multi-Agent Scenarios", () => { + it("should support multiple agents with different configurations", async () => { const agent1Runtime = { - agentId: 'agent1' as UUID, + agentId: "agent1" as UUID, getSetting: mock().mockImplementation((key: string) => { - if (key === 'GITHUB_TOKEN') { - return 'ghp_agent1_token'; + if (key === "GITHUB_TOKEN") { + return "ghp_agent1_token"; } - if (key === 'GITHUB_OWNER') { - return 'agent1-owner'; + if (key === "GITHUB_OWNER") { + return "agent1-owner"; } return null; }), } as any as IAgentRuntime; const agent2Runtime = { - agentId: 'agent2' as UUID, + agentId: "agent2" as UUID, getSetting: mock().mockImplementation((key: string) => { - if (key === 'GITHUB_TOKEN') { - return 'ghp_agent2_token'; + if (key === "GITHUB_TOKEN") { + return "ghp_agent2_token"; } - if (key === 'GITHUB_OWNER') { - return 'agent2-owner'; + if (key === "GITHUB_OWNER") { + return "agent2-owner"; } return null; }), @@ -163,109 +191,117 @@ describe('Runtime Scenarios: GitHub Plugin', () => { const service2 = await GitHubService.start(agent2Runtime); // Verify they have different configurations - expect(agent1Runtime.getSetting).toHaveBeenCalledWith('GITHUB_TOKEN'); - expect(agent2Runtime.getSetting).toHaveBeenCalledWith('GITHUB_TOKEN'); + expect(agent1Runtime.getSetting).toHaveBeenCalledWith("GITHUB_TOKEN"); + expect(agent2Runtime.getSetting).toHaveBeenCalledWith("GITHUB_TOKEN"); // Each service maintains its own state expect(service1).not.toBe(service2); }); }); - describe('Provider Runtime Scenarios', () => { - it('should provide context based on runtime state', async () => { + describe("Provider Runtime Scenarios", () => { + it("should provide context based on runtime state", async () => { const mockRuntime = { - agentId: 'test-agent' as UUID, - getSetting: mock().mockReturnValue('ghp_test123'), + agentId: "test-agent" as UUID, + getSetting: mock().mockReturnValue("ghp_test123"), getService: mock().mockReturnValue({ getActivityLog: mock().mockReturnValue([ - { action: 'searchRepositories', timestamp: Date.now(), success: true }, - { action: 'getRepository', timestamp: Date.now(), success: true }, + { + action: "searchRepositories", + timestamp: Date.now(), + success: true, + }, + { action: "getRepository", timestamp: Date.now(), success: true }, ]), - getAuthenticatedUser: mock().mockResolvedValue({ login: 'testuser' }), + getAuthenticatedUser: mock().mockResolvedValue({ login: "testuser" }), }), } as any as IAgentRuntime; // Get activity provider const activityProvider = githubPlugin.providers?.find( - (p) => p.name === 'GITHUB_ACTIVITY_CONTEXT' + (p) => p.name === "GITHUB_ACTIVITY_CONTEXT", ); expect(activityProvider).toBeDefined(); // Get context const context = await activityProvider!.get( mockRuntime, - createMemory('What have I done on GitHub?'), - createState() + createMemory("What have I done on GitHub?"), + createState(), ); - expect(context.text).toContain('GitHub activity'); - expect(mockRuntime.getService).toHaveBeenCalledWith('github'); + expect(context.text).toContain("GitHub activity"); + expect(mockRuntime.getService).toHaveBeenCalledWith("github"); }); }); - describe('Error Recovery Scenarios', () => { - it('should handle service unavailability gracefully', async () => { + describe("Error Recovery Scenarios", () => { + it("should handle service unavailability gracefully", async () => { const mockRuntime = { - agentId: 'test-agent' as UUID, + agentId: "test-agent" as UUID, getSetting: mock(), getService: mock().mockReturnValue(null), // No service available } as any as IAgentRuntime; // Try to use an action without service - const action = githubPlugin.actions?.find((a) => a.name === 'GET_GITHUB_REPOSITORY'); + const action = githubPlugin.actions?.find( + (a) => a.name === "GET_GITHUB_REPOSITORY", + ); expect(action).toBeDefined(); const callback = mock(); await action!.handler( mockRuntime, - createMemory('Get repo details'), + createMemory("Get repo details"), createState(), - { owner: 'test', repo: 'test' }, - callback + { owner: "test", repo: "test" }, + callback, ); // Should handle gracefully expect(callback).toHaveBeenCalledWith( expect.objectContaining({ - text: expect.stringContaining('Failed to get repository'), - }) + text: expect.stringContaining("Failed to get repository"), + }), ); }); - it('should recover from temporary failures', async () => { + it("should recover from temporary failures", async () => { let callCount = 0; const mockService = { getRepository: mock().mockImplementation(() => { callCount++; if (callCount === 1) { - throw new Error('Network error'); + throw new Error("Network error"); } - return { name: 'test-repo', stargazers_count: 100 }; + return { name: "test-repo", stargazers_count: 100 }; }), }; const mockRuntime = { - agentId: 'test-agent' as UUID, + agentId: "test-agent" as UUID, getSetting: mock(), getService: mock().mockReturnValue(mockService), } as any as IAgentRuntime; - const action = githubPlugin.actions?.find((a) => a.name === 'GET_GITHUB_REPOSITORY'); + const action = githubPlugin.actions?.find( + (a) => a.name === "GET_GITHUB_REPOSITORY", + ); const callback = mock(); // First attempt fails await action!.handler( mockRuntime, - createMemory('Get repo'), + createMemory("Get repo"), createState(), - { owner: 'test', repo: 'test' }, - callback + { owner: "test", repo: "test" }, + callback, ); expect(callback).toHaveBeenCalledWith( expect.objectContaining({ - text: expect.stringContaining('Failed to get repository'), - }) + text: expect.stringContaining("Failed to get repository"), + }), ); // Reset callback @@ -274,28 +310,28 @@ describe('Runtime Scenarios: GitHub Plugin', () => { // Second attempt succeeds await action!.handler( mockRuntime, - createMemory('Get repo again'), + createMemory("Get repo again"), createState(), - { owner: 'test', repo: 'test' }, - callback + { owner: "test", repo: "test" }, + callback, ); expect(callback).toHaveBeenCalledWith( expect.objectContaining({ - repository: expect.objectContaining({ name: 'test-repo' }), - }) + repository: expect.objectContaining({ name: "test-repo" }), + }), ); }); }); - describe('Configuration Update Scenarios', () => { - it('should handle runtime configuration changes', async () => { - let currentToken = 'ghp_initial_token'; + describe("Configuration Update Scenarios", () => { + it("should handle runtime configuration changes", async () => { + let currentToken = "ghp_initial_token"; const mockRuntime = { - agentId: 'test-agent' as UUID, + agentId: "test-agent" as UUID, getSetting: mock().mockImplementation((key: string) => { - if (key === 'GITHUB_TOKEN') { + if (key === "GITHUB_TOKEN") { return currentToken; } return null; @@ -304,26 +340,26 @@ describe('Runtime Scenarios: GitHub Plugin', () => { // Create initial service const service1 = await GitHubService.start(mockRuntime); - expect(mockRuntime.getSetting).toHaveBeenCalledWith('GITHUB_TOKEN'); + expect(mockRuntime.getSetting).toHaveBeenCalledWith("GITHUB_TOKEN"); // Simulate token update - currentToken = 'ghp_updated_token'; + currentToken = "ghp_updated_token"; // Create new service instance (simulating restart) const service2 = await GitHubService.start(mockRuntime); // Verify new token is used - expect(mockRuntime.getSetting).toHaveBeenCalledWith('GITHUB_TOKEN'); + expect(mockRuntime.getSetting).toHaveBeenCalledWith("GITHUB_TOKEN"); expect(service1).not.toBe(service2); }); }); - describe('Performance Scenarios', () => { - it('should handle rate limiting appropriately', async () => { + describe("Performance Scenarios", () => { + it("should handle rate limiting appropriately", async () => { const mockService = { searchRepositories: mock().mockResolvedValue({ total_count: 1000, - items: Array(30).fill({ name: 'repo' }), + items: Array(30).fill({ name: "repo" }), }), getRateLimit: mock().mockResolvedValue({ limit: 60, @@ -333,60 +369,67 @@ describe('Runtime Scenarios: GitHub Plugin', () => { }; const mockRuntime = { - agentId: 'test-agent' as UUID, + agentId: "test-agent" as UUID, getSetting: mock(), getService: mock().mockReturnValue(mockService), } as any as IAgentRuntime; // Check rate limit before heavy operation - const rateLimitAction = githubPlugin.actions?.find((a) => a.name === 'GET_GITHUB_RATE_LIMIT'); + const rateLimitAction = githubPlugin.actions?.find( + (a) => a.name === "GET_GITHUB_RATE_LIMIT", + ); let rateLimit: any; await rateLimitAction!.handler( mockRuntime, - createMemory('Check rate limit'), + createMemory("Check rate limit"), createState(), {}, - (response) => { + async (response) => { rateLimit = response; - } + return []; + }, ); expect(rateLimit.rateLimit.remaining).toBe(5); // Decide whether to proceed based on rate limit if (rateLimit.rateLimit.remaining < 10) { - logger.warn('Rate limit low, deferring operation'); + logger.warn("Rate limit low, deferring operation"); // In real scenario, would defer or queue the operation expect(rateLimit.rateLimit.remaining).toBeLessThan(10); } }); - it('should efficiently handle bulk operations', async () => { + it("should efficiently handle bulk operations", async () => { const mockService = { - getRepository: mock().mockImplementation((owner: string, repo: string) => ({ - name: repo, - owner: { login: owner }, - stargazers_count: Math.floor(Math.random() * 1000), - })), + getRepository: mock().mockImplementation( + (owner: string, repo: string) => ({ + name: repo, + owner: { login: owner }, + stargazers_count: Math.floor(Math.random() * 1000), + }), + ), getActivityLog: mock().mockReturnValue([]), }; const mockRuntime = { - agentId: 'test-agent' as UUID, + agentId: "test-agent" as UUID, getSetting: mock(), getService: mock().mockReturnValue(mockService), } as any as IAgentRuntime; // Simulate bulk repository checks const repos = [ - { owner: 'facebook', repo: 'react' }, - { owner: 'vuejs', repo: 'vue' }, - { owner: 'angular', repo: 'angular' }, + { owner: "facebook", repo: "react" }, + { owner: "vuejs", repo: "vue" }, + { owner: "angular", repo: "angular" }, ]; - const results = []; - const action = githubPlugin.actions?.find((a) => a.name === 'GET_GITHUB_REPOSITORY'); + const results: any[] = []; + const action = githubPlugin.actions?.find( + (a) => a.name === "GET_GITHUB_REPOSITORY", + ); for (const { owner, repo } of repos) { await action!.handler( @@ -394,12 +437,10 @@ describe('Runtime Scenarios: GitHub Plugin', () => { createMemory(`Check ${owner}/${repo}`), createState({ previousResults: results }), { owner, repo }, - (response) => { - results.push({ - repo: response.repository, - timestamp: Date.now(), - }); - } + async (response: Content) => { + results.push(response); + return []; + }, ); } @@ -408,15 +449,18 @@ describe('Runtime Scenarios: GitHub Plugin', () => { expect(mockService.getRepository).toHaveBeenCalledTimes(3); // Activity should be logged - const activityAction = githubPlugin.actions?.find((a) => a.name === 'GET_GITHUB_ACTIVITY'); + const activityAction = githubPlugin.actions?.find( + (a) => a.name === "GET_GITHUB_ACTIVITY", + ); await activityAction!.handler( mockRuntime, - createMemory('Show activity'), + createMemory("Show activity"), createState(), { limit: 10 }, - (response) => { - expect(response).toBeDefined(); - } + async (response: Content) => { + results.push(response); + return []; + }, ); }); }); @@ -426,10 +470,10 @@ describe('Runtime Scenarios: GitHub Plugin', () => { function createMemory(text: string): Memory { return { id: `mem-${Date.now()}-${Math.random()}` as UUID, - roomId: 'test-room' as UUID, - entityId: 'test-entity' as UUID, - agentId: 'test-agent' as UUID, - content: { text, source: 'test' }, + roomId: "test-room" as UUID, + entityId: "test-entity" as UUID, + agentId: "test-agent" as UUID, + content: { text, source: "test" }, createdAt: Date.now(), }; } @@ -438,6 +482,6 @@ function createState(data: Record = {}): State { return { values: {}, data, - text: '', + text: "", }; } diff --git a/src/__tests__/test-helpers.ts b/src/__tests__/test-helpers.ts index c58284f..b4a61a1 100644 --- a/src/__tests__/test-helpers.ts +++ b/src/__tests__/test-helpers.ts @@ -1,6 +1,11 @@ -import { IAgentRuntime, Memory, State, UUID } from '@elizaos/core'; -import { GitHubService } from '../services/github'; -import { mock, expect } from 'bun:test'; +import { IAgentRuntime, Memory, State, UUID } from "@elizaos/core"; +import { + createMockRuntime, + createMockMemory, + createMockState, +} from "@elizaos/test-utils"; +import { GitHubService } from "../services/github"; +import { expect, describe } from "bun:test"; // Define types inline since they're not exported from types.ts interface Repository { @@ -45,22 +50,36 @@ interface Issue { number: number; title: string; body: string | null; - state: 'open' | 'closed'; + state: "open" | "closed"; created_at: string; updated_at: string; + closed_at: string | null; user: { login: string; id: number; avatar_url: string; type: string; }; - labels: any[]; - assignee: any; - assignees: any[]; + labels: Array<{ + id: number; + name: string; + color: string; + description: string; + }>; + assignees: Array<{ + login: string; + id: number; + avatar_url: string; + type: string; + }>; + milestone: any; comments: number; - closed_at: string | null; - html_url: string; - [key: string]: any; + pull_request?: { + url: string; + html_url: string; + diff_url: string; + patch_url: string; + }; } interface PullRequest { @@ -68,7 +87,7 @@ interface PullRequest { number: number; title: string; body: string | null; - state: 'open' | 'closed' | 'merged'; + state: "open" | "closed" | "merged"; created_at: string; updated_at: string; user: { @@ -111,7 +130,7 @@ export class TestRepoManager { const repoName = `test-${name}-${Date.now()}`; const repo = await this.service.createRepository({ name: repoName, - description: 'Test repository created by ElizaOS GitHub plugin tests', + description: "Test repository created by ElizaOS GitHub plugin tests", private: true, auto_init: true, }); @@ -147,26 +166,26 @@ export class TestRepoManager { await this.service.createOrUpdateFile( login, name, - 'README.md', - '# Test Repository\n\nThis is a test repository for ElizaOS GitHub plugin.', - 'Initial commit', - 'main' + "README.md", + "# Test Repository\n\nThis is a test repository for ElizaOS GitHub plugin.", + "Initial commit", + "main", ); await this.service.createOrUpdateFile( login, name, - 'src/index.js', + "src/index.js", 'console.log("Hello from test repo!");', - 'Add index.js', - 'main' + "Add index.js", + "main", ); // Create a test issue await this.service.createIssue(login, name, { - title: 'Test Issue', - body: 'This is a test issue for automated testing', - labels: ['test', 'automated'], + title: "Test Issue", + body: "This is a test issue for automated testing", + labels: ["test", "automated"], }); } } @@ -178,23 +197,23 @@ export const createTestIssue = (overrides?: Partial): Issue => { return { id: Math.floor(Math.random() * 1000000), number: Math.floor(Math.random() * 1000), - title: 'Test Issue', - body: 'This is a test issue', - state: 'open', + title: "Test Issue", + body: "This is a test issue", + state: "open", created_at: new Date().toISOString(), updated_at: new Date().toISOString(), user: { - login: 'testuser', + login: "testuser", id: 12345, - avatar_url: 'https://example.com/avatar.png', - type: 'User', + avatar_url: "https://example.com/avatar.png", + type: "User", }, labels: [], assignee: null, assignees: [], comments: 0, closed_at: null, - html_url: 'https://github.com/test/repo/issues/1', + html_url: "https://github.com/test/repo/issues/1", ...overrides, } as Issue; }; @@ -203,47 +222,49 @@ export const createTestPR = (overrides?: Partial): PullRequest => { return { id: Math.floor(Math.random() * 1000000), number: Math.floor(Math.random() * 1000), - title: 'Test Pull Request', - body: 'This is a test pull request', - state: 'open', + title: "Test Pull Request", + body: "This is a test pull request", + state: "open", created_at: new Date().toISOString(), updated_at: new Date().toISOString(), user: { - login: 'testuser', + login: "testuser", id: 12345, - avatar_url: 'https://example.com/avatar.png', - type: 'User', + avatar_url: "https://example.com/avatar.png", + type: "User", }, head: { - ref: 'feature-branch', - sha: 'abc123', + ref: "feature-branch", + sha: "abc123", }, base: { - ref: 'main', - sha: 'def456', + ref: "main", + sha: "def456", }, merged: false, mergeable: true, - mergeable_state: 'clean', + mergeable_state: "clean", merged_at: null, - html_url: 'https://github.com/test/repo/pull/1', + html_url: "https://github.com/test/repo/pull/1", ...overrides, } as PullRequest; }; -export const createTestRepository = (overrides?: Partial): Repository => { +export const createTestRepository = ( + overrides?: Partial, +): Repository => { return { id: Math.floor(Math.random() * 1000000), - name: 'test-repo', - full_name: 'testuser/test-repo', + name: "test-repo", + full_name: "testuser/test-repo", owner: { - login: 'testuser', + login: "testuser", id: 12345, - avatar_url: 'https://example.com/avatar.png', - type: 'User', + avatar_url: "https://example.com/avatar.png", + type: "User", }, private: false, - description: 'A test repository', + description: "A test repository", fork: false, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), @@ -252,11 +273,11 @@ export const createTestRepository = (overrides?: Partial): Repositor size: 100, stargazers_count: 10, watchers_count: 10, - language: 'JavaScript', + language: "JavaScript", forks_count: 5, open_issues_count: 3, - default_branch: 'main', - topics: ['test'], + default_branch: "main", + topics: ["test"], has_issues: true, has_projects: true, has_wiki: true, @@ -264,7 +285,7 @@ export const createTestRepository = (overrides?: Partial): Repositor has_downloads: true, archived: false, disabled: false, - visibility: 'public', + visibility: "public", license: null, ...overrides, } as Repository; @@ -273,34 +294,29 @@ export const createTestRepository = (overrides?: Partial): Repositor /** * Memory and State Helpers */ -export const createTestMemory = (text: string, overrides?: Partial): Memory => { +export const createTestMemory = ( + text: string, + overrides?: Partial, +): Memory => { return { id: `test-${Date.now()}-${Math.random()}` as UUID, - roomId: 'test-room' as UUID, - entityId: 'test-entity' as UUID, - agentId: 'test-agent' as UUID, - content: { text, source: 'test' }, + roomId: "test-room" as UUID, + entityId: "test-entity" as UUID, + agentId: "test-agent" as UUID, + content: { text, source: "test" }, createdAt: Date.now(), ...overrides, }; }; -export const createTestState = (data: Record = {}): State => { - return { - values: {}, - data, - text: '', - }; -}; - /** * Assertion Helpers */ export const assertGitHubActionSucceeded = (response: any): void => { expect(response).toBeDefined(); expect(response.text).toBeDefined(); - expect(response.text).not.toContain('Failed'); - expect(response.text).not.toContain('Error'); + expect(response.text).not.toContain("Failed"); + expect(response.text).not.toContain("Error"); expect(response.actions).toBeDefined(); expect(Array.isArray(response.actions)).toBe(true); }; @@ -336,69 +352,376 @@ export const assertValidPullRequest = (pr: any): void => { }; /** - * Environment Helpers + * Test Utilities */ -export const isTestEnvironment = process.env.NODE_ENV === 'test'; -export const hasTestGitHubToken = !!(process.env.GITHUB_TEST_TOKEN || process.env.GITHUB_TOKEN); -export const describeIfTest = isTestEnvironment ? describe : describe.skip; -export const describeIfToken = hasTestGitHubToken ? describe : describe.skip; +/** + * Create a mock agent runtime with GitHub-specific defaults + */ +export function createTestRuntime( + overrides?: Partial, +): IAgentRuntime { + return createMockRuntime({ + // GitHub-specific defaults + getService: (name: string) => { + if (name === "github") { + return createMockGitHubService() as any; + } + return null; + }, + getSetting: (key: string) => { + const settings: Record = { + GITHUB_TOKEN: "test-token", + GITHUB_OWNER: "test-owner", + GITHUB_REPO: "test-repo", + GITHUB_API_URL: "https://api.github.com", + }; + return settings[key] || null; + }, + ...overrides, + }); +} /** - * Mock Helpers + * Create a test message with defaults */ -export const createMockGitHubService = (overrides?: Partial): any => { +export function createTestMessage(overrides?: Partial): Memory { + return createMockMemory({ + entityId: "00000000-0000-0000-0000-000000000001" as UUID, + agentId: "00000000-0000-0000-0000-000000000002" as UUID, + roomId: "00000000-0000-0000-0000-000000000003" as UUID, + content: { + text: "Test message", + source: "github", + }, + ...overrides, + }); +} + +/** + * Create a test state with defaults + */ +export function createTestState(overrides?: Partial): State { + return createMockState({ + recentMessages: [], + providers: [], + ...overrides, + }); +} + +/** + * Mock Data Factories - Keep these for GitHub-specific test data + */ +export const createMockRepository = ( + overrides?: Partial, +): Repository => ({ + id: 1, + name: "test-repo", + full_name: "test-owner/test-repo", + owner: { + login: "test-owner", + id: 1, + avatar_url: "https://avatars.githubusercontent.com/u/1", + type: "User", + }, + private: false, + description: "A test repository", + fork: false, + created_at: "2024-01-01T00:00:00Z", + updated_at: "2024-01-01T00:00:00Z", + pushed_at: "2024-01-01T00:00:00Z", + homepage: null, + size: 100, + stargazers_count: 10, + watchers_count: 10, + language: "TypeScript", + forks_count: 5, + open_issues_count: 3, + default_branch: "main", + topics: ["testing", "github"], + has_issues: true, + has_projects: true, + has_wiki: true, + has_pages: false, + has_downloads: true, + archived: false, + disabled: false, + visibility: "public", + license: null, + ...overrides, +}); + +export const createMockIssue = (overrides?: Partial): Issue => ({ + id: 1, + number: 1, + title: "Test Issue", + body: "This is a test issue body", + state: "open", + created_at: "2024-01-01T00:00:00Z", + updated_at: "2024-01-01T00:00:00Z", + closed_at: null, + user: { + login: "test-user", + id: 1, + avatar_url: "https://avatars.githubusercontent.com/u/1", + type: "User", + }, + labels: [], + assignees: [], + milestone: null, + comments: 0, + ...overrides, +}); + +/** + * GitHub-specific Mock Helpers + */ +export const createMockGitHubService = ( + overrides?: Partial, +): any => { return { - validateAuthentication: mock().mockResolvedValue(true), - getAuthenticatedUser: mock().mockResolvedValue({ login: 'testuser', id: 12345 }), - getRateLimit: mock().mockResolvedValue({ - limit: 5000, - remaining: 4999, - reset: Date.now() / 1000 + 3600, - used: 1, - }), - getRepository: mock().mockResolvedValue(createTestRepository()), - listRepositories: mock().mockResolvedValue([createTestRepository()]), - searchRepositories: mock().mockResolvedValue({ - total_count: 1, - items: [createTestRepository()], - }), - createRepository: mock().mockResolvedValue(createTestRepository()), - getIssue: mock().mockResolvedValue(createTestIssue()), - listIssues: mock().mockResolvedValue([createTestIssue()]), - searchIssues: mock().mockResolvedValue({ - total_count: 1, - items: [createTestIssue()], - }), - createIssue: mock().mockResolvedValue(createTestIssue()), - createIssueComment: mock().mockResolvedValue({ id: 1, body: 'Test comment' }), - getPullRequest: mock().mockResolvedValue(createTestPR()), - listPullRequests: mock().mockResolvedValue([createTestPR()]), - searchPullRequests: mock().mockResolvedValue({ - total_count: 1, - items: [createTestPR()], - }), - createPullRequest: mock().mockResolvedValue(createTestPR()), - mergePullRequest: mock().mockResolvedValue({ merged: true, sha: 'merge-sha' }), - getActivityLog: mock().mockReturnValue([]), - clearActivityLog: mock(), + isConnected: () => Promise.resolve(true), + listRepositories: () => Promise.resolve([createMockRepository()]), + getRepository: () => Promise.resolve(createMockRepository()), + createRepository: () => Promise.resolve(createMockRepository()), + deleteRepository: () => Promise.resolve(undefined), + searchRepositories: () => + Promise.resolve({ + total_count: 1, + incomplete_results: false, + items: [createMockRepository()], + }), + createIssue: () => Promise.resolve(createMockIssue()), + updateIssue: () => Promise.resolve(createMockIssue()), + listIssues: () => Promise.resolve([createMockIssue()]), + getIssue: () => Promise.resolve(createMockIssue()), + searchIssues: () => + Promise.resolve({ + total_count: 1, + incomplete_results: false, + items: [createMockIssue()], + }), ...overrides, }; }; +/** + * Test Environment Helpers + */ +export const isTestEnvironment = process.env.NODE_ENV === "test"; +export const hasTestGitHubToken = !!( + process.env.GITHUB_TEST_TOKEN || process.env.GITHUB_TOKEN +); + +export const describeIfTest: any = isTestEnvironment ? describe : describe.skip; +export const describeIfToken: any = hasTestGitHubToken + ? describe + : describe.skip; + +/** + * Mock Helpers - Keeping remaining GitHub-specific ones + */ +export const createMockPullRequest = (overrides?: Partial): any => ({ + id: 1, + number: 1, + title: "Test Pull Request", + body: "This is a test pull request", + state: "open", + head: { + ref: "feature-branch", + sha: "abc123", + }, + base: { + ref: "main", + sha: "def456", + }, + user: { + login: "test-user", + id: 1, + avatar_url: "https://avatars.githubusercontent.com/u/1", + type: "User", + }, + created_at: "2024-01-01T00:00:00Z", + updated_at: "2024-01-01T00:00:00Z", + merged_at: null, + draft: false, + ...overrides, +}); + +export const createMockUser = (overrides?: Partial): any => ({ + login: "test-user", + id: 1, + node_id: "MDQ6VXNlcjE=", + avatar_url: "https://avatars.githubusercontent.com/u/1", + gravatar_id: "", + url: "https://api.github.com/users/test-user", + html_url: "https://github.com/test-user", + followers_url: "https://api.github.com/users/test-user/followers", + following_url: + "https://api.github.com/users/test-user/following{/other_user}", + gists_url: "https://api.github.com/users/test-user/gists{/gist_id}", + starred_url: "https://api.github.com/users/test-user/starred{/owner}{/repo}", + subscriptions_url: "https://api.github.com/users/test-user/subscriptions", + organizations_url: "https://api.github.com/users/test-user/orgs", + repos_url: "https://api.github.com/users/test-user/repos", + events_url: "https://api.github.com/users/test-user/events{/privacy}", + received_events_url: "https://api.github.com/users/test-user/received_events", + type: "User", + site_admin: false, + name: "Test User", + company: "Test Company", + blog: "https://test-user.com", + location: "Test Location", + email: "test@example.com", + hireable: null, + bio: "Test bio", + twitter_username: "testuser", + public_repos: 10, + public_gists: 5, + followers: 100, + following: 50, + created_at: "2020-01-01T00:00:00Z", + updated_at: "2024-01-01T00:00:00Z", + ...overrides, +}); + +export const createMockActivity = (overrides?: Partial): any => ({ + id: "12345", + type: "PushEvent", + actor: { + login: "test-user", + display_login: "test-user", + gravatar_id: "", + url: "https://api.github.com/users/test-user", + avatar_url: "https://avatars.githubusercontent.com/u/1", + }, + repo: { + id: 1, + name: "test-owner/test-repo", + url: "https://api.github.com/repos/test-owner/test-repo", + }, + payload: { + push_id: 1234567890, + size: 1, + distinct_size: 1, + ref: "refs/heads/main", + head: "abc123", + before: "def456", + commits: [ + { + sha: "abc123", + author: { + email: "test@example.com", + name: "Test User", + }, + message: "Test commit", + distinct: true, + url: "https://api.github.com/repos/test-owner/test-repo/commits/abc123", + }, + ], + }, + public: true, + created_at: "2024-01-01T00:00:00Z", + ...overrides, +}); + +export const createMockBranch = (overrides?: Partial): any => ({ + name: "test-branch", + commit: { + sha: "abc123", + url: "https://api.github.com/repos/test-owner/test-repo/commits/abc123", + }, + protected: false, + ...overrides, +}); + +export const createMockStats = (overrides?: Partial): any => ({ + author: { + login: "test-user", + id: 1, + avatar_url: "https://avatars.githubusercontent.com/u/1", + gravatar_id: "", + url: "https://api.github.com/users/test-user", + html_url: "https://github.com/test-user", + followers_url: "https://api.github.com/users/test-user/followers", + following_url: + "https://api.github.com/users/test-user/following{/other_user}", + gists_url: "https://api.github.com/users/test-user/gists{/gist_id}", + starred_url: + "https://api.github.com/users/test-user/starred{/owner}{/repo}", + subscriptions_url: "https://api.github.com/users/test-user/subscriptions", + organizations_url: "https://api.github.com/users/test-user/orgs", + repos_url: "https://api.github.com/users/test-user/repos", + events_url: "https://api.github.com/users/test-user/events{/privacy}", + received_events_url: + "https://api.github.com/users/test-user/received_events", + type: "User", + site_admin: false, + }, + total: 100, + weeks: [ + { + w: "2024-01-01", + a: 50, + d: 30, + c: 5, + }, + ], + ...overrides, +}); + +/** + * Expectation Helpers + */ +export const expectValidGitHubToken = (token: string | null) => { + expect(token).toBeDefined(); + expect(token).toBeTruthy(); + expect(token?.length).toBeGreaterThan(10); +}; + +export const expectValidRepository = (repo: any) => { + expect(repo).toBeDefined(); + expect(repo.id).toBeDefined(); + expect(repo.name).toBeDefined(); + expect(repo.full_name).toBeDefined(); + expect(repo.owner).toBeDefined(); + expect(repo.owner.login).toBeDefined(); +}; + +export const expectValidIssue = (issue: any) => { + expect(issue).toBeDefined(); + expect(issue.id).toBeDefined(); + expect(issue.number).toBeDefined(); + expect(issue.title).toBeDefined(); + expect(issue.state).toMatch(/^(open|closed)$/); + expect(issue.user).toBeDefined(); + expect(issue.user.login).toBeDefined(); +}; + +export const expectValidPullRequest = (pr: any) => { + expect(pr).toBeDefined(); + expect(pr.id).toBeDefined(); + expect(pr.number).toBeDefined(); + expect(pr.title).toBeDefined(); + expect(pr.state).toMatch(/^(open|closed|merged)$/); + expect(pr.head).toBeDefined(); + expect(pr.base).toBeDefined(); + expect(pr.user).toBeDefined(); +}; + /** * Wait Helpers */ export const waitForCondition = async ( condition: () => boolean, timeout = 5000, - interval = 100 + interval = 100, ): Promise => { const startTime = Date.now(); while (!condition()) { if (Date.now() - startTime > timeout) { - throw new Error('Timeout waiting for condition'); + throw new Error("Timeout waiting for condition"); } await new Promise((resolve) => setTimeout(resolve, interval)); } @@ -409,7 +732,7 @@ export const waitForCondition = async ( */ export const measurePerformance = async ( name: string, - fn: () => Promise + fn: () => Promise, ): Promise<{ result: T; duration: number }> => { const start = performance.now(); const result = await fn(); @@ -419,3 +742,126 @@ export const measurePerformance = async ( return { result, duration }; }; + +/** + * Create a mock repository object for testing + */ +export function createMockRepo( + overrides: Partial = {}, +): Repository { + return { + id: 123456, + name: "test-repo", + full_name: "test-owner/test-repo", + owner: { + login: "test-owner", + id: 1, + avatar_url: "https://github.com/images/error/octocat_happy.gif", + type: "User", + }, + private: false, + html_url: "https://github.com/test-owner/test-repo", + description: "Test repository description", + fork: false, + url: "https://api.github.com/repos/test-owner/test-repo", + forks_url: "https://api.github.com/repos/test-owner/test-repo/forks", + keys_url: "https://api.github.com/repos/test-owner/test-repo/keys{/key_id}", + collaborators_url: + "https://api.github.com/repos/test-owner/test-repo/collaborators{/collaborator}", + teams_url: "https://api.github.com/repos/test-owner/test-repo/teams", + hooks_url: "https://api.github.com/repos/test-owner/test-repo/hooks", + issue_events_url: + "https://api.github.com/repos/test-owner/test-repo/issues/events{/number}", + events_url: "https://api.github.com/repos/test-owner/test-repo/events", + assignees_url: + "https://api.github.com/repos/test-owner/test-repo/assignees{/user}", + branches_url: + "https://api.github.com/repos/test-owner/test-repo/branches{/branch}", + tags_url: "https://api.github.com/repos/test-owner/test-repo/tags", + blobs_url: + "https://api.github.com/repos/test-owner/test-repo/git/blobs{/sha}", + git_tags_url: + "https://api.github.com/repos/test-owner/test-repo/git/tags{/sha}", + git_refs_url: + "https://api.github.com/repos/test-owner/test-repo/git/refs{/sha}", + trees_url: + "https://api.github.com/repos/test-owner/test-repo/git/trees{/sha}", + statuses_url: + "https://api.github.com/repos/test-owner/test-repo/statuses/{sha}", + languages_url: + "https://api.github.com/repos/test-owner/test-repo/languages", + stargazers_url: + "https://api.github.com/repos/test-owner/test-repo/stargazers", + contributors_url: + "https://api.github.com/repos/test-owner/test-repo/contributors", + subscribers_url: + "https://api.github.com/repos/test-owner/test-repo/subscribers", + subscription_url: + "https://api.github.com/repos/test-owner/test-repo/subscription", + commits_url: + "https://api.github.com/repos/test-owner/test-repo/commits{/sha}", + git_commits_url: + "https://api.github.com/repos/test-owner/test-repo/git/commits{/sha}", + comments_url: + "https://api.github.com/repos/test-owner/test-repo/comments{/number}", + issue_comment_url: + "https://api.github.com/repos/test-owner/test-repo/issues/comments{/number}", + contents_url: + "https://api.github.com/repos/test-owner/test-repo/contents/{+path}", + compare_url: + "https://api.github.com/repos/test-owner/test-repo/compare/{base}...{head}", + merges_url: "https://api.github.com/repos/test-owner/test-repo/merges", + archive_url: + "https://api.github.com/repos/test-owner/test-repo/{archive_format}{/ref}", + downloads_url: + "https://api.github.com/repos/test-owner/test-repo/downloads", + issues_url: + "https://api.github.com/repos/test-owner/test-repo/issues{/number}", + pulls_url: + "https://api.github.com/repos/test-owner/test-repo/pulls{/number}", + milestones_url: + "https://api.github.com/repos/test-owner/test-repo/milestones{/number}", + notifications_url: + "https://api.github.com/repos/test-owner/test-repo/notifications{?since,all,participating}", + labels_url: + "https://api.github.com/repos/test-owner/test-repo/labels{/name}", + releases_url: + "https://api.github.com/repos/test-owner/test-repo/releases{/id}", + deployments_url: + "https://api.github.com/repos/test-owner/test-repo/deployments", + created_at: "2020-01-01T00:00:00Z", + updated_at: "2023-01-01T00:00:00Z", + pushed_at: "2023-01-01T00:00:00Z", + git_url: "git://github.com/test-owner/test-repo.git", + ssh_url: "git@github.com:test-owner/test-repo.git", + clone_url: "https://github.com/test-owner/test-repo.git", + svn_url: "https://github.com/test-owner/test-repo", + homepage: null, + size: 100, + stargazers_count: 42, + watchers_count: 42, + language: "TypeScript", + has_issues: true, + has_projects: true, + has_downloads: true, + has_wiki: true, + has_pages: false, + has_discussions: false, + forks_count: 9, + mirror_url: null, + archived: false, + disabled: false, + open_issues_count: 3, + license: null, + allow_forking: true, + is_template: false, + web_commit_signoff_required: false, + topics: ["typescript", "testing"], + visibility: "public", + forks: 9, + open_issues: 3, + watchers: 42, + default_branch: "main", + ...overrides, + }; +} diff --git a/src/actions/activity.ts b/src/actions/activity.ts index b488e49..210560a 100644 --- a/src/actions/activity.ts +++ b/src/actions/activity.ts @@ -1,26 +1,27 @@ import { type Action, + type ActionResult, type Content, type HandlerCallback, type IAgentRuntime, type Memory, type State, logger, -} from '@elizaos/core'; -import { GitHubService } from '../services/github'; +} from "@elizaos/core"; +import { GitHubService } from "../services/github"; // Get GitHub Activity Action export const getGitHubActivityAction: Action = { - name: 'GET_GITHUB_ACTIVITY', - similes: ['SHOW_ACTIVITY', 'GITHUB_LOG', 'ACTIVITY_LOG', 'GITHUB_HISTORY'], - description: 'Shows recent GitHub activity performed by the agent', + name: "GET_GITHUB_ACTIVITY", + similes: ["SHOW_ACTIVITY", "GITHUB_LOG", "ACTIVITY_LOG", "GITHUB_HISTORY"], + description: "Shows recent GitHub activity performed by the agent", validate: async ( runtime: IAgentRuntime, _message: Memory, - _state: State | undefined + _state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -30,15 +31,15 @@ export const getGitHubActivityAction: Action = { state: State | undefined, options: { limit?: number; - filter?: 'all' | 'success' | 'failed'; - resource_type?: 'repository' | 'issue' | 'pull_request'; + filter?: "all" | "success" | "failed"; + resource_type?: "repository" | "issue" | "pull_request"; } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } const limit = options.limit || 20; @@ -47,22 +48,22 @@ export const getGitHubActivityAction: Action = { // Apply filters let filteredActivity = activityLog; - if (options.filter && options.filter !== 'all') { + if (options.filter && options.filter !== "all") { filteredActivity = activityLog.filter((item) => - options.filter === 'success' ? item.success : !item.success + options.filter === "success" ? item.success : !item.success, ); } if (options.resource_type) { filteredActivity = filteredActivity.filter( - (item) => item.resource_type === options.resource_type + (item) => item.resource_type === options.resource_type, ); } if (filteredActivity.length === 0) { const responseContent: Content = { - text: 'No GitHub activity found matching the specified criteria.', - actions: ['GET_GITHUB_ACTIVITY'], + text: "No GitHub activity found matching the specified criteria.", + actions: ["GET_GITHUB_ACTIVITY"], source: message.content.source, }; @@ -71,6 +72,7 @@ export const getGitHubActivityAction: Action = { } return { + success: true, text: responseContent.text, values: { activityCount: 0, @@ -80,7 +82,7 @@ export const getGitHubActivityAction: Action = { activity: [], github: state?.github || {}, }, - }; + } as ActionResult; } // Group activities by date @@ -93,7 +95,7 @@ export const getGitHubActivityAction: Action = { acc[date].push(activity); return acc; }, - {} as Record + {} as Record, ); const activityText = Object.entries(activityByDate) @@ -102,22 +104,22 @@ export const getGitHubActivityAction: Action = { const activityList = activities .map((activity) => { const time = new Date(activity.timestamp).toLocaleTimeString(); - const status = activity.success ? '✅' : '❌'; - const action = activity.action.replace(/_/g, ' ').toLowerCase(); - return ` ${time} ${status} ${action} ${activity.resource_type} ${activity.resource_id}${activity.error ? ` (${activity.error})` : ''}`; + const status = activity.success ? "✅" : "❌"; + const action = activity.action.replace(/_/g, " ").toLowerCase(); + return ` ${time} ${status} ${action} ${activity.resource_type} ${activity.resource_id}${activity.error ? ` (${activity.error})` : ""}`; }) - .join('\n'); + .join("\n"); return `**${date}**\n${activityList}`; }) - .join('\n\n'); + .join("\n\n"); const successCount = filteredActivity.filter((a) => a.success).length; const failureCount = filteredActivity.filter((a) => !a.success).length; const responseContent: Content = { text: `GitHub Activity Log (${filteredActivity.length} entries)\nSuccess: ${successCount} | Failed: ${failureCount}\n\n${activityText}`, - actions: ['GET_GITHUB_ACTIVITY'], + actions: ["GET_GITHUB_ACTIVITY"], source: message.content.source, }; @@ -127,6 +129,7 @@ export const getGitHubActivityAction: Action = { // Return result for chaining return { + success: true, text: responseContent.text, values: { activity: filteredActivity, @@ -146,12 +149,12 @@ export const getGitHubActivityAction: Action = { }, }, }, - }; + } as ActionResult; } catch (error) { - logger.error('Error in GET_GITHUB_ACTIVITY action:', error); + logger.error("Error in GET_GITHUB_ACTIVITY action:", error); const errorContent: Content = { text: `Failed to get GitHub activity: ${error instanceof Error ? error.message : String(error)}`, - actions: ['GET_GITHUB_ACTIVITY'], + actions: ["GET_GITHUB_ACTIVITY"], source: message.content.source, }; @@ -160,6 +163,7 @@ export const getGitHubActivityAction: Action = { } return { + success: false, text: errorContent.text, values: { cleared: false, @@ -175,38 +179,38 @@ export const getGitHubActivityAction: Action = { }, }, }, - }; + } as ActionResult; } }, examples: [ [ { - name: 'User', + name: "User", content: { - text: 'Show me my recent GitHub activity', + text: "Show me my recent GitHub activity", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'GitHub Activity Log (5 entries)\nSuccess: 4 | Failed: 1\n\n**3/21/2024**\n 2:30 PM ✅ create issue issue elizaOS/eliza#43\n 2:25 PM ✅ get repository repository elizaOS/eliza\n 2:20 PM ❌ create repository repository invalid-name (Repository name is invalid)\n\n**3/20/2024**\n 4:15 PM ✅ list repositories repository authenticated_user\n 3:45 PM ✅ search repositories repository search', - actions: ['GET_GITHUB_ACTIVITY'], + text: "GitHub Activity Log (5 entries)\nSuccess: 4 | Failed: 1\n\n**3/21/2024**\n 2:30 PM ✅ create issue issue elizaOS/eliza#43\n 2:25 PM ✅ get repository repository elizaOS/eliza\n 2:20 PM ❌ create repository repository invalid-name (Repository name is invalid)\n\n**3/20/2024**\n 4:15 PM ✅ list repositories repository authenticated_user\n 3:45 PM ✅ search repositories repository search", + actions: ["GET_GITHUB_ACTIVITY"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Show me what went wrong with my recent GitHub operations and check my rate limit', + text: "Show me what went wrong with my recent GitHub operations and check my rate limit", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'GitHub Activity Log (3 failed entries)\nSuccess: 0 | Failed: 3\n\n**3/21/2024**\n 3:45 PM ❌ create pull_request pull_request user/repo#0 (Base branch does not exist)\n 3:30 PM ❌ merge pull_request pull_request user/repo#15 (PR has merge conflicts)\n 3:15 PM ❌ create repository repository @invalid/name (Repository name is invalid)\n\nLet me check your current API rate limit status...', - actions: ['GET_GITHUB_ACTIVITY', 'GET_GITHUB_RATE_LIMIT'], + text: "GitHub Activity Log (3 failed entries)\nSuccess: 0 | Failed: 3\n\n**3/21/2024**\n 3:45 PM ❌ create pull_request pull_request user/repo#0 (Base branch does not exist)\n 3:30 PM ❌ merge pull_request pull_request user/repo#15 (PR has merge conflicts)\n 3:15 PM ❌ create repository repository @invalid/name (Repository name is invalid)\n\nLet me check your current API rate limit status...", + actions: ["GET_GITHUB_ACTIVITY", "GET_GITHUB_RATE_LIMIT"], }, }, ], @@ -215,16 +219,16 @@ export const getGitHubActivityAction: Action = { // Clear GitHub Activity Action export const clearGitHubActivityAction: Action = { - name: 'CLEAR_GITHUB_ACTIVITY', - similes: ['CLEAR_LOG', 'RESET_ACTIVITY', 'CLEAR_HISTORY'], - description: 'Clears the GitHub activity log', + name: "CLEAR_GITHUB_ACTIVITY", + similes: ["CLEAR_LOG", "RESET_ACTIVITY", "CLEAR_HISTORY"], + description: "Clears the GitHub activity log", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -233,19 +237,19 @@ export const clearGitHubActivityAction: Action = { message: Memory, state: State | undefined, options: {} = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } githubService.clearActivityLog(); const responseContent: Content = { - text: 'GitHub activity log has been cleared.', - actions: ['CLEAR_GITHUB_ACTIVITY'], + text: "GitHub activity log has been cleared.", + actions: ["CLEAR_GITHUB_ACTIVITY"], source: message.content.source, }; @@ -255,6 +259,7 @@ export const clearGitHubActivityAction: Action = { // Return result for chaining return { + success: true, text: responseContent.text, values: { cleared: true, @@ -270,12 +275,12 @@ export const clearGitHubActivityAction: Action = { }, }, }, - }; + } as ActionResult; } catch (error) { - logger.error('Error in CLEAR_GITHUB_ACTIVITY action:', error); + logger.error("Error in CLEAR_GITHUB_ACTIVITY action:", error); const errorContent: Content = { text: `Failed to clear GitHub activity: ${error instanceof Error ? error.message : String(error)}`, - actions: ['CLEAR_GITHUB_ACTIVITY'], + actions: ["CLEAR_GITHUB_ACTIVITY"], source: message.content.source, }; @@ -284,6 +289,7 @@ export const clearGitHubActivityAction: Action = { } return { + success: false, text: errorContent.text, values: { cleared: false, @@ -299,38 +305,38 @@ export const clearGitHubActivityAction: Action = { }, }, }, - }; + } as ActionResult; } }, examples: [ [ { - name: 'User', + name: "User", content: { - text: 'Clear my GitHub activity log', + text: "Clear my GitHub activity log", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'GitHub activity log has been cleared.', - actions: ['CLEAR_GITHUB_ACTIVITY'], + text: "GitHub activity log has been cleared.", + actions: ["CLEAR_GITHUB_ACTIVITY"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Clear the activity log and start fresh with listing my repositories', + text: "Clear the activity log and start fresh with listing my repositories", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'GitHub activity log has been cleared.\n\nNow let me list your repositories to start fresh...', - actions: ['CLEAR_GITHUB_ACTIVITY', 'LIST_GITHUB_REPOSITORIES'], + text: "GitHub activity log has been cleared.\n\nNow let me list your repositories to start fresh...", + actions: ["CLEAR_GITHUB_ACTIVITY", "LIST_GITHUB_REPOSITORIES"], }, }, ], @@ -339,16 +345,16 @@ export const clearGitHubActivityAction: Action = { // Get GitHub Rate Limit Action export const getGitHubRateLimitAction: Action = { - name: 'GET_GITHUB_RATE_LIMIT', - similes: ['CHECK_RATE_LIMIT', 'RATE_LIMIT_STATUS', 'API_LIMITS'], - description: 'Shows current GitHub API rate limit status', + name: "GET_GITHUB_RATE_LIMIT", + similes: ["CHECK_RATE_LIMIT", "RATE_LIMIT_STATUS", "API_LIMITS"], + description: "Shows current GitHub API rate limit status", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -357,18 +363,20 @@ export const getGitHubRateLimitAction: Action = { message: Memory, state: State | undefined, options: {} = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } const rateLimit = await githubService.getRateLimit(); const resetTime = new Date(rateLimit.reset * 1000); const now = new Date(); - const resetInMinutes = Math.ceil((resetTime.getTime() - now.getTime()) / (1000 * 60)); + const resetInMinutes = Math.ceil( + (resetTime.getTime() - now.getTime()) / (1000 * 60), + ); const responseContent: Content & { rateLimit?: any; data?: any } = { text: `GitHub API Rate Limit Status: @@ -380,7 +388,7 @@ Reset In: ${resetInMinutes} minutes Resource: ${rateLimit.resource} Usage: ${Math.round((rateLimit.used / rateLimit.limit) * 100)}%`, - actions: ['GET_GITHUB_RATE_LIMIT'], + actions: ["GET_GITHUB_RATE_LIMIT"], source: message.content.source, rateLimit, data: { @@ -395,6 +403,7 @@ Usage: ${Math.round((rateLimit.used / rateLimit.limit) * 100)}%`, // Return result for chaining return { + success: true, text: responseContent.text, values: { rateLimit, @@ -408,12 +417,12 @@ Usage: ${Math.round((rateLimit.used / rateLimit.limit) * 100)}%`, lastRateLimit: rateLimit, }, }, - }; + } as ActionResult; } catch (error) { - logger.error('Error in GET_GITHUB_RATE_LIMIT action:', error); + logger.error("Error in GET_GITHUB_RATE_LIMIT action:", error); const errorContent: Content = { text: `Failed to get rate limit status: ${error instanceof Error ? error.message : String(error)}`, - actions: ['GET_GITHUB_RATE_LIMIT'], + actions: ["GET_GITHUB_RATE_LIMIT"], source: message.content.source, }; @@ -422,6 +431,7 @@ Usage: ${Math.round((rateLimit.used / rateLimit.limit) * 100)}%`, } return { + success: false, text: errorContent.text, values: { cleared: false, @@ -432,38 +442,38 @@ Usage: ${Math.round((rateLimit.used / rateLimit.limit) * 100)}%`, lastRateLimit: null, }, }, - }; + } as ActionResult; } }, examples: [ [ { - name: 'User', + name: "User", content: { - text: 'Check my GitHub API rate limit', + text: "Check my GitHub API rate limit", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'GitHub API Rate Limit Status:\nLimit: 5000 requests per hour\nUsed: 127 requests\nRemaining: 4873 requests\nReset Time: 3/21/2024, 3:45:00 PM\nReset In: 23 minutes\nResource: core\n\nUsage: 3%', - actions: ['GET_GITHUB_RATE_LIMIT'], + text: "GitHub API Rate Limit Status:\nLimit: 5000 requests per hour\nUsed: 127 requests\nRemaining: 4873 requests\nReset Time: 3/21/2024, 3:45:00 PM\nReset In: 23 minutes\nResource: core\n\nUsage: 3%", + actions: ["GET_GITHUB_RATE_LIMIT"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Check if I have enough API calls left to search through 100 repositories', + text: "Check if I have enough API calls left to search through 100 repositories", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'GitHub API Rate Limit Status:\nLimit: 5000 requests per hour\nUsed: 2341 requests\nRemaining: 2659 requests\nReset Time: 3/21/2024, 4:00:00 PM\nReset In: 35 minutes\nResource: core\n\nUsage: 47%\n\nYou have 2659 requests remaining, which is more than enough to search through 100 repositories. Each search typically uses 1-2 API calls, so you should be fine. Let me start the repository search...', - actions: ['GET_GITHUB_RATE_LIMIT', 'SEARCH_GITHUB_REPOSITORIES'], + text: "GitHub API Rate Limit Status:\nLimit: 5000 requests per hour\nUsed: 2341 requests\nRemaining: 2659 requests\nReset Time: 3/21/2024, 4:00:00 PM\nReset In: 35 minutes\nResource: core\n\nUsage: 47%\n\nYou have 2659 requests remaining, which is more than enough to search through 100 repositories. Each search typically uses 1-2 API calls, so you should be fine. Let me start the repository search...", + actions: ["GET_GITHUB_RATE_LIMIT", "SEARCH_GITHUB_REPOSITORIES"], }, }, ], diff --git a/src/actions/autoCoder.ts b/src/actions/autoCoder.ts index 497bf84..7f8ef6b 100644 --- a/src/actions/autoCoder.ts +++ b/src/actions/autoCoder.ts @@ -6,27 +6,27 @@ import { type HandlerCallback, logger, ModelType, -} from '@elizaos/core'; -import { GitHubService } from '../services/github'; -import { z } from 'zod'; +} from "@elizaos/core"; +import { GitHubService } from "../services/github"; +import { z } from "zod"; // Structured schemas for LLM responses const IssueAnalysisSchema = z.object({ canAutomate: z.boolean(), - complexity: z.enum(['simple', 'medium', 'complex']), + complexity: z.enum(["simple", "medium", "complex"]), confidence: z.number().min(0).max(1), reasoning: z.string(), - issueType: z.enum(['bug', 'feature', 'documentation', 'refactor', 'other']), + issueType: z.enum(["bug", "feature", "documentation", "refactor", "other"]), summary: z.string(), requiredFiles: z.array(z.string()), estimatedChanges: z.number().min(0), - riskLevel: z.enum(['low', 'medium', 'high']), + riskLevel: z.enum(["low", "medium", "high"]), dependencies: z.array(z.string()).optional(), }); const CodeChangeSchema = z.object({ file: z.string(), - action: z.enum(['create', 'update', 'delete']), + action: z.enum(["create", "update", "delete"]), content: z.string().optional(), reasoning: z.string(), lineNumbers: z @@ -53,14 +53,14 @@ type CodeGeneration = z.infer; async function shouldAutoCode( runtime: IAgentRuntime, issue: any, - repository: any + repository: any, ): Promise { const prompt = `Analyze this GitHub issue to determine if it's suitable for automated coding: Repository: ${repository.full_name} Issue #${issue.number}: ${issue.title} -Body: ${issue.body || 'No description provided'} -Labels: ${issue.labels.map((l: any) => l.name).join(', ') || 'None'} +Body: ${issue.body || "No description provided"} +Labels: ${issue.labels.map((l: any) => l.name).join(", ") || "None"} Author: ${issue.user.login} Consider: @@ -86,37 +86,41 @@ Respond with JSON: const result = JSON.parse(response); return result.shouldAutoCode && result.confidence > 0.7; } catch (_error) { - logger.warn('Failed to evaluate auto-coding suitability:', _error); + logger.warn("Failed to evaluate auto-coding suitability:", _error); return false; } } export const autoCodeIssueAction: Action = { - name: 'AUTO_CODE_ISSUE', - similes: ['FIX_ISSUE', 'SOLVE_ISSUE', 'AUTO_FIX_ISSUE'], + name: "AUTO_CODE_ISSUE", + similes: ["FIX_ISSUE", "SOLVE_ISSUE", "AUTO_FIX_ISSUE"], description: - 'Intelligently analyze a GitHub issue and create a pull request with a real code fix', + "Intelligently analyze a GitHub issue and create a pull request with a real code fix", examples: [ [ { - name: '{{user}}', + name: "{{user}}", content: { - text: '@agent please fix this issue', + text: "@agent please fix this issue", }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { text: "I'll analyze this issue using AI and create a pull request with a real fix.", - actions: ['AUTO_CODE_ISSUE'], + actions: ["AUTO_CODE_ISSUE"], }, }, ], ], - async validate(runtime: IAgentRuntime, message: Memory, state?: State): Promise { - const githubService = runtime.getService('github'); + async validate( + runtime: IAgentRuntime, + message: Memory, + state?: State, + ): Promise { + const githubService = runtime.getService("github"); if (!githubService) { return false; } @@ -134,33 +138,35 @@ export const autoCodeIssueAction: Action = { async handler( runtime: IAgentRuntime, message: Memory, - state: State = { values: {}, data: {}, text: '' }, + state: State = { values: {}, data: {}, text: "" }, options?: any, - callback?: HandlerCallback + callback?: HandlerCallback, ): Promise { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service is not available'); + throw new Error("GitHub service is not available"); } const { issue, repository, action: eventAction } = options || {}; if (!issue || !repository) { await callback?.({ - text: 'This action requires issue and repository information from a GitHub webhook event.', - thought: 'Missing required webhook data', + text: "This action requires issue and repository information from a GitHub webhook event.", + thought: "Missing required webhook data", }); return; } - logger.info(`Intelligent auto-coding for issue #${issue.number} in ${repository.full_name}`); + logger.info( + `Intelligent auto-coding for issue #${issue.number} in ${repository.full_name}`, + ); // Check if already handled if (issue.assignee && issue.assignee.login !== runtime.character.name) { await callback?.({ text: `Issue #${issue.number} is already assigned to ${issue.assignee.login}. I won't interfere.`, - thought: 'Issue already assigned to someone else', + thought: "Issue already assigned to someone else", }); return; } @@ -169,12 +175,12 @@ export const autoCodeIssueAction: Action = { const analysisPrompt = `Analyze this GitHub issue comprehensively: Repository: ${repository.full_name} -Language: ${repository.language || 'Unknown'} -Description: ${repository.description || 'No description'} +Language: ${repository.language || "Unknown"} +Description: ${repository.description || "No description"} Issue #${issue.number}: ${issue.title} -Body: ${issue.body || 'No description provided'} -Labels: ${issue.labels.map((l: any) => l.name).join(', ') || 'None'} +Body: ${issue.body || "No description provided"} +Labels: ${issue.labels.map((l: any) => l.name).join(", ") || "None"} Author: ${issue.user.login} Provide a detailed analysis as JSON: @@ -201,16 +207,20 @@ Provide a detailed analysis as JSON: try { analysis = IssueAnalysisSchema.parse(JSON.parse(analysisResponse)); } catch (error) { - logger.error('Failed to parse issue analysis:', error); + logger.error("Failed to parse issue analysis:", error); await callback?.({ text: `I analyzed issue #${issue.number} but the analysis was inconclusive. This likely indicates the issue is too complex or ambiguous for automated fixing.`, - thought: 'Failed to parse structured analysis', + thought: "Failed to parse structured analysis", }); return; } // Intelligent decision making based on analysis - if (!analysis.canAutomate || analysis.confidence < 0.6 || analysis.riskLevel === 'high') { + if ( + !analysis.canAutomate || + analysis.confidence < 0.6 || + analysis.riskLevel === "high" + ) { await callback?.({ text: `Issue #${issue.number} analysis complete: @@ -227,7 +237,10 @@ This issue requires human intervention due to its complexity or risk level. I re } // Step 2: Get repository structure for intelligent file analysis - const repoStructure = await analyzeRepositoryStructure(githubService, repository); + const repoStructure = await analyzeRepositoryStructure( + githubService, + repository, + ); // Step 3: Generate code changes using AI with repository context const codeGenPrompt = `Generate specific code changes for this issue: @@ -235,13 +248,13 @@ This issue requires human intervention due to its complexity or risk level. I re Repository: ${repository.full_name} Issue: ${issue.title} Type: ${analysis.issueType} -Files to modify: ${analysis.requiredFiles.join(', ')} +Files to modify: ${analysis.requiredFiles.join(", ")} Repository structure context: ${repoStructure} Issue details: -${issue.body || 'No description provided'} +${issue.body || "No description provided"} Generate actual code changes as JSON: { @@ -271,10 +284,10 @@ Generate actual code changes as JSON: try { codeGeneration = CodeGenerationSchema.parse(JSON.parse(codeResponse)); } catch (error) { - logger.error('Failed to parse code generation:', error); + logger.error("Failed to parse code generation:", error); await callback?.({ text: `I understand what needs to be done for issue #${issue.number}, but I couldn't generate the specific code changes reliably. This suggests the changes are too complex for automated implementation.`, - thought: 'Failed to generate code changes', + thought: "Failed to generate code changes", }); return; } @@ -294,21 +307,21 @@ I recommend having a human developer review this issue.`, // Step 4: Create branch and implement changes const defaultBranch = await githubService.getDefaultBranch( repository.owner.login, - repository.name + repository.name, ); const branchName = `auto-fix/issue-${issue.number}`; const defaultBranchRef = await githubService.getRef( repository.owner.login, repository.name, - `heads/${defaultBranch}` + `heads/${defaultBranch}`, ); await githubService.createBranch( repository.owner.login, repository.name, branchName, - defaultBranchRef.object.sha + defaultBranchRef.object.sha, ); logger.info(`Created branch ${branchName} for issue #${issue.number}`); @@ -318,18 +331,18 @@ I recommend having a human developer review this issue.`, for (const change of codeGeneration.changes) { try { - if (change.action === 'create') { + if (change.action === "create") { await githubService.createOrUpdateFile( repository.owner.login, repository.name, change.file, - change.content || '', + change.content || "", `Auto-fix: Create ${change.file} for issue #${issue.number}`, - branchName + branchName, ); - } else if (change.action === 'update') { + } else if (change.action === "update") { // Get existing file content for intelligent updates - let existingContent = ''; + let existingContent = ""; let fileSha: string | undefined; try { @@ -337,12 +350,14 @@ I recommend having a human developer review this issue.`, repository.owner.login, repository.name, change.file, - defaultBranch + defaultBranch, ); existingContent = fileData.content; fileSha = fileData.sha; } catch (error) { - logger.warn(`File ${change.file} not found, will create new file`); + logger.warn( + `File ${change.file} not found, will create new file`, + ); } // Apply intelligent content update @@ -355,14 +370,14 @@ I recommend having a human developer review this issue.`, updatedContent, `Auto-fix: Update ${change.file} for issue #${issue.number}`, branchName, - fileSha + fileSha, ); - } else if (change.action === 'delete') { + } else if (change.action === "delete") { const fileData = await githubService.getFileContent( repository.owner.login, repository.name, change.file, - defaultBranch + defaultBranch, ); await githubService.deleteFile( @@ -371,14 +386,19 @@ I recommend having a human developer review this issue.`, change.file, `Auto-fix: Delete ${change.file} for issue #${issue.number}`, fileData.sha, - branchName + branchName, ); } changedFiles.push(change.file); - logger.info(`${change.action} ${change.file} in branch ${branchName}`); + logger.info( + `${change.action} ${change.file} in branch ${branchName}`, + ); } catch (error) { - logger.error(`Failed to ${change.action} file ${change.file}:`, error); + logger.error( + `Failed to ${change.action} file ${change.file}:`, + error, + ); // Continue with other changes } } @@ -386,7 +406,7 @@ I recommend having a human developer review this issue.`, if (changedFiles.length === 0) { await callback?.({ text: `I analyzed issue #${issue.number} and generated a solution, but encountered errors applying the changes to the repository. This may be due to file permissions or repository structure changes.`, - thought: 'No files were successfully changed', + thought: "No files were successfully changed", }); return; } @@ -404,19 +424,24 @@ I recommend having a human developer review this issue.`, ### 🔧 Changes Made ${codeGeneration.changes - .map((change) => `- **${change.action.toUpperCase()}** \`${change.file}\`: ${change.reasoning}`) - .join('\n')} + .map( + (change) => + `- **${change.action.toUpperCase()}** \`${change.file}\`: ${change.reasoning}`, + ) + .join("\n")} -### 🧪 Testing ${codeGeneration.testingNeeded ? 'Required' : 'Recommended'} +### 🧪 Testing ${codeGeneration.testingNeeded ? "Required" : "Recommended"} ${ codeGeneration.testingNeeded - ? '⚠️ **Testing is required** before merging these changes.' - : '✅ Changes are low-risk but testing is still recommended.' + ? "⚠️ **Testing is required** before merging these changes." + : "✅ Changes are low-risk but testing is still recommended." } ${ - codeGeneration.deploymentNotes ? `### 📋 Deployment Notes\n${codeGeneration.deploymentNotes}` : '' + codeGeneration.deploymentNotes + ? `### 📋 Deployment Notes\n${codeGeneration.deploymentNotes}` + : "" } ### 🔍 Review Checklist @@ -431,14 +456,20 @@ ${ Fixes #${issue.number}`; - const pr = await githubService.createPullRequest(repository.owner.login, repository.name, { - title: `🤖 Auto-fix: ${issue.title}`, - body: prBody, - head: branchName, - base: defaultBranch, - }); + const pr = await githubService.createPullRequest( + repository.owner.login, + repository.name, + { + title: `🤖 Auto-fix: ${issue.title}`, + body: prBody, + head: branchName, + base: defaultBranch, + }, + ); - logger.info(`Created intelligent PR #${pr.number} for issue #${issue.number}`); + logger.info( + `Created intelligent PR #${pr.number} for issue #${issue.number}`, + ); // Step 7: Comment on the issue with analysis await githubService.createIssueComment( @@ -455,15 +486,15 @@ I've analyzed this issue and created an automated fix: **PR #${pr.number}** - **Risk Level**: ${analysis.riskLevel} - **AI Confidence**: ${Math.round(codeGeneration.confidence * 100)}% -**Files Modified**: ${changedFiles.join(', ')} +**Files Modified**: ${changedFiles.join(", ")} ${ codeGeneration.testingNeeded - ? '⚠️ **Testing is required** before merging.' - : '✅ Changes appear safe but review is recommended.' + ? "⚠️ **Testing is required** before merging." + : "✅ Changes appear safe but review is recommended." } -Please review the PR and let me know if adjustments are needed!` +Please review the PR and let me know if adjustments are needed!`, ); await callback?.({ @@ -475,10 +506,10 @@ Please review the PR and let me know if adjustments are needed!` The solution has been implemented based on AI analysis of the repository structure and issue requirements.`, thought: `Created intelligent automated fix with ${changedFiles.length} changes`, - actions: ['AUTO_CODE_ISSUE'], + actions: ["AUTO_CODE_ISSUE"], }); } catch (error) { - logger.error('Failed to auto-code issue:', error); + logger.error("Failed to auto-code issue:", error); await callback?.({ text: `❌ Failed to create automated fix: ${error instanceof Error ? error.message : String(error)} @@ -487,7 +518,7 @@ This could be due to: - Complex code requirements - API rate limits - File structure changes`, - thought: 'Error in intelligent auto-coding process', + thought: "Error in intelligent auto-coding process", }); } }, @@ -496,49 +527,58 @@ This could be due to: // Helper function for repository analysis async function analyzeRepositoryStructure( githubService: GitHubService, - repository: any + repository: any, ): Promise { try { // Get repository tree structure - const tree = await githubService.getRepositoryTree(repository.owner.login, repository.name); + const tree = await githubService.getRepositoryTree( + repository.owner.login, + repository.name, + ); // Analyze key files and structure const keyFiles = tree .filter( (item: any) => - item.type === 'blob' && - (item.path.endsWith('.js') || - item.path.endsWith('.ts') || - item.path.endsWith('.py') || - item.path.endsWith('.java') || - item.path.endsWith('.md') || - item.path === 'package.json' || - item.path === 'requirements.txt' || - item.path === 'pom.xml') + item.type === "blob" && + (item.path.endsWith(".js") || + item.path.endsWith(".ts") || + item.path.endsWith(".py") || + item.path.endsWith(".java") || + item.path.endsWith(".md") || + item.path === "package.json" || + item.path === "requirements.txt" || + item.path === "pom.xml"), ) .slice(0, 20); // Limit to first 20 files return `Key files found: -${keyFiles.map((f: any) => `- ${f.path}`).join('\n')} +${keyFiles.map((f: any) => `- ${f.path}`).join("\n")} Total files: ${tree.length} -Primary language: ${repository.language || 'Unknown'}`; +Primary language: ${repository.language || "Unknown"}`; } catch (error) { - logger.warn('Could not analyze repository structure:', error); + logger.warn("Could not analyze repository structure:", error); return `Repository: ${repository.full_name} -Language: ${repository.language || 'Unknown'} +Language: ${repository.language || "Unknown"} Structure analysis unavailable`; } } // Schema for mention analysis const MentionAnalysisSchema = z.object({ - requestType: z.enum(['code_fix', 'question', 'review', 'clarification', 'other']), + requestType: z.enum([ + "code_fix", + "question", + "review", + "clarification", + "other", + ]), confidence: z.number().min(0).max(1), reasoning: z.string(), shouldAutoCode: z.boolean(), suggestedResponse: z.string(), - urgency: z.enum(['low', 'medium', 'high']), + urgency: z.enum(["low", "medium", "high"]), requiresHuman: z.boolean(), }); @@ -550,17 +590,17 @@ async function analyzeMention( mentionText: string, issue: any, repository: any, - isComment: boolean + isComment: boolean, ): Promise { const prompt = `Analyze this GitHub mention to understand what the user wants: Repository: ${repository.full_name} -Context: ${isComment ? 'Comment on existing issue' : 'New issue'} +Context: ${isComment ? "Comment on existing issue" : "New issue"} Issue: #${issue.number} - ${issue.title} Mention text: "${mentionText}" -User profile: ${issue.user?.login || 'unknown'} -Issue labels: ${issue.labels?.map((l: any) => l.name).join(', ') || 'none'} +User profile: ${issue.user?.login || "unknown"} +Issue labels: ${issue.labels?.map((l: any) => l.name).join(", ") || "none"} Analyze what they're asking for and how I should respond: @@ -583,45 +623,50 @@ Analyze what they're asking for and how I should respond: return MentionAnalysisSchema.parse(JSON.parse(response)); } catch (error) { - logger.warn('Failed to analyze mention:', error); + logger.warn("Failed to analyze mention:", error); return { - requestType: 'other', + requestType: "other", confidence: 0.1, - reasoning: 'Failed to parse mention intent', + reasoning: "Failed to parse mention intent", shouldAutoCode: false, suggestedResponse: "I see you've mentioned me! Could you please clarify what you'd like me to help with?", - urgency: 'low', + urgency: "low", requiresHuman: false, }; } } export const respondToMentionAction: Action = { - name: 'RESPOND_TO_GITHUB_MENTION', - similes: ['HANDLE_GITHUB_MENTION', 'REPLY_TO_MENTION'], - description: 'Intelligently respond when the agent is mentioned in a GitHub issue or comment', + name: "RESPOND_TO_GITHUB_MENTION", + similes: ["HANDLE_GITHUB_MENTION", "REPLY_TO_MENTION"], + description: + "Intelligently respond when the agent is mentioned in a GitHub issue or comment", examples: [ [ { - name: '{{user}}', + name: "{{user}}", content: { - text: '@agent can you help fix this bug?', + text: "@agent can you help fix this bug?", }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { text: "I'll analyze this issue and see if I can provide an automated fix.", - actions: ['RESPOND_TO_GITHUB_MENTION'], + actions: ["RESPOND_TO_GITHUB_MENTION"], }, }, ], ], - async validate(runtime: IAgentRuntime, message: Memory, state?: State): Promise { - const githubService = runtime.getService('github'); + async validate( + runtime: IAgentRuntime, + message: Memory, + state?: State, + ): Promise { + const githubService = runtime.getService("github"); if (!githubService) { return false; } @@ -632,15 +677,15 @@ export const respondToMentionAction: Action = { return false; } - const mentionText = comment?.body || issue?.body || ''; + const mentionText = comment?.body || issue?.body || ""; const agentName = runtime.character.name; // Use more sophisticated mention detection const mentionPatterns = [ - new RegExp(`@${agentName}\\b`, 'i'), - new RegExp(`@${agentName.toLowerCase()}\\b`, 'i'), - new RegExp(`${agentName}\\b.*help`, 'i'), - new RegExp(`${agentName}\\b.*fix`, 'i'), + new RegExp(`@${agentName}\\b`, "i"), + new RegExp(`@${agentName.toLowerCase()}\\b`, "i"), + new RegExp(`${agentName}\\b.*help`, "i"), + new RegExp(`${agentName}\\b.*fix`, "i"), ]; return mentionPatterns.some((pattern) => pattern.test(mentionText)); @@ -649,23 +694,25 @@ export const respondToMentionAction: Action = { async handler( runtime: IAgentRuntime, message: Memory, - state: State = { values: {}, data: {}, text: '' }, + state: State = { values: {}, data: {}, text: "" }, options?: any, - callback?: HandlerCallback + callback?: HandlerCallback, ): Promise { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service is not available'); + throw new Error("GitHub service is not available"); } const { issue, comment, repository, action: eventAction } = options || {}; if (!repository || !issue) { - throw new Error('Repository and issue information is required'); + throw new Error("Repository and issue information is required"); } - logger.info(`Intelligent mention response in ${repository.full_name}#${issue.number}`); + logger.info( + `Intelligent mention response in ${repository.full_name}#${issue.number}`, + ); const isIssueComment = !!comment; const mentionText = isIssueComment ? comment.body : issue.body; @@ -676,7 +723,7 @@ export const respondToMentionAction: Action = { mentionText, issue, repository, - isIssueComment + isIssueComment, ); // Generate intelligent response based on analysis @@ -687,7 +734,7 @@ export const respondToMentionAction: Action = { responseText += `\n\n*Note: I'm ${Math.round(analysis.confidence * 100)}% confident in understanding your request. Please feel free to clarify if needed.*`; } - if (analysis.urgency === 'high') { + if (analysis.urgency === "high") { responseText = `🚨 **High Priority Request Detected**\n\n${responseText}`; } @@ -700,22 +747,26 @@ export const respondToMentionAction: Action = { repository.owner.login, repository.name, issue.number, - responseText + responseText, ); // Decide whether to trigger auto-coding based on analysis if ( analysis.shouldAutoCode && analysis.confidence > 0.6 && - analysis.requestType === 'code_fix' && + analysis.requestType === "code_fix" && !analysis.requiresHuman ) { - logger.info(`Triggering auto-code for mention with confidence ${analysis.confidence}`); + logger.info( + `Triggering auto-code for mention with confidence ${analysis.confidence}`, + ); // Use the action system to trigger auto-coding try { // Find and execute the auto-code action - const autoCodeAction = runtime.actions.find((a) => a.name === 'AUTO_CODE_ISSUE'); + const autoCodeAction = runtime.actions.find( + (a) => a.name === "AUTO_CODE_ISSUE", + ); if (autoCodeAction) { await autoCodeAction.handler( runtime, @@ -726,18 +777,18 @@ export const respondToMentionAction: Action = { repository, action: eventAction, }, - callback + callback, ); } } catch (error) { - logger.error('Failed to trigger auto-coding:', error); + logger.error("Failed to trigger auto-coding:", error); // Post a follow-up comment about the auto-coding failure await githubService.createIssueComment( repository.owner.login, repository.name, issue.number, - 'I attempted to create an automated fix but encountered an issue. A human developer should review this request.' + "I attempted to create an automated fix but encountered an issue. A human developer should review this request.", ); } } @@ -746,35 +797,35 @@ export const respondToMentionAction: Action = { text: `🤖 **Intelligent mention response generated** **Analysis**: ${analysis.requestType} (confidence: ${Math.round(analysis.confidence * 100)}%) -**Auto-coding**: ${analysis.shouldAutoCode ? 'Triggered' : 'Not suitable'} +**Auto-coding**: ${analysis.shouldAutoCode ? "Triggered" : "Not suitable"} **Urgency**: ${analysis.urgency} **Reasoning**: ${analysis.reasoning}`, thought: `Intelligent mention handling: ${analysis.requestType} with ${Math.round(analysis.confidence * 100)}% confidence`, - actions: ['RESPOND_TO_GITHUB_MENTION'], + actions: ["RESPOND_TO_GITHUB_MENTION"], }); } catch (error) { - logger.error('Failed to handle GitHub mention intelligently:', error); + logger.error("Failed to handle GitHub mention intelligently:", error); // Fallback to simple response try { const { issue, repository } = options || {}; if (issue && repository) { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); await githubService?.createIssueComment( repository.owner.login, repository.name, issue.number, - "I see you've mentioned me, but I encountered an error processing your request. Please try again or contact a human developer." + "I see you've mentioned me, but I encountered an error processing your request. Please try again or contact a human developer.", ); } } catch (fallbackError) { - logger.error('Fallback response also failed:', fallbackError); + logger.error("Fallback response also failed:", fallbackError); } await callback?.({ text: `❌ Failed to handle mention intelligently: ${error instanceof Error ? error.message : String(error)}`, - thought: 'Error in intelligent mention handling', + thought: "Error in intelligent mention handling", }); } }, diff --git a/src/actions/branches.ts b/src/actions/branches.ts index 0759219..179d644 100644 --- a/src/actions/branches.ts +++ b/src/actions/branches.ts @@ -1,26 +1,28 @@ import { type Action, + type ActionResult, type Content, type HandlerCallback, type IAgentRuntime, type Memory, type State, logger, -} from '@elizaos/core'; -import { GitHubService } from '../services/github'; +} from "@elizaos/core"; +import { GitHubService } from "../services/github"; // List Branches Action export const listBranchesAction: Action = { - name: 'LIST_GITHUB_BRANCHES', - similes: ['LIST_BRANCHES', 'SHOW_BRANCHES', 'GET_BRANCHES', 'BRANCH_LIST'], - description: 'Lists branches in a GitHub repository, sorted by last commit date', + name: "LIST_GITHUB_BRANCHES", + similes: ["LIST_BRANCHES", "SHOW_BRANCHES", "GET_BRANCHES", "BRANCH_LIST"], + description: + "Lists branches in a GitHub repository, sorted by last commit date", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -34,29 +36,33 @@ export const listBranchesAction: Action = { protected?: boolean; limit?: number; }, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract owner and repo from message text or options - const text = message.content.text || ''; - const ownerRepoMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/); + const text = message.content.text || ""; + const ownerRepoMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/, + ); const owner = options?.owner || ownerRepoMatch?.[1] || state?.data?.github?.lastRepository?.owner?.login || - runtime.getSetting('GITHUB_OWNER'); + runtime.getSetting("GITHUB_OWNER"); const repo = - options?.repo || ownerRepoMatch?.[2] || state?.data?.github?.lastRepository?.name; + options?.repo || + ownerRepoMatch?.[2] || + state?.data?.github?.lastRepository?.name; if (!owner || !repo) { throw new Error( - 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options' + 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options', ); } @@ -82,16 +88,22 @@ export const listBranchesAction: Action = { const branchesWithDetails = await Promise.all( allBranches.slice(0, options?.limit || 20).map(async (branch) => { try { - const branchData = await githubService.getBranch(owner, repo, branch.name); + const branchData = await githubService.getBranch( + owner, + repo, + branch.name, + ); return { name: branch.name, protected: branch.protected, commit: { sha: branchData.commit.sha, - message: branchData.commit.commit.message.split('\n')[0], - author: branchData.commit.commit.author?.name || 'Unknown', - date: branchData.commit.commit.author?.date || new Date().toISOString(), + message: branchData.commit.commit.message.split("\n")[0], + author: branchData.commit.commit.author?.name || "Unknown", + date: + branchData.commit.commit.author?.date || + new Date().toISOString(), }, }; } catch (error) { @@ -101,40 +113,43 @@ export const listBranchesAction: Action = { protected: branch.protected, commit: { sha: branch.commit.sha, - message: 'Unable to fetch commit details', - author: 'Unknown', + message: "Unable to fetch commit details", + author: "Unknown", date: new Date().toISOString(), }, }; } - }) + }), ); // Sort by commit date (most recent first) branchesWithDetails.sort( - (a, b) => new Date(b.commit.date).getTime() - new Date(a.commit.date).getTime() + (a, b) => + new Date(b.commit.date).getTime() - new Date(a.commit.date).getTime(), ); // Filter by protected status if requested const filteredBranches = options?.protected !== undefined - ? branchesWithDetails.filter((b) => b.protected === options?.protected) + ? branchesWithDetails.filter( + (b) => b.protected === options?.protected, + ) : branchesWithDetails; const branchList = filteredBranches .map((branch) => { const date = new Date(branch.commit.date); const timeAgo = getTimeAgo(date); - return `• ${branch.name}${branch.protected ? ' 🔒' : ''} + return `• ${branch.name}${branch.protected ? " 🔒" : ""} Last commit: ${timeAgo} by ${branch.commit.author} "${branch.commit.message}" SHA: ${branch.commit.sha.substring(0, 7)}`; }) - .join('\n\n'); + .join("\n\n"); const responseContent: Content = { text: `Branches for ${owner}/${repo} (${filteredBranches.length} shown, ${allBranches.length} total):\n\n${branchList}`, - actions: ['LIST_GITHUB_BRANCHES'], + actions: ["LIST_GITHUB_BRANCHES"], source: message.content.source, // Include data for callbacks branches: filteredBranches, @@ -148,6 +163,7 @@ export const listBranchesAction: Action = { // Return result for chaining return { + success: true, text: responseContent.text, values: { branches: filteredBranches, @@ -166,12 +182,12 @@ export const listBranchesAction: Action = { }, }, }, - }; + } as ActionResult; } catch (error) { - logger.error('Error in LIST_GITHUB_BRANCHES action:', error); + logger.error("Error in LIST_GITHUB_BRANCHES action:", error); const errorContent: Content = { text: `Failed to list branches: ${error instanceof Error ? error.message : String(error)}`, - actions: ['LIST_GITHUB_BRANCHES'], + actions: ["LIST_GITHUB_BRANCHES"], source: message.content.source, }; @@ -179,38 +195,42 @@ export const listBranchesAction: Action = { await callback(errorContent); } - return errorContent; + return { + success: false, + text: errorContent.text, + error: error instanceof Error ? error.message : String(error), + } as ActionResult; } }, examples: [ [ { - name: 'User', + name: "User", content: { - text: 'List branches for facebook/react', + text: "List branches for facebook/react", }, }, { - name: 'Assistant', + name: "Assistant", content: { text: 'Branches for facebook/react (20 shown, 145 total):\n\n• main 🔒\n Last commit: 2 hours ago by React Bot\n "Update experimental API docs"\n SHA: abc1234\n\n• 18.x 🔒\n Last commit: 3 days ago by Dan Abramov\n "Backport fix for hydration warning"\n SHA: def5678\n\n• experimental\n Last commit: 5 days ago by Andrew Clark\n "Experimental: Add new Suspense features"\n SHA: ghi9012', - actions: ['LIST_GITHUB_BRANCHES'], + actions: ["LIST_GITHUB_BRANCHES"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Show me the most recently updated branches in my project repo and check for stale branches', + text: "Show me the most recently updated branches in my project repo and check for stale branches", }, }, { - name: 'Assistant', + name: "Assistant", content: { text: 'Branches for user/project (15 shown, 23 total):\n\n• feature/new-api\n Last commit: 1 hour ago by You\n "Add REST API endpoints"\n SHA: abc1234\n\n• main 🔒\n Last commit: 2 days ago by GitHub Actions\n "Merge pull request #45 from user/feature/auth"\n SHA: def5678\n\n• feature/auth\n Last commit: 3 days ago by You\n "Implement JWT authentication"\n SHA: ghi9012\n\n• bugfix/memory-leak\n Last commit: 2 weeks ago by Contributor\n "Fix memory leak in service worker"\n SHA: jkl3456\n\n• feature/old-feature\n Last commit: 3 months ago by Former Contributor\n "WIP: Started new feature"\n SHA: mno7890\n\nI notice you have some potentially stale branches:\n- feature/old-feature: Last updated 3 months ago\n- Several other branches not shown are over 6 months old\n\nWould you like me to help identify which branches can be safely deleted?', - actions: ['LIST_GITHUB_BRANCHES'], + actions: ["LIST_GITHUB_BRANCHES"], }, }, ], @@ -219,17 +239,16 @@ export const listBranchesAction: Action = { // Create Branch Action export const createBranchAction: Action = { - name: 'CREATE_GITHUB_BRANCH', - similes: ['NEW_BRANCH', 'MAKE_BRANCH', 'BRANCH_FROM'], - description: 'Creates a new branch in a GitHub repository', - enabled: false, // Disabled by default - branch creation modifies repository state + name: "CREATE_GITHUB_BRANCH", + similes: ["NEW_BRANCH", "MAKE_BRANCH", "BRANCH_FROM"], + description: "Creates a new branch in a GitHub repository", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -243,38 +262,48 @@ export const createBranchAction: Action = { branch?: string; from?: string; } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract parameters from message text or options - const text = message.content.text || ''; - const ownerRepoMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/); - const branchMatch = text.match(/(?:branch|called|named)\s+["\']?([a-zA-Z0-9._\/-]+)["\']?/i); - const fromMatch = text.match(/(?:from|based on|off)\s+["\']?([a-zA-Z0-9._\/-]+)["\']?/i); + const text = message.content.text || ""; + const ownerRepoMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/, + ); + const branchMatch = text.match( + /(?:branch|called|named)\s+["\']?([a-zA-Z0-9._\/-]+)["\']?/i, + ); + const fromMatch = text.match( + /(?:from|based on|off)\s+["\']?([a-zA-Z0-9._\/-]+)["\']?/i, + ); const owner = options?.owner || ownerRepoMatch?.[1] || state?.data?.github?.lastRepository?.owner?.login || - runtime.getSetting('GITHUB_OWNER'); + runtime.getSetting("GITHUB_OWNER"); const repo = - options?.repo || ownerRepoMatch?.[2] || state?.data?.github?.lastRepository?.name; + options?.repo || + ownerRepoMatch?.[2] || + state?.data?.github?.lastRepository?.name; const branch = options.branch || branchMatch?.[1]; - const from = options.from || fromMatch?.[1] || 'main'; + const from = options.from || fromMatch?.[1] || "main"; if (!owner || !repo) { throw new Error( - 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options' + 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options', ); } if (!branch) { - throw new Error('Branch name is required. Please specify the name for the new branch'); + throw new Error( + "Branch name is required. Please specify the name for the new branch", + ); } logger.info(`Creating branch ${branch} from ${from} in ${owner}/${repo}`); @@ -283,14 +312,19 @@ export const createBranchAction: Action = { const refData = await githubService.getRef(owner, repo, `heads/${from}`); // Create the new branch - const _newBranch = await githubService.createBranch(owner, repo, branch, refData.object.sha); + const _newBranch = await githubService.createBranch( + owner, + repo, + branch, + refData.object.sha, + ); const responseContent: Content = { text: `Successfully created branch "${branch}" from "${from}" in ${owner}/${repo} Branch: ${branch} Based on: ${from} (${refData.object.sha.substring(0, 7)}) Created at: ${new Date().toLocaleString()}`, - actions: ['CREATE_GITHUB_BRANCH'], + actions: ["CREATE_GITHUB_BRANCH"], source: message.content.source, }; @@ -300,6 +334,7 @@ Created at: ${new Date().toLocaleString()}`, // Return result for chaining return { + success: true, text: responseContent.text, values: { branch, @@ -318,12 +353,12 @@ Created at: ${new Date().toLocaleString()}`, lastCreatedBranch: branch, }, }, - }; + } as ActionResult; } catch (error) { - logger.error('Error in CREATE_GITHUB_BRANCH action:', error); + logger.error("Error in CREATE_GITHUB_BRANCH action:", error); const errorContent: Content = { text: `Failed to create branch: ${error instanceof Error ? error.message : String(error)}`, - actions: ['CREATE_GITHUB_BRANCH'], + actions: ["CREATE_GITHUB_BRANCH"], source: message.content.source, }; @@ -331,38 +366,42 @@ Created at: ${new Date().toLocaleString()}`, await callback(errorContent); } - return errorContent; + return { + success: false, + text: errorContent.text, + error: error instanceof Error ? error.message : String(error), + } as ActionResult; } }, examples: [ [ { - name: 'User', + name: "User", content: { - text: 'Create a new branch called feature/payment-integration from main in my project repo', + text: "Create a new branch called feature/payment-integration from main in my project repo", }, }, { - name: 'Assistant', + name: "Assistant", content: { text: 'Successfully created branch "feature/payment-integration" from "main" in user/project\nBranch: feature/payment-integration\nBased on: main (abc1234)\nCreated at: 3/21/2024, 2:30:45 PM', - actions: ['CREATE_GITHUB_BRANCH'], + actions: ["CREATE_GITHUB_BRANCH"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Create a bugfix branch for issue #123 based on the release-v2 branch', + text: "Create a bugfix branch for issue #123 based on the release-v2 branch", }, }, { - name: 'Assistant', + name: "Assistant", content: { text: 'I\'ll create a bugfix branch for issue #123. Let me first check the issue details...\n\nIssue #123: Fix user authentication timeout\nRepository: user/api-server\nState: open\nLabels: bug, priority-high\n\nNow creating the branch...\n\nSuccessfully created branch "bugfix/issue-123-auth-timeout" from "release-v2" in user/api-server\nBranch: bugfix/issue-123-auth-timeout\nBased on: release-v2 (def5678)\nCreated at: 3/21/2024, 2:35:15 PM\n\nThe branch is ready for your fix. Would you like me to create a pull request draft for this issue?', - actions: ['GET_GITHUB_ISSUE', 'CREATE_GITHUB_BRANCH'], + actions: ["GET_GITHUB_ISSUE", "CREATE_GITHUB_BRANCH"], }, }, ], @@ -371,16 +410,16 @@ Created at: ${new Date().toLocaleString()}`, // Get Branch Protection Action export const getBranchProtectionAction: Action = { - name: 'GET_BRANCH_PROTECTION', - similes: ['CHECK_PROTECTION', 'BRANCH_RULES', 'PROTECTION_STATUS'], - description: 'Gets branch protection rules for a GitHub repository branch', + name: "GET_BRANCH_PROTECTION", + similes: ["CHECK_PROTECTION", "BRANCH_RULES", "PROTECTION_STATUS"], + description: "Gets branch protection rules for a GitHub repository branch", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -393,41 +432,57 @@ export const getBranchProtectionAction: Action = { repo?: string; branch?: string; } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract parameters from message text or options - const text = message.content.text || ''; - const ownerRepoMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/); - const branchMatch = text.match(/(?:branch\s+)?["\']?([a-zA-Z0-9._\/-]+)["\']?/i); + const text = message.content.text || ""; + const ownerRepoMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/, + ); + const branchMatch = text.match( + /(?:branch\s+)?["\']?([a-zA-Z0-9._\/-]+)["\']?/i, + ); const owner = options?.owner || ownerRepoMatch?.[1] || state?.data?.github?.lastRepository?.owner?.login || - runtime.getSetting('GITHUB_OWNER'); + runtime.getSetting("GITHUB_OWNER"); const repo = - options?.repo || ownerRepoMatch?.[2] || state?.data?.github?.lastRepository?.name; + options?.repo || + ownerRepoMatch?.[2] || + state?.data?.github?.lastRepository?.name; const branch = - options.branch || branchMatch?.[1] || state?.github?.lastCreatedBranch || 'main'; + options.branch || + branchMatch?.[1] || + state?.github?.lastCreatedBranch || + "main"; if (!owner || !repo) { throw new Error( - 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options' + 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options', ); } - logger.info(`Getting branch protection for ${branch} in ${owner}/${repo}`); + logger.info( + `Getting branch protection for ${branch} in ${owner}/${repo}`, + ); try { - const protection = await githubService.getBranchProtection(owner, repo, branch); + const protection = await githubService.getBranchProtection( + owner, + repo, + branch, + ); - const requiredChecks = protection.required_status_checks?.contexts || []; + const requiredChecks = + protection.required_status_checks?.contexts || []; const requiredReviews = protection.required_pull_request_reviews; const restrictions = protection.restrictions; @@ -435,26 +490,26 @@ export const getBranchProtectionAction: Action = { text: `Branch Protection for "${branch}" in ${owner}/${repo}: **Status Checks:** -${protection.required_status_checks ? `✅ Required checks: ${requiredChecks.join(', ') || 'None specified'}` : '❌ No required status checks'} +${protection.required_status_checks ? `✅ Required checks: ${requiredChecks.join(", ") || "None specified"}` : "❌ No required status checks"} **Pull Request Reviews:** ${ requiredReviews ? `✅ Required approvals: ${requiredReviews.required_approving_review_count || 1} -✅ Dismiss stale reviews: ${requiredReviews.dismiss_stale_reviews ? 'Yes' : 'No'} -✅ Require code owner reviews: ${requiredReviews.require_code_owner_reviews ? 'Yes' : 'No'}` - : '❌ No review requirements' +✅ Dismiss stale reviews: ${requiredReviews.dismiss_stale_reviews ? "Yes" : "No"} +✅ Require code owner reviews: ${requiredReviews.require_code_owner_reviews ? "Yes" : "No"}` + : "❌ No review requirements" } **Restrictions:** -${restrictions ? `✅ Restricted to: ${restrictions.users?.map((u: any) => `@${u.login}`).join(', ') || 'No users'}, ${restrictions.teams?.map((t: any) => t.name).join(', ') || 'No teams'}` : '❌ No push restrictions'} +${restrictions ? `✅ Restricted to: ${restrictions.users?.map((u: any) => `@${u.login}`).join(", ") || "No users"}, ${restrictions.teams?.map((t: any) => t.name).join(", ") || "No teams"}` : "❌ No push restrictions"} **Other Settings:** -• Enforce admins: ${protection.enforce_admins?.enabled ? 'Yes' : 'No'} -• Allow force pushes: ${protection.allow_force_pushes?.enabled ? 'Yes' : 'No'} -• Allow deletions: ${protection.allow_deletions?.enabled ? 'Yes' : 'No'} -• Required linear history: ${protection.required_linear_history?.enabled ? 'Yes' : 'No'}`, - actions: ['GET_BRANCH_PROTECTION'], +• Enforce admins: ${protection.enforce_admins?.enabled ? "Yes" : "No"} +• Allow force pushes: ${protection.allow_force_pushes?.enabled ? "Yes" : "No"} +• Allow deletions: ${protection.allow_deletions?.enabled ? "Yes" : "No"} +• Required linear history: ${protection.required_linear_history?.enabled ? "Yes" : "No"}`, + actions: ["GET_BRANCH_PROTECTION"], source: message.content.source, }; @@ -463,6 +518,7 @@ ${restrictions ? `✅ Restricted to: ${restrictions.users?.map((u: any) => `@${u } return { + success: true, text: responseContent.text, values: { protection, @@ -479,12 +535,12 @@ ${restrictions ? `✅ Restricted to: ${restrictions.users?.map((u: any) => `@${u }, }, }, - }; + } as ActionResult; } catch (error: any) { if (error.status === 404) { const responseContent: Content = { text: `Branch "${branch}" in ${owner}/${repo} has no protection rules configured.`, - actions: ['GET_BRANCH_PROTECTION'], + actions: ["GET_BRANCH_PROTECTION"], source: message.content.source, }; @@ -493,6 +549,7 @@ ${restrictions ? `✅ Restricted to: ${restrictions.users?.map((u: any) => `@${u } return { + success: true, text: responseContent.text, values: { protected: false, @@ -502,15 +559,15 @@ ${restrictions ? `✅ Restricted to: ${restrictions.users?.map((u: any) => `@${u data: { github: state?.github || {}, }, - }; + } as ActionResult; } throw error; } } catch (error) { - logger.error('Error in GET_BRANCH_PROTECTION action:', error); + logger.error("Error in GET_BRANCH_PROTECTION action:", error); const errorContent: Content = { text: `Failed to get branch protection: ${error instanceof Error ? error.message : String(error)}`, - actions: ['GET_BRANCH_PROTECTION'], + actions: ["GET_BRANCH_PROTECTION"], source: message.content.source, }; @@ -518,23 +575,27 @@ ${restrictions ? `✅ Restricted to: ${restrictions.users?.map((u: any) => `@${u await callback(errorContent); } - return errorContent; + return { + success: false, + text: errorContent.text, + error: error instanceof Error ? error.message : String(error), + } as ActionResult; } }, examples: [ [ { - name: 'User', + name: "User", content: { - text: 'Check branch protection for main branch in facebook/react', + text: "Check branch protection for main branch in facebook/react", }, }, { - name: 'Assistant', + name: "Assistant", content: { text: 'Branch Protection for "main" in facebook/react:\n\n**Status Checks:**\n✅ Required checks: ci/circleci, continuous-integration/travis-ci\n\n**Pull Request Reviews:**\n✅ Required approvals: 2\n✅ Dismiss stale reviews: Yes\n✅ Require code owner reviews: Yes\n\n**Restrictions:**\n✅ Restricted to: @react-core-team\n\n**Other Settings:**\n• Enforce admins: No\n• Allow force pushes: No\n• Allow deletions: No\n• Required linear history: Yes', - actions: ['GET_BRANCH_PROTECTION'], + actions: ["GET_BRANCH_PROTECTION"], }, }, ], @@ -552,19 +613,19 @@ function getTimeAgo(date: Date): string { const diffMonths = Math.floor(diffMs / 2592000000); if (diffMins < 1) { - return 'just now'; + return "just now"; } if (diffMins < 60) { - return `${diffMins} minute${diffMins === 1 ? '' : 's'} ago`; + return `${diffMins} minute${diffMins === 1 ? "" : "s"} ago`; } if (diffHours < 24) { - return `${diffHours} hour${diffHours === 1 ? '' : 's'} ago`; + return `${diffHours} hour${diffHours === 1 ? "" : "s"} ago`; } if (diffDays < 7) { - return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`; + return `${diffDays} day${diffDays === 1 ? "" : "s"} ago`; } if (diffWeeks < 4) { - return `${diffWeeks} week${diffWeeks === 1 ? '' : 's'} ago`; + return `${diffWeeks} week${diffWeeks === 1 ? "" : "s"} ago`; } - return `${diffMonths} month${diffMonths === 1 ? '' : 's'} ago`; + return `${diffMonths} month${diffMonths === 1 ? "" : "s"} ago`; } diff --git a/src/actions/issues.ts b/src/actions/issues.ts index 3834738..2f34291 100644 --- a/src/actions/issues.ts +++ b/src/actions/issues.ts @@ -8,22 +8,26 @@ import { type Memory, type State, logger, -} from '@elizaos/core'; -import { GitHubService, type GitHubIssue, type CreateIssueOptions } from '../index'; +} from "@elizaos/core"; +import { + GitHubService, + type GitHubIssue, + type CreateIssueOptions, +} from "../index"; // Get Issue Action export const getIssueAction: Action = { - name: 'GET_GITHUB_ISSUE', - similes: ['CHECK_ISSUE', 'FETCH_ISSUE', 'ISSUE_INFO', 'INSPECT_ISSUE'], + name: "GET_GITHUB_ISSUE", + similes: ["CHECK_ISSUE", "FETCH_ISSUE", "ISSUE_INFO", "INSPECT_ISSUE"], description: - 'Retrieves information about a specific GitHub issue and enables chaining with actions like listing pull requests, creating related issues, or analyzing issue patterns', + "Retrieves information about a specific GitHub issue and enables chaining with actions like listing pull requests, creating related issues, or analyzing issue patterns", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -32,18 +36,22 @@ export const getIssueAction: Action = { message: Memory, state: State | undefined, options: { owner?: string; repo?: string; issue_number?: number } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ): Promise => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract owner, repo, and issue number from message text or options - const text = message.content.text || ''; - const issueMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)\/issues\/(\d+)/); - const ownerRepoMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/); + const text = message.content.text || ""; + const issueMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)\/issues\/(\d+)/, + ); + const ownerRepoMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/, + ); const issueNumMatch = text.match(/(?:issue\s*#?|#)(\d+)/i); const owner = @@ -51,48 +59,55 @@ export const getIssueAction: Action = { issueMatch?.[1] || ownerRepoMatch?.[1] || state?.github?.lastRepository?.owner?.login || - runtime.getSetting('GITHUB_OWNER'); + runtime.getSetting("GITHUB_OWNER"); const repo = options.repo || issueMatch?.[2] || ownerRepoMatch?.[2] || state?.github?.lastRepository?.name; const issue_number = - options.issue_number || parseInt(issueMatch?.[3] || issueNumMatch?.[1] || '0', 10); + options.issue_number || + parseInt(issueMatch?.[3] || issueNumMatch?.[1] || "0", 10); if (!owner || !repo || !issue_number) { throw new Error( - 'Repository owner, name, and issue number are required. Please specify as "owner/repo#123" or provide them in options' + 'Repository owner, name, and issue number are required. Please specify as "owner/repo#123" or provide them in options', ); } - logger.info(`Getting issue information for ${owner}/${repo}#${issue_number}`); + logger.info( + `Getting issue information for ${owner}/${repo}#${issue_number}`, + ); const issue = await githubService.getIssue(owner, repo, issue_number); const labels = issue.labels - ?.map((label: any) => (typeof label === 'string' ? label : label.name || '')) - .join(', ') || ''; + ?.map((label: any) => + typeof label === "string" ? label : label.name || "", + ) + .join(", ") || ""; const assignees = - issue.assignees?.map((assignee: any) => `@${assignee.login}`).join(', ') || ''; + issue.assignees + ?.map((assignee: any) => `@${assignee.login}`) + .join(", ") || ""; const responseContent: Content = { text: `Issue #${issue.number}: ${issue.title} Repository: ${owner}/${repo} State: ${issue.state} -Author: @${issue.user?.login || 'unknown'} +Author: @${issue.user?.login || "unknown"} Created: ${new Date(issue.created_at).toLocaleDateString()} Updated: ${new Date(issue.updated_at).toLocaleDateString()} Comments: ${issue.comments} -Labels: ${labels || 'None'} -Assignees: ${assignees || 'None'} -${issue.milestone ? `Milestone: ${issue.milestone.title}` : ''} +Labels: ${labels || "None"} +Assignees: ${assignees || "None"} +${issue.milestone ? `Milestone: ${issue.milestone.title}` : ""} Description: -${issue.body || 'No description provided'} +${issue.body || "No description provided"} URL: ${issue.html_url}`, - actions: ['GET_GITHUB_ISSUE'], + actions: ["GET_GITHUB_ISSUE"], source: message.content.source, }; @@ -111,7 +126,7 @@ URL: ${issue.html_url}`, issueState: issue.state, }, data: { - actionName: 'GET_GITHUB_ISSUE', + actionName: "GET_GITHUB_ISSUE", issue, github: { ...state?.github, @@ -124,10 +139,10 @@ URL: ${issue.html_url}`, }, }; } catch (error) { - logger.error('Error in GET_GITHUB_ISSUE action:', error); + logger.error("Error in GET_GITHUB_ISSUE action:", error); const errorContent: Content = { text: `Failed to get issue information: ${error instanceof Error ? error.message : String(error)}`, - actions: ['GET_GITHUB_ISSUE'], + actions: ["GET_GITHUB_ISSUE"], source: message.content.source, }; @@ -142,7 +157,7 @@ URL: ${issue.html_url}`, error: error instanceof Error ? error.message : String(error), }, data: { - actionName: 'GET_GITHUB_ISSUE', + actionName: "GET_GITHUB_ISSUE", error: error instanceof Error ? error.message : String(error), }, }; @@ -152,31 +167,31 @@ URL: ${issue.html_url}`, examples: [ [ { - name: '{{user}}', + name: "{{user}}", content: { - text: 'Get information about issue #42 in elizaOS/eliza', + text: "Get information about issue #42 in elizaOS/eliza", }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { - text: 'Issue #42: Bug in authentication flow\nRepository: elizaOS/eliza\nState: open\nAuthor: @contributor\nCreated: 3/15/2024\nUpdated: 3/20/2024\nComments: 5\nLabels: bug, authentication\nAssignees: @maintainer\n\nDescription:\nThe authentication flow is failing when using GitHub tokens...\n\nURL: https://github.com/elizaOS/eliza/issues/42', - actions: ['GET_GITHUB_ISSUE'], + text: "Issue #42: Bug in authentication flow\nRepository: elizaOS/eliza\nState: open\nAuthor: @contributor\nCreated: 3/15/2024\nUpdated: 3/20/2024\nComments: 5\nLabels: bug, authentication\nAssignees: @maintainer\n\nDescription:\nThe authentication flow is failing when using GitHub tokens...\n\nURL: https://github.com/elizaOS/eliza/issues/42", + actions: ["GET_GITHUB_ISSUE"], }, }, ], [ { - name: '{{user}}', + name: "{{user}}", content: { - text: 'Check issue #123 in facebook/react and see if there are any related pull requests', + text: "Check issue #123 in facebook/react and see if there are any related pull requests", }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { text: "I'll check the details of issue #123 in facebook/react and then search for related pull requests.", - actions: ['GET_GITHUB_ISSUE', 'SEARCH_GITHUB_PULL_REQUESTS'], + actions: ["GET_GITHUB_ISSUE", "SEARCH_GITHUB_PULL_REQUESTS"], }, }, ], @@ -185,17 +200,17 @@ URL: ${issue.html_url}`, // List Issues Action export const listIssuesAction: Action = { - name: 'LIST_GITHUB_ISSUES', - similes: ['LIST_ISSUES', 'SHOW_ISSUES', 'GET_ISSUES'], + name: "LIST_GITHUB_ISSUES", + similes: ["LIST_ISSUES", "SHOW_ISSUES", "GET_ISSUES"], description: - 'Lists GitHub issues for a repository with filtering options, enabling workflows like issue triage, bulk operations, or pattern analysis across issues', + "Lists GitHub issues for a repository with filtering options, enabling workflows like issue triage, bulk operations, or pattern analysis across issues", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -206,39 +221,48 @@ export const listIssuesAction: Action = { options: { owner?: string; repo?: string; - state?: 'open' | 'closed' | 'all'; + state?: "open" | "closed" | "all"; labels?: string; limit?: number; } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ): Promise => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract owner and repo from message text or options - const text = message.content.text || ''; - const ownerRepoMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/); + const text = message.content.text || ""; + const ownerRepoMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/, + ); const owner = options.owner || ownerRepoMatch?.[1] || state?.github?.lastRepository?.owner?.login || - runtime.getSetting('GITHUB_OWNER'); - const repo = options.repo || ownerRepoMatch?.[2] || state?.github?.lastRepository?.name; + runtime.getSetting("GITHUB_OWNER"); + const repo = + options.repo || + ownerRepoMatch?.[2] || + state?.github?.lastRepository?.name; if (!owner || !repo) { throw new Error( - 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options' + 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options', ); } // Extract state filter from text const issueState = options.state || - (text.includes('closed') ? 'closed' : text.includes('all') ? 'all' : 'open'); + (text.includes("closed") + ? "closed" + : text.includes("all") + ? "all" + : "open"); logger.info(`Listing ${issueState} issues for ${owner}/${repo}`); const issues = await githubService.listIssues(owner, repo, { @@ -251,15 +275,17 @@ export const listIssuesAction: Action = { .map((issue: any) => { const labels = issue.labels - ?.map((label: any) => (typeof label === 'string' ? label : label.name || '')) - .join(', ') || ''; - return `• #${issue.number}: ${issue.title} (${issue.state})${labels ? ` [${labels}]` : ''}`; + ?.map((label: any) => + typeof label === "string" ? label : label.name || "", + ) + .join(", ") || ""; + return `• #${issue.number}: ${issue.title} (${issue.state})${labels ? ` [${labels}]` : ""}`; }) - .join('\n'); + .join("\n"); const responseContent: Content = { text: `${issueState.charAt(0).toUpperCase() + issueState.slice(1)} issues for ${owner}/${repo} (${issues.length} shown):\n${issueList}`, - actions: ['LIST_GITHUB_ISSUES'], + actions: ["LIST_GITHUB_ISSUES"], source: message.content.source, // Include data for callbacks issues, @@ -284,7 +310,9 @@ export const listIssuesAction: Action = { ...state?.data?.github, // Preserve previous github state from data ...state?.github, // Also check root-level github state lastIssues: issues, - lastRepository: state?.data?.github?.lastRepository || state?.github?.lastRepository, // Preserve lastRepository + lastRepository: + state?.data?.github?.lastRepository || + state?.github?.lastRepository, // Preserve lastRepository issues: { ...state?.data?.github?.issues, ...state?.github?.issues, @@ -293,17 +321,17 @@ export const listIssuesAction: Action = { acc[`${owner}/${repo}#${issue.number}`] = issue; return acc; }, - {} as Record + {} as Record, ), }, }, }, }; } catch (error) { - logger.error('Error in LIST_GITHUB_ISSUES action:', error); + logger.error("Error in LIST_GITHUB_ISSUES action:", error); const errorContent: Content = { text: `Failed to list issues: ${error instanceof Error ? error.message : String(error)}`, - actions: ['LIST_GITHUB_ISSUES'], + actions: ["LIST_GITHUB_ISSUES"], source: message.content.source, }; @@ -318,31 +346,31 @@ export const listIssuesAction: Action = { examples: [ [ { - name: 'User', + name: "User", content: { - text: 'List open issues for elizaOS/eliza', + text: "List open issues for elizaOS/eliza", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'Open issues for elizaOS/eliza (5 shown):\n• #42: Bug in authentication flow (open) [bug, authentication]\n• #41: Feature request: Add new provider (open) [enhancement]\n• #40: Documentation update needed (open) [documentation]', - actions: ['LIST_GITHUB_ISSUES'], + text: "Open issues for elizaOS/eliza (5 shown):\n• #42: Bug in authentication flow (open) [bug, authentication]\n• #41: Feature request: Add new provider (open) [enhancement]\n• #40: Documentation update needed (open) [documentation]", + actions: ["LIST_GITHUB_ISSUES"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Show me high priority bugs in microsoft/vscode and check their activity', + text: "Show me high priority bugs in microsoft/vscode and check their activity", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'Searching for high priority bugs in microsoft/vscode...\n\nOpen issues for microsoft/vscode (8 shown):\n• #1234: Critical: Editor crashes on large files (open) [bug, high-priority, crash]\n• #1230: Memory leak in extension host (open) [bug, high-priority, performance]\n• #1225: Terminal not responding after update (open) [bug, high-priority, terminal]\n• #1220: Debugger fails to attach (open) [bug, high-priority, debug]\n\nLet me check the recent activity on these critical issues...', - actions: ['LIST_GITHUB_ISSUES', 'GET_GITHUB_ACTIVITY'], + text: "Searching for high priority bugs in microsoft/vscode...\n\nOpen issues for microsoft/vscode (8 shown):\n• #1234: Critical: Editor crashes on large files (open) [bug, high-priority, crash]\n• #1230: Memory leak in extension host (open) [bug, high-priority, performance]\n• #1225: Terminal not responding after update (open) [bug, high-priority, terminal]\n• #1220: Debugger fails to attach (open) [bug, high-priority, debug]\n\nLet me check the recent activity on these critical issues...", + actions: ["LIST_GITHUB_ISSUES", "GET_GITHUB_ACTIVITY"], }, }, ], @@ -351,18 +379,17 @@ export const listIssuesAction: Action = { // Create Issue Action export const createIssueAction: Action = { - name: 'CREATE_GITHUB_ISSUE', - similes: ['NEW_ISSUE', 'SUBMIT_ISSUE', 'REPORT_ISSUE', 'FILE_ISSUE'], + name: "CREATE_GITHUB_ISSUE", + similes: ["NEW_ISSUE", "SUBMIT_ISSUE", "REPORT_ISSUE", "FILE_ISSUE"], description: - 'Creates a new GitHub issue and enables chaining with actions like creating branches, assigning users, or linking to pull requests', - enabled: false, // Disabled by default - issue creation modifies repository state + "Creates a new GitHub issue and enables chaining with actions like creating branches, assigning users, or linking to pull requests", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -371,53 +398,65 @@ export const createIssueAction: Action = { message: Memory, state: State | undefined, options: any = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ): Promise => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract owner and repo from message text or options - const text = message.content.text || ''; - const ownerRepoMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/); + const text = message.content.text || ""; + const ownerRepoMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/, + ); const owner = options.owner || ownerRepoMatch?.[1] || state?.github?.lastRepository?.owner?.login || - runtime.getSetting('GITHUB_OWNER'); - const repo = options.repo || ownerRepoMatch?.[2] || state?.github?.lastRepository?.name; + runtime.getSetting("GITHUB_OWNER"); + const repo = + options.repo || + ownerRepoMatch?.[2] || + state?.github?.lastRepository?.name; if (!owner || !repo) { throw new Error( - 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options' + 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options', ); } // Extract title from message text or options const titleMatch = text.match(/(?:title|issue)\s*[:=]\s*["\']?([^"'\n]+)["\']?/i) || - text.match(/(?:create|submit|file)\s+(?:issue|bug)?\s*["\']?([^"'\n]+)["\']?/i); + text.match( + /(?:create|submit|file)\s+(?:issue|bug)?\s*["\']?([^"'\n]+)["\']?/i, + ); const title = options.title || titleMatch?.[1]; if (!title) { - throw new Error('Issue title is required. Please specify the title for the issue'); + throw new Error( + "Issue title is required. Please specify the title for the issue", + ); } // Extract body/description from message text or options const bodyMatch = text.match( - /(?:description|body|details?)\s*[:=]\s*["\']?([^"'\n]+)["\']?/i + /(?:description|body|details?)\s*[:=]\s*["\']?([^"'\n]+)["\']?/i, ); const body = options.body || bodyMatch?.[1]; // Extract labels from message text or options - const labelsMatch = text.match(/(?:labels?)\s*[:=]\s*["\']?([^"'\n]+)["\']?/i); + const labelsMatch = text.match( + /(?:labels?)\s*[:=]\s*["\']?([^"'\n]+)["\']?/i, + ); const labelsText = labelsMatch?.[1]; const labels = - options.labels || (labelsText ? labelsText.split(/[,\s]+/).filter(Boolean) : undefined); + options.labels || + (labelsText ? labelsText.split(/[,\s]+/).filter(Boolean) : undefined); const issueOptions: CreateIssueOptions = { title, @@ -434,18 +473,20 @@ export const createIssueAction: Action = { text: `Successfully created issue #${issue.number}: ${issue.title} Repository: ${owner}/${repo} State: ${issue.state} -Author: @${issue.user?.login || 'unknown'} +Author: @${issue.user?.login || "unknown"} Created: ${new Date(issue.created_at).toLocaleDateString()} Labels: ${ issue.labels - ?.map((label: any) => (typeof label === 'string' ? label : label.name || '')) - .join(', ') || 'None' + ?.map((label: any) => + typeof label === "string" ? label : label.name || "", + ) + .join(", ") || "None" } -${issue.body || 'No description provided'} +${issue.body || "No description provided"} URL: ${issue.html_url}`, - actions: ['CREATE_GITHUB_ISSUE'], + actions: ["CREATE_GITHUB_ISSUE"], source: message.content.source, }; @@ -476,10 +517,10 @@ URL: ${issue.html_url}`, }, }; } catch (error) { - logger.error('Error in CREATE_GITHUB_ISSUE action:', error); + logger.error("Error in CREATE_GITHUB_ISSUE action:", error); const errorContent: Content = { text: `Failed to create issue: ${error instanceof Error ? error.message : String(error)}`, - actions: ['CREATE_GITHUB_ISSUE'], + actions: ["CREATE_GITHUB_ISSUE"], source: message.content.source, }; @@ -494,31 +535,31 @@ URL: ${issue.html_url}`, examples: [ [ { - name: 'User', + name: "User", content: { text: 'Create an issue in elizaOS/eliza with title: "Authentication bug" and description: "The GitHub auth is not working properly" and add labels: bug, authentication', }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'Successfully created issue #43: Authentication bug\nRepository: elizaOS/eliza\nState: open\nAuthor: @agent\nCreated: 3/21/2024\nLabels: bug, authentication\n\nThe GitHub auth is not working properly\n\nURL: https://github.com/elizaOS/eliza/issues/43', - actions: ['CREATE_GITHUB_ISSUE'], + text: "Successfully created issue #43: Authentication bug\nRepository: elizaOS/eliza\nState: open\nAuthor: @agent\nCreated: 3/21/2024\nLabels: bug, authentication\n\nThe GitHub auth is not working properly\n\nURL: https://github.com/elizaOS/eliza/issues/43", + actions: ["CREATE_GITHUB_ISSUE"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Create a feature request issue in my new-project repo and then create a branch for it', + text: "Create a feature request issue in my new-project repo and then create a branch for it", }, }, { - name: 'Assistant', + name: "Assistant", content: { text: "First, I'll create the feature request issue...\n\nSuccessfully created issue #1: Add user authentication\nRepository: user/new-project\nState: open\nAuthor: @agent\nCreated: 3/21/2024\nLabels: enhancement\n\nImplement user authentication system with JWT tokens\n\nURL: https://github.com/user/new-project/issues/1\n\nNow I'll create a feature branch for this issue...", - actions: ['CREATE_GITHUB_ISSUE', 'CREATE_GITHUB_BRANCH'], + actions: ["CREATE_GITHUB_ISSUE", "CREATE_GITHUB_BRANCH"], }, }, ], @@ -527,17 +568,17 @@ URL: ${issue.html_url}`, // Search Issues Action export const searchIssuesAction: Action = { - name: 'SEARCH_GITHUB_ISSUES', - similes: ['FIND_ISSUES', 'SEARCH_BUGS', 'ISSUE_SEARCH'], + name: "SEARCH_GITHUB_ISSUES", + similes: ["FIND_ISSUES", "SEARCH_BUGS", "ISSUE_SEARCH"], description: - 'Searches for GitHub issues across repositories using advanced queries, enabling cross-project analysis, pattern detection, and bulk issue management workflows', + "Searches for GitHub issues across repositories using advanced queries, enabling cross-project analysis, pattern detection, and bulk issue management workflows", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -547,32 +588,34 @@ export const searchIssuesAction: Action = { state: State | undefined, options: { query?: string; - sort?: 'comments' | 'reactions' | 'created' | 'updated'; + sort?: "comments" | "reactions" | "created" | "updated"; limit?: number; } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ): Promise => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract search query from message text or options - const text = message.content.text || ''; + const text = message.content.text || ""; const queryMatch = text.match( - /(?:search|find|look for)\s+(?:issues?\s+)?(?:for\s+)?["\']?([^"'\n]+?)["\']?(?:\s|$)/i + /(?:search|find|look for)\s+(?:issues?\s+)?(?:for\s+)?["\']?([^"'\n]+?)["\']?(?:\s|$)/i, ); const query = options.query || queryMatch?.[1]; if (!query) { - throw new Error('Search query is required. Please specify what issues to search for'); + throw new Error( + "Search query is required. Please specify what issues to search for", + ); } logger.info(`Searching issues for: ${query}`); const searchResult = await githubService.searchIssues(query, { - sort: options.sort || 'updated', + sort: options.sort || "updated", per_page: options.limit || 10, }); @@ -580,18 +623,21 @@ export const searchIssuesAction: Action = { .map((issue: any) => { const labels = issue.labels - ?.map((label: any) => (typeof label === 'string' ? label : label.name || '')) - .join(', ') || ''; + ?.map((label: any) => + typeof label === "string" ? label : label.name || "", + ) + .join(", ") || ""; const repoName = issue.html_url - ? issue.html_url.match(/github\.com\/([^\/]+\/[^\/]+)/)?.[1] || 'unknown' - : 'unknown'; - return `• ${repoName}#${issue.number}: ${issue.title} (${issue.state})${labels ? ` [${labels}]` : ''}`; + ? issue.html_url.match(/github\.com\/([^\/]+\/[^\/]+)/)?.[1] || + "unknown" + : "unknown"; + return `• ${repoName}#${issue.number}: ${issue.title} (${issue.state})${labels ? ` [${labels}]` : ""}`; }) - .join('\n'); + .join("\n"); const responseContent: Content = { text: `Found ${searchResult.total_count || 0} issues for "${query}" (showing ${searchResult.items?.length || 0}):\n${issueList}`, - actions: ['SEARCH_GITHUB_ISSUES'], + actions: ["SEARCH_GITHUB_ISSUES"], source: message.content.source, // Include data for callbacks issues: searchResult.items || [], @@ -618,7 +664,9 @@ export const searchIssuesAction: Action = { ...state?.github, // Also check root-level github state lastIssueSearchResults: searchResult, lastIssueSearchQuery: query, - lastRateLimit: state?.data?.github?.lastRateLimit || state?.github?.lastRateLimit, // Preserve lastRateLimit + lastRateLimit: + state?.data?.github?.lastRateLimit || + state?.github?.lastRateLimit, // Preserve lastRateLimit issues: { ...state?.data?.github?.issues, ...state?.github?.issues, @@ -632,17 +680,17 @@ export const searchIssuesAction: Action = { } return acc; }, - {} as Record + {} as Record, ), }, }, }, }; } catch (error) { - logger.error('Error in SEARCH_GITHUB_ISSUES action:', error); + logger.error("Error in SEARCH_GITHUB_ISSUES action:", error); const errorContent: Content = { text: `Failed to search issues: ${error instanceof Error ? error.message : String(error)}`, - actions: ['SEARCH_GITHUB_ISSUES'], + actions: ["SEARCH_GITHUB_ISSUES"], source: message.content.source, }; @@ -657,31 +705,31 @@ export const searchIssuesAction: Action = { examples: [ [ { - name: 'User', + name: "User", content: { - text: 'Search for issues about authentication bugs', + text: "Search for issues about authentication bugs", }, }, { - name: 'Assistant', + name: "Assistant", content: { text: 'Found 150 issues for "authentication bugs" (showing 10):\n• elizaOS/eliza#42: Authentication flow failing (open) [bug, authentication]\n• user/project#15: Auth token validation issue (closed) [bug]\n• company/app#88: Login authentication error (open) [bug, security]', - actions: ['SEARCH_GITHUB_ISSUES'], + actions: ["SEARCH_GITHUB_ISSUES"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Find all TypeScript migration issues across popular repos and analyze the patterns', + text: "Find all TypeScript migration issues across popular repos and analyze the patterns", }, }, { - name: 'Assistant', + name: "Assistant", content: { text: 'Found 3,250 issues for "TypeScript migration" (showing 10):\n• facebook/jest#11234: Migrate codebase to TypeScript (open) [enhancement, typescript, breaking-change]\n• expressjs/express#4567: TypeScript support roadmap (open) [enhancement, typescript, discussion]\n• webpack/webpack#8901: Convert core modules to TypeScript (in progress) [enhancement, typescript]\n• redux-saga/redux-saga#2345: TypeScript migration tracking (open) [enhancement, typescript, meta]\n\nI notice most migrations follow a phased approach. Let me check the activity on these migration efforts...', - actions: ['SEARCH_GITHUB_ISSUES', 'GET_GITHUB_ACTIVITY'], + actions: ["SEARCH_GITHUB_ISSUES", "GET_GITHUB_ACTIVITY"], }, }, ], diff --git a/src/actions/pullRequests.ts b/src/actions/pullRequests.ts index b22aa1c..95c7f29 100644 --- a/src/actions/pullRequests.ts +++ b/src/actions/pullRequests.ts @@ -1,27 +1,28 @@ import { type Action, + type ActionResult, type Content, type HandlerCallback, type IAgentRuntime, type Memory, type State, logger, -} from '@elizaos/core'; -import { GitHubService } from '../services/github'; -import { CreatePullRequestOptions, GitHubPullRequest } from '../types'; +} from "@elizaos/core"; +import { GitHubService } from "../services/github"; +import { CreatePullRequestOptions, GitHubPullRequest } from "../types"; // Get Pull Request Action export const getPullRequestAction: Action = { - name: 'GET_GITHUB_PULL_REQUEST', - similes: ['CHECK_PR', 'FETCH_PULL_REQUEST', 'PR_INFO', 'INSPECT_PR'], - description: 'Retrieves information about a specific GitHub pull request', + name: "GET_GITHUB_PULL_REQUEST", + similes: ["CHECK_PR", "FETCH_PULL_REQUEST", "PR_INFO", "INSPECT_PR"], + description: "Retrieves information about a specific GitHub pull request", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -30,18 +31,22 @@ export const getPullRequestAction: Action = { message: Memory, state: State | undefined, options: { owner?: string; repo?: string; pull_number?: number } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract owner, repo, and PR number from message text or options - const text = message.content.text || ''; - const prMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)\/pull\/(\d+)/); - const ownerRepoMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/); + const text = message.content.text || ""; + const prMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)\/pull\/(\d+)/, + ); + const ownerRepoMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/, + ); const prNumMatch = text.match(/(?:pr\s*#?|pull\s*request\s*#?|#)(\d+)/i); const owner = @@ -49,52 +54,62 @@ export const getPullRequestAction: Action = { prMatch?.[1] || ownerRepoMatch?.[1] || state?.github?.lastRepository?.owner?.login || - runtime.getSetting('GITHUB_OWNER'); + runtime.getSetting("GITHUB_OWNER"); const repo = - options.repo || prMatch?.[2] || ownerRepoMatch?.[2] || state?.github?.lastRepository?.name; + options.repo || + prMatch?.[2] || + ownerRepoMatch?.[2] || + state?.github?.lastRepository?.name; const pull_number = - options.pull_number || parseInt(prMatch?.[3] || prNumMatch?.[1] || '0', 10); + options.pull_number || + parseInt(prMatch?.[3] || prNumMatch?.[1] || "0", 10); if (!owner || !repo || !pull_number) { throw new Error( - 'Repository owner, name, and PR number are required. Please specify as "owner/repo#123" or provide them in options' + 'Repository owner, name, and PR number are required. Please specify as "owner/repo#123" or provide them in options', ); } - logger.info(`Getting pull request information for ${owner}/${repo}#${pull_number}`); + logger.info( + `Getting pull request information for ${owner}/${repo}#${pull_number}`, + ); const pr = await githubService.getPullRequest(owner, repo, pull_number); const labels = pr.labels - ?.map((label: any) => (typeof label === 'string' ? label : label.name || '')) - .join(', ') || ''; - const assignees = pr.assignees?.map((assignee: any) => `@${assignee.login}`).join(', ') || ''; + ?.map((label: any) => + typeof label === "string" ? label : label.name || "", + ) + .join(", ") || ""; + const assignees = + pr.assignees?.map((assignee: any) => `@${assignee.login}`).join(", ") || + ""; const responseContent: Content = { text: `Pull Request #${pr.number}: ${pr.title} Repository: ${owner}/${repo} -State: ${pr.state}${pr.merged ? ' (merged)' : ''} -Draft: ${pr.draft ? 'Yes' : 'No'} +State: ${pr.state}${pr.merged ? " (merged)" : ""} +Draft: ${pr.draft ? "Yes" : "No"} Author: @${pr.user.login} Created: ${new Date(pr.created_at).toLocaleDateString()} Updated: ${new Date(pr.updated_at).toLocaleDateString()} -${pr.merged_at ? `Merged: ${new Date(pr.merged_at).toLocaleDateString()}` : ''} +${pr.merged_at ? `Merged: ${new Date(pr.merged_at).toLocaleDateString()}` : ""} Comments: ${pr.comments} Commits: ${pr.commits} Files Changed: ${pr.changed_files} Additions: +${pr.additions} Deletions: -${pr.deletions} -Labels: ${labels || 'None'} -Assignees: ${assignees || 'None'} +Labels: ${labels || "None"} +Assignees: ${assignees || "None"} Head: ${pr.head.ref} (${pr.head.sha.substring(0, 7)}) Base: ${pr.base.ref} (${pr.base.sha.substring(0, 7)}) -Mergeable: ${pr.mergeable === null ? 'Unknown' : pr.mergeable ? 'Yes' : 'No'} +Mergeable: ${pr.mergeable === null ? "Unknown" : pr.mergeable ? "Yes" : "No"} Description: -${pr.body || 'No description provided'} +${pr.body || "No description provided"} URL: ${pr.html_url}`, - actions: ['GET_GITHUB_PULL_REQUEST'], + actions: ["GET_GITHUB_PULL_REQUEST"], source: message.content.source, }; @@ -104,6 +119,7 @@ URL: ${pr.html_url}`, // Return result for chaining return { + success: true, text: responseContent.text, values: { pullRequest: pr, @@ -121,12 +137,12 @@ URL: ${pr.html_url}`, }, }, }, - }; + } as ActionResult; } catch (error) { - logger.error('Error in GET_GITHUB_PULL_REQUEST action:', error); + logger.error("Error in GET_GITHUB_PULL_REQUEST action:", error); const errorContent: Content = { text: `Failed to get pull request information: ${error instanceof Error ? error.message : String(error)}`, - actions: ['GET_GITHUB_PULL_REQUEST'], + actions: ["GET_GITHUB_PULL_REQUEST"], source: message.content.source, }; @@ -134,38 +150,42 @@ URL: ${pr.html_url}`, await callback(errorContent); } - return errorContent; + return { + success: false, + text: errorContent.text, + error: error instanceof Error ? error.message : String(error), + } as ActionResult; } }, examples: [ [ { - name: 'User', + name: "User", content: { - text: 'Get information about PR #25 in elizaOS/eliza', + text: "Get information about PR #25 in elizaOS/eliza", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'Pull Request #25: Add new authentication provider\nRepository: elizaOS/eliza\nState: open\nDraft: No\nAuthor: @contributor\nCreated: 3/15/2024\nUpdated: 3/20/2024\nComments: 3\nCommits: 5\nFiles Changed: 8\nAdditions: +120\nDeletions: -15\nLabels: enhancement, authentication\nAssignees: @maintainer\nHead: feature/auth-provider (abc1234)\nBase: main (def5678)\nMergeable: Yes\n\nDescription:\nThis PR adds a new authentication provider for GitHub integration...\n\nURL: https://github.com/elizaOS/eliza/pull/25', - actions: ['GET_GITHUB_PULL_REQUEST'], + text: "Pull Request #25: Add new authentication provider\nRepository: elizaOS/eliza\nState: open\nDraft: No\nAuthor: @contributor\nCreated: 3/15/2024\nUpdated: 3/20/2024\nComments: 3\nCommits: 5\nFiles Changed: 8\nAdditions: +120\nDeletions: -15\nLabels: enhancement, authentication\nAssignees: @maintainer\nHead: feature/auth-provider (abc1234)\nBase: main (def5678)\nMergeable: Yes\n\nDescription:\nThis PR adds a new authentication provider for GitHub integration...\n\nURL: https://github.com/elizaOS/eliza/pull/25", + actions: ["GET_GITHUB_PULL_REQUEST"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Check PR #456 in vercel/next.js and see what issues it addresses', + text: "Check PR #456 in vercel/next.js and see what issues it addresses", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'Pull Request #456: Fix hydration mismatch in dynamic routes\nRepository: vercel/next.js\nState: open\nDraft: No\nAuthor: @contributor\nCreated: 3/18/2024\nUpdated: 3/21/2024\nComments: 8\nCommits: 3\nFiles Changed: 12\nAdditions: +85\nDeletions: -42\nLabels: bug, hydration\nAssignees: @next-team\nHead: fix/hydration-mismatch (xyz789)\nBase: canary (abc123)\nMergeable: Yes\n\nDescription:\nFixes #445, #389 - Resolves hydration mismatches when using dynamic routes with SSR...\n\nURL: https://github.com/vercel/next.js/pull/456\n\nLet me check the referenced issues to understand what problems this PR solves...', - actions: ['GET_GITHUB_PULL_REQUEST', 'GET_GITHUB_ISSUE'], + text: "Pull Request #456: Fix hydration mismatch in dynamic routes\nRepository: vercel/next.js\nState: open\nDraft: No\nAuthor: @contributor\nCreated: 3/18/2024\nUpdated: 3/21/2024\nComments: 8\nCommits: 3\nFiles Changed: 12\nAdditions: +85\nDeletions: -42\nLabels: bug, hydration\nAssignees: @next-team\nHead: fix/hydration-mismatch (xyz789)\nBase: canary (abc123)\nMergeable: Yes\n\nDescription:\nFixes #445, #389 - Resolves hydration mismatches when using dynamic routes with SSR...\n\nURL: https://github.com/vercel/next.js/pull/456\n\nLet me check the referenced issues to understand what problems this PR solves...", + actions: ["GET_GITHUB_PULL_REQUEST", "GET_GITHUB_ISSUE"], }, }, ], @@ -174,16 +194,16 @@ URL: ${pr.html_url}`, // List Pull Requests Action export const listPullRequestsAction: Action = { - name: 'LIST_GITHUB_PULL_REQUESTS', - similes: ['LIST_PRS', 'SHOW_PULL_REQUESTS', 'GET_PRS'], - description: 'Lists GitHub pull requests for a repository', + name: "LIST_GITHUB_PULL_REQUESTS", + similes: ["LIST_PRS", "SHOW_PULL_REQUESTS", "GET_PRS"], + description: "Lists GitHub pull requests for a repository", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -194,40 +214,49 @@ export const listPullRequestsAction: Action = { options: { owner?: string; repo?: string; - state?: 'open' | 'closed' | 'all'; + state?: "open" | "closed" | "all"; head?: string; base?: string; limit?: number; } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract owner and repo from message text or options - const text = message.content.text || ''; - const ownerRepoMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/); + const text = message.content.text || ""; + const ownerRepoMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/, + ); const owner = options.owner || ownerRepoMatch?.[1] || state?.github?.lastRepository?.owner?.login || - runtime.getSetting('GITHUB_OWNER'); - const repo = options.repo || ownerRepoMatch?.[2] || state?.github?.lastRepository?.name; + runtime.getSetting("GITHUB_OWNER"); + const repo = + options.repo || + ownerRepoMatch?.[2] || + state?.github?.lastRepository?.name; if (!owner || !repo) { throw new Error( - 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options' + 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options', ); } // Extract state filter from text const prState = options.state || - (text.includes('closed') ? 'closed' : text.includes('all') ? 'all' : 'open'); + (text.includes("closed") + ? "closed" + : text.includes("all") + ? "all" + : "open"); logger.info(`Listing ${prState} pull requests for ${owner}/${repo}`); const prs = await githubService.listPullRequests(owner, repo, { @@ -239,16 +268,18 @@ export const listPullRequestsAction: Action = { const prList = prs .map((pr: any) => { - const labels = pr.labels ? pr.labels.map((label: any) => label.name).join(', ') : ''; - const status = pr.merged ? 'merged' : pr.state; - const draft = pr.draft ? ' (draft)' : ''; - return `• #${pr.number}: ${pr.title} (${status}${draft})${labels ? ` [${labels}]` : ''}`; + const labels = pr.labels + ? pr.labels.map((label: any) => label.name).join(", ") + : ""; + const status = pr.merged ? "merged" : pr.state; + const draft = pr.draft ? " (draft)" : ""; + return `• #${pr.number}: ${pr.title} (${status}${draft})${labels ? ` [${labels}]` : ""}`; }) - .join('\n'); + .join("\n"); const responseContent: Content = { text: `${prState.charAt(0).toUpperCase() + prState.slice(1)} pull requests for ${owner}/${repo} (${prs.length} shown):\n${prList}`, - actions: ['LIST_GITHUB_PULL_REQUESTS'], + actions: ["LIST_GITHUB_PULL_REQUESTS"], source: message.content.source, // Include data for callbacks pullRequests: prs, @@ -262,6 +293,7 @@ export const listPullRequestsAction: Action = { // Return result for chaining return { + success: true, text: responseContent.text, values: { pullRequests: prs, @@ -280,17 +312,17 @@ export const listPullRequestsAction: Action = { acc[`${owner}/${repo}#${pr.number}`] = pr; return acc; }, - {} as Record + {} as Record, ), }, }, }, - }; + } as ActionResult; } catch (error) { - logger.error('Error in LIST_GITHUB_PULL_REQUESTS action:', error); + logger.error("Error in LIST_GITHUB_PULL_REQUESTS action:", error); const errorContent: Content = { text: `Failed to list pull requests: ${error instanceof Error ? error.message : String(error)}`, - actions: ['LIST_GITHUB_PULL_REQUESTS'], + actions: ["LIST_GITHUB_PULL_REQUESTS"], source: message.content.source, }; @@ -298,38 +330,42 @@ export const listPullRequestsAction: Action = { await callback(errorContent); } - return errorContent; + return { + success: false, + text: errorContent.text, + error: error instanceof Error ? error.message : String(error), + } as ActionResult; } }, examples: [ [ { - name: 'User', + name: "User", content: { - text: 'List open pull requests for elizaOS/eliza', + text: "List open pull requests for elizaOS/eliza", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'Open pull requests for elizaOS/eliza (3 shown):\n• #25: Add new authentication provider (open) [enhancement, authentication]\n• #24: Fix memory leak in service (open) [bug]\n• #23: Update documentation (open) (draft) [documentation]', - actions: ['LIST_GITHUB_PULL_REQUESTS'], + text: "Open pull requests for elizaOS/eliza (3 shown):\n• #25: Add new authentication provider (open) [enhancement, authentication]\n• #24: Fix memory leak in service (open) [bug]\n• #23: Update documentation (open) (draft) [documentation]", + actions: ["LIST_GITHUB_PULL_REQUESTS"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Show me PRs ready for review in rust-lang/rust and check their CI status', + text: "Show me PRs ready for review in rust-lang/rust and check their CI status", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'Open pull requests for rust-lang/rust (10 shown):\n• #98765: Optimize compiler performance for large crates (open) [performance, ready-for-review]\n• #98760: Add new lint for unsafe code patterns (open) [lint, ready-for-review]\n• #98755: Fix ICE in type inference (open) [bug, ready-for-review]\n• #98750: Implement RFC 3324 (open) [rfc, ready-for-review]\n• #98745: Update LLVM to version 16 (open) [dependencies, ready-for-review]\n\nNow let me check the CI status for these pull requests...', - actions: ['LIST_GITHUB_PULL_REQUESTS', 'GET_GITHUB_WORKFLOW_RUNS'], + text: "Open pull requests for rust-lang/rust (10 shown):\n• #98765: Optimize compiler performance for large crates (open) [performance, ready-for-review]\n• #98760: Add new lint for unsafe code patterns (open) [lint, ready-for-review]\n• #98755: Fix ICE in type inference (open) [bug, ready-for-review]\n• #98750: Implement RFC 3324 (open) [rfc, ready-for-review]\n• #98745: Update LLVM to version 16 (open) [dependencies, ready-for-review]\n\nNow let me check the CI status for these pull requests...", + actions: ["LIST_GITHUB_PULL_REQUESTS", "GET_GITHUB_WORKFLOW_RUNS"], }, }, ], @@ -338,17 +374,16 @@ export const listPullRequestsAction: Action = { // Create Pull Request Action export const createPullRequestAction: Action = { - name: 'CREATE_GITHUB_PULL_REQUEST', - similes: ['NEW_PR', 'SUBMIT_PR', 'CREATE_PR', 'OPEN_PULL_REQUEST'], - description: 'Creates a new GitHub pull request', - enabled: false, // Disabled by default - PR creation modifies repository state + name: "CREATE_GITHUB_PULL_REQUEST", + similes: ["NEW_PR", "SUBMIT_PR", "CREATE_PR", "OPEN_PULL_REQUEST"], + description: "Creates a new GitHub pull request", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -357,61 +392,73 @@ export const createPullRequestAction: Action = { message: Memory, state: State | undefined, options: any = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract owner and repo from message text or options - const text = message.content.text || ''; - const ownerRepoMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/); + const text = message.content.text || ""; + const ownerRepoMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/, + ); const owner = options.owner || ownerRepoMatch?.[1] || state?.github?.lastRepository?.owner?.login || - runtime.getSetting('GITHUB_OWNER'); - const repo = options.repo || ownerRepoMatch?.[2] || state?.github?.lastRepository?.name; + runtime.getSetting("GITHUB_OWNER"); + const repo = + options.repo || + ownerRepoMatch?.[2] || + state?.github?.lastRepository?.name; if (!owner || !repo) { throw new Error( - 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options' + 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options', ); } // Extract title from message text or options const titleMatch = text.match(/(?:title|pr)\s*[:=]\s*["\']?([^"'\n]+)["\']?/i) || - text.match(/(?:create|submit|open)\s+(?:pr|pull\s*request)?\s*["\']?([^"'\n]+)["\']?/i); + text.match( + /(?:create|submit|open)\s+(?:pr|pull\s*request)?\s*["\']?([^"'\n]+)["\']?/i, + ); const title = options.title || titleMatch?.[1]; if (!title) { - throw new Error('Pull request title is required. Please specify the title for the PR'); + throw new Error( + "Pull request title is required. Please specify the title for the PR", + ); } // Extract head and base branches from message text or options const branchMatch = text.match( - /(?:from|head)\s*[:=]?\s*([^\s]+)(?:\s+(?:to|into|base)\s*[:=]?\s*([^\s]+))?/i + /(?:from|head)\s*[:=]?\s*([^\s]+)(?:\s+(?:to|into|base)\s*[:=]?\s*([^\s]+))?/i, ); const head = options.head || branchMatch?.[1]; - const base = options.base || branchMatch?.[2] || 'main'; + const base = options.base || branchMatch?.[2] || "main"; if (!head) { - throw new Error('Head branch is required. Please specify the branch to merge from'); + throw new Error( + "Head branch is required. Please specify the branch to merge from", + ); } // Extract body/description from message text or options const bodyMatch = text.match( - /(?:description|body|details?)\s*[:=]\s*["\']?([^"'\n]+)["\']?/i + /(?:description|body|details?)\s*[:=]\s*["\']?([^"'\n]+)["\']?/i, ); const body = options.body || bodyMatch?.[1]; // Check for draft keyword - const draft = options.draft !== undefined ? options.draft : text.includes('draft'); + const draft = + options.draft !== undefined ? options.draft : text.includes("draft"); const prOptions: CreatePullRequestOptions = { title, @@ -429,16 +476,16 @@ export const createPullRequestAction: Action = { text: `Successfully created pull request #${pr.number}: ${pr.title} Repository: ${owner}/${repo} State: ${pr.state} -Draft: ${pr.draft ? 'Yes' : 'No'} +Draft: ${pr.draft ? "Yes" : "No"} Author: @${pr.user.login} Created: ${new Date(pr.created_at).toLocaleDateString()} Head: ${pr.head.ref} Base: ${pr.base.ref} -${pr.body || 'No description provided'} +${pr.body || "No description provided"} URL: ${pr.html_url}`, - actions: ['CREATE_GITHUB_PULL_REQUEST'], + actions: ["CREATE_GITHUB_PULL_REQUEST"], source: message.content.source, }; @@ -448,6 +495,7 @@ URL: ${pr.html_url}`, // Return result for chaining return { + success: true, text: responseContent.text, values: { pullRequest: pr, @@ -469,10 +517,10 @@ URL: ${pr.html_url}`, }, }; } catch (error) { - logger.error('Error in CREATE_GITHUB_PULL_REQUEST action:', error); + logger.error("Error in CREATE_GITHUB_PULL_REQUEST action:", error); const errorContent: Content = { text: `Failed to create pull request: ${error instanceof Error ? error.message : String(error)}`, - actions: ['CREATE_GITHUB_PULL_REQUEST'], + actions: ["CREATE_GITHUB_PULL_REQUEST"], source: message.content.source, }; @@ -480,38 +528,41 @@ URL: ${pr.html_url}`, await callback(errorContent); } - return errorContent; + return { + success: false, + ...errorContent, + }; } }, examples: [ [ { - name: 'User', + name: "User", content: { text: 'Create a pull request in elizaOS/eliza with title: "Add GitHub integration" from feature/github-integration to main with description: "This PR adds comprehensive GitHub integration capabilities"', }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'Successfully created pull request #26: Add GitHub integration\nRepository: elizaOS/eliza\nState: open\nDraft: No\nAuthor: @agent\nCreated: 3/21/2024\nHead: feature/github-integration\nBase: main\n\nThis PR adds comprehensive GitHub integration capabilities\n\nURL: https://github.com/elizaOS/eliza/pull/26', - actions: ['CREATE_GITHUB_PULL_REQUEST'], + text: "Successfully created pull request #26: Add GitHub integration\nRepository: elizaOS/eliza\nState: open\nDraft: No\nAuthor: @agent\nCreated: 3/21/2024\nHead: feature/github-integration\nBase: main\n\nThis PR adds comprehensive GitHub integration capabilities\n\nURL: https://github.com/elizaOS/eliza/pull/26", + actions: ["CREATE_GITHUB_PULL_REQUEST"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Create a draft PR for my bugfix branch and link it to issue #789', + text: "Create a draft PR for my bugfix branch and link it to issue #789", }, }, { - name: 'Assistant', + name: "Assistant", content: { text: "First, let me check issue #789 to understand the context...\n\nIssue #789: Database connection timeout\nRepository: user/api-server\nState: open\nLabels: bug, database, priority-high\n\nNow I'll create a draft pull request for your bugfix...\n\nSuccessfully created pull request #790: Fix database connection timeout\nRepository: user/api-server\nState: open\nDraft: Yes\nAuthor: @agent\nCreated: 3/21/2024\nHead: bugfix/db-timeout\nBase: main\n\nFixes #789 - Resolves database connection timeout issues by implementing connection pooling\n\nURL: https://github.com/user/api-server/pull/790", - actions: ['GET_GITHUB_ISSUE', 'CREATE_GITHUB_PULL_REQUEST'], + actions: ["GET_GITHUB_ISSUE", "CREATE_GITHUB_PULL_REQUEST"], }, }, ], @@ -520,17 +571,16 @@ URL: ${pr.html_url}`, // Merge Pull Request Action export const mergePullRequestAction: Action = { - name: 'MERGE_GITHUB_PULL_REQUEST', - similes: ['MERGE_PR', 'ACCEPT_PR', 'APPROVE_PR'], - description: 'Merges a GitHub pull request', - enabled: false, // Disabled by default - merging modifies repository state + name: "MERGE_GITHUB_PULL_REQUEST", + similes: ["MERGE_PR", "ACCEPT_PR", "APPROVE_PR"], + description: "Merges a GitHub pull request", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -544,20 +594,24 @@ export const mergePullRequestAction: Action = { pull_number?: number; commit_title?: string; commit_message?: string; - merge_method?: 'merge' | 'squash' | 'rebase'; + merge_method?: "merge" | "squash" | "rebase"; } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract owner, repo, and PR number from message text or options - const text = message.content.text || ''; - const prMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)\/pull\/(\d+)/); - const ownerRepoMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/); + const text = message.content.text || ""; + const prMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)\/pull\/(\d+)/, + ); + const ownerRepoMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/, + ); const prNumMatch = text.match(/(?:pr\s*#?|pull\s*request\s*#?|#)(\d+)/i); const owner = @@ -566,7 +620,7 @@ export const mergePullRequestAction: Action = { ownerRepoMatch?.[1] || state?.github?.lastPullRequest?.base?.repo?.owner?.login || state?.github?.lastRepository?.owner?.login || - runtime.getSetting('GITHUB_OWNER'); + runtime.getSetting("GITHUB_OWNER"); const repo = options.repo || prMatch?.[2] || @@ -575,26 +629,37 @@ export const mergePullRequestAction: Action = { state?.github?.lastRepository?.name; const pull_number = options.pull_number || - parseInt(prMatch?.[3] || prNumMatch?.[1] || '0', 10) || + parseInt(prMatch?.[3] || prNumMatch?.[1] || "0", 10) || state?.github?.lastPullRequest?.number; if (!owner || !repo || !pull_number) { throw new Error( - 'Repository owner, name, and PR number are required. Please specify as "owner/repo#123" or provide them in options' + 'Repository owner, name, and PR number are required. Please specify as "owner/repo#123" or provide them in options', ); } // Extract merge method from text const mergeMethod = options.merge_method || - (text.includes('squash') ? 'squash' : text.includes('rebase') ? 'rebase' : 'merge'); - - logger.info(`Merging pull request ${owner}/${repo}#${pull_number} using ${mergeMethod}`); - const result = await githubService.mergePullRequest(owner, repo, pull_number, { - commit_title: options.commit_title, - commit_message: options.commit_message, - merge_method: mergeMethod, - }); + (text.includes("squash") + ? "squash" + : text.includes("rebase") + ? "rebase" + : "merge"); + + logger.info( + `Merging pull request ${owner}/${repo}#${pull_number} using ${mergeMethod}`, + ); + const result = await githubService.mergePullRequest( + owner, + repo, + pull_number, + { + commit_title: options.commit_title, + commit_message: options.commit_message, + merge_method: mergeMethod, + }, + ); const responseContent: Content = { text: `Successfully merged pull request #${pull_number} @@ -602,7 +667,7 @@ Repository: ${owner}/${repo} Merge method: ${mergeMethod} Commit SHA: ${result.sha} Message: ${result.message}`, - actions: ['MERGE_GITHUB_PULL_REQUEST'], + actions: ["MERGE_GITHUB_PULL_REQUEST"], source: message.content.source, }; @@ -612,6 +677,7 @@ Message: ${result.message}`, // Return result for chaining return { + success: true, text: responseContent.text, values: { merged: true, @@ -629,10 +695,10 @@ Message: ${result.message}`, }, }; } catch (error) { - logger.error('Error in MERGE_GITHUB_PULL_REQUEST action:', error); + logger.error("Error in MERGE_GITHUB_PULL_REQUEST action:", error); const errorContent: Content = { text: `Failed to merge pull request: ${error instanceof Error ? error.message : String(error)}`, - actions: ['MERGE_GITHUB_PULL_REQUEST'], + actions: ["MERGE_GITHUB_PULL_REQUEST"], source: message.content.source, }; @@ -640,38 +706,45 @@ Message: ${result.message}`, await callback(errorContent); } - return errorContent; + return { + success: false, + ...errorContent, + }; } }, examples: [ [ { - name: 'User', + name: "User", content: { - text: 'Merge PR #25 in elizaOS/eliza using squash method', + text: "Merge PR #25 in elizaOS/eliza using squash method", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'Successfully merged pull request #25\nRepository: elizaOS/eliza\nMerge method: squash\nCommit SHA: abc123def456\nMessage: Pull request successfully merged', - actions: ['MERGE_GITHUB_PULL_REQUEST'], + text: "Successfully merged pull request #25\nRepository: elizaOS/eliza\nMerge method: squash\nCommit SHA: abc123def456\nMessage: Pull request successfully merged", + actions: ["MERGE_GITHUB_PULL_REQUEST"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Review and merge the documentation PR if all checks pass', + text: "Review and merge the documentation PR if all checks pass", }, }, { - name: 'Assistant', + name: "Assistant", content: { text: "Let me check the most recent documentation PR...\n\nPull Request #23: Update documentation (open)\nRepository: user/project\nMergeable: Yes\nAll checks passing: ✓\n\nThe PR looks good with all checks passing. I'll merge it now...\n\nSuccessfully merged pull request #23\nRepository: user/project\nMerge method: merge\nCommit SHA: def789abc123\nMessage: Merge pull request #23 from user/update-docs\n\nNow let me update the activity log...", - actions: ['GET_GITHUB_PULL_REQUEST', 'MERGE_GITHUB_PULL_REQUEST', 'GET_GITHUB_ACTIVITY'], + actions: [ + "GET_GITHUB_PULL_REQUEST", + "MERGE_GITHUB_PULL_REQUEST", + "GET_GITHUB_ACTIVITY", + ], }, }, ], diff --git a/src/actions/repository.ts b/src/actions/repository.ts index f7b52aa..33aeca9 100644 --- a/src/actions/repository.ts +++ b/src/actions/repository.ts @@ -8,23 +8,23 @@ import { type Memory, type State, logger, -} from '@elizaos/core'; -import { GitHubService } from '../services/github'; -import { CreateRepositoryOptions, GitHubRepository } from '../types'; +} from "@elizaos/core"; +import { GitHubService } from "../services/github"; +import { CreateRepositoryOptions, GitHubRepository } from "../types"; // Get Repository Action export const getRepositoryAction: Action = { - name: 'GET_GITHUB_REPOSITORY', - similes: ['CHECK_REPO', 'FETCH_REPOSITORY', 'REPO_INFO', 'INSPECT_REPO'], + name: "GET_GITHUB_REPOSITORY", + similes: ["CHECK_REPO", "FETCH_REPOSITORY", "REPO_INFO", "INSPECT_REPO"], description: - 'Retrieves information about a GitHub repository including stats, language, and metadata. Can be chained with LIST_GITHUB_ISSUES or LIST_GITHUB_PULL_REQUESTS to explore repository content', + "Retrieves information about a GitHub repository including stats, language, and metadata. Can be chained with LIST_GITHUB_ISSUES or LIST_GITHUB_PULL_REQUESTS to explore repository content", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -33,24 +33,29 @@ export const getRepositoryAction: Action = { message: Memory, state: State | undefined, options: { owner?: string; repo?: string } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract owner and repo from message text or options - const text = message.content.text || ''; - const ownerRepoMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/); + const text = message.content.text || ""; + const ownerRepoMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/, + ); - const owner = options.owner || ownerRepoMatch?.[1] || runtime.getSetting('GITHUB_OWNER'); + const owner = + options.owner || + ownerRepoMatch?.[1] || + runtime.getSetting("GITHUB_OWNER"); const repo = options.repo || ownerRepoMatch?.[2]; if (!owner || !repo) { throw new Error( - 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options' + 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options', ); } @@ -59,16 +64,16 @@ export const getRepositoryAction: Action = { const responseContent: Content = { text: `Repository: ${repository.full_name} -Description: ${repository.description || 'No description'} -Language: ${repository.language || 'Unknown'} +Description: ${repository.description || "No description"} +Language: ${repository.language || "Unknown"} Stars: ${repository.stargazers_count} Forks: ${repository.forks_count} Open Issues: ${repository.open_issues_count} -Private: ${repository.private ? 'Yes' : 'No'} +Private: ${repository.private ? "Yes" : "No"} Created: ${new Date(repository.created_at).toLocaleDateString()} Last Updated: ${new Date(repository.updated_at).toLocaleDateString()} URL: ${repository.html_url}`, - actions: ['GET_GITHUB_REPOSITORY'], + actions: ["GET_GITHUB_REPOSITORY"], source: message.content.source, // Include data for callbacks repository, @@ -82,6 +87,7 @@ URL: ${repository.html_url}`, // Return ActionResult for chaining return { + success: true, text: responseContent.text, values: { success: true, @@ -93,7 +99,7 @@ URL: ${repository.html_url}`, language: repository.language, }, data: { - actionName: 'GET_GITHUB_REPOSITORY', + actionName: "GET_GITHUB_REPOSITORY", repository, github: { ...state?.github, @@ -104,12 +110,12 @@ URL: ${repository.html_url}`, }, }, }, - }; + } as ActionResult; } catch (error) { - logger.error('Error in GET_GITHUB_REPOSITORY action:', error); + logger.error("Error in GET_GITHUB_REPOSITORY action:", error); const errorContent: Content = { text: `Failed to get repository information: ${error instanceof Error ? error.message : String(error)}`, - actions: ['GET_GITHUB_REPOSITORY'], + actions: ["GET_GITHUB_REPOSITORY"], source: message.content.source, }; @@ -118,47 +124,48 @@ URL: ${repository.html_url}`, } return { + success: false, text: errorContent.text, values: { success: false, error: error instanceof Error ? error.message : String(error), }, data: { - actionName: 'GET_GITHUB_REPOSITORY', + actionName: "GET_GITHUB_REPOSITORY", error: error instanceof Error ? error.message : String(error), }, - }; + } as ActionResult; } }, examples: [ [ { - name: '{{user}}', + name: "{{user}}", content: { - text: 'Get information about elizaOS/eliza repository', + text: "Get information about elizaOS/eliza repository", }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { - text: 'Repository: elizaOS/eliza\nDescription: Eliza is a simple, fast, and lightweight AI agent framework\nLanguage: TypeScript\nStars: 1234\nForks: 567\nOpen Issues: 42\nPrivate: No\nCreated: 1/15/2024\nLast Updated: 3/20/2024\nURL: https://github.com/elizaOS/eliza', - actions: ['GET_GITHUB_REPOSITORY'], + text: "Repository: elizaOS/eliza\nDescription: Eliza is a simple, fast, and lightweight AI agent framework\nLanguage: TypeScript\nStars: 1234\nForks: 567\nOpen Issues: 42\nPrivate: No\nCreated: 1/15/2024\nLast Updated: 3/20/2024\nURL: https://github.com/elizaOS/eliza", + actions: ["GET_GITHUB_REPOSITORY"], }, }, ], [ { - name: '{{user}}', + name: "{{user}}", content: { - text: 'Check the nodejs/node repository and then list its open issues', + text: "Check the nodejs/node repository and then list its open issues", }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { text: "I'll get information about the nodejs/node repository and then list its open issues.", - actions: ['GET_GITHUB_REPOSITORY', 'LIST_GITHUB_ISSUES'], + actions: ["GET_GITHUB_REPOSITORY", "LIST_GITHUB_ISSUES"], }, }, ], @@ -167,17 +174,17 @@ URL: ${repository.html_url}`, // List Repositories Action export const listRepositoriesAction: Action = { - name: 'LIST_GITHUB_REPOSITORIES', - similes: ['LIST_REPOS', 'MY_REPOSITORIES', 'SHOW_REPOS'], + name: "LIST_GITHUB_REPOSITORIES", + similes: ["LIST_REPOS", "MY_REPOSITORIES", "SHOW_REPOS"], description: - 'Lists GitHub repositories for the authenticated user with stats and metadata. Can be chained with GET_GITHUB_REPOSITORY to inspect specific repositories or CREATE_GITHUB_REPOSITORY to add new ones', + "Lists GitHub repositories for the authenticated user with stats and metadata. Can be chained with GET_GITHUB_REPOSITORY to inspect specific repositories or CREATE_GITHUB_REPOSITORY to add new ones", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -186,34 +193,34 @@ export const listRepositoriesAction: Action = { message: Memory, state: State | undefined, options: { - type?: 'all' | 'owner' | 'public' | 'private' | 'member'; - sort?: 'created' | 'updated' | 'pushed' | 'full_name'; + type?: "all" | "owner" | "public" | "private" | "member"; + sort?: "created" | "updated" | "pushed" | "full_name"; limit?: number; } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } const repositories = await githubService.listRepositories({ - type: options.type || 'owner', - sort: options.sort || 'updated', + type: options.type || "owner", + sort: options.sort || "updated", per_page: options.limit || 10, }); const repoList = repositories .map( (repo: any) => - `• ${repo.full_name} (${repo.language || 'Unknown'}) - ⭐ ${repo.stargazers_count}` + `• ${repo.full_name} (${repo.language || "Unknown"}) - ⭐ ${repo.stargazers_count}`, ) - .join('\n'); + .join("\n"); const responseContent: Content = { text: `Your repositories (${repositories.length} shown):\n${repoList}`, - actions: ['LIST_GITHUB_REPOSITORIES'], + actions: ["LIST_GITHUB_REPOSITORIES"], source: message.content.source, // Include data for callbacks repositories, @@ -226,6 +233,7 @@ export const listRepositoriesAction: Action = { // Return ActionResult for chaining return { + success: true, text: responseContent.text, values: { success: true, @@ -234,7 +242,7 @@ export const listRepositoriesAction: Action = { repositoryNames: repositories.map((repo: any) => repo.full_name), }, data: { - actionName: 'LIST_GITHUB_REPOSITORIES', + actionName: "LIST_GITHUB_REPOSITORIES", repositories, github: { ...state?.github, @@ -246,17 +254,17 @@ export const listRepositoriesAction: Action = { acc[repo.full_name] = repo; return acc; }, - {} as Record + {} as Record, ), }, }, }, }; } catch (error) { - logger.error('Error in LIST_GITHUB_REPOSITORIES action:', error); + logger.error("Error in LIST_GITHUB_REPOSITORIES action:", error); const errorContent: Content = { text: `Failed to list repositories: ${error instanceof Error ? error.message : String(error)}`, - actions: ['LIST_GITHUB_REPOSITORIES'], + actions: ["LIST_GITHUB_REPOSITORIES"], source: message.content.source, }; @@ -265,13 +273,14 @@ export const listRepositoriesAction: Action = { } return { + success: false, text: errorContent.text, values: { success: false, error: error instanceof Error ? error.message : String(error), }, data: { - actionName: 'LIST_GITHUB_REPOSITORIES', + actionName: "LIST_GITHUB_REPOSITORIES", error: error instanceof Error ? error.message : String(error), }, }; @@ -281,31 +290,31 @@ export const listRepositoriesAction: Action = { examples: [ [ { - name: '{{user}}', + name: "{{user}}", content: { - text: 'Show me my repositories', + text: "Show me my repositories", }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { - text: 'Your repositories (3 shown):\n• user/awesome-project (JavaScript) - ⭐ 25\n• user/my-plugin (TypeScript) - ⭐ 8\n• user/test-repo (Python) - ⭐ 2', - actions: ['LIST_GITHUB_REPOSITORIES'], + text: "Your repositories (3 shown):\n• user/awesome-project (JavaScript) - ⭐ 25\n• user/my-plugin (TypeScript) - ⭐ 8\n• user/test-repo (Python) - ⭐ 2", + actions: ["LIST_GITHUB_REPOSITORIES"], }, }, ], [ { - name: '{{user}}', + name: "{{user}}", content: { - text: 'List my most recent repositories and check which ones have open pull requests', + text: "List my most recent repositories and check which ones have open pull requests", }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { text: "I'll list your most recent repositories and then check for open pull requests in each.", - actions: ['LIST_GITHUB_REPOSITORIES', 'LIST_GITHUB_PULL_REQUESTS'], + actions: ["LIST_GITHUB_REPOSITORIES", "LIST_GITHUB_PULL_REQUESTS"], }, }, ], @@ -314,18 +323,17 @@ export const listRepositoriesAction: Action = { // Create Repository Action export const createRepositoryAction: Action = { - name: 'CREATE_GITHUB_REPOSITORY', - similes: ['NEW_REPO', 'MAKE_REPOSITORY', 'CREATE_REPO'], + name: "CREATE_GITHUB_REPOSITORY", + similes: ["NEW_REPO", "MAKE_REPOSITORY", "CREATE_REPO"], description: - 'Creates a new GitHub repository with optional description and privacy settings. Can be chained with CREATE_GITHUB_ISSUE to add initial issues or LIST_GITHUB_REPOSITORIES to view all repositories', - enabled: false, // Disabled by default - repository creation is an infrastructure change + "Creates a new GitHub repository with optional description and privacy settings. Can be chained with CREATE_GITHUB_ISSUE to add initial issues or LIST_GITHUB_REPOSITORIES to view all repositories", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -334,39 +342,41 @@ export const createRepositoryAction: Action = { message: Memory, state: State | undefined, options: any = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract repository name from message text or options - const text = message.content.text || ''; + const text = message.content.text || ""; const nameMatch = text.match( - /(?:create|make|new)\s+(?:repo(?:sitory)?\s+)?(?:called\s+)?["\']?([a-zA-Z0-9_-]+)["\']?/i + /(?:create|make|new)\s+(?:repo(?:sitory)?\s+)?(?:called\s+)?["\']?([a-zA-Z0-9_-]+)["\']?/i, ); const name = options.name || nameMatch?.[1]; if (!name) { throw new Error( - 'Repository name is required. Please specify the name of the repository to create' + "Repository name is required. Please specify the name of the repository to create", ); } // Extract description if present - const descMatch = text.match(/(?:description|desc|about):\s*["\']?([^"'\n]+)["\']?/i); + const descMatch = text.match( + /(?:description|desc|about):\s*["\']?([^"'\n]+)["\']?/i, + ); const description = options.description || descMatch?.[1]; // Check for private/public keywords const isPrivate = options.private !== undefined ? options.private - : text.includes('private') + : text.includes("private") ? true - : text.includes('public') + : text.includes("public") ? false : false; @@ -381,15 +391,16 @@ export const createRepositoryAction: Action = { }; logger.info(`Creating repository: ${name}`); - const repository = await githubService.createRepository(repositoryOptions); + const repository = + await githubService.createRepository(repositoryOptions); const responseContent: Content = { text: `Successfully created repository: ${repository.full_name} -Description: ${repository.description || 'No description'} -Private: ${repository.private ? 'Yes' : 'No'} +Description: ${repository.description || "No description"} +Private: ${repository.private ? "Yes" : "No"} URL: ${repository.html_url} Clone URL: ${repository.clone_url}`, - actions: ['CREATE_GITHUB_REPOSITORY'], + actions: ["CREATE_GITHUB_REPOSITORY"], source: message.content.source, // Include data for callbacks repository, @@ -403,6 +414,7 @@ Clone URL: ${repository.clone_url}`, // Return ActionResult for chaining return { + success: true, text: responseContent.text, values: { success: true, @@ -412,7 +424,7 @@ Clone URL: ${repository.clone_url}`, repositoryName: repository.full_name, }, data: { - actionName: 'CREATE_GITHUB_REPOSITORY', + actionName: "CREATE_GITHUB_REPOSITORY", repository, github: { ...state?.github, @@ -426,10 +438,10 @@ Clone URL: ${repository.clone_url}`, }, }; } catch (error) { - logger.error('Error in CREATE_GITHUB_REPOSITORY action:', error); + logger.error("Error in CREATE_GITHUB_REPOSITORY action:", error); const errorContent: Content = { text: `Failed to create repository: ${error instanceof Error ? error.message : String(error)}`, - actions: ['CREATE_GITHUB_REPOSITORY'], + actions: ["CREATE_GITHUB_REPOSITORY"], source: message.content.source, }; @@ -438,13 +450,14 @@ Clone URL: ${repository.clone_url}`, } return { + success: false, text: errorContent.text, values: { success: false, error: error instanceof Error ? error.message : String(error), }, data: { - actionName: 'CREATE_GITHUB_REPOSITORY', + actionName: "CREATE_GITHUB_REPOSITORY", error: error instanceof Error ? error.message : String(error), }, }; @@ -454,31 +467,31 @@ Clone URL: ${repository.clone_url}`, examples: [ [ { - name: '{{user}}', + name: "{{user}}", content: { text: 'Create a new repository called my-awesome-project with description: "A really cool project" and make it public', }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { - text: 'Successfully created repository: user/my-awesome-project\nDescription: A really cool project\nPrivate: No\nURL: https://github.com/user/my-awesome-project\nClone URL: https://github.com/user/my-awesome-project.git', - actions: ['CREATE_GITHUB_REPOSITORY'], + text: "Successfully created repository: user/my-awesome-project\nDescription: A really cool project\nPrivate: No\nURL: https://github.com/user/my-awesome-project\nClone URL: https://github.com/user/my-awesome-project.git", + actions: ["CREATE_GITHUB_REPOSITORY"], }, }, ], [ { - name: '{{user}}', + name: "{{user}}", content: { text: 'Create a private repository called "secret-project" and then create an initial issue for setting up the README', }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { text: "I'll create a private repository called 'secret-project' and then set up an initial issue for the README.", - actions: ['CREATE_GITHUB_REPOSITORY', 'CREATE_GITHUB_ISSUE'], + actions: ["CREATE_GITHUB_REPOSITORY", "CREATE_GITHUB_ISSUE"], }, }, ], @@ -487,17 +500,17 @@ Clone URL: ${repository.clone_url}`, // Search Repositories Action export const searchRepositoriesAction: Action = { - name: 'SEARCH_GITHUB_REPOSITORIES', - similes: ['FIND_REPOS', 'SEARCH_REPOS', 'REPO_SEARCH'], + name: "SEARCH_GITHUB_REPOSITORIES", + similes: ["FIND_REPOS", "SEARCH_REPOS", "REPO_SEARCH"], description: - 'Searches for GitHub repositories based on query with sorting and filtering options. Can be chained with GET_GITHUB_REPOSITORY to inspect specific results or GET_GITHUB_ACTIVITY to check repository activity', + "Searches for GitHub repositories based on query with sorting and filtering options. Can be chained with GET_GITHUB_REPOSITORY to inspect specific results or GET_GITHUB_ACTIVITY to check repository activity", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -507,45 +520,47 @@ export const searchRepositoriesAction: Action = { state: State | undefined, options: { query?: string; - sort?: 'stars' | 'forks' | 'help-wanted-issues' | 'updated'; + sort?: "stars" | "forks" | "help-wanted-issues" | "updated"; limit?: number; } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract search query from message text or options - const text = message.content.text || ''; + const text = message.content.text || ""; const queryMatch = text.match( - /(?:search|find|look for)\s+(?:repos?(?:itories?)?\s+)?(?:for\s+)?["\']?([^"'\n]+?)["\']?(?:\s|$)/i + /(?:search|find|look for)\s+(?:repos?(?:itories?)?\s+)?(?:for\s+)?["\']?([^"'\n]+?)["\']?(?:\s|$)/i, ); const query = options.query || queryMatch?.[1]; if (!query) { - throw new Error('Search query is required. Please specify what repositories to search for'); + throw new Error( + "Search query is required. Please specify what repositories to search for", + ); } logger.info(`Searching repositories for: ${query}`); const searchResult = await githubService.searchRepositories(query, { - sort: options.sort || 'stars', + sort: options.sort || "stars", per_page: options.limit || 10, }); const repoList = searchResult.items .map( (repo: any) => - `• ${repo.full_name} (${repo.language || 'Unknown'}) - ⭐ ${repo.stargazers_count}\n ${repo.description || 'No description'}` + `• ${repo.full_name} (${repo.language || "Unknown"}) - ⭐ ${repo.stargazers_count}\n ${repo.description || "No description"}`, ) - .join('\n'); + .join("\n"); const responseContent: Content = { text: `Found ${searchResult.total_count} repositories for "${query}" (showing ${searchResult.items.length}):\n${repoList}`, - actions: ['SEARCH_GITHUB_REPOSITORIES'], + actions: ["SEARCH_GITHUB_REPOSITORIES"], source: message.content.source, // Include data for callbacks repositories: searchResult.items, @@ -559,16 +574,19 @@ export const searchRepositoriesAction: Action = { // Return ActionResult for chaining return { + success: true, text: responseContent.text, values: { success: true, repositories: searchResult.items, query, totalCount: searchResult.total_count, - repositoryNames: searchResult.items.map((repo: any) => repo.full_name), + repositoryNames: searchResult.items.map( + (repo: any) => repo.full_name, + ), }, data: { - actionName: 'SEARCH_GITHUB_REPOSITORIES', + actionName: "SEARCH_GITHUB_REPOSITORIES", repositories: searchResult.items, github: { ...state?.github, @@ -581,17 +599,17 @@ export const searchRepositoriesAction: Action = { acc[repo.full_name] = repo; return acc; }, - {} as Record + {} as Record, ), }, }, }, }; } catch (error) { - logger.error('Error in SEARCH_GITHUB_REPOSITORIES action:', error); + logger.error("Error in SEARCH_GITHUB_REPOSITORIES action:", error); const errorContent: Content = { text: `Failed to search repositories: ${error instanceof Error ? error.message : String(error)}`, - actions: ['SEARCH_GITHUB_REPOSITORIES'], + actions: ["SEARCH_GITHUB_REPOSITORIES"], source: message.content.source, }; @@ -600,13 +618,14 @@ export const searchRepositoriesAction: Action = { } return { + success: false, text: errorContent.text, values: { success: false, error: error instanceof Error ? error.message : String(error), }, data: { - actionName: 'SEARCH_GITHUB_REPOSITORIES', + actionName: "SEARCH_GITHUB_REPOSITORIES", error: error instanceof Error ? error.message : String(error), }, }; @@ -616,31 +635,31 @@ export const searchRepositoriesAction: Action = { examples: [ [ { - name: '{{user}}', + name: "{{user}}", content: { - text: 'Search for repositories about machine learning', + text: "Search for repositories about machine learning", }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { text: 'Found 50000 repositories for "machine learning" (showing 10):\n• tensorflow/tensorflow (C++) - ⭐ 185000\n An Open Source Machine Learning Framework for Everyone\n• scikit-learn/scikit-learn (Python) - ⭐ 59000\n Machine learning library for Python', - actions: ['SEARCH_GITHUB_REPOSITORIES'], + actions: ["SEARCH_GITHUB_REPOSITORIES"], }, }, ], [ { - name: '{{user}}', + name: "{{user}}", content: { - text: 'Find repositories about React hooks and check the most popular one for recent activity', + text: "Find repositories about React hooks and check the most popular one for recent activity", }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { text: "I'll search for React hooks repositories and then check the most popular one for recent activity.", - actions: ['SEARCH_GITHUB_REPOSITORIES', 'GET_GITHUB_ACTIVITY'], + actions: ["SEARCH_GITHUB_REPOSITORIES", "GET_GITHUB_ACTIVITY"], }, }, ], diff --git a/src/actions/search.ts b/src/actions/search.ts index fd27563..f4c37f0 100644 --- a/src/actions/search.ts +++ b/src/actions/search.ts @@ -1,26 +1,28 @@ import { type Action, + type ActionResult, type Content, type HandlerCallback, type IAgentRuntime, type Memory, type State, logger, -} from '@elizaos/core'; -import { GitHubService } from '../services/github'; +} from "@elizaos/core"; +import { GitHubService } from "../services/github"; // Advanced GitHub Search Action (searches repos, issues, and PRs) export const searchGitHubAction: Action = { - name: 'SEARCH_GITHUB', - similes: ['GITHUB_SEARCH', 'FIND_ON_GITHUB', 'SEARCH_ALL'], - description: 'Comprehensive GitHub search across repositories, issues, and pull requests', + name: "SEARCH_GITHUB", + similes: ["GITHUB_SEARCH", "FIND_ON_GITHUB", "SEARCH_ALL"], + description: + "Comprehensive GitHub search across repositories, issues, and pull requests", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -30,46 +32,46 @@ export const searchGitHubAction: Action = { state: State | undefined, options: { query?: string; - type?: 'repositories' | 'issues' | 'pull_requests' | 'all'; + type?: "repositories" | "issues" | "pull_requests" | "all"; sort?: string; limit?: number; } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract search query from message text or options - const text = message.content.text || ''; + const text = message.content.text || ""; const queryMatch = text.match( - /(?:search|find)\s+(?:for\s+)?["\']?([^"'\n]+?)["\']?(?:\s|$)/i + /(?:search|find)\s+(?:for\s+)?["\']?([^"'\n]+?)["\']?(?:\s|$)/i, ); const query = options.query || queryMatch?.[1]; if (!query) { - throw new Error('Search query is required'); + throw new Error("Search query is required"); } // Determine search type from context const searchType = options.type || - (text.includes('issue') - ? 'issues' - : text.includes('pr') || text.includes('pull request') - ? 'pull_requests' - : text.includes('repo') - ? 'repositories' - : 'all'); + (text.includes("issue") + ? "issues" + : text.includes("pr") || text.includes("pull request") + ? "pull_requests" + : text.includes("repo") + ? "repositories" + : "all"); const results: any = {}; - let responseText = ''; + let responseText = ""; - if (searchType === 'all' || searchType === 'repositories') { + if (searchType === "all" || searchType === "repositories") { const repos = await githubService.searchRepositories(query, { - sort: 'stars', + sort: "stars", per_page: options.limit || 5, }); results.repositories = repos.items; @@ -78,16 +80,16 @@ export const searchGitHubAction: Action = { responseText += repos.items .map( (r: any) => - `• ${r.full_name} - ⭐ ${r.stargazers_count} - ${r.description || 'No description'}` + `• ${r.full_name} - ⭐ ${r.stargazers_count} - ${r.description || "No description"}`, ) - .join('\n'); - responseText += '\n\n'; + .join("\n"); + responseText += "\n\n"; } } - if (searchType === 'all' || searchType === 'issues') { + if (searchType === "all" || searchType === "issues") { const issues = await githubService.searchIssues(`${query} is:issue`, { - sort: 'updated', + sort: "updated", per_page: options.limit || 5, }); results.issues = issues.items; @@ -97,17 +99,17 @@ export const searchGitHubAction: Action = { .map((i: any) => { const repoName = i.html_url ? i.html_url.match(/github\.com\/([^\/]+\/[^\/]+)/)?.[1] - : 'unknown'; + : "unknown"; return `• ${repoName}#${i.number}: ${i.title} (${i.state})`; }) - .join('\n'); - responseText += '\n\n'; + .join("\n"); + responseText += "\n\n"; } } - if (searchType === 'all' || searchType === 'pull_requests') { + if (searchType === "all" || searchType === "pull_requests") { const prs = await githubService.searchIssues(`${query} is:pr`, { - sort: 'updated', + sort: "updated", per_page: options.limit || 5, }); results.pullRequests = prs.items; @@ -117,11 +119,11 @@ export const searchGitHubAction: Action = { .map((pr: any) => { const repoName = pr.html_url ? pr.html_url.match(/github\.com\/([^\/]+\/[^\/]+)/)?.[1] - : 'unknown'; + : "unknown"; return `• ${repoName}#${pr.number}: ${pr.title} (${pr.state})`; }) - .join('\n'); - responseText += '\n\n'; + .join("\n"); + responseText += "\n\n"; } } @@ -133,7 +135,7 @@ export const searchGitHubAction: Action = { const responseContent: Content = { text: responseText, - actions: ['SEARCH_GITHUB'], + actions: ["SEARCH_GITHUB"], source: message.content.source, // Include data for callbacks results, @@ -147,6 +149,7 @@ export const searchGitHubAction: Action = { // Return result for chaining return { + success: true, text: responseContent.text, values: { results, @@ -162,12 +165,12 @@ export const searchGitHubAction: Action = { lastSearchType: searchType, }, }, - }; + } as ActionResult; } catch (error) { - logger.error('Error in SEARCH_GITHUB action:', error); + logger.error("Error in SEARCH_GITHUB action:", error); const errorContent: Content = { text: `Failed to search GitHub: ${error instanceof Error ? error.message : String(error)}`, - actions: ['SEARCH_GITHUB'], + actions: ["SEARCH_GITHUB"], source: message.content.source, }; @@ -175,53 +178,57 @@ export const searchGitHubAction: Action = { await callback(errorContent); } - return errorContent; + return { + success: false, + text: errorContent.text, + error: error instanceof Error ? error.message : String(error), + } as ActionResult; } }, examples: [ [ { - name: 'User', + name: "User", content: { text: 'Search GitHub for "machine learning"', }, }, { - name: 'Assistant', + name: "Assistant", content: { text: 'Search results for "machine learning":\n\n**Repositories (150,000 total):**\n• tensorflow/tensorflow - ⭐ 185000 - An Open Source Machine Learning Framework for Everyone\n• scikit-learn/scikit-learn - ⭐ 59000 - machine learning in Python\n• pytorch/pytorch - ⭐ 75000 - Tensors and Dynamic neural networks in Python\n• keras-team/keras - ⭐ 61000 - Deep Learning for humans\n• apache/spark - ⭐ 38000 - Apache Spark - A unified analytics engine\n\n**Issues (25,000 total):**\n• tensorflow/tensorflow#58432: Add support for new optimizer (open)\n• scikit-learn/scikit-learn#24567: Bug in RandomForest prediction (open)', - actions: ['SEARCH_GITHUB'], + actions: ["SEARCH_GITHUB"], }, }, ], [ { - name: 'User', + name: "User", content: { text: 'Find everything related to "GraphQL Apollo" on GitHub', }, }, { - name: 'Assistant', + name: "Assistant", content: { text: 'Search results for "GraphQL Apollo":\n\n**Repositories (8,500 total):**\n• apollographql/apollo-server - ⭐ 13500 - 🌍 Spec-compliant GraphQL server\n• apollographql/apollo-client - ⭐ 19000 - 🚀 A fully-featured GraphQL client\n• apollographql/apollo - ⭐ 2300 - 🚀 The Apollo GraphQL platform\n\n**Issues (2,100 total):**\n• apollographql/apollo-client#9876: Cache invalidation not working properly (open)\n• apollographql/apollo-server#5432: Federation v2 support (closed)\n\n**Pull Requests (890 total):**\n• apollographql/apollo-client#9890: Fix subscription memory leak (open)\n• apollographql/apollo-server#5445: Add custom scalar support (merged)\n\nWould you like me to dive deeper into any of these results?', - actions: ['SEARCH_GITHUB'], + actions: ["SEARCH_GITHUB"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Search for TypeScript issues in popular frameworks', + text: "Search for TypeScript issues in popular frameworks", }, }, { - name: 'Assistant', + name: "Assistant", content: { text: 'Search results for "TypeScript issues in popular frameworks":\n\n**Issues (12,345 total):**\n• microsoft/vscode#98765: TypeScript 5.0 support (open)\n• angular/angular#45678: Type inference regression in v15 (open)\n• vuejs/core#8901: TypeScript strict mode compatibility (closed)\n• facebook/react#23456: JSX transform TypeScript types (open)\n• nestjs/nest#7890: Decorator metadata issues with TS 5.0 (open)\n\n**Pull Requests (3,456 total):**\n• DefinitelyTyped/DefinitelyTyped#67890: Update React types for v18 (open)\n• microsoft/TypeScript#54321: Fix generic constraint inference (merged)\n• angular/angular#45679: Fix TypeScript 5.0 compatibility (open)\n\nThese results show active TypeScript-related development across major frameworks. Would you like me to search for something more specific?', - actions: ['SEARCH_GITHUB'], + actions: ["SEARCH_GITHUB"], }, }, ], diff --git a/src/actions/stats.ts b/src/actions/stats.ts index bdb1900..43b4a03 100644 --- a/src/actions/stats.ts +++ b/src/actions/stats.ts @@ -1,27 +1,28 @@ import { type Action, + type ActionResult, type Content, type HandlerCallback, type IAgentRuntime, type Memory, type State, logger, -} from '@elizaos/core'; -import { GitHubService } from '../services/github'; +} from "@elizaos/core"; +import { GitHubService } from "../services/github"; // Get Repository Stats Action export const getRepositoryStatsAction: Action = { - name: 'GET_GITHUB_REPO_STATS', - similes: ['REPO_STATS', 'REPOSITORY_STATS', 'PROJECT_STATS', 'REPO_METRICS'], + name: "GET_GITHUB_REPO_STATS", + similes: ["REPO_STATS", "REPOSITORY_STATS", "PROJECT_STATS", "REPO_METRICS"], description: - 'Gets comprehensive statistics about a GitHub repository including contributors, commits, and activity', + "Gets comprehensive statistics about a GitHub repository including contributors, commits, and activity", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -30,28 +31,33 @@ export const getRepositoryStatsAction: Action = { message: Memory, state: State | undefined, options: { owner?: string; repo?: string } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract owner and repo from message text or options - const text = message.content.text || ''; - const ownerRepoMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/); + const text = message.content.text || ""; + const ownerRepoMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/, + ); const owner = options.owner || ownerRepoMatch?.[1] || state?.github?.lastRepository?.owner?.login || - runtime.getSetting('GITHUB_OWNER'); - const repo = options.repo || ownerRepoMatch?.[2] || state?.github?.lastRepository?.name; + runtime.getSetting("GITHUB_OWNER"); + const repo = + options.repo || + ownerRepoMatch?.[2] || + state?.github?.lastRepository?.name; if (!owner || !repo) { throw new Error( - 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options' + 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options', ); } @@ -61,20 +67,32 @@ export const getRepositoryStatsAction: Action = { const repository = await githubService.getRepository(owner, repo); // Get contributor stats - const contributors = await githubService.getContributorsStats(owner, repo); + const contributors = await githubService.getContributorsStats( + owner, + repo, + ); // Get commit activity - const commitActivity = await githubService.getCommitActivityStats(owner, repo); + const commitActivity = await githubService.getCommitActivityStats( + owner, + repo, + ); // Get code frequency - const codeFrequency = await githubService.getCodeFrequencyStats(owner, repo); + const codeFrequency = await githubService.getCodeFrequencyStats( + owner, + repo, + ); // Get language breakdown const languages = await githubService.getLanguages(owner, repo); // Calculate stats const totalCommits = - contributors?.reduce((sum: number, c: any) => sum + (c.total || 0), 0) || 0; + contributors?.reduce( + (sum: number, c: any) => sum + (c.total || 0), + 0, + ) || 0; const topContributors = (contributors || []) .sort((a: any, b: any) => (b.total || 0) - (a.total || 0)) .slice(0, 5); @@ -83,7 +101,7 @@ export const getRepositoryStatsAction: Action = { const recentWeeks = (commitActivity || []).slice(-4); const recentCommits = recentWeeks.reduce( (sum: number, week: any) => sum + (week.total || 0), - 0 + 0, ); // Code changes (last entry in code frequency) @@ -94,7 +112,7 @@ export const getRepositoryStatsAction: Action = { // Language stats const totalBytes = Object.values(languages || {}).reduce( (sum: number, bytes: any) => sum + bytes, - 0 + 0, ); const languagePercentages = Object.entries(languages || {}) .map(([lang, bytes]: [string, any]) => ({ @@ -121,16 +139,16 @@ Total Commits: ${totalCommits} Top Contributors: ${topContributors - .map((c: any) => `• @${c.author?.login || 'unknown'} - ${c.total} commits`) - .join('\n')} + .map((c: any) => `• @${c.author?.login || "unknown"} - ${c.total} commits`) + .join("\n")} **Recent Activity (Last 4 weeks):** Total Commits: ${recentCommits} Last Week: +${Math.abs(additions)} lines, -${Math.abs(deletions)} lines **Languages:** -${languagePercentages.map((l) => `• ${l.language}: ${l.percentage}%`).join('\n')}`, - actions: ['GET_GITHUB_REPO_STATS'], +${languagePercentages.map((l) => `• ${l.language}: ${l.percentage}%`).join("\n")}`, + actions: ["GET_GITHUB_REPO_STATS"], source: message.content.source, }; @@ -140,6 +158,7 @@ ${languagePercentages.map((l) => `• ${l.language}: ${l.percentage}%`).join('\n // Return result for chaining return { + success: true, text: responseContent.text, values: { repository: `${owner}/${repo}`, @@ -185,12 +204,12 @@ ${languagePercentages.map((l) => `• ${l.language}: ${l.percentage}%`).join('\n }, }, }, - }; + } as ActionResult; } catch (error) { - logger.error('Error in GET_GITHUB_REPO_STATS action:', error); + logger.error("Error in GET_GITHUB_REPO_STATS action:", error); const errorContent: Content = { text: `Failed to get repository stats: ${error instanceof Error ? error.message : String(error)}`, - actions: ['GET_GITHUB_REPO_STATS'], + actions: ["GET_GITHUB_REPO_STATS"], source: message.content.source, }; @@ -198,38 +217,42 @@ ${languagePercentages.map((l) => `• ${l.language}: ${l.percentage}%`).join('\n await callback(errorContent); } - return errorContent; + return { + success: false, + text: errorContent.text, + error: error instanceof Error ? error.message : String(error), + } as ActionResult; } }, examples: [ [ { - name: 'User', + name: "User", content: { - text: 'Get stats for facebook/react repository', + text: "Get stats for facebook/react repository", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'Repository Stats for facebook/react:\n\n**Basic Info:**\n• Created: 5/29/2013\n• Last Updated: 3/21/2024\n• Stars: ⭐ 223456\n• Forks: 🍴 45678\n• Open Issues: 📝 1234\n• Size: 234.5 MB\n\n**Contributors:**\nTotal Contributors: 1584\nTotal Commits: 15234\n\nTop Contributors:\n• @gaearon - 892 commits\n• @sophiebits - 765 commits\n• @sebmarkbage - 654 commits\n• @acdlite - 543 commits\n• @zpao - 432 commits\n\n**Recent Activity (Last 4 weeks):**\nTotal Commits: 234\nLast Week: +1234 lines, -567 lines\n\n**Languages:**\n• JavaScript: 68.2%\n• TypeScript: 25.3%\n• CSS: 3.8%\n• HTML: 2.1%\n• Other: 0.6%', - actions: ['GET_GITHUB_REPO_STATS'], + text: "Repository Stats for facebook/react:\n\n**Basic Info:**\n• Created: 5/29/2013\n• Last Updated: 3/21/2024\n• Stars: ⭐ 223456\n• Forks: 🍴 45678\n• Open Issues: 📝 1234\n• Size: 234.5 MB\n\n**Contributors:**\nTotal Contributors: 1584\nTotal Commits: 15234\n\nTop Contributors:\n• @gaearon - 892 commits\n• @sophiebits - 765 commits\n• @sebmarkbage - 654 commits\n• @acdlite - 543 commits\n• @zpao - 432 commits\n\n**Recent Activity (Last 4 weeks):**\nTotal Commits: 234\nLast Week: +1234 lines, -567 lines\n\n**Languages:**\n• JavaScript: 68.2%\n• TypeScript: 25.3%\n• CSS: 3.8%\n• HTML: 2.1%\n• Other: 0.6%", + actions: ["GET_GITHUB_REPO_STATS"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Analyze the activity and health of my project repository', + text: "Analyze the activity and health of my project repository", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'Repository Stats for user/project:\n\n**Basic Info:**\n• Created: 1/15/2024\n• Last Updated: 3/21/2024\n• Stars: ⭐ 42\n• Forks: 🍴 8\n• Open Issues: 📝 5\n• Size: 12.3 MB\n\n**Contributors:**\nTotal Contributors: 3\nTotal Commits: 234\n\nTop Contributors:\n• @user - 189 commits\n• @contributor1 - 34 commits\n• @contributor2 - 11 commits\n\n**Recent Activity (Last 4 weeks):**\nTotal Commits: 45\nLast Week: +892 lines, -234 lines\n\n**Languages:**\n• TypeScript: 72.5%\n• JavaScript: 15.2%\n• CSS: 8.3%\n• HTML: 4.0%\n\nThe repository shows healthy activity with consistent commits. Consider addressing the 5 open issues to improve project health.', - actions: ['GET_GITHUB_REPO_STATS'], + text: "Repository Stats for user/project:\n\n**Basic Info:**\n• Created: 1/15/2024\n• Last Updated: 3/21/2024\n• Stars: ⭐ 42\n• Forks: 🍴 8\n• Open Issues: 📝 5\n• Size: 12.3 MB\n\n**Contributors:**\nTotal Contributors: 3\nTotal Commits: 234\n\nTop Contributors:\n• @user - 189 commits\n• @contributor1 - 34 commits\n• @contributor2 - 11 commits\n\n**Recent Activity (Last 4 weeks):**\nTotal Commits: 45\nLast Week: +892 lines, -234 lines\n\n**Languages:**\n• TypeScript: 72.5%\n• JavaScript: 15.2%\n• CSS: 8.3%\n• HTML: 4.0%\n\nThe repository shows healthy activity with consistent commits. Consider addressing the 5 open issues to improve project health.", + actions: ["GET_GITHUB_REPO_STATS"], }, }, ], @@ -238,16 +261,17 @@ ${languagePercentages.map((l) => `• ${l.language}: ${l.percentage}%`).join('\n // Get Repository Traffic Action export const getRepositoryTrafficAction: Action = { - name: 'GET_GITHUB_REPO_TRAFFIC', - similes: ['REPO_TRAFFIC', 'VISITOR_STATS', 'TRAFFIC_ANALYTICS'], - description: 'Gets traffic statistics for a GitHub repository (requires push access)', + name: "GET_GITHUB_REPO_TRAFFIC", + similes: ["REPO_TRAFFIC", "VISITOR_STATS", "TRAFFIC_ANALYTICS"], + description: + "Gets traffic statistics for a GitHub repository (requires push access)", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -256,28 +280,33 @@ export const getRepositoryTrafficAction: Action = { message: Memory, state: State | undefined, options: { owner?: string; repo?: string } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract owner and repo from message text or options - const text = message.content.text || ''; - const ownerRepoMatch = text.match(/(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/); + const text = message.content.text || ""; + const ownerRepoMatch = text.match( + /(?:github\.com\/)?([^\/\s]+)\/([^\/\s]+)/, + ); const owner = options.owner || ownerRepoMatch?.[1] || state?.github?.lastRepository?.owner?.login || - runtime.getSetting('GITHUB_OWNER'); - const repo = options.repo || ownerRepoMatch?.[2] || state?.github?.lastRepository?.name; + runtime.getSetting("GITHUB_OWNER"); + const repo = + options.repo || + ownerRepoMatch?.[2] || + state?.github?.lastRepository?.name; if (!owner || !repo) { throw new Error( - 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options' + 'Repository owner and name are required. Please specify as "owner/repo" or provide them in options', ); } @@ -311,14 +340,14 @@ export const getRepositoryTrafficAction: Action = { ${paths .slice(0, 5) .map((p: any) => `• ${p.path} - ${p.count} views (${p.uniques} unique)`) - .join('\n')} + .join("\n")} **Top Referrers:** ${referrers .slice(0, 5) .map((r: any) => `• ${r.referrer} - ${r.count} views (${r.uniques} unique)`) - .join('\n')}`, - actions: ['GET_GITHUB_REPO_TRAFFIC'], + .join("\n")}`, + actions: ["GET_GITHUB_REPO_TRAFFIC"], source: message.content.source, }; @@ -327,6 +356,7 @@ ${referrers } return { + success: true, text: responseContent.text, values: { repository: `${owner}/${repo}`, @@ -367,7 +397,7 @@ ${referrers if (error.status === 403) { const responseContent: Content = { text: `Traffic statistics are not available for ${owner}/${repo}. This feature requires push access to the repository.`, - actions: ['GET_GITHUB_REPO_TRAFFIC'], + actions: ["GET_GITHUB_REPO_TRAFFIC"], source: message.content.source, }; @@ -376,6 +406,7 @@ ${referrers } return { + success: false, text: responseContent.text, values: { repository: `${owner}/${repo}`, @@ -389,10 +420,10 @@ ${referrers throw error; } } catch (error) { - logger.error('Error in GET_GITHUB_REPO_TRAFFIC action:', error); + logger.error("Error in GET_GITHUB_REPO_TRAFFIC action:", error); const errorContent: Content = { text: `Failed to get repository traffic: ${error instanceof Error ? error.message : String(error)}`, - actions: ['GET_GITHUB_REPO_TRAFFIC'], + actions: ["GET_GITHUB_REPO_TRAFFIC"], source: message.content.source, }; @@ -400,23 +431,26 @@ ${referrers await callback(errorContent); } - return errorContent; + return { + success: false, + ...errorContent, + }; } }, examples: [ [ { - name: 'User', + name: "User", content: { - text: 'Show traffic stats for my awesome-project repository', + text: "Show traffic stats for my awesome-project repository", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'Traffic Stats for user/awesome-project:\n\n**Views (Last 14 days):**\n• Total Views: 1,234\n• Unique Visitors: 456\n\n**Clones (Last 14 days):**\n• Total Clones: 89\n• Unique Cloners: 34\n\n**Popular Content:**\n• /README.md - 456 views (234 unique)\n• /docs/getting-started.md - 123 views (89 unique)\n• /src/index.js - 78 views (45 unique)\n• /package.json - 56 views (34 unique)\n• /LICENSE - 45 views (23 unique)\n\n**Top Referrers:**\n• google.com - 234 views (123 unique)\n• github.com - 189 views (98 unique)\n• stackoverflow.com - 78 views (45 unique)\n• reddit.com - 56 views (34 unique)\n• twitter.com - 45 views (23 unique)', - actions: ['GET_GITHUB_REPO_TRAFFIC'], + text: "Traffic Stats for user/awesome-project:\n\n**Views (Last 14 days):**\n• Total Views: 1,234\n• Unique Visitors: 456\n\n**Clones (Last 14 days):**\n• Total Clones: 89\n• Unique Cloners: 34\n\n**Popular Content:**\n• /README.md - 456 views (234 unique)\n• /docs/getting-started.md - 123 views (89 unique)\n• /src/index.js - 78 views (45 unique)\n• /package.json - 56 views (34 unique)\n• /LICENSE - 45 views (23 unique)\n\n**Top Referrers:**\n• google.com - 234 views (123 unique)\n• github.com - 189 views (98 unique)\n• stackoverflow.com - 78 views (45 unique)\n• reddit.com - 56 views (34 unique)\n• twitter.com - 45 views (23 unique)", + actions: ["GET_GITHUB_REPO_TRAFFIC"], }, }, ], diff --git a/src/actions/users.ts b/src/actions/users.ts index 119725e..e11ae44 100644 --- a/src/actions/users.ts +++ b/src/actions/users.ts @@ -1,26 +1,27 @@ import { type Action, + type ActionResult, type Content, type HandlerCallback, type IAgentRuntime, type Memory, type State, logger, -} from '@elizaos/core'; -import { GitHubService } from '../services/github'; +} from "@elizaos/core"; +import { GitHubService } from "../services/github"; // Get User Profile Action export const getUserProfileAction: Action = { - name: 'GET_GITHUB_USER', - similes: ['GET_USER', 'USER_PROFILE', 'CHECK_USER', 'USER_INFO'], - description: 'Retrieves detailed information about a GitHub user', + name: "GET_GITHUB_USER", + similes: ["GET_USER", "USER_PROFILE", "CHECK_USER", "USER_INFO"], + description: "Retrieves detailed information about a GitHub user", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -29,34 +30,36 @@ export const getUserProfileAction: Action = { message: Memory, state: State | undefined, options: { username?: string } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract username from message text or options - const text = message.content.text || ''; - const usernameMatch = text.match(/@?([a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38})/); + const text = message.content.text || ""; + const usernameMatch = text.match( + /@?([a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38})/, + ); const username = options.username || usernameMatch?.[1]; if (!username) { // Get current authenticated user if no username provided - logger.info('No username provided, getting authenticated user'); + logger.info("No username provided, getting authenticated user"); const user = await githubService.getCurrentUser(); const responseContent: Content = { text: `Your GitHub Profile: Username: @${user.login} -Name: ${user.name || 'Not set'} -Bio: ${user.bio || 'No bio'} -Company: ${user.company || 'Not specified'} -Location: ${user.location || 'Not specified'} -Email: ${user.email || 'Not public'} -Twitter: ${user.twitter_username ? `@${user.twitter_username}` : 'Not linked'} -Blog: ${user.blog || 'No blog'} +Name: ${user.name || "Not set"} +Bio: ${user.bio || "No bio"} +Company: ${user.company || "Not specified"} +Location: ${user.location || "Not specified"} +Email: ${user.email || "Not public"} +Twitter: ${user.twitter_username ? `@${user.twitter_username}` : "Not linked"} +Blog: ${user.blog || "No blog"} Public Repos: ${user.public_repos} Public Gists: ${user.public_gists} @@ -67,7 +70,7 @@ Created: ${new Date(user.created_at).toLocaleDateString()} Updated: ${new Date(user.updated_at).toLocaleDateString()} Profile URL: ${user.html_url}`, - actions: ['GET_GITHUB_USER'], + actions: ["GET_GITHUB_USER"], source: message.content.source, }; @@ -76,6 +79,7 @@ Profile URL: ${user.html_url}`, } return { + success: true, text: responseContent.text, values: { user, @@ -98,13 +102,13 @@ Profile URL: ${user.html_url}`, const responseContent: Content = { text: `GitHub User: @${user.login} -Name: ${user.name || 'Not set'} -Bio: ${user.bio || 'No bio'} -Company: ${user.company || 'Not specified'} -Location: ${user.location || 'Not specified'} -Email: ${user.email || 'Not public'} -Twitter: ${user.twitter_username ? `@${user.twitter_username}` : 'Not linked'} -Blog: ${user.blog || 'No blog'} +Name: ${user.name || "Not set"} +Bio: ${user.bio || "No bio"} +Company: ${user.company || "Not specified"} +Location: ${user.location || "Not specified"} +Email: ${user.email || "Not public"} +Twitter: ${user.twitter_username ? `@${user.twitter_username}` : "Not linked"} +Blog: ${user.blog || "No blog"} Type: ${user.type} Public Repos: ${user.public_repos} @@ -116,7 +120,7 @@ Created: ${new Date(user.created_at).toLocaleDateString()} Updated: ${new Date(user.updated_at).toLocaleDateString()} Profile URL: ${user.html_url}`, - actions: ['GET_GITHUB_USER'], + actions: ["GET_GITHUB_USER"], source: message.content.source, }; @@ -126,6 +130,7 @@ Profile URL: ${user.html_url}`, // Return result for chaining return { + success: true, text: responseContent.text, values: { user, @@ -142,12 +147,12 @@ Profile URL: ${user.html_url}`, }, }, }, - }; + } as ActionResult; } catch (error) { - logger.error('Error in GET_GITHUB_USER action:', error); + logger.error("Error in GET_GITHUB_USER action:", error); const errorContent: Content = { text: `Failed to get user information: ${error instanceof Error ? error.message : String(error)}`, - actions: ['GET_GITHUB_USER'], + actions: ["GET_GITHUB_USER"], source: message.content.source, }; @@ -155,38 +160,42 @@ Profile URL: ${user.html_url}`, await callback(errorContent); } - return errorContent; + return { + success: false, + text: errorContent.text, + error: error instanceof Error ? error.message : String(error), + } as ActionResult; } }, examples: [ [ { - name: 'User', + name: "User", content: { - text: 'Get information about @octocat', + text: "Get information about @octocat", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'GitHub User: @octocat\nName: The Octocat\nBio: GitHub mascot\nCompany: @github\nLocation: San Francisco\nEmail: Not public\nTwitter: Not linked\nBlog: https://github.blog\nType: User\n\nPublic Repos: 8\nPublic Gists: 8\nFollowers: 3938\nFollowing: 9\n\nCreated: 1/25/2011\nUpdated: 3/21/2024\n\nProfile URL: https://github.com/octocat', - actions: ['GET_GITHUB_USER'], + text: "GitHub User: @octocat\nName: The Octocat\nBio: GitHub mascot\nCompany: @github\nLocation: San Francisco\nEmail: Not public\nTwitter: Not linked\nBlog: https://github.blog\nType: User\n\nPublic Repos: 8\nPublic Gists: 8\nFollowers: 3938\nFollowing: 9\n\nCreated: 1/25/2011\nUpdated: 3/21/2024\n\nProfile URL: https://github.com/octocat", + actions: ["GET_GITHUB_USER"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Check torvalds profile and see his top repositories', + text: "Check torvalds profile and see his top repositories", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'GitHub User: @torvalds\nName: Linus Torvalds\nBio: Creator of Linux\nCompany: Linux Foundation\nLocation: Portland, OR\nEmail: Not public\nTwitter: Not linked\nBlog: Not specified\nType: User\n\nPublic Repos: 6\nPublic Gists: 0\nFollowers: 171234\nFollowing: 0\n\nCreated: 9/3/2011\nUpdated: 3/21/2024\n\nProfile URL: https://github.com/torvalds\n\nNow let me check his repositories...', - actions: ['GET_GITHUB_USER', 'LIST_USER_REPOSITORIES'], + text: "GitHub User: @torvalds\nName: Linus Torvalds\nBio: Creator of Linux\nCompany: Linux Foundation\nLocation: Portland, OR\nEmail: Not public\nTwitter: Not linked\nBlog: Not specified\nType: User\n\nPublic Repos: 6\nPublic Gists: 0\nFollowers: 171234\nFollowing: 0\n\nCreated: 9/3/2011\nUpdated: 3/21/2024\n\nProfile URL: https://github.com/torvalds\n\nNow let me check his repositories...", + actions: ["GET_GITHUB_USER", "LIST_USER_REPOSITORIES"], }, }, ], @@ -195,17 +204,17 @@ Profile URL: ${user.html_url}`, // Get User Stats Action export const getUserStatsAction: Action = { - name: 'GET_GITHUB_USER_STATS', - similes: ['USER_STATS', 'USER_ACTIVITY', 'USER_CONTRIBUTIONS'], + name: "GET_GITHUB_USER_STATS", + similes: ["USER_STATS", "USER_ACTIVITY", "USER_CONTRIBUTIONS"], description: - 'Gets comprehensive statistics about a GitHub user including contributions and language breakdown', + "Gets comprehensive statistics about a GitHub user including contributions and language breakdown", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -214,26 +223,30 @@ export const getUserStatsAction: Action = { message: Memory, state: State | undefined, options: { username?: string; year?: number } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract username from message text or options - const text = message.content.text || ''; - const usernameMatch = text.match(/@?([a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38})/); + const text = message.content.text || ""; + const usernameMatch = text.match( + /@?([a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38})/, + ); const username = - options.username || usernameMatch?.[1] || (await githubService.getCurrentUser()).login; + options.username || + usernameMatch?.[1] || + (await githubService.getCurrentUser()).login; logger.info(`Getting stats for user ${username}`); // Get user's repositories to calculate stats const repos = await githubService.listUserRepositories(username, { - type: 'owner', - sort: 'updated', + type: "owner", + sort: "updated", per_page: 100, }); @@ -245,7 +258,8 @@ export const getUserStatsAction: Action = { for (const repo of repos) { if (repo.language) { - languageStats[repo.language] = (languageStats[repo.language] || 0) + 1; + languageStats[repo.language] = + (languageStats[repo.language] || 0) + 1; } totalStars += repo.stargazers_count || 0; totalForks += repo.forks_count || 0; @@ -268,9 +282,15 @@ export const getUserStatsAction: Action = { eventTypes[event.type] = (eventTypes[event.type] || 0) + 1; } - const recentCommits = events.filter((e: any) => e.type === 'PushEvent').length; - const recentPRs = events.filter((e: any) => e.type === 'PullRequestEvent').length; - const recentIssues = events.filter((e: any) => e.type === 'IssuesEvent').length; + const recentCommits = events.filter( + (e: any) => e.type === "PushEvent", + ).length; + const recentPRs = events.filter( + (e: any) => e.type === "PullRequestEvent", + ).length; + const recentIssues = events.filter( + (e: any) => e.type === "IssuesEvent", + ).length; const responseContent: Content = { text: `GitHub Stats for @${username}: @@ -282,7 +302,7 @@ Total Forks: 🍴 ${totalForks} Total Open Issues: 📝 ${totalIssues} **Top Languages:** -${sortedLanguages.map(([lang, count]) => `• ${lang}: ${count} repos`).join('\n') || 'No languages detected'} +${sortedLanguages.map(([lang, count]) => `• ${lang}: ${count} repos`).join("\n") || "No languages detected"} **Recent Activity (last 100 events):** Push Events (Commits): ${recentCommits} @@ -292,13 +312,16 @@ Total Events: ${events.length} **Most Popular Repositories:** ${repos - .sort((a: any, b: any) => (b.stargazers_count || 0) - (a.stargazers_count || 0)) + .sort( + (a: any, b: any) => (b.stargazers_count || 0) - (a.stargazers_count || 0), + ) .slice(0, 5) .map( - (r: any) => `• ${r.name} - ⭐ ${r.stargazers_count || 0} - ${r.description || 'No description'}` + (r: any) => + `• ${r.name} - ⭐ ${r.stargazers_count || 0} - ${r.description || "No description"}`, ) - .join('\n')}`, - actions: ['GET_GITHUB_USER_STATS'], + .join("\n")}`, + actions: ["GET_GITHUB_USER_STATS"], source: message.content.source, }; @@ -308,6 +331,7 @@ ${repos // Return result for chaining return { + success: true, text: responseContent.text, values: { username, @@ -349,10 +373,10 @@ ${repos }, }; } catch (error) { - logger.error('Error in GET_GITHUB_USER_STATS action:', error); + logger.error("Error in GET_GITHUB_USER_STATS action:", error); const errorContent: Content = { text: `Failed to get user stats: ${error instanceof Error ? error.message : String(error)}`, - actions: ['GET_GITHUB_USER_STATS'], + actions: ["GET_GITHUB_USER_STATS"], source: message.content.source, }; @@ -360,38 +384,41 @@ ${repos await callback(errorContent); } - return errorContent; + return { + success: false, + ...errorContent, + }; } }, examples: [ [ { - name: 'User', + name: "User", content: { - text: 'Get stats for @gaearon', + text: "Get stats for @gaearon", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'GitHub Stats for @gaearon:\n\n**Repository Statistics:**\nTotal Repositories: 245\nTotal Stars Received: ⭐ 89234\nTotal Forks: 🍴 12456\nTotal Open Issues: 📝 234\n\n**Top Languages:**\n• JavaScript: 178 repos\n• TypeScript: 45 repos\n• HTML: 12 repos\n• CSS: 8 repos\n• Shell: 2 repos\n\n**Recent Activity (last 100 events):**\nPush Events (Commits): 34\nPull Request Events: 12\nIssue Events: 8\nTotal Events: 100\n\n**Most Popular Repositories:**\n• redux - ⭐ 60234 - Predictable state container for JavaScript apps\n• react-hot-loader - ⭐ 12456 - Live editing of React\n• overreacted.io - ⭐ 6789 - Personal blog by Dan Abramov\n• whatthefuck.is - ⭐ 4567 - An opinionated glossary of computer science terms\n• react-dnd - ⭐ 3456 - Drag and Drop for React', - actions: ['GET_GITHUB_USER_STATS'], + text: "GitHub Stats for @gaearon:\n\n**Repository Statistics:**\nTotal Repositories: 245\nTotal Stars Received: ⭐ 89234\nTotal Forks: 🍴 12456\nTotal Open Issues: 📝 234\n\n**Top Languages:**\n• JavaScript: 178 repos\n• TypeScript: 45 repos\n• HTML: 12 repos\n• CSS: 8 repos\n• Shell: 2 repos\n\n**Recent Activity (last 100 events):**\nPush Events (Commits): 34\nPull Request Events: 12\nIssue Events: 8\nTotal Events: 100\n\n**Most Popular Repositories:**\n• redux - ⭐ 60234 - Predictable state container for JavaScript apps\n• react-hot-loader - ⭐ 12456 - Live editing of React\n• overreacted.io - ⭐ 6789 - Personal blog by Dan Abramov\n• whatthefuck.is - ⭐ 4567 - An opinionated glossary of computer science terms\n• react-dnd - ⭐ 3456 - Drag and Drop for React", + actions: ["GET_GITHUB_USER_STATS"], }, }, ], [ { - name: 'User', + name: "User", content: { - text: 'Show me my GitHub statistics and language breakdown', + text: "Show me my GitHub statistics and language breakdown", }, }, { - name: 'Assistant', + name: "Assistant", content: { text: "GitHub Stats for @user:\n\n**Repository Statistics:**\nTotal Repositories: 42\nTotal Stars Received: ⭐ 156\nTotal Forks: 🍴 23\nTotal Open Issues: 📝 8\n\n**Top Languages:**\n• TypeScript: 15 repos\n• JavaScript: 12 repos\n• Python: 8 repos\n• Go: 4 repos\n• Rust: 3 repos\n\n**Recent Activity (last 100 events):**\nPush Events (Commits): 67\nPull Request Events: 8\nIssue Events: 5\nTotal Events: 100\n\n**Most Popular Repositories:**\n• awesome-project - ⭐ 45 - A really cool project\n• my-cli-tool - ⭐ 34 - Useful CLI tool for developers\n• react-components - ⭐ 28 - Reusable React components\n• api-wrapper - ⭐ 21 - API wrapper for various services\n• dotfiles - ⭐ 18 - My personal dotfiles\n\nYour most active language is TypeScript, and you've been quite active with 67 commits recently!", - actions: ['GET_GITHUB_USER_STATS'], + actions: ["GET_GITHUB_USER_STATS"], }, }, ], @@ -400,16 +427,16 @@ ${repos // List User Repositories Action export const listUserRepositoriesAction: Action = { - name: 'LIST_USER_REPOSITORIES', - similes: ['USER_REPOS', 'USER_PROJECTS', 'GET_USER_REPOS'], - description: 'Lists repositories for a specific GitHub user', + name: "LIST_USER_REPOSITORIES", + similes: ["USER_REPOS", "USER_PROJECTS", "GET_USER_REPOS"], + description: "Lists repositories for a specific GitHub user", validate: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); return !!githubService; }, @@ -419,44 +446,51 @@ export const listUserRepositoriesAction: Action = { state: State | undefined, options: { username?: string; - type?: 'all' | 'owner' | 'member'; - sort?: 'created' | 'updated' | 'pushed' | 'full_name'; + type?: "all" | "owner" | "member"; + sort?: "created" | "updated" | "pushed" | "full_name"; limit?: number; } = {}, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service not available'); + throw new Error("GitHub service not available"); } // Extract username from message text or options - const text = message.content.text || ''; - const usernameMatch = text.match(/@?([a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38})/); - const username = options.username || usernameMatch?.[1] || state?.github?.lastUser?.login; + const text = message.content.text || ""; + const usernameMatch = text.match( + /@?([a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38})/, + ); + const username = + options.username || + usernameMatch?.[1] || + state?.github?.lastUser?.login; if (!username) { - throw new Error('Username is required. Please specify a GitHub username'); + throw new Error( + "Username is required. Please specify a GitHub username", + ); } logger.info(`Listing repositories for user ${username}`); const repos = await githubService.listUserRepositories(username, { - type: options.type || 'owner', - sort: options.sort || 'updated', + type: options.type || "owner", + sort: options.sort || "updated", per_page: options.limit || 10, }); const repoList = repos .map( (repo: any) => - `• ${repo.name} (${repo.language || 'Unknown'}) - ⭐ ${repo.stargazers_count}${repo.fork ? ' (fork)' : ''}${repo.description ? `\n ${repo.description}` : ''}` + `• ${repo.name} (${repo.language || "Unknown"}) - ⭐ ${repo.stargazers_count}${repo.fork ? " (fork)" : ""}${repo.description ? `\n ${repo.description}` : ""}`, ) - .join('\n'); + .join("\n"); const responseContent: Content = { text: `Repositories for @${username} (${repos.length} shown):\n${repoList}`, - actions: ['LIST_USER_REPOSITORIES'], + actions: ["LIST_USER_REPOSITORIES"], source: message.content.source, // Include data for callbacks repositories: repos, @@ -469,6 +503,7 @@ export const listUserRepositoriesAction: Action = { // Return result for chaining return { + success: true, text: responseContent.text, values: { repositories: repos, @@ -487,10 +522,10 @@ export const listUserRepositoriesAction: Action = { }, }; } catch (error) { - logger.error('Error in LIST_USER_REPOSITORIES action:', error); + logger.error("Error in LIST_USER_REPOSITORIES action:", error); const errorContent: Content = { text: `Failed to list user repositories: ${error instanceof Error ? error.message : String(error)}`, - actions: ['LIST_USER_REPOSITORIES'], + actions: ["LIST_USER_REPOSITORIES"], source: message.content.source, }; @@ -498,23 +533,26 @@ export const listUserRepositoriesAction: Action = { await callback(errorContent); } - return errorContent; + return { + success: false, + ...errorContent, + }; } }, examples: [ [ { - name: 'User', + name: "User", content: { - text: 'List repositories for @sindresorhus', + text: "List repositories for @sindresorhus", }, }, { - name: 'Assistant', + name: "Assistant", content: { - text: 'Repositories for @sindresorhus (10 shown):\n• awesome - (Unknown) - ⭐ 285234\n 😎 Awesome lists about all kinds of interesting topics\n• awesome-nodejs - (Unknown) - ⭐ 54567\n ⚡ Delightful Node.js packages and resources\n• got - (TypeScript) - ⭐ 13456\n 🌐 Human-friendly and powerful HTTP request library for Node.js\n• ora - (JavaScript) - ⭐ 8901\n Elegant terminal spinner\n• execa - (JavaScript) - ⭐ 5678\n Process execution for humans\n• p-limit - (JavaScript) - ⭐ 1234\n Run multiple promise-returning & async functions with limited concurrency', - actions: ['LIST_USER_REPOSITORIES'], + text: "Repositories for @sindresorhus (10 shown):\n• awesome - (Unknown) - ⭐ 285234\n 😎 Awesome lists about all kinds of interesting topics\n• awesome-nodejs - (Unknown) - ⭐ 54567\n ⚡ Delightful Node.js packages and resources\n• got - (TypeScript) - ⭐ 13456\n 🌐 Human-friendly and powerful HTTP request library for Node.js\n• ora - (JavaScript) - ⭐ 8901\n Elegant terminal spinner\n• execa - (JavaScript) - ⭐ 5678\n Process execution for humans\n• p-limit - (JavaScript) - ⭐ 1234\n Run multiple promise-returning & async functions with limited concurrency", + actions: ["LIST_USER_REPOSITORIES"], }, }, ], diff --git a/src/actions/webhooks.ts b/src/actions/webhooks.ts index cf196dc..a815cb3 100644 --- a/src/actions/webhooks.ts +++ b/src/actions/webhooks.ts @@ -6,13 +6,19 @@ import { type HandlerCallback, logger, ModelType, -} from '@elizaos/core'; -import { GitHubService } from '../services/github'; -import { z } from 'zod'; +} from "@elizaos/core"; +import { GitHubService } from "../services/github"; +import { z } from "zod"; // Schema for webhook evaluation const WebhookIntentSchema = z.object({ - intent: z.enum(['create_webhook', 'list_webhooks', 'delete_webhook', 'ping_webhook', 'unclear']), + intent: z.enum([ + "create_webhook", + "list_webhooks", + "delete_webhook", + "ping_webhook", + "unclear", + ]), confidence: z.number().min(0).max(1), reasoning: z.string(), parameters: z @@ -31,14 +37,14 @@ type WebhookIntent = z.infer; async function analyzeWebhookIntent( runtime: IAgentRuntime, message: Memory, - state: State + state: State, ): Promise { const prompt = `Analyze this message to determine if the user wants to perform a webhook-related action: Message: "${message.content.text}" Context from state: -${state.data?.github?.lastRepository ? `Current repository: ${state.data.github.lastRepository.full_name}` : 'No repository context'} +${state.data?.github?.lastRepository ? `Current repository: ${state.data.github.lastRepository.full_name}` : "No repository context"} Determine: 1. What webhook action they want (create, list, delete, ping, or unclear) @@ -69,67 +75,71 @@ Format as JSON matching this schema: const parsed = WebhookIntentSchema.parse(JSON.parse(response)); return parsed; } catch (error) { - logger.warn('Failed to analyze webhook intent:', error); + logger.warn("Failed to analyze webhook intent:", error); return { - intent: 'unclear', + intent: "unclear", confidence: 0, - reasoning: 'Failed to parse intent', + reasoning: "Failed to parse intent", }; } } export const createWebhookAction: Action = { - name: 'CREATE_GITHUB_WEBHOOK', - similes: ['SETUP_WEBHOOK', 'ADD_WEBHOOK', 'CONFIGURE_WEBHOOK'], - description: 'Create a GitHub webhook for real-time event processing using Ngrok tunnel', - enabled: false, // Disabled by default - webhook creation is an infrastructure change + name: "CREATE_GITHUB_WEBHOOK", + similes: ["SETUP_WEBHOOK", "ADD_WEBHOOK", "CONFIGURE_WEBHOOK"], + description: + "Create a GitHub webhook for real-time event processing using Ngrok tunnel", examples: [ [ { - name: '{{user}}', + name: "{{user}}", content: { - text: 'Create a webhook for owner/repo to listen for issues and pull requests', + text: "Create a webhook for owner/repo to listen for issues and pull requests", }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { text: "I'll create a webhook for the repository with the appropriate events configured.", - actions: ['CREATE_GITHUB_WEBHOOK'], + actions: ["CREATE_GITHUB_WEBHOOK"], }, }, ], ], - async validate(runtime: IAgentRuntime, message: Memory, state?: State): Promise { + async validate( + runtime: IAgentRuntime, + message: Memory, + state?: State, + ): Promise { // Use LLM to determine if this is a webhook creation request const intent = await analyzeWebhookIntent( runtime, message, - state || { values: {}, data: {}, text: '' } + state || { values: {}, data: {}, text: "" }, ); - return intent.intent === 'create_webhook' && intent.confidence > 0.7; + return intent.intent === "create_webhook" && intent.confidence > 0.7; }, async handler( runtime: IAgentRuntime, message: Memory, - state: State = { values: {}, data: {}, text: '' }, + state: State = { values: {}, data: {}, text: "" }, options: any = {}, - callback: HandlerCallback = async () => [] + callback: HandlerCallback = async () => [], ): Promise { try { - const githubService = runtime.getService('github'); - const tunnelService = runtime.getService('tunnel') as any; + const githubService = runtime.getService("github"); + const tunnelService = runtime.getService("tunnel") as any; if (!githubService) { - throw new Error('GitHub service is not available'); + throw new Error("GitHub service is not available"); } if (!tunnelService || !tunnelService.isActive()) { throw new Error( - 'Ngrok tunnel service is not available. Please start the tunnel service first.' + "Ngrok tunnel service is not available. Please start the tunnel service first.", ); } @@ -137,10 +147,10 @@ export const createWebhookAction: Action = { const intent = await analyzeWebhookIntent( runtime, message, - state || { values: {}, data: {}, text: '' } + state || { values: {}, data: {}, text: "" }, ); - if (intent.intent !== 'create_webhook') { + if (intent.intent !== "create_webhook") { await callback({ text: "I understand you want to work with webhooks, but I'm not sure exactly what you want to create. Could you be more specific?", thought: `Intent analysis: ${intent.reasoning}`, @@ -173,18 +183,21 @@ Format as JSON: "inferredRepo": "possible_repo" }`; - const clarificationResponse = await runtime.useModel(ModelType.TEXT_LARGE, { - prompt: clarificationPrompt, - temperature: 0.3, - max_tokens: 200, - }); + const clarificationResponse = await runtime.useModel( + ModelType.TEXT_LARGE, + { + prompt: clarificationPrompt, + temperature: 0.3, + max_tokens: 200, + }, + ); const clarification = JSON.parse(clarificationResponse); if (clarification.needsClarification) { await callback({ text: clarification.clarificationMessage, - thought: 'Need repository clarification for webhook creation', + thought: "Need repository clarification for webhook creation", }); return; } @@ -196,8 +209,8 @@ Format as JSON: if (!owner || !repo) { await callback({ - text: 'I need to know which repository you want to create a webhook for. Please specify the owner and repository name.', - thought: 'Missing repository information', + text: "I need to know which repository you want to create a webhook for. Please specify the owner and repository name.", + thought: "Missing repository information", }); return; } @@ -207,20 +220,20 @@ Format as JSON: const webhookUrl = `${tunnelUrl}/api/github/webhook`; // Get webhook secret - const webhookSecret = runtime.getSetting('GITHUB_WEBHOOK_SECRET'); + const webhookSecret = runtime.getSetting("GITHUB_WEBHOOK_SECRET"); if (!webhookSecret) { logger.warn( - 'No webhook secret configured - webhook will be created without signature verification' + "No webhook secret configured - webhook will be created without signature verification", ); } // Default events or use specified events const events = intent.parameters?.events || [ - 'issues', - 'issue_comment', - 'pull_request', - 'pull_request_review', - 'push', + "issues", + "issue_comment", + "pull_request", + "pull_request_review", + "push", ]; // Create the webhook @@ -229,10 +242,10 @@ Format as JSON: repo, { url: webhookUrl, - content_type: 'json', + content_type: "json", secret: webhookSecret, }, - events + events, ); // Test the webhook @@ -244,12 +257,12 @@ Format as JSON: **Webhook Details:** - ID: ${webhook.id} - URL: ${webhookUrl} -- Events: ${events.join(', ')} -- Secret: ${webhookSecret ? 'Configured ✅' : 'Not configured ⚠️'} +- Events: ${events.join(", ")} +- Secret: ${webhookSecret ? "Configured ✅" : "Not configured ⚠️"} The webhook has been tested with a ping and is ready to receive events.`, thought: `Created webhook ${webhook.id} for ${owner}/${repo}`, - actions: ['CREATE_GITHUB_WEBHOOK'], + actions: ["CREATE_GITHUB_WEBHOOK"], }); // Update state @@ -267,7 +280,7 @@ The webhook has been tested with a ping and is ready to receive events.`, events, }; } catch (error) { - logger.error('Failed to create GitHub webhook:', error); + logger.error("Failed to create GitHub webhook:", error); await callback({ text: `❌ Failed to create webhook: ${error instanceof Error ? error.message : String(error)} @@ -275,61 +288,65 @@ Common issues: - Make sure you have admin access to the repository - Verify your GitHub token has webhook permissions - Check that the Ngrok tunnel is active`, - thought: 'Error creating webhook', + thought: "Error creating webhook", }); } }, }; export const listWebhooksAction: Action = { - name: 'LIST_GITHUB_WEBHOOKS', - similes: ['SHOW_WEBHOOKS', 'GET_WEBHOOKS', 'VIEW_WEBHOOKS'], - description: 'List all webhooks configured for a GitHub repository', + name: "LIST_GITHUB_WEBHOOKS", + similes: ["SHOW_WEBHOOKS", "GET_WEBHOOKS", "VIEW_WEBHOOKS"], + description: "List all webhooks configured for a GitHub repository", examples: [ [ { - name: '{{user}}', + name: "{{user}}", content: { - text: 'List webhooks for owner/repo', + text: "List webhooks for owner/repo", }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { text: "I'll show you all the webhooks configured for that repository.", - actions: ['LIST_GITHUB_WEBHOOKS'], + actions: ["LIST_GITHUB_WEBHOOKS"], }, }, ], ], - async validate(runtime: IAgentRuntime, message: Memory, state?: State): Promise { + async validate( + runtime: IAgentRuntime, + message: Memory, + state?: State, + ): Promise { const intent = await analyzeWebhookIntent( runtime, message, - state || { values: {}, data: {}, text: '' } + state || { values: {}, data: {}, text: "" }, ); - return intent.intent === 'list_webhooks' && intent.confidence > 0.7; + return intent.intent === "list_webhooks" && intent.confidence > 0.7; }, async handler( runtime: IAgentRuntime, message: Memory, - state: State = { values: {}, data: {}, text: '' }, + state: State = { values: {}, data: {}, text: "" }, options: any = {}, - callback: HandlerCallback = async () => [] + callback: HandlerCallback = async () => [], ): Promise { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service is not available'); + throw new Error("GitHub service is not available"); } const intent = await analyzeWebhookIntent( runtime, message, - state || { values: {}, data: {}, text: '' } + state || { values: {}, data: {}, text: "" }, ); let owner = intent.parameters?.owner; @@ -342,8 +359,8 @@ export const listWebhooksAction: Action = { repo = repo || state.data?.github?.lastRepository?.name; } else { await callback({ - text: 'I need to know which repository you want to list webhooks for. Please specify the owner and repository name.', - thought: 'Missing repository information', + text: "I need to know which repository you want to list webhooks for. Please specify the owner and repository name.", + thought: "Missing repository information", }); return; } @@ -364,16 +381,16 @@ export const listWebhooksAction: Action = { webhooks.forEach((webhook, index) => { responseText += `**${index + 1}. Webhook #${webhook.id}**\n`; responseText += `- URL: ${webhook.config.url}\n`; - responseText += `- Events: ${webhook.events.join(', ')}\n`; - responseText += `- Active: ${webhook.active ? '✅' : '❌'}\n`; - responseText += `- Last Response: ${webhook.last_response?.code || 'N/A'}\n`; + responseText += `- Events: ${webhook.events.join(", ")}\n`; + responseText += `- Active: ${webhook.active ? "✅" : "❌"}\n`; + responseText += `- Last Response: ${webhook.last_response?.code || "N/A"}\n`; responseText += `- Created: ${new Date(webhook.created_at).toLocaleDateString()}\n\n`; }); await callback({ text: responseText, thought: `Listed ${webhooks.length} webhooks for ${owner}/${repo}`, - actions: ['LIST_GITHUB_WEBHOOKS'], + actions: ["LIST_GITHUB_WEBHOOKS"], }); // Update state @@ -385,65 +402,68 @@ export const listWebhooksAction: Action = { } state!.data.github.lastWebhooks = webhooks; } catch (error) { - logger.error('Failed to list GitHub webhooks:', error); + logger.error("Failed to list GitHub webhooks:", error); await callback({ text: `❌ Failed to list webhooks: ${error instanceof Error ? error.message : String(error)}`, - thought: 'Error listing webhooks', + thought: "Error listing webhooks", }); } }, }; export const deleteWebhookAction: Action = { - name: 'DELETE_GITHUB_WEBHOOK', - similes: ['REMOVE_WEBHOOK', 'DESTROY_WEBHOOK'], - description: 'Delete a specific GitHub webhook from a repository', - enabled: false, // Disabled by default - webhook deletion is an infrastructure change + name: "DELETE_GITHUB_WEBHOOK", + similes: ["REMOVE_WEBHOOK", "DESTROY_WEBHOOK"], + description: "Delete a specific GitHub webhook from a repository", examples: [ [ { - name: '{{user}}', + name: "{{user}}", content: { - text: 'Delete webhook 12345 from owner/repo', + text: "Delete webhook 12345 from owner/repo", }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { text: "I'll delete that webhook from the repository.", - actions: ['DELETE_GITHUB_WEBHOOK'], + actions: ["DELETE_GITHUB_WEBHOOK"], }, }, ], ], - async validate(runtime: IAgentRuntime, message: Memory, state?: State): Promise { + async validate( + runtime: IAgentRuntime, + message: Memory, + state?: State, + ): Promise { const intent = await analyzeWebhookIntent( runtime, message, - state || { values: {}, data: {}, text: '' } + state || { values: {}, data: {}, text: "" }, ); - return intent.intent === 'delete_webhook' && intent.confidence > 0.7; + return intent.intent === "delete_webhook" && intent.confidence > 0.7; }, async handler( runtime: IAgentRuntime, message: Memory, - state: State = { values: {}, data: {}, text: '' }, + state: State = { values: {}, data: {}, text: "" }, options: any = {}, - callback: HandlerCallback = async () => [] + callback: HandlerCallback = async () => [], ): Promise { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service is not available'); + throw new Error("GitHub service is not available"); } const intent = await analyzeWebhookIntent( runtime, message, - state || { values: {}, data: {}, text: '' } + state || { values: {}, data: {}, text: "" }, ); let owner = intent.parameters?.owner; @@ -453,7 +473,7 @@ export const deleteWebhookAction: Action = { if (!webhookId) { await callback({ text: 'I need the webhook ID to delete. Please specify which webhook you want to delete (e.g., "delete webhook 12345").', - thought: 'Missing webhook ID', + thought: "Missing webhook ID", }); return; } @@ -465,8 +485,8 @@ export const deleteWebhookAction: Action = { repo = repo || state.data?.github?.lastRepository?.name; } else { await callback({ - text: 'I need to know which repository the webhook belongs to. Please specify the owner and repository name.', - thought: 'Missing repository information', + text: "I need to know which repository the webhook belongs to. Please specify the owner and repository name.", + thought: "Missing repository information", }); return; } @@ -480,7 +500,7 @@ export const deleteWebhookAction: Action = { await callback({ text: `❌ Webhook #${webhookId} not found in ${owner}/${repo}. -Available webhooks: ${webhooks.map((w) => `#${w.id}`).join(', ') || 'None'}`, +Available webhooks: ${webhooks.map((w) => `#${w.id}`).join(", ") || "None"}`, thought: `Webhook ${webhookId} not found`, }); return; @@ -494,69 +514,73 @@ Available webhooks: ${webhooks.map((w) => `#${w.id}`).join(', ') || 'None'}`, **Deleted webhook details:** - URL: ${webhook.config.url} -- Events: ${webhook.events.join(', ')}`, +- Events: ${webhook.events.join(", ")}`, thought: `Deleted webhook ${webhookId} from ${owner}/${repo}`, - actions: ['DELETE_GITHUB_WEBHOOK'], + actions: ["DELETE_GITHUB_WEBHOOK"], }); } catch (error) { - logger.error('Failed to delete GitHub webhook:', error); + logger.error("Failed to delete GitHub webhook:", error); await callback({ text: `❌ Failed to delete webhook: ${error instanceof Error ? error.message : String(error)}`, - thought: 'Error deleting webhook', + thought: "Error deleting webhook", }); } }, }; export const pingWebhookAction: Action = { - name: 'PING_GITHUB_WEBHOOK', - similes: ['TEST_WEBHOOK', 'CHECK_WEBHOOK'], - description: 'Send a ping to test a GitHub webhook', + name: "PING_GITHUB_WEBHOOK", + similes: ["TEST_WEBHOOK", "CHECK_WEBHOOK"], + description: "Send a ping to test a GitHub webhook", examples: [ [ { - name: '{{user}}', + name: "{{user}}", content: { - text: 'Ping webhook 12345 on owner/repo', + text: "Ping webhook 12345 on owner/repo", }, }, { - name: '{{agent}}', + name: "{{agent}}", content: { text: "I'll send a test ping to that webhook.", - actions: ['PING_GITHUB_WEBHOOK'], + actions: ["PING_GITHUB_WEBHOOK"], }, }, ], ], - async validate(runtime: IAgentRuntime, message: Memory, state?: State): Promise { + async validate( + runtime: IAgentRuntime, + message: Memory, + state?: State, + ): Promise { const intent = await analyzeWebhookIntent( runtime, message, - state || { values: {}, data: {}, text: '' } + state || { values: {}, data: {}, text: "" }, ); - return intent.intent === 'ping_webhook' && intent.confidence > 0.7; + return intent.intent === "ping_webhook" && intent.confidence > 0.7; }, async handler( runtime: IAgentRuntime, message: Memory, - state: State = { values: {}, data: {}, text: '' }, + state: State = { values: {}, data: {}, text: "" }, options: any = {}, - callback: HandlerCallback = async () => [] + callback: HandlerCallback = async () => [], ): Promise { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { - throw new Error('GitHub service is not available'); + throw new Error("GitHub service is not available"); } const intent = await analyzeWebhookIntent( runtime, message, - state || { values: {}, data: {}, text: '' } + state || { values: {}, data: {}, text: "" }, ); let owner = intent.parameters?.owner; @@ -566,7 +590,7 @@ export const pingWebhookAction: Action = { if (!webhookId) { await callback({ text: 'I need the webhook ID to ping. Please specify which webhook you want to test (e.g., "ping webhook 12345").', - thought: 'Missing webhook ID', + thought: "Missing webhook ID", }); return; } @@ -578,8 +602,8 @@ export const pingWebhookAction: Action = { repo = repo || state.data?.github?.lastRepository?.name; } else { await callback({ - text: 'I need to know which repository the webhook belongs to. Please specify the owner and repository name.', - thought: 'Missing repository information', + text: "I need to know which repository the webhook belongs to. Please specify the owner and repository name.", + thought: "Missing repository information", }); return; } @@ -593,10 +617,10 @@ export const pingWebhookAction: Action = { The webhook should receive a test payload. Check your webhook endpoint logs or GitHub's webhook delivery logs to verify it was received.`, thought: `Pinged webhook ${webhookId} on ${owner}/${repo}`, - actions: ['PING_GITHUB_WEBHOOK'], + actions: ["PING_GITHUB_WEBHOOK"], }); } catch (error) { - logger.error('Failed to ping GitHub webhook:', error); + logger.error("Failed to ping GitHub webhook:", error); await callback({ text: `❌ Failed to ping webhook: ${error instanceof Error ? error.message : String(error)} @@ -604,7 +628,7 @@ Common issues: - Webhook ID doesn't exist - Network connectivity problems - GitHub API rate limits`, - thought: 'Error pinging webhook', + thought: "Error pinging webhook", }); } }, diff --git a/src/frontend/index.css b/src/frontend/index.css index 6da3b37..da7ce48 100644 --- a/src/frontend/index.css +++ b/src/frontend/index.css @@ -1,5 +1,5 @@ @config "../../tailwind.config.js"; -@import 'tailwindcss'; +@import "tailwindcss"; @layer base { :root { diff --git a/src/frontend/index.tsx b/src/frontend/index.tsx index 5b9b986..30a329d 100644 --- a/src/frontend/index.tsx +++ b/src/frontend/index.tsx @@ -1,8 +1,12 @@ -import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query'; -import { createRoot } from 'react-dom/client'; -import './index.css'; -import React from 'react'; -import type { UUID } from '@elizaos/core'; +import { + QueryClient, + QueryClientProvider, + useQuery, +} from "@tanstack/react-query"; +import { createRoot } from "react-dom/client"; +import "./index.css"; +import React from "react"; +import type { UUID } from "@elizaos/core"; const queryClient = new QueryClient(); @@ -25,11 +29,11 @@ interface TimeResponse { */ function TimeDisplay({ apiBase }: { apiBase: string }) { const { data, isLoading, error, refetch } = useQuery({ - queryKey: ['currentTime'], + queryKey: ["currentTime"], queryFn: async (): Promise => { const response = await fetch(`${apiBase}/api/time`); if (!response.ok) { - throw new Error('Failed to fetch time'); + throw new Error("Failed to fetch time"); } return response.json() as Promise; }, @@ -43,7 +47,8 @@ function TimeDisplay({ apiBase }: { apiBase: string }) { if (error) { return (
- Error fetching time: {error instanceof Error ? error.message : 'Unknown error'} + Error fetching time:{" "} + {error instanceof Error ? error.message : "Unknown error"}
); } @@ -79,17 +84,19 @@ function TimeDisplay({ apiBase }: { apiBase: string }) { function ExampleRoute() { const config = (window as any).ELIZA_CONFIG as ElizaConfig | undefined; const agentId = config?.agentId; - const apiBase = config?.apiBase || 'http://localhost:3000'; + const apiBase = config?.apiBase || "http://localhost:3000"; // Apply dark mode to the root element React.useEffect(() => { - document.documentElement.classList.add('dark'); + document.documentElement.classList.add("dark"); }, []); if (!agentId) { return (
-
Error: Agent ID not found
+
+ Error: Agent ID not found +
The server should inject the agent ID configuration.
@@ -103,13 +110,21 @@ function ExampleRoute() { /** * Example provider component */ -function ExampleProvider({ agentId, apiBase }: { agentId: UUID; apiBase: string }) { +function ExampleProvider({ + agentId, + apiBase, +}: { + agentId: UUID; + apiBase: string; +}) { return (

Plugin Starter Example

-
Agent ID: {agentId}
+
+ Agent ID: {agentId} +
@@ -118,7 +133,7 @@ function ExampleProvider({ agentId, apiBase }: { agentId: UUID; apiBase: string } // Initialize the application - no router needed for iframe -const rootElement = document.getElementById('root'); +const rootElement = document.getElementById("root"); if (rootElement) { createRoot(rootElement).render(); } @@ -152,11 +167,11 @@ const PanelComponent: React.FC = ({ agentId }) => { // Export the panel configuration for integration with the agent UI export const panels: AgentPanel[] = [ { - name: 'Example', - path: 'example', + name: "Example", + path: "example", component: PanelComponent, - icon: 'Book', + icon: "Book", public: false, - shortLabel: 'Example', + shortLabel: "Example", }, ]; diff --git a/src/index.ts b/src/index.ts index 488a2d2..531b09f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,19 @@ -import type { Plugin } from '@elizaos/core'; -import { type Action, type IAgentRuntime, type Provider, logger, ModelType } from '@elizaos/core'; -import { GitHubService } from './services/github'; -import { githubConfigSchema, githubConfigSchemaFlexible, type GitHubConfig } from './types'; -import crypto from 'crypto'; -import { z } from 'zod'; +import type { Plugin } from "@elizaos/core"; +import { + type Action, + type IAgentRuntime, + type Provider, + logger, + ModelType, +} from "@elizaos/core"; +import { GitHubService } from "./services/github"; +import { + githubConfigSchema, + githubConfigSchemaFlexible, + type GitHubConfig, +} from "./types"; +import * as crypto from "crypto"; +import { z } from "zod"; // Schema for intelligent webhook event analysis const WebhookEventAnalysisSchema = z.object({ @@ -12,8 +22,13 @@ const WebhookEventAnalysisSchema = z.object({ reasoning: z.string(), mentionContext: z.string().optional(), actionRequired: z.boolean(), - urgency: z.enum(['low', 'medium', 'high']), - mentionType: z.enum(['direct_mention', 'implicit_request', 'false_positive', 'none']), + urgency: z.enum(["low", "medium", "high"]), + mentionType: z.enum([ + "direct_mention", + "implicit_request", + "false_positive", + "none", + ]), }); type WebhookEventAnalysis = z.infer; @@ -23,7 +38,7 @@ const MessageRelevanceSchema = z.object({ isGitHubRelated: z.boolean(), confidence: z.number().min(0).max(1), reasoning: z.string(), - context: z.enum(['repository', 'issue', 'pull_request', 'general', 'none']), + context: z.enum(["repository", "issue", "pull_request", "general", "none"]), requiresAction: z.boolean(), }); @@ -34,7 +49,7 @@ async function analyzeWebhookMention( runtime: IAgentRuntime, content: string, agentName: string, - context: string + context: string, ): Promise { const prompt = `Analyze this GitHub content to determine if the agent "${agentName}" is being mentioned or requested: @@ -67,9 +82,9 @@ Respond with JSON: // Extract JSON from response that might contain markdown backticks let jsonText = response; - if (typeof response === 'string') { + if (typeof response === "string") { // Remove markdown code block markers if present - jsonText = response.replace(/```json\s*|\s*```/g, '').trim(); + jsonText = response.replace(/```json\s*|\s*```/g, "").trim(); // Try to find JSON object in the text const jsonMatch = jsonText.match(/\{[\s\S]*\}/); if (jsonMatch) { @@ -79,14 +94,14 @@ Respond with JSON: return WebhookEventAnalysisSchema.parse(JSON.parse(jsonText)); } catch (_error) { - logger.warn('Failed to analyze webhook mention:', _error); + logger.warn("Failed to analyze webhook mention:", _error); return { isAgentMentioned: false, confidence: 0, - reasoning: 'Failed to parse mention analysis', + reasoning: "Failed to parse mention analysis", actionRequired: false, - urgency: 'low', - mentionType: 'none', + urgency: "low", + mentionType: "none", }; } } @@ -94,7 +109,7 @@ Respond with JSON: // Use LLM to analyze message relevance to GitHub async function analyzeMessageRelevance( runtime: IAgentRuntime, - text: string + text: string, ): Promise { const prompt = `Analyze this message to determine if it's related to GitHub or requires GitHub plugin functionality: @@ -126,9 +141,9 @@ Respond with JSON: // Extract JSON from response that might contain markdown backticks let jsonText = response; - if (typeof response === 'string') { + if (typeof response === "string") { // Remove markdown code block markers if present - jsonText = response.replace(/```json\s*|\s*```/g, '').trim(); + jsonText = response.replace(/```json\s*|\s*```/g, "").trim(); // Try to find JSON object in the text const jsonMatch = jsonText.match(/\{[\s\S]*\}/); if (jsonMatch) { @@ -138,12 +153,12 @@ Respond with JSON: return MessageRelevanceSchema.parse(JSON.parse(jsonText)); } catch (error) { - logger.warn('Failed to analyze message relevance:', error); + logger.warn("Failed to analyze message relevance:", error); return { isGitHubRelated: false, confidence: 0, - reasoning: 'Failed to parse relevance analysis', - context: 'none', + reasoning: "Failed to parse relevance analysis", + context: "none", requiresAction: false, }; } @@ -155,52 +170,58 @@ import { listRepositoriesAction, createRepositoryAction, searchRepositoriesAction, -} from './actions/repository'; +} from "./actions/repository"; import { getIssueAction, listIssuesAction, createIssueAction, searchIssuesAction, -} from './actions/issues'; +} from "./actions/issues"; import { getPullRequestAction, listPullRequestsAction, createPullRequestAction, mergePullRequestAction, -} from './actions/pullRequests'; +} from "./actions/pullRequests"; import { getGitHubActivityAction, clearGitHubActivityAction, getGitHubRateLimitAction, -} from './actions/activity'; +} from "./actions/activity"; -import { searchGitHubAction } from './actions/search'; +import { searchGitHubAction } from "./actions/search"; import { getUserProfileAction, getUserStatsAction, listUserRepositoriesAction, -} from './actions/users'; +} from "./actions/users"; import { listBranchesAction, createBranchAction, getBranchProtectionAction, -} from './actions/branches'; +} from "./actions/branches"; -import { getRepositoryStatsAction, getRepositoryTrafficAction } from './actions/stats'; +import { + getRepositoryStatsAction, + getRepositoryTrafficAction, +} from "./actions/stats"; import { createWebhookAction, listWebhooksAction, deleteWebhookAction, pingWebhookAction, -} from './actions/webhooks'; +} from "./actions/webhooks"; -import { autoCodeIssueAction, respondToMentionAction } from './actions/autoCoder'; +import { + autoCodeIssueAction, + respondToMentionAction, +} from "./actions/autoCoder"; // Import all providers import { @@ -209,7 +230,7 @@ import { githubPullRequestsProvider, githubActivityProvider, githubUserProvider, -} from './providers/github'; +} from "./providers/github"; // Test suites are defined in a separate module to avoid bundling in production // They will be loaded dynamically by the test runner when needed @@ -218,14 +239,14 @@ import { function verifyWebhookSignature( payload: any, signature: string | undefined, - secret: string + secret: string, ): boolean { if (!signature || !secret) { return false; } - const hmac = crypto.createHmac('sha256', secret); - const digest = `sha256=${hmac.update(JSON.stringify(payload)).digest('hex')}`; + const hmac = crypto.createHmac("sha256", secret); + const digest = `sha256=${hmac.update(JSON.stringify(payload)).digest("hex")}`; // Use timingSafeEqual to prevent timing attacks if (signature.length !== digest.length) { @@ -239,7 +260,7 @@ function verifyWebhookSignature( async function processWebhookEvent( runtime: IAgentRuntime, event: string, - payload: any + payload: any, ): Promise { // Emit the raw GitHub event await runtime.emitEvent(`github:${event}`, { @@ -251,25 +272,30 @@ async function processWebhookEvent( // Handle specific events switch (event) { - case 'issues': - if (payload.action === 'opened' || payload.action === 'edited') { + case "issues": + if (payload.action === "opened" || payload.action === "edited") { const issue = payload.issue; - const body = issue.body || ''; - const title = issue.title || ''; + const body = issue.body || ""; + const title = issue.title || ""; const agentName = runtime.character.name; const content = `${title}\n${body}`; const context = `Issue #${issue.number} in ${payload.repository.full_name}`; // Use intelligent analysis instead of string matching - const analysis = await analyzeWebhookMention(runtime, content, agentName, context); + const analysis = await analyzeWebhookMention( + runtime, + content, + agentName, + context, + ); if (analysis.isAgentMentioned && analysis.confidence > 0.6) { logger.info( - `Agent ${agentName} intelligently detected in issue #${issue.number} (confidence: ${Math.round(analysis.confidence * 100)}%)` + `Agent ${agentName} intelligently detected in issue #${issue.number} (confidence: ${Math.round(analysis.confidence * 100)}%)`, ); logger.debug(`Mention analysis: ${analysis.reasoning}`); - await runtime.emitEvent('github:agent_mentioned', { + await runtime.emitEvent("github:agent_mentioned", { runtime, issue, repository: payload.repository, @@ -277,28 +303,35 @@ async function processWebhookEvent( analysis, // Include analysis for downstream processing }); } else if (analysis.confidence > 0.3) { - logger.debug(`Possible mention detected but below threshold: ${analysis.reasoning}`); + logger.debug( + `Possible mention detected but below threshold: ${analysis.reasoning}`, + ); } } break; - case 'issue_comment': - if (payload.action === 'created' || payload.action === 'edited') { + case "issue_comment": + if (payload.action === "created" || payload.action === "edited") { const comment = payload.comment; - const body = comment.body || ''; + const body = comment.body || ""; const agentName = runtime.character.name; const context = `Comment on issue #${payload.issue.number} in ${payload.repository.full_name}`; // Use intelligent analysis instead of string matching - const analysis = await analyzeWebhookMention(runtime, body, agentName, context); + const analysis = await analyzeWebhookMention( + runtime, + body, + agentName, + context, + ); if (analysis.isAgentMentioned && analysis.confidence > 0.6) { logger.info( - `Agent ${agentName} intelligently detected in issue comment (confidence: ${Math.round(analysis.confidence * 100)}%)` + `Agent ${agentName} intelligently detected in issue comment (confidence: ${Math.round(analysis.confidence * 100)}%)`, ); logger.debug(`Mention analysis: ${analysis.reasoning}`); - await runtime.emitEvent('github:agent_mentioned_comment', { + await runtime.emitEvent("github:agent_mentioned_comment", { runtime, issue: payload.issue, comment, @@ -308,16 +341,16 @@ async function processWebhookEvent( }); } else if (analysis.confidence > 0.3) { logger.debug( - `Possible mention in comment detected but below threshold: ${analysis.reasoning}` + `Possible mention in comment detected but below threshold: ${analysis.reasoning}`, ); } } break; - case 'pull_request': + case "pull_request": // Emit PR-specific events - if (payload.action === 'opened') { - await runtime.emitEvent('github:pr_opened', { + if (payload.action === "opened") { + await runtime.emitEvent("github:pr_opened", { runtime, pullRequest: payload.pull_request, repository: payload.repository, @@ -393,11 +426,11 @@ const githubProviders: Provider[] = [ ]; export const githubPlugin: Plugin = { - name: 'plugin-github', + name: "plugin-github", description: - 'Comprehensive GitHub integration plugin for ElizaOS with repository management, issue tracking, and PR workflows', + "Comprehensive GitHub integration plugin for ElizaOS with repository management, issue tracking, and PR workflows", - dependencies: ['ngrok'], + dependencies: ["ngrok"], config: { GITHUB_TOKEN: process.env.GITHUB_TOKEN, @@ -405,14 +438,17 @@ export const githubPlugin: Plugin = { GITHUB_WEBHOOK_SECRET: process.env.GITHUB_WEBHOOK_SECRET, }, - async init(config: Record, runtime?: IAgentRuntime): Promise { - logger.info('Initializing GitHub plugin...'); + async init( + config: Record, + runtime?: IAgentRuntime, + ): Promise { + logger.info("Initializing GitHub plugin..."); try { // Try to get token from runtime if available const token = - runtime?.getSetting('GITHUB_TOKEN') || - runtime?.getSetting('GITHUB_TOKEN') || + runtime?.getSetting("GITHUB_TOKEN") || + runtime?.getSetting("GITHUB_TOKEN") || config.GITHUB_TOKEN || config.GITHUB_TOKEN || process.env.GITHUB_TOKEN || @@ -420,33 +456,39 @@ export const githubPlugin: Plugin = { // Detect if we're in a test environment const isTestEnv = - process.env.NODE_ENV === 'test' || - process.env.VITEST === 'true' || + process.env.NODE_ENV === "test" || + process.env.VITEST === "true" || process.env.JEST_WORKER_ID !== undefined || - process.argv.some((arg) => arg.includes('test') || arg.includes('spec')) || - typeof globalThis.describe !== 'undefined' || // Vitest globals - typeof globalThis.it !== 'undefined' || - typeof globalThis.expect !== 'undefined' || + process.argv.some( + (arg) => arg.includes("test") || arg.includes("spec"), + ) || + typeof (globalThis as any).describe !== "undefined" || // Vitest globals + typeof (globalThis as any).it !== "undefined" || + typeof (globalThis as any).expect !== "undefined" || (token && - (token.startsWith('test-') || - token.startsWith('dummy-') || - token === 'dummy-token-for-testing')) || + (token.startsWith("test-") || + token.startsWith("dummy-") || + token === "dummy-token-for-testing")) || // Additional test environment detection for benchmarks and scenarios - process.argv.some((arg) => arg.includes('benchmark') || arg.includes('scenario')) || - process.cwd().includes('scenarios'); + process.argv.some( + (arg) => arg.includes("benchmark") || arg.includes("scenario"), + ) || + process.cwd().includes("scenarios"); // Debug log for test environment detection - if (token && token.includes('dummy')) { + if (token && token.includes("dummy")) { console.log( - `GitHub Plugin Debug: isTestEnv=${isTestEnv}, token=${token}, NODE_ENV=${process.env.NODE_ENV}` + `GitHub Plugin Debug: isTestEnv=${isTestEnv}, token=${token}, NODE_ENV=${process.env.NODE_ENV}`, ); } const owner = - runtime?.getSetting('GITHUB_OWNER') || config.GITHUB_OWNER || process.env.GITHUB_OWNER; + runtime?.getSetting("GITHUB_OWNER") || + config.GITHUB_OWNER || + process.env.GITHUB_OWNER; const webhookSecret = - runtime?.getSetting('GITHUB_WEBHOOK_SECRET') || + runtime?.getSetting("GITHUB_WEBHOOK_SECRET") || config.GITHUB_WEBHOOK_SECRET || process.env.GITHUB_WEBHOOK_SECRET; @@ -457,7 +499,9 @@ export const githubPlugin: Plugin = { }; // Use flexible validation for testing - const configSchema = isTestEnv ? githubConfigSchemaFlexible : githubConfigSchema; + const configSchema = isTestEnv + ? githubConfigSchemaFlexible + : githubConfigSchema; // In test mode, be more permissive with validation if (isTestEnv) { @@ -465,15 +509,15 @@ export const githubPlugin: Plugin = { await configSchema.parseAsync(validatedConfig); } catch (validationError) { logger.warn( - 'Test mode: Config validation failed but continuing with mock config:', - validationError + "Test mode: Config validation failed but continuing with mock config:", + validationError, ); // Continue with mock configuration in test mode } } else { // Production mode: require strict validation if (!token) { - throw new Error('GitHub token is required'); + throw new Error("GitHub token is required"); } await configSchema.parseAsync(validatedConfig); } @@ -486,101 +530,106 @@ export const githubPlugin: Plugin = { runtime.character.settings.githubConfig = validatedConfig; } - logger.info('GitHub plugin configuration validated successfully'); + logger.info("GitHub plugin configuration validated successfully"); if (validatedConfig.GITHUB_TOKEN) { logger.info( `GitHub token type: ${ - validatedConfig.GITHUB_TOKEN.startsWith('ghp_') - ? 'Personal Access Token' - : validatedConfig.GITHUB_TOKEN.startsWith('github_pat_') - ? 'Fine-grained Token' - : 'Other' - }` + validatedConfig.GITHUB_TOKEN.startsWith("ghp_") + ? "Personal Access Token" + : validatedConfig.GITHUB_TOKEN.startsWith("github_pat_") + ? "Fine-grained Token" + : "Other" + }`, ); } else if (isTestEnv) { - logger.info('Running in test mode without GitHub token'); + logger.info("Running in test mode without GitHub token"); } // Check for Ngrok service availability (in non-test mode only to avoid timing issues) if (runtime && !isTestEnv) { setTimeout(async () => { try { - const tunnelService = runtime.getService('tunnel') as any; + const tunnelService = runtime.getService("tunnel") as any; if (tunnelService && tunnelService.isActive()) { const tunnelUrl = await tunnelService.getUrl(); if (tunnelUrl) { logger.info( - `GitHub webhook endpoint available at: ${tunnelUrl}/api/github/webhook` + `GitHub webhook endpoint available at: ${tunnelUrl}/api/github/webhook`, ); if (validatedConfig.GITHUB_WEBHOOK_SECRET) { - logger.info('GitHub webhook secret is configured - signatures will be verified'); + logger.info( + "GitHub webhook secret is configured - signatures will be verified", + ); } else { logger.warn( - 'GitHub webhook secret is NOT configured - webhooks will be accepted without verification' + "GitHub webhook secret is NOT configured - webhooks will be accepted without verification", ); } } } else { - logger.info('Ngrok service not available - webhooks will only work with public URLs'); + logger.info( + "Ngrok service not available - webhooks will only work with public URLs", + ); } } catch (error) { - logger.debug('Could not check Ngrok service status:', error); + logger.debug("Could not check Ngrok service status:", error); } }, 1000); // Delay to allow Ngrok service to initialize } // Ensure we return void } catch (error) { - logger.error('GitHub plugin configuration validation failed:', error); + logger.error("GitHub plugin configuration validation failed:", error); // Detect test environment again (need to redeclare since we're in catch block) const isTestEnvInCatch = - process.env.NODE_ENV === 'test' || - process.env.VITEST === 'true' || - process.argv.some((arg) => arg.includes('test')) || - typeof globalThis.describe !== 'undefined' || - typeof globalThis.it !== 'undefined' || - typeof globalThis.expect !== 'undefined'; + process.env.NODE_ENV === "test" || + process.env.VITEST === "true" || + process.argv.some((arg) => arg.includes("test")) || + typeof (globalThis as any).describe !== "undefined" || + typeof (globalThis as any).it !== "undefined" || + typeof (globalThis as any).expect !== "undefined"; // In test environment, don't throw - just log the warning if (isTestEnvInCatch) { - logger.warn('Running in test mode with invalid GitHub configuration'); + logger.warn("Running in test mode with invalid GitHub configuration"); return; // Return void instead of Promise.resolve() } throw new Error( - `Invalid GitHub plugin configuration: ${error instanceof Error ? error.message : String(error)}` + `Invalid GitHub plugin configuration: ${error instanceof Error ? error.message : String(error)}`, ); } }, routes: [ { - name: 'github-status', - path: '/api/github/status', - type: 'GET', + name: "github-status", + path: "/api/github/status", + type: "GET", handler: async (req: any, res: any) => { try { // This endpoint provides GitHub plugin status const runtime = (req as any).runtime as IAgentRuntime; - const config = runtime?.character?.settings?.githubConfig as GitHubConfig; + const config = runtime?.character?.settings + ?.githubConfig as GitHubConfig; res.json({ - status: 'active', - plugin: 'plugin-github', - version: '1.0.0', + status: "active", + plugin: "plugin-github", + version: "1.0.0", authenticated: !!config?.GITHUB_TOKEN, - tokenType: config?.GITHUB_TOKEN?.startsWith('ghp_') - ? 'pat' - : config?.GITHUB_TOKEN?.startsWith('github_pat_') - ? 'fine-grained' - : 'unknown', + tokenType: config?.GITHUB_TOKEN?.startsWith("ghp_") + ? "pat" + : config?.GITHUB_TOKEN?.startsWith("github_pat_") + ? "fine-grained" + : "unknown", timestamp: new Date().toISOString(), }); } catch (error) { res.status(500).json({ - status: 'error', + status: "error", error: error instanceof Error ? error.message : String(error), timestamp: new Date().toISOString(), }); @@ -588,18 +637,18 @@ export const githubPlugin: Plugin = { }, }, { - name: 'github-activity', - path: '/api/github/activity', - type: 'GET', + name: "github-activity", + path: "/api/github/activity", + type: "GET", handler: async (req: any, res: any) => { try { // This endpoint provides recent GitHub activity const runtime = (req as any).runtime as IAgentRuntime; - const githubService = runtime?.getService('github'); + const githubService = runtime?.getService("github"); if (!githubService) { return res.status(503).json({ - error: 'GitHub service not available', + error: "GitHub service not available", timestamp: new Date().toISOString(), }); } @@ -614,14 +663,14 @@ export const githubPlugin: Plugin = { }; res.json({ - status: 'success', + status: "success", stats, activity: activityLog, timestamp: new Date().toISOString(), }); } catch (error) { res.status(500).json({ - status: 'error', + status: "error", error: error instanceof Error ? error.message : String(error), timestamp: new Date().toISOString(), }); @@ -629,18 +678,18 @@ export const githubPlugin: Plugin = { }, }, { - name: 'github-rate-limit', - path: '/api/github/rate-limit', - type: 'GET', + name: "github-rate-limit", + path: "/api/github/rate-limit", + type: "GET", handler: async (req: any, res: any) => { try { // This endpoint provides GitHub API rate limit status const runtime = (req as any).runtime as IAgentRuntime; - const githubService = runtime?.getService('github'); + const githubService = runtime?.getService("github"); if (!githubService) { return res.status(503).json({ - error: 'GitHub service not available', + error: "GitHub service not available", timestamp: new Date().toISOString(), }); } @@ -648,13 +697,13 @@ export const githubPlugin: Plugin = { const rateLimit = await githubService.getRateLimit(); res.json({ - status: 'success', + status: "success", rateLimit, timestamp: new Date().toISOString(), }); } catch (error) { res.status(500).json({ - status: 'error', + status: "error", error: error instanceof Error ? error.message : String(error), timestamp: new Date().toISOString(), }); @@ -662,18 +711,19 @@ export const githubPlugin: Plugin = { }, }, { - name: 'github-webhook', - path: '/api/github/webhook', - type: 'POST', + name: "github-webhook", + path: "/api/github/webhook", + type: "POST", handler: async (req: any, res: any) => { try { const runtime = (req as any).runtime as IAgentRuntime; - const config = runtime?.character?.settings?.githubConfig as GitHubConfig; + const config = runtime?.character?.settings + ?.githubConfig as GitHubConfig; const secret = config?.GITHUB_WEBHOOK_SECRET; // Get webhook event type - const event = req.headers['x-github-event']; - const signature = req.headers['x-hub-signature-256']; + const event = req.headers["x-github-event"]; + const signature = req.headers["x-hub-signature-256"]; const payload = req.body; // Log webhook receipt @@ -681,38 +731,40 @@ export const githubPlugin: Plugin = { // SECURITY: Make signature verification mandatory if (!secret) { - logger.error('GitHub webhook secret not configured - rejecting webhook for security'); + logger.error( + "GitHub webhook secret not configured - rejecting webhook for security", + ); res.statusCode = 401; - return res.end('Webhook secret required for security'); + return res.end("Webhook secret required for security"); } if (!signature) { - logger.error('GitHub webhook signature missing'); + logger.error("GitHub webhook signature missing"); res.statusCode = 401; - return res.end('Webhook signature required'); + return res.end("Webhook signature required"); } if (!verifyWebhookSignature(payload, signature, secret)) { - logger.error('GitHub webhook signature verification failed'); + logger.error("GitHub webhook signature verification failed"); res.statusCode = 401; - return res.end('Invalid webhook signature'); + return res.end("Invalid webhook signature"); } - logger.debug('GitHub webhook signature verified successfully'); + logger.debug("GitHub webhook signature verified successfully"); // Process the webhook event if (runtime) { await processWebhookEvent(runtime, event, payload); } else { - logger.warn('Runtime not available for webhook processing'); + logger.warn("Runtime not available for webhook processing"); } res.statusCode = 200; - res.end('OK'); + res.end("OK"); } catch (error) { - logger.error('Error processing GitHub webhook:', error); + logger.error("Error processing GitHub webhook:", error); res.statusCode = 500; - res.end('Internal Server Error'); + res.end("Internal Server Error"); } }, }, @@ -726,10 +778,13 @@ export const githubPlugin: Plugin = { if (message.content.text && runtime) { try { - const relevance = await analyzeMessageRelevance(runtime, message.content.text); + const relevance = await analyzeMessageRelevance( + runtime, + message.content.text, + ); if (relevance.isGitHubRelated && relevance.confidence > 0.7) { - logger.debug('GitHub-related message intelligently detected', { + logger.debug("GitHub-related message intelligently detected", { messageId: message.id, confidence: Math.round(relevance.confidence * 100), context: relevance.context, @@ -739,44 +794,52 @@ export const githubPlugin: Plugin = { // Could trigger specific GitHub actions based on context if (relevance.requiresAction && relevance.confidence > 0.8) { - logger.info(`High-confidence GitHub action required: ${relevance.reasoning}`); + logger.info( + `High-confidence GitHub action required: ${relevance.reasoning}`, + ); } } } catch (error) { // Fallback to basic pattern matching only as last resort const text = message.content.text.toLowerCase(); - const hasBasicGithubPattern = /github\.com|@[\w-]+\/[\w-]+|#\d+/.test(text); + const hasBasicGithubPattern = + /github\.com|@[\w-]+\/[\w-]+|#\d+/.test(text); if (hasBasicGithubPattern) { - logger.debug('GitHub-related message detected via fallback pattern matching', { - messageId: message.id, - note: 'LLM analysis failed, using basic patterns', - }); + logger.debug( + "GitHub-related message detected via fallback pattern matching", + { + messageId: message.id, + note: "LLM analysis failed, using basic patterns", + }, + ); } } } }, ], - 'github:agent_mentioned': [ + "github:agent_mentioned": [ async (params) => { const { runtime, issue, repository, action } = params; - logger.info(`Agent mentioned in issue #${issue.number} in ${repository.full_name}`); + logger.info( + `Agent mentioned in issue #${issue.number} in ${repository.full_name}`, + ); // Trigger the respond to mention action - await runtime.processAction('RESPOND_TO_GITHUB_MENTION', { + await runtime.processAction("RESPOND_TO_GITHUB_MENTION", { issue, repository, action, }); }, ], - 'github:agent_mentioned_comment': [ + "github:agent_mentioned_comment": [ async (params) => { const { runtime, issue, comment, repository, action } = params; logger.info(`Agent mentioned in comment on issue #${issue.number}`); // Trigger the respond to mention action - await runtime.processAction('RESPOND_TO_GITHUB_MENTION', { + await runtime.processAction("RESPOND_TO_GITHUB_MENTION", { issue, comment, repository, @@ -784,26 +847,32 @@ export const githubPlugin: Plugin = { }); }, ], - 'github:issues': [ + "github:issues": [ async (params) => { const { runtime, payload } = params; - logger.info(`GitHub issue event: ${payload.action} on issue #${payload.issue.number}`); + logger.info( + `GitHub issue event: ${payload.action} on issue #${payload.issue.number}`, + ); // Log issue events for monitoring - if (payload.action === 'opened') { - logger.info(`New issue opened: #${payload.issue.number} - ${payload.issue.title}`); + if (payload.action === "opened") { + logger.info( + `New issue opened: #${payload.issue.number} - ${payload.issue.title}`, + ); } }, ], - 'github:pull_request': [ + "github:pull_request": [ async (params) => { const { runtime, payload } = params; - logger.info(`GitHub PR event: ${payload.action} on PR #${payload.pull_request.number}`); + logger.info( + `GitHub PR event: ${payload.action} on PR #${payload.pull_request.number}`, + ); // Log PR events for monitoring - if (payload.action === 'opened') { + if (payload.action === "opened") { logger.info( - `New PR opened: #${payload.pull_request.number} - ${payload.pull_request.title}` + `New PR opened: #${payload.pull_request.number} - ${payload.pull_request.title}`, ); } }, @@ -821,6 +890,6 @@ export const githubPlugin: Plugin = { export { GitHubService, githubActions, githubProviders }; // Export types for external use -export * from './types'; +export * from "./types"; export default githubPlugin; diff --git a/src/providers/github.ts b/src/providers/github.ts index 412c725..660bdc1 100644 --- a/src/providers/github.ts +++ b/src/providers/github.ts @@ -5,32 +5,32 @@ import { type ProviderResult, type State, logger, -} from '@elizaos/core'; -import { GitHubService } from '../services/github'; -import { GitHubRepository, GitHubIssue, GitHubPullRequest } from '../types'; +} from "@elizaos/core"; +import { GitHubService } from "../services/github"; +import { GitHubRepository, GitHubIssue, GitHubPullRequest } from "../types"; // GitHub Repository Context Provider export const githubRepositoryProvider: Provider = { - name: 'GITHUB_REPOSITORY_CONTEXT', - description: 'Provides context about GitHub repositories from current state', + name: "GITHUB_REPOSITORY_CONTEXT", + description: "Provides context about GitHub repositories from current state", get: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { try { const githubState = state?.github; if (!githubState) { return { - text: 'GitHub Repository context is available. I can help you search for repositories, get repository details, and manage repository-related tasks.', + text: "GitHub Repository context is available. I can help you search for repositories, get repository details, and manage repository-related tasks.", values: {}, data: {}, }; } - let contextText = ''; + let contextText = ""; const values: Record = {}; const data: Record = {}; @@ -38,12 +38,12 @@ export const githubRepositoryProvider: Provider = { if (githubState.lastRepository) { const repo = githubState.lastRepository; contextText += `Current Repository: ${repo.full_name}\n`; - contextText += `Description: ${repo.description || 'No description'}\n`; - contextText += `Language: ${repo.language || 'Unknown'}\n`; + contextText += `Description: ${repo.description || "No description"}\n`; + contextText += `Language: ${repo.language || "Unknown"}\n`; contextText += `Stars: ${repo.stargazers_count}\n`; contextText += `Forks: ${repo.forks_count}\n`; contextText += `Open Issues: ${repo.open_issues_count}\n`; - contextText += `Private: ${repo.private ? 'Yes' : 'No'}\n`; + contextText += `Private: ${repo.private ? "Yes" : "No"}\n`; values.currentRepository = repo.full_name; values.repositoryOwner = repo.owner.login; @@ -58,14 +58,23 @@ export const githubRepositoryProvider: Provider = { } // Recently accessed repositories - if (githubState.repositories && Object.keys(githubState.repositories).length > 0) { - const recentRepos = (Object.values(githubState.repositories) as GitHubRepository[]) - .sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()) + if ( + githubState.repositories && + Object.keys(githubState.repositories).length > 0 + ) { + const recentRepos = ( + Object.values(githubState.repositories) as GitHubRepository[] + ) + .sort( + (a, b) => + new Date(b.updated_at).getTime() - + new Date(a.updated_at).getTime(), + ) .slice(0, 5); - contextText += '\nRecent Repositories:\n'; + contextText += "\nRecent Repositories:\n"; recentRepos.forEach((repo) => { - contextText += `- ${repo.full_name} (${repo.language || 'Unknown'})\n`; + contextText += `- ${repo.full_name} (${repo.language || "Unknown"})\n`; }); values.recentRepositories = recentRepos.map((r) => r.full_name); @@ -85,7 +94,7 @@ export const githubRepositoryProvider: Provider = { // If we have no context yet, provide a helpful message if (!contextText) { contextText = - 'GitHub Repository context is available. I can help you search for repositories, get repository details, and manage repository-related tasks.'; + "GitHub Repository context is available. I can help you search for repositories, get repository details, and manage repository-related tasks."; } return { @@ -94,9 +103,9 @@ export const githubRepositoryProvider: Provider = { data, }; } catch (error) { - logger.error('Error in GitHub repository provider:', error); + logger.error("Error in GitHub repository provider:", error); return { - text: '', + text: "", values: {}, data: {}, }; @@ -106,26 +115,26 @@ export const githubRepositoryProvider: Provider = { // GitHub Issues Context Provider export const githubIssuesProvider: Provider = { - name: 'GITHUB_ISSUES_CONTEXT', - description: 'Provides context about GitHub issues from current state', + name: "GITHUB_ISSUES_CONTEXT", + description: "Provides context about GitHub issues from current state", get: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { try { const githubState = state?.github; if (!githubState) { return { - text: '', + text: "", values: {}, data: {}, }; } - let contextText = ''; + let contextText = ""; const values: Record = {}; const data: Record = {}; @@ -139,11 +148,11 @@ export const githubIssuesProvider: Provider = { contextText += `Comments: ${issue.comments}\n`; if (issue.labels.length > 0) { - contextText += `Labels: ${issue.labels.map((l: any) => l.name).join(', ')}\n`; + contextText += `Labels: ${issue.labels.map((l: any) => l.name).join(", ")}\n`; } if (issue.assignees.length > 0) { - contextText += `Assignees: ${issue.assignees.map((a: any) => `@${a.login}`).join(', ')}\n`; + contextText += `Assignees: ${issue.assignees.map((a: any) => `@${a.login}`).join(", ")}\n`; } values.currentIssue = issue.number; @@ -159,20 +168,28 @@ export const githubIssuesProvider: Provider = { // Recent issues if (githubState.issues && Object.keys(githubState.issues).length > 0) { - const recentIssues = (Object.values(githubState.issues) as GitHubIssue[]) - .sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()) + const recentIssues = ( + Object.values(githubState.issues) as GitHubIssue[] + ) + .sort( + (a, b) => + new Date(b.updated_at).getTime() - + new Date(a.updated_at).getTime(), + ) .slice(0, 5); - contextText += '\nRecent Issues:\n'; + contextText += "\nRecent Issues:\n"; recentIssues.forEach((issue) => { const repoMatch = Object.keys(githubState.issues || {}).find( - (key) => githubState.issues![key].id === issue.id + (key) => githubState.issues![key].id === issue.id, ); - const repoName = repoMatch?.split('#')[0] || 'unknown'; + const repoName = repoMatch?.split("#")[0] || "unknown"; contextText += `- ${repoName}#${issue.number}: ${issue.title} (${issue.state})\n`; }); - values.recentIssues = recentIssues.map((i) => `#${i.number}: ${i.title}`); + values.recentIssues = recentIssues.map( + (i) => `#${i.number}: ${i.title}`, + ); data.issues = githubState.issues; } @@ -204,9 +221,9 @@ export const githubIssuesProvider: Provider = { data, }; } catch (error) { - logger.error('Error in GitHub issues provider:', error); + logger.error("Error in GitHub issues provider:", error); return { - text: '', + text: "", values: {}, data: {}, }; @@ -216,26 +233,26 @@ export const githubIssuesProvider: Provider = { // GitHub Pull Requests Context Provider export const githubPullRequestsProvider: Provider = { - name: 'GITHUB_PULL_REQUESTS_CONTEXT', - description: 'Provides context about GitHub pull requests from current state', + name: "GITHUB_PULL_REQUESTS_CONTEXT", + description: "Provides context about GitHub pull requests from current state", get: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { try { const githubState = state?.github; if (!githubState) { return { - text: '', + text: "", values: {}, data: {}, }; } - let contextText = ''; + let contextText = ""; const values: Record = {}; const data: Record = {}; @@ -243,8 +260,8 @@ export const githubPullRequestsProvider: Provider = { if (githubState.lastPullRequest) { const pr = githubState.lastPullRequest; contextText += `Current Pull Request: #${pr.number} - ${pr.title}\n`; - contextText += `State: ${pr.state}${pr.merged ? ' (merged)' : ''}\n`; - contextText += `Draft: ${pr.draft ? 'Yes' : 'No'}\n`; + contextText += `State: ${pr.state}${pr.merged ? " (merged)" : ""}\n`; + contextText += `Draft: ${pr.draft ? "Yes" : "No"}\n`; contextText += `Author: @${pr.user.login}\n`; contextText += `Created: ${new Date(pr.created_at).toLocaleDateString()}\n`; contextText += `Head: ${pr.head.ref} → Base: ${pr.base.ref}\n`; @@ -252,11 +269,11 @@ export const githubPullRequestsProvider: Provider = { contextText += `Additions: +${pr.additions}, Deletions: -${pr.deletions}\n`; if (pr.labels.length > 0) { - contextText += `Labels: ${pr.labels.map((l: any) => l.name).join(', ')}\n`; + contextText += `Labels: ${pr.labels.map((l: any) => l.name).join(", ")}\n`; } if (pr.assignees.length > 0) { - contextText += `Assignees: ${pr.assignees.map((a: any) => `@${a.login}`).join(', ')}\n`; + contextText += `Assignees: ${pr.assignees.map((a: any) => `@${a.login}`).join(", ")}\n`; } values.currentPullRequest = pr.number; @@ -277,22 +294,33 @@ export const githubPullRequestsProvider: Provider = { } // Recent pull requests - if (githubState.pullRequests && Object.keys(githubState.pullRequests).length > 0) { - const recentPRs = (Object.values(githubState.pullRequests) as GitHubPullRequest[]) - .sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()) + if ( + githubState.pullRequests && + Object.keys(githubState.pullRequests).length > 0 + ) { + const recentPRs = ( + Object.values(githubState.pullRequests) as GitHubPullRequest[] + ) + .sort( + (a, b) => + new Date(b.updated_at).getTime() - + new Date(a.updated_at).getTime(), + ) .slice(0, 5); - contextText += '\nRecent Pull Requests:\n'; + contextText += "\nRecent Pull Requests:\n"; recentPRs.forEach((pr) => { const repoMatch = Object.keys(githubState.pullRequests || {}).find( - (key) => githubState.pullRequests![key].id === pr.id + (key) => githubState.pullRequests![key].id === pr.id, ); - const repoName = repoMatch?.split('#')[0] || 'unknown'; - const status = pr.merged ? 'merged' : pr.state; + const repoName = repoMatch?.split("#")[0] || "unknown"; + const status = pr.merged ? "merged" : pr.state; contextText += `- ${repoName}#${pr.number}: ${pr.title} (${status})\n`; }); - values.recentPullRequests = recentPRs.map((pr) => `#${pr.number}: ${pr.title}`); + values.recentPullRequests = recentPRs.map( + (pr) => `#${pr.number}: ${pr.title}`, + ); data.pullRequests = githubState.pullRequests; } @@ -327,9 +355,9 @@ export const githubPullRequestsProvider: Provider = { data, }; } catch (error) { - logger.error('Error in GitHub pull requests provider:', error); + logger.error("Error in GitHub pull requests provider:", error); return { - text: '', + text: "", values: {}, data: {}, }; @@ -339,34 +367,34 @@ export const githubPullRequestsProvider: Provider = { // GitHub Activity Context Provider export const githubActivityProvider: Provider = { - name: 'GITHUB_ACTIVITY_CONTEXT', - description: 'Provides context about recent GitHub activity and statistics', + name: "GITHUB_ACTIVITY_CONTEXT", + description: "Provides context about recent GitHub activity and statistics", get: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); const githubState = state?.github; if (!githubService && !githubState) { return { - text: '', + text: "", values: {}, data: {}, }; } - let contextText = ''; + let contextText = ""; const values: Record = {}; const data: Record = {}; // Activity statistics from state if (githubState?.activityStats) { const stats = githubState.activityStats; - contextText += 'GitHub Activity Summary:\n'; + contextText += "GitHub Activity Summary:\n"; contextText += `Total Actions: ${stats.total}\n`; contextText += `Successful: ${stats.success}\n`; contextText += `Failed: ${stats.failed}\n`; @@ -375,11 +403,12 @@ export const githubActivityProvider: Provider = { values.totalActions = stats.total; values.successfulActions = stats.success; values.failedActions = stats.failed; - values.successRate = stats.total > 0 ? Math.round((stats.success / stats.total) * 100) : 0; + values.successRate = + stats.total > 0 ? Math.round((stats.success / stats.total) * 100) : 0; data.activityStats = stats; } else { - contextText += 'GitHub activity context:\n'; + contextText += "GitHub activity context:\n"; } // Recent activity from service @@ -388,11 +417,11 @@ export const githubActivityProvider: Provider = { const recentActivity = githubService.getActivityLog(10); if (recentActivity.length > 0) { - contextText += '\nRecent Activity (last 10 actions):\n'; + contextText += "\nRecent Activity (last 10 actions):\n"; recentActivity.forEach((activity) => { const time = new Date(activity.timestamp).toLocaleTimeString(); - const status = activity.success ? '✅' : '❌'; - const action = activity.action.replace(/_/g, ' ').toLowerCase(); + const status = activity.success ? "✅" : "❌"; + const action = activity.action.replace(/_/g, " ").toLowerCase(); contextText += `- ${time} ${status} ${action} ${activity.resource_type}\n`; }); @@ -401,7 +430,7 @@ export const githubActivityProvider: Provider = { data.recentActivity = recentActivity; } } catch (error) { - logger.warn('Could not fetch recent activity from service:', error); + logger.warn("Could not fetch recent activity from service:", error); } } @@ -410,7 +439,7 @@ export const githubActivityProvider: Provider = { const rateLimit = githubState.rateLimit; const usage = Math.round((rateLimit.used / rateLimit.limit) * 100); - contextText += '\nAPI Rate Limit:\n'; + contextText += "\nAPI Rate Limit:\n"; contextText += `Used: ${rateLimit.used}/${rateLimit.limit} (${usage}%)\n`; contextText += `Remaining: ${rateLimit.remaining}\n`; @@ -432,9 +461,9 @@ export const githubActivityProvider: Provider = { data, }; } catch (error) { - logger.error('Error in GitHub activity provider:', error); + logger.error("Error in GitHub activity provider:", error); return { - text: '', + text: "", values: {}, data: {}, }; @@ -444,20 +473,20 @@ export const githubActivityProvider: Provider = { // GitHub User Context Provider export const githubUserProvider: Provider = { - name: 'GITHUB_USER_CONTEXT', - description: 'Provides context about the authenticated GitHub user', + name: "GITHUB_USER_CONTEXT", + description: "Provides context about the authenticated GitHub user", get: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined + state: State | undefined, ): Promise => { try { - const githubService = runtime.getService('github'); + const githubService = runtime.getService("github"); if (!githubService) { return { - text: '', + text: "", values: {}, data: {}, }; @@ -467,11 +496,11 @@ export const githubUserProvider: Provider = { const user = await githubService.getCurrentUser(); const contextText = `GitHub User: @${user.login} -Name: ${user.name || 'Not specified'} -Email: ${user.email || 'Not public'} -Bio: ${user.bio || 'No bio'} -Company: ${user.company || 'Not specified'} -Location: ${user.location || 'Not specified'} +Name: ${user.name || "Not specified"} +Email: ${user.email || "Not public"} +Bio: ${user.bio || "No bio"} +Company: ${user.company || "Not specified"} +Location: ${user.location || "Not specified"} Public Repos: ${user.public_repos} Followers: ${user.followers} Following: ${user.following} @@ -502,17 +531,17 @@ Profile: ${user.html_url}`; data, }; } catch (error) { - logger.warn('Could not fetch GitHub user information:', error); + logger.warn("Could not fetch GitHub user information:", error); return { - text: 'GitHub user information unavailable', + text: "GitHub user information unavailable", values: {}, data: {}, }; } } catch (error) { - logger.error('Error in GitHub user provider:', error); + logger.error("Error in GitHub user provider:", error); return { - text: '', + text: "", values: {}, data: {}, }; diff --git a/src/services/github.ts b/src/services/github.ts index 45a360f..0745c4a 100644 --- a/src/services/github.ts +++ b/src/services/github.ts @@ -1,5 +1,5 @@ -import { logger, Service, type IAgentRuntime } from '@elizaos/core'; -import { Octokit } from '@octokit/rest'; +import { logger, Service, type IAgentRuntime } from "@elizaos/core"; +import { Octokit } from "@octokit/rest"; export interface GitHubConfig { GITHUB_TOKEN: string; @@ -11,7 +11,7 @@ export interface GitHubActivityItem { id: string; timestamp: string; action: string; - resource_type: 'repository' | 'issue' | 'pr' | 'user'; + resource_type: "repository" | "issue" | "pr" | "user"; resource_id: string; details: Record; success: boolean; @@ -23,36 +23,37 @@ export class GitHubAPIError extends Error { constructor( message: string, public status?: number, - public response?: any + public response?: any, ) { super(message); - this.name = 'GitHubAPIError'; + this.name = "GitHubAPIError"; } } export class GitHubAuthenticationError extends GitHubAPIError { constructor(message: string) { super(message, 401); - this.name = 'GitHubAuthenticationError'; + this.name = "GitHubAuthenticationError"; } } export class GitHubRateLimitError extends GitHubAPIError { constructor( message: string, - public resetTime: number + public resetTime: number, ) { super(message, 403); - this.name = 'GitHubRateLimitError'; + this.name = "GitHubRateLimitError"; } } // Removed unused type imports export class GitHubService extends Service { - static serviceType = 'github'; + static serviceType = "github"; - capabilityDescription = 'GitHub API integration for repository, issue, and PR management'; + capabilityDescription = + "GitHub API integration for repository, issue, and PR management"; private octokit: Octokit; private rateLimitRemaining: number = 5000; @@ -64,12 +65,12 @@ export class GitHubService extends Service { super(runtime); // Get config from runtime settings - const githubToken = runtime?.getSetting('GITHUB_TOKEN') as string; - const githubUsername = runtime?.getSetting('GITHUB_USERNAME') as string; - const githubEmail = runtime?.getSetting('GITHUB_EMAIL') as string; + const githubToken = runtime?.getSetting("GITHUB_TOKEN") as string; + const githubUsername = runtime?.getSetting("GITHUB_USERNAME") as string; + const githubEmail = runtime?.getSetting("GITHUB_EMAIL") as string; if (!githubToken) { - throw new Error('GitHub token is required'); + throw new Error("GitHub token is required"); } this.githubConfig = { @@ -86,19 +87,19 @@ export class GitHubService extends Service { this.octokit = new Octokit({ auth: this.githubConfig.GITHUB_TOKEN, - userAgent: 'ElizaOS GitHub Plugin', + userAgent: "ElizaOS GitHub Plugin", }); } static async start(runtime: IAgentRuntime): Promise { const service = new GitHubService(runtime); - logger.info('GitHub service started'); + logger.info("GitHub service started"); return service; } async stop(): Promise { this.activityLog = []; - logger.info('GitHub service stopped'); + logger.info("GitHub service stopped"); } /** @@ -111,16 +112,23 @@ export class GitHubService extends Service { this.updateRateLimit((data as any)?.headers || {}); this.logActivity( - 'validate_authentication', - 'user', - data.login || 'unknown', + "validate_authentication", + "user", + data.login || "unknown", { user_id: data.id }, - true + true, ); return true; } catch (error) { - this.logActivity('validate_authentication', 'user', 'unknown', {}, false, String(error)); + this.logActivity( + "validate_authentication", + "user", + "unknown", + {}, + false, + String(error), + ); return false; } } @@ -134,7 +142,9 @@ export class GitHubService extends Service { // If we're near the rate limit and reset time hasn't passed, wait if (this.rateLimitRemaining < 100 && now < this.rateLimitReset) { const waitTime = (this.rateLimitReset - now + 1) * 1000; - logger.warn(`GitHub rate limit low (${this.rateLimitRemaining}), waiting ${waitTime}ms`); + logger.warn( + `GitHub rate limit low (${this.rateLimitRemaining}), waiting ${waitTime}ms`, + ); await new Promise((resolve) => setTimeout(resolve, waitTime)); } } @@ -143,11 +153,11 @@ export class GitHubService extends Service { * Update rate limit info from response headers */ private updateRateLimit(headers: any): void { - if (headers['x-ratelimit-remaining']) { - this.rateLimitRemaining = parseInt(headers['x-ratelimit-remaining'], 10); + if (headers["x-ratelimit-remaining"]) { + this.rateLimitRemaining = parseInt(headers["x-ratelimit-remaining"], 10); } - if (headers['x-ratelimit-reset']) { - this.rateLimitReset = parseInt(headers['x-ratelimit-reset'], 10); + if (headers["x-ratelimit-reset"]) { + this.rateLimitReset = parseInt(headers["x-ratelimit-reset"], 10); } } @@ -160,13 +170,13 @@ export class GitHubService extends Service { // Remove sensitive headers and response data if (sanitized.response) { delete sanitized.response.headers?.authorization; - delete sanitized.response.headers?.['x-github-token']; + delete sanitized.response.headers?.["x-github-token"]; delete sanitized.response.request?.headers?.authorization; } if (sanitized.request) { delete sanitized.request.headers?.authorization; - delete sanitized.request.headers?.['x-github-token']; + delete sanitized.request.headers?.["x-github-token"]; } return sanitized; @@ -175,7 +185,10 @@ export class GitHubService extends Service { /** * Validate GitHub username/repo name format */ - private validateGitHubName(name: string, type: 'owner' | 'repo' | 'username'): void { + private validateGitHubName( + name: string, + type: "owner" | "repo" | "username", + ): void { const pattern = /^[a-zA-Z0-9\-_.]+$/; if (!pattern.test(name)) { throw new Error(`Invalid GitHub ${type} name: ${name}`); @@ -191,10 +204,23 @@ export class GitHubService extends Service { const { data, headers } = await this.octokit.users.getAuthenticated(); this.updateRateLimit(headers); - this.logActivity('get_authenticated_user', 'user', data.login, { user: data }, true); + this.logActivity( + "get_authenticated_user", + "user", + data.login, + { user: data }, + true, + ); return data; } catch (error) { - this.logActivity('get_authenticated_user', 'user', 'unknown', {}, false, String(error)); + this.logActivity( + "get_authenticated_user", + "user", + "unknown", + {}, + false, + String(error), + ); throw this.handleError(error); } } @@ -204,7 +230,7 @@ export class GitHubService extends Service { */ async getUserByUsername(username: string): Promise { try { - this.validateGitHubName(username, 'owner'); + this.validateGitHubName(username, "owner"); await this.checkRateLimit(); const { data, headers } = await this.octokit.users.getByUsername({ @@ -212,10 +238,10 @@ export class GitHubService extends Service { }); this.updateRateLimit(headers); - this.logActivity('get_user', 'user', username, { user: data }, true); + this.logActivity("get_user", "user", username, { user: data }, true); return data; } catch (error) { - this.logActivity('get_user', 'user', username, {}, false, String(error)); + this.logActivity("get_user", "user", username, {}, false, String(error)); throw this.handleError(error); } } @@ -225,8 +251,8 @@ export class GitHubService extends Service { */ async getRepository(owner: string, repo: string): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); const { data, headers } = await this.octokit.repos.get({ @@ -236,21 +262,21 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'get_repository', - 'repository', + "get_repository", + "repository", `${owner}/${repo}`, { repository: data }, - true + true, ); return data; } catch (error) { this.logActivity( - 'get_repository', - 'repository', + "get_repository", + "repository", `${owner}/${repo}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -261,43 +287,45 @@ export class GitHubService extends Service { */ async getRepositories( options: { - visibility?: 'all' | 'public' | 'private'; - affiliation?: 'owner' | 'collaborator' | 'organization_member'; - type?: 'all' | 'owner' | 'public' | 'private' | 'member'; - sort?: 'created' | 'updated' | 'pushed' | 'full_name'; - direction?: 'asc' | 'desc'; + visibility?: "all" | "public" | "private"; + affiliation?: "owner" | "collaborator" | "organization_member"; + type?: "all" | "owner" | "public" | "private" | "member"; + sort?: "created" | "updated" | "pushed" | "full_name"; + direction?: "asc" | "desc"; per_page?: number; - } = {} + } = {}, ): Promise { try { await this.checkRateLimit(); - const { data, headers } = await this.octokit.repos.listForAuthenticatedUser({ - visibility: options.visibility || 'all', - affiliation: options.affiliation || 'owner,collaborator,organization_member', - type: options.type || 'all', - sort: options.sort || 'updated', - direction: options.direction || 'desc', - per_page: options.per_page || 30, - }); + const { data, headers } = + await this.octokit.repos.listForAuthenticatedUser({ + visibility: options.visibility || "all", + affiliation: + options.affiliation || "owner,collaborator,organization_member", + type: options.type || "all", + sort: options.sort || "updated", + direction: options.direction || "desc", + per_page: options.per_page || 30, + }); this.updateRateLimit(headers); this.logActivity( - 'list_repositories', - 'repository', - 'authenticated_user', + "list_repositories", + "repository", + "authenticated_user", { count: data.length }, - true + true, ); return data; } catch (error) { this.logActivity( - 'list_repositories', - 'repository', - 'authenticated_user', + "list_repositories", + "repository", + "authenticated_user", {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -311,54 +339,54 @@ export class GitHubService extends Service { repo: string, options: { milestone?: string | number; - state?: 'open' | 'closed' | 'all'; + state?: "open" | "closed" | "all"; assignee?: string; creator?: string; mentioned?: string; labels?: string; - sort?: 'created' | 'updated' | 'comments'; - direction?: 'asc' | 'desc'; + sort?: "created" | "updated" | "comments"; + direction?: "asc" | "desc"; since?: string; per_page?: number; - } = {} + } = {}, ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); const { data, headers } = await this.octokit.issues.listForRepo({ owner, repo, milestone: options.milestone ? String(options.milestone) : undefined, - state: options.state || 'open', + state: options.state || "open", assignee: options.assignee, creator: options.creator, mentioned: options.mentioned, labels: options.labels, - sort: options.sort || 'created', - direction: options.direction || 'desc', + sort: options.sort || "created", + direction: options.direction || "desc", since: options.since, per_page: options.per_page || 30, }); this.updateRateLimit(headers); this.logActivity( - 'get_repository_issues', - 'repository', + "get_repository_issues", + "repository", `${owner}/${repo}`, { count: data.length }, - true + true, ); return data; } catch (error) { this.logActivity( - 'get_repository_issues', - 'repository', + "get_repository_issues", + "repository", `${owner}/${repo}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -367,10 +395,14 @@ export class GitHubService extends Service { /** * Get specific issue */ - async getIssue(owner: string, repo: string, issue_number: number): Promise { + async getIssue( + owner: string, + repo: string, + issue_number: number, + ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); const { data, headers } = await this.octokit.issues.get({ @@ -381,21 +413,21 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'get_issue', - 'issue', + "get_issue", + "issue", `${owner}/${repo}#${issue_number}`, { issue: data }, - true + true, ); return data; } catch (error) { this.logActivity( - 'get_issue', - 'issue', + "get_issue", + "issue", `${owner}/${repo}#${issue_number}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -408,14 +440,14 @@ export class GitHubService extends Service { owner: string, repo: string, issue_number: number, - body: string + body: string, ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); if (!body.trim()) { - throw new Error('Comment body cannot be empty'); + throw new Error("Comment body cannot be empty"); } await this.checkRateLimit(); @@ -429,21 +461,21 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'create_issue_comment', - 'issue', + "create_issue_comment", + "issue", `${owner}/${repo}#${issue_number}`, { comment: data }, - true + true, ); return data; } catch (error) { this.logActivity( - 'create_issue_comment', - 'issue', + "create_issue_comment", + "issue", `${owner}/${repo}#${issue_number}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -459,11 +491,11 @@ export class GitHubService extends Service { options: { since?: string; per_page?: number; - } = {} + } = {}, ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); const { data, headers } = await this.octokit.issues.listComments({ @@ -476,21 +508,21 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'get_issue_comments', - 'issue', + "get_issue_comments", + "issue", `${owner}/${repo}#${issue_number}`, { count: data.length }, - true + true, ); return data; } catch (error) { this.logActivity( - 'get_issue_comments', - 'issue', + "get_issue_comments", + "issue", `${owner}/${repo}#${issue_number}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -503,47 +535,47 @@ export class GitHubService extends Service { owner: string, repo: string, options: { - state?: 'open' | 'closed' | 'all'; + state?: "open" | "closed" | "all"; head?: string; base?: string; - sort?: 'created' | 'updated' | 'popularity'; - direction?: 'asc' | 'desc'; + sort?: "created" | "updated" | "popularity"; + direction?: "asc" | "desc"; per_page?: number; - } = {} + } = {}, ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); const { data, headers } = await this.octokit.pulls.list({ owner, repo, - state: options.state || 'open', + state: options.state || "open", head: options.head, base: options.base, - sort: options.sort || 'created', - direction: options.direction || 'desc', + sort: options.sort || "created", + direction: options.direction || "desc", per_page: options.per_page || 30, }); this.updateRateLimit(headers); this.logActivity( - 'get_repository_pull_requests', - 'repository', + "get_repository_pull_requests", + "repository", `${owner}/${repo}`, { count: data.length }, - true + true, ); return data; } catch (error) { this.logActivity( - 'get_repository_pull_requests', - 'repository', + "get_repository_pull_requests", + "repository", `${owner}/${repo}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -552,10 +584,14 @@ export class GitHubService extends Service { /** * Get specific pull request */ - async getPullRequest(owner: string, repo: string, pull_number: number): Promise { + async getPullRequest( + owner: string, + repo: string, + pull_number: number, + ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); const { data, headers } = await this.octokit.pulls.get({ @@ -566,21 +602,21 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'get_pull_request', - 'pr', + "get_pull_request", + "pr", `${owner}/${repo}#${pull_number}`, { pr: data }, - true + true, ); return data; } catch (error) { this.logActivity( - 'get_pull_request', - 'pr', + "get_pull_request", + "pr", `${owner}/${repo}#${pull_number}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -599,11 +635,11 @@ export class GitHubService extends Service { body?: string; maintainer_can_modify?: boolean; draft?: boolean; - } + }, ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); const { data, headers } = await this.octokit.pulls.create({ @@ -619,15 +655,22 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'create_pull_request', - 'pr', + "create_pull_request", + "pr", `${owner}/${repo}#${data.number}`, { pr: data }, - true + true, ); return data; } catch (error) { - this.logActivity('create_pull_request', 'pr', `${owner}/${repo}`, {}, false, String(error)); + this.logActivity( + "create_pull_request", + "pr", + `${owner}/${repo}`, + {}, + false, + String(error), + ); throw this.handleError(error); } } @@ -642,41 +685,42 @@ export class GitHubService extends Service { content: string, message: string, branch?: string, - sha?: string + sha?: string, ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); - const { data, headers } = await this.octokit.repos.createOrUpdateFileContents({ - owner, - repo, - path, - message, - content: Buffer.from(content).toString('base64'), - branch, - sha, - }); + const { data, headers } = + await this.octokit.repos.createOrUpdateFileContents({ + owner, + repo, + path, + message, + content: Buffer.from(content).toString("base64"), + branch, + sha, + }); this.updateRateLimit(headers); this.logActivity( - 'create_or_update_file', - 'repository', + "create_or_update_file", + "repository", `${owner}/${repo}:${path}`, { commit: data.commit }, - true + true, ); return data; } catch (error) { this.logActivity( - 'create_or_update_file', - 'repository', + "create_or_update_file", + "repository", `${owner}/${repo}:${path}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -689,11 +733,11 @@ export class GitHubService extends Service { owner: string, repo: string, path: string, - ref?: string + ref?: string, ): Promise<{ content: string; sha: string }> { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -706,15 +750,15 @@ export class GitHubService extends Service { this.updateRateLimit(headers); // Handle file content (not directory) - if ('content' in data && typeof data.content === 'string') { - const content = Buffer.from(data.content, 'base64').toString('utf-8'); + if ("content" in data && typeof data.content === "string") { + const content = Buffer.from(data.content, "base64").toString("utf-8"); this.logActivity( - 'get_file_content', - 'repository', + "get_file_content", + "repository", `${owner}/${repo}:${path}`, { fileSize: content.length }, - true + true, ); return { @@ -723,15 +767,15 @@ export class GitHubService extends Service { }; } - throw new Error('Path is not a file or content not available'); + throw new Error("Path is not a file or content not available"); } catch (error) { this.logActivity( - 'get_file_content', - 'repository', + "get_file_content", + "repository", `${owner}/${repo}:${path}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -746,11 +790,11 @@ export class GitHubService extends Service { path: string, message: string, sha: string, - branch?: string + branch?: string, ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -765,21 +809,21 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'delete_file', - 'repository', + "delete_file", + "repository", `${owner}/${repo}:${path}`, { commit: data.commit }, - true + true, ); return data; } catch (error) { this.logActivity( - 'delete_file', - 'repository', + "delete_file", + "repository", `${owner}/${repo}:${path}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -788,10 +832,14 @@ export class GitHubService extends Service { /** * Get repository tree */ - async getRepositoryTree(owner: string, repo: string, tree_sha?: string): Promise { + async getRepositoryTree( + owner: string, + repo: string, + tree_sha?: string, + ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -804,27 +852,27 @@ export class GitHubService extends Service { const { data, headers } = await this.octokit.git.getTree({ owner, repo, - tree_sha: tree_sha || 'HEAD', - recursive: 'true', + tree_sha: tree_sha || "HEAD", + recursive: "true", }); this.updateRateLimit(headers); this.logActivity( - 'get_repository_tree', - 'repository', + "get_repository_tree", + "repository", `${owner}/${repo}`, { treeCount: data.tree.length }, - true + true, ); return data.tree; } catch (error) { this.logActivity( - 'get_repository_tree', - 'repository', + "get_repository_tree", + "repository", `${owner}/${repo}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -850,15 +898,15 @@ export class GitHubService extends Service { repo: string, config: { url: string; - content_type?: 'json' | 'form'; + content_type?: "json" | "form"; secret?: string; insecure_ssl?: string; }, - events: string[] = ['push'] + events: string[] = ["push"], ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -870,16 +918,22 @@ export class GitHubService extends Service { }); this.updateRateLimit(headers); - this.logActivity('create_webhook', 'repository', `${owner}/${repo}`, { webhook: data }, true); + this.logActivity( + "create_webhook", + "repository", + `${owner}/${repo}`, + { webhook: data }, + true, + ); return data; } catch (error) { this.logActivity( - 'create_webhook', - 'repository', + "create_webhook", + "repository", `${owner}/${repo}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -890,8 +944,8 @@ export class GitHubService extends Service { */ async listWebhooks(owner: string, repo: string): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -902,15 +956,22 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'list_webhooks', - 'repository', + "list_webhooks", + "repository", `${owner}/${repo}`, { count: data.length }, - true + true, ); return data; } catch (error) { - this.logActivity('list_webhooks', 'repository', `${owner}/${repo}`, {}, false, String(error)); + this.logActivity( + "list_webhooks", + "repository", + `${owner}/${repo}`, + {}, + false, + String(error), + ); throw this.handleError(error); } } @@ -918,10 +979,14 @@ export class GitHubService extends Service { /** * Delete webhook */ - async deleteWebhook(owner: string, repo: string, hook_id: number): Promise { + async deleteWebhook( + owner: string, + repo: string, + hook_id: number, + ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -932,20 +997,20 @@ export class GitHubService extends Service { }); this.logActivity( - 'delete_webhook', - 'repository', + "delete_webhook", + "repository", `${owner}/${repo}`, { webhook_id: hook_id }, - true + true, ); } catch (error) { this.logActivity( - 'delete_webhook', - 'repository', + "delete_webhook", + "repository", `${owner}/${repo}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -954,10 +1019,14 @@ export class GitHubService extends Service { /** * Ping webhook */ - async pingWebhook(owner: string, repo: string, hook_id: number): Promise { + async pingWebhook( + owner: string, + repo: string, + hook_id: number, + ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -968,14 +1037,21 @@ export class GitHubService extends Service { }); this.logActivity( - 'ping_webhook', - 'repository', + "ping_webhook", + "repository", `${owner}/${repo}`, { webhook_id: hook_id }, - true + true, ); } catch (error) { - this.logActivity('ping_webhook', 'repository', `${owner}/${repo}`, {}, false, String(error)); + this.logActivity( + "ping_webhook", + "repository", + `${owner}/${repo}`, + {}, + false, + String(error), + ); throw this.handleError(error); } } @@ -1011,7 +1087,7 @@ export class GitHubService extends Service { reset: this.rateLimitReset, limit: 5000, used, - resource: 'core', + resource: "core", }; } @@ -1040,8 +1116,8 @@ export class GitHubService extends Service { */ async getRef(owner: string, repo: string, ref: string): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -1052,16 +1128,22 @@ export class GitHubService extends Service { }); this.updateRateLimit(headers); - this.logActivity('get_ref', 'repository', `${owner}/${repo}:${ref}`, { ref: data }, true); + this.logActivity( + "get_ref", + "repository", + `${owner}/${repo}:${ref}`, + { ref: data }, + true, + ); return data; } catch (error) { this.logActivity( - 'get_ref', - 'repository', + "get_ref", + "repository", `${owner}/${repo}:${ref}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -1070,10 +1152,15 @@ export class GitHubService extends Service { /** * Create a new branch */ - async createBranch(owner: string, repo: string, branchName: string, sha: string): Promise { + async createBranch( + owner: string, + repo: string, + branchName: string, + sha: string, + ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -1086,21 +1173,21 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'create_branch', - 'repository', + "create_branch", + "repository", `${owner}/${repo}:${branchName}`, { branch: data }, - true + true, ); return data; } catch (error) { this.logActivity( - 'create_branch', - 'repository', + "create_branch", + "repository", `${owner}/${repo}:${branchName}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -1111,8 +1198,8 @@ export class GitHubService extends Service { */ async listBranches(owner: string, repo: string): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -1123,15 +1210,22 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'list_branches', - 'repository', + "list_branches", + "repository", `${owner}/${repo}`, { count: data.length }, - true + true, ); return data; } catch (error) { - this.logActivity('list_branches', 'repository', `${owner}/${repo}`, {}, false, String(error)); + this.logActivity( + "list_branches", + "repository", + `${owner}/${repo}`, + {}, + false, + String(error), + ); throw this.handleError(error); } } @@ -1141,8 +1235,8 @@ export class GitHubService extends Service { */ async getBranch(owner: string, repo: string, branch: string): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); const { data, headers } = await this.octokit.repos.getBranch({ @@ -1152,16 +1246,22 @@ export class GitHubService extends Service { }); this.updateRateLimit(headers); - this.logActivity('get_branch', 'repository', `${owner}/${repo}:${branch}`, { data }, true); + this.logActivity( + "get_branch", + "repository", + `${owner}/${repo}:${branch}`, + { data }, + true, + ); return data; } catch (error) { this.logActivity( - 'get_branch', - 'repository', + "get_branch", + "repository", `${owner}/${repo}:${branch}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -1170,10 +1270,14 @@ export class GitHubService extends Service { /** * Delete a branch */ - async deleteBranch(owner: string, repo: string, branch: string): Promise { + async deleteBranch( + owner: string, + repo: string, + branch: string, + ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); const { headers } = await this.octokit.git.deleteRef({ @@ -1183,15 +1287,21 @@ export class GitHubService extends Service { }); this.updateRateLimit(headers); - this.logActivity('delete_branch', 'repository', `${owner}/${repo}:${branch}`, {}, true); + this.logActivity( + "delete_branch", + "repository", + `${owner}/${repo}:${branch}`, + {}, + true, + ); } catch (error) { this.logActivity( - 'delete_branch', - 'repository', + "delete_branch", + "repository", `${owner}/${repo}:${branch}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -1200,10 +1310,15 @@ export class GitHubService extends Service { /** * Compare two branches */ - async compareBranches(owner: string, repo: string, base: string, head: string): Promise { + async compareBranches( + owner: string, + repo: string, + base: string, + head: string, + ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); const { data, headers } = await this.octokit.repos.compareCommits({ @@ -1215,21 +1330,21 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'compare_branches', - 'repository', + "compare_branches", + "repository", `${owner}/${repo}:${base}...${head}`, { comparison: data }, - true + true, ); return data; } catch (error) { this.logActivity( - 'compare_branches', - 'repository', + "compare_branches", + "repository", `${owner}/${repo}:${base}...${head}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -1238,10 +1353,14 @@ export class GitHubService extends Service { /** * Get branch protection settings */ - async getBranchProtection(owner: string, repo: string, branch: string): Promise { + async getBranchProtection( + owner: string, + repo: string, + branch: string, + ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -1253,21 +1372,21 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'get_branch_protection', - 'repository', + "get_branch_protection", + "repository", `${owner}/${repo}:${branch}`, { protection: data }, - true + true, ); return data; } catch (error) { this.logActivity( - 'get_branch_protection', - 'repository', + "get_branch_protection", + "repository", `${owner}/${repo}:${branch}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -1276,7 +1395,11 @@ export class GitHubService extends Service { /** * List issues */ - async listIssues(owner: string, repo: string, options: any = {}): Promise { + async listIssues( + owner: string, + repo: string, + options: any = {}, + ): Promise { return this.getRepositoryIssues(owner, repo, options); } @@ -1292,11 +1415,11 @@ export class GitHubService extends Service { assignees?: string[]; milestone?: number; labels?: string[]; - } + }, ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -1312,15 +1435,22 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'create_issue', - 'issue', + "create_issue", + "issue", `${owner}/${repo}#${data.number}`, { issue: data }, - true + true, ); return data; } catch (error) { - this.logActivity('create_issue', 'issue', `${owner}/${repo}`, {}, false, String(error)); + this.logActivity( + "create_issue", + "issue", + `${owner}/${repo}`, + {}, + false, + String(error), + ); throw this.handleError(error); } } @@ -1332,18 +1462,33 @@ export class GitHubService extends Service { try { await this.checkRateLimit(); - const { data, headers } = await this.octokit.search.issuesAndPullRequests({ - q: query, - sort: options.sort, - order: options.order, - per_page: options.per_page || 30, - }); + const { data, headers } = await this.octokit.search.issuesAndPullRequests( + { + q: query, + sort: options.sort, + order: options.order, + per_page: options.per_page || 30, + }, + ); this.updateRateLimit(headers); - this.logActivity('search_issues', 'issue', query, { count: data.items.length }, true); + this.logActivity( + "search_issues", + "issue", + query, + { count: data.items.length }, + true, + ); return data; // Return the full search result with items and total_count } catch (error) { - this.logActivity('search_issues', 'issue', query, {}, false, String(error)); + this.logActivity( + "search_issues", + "issue", + query, + {}, + false, + String(error), + ); throw this.handleError(error); } } @@ -1354,14 +1499,18 @@ export class GitHubService extends Service { async searchPullRequests(query: string, options: any = {}): Promise { // Pull requests are also issues in GitHub's API, so we use the same endpoint // but ensure we're searching for PRs - const prQuery = query.includes('is:pr') ? query : `${query} is:pr`; + const prQuery = query.includes("is:pr") ? query : `${query} is:pr`; return this.searchIssues(prQuery, options); } /** * List pull requests */ - async listPullRequests(owner: string, repo: string, options: any = {}): Promise { + async listPullRequests( + owner: string, + repo: string, + options: any = {}, + ): Promise { return this.getRepositoryPullRequests(owner, repo, options); } @@ -1375,12 +1524,12 @@ export class GitHubService extends Service { options: { commit_title?: string; commit_message?: string; - merge_method?: 'merge' | 'squash' | 'rebase'; - } = {} + merge_method?: "merge" | "squash" | "rebase"; + } = {}, ): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -1390,26 +1539,26 @@ export class GitHubService extends Service { pull_number, commit_title: options.commit_title, commit_message: options.commit_message, - merge_method: options.merge_method || 'merge', + merge_method: options.merge_method || "merge", }); this.updateRateLimit(headers); this.logActivity( - 'merge_pull_request', - 'pr', + "merge_pull_request", + "pr", `${owner}/${repo}#${pull_number}`, { merge: data }, - true + true, ); return data; } catch (error) { this.logActivity( - 'merge_pull_request', - 'pr', + "merge_pull_request", + "pr", `${owner}/${repo}#${pull_number}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -1436,26 +1585,34 @@ export class GitHubService extends Service { try { await this.checkRateLimit(); - const { data, headers } = await this.octokit.repos.createForAuthenticatedUser({ - name: options.name, - description: options.description, - private: options.private, - auto_init: options.auto_init, - gitignore_template: options.gitignore_template, - license_template: options.license_template, - }); + const { data, headers } = + await this.octokit.repos.createForAuthenticatedUser({ + name: options.name, + description: options.description, + private: options.private, + auto_init: options.auto_init, + gitignore_template: options.gitignore_template, + license_template: options.license_template, + }); this.updateRateLimit(headers); this.logActivity( - 'create_repository', - 'repository', + "create_repository", + "repository", data.full_name, { repository: data }, - true + true, ); return data; } catch (error) { - this.logActivity('create_repository', 'repository', options.name, {}, false, String(error)); + this.logActivity( + "create_repository", + "repository", + options.name, + {}, + false, + String(error), + ); throw this.handleError(error); } } @@ -1476,15 +1633,22 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'search_repositories', - 'repository', + "search_repositories", + "repository", query, { count: data.items.length }, - true + true, ); return data; // Return the full search result with items and total_count } catch (error) { - this.logActivity('search_repositories', 'repository', query, {}, false, String(error)); + this.logActivity( + "search_repositories", + "repository", + query, + {}, + false, + String(error), + ); throw this.handleError(error); } } @@ -1494,8 +1658,8 @@ export class GitHubService extends Service { */ async getContributorsStats(owner: string, repo: string): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -1506,21 +1670,21 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'get_contributors_stats', - 'repository', + "get_contributors_stats", + "repository", `${owner}/${repo}`, { stats: data }, - true + true, ); return data; } catch (error) { this.logActivity( - 'get_contributors_stats', - 'repository', + "get_contributors_stats", + "repository", `${owner}/${repo}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -1531,33 +1695,35 @@ export class GitHubService extends Service { */ async getCommitActivityStats(owner: string, repo: string): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); - const { data, headers } = await this.octokit.repos.getCommitActivityStats({ - owner, - repo, - }); + const { data, headers } = await this.octokit.repos.getCommitActivityStats( + { + owner, + repo, + }, + ); this.updateRateLimit(headers); this.logActivity( - 'get_commit_activity_stats', - 'repository', + "get_commit_activity_stats", + "repository", `${owner}/${repo}`, { stats: data }, - true + true, ); return data; } catch (error) { this.logActivity( - 'get_commit_activity_stats', - 'repository', + "get_commit_activity_stats", + "repository", `${owner}/${repo}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -1568,8 +1734,8 @@ export class GitHubService extends Service { */ async getCodeFrequencyStats(owner: string, repo: string): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -1580,21 +1746,21 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'get_code_frequency_stats', - 'repository', + "get_code_frequency_stats", + "repository", `${owner}/${repo}`, { stats: data }, - true + true, ); return data; } catch (error) { this.logActivity( - 'get_code_frequency_stats', - 'repository', + "get_code_frequency_stats", + "repository", `${owner}/${repo}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -1605,8 +1771,8 @@ export class GitHubService extends Service { */ async getLanguages(owner: string, repo: string): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -1617,15 +1783,22 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'get_languages', - 'repository', + "get_languages", + "repository", `${owner}/${repo}`, { languages: data }, - true + true, ); return data; } catch (error) { - this.logActivity('get_languages', 'repository', `${owner}/${repo}`, {}, false, String(error)); + this.logActivity( + "get_languages", + "repository", + `${owner}/${repo}`, + {}, + false, + String(error), + ); throw this.handleError(error); } } @@ -1635,8 +1808,8 @@ export class GitHubService extends Service { */ async getTrafficViews(owner: string, repo: string): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -1647,21 +1820,21 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'get_traffic_views', - 'repository', + "get_traffic_views", + "repository", `${owner}/${repo}`, { views: data }, - true + true, ); return data; } catch (error) { this.logActivity( - 'get_traffic_views', - 'repository', + "get_traffic_views", + "repository", `${owner}/${repo}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -1672,8 +1845,8 @@ export class GitHubService extends Service { */ async getTrafficClones(owner: string, repo: string): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -1684,21 +1857,21 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'get_traffic_clones', - 'repository', + "get_traffic_clones", + "repository", `${owner}/${repo}`, { clones: data }, - true + true, ); return data; } catch (error) { this.logActivity( - 'get_traffic_clones', - 'repository', + "get_traffic_clones", + "repository", `${owner}/${repo}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -1709,8 +1882,8 @@ export class GitHubService extends Service { */ async getTopPaths(owner: string, repo: string): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -1720,10 +1893,23 @@ export class GitHubService extends Service { }); this.updateRateLimit(headers); - this.logActivity('get_top_paths', 'repository', `${owner}/${repo}`, { paths: data }, true); + this.logActivity( + "get_top_paths", + "repository", + `${owner}/${repo}`, + { paths: data }, + true, + ); return data; } catch (error) { - this.logActivity('get_top_paths', 'repository', `${owner}/${repo}`, {}, false, String(error)); + this.logActivity( + "get_top_paths", + "repository", + `${owner}/${repo}`, + {}, + false, + String(error), + ); throw this.handleError(error); } } @@ -1733,8 +1919,8 @@ export class GitHubService extends Service { */ async getTopReferrers(owner: string, repo: string): Promise { try { - this.validateGitHubName(owner, 'owner'); - this.validateGitHubName(repo, 'repo'); + this.validateGitHubName(owner, "owner"); + this.validateGitHubName(repo, "repo"); await this.checkRateLimit(); @@ -1745,21 +1931,21 @@ export class GitHubService extends Service { this.updateRateLimit(headers); this.logActivity( - 'get_top_referrers', - 'repository', + "get_top_referrers", + "repository", `${owner}/${repo}`, { referrers: data }, - true + true, ); return data; } catch (error) { this.logActivity( - 'get_top_referrers', - 'repository', + "get_top_referrers", + "repository", `${owner}/${repo}`, {}, false, - String(error) + String(error), ); throw this.handleError(error); } @@ -1770,7 +1956,7 @@ export class GitHubService extends Service { */ async getUser(username: string): Promise { try { - this.validateGitHubName(username, 'username'); + this.validateGitHubName(username, "username"); await this.checkRateLimit(); @@ -1779,10 +1965,10 @@ export class GitHubService extends Service { }); this.updateRateLimit(headers); - this.logActivity('get_user', 'user', username, { user: data }, true); + this.logActivity("get_user", "user", username, { user: data }, true); return data; } catch (error) { - this.logActivity('get_user', 'user', username, {}, false, String(error)); + this.logActivity("get_user", "user", username, {}, false, String(error)); throw this.handleError(error); } } @@ -1790,24 +1976,40 @@ export class GitHubService extends Service { /** * List user repositories */ - async listUserRepositories(username: string, options: any = {}): Promise { + async listUserRepositories( + username: string, + options: any = {}, + ): Promise { try { - this.validateGitHubName(username, 'username'); + this.validateGitHubName(username, "username"); await this.checkRateLimit(); const { data, headers } = await this.octokit.repos.listForUser({ username, - type: options.type || 'owner', - sort: options.sort || 'updated', + type: options.type || "owner", + sort: options.sort || "updated", per_page: options.per_page || 30, }); this.updateRateLimit(headers); - this.logActivity('list_user_repositories', 'user', username, { count: data.length }, true); + this.logActivity( + "list_user_repositories", + "user", + username, + { count: data.length }, + true, + ); return data; } catch (error) { - this.logActivity('list_user_repositories', 'user', username, {}, false, String(error)); + this.logActivity( + "list_user_repositories", + "user", + username, + {}, + false, + String(error), + ); throw this.handleError(error); } } @@ -1817,31 +2019,45 @@ export class GitHubService extends Service { */ async listUserEvents(username: string, options: any = {}): Promise { try { - this.validateGitHubName(username, 'username'); + this.validateGitHubName(username, "username"); await this.checkRateLimit(); - const { data, headers } = await this.octokit.activity.listEventsForAuthenticatedUser({ - username, - per_page: options.per_page || 30, - }); + const { data, headers } = + await this.octokit.activity.listEventsForAuthenticatedUser({ + username, + per_page: options.per_page || 30, + }); this.updateRateLimit(headers); - this.logActivity('list_user_events', 'user', username, { count: data.length }, true); + this.logActivity( + "list_user_events", + "user", + username, + { count: data.length }, + true, + ); return data; } catch (error) { - this.logActivity('list_user_events', 'user', username, {}, false, String(error)); + this.logActivity( + "list_user_events", + "user", + username, + {}, + false, + String(error), + ); throw this.handleError(error); } } private logActivity( action: string, - resourceType: 'repository' | 'issue' | 'pr' | 'user', + resourceType: "repository" | "issue" | "pr" | "user", resourceId: string, details: Record = {}, success: boolean = true, - error?: string + error?: string, ) { const activity: GitHubActivityItem = { id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, @@ -1862,47 +2078,63 @@ export class GitHubService extends Service { } if (success) { - logger.info(`GitHub activity: ${action} on ${resourceType} ${resourceId}`); + logger.info( + `GitHub activity: ${action} on ${resourceType} ${resourceId}`, + ); } else { - logger.error(`GitHub activity failed: ${action} on ${resourceType} ${resourceId} - ${error}`); + logger.error( + `GitHub activity failed: ${action} on ${resourceType} ${resourceId} - ${error}`, + ); } } private handleError(error: any): GitHubAPIError { // Log the sanitized error for debugging - logger.error('[GitHubService] GitHub API error:', this.sanitizeError(error)); + logger.error( + "[GitHubService] GitHub API error:", + this.sanitizeError(error), + ); if (error.status === 401) { return new GitHubAuthenticationError( - 'GitHub authentication failed. Please verify your token.' + "GitHub authentication failed. Please verify your token.", ); } - if (error.status === 403 && error.response?.headers?.['x-ratelimit-remaining'] === '0') { - const resetTime = parseInt(error.response.headers['x-ratelimit-reset'] || '0', 10); - return new GitHubRateLimitError('GitHub API rate limit exceeded', resetTime); + if ( + error.status === 403 && + error.response?.headers?.["x-ratelimit-remaining"] === "0" + ) { + const resetTime = parseInt( + error.response.headers["x-ratelimit-reset"] || "0", + 10, + ); + return new GitHubRateLimitError( + "GitHub API rate limit exceeded", + resetTime, + ); } if (error.status === 404) { return new GitHubAPIError( - 'Resource not found. Please check the repository/issue/PR exists and you have access.', + "Resource not found. Please check the repository/issue/PR exists and you have access.", error.status, - undefined // Don't include response to avoid potential token exposure + undefined, // Don't include response to avoid potential token exposure ); } if (error.status === 422) { return new GitHubAPIError( - 'Invalid request. Please check the provided parameters.', + "Invalid request. Please check the provided parameters.", error.status, - undefined + undefined, ); } return new GitHubAPIError( - error.message || 'GitHub API error occurred', + error.message || "GitHub API error occurred", error.status || 500, - undefined // Don't include response to avoid potential token exposure + undefined, // Don't include response to avoid potential token exposure ); } } diff --git a/src/test-exports.ts b/src/test-exports.ts index 3d28f12..6cf2c27 100644 --- a/src/test-exports.ts +++ b/src/test-exports.ts @@ -1,12 +1,18 @@ // Separate test exports to avoid bundling test code in production builds // This file should only be imported by test runners -import { GitHubPluginTestSuite, GitHubIntelligentAnalysisTestSuite } from './tests'; +import { + GitHubPluginTestSuite, + GitHubIntelligentAnalysisTestSuite, +} from "./tests"; export { GitHubPluginTestSuite, GitHubIntelligentAnalysisTestSuite }; // Combined test suites for comprehensive testing -export const AllGitHubTestSuites = [GitHubPluginTestSuite, GitHubIntelligentAnalysisTestSuite]; +export const AllGitHubTestSuites = [ + GitHubPluginTestSuite, + GitHubIntelligentAnalysisTestSuite, +]; // Default export for compatibility export default GitHubPluginTestSuite; diff --git a/src/tests.ts b/src/tests.ts index 6382e98..e611b8f 100644 --- a/src/tests.ts +++ b/src/tests.ts @@ -5,57 +5,69 @@ import { type TestSuite, logger, Service, -} from '@elizaos/core'; -import { GitHubService } from './services/github'; +} from "@elizaos/core"; +import { GitHubService } from "./services/github"; import { getRepositoryAction, listRepositoriesAction, searchRepositoriesAction, -} from './actions/repository'; -import { getGitHubActivityAction, getGitHubRateLimitAction } from './actions/activity'; -import intelligentAnalysisTestSuite from './__tests__/e2e/intelligent-analysis.test'; +} from "./actions/repository"; +import { + getGitHubActivityAction, + getGitHubRateLimitAction, +} from "./actions/activity"; +import intelligentAnalysisTestSuite from "./__tests__/e2e/intelligent-analysis.test"; // Helper function to create valid UUID-like IDs const createTestId = () => - 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' as `${string}-${string}-${string}-${string}-${string}`; + "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" as `${string}-${string}-${string}-${string}-${string}`; // Helper function to ensure GitHub service is available -async function ensureGitHubService(runtime: IAgentRuntime): Promise { - let githubService = runtime.getService('github'); +async function ensureGitHubService( + runtime: IAgentRuntime, +): Promise { + let githubService = runtime.getService("github"); if (!githubService) { - logger.info('GitHub service not found, attempting to start it...'); + logger.info("GitHub service not found, attempting to start it..."); try { // Try to start the service manually const newService = await GitHubService.start(runtime); - logger.info('GitHub service started successfully'); + logger.info("GitHub service started successfully"); // Try to access the runtime's internal service map const runtimeAny = runtime as any; // Check various possible service map locations if (runtimeAny._services && runtimeAny._services instanceof Map) { - logger.info('Found _services map, registering service...'); - runtimeAny._services.set('github', newService); + logger.info("Found _services map, registering service..."); + runtimeAny._services.set("github", newService); } else if (runtimeAny.services && runtimeAny.services instanceof Map) { - logger.info('Found services map, registering service...'); - runtimeAny.services.set('github', newService); - } else if (runtimeAny.serviceRegistry && runtimeAny.serviceRegistry instanceof Map) { - logger.info('Found serviceRegistry map, registering service...'); - runtimeAny.serviceRegistry.set('github', newService); + logger.info("Found services map, registering service..."); + runtimeAny.services.set("github", newService); + } else if ( + runtimeAny.serviceRegistry && + runtimeAny.serviceRegistry instanceof Map + ) { + logger.info("Found serviceRegistry map, registering service..."); + runtimeAny.serviceRegistry.set("github", newService); } else { - logger.info('No service map found, overriding getService method...'); + logger.info("No service map found, overriding getService method..."); // Override getService for this test run const originalGetService = runtime.getService.bind(runtime); runtime.getService = function ( serviceNameOrClass?: | string - | { new (...args: any[]): T; serviceName?: string; serviceType?: string } + | { + new (...args: any[]): T; + serviceName?: string; + serviceType?: string; + }, ): T | null { - if (typeof serviceNameOrClass === 'string') { + if (typeof serviceNameOrClass === "string") { if ( - serviceNameOrClass === 'github' || + serviceNameOrClass === "github" || serviceNameOrClass === GitHubService.serviceType ) { return newService as unknown as T; @@ -63,7 +75,7 @@ async function ensureGitHubService(runtime: IAgentRuntime): Promise { try { const githubService = await ensureGitHubService(runtime); if (!githubService) { - throw new Error('GitHub service is not available in runtime'); + throw new Error("GitHub service is not available in runtime"); } // Verify service has required methods - if (typeof githubService.getRepository !== 'function') { - throw new Error('GitHub service missing getRepository method'); + if (typeof githubService.getRepository !== "function") { + throw new Error("GitHub service missing getRepository method"); } - if (typeof githubService.getRateLimit !== 'function') { - throw new Error('GitHub service missing getRateLimit method'); + if (typeof githubService.getRateLimit !== "function") { + throw new Error("GitHub service missing getRateLimit method"); } - logger.info('✅ GitHub service initialized successfully with all required methods'); + logger.info( + "✅ GitHub service initialized successfully with all required methods", + ); } catch (error) { - logger.error('❌ GitHub service initialization test failed:', error); + logger.error("❌ GitHub service initialization test failed:", error); throw error; } }, }, { - name: 'get_repository_action_test', + name: "get_repository_action_test", fn: async (runtime: IAgentRuntime) => { try { // Ensure service is available @@ -134,24 +148,24 @@ export const githubPluginTestSuite = { roomId: createTestId(), createdAt: Date.now(), content: { - text: 'Get information about octocat/Hello-World repository', - source: 'test', + text: "Get information about octocat/Hello-World repository", + source: "test", }, }; const testState: State = { values: {}, data: { - owner: 'octocat', - repo: 'Hello-World', + owner: "octocat", + repo: "Hello-World", }, - text: '', + text: "", }; // Validate action first const isValid = await action.validate(runtime, testMemory, testState); if (!isValid) { - throw new Error('Action validation failed'); + throw new Error("Action validation failed"); } // Execute the action with proper error handling @@ -163,37 +177,37 @@ export const githubPluginTestSuite = { runtime, testMemory, testState, - { owner: 'octocat', repo: 'Hello-World' }, + { owner: "octocat", repo: "Hello-World" }, async (response) => { - logger.info('Action response:', response); + logger.info("Action response:", response); responseReceived = true; return []; }, - [] + [], ); } catch (actionError) { - logger.error('Error in GET_GITHUB_REPOSITORY action:', actionError); + logger.error("Error in GET_GITHUB_REPOSITORY action:", actionError); throw actionError; } if (!result) { - throw new Error('No result returned from action'); + throw new Error("No result returned from action"); } if (!result.values?.repository) { - logger.error('Result values:', result.values); - throw new Error('Repository information not found in response'); + logger.error("Result values:", result.values); + throw new Error("Repository information not found in response"); } - logger.info('✅ Get repository action test passed'); + logger.info("✅ Get repository action test passed"); } catch (error) { - logger.error('❌ Get repository action test failed:', error); + logger.error("❌ Get repository action test failed:", error); throw error; } }, }, { - name: 'github_rate_limit_check_test', + name: "github_rate_limit_check_test", fn: async (runtime: IAgentRuntime) => { try { // Ensure service is available @@ -207,15 +221,15 @@ export const githubPluginTestSuite = { roomId: createTestId(), createdAt: Date.now(), content: { - text: 'Check GitHub API rate limit', - source: 'test', + text: "Check GitHub API rate limit", + source: "test", }, }; const testState: State = { values: {}, data: {}, - text: '', + text: "", }; // Execute the action @@ -228,67 +242,71 @@ export const githubPluginTestSuite = { testState, {}, async (response) => { - logger.info('Rate limit response:', response); + logger.info("Rate limit response:", response); return []; }, - [] + [], ); } catch (actionError) { - logger.error('Error in GET_GITHUB_RATE_LIMIT action:', actionError); + logger.error("Error in GET_GITHUB_RATE_LIMIT action:", actionError); throw actionError; } if (!result) { - throw new Error('No result returned from rate limit action'); + throw new Error("No result returned from rate limit action"); } if (!result.values?.rateLimit) { - logger.error('Result values:', result.values); - throw new Error('Rate limit information not found in response'); + logger.error("Result values:", result.values); + throw new Error("Rate limit information not found in response"); } - logger.info('✅ GitHub rate limit check test passed'); + logger.info("✅ GitHub rate limit check test passed"); } catch (error) { - logger.error('❌ GitHub rate limit check test failed:', error); + logger.error("❌ GitHub rate limit check test failed:", error); throw error; } }, }, { - name: 'providers_integration_test', + name: "providers_integration_test", fn: async (runtime: IAgentRuntime) => { try { const providers = runtime.providers || []; const githubProviders = providers.filter( - (p: any) => p.name && p.name.toLowerCase().includes('github') + (p: any) => p.name && p.name.toLowerCase().includes("github"), ); if (githubProviders.length === 0) { - throw new Error('No GitHub providers found in runtime'); + throw new Error("No GitHub providers found in runtime"); } logger.info( - `Found ${githubProviders.length} GitHub providers: ${githubProviders.map((p: any) => p.name).join(', ')}` + `Found ${githubProviders.length} GitHub providers: ${githubProviders.map((p: any) => p.name).join(", ")}`, ); - logger.info('✅ Providers integration test passed'); + logger.info("✅ Providers integration test passed"); } catch (error) { - logger.error('❌ Providers integration test failed:', error); + logger.error("❌ Providers integration test failed:", error); throw error; } }, }, // Complex Runtime Scenarios - Action Chaining { - name: 'complex_scenario_repository_analysis', + name: "complex_scenario_repository_analysis", fn: async (runtime: IAgentRuntime) => { try { - logger.info('Starting complex scenario: Repository Analysis Workflow'); + logger.info( + "Starting complex scenario: Repository Analysis Workflow", + ); // Ensure service is available await ensureGitHubService(runtime); // Step 1: Search for popular TypeScript repositories - logger.info('Step 1: Searching for popular TypeScript repositories...'); + logger.info( + "Step 1: Searching for popular TypeScript repositories...", + ); const searchMemory: Memory = { id: createTestId(), entityId: createTestId(), @@ -296,20 +314,20 @@ export const githubPluginTestSuite = { roomId: createTestId(), createdAt: Date.now(), content: { - text: 'Search for popular TypeScript repositories with more than 1000 stars', - source: 'test', + text: "Search for popular TypeScript repositories with more than 1000 stars", + source: "test", }, }; const searchState: State = { values: {}, data: { - query: 'language:typescript stars:>1000', - sort: 'stars', - order: 'desc', + query: "language:typescript stars:>1000", + sort: "stars", + order: "desc", per_page: 5, }, - text: '', + text: "", }; // Execute repository search @@ -319,31 +337,36 @@ export const githubPluginTestSuite = { searchMemory, searchState, { - query: 'language:typescript stars:>1000', - sort: 'stars' as const, + query: "language:typescript stars:>1000", + sort: "stars" as const, limit: 5, }, async (response) => { - logger.info('Search response received'); + logger.info("Search response received"); return []; }, - [] + [], ); if ( !searchResult || - typeof searchResult === 'boolean' || + typeof searchResult === "boolean" || !searchResult.values?.repositories ) { - throw new Error('Repository search failed'); + throw new Error("Repository search failed"); } logger.info(`Found ${searchResult.values.totalCount} repositories`); // Step 2: Get detailed information about the first repository - if (searchResult.values.repositories && searchResult.values.repositories.length > 0) { + if ( + searchResult.values.repositories && + searchResult.values.repositories.length > 0 + ) { const firstRepo = searchResult.values.repositories[0]; - logger.info(`Step 2: Getting details for repository: ${firstRepo.full_name}`); + logger.info( + `Step 2: Getting details for repository: ${firstRepo.full_name}`, + ); const detailsMemory: Memory = { id: createTestId(), @@ -353,7 +376,7 @@ export const githubPluginTestSuite = { createdAt: Date.now(), content: { text: `Get detailed information about ${firstRepo.full_name}`, - source: 'test', + source: "test", }, }; @@ -363,7 +386,7 @@ export const githubPluginTestSuite = { owner: firstRepo.owner.login, repo: firstRepo.name, }, - text: '', + text: "", }; const detailsResult = await getRepositoryAction.handler( @@ -372,22 +395,22 @@ export const githubPluginTestSuite = { detailsState, { owner: firstRepo.owner.login, repo: firstRepo.name }, async (response) => { - logger.info('Repository details received'); + logger.info("Repository details received"); return []; }, - [] + [], ); if ( !detailsResult || - typeof detailsResult === 'boolean' || + typeof detailsResult === "boolean" || !detailsResult.values?.repository ) { - throw new Error('Failed to get repository details'); + throw new Error("Failed to get repository details"); } // Step 3: Check rate limit after operations - logger.info('Step 3: Checking API rate limit...'); + logger.info("Step 3: Checking API rate limit..."); const rateLimitResult = await getGitHubRateLimitAction.handler( runtime, { @@ -396,45 +419,47 @@ export const githubPluginTestSuite = { agentId: runtime.agentId, roomId: createTestId(), createdAt: Date.now(), - content: { text: 'Check rate limit', source: 'test' }, + content: { text: "Check rate limit", source: "test" }, }, - { values: {}, data: {}, text: '' }, + { values: {}, data: {}, text: "" }, {}, async () => [], - [] + [], ); if ( !rateLimitResult || - typeof rateLimitResult === 'boolean' || + typeof rateLimitResult === "boolean" || !rateLimitResult.values?.rateLimit ) { - throw new Error('Failed to check rate limit'); + throw new Error("Failed to check rate limit"); } logger.info( - `Rate limit: ${rateLimitResult.values.rateLimit.remaining}/${rateLimitResult.values.rateLimit.limit}` + `Rate limit: ${rateLimitResult.values.rateLimit.remaining}/${rateLimitResult.values.rateLimit.limit}`, ); } - logger.info('✅ Complex scenario: Repository Analysis Workflow completed successfully'); + logger.info( + "✅ Complex scenario: Repository Analysis Workflow completed successfully", + ); } catch (error) { - logger.error('❌ Complex scenario failed:', error); + logger.error("❌ Complex scenario failed:", error); throw error; } }, }, { - name: 'complex_scenario_issue_workflow', + name: "complex_scenario_issue_workflow", fn: async (runtime: IAgentRuntime) => { try { - logger.info('Starting complex scenario: Issue Management Workflow'); + logger.info("Starting complex scenario: Issue Management Workflow"); // Ensure service is available await ensureGitHubService(runtime); // Step 1: Get activity to see what repositories we're working with - logger.info('Step 1: Getting recent GitHub activity...'); + logger.info("Step 1: Getting recent GitHub activity..."); const activityResult = await getGitHubActivityAction.handler( runtime, { @@ -443,22 +468,22 @@ export const githubPluginTestSuite = { agentId: runtime.agentId, roomId: createTestId(), createdAt: Date.now(), - content: { text: 'Show my GitHub activity', source: 'test' }, + content: { text: "Show my GitHub activity", source: "test" }, }, - { values: {}, data: {}, text: '' }, + { values: {}, data: {}, text: "" }, { limit: 10 }, async () => [], - [] + [], ); if (!activityResult) { - throw new Error('Failed to get activity'); + throw new Error("Failed to get activity"); } - logger.info('Activity retrieved successfully'); + logger.info("Activity retrieved successfully"); // Step 2: List repositories (simulating finding a repo to work with) - logger.info('Step 2: Listing available repositories...'); + logger.info("Step 2: Listing available repositories..."); const listReposAction = listRepositoriesAction; const reposResult = await listReposAction.handler( runtime, @@ -468,16 +493,16 @@ export const githubPluginTestSuite = { agentId: runtime.agentId, roomId: createTestId(), createdAt: Date.now(), - content: { text: 'List my repositories', source: 'test' }, + content: { text: "List my repositories", source: "test" }, }, - { values: {}, data: {}, text: '' }, - { type: 'all', sort: 'updated', per_page: 5 }, + { values: {}, data: {}, text: "" }, + { type: "all", sort: "updated", per_page: 5 }, async () => [], - [] + [], ); // Step 3: Chain multiple operations in sequence - logger.info('Step 3: Performing repository health check...'); + logger.info("Step 3: Performing repository health check..."); // Check rate limit multiple times to simulate real usage const rateLimitChecks = []; @@ -490,12 +515,12 @@ export const githubPluginTestSuite = { agentId: runtime.agentId, roomId: createTestId(), createdAt: Date.now(), - content: { text: `Rate limit check ${i + 1}`, source: 'test' }, + content: { text: `Rate limit check ${i + 1}`, source: "test" }, }, - { values: {}, data: {}, text: '' }, + { values: {}, data: {}, text: "" }, {}, async () => [], - [] + [], ); rateLimitChecks.push(check); @@ -504,30 +529,32 @@ export const githubPluginTestSuite = { } logger.info(`Performed ${rateLimitChecks.length} rate limit checks`); - logger.info('✅ Complex scenario: Issue Management Workflow completed successfully'); + logger.info( + "✅ Complex scenario: Issue Management Workflow completed successfully", + ); } catch (error) { - logger.error('❌ Complex scenario failed:', error); + logger.error("❌ Complex scenario failed:", error); throw error; } }, }, { - name: 'complex_scenario_multi_repository_analysis', + name: "complex_scenario_multi_repository_analysis", fn: async (runtime: IAgentRuntime) => { try { - logger.info('Starting complex scenario: Multi-Repository Analysis'); + logger.info("Starting complex scenario: Multi-Repository Analysis"); // Ensure service is available await ensureGitHubService(runtime); // Analyze multiple repositories in parallel const repoTargets = [ - { owner: 'facebook', repo: 'react' }, - { owner: 'vuejs', repo: 'vue' }, - { owner: 'angular', repo: 'angular' }, + { owner: "facebook", repo: "react" }, + { owner: "vuejs", repo: "vue" }, + { owner: "angular", repo: "angular" }, ]; - logger.info('Analyzing multiple repositories in parallel...'); + logger.info("Analyzing multiple repositories in parallel..."); const analysisPromises = repoTargets.map(async (target) => { const memory: Memory = { @@ -538,14 +565,14 @@ export const githubPluginTestSuite = { createdAt: Date.now(), content: { text: `Analyze ${target.owner}/${target.repo}`, - source: 'test', + source: "test", }, }; const state: State = { values: {}, data: target, - text: '', + text: "", }; try { @@ -555,10 +582,12 @@ export const githubPluginTestSuite = { state, target, async (response) => { - logger.info(`Analysis complete for ${target.owner}/${target.repo}`); + logger.info( + `Analysis complete for ${target.owner}/${target.repo}`, + ); return []; }, - [] + [], ); return { target, result, success: true }; @@ -572,10 +601,14 @@ export const githubPluginTestSuite = { const successful = results.filter((r) => r.success).length; const failed = results.filter((r) => !r.success).length; - logger.info(`Analysis complete: ${successful} successful, ${failed} failed`); + logger.info( + `Analysis complete: ${successful} successful, ${failed} failed`, + ); if (failed > 0) { - throw new Error(`Some repository analyses failed: ${failed} out of ${results.length}`); + throw new Error( + `Some repository analyses failed: ${failed} out of ${results.length}`, + ); } // Final rate limit check @@ -587,31 +620,35 @@ export const githubPluginTestSuite = { agentId: runtime.agentId, roomId: createTestId(), createdAt: Date.now(), - content: { text: 'Final rate limit check', source: 'test' }, + content: { text: "Final rate limit check", source: "test" }, }, - { values: {}, data: {}, text: '' }, + { values: {}, data: {}, text: "" }, {}, async () => [], - [] + [], ); - if (finalRateLimit && typeof finalRateLimit !== 'boolean') { + if (finalRateLimit && typeof finalRateLimit !== "boolean") { logger.info( - `Final rate limit: ${finalRateLimit.values?.rateLimit?.remaining} remaining` + `Final rate limit: ${finalRateLimit.values?.rateLimit?.remaining} remaining`, ); } - logger.info('✅ Complex scenario: Multi-Repository Analysis completed successfully'); + logger.info( + "✅ Complex scenario: Multi-Repository Analysis completed successfully", + ); } catch (error) { - logger.error('❌ Complex scenario failed:', error); + logger.error("❌ Complex scenario failed:", error); throw error; } }, }, { - name: 'complex_scenario_state_persistence', + name: "complex_scenario_state_persistence", fn: async (runtime: IAgentRuntime) => { try { - logger.info('Starting complex scenario: State Persistence and Context'); + logger.info( + "Starting complex scenario: State Persistence and Context", + ); // Ensure service is available const githubService = await ensureGitHubService(runtime); @@ -620,7 +657,7 @@ export const githubPluginTestSuite = { const state: State = { values: {}, data: {}, - text: '', + text: "", github: { repositories: {}, issues: {}, @@ -630,7 +667,7 @@ export const githubPluginTestSuite = { }; // Step 1: Get a repository and add to state - logger.info('Step 1: Building repository context...'); + logger.info("Step 1: Building repository context..."); const repoResult = await getRepositoryAction.handler( runtime, { @@ -639,25 +676,32 @@ export const githubPluginTestSuite = { agentId: runtime.agentId, roomId: createTestId(), createdAt: Date.now(), - content: { text: 'Get octocat/Hello-World', source: 'test' }, + content: { text: "Get octocat/Hello-World", source: "test" }, }, state, - { owner: 'octocat', repo: 'Hello-World' }, + { owner: "octocat", repo: "Hello-World" }, async () => [], - [] + [], ); - if (repoResult && typeof repoResult !== 'boolean' && repoResult.values?.repository) { + if ( + repoResult && + typeof repoResult !== "boolean" && + repoResult.values?.repository + ) { state.github!.lastRepository = repoResult.values.repository; - state.github!.repositories!['octocat/Hello-World'] = repoResult.values.repository; + state.github!.repositories!["octocat/Hello-World"] = + repoResult.values.repository; state.github!.activityStats!.total++; state.github!.activityStats!.success++; } // Step 2: Use providers to get context from state - logger.info('Step 2: Testing providers with built-up state...'); + logger.info("Step 2: Testing providers with built-up state..."); const providers = runtime.providers || []; - const repoProvider = providers.find((p: any) => p.name === 'GITHUB_REPOSITORY_CONTEXT'); + const repoProvider = providers.find( + (p: any) => p.name === "GITHUB_REPOSITORY_CONTEXT", + ); if (repoProvider) { const providerResult = await repoProvider.get( @@ -668,30 +712,39 @@ export const githubPluginTestSuite = { agentId: runtime.agentId, roomId: createTestId(), createdAt: Date.now(), - content: { text: 'Get context', source: 'test' }, + content: { text: "Get context", source: "test" }, }, - state + state, ); - if (!providerResult.text || !providerResult.text.includes('octocat/Hello-World')) { - throw new Error('Provider did not return expected repository context'); + if ( + !providerResult.text || + !providerResult.text.includes("octocat/Hello-World") + ) { + throw new Error( + "Provider did not return expected repository context", + ); } - logger.info('Provider successfully returned repository context from state'); + logger.info( + "Provider successfully returned repository context from state", + ); } // Step 3: Activity tracking - logger.info('Step 3: Verifying activity tracking...'); + logger.info("Step 3: Verifying activity tracking..."); const activityLog = githubService.getActivityLog(10); if (activityLog.length === 0) { - throw new Error('No activity logged despite operations'); + throw new Error("No activity logged despite operations"); } logger.info(`Activity log contains ${activityLog.length} entries`); // Step 4: Multiple operations to test state accumulation - logger.info('Step 4: Testing state accumulation through multiple operations...'); + logger.info( + "Step 4: Testing state accumulation through multiple operations...", + ); // Perform several rate limit checks for (let i = 0; i < 3; i++) { @@ -703,12 +756,12 @@ export const githubPluginTestSuite = { agentId: runtime.agentId, roomId: createTestId(), createdAt: Date.now(), - content: { text: `Rate check ${i}`, source: 'test' }, + content: { text: `Rate check ${i}`, source: "test" }, }, state, {}, async () => [], - [] + [], ); state.github!.activityStats!.total++; @@ -717,13 +770,19 @@ export const githubPluginTestSuite = { // Verify final state if (state.github!.activityStats!.total < 4) { - throw new Error('State was not properly accumulated through operations'); + throw new Error( + "State was not properly accumulated through operations", + ); } - logger.info(`Final state: ${state.github!.activityStats!.total} total operations`); - logger.info('✅ Complex scenario: State Persistence and Context completed successfully'); + logger.info( + `Final state: ${state.github!.activityStats!.total} total operations`, + ); + logger.info( + "✅ Complex scenario: State Persistence and Context completed successfully", + ); } catch (error) { - logger.error('❌ Complex scenario failed:', error); + logger.error("❌ Complex scenario failed:", error); throw error; } }, @@ -736,7 +795,10 @@ export const GitHubPluginTestSuite = githubPluginTestSuite; export const GitHubIntelligentAnalysisTestSuite = intelligentAnalysisTestSuite; // Combined test suites for comprehensive testing -export const AllGitHubTestSuites = [githubPluginTestSuite, intelligentAnalysisTestSuite]; +export const AllGitHubTestSuites = [ + githubPluginTestSuite, + intelligentAnalysisTestSuite, +]; // Default export for compatibility export default githubPluginTestSuite; diff --git a/src/types.ts b/src/types.ts index 9e35388..fb49b0a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,19 +1,19 @@ -import { z } from 'zod'; +import { z } from "zod"; // GitHub Configuration Schema (Production) export const githubConfigSchema = z.object({ GITHUB_TOKEN: z .string() - .min(1, 'GitHub token is required') + .min(1, "GitHub token is required") .refine( (token) => - token.startsWith('ghp_') || - token.startsWith('github_pat_') || - token.startsWith('gho_') || - token.startsWith('ghu_') || - token.startsWith('ghs_') || - token.startsWith('ghr_'), - 'Invalid GitHub token format' + token.startsWith("ghp_") || + token.startsWith("github_pat_") || + token.startsWith("gho_") || + token.startsWith("ghu_") || + token.startsWith("ghs_") || + token.startsWith("ghr_"), + "Invalid GitHub token format", ), GITHUB_OWNER: z.string().optional(), GITHUB_WEBHOOK_SECRET: z.string().optional(), @@ -28,21 +28,27 @@ export const githubConfigSchemaFlexible = z.object({ .refine( (token) => !token || // Allow undefined/null - token === '' || // Allow empty string - token.startsWith('ghp_') || - token.startsWith('github_pat_') || - token.startsWith('gho_') || - token.startsWith('ghu_') || - token.startsWith('ghs_') || - token.startsWith('ghr_') || - token === 'invalid-token-format' || // Allow invalid for testing - token === 'dummy-token-for-testing' || // Allow specific test token - token.startsWith('test-') || // Allow test tokens - token.startsWith('dummy-'), // Allow dummy tokens - 'Invalid GitHub token format' + token === "" || // Allow empty string + token.startsWith("ghp_") || + token.startsWith("github_pat_") || + token.startsWith("gho_") || + token.startsWith("ghu_") || + token.startsWith("ghs_") || + token.startsWith("ghr_") || + token === "invalid-token-format" || // Allow invalid for testing + token === "dummy-token-for-testing" || // Allow specific test token + token.startsWith("test-") || // Allow test tokens + token.startsWith("dummy-"), // Allow dummy tokens + "Invalid GitHub token format", ), - GITHUB_OWNER: z.union([z.string(), z.null(), z.undefined()]).optional().nullable(), - GITHUB_WEBHOOK_SECRET: z.union([z.string(), z.null(), z.undefined()]).optional().nullable(), + GITHUB_OWNER: z + .union([z.string(), z.null(), z.undefined()]) + .optional() + .nullable(), + GITHUB_WEBHOOK_SECRET: z + .union([z.string(), z.null(), z.undefined()]) + .optional() + .nullable(), }); export type GitHubConfig = z.infer; @@ -79,7 +85,7 @@ export interface GitHubRepository { owner: { login: string; id: number; - type: 'User' | 'Organization'; + type: "User" | "Organization"; avatar_url: string; }; } @@ -90,7 +96,7 @@ export interface GitHubIssue { number: number; title: string; body: string | null; - state: 'open' | 'closed'; + state: "open" | "closed"; labels: Array<{ id: number; name: string; @@ -116,7 +122,7 @@ export interface GitHubIssue { id: number; title: string; description: string | null; - state: 'open' | 'closed'; + state: "open" | "closed"; due_on: string | null; } | null; } @@ -127,7 +133,7 @@ export interface GitHubPullRequest { number: number; title: string; body: string | null; - state: 'open' | 'closed' | 'merged'; + state: "open" | "closed" | "merged"; draft: boolean; merged: boolean; mergeable: boolean | null; @@ -212,7 +218,7 @@ export interface GitHubActivityItem { id: string; timestamp: string; action: string; - resource_type: 'repository' | 'issue' | 'pull_request'; + resource_type: "repository" | "issue" | "pull_request"; resource_id: string; details: Record; success: boolean; @@ -253,27 +259,27 @@ export class GitHubAPIError extends Error { constructor( message: string, public status: number, - public response?: any + public response?: any, ) { super(message); - this.name = 'GitHubAPIError'; + this.name = "GitHubAPIError"; } } export class GitHubAuthenticationError extends GitHubAPIError { - constructor(message: string = 'GitHub authentication failed') { + constructor(message: string = "GitHub authentication failed") { super(message, 401); - this.name = 'GitHubAuthenticationError'; + this.name = "GitHubAuthenticationError"; } } export class GitHubRateLimitError extends GitHubAPIError { constructor( - message: string = 'GitHub API rate limit exceeded', - public resetTime: number + message: string = "GitHub API rate limit exceeded", + public resetTime: number, ) { super(message, 403); - this.name = 'GitHubRateLimitError'; + this.name = "GitHubRateLimitError"; } } diff --git a/test-results.json b/test-results.json deleted file mode 100644 index 1872168..0000000 --- a/test-results.json +++ /dev/null @@ -1 +0,0 @@ -(eval):1: command not found: timeout diff --git a/tsconfig.json b/tsconfig.json index bb5b594..25f333f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,24 @@ { - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "@elizaos/core/configs/typescript/tsconfig.plugin.json", "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["ES2020", "DOM"], + "jsx": "react-jsx", + "moduleResolution": "node", + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, "baseUrl": "./src" }, - "include": ["src/**/*.ts", "src/**/*.tsx", "__tests__/**/*.ts", "cypress.d.ts"], + "include": ["src/**/*", "@types/**/*"], "exclude": ["node_modules", "dist"] } diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..989988d --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,32 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/index.ts'], + outDir: 'dist', + tsconfig: './tsconfig.build.json', + sourcemap: true, + clean: true, + format: ['esm'], + dts: { + // This will make the build fail on TypeScript errors + resolve: true, + compilerOptions: { + strict: true, + } + }, + external: [ + 'dotenv', + 'fs', + 'path', + 'crypto', + 'https', + 'http', + 'agentkeepalive', + 'zod', + '@elizaos/core', + 'axios', + 'cheerio', + 'bun:test', + // Add other modules that should be externalized + ], +}); \ No newline at end of file