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
35 changes: 35 additions & 0 deletions .agents/skills/tma-knowledge-search/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
name: tma-knowledge-search
description: Search the SpawnDock TMA knowledge API for Telegram Mini App and SpawnDock-specific implementation guidance. Use when Codex needs authoritative TMA workflow details, Telegram WebApp API usage, SpawnDock TMA template behavior, or wants to verify how a feature should be built for Telegram Mini Apps before answering or coding.
---

# TMA Knowledge Search

Use this skill when local repo context is not enough for a Telegram Mini App question and the answer should come from the SpawnDock TMA knowledge base.

## Workflow

1. Form a focused English query about the TMA implementation detail you need.
2. Run `scripts/search_tma_knowledge.py "<query>"`.
3. Read the returned `answer` first, then inspect any `sources`.
4. Use the API result as the primary TMA-specific reference in your answer or implementation plan.

## Query Rules

- Prefer English queries even if the user writes in another language.
- Ask about one concrete problem at a time.
- Include key TMA terms in the query: `Telegram Mini App`, `WebApp`, `MainButton`, `theme`, `viewport`, `SpawnDock`, `Next.js template`, and similar domain words when relevant.
- Re-query with a narrower prompt if the first result is generic.
- Avoid unnecessary repeat calls: the endpoint can rate-limit quickly on the free tier.

## Output Handling

- Treat the API response as TMA-specific guidance, not as a generic web best-practices source.
- If the API returns no useful sources, say that clearly and fall back to repo code or official Telegram docs as needed.
- Keep citations lightweight: mention the knowledge API result and summarize the relevant guidance rather than dumping raw JSON.

## Resources

- `scripts/search_tma_knowledge.py`: sends the POST request and prints a readable summary or raw JSON.
- The script automatically uses `SPAWNDOCK_API_TOKEN`, `API_TOKEN`, or the nearest `spawndock.config.json` `apiToken` when available.
- `references/api.md`: request and response contract for the knowledge endpoint.
7 changes: 7 additions & 0 deletions .agents/skills/tma-knowledge-search/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
interface:
display_name: "TMA Knowledge Search"
short_description: "Search TMA implementation knowledge"
default_prompt: "Use $tma-knowledge-search to look up Telegram Mini App and SpawnDock TMA implementation details before answering."

policy:
allow_implicit_invocation: true
44 changes: 44 additions & 0 deletions .agents/skills/tma-knowledge-search/references/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# SpawnDock TMA Knowledge API

Use this endpoint when you need Telegram Mini App or SpawnDock-specific implementation guidance:

```bash
curl -X POST \
'https://spawn-dock.w3voice.net/knowledge/api/v1/search' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"query": "How do I use MainButton in a Telegram Mini App?",
"locale": "en"
}'
```

## Request

- Method: `POST`
- URL: `https://spawn-dock.w3voice.net/knowledge/api/v1/search`
- Content-Type: `application/json`
- Body fields:
- `query` string, required
- `locale` string, optional in practice for the script, default `en`

## Observed response shape

```json
{
"answer": "Human-readable answer",
"sources": [],
"meta": {
"locale_requested": "en"
}
}
```

## Notes

- The skill defaults to `locale=en`.
- `Authorization: Bearer <API_TOKEN>` is optional and enables the higher-tier limits when the token is valid.
- SpawnDock bootstrap can write that token into `spawndock.config.json` as `apiToken` and into `.env.local` as `SPAWNDOCK_API_TOKEN`.
- The API can return an empty `sources` array.
- Use the answer as TMA-specific guidance, then inspect `sources` when present.
- The endpoint can return `429 rate_limit exceeded (minute)` after a small number of requests on the free tier.
183 changes: 183 additions & 0 deletions .agents/skills/tma-knowledge-search/scripts/search_tma_knowledge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#!/usr/bin/env python3
import argparse
import json
import os
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path


API_URL = "https://spawn-dock.w3voice.net/knowledge/api/v1/search"


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Query the SpawnDock TMA knowledge API."
)
parser.add_argument("query", help="Knowledge search query")
parser.add_argument("--locale", default="en", help="Response locale (default: en)")
parser.add_argument(
"--api-token",
help="Optional Bearer token override. Defaults to SPAWNDOCK_API_TOKEN/API_TOKEN or spawndock.config.json",
)
parser.add_argument(
"--config",
help="Optional path to spawndock.config.json. Defaults to the nearest config found from cwd upward.",
)
parser.add_argument(
"--timeout",
type=float,
default=20.0,
help="HTTP timeout in seconds (default: 20)",
)
parser.add_argument(
"--raw",
action="store_true",
help="Print raw JSON response instead of a formatted summary",
)
parser.add_argument(
"--retries",
type=int,
default=2,
help="Retry count for transient HTTP 5xx failures (default: 2)",
)
return parser.parse_args()


