repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | #!/usr/bin/env node |
| #2 | /** |
| #3 | * 自动提交命令 |
| #4 | * 用于自动处理 GitHub Issue 并创建 PR |
| #5 | */ |
| #6 | import { Octokit } from '@octokit/rest'; |
| #7 | import 'dotenv/config'; |
| #8 | import { kebabCase } from 'lodash-es'; |
| #9 | import { execSync } from 'node:child_process'; |
| #10 | import { existsSync } from 'node:fs'; |
| #11 | import { resolve } from 'node:path'; |
| #12 | import pMap from 'p-map'; |
| #13 | |
| #14 | import { agentsDir, config, githubHomepage } from '../core/constants'; |
| #15 | import { checkHeader } from '../utils/common'; |
| #16 | import { getBuildLocaleAgentFileName, writeJSON } from '../utils/file'; |
| #17 | import { Logger } from '../utils/logger'; |
| #18 | import { CheckEnglishIdentifier, formatAgentJSON } from '../validators/agent-validator'; |
| #19 | |
| #20 | // GitHub 标签常量 |
| #21 | const GENERATE_LABEL = '🤖 Agent PR'; |
| #22 | const SUCCESS_LABEL = '✅ Auto Check Pass'; |
| #23 | const ERROR_LABEL = '🚨 Auto Check Fail'; |
| #24 | |
| #25 | /** |
| #26 | * 自动提交处理器类 |
| #27 | * 负责处理 GitHub Issue 并自动创建 PR |
| #28 | */ |
| #29 | class AutoSubmit { |
| #30 | owner = 'clawdos'; |
| #31 | repo = 'clawd-ai-agents'; |
| #32 | issueNumber = Number(process.env.ISSUE_NUMBER); |
| #33 | private octokit: Octokit; |
| #34 | |
| #35 | constructor() { |
| #36 | this.octokit = new Octokit({ auth: `token ${process.env.GH_TOKEN}` }); |
| #37 | } |
| #38 | |
| #39 | /** |
| #40 | * 执行自动提交流程 |
| #41 | */ |
| #42 | async run() { |
| #43 | try { |
| #44 | await this.submit(); |
| #45 | } catch (error) { |
| #46 | await this.removeLabels(GENERATE_LABEL); |
| #47 | await this.removeLabels(SUCCESS_LABEL); |
| #48 | await this.addLabels(ERROR_LABEL); |
| #49 | await this.createComment( |
| #50 | [ |
| #51 | '**🚨 自动检查失败:**', |
| #52 | '- 修复下方错误', |
| #53 | `- 为当前 Issue 添加标签 \`${GENERATE_LABEL}\``, |
| #54 | '- 等待自动化重新生成', |
| #55 | '```bash', |
| #56 | error?.message, |
| #57 | '```', |
| #58 | ].join('\n'), |
| #59 | ); |
| #60 | Logger.error('自动提交流程失败', error); |
| #61 | } |
| #62 | } |
| #63 | |
| #64 | /** |
| #65 | * 提交处理逻辑 |
| #66 | */ |
| #67 | async submit() { |
| #68 | const issue = await this.getIssue(); |
| #69 | if (!issue) return; |
| #70 | Logger.start('获取 Issue 信息', `#${this.issueNumber}`); |
| #71 | |
| #72 | const { agent, locale } = await this.formatIssue(issue); |
| #73 | |
| #74 | // 验证标识符 |
| #75 | if ( |
| #76 | !agent.identifier || |
| #77 | agent.identifier === 'undefined' || |
| #78 | agent.identifier === 'index' || |
| #79 | !CheckEnglishIdentifier(agent.identifier) |
| #80 | ) { |
| #81 | await this.createComment( |
| #82 | [ |
| #83 | `**🚨 自动检查失败:** 标识符无效`, |
| #84 | '- 重命名你的 Agent 标识符', |
| #85 | `- 为当前 Issue 添加标签 \`${GENERATE_LABEL}\``, |
| #86 | '- 等待自动化重新生成', |
| #87 | '---', |
| #88 | agent.identifier, |
| #89 | ].join('\n'), |
| #90 | ); |
| #91 | await this.removeLabels(GENERATE_LABEL); |
| #92 | await this.addLabels(ERROR_LABEL); |
| #93 | Logger.error('自动检查失败', '标识符无效'); |
| #94 | return; |
| #95 | } |
| #96 | |
| #97 | const comment = this.genCommentMessage(agent); |
| #98 | const agentName = agent.identifier; |
| #99 | const fileName = getBuildLocaleAgentFileName(agentName, locale); |
| #100 | const filePath = resolve(agentsDir, fileName); |
| #101 | |
| #102 | // 检查同名文件 |
| #103 | if (existsSync(filePath)) { |
| #104 | await this.createComment( |
| #105 | [ |
| #106 | `**🚨 自动检查失败:** 同名文件已存在 <${`${githubHomepage}/blob/main/agents/${fileName}`}>`, |
| #107 | '- 重命名你的 Agent 标识符', |
| #108 | `- 为当前 Issue 添加标签 \`${GENERATE_LABEL}\``, |
| #109 | '- 等待自动化重新生成', |
| #110 | '---', |
| #111 | comment, |
| #112 | ].join('\n'), |
| #113 | ); |
| #114 | await this.removeLabels(GENERATE_LABEL); |
| #115 | await this.addLabels(ERROR_LABEL); |
| #116 | Logger.error('自动检查失败', '同名文件已存在'); |
| #117 | return; |
| #118 | } |
| #119 | |
| #120 | // 在 Issue 中添加评论 |
| #121 | await this.createComment(comment); |
| #122 | Logger.success('自动检查通过'); |
| #123 | |
| #124 | // 提交并创建 PR |
| #125 | await this.gitCommit(filePath, agent, agentName); |
| #126 | Logger.start('提交到分支', `agent/${agentName}`); |
| #127 | |
| #128 | await this.addLabels(SUCCESS_LABEL); |
| #129 | } |
| #130 | |
| #131 | /** |
| #132 | * Git 提交流程 |
| #133 | */ |
| #134 | async gitCommit(filePath: string, agent: any, agentName: string) { |
| #135 | execSync('git diff'); |
| #136 | execSync('git config --global user.name "clawdos"'); |
| #137 | execSync('git config --global user.email "clawdos@users.noreply.github.com"'); |
| #138 | execSync('git pull'); |
| #139 | execSync(`git checkout -b agent/${agentName}`); |
| #140 | Logger.info('切换分支', `agent/${agentName}`); |
| #141 | |
| #142 | // 生成文件 |
| #143 | writeJSON(filePath, agent); |
| #144 | Logger.file('create', filePath); |
| #145 | |
| #146 | // 代码格式化 |
| #147 | execSync(`echo "module.exports = require('@clawdos/lint').prettier;" >> .prettierrc.cjs`); |
| #148 | execSync('bun run prettier'); |
| #149 | Logger.info('代码格式化完成'); |
| #150 | |
| #151 | // 提交代码 |
| #152 | execSync('git add -A'); |
| #153 | execSync(`git commit -m "🤖 chore(auto-submit): Add ${agentName} (#${this.issueNumber})"`); |
| #154 | execSync(`git push origin agent/${agentName}`); |
| #155 | Logger.success('推送 Agent 完成'); |
| #156 | |
| #157 | // 创建 PR |
| #158 | const comment = this.genCommentMessage(agent); |
| #159 | await this.createPullRequest( |
| #160 | agentName, |
| #161 | agent.author, |
| #162 | [comment, `[@${agent.author}](${agent.homepage}) (resolve #${this.issueNumber})`].join('\n'), |
| #163 | ); |
| #164 | Logger.success('创建 PR 完成'); |
| #165 | } |
| #166 | |
| #167 | /** |
| #168 | * 生成评论消息 |
| #169 | */ |
| #170 | genCommentMessage(json: any) { |
| #171 | return ['🤖 自动生成的 Agent 配置文件', '```json', JSON.stringify(json, null, 2), '```'].join( |
| #172 | '\n', |
| #173 | ); |
| #174 | } |
| #175 | |
| #176 | /** |
| #177 | * 创建 Pull Request |
| #178 | */ |
| #179 | async createPullRequest(agentName: string, author: string, body: string) { |
| #180 | const { owner, repo, octokit } = this; |
| #181 | await octokit.pulls.create({ |
| #182 | base: 'main', |
| #183 | body, |
| #184 | head: `agent/${agentName}`, |
| #185 | owner: owner, |
| #186 | repo: repo, |
| #187 | title: `✨ feat(agent): ${agentName} @${author}`, |
| #188 | }); |
| #189 | } |
| #190 | |
| #191 | /** |
| #192 | * 获取 Issue 信息 |
| #193 | */ |
| #194 | async getIssue() { |
| #195 | const { owner, repo, octokit, issueNumber } = this; |
| #196 | const issue = await octokit.issues.get({ |
| #197 | issue_number: issueNumber, |
| #198 | owner, |
| #199 | repo, |
| #200 | }); |
| #201 | return issue.data; |
| #202 | } |
| #203 | |
| #204 | /** |
| #205 | * 添加标签 |
| #206 | */ |
| #207 | async addLabels(label: string) { |
| #208 | const { owner, repo, octokit, issueNumber } = this; |
| #209 | await octokit.issues.addLabels({ |
| #210 | issue_number: issueNumber, |
| #211 | labels: [label], |
| #212 | owner, |
| #213 | repo, |
| #214 | }); |
| #215 | } |
| #216 | |
| #217 | /** |
| #218 | * 移除标签 |
| #219 | */ |
| #220 | async removeLabels(label: string) { |
| #221 | const { owner, repo, octokit, issueNumber } = this; |
| #222 | const issue = await this.getIssue(); |
| #223 | |
| #224 | const baseLabels = issue.labels.map((l) => (typeof l === 'string' ? l : l.name)); |
| #225 | const removeLabels = baseLabels.filter((name) => name === label); |
| #226 | |
| #227 | // 并行移除所有匹配的标签 |
| #228 | await pMap( |
| #229 | removeLabels, |
| #230 | async (labelToRemove) => { |
| #231 | await octokit.issues.removeLabel({ |
| #232 | issue_number: issueNumber, |
| #233 | name: labelToRemove, |
| #234 | owner, |
| #235 | repo, |
| #236 | }); |
| #237 | }, |
| #238 | { concurrency: config.concurrency }, // 使用配置中的并发数控制 |
| #239 | ); |
| #240 | } |
| #241 | |
| #242 | /** |
| #243 | * 创建评论 |
| #244 | */ |
| #245 | async createComment(body: string) { |
| #246 | const { owner, repo, octokit, issueNumber } = this; |
| #247 | await octokit.issues.createComment({ |
| #248 | body, |
| #249 | issue_number: issueNumber, |
| #250 | owner, |
| #251 | repo, |
| #252 | }); |
| #253 | } |
| #254 | |
| #255 | /** |
| #256 | * Markdown 转 JSON |
| #257 | */ |
| #258 | markdownToJson(markdown: string) { |
| #259 | const lines = markdown.split('\n'); |
| #260 | const json: any = {}; |
| #261 | let currentKey = ''; |
| #262 | let currentValue = ''; |
| #263 | |
| #264 | for (const line of lines) { |
| #265 | if (checkHeader(line)) { |
| #266 | if (currentKey) { |
| #267 | json[currentKey] = currentValue.trim(); |
| #268 | } |
| #269 | currentKey = line.replace('### ', '').trim(); |
| #270 | currentValue = ''; |
| #271 | } else { |
| #272 | currentValue += line + '\n'; |
| #273 | } |
| #274 | } |
| #275 | |
| #276 | if (currentKey) { |
| #277 | json[currentKey] = currentValue.trim(); |
| #278 | } |
| #279 | |
| #280 | return json; |
| #281 | } |
| #282 | |
| #283 | /** |
| #284 | * 格式化 Issue 内容 |
| #285 | */ |
| #286 | async formatIssue(data: any) { |
| #287 | const json = this.markdownToJson(data.body); |
| #288 | const locale = json.locale || 'en-US'; |
| #289 | |
| #290 | const agent = await formatAgentJSON( |
| #291 | { |
| #292 | author: data.user.login, |
| #293 | config: { |
| #294 | systemRole: json.systemRole, |
| #295 | }, |
| #296 | createdAt: new Date().toISOString().split('T')[0], |
| #297 | homepage: data.user.html_url, |
| #298 | identifier: kebabCase(json.identifier), |
| #299 | meta: { |
| #300 | avatar: json.avatar, |
| #301 | description: json.description, |
| #302 | tags: json.tags?.split(',').map((tag: string) => tag.trim()) || [], |
| #303 | title: json.title, |
| #304 | }, |
| #305 | schemaVersion: 1, |
| #306 | }, |
| #307 | locale, |
| #308 | ); |
| #309 | |
| #310 | return { agent, locale }; |
| #311 | } |
| #312 | } |
| #313 | |
| #314 | // 执行自动提交 |
| #315 | await new AutoSubmit().run(); |
| #316 | |
| #317 | |
| #318 |