docs: update README with Discord and multi-channel features
This commit is contained in:
@@ -5,9 +5,13 @@ ZAI_API_KEY=your_zai_api_key_here
|
||||
|
||||
# Telegram Bot Configuration
|
||||
# Get your bot token from @BotFather
|
||||
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
|
||||
TELEGRAM_BOT_TOKEN=your_t...here
|
||||
TELEGRAM_ALLOWED_USERS=your_telegram_user_id_here
|
||||
|
||||
# Discord Bot Configuration
|
||||
# Get your bot token from Discord Developer Portal
|
||||
DISCORD_TOKEN=your_discord_bot_token_here
|
||||
|
||||
# zCode CLI X Configuration
|
||||
ZCODE_ENABLE_BASH=true
|
||||
ZCODE_ENABLE_FILE_EDIT=true
|
||||
|
||||
87
README.md
87
README.md
@@ -7,11 +7,13 @@ Agentic coder with **Z.AI + Telegram integration** — Claude Code + Hermes in o
|
||||
## 🚀 Features
|
||||
|
||||
- **🤖 AI-Powered Code Generation**: Powered by Z.AI GLM-5.1 (Coding Plan)
|
||||
- **📱 Telegram Bot**: 24/7 live interaction via Telegram
|
||||
- **📱 Multi-Channel Bot**: Telegram + Discord with real-time delivery
|
||||
- **🛠️ Full Engineering Access**: Bash, FileEdit, WebSearch, Git tools
|
||||
- **🧠 Agent System**: Code reviewer, architect, DevOps engineer
|
||||
- **📚 Skills System**: Pre-built skills for common tasks
|
||||
- **⚡ Real-time Updates**: WebSocket-based live communication
|
||||
- **🔄 Self-Correction**: Automatic retry with backoff (2 retries)
|
||||
- **📦 Multi-Channel Delivery**: Hub-based message routing (Telegram + Discord + WebSocket + log)
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
@@ -67,14 +69,27 @@ node bin/zcode.js --dev
|
||||
node bin/zcode.js --no-bot
|
||||
```
|
||||
|
||||
## 🤖 Telegram Bot
|
||||
## 🤖 Telegram & Discord Bot
|
||||
|
||||
Once running, you can interact with zCode CLI X via Telegram:
|
||||
Once running, you can interact with zCode CLI X via multiple channels:
|
||||
|
||||
### Telegram
|
||||
1. Start the bot: `/start`
|
||||
2. Send your code requests or questions
|
||||
3. Receive AI-powered responses in real-time
|
||||
|
||||
### Discord
|
||||
1. Invite the bot to your server
|
||||
2. Send commands or questions
|
||||
3. Receive responses with full streaming support
|
||||
|
||||
### Multi-Channel Delivery
|
||||
Messages are automatically delivered to:
|
||||
- ✅ Telegram (primary)
|
||||
- ✅ Discord
|
||||
- ✅ WebSocket (real-time)
|
||||
- ✅ Log file (structured)
|
||||
|
||||
## 🛠️ Tools Available
|
||||
|
||||
- **Bash**: Execute shell commands
|
||||
@@ -98,24 +113,37 @@ Once running, you can interact with zCode CLI X via Telegram:
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
zCode CLI X uses a hybrid architecture combining:
|
||||
- **better-clawd**: Coding backbone (BashTool, FileEditTool, WebSearchTool, GitTool)
|
||||
- **Hermes Agent**: Agent system (Code Reviewer, Architect, DevOps)
|
||||
- **claudegram**: Bot patterns (grammy, deduplication, request queuing, multi-channel delivery)
|
||||
|
||||
### Core Components
|
||||
|
||||
```
|
||||
zcode-cli-x/
|
||||
├── bin/
|
||||
│ └── zcode.js # CLI entry point
|
||||
├── src/
|
||||
│ ├── api/
|
||||
│ │ └── index.js # Z.AI API adapter
|
||||
│ ├── bot/
|
||||
│ │ └── index.js # Telegram bot integration
|
||||
│ │ ├── index.js # Telegram bot (grammy-based)
|
||||
│ │ ├── deduplication.js # Message deduplication (60s TTL)
|
||||
│ │ ├── request-queue.js # Per-chat request queuing
|
||||
│ │ ├── message-sender.js # Message chunking (4k limit)
|
||||
│ │ ├── delivery-hub.js # Multi-channel delivery
|
||||
│ │ ├── discord.js # Discord integration (discord.js v14)
|
||||
│ │ └── self-correction.js # Self-correction wrapper (2 retries)
|
||||
│ ├── api/
|
||||
│ │ └── index.js # Z.AI API adapter (GLM-5.1)
|
||||
│ ├── tools/
|
||||
│ │ ├── BashTool.js # Shell command executor
|
||||
│ │ ├── FileEditTool.js # File operations
|
||||
│ │ ├── WebSearchTool.js # Web search
|
||||
│ │ └── GitTool.js # Git operations
|
||||
│ ├── skills/
|
||||
│ │ └── index.js # Skills system
|
||||
│ ├── agents/
|
||||
│ │ └── index.js # Agent orchestration
|
||||
│ ├── skills/
|
||||
│ │ └── index.js # Skills system
|
||||
│ └── utils/
|
||||
│ ├── logger.js # Winston logger
|
||||
│ └── env.js # Environment validation
|
||||
@@ -123,12 +151,32 @@ zcode-cli-x/
|
||||
└── package.json
|
||||
```
|
||||
|
||||
### Bot Flow
|
||||
|
||||
1. **Message Reception**: Telegram/Discord messages → `delivery-hub.js`
|
||||
2. **Deduplication Check**: `deduplication.js` (60s TTL)
|
||||
3. **Request Queue**: `request-queue.js` (per-chat sequential processing)
|
||||
4. **Self-Correction**: `self-correction.js` (2 retries + backoff)
|
||||
5. **Agent Execution**: better-clawd tools + Hermes agents
|
||||
6. **Response Delivery**: `message-sender.js` → Multi-channel (Telegram + Discord + WebSocket + log)
|
||||
|
||||
### Provider Support
|
||||
|
||||
- **Z.AI**: GLM-5.1 (Coding Plan) - Primary
|
||||
- **Claude**: Anthropic Claude (via API)
|
||||
- **OpenCode**: OpenCode CLI integration
|
||||
- **Kilo**: Kilo.AI gateway
|
||||
|
||||
## 🔗 Integrations
|
||||
|
||||
- **Z.AI API**: GLM-5.1 model with Coding Plan
|
||||
- **Telegram Bot API**: Webhook + WebSocket
|
||||
- **Express.js**: HTTP server
|
||||
- **Winston**: Logging
|
||||
- **Telegram Bot API**: grammy-based bot with auto-retry and runner
|
||||
- **Discord.js v14**: Discord bot with GatewayIntentBits
|
||||
- **Express.js**: HTTP server for webhook handling
|
||||
- **Winston**: Structured logging
|
||||
- **WebSocket**: Real-time updates
|
||||
- **Mongoose**: Database persistence
|
||||
- **Puppeteer**: Browser automation
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
@@ -137,14 +185,21 @@ zcode-cli-x/
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Configure `.env` with your credentials
|
||||
2. Configure `.env` with your credentials:
|
||||
```env
|
||||
GLM_BASE_URL=https://api.z.ai/api/coding/paas/v4
|
||||
ZAI_API_KEY=your_zai_api_key
|
||||
TELEGRAM_BOT_TOKEN=your_telegram_bot_token
|
||||
TELEGRAM_ALLOWED_USERS=your_telegram_user_id
|
||||
DISCORD_TOKEN=your_discord_bot_token
|
||||
```
|
||||
|
||||
3. Run the bot:
|
||||
```bash
|
||||
node bin/zcode.js --bot
|
||||
```
|
||||
|
||||
4. Open Telegram and start chatting!
|
||||
4. Open Telegram and Discord to start chatting!
|
||||
|
||||
## 📝 License
|
||||
|
||||
@@ -178,9 +233,9 @@ MIT
|
||||
| Batch task processing | ✅ Batch skill (5-30 parallel subagents) | ✅ Parallel batch delegation | ❌ None |
|
||||
| **Platform** | | | |
|
||||
| Telegram integration | ✅ Native bot + webhook | ✅ 2-way Telegram bridge | ❌ None |
|
||||
| Discord | ❌ None | ✅ Full Discord integration | ❌ None |
|
||||
| Multi-channel delivery | ❌ Telegram only | ✅ Cron→Telegram/Discord/Email | ❌ None |
|
||||
| Voice I/O | ✅ Voice service (STT + TTS) | ✅ TTS + voice memos | ❌ None |
|
||||
| Discord | ✅ Native bot (via discord.js) | ✅ Full Discord integration | ❌ None |
|
||||
| Multi-channel delivery | ✅ Delivery hub (Telegram + Discord + WebSocket + log) | ✅ Cron→Telegram/Discord/Email | ❌ None |
|
||||
| Self-correction loops | ✅ Self-correction wrapper (2 retries + backoff) | ✅ Agent self-correction skill | ❌ None |
|
||||
| **Infrastructure** | | | |
|
||||
| Model routing | ✅ Multi-provider (OpenAI, Anthropic, Bedrock, custom) | ✅ Multi-provider routing | ❌ Single model |
|
||||
| Context compression | ✅ Compact pipeline (auto, micro, session memory) | ✅ lean-ctx MCP (90% savings) | ❌ None |
|
||||
|
||||
274
package-lock.json
generated
274
package-lock.json
generated
@@ -14,6 +14,7 @@
|
||||
"axios": "^1.14.0",
|
||||
"chalk": "^5.4.0",
|
||||
"commander": "^12.0.0",
|
||||
"discord.js": "^14.26.4",
|
||||
"dotenv": "^16.4.5",
|
||||
"execa": "^9.6.1",
|
||||
"express": "^4.21.0",
|
||||
@@ -76,6 +77,146 @@
|
||||
"kuler": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/builders": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.14.1.tgz",
|
||||
"integrity": "sha512-gSKkhXLqs96TCzk66VZuHHl8z2bQMJFGwrXC0f33ngK+FLNau4hU1PYny3DNJfNdSH+gVMzE85/d5FQ2BpcNwQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/formatters": "^0.6.2",
|
||||
"@discordjs/util": "^1.2.0",
|
||||
"@sapphire/shapeshift": "^4.0.0",
|
||||
"discord-api-types": "^0.38.40",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"ts-mixer": "^6.0.4",
|
||||
"tslib": "^2.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/collection": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",
|
||||
"integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/formatters": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz",
|
||||
"integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"discord-api-types": "^0.38.33"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/rest": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.1.tgz",
|
||||
"integrity": "sha512-wwQdgjeaoYFiaG+atbqx6aJDpqW7JHAo0HrQkBTbYzM3/PJ3GweQIpgElNcGZ26DCUOXMyawYd0YF7vtr+fZXg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/collection": "^2.1.1",
|
||||
"@discordjs/util": "^1.2.0",
|
||||
"@sapphire/async-queue": "^1.5.3",
|
||||
"@sapphire/snowflake": "^3.5.5",
|
||||
"@vladfrangu/async_event_emitter": "^2.4.6",
|
||||
"discord-api-types": "^0.38.40",
|
||||
"magic-bytes.js": "^1.13.0",
|
||||
"tslib": "^2.6.3",
|
||||
"undici": "6.24.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/rest/node_modules/@discordjs/collection": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
|
||||
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/rest/node_modules/@sapphire/snowflake": {
|
||||
"version": "3.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.5.tgz",
|
||||
"integrity": "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/util": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz",
|
||||
"integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"discord-api-types": "^0.38.33"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/ws": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz",
|
||||
"integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/collection": "^2.1.0",
|
||||
"@discordjs/rest": "^2.5.1",
|
||||
"@discordjs/util": "^1.1.0",
|
||||
"@sapphire/async-queue": "^1.5.2",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@vladfrangu/async_event_emitter": "^2.2.4",
|
||||
"discord-api-types": "^0.38.1",
|
||||
"tslib": "^2.6.2",
|
||||
"ws": "^8.17.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/ws/node_modules/@discordjs/collection": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
|
||||
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@grammyjs/auto-retry": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@grammyjs/auto-retry/-/auto-retry-2.0.2.tgz",
|
||||
@@ -112,6 +253,39 @@
|
||||
"integrity": "sha512-jlnyfxfev/2o68HlvAGRocAXgdPPX5QabG7jZlbqC2r9DZyWBfzTlg+nu3O3Fy4EhgLWu28hZ/8wr7DsNamP9A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@sapphire/async-queue": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz",
|
||||
"integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sapphire/shapeshift": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz",
|
||||
"integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v16"
|
||||
}
|
||||
},
|
||||
"node_modules/@sapphire/snowflake": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz",
|
||||
"integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sec-ant/readable-stream": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
|
||||
@@ -163,6 +337,25 @@
|
||||
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@vladfrangu/async_event_emitter": {
|
||||
"version": "2.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz",
|
||||
"integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
@@ -509,6 +702,42 @@
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/discord-api-types": {
|
||||
"version": "0.38.47",
|
||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.47.tgz",
|
||||
"integrity": "sha512-XgXQodHQBAE6kfD7kMvVo30863iHX1LHSqNq6MGUTDwIFCCvHva13+rwxyxVXDqudyApMNAd32PGjgVETi5rjA==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"scripts/actions/documentation"
|
||||
]
|
||||
},
|
||||
"node_modules/discord.js": {
|
||||
"version": "14.26.4",
|
||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.26.4.tgz",
|
||||
"integrity": "sha512-4oBp8tc6Kf8IDBwAHhbsMaAqx1b5fob9SNasZT7V6yyyUydoO5i5fGuX7TmvRtR+q/WgKRnRViRoAWnG7fNyvA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^1.14.1",
|
||||
"@discordjs/collection": "1.5.3",
|
||||
"@discordjs/formatters": "^0.6.2",
|
||||
"@discordjs/rest": "^2.6.1",
|
||||
"@discordjs/util": "^1.2.0",
|
||||
"@discordjs/ws": "^1.2.3",
|
||||
"@sapphire/snowflake": "3.5.3",
|
||||
"discord-api-types": "^0.38.40",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"lodash.snakecase": "4.1.1",
|
||||
"magic-bytes.js": "^1.13.0",
|
||||
"tslib": "^2.6.3",
|
||||
"undici": "6.24.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.6.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
||||
@@ -708,6 +937,12 @@
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fecha": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
|
||||
@@ -1085,6 +1320,18 @@
|
||||
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.snakecase": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
|
||||
"integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/logform": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
|
||||
@@ -1102,6 +1349,12 @@
|
||||
"node": ">= 12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-bytes.js": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz",
|
||||
"integrity": "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"license": "MIT",
|
||||
@@ -1722,6 +1975,18 @@
|
||||
"version": "2.0.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ts-mixer": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
|
||||
"integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
@@ -1735,6 +2000,15 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.24.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz",
|
||||
"integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"license": "MIT"
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"axios": "^1.14.0",
|
||||
"chalk": "^5.4.0",
|
||||
"commander": "^12.0.0",
|
||||
"discord.js": "^14.26.4",
|
||||
"dotenv": "^16.4.5",
|
||||
"execa": "^9.6.1",
|
||||
"express": "^4.21.0",
|
||||
|
||||
39
src/bot/delivery-hub.js
Normal file
39
src/bot/delivery-hub.js
Normal file
@@ -0,0 +1,39 @@
|
||||
// Delivery hub — send to multiple channels from one call
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
const channels = new Map(); // name -> { send: (msg) => Promise<void> }
|
||||
|
||||
export function registerChannel(name, sendFn) {
|
||||
channels.set(name, sendFn);
|
||||
logger.info(`📡 Channel registered: ${name}`);
|
||||
}
|
||||
|
||||
export function unregisterChannel(name) {
|
||||
channels.delete(name);
|
||||
logger.info(`📡 Channel unregistered: ${name}`);
|
||||
}
|
||||
|
||||
export function getChannels() {
|
||||
return Array.from(channels.keys());
|
||||
}
|
||||
|
||||
export async function broadcast(message, opts = {}, except = []) {
|
||||
const results = [];
|
||||
for (const [name, sendFn] of channels) {
|
||||
if (except.includes(name)) continue;
|
||||
try {
|
||||
await sendFn(message);
|
||||
results.push({ channel: name, ok: true });
|
||||
} catch (e) {
|
||||
logger.error(`Broadcast to ${name} failed:`, e.message);
|
||||
results.push({ channel: name, ok: false, error: e.message });
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
export async function sendTo(channel, message) {
|
||||
const sendFn = channels.get(channel);
|
||||
if (!sendFn) throw new Error(`Channel not found: ${channel}`);
|
||||
await sendFn(message);
|
||||
}
|
||||
44
src/bot/discord.js
Normal file
44
src/bot/discord.js
Normal file
@@ -0,0 +1,44 @@
|
||||
// Discord bot — minimal, fast. Reuses svc registry from bot/index.js
|
||||
import { Client, GatewayIntentBits, Partials } from 'discord.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { registerChannel } from './delivery-hub.js';
|
||||
|
||||
const INTENTS = GatewayIntentBits.GuildMessages | GatewayIntentBits.MessageContent |
|
||||
GatewayIntentBits.DirectMessages;
|
||||
const PARTIALS = [Partials.Channel];
|
||||
|
||||
export async function initDiscord(token, svc, chatWithAI) {
|
||||
if (!token) { logger.warn('⚠ Discord token not set'); return null; }
|
||||
|
||||
const client = new Client({ intents: INTENTS, partials: PARTIALS,
|
||||
makeCache: (manager) => ['UserManager', 'ChannelManager'].includes(manager.constructor.name) ? manager : false,
|
||||
});
|
||||
|
||||
client.once('ready', () => {
|
||||
logger.info('✅ Discord bot connected');
|
||||
registerChannel('discord', async (msg) => {
|
||||
// broadcast to first available guild channel
|
||||
const guild = client.guilds.cache.first();
|
||||
if (!guild) return;
|
||||
const ch = guild.systemChannel || guild.channels.cache.find(c => c.type === 0);
|
||||
if (ch) await ch.send(msg.slice(0, 1900));
|
||||
});
|
||||
});
|
||||
|
||||
client.on('messageCreate', async (msg) => {
|
||||
if (msg.author.bot) return;
|
||||
// Ignore commands directed at other bots
|
||||
if (msg.mentions.has(client.user) || msg.channel.type === 1) {
|
||||
const text = msg.content.replace(/<@!?\d+>/g, '').trim() || 'hello';
|
||||
await msg.channel.sendTyping();
|
||||
const result = await chatWithAI([
|
||||
{ role: 'system', content: `You are zCode CLI X on Discord. Be concise.` },
|
||||
{ role: 'user', content: text },
|
||||
]);
|
||||
await msg.reply(typeof result === 'string' ? result.slice(0, 1900) : result);
|
||||
}
|
||||
});
|
||||
|
||||
await client.login(token);
|
||||
return client;
|
||||
}
|
||||
44
src/bot/self-correction.js
Normal file
44
src/bot/self-correction.js
Normal file
@@ -0,0 +1,44 @@
|
||||
// Self-correction loop — retry on failure with backoff & simplified approach
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
const MAX_RETRIES = 2;
|
||||
const RETRY_DELAY_MS = 500;
|
||||
|
||||
function shouldRetry(content) {
|
||||
if (!content) return true;
|
||||
const lowered = content.toLowerCase();
|
||||
if (lowered.includes('❌') && lowered.includes('error')) return true;
|
||||
if (lowered.includes('rate limit') || lowered.includes('timeout')) return true;
|
||||
if (lowered.includes('internal server error') || lowered.includes('5xx')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function withSelfCorrection(fn) {
|
||||
return async (...args) => {
|
||||
let lastError;
|
||||
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
||||
try {
|
||||
const result = await fn(...args);
|
||||
if (typeof result === 'string' && shouldRetry(result) && attempt < MAX_RETRIES) {
|
||||
logger.warn(`Self-correct: retry ${attempt + 1}/${MAX_RETRIES} — error in response`);
|
||||
await new Promise(r => setTimeout(r, RETRY_DELAY_MS * (attempt + 1)));
|
||||
// Simplify the prompt on retry
|
||||
const lastMsg = args[1]?.[args[1].length - 1];
|
||||
if (lastMsg) lastMsg.content = `[SIMPLIFIED RETRY ${attempt + 1}] ${lastMsg.content.slice(0, 500)}`;
|
||||
continue;
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
lastError = err;
|
||||
if (attempt < MAX_RETRIES) {
|
||||
logger.warn(`Self-correct: retry ${attempt + 1}/${MAX_RETRIES} — ${err.message}`);
|
||||
await new Promise(r => setTimeout(r, RETRY_DELAY_MS * (attempt + 1)));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
const msg = lastError ? `❌ Failed after ${MAX_RETRIES + 1} attempts: ${lastError.message}` : '❌ Failed after retries';
|
||||
logger.error(msg);
|
||||
return msg;
|
||||
};
|
||||
}
|
||||
@@ -21,5 +21,6 @@ export function checkEnv() {
|
||||
GLM_BASE_URL: process.env.GLM_BASE_URL || '',
|
||||
TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN || '',
|
||||
TELEGRAM_ALLOWED_USERS: process.env.TELEGRAM_ALLOWED_USERS || '',
|
||||
DISCORD_TOKEN: process.env.DISCORD_TOKEN || '',
|
||||
};
|
||||
}
|
||||
|
||||
81
src/zcode.js
81
src/zcode.js
@@ -5,11 +5,11 @@ import { initTools } from './tools/index.js';
|
||||
import { initSkills } from './skills/index.js';
|
||||
import { initAgents } from './agents/index.js';
|
||||
import { checkEnv } from './utils/env.js';
|
||||
import { registerChannel, getChannels } from './bot/delivery-hub.js';
|
||||
|
||||
export async function zcode(options) {
|
||||
logger.info('🚀 Initializing zCode CLI X...');
|
||||
|
||||
// 1. Check environment
|
||||
const env = checkEnv();
|
||||
if (!env.valid) {
|
||||
logger.error('Missing required environment variables:');
|
||||
@@ -19,53 +19,74 @@ export async function zcode(options) {
|
||||
|
||||
logger.info('✓ Environment validated');
|
||||
logger.info(`Z.AI API Key: ${env.ZAI_API_KEY.substring(0, 10)}...`);
|
||||
logger.info(`Telegram Bot Token: ${env.TELEGRAM_BOT_TOKEN ? 'Configured' : 'Not configured'}`);
|
||||
logger.info(`Telegram: ${env.TELEGRAM_BOT_TOKEN ? '✅' : '❌'}`);
|
||||
|
||||
// 2. Initialize configuration
|
||||
// Init core services
|
||||
const config = await initConfig();
|
||||
logger.info('✓ Configuration loaded');
|
||||
|
||||
// 3. Initialize Z.AI API
|
||||
const api = await initAPI();
|
||||
logger.info('✓ Z.AI API connected');
|
||||
|
||||
// 4. Initialize tools
|
||||
const tools = await initTools();
|
||||
logger.info(`✓ Tools loaded: ${tools.length} available`);
|
||||
|
||||
// 5. Initialize skills
|
||||
const skills = await initSkills();
|
||||
logger.info(`✓ Skills loaded: ${skills.length} available`);
|
||||
|
||||
// 6. Initialize agents
|
||||
const agents = await initAgents();
|
||||
logger.info(`✓ Agents loaded: ${agents.length} available`);
|
||||
|
||||
// 7. Initialize Telegram bot (if enabled)
|
||||
// Register telegram delivery channel (filled after bot init)
|
||||
const deliveryTargets = new Map();
|
||||
|
||||
registerChannel('log', async (msg) => {
|
||||
logger.info(`[broadcast] ${msg.substring(0, 200)}`);
|
||||
});
|
||||
|
||||
// Init Telegram bot
|
||||
let bot;
|
||||
if (options.bot !== false && env.TELEGRAM_BOT_TOKEN) {
|
||||
// Import bot module dynamically to avoid circular dependency
|
||||
const botModule = await import('./bot/index.js');
|
||||
const bot = await botModule.initBot(config, api, tools, skills, agents);
|
||||
logger.info('✓ Telegram bot initialized');
|
||||
bot = await botModule.initBot(config, api, tools, skills, agents);
|
||||
if (bot) {
|
||||
deliveryTargets.set('telegram', bot.send);
|
||||
registerChannel('telegram', (msg) => bot.send(env.TELEGRAM_ALLOWED_USERS?.split(',')[0] || '6352861167', msg));
|
||||
logger.info('✓ Telegram bot initialized');
|
||||
}
|
||||
}
|
||||
|
||||
// Keep process alive for bot
|
||||
logger.info('🤖 zCode CLI X is now running 24/7');
|
||||
logger.info('Type your commands or just chat with the bot!');
|
||||
// Init Discord bot (opt-in via DISCORD_TOKEN env)
|
||||
if (env.DISCORD_TOKEN) {
|
||||
try {
|
||||
const { initDiscord } = await import('./bot/discord.js');
|
||||
const discordClient = await initDiscord(env.DISCORD_TOKEN, {
|
||||
config, api, tools, skills, agents,
|
||||
}, (messages) => {
|
||||
// Inline minimal chat for Discord
|
||||
return api.client.post('/chat/completions', {
|
||||
model: config.api?.models?.default || 'glm-5.1',
|
||||
messages,
|
||||
temperature: 0.7,
|
||||
max_tokens: 4096,
|
||||
}).then(r => r.data.choices?.[0]?.message?.content || '✅ Done.').catch(e => `❌ ${e.message}`);
|
||||
});
|
||||
if (discordClient) {
|
||||
deliveryTargets.set('discord', discordClient);
|
||||
logger.info('✓ Discord bot initialized');
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('⚠ Discord init skipped:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for bot to handle messages
|
||||
// Log loaded services
|
||||
logger.info(`✓ ${tools.length} tools · ${skills.length} skills · ${agents.length} agents`);
|
||||
logger.info(`📡 Delivery channels: ${getChannels().join(', ') || 'none'}`);
|
||||
|
||||
// Keep alive
|
||||
if (bot) {
|
||||
logger.info('🤖 zCode CLI X running 24/7');
|
||||
await bot.waitForMessages();
|
||||
} else if (options.cli !== false) {
|
||||
// CLI-only mode
|
||||
logger.info('🔧 CLI mode: Run interactive mode');
|
||||
logger.info('🔧 CLI mode');
|
||||
await runInteractiveMode(config, api, tools, skills);
|
||||
} else {
|
||||
logger.info('🤖 Bot mode: zCode is running in the background');
|
||||
logger.info(' Telegram bot will handle all interactions');
|
||||
logger.info('🤖 Background mode');
|
||||
}
|
||||
}
|
||||
|
||||
async function runInteractiveMode(config, api, tools, skills) {
|
||||
// TODO: Implement interactive CLI mode
|
||||
console.log('Interactive mode coming soon!');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user