mcpField
K
kicksmash

kicksmash

远程服务
kicksma.sh游戏娱乐0今天更新

关于 kicksmash

kicksmash 是开源、移动优先的网页应用,通过可分享链接组织板式网球比赛,无需安装应用或创建账户。它支持报名、赛程、球员等级及由组织者管理的比赛流程,供个人和 Agent 使用。

主要功能

  1. 01创建并分享比赛链接
  2. 02加入比赛并管理名单
  3. 03生成 americano 赛程
  4. 04设置球员等级和范围
  5. 05推荐均衡分队

适用场景

  1. 01在群聊中组织板式网球比赛
  2. 02协调报名和候补名单
  3. 03安排 americano 锦标赛
  4. 04按等级分组参赛者

MCP 相关

在 /mcp 提供 MCP 服务,并提供 REST API 供助手使用。项目还发布 OpenAPI 规范,并支持通过 URL 配置 Agent。

配置

远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。

服务地址https://kicksma.sh/mcp
一键复制安装命令
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。

工具

15

此工具无需参数。

详细介绍

Kicksmash — 通过一个链接约战板式网球

CI 许可证:Apache-2.0

一款移动端优先的板式网球约赛 Web 应用,无需安装应用、无需账号、无需密码。创建比赛,通过 WhatsApp 或 Telegram 分享 kicksma.sh/{code},朋友点击链接 → 输入一次姓名 → 即可加入。

  • 技术栈: Next.js 15(App Router、TypeScript)· Supabase Postgres + Drizzle · Resend · next-intl(EN/RU/ES)· Tailwind v4 · Vercel(Cron + OG images)。
  • 开源,采用 Apache License 2.0。运行自己的副本,在此基础上构建,提交 PR:参见 CONTRIBUTING.md 和 SECURITY.md。
  • 身份: 一次性输入姓名 → 将玩家 UUID 存入签名的 httpOnly cookie(有效期 1 年),并将恢复密钥存入 localStorage(服务器会为该玩家及此用途签名;cookie 丢失时可通过密钥恢复,绝不会仅凭玩家 ID 恢复)。跨设备使用:每位玩家都有一个私密的个人链接(/p/{token},显示在「我的比赛」、每封邮件和日历邀请中),可让任何设备登录;曾经使用过的邮箱可通过 6 位代码恢复历史记录,并合并所有共用该邮箱的身份。主屏幕快捷方式会打开个人链接,日历条目和邮件中会附带私密赛事链接(/p/{token}/{code}:让设备登录并打开比赛)。新添加的邮箱会收到个人链接(参加比赛时附在日历邀请中,否则单独发送)。令牌为 12 个字符;旧的 32 字符令牌在延迟缩短后仍会作为 previous_token 继续有效。邮箱设置后可以更改,但不能清空;旧地址会保留为 recovery_email,因此发送到任一地址的恢复代码都能让玩家恢复访问。「我的比赛」中的「把此链接发到我的邮箱」会将个人链接发送到邮箱(也可使用系统分享、复制和二维码)。Passkeys(DECIDING 规则 39):加入比赛后,「在这部手机上记住我」会将 passkey 保存到手机的密码管理器;此后,在这部手机的任何浏览器中点按「使用 Face ID 或指纹登录」即可登录。该选项首先显示在比赛页面加入表单姓名栏下方展开的「以前参加过?」区域中。通过姓名建立的会话绝不会保存 passkey;登录时只会合并此浏览器创建的、姓名相同的全新空记录。Kicksmash 会保存公钥、标签和两个日期(passkeys);「我的比赛」中会列出「记住你的手机」,并提供「移除」选项。
  • 通知、安静时段和收件箱: 发给玩家的所有通知都经过同一关口(src/lib/domain/notices.ts):首先进入「我的比赛」中的收件箱(保留九十天,最新的排在前面,使用玩家的语言),只有在该类通知已开启时(共七类,除俱乐部的新比赛通知外均默认开启),且不处于玩家的安静时段内才会通过相应渠道发送;但若比赛将在三小时内开始,则不受安静时段限制。安静时段内暂缓的通知,会在安静时段结束后由每小时运行的 cron 按渠道各发送一条简短消息。
  • 推送提醒: 每场比赛开始前一小时,通过 Web Push(VAPID)向玩家启用推送的每台设备发送提醒(iPhone:需从主屏幕应用启用)。Supabase pg_cron + pg_net 每 5 分钟调用一次 /api/cron/push(Vercel Hobby 的 cron 每天只运行一次)。设置 VAPID_PUBLIC_KEY、VAPID_PRIVATE_KEY、VAPID_SUBJECT。
  • 链接: /{code}(4 个字符,公开)· /{code}/i/{6}(个人邀请)· /{code}/manage/{10}(组织者密钥)。
  • 面向 Agent,开放使用。 提供公开 REST API(/api/openapi.json 中的 OpenAPI 3.1)、位于 /mcp 的 MCP 服务,任何助手都可通过 URL 添加该服务、即时自助获取密钥、签名 Webhook、llms.txt、/.well-known/mcp.json、欢迎 AI 爬虫的 robots.txt、AGENTS.md、可安装的技能(npx skills add evhg/padel-matchup),以及按群组和场馆提供的日历订阅。公开数据采用 CC BY 4.0 许可。参见 /developers 和 /agents。
  • 所有功能均可选用邮箱。 未设置 RESEND_API_KEY 时,应用仍可完整运行,只是隐藏邮箱功能。
  • 隐私,简要说明与完整说明: /about 用几行文字说明存储的内容、绝不会做的事,以及如何离开;/privacy(页脚中不太显眼的唯一链接)和 /terms 则完整说明相关内容、Cookie、数据处理方,以及代码实际执行的各项限制(src/lib/legal.ts)。每封由组织者发起的邮件(邀请、邀请提醒)都带有经过签名的一键退订链接 /unsubscribe;玩家再次添加自己的邮箱后,退订状态会解除。「删除我的账户」位于「我的比赛」底部,会清除个人数据(姓名、邮箱、电话、个人链接、关联的 Telegram、Discord 和 LINE 账户、公开页面和 passkey),释放即将开始的比赛席位,取消玩家自己即将开始的比赛;若玩家是教练,还会取消即将开始的课程(通知每位学员,并注明教练的真实姓名),并删除教练页面及其所有内容;旧比分仍会显示为「已删除的玩家」。若教练只想删除页面而保留账户,可在设置底部选择「删除我的教练页面」;如果还有未开始的课程,该操作将被拒绝。
  • 等级: 每位玩家都可声明自己的 padel 等级(0–7,按 0.25 递增,采用 padel 应用常用的等级体系),可在「我的比赛」中设置,也可在比赛首次要求填写等级范围时设置。等级会以小标签显示在所有姓名旁边(名单、积分榜、组队选择器)。比赛结果会相应调整等级:组织者确认 2v2 比分或完成锦标赛后,系统会应用一次类似 Elo 的小幅变动(每场比赛最多 ±0.10,每场锦标赛最多 ±0.12),并记录变动(「3.25 → 3.30,赛后」)。没有等级的玩家不会升降级,也不参与计算。组织者可为每场比赛或锦标赛设置等级范围:预设范围包括 Bronze 1.0–2.5、Silver 2.5–3.5、Gold 3.0–4.5、Platinum 4.5+,也可自定义最低和最高等级。范围内的玩家可照常加入;范围外的玩家可申请加入,由组织者批准(安排入场;若已满员,则加入候补名单)或拒绝,处理结果会显示在加入栏和动态列表中。预留或受邀的玩家,以及组织者,不受等级范围限制。若四名玩家都有等级,比分面板会推荐均衡队伍(等级差最小的分队)。「我的比赛」会新增统计栏:已参加、已获胜、胜率和领奖台次数。
  • 滥用限制(除非另有说明,按 UTC 日计算;对普通用户较宽松,对脚本较严格):每个 IP 每天最多创建 40 个新身份,每位玩家每天最多创建 20 场比赛,每位组织者每天最多发送 40 份邀请,每位玩家每小时最多加入 30 次,每天最多更改 10 次邮箱、发送 5 封个人链接邮件;每个 IP 每天最多请求 20 个恢复代码、提交 60 份浏览器崩溃报告。计数器存放于 metrics_daily,无需额外基础设施。触发限制时会返回「请求过多」,且不会执行其他操作。

