Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions packages/asana/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# @pleaseai/emulate-asana

Asana REST API (v1.0) emulator for local development and CI.

A stateful, in-memory implementation of the Asana API surface — no network and
no real Asana account required. It covers users, workspaces, teams, projects,
sections, tasks, tags, stories, and webhooks, including subtasks, dependencies,
followers, project/section membership, and offset-based pagination.

OAuth 2.0 and an inspector UI are follow-up work.

## Install

```bash
npm install @pleaseai/emulate-asana
```

Usually you do not install this directly — run it through the `@pleaseai/emulate` CLI.

## Start

```bash
# Through the emulate CLI (asana listens on its default port 4005)
bun packages/emulate/dist/index.js --service asana

# Start every service
bun packages/emulate/dist/index.js
```

A single service starts on the base port (default `4000`); when started with
`--service asana` it runs on port `4005`. Use `-p <port>` to change it.

## Auth

All `/api/1.0/*` routes are guarded by bearer-token auth. Send any token via the
`Authorization` header — by default an unmatched token resolves to the first
seeded user, so `me` keywords (`assignee=me`, `GET /users/me`) work out of the box:

```bash
curl http://localhost:4005/api/1.0/users/me \
-H "Authorization: Bearer test-token"
```

To map specific tokens to specific users, set them under the top-level `tokens`
key in your config:

```yaml
tokens:
test-token:
login: dev@example.com # matches a seeded user's email, gid, or name
scopes: []
```

## Request & response shape

The emulator follows Asana's envelope conventions:

- Write bodies are wrapped in `data`: `{"data": {"name": "My Task"}}` (a bare
object is also accepted).
- Successful responses are wrapped in `data`; list responses include a
`next_page` cursor (`{ offset, path, uri }` or `null`).
- Errors return `{"errors": [{"message": "..."}]}` with the matching HTTP status.
- Lists accept `limit` (1–100, default 20) and `offset` for pagination.

```bash
# Create a task
curl -X POST http://localhost:4005/api/1.0/tasks \
-H "Authorization: Bearer test-token" \
-H "Content-Type: application/json" \
-d '{"data":{"name":"Write the README","projects":["<project_gid>"],"assignee":"me"}}'

# List tasks in a project
curl "http://localhost:4005/api/1.0/tasks?project=<project_gid>&limit=50" \
-H "Authorization: Bearer test-token"
```

## Emulated endpoints

| Resource | Endpoints |
| --- | --- |
| Users | `GET /users`, `GET /users/:gid` (incl. `me`) |
| Workspaces | `GET /workspaces`, `GET/PUT /workspaces/:gid` |
| Teams | `POST /teams`, `GET /teams/:gid`, `GET /workspaces/:gid/teams`, `GET /teams/:gid/users`, `GET /teams/:gid/projects`, `POST /teams/:gid/addUser`, `POST /teams/:gid/removeUser` |
| Projects | `GET/POST /projects`, `GET/PUT/DELETE /projects/:gid`, `GET /projects/:gid/tasks`, `GET /projects/:gid/sections`, `GET /projects/:gid/task_counts` |
| Sections | `POST /projects/:gid/sections`, `GET/PUT/DELETE /sections/:gid`, `GET /sections/:gid/tasks`, `POST /sections/:gid/addTask` |
| Tasks | `GET/POST /tasks`, `GET/PUT/DELETE /tasks/:gid`, subtasks, stories, tags, projects, dependencies/dependents, `addProject`/`removeProject`, `addTag`/`removeTag`, `addDependencies`/`removeDependencies`, `addFollowers`/`removeFollowers`, `setParent` |
| Tags | `GET/POST /tags`, `GET/PUT/DELETE /tags/:gid`, `GET /tags/:gid/tasks`, `GET/POST /workspaces/:gid/tags` |
| Stories | `GET/PUT/DELETE /stories/:gid` (comments created via `POST /tasks/:gid/stories`) |
| Webhooks | `GET/POST /webhooks`, `GET/PUT/DELETE /webhooks/:gid` |

## Seed config

