@elmiristic/agent-ready 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # agent-ready
2
+
3
+ Wire an AI coding agent into any GitHub repo in 5 minutes.
4
+
5
+ Tag a Trello card **agent-ready**, and Claude reads the task, edits the code, opens a PR, and pings you on Telegram — twice a day, automatically.
6
+
7
+ ## Quick start
8
+
9
+ ```bash
10
+ npx agent-ready init
11
+ ```
12
+
13
+ The wizard will:
14
+ 1. Connect your Trello board
15
+ 2. Add your Anthropic API key
16
+ 3. Give you a Telegram chat ID via @agent_ready_bot
17
+ 4. Set all GitHub secrets automatically
18
+ 5. Write the GitHub Actions workflows into your repo
19
+
20
+ Then commit and push `.github/` and you're live.
21
+
22
+ ## How it works
23
+
24
+ 1. Add the `agent-ready` label to a Trello card in your **To Do** list
25
+ 2. The agent runs at **9am and 5pm** (or trigger manually in GitHub Actions)
26
+ 3. Claude reads the card title + description, identifies relevant files, writes the code
27
+ 4. A PR is opened — nothing touches `main` without your review
28
+ 5. You get a Telegram message with the PR link
29
+ 6. Merge the PR → card moves to **Done** automatically
30
+
31
+ ## Requirements
32
+
33
+ - Node.js 18+
34
+ - A GitHub repository
35
+ - GitHub CLI (`gh`) for automatic secret setup (optional — can set manually)
36
+ - A Trello account
37
+ - An Anthropic API key
38
+
39
+ ## License
40
+
41
+ MIT
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { init } from '../src/init.js'
4
+
5
+ const [, , command] = process.argv
6
+
7
+ switch (command) {
8
+ case 'init':
9
+ await init()
10
+ break
11
+ default:
12
+ console.log(`
13
+ agent-ready — wire an AI coding agent into any repo in 5 minutes
14
+
15
+ Usage:
16
+ npx agent-ready init Set up the agent in your current repo
17
+ `)
18
+ process.exit(0)
19
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@elmiristic/agent-ready",
3
+ "version": "0.1.0",
4
+ "description": "Wire a Trello + Claude AI + GitHub Actions coding agent into any repo in 5 minutes.",
5
+ "type": "module",
6
+ "bin": {
7
+ "agent-ready": "./bin/agent-ready.js"
8
+ },
9
+ "scripts": {
10
+ "dev": "node bin/agent-ready.js init"
11
+ },
12
+ "keywords": [
13
+ "ai",
14
+ "agent",
15
+ "trello",
16
+ "github-actions",
17
+ "claude",
18
+ "anthropic",
19
+ "automation",
20
+ "cli",
21
+ "telegram",
22
+ "coding-agent"
23
+ ],
24
+ "author": "Amazesofts",
25
+ "license": "MIT",
26
+ "dependencies": {
27
+ "@inquirer/prompts": "^7.5.0",
28
+ "chalk": "^5.4.1",
29
+ "execa": "^9.5.2",
30
+ "fs-extra": "^11.3.0",
31
+ "ora": "^8.1.1"
32
+ },
33
+ "engines": {
34
+ "node": ">=18"
35
+ }
36
+ }
package/src/init.js ADDED
@@ -0,0 +1,28 @@
1
+ import chalk from 'chalk'
2
+ import { welcome } from './steps/welcome.js'
3
+ import { collectTrello } from './steps/collectTrello.js'
4
+ import { collectAnthropic } from './steps/collectAnthropic.js'
5
+ import { collectTelegram } from './steps/collectTelegram.js'
6
+ import { setGithubSecrets } from './steps/setGithubSecrets.js'
7
+ import { scaffold } from './steps/scaffold.js'
8
+ import { done } from './steps/done.js'
9
+
10
+ export async function init() {
11
+ try {
12
+ await welcome()
13
+ const trello = await collectTrello()
14
+ const anthropic = await collectAnthropic()
15
+ const telegram = await collectTelegram()
16
+ const secrets = { ...trello, ...anthropic, ...telegram }
17
+ await setGithubSecrets(secrets)
18
+ await scaffold()
19
+ await done()
20
+ } catch (err) {
21
+ if (err.name === 'ExitPromptError') {
22
+ console.log(chalk.dim('\n Setup cancelled.'))
23
+ process.exit(0)
24
+ }
25
+ console.error(chalk.red('\n Error: ' + err.message))
26
+ process.exit(1)
27
+ }
28
+ }
@@ -0,0 +1,23 @@
1
+ import chalk from 'chalk'
2
+ import { password } from '@inquirer/prompts'
3
+
4
+ export async function collectAnthropic() {
5
+ console.log(chalk.bold(' Step 2 of 3 — Anthropic'))
6
+ console.log()
7
+ console.log(
8
+ chalk.dim(' 1. Go to: ') + chalk.cyan('https://console.anthropic.com/settings/keys'),
9
+ )
10
+ console.log(chalk.dim(' 2. Create a new API key and paste it below'))
11
+ console.log()
12
+
13
+ const apiKey = await password({
14
+ message: ' Anthropic API Key',
15
+ mask: '●',
16
+ })
17
+
18
+ console.log()
19
+
20
+ return {
21
+ ANTHROPIC_API_KEY: apiKey,
22
+ }
23
+ }
@@ -0,0 +1,25 @@
1
+ import chalk from 'chalk'
2
+ import { input } from '@inquirer/prompts'
3
+
4
+ export async function collectTelegram() {
5
+ console.log(chalk.bold(' Step 3 of 3 — Telegram'))
6
+ console.log()
7
+ console.log(chalk.dim(' Open Telegram and message: ') + chalk.cyan('@agentreadybot'))
8
+ console.log()
9
+ console.log(chalk.dim(' Send: ') + chalk.white('/start'))
10
+ console.log()
11
+ console.log(chalk.dim(' The bot will reply with your Chat ID. Paste it below.'))
12
+ console.log()
13
+
14
+ const chatId = await input({
15
+ message: ' Your Telegram Chat ID',
16
+ })
17
+
18
+ console.log()
19
+
20
+ return {
21
+ TELEGRAM_BOT_TOKEN: 'managed',
22
+ TELEGRAM_CHAT_ID: chatId,
23
+ AGENT_READY_SECRET: 'ar_shared_2026',
24
+ }
25
+ }
@@ -0,0 +1,76 @@
1
+ import chalk from 'chalk'
2
+ import { input, password, confirm } from '@inquirer/prompts'
3
+
4
+ export async function collectTrello() {
5
+ console.log(chalk.bold(' Step 1 of 3 — Trello'))
6
+ console.log()
7
+ console.log(
8
+ chalk.dim(' 1. Go to: ') +
9
+ chalk.cyan('https://trello.com/power-ups/admin'),
10
+ )
11
+ console.log(chalk.dim(' 2. Create a Power-Up → go to API Key tab'))
12
+ console.log(chalk.dim(' 3. Copy your Key, then click "Token" to generate your Token'))
13
+ console.log()
14
+
15
+ const apiKey = await password({
16
+ message: ' Trello API Key',
17
+ mask: '●',
18
+ })
19
+
20
+ const token = await password({
21
+ message: ' Trello Token',
22
+ mask: '●',
23
+ })
24
+
25
+ console.log()
26
+ console.log(chalk.dim(' To find your List IDs, open this URL in your browser:'))
27
+ console.log(
28
+ chalk.cyan(
29
+ ` https://api.trello.com/1/members/me/boards?key=${apiKey}&token=${token}`,
30
+ ),
31
+ )
32
+ console.log(chalk.dim(' Copy your board ID, then open:'))
33
+ console.log(
34
+ chalk.cyan(
35
+ ` https://api.trello.com/1/boards/BOARD_ID/lists?key=${apiKey}&token=${token}`,
36
+ ),
37
+ )
38
+ console.log()
39
+
40
+ const todoListId = await input({
41
+ message: ' To Do list ID',
42
+ })
43
+
44
+ const doingListId = await input({
45
+ message: ' Doing list ID',
46
+ })
47
+
48
+ const doneListId = await input({
49
+ message: ' Done list ID',
50
+ })
51
+
52
+ console.log()
53
+ console.log(chalk.dim(' Now create a label called "agent-ready" on your board.'))
54
+ console.log(chalk.dim(' Then open this URL to find its ID:'))
55
+ console.log(
56
+ chalk.cyan(
57
+ ` https://api.trello.com/1/boards/BOARD_ID/labels?key=${apiKey}&token=${token}`,
58
+ ),
59
+ )
60
+ console.log()
61
+
62
+ const labelId = await input({
63
+ message: ' agent-ready label ID',
64
+ })
65
+
66
+ console.log()
67
+
68
+ return {
69
+ TRELLO_API_KEY: apiKey,
70
+ TRELLO_TOKEN: token,
71
+ TRELLO_LIST_ID: todoListId,
72
+ TRELLO_DOING_LIST_ID: doingListId,
73
+ TRELLO_DONE_LIST_ID: doneListId,
74
+ TRELLO_AGENT_LABEL_ID: labelId,
75
+ }
76
+ }
@@ -0,0 +1,29 @@
1
+ import chalk from 'chalk'
2
+
3
+ export async function done() {
4
+ console.log(chalk.dim(' ─────────────────────────────────────────────────'))
5
+ console.log()
6
+ console.log(' ' + chalk.green('✓') + chalk.bold(' agent-ready is set up.'))
7
+ console.log()
8
+ console.log(chalk.dim(' How it works:'))
9
+ console.log(
10
+ chalk.dim(' 1. Add the ') +
11
+ chalk.yellow('agent-ready') +
12
+ chalk.dim(' label to a Trello card in your To Do list'),
13
+ )
14
+ console.log(
15
+ chalk.dim(' 2. The agent runs at 9am and 5pm (or trigger manually in GitHub Actions)'),
16
+ )
17
+ console.log(chalk.dim(' 3. Claude reads the task, edits the code, opens a PR'))
18
+ console.log(chalk.dim(' 4. You get a Telegram message with the PR link'))
19
+ console.log(chalk.dim(' 5. Review and merge — nothing touches main without you'))
20
+ console.log()
21
+ console.log(chalk.dim(' Commit the generated files and push to get started:'))
22
+ console.log()
23
+ console.log(' ' + chalk.cyan('git add .github && git commit -m "feat: add agent-ready" && git push'))
24
+ console.log()
25
+ console.log(
26
+ chalk.dim(' Docs: ') + chalk.cyan('https://github.com/elmir-mammadli/agent-ready'),
27
+ )
28
+ console.log()
29
+ }
@@ -0,0 +1,48 @@
1
+ import chalk from 'chalk'
2
+ import fs from 'fs-extra'
3
+ import path from 'path'
4
+ import ora from 'ora'
5
+ import { workflowTemplate } from '../templates/workflow.js'
6
+ import { agentScriptTemplate } from '../templates/agentScript.js'
7
+ import { doneWorkflowTemplate } from '../templates/doneWorkflow.js'
8
+
9
+ export async function scaffold() {
10
+ const spinner = ora({ text: ' Generating workflow files...', indent: 2 }).start()
11
+
12
+ const cwd = process.cwd()
13
+
14
+ const workflowsDir = path.join(cwd, '.github', 'workflows')
15
+ const scriptsDir = path.join(cwd, '.github', 'scripts')
16
+
17
+ await fs.ensureDir(workflowsDir)
18
+ await fs.ensureDir(scriptsDir)
19
+
20
+ await fs.writeFile(
21
+ path.join(workflowsDir, 'agent-ready.yml'),
22
+ workflowTemplate(),
23
+ )
24
+
25
+ await fs.writeFile(
26
+ path.join(workflowsDir, 'agent-ready-done.yml'),
27
+ doneWorkflowTemplate(),
28
+ )
29
+
30
+ await fs.writeFile(
31
+ path.join(scriptsDir, 'agent-ready.mjs'),
32
+ agentScriptTemplate(),
33
+ )
34
+
35
+ spinner.succeed(' Workflow files generated.')
36
+ console.log()
37
+ console.log(
38
+ chalk.dim(' Created: ') + chalk.white('.github/workflows/agent-ready.yml'),
39
+ )
40
+ console.log(
41
+ chalk.dim(' Created: ') +
42
+ chalk.white('.github/workflows/agent-ready-done.yml'),
43
+ )
44
+ console.log(
45
+ chalk.dim(' Created: ') + chalk.white('.github/scripts/agent-ready.mjs'),
46
+ )
47
+ console.log()
48
+ }
@@ -0,0 +1,60 @@
1
+ import chalk from 'chalk'
2
+ import { confirm } from '@inquirer/prompts'
3
+ import { execa } from 'execa'
4
+ import ora from 'ora'
5
+
6
+ export async function setGithubSecrets(secrets) {
7
+ console.log(chalk.bold(' GitHub Secrets'))
8
+ console.log()
9
+
10
+ const useGhCli = await confirm({
11
+ message:
12
+ ' Set GitHub secrets automatically? (requires GitHub CLI — gh.io/cli)',
13
+ default: true,
14
+ })
15
+
16
+ console.log()
17
+
18
+ const SKIP_DISPLAY = new Set(['TELEGRAM_BOT_TOKEN', 'AGENT_READY_SECRET'])
19
+
20
+ if (!useGhCli) {
21
+ console.log(chalk.dim(' Add these secrets manually in:'))
22
+ console.log(
23
+ chalk.dim(' GitHub repo → Settings → Secrets → Actions → New secret'),
24
+ )
25
+ console.log()
26
+ for (const [key] of Object.entries(secrets)) {
27
+ if (SKIP_DISPLAY.has(key)) continue
28
+ console.log(` ${chalk.yellow(key)}`)
29
+ }
30
+ console.log()
31
+ console.log(
32
+ chalk.dim(' Note: TELEGRAM_BOT_TOKEN and AGENT_READY_SECRET are managed by agent-ready.'),
33
+ )
34
+ console.log()
35
+ return
36
+ }
37
+
38
+ const spinner = ora({ text: ' Setting GitHub secrets...', indent: 2 }).start()
39
+
40
+ let failed = []
41
+
42
+ for (const [key, value] of Object.entries(secrets)) {
43
+ if (key === 'TELEGRAM_BOT_TOKEN') continue
44
+ try {
45
+ await execa('gh', ['secret', 'set', key, '--body', value])
46
+ } catch {
47
+ failed.push(key)
48
+ }
49
+ }
50
+
51
+ if (failed.length > 0) {
52
+ spinner.warn(
53
+ ` Some secrets failed to set: ${failed.join(', ')}\n Set them manually in GitHub repo → Settings → Secrets → Actions`,
54
+ )
55
+ } else {
56
+ spinner.succeed(' All secrets set.')
57
+ }
58
+
59
+ console.log()
60
+ }
@@ -0,0 +1,22 @@
1
+ import chalk from 'chalk'
2
+
3
+ export async function welcome() {
4
+ console.log()
5
+ console.log(
6
+ chalk.bold(' agent-ready') + chalk.dim(' — AI coding agent for your repo'),
7
+ )
8
+ console.log()
9
+ console.log(
10
+ chalk.dim(' Tag a Trello card ') +
11
+ chalk.yellow('agent-ready') +
12
+ chalk.dim(', Claude reads the task,'),
13
+ )
14
+ console.log(
15
+ chalk.dim(' writes the code, opens a PR, and pings you on Telegram.'),
16
+ )
17
+ console.log()
18
+ console.log(chalk.dim(' This wizard sets everything up in about 5 minutes.'))
19
+ console.log()
20
+ console.log(chalk.dim(' ─────────────────────────────────────────────────'))
21
+ console.log()
22
+ }
@@ -0,0 +1,225 @@
1
+ export function agentScriptTemplate() {
2
+ return `import { execSync } from 'child_process'
3
+ import { readFileSync, writeFileSync, mkdirSync } from 'fs'
4
+ import { dirname } from 'path'
5
+ import Anthropic from '@anthropic-ai/sdk'
6
+
7
+ const AGENT_READY_API = 'https://telegram-bot-ruby-tau.vercel.app'
8
+
9
+ const {
10
+ TRELLO_API_KEY,
11
+ TRELLO_TOKEN,
12
+ TRELLO_LIST_ID,
13
+ TRELLO_DOING_LIST_ID,
14
+ TRELLO_AGENT_LABEL_ID,
15
+ ANTHROPIC_API_KEY,
16
+ TELEGRAM_CHAT_ID,
17
+ AGENT_READY_SECRET,
18
+ GITHUB_TOKEN,
19
+ GITHUB_REPOSITORY,
20
+ } = process.env
21
+
22
+ const required = {
23
+ TRELLO_API_KEY, TRELLO_TOKEN, TRELLO_LIST_ID,
24
+ TRELLO_DOING_LIST_ID, TRELLO_AGENT_LABEL_ID,
25
+ ANTHROPIC_API_KEY, TELEGRAM_CHAT_ID,
26
+ GITHUB_TOKEN, GITHUB_REPOSITORY,
27
+ }
28
+ const missing = Object.entries(required).filter(([, v]) => !v).map(([k]) => k)
29
+ if (missing.length > 0) {
30
+ console.error('Missing secrets: ' + missing.join(', '))
31
+ process.exit(1)
32
+ }
33
+
34
+ const anthropic = new Anthropic({ apiKey: ANTHROPIC_API_KEY })
35
+
36
+ async function trello(path, method = 'GET', body = null) {
37
+ const sep = path.includes('?') ? '&' : '?'
38
+ const url = \`https://api.trello.com/1\${path}\${sep}key=\${TRELLO_API_KEY}&token=\${TRELLO_TOKEN}\`
39
+ const opts = { method, headers: { 'Content-Type': 'application/json' } }
40
+ if (body) opts.body = JSON.stringify(body)
41
+ const res = await fetch(url, opts)
42
+ if (!res.ok) throw new Error(\`Trello \${method} \${path}: \${res.status} \${await res.text()}\`)
43
+ return res.json()
44
+ }
45
+
46
+ async function sendTelegram(text) {
47
+ const headers = { 'Content-Type': 'application/json' }
48
+ if (AGENT_READY_SECRET) headers['x-internal-secret'] = AGENT_READY_SECRET
49
+ await fetch(\`\${AGENT_READY_API}/api/send\`, {
50
+ method: 'POST',
51
+ headers,
52
+ body: JSON.stringify({ chat_id: TELEGRAM_CHAT_ID, text }),
53
+ })
54
+ }
55
+
56
+ function parseClaudeJson(msg) {
57
+ if (msg.stop_reason === 'max_tokens') {
58
+ throw new Error('Claude response was cut off. Narrow the task scope in the card description.')
59
+ }
60
+ const raw = msg.content[0].text.trim()
61
+ const json = raw.replace(/^\`\`\`(?:json)?\\s*/i, '').replace(/\\s*\`\`\`$/, '')
62
+ return JSON.parse(json)
63
+ }
64
+
65
+ function getFileTree() {
66
+ return execSync(
67
+ 'find src -type f \\\\( -name "*.tsx" -o -name "*.ts" -o -name "*.css" -o -name "*.js" \\\\) | grep -v ".next" | grep -v "node_modules" | sort',
68
+ { encoding: 'utf8' },
69
+ ).trim()
70
+ }
71
+
72
+ async function getRelevantFiles(card, fileTree) {
73
+ const msg = await anthropic.messages.create({
74
+ model: 'claude-sonnet-4-6',
75
+ max_tokens: 512,
76
+ messages: [{
77
+ role: 'user',
78
+ content: \`You are a coding agent. Given a task and file tree, identify which files to read.
79
+
80
+ Task: \${card.name}
81
+ Description: \${card.desc || 'No description.'}
82
+
83
+ File tree:
84
+ \${fileTree}
85
+
86
+ Return ONLY a JSON array of file paths, max 6 files.
87
+ Example: ["src/app/page.tsx", "src/components/Hero.tsx"]\`,
88
+ }],
89
+ })
90
+ return parseClaudeJson(msg)
91
+ }
92
+
93
+ async function makeChanges(card, fileContents) {
94
+ const filesText = Object.entries(fileContents)
95
+ .map(([path, content]) => \`### \${path}\\n\\\`\\\`\\\`\\n\${content}\\n\\\`\\\`\\\`\`)
96
+ .join('\\n\\n')
97
+
98
+ const msg = await anthropic.messages.create({
99
+ model: 'claude-sonnet-4-6',
100
+ max_tokens: 16000,
101
+ messages: [{
102
+ role: 'user',
103
+ content: \`You are a coding agent.
104
+
105
+ Task: \${card.name}
106
+ Description: \${card.desc || 'No description.'}
107
+
108
+ File contents:
109
+ \${filesText}
110
+
111
+ Return ONLY valid JSON — no markdown, no explanation:
112
+ {
113
+ "changes": [{"path": "src/...", "content": "...complete file content..."}],
114
+ "summary": "One sentence describing the change",
115
+ "branchName": "lowercase-hyphen-slug-max-40-chars"
116
+ }
117
+
118
+ Rules:
119
+ - Only include files you actually modified
120
+ - Return complete file content, not a diff
121
+ - branchName: lowercase, hyphens only, max 40 chars\`,
122
+ }],
123
+ })
124
+ return parseClaudeJson(msg)
125
+ }
126
+
127
+ async function createPR(branch, card, summary) {
128
+ const [owner, repo] = GITHUB_REPOSITORY.split('/')
129
+ const res = await fetch(\`https://api.github.com/repos/\${owner}/\${repo}/pulls\`, {
130
+ method: 'POST',
131
+ headers: {
132
+ Authorization: \`Bearer \${GITHUB_TOKEN}\`,
133
+ 'Content-Type': 'application/json',
134
+ 'X-GitHub-Api-Version': '2022-11-28',
135
+ },
136
+ body: JSON.stringify({
137
+ title: \`agent: \${card.name}\`,
138
+ body: \`## Summary\\n\${summary}\\n\\n## Trello Card\\n\${card.shortUrl}\\n\\n<!-- trello-card-id: \${card.id} -->\`,
139
+ head: branch,
140
+ base: 'main',
141
+ }),
142
+ })
143
+ if (!res.ok) throw new Error(\`GitHub PR: \${res.status} \${await res.text()}\`)
144
+ return (await res.json()).html_url
145
+ }
146
+
147
+ async function main() {
148
+ console.log('Fetching Trello cards...')
149
+ const allCards = await trello(\`/lists/\${TRELLO_LIST_ID}/cards\`)
150
+ const cards = allCards.filter(c => c.idLabels.includes(TRELLO_AGENT_LABEL_ID))
151
+
152
+ console.log(\`\${allCards.length} total, \${cards.length} agent-ready.\`)
153
+
154
+ if (cards.length === 0) {
155
+ await sendTelegram('agent-ready: No agent-ready tasks found. All clear.')
156
+ return
157
+ }
158
+
159
+ const card = cards[0]
160
+ console.log(\`Processing: "\${card.name}"\`)
161
+
162
+ await trello(\`/cards/\${card.id}?idList=\${TRELLO_DOING_LIST_ID}\`, 'PUT')
163
+ await sendTelegram(\`agent-ready: Starting task\\n\${card.name}\\n\\nWorking on it...\`)
164
+
165
+ execSync('git config user.name "agent-ready"')
166
+ execSync('git config user.email "agent@agent-ready.dev"')
167
+ execSync(\`git remote set-url origin https://x-access-token:\${GITHUB_TOKEN}@github.com/\${GITHUB_REPOSITORY}.git\`)
168
+
169
+ const fileTree = getFileTree()
170
+
171
+ console.log('Identifying relevant files...')
172
+ const filePaths = await getRelevantFiles(card, fileTree)
173
+ console.log('Files:', filePaths)
174
+
175
+ const fileContents = {}
176
+ for (const filePath of filePaths) {
177
+ try {
178
+ const content = readFileSync(filePath, 'utf8')
179
+ const lines = content.split('\\n')
180
+ fileContents[filePath] = lines.length > 300
181
+ ? lines.slice(0, 300).join('\\n') + '\\n// ... truncated ...'
182
+ : content
183
+ } catch {
184
+ console.warn(\`Could not read \${filePath}\`)
185
+ }
186
+ }
187
+
188
+ console.log('Generating changes...')
189
+ const { changes, summary, branchName } = await makeChanges(card, fileContents)
190
+ console.log('Summary:', summary)
191
+
192
+ const branch = \`agent/\${card.id}_\${branchName}\`
193
+ execSync(\`git checkout -b \${branch}\`)
194
+
195
+ for (const { path, content } of changes) {
196
+ mkdirSync(dirname(path), { recursive: true })
197
+ writeFileSync(path, content, 'utf8')
198
+ console.log('Written:', path)
199
+ }
200
+
201
+ execSync('git add -A')
202
+ execSync(\`git commit -m "agent: \${summary}"\`)
203
+ execSync(\`git push origin \${branch}\`)
204
+
205
+ const prUrl = await createPR(branch, card, summary)
206
+ console.log('PR:', prUrl)
207
+
208
+ await sendTelegram(
209
+ \`agent-ready: Task complete\\n\\n\` +
210
+ \`Task: \${card.name}\\n\` +
211
+ \`Done: \${summary}\\n\` +
212
+ \`PR: \${prUrl}\\n\\n\` +
213
+ \`Review and merge when ready.\`
214
+ )
215
+
216
+ console.log('Done.')
217
+ }
218
+
219
+ main().catch(async err => {
220
+ console.error('Fatal:', err.message)
221
+ await sendTelegram(\`agent-ready: Error\\n\${err.message}\`).catch(() => {})
222
+ process.exit(1)
223
+ })
224
+ `
225
+ }
@@ -0,0 +1,38 @@
1
+ export function doneWorkflowTemplate() {
2
+ return `name: agent-ready — mark done
3
+
4
+ on:
5
+ pull_request:
6
+ types: [closed]
7
+
8
+ jobs:
9
+ mark-done:
10
+ if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'agent/')
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Move Trello card to Done and notify Telegram
15
+ run: |
16
+ CARD_ID=$(echo "$PR_BODY" | grep -o 'trello-card-id: [a-f0-9]*' | awk '{print $2}')
17
+
18
+ if [ -z "$CARD_ID" ]; then
19
+ echo "No card ID found. Skipping."
20
+ exit 0
21
+ fi
22
+
23
+ curl -s -X PUT \\
24
+ "https://api.trello.com/1/cards/\${CARD_ID}?idList=\${TRELLO_DONE_LIST_ID}&key=\${TRELLO_API_KEY}&token=\${TRELLO_TOKEN}"
25
+
26
+ MESSAGE="Merged: \${PR_TITLE}%0ACard moved to Done."
27
+ curl -s -X POST "https://telegram-bot-ruby-tau.vercel.app/api/send" \\
28
+ -H "Content-Type: application/json" \\
29
+ -d "{\\"chat_id\\":\\"\${TELEGRAM_CHAT_ID}\\",\\"text\\":\\"\${MESSAGE}\\"}"
30
+ env:
31
+ PR_BODY: \${{ github.event.pull_request.body }}
32
+ PR_TITLE: \${{ github.event.pull_request.title }}
33
+ TRELLO_API_KEY: \${{ secrets.TRELLO_API_KEY }}
34
+ TRELLO_TOKEN: \${{ secrets.TRELLO_TOKEN }}
35
+ TRELLO_DONE_LIST_ID: \${{ secrets.TRELLO_DONE_LIST_ID }}
36
+ TELEGRAM_CHAT_ID: \${{ secrets.TELEGRAM_CHAT_ID }}
37
+ `
38
+ }
@@ -0,0 +1,42 @@
1
+ export function workflowTemplate() {
2
+ return `name: agent-ready
3
+
4
+ on:
5
+ schedule:
6
+ - cron: '0 9 * * *'
7
+ - cron: '0 17 * * *'
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: write
12
+ pull-requests: write
13
+
14
+ jobs:
15
+ run-agent:
16
+ runs-on: ubuntu-latest
17
+ timeout-minutes: 15
18
+
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+
22
+ - uses: actions/setup-node@v4
23
+ with:
24
+ node-version: '20'
25
+
26
+ - name: Install agent dependencies
27
+ run: npm install --prefix .github/scripts @anthropic-ai/sdk
28
+
29
+ - name: Run agent
30
+ run: node .github/scripts/agent-ready.mjs
31
+ env:
32
+ TRELLO_API_KEY: \${{ secrets.TRELLO_API_KEY }}
33
+ TRELLO_TOKEN: \${{ secrets.TRELLO_TOKEN }}
34
+ TRELLO_LIST_ID: \${{ secrets.TRELLO_LIST_ID }}
35
+ TRELLO_DOING_LIST_ID: \${{ secrets.TRELLO_DOING_LIST_ID }}
36
+ TRELLO_AGENT_LABEL_ID: \${{ secrets.TRELLO_AGENT_LABEL_ID }}
37
+ ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
38
+ TELEGRAM_CHAT_ID: \${{ secrets.TELEGRAM_CHAT_ID }}
39
+ AGENT_READY_SECRET: \${{ secrets.AGENT_READY_SECRET }}
40
+ GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
41
+ `
42
+ }
@@ -0,0 +1,38 @@
1
+ const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN
2
+ const INTERNAL_SECRET = process.env.INTERNAL_SECRET
3
+
4
+ export default async function handler(req, res) {
5
+ if (req.method !== 'POST') {
6
+ return res.status(405).json({ error: 'Method not allowed' })
7
+ }
8
+
9
+ const authHeader = req.headers['x-internal-secret']
10
+ if (INTERNAL_SECRET && authHeader !== INTERNAL_SECRET) {
11
+ return res.status(401).json({ error: 'Unauthorized' })
12
+ }
13
+
14
+ let body
15
+ try {
16
+ body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body
17
+ } catch {
18
+ return res.status(400).json({ error: 'Invalid JSON' })
19
+ }
20
+
21
+ const { chat_id, text } = body
22
+ if (!chat_id || !text) {
23
+ return res.status(400).json({ error: 'chat_id and text required' })
24
+ }
25
+
26
+ const tgRes = await fetch(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, {
27
+ method: 'POST',
28
+ headers: { 'Content-Type': 'application/json' },
29
+ body: JSON.stringify({ chat_id, text, parse_mode: 'HTML' }),
30
+ })
31
+
32
+ const tgData = await tgRes.json()
33
+ if (!tgRes.ok) {
34
+ return res.status(502).json({ error: 'Telegram error', detail: tgData })
35
+ }
36
+
37
+ return res.status(200).json({ ok: true })
38
+ }
@@ -0,0 +1,43 @@
1
+ const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN
2
+
3
+ function tg(method, body) {
4
+ return fetch(`https://api.telegram.org/bot${BOT_TOKEN}/${method}`, {
5
+ method: 'POST',
6
+ headers: { 'Content-Type': 'application/json' },
7
+ body: JSON.stringify(body),
8
+ })
9
+ }
10
+
11
+ export default async function handler(req, res) {
12
+ if (req.method !== 'POST') {
13
+ return res.status(405).json({ error: 'Method not allowed' })
14
+ }
15
+
16
+ let body
17
+ try {
18
+ body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body
19
+ } catch {
20
+ return res.status(400).json({ error: 'Invalid JSON' })
21
+ }
22
+
23
+ const message = body?.message
24
+ if (!message) return res.status(200).json({ ok: true })
25
+
26
+ const chatId = message.chat.id
27
+ const text = message.text?.trim()
28
+
29
+ if (text === '/start') {
30
+ await tg('sendMessage', {
31
+ chat_id: chatId,
32
+ parse_mode: 'HTML',
33
+ text:
34
+ `👋 <b>Welcome to agent-ready!</b>\n\n` +
35
+ `Your Telegram Chat ID is:\n\n` +
36
+ `<code>${chatId}</code>\n\n` +
37
+ `Copy this and paste it when running <code>npx agent-ready init</code>.\n\n` +
38
+ `You'll receive notifications here whenever your coding agent finishes a task or opens a PR.`,
39
+ })
40
+ }
41
+
42
+ return res.status(200).json({ ok: true })
43
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "agent-ready-telegram-bot",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vercel dev",
8
+ "deploy": "vercel --prod"
9
+ },
10
+ "dependencies": {
11
+ "itty-router": "^5.0.18"
12
+ }
13
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "functions": {
3
+ "api/*.js": {
4
+ "maxDuration": 10
5
+ }
6
+ },
7
+ "headers": [
8
+ {
9
+ "source": "/api/(.*)",
10
+ "headers": [
11
+ { "key": "Cache-Control", "value": "no-store" }
12
+ ]
13
+ }
14
+ ]
15
+ }