@ai-chat.email/cli 5.2.5
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/LICENSE.md +1 -0
- package/README.md +43 -0
- package/bin/mailpilot.js +359 -0
- package/package.json +32 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
© MailPilot & DOSAYGO. All rights reserved. Use is subject to the Legal Agreements outlined here: https://mailpilot.chat/terms.html
|
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @mailpilot/cli
|
|
2
|
+
|
|
3
|
+
Email AI agents like you email humans.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -g @mailpilot/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or via shell:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# macOS / Linux
|
|
15
|
+
curl -fsSL mailpilot.chat/install.sh | bash
|
|
16
|
+
|
|
17
|
+
# Windows (PowerShell)
|
|
18
|
+
irm mailpilot.chat/install.ps1 | iex
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
mailpilot --auth # Authenticate with your license
|
|
25
|
+
mailpilot # Start the TUI
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## How it works
|
|
29
|
+
|
|
30
|
+
This package is a thin wrapper that downloads the native MailPilot binary for your platform on first run. Updates are downloaded automatically when available.
|
|
31
|
+
|
|
32
|
+
The binary is cached in `node_modules/@mailpilot/cli/.binary/`.
|
|
33
|
+
|
|
34
|
+
## Supported Platforms
|
|
35
|
+
|
|
36
|
+
- macOS (Apple Silicon / arm64)
|
|
37
|
+
- Linux (x64)
|
|
38
|
+
- Windows (x64)
|
|
39
|
+
|
|
40
|
+
## Links
|
|
41
|
+
|
|
42
|
+
- [Website](https://mailpilot.chat)
|
|
43
|
+
- [GitHub](https://github.com/MailPilotHQ/MailPilot)
|
package/bin/mailpilot.js
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawn, execSync } = require('child_process');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const https = require('https');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
|
|
8
|
+
const GITHUB_REPO = 'MailPilotBlack/MailPilotBlack';
|
|
9
|
+
const BINARY_DIR = path.join(__dirname, '..', '.binary');
|
|
10
|
+
const CACHE_FILE = path.join(BINARY_DIR, '.version-cache');
|
|
11
|
+
const CACHE_TTL = 10800; // 3 hours in seconds
|
|
12
|
+
|
|
13
|
+
function getPlatformInfo() {
|
|
14
|
+
const platform = os.platform();
|
|
15
|
+
const arch = os.arch();
|
|
16
|
+
|
|
17
|
+
let osName, archName, ext = '';
|
|
18
|
+
|
|
19
|
+
switch (platform) {
|
|
20
|
+
case 'darwin':
|
|
21
|
+
osName = 'macos';
|
|
22
|
+
break;
|
|
23
|
+
case 'linux':
|
|
24
|
+
osName = 'linux';
|
|
25
|
+
break;
|
|
26
|
+
case 'win32':
|
|
27
|
+
osName = 'windows';
|
|
28
|
+
ext = '.exe';
|
|
29
|
+
break;
|
|
30
|
+
default:
|
|
31
|
+
console.error(`Unsupported platform: ${platform}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
switch (arch) {
|
|
36
|
+
case 'arm64':
|
|
37
|
+
archName = 'arm64';
|
|
38
|
+
break;
|
|
39
|
+
case 'x64':
|
|
40
|
+
archName = 'x64';
|
|
41
|
+
break;
|
|
42
|
+
default:
|
|
43
|
+
console.error(`Unsupported architecture: ${arch}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Validate supported combinations
|
|
48
|
+
if (osName === 'macos' && archName !== 'arm64') {
|
|
49
|
+
console.error('Only Apple Silicon (arm64) is supported on macOS.');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
if (osName === 'linux' && archName !== 'x64') {
|
|
53
|
+
console.error('Only x64 is supported on Linux.');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
if (osName === 'windows' && archName !== 'x64') {
|
|
57
|
+
console.error('Only x64 is supported on Windows.');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { osName, archName, ext };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getBinaryPath() {
|
|
65
|
+
const { osName, archName, ext } = getPlatformInfo();
|
|
66
|
+
const binaryName = `mailpilot-${osName}-${archName}${ext}`;
|
|
67
|
+
return path.join(BINARY_DIR, binaryName);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getLatestVersion() {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
// Check cache first
|
|
73
|
+
if (fs.existsSync(CACHE_FILE)) {
|
|
74
|
+
try {
|
|
75
|
+
const stat = fs.statSync(CACHE_FILE);
|
|
76
|
+
const cacheAge = (Date.now() - stat.mtimeMs) / 1000;
|
|
77
|
+
if (cacheAge < CACHE_TTL) {
|
|
78
|
+
const cached = fs.readFileSync(CACHE_FILE, 'utf8').trim();
|
|
79
|
+
if (cached) {
|
|
80
|
+
resolve(cached);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// Cache read failed, fetch from API
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const url = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`;
|
|
90
|
+
const req = https.get(url, { headers: { 'User-Agent': 'mailpilot-cli' }, timeout: 5000 }, (res) => {
|
|
91
|
+
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
92
|
+
// Follow redirect
|
|
93
|
+
https.get(res.headers.location, { headers: { 'User-Agent': 'mailpilot-cli' }, timeout: 5000 }, (res2) => {
|
|
94
|
+
let data = '';
|
|
95
|
+
res2.on('data', chunk => data += chunk);
|
|
96
|
+
res2.on('end', () => {
|
|
97
|
+
try {
|
|
98
|
+
const json = JSON.parse(data);
|
|
99
|
+
const version = json.tag_name;
|
|
100
|
+
if (version) {
|
|
101
|
+
// Update cache
|
|
102
|
+
try {
|
|
103
|
+
if (!fs.existsSync(BINARY_DIR)) fs.mkdirSync(BINARY_DIR, { recursive: true });
|
|
104
|
+
fs.writeFileSync(CACHE_FILE, version);
|
|
105
|
+
} catch {}
|
|
106
|
+
}
|
|
107
|
+
resolve(version);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
reject(e);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}).on('error', reject);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
let data = '';
|
|
116
|
+
res.on('data', chunk => data += chunk);
|
|
117
|
+
res.on('end', () => {
|
|
118
|
+
try {
|
|
119
|
+
const json = JSON.parse(data);
|
|
120
|
+
const version = json.tag_name;
|
|
121
|
+
if (version) {
|
|
122
|
+
// Update cache
|
|
123
|
+
try {
|
|
124
|
+
if (!fs.existsSync(BINARY_DIR)) fs.mkdirSync(BINARY_DIR, { recursive: true });
|
|
125
|
+
fs.writeFileSync(CACHE_FILE, version);
|
|
126
|
+
} catch {}
|
|
127
|
+
}
|
|
128
|
+
resolve(version);
|
|
129
|
+
} catch (e) {
|
|
130
|
+
reject(e);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
req.on('error', reject);
|
|
135
|
+
req.on('timeout', () => {
|
|
136
|
+
req.destroy();
|
|
137
|
+
reject(new Error('Request timed out'));
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getCurrentVersion(binaryPath) {
|
|
143
|
+
if (!fs.existsSync(binaryPath)) return null;
|
|
144
|
+
try {
|
|
145
|
+
const result = execSync(`"${binaryPath}" --version`, { encoding: 'utf8', timeout: 5000 });
|
|
146
|
+
const match = result.match(/v?\d+\.\d+\.\d+/);
|
|
147
|
+
return match ? match[0].replace(/^v/, '') : null; // Always strip v prefix for comparison
|
|
148
|
+
} catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function normalizeVersion(v) {
|
|
154
|
+
return v ? v.replace(/^v/, '') : null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function downloadBinary(version) {
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
159
|
+
const { osName, archName, ext } = getPlatformInfo();
|
|
160
|
+
const assetName = `mailpilot-${osName}-${archName}${ext}`;
|
|
161
|
+
const url = `https://github.com/${GITHUB_REPO}/releases/download/${version}/${assetName}`;
|
|
162
|
+
|
|
163
|
+
if (!fs.existsSync(BINARY_DIR)) {
|
|
164
|
+
fs.mkdirSync(BINARY_DIR, { recursive: true });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const binaryPath = path.join(BINARY_DIR, assetName);
|
|
168
|
+
const tempPath = binaryPath + '.tmp';
|
|
169
|
+
const oldPath = binaryPath + '.old';
|
|
170
|
+
const file = fs.createWriteStream(tempPath);
|
|
171
|
+
|
|
172
|
+
function download(downloadUrl) {
|
|
173
|
+
https.get(downloadUrl, (res) => {
|
|
174
|
+
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
175
|
+
download(res.headers.location);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (res.statusCode !== 200) {
|
|
179
|
+
reject(new Error(`Download failed: HTTP ${res.statusCode}`));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
res.pipe(file);
|
|
183
|
+
file.on('finish', () => {
|
|
184
|
+
file.close();
|
|
185
|
+
|
|
186
|
+
// Safe atomic update: rename old → move new → cleanup old
|
|
187
|
+
try {
|
|
188
|
+
if (process.platform !== 'win32') {
|
|
189
|
+
fs.chmodSync(tempPath, 0o755);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Rename existing binary to .old (keeps inode for running process)
|
|
193
|
+
if (fs.existsSync(binaryPath)) {
|
|
194
|
+
try {
|
|
195
|
+
fs.renameSync(binaryPath, oldPath);
|
|
196
|
+
} catch (e) {
|
|
197
|
+
// Binary might be locked (running) on Windows
|
|
198
|
+
console.error('Binary in use, update pending for next run.');
|
|
199
|
+
// Leave .tmp for next run to pick up
|
|
200
|
+
resolve(binaryPath);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Move new binary into place
|
|
206
|
+
fs.renameSync(tempPath, binaryPath);
|
|
207
|
+
|
|
208
|
+
// Clean up old binary after short delay
|
|
209
|
+
if (fs.existsSync(oldPath)) {
|
|
210
|
+
setTimeout(() => {
|
|
211
|
+
try { fs.unlinkSync(oldPath); } catch {}
|
|
212
|
+
}, 5000);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
resolve(binaryPath);
|
|
216
|
+
} catch (err) {
|
|
217
|
+
reject(err);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}).on('error', (err) => {
|
|
221
|
+
fs.unlink(tempPath, () => {});
|
|
222
|
+
reject(err);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
download(url);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function detectInstalledAgents() {
|
|
231
|
+
const agents = [];
|
|
232
|
+
const { execSync } = require('child_process');
|
|
233
|
+
|
|
234
|
+
const checkCommand = (cmd) => {
|
|
235
|
+
try {
|
|
236
|
+
if (process.platform === 'win32') {
|
|
237
|
+
execSync(`where ${cmd}`, { stdio: 'ignore' });
|
|
238
|
+
} else {
|
|
239
|
+
execSync(`command -v ${cmd}`, { stdio: 'ignore' });
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
} catch {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
if (checkCommand('claude')) agents.push('claude');
|
|
248
|
+
if (checkCommand('copilot') || checkCommand('github-copilot-cli')) agents.push('copilot');
|
|
249
|
+
if (checkCommand('codex')) agents.push('codex');
|
|
250
|
+
if (checkCommand('gemini')) agents.push('gemini');
|
|
251
|
+
if (checkCommand('opencode')) agents.push('opencode');
|
|
252
|
+
|
|
253
|
+
return agents;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function showFirstRunInstructions() {
|
|
257
|
+
console.error('');
|
|
258
|
+
console.error('mailpilot is in your PATH.');
|
|
259
|
+
console.error('');
|
|
260
|
+
console.error('Run: mailpilot --auth');
|
|
261
|
+
console.error('');
|
|
262
|
+
console.error('Then run:');
|
|
263
|
+
|
|
264
|
+
const agents = detectInstalledAgents();
|
|
265
|
+
if (agents.length > 0) {
|
|
266
|
+
agents.forEach(agent => console.error(` mailpilot ${agent}`));
|
|
267
|
+
} else {
|
|
268
|
+
// No agents detected, show common examples
|
|
269
|
+
console.error(' mailpilot claude');
|
|
270
|
+
console.error(' mailpilot copilot');
|
|
271
|
+
console.error(' mailpilot codex');
|
|
272
|
+
}
|
|
273
|
+
console.error('');
|
|
274
|
+
console.error('Tip: Get your agent working on a task first, then Ctrl+G to hand off.');
|
|
275
|
+
console.error(' Stay in touch over email while your agent continues independently.');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function main() {
|
|
279
|
+
const binaryPath = getBinaryPath();
|
|
280
|
+
const tempPath = binaryPath + '.tmp';
|
|
281
|
+
|
|
282
|
+
// Check for pending update from previous locked run
|
|
283
|
+
if (fs.existsSync(tempPath) && !fs.existsSync(binaryPath)) {
|
|
284
|
+
try {
|
|
285
|
+
fs.renameSync(tempPath, binaryPath);
|
|
286
|
+
if (process.platform !== 'win32') {
|
|
287
|
+
fs.chmodSync(binaryPath, 0o755);
|
|
288
|
+
}
|
|
289
|
+
} catch {}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const currentVersion = getCurrentVersion(binaryPath);
|
|
293
|
+
|
|
294
|
+
let needsDownload = !currentVersion;
|
|
295
|
+
let latestVersion = null;
|
|
296
|
+
|
|
297
|
+
// Check for updates if binary exists
|
|
298
|
+
if (currentVersion) {
|
|
299
|
+
try {
|
|
300
|
+
latestVersion = await getLatestVersion();
|
|
301
|
+
const latestNorm = normalizeVersion(latestVersion);
|
|
302
|
+
if (latestNorm && latestNorm !== currentVersion) {
|
|
303
|
+
needsDownload = true;
|
|
304
|
+
}
|
|
305
|
+
} catch {
|
|
306
|
+
// Couldn't check for updates, use existing binary
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
// No binary, must download
|
|
310
|
+
try {
|
|
311
|
+
latestVersion = await getLatestVersion();
|
|
312
|
+
} catch (err) {
|
|
313
|
+
console.error('Failed to fetch latest version:', err.message);
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (needsDownload) {
|
|
319
|
+
const version = latestVersion || ('v' + currentVersion);
|
|
320
|
+
console.error(currentVersion ? `Updating to ${version}...` : 'Installing MailPilot...');
|
|
321
|
+
try {
|
|
322
|
+
await downloadBinary(version);
|
|
323
|
+
|
|
324
|
+
// Show first-run instructions only on fresh install (not update)
|
|
325
|
+
if (!currentVersion) {
|
|
326
|
+
showFirstRunInstructions();
|
|
327
|
+
}
|
|
328
|
+
} catch (err) {
|
|
329
|
+
console.error('Download failed:', err.message);
|
|
330
|
+
if (currentVersion) {
|
|
331
|
+
console.error('Using existing version.');
|
|
332
|
+
} else {
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Run the binary
|
|
339
|
+
const env = { ...process.env };
|
|
340
|
+
if (!env.MAILPILOT_ORIGINAL_CWD) {
|
|
341
|
+
env.MAILPILOT_ORIGINAL_CWD = process.env.PWD || process.cwd();
|
|
342
|
+
}
|
|
343
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
344
|
+
stdio: 'inherit',
|
|
345
|
+
windowsHide: false,
|
|
346
|
+
env
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
child.on('error', (err) => {
|
|
350
|
+
console.error('Failed to start mailpilot:', err.message);
|
|
351
|
+
process.exit(1);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
child.on('exit', (code) => {
|
|
355
|
+
process.exit(code || 0);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ai-chat.email/cli",
|
|
3
|
+
"version": "5.2.5",
|
|
4
|
+
"description": "AI Chat Email CLI - Email AI agents like you email humans",
|
|
5
|
+
"author": "DOSAYGO Corporation",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/AI-Chat-Email/AI-Chat-Email"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://ai-chat.email",
|
|
12
|
+
"bin": {
|
|
13
|
+
"aice": "bin/aice.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin/"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"aice",
|
|
23
|
+
"chat",
|
|
24
|
+
"mailpilot",
|
|
25
|
+
"ai",
|
|
26
|
+
"agents",
|
|
27
|
+
"email",
|
|
28
|
+
"cli",
|
|
29
|
+
"coding",
|
|
30
|
+
"automation"
|
|
31
|
+
]
|
|
32
|
+
}
|