Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c6a7a59
fix(billing): await the credit debit before responding (F1)
oratis Aug 21, 2026
f345157
fix(billing): charge SSE streams when the stream ends, not at the hea…
oratis Aug 21, 2026
fc86e1e
fix(security): stop bucketing gateway rate limits by a spoofable clie…
oratis Aug 21, 2026
72c5b09
feat(gateway): honour ApiKey.rateLimit instead of a hard-coded 120
oratis Aug 21, 2026
1cc653d
fix(billing): stop losing the PayPal top-up fee and silent post-captu…
oratis Aug 21, 2026
11d30be
fix(security): accept CRON_SECRET only in the Authorization header
oratis Aug 21, 2026
d09836f
feat(gateway): emit attributable telemetry for rejected calls (F4)
oratis Aug 21, 2026
e9c3ffd
fix(privacy): hash RequestLog client IPs and add a retention sweep (F5)
oratis Aug 21, 2026
5480e78
feat(health): record agent health transitions instead of only the lat…
oratis Aug 21, 2026
21e279b
docs(content): stop presenting scraped ClawSkills counts as live inst…
oratis Aug 21, 2026
96b89f5
docs(analytics): add the telemetry plan, with its refuted claims corr…
oratis Aug 21, 2026
d0b45a4
docs(migrations): record the production apply procedure and ordering
oratis Aug 21, 2026
d5b8262
feat(security,ux): secret scanning, security headers, unified nav, ca…
oratis Sep 6, 2026
d566d52
perf,privacy: search indexes, consent-gated analytics, honest like state
oratis Sep 6, 2026
275948d
fix(lint): remove ref-write-during-render and setState-in-effect from…
oratis Sep 6, 2026
516144d
feat: server-render the catalog, finish the developer funnel, and mak…
oratis Sep 6, 2026
1ff84fd
docs(readme): match the API reference and check list to what the code…
oratis Sep 6, 2026
511239a
fix(a11y): raise the last low-contrast text, respecting dark surfaces
oratis Sep 6, 2026
26694ce
i18n: retranslate all 14 catalogs against the rewritten English, and …
oratis Sep 7, 2026
f971f6e
fix(ci): regenerate the lockfile from a clean tree so npm ci works on…
oratis Sep 7, 2026
a25383d
fix: address the adversarial review — privilege escalation, bypassabl…
oratis Sep 7, 2026
69488e1
fix(rtl): mirror the remaining directional glyphs
oratis Sep 7, 2026
fd7eae9
fix: address the second adversarial review — sync job, moderation byp…
oratis Sep 7, 2026
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
44 changes: 44 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: ci

# Every push to main and every PR: install, secret scan, i18n parity, type check,
# lint, unit tests, production build. The build needs no database — every page that
# touches Prisma is force-dynamic, and the sitemap degrades when the DB is absent.
on:
pull_request:
push:
branches: [main]

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
checks:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: npm
- run: npm ci
- name: Secret scan
run: npm run check:secrets
- name: i18n parity
run: npm run check:i18n
- name: Skill assets in sync
run: npm run check:skill-assets
- name: Prisma client
run: npx prisma generate
- name: Type check
run: npm run typecheck
- name: Lint
run: npm run lint
- name: Unit tests
run: npm test
- name: Build
run: npm run build
env:
NEXTAUTH_SECRET: ci-build-only
NEXTAUTH_URL: http://localhost:3000
20 changes: 0 additions & 20 deletions .github/workflows/i18n.yml

This file was deleted.

1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
4 changes: 4 additions & 0 deletions Dockerfile.sync
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ WORKDIR /app
COPY package*.json ./
COPY prisma ./prisma/
COPY scripts/daily-sync.ts ./scripts/
# daily-sync.ts imports ../src/lib/ingestion-log for its run-summary telemetry.
# The module is dependency-free, so this one file is the whole subtree it needs;
# without it `npx tsx scripts/daily-sync.ts` dies at MODULE_NOT_FOUND before main().
COPY src/lib/ingestion-log.ts ./src/lib/