def find_config_path(explicit_path: str | None) -> Path | None:
if explicit_path:
path = Path(explicit_path).expanduser()
return path if path.is_file() else None

for base in [Path.cwd(), *Path.cwd().parents]:
candidate = base / "spawndock.config.json"
if candidate.is_file():
return candidate

return None


def read_config_api_token(config_path: Path | None) -> str | None:
if config_path is None:
return None

try:
data = json.loads(config_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return None

token = data.get("apiToken")
return token.strip() if isinstance(token, str) and token.strip() else None


def resolve_api_token(cli_token: str | None, config_path: Path | None) -> str | None:
if cli_token and cli_token.strip():
return cli_token.strip()

for key in ("SPAWNDOCK_API_TOKEN", "API_TOKEN"):
value = os.environ.get(key, "").strip()
if value:
return value

return read_config_api_token(config_path)


def request_knowledge(
query: str,
locale: str,
timeout: float,
retries: int,
api_token: str | None,
) -> dict:
payload = json.dumps({"query": query, "locale": locale}).encode("utf-8")
for attempt in range(retries + 1):
headers = {
"accept": "application/json",
"content-type": "application/json",
}
if api_token:
headers["authorization"] = f"Bearer {api_token}"

req = urllib.request.Request(
API_URL,
data=payload,
headers=headers,
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=timeout) as response:
charset = response.headers.get_content_charset() or "utf-8"
return json.loads(response.read().decode(charset))
except urllib.error.HTTPError as exc:
if exc.code < 500 or attempt == retries:
raise
time.sleep(min(2**attempt, 5))

raise RuntimeError("Unreachable retry loop")


def format_response(data: dict) -> str:
lines: list[str] = []
answer = data.get("answer")
sources = data.get("sources") or []
meta = data.get("meta") or {}

lines.append("Answer:")
lines.append(answer if answer else "(empty)")

if sources:
lines.append("")
lines.append("Sources:")
for idx, source in enumerate(sources, start=1):
if isinstance(source, dict):
title = source.get("title") or source.get("name") or f"Source {idx}"
url = source.get("url") or source.get("href") or ""
snippet = source.get("snippet") or source.get("text") or ""
line = f"{idx}. {title}"
if url:
line += f" - {url}"
lines.append(line)
if snippet:
lines.append(f" {snippet}")
else:
lines.append(f"{idx}. {source}")

if meta:
lines.append("")
lines.append("Meta:")
lines.append(json.dumps(meta, ensure_ascii=False, sort_keys=True))

return "\n".join(lines)


def main() -> int:
args = parse_args()
config_path = find_config_path(args.config)
api_token = resolve_api_token(args.api_token, config_path)
try:
data = request_knowledge(
args.query,
args.locale,
args.timeout,
args.retries,
api_token,
)
except urllib.error.HTTPError as exc:
body = exc.read().decode("utf-8", errors="replace")
print(f"HTTP error: {exc.code}\n{body}", file=sys.stderr)
return 1
except urllib.error.URLError as exc:
print(f"Request failed: {exc}", file=sys.stderr)
return 1

if args.raw:
print(json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True))
else:
print(format_response(data))
return 0


if __name__ == "__main__":
raise SystemExit(main())
68 changes: 68 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# SpawnDock TMA Template

You are an AI agent working inside the SpawnDock Telegram Mini App template.

Your job is to build and improve a production-ready Telegram Mini App in this repository. Treat this as a real TMA project, not as a generic web app and not as a docs-only exercise.

## Default Mode

- Work end-to-end inside the repo: design, implement, and validate when the user is asking for a result.
- Prefer concrete code and file changes over abstract advice unless the user explicitly wants brainstorming only.
- If requirements are incomplete, choose the smallest sensible TMA-first default and keep moving.
- Ask a question only when missing information would materially change the product flow or create a high risk of doing the wrong work.

## Project Contract

- Preserve the current stack: Next.js, App Router, TypeScript, SpawnDock scripts, and the existing Telegram/TMA integrations.
- Do not replace the framework, routing model, or core dev workflow unless the user explicitly asks for that change.
- Prefer changes that fit the existing `src/app` structure, shared styling approach, and `spawndock/*.mjs` wrappers.
- Use `pnpm` commands, not `npm`, unless a task explicitly requires otherwise.

## TMA Rules

- Always treat the app as a Telegram Mini App first.
- Prefer mobile-first, touch-first UX and layouts that work well inside Telegram WebView.
- Use Telegram WebApp APIs where appropriate: `ready`, `expand`, `MainButton`, `BackButton`, `themeParams`, `HapticFeedback`, `openLink`, `openTelegramLink`, `sendData`, and viewport APIs.
- Respect Telegram theming. Do not hardcode colors where Telegram theme params should drive the UI.
- Avoid browser patterns that are fragile inside Telegram WebView: `alert()`, `confirm()`, `prompt()`, `target=\"_blank\"`, `window.open()`, and desktop-first navigation patterns.
- Do not introduce `BrowserRouter`-style assumptions for TMA navigation.

