From 78ae8f4807ac13c96a402a8020dcf11df2e8ef6d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 19:41:17 +0000 Subject: [PATCH] feat(skills): add an agent skill for each package Adds skills/, one skill per package in packages/, modeled on the ask-sonner skill from emilkowalski/skills: YAML frontmatter with a trigger-oriented description, then setup, which call to reach for, recipes, and a symptom/cause/fix troubleshooting table. Packages with a larger surface (about-system, manage-storage, code-tree-graph, verify-phone-sms, api2ai) also get an API.md holding the exhaustive prop, option, and env-var tables. Content is written from each package's source, not only its README, so the troubleshooting rows capture real behavior: - about-system: infoFunctions lives on the /api subpath, not the root export; the cache-clearing flag is --refresh, not --cache-clear - create-starter-app: two of the five menu template ids don't match any directory under starter-templates/, and the published files list can't reach the templates at all - verify-phone-sms: /api/verify is a stub that returns verified:true for any input until code storage is implemented - react-app-store-buttons: the package.json name and the README name disagree - api2ai: the directory here is the Next.js site; the CLI ships as the published npm package Also adds skills/README.md as an index with install commands and a note on adding skills for new packages, and links it from the root README. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015NP4dnGytHEr9swAFXfDn1 --- README.md | 11 +++ skills/README.md | 43 +++++++++ skills/ask-about-system/API.md | 73 +++++++++++++++ skills/ask-about-system/SKILL.md | 68 ++++++++++++++ skills/ask-api2ai/API.md | 80 ++++++++++++++++ skills/ask-api2ai/SKILL.md | 67 +++++++++++++ skills/ask-app-store-buttons/SKILL.md | 68 ++++++++++++++ skills/ask-cloudflare-to-claude-fix/SKILL.md | 70 ++++++++++++++ skills/ask-code-tree-graph/API.md | 86 +++++++++++++++++ skills/ask-code-tree-graph/SKILL.md | 53 +++++++++++ skills/ask-create-cloud-db/SKILL.md | 75 +++++++++++++++ skills/ask-create-starter-app/SKILL.md | 65 +++++++++++++ skills/ask-export-svg-typescript/SKILL.md | 67 +++++++++++++ skills/ask-git0/SKILL.md | 65 +++++++++++++ skills/ask-manage-storage/API.md | 46 +++++++++ skills/ask-manage-storage/SKILL.md | 72 ++++++++++++++ skills/ask-open-ready/SKILL.md | 54 +++++++++++ skills/ask-server-shell-setup/SKILL.md | 73 +++++++++++++++ skills/ask-shadcn-theme-menu/SKILL.md | 63 +++++++++++++ skills/ask-verify-phone-sms/API.md | 98 ++++++++++++++++++++ skills/ask-verify-phone-sms/SKILL.md | 75 +++++++++++++++ skills/ask-web2mobile/SKILL.md | 73 +++++++++++++++ 22 files changed, 1445 insertions(+) create mode 100644 skills/README.md create mode 100644 skills/ask-about-system/API.md create mode 100644 skills/ask-about-system/SKILL.md create mode 100644 skills/ask-api2ai/API.md create mode 100644 skills/ask-api2ai/SKILL.md create mode 100644 skills/ask-app-store-buttons/SKILL.md create mode 100644 skills/ask-cloudflare-to-claude-fix/SKILL.md create mode 100644 skills/ask-code-tree-graph/API.md create mode 100644 skills/ask-code-tree-graph/SKILL.md create mode 100644 skills/ask-create-cloud-db/SKILL.md create mode 100644 skills/ask-create-starter-app/SKILL.md create mode 100644 skills/ask-export-svg-typescript/SKILL.md create mode 100644 skills/ask-git0/SKILL.md create mode 100644 skills/ask-manage-storage/API.md create mode 100644 skills/ask-manage-storage/SKILL.md create mode 100644 skills/ask-open-ready/SKILL.md create mode 100644 skills/ask-server-shell-setup/SKILL.md create mode 100644 skills/ask-shadcn-theme-menu/SKILL.md create mode 100644 skills/ask-verify-phone-sms/API.md create mode 100644 skills/ask-verify-phone-sms/SKILL.md create mode 100644 skills/ask-web2mobile/SKILL.md diff --git a/README.md b/README.md index 0826d08c..c85834a7 100644 --- a/README.md +++ b/README.md @@ -102,3 +102,14 @@ **[template-docusaurus](starter-templates/template-docusaurus/)** - Docusaurus 3 docs template with offline Lunr search, OpenAPI plugin, and classic theme optimized for technical docs. `bun create starter-app` ยท `npx create-starter-app` + +### ๐Ÿง  Agent Skills + +Every package has a matching [Agent Skill](skills/) โ€” setup, the calls worth knowing, recipes, and a troubleshooting table, written from the source rather than the README. Install all of them, or just the one you need: + +```bash +npx skills@latest add https://github.com/OpenSourceAGI/dev-tools-starter-agent +npx skills@latest add https://github.com/OpenSourceAGI/dev-tools-starter-agent --skill ask-manage-storage +``` + +See [skills/README.md](skills/README.md) for the full index. diff --git a/skills/README.md b/skills/README.md new file mode 100644 index 00000000..86b91f8e --- /dev/null +++ b/skills/README.md @@ -0,0 +1,43 @@ +# Skills + +One [Agent Skill](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview) per package in this repo, in the same shape as [emilkowalski/skills](https://github.com/emilkowalski/skills): a `SKILL.md` with setup, the calls worth knowing, recipes, and a troubleshooting table โ€” plus an `API.md` for the packages with a large enough surface to warrant one. + +Each skill is written from the package's source, not just its README, so the troubleshooting rows cover the real gotchas (flags the README gets wrong, exports that live on a subpath, stubs that return success unconditionally). + +## Install + +All of them: + +```bash +npx skills@latest add https://github.com/OpenSourceAGI/dev-tools-starter-agent +``` + +Just one: + +```bash +npx skills@latest add https://github.com/OpenSourceAGI/dev-tools-starter-agent --skill ask-manage-storage +``` + +## Reference + +| Skill | Package | Covers | +| --- | --- | --- | +| [ask-about-system](./ask-about-system/SKILL.md) | `about-system-info` | The system-info CLI and library: blocks, settings file, cache, shell greeting | +| [ask-api2ai](./ask-api2ai/SKILL.md) | `api2ai-mcp-generator` | Generating MCP servers from OpenAPI, tool filtering, the three-layer security model | +| [ask-app-store-buttons](./ask-app-store-buttons/SKILL.md) | `react-app-store-buttons` | Download badges, `appId` vs `href`, native deep links, OS highlighting | +| [ask-cloudflare-to-claude-fix](./ask-cloudflare-to-claude-fix/SKILL.md) | `cloudflare-to-claude-fix` | Queue consumer, routine `/fire` API, secrets, retries and the DLQ | +| [ask-code-tree-graph](./ask-code-tree-graph/SKILL.md) | `code-tree-graph` | `DependencyGraph`, `FileTreeView`, `TypeTable`, and the AST engine | +| [ask-create-cloud-db](./ask-create-cloud-db/SKILL.md) | `create-cloud-db` | Turso database creation and the `.env` rewrite | +| [ask-create-starter-app](./ask-create-starter-app/SKILL.md) | `create-starter-app` | The template menu, how templates resolve, which ids actually exist | +| [ask-export-svg-typescript](./ask-export-svg-typescript/SKILL.md) | `export-svg-icons-typescript` | SVG folder โ†’ tree-shakable TS barrel, runtime color and size options | +| [ask-git0](./ask-git0/SKILL.md) | `git0-repo-downloader` | Search, download, auto-install, IDE launch, rate limits | +| [ask-manage-storage](./ask-manage-storage/SKILL.md) | `manage-storage` | S3 / R2 / B2 through one call, provider detection, edge credentials | +| [ask-open-ready](./ask-open-ready/SKILL.md) | `open-when-ready` | Dev-server wrapper: ready/error detection, flags, log locations | +| [ask-server-shell-setup](./ask-server-shell-setup/SKILL.md) | `server-shell-setup` | The bootstrap installer, components, fish aliases | +| [ask-shadcn-theme-menu](./ask-shadcn-theme-menu/SKILL.md) | `shadcn-theme-menu` | Theme provider and switchers, color themes vs dark mode | +| [ask-verify-phone-sms](./ask-verify-phone-sms/SKILL.md) | `verify-phone-sms` | SNS-backed SMS verification, endpoints, auth, VoIP blocking | +| [ask-web2mobile](./ask-web2mobile/SKILL.md) | `web2mobile-wrapper` | Website โ†’ Expo WebView app, asset generation, EAS build/submit | + +## Adding a skill for a new package + +Create `skills/ask-/SKILL.md` with YAML frontmatter โ€” `name` matching the directory, and a `description` that names the package, lists what the skill covers, and ends with a `Use when โ€ฆ` clause naming concrete symptoms. That description is the only thing an agent sees when deciding whether to load the skill, so it does the triggering work. Keep the body to setup โ†’ which call to reach for โ†’ recipes โ†’ a symptom/cause/fix table, and split exhaustive prop or option tables into `API.md`. diff --git a/skills/ask-about-system/API.md b/skills/ask-about-system/API.md new file mode 100644 index 00000000..ffcacd9c --- /dev/null +++ b/skills/ask-about-system/API.md @@ -0,0 +1,73 @@ +# about-system API Reference + +Exact CLI flags, block names, settings keys, and cache TTLs. + +## CLI + +| Argument | Description | +| --- | --- | +| *(none)* | Print every block in `display_order`. | +| `cpu,ram_used,โ€ฆ` | Positional comma-separated block list โ€” print only these. | +| `--json` | Emit the info object as JSON instead of the emoji line. | +| `--install` | Append the greeting to the detected shell's config file. | +| `--refresh` | Clear the cache before collecting. | +| `--set ` | Write a settings value, e.g. `--set colors.user blue`. | +| `--settings-show` | Print the current settings JSON. | +| `--settings-reset` | Restore default settings. | +| `--settings-init` | Write a fresh settings file. | +| `--help` | Usage. | + +## Entry points + +| Import | Exports | +| --- | --- | +| `about-system` | `getSystemInfo(options?)`, `loadCache()`, `saveCache(cache)` | +| `about-system/api` | `infoFunctions`, `getSystemInfo`, `loadCache`, `saveCache` | +| `about-system/cli` | CLI entry (`about-system` bin) | +| `about-system/types` | `SystemInfo`, `SystemInfoOptions`, `Platform`, `InfoContext`, `GetSystemInfoFunction`, `DisplaySystemInfoFunction`, `PlatformAvailability` | + +## Info blocks + +`infoFunctions` keys, also usable as CLI positional filters and `display_order` entries: + +| Block | Output | +| --- | --- | +| `user`, `hostname`, `device`, `kernel`, `os` | `๐Ÿ‘ค user`, `๐Ÿ  host`, `๐Ÿ’ป MacBook Pro`, `๐Ÿ”ง 5.15.0`, `โšก Ubuntu 22.04` | +| `cpu`, `gpu`, `bench`, `cpu_bench_info`, `gpu_bench`, `gpu_bench_info` | Model strings plus Geekbench lookups from the bundled `bench/*.json` | +| `disk_used`, `ram_used`, `memory_available`, `swap_used`, `mount_points` | `๐Ÿ“ 75%`, `๐Ÿ’พ 8/16GB`, and related storage/memory readouts | +| `top_process`, `load_average`, `uptime`, `users_logged_in` | `๐Ÿ” 15% chrome`, load, `โฑ๏ธ 2d 5h 30m` | +| `ip`, `iplocal`, `city`, `domain`, `isp`, `network_interfaces` | Public/local IP, geo-IP city, reverse DNS, ISP | +| `shell`, `pacman`, `ports`, `containers`, `services_running` | Shell, package managers, open ports, Docker containers, systemd services | +| `temperature`, `battery`, `screen_resolution` | Sensors and display | + +Note the two internal renames: `os` maps to `os_info`, `pacman` maps to `packages`. + +## Settings file + +- Linux/macOS: `~/.config/systeminfo-settings.json` +- Windows: `%APPDATA%\systeminfo-settings.json` + +| Key | Shape | Notes | +| --- | --- | --- | +| `display_order` | `string[][]` | Array of lines, each an array of block names. Controls order and line breaks. | +| `colors` | `{ [block]: color }` | `red`, `orange`, `yellow`, `green`, `blue`, `cyan`, `purple`, `magenta`, `gray`, `lightblue`; `multicolor` for `ports`. | +| `emojis` | `{ [block]: string }` | Include the trailing space, e.g. `"๐Ÿš€ "`. | +| `labels` | `{ [block]: string }` | Text label used when emojis are off. | +| `display` | `{ show_emojis, single_line, line_wrap_length }` | | +| `network` | `{ show_offline_message }` | | +| `advanced` | `{ debug }` | | + +## Cache + +File: `systeminfo-cache.json` in the OS temp dir (`os.tmpdir()`). + +| Block group | TTL | +| --- | --- | +| `top_process` | 5 s | +| `ram_used` | 10 s | +| `temperature` | 30 s | +| `disk_used`, `battery` | 1 min | +| `ip`, `ports`, `containers`, `services_running`, `network_interfaces` | 5 min | +| `pacman`, `mount_points` | 10 min | +| `kernel` | 1 h | +| `cpu`, `gpu`, `os`, `device` | 24 h | diff --git a/skills/ask-about-system/SKILL.md b/skills/ask-about-system/SKILL.md new file mode 100644 index 00000000..d0378b63 --- /dev/null +++ b/skills/ask-about-system/SKILL.md @@ -0,0 +1,68 @@ +--- +name: ask-about-system +description: Guide to about-system (packages/about-system-info), the cross-platform system-info CLI and library โ€” install and run it, pick specific info blocks, JSON output, the settings file (colors, emojis, labels, display order), the cache, the shell-greeting installer, and the programmatic API. Use when working with about-system or troubleshooting it โ€” blocks that print empty, a greeting that doesn't run on terminal start, stale or wrong values, `infoFunctions` import errors, or `--set` changes that seem ignored. +--- + +# Working With about-system + +The CLI and library in `packages/about-system-info`, published to npm as **`about-system`** (the directory name is not the package name). It prints 30+ system metrics as one emoji line, on Linux, macOS, Windows, and Android/Termux. Full block list, settings keys, and cache TTLs live in [API.md](API.md); read it when you need an exact block name, setting path, or default. + +## Setup + +```bash +npx about-system # run once, no install +npm install -g about-system # then: about-system +about-system --install # add it as a shell greeting +``` + +The package is **ESM-only** (`"type": "module"`) and ships four entry points: `.` (library), `./api` (raw info functions), `./cli`, `./types`. + +## Picking the right call + +| You want | Call | +| --- | --- | +| Everything, formatted | `about-system` | +| Only some blocks | `about-system cpu,ram_used,disk_used` โ€” positional, comma-separated, no flag | +| Machine-readable output | `about-system --json` | +| One value in a script/dashboard | `import { getSystemInfo } from "about-system"` โ†’ `(await getSystemInfo()).cpu` | +| One block, cheaply, no full sweep | `import { infoFunctions } from "about-system/api"` โ†’ `infoFunctions.cpu({ cache: {} })` | +| Run on every terminal launch | `about-system --install` | +| Change a color/emoji/label | `about-system --set colors.user blue` | +| Force fresh values | `about-system --refresh` | + +## Recipes + +**Programmatic, whole snapshot** โ€” `getSystemInfo()` is async, loads the on-disk cache itself, and returns a `SystemInfo` object: + +```ts +import { getSystemInfo } from "about-system"; +const info = await getSystemInfo(); +console.log(info.cpu, info.ram_used); +``` + +**Individual blocks** โ€” `infoFunctions` is a map keyed by block name (`cpu`, `ram_used`, `uptime`, `ports`, `containers`, โ€ฆ). Pass a context so repeated calls share a cache; blocks that shell out or hit the network return promises: + +```ts +import { infoFunctions } from "about-system/api"; +const context = { cache: {} }; +const cpu = infoFunctions.cpu(context); +const uptime = infoFunctions.uptime(); +``` + +**Customize the line** โ€” `--set ` writes into the settings JSON: `display.show_emojis false`, `colors.cpu orange`, `emojis.cpu "๐Ÿš€ "`, `labels.ram_used "Memory"`, `display_order` (edit the file directly for nested arrays). `--settings-show` prints the current file, `--settings-reset` restores defaults, `--settings-init` writes a fresh one. + +**Types** โ€” `import type { SystemInfo, SystemInfoOptions, Platform, InfoContext } from "about-system/types"`. + +## Troubleshooting + +| Symptom | Cause โ†’ fix | +| --- | --- | +| `infoFunctions is not exported` / undefined import | The root entry exports only `getSystemInfo`, `loadCache`, `saveCache` and types. `infoFunctions` lives in the `about-system/api` subpath โ€” the README's root import is wrong. | +| `--cache-clear` does nothing / unknown flag | That flag in the README doesn't exist. The real one is `--refresh`. | +| Values are stale (IP, disk, uptime) | Cached by design, per-block TTL (IP 5 min, CPU/OS/device 24 h, top process 5 s). Run `--refresh`, or delete `systeminfo-cache.json` in the OS temp dir. | +| A block prints empty | The underlying tool isn't on that platform/PATH (`docker` for `containers`, `nvidia-smi`/`system_profiler` for `gpu`, `ss`/`netstat` for `ports`). Empty is the intended fallback, not a crash โ€” drop the block from `display_order` if you don't want the gap. | +| Network blocks (`ip`, `city`, `isp`, `domain`) all blank | No outbound network, or the lookup timed out. They share one cached IP-info fetch; everything else still renders. | +| Greeting didn't appear after `--install` | The line is appended to the config of the shell that was detected (`~/.bashrc`, `~/.zshrc`, `~/.config/fish/config.fish`, `~/.config/nushell/config.nu`). Open a new shell, or if you use PowerShell add the printed line to `$PROFILE` yourself. | +| `--set` seems ignored | You set a key the renderer doesn't read (typo in the path) or the block isn't in `display_order`. Check with `--settings-show`; reset with `--settings-reset` if the file got malformed. | +| `ERR_REQUIRE_ESM` when importing | ESM-only package. Use `import`, or `await import("about-system")` from CJS. | +| Emoji render as boxes | Terminal font lacks the glyphs โ€” `about-system --set display.show_emojis false` falls back to text labels. | diff --git a/skills/ask-api2ai/API.md b/skills/ask-api2ai/API.md new file mode 100644 index 00000000..2addadf6 --- /dev/null +++ b/skills/ask-api2ai/API.md @@ -0,0 +1,80 @@ +# api2ai API Reference + +## CLI + +``` +api2ai [output-folder] [options] +``` + +| Option | Default | Description | +| --- | --- | --- | +| `--name ` | `api-mcp-server` | Server name | +| `--base-url ` | from spec | Override the API base URL | +| `--port ` | `3000` | Server port | +| `--allow-mutations` | off | Enable `POST`/`PUT`/`PATCH`/`DELETE` tools by default | +| `--include-tags ` | โ€” | Comma-separated allowlist of tags | +| `--exclude-tags ` | โ€” | Comma-separated denylist of tags | +| `--approve-writes` | off | Drop the approval requirement for restricted tools | +| `--help` | โ€” | Usage | + +## Programmatic + +```js +import { generateMcpServer, extractTools, loadOpenApiSpec } from "api2ai"; +``` + +| Function | Purpose | +| --- | --- | +| `generateMcpServer(spec, outDir, options)` | Write a complete server; resolves to `{ toolCount, โ€ฆ }` | +| `loadOpenApiSpec(pathOrUrl)` | Load and parse a spec | +| `extractTools(spec, options)` | Get the tool list without generating files | + +Options: `serverName`, `baseUrl`, `port`, `allowMutations`, `includeTags`, `excludeTags`, `excludeOperationIds`, `filterFn(tool)`. Each `tool` exposes at least `method`, `pathTemplate`, `operationId`, and `riskLevel`. + +## Risk levels + +| Level | Assigned when | Default | +| --- | --- | --- | +| `low` | `GET`/`HEAD`/`OPTIONS`, no dangerous keywords | Enabled, no approval | +| `medium` | Any mutating method | Blocked unless `ALLOW_RESTRICTED_TOOLS=true` | +| `high` | Admin, auth, billing, payments, tokens, secrets, user management | Blocked, approval required | + +## Generated output + +``` +my-mcp-server/ +โ”œโ”€โ”€ .env / .env.example / .gitignore +โ”œโ”€โ”€ package.json +โ”œโ”€โ”€ README.md +โ””โ”€โ”€ src/ + โ”œโ”€โ”€ index.js # server + tool registrations + โ”œโ”€โ”€ http-client.js # hardened HTTP client + โ”œโ”€โ”€ tools-config.js # tools with risk metadata + โ””โ”€โ”€ policy.js # runtime policy +``` + +## Generated server endpoints + +| Endpoint | Purpose | +| --- | --- | +| `GET /inspector` | Interactive tool testing UI (no auth โ€” restrict in production) | +| `POST /mcp` | MCP protocol endpoint | +| `GET /sse` | Server-Sent Events transport | +| `GET /health` | Health check | + +## Generated server environment variables + +| Variable | Default | Description | +| --- | --- | --- | +| `PORT` | `3000` | Server port | +| `NODE_ENV` | `development` | | +| `API_BASE_URL` | from spec | Upstream base URL | +| `API_KEY` | โ€” | Bearer token for the upstream API | +| `API_AUTH_HEADER` | โ€” | Custom auth header as `Name:value` | +| `MCP_URL` | โ€” | Public URL used by widgets | +| `ALLOWED_ORIGINS` | โ€” | CORS origins in production | +| `ALLOW_RESTRICTED_TOOLS` | `false` | Unlock medium/high-risk tools | +| `REQUIRE_APPROVALS` | `true` | Approval gate for restricted tools | +| `ALLOWED_API_HOSTS` | spec host | Outbound host allowlist | +| `REQUEST_TIMEOUT_MS` | `30000` | Outbound timeout | +| `MAX_RESPONSE_BYTES` | `10485760` | Response size cap | diff --git a/skills/ask-api2ai/SKILL.md b/skills/ask-api2ai/SKILL.md new file mode 100644 index 00000000..997473fa --- /dev/null +++ b/skills/ask-api2ai/SKILL.md @@ -0,0 +1,67 @@ +--- +name: ask-api2ai +description: Guide to api2ai (packages/api2ai-mcp-generator), which generates MCP servers from an OpenAPI spec using mcp-use โ€” the CLI and programmatic generateMcpServer/extractTools API, tool filtering by tag, method, risk or operationId, the three-layer security model (risk classification, runtime policy, HTTP hardening), generated server endpoints and env vars, and connecting to Claude or ChatGPT. Use when working with api2ai or troubleshooting it โ€” tools missing from the generated server, mutations blocked, approval prompts, 401s from the upstream API, host-allowlist or timeout errors, or the inspector exposing more than intended. +--- + +# Working With api2ai + +Generates a production-ready MCP server from any OpenAPI spec, on top of [mcp-use](https://mcp-use.com). Exact CLI flags, env vars, and generated file layout live in [API.md](API.md). + +**Where the code is**: `packages/api2ai-mcp-generator` in this repo holds the Next.js site/docs for the project โ€” it has no `bin` entry and no `generate-mcp-use-server.js`. The generator itself is the published **`api2ai`** package, so `npx api2ai โ€ฆ` resolves from the registry, not from this folder. Don't go looking for the CLI source here. + +## Setup + +```bash +npx api2ai https://petstore3.swagger.io/api/v3/openapi.json ./petstore-mcp --name petstore-api +cd petstore-mcp && npm install && npm start +``` + +Then open `http://localhost:3000/inspector` to exercise the tools. + +## The security model โ€” read this before filing a bug + +Three layers, and most "my tool is missing" reports are layer 1 or 2 working as designed: + +1. **Generation-time risk classification.** Every operation is labeled and the label is baked into `src/tools-config.js`: `low` for `GET`/`HEAD`/`OPTIONS`, `medium` for any mutating method, `high` for anything matching admin/auth/billing/payments/tokens/secrets/user-management patterns. Only `low` is enabled by default. `--allow-mutations` promotes medium to enabled. +2. **Runtime policy.** `checkToolPolicy()` runs before every outbound call and reads env at call time โ€” `ALLOW_RESTRICTED_TOOLS=true` unlocks medium/high, `REQUIRE_APPROVALS=false` drops the per-call approval gate. No regeneration needed. +3. **HTTP hardening.** Timeouts (`REQUEST_TIMEOUT_MS`, 30 s), a response cap (`MAX_RESPONSE_BYTES`, 10 MB), `redirect: 'error'`, a host allowlist (`ALLOWED_API_HOSTS`), and credential-header protection โ€” tool arguments can never override `Authorization`, `Cookie`, or `X-API-Key`; env-configured auth wins. + +## Picking the right filter + +| You want | How | +| --- | --- | +| Read-only tools only | default behavior โ€” don't pass `--allow-mutations` | +| Writes enabled | `--allow-mutations`, plus `--approve-writes` to skip the approval requirement | +| A subset by tag | `--include-tags public` / `--exclude-tags admin,internal` | +| Arbitrary predicates | programmatic `filterFn: (tool) => โ€ฆ` on `riskLevel`, `method`, or `pathTemplate` | +| To drop named operations | `excludeOperationIds: ["deleteUser", โ€ฆ]` | + +```js +import { generateMcpServer, extractTools, loadOpenApiSpec } from "api2ai"; + +const result = await generateMcpServer(specUrl, "./out", { + serverName: "my-api", + allowMutations: false, + includeTags: ["public"], + filterFn: (tool) => tool.riskLevel === "low", +}); +``` + +## Connecting a client + +Claude Desktop: `{"mcpServers": {"my-api": {"url": "http://localhost:3000/mcp"}}}`. The generated server also speaks the OpenAI Apps SDK, and exposes `/sse` and `/health` alongside `/mcp` and `/inspector`. + +## Troubleshooting + +| Symptom | Cause โ†’ fix | +| --- | --- | +| Expected tools are missing | They were classified `medium`/`high` and are disabled by default. Regenerate with `--allow-mutations`, or set `ALLOW_RESTRICTED_TOOLS=true` at runtime. | +| Every call asks for approval | `REQUIRE_APPROVALS` defaults to true for restricted tools. Set it to `false`, or generate with `--approve-writes`. | +| Tool count is far lower than the number of paths | Tag filters, `excludeOperationIds`, or a `filterFn` are trimming it โ€” and operations without an `operationId` may not survive naming. Check `src/tools-config.js` for what was actually emitted. | +| `401`/`403` from the upstream API | Auth comes from env (`API_KEY` bearer, or `API_AUTH_HEADER` as `Name:value`), never from tool arguments โ€” that's the credential-header protection. Set the env var. | +| "Host not allowed" | `ALLOWED_API_HOSTS` doesn't include the host you're calling; it defaults to the spec's host, so a `--base-url` override needs the allowlist updated too. | +| Requests time out / responses truncated | The 30 s timeout and 10 MB response cap. Raise `REQUEST_TIMEOUT_MS` / `MAX_RESPONSE_BYTES` deliberately. | +| Redirects fail instead of following | Intentional (`redirect: 'error'`) to block host pivots. Point `API_BASE_URL` at the final host. | +| Anyone can drive the tools | `/inspector` exposes every registered tool with no auth. Put it behind a reverse proxy or firewall in production, and set `ALLOWED_ORIGINS`. | +| Spec fails to load | `loadOpenApiSpec` takes a URL or a local JSON/YAML path; check it's OpenAPI (not raw Swagger 1.x) and that the URL isn't behind auth. | +| `npx api2ai` not found in this repo | Correct โ€” the folder here is the Next.js site. The CLI comes from the npm package. | diff --git a/skills/ask-app-store-buttons/SKILL.md b/skills/ask-app-store-buttons/SKILL.md new file mode 100644 index 00000000..545c99ad --- /dev/null +++ b/skills/ask-app-store-buttons/SKILL.md @@ -0,0 +1,68 @@ +--- +name: ask-app-store-buttons +description: Guide to the app store download badges in packages/react-app-store-buttons โ€” rendering DownloadAppButton for iOS, Android, Chrome, macOS, Windows, Linux and Snap, appId vs href, native deep links, autoHighlight OS detection, badge sizing, the styles import, and the getOS/buildStoreUrl/buildDeepLink helpers. Use when working with these buttons or troubleshooting them โ€” a badge that doesn't render, the wrong package name on install, deep links that don't open the store app, highlight not matching the user's OS, or SSR/hydration mismatches from userAgent detection. +--- + +# Working With the App Store Buttons + +The React badge components in `packages/react-app-store-buttons`. **Mind the name**: `package.json` declares `react-app-store-buttons`, while the README and root README call it `react-native-app-buttons`. Check the registry name before writing an install command; inside this monorepo, import from the workspace package. Badge images are bundled as assets, so nothing is fetched from a CDN. + +## Setup + +```tsx +import { DownloadAppButton } from "react-app-store-buttons"; +import "react-app-store-buttons/styles"; // only if you don't use Tailwind +``` + +`react` and `react-dom` (โ‰ฅ17) are peers. That's the whole install โ€” no provider, no config. + +## Picking the right props + +| You want | Props | +| --- | --- | +| The store URL built for you | `platform` + `appId` | +| Your own URL (self-hosted binary, landing page) | `platform` + `href` | +| The button glowing on the user's own OS | `autoHighlight` | +| The glow always on/off regardless of OS | `highlight={true \| false}` โ€” overrides `autoHighlight` | +| A different badge size | `height={56}` (px, default 56) | +| Same-tab navigation | `newTab={false}` | + +`appId` and `href` are mutually exclusive in the types โ€” pass exactly one. + +Platforms: `ios`, `android`, `chrome-extension`, `chrome-extension-white`, `macos`, `windows`, `linux`, `linux-snap`. + +```tsx + + + +``` + +## Recipes + +**What `appId` means per platform** โ€” iOS/macOS: the numeric App Store id. Android: the package name (`com.example.app`). Windows: the Microsoft Store product id (`9NBLGGH4NNS1`). Chrome: the extension id. Snap: the snap name. + +**Deep links** โ€” when the visitor's OS matches the platform, the anchor uses the native scheme so the store app opens directly instead of the web page: + +| Platform | Native | Web fallback | +| --- | --- | --- | +| iOS | `itms-apps://itunes.apple.com/app/id{id}` | `https://apps.apple.com/app/id{id}` | +| macOS | `macappstore://itunes.apple.com/app/id{id}` | `https://apps.apple.com/app/id{id}` | +| Android | `market://details?id={pkg}` | `https://play.google.com/store/apps/details?id={pkg}` | +| Windows | `ms-windows-store://pdp/?productid={id}` | `https://apps.microsoft.com/detail/{id}?rtc=1` | +| Chrome | โ€” | `https://chromewebstore.google.com/detail/{id}` | +| Snap | โ€” | `https://snapcraft.io/{name}` | + +**The helpers, standalone** โ€” `getOS()`, the `OS` enum, `platformMatchesOS(platform, os)`, `buildStoreUrl(platform, appId)`, `buildDeepLink(platform, appId)`, and `resolveHref(platform, appId, os)` are all exported if you want the URL logic without the badge. + +## Troubleshooting + +| Symptom | Cause โ†’ fix | +| --- | --- | +| `npm i react-native-app-buttons` installs the wrong thing (or 404s) | The README name and the `package.json` name (`react-app-store-buttons`) disagree. Use the name the registry actually serves; in this repo, depend on the workspace package. | +| Badge area is blank | Images ship in `dist` as bundled assets โ€” a bundler configured to ignore image imports will drop them. Make sure your build handles the package's assets, and that you imported the component from the package root. | +| Badge is unstyled / no glow | The stylesheet wasn't imported. Add `import "react-app-store-buttons/styles"` unless your app already provides the Tailwind classes. | +| Hydration mismatch with `autoHighlight` | Detection reads `navigator.userAgent`, which doesn't exist during SSR. Render the highlight after mount, or drive it with an explicit `highlight` prop computed on the client. | +| Highlight never matches | `getOS()` is user-agent based and can't see past a spoofed or reduced UA (Chrome's UA-reduction, privacy browsers). Treat `autoHighlight` as a nicety, not a guarantee. | +| Deep link does nothing on desktop | Native schemes only resolve on the matching OS with the store app installed. Everywhere else the component already falls back to the web URL โ€” that's the intended behavior. | +| TypeScript error about `appId` and `href` | The props are a union: one or the other, never both, never neither. | +| Badges look inconsistent in a row | Set the same `height` on all of them; source badge artwork has different intrinsic aspect ratios. | diff --git a/skills/ask-cloudflare-to-claude-fix/SKILL.md b/skills/ask-cloudflare-to-claude-fix/SKILL.md new file mode 100644 index 00000000..8a900a55 --- /dev/null +++ b/skills/ask-cloudflare-to-claude-fix/SKILL.md @@ -0,0 +1,70 @@ +--- +name: ask-cloudflare-to-claude-fix +description: Guide to cloudflare-to-claude-fix (packages/cloudflare-to-claude-fix), the Cloudflare Queue consumer that fires a Claude Code routine when a Workers build fails โ€” creating the queue and DLQ, enabling build Event Subscriptions, the routine /fire API and its beta header, the ROUTINE_FIRE_URL / ROUTINE_FIRE_TOKEN / NOTIFY_WEBHOOK_URL secrets, retries, and local testing. Use when working with this Worker or troubleshooting it โ€” routines that never fire, 400/401/403/404/429 from the fire endpoint, messages piling up in the dead-letter queue, or build events that never reach the consumer. +--- + +# Working With cloudflare-to-claude-fix + +The Worker in `packages/cloudflare-to-claude-fix`. It consumes Cloudflare Workers **build events** from a queue, and for `status === "failed"` it POSTs the failure context to a Claude Code routine's `/fire` endpoint so an agent starts debugging. Requires Workers Paid (queues) and a Claude plan with Claude Code on the web. + +## Setup + +Five steps, in order โ€” skipping any one produces a silent no-op rather than an error: + +1. **Queues** โ€” `wrangler queues create workers-build-events` and `wrangler queues create workers-build-events-dlq`. +2. **Event Subscriptions** on the Worker you want watched: Workers & Pages โ†’ Settings โ†’ Event Subscriptions โ†’ queue `workers-build-events`, with build started/succeeded/failed/cancelled enabled. This consumer acks and discards everything that isn't a failure. +3. **A routine** at [claude.ai/code/routines](https://claude.ai/code/routines), pointed at the repo that Worker deploys from, with an **API trigger**. Generate the token (shown once) and copy the fire URL. +4. **Secrets** โ€” `wrangler secret put ROUTINE_FIRE_URL`, `ROUTINE_FIRE_TOKEN`, and optionally `NOTIFY_WEBHOOK_URL` (Slack or Discord incoming webhook). Never commit these. +5. **Deploy** โ€” `wrangler deploy`. + +## What the consumer does + +For each failed build it formats `build_id`, `worker_name`, `branch`, `commit_hash`, `author`, `timestamp`, and `error_messages` into one plaintext block (capped at 65,536 characters), POSTs it, and โ€” if `NOTIFY_WEBHOOK_URL` is set โ€” posts the returned `claude_code_session_url` to your chat channel. A failed POST calls `retry()`; after 3 attempts the message lands in the DLQ. + +## The `/fire` API + +``` +POST https://api.anthropic.com/v1/claude_code/routines/{routine_id}/fire +Authorization: Bearer sk-ant-oat01-โ€ฆ +anthropic-version: 2023-06-01 +anthropic-beta: experimental-cc-routine-2026-04-01 +Content-Type: application/json + +{ "text": "" } +``` + +Response: `{ "type": "routine_fire", "claude_code_session_id": โ€ฆ, "claude_code_session_url": โ€ฆ }`. + +| Status | Meaning | +| --- | --- | +| `400` | Missing beta header, text over 65,536 chars, or the routine is paused | +| `401` | Wrong or missing bearer token | +| `403` | Account lacks Claude Code on the web | +| `404` | Routine id not found | +| `429` | Daily run allowance exhausted | + +## Recipes + +**Local test** โ€” publish a synthetic failure and watch it flow: + +```bash +wrangler queues publish workers-build-events --message '{"build_id":"build_test001","status":"failed","worker_name":"my-api","branch":"feat/x","commit_hash":"abc1234","author":"you@example.com","error_messages":["Error: Cannot find module ./utils"],"timestamp":"2026-05-01T19:00:00Z"}' +wrangler tail cloudflare-to-claude-fix +``` + +**Rotate the token** โ€” regenerate it on the routine's API trigger (which immediately revokes the old one), then `wrangler secret put ROUTINE_FIRE_TOKEN` again. + +## Troubleshooting + +| Symptom | Cause โ†’ fix | +| --- | --- | +| Builds fail but nothing fires | Event Subscriptions aren't enabled on the *target* Worker, or point at a different queue. Verify with `wrangler tail` that messages arrive at all. | +| `400` from `/fire` | Usually the missing `anthropic-beta` header, or a payload over 65,536 characters (very long build logs) โ€” truncate before sending. Also check the routine isn't paused. | +| `401` | The token was rotated (generating a new one revokes the old immediately) or the secret wasn't re-put after rotation. | +| `403` | The account behind the token doesn't have Claude Code on the web. | +| `404` | `ROUTINE_FIRE_URL` has the wrong `trig_โ€ฆ` id, or the routine was deleted. | +| `429` | Daily routine-run allowance exhausted; fires resume the next day. Consider filtering which Workers publish build events. | +| Messages pile up in the DLQ | Three consecutive fire failures. Read one message to see which status code you're getting, fix the cause, then re-publish it to the main queue. | +| Successful builds trigger runs | They shouldn't โ€” only `status === "failed"` is acted on; everything else is acked. If you see otherwise, check for a second consumer bound to the same queue. | +| No Slack/Discord message | `NOTIFY_WEBHOOK_URL` unset or wrong; it's optional and failures there don't block the fire. | +| Secrets visible in the repo | They must only exist as Wrangler secrets. If one leaked, rotate the routine token immediately. | diff --git a/skills/ask-code-tree-graph/API.md b/skills/ask-code-tree-graph/API.md new file mode 100644 index 00000000..28cc4db0 --- /dev/null +++ b/skills/ask-code-tree-graph/API.md @@ -0,0 +1,86 @@ +# code-tree-graph API Reference + +## `` (server component) + +| Prop | Type | Default | Description | +| --- | --- | --- | --- | +| `paths` | `string[]` | required | Directories to analyze (absolute, or relative to `cwd`). | +| `descriptions` | `Record` | `{}` | Manual descriptions keyed by relative path. | +| `ignore` | `string[]` | `[]` | Names/patterns to exclude. | +| `ignoreFile` | `string` | โ€” | Path to a gitignore-style `.treeignore`. | +| `showLegend` | `boolean` | `true` | Show the toggle controls. | +| `showNpmImports` | `boolean` | `false` | Render external npm dependency nodes. | +| `showTypes` | `boolean` | `false` | Render type-definition nodes. | +| `showPrivateFunctions` | `boolean` | `false` | Render non-exported function nodes. | +| `showExportedFunctions` | `boolean` | `false` | Render exported function nodes. | +| `instructions` | `React.ReactNode` | built-in help | Replace the help panel content. | + +Node colors: entry points green, core modules blue, types purple, utils gray, npm deps orange. Interactions: drag to pan, Ctrl+scroll to zoom, click a node to jump to its file-tree row, hover for JSDoc/exports/signature, search to highlight. A GitHub URL or ZIP can be pasted to analyze a remote repo without cloning. + +## `` (server component) + +| Prop | Type | Default | Description | +| --- | --- | --- | --- | +| `paths` | `string[]` | required | Directories or files to scan. | +| `ghBase` | `string` | required | GitHub `tree/` URL for the scanned directory; file paths are appended to it. | +| `descriptions` | `Record` | `{}` | Manual descriptions by relative path. | +| `ignore` | `string[]` | `[]` | Names/patterns to exclude. | +| `ignoreFile` | `string` | โ€” | Path to a `.treeignore`. | +| `inferDescriptions` | `boolean` | `true` | Extract descriptions from leading JSDoc/comments. | +| `defaultImportFilter` | `"all" \| "local" \| "npm"` | โ€” | Initial import filter. | +| `defaultInternalFilter` | `"all" \| "declared-types" \| "exported-types" \| "functions" \| "classes"` | โ€” | Initial internals filter. | +| `defaultExportFilter` | `"all" \| "functions" \| "classes" \| "constants"` | โ€” | Initial export filter. | +| `defaultCollapseDepth` | `number` | โ€” | Initial collapse depth. | + +## `` (client component) + +```tsx + +``` + +## Programmatic API + +| Function | Signature | +| --- | --- | +| `generateFileTree` | `(packagesDir: string, descriptions?: Record, ignorePatterns?: Set, inferDescriptions?: boolean) => FileTreeNode[]` | +| `analyzeFileContent` | `(filename: string, source: string) => FileAnalysis` | +| `parseIgnoreFile` | `(path: string) => Set` | + +## Types + +```ts +interface FileTreeNode { + name: string; + type: "file" | "folder"; + path: string; // relative to scanned root + description?: string; + analysis?: FileAnalysis; + children?: FileTreeNode[]; + packageDependencies?: string[]; + packageExports?: AnalysisItem[]; +} + +interface FileAnalysis { + localImports: string[]; + localImportSymbols: { source: string; valueNames: string[]; typeNames: string[] }[]; + npmImports: string[]; + exports: AnalysisItem[]; + functions: AnalysisItem[]; + types: AnalysisItem[]; +} + +interface AnalysisItem { + name: string; + kind?: "function" | "class" | "constant" | "type"; + line?: number; + jsdoc?: string; + signature?: string; + properties?: TypeProperty[]; +} +``` + +## Runtime dependencies + +`@typescript-eslint/typescript-estree` (AST), `mermaid` (graph), `fuse.js` (search), `jszip` (remote repos), `marked` (JSDoc โ†’ Markdown), `@radix-ui/react-tooltip`, `lucide-react`, `svg-toolbelt` (pan/zoom). diff --git a/skills/ask-code-tree-graph/SKILL.md b/skills/ask-code-tree-graph/SKILL.md new file mode 100644 index 00000000..f2a73170 --- /dev/null +++ b/skills/ask-code-tree-graph/SKILL.md @@ -0,0 +1,53 @@ +--- +name: ask-code-tree-graph +description: Guide to code-tree-graph (packages/code-tree-graph), the dependency-graph and file-tree components for Fumadocs/Next.js โ€” mounting DependencyGraph, FileTreeView and TypeTable, the AST engine (generateFileTree, analyzeFileContent, parseIgnoreFile), path resolution, ignore patterns, and the CSS import. Use when working with code-tree-graph or troubleshooting it โ€” an empty or one-node graph, "window is not defined" / hydration errors from Mermaid, unstyled tables, paths that resolve differently in dev and build, or GitHub links pointing at the wrong file. +--- + +# Working With code-tree-graph + +The React components in `packages/code-tree-graph`, published as **`code-tree-graph`**. It parses TypeScript/JS with `@typescript-eslint/typescript-estree` at build time and renders the result as a Mermaid flowchart or a searchable file table. Full prop tables and node types live in [API.md](API.md). + +## Setup + +Three pieces: + +1. `npm i code-tree-graph`, with `react`, `react-dom`, `next`, and `fumadocs-core` present as peers. +2. **Import the stylesheet once** at the app root: `import "code-tree-graph/dist/index.css"`. Without it the tree table and tooltips render unstyled. +3. Use the components from a **server** component or MDX page. `DependencyGraph` and `FileTreeView` read the filesystem during render; only `TypeTable` (and the graph's interactive layer) is client-side. + +## Picking the right component + +| You want | Component | +| --- | --- | +| A visual map of what imports what | `` | +| A browsable table of files with exports/imports/JSDoc | `` | +| A prop/property table inside docs prose | `` | +| The parsed data, no UI | `generateFileTree(dir, descriptions, ignorePatterns, inferDescriptions)` | +| To analyze source you already have in memory | `analyzeFileContent(filename, sourceText)` | +| A `.gitignore`-style exclusion file | `parseIgnoreFile(path)` โ†’ pass the returned `Set` as `ignorePatterns` | + +## Recipes + +**Scoping the scan** โ€” `paths` are absolute or relative to `process.cwd()`, which for Next.js is the app directory, *not* the MDX file. In a monorepo that usually means `["../packages/core"]` from `apps/docs`. Verify the same relative path resolves under `next build`, which may run from a different cwd than `next dev`. + +**Trimming the graph** โ€” the node-type toggles all default to `false` (`showNpmImports`, `showTypes`, `showPrivateFunctions`, `showExportedFunctions`), so an out-of-the-box graph shows modules and their local imports only. Turn one on at a time; enabling all of them on a large package produces an unreadable chart. + +**Descriptions** โ€” `inferDescriptions` (default `true` on `FileTreeView`) pulls the leading JSDoc/comment of each file. Override individual entries with the `descriptions` map, keyed by path relative to the scanned root. + +**GitHub deep links** โ€” `ghBase` must point at the *tree* URL for the same directory you scanned (`https://github.com/user/repo/tree/master/packages/my-lib`), because file paths are appended to it verbatim. + +**Ignoring files** โ€” `ignore` takes names and patterns (`["node_modules", "dist", "*.test.ts"]`); `ignoreFile` points at a `.treeignore` parsed with gitignore semantics. Both feed the same exclusion set. + +## Troubleshooting + +| Symptom | Cause โ†’ fix | +| --- | --- | +| Graph or tree renders empty | `paths` resolved to a directory that doesn't exist from the current cwd, or everything in it matched `ignore`. Log the resolved absolute path first; relative paths are resolved against `process.cwd()`. | +| Only one node, no edges | The scanned files import across package boundaries only. Local edges come from relative imports; cross-package edges show up as npm nodes โ€” enable `showNpmImports`. | +| `window is not defined` / `document is not defined` | Mermaid rendering leaked into the server pass. Keep `DependencyGraph` in a server component and let its client child handle rendering; don't wrap it in your own `"use client"` file. | +| Hydration mismatch on the graph | Same cause โ€” the SVG is produced on the client after mount. Don't render Mermaid output during SSR or snapshot it into server HTML. | +| Tables/tooltips look unstyled | `code-tree-graph/dist/index.css` was never imported, or your bundler dropped it. Import it in the root layout. | +| `fs`/`path` errors during build | A component that reads the filesystem got pulled into a client bundle. Only `TypeTable` is safe to import from `"use client"` code. | +| GitHub links 404 | `ghBase` points at the repo root instead of the scanned subdirectory, or at `blob/` instead of `tree/`. | +| Scan is slow on a big repo | Every file is parsed to an AST. Narrow `paths`, and exclude `node_modules`, `dist`, `.next`, and test files via `ignore`/`ignoreFile`. | +| Search finds nothing | Fuse.js fuzzy-matches names, imports, exports, JSDoc, and signatures โ€” if descriptions were never inferred (`inferDescriptions={false}`) there's less to match. | diff --git a/skills/ask-create-cloud-db/SKILL.md b/skills/ask-create-cloud-db/SKILL.md new file mode 100644 index 00000000..22021659 --- /dev/null +++ b/skills/ask-create-cloud-db/SKILL.md @@ -0,0 +1,75 @@ +--- +name: ask-create-cloud-db +description: Guide to create-cloud-db (packages/create-cloud-db), the CLI that creates a Turso database and writes TURSO_DATABASE_URL and TURSO_AUTH_TOKEN into .env โ€” the turso auth login prerequisite, naming the database, how the .env file is rewritten, and wiring the result into Drizzle. Use when working with create-cloud-db or troubleshooting it โ€” "not logged in" errors, a database that already exists, .env values that get overwritten or ignored, or Drizzle failing to connect with the generated credentials. +--- + +# Working With create-cloud-db + +The CLI in `packages/create-cloud-db`, published as **`create-cloud-db`**. It is a thin, deliberate wrapper around the Turso CLI: create the database, mint a token, write both values into `.env`. It does not manage schemas, migrations, or multiple environments. + +## Setup + +The Turso CLI must exist and be logged in **before** you run it โ€” the tool shells out to it: + +```bash +bun i -g turso && turso auth login # or: bun x turso auth login +``` + +Then: + +```bash +npm create cloud-db # prompts for a name +npx create-cloud-db myapp-db # or pass one +``` + +## What it does, in order + +1. Confirms you're authenticated with Turso. +2. Creates the database if it doesn't already exist. +3. Generates the database URL and an auth token via the Turso CLI. +4. **Overwrites** `TURSO_DATABASE_URL` and `TURSO_AUTH_TOKEN` in `.env` โ€” updating them in place if present, appending if not. Other keys in the file are preserved. + +Result: + +```env +TURSO_DATABASE_URL=libsql://your-db-name.region.turso.io +TURSO_AUTH_TOKEN=eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9โ€ฆ +``` + +## Recipes + +**Wire it into scripts** so teammates get the same setup path: + +```json +"db:create": "create-cloud-db", +"db:generate": "drizzle-kit generate", +"db:push": "drizzle-kit push", +"db:studio": "drizzle-kit studio" +``` + +**Drizzle config** โ€” dialect `turso`, with a local SQLite fallback so the project still runs before anyone has created a cloud database: + +```ts +export default defineConfig({ + dialect: "turso", + schema: "./src/lib/db/schema.ts", + out: "./drizzle", + dbCredentials: { + url: process.env.TURSO_DATABASE_URL || "file:./localdb.sqlite", + authToken: process.env.TURSO_AUTH_TOKEN, + }, +}); +``` + +## Troubleshooting + +| Symptom | Cause โ†’ fix | +| --- | --- | +| "not logged in" / auth error | `turso auth login` hasn't been run in this shell, or the session expired. The CLI won't log you in for you. | +| `turso: command not found` | Install it globally (`bun i -g turso`) or run the tool where `bun x turso` resolves. | +| Database already exists | Reuse it โ€” pass the same name and the CLI regenerates a token against the existing database rather than failing outright. Pick a new name if you wanted a fresh one. | +| My hand-edited `TURSO_*` values disappeared | Expected: those two keys are rewritten every run, which is how stale placeholders get cleaned up. Keep custom values under different key names. | +| App still can't connect after a successful run | The process didn't reload `.env` (restart the dev server), or the framework needs the vars prefixed/registered (Next.js server-only vars, Cloudflare `wrangler secret put` for deploys). Local `.env` is not uploaded anywhere. | +| Token works locally, fails in production | `.env` is local-only. Add both values to your host's secret store (Cloudflare secrets, Vercel env vars) separately. | +| Wrong region / latency | Region is chosen by the Turso CLI at creation time; recreate with the Turso CLI directly if you need a specific one. | +| Need a second database for staging | Run the CLI with a different name, then copy the printed values into the target environment yourself โ€” the tool only manages the single pair of keys in `.env`. | diff --git a/skills/ask-create-starter-app/SKILL.md b/skills/ask-create-starter-app/SKILL.md new file mode 100644 index 00000000..b24cbb65 --- /dev/null +++ b/skills/ask-create-starter-app/SKILL.md @@ -0,0 +1,65 @@ +--- +name: ask-create-starter-app +description: Guide to create-starter-app (packages/create-starter-app), the interactive scaffolder that copies a starter template out of starter-templates/ โ€” the arrow-key menu, how templates are resolved and copied, the package.json rewrite, and which template ids actually exist on disk. Use when working with create-starter-app or troubleshooting it โ€” ENOENT when copying a template, the menu offering a template that isn't there, node_modules being copied, "directory already exists", or the CLI failing when installed from npm instead of run inside the monorepo. +--- + +# Working With create-starter-app + +The scaffolder in `packages/create-starter-app`, published as **`create-starter-app`** (`bun create starter-app` / `npx create-starter-app`). It is deliberately tiny: pick a template from an arrow-key menu, name the project, and it copies a directory. + +## Setup + +```bash +bun create starter-app # or: npx create-starter-app +``` + +Node โ‰ฅ18, ESM. There are no flags or arguments โ€” everything is prompted. + +## How it resolves templates + +`bin/create-starter-app.js` resolves `../../../starter-templates` relative to itself, i.e. **the `starter-templates/` directory at the root of this monorepo**. It then: + +1. Shows the menu (โ†‘โ†“ to move, Enter to confirm, `q` to quit). +2. Asks for a project name, defaulting to the template id minus the `template-` prefix. +3. Refuses to continue if that directory already exists. +4. `cpSync`s the template recursively, filtering out `node_modules`, `.next`, and `dist`. +5. Rewrites the copied `package.json`: sets `name` to the project name, sets `private: true`, deletes `version`. + +Then it prints the next steps: `cd`, copy `.env.example`, install, `bun dev`. + +## Menu entries vs. what exists on disk + +The five entries in `TEMPLATES` do not all match directory names under `starter-templates/`: + +| Menu id | On disk? | +| --- | --- | +| `template-nextjs-betterauth-shadcn-drizzle` | yes | +| `template-fumadocs` | yes | +| `template-docusaurus` | yes | +| `template-nextjs-betterauth-shadcn-prisma` | **no** โ€” no such directory | +| `template-svelte-betterauth-drizzle-shadcn` | **no** โ€” the directory is `template-svelte-betterauth-shadcn-drizzle` (word order differs) | + +`starter-templates/template-vinext-betterauth-shadcn-themes-teams-stripe` exists but is **not** in the menu. Picking one of the two mismatched entries throws `ENOENT` from `cpSync`. Fixing it means editing the `id` fields in `TEMPLATES` (or renaming the directories) โ€” the menu labels are decorative, only `id` is used for the path. + +## Recipes + +**Scaffold without the CLI** โ€” since it's a plain recursive copy, this is equivalent: + +```bash +cp -r starter-templates/template-fumadocs my-docs && cd my-docs +# then edit package.json name/version yourself +``` + +**Add a template** โ€” create the directory under `starter-templates/`, then add an entry to `TEMPLATES` in `bin/create-starter-app.js` whose `id` is exactly the directory name. + +## Troubleshooting + +| Symptom | Cause โ†’ fix | +| --- | --- | +| `ENOENT โ€ฆ starter-templates/template-โ€ฆ` | The chosen menu entry's `id` doesn't match a real directory (see the table above). Pick a working template or correct the id. | +| Works in the repo, breaks when installed from npm | The path climbs three levels out of the package to reach the monorepo's `starter-templates/`, and `files` only publishes `bin` and `starters` (a directory that doesn't exist). From a global/npx install there's nothing to copy โ€” run it from a checkout. | +| `Error: directory "x" already exists` | By design; it never merges into an existing directory. Choose another name or remove the old one. | +| Copied project contains `node_modules` | Only `node_modules`, `.next`, and `dist` are filtered, and only by substring match. Other build output (`.svelte-kit`, `.turbo`, `coverage`) comes along โ€” delete it after copying. | +| Menu doesn't respond to arrow keys | It reads raw stdin; it needs a real TTY. It won't work through a pipe, in a non-interactive CI step, or inside some editor terminals. | +| `version` missing from the new `package.json` | Intentional โ€” it's deleted so you set your own. `private: true` is also set to prevent accidental publishes. | +| The generated app won't start | Templates carry their own prerequisites (`.env.example` values, a database, a Cloudflare account). Read the template's own README before `bun dev`. | diff --git a/skills/ask-export-svg-typescript/SKILL.md b/skills/ask-export-svg-typescript/SKILL.md new file mode 100644 index 00000000..a1439a8d --- /dev/null +++ b/skills/ask-export-svg-typescript/SKILL.md @@ -0,0 +1,67 @@ +--- +name: ask-export-svg-typescript +description: Guide to export-svg-typescript (packages/export-svg-icons-typescript), the CLI that turns a folder of SVGs into a tree-shakable TypeScript barrel of icon functions โ€” the -i / -o flags, the generated function options (colors, size, width, height, raw), JSDoc tooltip previews, and regenerating after adding icons. Use when working with export-svg-typescript or troubleshooting it โ€” icons that don't recolor, an index.ts written to the wrong place, camelCase export names you can't guess, missing icons after adding files, or SVG strings rendering as text in JSX. +--- + +# Working With export-svg-typescript + +The generator in `packages/export-svg-icons-typescript`, published as **`export-svg-typescript`**. It reads a folder of `.svg` files and writes a single `index.ts` exporting one function per icon โ€” no SVG loader, no bundler plugin, no framework coupling. Each export returns an SVG **string** (or an `` tag) at call time, so color and size are runtime arguments. + +## Setup + +```bash +npx export-svg-typescript -i ./src/icons -o ./src/icons/index.ts +npm install -g export-svg-typescript # optional +``` + +Only two flags exist: + +| Flag | Default | Meaning | +| --- | --- | --- | +| `-i ` | `./svg` | Folder containing the source SVGs | +| `-o ` | `./index.ts` | Output file to write | + +Both defaults are relative to the current working directory, so always pass `-o` explicitly unless you're standing in the icon folder. Add it as a script โ€” `"icons": "npx export-svg-typescript -i ./src/icons -o ./src/icons/index.ts"` โ€” and re-run it whenever icons change; the output is generated, not hand-maintained. + +## Using the generated icons + +File names become camelCase exports: `icon-chat.svg` โ†’ `iconChat`, `loading-double-ring.svg` โ†’ `loadingDoubleRing`. + +```ts +import { loadingDoubleRing } from "./icons"; +loadingDoubleRing({ size: 200, colors: ["#5345bb"] }); +``` + +| Option | Type | Effect | +| --- | --- | --- | +| `colors` | `string[]` | Replaces hex colors **in order of first appearance** in the source SVG | +| `size` | `number \| string` | Sets width and height together (overrides both) | +| `width` / `height` | `number` | Set individually | +| `raw` | `boolean` | `true` returns the raw SVG string; otherwise an `` tag with a data-URI source | + +Each export carries a JSDoc block with a base64 preview of the icon, so hovering it in the editor shows the actual image. + +## Recipes + +**Rendering in React** โ€” the return value is a string, not an element. Either use the `` form directly, or inject the raw SVG: + +```tsx + +``` + +**Tree shaking** โ€” the barrel is a flat list of `export const` arrow functions, so bundlers drop the icons you never import. Don't re-export it through a wrapper that imports `*`. + +**Theming** โ€” pass `["currentColor"]` as the first color to inherit CSS color, provided the source SVG's first fill is the one you want replaced. + +## Troubleshooting + +| Symptom | Cause โ†’ fix | +| --- | --- | +| Colors don't change | The SVG uses named colors, `url(#gradient)` fills, or CSS classes instead of inline hex. Replacement matches hex values in appearance order only. | +| Wrong element got recolored | `colors` maps positionally: the first array entry replaces the first hex found, and so on. Count the hex values in the source file. | +| `index.ts` appeared somewhere unexpected | `-o` defaults to `./index.ts` relative to your cwd. Pass the full intended path. | +| A newly added icon isn't exported | The generator is a one-shot build step, not a watcher. Re-run it. | +| Can't guess the export name | It's the filename camelCased, with separators dropped (`my-cool_icon.svg` โ†’ `myCoolIcon`). Open the generated file to confirm. | +| SVG markup shows up as literal text in JSX | You rendered the string directly. Use the `` form or `dangerouslySetInnerHTML`. | +| Editor tooltips have no preview image | The preview is a base64 data URI in the JSDoc โ€” very large SVGs make it unwieldy, and some editors truncate hover cards. | +| Output file is huge | Every icon's markup is inlined as a template literal. That's the tradeoff for zero bundler config; keep the source folder scoped to icons you actually ship. | diff --git a/skills/ask-git0/SKILL.md b/skills/ask-git0/SKILL.md new file mode 100644 index 00000000..bf1cf16c --- /dev/null +++ b/skills/ask-git0/SKILL.md @@ -0,0 +1,65 @@ +--- +name: ask-git0 +description: Guide to git0 (packages/git0-repo-downloader), the GitHub repo search-download-setup CLI โ€” the g / gg / git0 / fm commands, searching by keyword vs downloading by URL or owner/repo, release binaries for your platform, automatic dependency install per project type, IDE launch, and GITHUB_TOKEN rate limits. Use when working with git0 or troubleshooting it โ€” "API rate limit exceeded", searches returning nothing, downloads landing in a suffixed folder, dependency install or IDE launch not firing, or release assets missing for a platform. +--- + +# Working With git0 + +The CLI in `packages/git0-repo-downloader`, published as **`git0`**. It replaces the clone โ†’ cd โ†’ install โ†’ open dance with one command: it downloads a repo tarball (no `.git` history, extracted while streaming, so faster than `git clone`), detects the project type, installs dependencies, and opens your editor. + +## Setup + +```bash +npm install -g git0 # or: bun install -g git0 +npx git0 facebook/react # or use it without installing +``` + +Four bins ship with it: `git0`, `g`, `gg` (all the same CLI) and `fm`. + +## Picking the right invocation + +| You want | Command | +| --- | --- | +| A repo you know the URL of | `g https://github.com/facebook/react` | +| A repo by `owner/repo` | `git0 facebook/react` | +| To find one by keyword | `g react starter` โ€” fuzzy search, then pick from the list | +| A prebuilt binary instead of source | run the search/download; when the repo has releases you're asked to choose binary, source, or both | +| A one-liner other people can paste | `npx git0 ` โ€” only Node required | + +## Recipes + +**What happens after download** โ€” the folder lands in the current directory, project type is detected, dependencies install, the IDE opens (deferred ~500 ms so extraction finishes first), and for Node projects the dev server starts. + +**Project-type detection** + +| Detected by | Install step | +| --- | --- | +| `package.json` | `bun install`, falling back to `npm install` | +| `Dockerfile` / `docker-compose.yml` | `docker-compose up -d` or `docker build` | +| `requirements.txt` / `setup.py` | virtualenv + `pip install` | +| `Cargo.toml` | `cargo build` | +| `go.mod` | `go mod tidy` | + +**IDE launch order** โ€” Antigravity, Cursor, Windsurf, VS Code, VS Code Server web UI, Neovim, WebStorm; the first one found on `PATH` wins. + +**Raise the rate limit** โ€” unauthenticated GitHub search is 60 requests/hour. Export a token for 5,000: + +```bash +export GITHUB_TOKEN=ghp_โ€ฆ +``` + +**Name collisions** โ€” if the target directory exists, git0 appends a counter (`react-2`, `react-3`) rather than overwriting. + +## Troubleshooting + +| Symptom | Cause โ†’ fix | +| --- | --- | +| `API rate limit exceeded` | 60 req/h unauthenticated. Set `GITHUB_TOKEN` in your shell profile. | +| Search returns nothing for a repo you know exists | Keyword search hits GitHub's search index, which excludes very new or low-signal repos. Pass `owner/repo` or the full URL to bypass search entirely. | +| Project ended up in `name-2/` | A directory with that name already existed โ€” this is the conflict handling, not a bug. Remove or rename the old one first. | +| Dependencies didn't install | No recognized manifest at the repo root (monorepo with everything under `packages/`, or a non-listed ecosystem). Run the install yourself in the right subdirectory. | +| Editor didn't open | None of the supported editors are on `PATH`. Launch manually, or add your editor's CLI shim (VS Code: "Shell Command: Install 'code' command"). | +| "No packages available for your platform" | The release has assets, but none matching your OS/arch. Choose the source download instead. | +| Private repo 404s | The token needs repo scope, and fine-grained tokens must grant access to that specific repository. | +| Downloaded tree has no git history | By design โ€” the tarball skips `.git`. Run `git init` (or `git clone`) if you need history. | +| `command not found: g` after global install | The global bin dir isn't on `PATH`, or `g` collides with an existing alias/function in your shell. Use `git0` explicitly. | diff --git a/skills/ask-manage-storage/API.md b/skills/ask-manage-storage/API.md new file mode 100644 index 00000000..0283e0d1 --- /dev/null +++ b/skills/ask-manage-storage/API.md @@ -0,0 +1,46 @@ +# manage-storage API Reference + +`manageStorage(action, options?)` โ€” one overloaded function; the return type follows the action. + +## Actions + +| Action | Required options | Returns | +| --- | --- | --- | +| `upload` | `key`, `body` | `{ success: true, key, โ€ฆ }` | +| `download` | `key` | The object's content | +| `delete` | `key` | `{ success: true, key }` | +| `deleteAll` | โ€” | `{ success: true, count }` | +| `list` | โ€” | `string[]` of keys | +| `copy` | `key`, `destinationKey` | `{ success: true, sourceKey, destinationKey }` | +| `rename` | `key`, `destinationKey` | `{ success: true, oldKey, newKey }` | + +## Options + +| Option | Type | Notes | +| --- | --- | --- | +| `key` | `string` | Object key/path. Required except for `list` / `deleteAll`. | +| `destinationKey` | `string` | Target key for `copy` / `rename`. | +| `body` | `string \| Buffer \| Stream` | Upload payload. | +| `provider` | `"amazon" \| "cloudflare" \| "backblaze"` | Forces a provider; otherwise auto-detected from env. | +| `BUCKET_NAME` | `string` | Overrides `*_BUCKET_NAME`. | +| `ACCESS_KEY_ID` | `string` | Overrides `*_ACCESS_KEY_ID`. | +| `SECRET_ACCESS_KEY` | `string` | Overrides `*_SECRET_ACCESS_KEY`. | +| `BUCKET_URL` | `string` | Overrides `*_BUCKET_URL` (the S3 endpoint). | + +## Environment variables + +| Provider | Variables | +| --- | --- | +| Cloudflare R2 | `CLOUDFLARE_BUCKET_NAME`, `CLOUDFLARE_ACCESS_KEY_ID`, `CLOUDFLARE_SECRET_ACCESS_KEY`, `CLOUDFLARE_BUCKET_URL` | +| Backblaze B2 | `BACKBLAZE_BUCKET_NAME`, `BACKBLAZE_ACCESS_KEY_ID`, `BACKBLAZE_SECRET_ACCESS_KEY`, `BACKBLAZE_BUCKET_URL` | +| Amazon S3 | `AMAZON_BUCKET_NAME`, `AMAZON_ACCESS_KEY_ID`, `AMAZON_SECRET_ACCESS_KEY`, `AMAZON_BUCKET_URL`, `AMAZON_REGION` | + +## Exported types + +`Provider`, `Action`, `StorageOptions`, `UploadResult`, `DeleteResult`, `DeleteAllResult`, `CopyResult`, `RenameResult`. + +## Provider notes + +- **R2** โ€” zero egress fees; endpoint is `https://.r2.cloudflarestorage.com`; region is effectively `auto`. +- **B2** โ€” cheapest storage; endpoint is region-numbered (`s3.us-west-004.backblazeb2.com`); use the *application key*, not the master key. +- **S3** โ€” `AMAZON_REGION` must match the bucket's region or requests get a `PermanentRedirect`. diff --git a/skills/ask-manage-storage/SKILL.md b/skills/ask-manage-storage/SKILL.md new file mode 100644 index 00000000..c59c198e --- /dev/null +++ b/skills/ask-manage-storage/SKILL.md @@ -0,0 +1,72 @@ +--- +name: ask-manage-storage +description: Guide to manage-storage (packages/manage-storage), the unified S3 / Cloudflare R2 / Backblaze B2 client โ€” provider auto-detection from env vars, the single manageStorage(action, options) call, upload/download/list/copy/rename/delete/deleteAll, runtime credential overrides for Workers and serverless, and return shapes. Use when working with manage-storage or troubleshooting it โ€” "no provider configured", credentials that work locally but not on the edge, wrong bucket or region, 403/SignatureDoesNotMatch, downloads coming back as the wrong type, or list returning nothing. +--- + +# Working With manage-storage + +The library in `packages/manage-storage`, published as **`manage-storage`**. One function, `manageStorage(action, options)`, built on `@aws-sdk/client-s3` and pointed at whichever S3-compatible provider your environment configures. Exact option and return types live in [API.md](API.md). + +## Setup + +Two pieces: + +1. **Credentials**, per provider, as env vars โ€” the provider is auto-detected from whichever prefix is present: + +```env +CLOUDFLARE_BUCKET_NAME=โ€ฆ CLOUDFLARE_ACCESS_KEY_ID=โ€ฆ CLOUDFLARE_SECRET_ACCESS_KEY=โ€ฆ CLOUDFLARE_BUCKET_URL=https://.r2.cloudflarestorage.com +BACKBLAZE_BUCKET_NAME=โ€ฆ BACKBLAZE_ACCESS_KEY_ID=โ€ฆ BACKBLAZE_SECRET_ACCESS_KEY=โ€ฆ BACKBLAZE_BUCKET_URL=https://s3.us-west-004.backblazeb2.com +AMAZON_BUCKET_NAME=โ€ฆ AMAZON_ACCESS_KEY_ID=โ€ฆ AMAZON_SECRET_ACCESS_KEY=โ€ฆ AMAZON_BUCKET_URL=https://s3.amazonaws.com AMAZON_REGION=us-east-1 +``` + +2. **The import** โ€” `import { manageStorage } from "manage-storage"` (also available as the default export). ESM, TypeScript types bundled. + +## Picking the right call + +| You want | Call | +| --- | --- | +| Store bytes | `manageStorage("upload", { key, body })` โ€” `body` is a string, Buffer, or stream | +| Read bytes back | `manageStorage("download", { key })` โ€” resolves to the content | +| Every key in the bucket | `manageStorage("list")` โ€” returns `string[]`, no options needed | +| Duplicate an object | `manageStorage("copy", { key, destinationKey })` | +| Move an object | `manageStorage("rename", { key, destinationKey })` โ€” copy then delete | +| Remove one object | `manageStorage("delete", { key })` | +| Empty the bucket | `manageStorage("deleteAll")` โ€” returns `{ success, count }`; irreversible | +| A specific provider when several are configured | add `provider: "cloudflare" \| "amazon" \| "backblaze"` | +| Credentials that aren't in `process.env` | add `BUCKET_NAME`, `ACCESS_KEY_ID`, `SECRET_ACCESS_KEY`, `BUCKET_URL` to the options | + +## Recipes + +**Edge and Workers runtimes** โ€” there is no `process.env` to detect from, so pass everything explicitly from the request's `env` binding: + +```js +await manageStorage("upload", { + key, body: content, + provider: "cloudflare", + BUCKET_NAME: env.CLOUDFLARE_BUCKET_NAME, + ACCESS_KEY_ID: env.CLOUDFLARE_ACCESS_KEY_ID, + SECRET_ACCESS_KEY: env.CLOUDFLARE_SECRET_ACCESS_KEY, + BUCKET_URL: env.CLOUDFLARE_BUCKET_URL, +}); +``` + +**Folders** โ€” there are none. Keys are flat strings; `documents/report.pdf` just contains a slash. Filter client-side: `(await manageStorage("list")).filter(k => k.startsWith("documents/"))`. + +**Batching** โ€” the calls are independent promises, so `Promise.all(files.map(f => manageStorage("upload", f)))` is the whole story. Nothing is rate-limited internally. + +**JSON round-trip** โ€” upload `JSON.stringify(obj)`, parse what `download` returns. Nothing is serialized for you. + +## Troubleshooting + +| Symptom | Cause โ†’ fix | +| --- | --- | +| "No storage provider configured" / provider undefined | None of the three env prefixes are fully set, or `.env` isn't loaded before the first call. Set all four vars for one provider, or pass `provider` + the runtime credential options. | +| Works locally, fails on Cloudflare Workers / Vercel Edge | Env-var auto-detection reads `process.env`, which edge runtimes don't populate. Pass credentials in the options object (see recipe above). | +| The wrong bucket or provider is used | Two providers are configured and detection picked the other one. Pass `provider` explicitly โ€” it overrides detection. | +| `403` / `SignatureDoesNotMatch` | Key/secret mismatch, or `BUCKET_URL` points at the wrong account or region endpoint. R2's endpoint is account-scoped (`https://.r2.cloudflarestorage.com`); B2's is region-scoped (`s3.us-west-004.โ€ฆ`). | +| `NoSuchBucket` | `*_BUCKET_NAME` is a bucket that doesn't exist in that account โ€” the library doesn't create buckets. | +| `PermanentRedirect` / region errors on S3 | `AMAZON_REGION` doesn't match the bucket's region. Set it to the bucket's actual region. | +| Download returns something other than a string | The body is streamed from the SDK; treat the result as content to hand along (e.g. straight into a `Response`) rather than assuming a `string` in every runtime. Parse only after you've confirmed the shape. | +| `list` comes back empty on a non-empty bucket | The credentials point at a different bucket, or the key prefix you expect lives under another account. Also note `list` returns keys only โ€” no sizes or timestamps. | +| `copy`/`rename` fails | `destinationKey` is missing, or the source key doesn't exist. `rename` is copy-then-delete and is not atomic โ€” an interrupted call can leave both copies. | +| Bundler complains about `@aws-sdk/client-s3` size | It's a real dependency, not inlined. Keep it external in serverless bundles rather than trying to tree-shake the client away. | diff --git a/skills/ask-open-ready/SKILL.md b/skills/ask-open-ready/SKILL.md new file mode 100644 index 00000000..59caef56 --- /dev/null +++ b/skills/ask-open-ready/SKILL.md @@ -0,0 +1,54 @@ +--- +name: ask-open-ready +description: Guide to open-ready (packages/open-when-ready), the dev-server wrapper that opens the browser when the server is ready and an AI assistant when it errors โ€” wrapping any CLI command, the --ai-base / --noAi / --noOpen / --pollDelay flags, how ready and error signals are detected, and where the log file goes. Use when working with open-ready or troubleshooting it โ€” the browser never opening, opening too early or at the wrong port, an AI tab opening on a harmless log line, or flags being swallowed by the wrapped command. +--- + +# Working With open-ready + +The wrapper in `packages/open-when-ready`, published as **`open-ready`** (bin `open-ready`, entry `open-when-ready.mjs`). It spawns your dev command, tees its output to a log file, polls that log, and reacts: browser on ready, AI assistant on error. + +## Setup + +```bash +npx open-ready npm run dev # no install +npm install -g open-ready # then: open-ready +``` + +Node โ‰ฅ18. It wraps anything that prints to stdout/stderr โ€” `next dev`, `vite`, `bun run dev`, a plain script. + +## Flags + +| Flag | Default | Effect | +| --- | --- | --- | +| `--ai-base ` | `https://perplexity.ai?q=` | Base URL opened on error, with the prompt appended | +| `--noAi` | `false` | Never open an AI tab on error | +| `--noOpen` | `false` | Never open the browser on ready | +| `--pollDelay ` | `1200` | How often the log is re-read | + +## How the signals work + +- **Error** โ€” a log line matching `error`, `failed`, `exception`, `SyntaxError`, or `โจฏ`. Up to ~1000 characters of surrounding context are extracted into a pre-filled prompt. +- **Ready** โ€” a line matching `ready - started server` or `Ready in Xms`. The port is then polled until it actually accepts connections before the browser opens. +- **Log file** โ€” `.next/port.log` for Next.js projects, otherwise `open-when-ready.log` in the current directory. + +## Recipes + +**Swap the assistant** โ€” `open-ready npm run dev --ai-base "https://chatgpt.com/?q="`. Any URL that accepts a query string works. + +**CI or headless** โ€” pass both `--noOpen` and `--noAi` and it degrades to a plain pass-through runner. + +**Slow-starting servers** โ€” raise `--pollDelay` to reduce log churn; the ready check waits for the port regardless, so a larger delay costs only detection latency. + +**In `package.json`** โ€” `"dev": "open-ready next dev"` keeps the behavior for everyone on the team. + +## Troubleshooting + +| Symptom | Cause โ†’ fix | +| --- | --- | +| Browser never opens | Your dev server prints a ready line the matcher doesn't recognize (only Next.js/Vite-style phrasings are matched). Confirm the phrasing in the log file, or open manually โ€” there is no custom-pattern flag. | +| Browser opens before the app responds | Rare, since the port is polled after the ready signal; if the framework prints ready before binding, raise `--pollDelay`. | +| An AI tab opens on a benign line | The error matcher is substring-based, so a log line containing "error" (e.g. an "0 errors" summary) trips it. Use `--noAi` for noisy servers. | +| Flags land on the wrapped command instead | Everything after the command is forwarded. Put `open-ready`'s own flags at the end (`open-ready npm run dev --noAi`) and check the wrapped tool isn't consuming them. | +| Nothing at all happens | The wrapped command exited immediately or wrote nothing to stdout/stderr. Run it bare first. | +| Log file keeps growing | It's a plain append-only file โ€” delete `open-when-ready.log` / `.next/port.log` between runs if size matters, and gitignore it. | +| Output looks buffered or colorless | The child's output is piped, so tools that detect a TTY may disable colors or batch writes. Force color with the tool's own flag (`--color`, `FORCE_COLOR=1`). | diff --git a/skills/ask-server-shell-setup/SKILL.md b/skills/ask-server-shell-setup/SKILL.md new file mode 100644 index 00000000..813fd05d --- /dev/null +++ b/skills/ask-server-shell-setup/SKILL.md @@ -0,0 +1,73 @@ +--- +name: ask-server-shell-setup +description: Guide to server-shell-setup (packages/server-shell-setup), the one-command dev-environment bootstrap for fish, nushell, nvim, helix, node via Volta, bun, docker, starship, code-server and more โ€” interactive vs unattended installs, selecting individual components, the fish aliases it adds, and the supported distros. Use when working with server-shell-setup or troubleshooting it โ€” the installer aborting on a fresh server, sudo or password prompts, a shell that doesn't become the default, docker rootless issues, or components that silently skip on an unsupported distro. +--- + +# Working With server-shell-setup + +The shell scripts in `packages/server-shell-setup` (`install-shell.sh`, plus `get-node.sh` and `clean-server-disk.sh`). It's not an npm package โ€” it's a bash installer you pipe from the network, aimed at a fresh VPS, container, or Termux session. + +**Supported systems**: Arch, Ubuntu/Debian, Android (Termux), macOS, Fedora, Alpine. The script detects the OS and picks the right package manager; unsupported combinations skip the component rather than aborting the run. + +## Setup + +On a brand-new server, set passwords first (many providers ship with none, which makes `sudo` fail in confusing ways): + +```bash +sudo passwd # root +sudo passwd $USER # your user +``` + +Then pick an install mode: + +```bash +wget -qO- tinyurl.com/shellsetup | bash # interactive menu +wget -qO- tinyurl.com/shellsetup | bash -s -- all # everything, unattended +wget -qO- tinyurl.com/shellsetup | bash -s -- starship,docker,node # specific components +``` + +The `-s --` is what forwards arguments through the pipe to bash โ€” dropping it silently gives you the interactive menu instead. + +## Components + +| Name | What you get | +| --- | --- | +| `fish` | Fish with oh-my-fish, fzf, z, pisces | +| `nushell` | Structured-data shell | +| `nvim` | Neovim preconfigured with NvChad | +| `helix` | Modal editor, no config needed | +| `node` | Node via Volta (no sudo/permission problems), plus pnpm, yarn, git0, vite, turbo | +| `bun` | Bun runtime + package manager | +| `docker` | Docker with rootless mode | +| `starship` | Prompt wired into bash, fish, and nushell | +| `systeminfo` | The `about-system` greeting on login | +| `pacstall` | AUR-style package manager for Ubuntu/Debian | +| `code` | code-server (VS Code in the browser) | +| `sudo` | Passwordless sudo for the current user | + +## Fish aliases it installs + +| Alias | Runs | +| --- | --- | +| `in ` | `sudo apt install ` | +| `e ` | `nvim ` | +| `del ` | `sudo rm -rf ` | +| `setup` | Re-runs this installer | +| `killport` | fzf picker to kill the process holding a port | +| `search ` | Search filenames and contents via ripgrep | +| `service_manager` | fzf picker to start/stop/restart/inspect systemd services | + +## Troubleshooting + +| Symptom | Cause โ†’ fix | +| --- | --- | +| Arguments ignored, menu appears anyway | You piped without `-s --`. Use `bash -s -- all`. | +| Menu appears but you're in CI | The interactive path needs a TTY. Always pass an explicit component list or `all` in automation. | +| `sudo: no password` / repeated prompts | Set the passwords first (above). The `sudo` component enables passwordless sudo, but it can't run before you can `sudo` at all. | +| Fish installed but the shell didn't change | The default shell change needs `chsh -s $(which fish)` and a new login session; in containers there's often no login shell at all โ€” invoke `fish` from your entrypoint. | +| Docker installed but `docker ps` fails | Rootless Docker needs its user daemon running and the socket env var set โ€” log out and back in, then check `systemctl --user status docker`. In containers without systemd, rootless mode won't come up. | +| A component silently didn't install | It isn't available for the detected OS. Re-run just that component to see the output rather than scrolling the full log. | +| Node installed but `node` isn't found | Volta puts shims in `~/.volta/bin`, added to the shell config it edited. Open a new shell, or source the config for the shell you're actually using. | +| `killport`/`service_manager` do nothing | They depend on `fzf` (fish component) and on systemd (`service_manager`), neither of which exists in a minimal container. | +| Running it twice broke something | It's re-runnable (`setup` alias exists for that), but shell config edits can stack. Check `~/.config/fish/config.fish` for duplicate lines. | +| Disk fills up mid-install | `clean-server-disk.sh` in the same folder clears package caches and old images. | diff --git a/skills/ask-shadcn-theme-menu/SKILL.md b/skills/ask-shadcn-theme-menu/SKILL.md new file mode 100644 index 00000000..d463ca89 --- /dev/null +++ b/skills/ask-shadcn-theme-menu/SKILL.md @@ -0,0 +1,63 @@ +--- +name: ask-shadcn-theme-menu +description: Guide to shadcn-theme-menu (packages/shadcn-theme-menu), the shadcn/ui theme switcher โ€” wiring ThemeProvider, ThemeToggle, ThemeDropdown, CinematicThemeSwitcher and SidebarUserMenu, importing themes.css, the 25 OKLCH color themes, setting themes programmatically, and injecting your own Button/DropdownMenu. Use when working with shadcn-theme-menu or troubleshooting it โ€” a flash of the wrong theme on load, colors that don't change when the theme does, dark mode not applying, hydration mismatch warnings, or the dropdown rendering unstyled. +--- + +# Working With shadcn-theme-menu + +The component set in `packages/shadcn-theme-menu`, published as **`shadcn-theme-menu`**. It layers a *color theme* (a `theme-*` class on ``, persisted in `localStorage`) on top of `next-themes`' *light/dark mode* (a `class` attribute). Those are two independent axes โ€” most confusion comes from conflating them. + +## Setup + +Three pieces, in this order: + +1. **The CSS**, once, at the app root: `import "shadcn-theme-menu/themes.css";` โ€” it defines every `theme-*` class as OKLCH custom properties. +2. **The provider**, wrapping the app: ``. It re-exports `next-themes`, so `useTheme()` works as usual. +3. **A switcher component** anywhere below it. + +```tsx +import { ThemeProvider, ThemeToggle, ThemeDropdown } from "shadcn-theme-menu"; +``` + +Peers you're expected to already have: `react`, `react-dom`, `next-themes`, `lucide-react`. + +## Picking the right component + +| You want | Component | +| --- | --- | +| Light/dark/system toggle only | `` โ€” `mode="light-dark"` drops the system option | +| Full palette picker with live preview | `` | +| An animated, particle-effect toggle | `` | +| The switcher inside a shadcn sidebar footer | `` | +| To set a theme from your own code | `themeNames`, `themeColors`, `formatThemeName` (see recipe) | + +## Recipes + +**Setting a color theme programmatically** โ€” remove the previous `theme-*` class, add the new one, persist under the `color-theme` key: + +```tsx +import { themeNames } from "shadcn-theme-menu"; + +localStorage.setItem("color-theme", name); +themeNames.forEach(t => document.documentElement.classList.remove(`theme-${t}`)); +document.documentElement.classList.add(`theme-${name}`); +``` + +**Available themes** โ€” 25 of them: `modern-minimal`, `elegant-luxury`, `cyberpunk`, `twitter`, `mocha-mousse`, `bubblegum`, `amethyst-haze`, `pink-lemonade`, `notebook`, `doom-64`, `catppuccin`, `graphite`, `perpetuity`, `kodama-grove`, `cosmic-night`, `tangerine`, `quantum-rose`, `nature`, `bold-tech`, `amber-minimal`, `supabase`, `neo-brutalism`, `solar-dusk`, `claymorphism`, `pastel-dreams`. `themeColors[name]` gives `{ primary, secondary }` for swatches; `formatThemeName("modern-minimal")` โ†’ `"Modern Minimal"`. + +**Using your own primitives** โ€” pass `Button` and a `DropdownMenu` object (`Root`, `Trigger`, `Content`, `Item`, `Label`, `Separator`) so the switcher inherits your design system instead of the bundled shadcn copies. + +**Reacting to changes** โ€” `onThemeChange` on `ThemeToggle`; `onColorThemeChange` / `onModeChange` on `ThemeDropdown`. + +## Troubleshooting + +| Symptom | Cause โ†’ fix | +| --- | --- | +| Flash of the wrong theme on first paint | The `theme-*` class is applied from `localStorage` after hydration. Add a small blocking inline script in `` that reads `color-theme` and sets the class before React mounts โ€” the same trick `next-themes` uses for dark mode. | +| Hydration mismatch warning on the toggle | The server can't know the stored theme. Render the switcher only after mount (`useEffect` + `mounted` flag), or pass `suppressHydrationWarning` on `` as `next-themes` recommends. | +| Mode toggles but colors never change | `themes.css` wasn't imported, so the `theme-*` classes have no definitions. Import it at the root; the package marks CSS as side-effectful so bundlers keep it. | +| Colors change but dark mode doesn't | The two axes are separate: dark mode is `next-themes`' `class` attribute, color theme is `theme-*`. Make sure `ThemeProvider` has `attribute="class"` and your Tailwind config uses `darkMode: "class"`. | +| Chosen theme resets on reload | Persistence uses the `color-theme` `localStorage` key; something else is clearing it, or the app runs in an incognito/storage-blocked context. | +| Dropdown renders unstyled | Your project has no Tailwind/shadcn base layer, or you passed custom `DropdownMenu` primitives without their styles. Import `themes.css` and keep your shadcn `globals.css`. | +| `Cannot find module 'next-themes'` | It's a peer dependency, not bundled โ€” install it. | +| Custom `Button` breaks the layout | The switchers pass their own `size`/`variant` props through; your Button must accept them or ignore them gracefully. | diff --git a/skills/ask-verify-phone-sms/API.md b/skills/ask-verify-phone-sms/API.md new file mode 100644 index 00000000..9fe072a0 --- /dev/null +++ b/skills/ask-verify-phone-sms/API.md @@ -0,0 +1,98 @@ +# verify-phone-sms API Reference + +## HTTP endpoints + +Auth applies to `/api/*`: `X-API-Key: ` or `Authorization: Bearer `. Rate limit: 100 requests per IP per 15 minutes. + +| Method | Path | Auth | Purpose | +| --- | --- | --- | --- | +| `GET` | `/` | no | Service info + endpoint index | +| `GET` | `/health` | no | Health check | +| `GET` | `/docs` | no | Swagger UI over the generated OpenAPI 3.0 doc | +| `POST` | `/api/send` | yes | Send a verification code | +| `POST` | `/api/verify` | yes | **Mock** โ€” always returns `verified: true` | +| `POST` | `/api/sms` | yes | Send an arbitrary SMS | + +### `POST /api/send` + +```json +{ + "phoneNumber": "+1234567890", + "code": "123456", + "blockVoip": true, + "senderId": "MyApp", + "messageTemplate": "Your code is: {code}", + "smsType": "Transactional" +} +``` + +Only `phoneNumber` is required; `code` is generated when omitted. Response: `{ success, message, messageId, code, phoneNumber, expiresIn }`. + +### `POST /api/verify` + +```json +{ "phoneNumber": "+1234567890", "code": "123456" } +``` + +Response: `{ success: true, message, verified: true }` โ€” unconditionally, until you implement storage. + +### `POST /api/sms` + +```json +{ "phoneNumber": "+1234567890", "message": "Hello", "senderId": "MyApp", "smsType": "Transactional" } +``` + +Response: `{ success, message, messageId, phoneNumber }`. + +### Errors + +`{ success: false, error, details }` with `400` (bad input), `401` (bad key), `429` (rate limited), `500` (server). + +## `verifyPhone(options)` + +Default export of `src/verify-phone.ts`. Returns `Promise`. + +| Option | Type | Default | Notes | +| --- | --- | --- | --- | +| `phoneNumber` | `string` | required | E.164 recommended (`+1234567890`) | +| `code` | `string` | required | Sent as-is; not generated for you | +| `accessKeyId` | `string` | env | AWS access key | +| `secretAccessKey` | `string` | env | AWS secret | +| `awsRegion` | `string` | `us-east-1` | | +| `blockVoip` | `boolean` | `false` | Reject VoIP numbers | +| `voipDetectionMethod` | `"api" \| "libphonenumber"` | `"api"` | External lookup vs local heuristics | +| `useLibPhoneNumber` | `boolean` | `false` | Parse/format with libphonenumber-js | +| `metadataType` | `"minimal" \| "full"` | `"minimal"` | 75 KB heuristics vs 140 KB type detection | +| `senderId` | `string` | `"Verify"` | Max 11 chars | +| `smsType` | `"Transactional" \| "Promotional"` | `"Transactional"` | | +| `messageTemplate` | `string` | `"Your verification code is: {code}."` | `{code}` placeholder | + +```ts +interface VerifyPhoneResult { + success: boolean; + message?: string; + messageId?: string; + code?: string; + phoneNumber?: string; + expiresIn?: number; + error?: string; + details?: string; + isVoip?: boolean; +} +``` + +Also exported: `isPhoneNumberVoip(phone: string): Promise`, and `createApp(env?)` / the default Hono `app` from `verify-phone-server.ts`. + +## Environment variables + +| Variable | Default | Required | +| --- | --- | --- | +| `AWS_ACCESS_KEY_ID` | โ€” | yes | +| `AWS_SECRET_ACCESS_KEY` | โ€” | yes | +| `AWS_REGION` | `us-east-1` | no | +| `API_KEY` | โ€” | yes | +| `SMS_SENDER_ID` | `Verify` | no | + +## Scripts + +`dev` (wrangler dev) ยท `deploy` / `deploy:staging` / `deploy:production` ยท `deploy:docs*` ยท `deploy:all*` ยท `test` / `test:run` / `test:ui` (vitest) ยท `typecheck` ยท `scripts/deploy.sh [env] [api|docs]`. diff --git a/skills/ask-verify-phone-sms/SKILL.md b/skills/ask-verify-phone-sms/SKILL.md new file mode 100644 index 00000000..bc30d232 --- /dev/null +++ b/skills/ask-verify-phone-sms/SKILL.md @@ -0,0 +1,75 @@ +--- +name: ask-verify-phone-sms +description: Guide to the SMS verification API in packages/verify-phone-sms โ€” the Hono/Cloudflare Workers server, the verifyPhone() function over AWS SNS, /api/send /api/verify /api/sms endpoints, API-key auth, rate limiting, VoIP blocking via external lookup or libphonenumber-js, and wrangler deploys per environment. Use when working with verify-phone-sms or troubleshooting it โ€” 401s from the API key, SMS that never arrives, AWS SNS sandbox and spending limits, sender-id rules, VoIP false positives, or /api/verify accepting any code. +--- + +# Working With verify-phone-sms + +The service in `packages/verify-phone-sms` (npm name `sms-verification-api`). Two layers: a `verifyPhone()` function that sends an SMS through AWS SNS's HTTP API, and a Hono server (`@hono/zod-openapi`) that exposes it on Cloudflare Workers with auth, rate limiting, and Swagger docs. Exact request/response schemas and options live in [API.md](API.md). + +## The one thing to know first + +**`/api/verify` is a stub.** The handler returns `{ success: true, verified: true }` for any input โ€” the source comments say code storage and expiry are left to the integrator. Before this is usable for real auth you must persist the issued code (KV, D1, Durable Object, Redis) keyed by phone number with a TTL, and compare against it. Don't ship the endpoint as-is. + +## Setup + +```bash +npm install +npm run dev # wrangler dev on http://localhost:8787 +npm run deploy # or deploy:staging / deploy:production +``` + +Secrets go through Wrangler, not `.env`, for deployed environments: + +```bash +wrangler secret put AWS_ACCESS_KEY_ID +wrangler secret put AWS_SECRET_ACCESS_KEY +wrangler secret put API_KEY +``` + +| Variable | Default | | +| --- | --- | --- | +| `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` | โ€” | required | +| `AWS_REGION` | `us-east-1` | must be a region where SNS SMS is available | +| `API_KEY` | โ€” | required; callers send it as `X-API-Key` or `Authorization: Bearer` | +| `SMS_SENDER_ID` | `Verify` | max 11 chars, alphanumeric | + +## Picking the right entry point + +| You want | Use | +| --- | --- | +| Send a code from your own backend | `import verifyPhone from "sms-verification-api"` โ€” default export, `verifyPhone({ phoneNumber, code, โ€ฆ })` | +| Just a VoIP check | `import { isPhoneNumberVoip } from "sms-verification-api"` | +| A hosted endpoint | `POST /api/send` with `X-API-Key` | +| Any non-verification SMS | `POST /api/sms` with `message` | +| Interactive docs | `GET /docs` (Swagger UI); `GET /` and `GET /health` are unauthenticated health checks | + +Note `verifyPhone` requires **both** `phoneNumber` and `code` โ€” it sends the code you give it, it does not generate one. The `/api/send` endpoint generates one for you when the body omits it. + +## Recipes + +**VoIP blocking** โ€” `blockVoip: true` plus a detection method: + +- `voipDetectionMethod: "api"` (default) โ€” external carrier lookup; more accurate, needs network. +- `voipDetectionMethod: "libphonenumber"` โ€” local heuristics (toll-free ranges, non-geographic numbers, repeated/sequential digits). Add `metadataType: "full"` (140 KB vs 75 KB) for real number-type detection instead of pattern guessing. + +**Formatting** โ€” `useLibPhoneNumber: true` parses and normalizes loosely formatted input (`555-123-4567`, `+44 20 7946 0958`) before sending. + +**Message text** โ€” `messageTemplate: "Your code is: {code}"`; `smsType: "Transactional"` (default) gets delivery priority over `"Promotional"`. + +**Auth middleware** โ€” applied to `/api/*` only; the rate limiter (15-minute window, 100 requests per IP, keyed off `CF-Connecting-IP`) runs on every route. + +## Troubleshooting + +| Symptom | Cause โ†’ fix | +| --- | --- | +| `/api/verify` accepts any code | It's a mock (see above). Implement storage + comparison before relying on it. | +| `401 Unauthorized` | `API_KEY` isn't set for the environment you deployed to, or the header is wrong. Secrets are per-environment โ€” `deploy:staging` needs its own `wrangler secret put --env staging`. | +| SMS never arrives, but the call succeeded | An SNS `messageId` means accepted, not delivered. In the SNS **SMS sandbox** only verified destination numbers receive messages โ€” verify the number or request production access. Also check the account's monthly SMS spend limit. | +| Delivery works for US numbers only | Not every region/route is enabled by default; some countries require sender-id registration or a long code. Check SNS delivery logs per destination country. | +| Sender ID ignored | Many countries (including the US) override or forbid alphanumeric sender ids. Max 11 alphanumeric characters where it is supported. | +| Legitimate mobile numbers blocked as VoIP | Heuristic detection flags toll-free and pattern-y numbers. Switch to `voipDetectionMethod: "api"`, or `metadataType: "full"`, or turn `blockVoip` off. | +| `429` from your own API | The built-in limiter: 100 requests per IP per 15 minutes, before any AWS call. | +| Works in `wrangler dev`, fails deployed | `.env` is dev-only; deployed Workers read Wrangler secrets. Set all four values per environment. | +| AWS `SignatureDoesNotMatch` / `InvalidClientTokenId` | Wrong key pair, or a region mismatch between the credentials and `AWS_REGION`. | +| Codes are logged in responses | `/api/send` echoes the generated code in its response โ€” useful in development, a leak in production. Strip it before exposing the endpoint publicly. | diff --git a/skills/ask-web2mobile/SKILL.md b/skills/ask-web2mobile/SKILL.md new file mode 100644 index 00000000..e79aeec3 --- /dev/null +++ b/skills/ask-web2mobile/SKILL.md @@ -0,0 +1,73 @@ +--- +name: ask-web2mobile +description: Guide to web2mobile-wrapper (packages/web2mobile-wrapper), the generator that wraps a website in an Expo/React Native WebView app โ€” the interactive CLI vs config.json, icon auto-detection and asset generation with Sharp, the generated mobile-app/ directory, and the EAS build and submit scripts. Use when working with web2mobile or troubleshooting it โ€” a blank or refusing-to-load WebView, X-Frame-Options and HTTPS requirements, icon generation failures, EAS build or login errors, or app-store rejections of thin wrapper apps. +--- + +# Working With web2mobile + +The generator in `packages/web2mobile-wrapper` (package name `create-mobile-wrapper`, bin `create-mobile-wrapper`; marked `private`, so run it from a checkout). It renders an Expo + `react-native-webview` app that loads your site, generates every icon size with Sharp, and wires up EAS build/submit profiles. + +## Setup + +Prerequisites: Node 18+, an **HTTPS** site, and a square logo. + +Two ways to configure โ€” the CLI prompts, or a `config.json`: + +```bash +npm install +npm run generate # prompts: app name, website URL, enable EAS? +``` + +```json +{ + "name": "Your App Name", + "url": "https://yoursite.com", + "icon": "path/to/logo.png", + "packageName": "com.yourcompany.app" +} +``` + +`name`, `url`, and `packageName` (reverse-domain) are required; `icon` is optional โ€” without it the tool searches `icon.png`/`logo.png`/`app-icon.png` in the cwd and in `assets/`, `public/`, `static/`, `images/`, `img/`, accepting `.png`, `.jpg`, `.jpeg`, `.svg`, and falls back to a placeholder. + +## What gets generated + +Everything lands in `mobile-app/`: + +``` +mobile-app/ +โ”œโ”€โ”€ assets/ icon.png (1024ยฒ) ยท adaptive-icon.png (1024ยฒ) ยท splash.png (2048ยฒ) ยท favicon.png (48ยฒ) +โ”œโ”€โ”€ App.js WebView wrapper +โ”œโ”€โ”€ app.json Expo config +โ”œโ”€โ”€ package.json +โ””โ”€โ”€ eas.json development / preview / production profiles +``` + +## Build and submit + +| Goal | Command (from the project root) | +| --- | --- | +| Test on a device | `npm start`, then scan the QR with Expo Go (`i`/`a` for simulators) | +| First-time EAS setup | `npx eas-cli login`, then `cd mobile-app && npx eas build:configure` | +| Android / iOS / both | `npm run build:android` ยท `build:ios` ยท `build:all` | +| Store release (auto-increment) | `npm run build:production` | +| Submit | `npm run submit:android` ยท `submit:ios` ยท `submit:all` | + +## Recipes + +**Customize behavior after generation** โ€” edit `mobile-app/App.js` for loading states, error handling, back-button navigation, custom headers, `injectedJavaScript`, or user agent; `mobile-app/app.json` for splash color, status bar style, orientation; `mobile-app/eas.json` for build profiles. Re-running the generator overwrites these, so keep custom edits in version control. + +**Detecting the wrapper from your site** โ€” set a custom user agent in `App.js` and branch on it server-side to hide install banners or enable app-only behavior. + +## Troubleshooting + +| Symptom | Cause โ†’ fix | +| --- | --- | +| Blank WebView | The URL isn't HTTPS (required), the site sends `X-Frame-Options`/CSP `frame-ancestors` that block embedding, or it failed silently โ€” add an `onError` handler in `App.js` and test the URL in a mobile browser first. | +| Login/session doesn't persist | The WebView has its own cookie/localStorage jar. Third-party-cookie-dependent auth flows often need a native redirect or a token handoff. | +| Generation fails on the icon | Sharp couldn't read the file. Use a square PNG โ‰ฅ512px (1024px recommended); SVG input works but converts less predictably. | +| Icons look cropped or off-center | Non-square source. The tool resizes and centers โ€” it can't fix a 16:9 logo. | +| `eas build` fails | Not logged in (`npx eas-cli login`), dependencies not installed in `mobile-app/`, or an invalid `config.json`. Build errors come from EAS, not this tool โ€” read its log URL. | +| `packageName` rejected | It must be reverse-domain (`com.company.app`), unique on both stores, and immutable after first publish. Choose carefully. | +| App store rejects the app | Both stores reject apps that are only a website in a shell. Add genuine native value (push notifications, offline handling, deep links) before submitting. | +| Re-running `generate` wiped my changes | Templates are re-rendered into `mobile-app/`. Commit before regenerating, or move customizations into the templates under `src/`. | +| `npm run start` can't find Expo | Scripts `cd mobile-app` first โ€” run `npm install` inside `mobile-app/` after generation. |