# tsx is not in package.json deps; install it into the image so the
# runtime `npx tsx` never has to fetch it. prisma generate runs against
Expand Down
97 changes: 71 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,25 @@ npx tsx prisma/seed.ts
npm run dev
```

Open [http://localhost:3000](http://localhost:3000). The dev server uses SWC and skips type-checking, so run `npx tsc --noEmit` before shipping.
Open [http://localhost:3000](http://localhost:3000).

### Checks

The dev server skips type-checking, so run the same checks CI does before pushing:

```bash
npm run typecheck # tsc --noEmit
npm run lint
npm test # vitest
npm run check:i18n # every locale matches messages/en.json, ICU is valid
npm run check:secrets # no credential-shaped values in tracked files
npm run check:skill-assets # the generated skill copies match takoapi_skill/SKILL.md
npm run build
```

The skill body has one source of truth (`takoapi_skill/SKILL.md`) and five generated
copies — the plugin, the three npx installer assets, and the heredoc inside
`public/install.sh`. Edit the source, then run `npm run build:skill-assets`.

### Environment Variables

Expand All @@ -149,32 +167,42 @@ Open [http://localhost:3000](http://localhost:3000). The dev server uses SWC and

## API Reference

### Agent registry
Full reference with request and response examples: **<https://takoapi.com/docs>**.

### Agent registry (no auth)

```
GET /api/registry # Markdown directory of all agents (default)
GET /api/registry # Markdown directory (default) — drop straight into an LLM
GET /api/registry?format=json # Structured JSON
GET /api/agents/{slug} # One agent: capabilities, protocols, pricing, health
```

Filters: `q` (search), `category`, `protocol` (`A2A` | `OPENAI_COMPAT` | `MCP`), `limit`.
Filters: `q` (search), `category`, `protocol` (`A2A` | `OPENAI_COMPAT` | `MCP`), `kind`
(`HOSTED` | `PROJECT`), `sort` (`stars` | `calls` | `rating`), `limit`.

Only `HOSTED` agents are callable; `PROJECT` entries are open-source repositories
listed for discovery. Responses report the totals per kind, so a client can tell
when it is holding a subset.

### Gateway (hosted agents)
### Gateway (hosted agents — requires an API key)

```
POST /v1/agents/{slug}/message # A2A passthrough
GET /v1/agents/{slug}/stream # Server-sent events
POST /v1/chat/completions # OpenAI-compatible shim
POST /v1/agents/{slug}/message # A2A passthrough; accepts {"text": …} or a full A2A message
POST /v1/agents/{slug}/stream # Same, relayed as Server-Sent Events
POST /v1/chat/completions # OpenAI-compatible; model = agent slug, stream supported
```

All gateway routes require an API key: `Authorization: Bearer $TAKO_KEY`.
`Authorization: Bearer $TAKO_KEY`. Default allowance 120 requests / 60 s per key.
Free agents cost nothing; priced agents debit prepaid credit, and a call that fails
— or a stream that does not run to completion — is not charged.

### Skills
### Skills (no auth)

```
GET /api/agent # Curated skills as Markdown (md) or JSON, for AI agents
GET /api/skills # List skills (paginated, sortable)
GET /api/skills/search?q= # Full-text search
GET /api/skills/:id # Skill detail (by ID or slug)
GET /api/agent # Curated skills as Markdown (default) or JSON, for AI agents
GET /api/skills # List skills (paginated, sortable, filterable)
GET /api/skills/search?q= # Search by name, description or author
GET /api/skills/{idOrSlug} # Skill detail
POST /api/skills/submit # Submit a new skill (auth required)
```

Expand All @@ -184,28 +212,45 @@ POST /api/skills/submit # Submit a new skill (auth required)
POST /mcp # Streamable-HTTP MCP endpoint (JSON-RPC 2.0)
```

Tools: `search_agents`, `get_agent`, `search_skills` (anonymous), and `invoke_agent` (send your key as `Authorization: Bearer $TAKO_KEY`). Register in any MCP client, e.g. `claude mcp add --transport http takoapi https://takoapi.com/mcp`.
Tools: `search_agents`, `get_agent`, `search_skills` (anonymous), and `invoke_agent`
(send your key as `Authorization: Bearer $TAKO_KEY`). Register in any MCP client, e.g.
`claude mcp add --transport http takoapi https://takoapi.com/mcp`.

## Project Structure

