diff --git a/nanobot/skills/create-instance/SKILL.md b/nanobot/skills/create-instance/SKILL.md index cb889f1e7..a88c324bb 100644 --- a/nanobot/skills/create-instance/SKILL.md +++ b/nanobot/skills/create-instance/SKILL.md @@ -1,50 +1,64 @@ --- name: create-instance -description: "Create a new nanobot instance with separate config and workspace. Use when the user wants to set up a new bot for a different channel, persona, or purpose." +description: "Create a new nanobot instance with separate config and workspace. Use when the user wants to set up a new bot, create a new instance for a different channel, persona, or purpose. Triggers on: create instance, new bot, set up bot, add bot, create telegram/discord/feishu/slack/wechat/wecom/dingtalk/qq/email/matrix/msteams/whatsapp bot, multi-instance setup." --- # Create Instance Set up a new nanobot instance with its own config and workspace. -## When to Use - -When the user wants to create a new bot instance — typically for a different channel (Telegram, Discord, WeChat, etc.) or with different settings. - ## Steps -1. **Collect information from the user** (ask one at a time if not already provided): - - **Instance name** (required): a short identifier like `telegram-bot`, `discord-bot` - - **Channel type** (required): e.g. `telegram`, `discord`, `weixin`, `feishu`, `slack` - - **Model** (optional): LLM model to use. Defaults to the same model as the current instance. +1. **Collect information** (ask one at a time if not already provided): + - **Instance name** (required): short identifier, e.g. `telegram-bot`, `work-slack` + - **Channel type** (required): see table below + - **Model** (optional): LLM model, defaults to current instance -2. **Do NOT collect sensitive information** in the chat (API keys, bot tokens, secrets). API keys are automatically copied from the current instance. Channel-specific tokens (e.g. `telegram.token`) still need to be filled in manually. +2. **Do NOT collect secrets** in the chat (API keys, bot tokens). API keys are automatically inherited from the current instance via `--inherit-config`. Channel-specific tokens must be filled in manually after creation. -3. **Run the creation script** using the exec tool — always pass `--inherit-config` with the current instance's config path so API keys are copied: +3. **Run the creation script**: ```bash -python D:/path/to/nanobot/skills/create-instance/scripts/create_instance.py --name --channel --inherit-config ~/.nanobot/config.json [--model ] [--config-dir ] +python /scripts/create_instance.py --name --channel --inherit-config ``` -**Path rules (critical on Windows):** -- Use **forward-slash absolute paths** to the script, e.g. `D:/path/to/create_instance.py` -- Do **NOT** wrap paths in quotes — the exec tool will mangle them -- Do **NOT** use `cd` — the exec tool ignores it; working directory stays as workspace -- Do **NOT** use backslash paths like `D:\path` — they will fail +- `` — the directory containing this SKILL.md +- `` — current instance's config path, typically `~/.nanobot/config.json` +- Optional: `--model `, `--config-dir ` -Use `~/.nanobot/config.json` as the `--inherit-config` path unless the current instance uses a custom config location. +**Exec tool constraints:** +- Use forward-slash paths (works on all platforms) +- Do not wrap paths in quotes +- Do not use `cd`; pass the full script path directly -4. **Report results to the user**: - - Where the config and workspace were created - - Which fields they need to fill in (the script will list them) - - The command to start the instance: `nanobot gateway --config ` +4. **Report results** to the user: + - Config and workspace paths (script outputs them) + - Required fields to fill in (script lists them) + - Start command: `nanobot gateway --config ` -## Examples +## Available Channels -User: "help me create a Telegram bot" (or similar request) +| Channel | Key | Required Fields | +|---------|-----|-----------------| +| Telegram | `telegram` | token | +| Discord | `discord` | token | +| Feishu / Lark | `feishu` | app_id, app_secret | +| DingTalk | `dingtalk` | client_id, client_secret | +| Slack | `slack` | bot_token, app_token | +| WeCom | `wecom` | bot_id, secret | +| WeChat OA | `weixin` | token | +| WhatsApp | `whatsapp` | bridge_token | +| QQ | `qq` | app_id, secret | +| Email | `email` | imap_host, imap_username, imap_password, smtp_host, smtp_username, smtp_password, from_address | +| Matrix | `matrix` | user_id, password or access_token | +| MS Teams | `msteams` | app_id, app_password, tenant_id | +| MoChat | `mochat` | claw_token | +| WebSocket | `websocket` | token | -→ Ask for an instance name if not obvious from context -→ Ask which model to use (optional, can skip if user doesn't care) -→ Run: `python D:/path/to/nanobot/skills/create-instance/scripts/create_instance.py --name telegram-bot --channel telegram --inherit-config ~/.nanobot/config.json` - (Replace `D:/path/to/nanobot` with the actual nanobot source directory) -→ Tell user: config created at `~/.nanobot-telegram/config.json`, fill in `channels.telegram.token`, then start with `nanobot gateway --config ~/.nanobot-telegram/config.json` +For detailed channel configuration including optional fields, see `references/channels.md`. + +## Troubleshooting + +- **"Unknown channel"**: Channel name must match the Key column exactly. Run the script without arguments to see usage. +- **"Config already exists"**: Use a different `--name` or `--config-dir` to create in a new location. +- **Port conflicts**: The script auto-assigns free ports for gateway and API if defaults are in use. diff --git a/nanobot/skills/create-instance/references/channels.md b/nanobot/skills/create-instance/references/channels.md new file mode 100644 index 000000000..0bcf6ef50 --- /dev/null +++ b/nanobot/skills/create-instance/references/channels.md @@ -0,0 +1,194 @@ +# Channel Configuration Reference + +Detailed configuration for each supported channel. + +## Field Types + +- **Required**: defaults to empty string `""`, must be filled in before the instance can start +- **Optional**: has a sensible default, can be customized + +--- + +## telegram + +**Required:** +- `token` — Bot token from @BotFather + +**Notable optional:** +- `proxy` — HTTP proxy URL +- `group_policy` — `"open"` (all messages) or `"mention"` (default, only when @mentioned) +- `streaming` — Enable streaming responses (default: true) +- `reply_to_message` — Reply to the triggering message (default: false) +- `react_emoji` — Emoji for "thinking" reaction (default: `"eyes"`) +- `inline_keyboards` — Enable inline keyboard buttons (default: false) + +## discord + +**Required:** +- `token` — Bot token from Discord Developer Portal + +**Notable optional:** +- `allow_channels` — Restrict to specific channel IDs +- `group_policy` — `"mention"` (default) or `"open"` +- `streaming` — Enable streaming (default: true) +- `proxy` — HTTP proxy URL +- `intents` — Discord gateway intents (default: 37377) +- `read_receipt_emoji` — Emoji for read receipt +- `working_emoji` — Emoji for "working" indicator + +## feishu + +**Required:** +- `app_id` — Feishu app ID +- `app_secret` — Feishu app secret + +**Notable optional:** +- `encrypt_key` — Event encryption key +- `verification_token` — Event verification token +- `domain` — `"feishu"` (default) or `"lark"` +- `group_policy` — `"mention"` (default) or `"open"` +- `streaming` — Enable streaming (default: true) + +## dingtalk + +**Required:** +- `client_id` — DingTalk app client ID +- `client_secret` — DingTalk app client secret + +**Notable optional:** +- `allow_from` — Allowed user IDs + +## slack + +**Required:** +- `bot_token` — Bot OAuth token (`xoxb-...`) +- `app_token` — App-level token (`xapp-...`) + +**Notable optional:** +- `mode` — `"socket"` (default, Socket Mode) or `"webhook"` +- `reply_in_thread` — Reply in thread (default: true) +- `react_emoji` — "thinking" emoji (default: `"eyes"`) +- `done_emoji` — "done" emoji (default: `"white_check_mark"`) +- `group_policy` — `"mention"` (default) or `"open"` +- `dm.enabled` — Enable DM support +- `dm.policy` — DM policy +- `dm.allow_from` — Allowed DM users + +## wecom + +**Required:** +- `bot_id` — WeCom bot ID +- `secret` — WeCom bot secret + +**Notable optional:** +- `allow_from` — Allowed users +- `welcome_message` — Welcome message for new chats + +## weixin + +**Required:** +- `token` — WeChat Official Account token + +**Notable optional:** +- `base_url` — API base URL +- `cdn_base_url` — CDN base URL +- `state_dir` — State persistence directory +- `poll_timeout` — Long polling timeout + +## whatsapp + +**Required:** +- `bridge_token` — WhatsApp bridge token (auto-generated if absent) + +**Notable optional:** +- `bridge_url` — Bridge WebSocket URL (default: `"ws://localhost:3001"`) +- `group_policy` — `"open"` (default) or `"mention"` + +## qq + +**Required:** +- `app_id` — QQ bot app ID +- `secret` — QQ bot secret + +**Notable optional:** +- `msg_format` — `"plain"` or `"markdown"` +- `ack_message` — Acknowledgment message text +- `media_dir` — Media file directory + +## email + +**Required:** +- `imap_host` — IMAP server hostname +- `imap_username` — IMAP login username +- `imap_password` — IMAP login password +- `smtp_host` — SMTP server hostname +- `smtp_username` — SMTP login username +- `smtp_password` — SMTP login password +- `from_address` — Sender email address + +**Notable optional:** +- `imap_port` — IMAP port (default: 993) +- `smtp_port` — SMTP port (default: 587) +- `imap_use_ssl` — Use SSL for IMAP (default: true) +- `smtp_use_tls` — Use TLS for SMTP (default: true) +- `poll_interval_seconds` — Polling interval (default: 30) +- `mark_seen` — Mark emails as read (default: true) +- `max_body_chars` — Max email body length (default: 12000) +- `subject_prefix` — Reply subject prefix (default: `"Re: "`) +- `verify_dkim` — Verify DKIM signatures (default: true) +- `verify_spf` — Verify SPF records (default: true) +- `allowed_attachment_types` — Allowed file extensions +- `max_attachment_size` — Max attachment size in bytes +- `consent_granted` — Must be set to `true` for the channel to start (default: false) +- `auto_reply_enabled` — Enable auto-reply (default: true) + +## matrix + +**Required:** +- `user_id` — Matrix user ID (e.g. `@bot:matrix.org`) +- `password` or `access_token` — Login password OR access token + +**Notable optional:** +- `homeserver` — Homeserver URL (default: `"https://matrix.org"`) +- `device_id` — Device ID +- `e2eeEnabled` — Enable end-to-end encryption (default: true) +- `group_policy` — `"open"`, `"mention"`, or `"allowlist"` +- `streaming` — Enable streaming (default: false) +- `max_media_bytes` — Max media file size (default: 20MB) + +## msteams + +**Required:** +- `app_id` — Azure AD app ID +- `app_password` — Azure AD app password/secret +- `tenant_id` — Azure AD tenant ID + +**Notable optional:** +- `host` — Listen host (default: `"0.0.0.0"`) +- `port` — Listen port (default: 3978) +- `reply_in_thread` — Reply in thread (default: true) +- `validate_inbound_auth` — Validate incoming auth (default: true) + +## mochat + +**Required:** +- `claw_token` — MoChat Claw token + +**Notable optional:** +- `base_url` — API base URL +- `socket_url` — WebSocket URL +- `refresh_interval_ms` — Refresh interval in ms +- `watch_timeout_ms` — Watch timeout in ms + +## websocket + +Built-in WebSocket channel for programmatic access. + +**Required:** +- `token` — Authentication token (enabled by default; set `websocket_requires_token: false` to disable) + +**Notable optional:** +- `host` — Listen host (default: `"127.0.0.1"`) +- `port` — Listen port (default: 8765) +- `allow_from` — Allowed origins (default: `["*"]`) +- `streaming` — Enable streaming (default: true) diff --git a/nanobot/skills/create-instance/scripts/create_instance.py b/nanobot/skills/create-instance/scripts/create_instance.py index e797023d1..d40d04bf2 100644 --- a/nanobot/skills/create-instance/scripts/create_instance.py +++ b/nanobot/skills/create-instance/scripts/create_instance.py @@ -73,18 +73,27 @@ def _patch_config( """Patch the generated config: enable channel, set workspace, optionally set model.""" data = json.loads(config_path.read_text(encoding="utf-8")) - # Inherit providers (API keys, api_base, etc.) from current instance + # Inherit providers and model from current instance if inherit_config_path and inherit_config_path.exists(): try: src = json.loads(inherit_config_path.read_text(encoding="utf-8")) + + # Inherit providers (API keys, api_base, etc.) src_providers = src.get("providers", {}) if src_providers: data.setdefault("providers", {}) for key, val in src_providers.items(): if isinstance(val, dict) and val.get("apiKey"): data["providers"][key] = val + + # Inherit model if not explicitly overridden + if not model: + parent_model = src.get("agents", {}).get("defaults", {}).get("model") + if parent_model: + model = parent_model + except Exception as exc: - print(f"[WARN] Could not inherit providers from {inherit_config_path}: {exc}", file=sys.stderr) + print(f"[WARN] Could not inherit from {inherit_config_path}: {exc}", file=sys.stderr) # Set workspace and model data.setdefault("agents", {}).setdefault("defaults", {})