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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ runs:
node-version-file: '.tool-versions'
package-manager-cache: false

- name: Setup Bun
if: runner.os != 'Windows'
uses: oven-sh/setup-bun@v2

- name: Install Node Gyp Build
shell: ${{ runner.os == 'Windows' && 'pwsh' || 'bash' }}
run: |
Expand All @@ -25,6 +29,13 @@ runs:
shell: ${{ runner.os == 'Windows' && 'pwsh' || 'bash' }}
run: pnpm i --frozen-lockfile

- name: Login to Botpress (optional)
if: ${{ env.BP_TOKEN != '' && env.BP_WORKSPACE_ID != '' }}
shell: ${{ runner.os == 'Windows' && 'pwsh' || 'bash' }}
run: |
pnpm dlx @botpress/cli login -y --api-url "$BP_API_URL" --workspace-id "$BP_WORKSPACE_ID" --token "$BP_TOKEN"
pnpm dlx @botpress/adk-cli login --token "$BP_TOKEN" --api-url "$BP_API_URL"

- name: Cache Turbo
uses: actions/cache@v5
with:
Expand All @@ -39,4 +50,7 @@ runs:
shell: ${{ runner.os == 'Windows' && 'pwsh' || 'bash' }}
env:
BP_VERBOSE: 'true'
BP_API_URL: ${{ env.BP_API_URL }}
BP_WORKSPACE_ID: ${{ env.BP_WORKSPACE_ID }}
BP_TOKEN: ${{ env.BP_TOKEN }}
run: pnpm turbo run build ${{ inputs.extra_filters }}
4 changes: 4 additions & 0 deletions .github/workflows/run-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ jobs:
- uses: actions/checkout@v2
- name: Setup
uses: ./.github/actions/setup
env:
BP_API_URL: 'https://api.botpress.dev'
BP_WORKSPACE_ID: ${{ secrets.STAGING_E2E_TESTS_WORKSPACE_ID }}
BP_TOKEN: ${{ secrets.STAGING_TOKEN_CLOUD_OPS_ACCOUNT }}
- run: pnpm run check:dep
- run: pnpm run check:sherif
- run: pnpm run check:oxlint
Expand Down
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ pnpm-lock.yaml
gen
dist
.botpress
.adk
**/.adk
**/.adk/**
.botpresshome
.botpresshome.*
bp_modules
Expand Down
34 changes: 34 additions & 0 deletions bots/quack-norris/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Dependencies
node_modules/
.pnpm-store/

# Build outputs
dist/
.adk/

# Environment files
.env
.env.local
.env.production

# IDE files
.vscode/
.idea/
*.swp
*.swo

# OS files
.DS_Store
Thumbs.db

# Logs
*.log
logs/

# Runtime files
*.pid
*.seed
*.pid.lock

NARRATIVE_BIBLE.md
agent.json
8 changes: 8 additions & 0 deletions bots/quack-norris/.mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"adk": {
"command": "adk",
"args": ["mcp"]
}
}
}
36 changes: 36 additions & 0 deletions bots/quack-norris/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# quack-norris

A Botpress Agent built with the ADK.

## Getting Started

1. Install dependencies:

```bash
bun install
```

2. Start development server:

```bash
adk dev
```

3. Deploy your agent:
```bash
adk deploy
```

## Project Structure

- `src/actions/` - Define callable functions
- `src/workflows/` - Define long-running processes
- `src/conversations/` - Define conversation handlers
- `src/tables/` - Define data storage schemas
- `src/triggers/` - Define event subscriptions
- `src/knowledge/` - Add knowledge base files

## Learn More

- [ADK Documentation](https://botpress.com/docs/adk)
- [Botpress Platform](https://botpress.com)
42 changes: 42 additions & 0 deletions bots/quack-norris/agent.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { z, defineConfig } from '@botpress/runtime'

export default defineConfig({
name: 'quack-norris',
description: 'A Discord RPG battle bot - turn-based combat between players',

defaultModels: {
zai: 'anthropic:claude-sonnet-4-20250514',
autonomous: 'anthropic:claude-sonnet-4-20250514',
},

bot: {
state: z.object({}),
},

user: {
state: z.object({
discordUserId: z.string().optional(),
wins: z.number().default(0),
losses: z.number().default(0),
}),
},

conversation: {
tags: {
gameId: { title: 'Game ID' },
phase: { title: 'Phase' },
},
},

dependencies: {
integrations: {
discord: {
version: 'shell/discord@0.1.0',
enabled: true,
config: {
botToken: process.env.QUACK_NORRIS_DISCORD_BOT_TOKEN ?? '',
},
},
},
},
})
349 changes: 349 additions & 0 deletions bots/quack-norris/bun.lock

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions bots/quack-norris/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "quack-norris",
"version": "1.0.0",
"description": "A Botpress Agent built with the ADK",
"type": "module",
"packageManager": "bun@latest",
"scripts": {
"dev": "adk dev",
"build": "node -e \"const cp = require('node:child_process'); if (process.env.CI && (!process.env.BP_TOKEN || !process.env.BP_WORKSPACE_ID)) { console.log('Skipping ADK build in CI without Botpress credentials'); process.exit(0); } const result = cp.spawnSync('adk', ['build'], { stdio: 'inherit', shell: process.platform === 'win32' }); process.exit(result.status ?? 1);\"",
"deploy": "adk deploy"
},
"dependencies": {
"@botpress/runtime": "^1.16.6"
},
"devDependencies": {
"@botpress/adk": "^1.16.6",
"@botpress/adk-cli": "^1.16.6",
"typescript": "^5.6.3"
}
}
90 changes: 90 additions & 0 deletions bots/quack-norris/src/actions/getGameStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Action, z } from '@botpress/runtime'
import { renderPlayerStatus } from '../lib/narration'
import type { Player } from '../lib/types'

export const getGameStatus = new Action({
name: 'getGameStatus',
description: 'Get the current status of a game with full class/energy/status details',

input: z.object({
gameId: z.string(),
}),

output: z.object({
phase: z.string(),
round: z.number(),
players: z.array(
z.object({
name: z.string(),
hp: z.number(),
maxHp: z.number(),
energy: z.number(),
maxEnergy: z.number(),
alive: z.boolean(),
duckClass: z.string().optional(),
specialCooldown: z.number(),
itemCount: z.number(),
})
),
winnerId: z.string().optional(),
formattedStatus: z.string(),
}),

async handler({ input }) {
const { GamesTable } = await import('../tables/Games')
const { PlayersTable } = await import('../tables/Players')

const { rows } = await GamesTable.findRows({ filter: { gameId: input.gameId }, limit: 1 })
const game = rows[0]
if (!game) {
throw new Error(`Game ${input.gameId} not found`)
}

// Fetch item counts from player profiles (parallel with partial-failure resilience)
const itemCounts = new Map<string, number>()
const profileResults = await Promise.allSettled(
(game.players as Player[]).map(async (p) => {
const { rows: profileRows } = await PlayersTable.findRows({
filter: { discordUserId: p.discordUserId },
limit: 1,
})
return { id: p.discordUserId, profile: profileRows[0] }
})
)
for (const result of profileResults) {
if (result.status === 'fulfilled') {
const count =
result.value.profile?.inventory?.reduce((sum: number, i: { quantity: number }) => sum + i.quantity, 0) ?? 0
itemCounts.set(result.value.id, count)
}
}

const players = game.players.map((p: Player) => ({
name: p.name,
hp: p.hp,
maxHp: p.maxHp,
energy: p.energy,
maxEnergy: p.maxEnergy,
alive: p.alive,
duckClass: p.duckClass,
specialCooldown: p.specialCooldown,
itemCount: itemCounts.get(p.discordUserId) ?? 0,
}))

const formattedStatus = game.players
.map((p: Player) => {
const items = itemCounts.get(p.discordUserId) ?? 0
const itemTag = items > 0 ? ` | Items: ${items}` : ''
return `${p.alive ? '❤️' : '💀'} ${renderPlayerStatus(p)}${itemTag}`
})
.join('\n\n')

return {
phase: game.phase,
round: game.round,
players,
winnerId: game.winnerId,
formattedStatus: `**Game Status** — Phase: ${game.phase} | Round: ${game.round}\n\n${formattedStatus}`,
}
},
})
Loading
Loading