## TMA Knowledge Search

- When local repo context is not enough for a Telegram Mini App or SpawnDock-specific implementation question, use the local `tma-knowledge-search` skill before generic web search.
- In generated projects the local skill lives at `.agents/skills/tma-knowledge-search`.
- Query the skill in English and ask one focused implementation question at a time.
- Read the returned `answer` first, then inspect `sources` when they are present.
- SpawnDock bootstrap also mirrors the same skill into `~/.codex/skills/tma-knowledge-search` when possible so Codex can discover it natively.

## Dev Flow

- `pnpm run dev` is the primary local workflow. It starts the Next.js dev server and the SpawnDock dev tunnel together.
- `pnpm run dev:next` starts only the local Next.js server.
- `pnpm run dev:tunnel` starts only the SpawnDock tunnel client.
- `pnpm run agent` starts the Next.js server, the tunnel, and the local agent runtime launcher.
- If the user asks to run the project, preview the app, or get a tunnel URL, prefer `pnpm run dev`.
- Do not describe `pnpm run dev` as “just Next.js dev”; in this template it is the combined app-plus-tunnel flow.
- If `spawndock.config.json` or `spawndock.dev-tunnel.json` is missing or invalid, the project is probably not fully bootstrapped yet.

## Implementation Expectations

- Build only what is needed for the requested feature set.
- Keep the architecture simple, shippable, and easy to extend.
- Reuse existing components and patterns before introducing new abstractions.
- Add loading, empty, and error states for user-facing flows when they matter.
- Make the smallest set of changes that fully solves the task.

## Validation

- Run the narrowest relevant checks after changes.
- Prefer `pnpm run build` for code validation.
- Use `pnpm run dev` when the task depends on runtime behavior, preview behavior, or the SpawnDock tunnel.
- If you could not run a relevant check, say so explicitly.

## Success Criteria

- The result fits the current template and preserves its workflow.
- The app behaves like a proper Telegram Mini App and respects Telegram constraints.
- The main local development flow, especially `pnpm run dev`, remains intact.
- The code is ready for real iteration, not just for demo output.
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# SpawnDock TMA Template

- Treat this repository as a Telegram Mini App project first, not as a generic web app.
- Prefer `pnpm run dev` for the main local workflow because it starts both Next.js and the SpawnDock dev tunnel.
- When local repo context is not enough for a Telegram Mini App or SpawnDock-specific implementation question, use the local `tma-knowledge-search` skill at `.agents/skills/tma-knowledge-search`.
- Query that skill in English, keep the question focused, and use its answer before falling back to generic web guidance.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ npx -y @spawn-dock/create@beta --token <pairing-token> [project-dir]
- `pnpm run publish:github-pages` exports the app and deploys it to GitHub Pages.
- `pnpm run start` starts the production Next.js server.

## Agent Guidance

- `AGENTS.md` contains the base system instructions for AI agents working inside this template.
- `CLAUDE.md` provides Claude Code project memory and points it to the local TMA knowledge skill.
- `.agents/skills/tma-knowledge-search` contains the local Telegram Mini App / SpawnDock knowledge-search skill used by compatible agents.
- Agents should treat `pnpm run dev` as the main local flow because it starts both Next.js and the SpawnDock dev tunnel.
- Use `pnpm run dev:next` only when you explicitly want the local app server without the tunnel.
- SpawnDock bootstrap also mirrors the same skill into `~/.codex/skills/tma-knowledge-search` when preparing a local Codex setup.

## SpawnDock Flow

The starter expects a bootstrap step that writes `spawndock.config.json` and
Expand All @@ -42,7 +51,9 @@ ready to connect to `@spawn-dock/mcp` via `/mcp/sse`.
## Local Config

- `spawndock.config.json` contains preview/runtime data for the app.
- `spawndock.config.json` can also include `apiToken` for the TMA knowledge-search skill.
- `spawndock.dev-tunnel.json` contains tunnel connection data.
- `.env.local` can include `SPAWNDOCK_API_TOKEN` for the same knowledge-search access.
- `opencode.json` is generated during bootstrap for OpenCode.
- `.mcp.json` is generated during bootstrap for Claude Code.
- `spawndock/mcp.mjs` resolves `mcpServerUrl` from `spawndock.config.json`.
Expand Down
3 changes: 2 additions & 1 deletion spawndock.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
"previewHost": "",
"localPort": 3000,
"mcpServerUrl": "",
"mcpApiKey": ""
"mcpApiKey": "",
"apiToken": ""
}