imtoagent 0.3.5 โ 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +203 -56
- package/bin/imtoagent-real +138 -94
- package/bin/imtoagent.cjs +12 -4
- package/install.sh +313 -0
- package/modules/agent/codex-adapter.ts +1 -1
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -4,12 +4,50 @@ Connect Feishu, Telegram, personal WeChat, and WeCom to AI coding agents like Cl
|
|
|
4
4
|
|
|
5
5
|
One gateway, multiple IMs, multiple agents, unified port proxy.
|
|
6
6
|
|
|
7
|
+
## ๐ Quick Start (5 Minutes)
|
|
8
|
+
|
|
9
|
+
### Step 1: Install (One Command)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
curl -fsSL https://raw.githubusercontent.com/imtoagent/imtoagent/main/install.sh | bash
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This script detects your environment, installs bun if needed, installs imtoagent, and guides you through setup.
|
|
16
|
+
|
|
17
|
+
### Step 2: Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
imtoagent setup
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The interactive wizard guides you through:
|
|
24
|
+
1. Select IM platform (Feishu/Telegram/WeChat/WeCom)
|
|
25
|
+
2. Enter Bot credentials
|
|
26
|
+
3. Choose Agent backend (Claude Code/Codex/OpenCode)
|
|
27
|
+
4. Configure model providers (API keys)
|
|
28
|
+
5. Generate soul files for personality injection
|
|
29
|
+
|
|
30
|
+
### Step 2: Verify
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
imtoagent status # check it's running
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
That's it! Send `/help` to your Bot in the IM to see available commands.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
**Alternative install methods:** See [Installation Methods](#installation-methods) below for npm global install or source install.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
7
44
|
## Architecture
|
|
8
45
|
|
|
9
46
|
```
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
47
|
+
IM Platform (Feishu/Telegram/WeChat/WeCom)
|
|
48
|
+
โ IM Registry Factory โ Bot Instance
|
|
49
|
+
โ AgentRuntime SDK โ Agent Adapter
|
|
50
|
+
โ Unified Proxy (:18899) โ Upstream Models
|
|
13
51
|
```
|
|
14
52
|
|
|
15
53
|
### Supported IM Adapters
|
|
@@ -43,63 +81,115 @@ One gateway, multiple IMs, multiple agents, unified port proxy.
|
|
|
43
81
|
| Codex | `npm install -g @openai/codex` |
|
|
44
82
|
| OpenCode | `npm install -g opencode` |
|
|
45
83
|
|
|
46
|
-
|
|
84
|
+
## Installation Methods
|
|
47
85
|
|
|
48
|
-
|
|
86
|
+
### Method 1: One-Click Install (Recommended)
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
curl -fsSL https://raw.githubusercontent.com/imtoagent/imtoagent/main/install.sh | bash
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This script does everything automatically:
|
|
93
|
+
- Detects your OS and environment
|
|
94
|
+
- Installs bun if missing
|
|
95
|
+
- Installs or upgrades imtoagent
|
|
96
|
+
- Runs the setup wizard if no configuration exists
|
|
97
|
+
- Starts the gateway and verifies it's running
|
|
98
|
+
|
|
99
|
+
**Flags:**
|
|
100
|
+
- `--non-interactive` โ Skip all prompts, auto-install and auto-start
|
|
101
|
+
- `--skip-bun` โ Skip bun installation check
|
|
102
|
+
- `--skip-start` โ Don't start the gateway after install
|
|
103
|
+
|
|
104
|
+
### Method 2: npm Global Install
|
|
49
105
|
|
|
50
106
|
```bash
|
|
51
107
|
npm install -g imtoagent
|
|
52
108
|
```
|
|
53
109
|
|
|
54
|
-
|
|
110
|
+
This is the simplest approach. After installation, the post-install script checks if you need initial configuration.
|
|
55
111
|
|
|
56
|
-
|
|
112
|
+
### Method 3: Source Install
|
|
57
113
|
|
|
58
114
|
```bash
|
|
59
|
-
git clone https://github.com/
|
|
115
|
+
git clone https://github.com/imtoagent/imtoagent.git
|
|
60
116
|
cd imtoagent
|
|
61
117
|
bun install
|
|
62
118
|
bun run bin/imtoagent setup
|
|
63
119
|
```
|
|
64
120
|
|
|
65
|
-
|
|
121
|
+
Use this for development or if you want to modify the source code.
|
|
122
|
+
|
|
123
|
+
### Prerequisites
|
|
124
|
+
|
|
125
|
+
- **Bun** runtime (โฅ1.0.0): `brew install oven-sh/bun/bun`
|
|
126
|
+
- **macOS or Linux**
|
|
127
|
+
- **At least one Agent backend** installed (see below)
|
|
128
|
+
|
|
129
|
+
### Agent Backend Installation
|
|
130
|
+
|
|
131
|
+
| Backend | Install Command |
|
|
132
|
+
|---------|----------------|
|
|
133
|
+
| Claude Code | `npm install -g @anthropic-ai/claude-agent-sdk` |
|
|
134
|
+
| Codex | `npm install -g @openai/codex` |
|
|
135
|
+
| OpenCode | `npm install -g opencode` |
|
|
136
|
+
|
|
137
|
+
You can install backends before or after installing imtoagent.
|
|
138
|
+
|
|
139
|
+
## Configuration
|
|
140
|
+
|
|
141
|
+
### First-Time Setup
|
|
66
142
|
|
|
67
143
|
```bash
|
|
68
144
|
imtoagent setup
|
|
69
145
|
```
|
|
70
146
|
|
|
71
|
-
The interactive
|
|
147
|
+
The interactive wizard will guide you through:
|
|
72
148
|
|
|
73
|
-
1. **Configure Bot** โ Select IM platform + Agent backend
|
|
149
|
+
1. **Configure Bot** โ Select IM platform + Agent backend combination
|
|
74
150
|
2. **Configure Model Providers** โ Add API credentials (DeepSeek, Dashscope, etc.)
|
|
75
|
-
3. **Generate Soul Files** โ Create rules.md
|
|
151
|
+
3. **Generate Soul Files** โ Create personality files (rules.md, identity.md, etc.)
|
|
76
152
|
4. **Write Config Files** โ Auto-generate `~/.imtoagent/config.json`
|
|
77
153
|
|
|
78
|
-
|
|
154
|
+
### Platform-Specific Requirements
|
|
79
155
|
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
- Feishu app
|
|
156
|
+
#### Feishu
|
|
157
|
+
- **App ID** (`cli_...`) and **App Secret**
|
|
158
|
+
- Enable in Feishu app console: Bot, Event Subscription, Message Send/Receive permissions
|
|
83
159
|
|
|
84
|
-
#### Telegram
|
|
85
|
-
|
|
86
|
-
-
|
|
87
|
-
- Optional: Proxy address (e.g., `http://127.0.0.1:7890`)
|
|
160
|
+
#### Telegram
|
|
161
|
+
- **Bot Token** from @BotFather
|
|
162
|
+
- Optional: HTTP proxy (e.g., `http://127.0.0.1:7890`)
|
|
88
163
|
|
|
89
164
|
#### Personal WeChat
|
|
165
|
+
- QR code automatically appears on first `imtoagent start`
|
|
166
|
+
- Scan with WeChat on your phone to complete binding
|
|
90
167
|
|
|
91
|
-
|
|
92
|
-
-
|
|
168
|
+
#### WeCom (Enterprise WeChat)
|
|
169
|
+
- Webhook callback URL configuration
|
|
170
|
+
- REST API credentials
|
|
93
171
|
|
|
94
|
-
|
|
172
|
+
## Running the Gateway
|
|
95
173
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
174
|
+
### Basic Commands
|
|
175
|
+
|
|
176
|
+
| Command | Description |
|
|
177
|
+
|---------|-------------|
|
|
178
|
+
| `imtoagent start` | Start gateway in background |
|
|
179
|
+
| `imtoagent stop` | Stop the gateway |
|
|
180
|
+
| `imtoagent status` | Check running status |
|
|
181
|
+
| `imtoagent restore` | Hot reload recovery |
|
|
182
|
+
| `imtoagent daemon` | Foreground daemon mode (crash auto-restart) |
|
|
183
|
+
|
|
184
|
+
### Running Modes
|
|
101
185
|
|
|
102
|
-
|
|
186
|
+
- **`start`** โ Background mode, terminal returns immediately
|
|
187
|
+
- **`run`** โ Foreground mode, real-time logs, Ctrl+C to stop
|
|
188
|
+
- **`daemon`** โ Foreground with auto-restart on crash
|
|
189
|
+
|
|
190
|
+
### Auto-Start on Boot
|
|
191
|
+
|
|
192
|
+
#### macOS (launchd)
|
|
103
193
|
|
|
104
194
|
```bash
|
|
105
195
|
# Create launchd configuration
|
|
@@ -134,36 +224,54 @@ cat > ~/Library/LaunchAgents/com.imtoagent.plist << 'EOF'
|
|
|
134
224
|
</plist>
|
|
135
225
|
EOF
|
|
136
226
|
|
|
137
|
-
# Load
|
|
227
|
+
# Load the service
|
|
138
228
|
launchctl load ~/Library/LaunchAgents/com.imtoagent.plist
|
|
139
229
|
```
|
|
140
230
|
|
|
141
|
-
|
|
231
|
+
#### Linux (systemd)
|
|
142
232
|
|
|
143
|
-
|
|
144
|
-
|------|------|
|
|
145
|
-
| `imtoagent setup` | Interactive setup wizard |
|
|
146
|
-
| `imtoagent start` | Start gateway in background |
|
|
147
|
-
| `imtoagent stop` | Stop the gateway |
|
|
148
|
-
| `imtoagent status` | Check running status |
|
|
149
|
-
| `imtoagent restore` | Hot reload recovery |
|
|
150
|
-
| `imtoagent daemon` | Foreground daemon mode (suitable for launchd/systemd) |
|
|
233
|
+
Create `/etc/systemd/system/imtoagent.service`:
|
|
151
234
|
|
|
152
|
-
|
|
235
|
+
```ini
|
|
236
|
+
[Unit]
|
|
237
|
+
Description=IMtoAgent Gateway
|
|
238
|
+
After=network.target
|
|
153
239
|
|
|
154
|
-
|
|
240
|
+
[Service]
|
|
241
|
+
Type=simple
|
|
242
|
+
User=youruser
|
|
243
|
+
WorkingDirectory=/home/youruser/.imtoagent
|
|
244
|
+
ExecStart=/usr/bin/bun run /usr/lib/node_modules/imtoagent/index.ts daemon
|
|
245
|
+
Restart=on-failure
|
|
246
|
+
RestartSec=5
|
|
247
|
+
|
|
248
|
+
[Install]
|
|
249
|
+
WantedBy=multi-user.target
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
systemctl daemon-reload
|
|
254
|
+
systemctl enable imtoagent
|
|
255
|
+
systemctl start imtoagent
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Using the Gateway
|
|
259
|
+
|
|
260
|
+
### Built-In Commands
|
|
261
|
+
|
|
262
|
+
Send these to your Bot in the IM chat:
|
|
155
263
|
|
|
156
264
|
| Command | Description |
|
|
157
|
-
|
|
158
|
-
| `/help` |
|
|
265
|
+
|---------|-------------|
|
|
266
|
+
| `/help` | Show available commands |
|
|
159
267
|
| `/status` | Gateway status |
|
|
160
268
|
| `/stats` | Usage statistics |
|
|
161
|
-
| `/model` | Switch model |
|
|
162
|
-
| `/providers` | View providers |
|
|
163
|
-
| `/memory` | View memory |
|
|
269
|
+
| `/model` | Switch AI model |
|
|
270
|
+
| `/providers` | View model providers |
|
|
271
|
+
| `/memory` | View memory status |
|
|
164
272
|
| `/soul` | Soul management |
|
|
165
|
-
| `/reload` | Reload |
|
|
166
|
-
| `/clear` | Clear session |
|
|
273
|
+
| `/reload` | Reload configuration |
|
|
274
|
+
| `/clear` | Clear conversation session |
|
|
167
275
|
| `/mode` | Switch mode (permission/auto/plan) |
|
|
168
276
|
| `/dir` | Switch working directory |
|
|
169
277
|
|
|
@@ -206,27 +314,66 @@ imtoagent/
|
|
|
206
314
|
|
|
207
315
|
## Data Directory
|
|
208
316
|
|
|
209
|
-
All runtime data is stored
|
|
317
|
+
All runtime data is stored in `~/.imtoagent/`:
|
|
210
318
|
|
|
211
319
|
```
|
|
212
320
|
~/.imtoagent/
|
|
213
321
|
โโโ config.json # Main config (Bot + providers + system)
|
|
214
|
-
โโโ providers.json # Model provider
|
|
215
|
-
โโโ opencode.json # OpenCode config
|
|
216
|
-
โโโ sessions/ #
|
|
322
|
+
โโโ providers.json # Model provider configurations
|
|
323
|
+
โโโ opencode.json # OpenCode-specific config
|
|
324
|
+
โโโ sessions/ # Conversation persistence
|
|
217
325
|
โโโ logs/ # Runtime logs
|
|
218
|
-
โโโ soul/ #
|
|
326
|
+
โโโ soul/ # Bot personality files
|
|
219
327
|
โโโ ClaudeBot/
|
|
220
328
|
โโโ CodexBot/
|
|
221
|
-
โโโ ...
|
|
329
|
+
โโโ ... # One directory per Bot
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Troubleshooting
|
|
333
|
+
|
|
334
|
+
### Common Issues
|
|
335
|
+
|
|
336
|
+
**Gateway won't start**
|
|
337
|
+
- Check `imtoagent status` for details
|
|
338
|
+
- Verify config with `cat ~/.imtoagent/config.json`
|
|
339
|
+
- Check logs: `cat ~/.imtoagent/logs/*.log`
|
|
340
|
+
|
|
341
|
+
**Setup wizard stuck**
|
|
342
|
+
- Ensure terminal supports interactive input
|
|
343
|
+
- Try running in a standard terminal (not IDE integrated)
|
|
344
|
+
|
|
345
|
+
**Bot not responding in IM**
|
|
346
|
+
- Verify credentials in config.json
|
|
347
|
+
- Check IM platform permissions (Feishu events, Telegram webhook, etc.)
|
|
348
|
+
- Ensure the gateway is running: `imtoagent status`
|
|
349
|
+
|
|
350
|
+
**Port 18899 already in use**
|
|
351
|
+
- Another service is using the proxy port
|
|
352
|
+
- Kill the existing process or change port in config
|
|
353
|
+
|
|
354
|
+
### Getting Help
|
|
355
|
+
|
|
356
|
+
- Check logs: `~/.imtoagent/logs/`
|
|
357
|
+
- Run `imtoagent status` for runtime information
|
|
358
|
+
- Open an issue on GitHub with logs attached
|
|
222
359
|
```
|
|
223
360
|
|
|
224
361
|
## Development
|
|
225
362
|
|
|
226
363
|
```bash
|
|
364
|
+
# Clone and setup development environment
|
|
365
|
+
git clone https://github.com/imtoagent/imtoagent.git
|
|
366
|
+
cd imtoagent
|
|
227
367
|
bun install
|
|
228
|
-
|
|
229
|
-
|
|
368
|
+
|
|
369
|
+
# Run directly
|
|
370
|
+
bun run index.ts
|
|
371
|
+
|
|
372
|
+
# Run setup wizard
|
|
373
|
+
bun run bin/imtoagent setup
|
|
374
|
+
|
|
375
|
+
# Run CLI from source
|
|
376
|
+
bun run bin/imtoagent status
|
|
230
377
|
```
|
|
231
378
|
|
|
232
379
|
## License
|
package/bin/imtoagent-real
CHANGED
|
@@ -5,14 +5,16 @@
|
|
|
5
5
|
// Available after npm install -g imtoagent:
|
|
6
6
|
// imtoagent setup โ interactive setup wizard
|
|
7
7
|
// imtoagent start โ start gateway in background
|
|
8
|
+
// imtoagent run โ start gateway in foreground
|
|
8
9
|
// imtoagent stop โ stop gateway
|
|
9
10
|
// imtoagent status โ check running status
|
|
10
11
|
// imtoagent restore โ hot reload
|
|
11
|
-
// imtoagent daemon โ foreground daemon (auto-restart
|
|
12
|
+
// imtoagent daemon โ foreground daemon (auto-restart, for launchd/systemd)
|
|
12
13
|
// ================================================================
|
|
13
14
|
|
|
14
15
|
import * as fs from 'fs';
|
|
15
16
|
import * as path from 'path';
|
|
17
|
+
import { spawn, execSync } from 'child_process';
|
|
16
18
|
import { getDataDir } from '../modules/utils/paths';
|
|
17
19
|
|
|
18
20
|
const PID_FILE = '/tmp/imtoagent.pid';
|
|
@@ -29,6 +31,9 @@ switch (command) {
|
|
|
29
31
|
case 'start':
|
|
30
32
|
await cmdStart();
|
|
31
33
|
break;
|
|
34
|
+
case 'run':
|
|
35
|
+
await cmdRun();
|
|
36
|
+
break;
|
|
32
37
|
case 'stop':
|
|
33
38
|
await cmdStop();
|
|
34
39
|
break;
|
|
@@ -42,14 +47,12 @@ switch (command) {
|
|
|
42
47
|
await cmdDaemon();
|
|
43
48
|
break;
|
|
44
49
|
case undefined: {
|
|
45
|
-
// No command โ auto-enter setup if not configured, show help otherwise
|
|
46
50
|
const dataDir = getDataDir();
|
|
47
51
|
const configPath = path.join(dataDir, 'config.json');
|
|
48
52
|
let needsSetup = !fs.existsSync(configPath);
|
|
49
53
|
if (!needsSetup) {
|
|
50
54
|
try {
|
|
51
55
|
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
52
|
-
// If config still has YOUR_ placeholders, setup is incomplete
|
|
53
56
|
needsSetup = /YOUR_[A-Z_]+/.test(raw);
|
|
54
57
|
} catch { needsSetup = true; }
|
|
55
58
|
}
|
|
@@ -81,11 +84,12 @@ imtoagent โ IM โ Agent Unified Gateway
|
|
|
81
84
|
|
|
82
85
|
Usage:
|
|
83
86
|
imtoagent setup Interactive setup wizard
|
|
84
|
-
imtoagent start Start gateway in background
|
|
87
|
+
imtoagent start Start gateway in background (returns immediately)
|
|
88
|
+
imtoagent run Start gateway in foreground (Ctrl+C to stop)
|
|
85
89
|
imtoagent stop Stop gateway
|
|
86
90
|
imtoagent status Check running status
|
|
87
91
|
imtoagent restore Hot reload
|
|
88
|
-
imtoagent daemon Foreground daemon
|
|
92
|
+
imtoagent daemon Foreground daemon with auto-restart (for launchd/systemd)
|
|
89
93
|
|
|
90
94
|
Data directory: ${getDataDir()}
|
|
91
95
|
`);
|
|
@@ -100,7 +104,22 @@ async function cmdSetup() {
|
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
// ================================================================
|
|
103
|
-
//
|
|
107
|
+
// Shared: build gateway launch args
|
|
108
|
+
// ================================================================
|
|
109
|
+
function getGatewayArgs() {
|
|
110
|
+
const pkgDir = path.resolve(import.meta.dirname, '..');
|
|
111
|
+
const indexFile = path.join(pkgDir, 'index.ts');
|
|
112
|
+
return { execPath: process.execPath, args: ['run', indexFile] };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function ensureLogDir(dataDir: string) {
|
|
116
|
+
const logsDir = path.join(dataDir, 'logs');
|
|
117
|
+
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
|
|
118
|
+
return path.join(logsDir, 'imtoagent.log');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ================================================================
|
|
122
|
+
// start โ background mode (spawn detached, log to file, return immediately)
|
|
104
123
|
// ================================================================
|
|
105
124
|
async function cmdStart() {
|
|
106
125
|
// Check if already running
|
|
@@ -112,12 +131,10 @@ async function cmdStart() {
|
|
|
112
131
|
console.error(` Run "imtoagent stop" to stop first`);
|
|
113
132
|
process.exit(1);
|
|
114
133
|
} catch {
|
|
115
|
-
// Stale PID file, clean up
|
|
116
134
|
fs.unlinkSync(PID_FILE);
|
|
117
135
|
}
|
|
118
136
|
}
|
|
119
137
|
|
|
120
|
-
// Check if config exists
|
|
121
138
|
const dataDir = getDataDir();
|
|
122
139
|
const configPath = path.join(dataDir, 'config.json');
|
|
123
140
|
if (!fs.existsSync(configPath)) {
|
|
@@ -125,7 +142,7 @@ async function cmdStart() {
|
|
|
125
142
|
process.exit(1);
|
|
126
143
|
}
|
|
127
144
|
|
|
128
|
-
//
|
|
145
|
+
// Backend check (non-blocking)
|
|
129
146
|
try {
|
|
130
147
|
const { checkBackend } = await import('../modules/utils/backend-check');
|
|
131
148
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
@@ -140,25 +157,89 @@ async function cmdStart() {
|
|
|
140
157
|
}
|
|
141
158
|
if (missingBackends.length > 0) {
|
|
142
159
|
console.error(`\nโ ๏ธ The following backends are configured but not installed, messages will fail after gateway starts:`);
|
|
143
|
-
for (const b of missingBackends) {
|
|
144
|
-
console.error(` โ ${b}`);
|
|
145
|
-
}
|
|
160
|
+
for (const b of missingBackends) console.error(` โ ${b}`);
|
|
146
161
|
console.error(`\nPlease install the missing backends, or run "imtoagent setup" to reconfigure.\n`);
|
|
147
|
-
// Don't force exit, let user start gateway and install backends later
|
|
148
162
|
}
|
|
149
163
|
} catch {
|
|
150
164
|
// Check failure doesn't block startup
|
|
151
165
|
}
|
|
152
166
|
|
|
153
|
-
|
|
167
|
+
const logFile = ensureLogDir(dataDir);
|
|
168
|
+
|
|
169
|
+
console.log('๐ Starting imtoagent gateway (background)...');
|
|
154
170
|
console.log(` Data directory: ${dataDir}`);
|
|
155
|
-
console.log(`
|
|
171
|
+
console.log(` Log file: ${logFile}`);
|
|
156
172
|
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
173
|
+
const { execPath, args } = getGatewayArgs();
|
|
174
|
+
const cmdLine = `"${execPath}" run "${path.resolve(import.meta.dirname, '..', 'index.ts')}"`;
|
|
175
|
+
|
|
176
|
+
// Use a shell to launch the gateway in background โ avoids event-loop blockers
|
|
177
|
+
const shellCmd = `IMTOAGENT_HOME="${dataDir}" ${cmdLine} >> "${logFile}" 2>&1 &
|
|
178
|
+
PID=$!
|
|
179
|
+
echo $PID`;
|
|
160
180
|
|
|
161
|
-
const
|
|
181
|
+
const { execSync } = await import('child_process');
|
|
182
|
+
const pidStr = execSync(shellCmd, {
|
|
183
|
+
cwd: dataDir,
|
|
184
|
+
env: { ...process.env, IMTOAGENT_HOME: dataDir },
|
|
185
|
+
encoding: 'utf-8',
|
|
186
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
187
|
+
}).trim();
|
|
188
|
+
const gatewayPid = parseInt(pidStr.split('\n').pop()!);
|
|
189
|
+
|
|
190
|
+
fs.writeFileSync(PID_FILE, String(gatewayPid));
|
|
191
|
+
console.log(`โ
Gateway started (PID=${gatewayPid})`);
|
|
192
|
+
|
|
193
|
+
// Wait for startup verification
|
|
194
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
195
|
+
try {
|
|
196
|
+
process.kill(gatewayPid, 0);
|
|
197
|
+
console.log('โ
Gateway is running');
|
|
198
|
+
} catch {
|
|
199
|
+
console.error('โ Gateway failed to start, check logs:');
|
|
200
|
+
if (fs.existsSync(logFile)) {
|
|
201
|
+
console.log(fs.readFileSync(logFile, 'utf-8').slice(-2000));
|
|
202
|
+
}
|
|
203
|
+
fs.unlinkSync(PID_FILE);
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Explicitly exit โ Bun may keep event loop alive due to inherited stdio
|
|
208
|
+
process.exit(0);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ================================================================
|
|
212
|
+
// run โ foreground mode (real-time logs, Ctrl+C to stop)
|
|
213
|
+
// ================================================================
|
|
214
|
+
async function cmdRun() {
|
|
215
|
+
const dataDir = getDataDir();
|
|
216
|
+
const configPath = path.join(dataDir, 'config.json');
|
|
217
|
+
if (!fs.existsSync(configPath)) {
|
|
218
|
+
console.error('โ No config file found. Please run "imtoagent setup" first');
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const logFile = ensureLogDir(dataDir);
|
|
223
|
+
|
|
224
|
+
// Warn if already running in background
|
|
225
|
+
if (fs.existsSync(PID_FILE)) {
|
|
226
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim());
|
|
227
|
+
try {
|
|
228
|
+
process.kill(pid, 0);
|
|
229
|
+
console.log(`โ ๏ธ Gateway is already running in background (PID=${pid})`);
|
|
230
|
+
console.log(` Run "imtoagent stop" first, or this may conflict.\n`);
|
|
231
|
+
} catch {
|
|
232
|
+
fs.unlinkSync(PID_FILE);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const { execPath, args } = getGatewayArgs();
|
|
237
|
+
|
|
238
|
+
console.log('๐ Starting imtoagent gateway (foreground mode)...');
|
|
239
|
+
console.log(' Press Ctrl+C to stop');
|
|
240
|
+
console.log('');
|
|
241
|
+
|
|
242
|
+
const child = Bun.spawn([execPath, ...args], {
|
|
162
243
|
cwd: dataDir,
|
|
163
244
|
env: { ...process.env, IMTOAGENT_HOME: dataDir },
|
|
164
245
|
stdout: 'pipe',
|
|
@@ -166,16 +247,10 @@ async function cmdStart() {
|
|
|
166
247
|
});
|
|
167
248
|
|
|
168
249
|
fs.writeFileSync(PID_FILE, String(child.pid));
|
|
169
|
-
console.log(`โ
Gateway started (PID=${child.pid})`);
|
|
170
250
|
|
|
171
|
-
|
|
172
|
-
const logsDir = path.join(dataDir, 'logs');
|
|
173
|
-
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
|
|
174
|
-
const logFile = path.join(logsDir, 'imtoagent.log');
|
|
251
|
+
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
|
|
175
252
|
|
|
176
|
-
|
|
177
|
-
(async () => {
|
|
178
|
-
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
|
|
253
|
+
const pumpOut = (async () => {
|
|
179
254
|
for await (const chunk of child.stdout as any) {
|
|
180
255
|
const line = new TextDecoder().decode(chunk);
|
|
181
256
|
process.stdout.write(line);
|
|
@@ -183,8 +258,7 @@ async function cmdStart() {
|
|
|
183
258
|
}
|
|
184
259
|
})().catch(() => {});
|
|
185
260
|
|
|
186
|
-
(async () => {
|
|
187
|
-
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
|
|
261
|
+
const pumpErr = (async () => {
|
|
188
262
|
for await (const chunk of child.stderr as any) {
|
|
189
263
|
const line = new TextDecoder().decode(chunk);
|
|
190
264
|
process.stderr.write(line);
|
|
@@ -192,18 +266,24 @@ async function cmdStart() {
|
|
|
192
266
|
}
|
|
193
267
|
})().catch(() => {});
|
|
194
268
|
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
process.kill(child.pid,
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
269
|
+
// Ctrl+C โ SIGTERM to child
|
|
270
|
+
const cleanup = () => {
|
|
271
|
+
console.log('\n๐ Stopping gateway...');
|
|
272
|
+
try { process.kill(child.pid, 'SIGTERM'); } catch {}
|
|
273
|
+
};
|
|
274
|
+
process.on('SIGINT', cleanup);
|
|
275
|
+
process.on('SIGTERM', cleanup);
|
|
276
|
+
|
|
277
|
+
const exitCode = await child.exited;
|
|
278
|
+
await Promise.allSettled([pumpOut, pumpErr]);
|
|
279
|
+
logStream.end();
|
|
280
|
+
|
|
281
|
+
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
282
|
+
|
|
283
|
+
if (exitCode === 0) {
|
|
284
|
+
console.log('โ
Gateway exited cleanly');
|
|
285
|
+
} else {
|
|
286
|
+
console.log(`โ ๏ธ Gateway exited with code ${exitCode}`);
|
|
207
287
|
}
|
|
208
288
|
}
|
|
209
289
|
|
|
@@ -222,7 +302,6 @@ async function cmdStop() {
|
|
|
222
302
|
console.log(`โน Stopping gateway (PID=${pid})...`);
|
|
223
303
|
process.kill(pid, 'SIGTERM');
|
|
224
304
|
|
|
225
|
-
// Wait for process to exit
|
|
226
305
|
for (let i = 0; i < 20; i++) {
|
|
227
306
|
try {
|
|
228
307
|
process.kill(pid, 0);
|
|
@@ -232,7 +311,6 @@ async function cmdStop() {
|
|
|
232
311
|
}
|
|
233
312
|
}
|
|
234
313
|
|
|
235
|
-
// Check if still running
|
|
236
314
|
try {
|
|
237
315
|
process.kill(pid, 0);
|
|
238
316
|
console.log('โ ๏ธ Process not responding, force killing...');
|
|
@@ -256,7 +334,6 @@ async function cmdStatus() {
|
|
|
256
334
|
console.log(`\n๐ imtoagent Status`);
|
|
257
335
|
console.log(` Data directory: ${dataDir}`);
|
|
258
336
|
|
|
259
|
-
// Process status
|
|
260
337
|
if (fs.existsSync(PID_FILE)) {
|
|
261
338
|
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim());
|
|
262
339
|
try {
|
|
@@ -269,7 +346,6 @@ async function cmdStatus() {
|
|
|
269
346
|
console.log(` Process: โธ Not running`);
|
|
270
347
|
}
|
|
271
348
|
|
|
272
|
-
// Config file
|
|
273
349
|
const configPath = path.join(dataDir, 'config.json');
|
|
274
350
|
if (fs.existsSync(configPath)) {
|
|
275
351
|
try {
|
|
@@ -286,7 +362,6 @@ async function cmdStatus() {
|
|
|
286
362
|
console.log(` Config: โ Not found (run "imtoagent setup")`);
|
|
287
363
|
}
|
|
288
364
|
|
|
289
|
-
// Log file
|
|
290
365
|
const logFile = path.join(dataDir, 'logs', 'imtoagent.log');
|
|
291
366
|
if (fs.existsSync(logFile)) {
|
|
292
367
|
const stats = fs.statSync(logFile);
|
|
@@ -320,13 +395,7 @@ async function cmdRestore() {
|
|
|
320
395
|
}
|
|
321
396
|
|
|
322
397
|
// ================================================================
|
|
323
|
-
// daemon โ foreground daemon
|
|
324
|
-
// ================================================================
|
|
325
|
-
// Design:
|
|
326
|
-
// - Runs in foreground, managed by launchd / systemd etc.
|
|
327
|
-
// - Auto-restarts on crash (exponential backoff, max 30s)
|
|
328
|
-
// - Graceful shutdown on SIGTERM/SIGINT, no restart
|
|
329
|
-
// - Logs written to ~/.imtoagent/logs/imtoagent.log
|
|
398
|
+
// daemon โ foreground daemon with auto-restart (for launchd/systemd)
|
|
330
399
|
// ================================================================
|
|
331
400
|
async function cmdDaemon(): Promise<void> {
|
|
332
401
|
const dataDir = getDataDir();
|
|
@@ -337,21 +406,15 @@ async function cmdDaemon(): Promise<void> {
|
|
|
337
406
|
process.exit(1);
|
|
338
407
|
}
|
|
339
408
|
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
const logFile = path.join(logsDir, 'imtoagent.log');
|
|
343
|
-
|
|
344
|
-
const pkgDir = path.resolve(import.meta.dirname, '..');
|
|
345
|
-
const indexFile = path.join(pkgDir, 'index.ts');
|
|
409
|
+
const logFile = ensureLogDir(dataDir);
|
|
410
|
+
const { execPath, args } = getGatewayArgs();
|
|
346
411
|
|
|
347
412
|
console.log(`๐ก imtoagent Daemon Mode`);
|
|
348
413
|
console.log(` Data directory: ${dataDir}`);
|
|
349
414
|
console.log(` Log file: ${logFile}`);
|
|
350
415
|
console.log(` Press Ctrl+C to stop\n`);
|
|
351
416
|
|
|
352
|
-
// Graceful shutdown flag
|
|
353
417
|
let shuttingDown = false;
|
|
354
|
-
|
|
355
418
|
const shutdown = () => {
|
|
356
419
|
if (shuttingDown) return;
|
|
357
420
|
shuttingDown = true;
|
|
@@ -362,15 +425,13 @@ async function cmdDaemon(): Promise<void> {
|
|
|
362
425
|
process.on('SIGINT', shutdown);
|
|
363
426
|
|
|
364
427
|
let retryDelay = 0;
|
|
365
|
-
const MAX_RETRY_DELAY = 30_000;
|
|
428
|
+
const MAX_RETRY_DELAY = 30_000;
|
|
366
429
|
|
|
367
430
|
while (!shuttingDown) {
|
|
368
|
-
// No delay on first run, exponential backoff after
|
|
369
431
|
if (retryDelay > 0) {
|
|
370
432
|
console.log(` Waiting ${retryDelay / 1000}s before restart...`);
|
|
371
433
|
await new Promise<void>(resolve => {
|
|
372
434
|
const timer = setTimeout(resolve, retryDelay);
|
|
373
|
-
// Exit immediately if shutdown signal received during wait
|
|
374
435
|
const check = setInterval(() => {
|
|
375
436
|
if (shuttingDown) {
|
|
376
437
|
clearTimeout(timer);
|
|
@@ -382,56 +443,39 @@ async function cmdDaemon(): Promise<void> {
|
|
|
382
443
|
if (shuttingDown) break;
|
|
383
444
|
}
|
|
384
445
|
|
|
385
|
-
|
|
446
|
+
// Open log fd for child stdout/stderr
|
|
447
|
+
const logFd = fs.openSync(logFile, 'a');
|
|
386
448
|
|
|
387
|
-
const child =
|
|
449
|
+
const child = spawn(execPath, args, {
|
|
388
450
|
cwd: dataDir,
|
|
389
451
|
env: { ...process.env, IMTOAGENT_HOME: dataDir },
|
|
390
|
-
|
|
391
|
-
|
|
452
|
+
detached: true,
|
|
453
|
+
stdio: ['ignore', logFd, logFd],
|
|
392
454
|
});
|
|
393
455
|
|
|
394
456
|
const childPid = child.pid;
|
|
395
457
|
fs.writeFileSync(PID_FILE, String(childPid));
|
|
396
458
|
console.log(`[${new Date().toISOString()}] ๐ Starting gateway (PID=${childPid})`);
|
|
397
459
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
logStream.write(line);
|
|
404
|
-
}
|
|
405
|
-
})().catch(() => {});
|
|
406
|
-
|
|
407
|
-
const pumpStderr = (async () => {
|
|
408
|
-
for await (const chunk of child.stderr as any) {
|
|
409
|
-
const line = new TextDecoder().decode(chunk);
|
|
410
|
-
process.stderr.write(line);
|
|
411
|
-
logStream.write(line);
|
|
412
|
-
}
|
|
413
|
-
})().catch(() => {});
|
|
414
|
-
|
|
415
|
-
// Wait for child process to exit
|
|
416
|
-
const exitCode = await child.exited;
|
|
417
|
-
await Promise.allSettled([pumpStdout, pumpStderr]);
|
|
418
|
-
logStream.end();
|
|
460
|
+
let childExitCode: number | null = null;
|
|
461
|
+
await new Promise<void>(resolve => {
|
|
462
|
+
child.on('exit', (code) => { childExitCode = code; resolve(); });
|
|
463
|
+
child.on('error', () => resolve());
|
|
464
|
+
});
|
|
419
465
|
|
|
466
|
+
fs.closeSync(logFd);
|
|
420
467
|
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
421
468
|
|
|
422
469
|
if (shuttingDown) break;
|
|
423
470
|
|
|
424
|
-
|
|
425
|
-
if (exitCode === 0) {
|
|
471
|
+
if (childExitCode === 0) {
|
|
426
472
|
console.log(`[${new Date().toISOString()}] โน Gateway exited cleanly (code=0), not restarting`);
|
|
427
473
|
break;
|
|
428
474
|
}
|
|
429
475
|
|
|
430
|
-
// Crash โ exponential backoff restart
|
|
431
476
|
retryDelay = retryDelay === 0 ? 3_000 : Math.min(retryDelay * 2, MAX_RETRY_DELAY);
|
|
432
|
-
console.log(`[${new Date().toISOString()}] โ ๏ธ Gateway crashed (code=${
|
|
477
|
+
console.log(`[${new Date().toISOString()}] โ ๏ธ Gateway crashed (code=${childExitCode}), restarting in ${retryDelay / 1000}s`);
|
|
433
478
|
}
|
|
434
479
|
|
|
435
480
|
console.log('๐ Daemon stopped');
|
|
436
481
|
}
|
|
437
|
-
|
package/bin/imtoagent.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"use strict";
|
|
4
4
|
var path = require("path");
|
|
5
5
|
var fs = require("fs");
|
|
6
|
-
var
|
|
6
|
+
var spawn = require("child_process").spawn;
|
|
7
7
|
|
|
8
8
|
var candidates = [
|
|
9
9
|
process.env.BUN_BIN,
|
|
@@ -12,7 +12,7 @@ var candidates = [
|
|
|
12
12
|
"/opt/homebrew/bin/bun",
|
|
13
13
|
];
|
|
14
14
|
try {
|
|
15
|
-
var r = spawnSync("which", ["bun"]);
|
|
15
|
+
var r = require("child_process").spawnSync("which", ["bun"]);
|
|
16
16
|
if (r.status === 0) candidates.unshift(r.stdout.toString().trim());
|
|
17
17
|
} catch (e) {}
|
|
18
18
|
|
|
@@ -32,8 +32,16 @@ if (!bunPath) {
|
|
|
32
32
|
|
|
33
33
|
var pkgDir = path.resolve(__dirname, "..");
|
|
34
34
|
var real = path.join(pkgDir, "bin", "imtoagent-real");
|
|
35
|
-
var
|
|
35
|
+
var child = spawn(bunPath, [real].concat(process.argv.slice(2)), {
|
|
36
36
|
stdio: "inherit",
|
|
37
37
|
env: Object.assign({}, process.env),
|
|
38
38
|
});
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
child.on("exit", function (code) {
|
|
41
|
+
process.exit(code || 0);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
child.on("error", function (err) {
|
|
45
|
+
console.error("โ Failed to start imtoagent:", err.message);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|
package/install.sh
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# imtoagent โ One-Click Install Script
|
|
4
|
+
# Usage: curl -fsSL https://raw.githubusercontent.com/imtoagent/imtoagent/main/scripts/install.sh | bash
|
|
5
|
+
#
|
|
6
|
+
# This script detects the environment, installs dependencies, installs/upgrades
|
|
7
|
+
# imtoagent, and optionally runs the interactive setup wizard.
|
|
8
|
+
#
|
|
9
|
+
# Flags:
|
|
10
|
+
# --non-interactive Skip interactive setup (for CI/automated installs)
|
|
11
|
+
# --skip-bun Skip bun installation check
|
|
12
|
+
# --skip-start Don't start the gateway after install
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
# ============================================================
|
|
17
|
+
# Colors & Helpers
|
|
18
|
+
# ============================================================
|
|
19
|
+
RED='\033[0;31m'
|
|
20
|
+
GREEN='\033[0;32m'
|
|
21
|
+
YELLOW='\033[1;33m'
|
|
22
|
+
BLUE='\033[0;34m'
|
|
23
|
+
CYAN='\033[0;36m'
|
|
24
|
+
BOLD='\033[1m'
|
|
25
|
+
NC='\033[0m'
|
|
26
|
+
|
|
27
|
+
info() { echo -e "${BLUE}โน ${NC}$1"; }
|
|
28
|
+
ok() { echo -e "${GREEN}โ
${NC}$1"; }
|
|
29
|
+
warn() { echo -e "${YELLOW}โ ๏ธ ${NC}$1"; }
|
|
30
|
+
error() { echo -e "${RED}โ ${NC}$1"; }
|
|
31
|
+
step() { echo -e "\n${BOLD}${CYAN}โธ $1${NC}"; }
|
|
32
|
+
done_ok() { echo -e " ${GREEN}โ${NC} $1"; }
|
|
33
|
+
|
|
34
|
+
# ============================================================
|
|
35
|
+
# Parse flags
|
|
36
|
+
# ============================================================
|
|
37
|
+
NON_INTERACTIVE=false
|
|
38
|
+
SKIP_BUN=false
|
|
39
|
+
SKIP_START=false
|
|
40
|
+
|
|
41
|
+
for arg in "$@"; do
|
|
42
|
+
case "$arg" in
|
|
43
|
+
--non-interactive) NON_INTERACTIVE=true ;;
|
|
44
|
+
--skip-bun) SKIP_BUN=true ;;
|
|
45
|
+
--skip-start) SKIP_START=true ;;
|
|
46
|
+
esac
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
# ============================================================
|
|
50
|
+
# Banner
|
|
51
|
+
# ============================================================
|
|
52
|
+
echo ""
|
|
53
|
+
echo -e "${BOLD} โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${NC}"
|
|
54
|
+
echo -e "${BOLD} โ ${CYAN}imtoagent${NC} โ IM โ Agent Unified Gateway${BOLD} โ${NC}"
|
|
55
|
+
echo -e "${BOLD} โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${NC}"
|
|
56
|
+
echo ""
|
|
57
|
+
|
|
58
|
+
# ============================================================
|
|
59
|
+
# 1. OS Detection
|
|
60
|
+
# ============================================================
|
|
61
|
+
step "1. Detecting environment"
|
|
62
|
+
|
|
63
|
+
OS=""
|
|
64
|
+
ARCH=""
|
|
65
|
+
|
|
66
|
+
case "$(uname -s)" in
|
|
67
|
+
Darwin*) OS="macos" ;;
|
|
68
|
+
Linux*) OS="linux" ;;
|
|
69
|
+
*) error "Unsupported OS: $(uname -s)"; exit 1 ;;
|
|
70
|
+
esac
|
|
71
|
+
|
|
72
|
+
case "$(uname -m)" in
|
|
73
|
+
arm64|aarch64) ARCH="aarch64" ;;
|
|
74
|
+
x86_64|amd64) ARCH="x64" ;;
|
|
75
|
+
*) ARCH="unknown" ;;
|
|
76
|
+
esac
|
|
77
|
+
|
|
78
|
+
done_ok "OS: $OS ($(uname -m))"
|
|
79
|
+
|
|
80
|
+
# ============================================================
|
|
81
|
+
# 2. Bun Detection & Installation
|
|
82
|
+
# ============================================================
|
|
83
|
+
if [ "$SKIP_BUN" = true ]; then
|
|
84
|
+
info "Skipping bun check (--skip-bun)"
|
|
85
|
+
else
|
|
86
|
+
step "2. Checking bun"
|
|
87
|
+
|
|
88
|
+
BUN_BIN=""
|
|
89
|
+
|
|
90
|
+
# Check environment variable first
|
|
91
|
+
if [ -n "${BUN_BIN:-}" ] && [ -x "$BUN_BIN" ]; then
|
|
92
|
+
BUN_BIN="$BUN_BIN"
|
|
93
|
+
# Check common paths
|
|
94
|
+
elif [ -x "$HOME/.bun/bin/bun" ]; then
|
|
95
|
+
BUN_BIN="$HOME/.bun/bin/bun"
|
|
96
|
+
elif [ -x "/opt/homebrew/bin/bun" ]; then
|
|
97
|
+
BUN_BIN="/opt/homebrew/bin/bun"
|
|
98
|
+
elif [ -x "/usr/local/bin/bun" ]; then
|
|
99
|
+
BUN_BIN="/usr/local/bin/bun"
|
|
100
|
+
elif command -v bun &>/dev/null; then
|
|
101
|
+
BUN_BIN="$(command -v bun)"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
if [ -n "$BUN_BIN" ]; then
|
|
105
|
+
BUN_VER=$("$BUN_BIN" --version 2>/dev/null || echo "unknown")
|
|
106
|
+
done_ok "bun found: $BUN_BIN (v${BUN_VER})"
|
|
107
|
+
else
|
|
108
|
+
warn "bun not found โ installing..."
|
|
109
|
+
echo ""
|
|
110
|
+
|
|
111
|
+
if [ "$OS" = "macos" ] || [ "$OS" = "linux" ]; then
|
|
112
|
+
echo " Downloading and installing bun..."
|
|
113
|
+
curl -fsSL https://bun.sh/install | bash 2>&1 | tail -5
|
|
114
|
+
|
|
115
|
+
# Add bun to PATH for this session
|
|
116
|
+
if [ -f "$HOME/.bun/bin/bun" ]; then
|
|
117
|
+
BUN_BIN="$HOME/.bun/bin/bun"
|
|
118
|
+
export PATH="$HOME/.bun/bin:$PATH"
|
|
119
|
+
elif command -v bun &>/dev/null; then
|
|
120
|
+
BUN_BIN="$(command -v bun)"
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
if [ -n "$BUN_BIN" ] && [ -x "$BUN_BIN" ]; then
|
|
124
|
+
BUN_VER=$("$BUN_BIN" --version 2>/dev/null || echo "unknown")
|
|
125
|
+
done_ok "bun installed: v${BUN_VER}"
|
|
126
|
+
else
|
|
127
|
+
error "bun installation failed. Please install manually:"
|
|
128
|
+
echo " curl -fsSL https://bun.sh/install | bash"
|
|
129
|
+
echo " Then re-run this script."
|
|
130
|
+
exit 1
|
|
131
|
+
fi
|
|
132
|
+
else
|
|
133
|
+
error "Unsupported platform for automatic bun install."
|
|
134
|
+
echo " Install bun manually: https://bun.sh"
|
|
135
|
+
exit 1
|
|
136
|
+
fi
|
|
137
|
+
fi
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
# ============================================================
|
|
141
|
+
# 3. Node.js Check (npm requires node)
|
|
142
|
+
# ============================================================
|
|
143
|
+
step "3. Checking node/npm"
|
|
144
|
+
|
|
145
|
+
if command -v node &>/dev/null; then
|
|
146
|
+
NODE_VER=$(node --version)
|
|
147
|
+
done_ok "node: ${NODE_VER}"
|
|
148
|
+
else
|
|
149
|
+
warn "node not found โ npm install will fail"
|
|
150
|
+
echo ""
|
|
151
|
+
echo " You need Node.js installed. Options:"
|
|
152
|
+
echo " macOS: brew install node"
|
|
153
|
+
echo " Linux: apt install nodejs npm (or use nvm)"
|
|
154
|
+
echo ""
|
|
155
|
+
echo " Or install via nvm:"
|
|
156
|
+
echo " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash"
|
|
157
|
+
echo " source ~/.bashrc # or ~/.zshrc"
|
|
158
|
+
echo " nvm install 20"
|
|
159
|
+
echo ""
|
|
160
|
+
if [ "$NON_INTERACTIVE" = true ]; then
|
|
161
|
+
error "Cannot continue in non-interactive mode without node."
|
|
162
|
+
exit 1
|
|
163
|
+
fi
|
|
164
|
+
read -rp " Continue anyway? [y/N] " CONTINUE
|
|
165
|
+
case "$CONTINUE" in
|
|
166
|
+
[yY]*) info "Continuing..." ;;
|
|
167
|
+
*) error "Aborted."; exit 1 ;;
|
|
168
|
+
esac
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
if command -v npm &>/dev/null; then
|
|
172
|
+
NPM_VER=$(npm --version)
|
|
173
|
+
done_ok "npm: v${NPM_VER}"
|
|
174
|
+
else
|
|
175
|
+
error "npm not found โ required for installation"
|
|
176
|
+
exit 1
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# ============================================================
|
|
180
|
+
# 4. Install / Upgrade imtoagent
|
|
181
|
+
# ============================================================
|
|
182
|
+
step "4. Installing imtoagent"
|
|
183
|
+
|
|
184
|
+
# Check if already installed
|
|
185
|
+
EXISTING_VER=""
|
|
186
|
+
if command -v imtoagent &>/dev/null; then
|
|
187
|
+
EXISTING_VER=$(imtoagent --version 2>/dev/null || echo "unknown")
|
|
188
|
+
warn "Already installed: v${EXISTING_VER}"
|
|
189
|
+
echo ""
|
|
190
|
+
if [ "$NON_INTERACTIVE" = true ]; then
|
|
191
|
+
info "Non-interactive mode โ upgrading"
|
|
192
|
+
else
|
|
193
|
+
read -rp " Upgrade to latest? [Y/n] " UPGRADE
|
|
194
|
+
case "$UPGRADE" in
|
|
195
|
+
[nN]*) info "Skipping upgrade."; exit 0 ;;
|
|
196
|
+
esac
|
|
197
|
+
fi
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
echo " Running: npm install -g imtoagent"
|
|
201
|
+
echo ""
|
|
202
|
+
npm install -g imtoagent 2>&1 | tail -10
|
|
203
|
+
|
|
204
|
+
INSTALLED_VER=$(imtoagent --version 2>/dev/null || echo "unknown")
|
|
205
|
+
done_ok "imtoagent v${INSTALLED_VER} installed globally"
|
|
206
|
+
|
|
207
|
+
# ============================================================
|
|
208
|
+
# 5. Configuration Check & Setup
|
|
209
|
+
# ============================================================
|
|
210
|
+
step "5. Checking configuration"
|
|
211
|
+
|
|
212
|
+
CONFIG_DIR="$HOME/.imtoagent"
|
|
213
|
+
CONFIG_FILE="$CONFIG_DIR/config.json"
|
|
214
|
+
|
|
215
|
+
NEED_SETUP=true
|
|
216
|
+
|
|
217
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
218
|
+
done_ok "Config found: $CONFIG_FILE"
|
|
219
|
+
|
|
220
|
+
# Quick check if config has any bots (grep for botType field)
|
|
221
|
+
if grep -q '"botType"' "$CONFIG_FILE" 2>/dev/null; then
|
|
222
|
+
BOT_COUNT=$(grep -c '"botType"' "$CONFIG_FILE" 2>/dev/null || echo "0")
|
|
223
|
+
done_ok "$BOT_COUNT bot(s) configured โ skipping setup wizard"
|
|
224
|
+
NEED_SETUP=false
|
|
225
|
+
else
|
|
226
|
+
warn "No bots configured in config"
|
|
227
|
+
fi
|
|
228
|
+
else
|
|
229
|
+
warn "No configuration found"
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
if [ "$NEED_SETUP" = true ]; then
|
|
233
|
+
if [ "$NON_INTERACTIVE" = true ]; then
|
|
234
|
+
info "Non-interactive mode โ skipping setup wizard"
|
|
235
|
+
echo ""
|
|
236
|
+
echo " Run 'imtoagent setup' manually to configure."
|
|
237
|
+
else
|
|
238
|
+
echo ""
|
|
239
|
+
echo " Starting interactive setup wizard..."
|
|
240
|
+
echo " You'll need:"
|
|
241
|
+
echo " โข IM platform credentials (Feishu App ID/Secret, Telegram Token, etc.)"
|
|
242
|
+
echo " โข Agent backend (Claude Code / Codex / OpenCode)"
|
|
243
|
+
echo " โข Model provider API keys"
|
|
244
|
+
echo ""
|
|
245
|
+
read -rp " Start setup now? [Y/n] " START_SETUP
|
|
246
|
+
case "$START_SETUP" in
|
|
247
|
+
[nN]*)
|
|
248
|
+
info "Skipping setup. Run 'imtoagent setup' later to configure."
|
|
249
|
+
;;
|
|
250
|
+
*)
|
|
251
|
+
echo ""
|
|
252
|
+
imtoagent setup
|
|
253
|
+
;;
|
|
254
|
+
esac
|
|
255
|
+
fi
|
|
256
|
+
fi
|
|
257
|
+
|
|
258
|
+
# ============================================================
|
|
259
|
+
# 6. Start Gateway (optional)
|
|
260
|
+
# ============================================================
|
|
261
|
+
if [ "$SKIP_START" = true ]; then
|
|
262
|
+
info "Skipping gateway start (--skip-start)"
|
|
263
|
+
else
|
|
264
|
+
step "6. Starting gateway"
|
|
265
|
+
|
|
266
|
+
# Check if already running
|
|
267
|
+
if imtoagent status 2>/dev/null | grep -q "running"; then
|
|
268
|
+
warn "Gateway already running"
|
|
269
|
+
imtoagent status 2>/dev/null || true
|
|
270
|
+
else
|
|
271
|
+
if [ "$NON_INTERACTIVE" = true ]; then
|
|
272
|
+
info "Non-interactive mode โ starting gateway in background"
|
|
273
|
+
imtoagent start
|
|
274
|
+
sleep 3
|
|
275
|
+
imtoagent status 2>/dev/null || true
|
|
276
|
+
else
|
|
277
|
+
read -rp " Start gateway now? [Y/n] " START_GATEWAY
|
|
278
|
+
case "$START_GATEWAY" in
|
|
279
|
+
[nN]*)
|
|
280
|
+
info "Not starting. Run 'imtoagent start' when ready."
|
|
281
|
+
;;
|
|
282
|
+
*)
|
|
283
|
+
imtoagent start
|
|
284
|
+
sleep 3
|
|
285
|
+
imtoagent status 2>/dev/null || true
|
|
286
|
+
;;
|
|
287
|
+
esac
|
|
288
|
+
fi
|
|
289
|
+
fi
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
# ============================================================
|
|
293
|
+
# Summary
|
|
294
|
+
# ============================================================
|
|
295
|
+
echo ""
|
|
296
|
+
echo -e "${BOLD} โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${NC}"
|
|
297
|
+
echo -e "${GREEN}${BOLD} Installation Complete!${NC}"
|
|
298
|
+
echo -e "${BOLD} โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${NC}"
|
|
299
|
+
echo ""
|
|
300
|
+
echo " Version: v${INSTALLED_VER}"
|
|
301
|
+
echo " Config: ${CONFIG_FILE}"
|
|
302
|
+
echo " Logs: ${CONFIG_DIR}/logs/"
|
|
303
|
+
echo ""
|
|
304
|
+
echo " Quick commands:"
|
|
305
|
+
echo " imtoagent status # Check gateway status"
|
|
306
|
+
echo " imtoagent stop # Stop the gateway"
|
|
307
|
+
echo " imtoagent setup # Configure bots"
|
|
308
|
+
echo " imtoagent restore # Hot reload"
|
|
309
|
+
echo ""
|
|
310
|
+
echo " Send ${BOLD}/help${NC} to your Bot in IM to see available commands."
|
|
311
|
+
echo ""
|
|
312
|
+
echo " Docs: https://github.com/imtoagent/imtoagent"
|
|
313
|
+
echo ""
|
|
@@ -88,7 +88,7 @@ async function spawnCodexResume(cwd: string, threadId: string, prompt: string):
|
|
|
88
88
|
try {
|
|
89
89
|
[stdout, stderr] = await Promise.all([
|
|
90
90
|
new Response(child.stdout).text().catch((e: any) => { throw new Error(`stdout read failed: ${e?.message || e}`); }),
|
|
91
|
-
new Response(child.stderr).text().catch((e: any) => { throw new Error(`stderr read failed: ${e?.message || e}
|
|
91
|
+
new Response(child.stderr).text().catch((e: any) => { throw new Error(`stderr read failed: ${e?.message || e}`); }),
|
|
92
92
|
]);
|
|
93
93
|
} catch (ioErr: any) {
|
|
94
94
|
try { child.kill('SIGKILL'); } catch {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imtoagent",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "IM โ Agent ็ปไธ็ฝๅ
ณ โ ้ฃไนฆ/Telegram/ๅพฎไฟก/ไผไธๅพฎไฟกๅฏนๆฅ Claude Code/Codex/OpenCode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"templates/",
|
|
14
14
|
"scripts/",
|
|
15
15
|
"README.md",
|
|
16
|
+
"install.sh",
|
|
16
17
|
"bin/imtoagent.ts",
|
|
17
18
|
"bin/imtoagent.cjs",
|
|
18
19
|
"bin/imtoagent-real"
|
|
@@ -32,7 +33,8 @@
|
|
|
32
33
|
},
|
|
33
34
|
"repository": {
|
|
34
35
|
"type": "git",
|
|
35
|
-
"url": "
|
|
36
|
+
"url": "https://github.com/imtoagent/imtoagent.git",
|
|
37
|
+
"directory": ""
|
|
36
38
|
},
|
|
37
39
|
"keywords": [
|
|
38
40
|
"im",
|
|
@@ -50,7 +52,7 @@
|
|
|
50
52
|
"author": "Keyi",
|
|
51
53
|
"license": "MIT",
|
|
52
54
|
"bugs": {
|
|
53
|
-
"url": "https://github.com/
|
|
55
|
+
"url": "https://github.com/imtoagent/imtoagent/issues"
|
|
54
56
|
},
|
|
55
|
-
"homepage": "https://github.com/
|
|
57
|
+
"homepage": "https://github.com/imtoagent/imtoagent#readme"
|
|
56
58
|
}
|