```
src/
app/
page.tsx # Home
agents/ # Agent marketplace + detail pages
skills/ # Skills browse + detail pages
trending/ # Trending leaderboard
dashboard/ # Developer console (API keys, usage)
admin/ # Moderation
api/ # REST API routes
v1/ # Gateway routes (A2A / OpenAI-compatible)
sitemap.ts, robots.ts # SEO
[locale]/ # Every page, under a locale segment (en unprefixed, 14 more)
page.tsx # Home
agents/ # Agent marketplace + detail pages
scenarios/ # Use-case landing pages
skills/, trending/ # Coding-agent skills catalog + leaderboard
blog/, install/, badge/
dashboard/ # Developer console (API keys, usage, credits)
profile/, bookmarks/ # My listings, saved agents
submit-agent/, submit/ # Publish an agent / a skill
admin/ # Moderation
auth/ # Sign in / up / password reset
api/ # REST API routes (public catalog, user, admin, cron, billing)
v1/ # Gateway routes (A2A passthrough, SSE, OpenAI-compatible)
mcp/ # Hosted MCP server (Streamable HTTP)
sitemap.xml/, sitemap/ # Sitemap index + chunked children
robots.ts, opengraph-image.tsx
proxy.ts # Locale routing (Next 16 request interceptor)
components/ # UI + layout
lib/ # auth, prisma, seo, helpers
i18n/ # next-intl routing + request config
lib/ # auth, prisma, billing, gateway, seo, helpers
hooks/ # client hooks
messages/ # 15 locale catalogs (checked by `npm run check:i18n`)
prisma/
schema.prisma # Database schema
migrations/ # Applied manually in production (see docs/agent-marketplace/HANDOFF.md §5)
seed.ts # Database seeder
scripts/ # Scrapers + maintenance scripts
scripts/ # Scrapers, sync jobs, CI checks (secrets, skill assets)
packages/takoapi-install/ # `npx takoapi-install`
plugins/takoapi/ # Claude Code plugin
takoapi_skill/SKILL.md # Source of truth for the skill body (run `npm run build:skill-assets`)
```

## Deployment
Expand Down
89 changes: 57 additions & 32 deletions docs/06-cron-schedule.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,41 @@
# Cron 调度 — 权威清单与幂等建置

> 三个自动摄取端点全靠 **Cloud Scheduler** 触发,但调度配置此前不在代码库里
> (无 `vercel.json`,`cloudbuild.yaml` 是 stale 模板)。本文件是它们的**唯一权威来源**:
> 频率、参数、鉴权,以及可复现的 `gcloud` 建置命令。改调度请改这里并同步执行。
> 关联:`docs/05-automation-ingestion-review.md` §7。
> 所有自动摄取与维护端点都靠 **Cloud Scheduler** 触发,调度配置本身不在代码库里
> (无 `vercel.json`)。本文件是它们的**唯一权威来源**:频率、参数、鉴权,以及可复现的
> `gcloud` 建置命令。改调度请改这里并同步执行。
> 关联:`docs/05-automation-ingestion-review.md` §7、`docs/08-tech-review-2026-09-05.md` §2.9。

## 端点一览

| Job | 端点 | 建议频率 | 参数 | 落库策略 |
|-----|------|----------|------|----------|
| `tako-scrape-agents` | `GET /api/cron/scrape-agents` | 每日 04:00 UTC | `?pages=1&minStars=200&max=2000` | PROJECT 直接 APPROVED;admin REJECT/DISABLE 的 slug 会被跳过 |
| `tako-import-hosted` | `GET /api/cron/import-hosted` | 每日 04:30 UTC | `?max=0`(不限) | HOSTED 落 PENDING,待 `/admin/agents` 审核;跨域变更也降级 PENDING |
| `tako-health` | `GET /api/cron/health` | 每 6 小时 | —— | 更新 `healthStatus`/`healthCheckedAt` |
| `tako-scrape-agents` | `GET /api/cron/scrape-agents` | 每日 04:00 UTC | `?pages=1&minStars=200&max=2000` | PROJECT 直接 APPROVED;admin REJECT/DISABLE 的 slug 会被跳过 |
| `tako-import-hosted` | `GET /api/cron/import-hosted` | 每日 04:30 UTC | `?max=0`(不限) | HOSTED 落 PENDING,待 `/admin/agents` 审核;跨域变更也降级 PENDING |
| `tako-check-github` | `GET /api/cron/check-github` | 每周一 05:00 UTC | —— | 探测 skill 的 GitHub 链接是否 404,写 `ghStatus`/`ghCheckedAt` |
| `tako-health` | `GET /api/cron/health` | 每 6 小时 | —— | 更新 `healthStatus`/`healthCheckedAt`,状态**翻转**时追加 `AgentHealthCheck` |
| `tako-weekly-digest` | `GET /api/cron/weekly-digest` | 每周一 09:00 UTC | —— | 给已验证订阅者发周报 |
| `tako-retention` | `GET /api/cron/retention` | 每日 02:00 UTC | —— | 删除 90 天前的 `RequestLog`(分批,单次上限 10 万行) |
| `tako-ingestion-health` | `GET /api/cron/ingestion-health` | 每 6 小时 | —— | **只读**。按来源报告目录新鲜度,超过 72 小时无新增即 `stale: true` |
| `takoapi-daily-sync` | Cloud Run **Job**(非 HTTP 端点) | 每日 03:00 UTC | —— | 从 clawskills.sh 增量同步 skills;见 `scripts/deploy-sync-job.sh` |

