diff --git a/cli.js b/cli.js index 1fb6d27..a44266a 100755 --- a/cli.js +++ b/cli.js @@ -745,6 +745,9 @@ function withGlobalOptions(handler) { globalFlags = getGlobalFlags(command); const name = commandPath(command); const actionArgs = args.slice(0, -1); + if (name === 'send text' || name === 'send file') { + validateSendParseMode(name, actionArgs.at(-1) ?? {}); + } if (name === 'auth' || name === 'auth logout') { const current = parseStoreLock(readStoreLock(resolveStoreDir()).info); if (current?.pid && isPidAlive(current.pid)) { @@ -1406,6 +1409,15 @@ function parseSendParseMode(value) { return normalized; } +function validateSendParseMode(commandName, options) { + const parseMode = parseSendParseMode(options.parseMode); + if (commandName === 'send file' && parseMode && + !(typeof options.caption === 'string' && options.caption.trim())) { + throw new Error('--parse-mode requires --caption for send file'); + } + return parseMode; +} + function resolveSource(source) { const resolved = source ? String(source).toLowerCase() : 'archive'; if (!['archive', 'live', 'both'].includes(resolved)) { @@ -3142,7 +3154,7 @@ async function runSendText(globalFlags, options = {}) { if (!options.message) { throw new Error('--message is required'); } - const parseMode = parseSendParseMode(options.parseMode); + const parseMode = validateSendParseMode('send text', options); const storeDir = resolveStoreDir(); const release = acquireStoreLock(storeDir); const { telegramClient, messageSyncService } = createServices({ storeDir }); @@ -3278,10 +3290,7 @@ async function runSendFile(globalFlags, options = {}) { if (!options.file) { throw new Error('--file is required'); } - const parseMode = parseSendParseMode(options.parseMode); - if (parseMode && !(typeof options.caption === 'string' && options.caption.trim())) { - throw new Error('--parse-mode requires --caption for send file'); - } + const parseMode = validateSendParseMode('send file', options); const storeDir = resolveStoreDir(); const release = acquireStoreLock(storeDir); const { telegramClient, messageSyncService } = createServices({ storeDir }); diff --git a/ops/office3/README.md b/ops/office3/README.md index 1b6ba0f..f9eb977 100644 --- a/ops/office3/README.md +++ b/ops/office3/README.md @@ -7,7 +7,7 @@ in `~/code/personal-ops/launchd/registry.json`. The plist launches the canonical Install the matching CLI through `~/dotfiles` first, then use `~/code/tgcli/scripts/install-office3-launchagent.sh` on office3. The installer -saves any previous plist as `~/Library/LaunchAgents/com.dapi.tgcli.plist.pre-2.9.0`. +saves any previous plist as `~/Library/LaunchAgents/com.dapi.tgcli.plist.pre-2.9.1`. Check `~/code/personal-ops/scripts/personalctl launchd status` and `plan` after installation. Service logs are mode `0600` under `~/code/tgcli/log/` and are ignored by Git. @@ -15,10 +15,10 @@ ignored by Git. For a 24-hour observation, run `python3 ~/code/tgcli/ops/office3/monitor-24h.py --port 5173` in the background on office3. It samples launchd service status, owner IPC, and the loopback MCP listener every five minutes without Telegram API calls. -The current result is `~/code/tgcli/log/office3-monitor-2.9.0-summary.json`; +The current result is `~/code/tgcli/log/office3-monitor-2.9.1-summary.json`; individual samples are in the adjacent `.jsonl` file. -If the new service fails, stop its LaunchAgent and reinstall the previous CLI -version through `~/dotfiles` using `TGCLI_SOURCE_DIR=''` and -`TGCLI_VERSION=2.8.3`. Restore the saved plist and bootstrap it. Do not remove -the Telegram store; the archive and session remain in place. +If the new service fails, stop its LaunchAgent and leave it stopped while +investigating. The saved plist can restore the previous configuration, but +version 2.9.0 is known to crash and must not be used as an unattended fallback. +Do not remove the Telegram store; the archive and session remain in place. diff --git a/ops/office3/monitor-24h.py b/ops/office3/monitor-24h.py index b711f7f..e85d64a 100644 --- a/ops/office3/monitor-24h.py +++ b/ops/office3/monitor-24h.py @@ -46,7 +46,7 @@ def probe(port): return { "at": utc_now(), - "healthy": running and ipc_ready and mcp_listening and version == "2.9.0" + "healthy": running and ipc_ready and mcp_listening and version == "2.9.1" and not dialog_refresh_deferred, "running": running, "pid": pid, @@ -79,8 +79,8 @@ def main(): log_dir = Path(__file__).resolve().parents[2] / "log" log_dir.mkdir(mode=0o700, exist_ok=True) - samples_path = log_dir / "office3-monitor-2.9.0.jsonl" - summary_path = log_dir / "office3-monitor-2.9.0-summary.json" + samples_path = log_dir / "office3-monitor-2.9.1.jsonl" + summary_path = log_dir / "office3-monitor-2.9.1-summary.json" samples = os.open(samples_path, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600) os.chmod(samples_path, 0o600) diff --git a/package-lock.json b/package-lock.json index b08ba3f..4884c51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "@dapi/tgcli", - "version": "2.9.0", + "version": "2.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@dapi/tgcli", - "version": "2.9.0", + "version": "2.9.1", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.27.1", "@mtcute/core": "0.32.1", "@mtcute/node": "0.32.1", - "better-sqlite3": "^12.6.0", + "better-sqlite3": "13.0.3", "commander": "^12.1.0", "qrcode-terminal": "^0.12.0" }, @@ -631,6 +631,20 @@ "better-sqlite3": "^12.10.0" } }, + "node_modules/@mtcute/node/node_modules/better-sqlite3": { + "version": "12.11.1", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz", + "integrity": "sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" + } + }, "node_modules/@mtcute/tl-runtime": { "version": "0.32.0", "resolved": "https://registry.npmjs.org/@mtcute/tl-runtime/-/tl-runtime-0.32.0.tgz", @@ -1223,17 +1237,15 @@ "license": "MIT" }, "node_modules/better-sqlite3": { - "version": "12.11.1", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz", - "integrity": "sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==", - "hasInstallScript": true, + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-13.0.3.tgz", + "integrity": "sha512-RbOBxmLBG8uvFUc15X9+9SFemKcQ0WBuISBVkpuiaUB2qblC8UWlHEjdWVoZ8AdhSwmoEgsiXKfopX0CQxaACQ==", "license": "MIT", "dependencies": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.1" + "node-addon-api": "^8.0.0" }, "engines": { - "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" + "node": ">=22" } }, "node_modules/bindings": { @@ -1485,9 +1497,9 @@ } }, "node_modules/detect-libc": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz", - "integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "engines": { "node": ">=8" @@ -2295,9 +2307,9 @@ } }, "node_modules/node-abi": { - "version": "3.77.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.77.0.tgz", - "integrity": "sha512-DSmt0OEcLoK4i3NuscSbGjOf3bqiDEutejqENSplMSFA/gmB8mkED9G4pKWnPl7MDU4rSHebKPHeitpDfyH0cQ==", + "version": "3.96.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz", + "integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==", "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -2306,16 +2318,13 @@ "node": ">=10" } }, - "node_modules/node-abi/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "node_modules/node-addon-api": { + "version": "8.9.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.2.tgz", + "integrity": "sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg==", + "license": "MIT", "engines": { - "node": ">=10" + "node": "^18 || ^20 || >= 21" } }, "node_modules/object-assign": { @@ -2468,6 +2477,7 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", "license": "MIT", "dependencies": { "detect-libc": "^2.0.0", @@ -2504,9 +2514,9 @@ } }, "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -2685,6 +2695,18 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", @@ -2933,9 +2955,9 @@ } }, "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", + "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", "license": "MIT", "dependencies": { "chownr": "^1.1.1", diff --git a/package.json b/package.json index 74ee535..06f050d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dapi/tgcli", - "version": "2.9.0", + "version": "2.9.1", "type": "module", "description": "Telegram CLI + MCP server powered by MTProto and the official MCP SDK", "main": "mcp-server.js", @@ -57,7 +57,7 @@ "@modelcontextprotocol/sdk": "^1.27.1", "@mtcute/core": "0.32.1", "@mtcute/node": "0.32.1", - "better-sqlite3": "^12.6.0", + "better-sqlite3": "13.0.3", "commander": "^12.1.0", "qrcode-terminal": "^0.12.0" }, diff --git a/scripts/install-office3-launchagent.sh b/scripts/install-office3-launchagent.sh index 118b6fe..60f59d7 100755 --- a/scripts/install-office3-launchagent.sh +++ b/scripts/install-office3-launchagent.sh @@ -13,8 +13,8 @@ installed_plist="$HOME/Library/LaunchAgents/$label.plist" log_dir="$repo_root/log" domain="gui/$(id -u)" -if [ "$("$HOME/.local/bin/tgcli" --version)" != "2.9.0" ]; then - echo "Install tgcli 2.9.0 through dotfiles before loading the service" >&2 +if [ "$("$HOME/.local/bin/tgcli" --version)" != "2.9.1" ]; then + echo "Install tgcli 2.9.1 through dotfiles before loading the service" >&2 exit 2 fi @@ -23,11 +23,14 @@ chmod 700 "$log_dir" touch "$log_dir/tgcli.log" "$log_dir/tgcli.error.log" chmod 600 "$log_dir/tgcli.log" "$log_dir/tgcli.error.log" if [ -f "$installed_plist" ]; then - cp -p "$installed_plist" "$installed_plist.pre-2.9.0" + cp -p "$installed_plist" "$installed_plist.pre-2.9.1" fi launchctl bootout "$domain/$label" 2>/dev/null || true cp "$source_plist" "$installed_plist" chmod 644 "$installed_plist" launchctl enable "$domain/$label" -launchctl bootstrap "$domain" "$installed_plist" +if ! launchctl bootstrap "$domain" "$installed_plist" 2>/dev/null; then + sleep 1 + launchctl bootstrap "$domain" "$installed_plist" +fi echo "Loaded $label from $installed_plist" diff --git a/tests/server-cli-integration.test.js b/tests/server-cli-integration.test.js index 315a00f..be4478a 100644 --- a/tests/server-cli-integration.test.js +++ b/tests/server-cli-integration.test.js @@ -151,7 +151,7 @@ it('keeps archive and IPC available when Telegram defers dialog refresh', async server.kill('SIGTERM'); await new Promise((resolve) => server.once('exit', resolve)); expect(readStoreLock(storeDir).exists).toBe(false); -}); +}, 15000); it('reuses an existing dialog registry without a new Telegram scan', async () => { storeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tgcli-server-cached-dialogs-'));