diff --git a/plugins/ai-sdk/.agents/skills/ai-sdk/SKILL.md b/plugins/ai-sdk/.agents/skills/ai-sdk/SKILL.md index 038ecd17..728d06bb 100644 --- a/plugins/ai-sdk/.agents/skills/ai-sdk/SKILL.md +++ b/plugins/ai-sdk/.agents/skills/ai-sdk/SKILL.md @@ -20,9 +20,9 @@ Whatever you remember about the AI SDK is likely outdated. The SDK changes frequ The `ai` package ships its full documentation and source code inside `node_modules`. These always match the installed version, so trust them over anything you remember. -1. Ensure `ai` is installed. If `node_modules/ai/` does not exist, install **only** the `ai` package using the project's package manager (e.g. `pnpm add ai`). Install provider packages (e.g. `@ai-sdk/openai`) and framework packages (e.g. `@ai-sdk/react`) later, when the task requires them. -2. Read and grep the bundled docs at `node_modules/ai/docs/` and the source at `node_modules/ai/src/`. -3. Provider and framework packages bundle their own docs at `node_modules/@ai-sdk//docs/`. +1. Ensure `ai` is installed. Check `node_modules/ai/` or locate the workspace package in a monorepo that depends on `ai` (e.g. `apps//node_modules/ai/`). If `ai` is not installed anywhere in the project, install **only** the `ai` package into the target package using the project's package manager (e.g. `pnpm add ai --filter ` or `npm install ai`). Install provider packages (e.g. `@ai-sdk/openai`) and framework packages (e.g. `@ai-sdk/react`) later, when the task requires them. +2. Read and grep the bundled docs at `node_modules/ai/docs/` (or `/node_modules/ai/docs/`) and the source at `node_modules/ai/src/`. +3. Provider and framework packages bundle their own docs at `node_modules/@ai-sdk//docs/` (or `/node_modules/@ai-sdk//docs/`). 4. If something isn't in the bundled docs, search https://ai-sdk.dev/docs. You can append `.md` to any docs page URL to get its markdown, and search via `https://ai-sdk.dev/api/search-docs?q=your_query`. 5. If you cannot find support for an answer in the docs or source, say so explicitly — do not guess. diff --git a/plugins/ai-sdk/agent/skills/ai-sdk/SKILL.md b/plugins/ai-sdk/agent/skills/ai-sdk/SKILL.md index edb003be..40ca751a 100644 --- a/plugins/ai-sdk/agent/skills/ai-sdk/SKILL.md +++ b/plugins/ai-sdk/agent/skills/ai-sdk/SKILL.md @@ -1,5 +1,14 @@ --- -description: "Answer questions about the AI SDK and help build AI-powered features. Use when developers: (1) Ask about AI SDK functions like generateText, streamText, ToolLoopAgent, embed, or tools, (2) Want to build AI agents, chatbots, RAG systems, or text generation features, (3) Have questions about AI providers (OpenAI, Anthropic, Google, etc.), streaming, tool calling, structured output, or embeddings, (4) Use React hooks like useChat or useCompletion. Triggers on: \"AI SDK\", \"Vercel AI SDK\", \"generateText\", \"streamText\", \"add AI to my app\", \"build an agent\", \"tool calling\", \"structured output\", \"useChat\"." +name: ai-sdk +description: 'Answer questions about the AI SDK and help build AI-powered + features. Use when developers: (1) Ask about AI SDK functions like + generateText, streamText, ToolLoopAgent, embed, or tools, (2) Want to build AI + agents, chatbots, RAG systems, or text generation features, (3) Have questions + about AI providers (OpenAI, Anthropic, Google, etc.), streaming, tool calling, + structured output, or embeddings, (4) Use React hooks like useChat or + useCompletion. Triggers on: "AI SDK", "Vercel AI SDK", "generateText", + "streamText", "add AI to my app", "build an agent", "tool calling", + "structured output", "useChat".' --- ## What the AI SDK Is @@ -18,9 +27,9 @@ Whatever you remember about the AI SDK is likely outdated. The SDK changes frequ The `ai` package ships its full documentation and source code inside `node_modules`. These always match the installed version, so trust them over anything you remember. -1. Ensure `ai` is installed. If `node_modules/ai/` does not exist, install **only** the `ai` package using the project's package manager (e.g. `pnpm add ai`). Install provider packages (e.g. `@ai-sdk/openai`) and framework packages (e.g. `@ai-sdk/react`) later, when the task requires them. -2. Read and grep the bundled docs at `node_modules/ai/docs/` and the source at `node_modules/ai/src/`. -3. Provider and framework packages bundle their own docs at `node_modules/@ai-sdk//docs/`. +1. Ensure `ai` is installed. Check `node_modules/ai/` or locate the workspace package in a monorepo that depends on `ai` (e.g. `apps//node_modules/ai/`). If `ai` is not installed anywhere in the project, install **only** the `ai` package into the target package using the project's package manager (e.g. `pnpm add ai --filter ` or `npm install ai`). Install provider packages (e.g. `@ai-sdk/openai`) and framework packages (e.g. `@ai-sdk/react`) later, when the task requires them. +2. Read and grep the bundled docs at `node_modules/ai/docs/` (or `/node_modules/ai/docs/`) and the source at `node_modules/ai/src/`. +3. Provider and framework packages bundle their own docs at `node_modules/@ai-sdk//docs/` (or `/node_modules/@ai-sdk//docs/`). 4. If something isn't in the bundled docs, search https://ai-sdk.dev/docs. You can append `.md` to any docs page URL to get its markdown, and search via `https://ai-sdk.dev/api/search-docs?q=your_query`. 5. If you cannot find support for an answer in the docs or source, say so explicitly — do not guess. diff --git a/plugins/ai-sdk/skills-lock.json b/plugins/ai-sdk/skills-lock.json index 70f032b3..b745916c 100644 --- a/plugins/ai-sdk/skills-lock.json +++ b/plugins/ai-sdk/skills-lock.json @@ -5,7 +5,7 @@ "source": "vercel/ai", "sourceType": "github", "skillPath": "skills/use-ai-sdk/SKILL.md", - "computedHash": "0883204d11b055e968dee697c5858e68064c27dfd4deb6310212895daa65c5ad" + "computedHash": "aee5bf7ecb066b54eae76799610fa8a9be9ff1ca9a000ea92d17c14ec862938b" } } } diff --git a/plugins/antfu/.agents/skills/antfu/SKILL.md b/plugins/antfu/.agents/skills/antfu/SKILL.md index 0e78e96a..148183c6 100644 --- a/plugins/antfu/.agents/skills/antfu/SKILL.md +++ b/plugins/antfu/.agents/skills/antfu/SKILL.md @@ -3,9 +3,11 @@ name: antfu description: Anthony Fu's opinionated tooling and conventions for JavaScript/TypeScript projects. Use when setting up new projects, configuring ESLint/Prettier alternatives, monorepos, library publishing, or when the user mentions Anthony Fu's preferences. metadata: author: Anthony Fu - version: "2026.06.22" + version: "2026.09.30" --- +> Reference template: [antfu/starter-ts](https://github.com/antfu/starter-ts). When scaffolding a new TypeScript project, mirror its `package.json` scripts, `eslint.config.js`, `knip.json`, `tsdown.config.ts` and workflows rather than inventing a new layout. + ## Coding Practices ### Code Organization @@ -100,31 +102,82 @@ Prefer this over `npm view version` when you only need the latest version, ### ESLint Setup ```js -// eslint.config.mjs +// eslint.config.js import antfu from '@antfu/eslint-config' -export default antfu() +export default antfu({ + type: 'lib', // or 'app' (default) + pnpm: true, // enforce pnpm catalogs in package.json + antislop: true, // flag redundant/duplicated AI-style code, ban explicit `any` +}) ``` - -When completing tasks, run `pnpm run lint --fix` to format the code and fix coding style. +Always enable `antislop: true`. It requires `eslint-plugin-slop` and `eslint-plugin-sonarjs` as dev dependencies. For detailed configuration options: [antfu-eslint-config](references/antfu-eslint-config.md) +### Knip + +Use [Knip](https://knip.dev) to catch unused files, exports and dependencies — the linter only sees one file at a time. + +```json +// knip.json +{ + "$schema": "https://unpkg.com/knip@6/schema.json", + "project": ["src/**/*.ts"], + "ignoreDependencies": [] +} +``` + +Fix what Knip reports by deleting; only add to `ignoreDependencies` for tools invoked outside `package.json` scripts (e.g. `taze`). + +### Scripts and the `ci` Gate + +Every project exposes a `ci` script that runs all checks in one command: + +```json +{ + "scripts": { + "build": "tsdown", + "lint": "eslint --cache", + "typecheck": "tsc", + "knip": "knip", + "test": "pnpm run build && vitest", + "ci": "pnpm run lint && pnpm run typecheck && pnpm run knip && pnpm run test --run" + } +} +``` + +**Before every commit, run `nr lint --fix` to format, then `nr ci` and make sure it passes.** Do not commit with a failing `ci`; fix the root cause instead of silencing rules or adding ignores. + ### Git Hooks +`simple-git-hooks` + `nano-staged`. The pre-commit hook runs the full `ci` gate; `prepare` also installs agent skills via `skills-npm`: + ```json { + "scripts": { + "prepare": "git config core.hooksPath .githooks && simple-git-hooks && skills-npm" + }, "simple-git-hooks": { - "pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && npx lint-staged" + "pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && pnpm run ci && pnpx nano-staged" }, - "lint-staged": { "*": "eslint --fix" }, - "scripts": { - "prepare": "npx simple-git-hooks" + "nano-staged": { + "*": "eslint --fix --no-warn-ignored" } } ``` +Add `.githooks` to `.gitignore` and `simple-git-hooks: true` under `allowBuilds` in `pnpm-workspace.yaml`. + +### skills-npm + +[`skills-npm`](https://github.com/antfu/skills-npm) symlinks agent skills shipped inside installed npm packages (and fetches those declared in a `skills` field) into the agent's skills directory on every install. Install as a dev dependency and wire it into `prepare` as above; add `skills/npm-*` to `.gitignore`. Commit the generated `skills-npm-lock.json`. + +### Publishing + +Prefer npm Trusted Publishing (OIDC) over `NPM_TOKEN` secrets: releases run on CI from a `v*` tag via `release.yml` with `id-token: write`, and `nr release` (`bumpp`) only bumps, tags and pushes. Details: [setting-up](references/setting-up.md#publishing-with-npm-trusted-publishing-oidc). + ### pnpm Catalogs Use named catalogs in `pnpm-workspace.yaml` for version management: @@ -144,8 +197,8 @@ Avoid the default catalog. Catalog names can be adjusted per project needs. | Topic | Description | Reference | |-------|-------------|-----------| -| ESLint Config | Framework support, formatters, rule overrides, VS Code settings | [antfu-eslint-config](references/antfu-eslint-config.md) | -| Project Setup | .gitignore, GitHub Actions, VS Code extensions | [setting-up](references/setting-up.md) | +| ESLint Config | Framework support, antislop, formatters, rule overrides, VS Code settings | [antfu-eslint-config](references/antfu-eslint-config.md) | +| Project Setup | .gitignore, GitHub Actions, OIDC trusted publishing, VS Code extensions | [setting-up](references/setting-up.md) | | App Development | Vue/Nuxt/UnoCSS conventions, auto-import control, Storybook component testing | [app-development](references/app-development.md) | -| Library Development | tsdown bundling, pure ESM publishing | [library-development](references/library-development.md) | +| Library Development | tsdown bundling, pure ESM publishing, publint, API snapshots | [library-development](references/library-development.md) | | Monorepo | pnpm workspaces, centralized alias, Turborepo | [monorepo](references/monorepo.md) | diff --git a/plugins/antfu/.agents/skills/antfu/references/antfu-eslint-config.md b/plugins/antfu/.agents/skills/antfu/references/antfu-eslint-config.md index e92fcba5..9bb734d3 100644 --- a/plugins/antfu/.agents/skills/antfu/references/antfu-eslint-config.md +++ b/plugins/antfu/.agents/skills/antfu/references/antfu-eslint-config.md @@ -18,6 +18,12 @@ export default antfu({ // Project type: 'lib' for libraries, 'app' (default) for applications type: 'lib', + // Enforce pnpm catalogs / workspace settings in package.json + pnpm: true, + + // Always on: catch redundant, duplicated, AI-style code + antislop: true, + // Global ignores (extends defaults, doesn't override) ignores: ['**/fixtures', '**/dist'], @@ -37,6 +43,32 @@ export default antfu({ }) ``` +## Antislop + +```js +export default antfu({ + antislop: true, +}) +// Requires: pnpm add -D eslint-plugin-slop eslint-plugin-sonarjs +``` + +Enables [`eslint-plugin-slop`](https://github.com/antfu/eslint-plugin-slop) plus a curated subset of `eslint-plugin-sonarjs` targeting redundant and duplicated code, and bans explicit `any` when TypeScript is enabled. Enable it on every project; when it flags code, simplify the code rather than disabling the rule. + +To toggle a plugin or scope inspection to recent changes: + +```js +export default antfu({ + antislop: { + sonarjs: false, + slop: { + inspection: { mode: 'recent-changes', tracebackCommits: 5 }, + }, + }, +}) +``` + +Linters see one file at a time, so pair it with [Knip](https://knip.dev) for unused files/exports/dependencies (and `jscpd` for cross-file duplication when needed). + ## Framework Support ### Vue @@ -230,24 +262,28 @@ export default antfu({ }) ``` -## Lint-Staged Setup +## Pre-commit Setup ```json { + "scripts": { + "prepare": "git config core.hooksPath .githooks && simple-git-hooks && skills-npm" + }, "simple-git-hooks": { - "pre-commit": "pnpm lint-staged" + "pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && pnpm run ci && pnpx nano-staged" }, - "lint-staged": { - "*": "eslint --fix" + "nano-staged": { + "*": "eslint --fix --no-warn-ignored" } } ``` ```bash -pnpm add -D lint-staged simple-git-hooks -npx simple-git-hooks +pnpm add -D nano-staged simple-git-hooks skills-npm ``` +The hook runs the full `ci` script (lint, typecheck, knip, tests) before auto-fixing staged files, so a commit never lands with a red check. + ## VS Code Settings Add to `.vscode/settings.json`: diff --git a/plugins/antfu/.agents/skills/antfu/references/library-development.md b/plugins/antfu/.agents/skills/antfu/references/library-development.md index 55f00744..298477bc 100644 --- a/plugins/antfu/.agents/skills/antfu/references/library-development.md +++ b/plugins/antfu/.agents/skills/antfu/references/library-development.md @@ -11,6 +11,8 @@ description: Building and publishing TypeScript libraries with tsdown. Use when | Output | Pure ESM only (no CJS) | | DTS | Generated via tsdown | | Exports | Auto-generated via tsdown | +| Package validation | publint (via tsdown) | +| Publishing | npm Trusted Publishing (OIDC) from CI | ## tsdown Configuration @@ -25,6 +27,7 @@ export default defineConfig({ format: ['esm'], dts: true, exports: true, + publint: true, }) ``` @@ -33,6 +36,7 @@ export default defineConfig({ | `format` | `['esm']` | Pure ESM, no CommonJS | | `dts` | `true` | Generate `.d.ts` files | | `exports` | `true` | Auto-update `exports` field in `package.json` | +| `publint` | `true` | Validate `package.json`/`exports` on every build (requires `publint` dev dependency) | ### Multiple Entry Points @@ -129,21 +133,29 @@ Required fields for pure ESM library: ```json { "type": "module", - "main": "./dist/index.mjs", - "module": "./dist/index.mjs", + "sideEffects": false, "types": "./dist/index.d.mts", "files": ["dist"], "scripts": { "build": "tsdown", - "prepack": "pnpm build", - "test": "vitest", - "release": "bumpp -r" + "dev": "tsdown --watch", + "lint": "eslint --cache", + "typecheck": "tsc", + "knip": "knip", + "test": "pnpm run build && vitest", + "ci": "pnpm run lint && pnpm run typecheck && pnpm run knip && pnpm run test --run", + "prepack": "nr build", + "release": "bumpp" } } ``` -The `exports` field is managed by tsdown when `exports: true`. +The `exports` field is managed by tsdown when `exports: true`. Use `bumpp -r` in monorepos. ### prepack Script -For each public package, add `"prepack": "pnpm build"` to `scripts`. This ensures the package is automatically built before publishing (e.g., when running `npm publish` or `pnpm publish`). This prevents accidentally publishing stale or missing build artifacts. +For each public package, add `"prepack": "nr build"` to `scripts`. This ensures the package is automatically built before publishing (e.g., when running `npm publish` or `pnpm publish`). This prevents accidentally publishing stale or missing build artifacts. + +### Releasing + +Run `nr ci` first — it must pass. Then `nr release`: `bumpp` bumps the version, commits, tags and pushes. The `v*` tag triggers `release.yml`, which builds and publishes via npm Trusted Publishing (OIDC). Do not publish from a local machine after the initial package creation; see [setting-up](setting-up.md#publishing-with-npm-trusted-publishing-oidc). diff --git a/plugins/antfu/.agents/skills/antfu/references/setting-up.md b/plugins/antfu/.agents/skills/antfu/references/setting-up.md index da3e6faa..f4541227 100644 --- a/plugins/antfu/.agents/skills/antfu/references/setting-up.md +++ b/plugins/antfu/.agents/skills/antfu/references/setting-up.md @@ -1,10 +1,12 @@ --- name: setting-up -description: Project setup files including .gitignore, GitHub Actions workflows, and VS Code extensions. Use when initializing new projects or adding CI/editor config. +description: Project setup files including .gitignore, GitHub Actions workflows, npm OIDC trusted publishing, and VS Code extensions. Use when initializing new projects or adding CI/editor/release config. --- # Project Setup +Start from [antfu/starter-ts](https://github.com/antfu/starter-ts); the files below mirror it. + ## .gitignore Create when `.gitignore` is not present: @@ -15,6 +17,7 @@ Create when `.gitignore` is not present: .cache .DS_Store .eslintcache +.githooks .idea .env .nuxt @@ -27,6 +30,7 @@ dist lib-cov logs node_modules +skills/npm-* temp ``` @@ -50,12 +54,12 @@ jobs: contents: read ``` -### Unit Test Workflow +### CI Workflow -**`.github/workflows/unit-test.yml`** - Run tests on push/PR: +**`.github/workflows/ci.yml`** - Run the checks on push/PR: ```yaml -name: Unit Test +name: CI on: push: @@ -92,6 +96,17 @@ jobs: id-token: write ``` +## Publishing with npm Trusted Publishing (OIDC) + +Prefer [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers) over long-lived `NPM_TOKEN` secrets. The workflow above authenticates with a short-lived GitHub OIDC token (`id-token: write`), so there is nothing to leak or rotate, and every release is built on CI from a tagged commit. + +One-time setup per package: + +1. Run `pnpm publish` locally once so the package exists on npm. +2. Open `https://www.npmjs.com/package//access` and link the GitHub repository + `release.yml` workflow as a trusted publisher. + +After that, `nr release` (`bumpp`) bumps the version, commits, tags and pushes; the tag triggers `release.yml`, which publishes. Never add an `NPM_TOKEN` secret to new projects, and don't run `npm publish` from a local machine for subsequent releases. + ## VS Code Extensions Configure in `.vscode/extensions.json`: diff --git a/plugins/antfu/agent/skills/antfu/SKILL.md b/plugins/antfu/agent/skills/antfu/SKILL.md index e4c8ed3a..107be794 100644 --- a/plugins/antfu/agent/skills/antfu/SKILL.md +++ b/plugins/antfu/agent/skills/antfu/SKILL.md @@ -6,8 +6,10 @@ description: Anthony Fu's opinionated tooling and conventions for mentions Anthony Fu's preferences. metadata: author: Anthony Fu - version: 2026.06.22 + version: 2026.09.30 --- +> Reference template: [antfu/starter-ts](https://github.com/antfu/starter-ts). When scaffolding a new TypeScript project, mirror its `package.json` scripts, `eslint.config.js`, `knip.json`, `tsdown.config.ts` and workflows rather than inventing a new layout. + ## Coding Practices ### Code Organization @@ -102,31 +104,82 @@ Prefer this over `npm view version` when you only need the latest version, ### ESLint Setup ```js -// eslint.config.mjs +// eslint.config.js import antfu from '@antfu/eslint-config' -export default antfu() +export default antfu({ + type: 'lib', // or 'app' (default) + pnpm: true, // enforce pnpm catalogs in package.json + antislop: true, // flag redundant/duplicated AI-style code, ban explicit `any` +}) ``` - -When completing tasks, run `pnpm run lint --fix` to format the code and fix coding style. +Always enable `antislop: true`. It requires `eslint-plugin-slop` and `eslint-plugin-sonarjs` as dev dependencies. For detailed configuration options: [antfu-eslint-config](references/antfu-eslint-config.md) +### Knip + +Use [Knip](https://knip.dev) to catch unused files, exports and dependencies — the linter only sees one file at a time. + +```json +// knip.json +{ + "$schema": "https://unpkg.com/knip@6/schema.json", + "project": ["src/**/*.ts"], + "ignoreDependencies": [] +} +``` + +Fix what Knip reports by deleting; only add to `ignoreDependencies` for tools invoked outside `package.json` scripts (e.g. `taze`). + +### Scripts and the `ci` Gate + +Every project exposes a `ci` script that runs all checks in one command: + +```json +{ + "scripts": { + "build": "tsdown", + "lint": "eslint --cache", + "typecheck": "tsc", + "knip": "knip", + "test": "pnpm run build && vitest", + "ci": "pnpm run lint && pnpm run typecheck && pnpm run knip && pnpm run test --run" + } +} +``` + +**Before every commit, run `nr lint --fix` to format, then `nr ci` and make sure it passes.** Do not commit with a failing `ci`; fix the root cause instead of silencing rules or adding ignores. + ### Git Hooks +`simple-git-hooks` + `nano-staged`. The pre-commit hook runs the full `ci` gate; `prepare` also installs agent skills via `skills-npm`: + ```json { + "scripts": { + "prepare": "git config core.hooksPath .githooks && simple-git-hooks && skills-npm" + }, "simple-git-hooks": { - "pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && npx lint-staged" + "pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && pnpm run ci && pnpx nano-staged" }, - "lint-staged": { "*": "eslint --fix" }, - "scripts": { - "prepare": "npx simple-git-hooks" + "nano-staged": { + "*": "eslint --fix --no-warn-ignored" } } ``` +Add `.githooks` to `.gitignore` and `simple-git-hooks: true` under `allowBuilds` in `pnpm-workspace.yaml`. + +### skills-npm + +[`skills-npm`](https://github.com/antfu/skills-npm) symlinks agent skills shipped inside installed npm packages (and fetches those declared in a `skills` field) into the agent's skills directory on every install. Install as a dev dependency and wire it into `prepare` as above; add `skills/npm-*` to `.gitignore`. Commit the generated `skills-npm-lock.json`. + +### Publishing + +Prefer npm Trusted Publishing (OIDC) over `NPM_TOKEN` secrets: releases run on CI from a `v*` tag via `release.yml` with `id-token: write`, and `nr release` (`bumpp`) only bumps, tags and pushes. Details: [setting-up](references/setting-up.md#publishing-with-npm-trusted-publishing-oidc). + ### pnpm Catalogs Use named catalogs in `pnpm-workspace.yaml` for version management: @@ -146,8 +199,8 @@ Avoid the default catalog. Catalog names can be adjusted per project needs. | Topic | Description | Reference | |-------|-------------|-----------| -| ESLint Config | Framework support, formatters, rule overrides, VS Code settings | [antfu-eslint-config](references/antfu-eslint-config.md) | -| Project Setup | .gitignore, GitHub Actions, VS Code extensions | [setting-up](references/setting-up.md) | +| ESLint Config | Framework support, antislop, formatters, rule overrides, VS Code settings | [antfu-eslint-config](references/antfu-eslint-config.md) | +| Project Setup | .gitignore, GitHub Actions, OIDC trusted publishing, VS Code extensions | [setting-up](references/setting-up.md) | | App Development | Vue/Nuxt/UnoCSS conventions, auto-import control, Storybook component testing | [app-development](references/app-development.md) | -| Library Development | tsdown bundling, pure ESM publishing | [library-development](references/library-development.md) | +| Library Development | tsdown bundling, pure ESM publishing, publint, API snapshots | [library-development](references/library-development.md) | | Monorepo | pnpm workspaces, centralized alias, Turborepo | [monorepo](references/monorepo.md) | diff --git a/plugins/antfu/agent/skills/antfu/references/antfu-eslint-config.md b/plugins/antfu/agent/skills/antfu/references/antfu-eslint-config.md index e92fcba5..9bb734d3 100644 --- a/plugins/antfu/agent/skills/antfu/references/antfu-eslint-config.md +++ b/plugins/antfu/agent/skills/antfu/references/antfu-eslint-config.md @@ -18,6 +18,12 @@ export default antfu({ // Project type: 'lib' for libraries, 'app' (default) for applications type: 'lib', + // Enforce pnpm catalogs / workspace settings in package.json + pnpm: true, + + // Always on: catch redundant, duplicated, AI-style code + antislop: true, + // Global ignores (extends defaults, doesn't override) ignores: ['**/fixtures', '**/dist'], @@ -37,6 +43,32 @@ export default antfu({ }) ``` +## Antislop + +```js +export default antfu({ + antislop: true, +}) +// Requires: pnpm add -D eslint-plugin-slop eslint-plugin-sonarjs +``` + +Enables [`eslint-plugin-slop`](https://github.com/antfu/eslint-plugin-slop) plus a curated subset of `eslint-plugin-sonarjs` targeting redundant and duplicated code, and bans explicit `any` when TypeScript is enabled. Enable it on every project; when it flags code, simplify the code rather than disabling the rule. + +To toggle a plugin or scope inspection to recent changes: + +```js +export default antfu({ + antislop: { + sonarjs: false, + slop: { + inspection: { mode: 'recent-changes', tracebackCommits: 5 }, + }, + }, +}) +``` + +Linters see one file at a time, so pair it with [Knip](https://knip.dev) for unused files/exports/dependencies (and `jscpd` for cross-file duplication when needed). + ## Framework Support ### Vue @@ -230,24 +262,28 @@ export default antfu({ }) ``` -## Lint-Staged Setup +## Pre-commit Setup ```json { + "scripts": { + "prepare": "git config core.hooksPath .githooks && simple-git-hooks && skills-npm" + }, "simple-git-hooks": { - "pre-commit": "pnpm lint-staged" + "pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && pnpm run ci && pnpx nano-staged" }, - "lint-staged": { - "*": "eslint --fix" + "nano-staged": { + "*": "eslint --fix --no-warn-ignored" } } ``` ```bash -pnpm add -D lint-staged simple-git-hooks -npx simple-git-hooks +pnpm add -D nano-staged simple-git-hooks skills-npm ``` +The hook runs the full `ci` script (lint, typecheck, knip, tests) before auto-fixing staged files, so a commit never lands with a red check. + ## VS Code Settings Add to `.vscode/settings.json`: diff --git a/plugins/antfu/agent/skills/antfu/references/library-development.md b/plugins/antfu/agent/skills/antfu/references/library-development.md index 55f00744..298477bc 100644 --- a/plugins/antfu/agent/skills/antfu/references/library-development.md +++ b/plugins/antfu/agent/skills/antfu/references/library-development.md @@ -11,6 +11,8 @@ description: Building and publishing TypeScript libraries with tsdown. Use when | Output | Pure ESM only (no CJS) | | DTS | Generated via tsdown | | Exports | Auto-generated via tsdown | +| Package validation | publint (via tsdown) | +| Publishing | npm Trusted Publishing (OIDC) from CI | ## tsdown Configuration @@ -25,6 +27,7 @@ export default defineConfig({ format: ['esm'], dts: true, exports: true, + publint: true, }) ``` @@ -33,6 +36,7 @@ export default defineConfig({ | `format` | `['esm']` | Pure ESM, no CommonJS | | `dts` | `true` | Generate `.d.ts` files | | `exports` | `true` | Auto-update `exports` field in `package.json` | +| `publint` | `true` | Validate `package.json`/`exports` on every build (requires `publint` dev dependency) | ### Multiple Entry Points @@ -129,21 +133,29 @@ Required fields for pure ESM library: ```json { "type": "module", - "main": "./dist/index.mjs", - "module": "./dist/index.mjs", + "sideEffects": false, "types": "./dist/index.d.mts", "files": ["dist"], "scripts": { "build": "tsdown", - "prepack": "pnpm build", - "test": "vitest", - "release": "bumpp -r" + "dev": "tsdown --watch", + "lint": "eslint --cache", + "typecheck": "tsc", + "knip": "knip", + "test": "pnpm run build && vitest", + "ci": "pnpm run lint && pnpm run typecheck && pnpm run knip && pnpm run test --run", + "prepack": "nr build", + "release": "bumpp" } } ``` -The `exports` field is managed by tsdown when `exports: true`. +The `exports` field is managed by tsdown when `exports: true`. Use `bumpp -r` in monorepos. ### prepack Script -For each public package, add `"prepack": "pnpm build"` to `scripts`. This ensures the package is automatically built before publishing (e.g., when running `npm publish` or `pnpm publish`). This prevents accidentally publishing stale or missing build artifacts. +For each public package, add `"prepack": "nr build"` to `scripts`. This ensures the package is automatically built before publishing (e.g., when running `npm publish` or `pnpm publish`). This prevents accidentally publishing stale or missing build artifacts. + +### Releasing + +Run `nr ci` first — it must pass. Then `nr release`: `bumpp` bumps the version, commits, tags and pushes. The `v*` tag triggers `release.yml`, which builds and publishes via npm Trusted Publishing (OIDC). Do not publish from a local machine after the initial package creation; see [setting-up](setting-up.md#publishing-with-npm-trusted-publishing-oidc). diff --git a/plugins/antfu/agent/skills/antfu/references/setting-up.md b/plugins/antfu/agent/skills/antfu/references/setting-up.md index da3e6faa..f4541227 100644 --- a/plugins/antfu/agent/skills/antfu/references/setting-up.md +++ b/plugins/antfu/agent/skills/antfu/references/setting-up.md @@ -1,10 +1,12 @@ --- name: setting-up -description: Project setup files including .gitignore, GitHub Actions workflows, and VS Code extensions. Use when initializing new projects or adding CI/editor config. +description: Project setup files including .gitignore, GitHub Actions workflows, npm OIDC trusted publishing, and VS Code extensions. Use when initializing new projects or adding CI/editor/release config. --- # Project Setup +Start from [antfu/starter-ts](https://github.com/antfu/starter-ts); the files below mirror it. + ## .gitignore Create when `.gitignore` is not present: @@ -15,6 +17,7 @@ Create when `.gitignore` is not present: .cache .DS_Store .eslintcache +.githooks .idea .env .nuxt @@ -27,6 +30,7 @@ dist lib-cov logs node_modules +skills/npm-* temp ``` @@ -50,12 +54,12 @@ jobs: contents: read ``` -### Unit Test Workflow +### CI Workflow -**`.github/workflows/unit-test.yml`** - Run tests on push/PR: +**`.github/workflows/ci.yml`** - Run the checks on push/PR: ```yaml -name: Unit Test +name: CI on: push: @@ -92,6 +96,17 @@ jobs: id-token: write ``` +## Publishing with npm Trusted Publishing (OIDC) + +Prefer [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers) over long-lived `NPM_TOKEN` secrets. The workflow above authenticates with a short-lived GitHub OIDC token (`id-token: write`), so there is nothing to leak or rotate, and every release is built on CI from a tagged commit. + +One-time setup per package: + +1. Run `pnpm publish` locally once so the package exists on npm. +2. Open `https://www.npmjs.com/package//access` and link the GitHub repository + `release.yml` workflow as a trusted publisher. + +After that, `nr release` (`bumpp`) bumps the version, commits, tags and pushes; the tag triggers `release.yml`, which publishes. Never add an `NPM_TOKEN` secret to new projects, and don't run `npm publish` from a local machine for subsequent releases. + ## VS Code Extensions Configure in `.vscode/extensions.json`: diff --git a/plugins/antfu/skills-lock.json b/plugins/antfu/skills-lock.json index 2aaebe12..807e63eb 100644 --- a/plugins/antfu/skills-lock.json +++ b/plugins/antfu/skills-lock.json @@ -5,7 +5,7 @@ "source": "antfu/skills", "sourceType": "github", "skillPath": "skills/antfu/SKILL.md", - "computedHash": "0aada58e7385bcc85abfd1d184ed7cf3757e1eb9e23d018e44ee6022093c5726" + "computedHash": "46935e4dc837849a1df6fa968c3ec32c647bd0faecf34f7a3577503f85d7bc52" } } } diff --git a/plugins/chat-sdk/.agents/skills/chat-sdk/SKILL.md b/plugins/chat-sdk/.agents/skills/chat-sdk/SKILL.md index 4e81c195..da786b3f 100644 --- a/plugins/chat-sdk/.agents/skills/chat-sdk/SKILL.md +++ b/plugins/chat-sdk/.agents/skills/chat-sdk/SKILL.md @@ -58,14 +58,15 @@ Listed in `node_modules/chat/resources/templates.json`: See the 'Official Adapters', 'Vendor-Official Adapters', and 'Community Adapters' sections in the [Chat SDK llms.txt file](https://chat-sdk.dev/llms.txt) for the current list of official, vendor-official, and community adapters. -### Adapter catalog subpath +### Adapter catalog package -Chat SDK exposes a zero-dependency static catalog at `chat/adapters`. +`@chat-adapter/catalog` is a zero-dependency static catalog of official, vendor-official, and community adapters. It replaces the deprecated `chat/adapters` subpath, which still re-exports the original API. -Agents can import `ADAPTERS`, `ADAPTER_NAMES`, `getAdapter`, `isAdapterSlug`, `listEnvVars`, `getSecretEnvVars`, and metadata types like `CatalogAdapter` and `AdapterSlug` from this subpath without importing any adapter implementation package. +Agents can import `listAdapters`, `getCatalogEntry`, `getFeatureSupport`, `ADAPTERS`, `ADAPTER_NAMES`, `getAdapter`, `isAdapterSlug`, `listEnvVars`, `getSecretEnvVars`, and metadata types like `CatalogAdapter`, `CatalogEntry`, and `AdapterSlug` without importing any adapter implementation package. Use it for: -- Listing official and vendor-official adapter slugs, names, npm packages, groups, and platform vs state types. +- Listing adapter slugs, names, npm packages, maintainers, groups, and platform vs state types. +- Checking which capabilities an adapter supports, such as streaming, modals, or distributed locking. - Building setup or onboarding flows that need package names, peer dependencies, and install guidance before any adapter is installed. -- Discovering required, optional, and credential-mode environment variables for an adapter, including which variables are secrets. -- Keeping vendor-official adapter docs and metadata aligned with the catalog when adding or updating a listed adapter. \ No newline at end of file +- Discovering required, optional, and credential-mode environment variables for official and vendor-official adapters, including which variables are secrets. +- Keeping adapter docs and metadata aligned with the catalog when adding or updating a listed adapter. \ No newline at end of file diff --git a/plugins/chat-sdk/agent/skills/chat-sdk/SKILL.md b/plugins/chat-sdk/agent/skills/chat-sdk/SKILL.md index 1f50ea90..d98c495e 100644 --- a/plugins/chat-sdk/agent/skills/chat-sdk/SKILL.md +++ b/plugins/chat-sdk/agent/skills/chat-sdk/SKILL.md @@ -63,14 +63,15 @@ Listed in `node_modules/chat/resources/templates.json`: See the 'Official Adapters', 'Vendor-Official Adapters', and 'Community Adapters' sections in the [Chat SDK llms.txt file](https://chat-sdk.dev/llms.txt) for the current list of official, vendor-official, and community adapters. -### Adapter catalog subpath +### Adapter catalog package -Chat SDK exposes a zero-dependency static catalog at `chat/adapters`. +`@chat-adapter/catalog` is a zero-dependency static catalog of official, vendor-official, and community adapters. It replaces the deprecated `chat/adapters` subpath, which still re-exports the original API. -Agents can import `ADAPTERS`, `ADAPTER_NAMES`, `getAdapter`, `isAdapterSlug`, `listEnvVars`, `getSecretEnvVars`, and metadata types like `CatalogAdapter` and `AdapterSlug` from this subpath without importing any adapter implementation package. +Agents can import `listAdapters`, `getCatalogEntry`, `getFeatureSupport`, `ADAPTERS`, `ADAPTER_NAMES`, `getAdapter`, `isAdapterSlug`, `listEnvVars`, `getSecretEnvVars`, and metadata types like `CatalogAdapter`, `CatalogEntry`, and `AdapterSlug` without importing any adapter implementation package. Use it for: -- Listing official and vendor-official adapter slugs, names, npm packages, groups, and platform vs state types. +- Listing adapter slugs, names, npm packages, maintainers, groups, and platform vs state types. +- Checking which capabilities an adapter supports, such as streaming, modals, or distributed locking. - Building setup or onboarding flows that need package names, peer dependencies, and install guidance before any adapter is installed. -- Discovering required, optional, and credential-mode environment variables for an adapter, including which variables are secrets. -- Keeping vendor-official adapter docs and metadata aligned with the catalog when adding or updating a listed adapter. \ No newline at end of file +- Discovering required, optional, and credential-mode environment variables for official and vendor-official adapters, including which variables are secrets. +- Keeping adapter docs and metadata aligned with the catalog when adding or updating a listed adapter. \ No newline at end of file diff --git a/plugins/chat-sdk/skills-lock.json b/plugins/chat-sdk/skills-lock.json index 27638740..7e529544 100644 --- a/plugins/chat-sdk/skills-lock.json +++ b/plugins/chat-sdk/skills-lock.json @@ -5,7 +5,7 @@ "source": "vercel/chat", "sourceType": "github", "skillPath": "skills/chat/SKILL.md", - "computedHash": "dd2e9e18aa536e4f2625a6830247e4f593ba0c65ba7527365c18375c2cc618bd" + "computedHash": "15ee91013fc51b443d2de8f0e81831b4f45d73ef59f0028472eeef62fd2dd22d" } } } diff --git a/plugins/edgeparse/.agents/skills/edgeparse/SKILL.md b/plugins/edgeparse/.agents/skills/edgeparse/SKILL.md index 0c594b6c..54329ef2 100644 --- a/plugins/edgeparse/.agents/skills/edgeparse/SKILL.md +++ b/plugins/edgeparse/.agents/skills/edgeparse/SKILL.md @@ -1,6 +1,6 @@ --- name: edgeparse -description: Extract structured content from any PDF for AI agents, RAG pipelines, and Copilot Skills. Use this skill whenever the user wants to read, analyze, or reason about a PDF document; needs to feed document content to an LLM; mentions PDF extraction, parsing, or conversion; wants tables, headings, or bounding boxes from a PDF; is building a RAG pipeline; or asks an agent to process a document. Install with: pip install edgeparse +description: "Extract structured content from any PDF for AI agents, RAG pipelines, and Copilot Skills. Use this skill whenever the user wants to read, analyze, or reason about a PDF document; needs to feed document content to an LLM; mentions PDF extraction, parsing, or conversion; wants tables, headings, or bounding boxes from a PDF; is building a RAG pipeline; or asks an agent to process a document. Install with: pip install edgeparse" license: Apache-2.0 metadata: authors: "EdgeParse Contributors" diff --git a/plugins/edgeparse/skills-lock.json b/plugins/edgeparse/skills-lock.json index 12e742d9..4aa88490 100644 --- a/plugins/edgeparse/skills-lock.json +++ b/plugins/edgeparse/skills-lock.json @@ -3,7 +3,9 @@ "skills": { "edgeparse": { "source": "raphaelmansuy/edgeparse", - "sourceType": "github" + "sourceType": "github", + "skillPath": "skills/edgeparse/SKILL.md", + "computedHash": "e710ee51a1362bd4f707f4041291cc3ddc87b4c31fa3a977af28a3c52f4be24c" } } } diff --git a/plugins/emulate/.agents/skills/apple/SKILL.md b/plugins/emulate/.agents/skills/apple/SKILL.md index f9f0a268..8d119c7f 100644 --- a/plugins/emulate/.agents/skills/apple/SKILL.md +++ b/plugins/emulate/.agents/skills/apple/SKILL.md @@ -10,6 +10,8 @@ Sign in with Apple emulation with authorization code flow, PKCE support, RS256 I ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Apple only npx emulate --service apple @@ -239,3 +241,7 @@ curl -X POST $APPLE_URL/auth/token \ ### Private Relay Email When a user has `is_private_email: true` in the seed config, the `id_token` will contain a generated `@privaterelay.appleid.com` email instead of the user's real email. This matches Apple's Hide My Email behavior. + +## Custom emulators alongside built-ins + +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "apple" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/.agents/skills/aws/SKILL.md b/plugins/emulate/.agents/skills/aws/SKILL.md index 56511349..0edab7ea 100644 --- a/plugins/emulate/.agents/skills/aws/SKILL.md +++ b/plugins/emulate/.agents/skills/aws/SKILL.md @@ -1,15 +1,17 @@ --- name: aws -description: Emulated AWS cloud services (S3, SQS, IAM, STS) for local development and testing. Use when the user needs to interact with AWS API endpoints locally, test S3 bucket and object operations, emulate SQS queues and messages, manage IAM users/roles/access keys, test STS assume role, or work without hitting real AWS APIs. Triggers include "AWS emulator", "emulate AWS", "mock S3", "local SQS", "test IAM", "emulate S3", "AWS locally", "STS assume role", or any task requiring local AWS service emulation. +description: Emulated AWS cloud services (S3, SQS, Cognito, IAM, STS) for local development and testing. Use when the user needs to interact with AWS API endpoints locally, test S3 bucket and object operations, emulate SQS queues and messages, test Cognito sign-up, sign-in, JWTs, or Identity Pools, manage IAM users/roles/access keys, test STS assume role, or work without hitting real AWS APIs. Triggers include "AWS emulator", "emulate AWS", "mock S3", "local SQS", "test IAM", "emulate S3", "AWS locally", "STS assume role", or any task requiring local AWS service emulation. allowed-tools: Bash(npx emulate:*), Bash(curl:*) --- # AWS Emulator -S3, SQS, IAM, and STS emulation with AWS SDK-compatible S3 paths and query-style SQS/IAM/STS endpoints. S3 uploads and downloads preserve arbitrary binary payloads, including raw byte lengths and ETags. All state is in-memory, and responses use AWS-compatible XML. +S3, SQS, Cognito, IAM, and STS emulation with AWS SDK-compatible S3 paths. S3 uploads and downloads preserve arbitrary binary payloads, including raw byte lengths and ETags. SQS supports AWS JSON 1.0 and XML Query protocols; IAM and STS use XML Query. All state is in-memory. ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # AWS only npx emulate --service aws @@ -170,7 +172,16 @@ curl -X PUT http://localhost:4006/dest-bucket/copy.txt \ ### SQS -All SQS operations use `POST /sqs/` with `Action` as a form-urlencoded parameter. +Current AWS SDK clients use the `/sqs` endpoint shown above. SQS accepts AWS JSON 1.0 requests at `POST /sqs/` with `Content-Type: application/x-amz-json-1.0` and `X-Amz-Target: AmazonSQS.`, returning JSON results and errors. XML Query requests with an `Action` form parameter remain supported. Queue operations also accept the returned `QueueUrl` as the endpoint. + +```bash +# List queues using AWS JSON 1.0 +curl -X POST http://localhost:4006/sqs/ \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/x-amz-json-1.0" \ + -H "X-Amz-Target: AmazonSQS.ListQueues" \ + -d '{}' +``` ```bash # Create queue @@ -297,6 +308,56 @@ curl -X POST http://localhost:4006/iam/ \ -d "Action=DeleteRole&RoleName=my-role" ``` +### Cognito + +Use `CognitoIdentityProviderClient` with endpoint `/cognito-idp` and `CognitoIdentityClient` with `/cognito-identity`. Both accept AWS JSON 1.1 and work with the official AWS SDK v3: + +```typescript +import { CognitoIdentityProviderClient, InitiateAuthCommand } from "@aws-sdk/client-cognito-identity-provider"; + +const cognito = new CognitoIdentityProviderClient({ + endpoint: "http://localhost:4007/cognito-idp", + region: "us-east-1", + credentials: { accessKeyId: "local-access-key", secretAccessKey: "local-secret" }, +}); +const result = await cognito.send(new InitiateAuthCommand({ + ClientId: "emulateclient", + AuthFlow: "USER_PASSWORD_AUTH", + AuthParameters: { USERNAME: "developer", PASSWORD: "LocalPass123!" }, +})); +``` + +A default User Pool `us-east-1_emulate`, public app client `emulateclient`, and Identity Pool `us-east-1:00000000-0000-4000-8000-000000000001` are created without users. Seed a confirmed account for the example above: + +```yaml +aws: + cognito: + user_pools: + - id: us-east-1_emulate + confirmation_code: "123456" + users: + - username: developer + password: LocalPass123! + attributes: + email: dev@example.com + email_verified: "true" + name: Developer +``` + +User pools also accept `name` and `clients` with `client_id`, optional `client_secret`, exact `redirect_uris`, and allowed `scopes`. Users accept `status` (`CONFIRMED`, `UNCONFIRMED`, or `FORCE_CHANGE_PASSWORD`) and `enabled`. Passwords require at least eight characters and are stored as salted hashes. Confidential clients require Cognito's HMAC `SecretHash` or `SECRET_HASH` for SDK flows. + +- `SignUp`, `ConfirmSignUp`, `ResendConfirmationCode`, `ForgotPassword`, and `ConfirmForgotPassword` model account confirmation and recovery. Codes expire after ten minutes and use the pool's `confirmation_code` fixture, default `123456`. No email or SMS is sent. Inspect active codes at `/_inspector?tab=cognito`; seeded unconfirmed users can request a code with `ResendConfirmationCode`. +- `InitiateAuth` supports `USER_PASSWORD_AUTH`, `REFRESH_TOKEN_AUTH`, and `REFRESH_TOKEN`. `AdminInitiateAuth` also supports `ADMIN_USER_PASSWORD_AUTH`. Admin-created temporary passwords return `NEW_PASSWORD_REQUIRED`, completed with `RespondToAuthChallenge` or `AdminRespondToAuthChallenge`. Challenge sessions expire after three minutes and are bound to the user and client. +- `GetUser`, `ChangePassword`, `GlobalSignOut`, and `RevokeToken` use issued tokens. `AdminCreateUser` (with `TemporaryPassword`), `AdminGetUser`, `AdminConfirmSignUp`, `AdminSetUserPassword`, `AdminEnableUser`, `AdminDisableUser`, `AdminDeleteUser`, and paginated `ListUsers` support test setup and administration. + +Tokens are RS256 JWTs with Cognito `token_use` claims. ID tokens identify the app client in `aud`; access tokens use `client_id`. ID and access tokens expire after one hour; refresh tokens after thirty days. Sign-out, revocation, disabling, deletion, and password resets invalidate affected sessions. Users, sessions, identities, and signing keys participate in snapshots, restore, and reset. + +The local issuer is `http://localhost:4007/cognito-idp/USER_POOL_ID`. Discovery and public keys are served at `ISSUER/.well-known/openid-configuration` and `ISSUER/.well-known/jwks.json`. Configure token verifiers with this local issuer and JWKS. `ISSUER/oauth2/authorize` presents a local user picker, `ISSUER/oauth2/token` exchanges authorization codes or refresh tokens, and `ISSUER/oauth2/userInfo` returns scoped profile claims. Authorization codes expire after five minutes, are single-use, and bind the client, exact redirect URI, and optional S256 PKCE challenge. Client authentication accepts `client_secret_basic` or `client_secret_post`. The default client permits `http://localhost:3000/callback`. + +Identity Pools support `GetId` and `GetCredentialsForIdentity`. Supply the ID token in `Logins` under `cognito-idp.REGION.amazonaws.com/USER_POOL_ID`. The default Identity Pool accepts the default User Pool and app client. Custom `aws.cognito.identity_pools` entries take `id`, optional `name`, `providers: [{ user_pool_id, client_id }]`, and `allow_unauthenticated_identities` (default `false`). Identities remain stable for the same authenticated subject. Credentials expire after one hour; login signatures, expiry, audience, and identity ownership are checked. + +SRP, MFA, external identity providers, Lambda triggers, pool/client management APIs, `ListUsers` filters, and Identity Pool role selection are not simulated. Administrative requests follow the AWS emulator's permissive credential behavior: SigV4 signatures and IAM policies are not evaluated. Identity Pool credentials are generated test credentials, without role or policy evaluation. + ### STS All STS operations use `POST /sts/` with `Action` as a form-urlencoded parameter. @@ -383,3 +444,7 @@ curl -X POST $BASE/iam/ \ -H "Authorization: Bearer $TOKEN" \ -d "Action=CreateAccessKey&UserName=ci-user" ``` + +## Custom emulators alongside built-ins + +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "aws" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/.agents/skills/emulate/SKILL.md b/plugins/emulate/.agents/skills/emulate/SKILL.md index ea60d764..bbb75ffc 100644 --- a/plugins/emulate/.agents/skills/emulate/SKILL.md +++ b/plugins/emulate/.agents/skills/emulate/SKILL.md @@ -14,7 +14,7 @@ Local drop-in replacement services for CI and no-network sandboxes. Fully statef npx emulate ``` -All services start with sensible defaults: +All services listen on IPv4 loopback (`127.0.0.1`) by default: | Service | Default Port | |-----------|-------------| @@ -66,6 +66,7 @@ npx emulate list | Flag | Default | Description | |------|---------|-------------| | `-p, --port` | `4000` | Base port (auto-increments per service) | +| `--host` | `127.0.0.1` | Listening address; use `0.0.0.0` to allow network access | | `-s, --service` | all | Comma-separated services to enable | | `--seed` | auto-detect | Path to seed config (YAML or JSON) | | `--base-url` | none | Override advertised base URL (supports `{service}` template) | @@ -74,6 +75,8 @@ npx emulate list The port can also be set via `EMULATE_PORT` or `PORT` environment variables. +For access from a container or another machine, use `npx emulate start --host 0.0.0.0`. Set `--base-url` to a URL reachable by those clients when using OAuth redirects or other advertised URLs. + The generated-secrets destination must not exist. emulate removes inherited ACLs, verifies effective owner-only access, and publishes complete JSON before opening listeners or configuring portless. Handled startup failures remove the invocation-owned artifact. A hard termination can leave a complete artifact that must be removed manually after confirming no invocation is using it. Only service-generated values appear in the artifact. Linux requires `setfacl` and `getfacl` from the `acl` package. The flag fails closed when access controls cannot be verified and is not supported on Windows. The advertised base URL (used in OAuth redirects, webhook URLs, etc.) can be overridden via `--base-url`, the `EMULATE_BASE_URL` env var (supports `{service}` template), or per-service `baseUrl` in the seed config. When running under portless, the `PORTLESS_URL` env var is also detected automatically. @@ -107,9 +110,12 @@ For GitHub App tests, inspect secret-free minted installation-token metadata at |--------|---------|-------------| | `service` | *(required)* | `'vercel'`, `'github'`, `'google'`, `'slack'`, `'apple'`, `'microsoft'`, `'okta'`, `'aws'`, `'resend'`, `'stripe'`, `'mongoatlas'`, `'clerk'`, `'linear'`, or `'twilio'` | | `port` | `4000` | Port for the HTTP server | +| `hostname` | `127.0.0.1` | Listening address for built-in and custom emulators | | `seed` | none | Inline seed data (same shape as YAML config) | | `baseUrl` | none | Override advertised base URL. Per-service `baseUrl` in seed config takes highest priority, then this option, then `EMULATE_BASE_URL` env var (supports `{service}`), then `PORTLESS_URL` (supports `{service}`, automatically set by the `portless` CLI wrapper), then `http://localhost:`. | +Use `hostname: '0.0.0.0'` to allow connections from containers or other machines, and `baseUrl` to advertise a URL reachable by those clients. + ### Instance Methods | Method | Description | diff --git a/plugins/emulate/.agents/skills/github/SKILL.md b/plugins/emulate/.agents/skills/github/SKILL.md index c2039ed7..598f3eb5 100644 --- a/plugins/emulate/.agents/skills/github/SKILL.md +++ b/plugins/emulate/.agents/skills/github/SKILL.md @@ -10,6 +10,8 @@ Fully stateful GitHub REST API emulation. Creates, updates, and deletes persist ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # GitHub only npx emulate --service github @@ -543,6 +545,10 @@ curl -X POST http://localhost:4001/repos/octocat/hello-world/check-runs \ # Automatic suite status rollup from check run results ``` +Check-run lists by ref and suite support `page` and `per_page` (default 30, maximum 100) with pagination `Link` headers. Ref lists apply `check_name`, `status`, and `filter` before pagination; `total_count` reports the full filtered count. + +Checks created with installation tokens include their GitHub App in API responses and webhooks. Suites are separated by App and commit, and `filter=latest` keeps the newest run for each App and check name. User-token checks return `app: null`; request-body `app_id` cannot set or change attribution. + ### OAuth ```bash @@ -633,3 +639,7 @@ The inspection route is emulator-specific. It lists App, installation, account, 3. Emulator redirects back with `?code=...&state=...` 4. Exchange code for token via `POST /login/oauth/access_token` 5. Use token to call API endpoints + +## Custom emulators alongside built-ins + +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "github" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/.agents/skills/google/SKILL.md b/plugins/emulate/.agents/skills/google/SKILL.md index 77c0bfbe..2f61e813 100644 --- a/plugins/emulate/.agents/skills/google/SKILL.md +++ b/plugins/emulate/.agents/skills/google/SKILL.md @@ -10,6 +10,8 @@ OAuth 2.0 and OpenID Connect emulation with authorization code flow, PKCE suppor ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Google only npx emulate --service google @@ -179,6 +181,8 @@ google: When no OAuth clients are configured, the emulator accepts any `client_id`. With clients configured, strict validation is enforced for `client_id`, `client_secret`, and `redirect_uri`. +Google authentication is permissive by default for local bearer-token workflows. Set `google.strict_scopes: true` in seed config to require emulator-issued, unexpired access tokens on Gmail, Calendar, Drive, and userinfo routes. Invalid credentials return `401 UNAUTHENTICATED`; missing method scopes return `403` with `insufficientPermissions`. Strict mode distinguishes read, write, send, settings, and metadata scopes, and userinfo includes only granted identity claims. Revoking an access or refresh token invalidates the linked access and refresh credentials. Discovery, signing keys, and the sign-in page remain public. This mode checks method scopes; resource-specific grants such as `drive.file` and Gmail add-on scopes are not modeled. + ### Hosted domain (hd) claim Google Workspace accounts include an `hd` claim in ID tokens and userinfo responses identifying the user's hosted domain. The emulator derives this automatically from the user's email domain. Consumer domains (`gmail.com`, `googlemail.com`) omit the claim, matching real Google behavior. diff --git a/plugins/emulate/.agents/skills/linear/SKILL.md b/plugins/emulate/.agents/skills/linear/SKILL.md index 2511a752..c4dc8e74 100644 --- a/plugins/emulate/.agents/skills/linear/SKILL.md +++ b/plugins/emulate/.agents/skills/linear/SKILL.md @@ -10,6 +10,8 @@ Stateful Linear GraphQL API emulation with organizations, users, teams, workflow ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Linear only npx emulate --service linear @@ -125,3 +127,7 @@ Open `GET /` in the Linear emulator to inspect issues, teams, users, projects, a ## Current Limits Full Linear schema coverage, exact production rate limiting, notification inbox behavior, rich document APIs, customer APIs, initiative APIs, exact search relevance, and full production agent behavior are not implemented. + +## Custom emulators alongside built-ins + +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "linear" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/.agents/skills/microsoft/SKILL.md b/plugins/emulate/.agents/skills/microsoft/SKILL.md index b18a5d1f..95b979aa 100644 --- a/plugins/emulate/.agents/skills/microsoft/SKILL.md +++ b/plugins/emulate/.agents/skills/microsoft/SKILL.md @@ -10,6 +10,8 @@ Microsoft Entra ID (Azure AD) v2.0 OAuth 2.0 and OpenID Connect emulation with a ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Microsoft only npx emulate --service microsoft @@ -361,3 +363,7 @@ const client = new microsoftIssuer.Client({ redirect_uris: ['http://localhost:3000/api/auth/callback/microsoft-entra-id'], }) ``` + +## Custom emulators alongside built-ins + +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "microsoft" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/.agents/skills/next/SKILL.md b/plugins/emulate/.agents/skills/next/SKILL.md index 2f455043..de57f8a3 100644 --- a/plugins/emulate/.agents/skills/next/SKILL.md +++ b/plugins/emulate/.agents/skills/next/SKILL.md @@ -26,7 +26,7 @@ import { createEmulateHandler } from '@emulators/adapter-next' import * as github from '@emulators/github' import * as google from '@emulators/google' -export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({ +export const { GET, POST, PUT, PATCH, DELETE, OPTIONS } = createEmulateHandler({ services: { github: { emulator: github, @@ -108,7 +108,7 @@ const kvAdapter = { async save(data: string) { await kv.set('emulate-state', data) }, } -export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({ +export const { GET, POST, PUT, PATCH, DELETE, OPTIONS } = createEmulateHandler({ services: { github: { emulator: github } }, persistence: kvAdapter, }) @@ -181,3 +181,7 @@ interface PersistenceAdapter { ``` `initialize` must atomically create the initial value or return the value another instance created first. Implement it with compare-and-set semantics such as Redis `SET NX`. The built-in `filePersistence(path)` from `@emulators/core` provides this behavior for local development. + +## Custom emulators + +The adapter accepts definitions created with `defineEmulator` from `emulate` in `services[name].emulator`. Reuse the definition from CLI/tests; custom inspector is opt-in. Root-relative redirect locations stay under the service mount, while custom HTML bodies pass through unchanged. Await the returned handler's `close()` in test teardown. Install `emulate` as a runtime dependency for deployed definitions. See https://emulate.dev/docs/custom-emulators for seeds, snapshots, persistence, and lifecycle rules. diff --git a/plugins/emulate/.agents/skills/resend/SKILL.md b/plugins/emulate/.agents/skills/resend/SKILL.md index 41d84036..09aa2947 100644 --- a/plugins/emulate/.agents/skills/resend/SKILL.md +++ b/plugins/emulate/.agents/skills/resend/SKILL.md @@ -8,10 +8,12 @@ allowed-tools: Bash(npx emulate:*), Bash(curl:*) Fully stateful Resend API emulation. Emails, domains, API keys, audiences, and contacts persist in memory. Sent emails are captured and viewable through the inbox UI or the REST API. -No real emails are sent. Every call to `POST /emails` stores the message locally so you can inspect it programmatically or in the browser. +No real emails are sent. Accepted calls to `POST /emails` store the message locally so you can inspect it programmatically or in the browser. Validated sends also record attempts, including recipient rejections. ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Resend only npx emulate --service resend @@ -214,6 +216,25 @@ curl -X POST http://localhost:4000/emails \ -d '{"from": "hello@example.com", "to": "user@example.com", "subject": "Hello"}' ``` +### Recipient restrictions and send attempts + +Set `resend.allowlist` in seed config to test recipient rejection: + +```yaml +resend: + allowlist: + - allowed@example.com + - copy@example.com +``` + +Omitting `allowlist` accepts every recipient; `allowlist: []` rejects every recipient. Every address in `to`, `cc`, and `bcc` must be listed. Matching ignores surrounding whitespace and letter case; entries are individual addresses, with no wildcard or domain matching. This is a local testing control, independent of Resend's domain verification and account restrictions. + +A rejected send returns `403 validation_error` naming the rejected addresses and configured allowlist. No email ID, email record, idempotency record, or webhook is created. A batch is rejected as a whole if any recipient is disallowed. Retrying a rejected request, even with the same idempotency key, creates a new attempt. Replaying an accepted idempotent request returns its original response even if the allowlist has since changed. + +Validated sends append one record per email to the `resend.send_attempts` store collection, including emails in a rejected batch. Each record contains `from`, `to`, `cc`, `bcc`, `subject`, `idempotency_key` (or `null`), and the store's `id`, `created_at`, and `updated_at`. Message bodies are not copied into attempts. Malformed requests, invalid or conflicting idempotency keys, and idempotency replays do not append attempts. + +Read attempts with `getResendStore(store).sendAttempts.all()` from `@emulators/resend`, or inspect the persisted state. Shared adapter snapshots use the key `resend:resend.send_attempts`. Attempts and recipient restrictions survive saved-state restoration, including rejected sends. There is no additional Resend API endpoint for attempts. A rejected send has an attempt without an email; a send that never happened has neither. + ### Domains ```bash @@ -350,6 +371,6 @@ const { data: emails } = await res.json() console.log(emails[0].html) // contains "123456" ``` -## Custom HTTP APIs alongside built-ins +## Custom emulators alongside built-ins -Use `npx emulate init --custom inventory` to scaffold a custom stateful API and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "resend" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing custom APIs, see https://emulate.dev/docs/custom-apis. +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "resend" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/.agents/skills/slack/SKILL.md b/plugins/emulate/.agents/skills/slack/SKILL.md index 520b7112..2eb2b401 100644 --- a/plugins/emulate/.agents/skills/slack/SKILL.md +++ b/plugins/emulate/.agents/skills/slack/SKILL.md @@ -12,6 +12,8 @@ Slack message text is limited to 40,000 Unicode characters across chat writes, i ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Slack only npx emulate --service slack @@ -40,7 +42,7 @@ curl -X POST http://localhost:4003/api/auth.test \ Requests without a token return `not_authed`. In relaxed scope mode, any non-empty unknown bearer token maps to the first seeded user. -Scope checks are relaxed by default for local development. Set `slack.strict_scopes: true` in seed config when you need supported Web API methods to return Slack-style `missing_scope` errors with `needed` and `provided` fields. Strict mode checks `chat:write`, `channels:read`, `channels:history`, `channels:join`, `channels:manage`, `channels:write`, `groups:read`, `groups:history`, `groups:write`, `im:read`, `im:history`, `im:write`, `mpim:read`, `mpim:history`, `mpim:write`, `users:read`, `users:read.email`, `users.profile:read`, `users.profile:write`, `users:write`, `files:read`, `files:write`, `pins:read`, `pins:write`, `bookmarks:read`, `bookmarks:write`, `reactions:read`, `reactions:write`, and `team:read`. Slack lists no method-specific scopes for `views.publish`, `views.open`, `views.update`, or `views.push`, so the emulator requires auth but does not add strict-scope checks for those methods. +Scope checks are relaxed by default for local development. Set `slack.strict_scopes: true` in seed config when you need supported Web API methods to return Slack-style `missing_scope` errors with `needed` and `provided` fields. Strict mode checks `chat:write`, `channels:read`, `channels:history`, `channels:join`, `channels:manage`, `channels:write`, `groups:read`, `groups:history`, `groups:write`, `im:read`, `im:history`, `im:write`, `mpim:read`, `mpim:history`, `mpim:write`, `users:read`, `users:read.email`, `users.profile:read`, `users.profile:write`, `users:write`, `files:read`, `files:write`, `pins:read`, `pins:write`, `bookmarks:read`, `bookmarks:write`, `reactions:read`, `reactions:write`, `team:read`, and `admin.conversations:write`. Slack lists no method-specific scopes for `views.publish`, `views.open`, `views.update`, or `views.push`, so the emulator requires auth but does not add strict-scope checks for those methods. ## Pointing Your App at the Emulator @@ -200,6 +202,8 @@ slack: When no OAuth apps are configured, the emulator accepts any `client_id`. With apps configured, strict validation is enforced for `client_id`, `client_secret`, and `redirect_uri`. +`signing_secret` signs every outbound event subscription callback. Configure the receiving app with the same secret; signed callbacks include `X-Slack-Request-Timestamp` and `X-Slack-Signature`, with `X-Slack-Signature` set to `v0=:")>`. Verify against the unparsed request body. When the secret is absent or empty, callbacks are unsigned. + ## API Endpoints ### Auth @@ -548,7 +552,7 @@ curl -X POST http://localhost:4003/api/views.generateTriggerId \ -H "Content-Type: application/json" \ -d '{"user_id": "U000000001"}' -# Open a modal view with the returned trigger_id or interactivity_pointer within 3 seconds +# Open a modal view with the returned trigger_id or interactivity_pointer before it expires curl -X POST http://localhost:4003/api/views.open \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ @@ -632,6 +636,31 @@ curl -X POST http://localhost:4003/services/T000000001/B000000001/X000000001 \ -d '{"text": "Thread update", "thread_ts": "1234567890.123456"}' ``` +### Interactivity + +Buttons and modal submissions can invoke your app's Interactivity Request URL. Configure `interactivity_url` on a seeded `slack.oauth_apps` entry and set `slack.signing_secret` to the same secret used by your receiver: + +```yaml +slack: + signing_secret: local-signing-secret + oauth_apps: + - app_id: A_MY_APP + client_id: my-client + client_secret: my-secret + name: My App + redirect_uris: [] + interactivity_url: http://localhost:3000/slack/interactions +``` + +- `POST /_slack/simulate/block-actions` accepts `channel`, `message_ts`, and a button selector (`action_id`, exact `text`, or `url`), plus the acting `user` ID or name. It supports buttons in actions blocks and section accessories, including ephemeral messages visible to that user. The simulator requires an authenticated emulator token and the acting user must belong to the conversation. +- `POST /_slack/simulate/view-submission` accepts `view_id`, optional `user`, and `values` keyed by input block ID. Text inputs take strings; static selects and radio buttons take an option value; multi-selects and checkboxes take arrays of option values. Omitted values retain previous input or initial values. Other input elements return `unsupported_element`. + +Callbacks are `application/x-www-form-urlencoded` with one `payload` JSON field, signed over the exact encoded request body. Button payloads include an app-bound, single-use `trigger_id` that can open a modal for the acting user. Triggers expire after three seconds by default; set `slack.view_trigger_ttl_seconds` to a positive number to allow more time in local tests. App ownership is preserved for messages posted with an app's bot or user token. Simulators return the payload and `delivery_id`; attempts and delivery results are saved in `slack.interaction_deliveries`. + +A button payload's `response_url` accepts JSON without a bearer token, up to five times within 30 minutes. It posts an ephemeral reply by default, or a public reply with `response_type: "in_channel"`. Use `replace_original: true` or `delete_original: true` to modify the source message. Replacement retains the source message's visibility. Rich blocks and thread replies are supported. + +A modal receiver must acknowledge within three seconds. An empty HTTP 200 closes the submitted view, while `response_action: "clear"` closes its stack. `errors` retains the view and returns block errors; `update` replaces its contents; `push` adds a new view subject to the three-view limit. Delivery failures and invalid acknowledgements leave the modal open. Shortcuts, slash commands, non-button actions, `view_closed`, and `block_suggestion` are not simulated. + ### OAuth ```bash @@ -660,15 +689,29 @@ Returns a Slack-style response: } ``` +`POST /api/apps.uninstall` accepts `client_id`, `client_secret`, and an installation's user token in the Authorization header or POST `token` field. It removes that app's installation in the token's workspace and revokes all associated OAuth bot and user tokens, including earlier grants. Revoked credentials return `token_revoked` on Web API calls even with permissive authentication. Other apps and workspaces keep their credentials. Granular bot tokens cannot uninstall an app and return `no_permission`; configured development tokens without an app binding may uninstall a local installation. No method scope is required. + +Uninstall dispatches `app_uninstalled` and `tokens_revoked` to matching event subscriptions with the removed app's `api_app_id`. `tokens_revoked.tokens.oauth` and `.bot` contain deduplicated user IDs, never token strings. Credentials are invalid before callbacks are delivered. Handlers should accept either event order. The app definition remains available for a new OAuth installation; reinstalling issues fresh tokens without reactivating revoked ones. + ### Inspector Open `GET /` in the Slack emulator to inspect conversations, messages, files, views, auth records, incoming webhooks, event subscriptions, and event deliveries. ## Event Dispatching -When supported Slack writes mutate state, the emulator dispatches `event_callback` payloads to configured webhook URLs. These payloads match Slack's Events API format: +For supported Slack writes that emit events, the emulator dispatches `event_callback` payloads to configured webhook URLs. These payloads match Slack's Events API format: + +Each callback includes outer `token`, `api_app_id`, `authorizations`, `team_id`, `event_id`, and integer Unix-seconds `event_time` alongside the inner `event`. For authenticated Web API writes, `team_id` comes from the presented Slack token's installation. Development tokens without a stored Slack record fall back to the affected channel, user, or file's team, then the seeded workspace team (or `T000000001`). Incoming webhook posts use their webhook record's team, or the target channel's team when no record matches. Each logical event gets a distinct `event_id` shared by deliveries to multiple subscribers. + +Callbacks identify each workspace installation with its recipient `api_app_id` and bot authorization. With multiple installed apps, each receives a separate envelope; configured webhook subscriptions are shared and can route by `api_app_id`. Without an installation, callbacks use `A000000001` and a local user authorization. The legacy `token` field defaults to `emulate-verification-token`; set `slack.verification_token` to match receivers that check it. Use `slack.signing_secret` for request signature verification. + +Every inner event includes `event_ts`. Message events include `channel_type` (`channel`, `group`, `im`, or `mpim`). Posts made with an issued or seeded bot token include the bot's `bot_id`, `app_id`, and `bot_profile` when the corresponding bot record exists. These fields persist in history, replies, and updates so handlers can identify their own messages. Removing an installed bot with `conversations.kick` or `conversations.leave` also emits `channel_left` or `group_left`, with `actor_id` and `event_ts`, for the affected app. + +To test bot handlers, invite an installed app's `bot_user_id` with `conversations.invite`, subscribe to `app_mention`, then post text containing `<@BOT_USER_ID>` with `chat.postMessage`. Channel mentions produce one callback per mentioned app, with outer `api_app_id`, inner `event_ts`, and the original message and thread identity. DMs and MPIMs use `message` events. - `message` events on `chat.postMessage` +- `app_mention` on `chat.postMessage` when channel message text mentions an installed member bot +- `app_uninstalled` / `tokens_revoked` on successful `apps.uninstall` - `message` with `subtype: message_changed` on `chat.update` - `message` with `subtype: message_deleted` on `chat.delete` - rich message fields are included on posted `message` events when present @@ -680,15 +723,64 @@ When supported Slack writes mutate state, the emulator dispatches `event_callbac - `channel_rename` / `group_rename` and matching name message subtypes on `conversations.rename` - `message` with public `channel_topic` / `channel_purpose` or private `group_topic` / `group_purpose` subtypes on topic and purpose writes - `member_joined_channel` / `member_left_channel` on invite, join, leave, and kick writes +- `channel_left` / `group_left` when an installed bot leaves or is removed - `im_created`, `im_open`, `im_close`, `im_marked`, and group open/close/marked events for DM and MPIM writes - `user_change` on profile writes - `presence_change` on presence writes - `file_created`, `file_shared`, and `file_deleted` on file writes - `message` with `subtype: file_share` on shared file uploads +When `slack.signing_secret` is configured, every existing outbound event subscription callback includes `X-Slack-Request-Timestamp` and `X-Slack-Signature`. The signature is `v0=:")>`, using the exact serialized callback body. Configure the receiver with the same secret and verify the unparsed request body. With an absent or empty secret, callbacks are unsigned. + +### Channel privacy conversions + +`POST /api/admin.conversations.convertToPrivate` and `POST /api/admin.conversations.convertToPublic` accept `channel_id`. Use a user token belonging to a seeded admin; bot and test tokens return `not_allowed_token_type`, and non-admin users return `missing_scope`. With `slack.strict_scopes: true`, the token also needs `admin.conversations:write`. For example: + +```yaml +slack: + tokens: + - token: local-admin-user + type: user + user: admin + scopes: [admin.conversations:write, channels:read, groups:read] +``` + +Conversions preserve the channel ID, members, and history, update visibility in conversation and file reads, and append a `message` with subtype `channel_convert_to_private` or `channel_convert_to_public`. Event callbacks include the acting user and the new `channel_type` (`group` or `channel`). Admins need not belong to the channel. DMs, MPIMs, and repeated conversions are rejected; `#general` cannot become private. + +These methods operate on the emulated workspace without Slack's Enterprise plan or organization-wide installation requirements. Enterprise roles, channel-management policies, and MPIM conversion are not simulated. + +### Slack Connect fixtures + +Seed a user with a different `team_id` to test messages from another workspace. `users.info` and `users.list` return that user's workspace and `is_stranger` flag. Omitted user teams default to the emulated workspace. `is_stranger` defaults to `false` and can be seeded to model a user outside the app's visible shared channels. + +```yaml +slack: + users: + - name: partner + team_id: T_PARTNER + is_stranger: false + tokens: + - token: xoxp-partner + type: user + user: partner + scopes: [chat:write] +``` + +Use the seeded user token to act as the external user. Invite the user and your app's bot to a channel with `conversations.invite`, then post a message mentioning the bot. The app-mention event includes the author's `user_team` and `source_team`; the outer `team_id` identifies the receiving installation workspace selected by the token, which defaults to the emulated workspace. Ordinary message callbacks retain the author user ID, which can be resolved with `users.info`. + +A channel with external members returns `is_ext_shared: true` and `is_shared: true`; its event callbacks include `is_ext_shared_channel: true`. Seed `channels[].is_ext_shared: true` to model an already shared channel. Sharing remains set after the last external member leaves or is removed, including across saved state. Slack Connect invitation and approval flows, workspace disconnection, and per-workspace channel settings are not simulated. + +### Event retries + +Use `POST /_slack/simulate/event-retry` with an authenticated emulator token and JSON `{ "event_id": "Ev...", "reason": "http_timeout" }` to test event deduplication. Get the event ID from a received callback or the inspector's Events tab. The simulator resends the latest recorded delivery for that event to its original subscription, preserving the entire payload and event ID. If several subscribers received it, only the latest recipient is retried. + +Each attempt gets an increasing `X-Slack-Retry-Num`, starting at `1`, and `X-Slack-Retry-Reason` (default `http_timeout`). Reasons also accept `http_error`, `connection_failed`, `ssl_error`, `too_many_redirects`, and `unknown_error`. When `slack.signing_secret` is configured, the retry gets a fresh request timestamp and signature. The response includes `delivery_id`, `retry_num`, `success`, and `status_code`; a failed callback is recorded and still consumes a retry number. + +Retries are explicit, with no automatic scheduling or three-attempt limit. Only the latest 1,000 event deliveries are retained in memory, so retries are unavailable after their history is evicted or the emulator restarts. Unknown events return `event_not_found`, and an inactive subscription returns `subscription_inactive`. + ## Current Limits -Slack Connect, Enterprise Grid admin APIs, Audit Logs API, SCIM, Legal Holds, Socket Mode, slash command and interaction simulation, user groups, reminders, stars, calls, canvases, lists, functions, workflows, chat streaming, legacy `files.upload`, exact rate limiting, and paid-plan behavior are not implemented. +Slack Connect invitation and approval flows, per-workspace channel settings, other Enterprise Grid admin APIs, Audit Logs API, SCIM, Legal Holds, Socket Mode, slash commands, shortcuts, and interaction types other than button actions and modal submissions, user groups, reminders, stars, calls, canvases, lists, functions, workflows, chat streaming, legacy `files.upload`, exact rate limiting, and paid-plan behavior are not implemented. ## Common Patterns @@ -729,3 +821,7 @@ curl -X POST http://localhost:4003/services/T000000001/B000000001/X000000001 \ -H "Content-Type: application/json" \ -d '{"text": "Build passed on main"}' ``` + +## Custom emulators alongside built-ins + +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "slack" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/.agents/skills/stripe/SKILL.md b/plugins/emulate/.agents/skills/stripe/SKILL.md index eb9f779b..0c4603b1 100644 --- a/plugins/emulate/.agents/skills/stripe/SKILL.md +++ b/plugins/emulate/.agents/skills/stripe/SKILL.md @@ -12,6 +12,8 @@ No real payments are processed. Every Stripe SDK call hits the emulator and prod ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Stripe only npx emulate --service stripe diff --git a/plugins/emulate/.agents/skills/vercel/SKILL.md b/plugins/emulate/.agents/skills/vercel/SKILL.md index 0c29a31e..f0ced5b5 100644 --- a/plugins/emulate/.agents/skills/vercel/SKILL.md +++ b/plugins/emulate/.agents/skills/vercel/SKILL.md @@ -10,6 +10,8 @@ Fully stateful Vercel REST API emulation with Vercel-style JSON responses and cu ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Vercel only npx emulate --service vercel @@ -379,11 +381,17 @@ curl -X PUT "http://localhost:4000/api/blob?pathname=docs/readme.txt" \ -H "Authorization: Bearer $BLOB_TOKEN" \ --data-binary @readme.txt +# Copy (x-if-match is compared with the source blob's ETag; +# an existing destination is always replaced) +curl -X PUT "http://localhost:4000/api/blob?pathname=docs/copy.txt&fromUrl=docs/readme.txt" \ + -H "Authorization: Bearer $BLOB_TOKEN" + # Metadata (head) curl "http://localhost:4000/api/blob?url=docs/readme.txt" \ -H "Authorization: Bearer $BLOB_TOKEN" -# List (prefix, limit, cursor, mode=folded) +# List (prefix, limit, cursor, mode=folded; folded pages share one +# sorted cursor across folders and root blobs) curl "http://localhost:4000/api/blob?prefix=docs/" \ -H "Authorization: Bearer $BLOB_TOKEN" @@ -393,11 +401,23 @@ curl -X POST http://localhost:4000/api/blob/delete \ -H "Content-Type: application/json" \ -d '{"urls": ["docs/readme.txt"]}' +# Start a multipart upload (returns { key, uploadId }) +curl -X POST "http://localhost:4000/api/blob/mpu?pathname=docs/large.bin" \ + -H "Authorization: Bearer $BLOB_TOKEN" \ + -H "x-mpu-action: create" + # Serve content (public, no auth; ?download=1 forces attachment) curl http://localhost:4000/blob/mystore/docs/readme.txt ``` -Multipart uploads and client (browser) uploads are not supported yet. +Upload and deletion rules: + +- A single leading slash is removed from the pathname, so `/docs/readme.txt` is stored as `docs/readme.txt`. As in the real service, `//` is otherwise accepted over HTTP, so `//a.txt` is stored as `/a.txt` (the `@vercel/blob` SDK rejects `//` before sending a request). This also applies to copy destinations and multipart uploads. +- The content type is taken from `x-content-type`, then the pathname extension when it is a known type, then the request `Content-Type` header, then `application/octet-stream`. +- `x-cache-control-max-age` has a minimum of 60 seconds (`0` is stored as `public, max-age=60`). Absent or invalid values default to 30 days. +- Deleting with a missing or empty `urls` array returns 400 `Missing urls in request body`. Malformed JSON returns 400 `Cannot delete more than 1,000 urls at once or body is malformed`, more than 1,000 urls returns 400 `Cannot delete more than 1,000 urls at once`, and any non-string entry returns 400 `Some urls are malformed`. Nothing is deleted in any of these cases. + +Multipart uploads can be created, but uploading parts and completing the upload are not supported yet and return 400. Client (browser) uploads are not supported yet. ### API Keys @@ -483,3 +503,7 @@ curl "http://localhost:4000/v10/projects?teamId=team_abc123" \ curl "http://localhost:4000/v10/projects?slug=my-team" \ -H "Authorization: Bearer $TOKEN" ``` + +## Custom emulators alongside built-ins + +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "vercel" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/agent/skills/apple/SKILL.md b/plugins/emulate/agent/skills/apple/SKILL.md index 40191caf..3aedb37e 100644 --- a/plugins/emulate/agent/skills/apple/SKILL.md +++ b/plugins/emulate/agent/skills/apple/SKILL.md @@ -14,6 +14,8 @@ Sign in with Apple emulation with authorization code flow, PKCE support, RS256 I ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Apple only npx emulate --service apple @@ -243,3 +245,7 @@ curl -X POST $APPLE_URL/auth/token \ ### Private Relay Email When a user has `is_private_email: true` in the seed config, the `id_token` will contain a generated `@privaterelay.appleid.com` email instead of the user's real email. This matches Apple's Hide My Email behavior. + +## Custom emulators alongside built-ins + +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "apple" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/agent/skills/aws/SKILL.md b/plugins/emulate/agent/skills/aws/SKILL.md index 460b0832..b036fa41 100644 --- a/plugins/emulate/agent/skills/aws/SKILL.md +++ b/plugins/emulate/agent/skills/aws/SKILL.md @@ -1,19 +1,22 @@ --- name: aws -description: Emulated AWS cloud services (S3, SQS, IAM, STS) for local +description: Emulated AWS cloud services (S3, SQS, Cognito, IAM, STS) for local development and testing. Use when the user needs to interact with AWS API endpoints locally, test S3 bucket and object operations, emulate SQS queues - and messages, manage IAM users/roles/access keys, test STS assume role, or - work without hitting real AWS APIs. Triggers include "AWS emulator", "emulate - AWS", "mock S3", "local SQS", "test IAM", "emulate S3", "AWS locally", "STS - assume role", or any task requiring local AWS service emulation. + and messages, test Cognito sign-up, sign-in, JWTs, or Identity Pools, manage + IAM users/roles/access keys, test STS assume role, or work without hitting + real AWS APIs. Triggers include "AWS emulator", "emulate AWS", "mock S3", + "local SQS", "test IAM", "emulate S3", "AWS locally", "STS assume role", or + any task requiring local AWS service emulation. --- # AWS Emulator -S3, SQS, IAM, and STS emulation with AWS SDK-compatible S3 paths and query-style SQS/IAM/STS endpoints. S3 uploads and downloads preserve arbitrary binary payloads, including raw byte lengths and ETags. All state is in-memory, and responses use AWS-compatible XML. +S3, SQS, Cognito, IAM, and STS emulation with AWS SDK-compatible S3 paths. S3 uploads and downloads preserve arbitrary binary payloads, including raw byte lengths and ETags. SQS supports AWS JSON 1.0 and XML Query protocols; IAM and STS use XML Query. All state is in-memory. ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # AWS only npx emulate --service aws @@ -174,7 +177,16 @@ curl -X PUT http://localhost:4006/dest-bucket/copy.txt \ ### SQS -All SQS operations use `POST /sqs/` with `Action` as a form-urlencoded parameter. +Current AWS SDK clients use the `/sqs` endpoint shown above. SQS accepts AWS JSON 1.0 requests at `POST /sqs/` with `Content-Type: application/x-amz-json-1.0` and `X-Amz-Target: AmazonSQS.`, returning JSON results and errors. XML Query requests with an `Action` form parameter remain supported. Queue operations also accept the returned `QueueUrl` as the endpoint. + +```bash +# List queues using AWS JSON 1.0 +curl -X POST http://localhost:4006/sqs/ \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/x-amz-json-1.0" \ + -H "X-Amz-Target: AmazonSQS.ListQueues" \ + -d '{}' +``` ```bash # Create queue @@ -301,6 +313,56 @@ curl -X POST http://localhost:4006/iam/ \ -d "Action=DeleteRole&RoleName=my-role" ``` +### Cognito + +Use `CognitoIdentityProviderClient` with endpoint `/cognito-idp` and `CognitoIdentityClient` with `/cognito-identity`. Both accept AWS JSON 1.1 and work with the official AWS SDK v3: + +```typescript +import { CognitoIdentityProviderClient, InitiateAuthCommand } from "@aws-sdk/client-cognito-identity-provider"; + +const cognito = new CognitoIdentityProviderClient({ + endpoint: "http://localhost:4007/cognito-idp", + region: "us-east-1", + credentials: { accessKeyId: "local-access-key", secretAccessKey: "local-secret" }, +}); +const result = await cognito.send(new InitiateAuthCommand({ + ClientId: "emulateclient", + AuthFlow: "USER_PASSWORD_AUTH", + AuthParameters: { USERNAME: "developer", PASSWORD: "LocalPass123!" }, +})); +``` + +A default User Pool `us-east-1_emulate`, public app client `emulateclient`, and Identity Pool `us-east-1:00000000-0000-4000-8000-000000000001` are created without users. Seed a confirmed account for the example above: + +```yaml +aws: + cognito: + user_pools: + - id: us-east-1_emulate + confirmation_code: "123456" + users: + - username: developer + password: LocalPass123! + attributes: + email: dev@example.com + email_verified: "true" + name: Developer +``` + +User pools also accept `name` and `clients` with `client_id`, optional `client_secret`, exact `redirect_uris`, and allowed `scopes`. Users accept `status` (`CONFIRMED`, `UNCONFIRMED`, or `FORCE_CHANGE_PASSWORD`) and `enabled`. Passwords require at least eight characters and are stored as salted hashes. Confidential clients require Cognito's HMAC `SecretHash` or `SECRET_HASH` for SDK flows. + +- `SignUp`, `ConfirmSignUp`, `ResendConfirmationCode`, `ForgotPassword`, and `ConfirmForgotPassword` model account confirmation and recovery. Codes expire after ten minutes and use the pool's `confirmation_code` fixture, default `123456`. No email or SMS is sent. Inspect active codes at `/_inspector?tab=cognito`; seeded unconfirmed users can request a code with `ResendConfirmationCode`. +- `InitiateAuth` supports `USER_PASSWORD_AUTH`, `REFRESH_TOKEN_AUTH`, and `REFRESH_TOKEN`. `AdminInitiateAuth` also supports `ADMIN_USER_PASSWORD_AUTH`. Admin-created temporary passwords return `NEW_PASSWORD_REQUIRED`, completed with `RespondToAuthChallenge` or `AdminRespondToAuthChallenge`. Challenge sessions expire after three minutes and are bound to the user and client. +- `GetUser`, `ChangePassword`, `GlobalSignOut`, and `RevokeToken` use issued tokens. `AdminCreateUser` (with `TemporaryPassword`), `AdminGetUser`, `AdminConfirmSignUp`, `AdminSetUserPassword`, `AdminEnableUser`, `AdminDisableUser`, `AdminDeleteUser`, and paginated `ListUsers` support test setup and administration. + +Tokens are RS256 JWTs with Cognito `token_use` claims. ID tokens identify the app client in `aud`; access tokens use `client_id`. ID and access tokens expire after one hour; refresh tokens after thirty days. Sign-out, revocation, disabling, deletion, and password resets invalidate affected sessions. Users, sessions, identities, and signing keys participate in snapshots, restore, and reset. + +The local issuer is `http://localhost:4007/cognito-idp/USER_POOL_ID`. Discovery and public keys are served at `ISSUER/.well-known/openid-configuration` and `ISSUER/.well-known/jwks.json`. Configure token verifiers with this local issuer and JWKS. `ISSUER/oauth2/authorize` presents a local user picker, `ISSUER/oauth2/token` exchanges authorization codes or refresh tokens, and `ISSUER/oauth2/userInfo` returns scoped profile claims. Authorization codes expire after five minutes, are single-use, and bind the client, exact redirect URI, and optional S256 PKCE challenge. Client authentication accepts `client_secret_basic` or `client_secret_post`. The default client permits `http://localhost:3000/callback`. + +Identity Pools support `GetId` and `GetCredentialsForIdentity`. Supply the ID token in `Logins` under `cognito-idp.REGION.amazonaws.com/USER_POOL_ID`. The default Identity Pool accepts the default User Pool and app client. Custom `aws.cognito.identity_pools` entries take `id`, optional `name`, `providers: [{ user_pool_id, client_id }]`, and `allow_unauthenticated_identities` (default `false`). Identities remain stable for the same authenticated subject. Credentials expire after one hour; login signatures, expiry, audience, and identity ownership are checked. + +SRP, MFA, external identity providers, Lambda triggers, pool/client management APIs, `ListUsers` filters, and Identity Pool role selection are not simulated. Administrative requests follow the AWS emulator's permissive credential behavior: SigV4 signatures and IAM policies are not evaluated. Identity Pool credentials are generated test credentials, without role or policy evaluation. + ### STS All STS operations use `POST /sts/` with `Action` as a form-urlencoded parameter. @@ -387,3 +449,7 @@ curl -X POST $BASE/iam/ \ -H "Authorization: Bearer $TOKEN" \ -d "Action=CreateAccessKey&UserName=ci-user" ``` + +## Custom emulators alongside built-ins + +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "aws" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/agent/skills/emulate/SKILL.md b/plugins/emulate/agent/skills/emulate/SKILL.md index c88d1d36..5ead9599 100644 --- a/plugins/emulate/agent/skills/emulate/SKILL.md +++ b/plugins/emulate/agent/skills/emulate/SKILL.md @@ -19,7 +19,7 @@ Local drop-in replacement services for CI and no-network sandboxes. Fully statef npx emulate ``` -All services start with sensible defaults: +All services listen on IPv4 loopback (`127.0.0.1`) by default: | Service | Default Port | |-----------|-------------| @@ -71,6 +71,7 @@ npx emulate list | Flag | Default | Description | |------|---------|-------------| | `-p, --port` | `4000` | Base port (auto-increments per service) | +| `--host` | `127.0.0.1` | Listening address; use `0.0.0.0` to allow network access | | `-s, --service` | all | Comma-separated services to enable | | `--seed` | auto-detect | Path to seed config (YAML or JSON) | | `--base-url` | none | Override advertised base URL (supports `{service}` template) | @@ -79,6 +80,8 @@ npx emulate list The port can also be set via `EMULATE_PORT` or `PORT` environment variables. +For access from a container or another machine, use `npx emulate start --host 0.0.0.0`. Set `--base-url` to a URL reachable by those clients when using OAuth redirects or other advertised URLs. + The generated-secrets destination must not exist. emulate removes inherited ACLs, verifies effective owner-only access, and publishes complete JSON before opening listeners or configuring portless. Handled startup failures remove the invocation-owned artifact. A hard termination can leave a complete artifact that must be removed manually after confirming no invocation is using it. Only service-generated values appear in the artifact. Linux requires `setfacl` and `getfacl` from the `acl` package. The flag fails closed when access controls cannot be verified and is not supported on Windows. The advertised base URL (used in OAuth redirects, webhook URLs, etc.) can be overridden via `--base-url`, the `EMULATE_BASE_URL` env var (supports `{service}` template), or per-service `baseUrl` in the seed config. When running under portless, the `PORTLESS_URL` env var is also detected automatically. @@ -112,9 +115,12 @@ For GitHub App tests, inspect secret-free minted installation-token metadata at |--------|---------|-------------| | `service` | *(required)* | `'vercel'`, `'github'`, `'google'`, `'slack'`, `'apple'`, `'microsoft'`, `'okta'`, `'aws'`, `'resend'`, `'stripe'`, `'mongoatlas'`, `'clerk'`, `'linear'`, or `'twilio'` | | `port` | `4000` | Port for the HTTP server | +| `hostname` | `127.0.0.1` | Listening address for built-in and custom emulators | | `seed` | none | Inline seed data (same shape as YAML config) | | `baseUrl` | none | Override advertised base URL. Per-service `baseUrl` in seed config takes highest priority, then this option, then `EMULATE_BASE_URL` env var (supports `{service}`), then `PORTLESS_URL` (supports `{service}`, automatically set by the `portless` CLI wrapper), then `http://localhost:`. | +Use `hostname: '0.0.0.0'` to allow connections from containers or other machines, and `baseUrl` to advertise a URL reachable by those clients. + ### Instance Methods | Method | Description | diff --git a/plugins/emulate/agent/skills/github/SKILL.md b/plugins/emulate/agent/skills/github/SKILL.md index fc6bc89e..44c34e28 100644 --- a/plugins/emulate/agent/skills/github/SKILL.md +++ b/plugins/emulate/agent/skills/github/SKILL.md @@ -14,6 +14,8 @@ Fully stateful GitHub REST API emulation. Creates, updates, and deletes persist ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # GitHub only npx emulate --service github @@ -547,6 +549,10 @@ curl -X POST http://localhost:4001/repos/octocat/hello-world/check-runs \ # Automatic suite status rollup from check run results ``` +Check-run lists by ref and suite support `page` and `per_page` (default 30, maximum 100) with pagination `Link` headers. Ref lists apply `check_name`, `status`, and `filter` before pagination; `total_count` reports the full filtered count. + +Checks created with installation tokens include their GitHub App in API responses and webhooks. Suites are separated by App and commit, and `filter=latest` keeps the newest run for each App and check name. User-token checks return `app: null`; request-body `app_id` cannot set or change attribution. + ### OAuth ```bash @@ -637,3 +643,7 @@ The inspection route is emulator-specific. It lists App, installation, account, 3. Emulator redirects back with `?code=...&state=...` 4. Exchange code for token via `POST /login/oauth/access_token` 5. Use token to call API endpoints + +## Custom emulators alongside built-ins + +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "github" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/agent/skills/google/SKILL.md b/plugins/emulate/agent/skills/google/SKILL.md index 54a18d02..04b9f38b 100644 --- a/plugins/emulate/agent/skills/google/SKILL.md +++ b/plugins/emulate/agent/skills/google/SKILL.md @@ -16,6 +16,8 @@ OAuth 2.0 and OpenID Connect emulation with authorization code flow, PKCE suppor ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Google only npx emulate --service google @@ -185,6 +187,8 @@ google: When no OAuth clients are configured, the emulator accepts any `client_id`. With clients configured, strict validation is enforced for `client_id`, `client_secret`, and `redirect_uri`. +Google authentication is permissive by default for local bearer-token workflows. Set `google.strict_scopes: true` in seed config to require emulator-issued, unexpired access tokens on Gmail, Calendar, Drive, and userinfo routes. Invalid credentials return `401 UNAUTHENTICATED`; missing method scopes return `403` with `insufficientPermissions`. Strict mode distinguishes read, write, send, settings, and metadata scopes, and userinfo includes only granted identity claims. Revoking an access or refresh token invalidates the linked access and refresh credentials. Discovery, signing keys, and the sign-in page remain public. This mode checks method scopes; resource-specific grants such as `drive.file` and Gmail add-on scopes are not modeled. + ### Hosted domain (hd) claim Google Workspace accounts include an `hd` claim in ID tokens and userinfo responses identifying the user's hosted domain. The emulator derives this automatically from the user's email domain. Consumer domains (`gmail.com`, `googlemail.com`) omit the claim, matching real Google behavior. diff --git a/plugins/emulate/agent/skills/linear/SKILL.md b/plugins/emulate/agent/skills/linear/SKILL.md index 2b2ff491..d0276ab6 100644 --- a/plugins/emulate/agent/skills/linear/SKILL.md +++ b/plugins/emulate/agent/skills/linear/SKILL.md @@ -14,6 +14,8 @@ Stateful Linear GraphQL API emulation with organizations, users, teams, workflow ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Linear only npx emulate --service linear @@ -129,3 +131,7 @@ Open `GET /` in the Linear emulator to inspect issues, teams, users, projects, a ## Current Limits Full Linear schema coverage, exact production rate limiting, notification inbox behavior, rich document APIs, customer APIs, initiative APIs, exact search relevance, and full production agent behavior are not implemented. + +## Custom emulators alongside built-ins + +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "linear" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/agent/skills/microsoft/SKILL.md b/plugins/emulate/agent/skills/microsoft/SKILL.md index c89433e4..cee4a9eb 100644 --- a/plugins/emulate/agent/skills/microsoft/SKILL.md +++ b/plugins/emulate/agent/skills/microsoft/SKILL.md @@ -16,6 +16,8 @@ Microsoft Entra ID (Azure AD) v2.0 OAuth 2.0 and OpenID Connect emulation with a ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Microsoft only npx emulate --service microsoft @@ -367,3 +369,7 @@ const client = new microsoftIssuer.Client({ redirect_uris: ['http://localhost:3000/api/auth/callback/microsoft-entra-id'], }) ``` + +## Custom emulators alongside built-ins + +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "microsoft" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/agent/skills/next/SKILL.md b/plugins/emulate/agent/skills/next/SKILL.md index fe756b35..1041fcc8 100644 --- a/plugins/emulate/agent/skills/next/SKILL.md +++ b/plugins/emulate/agent/skills/next/SKILL.md @@ -31,7 +31,7 @@ import { createEmulateHandler } from '@emulators/adapter-next' import * as github from '@emulators/github' import * as google from '@emulators/google' -export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({ +export const { GET, POST, PUT, PATCH, DELETE, OPTIONS } = createEmulateHandler({ services: { github: { emulator: github, @@ -113,7 +113,7 @@ const kvAdapter = { async save(data: string) { await kv.set('emulate-state', data) }, } -export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({ +export const { GET, POST, PUT, PATCH, DELETE, OPTIONS } = createEmulateHandler({ services: { github: { emulator: github } }, persistence: kvAdapter, }) @@ -186,3 +186,7 @@ interface PersistenceAdapter { ``` `initialize` must atomically create the initial value or return the value another instance created first. Implement it with compare-and-set semantics such as Redis `SET NX`. The built-in `filePersistence(path)` from `@emulators/core` provides this behavior for local development. + +## Custom emulators + +The adapter accepts definitions created with `defineEmulator` from `emulate` in `services[name].emulator`. Reuse the definition from CLI/tests; custom inspector is opt-in. Root-relative redirect locations stay under the service mount, while custom HTML bodies pass through unchanged. Await the returned handler's `close()` in test teardown. Install `emulate` as a runtime dependency for deployed definitions. See https://emulate.dev/docs/custom-emulators for seeds, snapshots, persistence, and lifecycle rules. diff --git a/plugins/emulate/agent/skills/resend/SKILL.md b/plugins/emulate/agent/skills/resend/SKILL.md index 34784cb5..bfe1172e 100644 --- a/plugins/emulate/agent/skills/resend/SKILL.md +++ b/plugins/emulate/agent/skills/resend/SKILL.md @@ -12,10 +12,12 @@ description: Emulated Resend email API for local development and testing. Use Fully stateful Resend API emulation. Emails, domains, API keys, audiences, and contacts persist in memory. Sent emails are captured and viewable through the inbox UI or the REST API. -No real emails are sent. Every call to `POST /emails` stores the message locally so you can inspect it programmatically or in the browser. +No real emails are sent. Accepted calls to `POST /emails` store the message locally so you can inspect it programmatically or in the browser. Validated sends also record attempts, including recipient rejections. ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Resend only npx emulate --service resend @@ -218,6 +220,25 @@ curl -X POST http://localhost:4000/emails \ -d '{"from": "hello@example.com", "to": "user@example.com", "subject": "Hello"}' ``` +### Recipient restrictions and send attempts + +Set `resend.allowlist` in seed config to test recipient rejection: + +```yaml +resend: + allowlist: + - allowed@example.com + - copy@example.com +``` + +Omitting `allowlist` accepts every recipient; `allowlist: []` rejects every recipient. Every address in `to`, `cc`, and `bcc` must be listed. Matching ignores surrounding whitespace and letter case; entries are individual addresses, with no wildcard or domain matching. This is a local testing control, independent of Resend's domain verification and account restrictions. + +A rejected send returns `403 validation_error` naming the rejected addresses and configured allowlist. No email ID, email record, idempotency record, or webhook is created. A batch is rejected as a whole if any recipient is disallowed. Retrying a rejected request, even with the same idempotency key, creates a new attempt. Replaying an accepted idempotent request returns its original response even if the allowlist has since changed. + +Validated sends append one record per email to the `resend.send_attempts` store collection, including emails in a rejected batch. Each record contains `from`, `to`, `cc`, `bcc`, `subject`, `idempotency_key` (or `null`), and the store's `id`, `created_at`, and `updated_at`. Message bodies are not copied into attempts. Malformed requests, invalid or conflicting idempotency keys, and idempotency replays do not append attempts. + +Read attempts with `getResendStore(store).sendAttempts.all()` from `@emulators/resend`, or inspect the persisted state. Shared adapter snapshots use the key `resend:resend.send_attempts`. Attempts and recipient restrictions survive saved-state restoration, including rejected sends. There is no additional Resend API endpoint for attempts. A rejected send has an attempt without an email; a send that never happened has neither. + ### Domains ```bash @@ -354,6 +375,6 @@ const { data: emails } = await res.json() console.log(emails[0].html) // contains "123456" ``` -## Custom HTTP APIs alongside built-ins +## Custom emulators alongside built-ins -Use `npx emulate init --custom inventory` to scaffold a custom stateful API and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "resend" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing custom APIs, see https://emulate.dev/docs/custom-apis. +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "resend" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/agent/skills/slack/SKILL.md b/plugins/emulate/agent/skills/slack/SKILL.md index 9cb115a4..03bf01b7 100644 --- a/plugins/emulate/agent/skills/slack/SKILL.md +++ b/plugins/emulate/agent/skills/slack/SKILL.md @@ -16,6 +16,8 @@ Slack message text is limited to 40,000 Unicode characters across chat writes, i ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Slack only npx emulate --service slack @@ -44,7 +46,7 @@ curl -X POST http://localhost:4003/api/auth.test \ Requests without a token return `not_authed`. In relaxed scope mode, any non-empty unknown bearer token maps to the first seeded user. -Scope checks are relaxed by default for local development. Set `slack.strict_scopes: true` in seed config when you need supported Web API methods to return Slack-style `missing_scope` errors with `needed` and `provided` fields. Strict mode checks `chat:write`, `channels:read`, `channels:history`, `channels:join`, `channels:manage`, `channels:write`, `groups:read`, `groups:history`, `groups:write`, `im:read`, `im:history`, `im:write`, `mpim:read`, `mpim:history`, `mpim:write`, `users:read`, `users:read.email`, `users.profile:read`, `users.profile:write`, `users:write`, `files:read`, `files:write`, `pins:read`, `pins:write`, `bookmarks:read`, `bookmarks:write`, `reactions:read`, `reactions:write`, and `team:read`. Slack lists no method-specific scopes for `views.publish`, `views.open`, `views.update`, or `views.push`, so the emulator requires auth but does not add strict-scope checks for those methods. +Scope checks are relaxed by default for local development. Set `slack.strict_scopes: true` in seed config when you need supported Web API methods to return Slack-style `missing_scope` errors with `needed` and `provided` fields. Strict mode checks `chat:write`, `channels:read`, `channels:history`, `channels:join`, `channels:manage`, `channels:write`, `groups:read`, `groups:history`, `groups:write`, `im:read`, `im:history`, `im:write`, `mpim:read`, `mpim:history`, `mpim:write`, `users:read`, `users:read.email`, `users.profile:read`, `users.profile:write`, `users:write`, `files:read`, `files:write`, `pins:read`, `pins:write`, `bookmarks:read`, `bookmarks:write`, `reactions:read`, `reactions:write`, `team:read`, and `admin.conversations:write`. Slack lists no method-specific scopes for `views.publish`, `views.open`, `views.update`, or `views.push`, so the emulator requires auth but does not add strict-scope checks for those methods. ## Pointing Your App at the Emulator @@ -204,6 +206,8 @@ slack: When no OAuth apps are configured, the emulator accepts any `client_id`. With apps configured, strict validation is enforced for `client_id`, `client_secret`, and `redirect_uri`. +`signing_secret` signs every outbound event subscription callback. Configure the receiving app with the same secret; signed callbacks include `X-Slack-Request-Timestamp` and `X-Slack-Signature`, with `X-Slack-Signature` set to `v0=:")>`. Verify against the unparsed request body. When the secret is absent or empty, callbacks are unsigned. + ## API Endpoints ### Auth @@ -552,7 +556,7 @@ curl -X POST http://localhost:4003/api/views.generateTriggerId \ -H "Content-Type: application/json" \ -d '{"user_id": "U000000001"}' -# Open a modal view with the returned trigger_id or interactivity_pointer within 3 seconds +# Open a modal view with the returned trigger_id or interactivity_pointer before it expires curl -X POST http://localhost:4003/api/views.open \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ @@ -636,6 +640,31 @@ curl -X POST http://localhost:4003/services/T000000001/B000000001/X000000001 \ -d '{"text": "Thread update", "thread_ts": "1234567890.123456"}' ``` +### Interactivity + +Buttons and modal submissions can invoke your app's Interactivity Request URL. Configure `interactivity_url` on a seeded `slack.oauth_apps` entry and set `slack.signing_secret` to the same secret used by your receiver: + +```yaml +slack: + signing_secret: local-signing-secret + oauth_apps: + - app_id: A_MY_APP + client_id: my-client + client_secret: my-secret + name: My App + redirect_uris: [] + interactivity_url: http://localhost:3000/slack/interactions +``` + +- `POST /_slack/simulate/block-actions` accepts `channel`, `message_ts`, and a button selector (`action_id`, exact `text`, or `url`), plus the acting `user` ID or name. It supports buttons in actions blocks and section accessories, including ephemeral messages visible to that user. The simulator requires an authenticated emulator token and the acting user must belong to the conversation. +- `POST /_slack/simulate/view-submission` accepts `view_id`, optional `user`, and `values` keyed by input block ID. Text inputs take strings; static selects and radio buttons take an option value; multi-selects and checkboxes take arrays of option values. Omitted values retain previous input or initial values. Other input elements return `unsupported_element`. + +Callbacks are `application/x-www-form-urlencoded` with one `payload` JSON field, signed over the exact encoded request body. Button payloads include an app-bound, single-use `trigger_id` that can open a modal for the acting user. Triggers expire after three seconds by default; set `slack.view_trigger_ttl_seconds` to a positive number to allow more time in local tests. App ownership is preserved for messages posted with an app's bot or user token. Simulators return the payload and `delivery_id`; attempts and delivery results are saved in `slack.interaction_deliveries`. + +A button payload's `response_url` accepts JSON without a bearer token, up to five times within 30 minutes. It posts an ephemeral reply by default, or a public reply with `response_type: "in_channel"`. Use `replace_original: true` or `delete_original: true` to modify the source message. Replacement retains the source message's visibility. Rich blocks and thread replies are supported. + +A modal receiver must acknowledge within three seconds. An empty HTTP 200 closes the submitted view, while `response_action: "clear"` closes its stack. `errors` retains the view and returns block errors; `update` replaces its contents; `push` adds a new view subject to the three-view limit. Delivery failures and invalid acknowledgements leave the modal open. Shortcuts, slash commands, non-button actions, `view_closed`, and `block_suggestion` are not simulated. + ### OAuth ```bash @@ -664,15 +693,29 @@ Returns a Slack-style response: } ``` +`POST /api/apps.uninstall` accepts `client_id`, `client_secret`, and an installation's user token in the Authorization header or POST `token` field. It removes that app's installation in the token's workspace and revokes all associated OAuth bot and user tokens, including earlier grants. Revoked credentials return `token_revoked` on Web API calls even with permissive authentication. Other apps and workspaces keep their credentials. Granular bot tokens cannot uninstall an app and return `no_permission`; configured development tokens without an app binding may uninstall a local installation. No method scope is required. + +Uninstall dispatches `app_uninstalled` and `tokens_revoked` to matching event subscriptions with the removed app's `api_app_id`. `tokens_revoked.tokens.oauth` and `.bot` contain deduplicated user IDs, never token strings. Credentials are invalid before callbacks are delivered. Handlers should accept either event order. The app definition remains available for a new OAuth installation; reinstalling issues fresh tokens without reactivating revoked ones. + ### Inspector Open `GET /` in the Slack emulator to inspect conversations, messages, files, views, auth records, incoming webhooks, event subscriptions, and event deliveries. ## Event Dispatching -When supported Slack writes mutate state, the emulator dispatches `event_callback` payloads to configured webhook URLs. These payloads match Slack's Events API format: +For supported Slack writes that emit events, the emulator dispatches `event_callback` payloads to configured webhook URLs. These payloads match Slack's Events API format: + +Each callback includes outer `token`, `api_app_id`, `authorizations`, `team_id`, `event_id`, and integer Unix-seconds `event_time` alongside the inner `event`. For authenticated Web API writes, `team_id` comes from the presented Slack token's installation. Development tokens without a stored Slack record fall back to the affected channel, user, or file's team, then the seeded workspace team (or `T000000001`). Incoming webhook posts use their webhook record's team, or the target channel's team when no record matches. Each logical event gets a distinct `event_id` shared by deliveries to multiple subscribers. + +Callbacks identify each workspace installation with its recipient `api_app_id` and bot authorization. With multiple installed apps, each receives a separate envelope; configured webhook subscriptions are shared and can route by `api_app_id`. Without an installation, callbacks use `A000000001` and a local user authorization. The legacy `token` field defaults to `emulate-verification-token`; set `slack.verification_token` to match receivers that check it. Use `slack.signing_secret` for request signature verification. + +Every inner event includes `event_ts`. Message events include `channel_type` (`channel`, `group`, `im`, or `mpim`). Posts made with an issued or seeded bot token include the bot's `bot_id`, `app_id`, and `bot_profile` when the corresponding bot record exists. These fields persist in history, replies, and updates so handlers can identify their own messages. Removing an installed bot with `conversations.kick` or `conversations.leave` also emits `channel_left` or `group_left`, with `actor_id` and `event_ts`, for the affected app. + +To test bot handlers, invite an installed app's `bot_user_id` with `conversations.invite`, subscribe to `app_mention`, then post text containing `<@BOT_USER_ID>` with `chat.postMessage`. Channel mentions produce one callback per mentioned app, with outer `api_app_id`, inner `event_ts`, and the original message and thread identity. DMs and MPIMs use `message` events. - `message` events on `chat.postMessage` +- `app_mention` on `chat.postMessage` when channel message text mentions an installed member bot +- `app_uninstalled` / `tokens_revoked` on successful `apps.uninstall` - `message` with `subtype: message_changed` on `chat.update` - `message` with `subtype: message_deleted` on `chat.delete` - rich message fields are included on posted `message` events when present @@ -684,15 +727,64 @@ When supported Slack writes mutate state, the emulator dispatches `event_callbac - `channel_rename` / `group_rename` and matching name message subtypes on `conversations.rename` - `message` with public `channel_topic` / `channel_purpose` or private `group_topic` / `group_purpose` subtypes on topic and purpose writes - `member_joined_channel` / `member_left_channel` on invite, join, leave, and kick writes +- `channel_left` / `group_left` when an installed bot leaves or is removed - `im_created`, `im_open`, `im_close`, `im_marked`, and group open/close/marked events for DM and MPIM writes - `user_change` on profile writes - `presence_change` on presence writes - `file_created`, `file_shared`, and `file_deleted` on file writes - `message` with `subtype: file_share` on shared file uploads +When `slack.signing_secret` is configured, every existing outbound event subscription callback includes `X-Slack-Request-Timestamp` and `X-Slack-Signature`. The signature is `v0=:")>`, using the exact serialized callback body. Configure the receiver with the same secret and verify the unparsed request body. With an absent or empty secret, callbacks are unsigned. + +### Channel privacy conversions + +`POST /api/admin.conversations.convertToPrivate` and `POST /api/admin.conversations.convertToPublic` accept `channel_id`. Use a user token belonging to a seeded admin; bot and test tokens return `not_allowed_token_type`, and non-admin users return `missing_scope`. With `slack.strict_scopes: true`, the token also needs `admin.conversations:write`. For example: + +```yaml +slack: + tokens: + - token: local-admin-user + type: user + user: admin + scopes: [admin.conversations:write, channels:read, groups:read] +``` + +Conversions preserve the channel ID, members, and history, update visibility in conversation and file reads, and append a `message` with subtype `channel_convert_to_private` or `channel_convert_to_public`. Event callbacks include the acting user and the new `channel_type` (`group` or `channel`). Admins need not belong to the channel. DMs, MPIMs, and repeated conversions are rejected; `#general` cannot become private. + +These methods operate on the emulated workspace without Slack's Enterprise plan or organization-wide installation requirements. Enterprise roles, channel-management policies, and MPIM conversion are not simulated. + +### Slack Connect fixtures + +Seed a user with a different `team_id` to test messages from another workspace. `users.info` and `users.list` return that user's workspace and `is_stranger` flag. Omitted user teams default to the emulated workspace. `is_stranger` defaults to `false` and can be seeded to model a user outside the app's visible shared channels. + +```yaml +slack: + users: + - name: partner + team_id: T_PARTNER + is_stranger: false + tokens: + - token: xoxp-partner + type: user + user: partner + scopes: [chat:write] +``` + +Use the seeded user token to act as the external user. Invite the user and your app's bot to a channel with `conversations.invite`, then post a message mentioning the bot. The app-mention event includes the author's `user_team` and `source_team`; the outer `team_id` identifies the receiving installation workspace selected by the token, which defaults to the emulated workspace. Ordinary message callbacks retain the author user ID, which can be resolved with `users.info`. + +A channel with external members returns `is_ext_shared: true` and `is_shared: true`; its event callbacks include `is_ext_shared_channel: true`. Seed `channels[].is_ext_shared: true` to model an already shared channel. Sharing remains set after the last external member leaves or is removed, including across saved state. Slack Connect invitation and approval flows, workspace disconnection, and per-workspace channel settings are not simulated. + +### Event retries + +Use `POST /_slack/simulate/event-retry` with an authenticated emulator token and JSON `{ "event_id": "Ev...", "reason": "http_timeout" }` to test event deduplication. Get the event ID from a received callback or the inspector's Events tab. The simulator resends the latest recorded delivery for that event to its original subscription, preserving the entire payload and event ID. If several subscribers received it, only the latest recipient is retried. + +Each attempt gets an increasing `X-Slack-Retry-Num`, starting at `1`, and `X-Slack-Retry-Reason` (default `http_timeout`). Reasons also accept `http_error`, `connection_failed`, `ssl_error`, `too_many_redirects`, and `unknown_error`. When `slack.signing_secret` is configured, the retry gets a fresh request timestamp and signature. The response includes `delivery_id`, `retry_num`, `success`, and `status_code`; a failed callback is recorded and still consumes a retry number. + +Retries are explicit, with no automatic scheduling or three-attempt limit. Only the latest 1,000 event deliveries are retained in memory, so retries are unavailable after their history is evicted or the emulator restarts. Unknown events return `event_not_found`, and an inactive subscription returns `subscription_inactive`. + ## Current Limits -Slack Connect, Enterprise Grid admin APIs, Audit Logs API, SCIM, Legal Holds, Socket Mode, slash command and interaction simulation, user groups, reminders, stars, calls, canvases, lists, functions, workflows, chat streaming, legacy `files.upload`, exact rate limiting, and paid-plan behavior are not implemented. +Slack Connect invitation and approval flows, per-workspace channel settings, other Enterprise Grid admin APIs, Audit Logs API, SCIM, Legal Holds, Socket Mode, slash commands, shortcuts, and interaction types other than button actions and modal submissions, user groups, reminders, stars, calls, canvases, lists, functions, workflows, chat streaming, legacy `files.upload`, exact rate limiting, and paid-plan behavior are not implemented. ## Common Patterns @@ -733,3 +825,7 @@ curl -X POST http://localhost:4003/services/T000000001/B000000001/X000000001 \ -H "Content-Type: application/json" \ -d '{"text": "Build passed on main"}' ``` + +## Custom emulators alongside built-ins + +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "slack" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/agent/skills/stripe/SKILL.md b/plugins/emulate/agent/skills/stripe/SKILL.md index 009b19e8..c14c2690 100644 --- a/plugins/emulate/agent/skills/stripe/SKILL.md +++ b/plugins/emulate/agent/skills/stripe/SKILL.md @@ -16,6 +16,8 @@ No real payments are processed. Every Stripe SDK call hits the emulator and prod ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Stripe only npx emulate --service stripe diff --git a/plugins/emulate/agent/skills/vercel/SKILL.md b/plugins/emulate/agent/skills/vercel/SKILL.md index 7118721c..7ede462f 100644 --- a/plugins/emulate/agent/skills/vercel/SKILL.md +++ b/plugins/emulate/agent/skills/vercel/SKILL.md @@ -15,6 +15,8 @@ Fully stateful Vercel REST API emulation with Vercel-style JSON responses and cu ## Start +Local listeners bind to `127.0.0.1` by default. For access from containers or other machines, pass `--host 0.0.0.0` to the CLI or `hostname: '0.0.0.0'` to `createEmulator`. Configure `--base-url` or `baseUrl` for advertised URLs reachable by those clients. + ```bash # Vercel only npx emulate --service vercel @@ -384,11 +386,17 @@ curl -X PUT "http://localhost:4000/api/blob?pathname=docs/readme.txt" \ -H "Authorization: Bearer $BLOB_TOKEN" \ --data-binary @readme.txt +# Copy (x-if-match is compared with the source blob's ETag; +# an existing destination is always replaced) +curl -X PUT "http://localhost:4000/api/blob?pathname=docs/copy.txt&fromUrl=docs/readme.txt" \ + -H "Authorization: Bearer $BLOB_TOKEN" + # Metadata (head) curl "http://localhost:4000/api/blob?url=docs/readme.txt" \ -H "Authorization: Bearer $BLOB_TOKEN" -# List (prefix, limit, cursor, mode=folded) +# List (prefix, limit, cursor, mode=folded; folded pages share one +# sorted cursor across folders and root blobs) curl "http://localhost:4000/api/blob?prefix=docs/" \ -H "Authorization: Bearer $BLOB_TOKEN" @@ -398,11 +406,23 @@ curl -X POST http://localhost:4000/api/blob/delete \ -H "Content-Type: application/json" \ -d '{"urls": ["docs/readme.txt"]}' +# Start a multipart upload (returns { key, uploadId }) +curl -X POST "http://localhost:4000/api/blob/mpu?pathname=docs/large.bin" \ + -H "Authorization: Bearer $BLOB_TOKEN" \ + -H "x-mpu-action: create" + # Serve content (public, no auth; ?download=1 forces attachment) curl http://localhost:4000/blob/mystore/docs/readme.txt ``` -Multipart uploads and client (browser) uploads are not supported yet. +Upload and deletion rules: + +- A single leading slash is removed from the pathname, so `/docs/readme.txt` is stored as `docs/readme.txt`. As in the real service, `//` is otherwise accepted over HTTP, so `//a.txt` is stored as `/a.txt` (the `@vercel/blob` SDK rejects `//` before sending a request). This also applies to copy destinations and multipart uploads. +- The content type is taken from `x-content-type`, then the pathname extension when it is a known type, then the request `Content-Type` header, then `application/octet-stream`. +- `x-cache-control-max-age` has a minimum of 60 seconds (`0` is stored as `public, max-age=60`). Absent or invalid values default to 30 days. +- Deleting with a missing or empty `urls` array returns 400 `Missing urls in request body`. Malformed JSON returns 400 `Cannot delete more than 1,000 urls at once or body is malformed`, more than 1,000 urls returns 400 `Cannot delete more than 1,000 urls at once`, and any non-string entry returns 400 `Some urls are malformed`. Nothing is deleted in any of these cases. + +Multipart uploads can be created, but uploading parts and completing the upload are not supported yet and return 400. Client (browser) uploads are not supported yet. ### API Keys @@ -488,3 +508,7 @@ curl "http://localhost:4000/v10/projects?teamId=team_abc123" \ curl "http://localhost:4000/v10/projects?slug=my-team" \ -H "Authorization: Bearer $TOKEN" ``` + +## Custom emulators alongside built-ins + +Use `npx emulate init --custom inventory` to scaffold a third-party API emulator and test. Register it in `emulate.config.ts` with `defineConfig` from `emulate`, alongside built-in entries such as `{ emulator: "vercel" }`. Run `npx emulate start --watch` to reload imports and inspect custom state at the printed `/_emulate` URL. Successful reloads reset the run to seed. Existing flat seed configs still work; `--config` selects an explicit file. For authoring and testing third-party API emulators, see https://emulate.dev/docs/custom-emulators. diff --git a/plugins/emulate/skills-lock.json b/plugins/emulate/skills-lock.json index 7301b885..91bef19e 100644 --- a/plugins/emulate/skills-lock.json +++ b/plugins/emulate/skills-lock.json @@ -5,73 +5,73 @@ "source": "vercel-labs/emulate", "sourceType": "github", "skillPath": "skills/apple/SKILL.md", - "computedHash": "14b0aeaebc8a8a8056f9f4f0babd39ecb168ab5e1cc89281e7fb64fc19ac48ee" + "computedHash": "69ab6f2682bfd5090e6b268ea0fd6e274c9c48e2ca4100f32e6e3b0f6946a10e" }, "aws": { "source": "vercel-labs/emulate", "sourceType": "github", "skillPath": "skills/aws/SKILL.md", - "computedHash": "735781aaf3e694d6872280bacaf9ef132da68322ef21ea5f3f63a073c6065a4b" + "computedHash": "18ef961539949106b17fda5fdf3c514c99374a99613f76d38757a2062339044c" }, "emulate": { "source": "vercel-labs/emulate", "sourceType": "github", "skillPath": "skills/emulate/SKILL.md", - "computedHash": "ebc9ed29875d7e356c9d9299b218bfdfe3583afad84e4b1ccf07adc91f908b61" + "computedHash": "f9f00dad70d82ec377bc6e0faf765b52bcde1784bc89905997a9ecfe553ea358" }, "github": { "source": "vercel-labs/emulate", "sourceType": "github", "skillPath": "skills/github/SKILL.md", - "computedHash": "b41f5efdebb56ee6965d9599b68bd40f946c95386af198e05ffa77d378be59c7" + "computedHash": "187a308c36d8b4f7563409d899257b96c316e5f406812c9bf72d4ab450da4c1c" }, "google": { "source": "vercel-labs/emulate", "sourceType": "github", "skillPath": "skills/google/SKILL.md", - "computedHash": "a80de94e5149e55daa865f7d5da60bdfd63f46c82682b0eb91ebb3db151c7a8e" + "computedHash": "4177214ab7832ee08c20aa2b4a02e344bb1438a57b4f6c505ce338e8d16474ac" }, "linear": { "source": "vercel-labs/emulate", "sourceType": "github", "skillPath": "skills/linear/SKILL.md", - "computedHash": "0959a13f8b49f4a32bcf3910a57a0c271b4ee1a68de9d35e44e5850ddbb64cb6" + "computedHash": "48d3b6c7869a98661c5bbd3a7b7df09ca4bbbae6b86450d6e57853edee822316" }, "microsoft": { "source": "vercel-labs/emulate", "sourceType": "github", "skillPath": "skills/microsoft/SKILL.md", - "computedHash": "87bb1d04f8d6aaf21700d033cc71fd1150c5a8e8a397a235278b4b226ce25c2d" + "computedHash": "028538190fd0e34e00df9ba026a9d1f165c5d2388f7c71a6a2a92c8ed285b461" }, "next": { "source": "vercel-labs/emulate", "sourceType": "github", "skillPath": "skills/next/SKILL.md", - "computedHash": "3f24bb5fea72610b2412ad6bc835ae913063e8e438ade72fc5a065e92b416e87" + "computedHash": "c581384e91811b427855b502280ee3fb143ed3b0d29c409afb0290c12697eb9a" }, "resend": { "source": "vercel-labs/emulate", "sourceType": "github", "skillPath": "skills/resend/SKILL.md", - "computedHash": "56161171fb5dc02b27b323c258976a541abd76141e3f85c7001a9f97bb10d20c" + "computedHash": "8bb5e578779dbdf54c4bfc7646c2795769ff08986f7512f2b7f9371ace5eef68" }, "slack": { "source": "vercel-labs/emulate", "sourceType": "github", "skillPath": "skills/slack/SKILL.md", - "computedHash": "b92398a0d0c3f5957bc15ac2ead16442238f0d423e19561bdea67ea61a134802" + "computedHash": "2e58613063c123bb7e3f02b5f400e6a2be341c2bbc1b80a9f8a8a83e1fb77dc5" }, "stripe": { "source": "vercel-labs/emulate", "sourceType": "github", "skillPath": "skills/stripe/SKILL.md", - "computedHash": "80159ad0e34e8e62ca1f4cca305ba73c5ec8a4bf95e9582afe94966934092be5" + "computedHash": "03845f355c42838653296330cdcf6ea91dec9929b29c12a1d26861f47d5f6b9e" }, "vercel": { "source": "vercel-labs/emulate", "sourceType": "github", "skillPath": "skills/vercel/SKILL.md", - "computedHash": "7f8a849d69b75ecc2528746521d0f9c226c61081a99124409787c8a4ceca9d2d" + "computedHash": "cba57cf4bfd387a81a826056e2c89af2961833f9664deee109c45f2cb08b1571" } } } diff --git a/plugins/firebase/.agents/skills/extension-to-functions-codebase/SKILL.md b/plugins/firebase/.agents/skills/extension-to-functions-codebase/SKILL.md index 0581d43b..6bb3ea9f 100644 --- a/plugins/firebase/.agents/skills/extension-to-functions-codebase/SKILL.md +++ b/plugins/firebase/.agents/skills/extension-to-functions-codebase/SKILL.md @@ -2,6 +2,7 @@ name: extension-to-functions-codebase description: Skill for converting an installed Firebase Extension (or extension source) into a standalone Cloud Functions for Firebase codebase or publishable npm package, including V1 to V2 trigger upgrades, lifecycle hooks, and declarative security metadata: + author: Google LLC category: Serverless --- diff --git a/plugins/firebase/.agents/skills/firebase-ai-logic-basics/SKILL.md b/plugins/firebase/.agents/skills/firebase-ai-logic-basics/SKILL.md index 54949b2e..685921fe 100644 --- a/plugins/firebase/.agents/skills/firebase-ai-logic-basics/SKILL.md +++ b/plugins/firebase/.agents/skills/firebase-ai-logic-basics/SKILL.md @@ -1,8 +1,10 @@ --- name: firebase-ai-logic-basics -description: Official skill for integrating Firebase AI Logic (Gemini API) into web applications. Covers setup, multimodal inference, structured output, and security. +description: >- + Integrates Firebase AI Logic (formerly Vertex AI for Firebase) into mobile and web apps (Android, iOS, Web, and Flutter) to call Gemini models from client code. Use when provisioning or initializing Firebase AI Logic, or adding Gemini features to an app: text generation, multimodal input, chat, streaming, structured output, hybrid on-device inference, App Check protection, or switching models with Remote Config. version: 1.0.1 metadata: + author: Google LLC category: AiAndMachineLearning --- @@ -11,57 +13,98 @@ metadata: ## Overview Firebase AI Logic is a product of Firebase that allows developers to add gen AI -to their mobile and web apps using client-side SDKs. You can call Gemini models -directly from your app without managing a dedicated backend. Firebase AI Logic, -which was previously known as "Vertex AI for Firebase", represents the evolution -of Google's AI integration platform for mobile and web developers. +to their mobile and web apps using client-side SDKs. The app can directly call +Gemini models via Firebase AI Logic without the developer needing to manage a +dedicated backend. Firebase AI Logic was previously known as "Vertex AI for +Firebase". It supports the two Gemini API providers: -- **Gemini Developer API**: It has a free tier ideal for prototyping, and - pay-as-you-go for production -- **Agent Platform Gemini API** (formerly branded Vertex AI): Ideal for scale - with enterprise-grade production readiness, requires Blaze plan +- **Gemini Developer API**: It has a free tier ideal for prototyping, and + pay-as-you-go for production +- **Agent Platform Gemini API** (formerly branded Vertex AI): Ideal for scale + with enterprise-grade production readiness, requires Blaze pricing plan Use the Gemini Developer API as a default, and only Agent Platform Gemini API (formerly branded Vertex AI) if the application requires it. -## Setup & Initialization +______________________________________________________________________ + +## Setup & initialization ### Prerequisites -- Before starting, ensure you have **Node.js 16+** and npm installed. Install - them if they aren’t already available. -- Identify the platform the user is interested in building on prior to - starting: Android, iOS, Flutter or Web. -- If their platform is unsupported, Direct the user to Firebase Docs to learn - how to set up AI Logic for their application (share this link with the user - https://firebase.google.com/docs/ai-logic/get-started) +- Identify the platform the user is interested in building on prior to starting: + Android, iOS, Web, or Flutter. +- If their platform is unsupported, Direct the user to Firebase Docs to learn + how to set up AI Logic for their application (share this link with the user + https://firebase.google.com/docs/ai-logic/get-started) +- The Firebase CLI commands below run through `npx` on every platform, so they + need **Node.js 20+** and npm. Use the `firebase-basics` skill to check for + them and to log in to the Firebase CLI. If Node.js is missing or older than + version 20, ask the user to install it as that skill describes. Don't install + system packages yourself, for example with `sudo apt-get`, because that + changes the user's machine without their consent. + +### 1. Provision Firebase AI Logic (ALL platforms) + +Use the **Firebase CLI** to provision Firebase AI Logic for the Firebase +Project: + +1. Make sure that you're in a firebase directory (with a `firebase.json`). -### Installation +1. Verify that you're running Firebase CLI commands against the intended + Firebase Project. When you run this command, the active project is marked + with "current": -The library is part of the standard Firebase Web SDK. + ```bash + npx -y firebase-tools@latest projects:list + ``` -`npm install firebase@latest` +1. Make sure there's at least one Firebase App for the target platform + registered with the current Firebase Project: -If you're in a firebase directory (with a firebase.json) the currently selected -project will be marked with "current" using this command: + ```bash + npx -y firebase-tools@latest apps:list + ``` -`npx -y firebase-tools@latest projects:list` +1. Provision Firebase AI Logic for the Firebase Project: -Ensure there's at least one app associated with the current project + ```bash + npx -y firebase-tools@latest init ailogic + ``` + + This command will enable the Gemini Developer API in the Firebase project. + +> [!WARNING] **CRITICAL: Provisioning Firebase AI Logic is Required** For all +> platforms (Android, iOS, Web, or Flutter), even if the app already uses +> Firebase, you MUST run `npx -y firebase-tools@latest init ailogic` to +> provision the service. The configuration files of `GoogleService-Info.plist`, +> `google-services.json`, and `firebase_options.dart` ONLY handle client +> configuration and do NOT enable the Firebase AI Logic service, leading to +> `PERMISSION_DENIED` errors. -`npx -y firebase-tools@latest apps:list` +If you can't run `init ailogic` yourself (for example because Node.js is missing +or the Firebase CLI isn't logged in) do NOT skip it. The app's requests to +Gemini models will fail until the Firebase AI Logic service is provisioned. Tell +the user to run this command in their project directory, and include it in your +final summary: -Initialize AI logic SDK with the init command +```bash +npx -y firebase-tools@latest init ailogic +``` -`npx -y firebase-tools@latest init ailogic` +### 2. Add the SDK and initialize the service in the app -This will automatically enable the Gemini Developer API in the Firebase console. +Adding the SDK dependency and initializing the service are platform-specific. +Before writing code, read the reference for the target platform listed under +[Initialization Code References](#initialization-code-references). More info in [Firebase AI Logic Getting Started](https://firebase.google.com/docs/ai-logic/get-started.md.txt) +______________________________________________________________________ + ## Core Capabilities > [!WARNING] **CRITICAL: Use current model names:** Always check the @@ -91,16 +134,42 @@ To improve the user experience by showing partial results as they arrive (like a typing effect), use `generateContentStream` instead of `generateContent` for faster display of results. +### Text-to-Speech (TTS) Generation + +Generate spoken audio directly on client devices without a custom speech +backend. Firebase AI Logic supports speech synthesis using dedicated Gemini TTS +models: + +- **Supported Models**: `gemini-3.1-flash-tts-preview` +- **Capabilities**: + - Single-speaker voice persona selection (`voiceName`) and multi-speaker + dialogues (up to 2 distinct speakers) via `SpeechConfig` / + `MultiSpeakerVoiceConfig` + - Direct audio responses by setting `responseModalities` to audio + - Streaming speech responses with `generateContentStream` for low-latency + playback + - Audio directives (`[Audio Profile: ...]`, `[Scene: ...]`, + `[Director's Note: ...]`) and emotional tags (`[whispers]`, `[laughs]`, + `[slowly]`) + - Client-side decoding and playback of 24 kHz 16-bit linear PCM (`audio/l16`) + or WAV container format (`audio/x-wav`) + +See the [Text-to-Speech (TTS) Generation Guide](references/tts_generation.md) +for full implementation details, streaming patterns, and copy-paste code +snippets for iOS (Swift), Android (Kotlin), and Web. + ### Generate Images with Nano Banana > [!WARNING] **Use current Image model names:** Always check the > [Firebase AI Logic Models documentation](https://firebase.google.com/docs/ai-logic/models.md.txt) > for the currently supported image generation (Nano Banana) model names. -- Requires an upgraded Blaze pay-as-you-go billing plan. +- Requires an upgraded Blaze pay-as-you-go billing plan. ### Search Grounding with the built in googleSearch tool +______________________________________________________________________ + ## Supported Platforms and Frameworks Supported Platforms and Frameworks include Kotlin and Java for Android, Swift @@ -114,23 +183,26 @@ Enforce a specific JSON schema for the response. ### On-Device AI (Hybrid) -Hybrid on-device inference for web apps, where the Firebase Javascript SDK -automatically checks for Gemini Nano's availability (after installation) and -switches between on-device or cloud-hosted prompt execution. This requires -specific steps to enable model usage in the Chrome browser, more info in the -[hybrid-on-device-inference documentation](https://firebase.google.com/docs/ai-logic/hybrid-on-device-inference.md.txt). +Hybrid inference runs prompts with an on-device model when one is available and +falls back to a cloud-hosted model otherwise (or vice versa). Availability and +setup steps differ by platform, more info in the +[hybrid and on-device documentation](https://firebase.google.com/docs/ai-logic/hybrid.md.txt), +which links to each platform's guide. + +______________________________________________________________________ ## Security & Production -### App Check +### Firebase App Check > [!WARNING] **Critical Safety Requirement:** In order to use AI Logic safely, > you MUST set up App Check on your app. This prevents unauthorized clients from > using your API quota and accessing your backend resources. See -[App Check with reCAPTCHA Enterprise](https://firebase.google.com/docs/app-check/web/recaptcha-enterprise-provider.md.txt) -for setup instructions. +[App Check for Firebase AI Logic](https://firebase.google.com/docs/ai-logic/app-check.md.txt) +for setup instructions. Each platform uses its own attestation provider, so +follow the instructions for the target platform. #### App Check Debug Tokens for Local Development & CI/CD @@ -138,57 +210,69 @@ Because App Check attestation providers (like Play Integrity or DeviceCheck) reject emulators, simulators, or CI environments, you must use **App Check Debug Tokens** during development and testing to bypass standard attestation. +> [!WARNING] **CRITICAL: Never Hardcode or Commit Debug Tokens** App Check debug +> tokens allow clients to bypass attestation and access backend resources +> without a genuine device. Treat them as private secrets. **Never commit debug +> tokens to version control or hardcode raw token strings in client code** (Web, +> Android, iOS, or Flutter). Always inject them through local environment +> variables, gitignored local configurations, or CI secrets. If a token is +> compromised, revoke it immediately in the Firebase Console. + ##### Local Development (Auto-Generated) -1. Configure your code's App Check provider to use the debug factory: - * **Web**: Set `self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;` before - initializing App Check. - * **Android**: Install `DebugAppCheckProviderFactory.getInstance()`. - * **iOS**: Set provider factory to `AppCheckDebugProviderFactory()`. -2. Run your app in the emulator/localhost. -3. Look at your runtime debugger console / Logcat logs for the generated UUID: - * *Example:* `AppCheck debug token: - "123a4567-b89c-12d3-e456-789012345678"` -4. Register this token in the Firebase Console under **Security > App Check > - Apps > Manage debug tokens**. +1. Configure your code's App Check provider to use the debug factory. The API is + platform-specific; see the App Check section of the platform reference or the + App Check documentation linked above. +1. Run your app in the emulator/localhost. +1. Look at your runtime debugger console / Logcat logs for the generated UUID: + - *Example:* `AppCheck debug token: "123a4567-b89c-12d3-e456-789012345678"` +1. Register this token in the Firebase Console under **Security > App Check > + Apps > Manage debug tokens**. + +> **💡 Tip (Prevent Debug Token Churn):** Simulator or emulator resets, fresh +> installs, and clearing browser data can make the SDK generate a new debug +> token that you must register again. To keep a stable debug token without +> hardcoding it, see the App Check section of the platform reference. ##### CI/CD Pipelines (Pre-Provisioned) -1. Generate and register a new debug token in the Firebase Console under - **Security > App Check > Apps > Manage debug tokens**. -2. Add this token string as an encrypted secret in your CI system (e.g. - `APP_CHECK_DEBUG_TOKEN`). -3. Configure your build to pass this secret as an environment variable to the - SDK during test execution (e.g. `self.FIREBASE_APPCHECK_DEBUG_TOKEN = - process.env.APP_CHECK_DEBUG_TOKEN`). +1. Generate and register a new debug token in the Firebase Console under + **Security > App Check > Apps > Manage debug tokens**. +1. Add this token string as an encrypted secret in your CI system (e.g. + `APP_CHECK_DEBUG_TOKEN`). +1. Configure your build to pass this secret as an environment variable to the + SDK during test execution. -### Remote Config +### Firebase Remote Config Consider that you do not need to hardcode model names (e.g., a specific model version string). Use Firebase Remote Config to update model versions dynamically without deploying new client code. See [Changing model names remotely](https://firebase.google.com/docs/ai-logic/change-model-name-remotely.md.txt) -> [!WARNING] **CRITICAL: Backend Provisioning Required** For all platforms -> (Flutter, Android, iOS, Web), you MUST run `npx firebase-tools init ailogic` -> to provision the service. `flutterfire configure` ONLY handles client -> configuration and does NOT enable the AI service, leading to -> `PERMISSION_DENIED` errors. +______________________________________________________________________ ## Initialization Code References -- **Web Modular API** - - Provider: Gemini Developer API - - Reference: [usage_patterns_web.md](references/usage_patterns_web.md) -- **Android (Kotlin)** - - Provider: Gemini Developer API - - Reference: [usage_patterns_android.md](references/usage_patterns_android.md) -- **iOS (Swift)** - - Provider: Gemini Developer API - - Reference: [ios_setup.md](references/ios_setup.md) -- **Flutter (Dart)** - - Provider: Gemini Developer API - - Reference: [flutter_setup.md](references/flutter_setup.md) +- **Android (Kotlin)** + + - Gemini API Provider: Gemini Developer API + - Reference: [usage_patterns_android.md](references/usage_patterns_android.md) + +- **iOS (Swift)** + + - Gemini API Provider: Gemini Developer API + - Reference: [ios_setup.md](references/ios_setup.md) + +- **Web Modular API** + + - Gemini API Provider: Gemini Developer API + - Reference: [usage_patterns_web.md](references/usage_patterns_web.md) + +- **Flutter (Dart)** + + - Gemini API Provider: Gemini Developer API + - Reference: [flutter_setup.md](references/flutter_setup.md) > [!WARNING] **CRITICAL: Use current model names:** Always check the > [Firebase AI Logic Models documentation](https://firebase.google.com/docs/ai-logic/models.md.txt) @@ -197,8 +281,9 @@ without deploying new client code. See ## References -[Web SDK code examples and usage patterns](references/usage_patterns_web.md) +[Android (Kotlin) SDK usage patterns](references/usage_patterns_android.md) [iOS SDK code examples and usage patterns](references/ios_setup.md) -[Flutter SDK code examples and usage patterns](references/flutter_setup.md) +[Web SDK setup, code examples, and usage patterns](references/usage_patterns_web.md) -[Android (Kotlin) SDK usage patterns](references/usage_patterns_android.md) +[Flutter SDK code examples and usage patterns](references/flutter_setup.md) +[Client-side Text-to-Speech (TTS) generation](references/tts_generation.md) diff --git a/plugins/firebase/.agents/skills/firebase-ai-logic-basics/references/flutter_setup.md b/plugins/firebase/.agents/skills/firebase-ai-logic-basics/references/flutter_setup.md index 64dd5391..bf31dc24 100644 --- a/plugins/firebase/.agents/skills/firebase-ai-logic-basics/references/flutter_setup.md +++ b/plugins/firebase/.agents/skills/firebase-ai-logic-basics/references/flutter_setup.md @@ -96,3 +96,40 @@ final chat = model.startChat(history: [ final response = await chat.sendMessage(Content.text('What is CBT?')); ``` + +## App Check Debug Provider + +For App Check debug tokens during local development and CI/CD, add the plugin +with `flutter pub add firebase_app_check`, then activate the debug providers +right after `Firebase.initializeApp()`: + +```dart +import 'package:firebase_app_check/firebase_app_check.dart'; +import 'package:flutter/foundation.dart'; + +// In main(), after `await Firebase.initializeApp();` +if (kDebugMode) { + await FirebaseAppCheck.instance.activate( + providerAndroid: const AndroidDebugProvider(), + providerApple: const AppleDebugProvider(), + providerWeb: WebDebugProvider(), + ); +} +``` + +- Use `providerAndroid`, `providerApple`, and `providerWeb`. The older + `androidProvider` and `appleProvider` parameters (`AndroidProvider.debug`, + `AppleProvider.debug`) still appear in many examples but are deprecated. +- Only use the debug providers in debug builds, as gated by `kDebugMode` above. + Release builds must activate the production providers (Play Integrity, App + Attest or DeviceCheck, and reCAPTCHA Enterprise) instead; see + [App Check for Firebase AI Logic](https://firebase.google.com/docs/ai-logic/app-check.md.txt). +- **CI/CD**: Pass the pre-provisioned token to all three debug providers, e.g. + `debugToken: const String.fromEnvironment('APP_CHECK_DEBUG_TOKEN')`, and build + with `--dart-define=APP_CHECK_DEBUG_TOKEN=`. +- **Flutter web**: `providerWeb` is required on web; without it, `activate()` + throws. `WebDebugProvider` turns on the web debug token itself, so + `web/index.html` doesn't need `self.FIREBASE_APPCHECK_DEBUG_TOKEN`. +- If the app uses `firebase_ai` 3.11.0 or lower, also set the `appCheck` + parameter of `FirebaseAI.googleAI(...)` to `FirebaseAppCheck.instance`. Newer + versions don't need this. diff --git a/plugins/firebase/.agents/skills/firebase-ai-logic-basics/references/ios_setup.md b/plugins/firebase/.agents/skills/firebase-ai-logic-basics/references/ios_setup.md index fda5bb6a..b0bdf62c 100644 --- a/plugins/firebase/.agents/skills/firebase-ai-logic-basics/references/ios_setup.md +++ b/plugins/firebase/.agents/skills/firebase-ai-logic-basics/references/ios_setup.md @@ -3,17 +3,46 @@ ## 1. Import and Initialize Ensure you have installed the `FirebaseAILogic` SDK via Swift Package Manager. +Use the `xcode-project-setup` skill to automate adding the SPM dependency to the +Xcode project. ```swift import FirebaseAILogic -// Initialize the Firebase AI service and the generative model. -let ai = FirebaseAI.firebaseAI() +// To enable replay protection for generative or preview models, pass +// useLimitedUseAppCheckTokens: true with your backend: +let ai = FirebaseAI.firebaseAI( + backend: .googleAI(), + useLimitedUseAppCheckTokens: true +) // [AGENT] Replace "" with the latest model from https://firebase.google.com/docs/ai-logic/models.md.txt let model = ai.generativeModel(modelName: "") ``` +### App Check Replay Protection + +Generative and preview models enforce replay protection with 5-minute +limited-use App Check tokens. If you call a protected model without enabling +limited-use tokens, the request fails with: + +```text +HTTP 403: "To access this model, you must enforce Firebase App Check" +``` + +To resolve this error, initialize `FirebaseAI` with +`useLimitedUseAppCheckTokens: true`: + +```swift +let ai = FirebaseAI.firebaseAI( + backend: .googleAI(), + useLimitedUseAppCheckTokens: true +) +``` + +This ensures the SDK fetches a fresh, short-lived limited-use token for each +request rather than reusing a standard cached App Check token. + ## 2. SwiftUI Integration (Best Practices) Use the `@Observable` pattern to manage AI state and provide a smooth UX with @@ -33,7 +62,10 @@ import FirebaseAILogic @Observable final class AIViewModel { // [AGENT] Replace with the latest model from https://firebase.google.com/docs/ai-logic/models.md.txt - private lazy var model = FirebaseAI.firebaseAI().generativeModel(modelName: "") + private lazy var model = FirebaseAI.firebaseAI( + backend: .googleAI(), + useLimitedUseAppCheckTokens: true + ).generativeModel(modelName: "") var responseText: String = "" var isFetching: Bool = false @@ -98,6 +130,68 @@ let model = FirebaseAI.firebaseAI().generativeModel( ) ``` +## 4. App Check Debug Provider + +For App Check debug tokens during local development and CI/CD, add the +`FirebaseAppCheck` product to the app target (with the `xcode-project-setup` +skill), then set the debug provider factory in debug builds *before* +`FirebaseApp.configure()`. Otherwise the debug provider isn't used. + +```swift +import FirebaseAppCheck +import FirebaseCore + +// In your app's init(), before configuring Firebase: +#if DEBUG +AppCheck.setAppCheckProviderFactory(AppCheckDebugProviderFactory()) +#endif +FirebaseApp.configure() +``` + +For CI/CD, add an `AppCheckDebugToken` environment variable to the Xcode test +scheme with a value like `$(APP_CHECK_DEBUG_TOKEN)`, then pass the token from +the CI test command: + +```bash +xcodebuild test -scheme -workspace .xcworkspace \ + APP_CHECK_DEBUG_TOKEN= +``` + +### Debug Token Persistence + +When running on simulators or during development, the Firebase iOS SDK generates +a new debug token UUID whenever `NSUserDefaults` is cleared (e.g., simulator +reset or fresh install). To avoid invalidating tokens registered in the Firebase +Console and prevent token churn, persist a stable token by setting the +`AppCheckDebugToken` environment variable, which the SDK reads automatically at +runtime: + +> [!WARNING] **CRITICAL: Never Hardcode or Commit Debug Tokens** Debug tokens +> grant access to backend resources without device attestation. Never commit +> debug tokens to version control or hardcode token strings in source code. + +- **In Xcode Scheme (Recommended):** Edit Scheme -> Run -> Arguments -> + Environment Variables -> Add `AppCheckDebugToken = `. Keep + user schemes (`xcuserdata/`) unshared and gitignored. +- **In Code (Safe Dynamic Loading Only):** If setting the environment variable + in code before configuring `AppCheckDebugProviderFactory`, load the token + dynamically from a gitignored local file or environment rather than hardcoding + the token literal: + +```swift +#if DEBUG +// ✅ SAFE: Load from gitignored local file or process environment +// Note: loadGitIgnoredDebugToken() is a placeholder for your custom helper (e.g., reading from a gitignored plist) +if let debugToken = loadGitIgnoredDebugToken() { + setenv("AppCheckDebugToken", debugToken, 0) +} +let providerFactory = AppCheckDebugProviderFactory() +AppCheck.setAppCheckProviderFactory(providerFactory) +#endif + +FirebaseApp.configure() // Configure Firebase AFTER setting the App Check provider factory +``` + # Advanced Features ### Chat Session (Multi-turn) diff --git a/plugins/firebase/.agents/skills/firebase-ai-logic-basics/references/tts_generation.md b/plugins/firebase/.agents/skills/firebase-ai-logic-basics/references/tts_generation.md new file mode 100644 index 00000000..5f6d04e9 --- /dev/null +++ b/plugins/firebase/.agents/skills/firebase-ai-logic-basics/references/tts_generation.md @@ -0,0 +1,676 @@ +# Client-Side Text-to-Speech (TTS) Generation with Gemini + +Firebase AI Logic enables client-side Text-to-Speech (TTS) generation directly +from your mobile and web applications without maintaining custom backend speech +services. Using specialized Gemini TTS models, apps can synthesize natural, +expressive audio with custom voice personas, multi-speaker dialogues, and +in-flight audio streaming. + +______________________________________________________________________ + +## Supported Models + +Always specify a dedicated Gemini TTS model when generating audio: + +| Model ID | Description | +| :----------------------------- | :--------------------------------------------------------------------------------- | +| `gemini-3.1-flash-tts-preview` | Low-latency speech synthesis (preview); supports single- and multi-speaker output. | + +> [!WARNING] Always verify currently supported model availability in the +> [Firebase AI Logic Models documentation](https://firebase.google.com/docs/ai-logic/models.md.txt). + +______________________________________________________________________ + +## Core Concepts & Configurations + +### 1. Response Modality + +To instruct Gemini to synthesize and return audio, configure +`responseModalities` in the generation configuration to include audio: + +- **Swift**: `generationConfig = GenerationConfig(responseModalities: [.audio])` +- **Kotlin**: + `generationConfig { responseModalities = listOf(ResponseModality.AUDIO) }` +- **Web (JS/TS)**: `generationConfig: { responseModalities: ["AUDIO"] }` + +### 2. Single-Speaker `SpeechConfig` + +Configure voice persona and optional language code (`languageCode`, e.g. +`"en-US"`, `"es-ES"`, `"ja-JP"`) using `SpeechConfig`. Voice names map to Google +prebuilt neural voices (for example: `Puck`, `Charon`, `Kore`, `Fenrir`, +`Aoede`): + +- **Swift**: + ```swift + let speechConfig = SpeechConfig( + voiceConfig: VoiceConfig( + prebuiltVoiceConfig: PrebuiltVoiceConfig(voiceName: "Puck") + ), + languageCode: "en-US" + ) + ``` +- **Kotlin**: + ```kotlin + val speechConfig = speechConfig { + voiceConfig = voiceConfig { + prebuiltVoiceConfig = prebuiltVoiceConfig { + voiceName = "Puck" + } + } + languageCode = "en-US" + } + ``` +- **Web**: + ```javascript + speechConfig: { + voiceConfig: { + prebuiltVoiceConfig: { + voiceName: "Puck" + } + }, + languageCode: "en-US" + } + ``` + +### 3. Multi-Speaker `MultiSpeakerVoiceConfig` (2-Speaker Dialogues) + +For scripts with two distinct characters or roles (e.g., host and guest, +narrator and character), configure `MultiSpeakerVoiceConfig` with +`speakerVoiceConfigs` linking speaker names to specific voices: + +- **Swift**: + ```swift + let multiSpeakerConfig = SpeechConfig( + multiSpeakerVoiceConfig: MultiSpeakerVoiceConfig( + speakerVoiceConfigs: [ + SpeakerVoiceConfig( + speaker: "Host", + voiceConfig: VoiceConfig( + prebuiltVoiceConfig: PrebuiltVoiceConfig(voiceName: "Puck") + ) + ), + SpeakerVoiceConfig( + speaker: "Guest", + voiceConfig: VoiceConfig( + prebuiltVoiceConfig: PrebuiltVoiceConfig(voiceName: "Aoede") + ) + ) + ] + ) + ) + ``` +- **Kotlin**: + ```kotlin + val multiSpeakerConfig = speechConfig { + multiSpeakerVoiceConfig = multiSpeakerVoiceConfig { + speakerVoiceConfigs = listOf( + speakerVoiceConfig { + speaker = "Host" + voiceConfig = voiceConfig { + prebuiltVoiceConfig = prebuiltVoiceConfig { voiceName = "Puck" } + } + }, + speakerVoiceConfig { + speaker = "Guest" + voiceConfig = voiceConfig { + prebuiltVoiceConfig = prebuiltVoiceConfig { voiceName = "Aoede" } + } + } + ) + } + } + ``` +- **Web**: + ```javascript + speechConfig: { + multiSpeakerVoiceConfig: { + speakerVoiceConfigs: [ + { + speaker: "Host", + voiceConfig: { prebuiltVoiceConfig: { voiceName: "Puck" } } + }, + { + speaker: "Guest", + voiceConfig: { prebuiltVoiceConfig: { voiceName: "Aoede" } } + } + ] + } + } + ``` + +______________________________________________________________________ + +## Audio Directives and Emotional Tags + +Gemini TTS models accept natural-language directives and inline tags in prompts +to control tone, cadence, pacing, and emotional expression. + +### Directives + +Include directives at the start of your prompt to set the overall tone and +scene: + +- `[Audio Profile: Warm, conversational podcast co-host with energetic delivery]` +- `[Scene: Quiet bedtime story in a calm room]` +- `[Director's Note: Speak deliberately, pausing for reflection after major questions]` + +### Emotional Tags & Delivery Cues + +Embed delivery markers inline within dialogue or narration: + +- `[whispers]` — Soft, confidential whisper. +- `[laughs]` / `[chuckles]` — Lighthearted laughter during delivery. +- `[sighs]` — Expressive exhale. +- `[slowly]` — Reduced tempo for emphasis or dramatic effect. +- `[excited]` — Higher energy and faster pacing. +- `[pause]` — Deliberate silence. + +### Multi-Speaker Prompt Example + +```text +[Scene: A lively coffee-shop tech discussion] +[Director's Note: Host is upbeat and curious; Guest is thoughtful and explanatory] + +Host: Welcome back! [excited] Today we are diving into client-side audio. +Guest: [laughs] It's about time! No servers in between means virtually zero latency. +Host: [whispers] Tell us the secret... how does it stream? +Guest: [slowly] Pure linear PCM, chunk by chunk, directly to your device's audio engine. +``` + +______________________________________________________________________ + +## Audio Decoding and Playback + +### Format Specifications + +Gemini TTS audio format depends on whether the request is unary or streaming: + +- **Unary Requests (`generateContent`)**: Returns complete **WAV (`audio/wav`)** + audio with a standard 44-byte RIFF header included by default (24 kHz, mono, + 16-bit signed little-endian PCM). These bytes can be passed directly to + standard media players (`AVAudioPlayer`, `MediaPlayer`, `