Seed data lets the emulator start pre-populated. References between entities are
resolved by name (e.g. a project's `team` points at a team's `name`); a
workspace defaults to the first one when omitted.

```yaml
asana:
workspaces:
- name: My Workspace
is_organization: true
users:
- name: Developer
email: dev@example.com
teams:
- name: Engineering
workspace: My Workspace
projects:
- name: My Project
workspace: My Workspace
team: Engineering
owner: Developer
default_view: board
sections:
- name: To Do
project: My Project
tags:
- name: urgent
workspace: My Workspace
color: red
tasks:
- name: Example Task
project: My Project
section: To Do
assignee: Developer
due_on: 2026-01-31
```

Without any seed config, the plugin seeds a single `My Workspace` so the
emulator is usable immediately.

## Programmatic use

```ts
import { authMiddleware, Hono, Store, WebhookDispatcher } from '@emulators/core'
import { asanaPlugin, seedFromConfig } from '@pleaseai/emulate-asana'

const store = new Store()
const webhooks = new WebhookDispatcher()
const baseUrl = 'http://localhost:4005'

const app = new Hono()
app.use('*', authMiddleware(/* tokenMap */))
asanaPlugin.register(app, store, webhooks, baseUrl)
asanaPlugin.seed?.(store, baseUrl)

seedFromConfig(store, baseUrl, {
workspaces: [{ name: 'My Workspace', is_organization: true }],
users: [{ name: 'Developer', email: 'dev@example.com' }],
})
```

`getAsanaStore(store)` exposes the typed collections (`users`, `workspaces`,
`tasks`, …) for direct inspection in tests.

## License

Apache-2.0
113 changes: 113 additions & 0 deletions skills/asana/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
name: asana
description: Emulated Asana REST API (v1.0) for local development and testing. Use when the user needs to test Asana integrations locally, create or query tasks/projects/sections, validate webhooks, or avoid hitting the real Asana API.
allowed-tools: Bash(bun:*), Bash(npx @pleaseai/emulate:*), Bash(curl:*)
---

# Asana API Emulator

A stateful, in-memory Asana REST API (v1.0) emulator.

Included now:

- Users, workspaces, teams, projects, sections, tasks, tags, stories, webhooks
- Subtasks, task dependencies/dependents, followers, project/section membership
- Bearer-token auth and offset-based pagination (`limit` 1–100, `offset`)

Follow-up work will add OAuth 2.0 and an inspector UI.

## Start

```bash
# From this repo (after `bun install && bun run build`)
bun packages/emulate/dist/index.js --service asana

# Or from the published package
npx @pleaseai/emulate --service asana
```