所有端点鉴权:`Authorization: Bearer <CRON_SECRET>`(见 `src/lib/cron-auth.ts`;
`CRON_SECRET` 未配置时端点 fail-closed 返回 401)。响应含 `durationMs` + `ranAt`,
调度日志据此判断是否接近超时 / 被截断。
所有 HTTP 端点鉴权:`Authorization: Bearer <CRON_SECRET>`(见 `src/lib/cron-auth.ts`)。
**只接受请求头**——`?key=` 形式已移除(Cloud Run 请求日志会记录完整 URL 含查询串,保留 30 天,
等于把密钥抄进一份 `logging.viewer` 就能读的日志)。`CRON_SECRET` 未配置时端点 fail-closed 返回 401。

## 建置(幂等 create-or-update)
响应含 `durationMs` + `ranAt`;摄取类端点另外向 stdout 打一行
`{"event":"ingestion_run", …}` 结构化日志(`src/lib/ingestion-log.ts`),供告警使用。

## 建置(幂等 create-or-update)

```bash
set -euo pipefail
PROJECT=takoapi-491505
REGION=us-central1
LOCATION=us-central1 # Cloud Scheduler location
BASE=https://takoapi.com # 或 run URL: https://takoapi-429522911261.us-central1.run.app
LOCATION=us-central1 # Cloud Scheduler location
BASE=https://takoapi.com # 或 run URL

# CRON_SECRET 来源(二选一,取决于是否启用 Secret Manager — 见 docs/00-infrastructure.md):
# a) Secret Manager:
SECRET=$(gcloud secrets versions access latest --secret=tako-cron-secret --project="$PROJECT")
# b) 若 Secret Manager 未启用,从 Cloud Run 服务 env 读取当前值:
# SECRET=$(gcloud run services describe takoapi --region "$REGION" --project="$PROJECT" \
# --format='value(spec.template.spec.containers[0].env)' | tr ',' '\n' | grep -A1 CRON_SECRET | tail -1)

# 幂等 upsert:create 失败(已存在)即改 update。
upsert_job () {
local name="$1" schedule="$2" uri="$3"
local verb=create
Expand All @@ -47,30 +49,53 @@ upsert_job () {
--max-retry-attempts=1
}

upsert_job tako-scrape-agents "0 4 * * *" "${BASE}/api/cron/scrape-agents?pages=1&minStars=200&max=2000"
upsert_job tako-import-hosted "30 4 * * *" "${BASE}/api/cron/import-hosted?max=0"
upsert_job tako-health "0 */6 * * *" "${BASE}/api/cron/health"
upsert_job tako-scrape-agents "0 4 * * *" "${BASE}/api/cron/scrape-agents?pages=1&minStars=200&max=2000"
upsert_job tako-import-hosted "30 4 * * *" "${BASE}/api/cron/import-hosted?max=0"
upsert_job tako-check-github "0 5 * * 1" "${BASE}/api/cron/check-github"
upsert_job tako-health "0 */6 * * *" "${BASE}/api/cron/health"
upsert_job tako-weekly-digest "0 9 * * 1" "${BASE}/api/cron/weekly-digest"
upsert_job tako-retention "0 2 * * *" "${BASE}/api/cron/retention"
upsert_job tako-ingestion-health "15 */6 * * *" "${BASE}/api/cron/ingestion-health"
```

