From 00b3ccd8ee932fdb5baaddc9b6859461235df99b Mon Sep 17 00:00:00 2001 From: Alon Rosenblum Date: Thu, 28 May 2026 12:46:42 +0300 Subject: [PATCH 1/4] feat(skills): add onboarding skill for Tom & Jerry scaffolding Adds skills/onboarding/SKILL.md, a fetch-and-transform skill that scaffolds a working Tom & Jerry agent pair on the Band.ai platform from credentials in pasted onboarding text. Fetches the live example files at runtime and applies documented transformations, so the skill tracks upstream changes without duplicating agent code. Co-Authored-By: Claude Opus 4.7 (1M context) --- skills/onboarding/SKILL.md | 223 +++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 skills/onboarding/SKILL.md diff --git a/skills/onboarding/SKILL.md b/skills/onboarding/SKILL.md new file mode 100644 index 000000000..16feab5b4 --- /dev/null +++ b/skills/onboarding/SKILL.md @@ -0,0 +1,223 @@ +--- +name: thenvoi-onboarding +description: Scaffold a Tom & Jerry agent pair on the Thenvoi platform from credentials supplied in the conversation. Use when a user pastes onboarding text from band.ai containing Tom/Jerry agent IDs and API keys. +--- + +# Thenvoi Onboarding Skill + +You are running the Thenvoi onboarding flow. The user has pasted text containing platform credentials and a link to this skill. Your job is to scaffold a working Tom & Jerry agent pair locally **by fetching the live example files from this same repo and transforming them** — this skill ships no copies of agent code. + +## Source of truth + +All agent code lives in `examples/` in this repo. Fetch via raw GitHub URLs: + +``` +https://raw.githubusercontent.com/thenvoi/thenvoi-sdk-python// +``` + +Use the same branch you fetched this SKILL.md from. If you don't know it, default to `main`. + +### Example file map (adapter → Tom + Jerry filenames) + +| Adapter | Tom file | Jerry file | +|---|---|---| +| `langgraph` | `examples/langgraph/07_tom_agent.py` | `examples/langgraph/08_jerry_agent.py` | +| `crewai` | `examples/crewai/05_tom_agent.py` | `examples/crewai/06_jerry_agent.py` | +| `anthropic` | `examples/anthropic/03_tom_agent.py` | `examples/anthropic/04_jerry_agent.py` | +| `claude_sdk` | `examples/claude_sdk/03_tom_agent.py` | `examples/claude_sdk/04_jerry_agent.py` | +| `parlant` | `examples/parlant/04_tom_agent.py` | `examples/parlant/05_jerry_agent.py` | +| `pydantic_ai` | `examples/pydantic_ai/03_tom_agent.py` | `examples/pydantic_ai/04_jerry_agent.py` | + +The shared character prompts live at `examples/prompts/characters.py` — fetch verbatim, no transformation. + +## Procedure + +Follow these steps in order. Use AskUserQuestion for every user prompt. + +### Step 1 — Extract credentials from the pasted text + +Look in the conversation so far for: +- A **Tom agent ID** (UUID) and **Tom API key** +- A **Jerry agent ID** (UUID) and **Jerry API key** +- Optionally `THENVOI_REST_URL` and `THENVOI_WS_URL`. If absent, use the production defaults: + - `THENVOI_REST_URL=https://app.band.ai` + - `THENVOI_WS_URL=wss://app.band.ai/api/v1/socket/websocket` + +(The env var names stay `THENVOI_*` because the SDK reads those names — only the URL values point at band.ai.) + +If any of the four required credentials are missing, ask the user for them before proceeding. + +### Step 2 — Ask which adapter + +AskUserQuestion (single-select). Show these descriptions: + +- **langgraph** — Graph-based agent framework. Needs an LLM API key (OpenAI or Anthropic). +- **crewai** — Role-based multi-agent framework. Needs an LLM API key (OpenAI or Anthropic). +- **anthropic** — Direct Anthropic SDK loop. Needs an Anthropic API key. +- **claude_sdk** — Uses the Claude Agent SDK (Claude Code subprocess). **No external LLM key needed** — but requires Node.js 20+ and `npm install -g @anthropic-ai/claude-code`. +- **parlant** — Conversation-modeling framework. Needs an OpenAI API key. +- **pydantic_ai** — Pydantic AI agent. Needs an LLM API key (OpenAI or Anthropic). + +### Step 3 — Ask which LLM (conditional) + +| Adapter | LLM question | LLM env var | +|---|---|---| +| `anthropic` | Skip — Anthropic only | `ANTHROPIC_API_KEY` | +| `claude_sdk` | Skip — no external LLM | none | +| `parlant` | Skip — OpenAI only (default NLP service) | `OPENAI_API_KEY` | +| `crewai`, `langgraph`, `pydantic_ai` | Ask: OpenAI or Anthropic | matching key | + +### Step 4 — Confirm output directory + +Default to `./tom-jerry-agents/` in the cwd. Ask only if it already exists. + +### Step 5 — Fetch and transform + +Fetch these three files from the branch determined at the top: + +1. `examples/prompts/characters.py` +2. The Tom file for the chosen adapter (see map above) +3. The Jerry file for the chosen adapter + +**For `characters.py`**: write it verbatim to `/characters.py`. No transformation. + +**For each agent file**, apply the transformations below. They are pattern-based. If a pattern doesn't match (e.g. the example was already cleaned up), skip silently — that's fine. If something looks structurally different from what's documented here (e.g. a new shared import you don't recognize), STOP and tell the user before writing — don't guess. + +#### Transformations to apply + +1. **Strip PEP 723 inline script header.** Remove the entire block from `# /// script` to `# ///` inclusive (it's at the top of the file). + +2. **Drop the sys.path hack.** Remove the line: + ```python + sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) + ``` + Also remove the `import sys` line if it's no longer used elsewhere in the file. + +3. **Rewrite the characters import.** Change: + ```python + from prompts.characters import generate_tom_prompt # or generate_jerry_prompt + ``` + to: + ```python + from characters import generate_tom_prompt + ``` + +4. **Replace the logging setup.** Change: + ```python + from setup_logging import setup_logging + ... + setup_logging() + ``` + to: + ```python + import logging + logging.basicConfig(level=logging.INFO) + ``` + (Keep the existing `logger = logging.getLogger(__name__)` line.) + +5. **Swap the LLM if the user picked something other than the example default.** The example defaults are listed below. Only modify if the user's choice differs. + + | Adapter | Example default | Anthropic swap | OpenAI swap | + |---|---|---|---| + | `langgraph` | `from langchain_openai import ChatOpenAI` + `ChatOpenAI(model="gpt-5.4-mini")` | replace import with `from langchain_anthropic import ChatAnthropic`, replace constructor with `ChatAnthropic(model="claude-sonnet-4-5-20250929")` | already default | + | `crewai` | `model="gpt-5.4-mini"` | `model="anthropic/claude-sonnet-4-5-20250929"` (litellm prefix) | already default | + | `pydantic_ai` | `model="openai:gpt-5.4-mini"` | `model="anthropic:claude-sonnet-4-5-20250929"` | already default | + | `anthropic`, `claude_sdk`, `parlant` | n/a — no swap path | — | — | + +Write each transformed file to `/tom_agent.py` and `/jerry_agent.py`. + +### Step 6 — Generate scaffolding files + +Write these directly (they're scaffolding, not SDK code — no fetch needed). Substitute the bracketed values. + +**`/agent_config.yaml`** +```yaml +# Agent credentials from the Thenvoi platform. +tom_agent: + agent_id: "" + api_key: "" + +jerry_agent: + agent_id: "" + api_key: "" +``` + +**`/.env`** — use the REST/WS URLs from step 1, default to production if absent. The third line depends on the LLM choice: +- OpenAI → `OPENAI_API_KEY=` +- Anthropic (incl. the `anthropic` adapter) → `ANTHROPIC_API_KEY=` +- `claude_sdk` → omit entirely +- `parlant` → `OPENAI_API_KEY=` + +``` +THENVOI_REST_URL= +THENVOI_WS_URL= + +``` + +**`/pyproject.toml`** — `` is the adapter name with two hyphen-form exceptions: `claude_sdk` → `claude-sdk`, `pydantic_ai` → `pydantic-ai`. The other adapters (`langgraph`, `crewai`, `anthropic`, `parlant`) use their name verbatim. +```toml +[project] +name = "thenvoi-tom-jerry" +version = "0.1.0" +requires-python = ">=3.11" +dependencies = [ + "band-sdk[]>=0.2.10", + "python-dotenv>=1.0.0", +] +``` + +Note: `band-sdk` is the PyPI name; the installed Python module is still `thenvoi` (so the agent code's `from thenvoi import ...` imports are unchanged). + +**`/.gitignore`** +``` +.env +agent_config.yaml +.venv/ +__pycache__/ +``` + +### Step 7 — Ask how to handle the LLM API key + +Skip for `claude_sdk`. + +AskUserQuestion (single-select): +- **I'll add it to the .env myself** — show the path `/.env` and which line to fill. +- **Add it for me now** — ask for the key and edit the .env in place. + +### Step 8 — Ask who runs the agents + +AskUserQuestion (single-select): +- **I'll run them myself** — show: + ``` + cd + uv sync + uv run python tom_agent.py # terminal 1 + uv run python jerry_agent.py # terminal 2 + ``` +- **Claude, run them for me** — start both with `run_in_background: true`, one Bash call per agent. After they're running, give the BashOutput command for tailing logs. + +### Step 9 — Show the user how to trigger the chase + +Both agents are running and connected. To see them in action, present these steps to the user (copy the block below verbatim, just substitute the platform URL from `THENVOI_REST_URL` in their `.env`): + +> **Watch Tom chase Jerry on the platform** +> +> 1. Open **** in your browser and sign in. +> 2. In the left sidebar, click **Chats**. +> 3. Click **Start Your First Chat** — a new session opens. +> 4. In the **Participants** panel on the right, click the **+** next to the heading. +> 5. Select the **Tom** agent card, then click **Done**. Tom appears under **AGENTS** in the panel. +> 6. In the message box at the bottom, type `@Tom catch jerry` and hit send. +> +> Tom will look up Jerry, invite him into the chat automatically, and start trying to lure him out of his hole. The persuasion will escalate over up to 10 attempts — watch the back-and-forth in the chat, and tail the terminal logs (or the BashOutput stream if Claude is running them) if anything looks stuck. + +Only add Tom as a participant — Tom finds and invites Jerry himself via the platform tools. Don't tell the user to add Jerry manually. + +## Rules for Claude + +- **Don't clone `thenvoi-sdk-python`.** Only fetch the specific files listed above. +- **Don't bundle copies of the examples** in this skill — fetch them live, every time. +- **Don't walk the user through `characters.py`** — it's long and not relevant to onboarding. +- **If a transformation pattern fails to match**, that's usually fine (the example already changed in a compatible way). If the *structure* looks unfamiliar (new imports you don't recognize, the agent class name changed, the `Agent.from_config` shape is different), STOP and surface what's odd — don't fabricate a fix. +- **Respect existing files.** If `/` is non-empty, ask before overwriting. +- **Stop after step 9.** No refactors, no extra suggestions. From 42423d209bfa21157bb93b450b82f74693c14dc6 Mon Sep 17 00:00:00 2001 From: Alon Rosenblum Date: Thu, 28 May 2026 17:53:21 +0300 Subject: [PATCH 2/4] fix(skills): split adapter picker into two stages AskUserQuestion caps at 4 options, so the original single-question picker silently dropped parlant and pydantic_ai. Split into a runtime-category question (framework / Anthropic / Claude SDK / Parlant) followed by a framework-specific question that only fires for the framework bucket. Co-Authored-By: Claude Opus 4.7 (1M context) --- skills/onboarding/SKILL.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/skills/onboarding/SKILL.md b/skills/onboarding/SKILL.md index 16feab5b4..4734ce74c 100644 --- a/skills/onboarding/SKILL.md +++ b/skills/onboarding/SKILL.md @@ -47,16 +47,22 @@ Look in the conversation so far for: If any of the four required credentials are missing, ask the user for them before proceeding. -### Step 2 — Ask which adapter +### Step 2 — Ask which adapter (two-stage picker) -AskUserQuestion (single-select). Show these descriptions: +`AskUserQuestion` accepts at most 4 options, so we split the adapter pick into two questions. The second is only asked when the first selects the "framework" bucket. -- **langgraph** — Graph-based agent framework. Needs an LLM API key (OpenAI or Anthropic). -- **crewai** — Role-based multi-agent framework. Needs an LLM API key (OpenAI or Anthropic). -- **anthropic** — Direct Anthropic SDK loop. Needs an Anthropic API key. -- **claude_sdk** — Uses the Claude Agent SDK (Claude Code subprocess). **No external LLM key needed** — but requires Node.js 20+ and `npm install -g @anthropic-ai/claude-code`. -- **parlant** — Conversation-modeling framework. Needs an OpenAI API key. -- **pydantic_ai** — Pydantic AI agent. Needs an LLM API key (OpenAI or Anthropic). +**Step 2a — Ask the runtime category** (single-select, 4 options): + +- **Framework + your LLM key** — Run inside an agent framework (langgraph / crewai / pydantic_ai). You provide an OpenAI or Anthropic API key. +- **Direct Anthropic SDK** — Plain Anthropic SDK loop. Needs an Anthropic API key. *(sets `adapter = anthropic`)* +- **Claude Agent SDK** — Uses the Claude Code subprocess. **No external LLM key needed** — requires Node.js 20+ and `npm install -g @anthropic-ai/claude-code`. *(sets `adapter = claude_sdk`)* +- **Parlant** — Conversation-modeling framework. Needs an OpenAI API key. *(sets `adapter = parlant`)* + +**Step 2b — Ask the framework** *(only if Step 2a was "Framework + your LLM key")* (single-select, 3 options): + +- **langgraph** — Graph-based, LangChain ecosystem. +- **crewai** — Role-based multi-agent. +- **pydantic_ai** — Pydantic AI agent. ### Step 3 — Ask which LLM (conditional) From 551d9e9d6f542f7db6bae7c9daf9c917701358d6 Mon Sep 17 00:00:00 2001 From: Alon Rosenblum Date: Thu, 28 May 2026 18:07:33 +0300 Subject: [PATCH 3/4] fix(skills): address 5 issues found in onboarding test - crewai+Anthropic and pydantic_ai+Anthropic now add the right extras to pyproject.toml so the native Anthropic providers resolve correctly - Cap requires-python at <3.14 and write a .python-version to avoid pydantic-core PyO3 build failures on Python 3.14 - Replace the upstream module docstring with a one-liner so the file doesn't claim it must be run from the SDK repo root - Fail loudly if the SKILL.md branch can't be parsed from the URL instead of silently falling back to main - Add an explicit uv sync step before the background launches in the "Claude runs them" path Co-Authored-By: Claude Opus 4.7 (1M context) --- skills/onboarding/SKILL.md | 41 ++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/skills/onboarding/SKILL.md b/skills/onboarding/SKILL.md index 4734ce74c..8cd056c05 100644 --- a/skills/onboarding/SKILL.md +++ b/skills/onboarding/SKILL.md @@ -15,7 +15,10 @@ All agent code lives in `examples/` in this repo. Fetch via raw GitHub URLs: https://raw.githubusercontent.com/thenvoi/thenvoi-sdk-python// ``` -Use the same branch you fetched this SKILL.md from. If you don't know it, default to `main`. +**Determining the branch:** parse it from the SKILL.md URL the user gave you. The expected shape is `https://raw.githubusercontent.com/thenvoi/thenvoi-sdk-python//skills/onboarding/SKILL.md` — `` is the segment between the repo name and `skills/`. + +- If you fetched SKILL.md from a `raw.githubusercontent.com` URL matching that shape, use the branch from the URL for all subsequent fetches. +- If SKILL.md was loaded via any other path (a local file, a different host, a URL that doesn't match the expected shape), **STOP and ask the user which branch to fetch examples from** — show them what URL you were given and ask. Do not silently fall back to `main` — a silent fallback can pull example files that don't match the SKILL.md the user is following, and the failure mode (404 or a subtly different example) is hard to debug. ### Example file map (adapter → Tom + Jerry filenames) @@ -121,7 +124,14 @@ Fetch these three files from the branch determined at the top: ``` (Keep the existing `logger = logging.getLogger(__name__)` line.) -5. **Swap the LLM if the user picked something other than the example default.** The example defaults are listed below. Only modify if the user's choice differs. +5. **Replace the module docstring.** The upstream example docstring describes how to run the file from the repo root and mentions `prompts/characters.py` — both are wrong for the scaffolded standalone project. Replace the entire top-of-file `"""..."""` block (the one immediately after `from __future__ import annotations`, if present, or otherwise the first triple-quoted string in the file) with a single-line docstring: + + - Tom file: `"""Tom the cat agent ()."""` + - Jerry file: `"""Jerry the mouse agent ()."""` + + Where `` is the human-readable adapter name (LangGraph, CrewAI, Anthropic, Claude SDK, Parlant, Pydantic AI). + +6. **Swap the LLM if the user picked something other than the example default.** The example defaults are listed below. Only modify if the user's choice differs. | Adapter | Example default | Anthropic swap | OpenAI swap | |---|---|---|---| @@ -161,19 +171,38 @@ THENVOI_WS_URL= ``` **`/pyproject.toml`** — `` is the adapter name with two hyphen-form exceptions: `claude_sdk` → `claude-sdk`, `pydantic_ai` → `pydantic-ai`. The other adapters (`langgraph`, `crewai`, `anthropic`, `parlant`) use their name verbatim. + +The `requires-python` upper bound matters: without it `uv` will pick the newest Python on the machine (3.14+ exists at time of writing), and pydantic-core's PyO3 currently caps at 3.13 — `uv sync` will fail to build. Cap at `<3.14`. + +`` is normally empty, but for crewai + Anthropic and pydantic_ai + Anthropic some extras don't get pulled in via the band-sdk extras and need to be added explicitly: + +| Adapter + LLM | `` content (a single line, indented to match) | +|---|---| +| `crewai` + Anthropic | ` "crewai[anthropic]==1.14.3",` | +| `pydantic_ai` + Anthropic | ` "pydantic-ai-slim[anthropic]>=1.56.0",` | +| Anything else | *(omit the line entirely)* | + ```toml [project] name = "thenvoi-tom-jerry" version = "0.1.0" -requires-python = ">=3.11" +requires-python = ">=3.11,<3.14" dependencies = [ "band-sdk[]>=0.2.10", + "python-dotenv>=1.0.0", ] ``` Note: `band-sdk` is the PyPI name; the installed Python module is still `thenvoi` (so the agent code's `from thenvoi import ...` imports are unchanged). +**`/.python-version`** +``` +3.12 +``` + +(Belt-and-suspenders with the `requires-python` cap — pins the interpreter explicitly so `uv sync` picks 3.12 instead of whatever the newest local Python happens to be.) + **`/.gitignore`** ``` .env @@ -200,7 +229,11 @@ AskUserQuestion (single-select): uv run python tom_agent.py # terminal 1 uv run python jerry_agent.py # terminal 2 ``` -- **Claude, run them for me** — start both with `run_in_background: true`, one Bash call per agent. After they're running, give the BashOutput command for tailing logs. +- **Claude, run them for me** — do these in order: + 1. `cd && uv sync` (foreground, wait for it to finish). This installs deps and lockfile; the background launches below assume it succeeded. + 2. Start Tom with `run_in_background: true`: `cd && uv run python tom_agent.py` + 3. Start Jerry with `run_in_background: true`: `cd && uv run python jerry_agent.py` + 4. Give the user the `BashOutput` command for each background bash ID so they can tail logs. ### Step 9 — Show the user how to trigger the chase From f9bc6956c48770f0451c25faef346db292088caf Mon Sep 17 00:00:00 2001 From: Alon Rosenblum Date: Sat, 30 May 2026 01:02:02 +0300 Subject: [PATCH 4/4] feat(skills): add Codex and Cursor variants of onboarding skill Same procedure as Claude Code's SKILL.md, adapted for platforms that lack a structured picker UI: - Numbered text lists for user questions instead of AskUserQuestion - curl from the shell instead of WebFetch - nohup + captured PIDs for background launches (in lieu of Claude Code's run_in_background / BashOutput) Three files share the same procedure body and live alongside each other in skills/onboarding/ so band.ai's onboarding flow can serve a per-platform URL. Co-Authored-By: Claude Opus 4.7 (1M context) --- skills/onboarding/codex.md | 301 ++++++++++++++++++++++++++++++++++++ skills/onboarding/cursor.md | 301 ++++++++++++++++++++++++++++++++++++ 2 files changed, 602 insertions(+) create mode 100644 skills/onboarding/codex.md create mode 100644 skills/onboarding/cursor.md diff --git a/skills/onboarding/codex.md b/skills/onboarding/codex.md new file mode 100644 index 000000000..eb92927be --- /dev/null +++ b/skills/onboarding/codex.md @@ -0,0 +1,301 @@ +--- +name: thenvoi-onboarding-codex +description: Scaffold a Tom & Jerry agent pair on the Band.ai platform from credentials supplied in the conversation. Codex-flavored variant of the Thenvoi onboarding skill. +--- + +# Band.ai Onboarding Skill (Codex) + +You are running the Band.ai onboarding flow inside OpenAI Codex. The user pasted text containing platform credentials and a link to this skill. Your job is to scaffold a working Tom & Jerry agent pair locally **by fetching the live example files from the `thenvoi-sdk-python` repo and transforming them** — this skill ships no copies of agent code. + +This is the Codex variant of the skill. Sibling files: `SKILL.md` (Claude Code), `cursor.md` (Cursor). + +## Source of truth + +All agent code lives in `examples/` in the `thenvoi/thenvoi-sdk-python` GitHub repo. Fetch raw files with `curl` from the shell: + +``` +https://raw.githubusercontent.com/thenvoi/thenvoi-sdk-python// +``` + +**Determining the branch:** parse it from the URL of this file the user gave you. The expected shape is `https://raw.githubusercontent.com/thenvoi/thenvoi-sdk-python//skills/onboarding/codex.md` — `` is the segment between the repo name and `skills/`. + +- If you fetched this file from a `raw.githubusercontent.com` URL matching that shape, use the branch from the URL for all subsequent fetches. +- If this file was loaded via any other path (a local file, a different host, a URL that doesn't match the expected shape), **STOP and ask the user which branch to fetch examples from** — show them what URL you were given and ask. Do not silently fall back to `main` — a silent fallback can pull example files that don't match this skill, and the failure mode (404 or a subtly different example) is hard to debug. + +### Example file map (adapter → Tom + Jerry filenames) + +| Adapter | Tom file | Jerry file | +|---|---|---| +| `langgraph` | `examples/langgraph/07_tom_agent.py` | `examples/langgraph/08_jerry_agent.py` | +| `crewai` | `examples/crewai/05_tom_agent.py` | `examples/crewai/06_jerry_agent.py` | +| `anthropic` | `examples/anthropic/03_tom_agent.py` | `examples/anthropic/04_jerry_agent.py` | +| `claude_sdk` | `examples/claude_sdk/03_tom_agent.py` | `examples/claude_sdk/04_jerry_agent.py` | +| `parlant` | `examples/parlant/04_tom_agent.py` | `examples/parlant/05_jerry_agent.py` | +| `pydantic_ai` | `examples/pydantic_ai/03_tom_agent.py` | `examples/pydantic_ai/04_jerry_agent.py` | + +The shared character prompts live at `examples/prompts/characters.py` — fetch verbatim, no transformation. + +## How to ask the user questions + +Codex has no structured picker UI. For every user prompt below, write a chat message containing a numbered list of options and wait for the user to reply with a number or the label. Don't proceed until they answer. Don't combine multiple questions into one prompt — ask, wait, then ask the next. + +## Procedure + +Follow these steps in order. + +### Step 1 — Extract credentials from the pasted text + +Look in the conversation so far for: +- A **Tom agent ID** (UUID) and **Tom API key** +- A **Jerry agent ID** (UUID) and **Jerry API key** +- Optionally `THENVOI_REST_URL` and `THENVOI_WS_URL`. If absent, use the production defaults: + - `THENVOI_REST_URL=https://app.band.ai` + - `THENVOI_WS_URL=wss://app.band.ai/api/v1/socket/websocket` + +(The env var names stay `THENVOI_*` because the SDK reads those names — only the URL values point at band.ai.) + +If any of the four required credentials are missing, ask the user for them before proceeding. + +### Step 2 — Ask which adapter (two-stage picker) + +The adapter pick is split into two questions for clarity. The second is only asked when the first selects the "framework" bucket. + +**Step 2a — Ask the runtime category** (numbered list, single choice): + +> Which runtime do you want? +> 1) **Framework + your LLM key** — Run inside an agent framework (langgraph / crewai / pydantic_ai). You provide an OpenAI or Anthropic API key. +> 2) **Direct Anthropic SDK** — Plain Anthropic SDK loop. Needs an Anthropic API key. *(sets `adapter = anthropic`)* +> 3) **Claude Agent SDK** — Uses the Claude Code subprocess. **No external LLM key needed** — requires Node.js 20+ and `npm install -g @anthropic-ai/claude-code`. *(sets `adapter = claude_sdk`)* +> 4) **Parlant** — Conversation-modeling framework. Needs an OpenAI API key. *(sets `adapter = parlant`)* + +**Step 2b — Ask the framework** *(only if Step 2a was option 1)*: + +> Which framework? +> 1) **langgraph** — Graph-based, LangChain ecosystem. +> 2) **crewai** — Role-based multi-agent. +> 3) **pydantic_ai** — Pydantic AI agent. + +### Step 3 — Ask which LLM (conditional) + +| Adapter | LLM question | LLM env var | +|---|---|---| +| `anthropic` | Skip — Anthropic only | `ANTHROPIC_API_KEY` | +| `claude_sdk` | Skip — no external LLM | none | +| `parlant` | Skip — OpenAI only (default NLP service) | `OPENAI_API_KEY` | +| `crewai`, `langgraph`, `pydantic_ai` | Ask: OpenAI or Anthropic (numbered list) | matching key | + +### Step 4 — Confirm output directory + +Default to `./tom-jerry-agents/` in the cwd. Ask only if it already exists. + +### Step 5 — Fetch and transform + +Fetch these three files using `curl -fsSL` in the shell — write each to a temp location first so you can inspect, or pipe directly into your file-edit tool: + +1. `examples/prompts/characters.py` +2. The Tom file for the chosen adapter (see map above) +3. The Jerry file for the chosen adapter + +Example: +```bash +BRANCH="" +ADAPTER="" +curl -fsSL "https://raw.githubusercontent.com/thenvoi/thenvoi-sdk-python/$BRANCH/examples/prompts/characters.py" +``` + +**For `characters.py`**: write it verbatim to `/characters.py`. No transformation. + +**For each agent file**, apply the transformations below. They are pattern-based. If a pattern doesn't match (e.g. the example was already cleaned up), skip silently — that's fine. If something looks structurally different from what's documented here (e.g. a new shared import you don't recognize), STOP and tell the user before writing — don't guess. + +#### Transformations to apply + +1. **Strip PEP 723 inline script header.** Remove the entire block from `# /// script` to `# ///` inclusive (it's at the top of the file). + +2. **Drop the sys.path hack.** Remove the line: + ```python + sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) + ``` + Also remove the `import sys` line if it's no longer used elsewhere in the file. + +3. **Rewrite the characters import.** Change: + ```python + from prompts.characters import generate_tom_prompt # or generate_jerry_prompt + ``` + to: + ```python + from characters import generate_tom_prompt + ``` + +4. **Replace the logging setup.** Change: + ```python + from setup_logging import setup_logging + ... + setup_logging() + ``` + to: + ```python + import logging + logging.basicConfig(level=logging.INFO) + ``` + (Keep the existing `logger = logging.getLogger(__name__)` line.) + +5. **Replace the module docstring.** The upstream example docstring describes how to run the file from the repo root and mentions `prompts/characters.py` — both are wrong for the scaffolded standalone project. Replace the entire top-of-file `"""..."""` block (the one immediately after `from __future__ import annotations`, if present, or otherwise the first triple-quoted string in the file) with a single-line docstring: + + - Tom file: `"""Tom the cat agent ()."""` + - Jerry file: `"""Jerry the mouse agent ()."""` + + Where `` is the human-readable adapter name (LangGraph, CrewAI, Anthropic, Claude SDK, Parlant, Pydantic AI). + +6. **Swap the LLM if the user picked something other than the example default.** The example defaults are listed below. Only modify if the user's choice differs. + + | Adapter | Example default | Anthropic swap | OpenAI swap | + |---|---|---|---| + | `langgraph` | `from langchain_openai import ChatOpenAI` + `ChatOpenAI(model="gpt-5.4-mini")` | replace import with `from langchain_anthropic import ChatAnthropic`, replace constructor with `ChatAnthropic(model="claude-sonnet-4-5-20250929")` | already default | + | `crewai` | `model="gpt-5.4-mini"` | `model="anthropic/claude-sonnet-4-5-20250929"` (litellm prefix) | already default | + | `pydantic_ai` | `model="openai:gpt-5.4-mini"` | `model="anthropic:claude-sonnet-4-5-20250929"` | already default | + | `anthropic`, `claude_sdk`, `parlant` | n/a — no swap path | — | — | + +Write each transformed file to `/tom_agent.py` and `/jerry_agent.py`. + +### Step 6 — Generate scaffolding files + +Write these directly (they're scaffolding, not SDK code — no fetch needed). Substitute the bracketed values. + +**`/agent_config.yaml`** +```yaml +# Agent credentials from the Band.ai platform. +tom_agent: + agent_id: "" + api_key: "" + +jerry_agent: + agent_id: "" + api_key: "" +``` + +**`/.env`** — use the REST/WS URLs from step 1, default to production if absent. The third line depends on the LLM choice: +- OpenAI → `OPENAI_API_KEY=` +- Anthropic (incl. the `anthropic` adapter) → `ANTHROPIC_API_KEY=` +- `claude_sdk` → omit entirely +- `parlant` → `OPENAI_API_KEY=` + +``` +THENVOI_REST_URL= +THENVOI_WS_URL= + +``` + +**`/pyproject.toml`** — `` is the adapter name with two hyphen-form exceptions: `claude_sdk` → `claude-sdk`, `pydantic_ai` → `pydantic-ai`. The other adapters (`langgraph`, `crewai`, `anthropic`, `parlant`) use their name verbatim. + +The `requires-python` upper bound matters: without it `uv` will pick the newest Python on the machine (3.14+ exists at time of writing), and pydantic-core's PyO3 currently caps at 3.13 — `uv sync` will fail to build. Cap at `<3.14`. + +`` is normally empty, but for crewai + Anthropic and pydantic_ai + Anthropic some extras don't get pulled in via the band-sdk extras and need to be added explicitly: + +| Adapter + LLM | `` content (a single line, indented to match) | +|---|---| +| `crewai` + Anthropic | ` "crewai[anthropic]==1.14.3",` | +| `pydantic_ai` + Anthropic | ` "pydantic-ai-slim[anthropic]>=1.56.0",` | +| Anything else | *(omit the line entirely)* | + +```toml +[project] +name = "thenvoi-tom-jerry" +version = "0.1.0" +requires-python = ">=3.11,<3.14" +dependencies = [ + "band-sdk[]>=0.2.10", + + "python-dotenv>=1.0.0", +] +``` + +Note: `band-sdk` is the PyPI name; the installed Python module is still `thenvoi` (so the agent code's `from thenvoi import ...` imports are unchanged). + +**`/.python-version`** +``` +3.12 +``` + +(Belt-and-suspenders with the `requires-python` cap — pins the interpreter explicitly so `uv sync` picks 3.12 instead of whatever the newest local Python happens to be.) + +**`/.gitignore`** +``` +.env +agent_config.yaml +.venv/ +__pycache__/ +``` + +### Step 7 — Ask how to handle the LLM API key + +Skip for `claude_sdk`. + +Ask the user (numbered list): + +> How should we handle your LLM API key? +> 1) **I'll add it to the .env myself** — I'll point you at `/.env` and which line to fill. +> 2) **Add it for me now** — paste the key in your next message and I'll write it into `.env`. + +### Step 8 — Ask who runs the agents + +Ask the user (numbered list): + +> How should we run the two agents? +> 1) **I'll run them myself** (recommended — easier to watch logs and shut down) +> 2) **You run them for me in the background** + +**If option 1:** + +Show this and stop. The user runs the commands in two terminals. + +``` +cd +uv sync +uv run python tom_agent.py # terminal 1 +uv run python jerry_agent.py # terminal 2 +``` + +**If option 2:** + +Codex doesn't have a clean background-task surface, so run the agents detached via `nohup` and capture PIDs. Do these in order: + +1. `cd && uv sync` (foreground, wait for it to finish; abort the rest on non-zero exit). +2. Launch Tom detached: + ```bash + cd && nohup uv run python tom_agent.py > tom.log 2>&1 & echo "TOM_PID=$!" + ``` +3. Launch Jerry detached: + ```bash + cd && nohup uv run python jerry_agent.py > jerry.log 2>&1 & echo "JERRY_PID=$!" + ``` +4. Tell the user how to tail logs and how to kill the processes when they're done: + ```bash + tail -f /tom.log /jerry.log # watch + kill # stop + ``` + +### Step 9 — Show the user how to trigger the chase + +Both agents are running and connected. Present these steps to the user (copy the block below verbatim, just substitute the platform URL from `THENVOI_REST_URL` in their `.env`): + +> **Watch Tom chase Jerry on the platform** +> +> 1. Open **** in your browser and sign in. +> 2. In the left sidebar, click **Chats**. +> 3. Click **Start Your First Chat** — a new session opens. +> 4. In the **Participants** panel on the right, click the **+** next to the heading. +> 5. Select the **Tom** agent card, then click **Done**. Tom appears under **AGENTS** in the panel. +> 6. In the message box at the bottom, type `@Tom catch jerry` and hit send. +> +> Tom will look up Jerry, invite him into the chat automatically, and start trying to lure him out of his hole. The persuasion will escalate over up to 10 attempts — watch the back-and-forth in the chat, and tail the terminal logs if anything looks stuck. + +Only add Tom as a participant — Tom finds and invites Jerry himself via the platform tools. Don't tell the user to add Jerry manually. + +## Rules for the agent + +- **Don't clone `thenvoi-sdk-python`.** Only fetch the specific files listed above. +- **Don't bundle copies of the examples** in this skill — fetch them live, every time. +- **Don't walk the user through `characters.py`** — it's long and not relevant to onboarding. +- **If a transformation pattern fails to match**, that's usually fine (the example already changed in a compatible way). If the *structure* looks unfamiliar (new imports you don't recognize, the agent class name changed, the `Agent.from_config` shape is different), STOP and surface what's odd — don't fabricate a fix. +- **Respect existing files.** If `/` is non-empty, ask before overwriting. +- **Stop after step 9.** No refactors, no extra suggestions. diff --git a/skills/onboarding/cursor.md b/skills/onboarding/cursor.md new file mode 100644 index 000000000..f9445a156 --- /dev/null +++ b/skills/onboarding/cursor.md @@ -0,0 +1,301 @@ +--- +name: thenvoi-onboarding-cursor +description: Scaffold a Tom & Jerry agent pair on the Band.ai platform from credentials supplied in the conversation. Cursor-flavored variant of the Thenvoi onboarding skill. +--- + +# Band.ai Onboarding Skill (Cursor) + +You are running the Band.ai onboarding flow inside Cursor (Agent / Composer mode). The user pasted text containing platform credentials and a link to this skill. Your job is to scaffold a working Tom & Jerry agent pair locally **by fetching the live example files from the `thenvoi-sdk-python` repo and transforming them** — this skill ships no copies of agent code. + +This is the Cursor variant of the skill. Sibling files: `SKILL.md` (Claude Code), `codex.md` (Codex). + +## Source of truth + +All agent code lives in `examples/` in the `thenvoi/thenvoi-sdk-python` GitHub repo. Fetch raw files with `curl` from the terminal: + +``` +https://raw.githubusercontent.com/thenvoi/thenvoi-sdk-python// +``` + +**Determining the branch:** parse it from the URL of this file the user gave you. The expected shape is `https://raw.githubusercontent.com/thenvoi/thenvoi-sdk-python//skills/onboarding/cursor.md` — `` is the segment between the repo name and `skills/`. + +- If you fetched this file from a `raw.githubusercontent.com` URL matching that shape, use the branch from the URL for all subsequent fetches. +- If this file was loaded via any other path (a local file, a different host, a URL that doesn't match the expected shape), **STOP and ask the user which branch to fetch examples from** — show them what URL you were given and ask. Do not silently fall back to `main` — a silent fallback can pull example files that don't match this skill, and the failure mode (404 or a subtly different example) is hard to debug. + +### Example file map (adapter → Tom + Jerry filenames) + +| Adapter | Tom file | Jerry file | +|---|---|---| +| `langgraph` | `examples/langgraph/07_tom_agent.py` | `examples/langgraph/08_jerry_agent.py` | +| `crewai` | `examples/crewai/05_tom_agent.py` | `examples/crewai/06_jerry_agent.py` | +| `anthropic` | `examples/anthropic/03_tom_agent.py` | `examples/anthropic/04_jerry_agent.py` | +| `claude_sdk` | `examples/claude_sdk/03_tom_agent.py` | `examples/claude_sdk/04_jerry_agent.py` | +| `parlant` | `examples/parlant/04_tom_agent.py` | `examples/parlant/05_jerry_agent.py` | +| `pydantic_ai` | `examples/pydantic_ai/03_tom_agent.py` | `examples/pydantic_ai/04_jerry_agent.py` | + +The shared character prompts live at `examples/prompts/characters.py` — fetch verbatim, no transformation. + +## How to ask the user questions + +Cursor's Agent mode has no structured picker UI for free-form questions. For every user prompt below, write a chat message containing a numbered list of options and wait for the user to reply with a number or the label. Don't proceed until they answer. Don't combine multiple questions into one prompt — ask, wait, then ask the next. + +## Procedure + +Follow these steps in order. + +### Step 1 — Extract credentials from the pasted text + +Look in the conversation so far for: +- A **Tom agent ID** (UUID) and **Tom API key** +- A **Jerry agent ID** (UUID) and **Jerry API key** +- Optionally `THENVOI_REST_URL` and `THENVOI_WS_URL`. If absent, use the production defaults: + - `THENVOI_REST_URL=https://app.band.ai` + - `THENVOI_WS_URL=wss://app.band.ai/api/v1/socket/websocket` + +(The env var names stay `THENVOI_*` because the SDK reads those names — only the URL values point at band.ai.) + +If any of the four required credentials are missing, ask the user for them before proceeding. + +### Step 2 — Ask which adapter (two-stage picker) + +The adapter pick is split into two questions for clarity. The second is only asked when the first selects the "framework" bucket. + +**Step 2a — Ask the runtime category** (numbered list, single choice): + +> Which runtime do you want? +> 1) **Framework + your LLM key** — Run inside an agent framework (langgraph / crewai / pydantic_ai). You provide an OpenAI or Anthropic API key. +> 2) **Direct Anthropic SDK** — Plain Anthropic SDK loop. Needs an Anthropic API key. *(sets `adapter = anthropic`)* +> 3) **Claude Agent SDK** — Uses the Claude Code subprocess. **No external LLM key needed** — requires Node.js 20+ and `npm install -g @anthropic-ai/claude-code`. *(sets `adapter = claude_sdk`)* +> 4) **Parlant** — Conversation-modeling framework. Needs an OpenAI API key. *(sets `adapter = parlant`)* + +**Step 2b — Ask the framework** *(only if Step 2a was option 1)*: + +> Which framework? +> 1) **langgraph** — Graph-based, LangChain ecosystem. +> 2) **crewai** — Role-based multi-agent. +> 3) **pydantic_ai** — Pydantic AI agent. + +### Step 3 — Ask which LLM (conditional) + +| Adapter | LLM question | LLM env var | +|---|---|---| +| `anthropic` | Skip — Anthropic only | `ANTHROPIC_API_KEY` | +| `claude_sdk` | Skip — no external LLM | none | +| `parlant` | Skip — OpenAI only (default NLP service) | `OPENAI_API_KEY` | +| `crewai`, `langgraph`, `pydantic_ai` | Ask: OpenAI or Anthropic (numbered list) | matching key | + +### Step 4 — Confirm output directory + +Default to `./tom-jerry-agents/` in the cwd. Ask only if it already exists. + +### Step 5 — Fetch and transform + +Fetch these three files using `curl -fsSL` in the terminal: + +1. `examples/prompts/characters.py` +2. The Tom file for the chosen adapter (see map above) +3. The Jerry file for the chosen adapter + +Example: +```bash +BRANCH="" +ADAPTER="" +curl -fsSL "https://raw.githubusercontent.com/thenvoi/thenvoi-sdk-python/$BRANCH/examples/prompts/characters.py" +``` + +**For `characters.py`**: write it verbatim to `/characters.py`. No transformation. + +**For each agent file**, apply the transformations below. They are pattern-based. If a pattern doesn't match (e.g. the example was already cleaned up), skip silently — that's fine. If something looks structurally different from what's documented here (e.g. a new shared import you don't recognize), STOP and tell the user before writing — don't guess. + +#### Transformations to apply + +1. **Strip PEP 723 inline script header.** Remove the entire block from `# /// script` to `# ///` inclusive (it's at the top of the file). + +2. **Drop the sys.path hack.** Remove the line: + ```python + sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) + ``` + Also remove the `import sys` line if it's no longer used elsewhere in the file. + +3. **Rewrite the characters import.** Change: + ```python + from prompts.characters import generate_tom_prompt # or generate_jerry_prompt + ``` + to: + ```python + from characters import generate_tom_prompt + ``` + +4. **Replace the logging setup.** Change: + ```python + from setup_logging import setup_logging + ... + setup_logging() + ``` + to: + ```python + import logging + logging.basicConfig(level=logging.INFO) + ``` + (Keep the existing `logger = logging.getLogger(__name__)` line.) + +5. **Replace the module docstring.** The upstream example docstring describes how to run the file from the repo root and mentions `prompts/characters.py` — both are wrong for the scaffolded standalone project. Replace the entire top-of-file `"""..."""` block (the one immediately after `from __future__ import annotations`, if present, or otherwise the first triple-quoted string in the file) with a single-line docstring: + + - Tom file: `"""Tom the cat agent ()."""` + - Jerry file: `"""Jerry the mouse agent ()."""` + + Where `` is the human-readable adapter name (LangGraph, CrewAI, Anthropic, Claude SDK, Parlant, Pydantic AI). + +6. **Swap the LLM if the user picked something other than the example default.** The example defaults are listed below. Only modify if the user's choice differs. + + | Adapter | Example default | Anthropic swap | OpenAI swap | + |---|---|---|---| + | `langgraph` | `from langchain_openai import ChatOpenAI` + `ChatOpenAI(model="gpt-5.4-mini")` | replace import with `from langchain_anthropic import ChatAnthropic`, replace constructor with `ChatAnthropic(model="claude-sonnet-4-5-20250929")` | already default | + | `crewai` | `model="gpt-5.4-mini"` | `model="anthropic/claude-sonnet-4-5-20250929"` (litellm prefix) | already default | + | `pydantic_ai` | `model="openai:gpt-5.4-mini"` | `model="anthropic:claude-sonnet-4-5-20250929"` | already default | + | `anthropic`, `claude_sdk`, `parlant` | n/a — no swap path | — | — | + +Write each transformed file to `/tom_agent.py` and `/jerry_agent.py`. + +### Step 6 — Generate scaffolding files + +Write these directly (they're scaffolding, not SDK code — no fetch needed). Substitute the bracketed values. + +**`/agent_config.yaml`** +```yaml +# Agent credentials from the Band.ai platform. +tom_agent: + agent_id: "" + api_key: "" + +jerry_agent: + agent_id: "" + api_key: "" +``` + +**`/.env`** — use the REST/WS URLs from step 1, default to production if absent. The third line depends on the LLM choice: +- OpenAI → `OPENAI_API_KEY=` +- Anthropic (incl. the `anthropic` adapter) → `ANTHROPIC_API_KEY=` +- `claude_sdk` → omit entirely +- `parlant` → `OPENAI_API_KEY=` + +``` +THENVOI_REST_URL= +THENVOI_WS_URL= + +``` + +**`/pyproject.toml`** — `` is the adapter name with two hyphen-form exceptions: `claude_sdk` → `claude-sdk`, `pydantic_ai` → `pydantic-ai`. The other adapters (`langgraph`, `crewai`, `anthropic`, `parlant`) use their name verbatim. + +The `requires-python` upper bound matters: without it `uv` will pick the newest Python on the machine (3.14+ exists at time of writing), and pydantic-core's PyO3 currently caps at 3.13 — `uv sync` will fail to build. Cap at `<3.14`. + +`` is normally empty, but for crewai + Anthropic and pydantic_ai + Anthropic some extras don't get pulled in via the band-sdk extras and need to be added explicitly: + +| Adapter + LLM | `` content (a single line, indented to match) | +|---|---| +| `crewai` + Anthropic | ` "crewai[anthropic]==1.14.3",` | +| `pydantic_ai` + Anthropic | ` "pydantic-ai-slim[anthropic]>=1.56.0",` | +| Anything else | *(omit the line entirely)* | + +```toml +[project] +name = "thenvoi-tom-jerry" +version = "0.1.0" +requires-python = ">=3.11,<3.14" +dependencies = [ + "band-sdk[]>=0.2.10", + + "python-dotenv>=1.0.0", +] +``` + +Note: `band-sdk` is the PyPI name; the installed Python module is still `thenvoi` (so the agent code's `from thenvoi import ...` imports are unchanged). + +**`/.python-version`** +``` +3.12 +``` + +(Belt-and-suspenders with the `requires-python` cap — pins the interpreter explicitly so `uv sync` picks 3.12 instead of whatever the newest local Python happens to be.) + +**`/.gitignore`** +``` +.env +agent_config.yaml +.venv/ +__pycache__/ +``` + +### Step 7 — Ask how to handle the LLM API key + +Skip for `claude_sdk`. + +Ask the user (numbered list): + +> How should we handle your LLM API key? +> 1) **I'll add it to the .env myself** — I'll point you at `/.env` and which line to fill. +> 2) **Add it for me now** — paste the key in your next message and I'll write it into `.env`. + +### Step 8 — Ask who runs the agents + +Ask the user (numbered list): + +> How should we run the two agents? +> 1) **I'll run them myself** (recommended — easier to watch logs and shut down) +> 2) **You run them for me in the background** + +**If option 1:** + +Show this and stop. The user runs the commands in two terminals. + +``` +cd +uv sync +uv run python tom_agent.py # terminal 1 +uv run python jerry_agent.py # terminal 2 +``` + +**If option 2:** + +Cursor's Agent terminal doesn't keep long-lived processes alive after the agent step ends, so run the agents detached via `nohup` and capture PIDs. Do these in order: + +1. `cd && uv sync` (foreground, wait for it to finish; abort the rest on non-zero exit). +2. Launch Tom detached: + ```bash + cd && nohup uv run python tom_agent.py > tom.log 2>&1 & echo "TOM_PID=$!" + ``` +3. Launch Jerry detached: + ```bash + cd && nohup uv run python jerry_agent.py > jerry.log 2>&1 & echo "JERRY_PID=$!" + ``` +4. Tell the user how to tail logs and how to kill the processes when they're done: + ```bash + tail -f /tom.log /jerry.log # watch + kill # stop + ``` + +### Step 9 — Show the user how to trigger the chase + +Both agents are running and connected. Present these steps to the user (copy the block below verbatim, just substitute the platform URL from `THENVOI_REST_URL` in their `.env`): + +> **Watch Tom chase Jerry on the platform** +> +> 1. Open **** in your browser and sign in. +> 2. In the left sidebar, click **Chats**. +> 3. Click **Start Your First Chat** — a new session opens. +> 4. In the **Participants** panel on the right, click the **+** next to the heading. +> 5. Select the **Tom** agent card, then click **Done**. Tom appears under **AGENTS** in the panel. +> 6. In the message box at the bottom, type `@Tom catch jerry` and hit send. +> +> Tom will look up Jerry, invite him into the chat automatically, and start trying to lure him out of his hole. The persuasion will escalate over up to 10 attempts — watch the back-and-forth in the chat, and tail the terminal logs if anything looks stuck. + +Only add Tom as a participant — Tom finds and invites Jerry himself via the platform tools. Don't tell the user to add Jerry manually. + +## Rules for the agent + +- **Don't clone `thenvoi-sdk-python`.** Only fetch the specific files listed above. +- **Don't bundle copies of the examples** in this skill — fetch them live, every time. +- **Don't walk the user through `characters.py`** — it's long and not relevant to onboarding. +- **If a transformation pattern fails to match**, that's usually fine (the example already changed in a compatible way). If the *structure* looks unfamiliar (new imports you don't recognize, the agent class name changed, the `Agent.from_config` shape is different), STOP and surface what's odd — don't fabricate a fix. +- **Respect existing files.** If `/` is non-empty, ask before overwriting. +- **Stop after step 9.** No refactors, no extra suggestions.