alvin-bot 4.5.1 → 4.7.0
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/CHANGELOG.md +278 -0
- package/README.md +25 -2
- package/bin/cli.js +325 -26
- package/dist/handlers/commands.js +505 -63
- package/dist/handlers/message.js +209 -14
- package/dist/i18n.js +470 -13
- package/dist/index.js +45 -5
- package/dist/providers/claude-sdk-provider.js +106 -14
- package/dist/providers/ollama-provider.js +32 -0
- package/dist/providers/openai-compatible.js +10 -1
- package/dist/providers/registry.js +112 -17
- package/dist/providers/types.js +25 -3
- package/dist/services/compaction.js +2 -0
- package/dist/services/cron.js +53 -42
- package/dist/services/heartbeat.js +41 -7
- package/dist/services/language-detect.js +12 -2
- package/dist/services/ollama-manager.js +339 -0
- package/dist/services/personality.js +20 -14
- package/dist/services/session.js +21 -3
- package/dist/services/subagent-delivery.js +266 -0
- package/dist/services/subagent-stats.js +123 -0
- package/dist/services/subagents.js +509 -42
- package/dist/services/telegram.js +28 -1
- package/dist/services/updater.js +158 -0
- package/dist/services/usage-tracker.js +11 -4
- package/dist/services/users.js +2 -1
- package/docs/HANDBOOK.md +856 -0
- package/package.json +7 -2
- package/test/claude-sdk-provider.test.ts +69 -0
- package/test/i18n.test.ts +108 -0
- package/test/registry.test.ts +201 -0
- package/test/subagent-delivery.test.ts +273 -0
- package/test/subagent-stats.test.ts +119 -0
- package/test/subagents-commands.test.ts +64 -0
- package/test/subagents-config.test.ts +114 -0
- package/test/subagents-depth.test.ts +58 -0
- package/test/subagents-inheritance.test.ts +67 -0
- package/test/subagents-name-resolver.test.ts +122 -0
- package/test/subagents-priority-reject.test.ts +88 -0
- package/test/subagents-queue.test.ts +127 -0
- package/test/subagents-shutdown.test.ts +126 -0
- package/test/subagents-toolset.test.ts +51 -0
- package/vitest.config.ts +17 -0
package/bin/cli.js
CHANGED
|
@@ -1153,6 +1153,232 @@ async function version() {
|
|
|
1153
1153
|
}
|
|
1154
1154
|
}
|
|
1155
1155
|
|
|
1156
|
+
// ── LaunchAgent helpers (macOS only) ────────────────────────────────────────
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* Render the launchd plist that runs `node dist/index.js` as a per-user
|
|
1160
|
+
* agent. Inherits the GUI login session so the macOS Keychain is
|
|
1161
|
+
* automatically unlocked — which means Claude Code OAuth tokens (Max
|
|
1162
|
+
* subscription) work without a manual `security unlock-keychain`.
|
|
1163
|
+
*/
|
|
1164
|
+
function renderLaunchdPlist({ label, nodePath, entryPoint, cwd, home, logDir }) {
|
|
1165
|
+
// PATH covers both Apple Silicon and Intel Homebrew plus the legacy
|
|
1166
|
+
// user-local claude binary path.
|
|
1167
|
+
const pathValue = `${home}/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin`;
|
|
1168
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1169
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1170
|
+
<plist version="1.0">
|
|
1171
|
+
<dict>
|
|
1172
|
+
<key>Label</key>
|
|
1173
|
+
<string>${label}</string>
|
|
1174
|
+
|
|
1175
|
+
<key>ProgramArguments</key>
|
|
1176
|
+
<array>
|
|
1177
|
+
<string>${nodePath}</string>
|
|
1178
|
+
<string>${entryPoint}</string>
|
|
1179
|
+
</array>
|
|
1180
|
+
|
|
1181
|
+
<key>WorkingDirectory</key>
|
|
1182
|
+
<string>${cwd}</string>
|
|
1183
|
+
|
|
1184
|
+
<key>RunAtLoad</key>
|
|
1185
|
+
<true/>
|
|
1186
|
+
|
|
1187
|
+
<key>KeepAlive</key>
|
|
1188
|
+
<dict>
|
|
1189
|
+
<key>SuccessfulExit</key>
|
|
1190
|
+
<false/>
|
|
1191
|
+
<key>Crashed</key>
|
|
1192
|
+
<true/>
|
|
1193
|
+
</dict>
|
|
1194
|
+
|
|
1195
|
+
<key>ThrottleInterval</key>
|
|
1196
|
+
<integer>10</integer>
|
|
1197
|
+
|
|
1198
|
+
<key>StandardOutPath</key>
|
|
1199
|
+
<string>${logDir}/alvin-bot.out.log</string>
|
|
1200
|
+
|
|
1201
|
+
<key>StandardErrorPath</key>
|
|
1202
|
+
<string>${logDir}/alvin-bot.err.log</string>
|
|
1203
|
+
|
|
1204
|
+
<key>EnvironmentVariables</key>
|
|
1205
|
+
<dict>
|
|
1206
|
+
<key>PATH</key>
|
|
1207
|
+
<string>${pathValue}</string>
|
|
1208
|
+
<key>HOME</key>
|
|
1209
|
+
<string>${home}</string>
|
|
1210
|
+
<key>NODE_ENV</key>
|
|
1211
|
+
<string>production</string>
|
|
1212
|
+
</dict>
|
|
1213
|
+
</dict>
|
|
1214
|
+
</plist>
|
|
1215
|
+
`;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Common paths + label used by all three launchd subcommands.
|
|
1220
|
+
*/
|
|
1221
|
+
function launchdPaths() {
|
|
1222
|
+
const home = homedir();
|
|
1223
|
+
const label = "com.alvinbot.app";
|
|
1224
|
+
const plistPath = join(home, "Library", "LaunchAgents", `${label}.plist`);
|
|
1225
|
+
const logDir = join(home, ".alvin-bot", "logs");
|
|
1226
|
+
// dist/index.js lives two levels up from bin/cli.js, then dist/
|
|
1227
|
+
const entryPoint = resolve(join(import.meta.dirname, "..", "dist", "index.js"));
|
|
1228
|
+
const cwd = resolve(join(import.meta.dirname, ".."));
|
|
1229
|
+
const nodePath = process.execPath;
|
|
1230
|
+
return { home, label, plistPath, logDir, entryPoint, cwd, nodePath };
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
async function launchdInstall() {
|
|
1234
|
+
if (process.platform !== "darwin") {
|
|
1235
|
+
console.log("❌ alvin-bot launchd is macOS-only.");
|
|
1236
|
+
console.log(" Linux users: create a systemd user unit for dist/index.js.");
|
|
1237
|
+
console.log(" Windows users: use Task Scheduler or NSSM.");
|
|
1238
|
+
process.exit(1);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
const { home, label, plistPath, logDir, entryPoint, cwd, nodePath } = launchdPaths();
|
|
1242
|
+
|
|
1243
|
+
// Sanity-check that dist/ is built
|
|
1244
|
+
if (!existsSync(entryPoint)) {
|
|
1245
|
+
console.log(`❌ Build not found at ${entryPoint}`);
|
|
1246
|
+
console.log(" Run 'npm run build' first.");
|
|
1247
|
+
process.exit(1);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// Ensure the LaunchAgents dir and log dir exist
|
|
1251
|
+
mkdirSync(join(home, "Library", "LaunchAgents"), { recursive: true });
|
|
1252
|
+
mkdirSync(logDir, { recursive: true });
|
|
1253
|
+
|
|
1254
|
+
// Render and write the plist
|
|
1255
|
+
const plist = renderLaunchdPlist({ label, nodePath, entryPoint, cwd, home, logDir });
|
|
1256
|
+
writeFileSync(plistPath, plist, { mode: 0o644 });
|
|
1257
|
+
console.log(`📝 Wrote ${plistPath}`);
|
|
1258
|
+
|
|
1259
|
+
// Unload any previous instance (best-effort)
|
|
1260
|
+
try {
|
|
1261
|
+
execSync(`launchctl unload -w "${plistPath}"`, { stdio: "pipe" });
|
|
1262
|
+
} catch { /* not loaded yet — fine */ }
|
|
1263
|
+
|
|
1264
|
+
// Stop any nohup'd bot that might still be running
|
|
1265
|
+
try {
|
|
1266
|
+
execSync(`pkill -TERM -f 'node.*dist/index.js' || true`, { stdio: "pipe" });
|
|
1267
|
+
} catch { /* nothing to kill */ }
|
|
1268
|
+
|
|
1269
|
+
// Load fresh
|
|
1270
|
+
try {
|
|
1271
|
+
execSync(`launchctl load -w "${plistPath}"`, { stdio: "inherit" });
|
|
1272
|
+
} catch (err) {
|
|
1273
|
+
console.log(`❌ launchctl load failed: ${err.message}`);
|
|
1274
|
+
console.log(" Try manually: launchctl load -w " + plistPath);
|
|
1275
|
+
process.exit(1);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
console.log("");
|
|
1279
|
+
console.log("✅ alvin-bot is now a launchd user agent.");
|
|
1280
|
+
console.log(` Label: ${label}`);
|
|
1281
|
+
console.log(` Logs: ${logDir}/alvin-bot.out.log`);
|
|
1282
|
+
console.log(` Errors: ${logDir}/alvin-bot.err.log`);
|
|
1283
|
+
console.log("");
|
|
1284
|
+
console.log(" Status: alvin-bot launchd status");
|
|
1285
|
+
console.log(" Stop: alvin-bot launchd uninstall");
|
|
1286
|
+
console.log(" Restart: launchctl kickstart -k gui/$UID/" + label);
|
|
1287
|
+
console.log("");
|
|
1288
|
+
console.log(" Because launchd runs the bot inside your GUI login session,");
|
|
1289
|
+
console.log(" the macOS Keychain is automatically unlocked — Claude Code");
|
|
1290
|
+
console.log(" OAuth tokens (Max subscription) just work, no SSH keychain");
|
|
1291
|
+
console.log(" dance needed anymore.");
|
|
1292
|
+
process.exit(0);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
async function launchdUninstall() {
|
|
1296
|
+
if (process.platform !== "darwin") {
|
|
1297
|
+
console.log("❌ alvin-bot launchd is macOS-only.");
|
|
1298
|
+
process.exit(1);
|
|
1299
|
+
}
|
|
1300
|
+
const { plistPath, label } = launchdPaths();
|
|
1301
|
+
if (!existsSync(plistPath)) {
|
|
1302
|
+
console.log(`⚠️ No LaunchAgent plist at ${plistPath}`);
|
|
1303
|
+
console.log(" Nothing to uninstall.");
|
|
1304
|
+
process.exit(0);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
try {
|
|
1308
|
+
execSync(`launchctl unload -w "${plistPath}"`, { stdio: "inherit" });
|
|
1309
|
+
console.log(`✅ Unloaded ${label}`);
|
|
1310
|
+
} catch (err) {
|
|
1311
|
+
console.log(`⚠️ Unload reported an error (may not have been running): ${err.message}`);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
try {
|
|
1315
|
+
execSync(`rm -f "${plistPath}"`);
|
|
1316
|
+
console.log(`🗑 Removed ${plistPath}`);
|
|
1317
|
+
} catch (err) {
|
|
1318
|
+
console.log(`⚠️ Could not remove plist: ${err.message}`);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
console.log("");
|
|
1322
|
+
console.log("✅ alvin-bot is no longer a launchd user agent.");
|
|
1323
|
+
process.exit(0);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
async function launchdStatus() {
|
|
1327
|
+
if (process.platform !== "darwin") {
|
|
1328
|
+
console.log("❌ alvin-bot launchd is macOS-only.");
|
|
1329
|
+
process.exit(1);
|
|
1330
|
+
}
|
|
1331
|
+
const { plistPath, label, logDir } = launchdPaths();
|
|
1332
|
+
|
|
1333
|
+
console.log(`📋 alvin-bot launchd status`);
|
|
1334
|
+
console.log("");
|
|
1335
|
+
console.log(`Label: ${label}`);
|
|
1336
|
+
console.log(`Plist: ${plistPath}`);
|
|
1337
|
+
console.log(`Plist exists: ${existsSync(plistPath) ? "yes" : "no"}`);
|
|
1338
|
+
console.log("");
|
|
1339
|
+
|
|
1340
|
+
try {
|
|
1341
|
+
const out = execSync(`launchctl list | grep ${label} || true`, { encoding: "utf-8" });
|
|
1342
|
+
if (out.trim()) {
|
|
1343
|
+
// Format: <PID>\t<ExitCode>\t<Label>
|
|
1344
|
+
const parts = out.trim().split(/\s+/);
|
|
1345
|
+
const pid = parts[0];
|
|
1346
|
+
const exitCode = parts[1];
|
|
1347
|
+
const isRunning = pid !== "-" && pid !== "0";
|
|
1348
|
+
console.log(`Running: ${isRunning ? "✅ yes (PID " + pid + ")" : "❌ no (last exit " + exitCode + ")"}`);
|
|
1349
|
+
} else {
|
|
1350
|
+
console.log(`Running: ❌ not loaded`);
|
|
1351
|
+
}
|
|
1352
|
+
} catch {
|
|
1353
|
+
console.log(`Running: ❌ unknown (launchctl list failed)`);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
console.log("");
|
|
1357
|
+
console.log(`Log dir: ${logDir}`);
|
|
1358
|
+
const outLog = join(logDir, "alvin-bot.out.log");
|
|
1359
|
+
const errLog = join(logDir, "alvin-bot.err.log");
|
|
1360
|
+
if (existsSync(outLog)) {
|
|
1361
|
+
try {
|
|
1362
|
+
const tail = execSync(`tail -n 5 "${outLog}"`, { encoding: "utf-8" });
|
|
1363
|
+
console.log("");
|
|
1364
|
+
console.log("── Last 5 lines of stdout ──");
|
|
1365
|
+
console.log(tail.trimEnd());
|
|
1366
|
+
} catch { /* ignore */ }
|
|
1367
|
+
}
|
|
1368
|
+
if (existsSync(errLog)) {
|
|
1369
|
+
try {
|
|
1370
|
+
const tail = execSync(`tail -n 5 "${errLog}"`, { encoding: "utf-8" });
|
|
1371
|
+
const trimmed = tail.trimEnd();
|
|
1372
|
+
if (trimmed) {
|
|
1373
|
+
console.log("");
|
|
1374
|
+
console.log("── Last 5 lines of stderr ──");
|
|
1375
|
+
console.log(trimmed);
|
|
1376
|
+
}
|
|
1377
|
+
} catch { /* ignore */ }
|
|
1378
|
+
}
|
|
1379
|
+
process.exit(0);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1156
1382
|
// ── CLI Router ──────────────────────────────────────────────────────────────
|
|
1157
1383
|
|
|
1158
1384
|
const cmd = process.argv[2];
|
|
@@ -1170,40 +1396,93 @@ switch (cmd) {
|
|
|
1170
1396
|
const fg = process.argv.includes("--foreground") || process.argv.includes("-f");
|
|
1171
1397
|
if (fg) {
|
|
1172
1398
|
import("../dist/index.js");
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1399
|
+
break;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// On macOS, if a LaunchAgent plist already exists, we're in "launchd
|
|
1403
|
+
// mode" — don't start pm2 in parallel. Reload the LaunchAgent instead
|
|
1404
|
+
// so a plain `alvin-bot start` still works as "bring the bot up".
|
|
1405
|
+
if (process.platform === "darwin") {
|
|
1406
|
+
const { plistPath, label } = launchdPaths();
|
|
1407
|
+
if (existsSync(plistPath)) {
|
|
1408
|
+
console.log(`🚀 Detected existing LaunchAgent (${label})`);
|
|
1409
|
+
console.log(` Reloading via 'launchctl kickstart -k'...`);
|
|
1180
1410
|
try {
|
|
1181
|
-
execSync(
|
|
1411
|
+
execSync(`launchctl kickstart -k gui/$(id -u)/${label}`, {
|
|
1412
|
+
stdio: "inherit",
|
|
1413
|
+
shell: "/bin/zsh",
|
|
1414
|
+
});
|
|
1182
1415
|
} catch {
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1416
|
+
// Maybe unloaded — load it fresh
|
|
1417
|
+
try {
|
|
1418
|
+
execSync(`launchctl load -w "${plistPath}"`, { stdio: "inherit" });
|
|
1419
|
+
} catch (err) {
|
|
1420
|
+
console.log(`❌ launchctl load failed: ${err.message}`);
|
|
1421
|
+
process.exit(1);
|
|
1422
|
+
}
|
|
1187
1423
|
}
|
|
1424
|
+
console.log("\n✅ Bot is running via launchd.");
|
|
1425
|
+
console.log(" Status: alvin-bot launchd status");
|
|
1426
|
+
console.log(" Stop: alvin-bot stop");
|
|
1427
|
+
console.log(" Logs: ~/.alvin-bot/logs/alvin-bot.out.log");
|
|
1428
|
+
process.exit(0);
|
|
1188
1429
|
}
|
|
1189
|
-
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// Fall-through: pm2 path (Linux, Windows, or macOS without LaunchAgent)
|
|
1433
|
+
try {
|
|
1434
|
+
execSync("pm2 --version", { stdio: "pipe" });
|
|
1435
|
+
} catch {
|
|
1436
|
+
console.log("Installing PM2 for background operation...");
|
|
1190
1437
|
try {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
}
|
|
1198
|
-
console.log("\n✅ Bot is running in the background.");
|
|
1199
|
-
console.log(" Logs: pm2 logs alvin-bot");
|
|
1200
|
-
console.log(" Stop: alvin-bot stop");
|
|
1201
|
-
console.log(" Restart: alvin-bot start\n");
|
|
1202
|
-
process.exit(0);
|
|
1438
|
+
execSync("npm install -g pm2", { stdio: "inherit", timeout: 60000 });
|
|
1439
|
+
} catch {
|
|
1440
|
+
console.log("Could not install PM2. Starting in foreground instead.");
|
|
1441
|
+
console.log("Tip: Install PM2 manually (npm install -g pm2) to run in background.\n");
|
|
1442
|
+
await import("../dist/index.js");
|
|
1443
|
+
break;
|
|
1444
|
+
}
|
|
1203
1445
|
}
|
|
1204
|
-
|
|
1446
|
+
const cliPath = resolve(join(import.meta.dirname, "cli.js"));
|
|
1447
|
+
try {
|
|
1448
|
+
execSync("pm2 delete alvin-bot", { stdio: "pipe" });
|
|
1449
|
+
} catch { /* not running — fine */ }
|
|
1450
|
+
execSync(`pm2 start "${cliPath}" --name alvin-bot -- start --foreground`, {
|
|
1451
|
+
stdio: "inherit",
|
|
1452
|
+
timeout: 15000,
|
|
1453
|
+
});
|
|
1454
|
+
console.log("\n✅ Bot is running in the background via PM2.");
|
|
1455
|
+
console.log(" Logs: pm2 logs alvin-bot");
|
|
1456
|
+
console.log(" Stop: alvin-bot stop");
|
|
1457
|
+
console.log(" Restart: alvin-bot start");
|
|
1458
|
+
if (process.platform === "darwin") {
|
|
1459
|
+
console.log("");
|
|
1460
|
+
console.log(" 💡 Tip: on macOS with Claude Code, switch to launchd for");
|
|
1461
|
+
console.log(" automatic Keychain access: alvin-bot launchd install");
|
|
1462
|
+
}
|
|
1463
|
+
console.log("");
|
|
1464
|
+
process.exit(0);
|
|
1205
1465
|
}
|
|
1206
1466
|
case "stop": {
|
|
1467
|
+
// On macOS with a LaunchAgent, stopping means unloading the LaunchAgent,
|
|
1468
|
+
// not asking pm2 to stop a process it never managed.
|
|
1469
|
+
if (process.platform === "darwin") {
|
|
1470
|
+
const { plistPath, label } = launchdPaths();
|
|
1471
|
+
if (existsSync(plistPath)) {
|
|
1472
|
+
console.log(`⏹ Stopping LaunchAgent (${label})...`);
|
|
1473
|
+
try {
|
|
1474
|
+
execSync(`launchctl unload -w "${plistPath}"`, { stdio: "inherit" });
|
|
1475
|
+
console.log("✅ LaunchAgent stopped.");
|
|
1476
|
+
console.log(" (The plist is still installed. To remove it: alvin-bot launchd uninstall)");
|
|
1477
|
+
} catch (err) {
|
|
1478
|
+
console.log(`❌ launchctl unload failed: ${err.message}`);
|
|
1479
|
+
process.exit(1);
|
|
1480
|
+
}
|
|
1481
|
+
process.exit(0);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
// Fall-through: pm2 path
|
|
1207
1486
|
try {
|
|
1208
1487
|
execSync("pm2 stop alvin-bot", { stdio: "inherit", timeout: 10000 });
|
|
1209
1488
|
} catch {
|
|
@@ -1211,6 +1490,25 @@ switch (cmd) {
|
|
|
1211
1490
|
}
|
|
1212
1491
|
process.exit(0);
|
|
1213
1492
|
}
|
|
1493
|
+
case "launchd": {
|
|
1494
|
+
const sub = process.argv[3];
|
|
1495
|
+
if (sub === "install") {
|
|
1496
|
+
await launchdInstall();
|
|
1497
|
+
} else if (sub === "uninstall") {
|
|
1498
|
+
await launchdUninstall();
|
|
1499
|
+
} else if (sub === "status") {
|
|
1500
|
+
await launchdStatus();
|
|
1501
|
+
} else {
|
|
1502
|
+
console.log("Usage: alvin-bot launchd <install|uninstall|status>");
|
|
1503
|
+
console.log("");
|
|
1504
|
+
console.log(" install — Install as a macOS launchd user agent.");
|
|
1505
|
+
console.log(" Runs on login, keychain auto-unlocked.");
|
|
1506
|
+
console.log(" uninstall — Unload and remove the LaunchAgent plist.");
|
|
1507
|
+
console.log(" status — Show current launchd state + recent logs.");
|
|
1508
|
+
process.exit(1);
|
|
1509
|
+
}
|
|
1510
|
+
break;
|
|
1511
|
+
}
|
|
1214
1512
|
case "tui":
|
|
1215
1513
|
case "chat":
|
|
1216
1514
|
import("../dist/tui/index.js").then(m => m.startTUI()).catch(console.error);
|
|
@@ -1254,6 +1552,7 @@ ${t("cli.commands")}
|
|
|
1254
1552
|
start ${t("cli.startDesc")} (background via PM2)
|
|
1255
1553
|
start -f Start in foreground (for debugging)
|
|
1256
1554
|
stop Stop the bot
|
|
1555
|
+
launchd macOS only: install/uninstall/status as launchd user agent
|
|
1257
1556
|
version ${t("cli.versionDesc")}
|
|
1258
1557
|
|
|
1259
1558
|
${t("cli.example")}
|