快速开始(零配置,约 2 分钟)

pnpm install
pnpm dev

就这样。未设置 DATABASE_URL 时,应用会在 ./.pglite 中启动嵌入式 PGlite 数据库,应用迁移并播种两个示例赛事:

URL内容
http://localhost:3000/PLAY即将开始的比赛:2 人已加入,1 个邀请名额已预留,1 个空位
http://localhost:3000/PAST已结束的比赛,比分为经组织者确认的 3 盘制比分
http://localhost:3000/new创建自己的比赛

将 .env.example 复制为 .env 即可修改任何设置。所有流程(加入、候补名单、邀请、比分、“我的比赛”、OG 预览)都能端到端运行,无需任何密钥。

pnpm test        # vitest: slot-claim concurrency, invite transitions, score-lock rules, reminders, identity, americano rotation, rate limits, opt-outs, account deletion
pnpm typecheck
pnpm lint
pnpm build
pnpm e2e         # the sixteen Playwright journeys against a fresh production build (`node scripts/suites.mjs --why` says which a change can break); first time: pnpm exec playwright install chromium

pnpm e2e 会在端口 3001 上启动 next start,使用临时 PGlite 数据库、虚拟 Resend 密钥(启用邮件 UI,发送会安全失败)和生成的 VAPID 密钥,然后运行所有 e2e/*.mjs 测试套件。设置 SHOTS=./shots 可保存完整页面截图;设置 PW_CHROMIUM=/path/to/chromium 可使用预安装的浏览器。每次推送和拉取请求都会触发 GitHub Actions,运行类型检查、lint、在 PGlite 和真实 Postgres 服务上运行 vitest、构建以及 e2e 测试套件(.github/workflows/ci.yml)。

默认情况下,测试在内存中的 PGlite 上运行。要在真实 Postgres 上运行测试(实现真正的并发),请将 TEST_DATABASE_URL 指向一个一次性数据库——每个测试文件运行前都会删除其 public 架构:

TEST_DATABASE_URL=postgres://postgres:postgres@localhost:5432/padel_test pnpm test

环境变量

生产环境只需要一个变量:数据库 URL。其他所有变量都有安全的默认值。

变量必填用途
DATABASE_URL✅Supabase Transaction pooler 连接字符串(端口 6543),必须与 Supabase 的 Connect 对话框中显示的内容完全一致。POSTGRES_URL(Vercel ⇄ Supabase 集成)和 SUPABASE_DB_URL 也可用。为空时使用内嵌的 PGlite 数据库,仅用于本地开发。
DATABASE_PASSWORD如果 URL 中仍显示 [YOUR-PASSWORD]自动替换到 URL 中并进行百分号编码。
DIRECT_DATABASE_URL否用于 pnpm db:migrate 和 pnpm db:generate 的直连 URL(端口 5432)。POSTGRES_URL_NON_POOLING 也可用。
AUTO_MIGRATE否设为 false 可阻止应用在首次连接时应用迁移。此安全网仅适用于全新数据库:生产环境中的每项迁移都由 Migrate 工作流执行(AGENTS.md 规则 7)。
APP_BASE_URL否默认为 Vercel 生产域名。在本地和其他托管平台上设置此变量。NEXT_PUBLIC_APP_BASE_URL 是该值供浏览器使用的副本。
SESSION_SECRET建议设置用于签署身份 Cookie。未设置时,会根据数据库 URL 派生一个稳定的密钥。
CRON_SECRET建议设置保护 /api/cron/* 和一次性设置路由。设置后,Vercel 会自动发送此密钥。
RESEND_API_KEY否启用所有邮件:日历邀请、通知和提醒。
EMAIL_FROM否默认为 Kicksmash <matches@<your domain>>;该域名必须在 Resend 中通过验证。
RESEND_WEBHOOK_SECRET否验证 Resend 的入站 Webhook,以便发给 feedback@ 或 claude@ 的回复能成为备注。
OUTREACH_FROM否外联邮件的发件人行。默认为 Claude at Kicksmash <claude@<your domain>>。
VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT否启用推送提醒(npx web-push generate-vapid-keys)。
TELEGRAM_BOT_TOKEN / TELEGRAM_WEBHOOK_SECRET / TELEGRAM_BOT_USERNAME否启用 Telegram 机器人和 Telegram 登录。使用 GET /api/telegram/setup(Bearer CRON_SECRET)注册一次 Webhook。用户名默认为 src/lib/config.ts 中的值;是否存在机器人则由令牌决定。
TELEGRAM_MINIAPP_SLUG否Mini App 的短名称,让卡片能直接链接到它。未设置时,机器人卡片会链接到网页;在应用上线前,这样正合适。
TELEGRAM_OWNER_ID否所有者的 Telegram ID:提案、俱乐部认领和草稿会发送给该用户,以便一键回复;管理页面也仅对此账户开放。
DISCORD_BOT_TOKEN / DISCORD_PUBLIC_KEY否启用 Discord 机器人。使用 GET /api/discord/setup(Bearer CRON_SECRET)注册命令和交互 URL;该接口会返回安装链接。
DISCORD_APPLICATION_ID否未设置时从机器人令牌中读取。
DISCORD_INVITE_URL否在社区页面显示服务器。
WHATSAPP_TOKEN / WHATSAPP_PHONE_ID / WHATSAPP_NUMBER / WHATSAPP_VERIFY_TOKEN / WHATSAPP_APP_SECRET否启用 WhatsApp 对话:一次联系一个人,而不是卡片频道。Meta 的 Groups API 只能创建由企业拥有、通过邀请链接加入的群组。每个群组最多容纳 8 人,需要 Official Business Account,且无法编辑消息。因此机器人永远无法加入团队已有的聊天。机器人创建的群组需等待所有者完成 Meta 操作(ROADMAP.md)。将 Meta 的 Webhook 指向 /api/whatsapp/webhook:GET 请求会使用 WHATSAPP_VERIFY_TOKEN 响应订阅握手;所有 POST 请求都必须使用 WHATSAPP_APP_SECRET 签名,否则会被拒绝。WHATSAPP_NUMBER 是人们发消息的号码,并用于生成组织者可粘贴到自己群组中的 wa.me 链接。
WHATSAPP_TEMPLATES_PER_DAY否WhatsApp 每个 UTC 日可发送的消息模板数量:比赛变更、空位、比分询问和结果卡片,发送给将 WhatsApp 设为联系渠道的球员。默认为 50;设为 0 可关闭,这些通知会改用邮件或推送发送。Meta 会对在 24 小时会话窗口之外送达的每个模板收费,因此此项用于控制成本(docs/OPERATING.md)。
WHATSAPP_WABA_ID否WhatsApp Business Account ID(不是电话号码 ID)。只有 scripts/whatsapp-templates.mjs 会读取此值,用于列出 Meta 保存的模板,并根据 data/whatsapp-templates.json 创建缺失的模板。
LINE_CHANNEL_TOKEN / LINE_CHANNEL_SECRET否启用 LINE 机器人:在泰国团队群聊中发送卡片。将 LINE 的 Webhook 指向 /api/line/webhook;所有请求都必须使用 LINE_CHANNEL_SECRET 签名,否则会被拒绝。LINE 无法编辑已发送的消息,因此只有在球员会注意到内容变化时才会重新发送卡片;只要可能获得回复,就会使用免费回复,而不是按量计费的推送。
PASSPORT_PRIVATE_KEY / PASSPORT_PUBLIC_KEY否用于签署球员护照的 Ed25519 密钥对(每个值均为原始 32 字节十六进制)。未设置时,护照会携带 alg: "none"。
ANTHROPIC_API_KEY否为监听工作台起草回复,并起草所有者收到的备注提案。
ANTHROPIC_WORKSPACE_ID否用于没有专属工作区的密钥:之后每次 Messages API 调用都会将其作为 anthropic-workspace-id 标头发送。可在 Claude Console 的 Settings,然后是 Workspaces 中找到;ID 以 wrkspc_ 开头。如果密钥是在某个工作区内创建的,请留空。既没有工作区,也未在此设置 ID 的密钥会被拒绝,失效密钥也一样(docs/OPERATING.md)。
LISTEN_MODEL否这些草稿使用的模型。默认为 claude-sonnet-5,src/lib/ops/anthropic.ts 也会为其定价。
ANTHROPIC_MONTHLY_CAP_USD否应用为自身设定的美元支出上限。默认为 20。
ANTHROPIC_ADMIN_KEY否Admin API 密钥,使服务面板能读取实际支出,而不是自身的估算值。
SCRAPE_DISABLED否以逗号分隔的平台 ID(playtomic,matchi),或 all:这些平台每 15 分钟读取一次可用球场时段的任务将停止(DECIDING 规则 35)。Vercel 会在下次部署时应用此设置;也可通过 POST /api/admin/metrics 设置 scrape_off_<platform> 行,无需部署(docs/OPERATING.md)。为空时,会读取所有有读取器且在区块结束后未处于休眠状态的平台。
TAVILY_API_KEY否研究工作台:按城市查找俱乐部和教练,并为答案页面提供信息依据。
REDDIT_CLIENT_ID / REDDIT_CLIENT_SECRET / REDDIT_USERNAME / REDDIT_PASSWORD否允许以项目账户在 Reddit 发布获批的回复。未设置时,点击 Approve 就是复制并粘贴。
GOOGLE_SERVICE_ACCOUNT_JSON否可访问 Search Console 的服务账户,让服务面板能够读取展示次数和点击次数。
INDEXNOW_KEY否网页一有变更,就通知 Bing 和 Yandex。
BACKUP_GITHUB_TOKEN / BACKUP_GITHUB_REPO否每晚的数据库快照会提交到该仓库。
UPTIME_REPO否GitHub Actions 可用性探测所在的 owner/repo,让服务面板能读取未解决的事件。
OPERATOR_VERCEL_TEAM / OPERATOR_VERCEL_PROJECT否指定 Vercel 项目,以便报告缺失的密钥时附上设置链接。
生成密钥:openssl rand -base64 32。上方的表格和 .env.example 由 node scripts/gen-docs.mjs 根据代码中的 process.env 读取项生成;如果任一内容落后于代码,tests/docs.test.ts 就会失败,因此代码读取的变量不会出现未记录的情况。随时访问 /api/health 检查部署状态(不会返回密钥)。

生产环境设置

操作指南位于 docs/DEPLOY.md:介绍 Supabase、Vercel、域名、Resend 和 cron 任务,并提供仅使用浏览器的版本和 CLI 版本。本地运行应用无需进行其中任何设置。

npm 软件包

纯引擎作为软件包发布,从 src/lib/domain 生成,以确保只有一个事实来源:

  • @erikv69/americano:使用 buildSchedule({ names | players, courts, rounds, seed }) 生成完整的 americano 赛程,还提供按轮次规划的函数(planRound、planMexicanoRound、planKingRound)、历史记录和积分榜。
  • @erikv69/levels:等级区间、预设、范围,以及 levelFit、balancedTeams、matchDeltas 和 tournamentDeltas。

pnpm packages:build 会重新生成 packages/*/src 和 packages/*/dist(两者均被 git 忽略);每次 CI 运行时,tests/packages.test.ts 都会构建这些内容。发布步骤:更新 packages/<name>/package.json 中的版本号,进行构建,然后在该目录下运行 npm publish。

社区

问题、想法,以及“我基于这个 API 做了个东西”,都可以发到 GitHub Discussions。Bug 请提交到 issue。我们也有一个 Discord 服务(链接位于 /developers),机器人约每小时回答一次问题。Telegram 机器人和 Reddit 账号会在大家所在的平台回答问题;代码和路线图都在这里。

部署自己的实例

Kicksmash 是一个 Next.js 项目和一个 Postgres 数据库,采用 Apache-2.0 许可证。可为你的俱乐部、城市或国家运行: docs/DEPLOY.md 提供 Vercel 按钮、Docker 构建和操作指南。 上方的环境变量表就是完整的配置,而且测试会确保这一点始终成立。

内置的产品规则

  • Match = 恰好 4 名球员。 Tournament = 创建者设定人数上限(4–64,人数为 4 的倍数),采用 americano 赛制:第 1 轮开始时至少有 4 个名字(americano 和 mexicano 轮流让未能上场的人休息,确保每个人都休息过一次之前,不会有人休息两次;King of the Court 仍要求名字为 4 的倍数;已预留但尚未接受邀请的名字也计入人数;接受邀请后,预留的占位球员会与真实球员合并),并关闭仍未填满的名额,因此锦标赛人数恰好等于实际到场人数;组织者当天生成轮次(轮换搭档、公平轮休、1–N 片球场),球场跟随球员设置(每 4 人一片),并可设置真实名称;当参赛人数为 4 的倍数时,赛程精确安排(每一对搭档会在 players−1 轮中搭档一次;下一轮重赛第 1 轮);即使已录入比分,也可以删除最新一轮(需确认);只要轮次已生成,任何参与者都可以录入各片球场的分数(无需等到开始时间);积分榜实时更新;组织者最终确认以锁定赛果。
  • 快速创建: 快捷选择项仅取自组织者自己的历史记录(根据实际进行过的比赛中,常见的星期/时间段,且至少有两个已占席位;若没有,则使用完整历史记录,直到有一个可用项;投射到下一次对应日期;默认日期为最常见的时间段);键盘的 Go 键会移至下一个字段,绝不会创建比赛;场馆可选填(“Court TBD”),球场字段也可选填(球场会显示在页面、分享文本、邮件和日历标题中);首页本身就是表单;结果录入后会出现“再来一场”,并按它上方唯一控件中的日期和时间当天创建一场克隆比赛(默认顺延一周);该场比赛的每位球员只会收到一条“我来吗?”消息,且只发送一次(DECIDING 规则 37);“添加到主屏幕”提示会为组织者提供一个类似应用的快捷方式,指向其个人链接。
  • 粘贴你的预订信息: 在某个平台的应用(Playtomic、MATCHi、The Padel Society 等)中预订好的球场,可以通过粘贴链接或分享文本转为比赛。在首页,“已经预订?粘贴信息”会打开创建表单,并填入俱乐部、日期、时间、时长、球场、价格和球员,同时标明预订信息中缺少的内容或需要核对的内容。只有当预订信息说明价格是按人计算时,才会将价格填入“每位球员的费用”;其他价格都是球场总价,会在一行中说明,绝不会要求每位球员分别支付。在与机器人的私聊中,回复会说明它读取到的内容;如果文本中提供了俱乐部、日期和时间,只需轻点一次“创建比赛”即可创建比赛。Kicksmash 只读取一次粘贴的链接,就像聊天应用的预览机器人读取链接一样(src/lib/booking/unfurl.ts,DECIDING 规则 35)。它绝不会登录平台,也绝不会预订或付款。
  • 谁已付款: 当比赛注明费用时,所有人都会看到同一份姓名列表。球员轻点 “我已付款”;组织者轻点确认已收到款项,也可以撤销确认。这里有两个字段,而不是一个标记—— “我付了”和“对方收到了”是不同的人提出的不同声明,而且只有组织者可以填写 第二项。Kicksmash 不会经手任何款项,也不会上传任何内容;应用会记住双方的说法,因此没人需要在聊天中催款。
  • 满员时: 每场活动可选择候补名单并在有人退出时自动递补(离开/移除/拒绝邀请),或直接关闭。若有空位且无人排队等候该空位(有人退出,或某个位置始终无人认领),且比赛将在 90 分钟至 48 小时后开始,就会向可能接替的人发送一条通知:曾询问该时段和地点的人、群组其他成员、过去 60 天内曾与该场比赛参赛者一起参加过已结束比赛的球员;如果该比赛发布在俱乐部的公告板上,还会通知过去 90 天内在该场馆参加过比赛或即将参加比赛的球员。每个人只会通过其已有的渠道收到一次通知:优先发送带有卡片上“我来”一键按钮的 Telegram 私聊;否则发送含比赛链接的邮件;再否则发送推送通知。每场比赛每位球员最多收到一次,而且只通知符合级别范围且有可用通知渠道的球员,最多 40 人;绝不会通知组织者,也不会通知刚刚退出的人。没有群组的私人比赛只会通知参赛者过去的搭档,不会通知其他人。
  • 加入采用先到先得,并以原子方式处理:每次变更都会锁定活动行,并通过一次 UPDATE … WHERE id = (SELECT … LIMIT 1) 精确认领一个席位,因此两次点击最后一个空位也能妥善处理(已通过 12 次并行加入测试)。
  • 预留席位会提供个人邀请链接。组织者轻点名单中的空位即可预留,空位会原地展开(姓名、可选电话/邮件、“完成”);填写邮件地址后会立即发送邀请(“邀请已发送”);未填写时,该行会显示“尚未接受邀请”,并提供“立即邀请”/“移除球员”选项。其他人轻点空位即可直接加入。没有邮件或电话的预留姓名,也可以通过普通比赛链接认领席位:在加入表单中输入姓名后,会出现“认领 Danny 的预留席位”,两次点击即可完成(DECIDING 规则 34)。已经参加比赛的球员不能使用其他人的个人链接:页面会说明席位属于谁,服务器也会拒绝操作。如果预留球员的邮件地址已关联到另一个身份,邀请页面会在确认前提供“以前和 … 一起打过球?使用验证码恢复”;在预留表单中输入已知邮件地址或电话号码时,会建议使用该球员的姓名。对于已知邮件地址,会显示“编辑”按钮,而不会再次询问;同时提供活动邮件的开关(加入/离开/回复通知、阵容变更、比分提醒);加入比赛后总会发送日历邀请,时间变更和取消也会发送,除非你在“我的比赛”中的“通知”下关闭比赛变更通知。
  • 活动动态会从查看者的角度表述:“你添加了 Zed”,而不是“Zed 被 Erik 添加”。全程没有弹出窗口:在 iOS 上打开键盘后,浮层会滑出屏幕。
  • 保持更新,日历自动维护。 没有通知渠道的成员加入后,应用会立即询问“保持更新”:他们希望通过什么方式接收通知。可选 WhatsApp(配置了电话号码后)、Telegram 或邮件,绝不使用推送通知。Telegram 和 WhatsApp 都只需轻点一次,即可打开用户已安装的应用(签名链接会将该账号或电话号码关联到网页球员);邮件会向已占席位的球员发送正式邀请(比赛变更或取消时自动更新),向候补名单上的球员发送个人链接,若有空位,邀请会随席位一起发送;卡片会说明通知已发送到哪里。关联好一个渠道后,卡片就会变成一条简短提示(“更新将发送到你的 Telegram ✓”),不再询问。WhatsApp 球员会收到比赛变更、空位通知(点击“我来”即可加入)、比分询问和赛果卡片,这四种实用模板均由 Meta 批准(data/whatsapp-templates.json),每天最多发送 WHATSAPP_TEMPLATES_PER_DAY 条;无法发送的模板会依次尝试邮件和推送通知。单场比赛不提供 Google/Apple 按钮:这些按钮会创建无法更新的未关联副本。聊天应用用户的日历会改为提供其个人比赛动态(/p/{key}/calendar.ics,包含过去 30 天的数据,与邀请中的 UID 和字段相同,并标记已取消的比赛),并以 webcal:// 形式提供,还可以通过电脑上的 Google Calendar 使用;密钥是个人令牌的哈希,因此该地址不会让任何人登录。邀请条目只包含一个链接,即短版私人活动链接(kicksma.sh/p/{12 chars}/{code}),并在名单确定后附上球员名单;动态条目包含公开比赛链接。
  • 阵容已满: 所有席位均已加入/确认后,会重新发送日历条目,标题中加入 - COMPLETE,描述中列出球员姓名(有人退出后会恢复原样)。应用绝不会主动给任何人发消息;组织者可以使用一键 WhatsApp/Telegram 转发按钮,按钮会预先填好本地化文本。被拒绝的席位会重新成为空位。
  • 联系人簿: 曾加入或受邀参加你活动的所有人,他们的邮件/电话会自动重复使用。
  • 场馆记忆: 自动填入上次使用的场馆;组合框中列出所有以前使用过的场馆;输入自由文本即可添加新场馆。
  • 时区: 默认使用 Vercel 的 x-vercel-ip-timezone(浏览器时区作为备用),可编辑,并以 UTC 保存。
  • 比分: 比赛开始后,任何参与者都可以录入比分;球员可以更正彼此的录入;组织者录入/编辑后,比分即锁定(“已由组织者确认”)。可录入 1–3 盘比分,可选分配队伍 → 在我的比赛中显示胜/负。
  • 组织者访问权限 = 创建者 Cookie 或 10 字符的管理链接(会为每场活动设置一个 httpOnly Cookie)。
  • Telegram 机器人(@kicksmash_bot),安静运行: 将其添加到板式网球群聊后,它会在群里为每场比赛只保留一张卡片:标题、时间、场馆、级别范围、带级别的球员名单、剩余席位,以及两个按钮:“我来”和“无法参加”。点击按钮会通过与网页按钮相同的代码路径加入或退出(组织者通知、日历邀请、候补递补、webhook);卡片会原地编辑,绝不重新发布。机器人只会发布四种新消息:卡片本身(/new 会提供一个与聊天绑定的创建链接;若机器人是管理员或隐私模式关闭,粘贴 kicksma.sh 链接会生成卡片;/match CODE 始终可用)、一条简短的“阵容已满”通知、约一小时前发送的一条提醒(通过 5 分钟推送 cron),以及组织者确认后发送的赛果图片。每个聊天可选择英语或俄语(/lang)。点击按钮的人会以名字的形式成为球员;在“我的比赛”中使用Telegram 登录(Login Widget)可将该账号关联或合并到浏览器身份。回复卡片“+1”或“无法参加”的效果与点击两个按钮相同,机器人会用表情回应,而不是发送消息。隐私模式保持开启:机器人只会在队伍自己的群组中读取普通消息。群组管理员可以通过队伍页面上显示给队伍管理员的链接,将群组关联到队伍(“为这个队伍运行 Telegram 群组”);在这个群组中,机器人会被设为管理员,且其通知会被置顶。这样一来,无需点击按钮即可使用英语、俄语和西班牙语输入“参加”“退出”和“星期四晚上 7 点 Rawai 有谁参加?”(在聊天已知的球场)。群组管理员发送 /quiet 后,机器人就不会再根据文字采取行动。机器人只保存名字、Telegram 用户名、谁参加或退出、球场和比分,绝不保存消息文本。模块:src/lib/telegram/。
  • Discord 机器人,同样保持克制: 仅使用 HTTP 交互(没有 gateway 进程),因此运行在同一组无服务器函数上。/new 会提供一个与频道绑定的创建链接,/match CODE 会发布卡片,两个按钮通过共享操作加入或退出,卡片会为所有人原地更新;此外还会发送一条“阵容已满”通知、一条提醒,以及一次赛果通知。/ask 会即时回答有关板式网球或 Kicksmash 的问题;每小时的定时任务会读取机器人所在服务器中的新消息,筛选看起来像问题的内容,并以回复的形式作答(这是我们自己的社区,无需点击;仍受共享的每日额度限制,而且每条回复都会生成一个答案页面)。每个频道可选择语言:/lang en|ru。模块:src/lib/discord/。
  • 倾听工作台(回复有帮助,但绝不擅自发布): 应用每小时读取公开动态中有关组织板式网球活动的问题(通过 Algolia 读取 Hacker News,通过 RSS 读取 r/padel 和 Reddit 搜索结果),保留最近一周的内容,进行低成本筛选(包含板式网球内容并有组织活动意图),再要求模型按照严格的语气规则,用帖子所用语言起草回复:先回答问题,不夸大宣传,只有在能解决问题时才最多提及一次 kicksma.sh,并说明我们正在开发该产品。草稿会发送到所有者的 Telegram,并提供批准/跳过/编辑按钮,每天最多六条;批准后会以项目账号发布到 Reddit(如果没有 Reddit 密钥,则标记为需手动复制)。/admin/listen 是工作台入口(仅限所有者,通过 Telegram 登录)。每日草稿和令牌上限可保护有额度限制的 API 密钥。已批准的回复会生成长期有效的答案页面,路径为 /answers/{slug}(通用化改写的问题、QAPage JSON-LD,并纳入 sitemap),并立即发布;每周日会在 Telegram 发送一份摘要,列出本周内容,并为每个新页面提供一键取消发布选项。模块:src/lib/listen/。
  • 反馈以及由谁决定: Telegram 或 Discord 中的 /feedback、/feedback 网页表单,或发往 feedback@ 的邮件,都会成为一条反馈。提交者会立即收到诚恳的感谢(说明反馈已被阅读;如果相关功能开发完成,会通知他们;不承诺其他事项)。收到真实反馈后,所有者会立即在 Telegram 上收到提案,其中包括 docs/DECIDING.md 中规则给出的判断、会改动什么及改动位置、工作量(小、中、大)、不看代码得出的时间估算、所需条件和建议,结尾为“开发”或“跳过”。未经所有者同意,不会根据反馈开始开发;提交者只会收到已发布内容的通知。商店首次遇到的生产环境错误也会在首次出现时给所有者发送一条通知,每小时最多几条。触发通知的是反馈和错误;不会有每日循环。
  • 嵌入和 oEmbed: /embed/board/{slug} 和 /embed/match/{code} 是适用于 iframe 的视图(没有页眉,会在新标签页中打开 kicksma.sh,页脚显示“来自 kicksma.sh 的实时内容”);场馆公告板会在“嵌入此公告板”下方显示代码片段。/api/oembed?url=…&format=json 是 oEmbed 提供方;比赛页和公告板页面会通过 <link rel="alternate" type="application/json+oembed"> 声明,因此 WordPress、Discourse、Ghost 和 Notion 可以将粘贴的链接展开为实时卡片。辅助模块:src/lib/embed.ts。
  • 自行部署: 应用是一个使用 Postgres 数据库的 Next.js 项目。Dockerfile 会构建独立镜像;README 中的环境变量表就是完整配置,并有测试确保这一点。所有内容均采用 Apache-2.0 许可;可供你的俱乐部、城市或国家使用。
  • 锦标赛赛制: 除了 americano 轮换赛制,锦标赛还可采用 mexicano(第 1 轮随机安排,之后球场按积分榜轮换,每片球场上由第 1 名和第 4 名对阵第 2 名和第 3 名;须等所有比分录入后才能开始下一轮)或 King of the Court(胜者升至上一片球场,负者降至下一片球场;最高位球场的胜者和最低位球场的负者留在原处;每轮拆分搭档;积分榜根据最终所在球场排列)。创建锦标赛时通过一个选项选择赛制;第 1 轮开始前可以更改。引擎:src/lib/domain/formats.ts。
  • 组织者验证级别: 比赛结果最终确认后,组织者会看到折叠的“确认级别”行,并可逐个或一次性确认与自己一起比赛的球员级别。已确认的级别会在级别标签旁显示 ✓,且在级别变化不足半级时保持确认状态。
  • 排行榜(默认关闭,需主动开启): /v/{slug}/ranking 根据俱乐部过去 90 天内最终确认的结果进行排名(胜利 3 分,平局 1 分,锦标赛领奖台名次分别为 3/2/1 分);/phuket 和 /singapore 会汇总城市内各俱乐部的结果,并列出当地开放中的比赛。只有开启“在排行榜中显示我”的球员(可在“我的比赛”中开启,也可在排行榜页面轻点一次开启)才会显示。城市列表:src/lib/domain/cities.ts。
  • 级别(0–7): 球员只需声明一次自己的级别,按四分之一步进(使用按级别段分组的普通下拉菜单,并提供简短指南);级别会显示在名单、积分榜和比分面板中的姓名旁。赛果会适当调整级别:组织者确认 2v2 比赛比分或最终确认锦标赛后,会应用类似 Elo 的变化量(级别相差 1 约等于 10:1 的胜率,单场比赛最多 ±0.10,锦标赛最多 ±0.12),并且每场活动只应用一次,适用于所有已评级球员;级别来源会变为“根据赛果调整”,并在“我的比赛”中显示最近一次变化,同时保留有上限的记录。未评级球员的级别绝不会变化,也不会计入。
  • 级别范围: 比赛或锦标赛可设为 Bronze(1.0–2.5)、Silver(2.5–3.5)、Gold(3.0–4.5)、Platinum(4.5+),也可自定义最小值和最大值;范围会显示在顶部横幅的标签中。范围内的球员照常加入(加入表单会询问一次级别);范围外的球员会看到申请加入按钮,组织者会看到包含级别的申请,并可批准(安排入座或加入候补名单)或拒绝;申请者会在加入栏中看到答复,并收到日历邀请或简短邮件。组织者预留的邀请和递补不受级别范围限制;与组织者一起参加过已结束且已录入比分的比赛的球员也不受限制(所有者,2026 年 10 月 11 日)。队伍平衡: 四名球员都有级别时,比分面板会建议差距最小的 2v2 分组。
  • “我的比赛”统计条: 已参加场次、胜场、胜率、领奖台次数(根据已确认的队伍结果和最终确认的排名统计)。
  • 俱乐部页面和创始俱乐部: 任何场馆的公告板都可以由经营该场馆的俱乐部在 /clubs/claim 认领(名称、世界任意地点的城市和国家、预订页面、网站、球场数量,以及对俱乐部的简短介绍)。认领可自行完成,并附有验证步骤:申请者说明自己在俱乐部的职务并提供工作联系方式;俱乐部自有域名的工作邮箱会当场通过 6 位验证码确认,其他情况由所有者人工核验;接着在 Telegram 上轻点一次,页面即上线,申请者会通过 Telegram 或邮件收到答复。待处理的认领信息除申请者本人外不会向任何人显示。页面上线后,/v/{slug} 会显示“在 Playtomic / MATCHi / Playbypoint 上预订……”(根据链接识别平台,每场该俱乐部的比赛也会显示),以及网站、介绍文本、徽章和今日空闲球场,前提是俱乐部提供了已有的数据源:预订日历(.ics,空闲数量 = 开放时段内的球场数量 − 时间重叠的预订数量)或空闲时段的 JSON 列表,每小时刷新。若俱乐部没有共享数据源,且其预订链接(若没有,则为网站)位于 Kicksmash 支持读取的平台上(Playtomic、MATCHi 和 Book & Go),Kicksmash 会从该平台的公开俱乐部页面读取未来三天的空闲球场时段。对于阅读器支持的每家俱乐部,今天的数据每 15 分钟读取一次,接下来两天的数据大约每小时读取一次。页面和 API 会注明平台(DECIDING 规则 35:无需登录、不预订、不付款;球员自行在平台上预订和付款;/about 会说明 KicksmashBot 读取的内容)。俱乐部共享的数据源始终优先。若未来两到六小时有空闲球场,会向在该俱乐部、该日期和大致时段请求过比赛的球员各发送一次通知,通过他们现有的渠道发送,并提供一个按钮,跳转至已预填该俱乐部和时段的比赛表单(每个需求每天最多一个球场)。若目录中列出的俱乐部认领申请被拒绝,除非拒绝理由是其并非俱乐部或与其他俱乐部重复,否则该条目会以新的管理链接恢复为目录原有信息。每个城市最先入驻的十家俱乐部都是创始俱乐部:永久免费。/clubs 按城市列出已上线的俱乐部;GET /api/v1/clubs?city=phuket 和 GET /api/v1/clubs/{slug} 提供相同的公开信息;MCP 工具为 find_clubs。俱乐部可以通过私人链接管理页面,该链接也会显示在“我的比赛”中。模块:src/lib/domain/clubs.ts、src/lib/booking/(平台检测、俱乐部数据源、scrape.ts 中每 15 分钟一次的读取,以及 adapters/ 中各平台专用的阅读器)。
  • 球员证件页(默认关闭,需主动开启): 球员可在“我的比赛”下方开启 /u/{slug} 公共页面(名字、级别段和组织者确认标记、已参加场次/胜场/胜率/领奖台次数、参加过比赛的俱乐部),并获取签名级别文档 /u/{slug}/passport.json:使用 Ed25519 对规范化 JSON 签名,公钥位于 /.well-known/kicksmash-passport.json,@erikv69/levels 中的 verifyPassport 可用于验证,有效期为 90 天。没有个人资料列表。下载我的数据(/api/me/export)会导出一个包含我们持有的所有信息的 JSON 文件,但不包含令牌、管理链接和通行密钥的凭据 ID 与公钥(电话会显示其标签和日期)。导入级别: 级别选择器会折叠显示“在其他应用中有级别?”选项,可将 Playtomic(同为 0–7)、1–10 俱乐部量表和五级分类系统映射到 0–7,并公开映射过程;映射表位于 /levels。模块:src/lib/domain/passport.ts(纯函数,使用 WebCrypto)、src/lib/domain/profile.ts。
  • 赛果卡片: 比赛录入比分或锦标赛录入一轮比分后,“分享赛果”会打开 /{code}/card:页面链接会在 WhatsApp 和 Telegram 中展开为自动生成的图片(带胜方高亮的比分框,或积分榜前五名),用户可以长按图片保存,使用分享按钮,或通过“组织你自己的比赛”开始创建。
  • 群组: 在任何有两名或更多球员的比赛中点击“将这个队伍转为群组”,即可创建群组(/g/{6 chars}),并将该场比赛的设置作为默认值(场馆、赛制、人数上限、级别范围、时区),将球员设为成员;点击的人是管理员。任何拥有链接的人都可以加入(仅需姓名);加入群组比赛或接受群组邀请也会成为成员。在群组页面的成员名单中、GET /api/v1/groups/{code} 以及 MCP 工具 get_group 中,访客可以看到成员的名字和人数,但看不到级别(src/lib/domain/groupAccess.ts 中的 canSeeMemberLevels);比赛本身的名单仍会显示已入座球员的级别,与其他比赛页面一样。管理员可以开启申请加入(所有者决定 E,2026 年 10 月 9 日):新成员可附上选填说明提出申请,管理员会通过 Telegram、邮件或推送收到通知,并可在群组页面中批准或拒绝;申请者会收到答复,被拒绝的人可在七天后再次申请。在这类群组中,加入群组比赛只代表占据一个席位,不会自动成为群组成员(joinGroup 接受 via:self、match 或 admin)。任何成员都可以创建下一场比赛:从群组页面打开的创建表单会预先填好信息(包括下一个每周时段),比赛会关联回群组,其他每位成员都会收到包含个人链接的邮件和推送通知。管理员可以设置每周时段:每小时运行的 cron 会提前几天创建下一场比赛(提前时间为 1–14 天,默认为 5 天),安排群组创建者入座,通知所有人,并确保同一个时段只创建一次。群组至少有两场已录入比分的比赛,且都在过去 90 天内时,比赛列表下方会显示简洁的赛季积分榜:列出每位参赛成员的名字、已参加场次、胜场和连胜场次(连胜三场起显示 🔥),胜场最多者排在最前。“我的比赛”会列出你的群组及各群组的下一场比赛。
  • 场馆公告板(默认关闭,需主动开启): 组织者在创建或编辑带有场馆的比赛时,可以勾选“在场馆公告板上显示”。列出的、即将举行且未取消的比赛会出现在 /v/{venue-slug}(以场馆命名的公共页面,显示剩余席位和级别范围),比赛也会显示“位于……公告板”标签。/v/{slug}/poster 是单页可打印海报,附有指向公告板的 QR 码(“扫描查看……的空位板式网球比赛”)。公告板的空状态和页脚会链接到已预填场馆并默认开启发布的创建表单。Slug 仅包含 ASCII 字符,并会在场馆重命名时保持同步;移除场馆会取消比赛的公告板发布。
  • Americano 生成器: /americano 是公开且可被索引的页面,会在浏览器中运行同一轮换引擎:球员(或粘贴的姓名)、球场、轮次;参赛人数为 4 的倍数时安排精确轮换,否则公平轮休;提供打印样式;“在 Kicksmash 上实时运行”会将赛程带入真实锦标赛:姓名会一同带入(/?type=tournament&capacity=N&s=gen&names=…),创建表单会在“你的 americano,即刻开赛”下方打开,并列出带入的球员;锦标赛一创建,每个人都会通过 seatNames 获得带邀请链接的预留席位。粘贴的名单最多 64 人,这是引擎支持的上限。两个生成器页面都提供反馈入口。sitemap.xml 会以所有语言列出 /、/play、/americano、/about、/privacy 和 /terms,以及城市、俱乐部、教练、系列活动和答案页面;robots.txt 允许爬虫读取这些页面;个人、管理、邀请、分享和卡片页面不会被收录。

API、MCP 服务和 Webhook

网站上的所有公开内容都可供程序和助手使用,创建表单中的所有操作都可通过一次调用完成。

接口位置
REST 读取(无需密钥)GET /api/v1/matches/{code}, /api/v1/boards/{slug}, /api/v1/groups/{code}, /api/v1/schedule?players=8&courts=2
REST 写入(密钥可选;不提供密钥时按地址限流)POST /api/v1/matches, POST /api/v1/matches/{code}/join
密钥POST /api/v1/keys → 即时生成,仅显示一次;Authorization: Bearer ks_live_…
Webhook(需要密钥)使用 url、events 和可选的 filter 调用 POST /api/v1/webhooks;签名为 X-Kicksmash-Signature: t=…,v1=…;每小时 cron 会按退避策略重试
MCPPOST /mcp(可流式传输的 HTTP、无状态 JSON)。比赛和小组:about_kicksmash、get_match、find_matches、get_group、generate_schedule、create_match、join_match、create_api_key。俱乐部和系列赛:find_clubs、find_series。教练服务:find_coaches、coach_slots、request_coach、book_lesson、cancel_lesson。此外还提供包含模型参考和 OpenAPI 文档的资源。如果此行与 src/lib/api/mcp.ts 不一致,tests/docs.test.ts 会报错。
发现/llms.txt、/llms-full.txt、/.well-known/mcp.json、/api/openapi.json、/developers、/agents;robots.txt 明确允许 AI 爬虫访问
订阅源/g/{code}/calendar.ics、/v/{slug}/calendar.ics;以及仅供玩家本人使用的私有日历 /p/{key}/calendar.ics

公开数据结构(src/lib/api/serialize.ts)仅包含名字和等级;不会出现邮箱、电话、个人令牌和管理链接。错误格式为 { error: { code, message, hint, status } };每条提示都会说明下一步该怎么做。限制配置位于 src/lib/domain/ratelimit.ts。

注册表和市场

仓库根目录中的 server.json 是官方 MCP 注册表的清单(命名空间 io.github.evhg);仓库所有者只需运行一条命令即可发布:mcp-publisher login github && mcp-publisher publish。技能索引会从公开仓库中获取 skills/kicksmash/SKILL.md 中的技能。Smithery、Glama 和 mcp.so 可通过网页表单接入远程 URL https://kicksma.sh/mcp。

管理仪表板

/admin 是公开的只读使用情况页面:顶部主视觉数据搭配健康状态行(数据库、电子邮件、推送、两个 cron、今日错误),统计卡片配有迷你趋势图,仪表显示相对于免费套餐上限的用量(Supabase 500 MB、Resend 每月 3,000 封、每日 100 封),并提供 7/30/90 天趋势图表(增长、加入人数、外发消息、错误、数据库大小、总计)。计数器存储在 metrics_daily 中(由应用递增,由每小时 cron 快照);不会显示任何个人数据。错误只计数,从不描述:抛出异常的服务器操作、失败的 cron 步骤,以及浏览器崩溃页面(/api/client-error,受限流保护)都会各自递增一个计数器,因此健康状态行就是唯一的告警方式。Vercel Hobby 套餐没有公开 API 可查询带宽,因此此处提供了指向仪表板的链接。

项目结构

src/app/                 routes: / · /[code] · /[code]/share · /[code]/card (+ opengraph-image) · /[code]/i/[invite] · /[code]/manage/[manage] · /g/[code] (+ calendar.ics) · /v/[slug] (+ /poster, /ranking, calendar.ics) · /phuket · /singapore · /clubs (+ /claim) · /v/[slug]/manage/[token] · /api/v1/clubs (+ /[slug]) · /u/[slug] (+ passport.json) · /api/me/export · /.well-known/kicksmash-passport.json · /api/telegram/{webhook,setup,login} · /api/discord/{interactions,setup} · /admin/listen · /answers (+ /[slug]) · /embed/{board,match}/… · /api/oembed · /me · /p/[token] (+ /calendar, calendar.ics) · /about · /privacy · /terms · /play · /americano · /developers · /agents · /mcp · /api/v1/* · /api/openapi.json · /llms.txt · /.well-known/mcp.json · /unsubscribe · /admin · /api/cron/{hourly,push} · /api/client-error · robots.txt · sitemap.xml
src/app/[code]/opengraph-image.tsx   link preview (Inter w/ Cyrillic, organizer's language)
src/actions/             server actions (identity incl. restore codes, events, slots, scores)
src/lib/domain/          pure business logic, driver-agnostic (events, slots, scores, reminders, queries)
src/lib/notify.ts        every outbound email; safe no-op without RESEND_API_KEY
src/lib/calendar.ts      Google Calendar URL + RFC 5545 .ics builder (also served at /{code}/calendar.ics); inviteFields, what every entry says
src/lib/calendarFeed.ts  a player's own feed: its key, its matches, its .ics
src/lib/domain/identity.ts personal tokens, email one-time codes, identity merge
src/lib/domain/{ratelimit,optouts,anonymize}.ts   abuse ceilings, unsubscribe list, account deletion
src/lib/domain/{levels,requests,rating}.ts   level maths (ranges, presets, balanced teams, deltas), join requests, result-based adjustment
src/lib/domain/groups.ts   groups, membership (joinGroup with via), asks to join, weekly slots (recurrenceDue / autoCreateGroupMatches)
src/lib/domain/groupAccess.ts   pure: who sees members' levels, what a join does, when a declined person may ask again
src/lib/domain/venueBoard.ts   venue slugs, the public board query, listing toggle
src/lib/api/               public REST (operations, serialize, keys, webhooks, openapi), MCP server (mcp.ts), model-facing docs (docs.ts)
src/lib/channels/          one card algorithm (cards.ts) with Telegram and Discord as adapters over it
src/lib/domain/facts.ts    the append-only fact log every view is a query over
skills/kicksmash/SKILL.md  installable skill for coding agents; AGENTS.md at the root for agents working on this repo
src/lib/domain/{levels,rating,requests}.ts       level scale, presets, fit, balanced teams, Elo-style deltas; join requests
src/lib/alerts.ts        error counters for the admin health row
src/db/schema/           the tables, one file per domain (enums, players, events, demand, groups, clubs, coaching, channels, api, ops)
src/db/                  driver factory (postgres-js | PGlite), seed
drizzle/                 generated SQL migrations
messages/{en,ru,es}.json all UI, share and email copy (identical key sets, typed in global.d.ts)
tests/                   vitest against PGlite (or TEST_DATABASE_URL)
e2e/                     Playwright journeys + runner (pnpm e2e)
.github/workflows/ci.yml typecheck · lint · schema vs migrations · vitest (PGlite + Postgres) · build · e2e (2 shards)
scripts/                 gate.sh (the pre-push gate), check-migrations.sh, gen-docs.mjs (this README's env table)

架构变更:编辑 src/db/schema/ 中对应领域的文件 → 运行 pnpm db:generate → 提交 drizzle/ → Migrate 工作流会在变更进入 main 时应用它(AGENTS.md 规则 7)。如果两者不一致,bash scripts/check-migrations.sh 会报错。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。