Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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 });
Expand Down
12 changes: 6 additions & 6 deletions ops/office3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ 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.

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.
6 changes: 3 additions & 3 deletions ops/office3/monitor-24h.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)

Expand Down
84 changes: 53 additions & 31 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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"
},
Expand Down
11 changes: 7 additions & 4 deletions scripts/install-office3-launchagent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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"
2 changes: 1 addition & 1 deletion tests/server-cli-integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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-'));
Expand Down
Loading