> `--attempt-deadline=320s` 略高于 scrape-agents 的 `maxDuration=300`,以免 Scheduler 先于端点超时。
> **Cloud Run 服务的 request timeout 必须 ≥ 320s**(否则那才是真正的截断点);核对:
> `--attempt-deadline=320s` 略高于 scrape-agents 的 `maxDuration=300`,以免 Scheduler 先于端点超时。
> **Cloud Run 服务的 request timeout 必须 ≥ 320s**(否则那才是真正的截断点);核对:
> `gcloud run services describe takoapi --region us-central1 --project=takoapi-491505 --format='value(spec.template.spec.timeoutSeconds)'`。

## 必须配的告警(否则等于没做)

摄取管道曾经**静默停摆两个月**:`takoapi-daily-sync` 每天 03:00 正常触发、正常退出 0,
但 clawskills.sh 改版后选择器失配,一条都没导入。目录里最新的 skill 停在 2026-07-04,
而没有任何信号提示。现在 job 在"跑了但一条没找到"时会以非 0 退出,
但**只有配了告警才有人知道**。建议在 Cloud Monitoring 建三条:

| 告警 | 条件 | 为什么 |
|---|---|---|
| 摄取任务失败 | Cloud Scheduler / Cloud Run Job 执行失败 ≥ 1 次 | 抓取失配、鉴权过期 |
| 目录停滞 | `/api/cron/ingestion-health` 返回体里任一来源 `stale: true` | 唯一能抓住"成功但没产出"的信号 |
| 网关 5xx 率 | `jsonPayload.event="gateway_call_rejected"` 或 5xx 超阈值 | 上游批量失效 |

## 运维

```bash
# 列出 / 查看
gcloud scheduler jobs list --location=us-central1 --project=takoapi-491505
# 手动触发一次(冒烟)
gcloud scheduler jobs run tako-health --location=us-central1 --project=takoapi-491505
# 轮换 CRON_SECRET 后,重跑上面的 upsert_job 即可刷新 header
# 暂停 / 恢复
gcloud scheduler jobs run tako-ingestion-health --location=us-central1 --project=takoapi-491505
gcloud scheduler jobs pause tako-scrape-agents --location=us-central1 --project=takoapi-491505
gcloud scheduler jobs resume tako-scrape-agents --location=us-central1 --project=takoapi-491505
```

## 密钥到期日历(人工提醒,没有代码会替你做)

| 密钥 | 到期 / 轮换周期 | 到期后的症状 |
|---|---|---|
| `APPLE_CLIENT_SECRET` | **2026-09-25**(JWT,Apple 上限 6 个月) | Apple 登录静默失败,无报错页 |
| `tako-github-token` | GitHub PAT,按创建时设定 | 抓取返回 401;`scrape-agents` 现在会抛错而非静默返回空 |
| `CRON_SECRET` | 建议每 6 个月 | 轮换后必须重跑上面的 `upsert_job`(header 里带的是明文值) |
| Cloud SQL 密码 | 见 `docs/08-tech-review-2026-09-05.md` §1.1 —— **待轮换** | —— |

## 安全备注

- header 里带的是明文 bearer secret;`gcloud scheduler jobs describe` 可见,权限同项目其他密钥。
若日后启用 OIDC(service account),可改用 `--oidc-service-account-email` 并把 `cron-auth.ts` 换成校验 OIDC token。
- **别把 `SECRET` 硬编码进本文件或提交历史**;上面的命令都从 Secret Manager / 运行时 env 现取。
- header 里带的是明文 bearer secret;`gcloud scheduler jobs describe` 可见,权限同项目其他密钥。
若日后启用 OIDC(service account),可改用 `--oidc-service-account-email` 并把 `cron-auth.ts` 换成校验 OIDC token。
- **别把 `SECRET` 硬编码进本文件或提交历史**;上面的命令从 Secret Manager 现取。
- 仓库根有 `npm run check:secrets`(CI 也跑),会在任何被追踪文件里出现凭据形态的值时让构建失败。
Loading
Loading