A single service starts on the base port (default `4000`). Use `-p <port>` to
change it. When started alongside other services, ports are assigned
sequentially from the base port (asana's slot is `4005`).

Default URL (asana alone):

```text
http://localhost:4000
```

## Auth

All `/api/1.0/*` routes require a bearer token. Any token works — an unmatched
token resolves to the first seeded user, so `me` keywords work out of the box.

```bash
curl http://localhost:4000/api/1.0/users/me \
-H "Authorization: Bearer test-token"
```

Map specific tokens to users via the top-level `tokens` key in the config:

```yaml
tokens:
test-token:
login: dev@example.com # matches a seeded user's email, gid, or name
scopes: []
```

## Conventions

- Write bodies are wrapped in `data`: `{"data": {"name": "My Task"}}`.
- Successful responses are wrapped in `data`; lists add a `next_page` cursor.
- Errors return `{"errors": [{"message": "..."}]}`.

## Example

```bash
# Create a task (assigned to the authenticated user)
curl -X POST http://localhost:4000/api/1.0/tasks \
-H "Authorization: Bearer test-token" \
-H "Content-Type: application/json" \
-d '{"data":{"name":"Write the README","projects":["<project_gid>"],"assignee":"me"}}'

# List tasks in a project
curl "http://localhost:4000/api/1.0/tasks?project=<project_gid>&limit=50" \
-H "Authorization: Bearer test-token"
```

## Seed Config

Add an `asana:` section to `emulate.config.yaml` (or pass `--seed <file>`).
Inter-entity references are resolved by name; the workspace defaults to the
first one when omitted.

```yaml
asana:
workspaces:
- name: My Workspace
is_organization: true
users:
- name: Developer
email: dev@example.com
teams:
- name: Engineering
workspace: My Workspace
projects:
- name: My Project
workspace: My Workspace
team: Engineering
owner: Developer
sections:
- name: To Do
project: My Project
tags:
- name: urgent
workspace: My Workspace
color: red
tasks:
- name: Example Task
project: My Project
section: To Do
assignee: Developer
due_on: 2026-01-31
```
88 changes: 88 additions & 0 deletions skills/firebase/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
name: firebase
description: Emulated Firebase Auth (Identity Toolkit + Secure Token) and FCM v1 for local development and testing. Use when the user needs to test Firebase Auth locally, sign up / sign in with email+password, refresh ID tokens, send FCM messages, or avoid hitting real Firebase services.
allowed-tools: Bash(bun:*), Bash(npx @pleaseai/emulate:*), Bash(curl:*)
---

# Firebase Emulator (Auth + FCM)

A stateful emulator for Firebase Authentication and Cloud Messaging.

Included now:

- Identity Toolkit REST: `signUp`, `signInWithPassword`, `lookup`, `update`,
`delete`, `sendOobCode`
- Secure Token: refresh ID tokens (`POST /v1/token`)
- FCM v1: `POST /v1/projects/<projectId>/messages:send`

## Start

```bash
# From this repo (after `bun install && bun run build`)
bun packages/emulate/dist/index.js --service firebase

# Or from the published package
npx @pleaseai/emulate --service firebase
```

A single service starts on the base port (default `4000`). Use `-p <port>` to
change it. When started alongside other services, ports are assigned
sequentially from the base port (firebase's slot is `4003`).

Point a Firebase SDK at the emulator with
`FIREBASE_AUTH_EMULATOR_HOST=localhost:4000`.

## Auth

- Identity Toolkit and Secure Token take the API key as a query param:
`?key=<api_key>`.
- FCM `messages:send` requires `Authorization: Bearer <token>` (presence only —
the token is not cryptographically verified).

The `googleapis.com`-prefixed path variants are also accepted, e.g.
`/identitytoolkit.googleapis.com/v1/accounts:signUp` and
`/securetoken.googleapis.com/v1/token`.

## Flow

```bash
# 1. Sign up with email + password
curl -X POST "http://localhost:4000/v1/accounts:signUp?key=firebase_api_key_example" \
-H "Content-Type: application/json" \
-d '{"email":"hong@example.com","password":"password123","returnSecureToken":true}'
# → {"idToken":"...","refreshToken":"...","expiresIn":"3600","localId":"..."}

# 2. Sign in with email + password
curl -X POST "http://localhost:4000/v1/accounts:signInWithPassword?key=firebase_api_key_example" \
-H "Content-Type: application/json" \
-d '{"email":"hong@example.com","password":"password123","returnSecureToken":true}'

# 3. Refresh the ID token
curl -X POST "http://localhost:4000/v1/token?key=firebase_api_key_example" \
-H "Content-Type: application/json" \
-d '{"grant_type":"refresh_token","refresh_token":"<refreshToken>"}'

# 4. Send an FCM message
curl -X POST "http://localhost:4000/v1/projects/demo-project/messages:send" \
-H "Authorization: Bearer test-token" \
-H "Content-Type: application/json" \
-d '{"message":{"token":"<device_token>","notification":{"title":"Hi","body":"World"}}}'
```

Inspect emulator state (no auth) at `GET /internal/messages` and
`GET /internal/oob_codes`.

## Seed Config

Add a `firebase:` section to `emulate.config.yaml` (or pass `--seed <file>`):

```yaml
firebase:
projects:
- project_id: demo-project
api_key: firebase_api_key_example
users:
- email: hong@example.com
password: password123
display_name: 홍길동
```
Loading