Summary
Add full CRUD support for Telegram Chat Folders (Dialog Filters) via tgcli folders commands. Includes support for shared/chatlist folders.
Proposed Commands
tgcli folders list # List all folders
tgcli folders show <id|name> # Folder details (chats, filters)
tgcli folders create --title <name> # Create folder
tgcli folders edit <id|name> # Edit folder
tgcli folders delete <id|name> # Delete folder
tgcli folders reorder --ids <id1,id2,...> # Reorder folders
tgcli folders chats add <id|name> --chat <chatId> # Add chat to folder
tgcli folders chats remove <id|name> --chat <chatId> # Remove chat from folder
tgcli folders join <invite-link> # Join shared folder
Create/Edit Options
| Flag |
Description |
--title |
Folder name (max 12 chars) |
--emoji |
Emoji icon |
--include-contacts |
Include contacts |
--include-non-contacts |
Include non-contacts |
--include-groups |
Include groups |
--include-channels |
Include channels |
--include-bots |
Include bots |
--exclude-muted |
Exclude muted |
--exclude-read |
Exclude read |
--exclude-archived |
Exclude archived |
--chat |
Chat to include (repeatable) |
--exclude-chat |
Chat to exclude (repeatable) |
--pin-chat |
Pin chat in folder |
Technical Notes
- All required methods already available in
@mtcute/core: getFolders, createFolder, editFolder, deleteFolder, findFolder, setFoldersOrder, joinChatlist
- Pattern follows existing
channels/groups/contacts commands
- Folder resolution by ID or title (like existing chat resolution patterns)
show command needs to resolve included peers to display chat names
- Listing chats in a folder is slow (Telegram API limitation: must fetch all dialogs and filter)
Implementation Plan
Task 1: Add folder domain methods to telegram-client.js
Files: Modify telegram-client.js (add methods after existing listForumTopics at ~line 1196)
Methods to add:
getFolders() — list all folders with normalized output
findFolder(idOrName) — resolve folder by numeric ID or title string
showFolder(idOrName) — detailed folder info with filter flags and peer lists
createFolder(options) — create folder with title, emoji, filter flags, peer lists
editFolder(idOrName, modification) — modify existing folder properties
deleteFolder(idOrName) — delete folder by ID or title
setFoldersOrder(ids) — reorder folders
addChatToFolder(idOrName, chatId) — append chat to folder's includePeers
removeChatFromFolder(idOrName, chatId) — remove chat from folder's includePeers
joinChatlist(link) — join shared folder via invite link
async getFolders() {
await this.ensureLogin();
const result = await this.client.getFolders();
return result.filters.map((f) => {
if (f._ === 'dialogFilterDefault') return { id: 0, title: 'All Chats', type: 'default' };
return {
id: f.id,
title: typeof f.title === 'string' ? f.title : f.title.text,
emoji: f.emoticon ?? null,
color: f.color ?? null,
type: f._ === 'dialogFilterChatlist' ? 'chatlist' : 'filter',
contacts: f.contacts ?? false,
nonContacts: f.nonContacts ?? false,
groups: f.groups ?? false,
broadcasts: f.broadcasts ?? false,
bots: f.bots ?? false,
excludeMuted: f.excludeMuted ?? false,
excludeRead: f.excludeRead ?? false,
excludeArchived: f.excludeArchived ?? false,
includePeers: f.includePeers?.length ?? 0,
excludePeers: f.excludePeers?.length ?? 0,
pinnedPeers: f.pinnedPeers?.length ?? 0,
};
});
}
async findFolder(idOrName) {
await this.ensureLogin();
const id = Number(idOrName);
if (!isNaN(id)) return this.client.findFolder({ id });
return this.client.findFolder({ title: String(idOrName) });
}
async showFolder(idOrName) {
await this.ensureLogin();
const folder = await this.findFolder(idOrName);
if (!folder) throw new Error(`Folder not found: ${idOrName}`);
return {
id: folder.id,
title: typeof folder.title === 'string' ? folder.title : folder.title.text,
emoji: folder.emoticon ?? null, color: folder.color ?? null,
type: folder._ === 'dialogFilterChatlist' ? 'chatlist' : 'filter',
contacts: folder.contacts ?? false, nonContacts: folder.nonContacts ?? false,
groups: folder.groups ?? false, broadcasts: folder.broadcasts ?? false, bots: folder.bots ?? false,
excludeMuted: folder.excludeMuted ?? false, excludeRead: folder.excludeRead ?? false, excludeArchived: folder.excludeArchived ?? false,
includePeers: folder.includePeers ?? [], excludePeers: folder.excludePeers ?? [], pinnedPeers: folder.pinnedPeers ?? [],
};
}
async createFolder(options) {
await this.ensureLogin();
const params = { title: options.title };
if (options.emoji) params.emoticon = options.emoji;
if (options.contacts) params.contacts = true;
if (options.nonContacts) params.nonContacts = true;
if (options.groups) params.groups = true;
if (options.broadcasts) params.broadcasts = true;
if (options.bots) params.bots = true;
if (options.excludeMuted) params.excludeMuted = true;
if (options.excludeRead) params.excludeRead = true;
if (options.excludeArchived) params.excludeArchived = true;
if (options.includePeers?.length) params.includePeers = options.includePeers;
if (options.excludePeers?.length) params.excludePeers = options.excludePeers;
if (options.pinnedPeers?.length) params.pinnedPeers = options.pinnedPeers;
const result = await this.client.createFolder(params);
return { id: result.id, title: typeof result.title === 'string' ? result.title : result.title.text };
}
async editFolder(idOrName, modification) {
await this.ensureLogin();
const folder = await this.findFolder(idOrName);
if (!folder) throw new Error(`Folder not found: ${idOrName}`);
const mod = {};
if (modification.title !== undefined) mod.title = modification.title;
if (modification.emoji !== undefined) mod.emoticon = modification.emoji;
if (modification.contacts !== undefined) mod.contacts = modification.contacts;
if (modification.nonContacts !== undefined) mod.nonContacts = modification.nonContacts;
if (modification.groups !== undefined) mod.groups = modification.groups;
if (modification.broadcasts !== undefined) mod.broadcasts = modification.broadcasts;
if (modification.bots !== undefined) mod.bots = modification.bots;
if (modification.excludeMuted !== undefined) mod.excludeMuted = modification.excludeMuted;
if (modification.excludeRead !== undefined) mod.excludeRead = modification.excludeRead;
if (modification.excludeArchived !== undefined) mod.excludeArchived = modification.excludeArchived;
if (modification.includePeers !== undefined) mod.includePeers = modification.includePeers;
if (modification.excludePeers !== undefined) mod.excludePeers = modification.excludePeers;
if (modification.pinnedPeers !== undefined) mod.pinnedPeers = modification.pinnedPeers;
const result = await this.client.editFolder({ folder, modification: mod });
return { id: result.id, title: typeof result.title === 'string' ? result.title : result.title.text };
}
async deleteFolder(idOrName) {
await this.ensureLogin();
const folder = await this.findFolder(idOrName);
if (!folder) throw new Error(`Folder not found: ${idOrName}`);
await this.client.deleteFolder(folder.id);
return { deleted: true, id: folder.id };
}
async setFoldersOrder(ids) {
await this.ensureLogin();
await this.client.setFoldersOrder(ids.map(Number));
return { ok: true };
}
async addChatToFolder(idOrName, chatId) {
await this.ensureLogin();
const folder = await this.findFolder(idOrName);
if (!folder) throw new Error(`Folder not found: ${idOrName}`);
const peers = folder.includePeers ? [...folder.includePeers] : [];
peers.push(chatId);
await this.client.editFolder({ folder, modification: { includePeers: peers } });
return { ok: true, folderId: folder.id };
}
async removeChatFromFolder(idOrName, chatId) {
await this.ensureLogin();
const folder = await this.findFolder(idOrName);
if (!folder) throw new Error(`Folder not found: ${idOrName}`);
const chatIdStr = String(chatId);
const peers = (folder.includePeers ?? []).filter((p) => {
const peerId = typeof p === 'object' && p !== null ? String(p.userId ?? p.channelId ?? p.chatId ?? '') : String(p);
return peerId !== chatIdStr;
});
await this.client.editFolder({ folder, modification: { includePeers: peers } });
return { ok: true, folderId: folder.id };
}
async joinChatlist(link) {
await this.ensureLogin();
const result = await this.client.joinChatlist(link);
return { id: result.id, title: typeof result.title === 'string' ? result.title : result.title.text, type: 'chatlist' };
}
Commit: feat(folders): add folder domain methods to telegram-client
Task 2: Add CLI subcommands in cli.js
Files: Modify cli.js (insert folders block before disableHelpCommand(program) at ~line 408; add handler functions)
Commander subcommands: folders list, folders show <folder>, folders create, folders edit <folder>, folders delete <folder>, folders reorder, folders join <link>, folders chats add <folder>, folders chats remove <folder>.
Handler functions follow same pattern as runGroupsList/runGroupsInfo: resolveStoreDir → acquireReadLock → createServices → auth check → call domain method → writeJson/console output → cleanup in finally.
Commit: feat(folders): add CLI subcommands for folder management
Task 3: Add MCP tools in mcp-server.js
Files: Modify mcp-server.js (add Zod schemas near top ~line 150, register tools near end ~line 1270)
9 MCP tools: listFolders, showFolder, createFolder, editFolder, deleteFolder, reorderFolders, addChatToFolder, removeChatFromFolder, joinChatlist.
Pattern follows existing server.tool(name, description, schema, handler) with await telegramClient.ensureLogin() and JSON response.
Commit: feat(folders): add MCP tools for folder management
Task 4: Smoke test all commands
node cli.js folders list --json --timeout 30s
node cli.js folders create --title "Test" --emoji "🧪" --json --timeout 30s
node cli.js folders show <id> --json --timeout 30s
node cli.js folders delete <id> --json --timeout 30s
node cli.js folders --help
Task 5: Update tgcli skill documentation
Add ### Folders section to skill file:
tgcli folders list --json --timeout 30s
tgcli folders show <id|name> --json --timeout 30s
tgcli folders create --title "AI channels" --emoji "🤖" --include-channels --json --timeout 30s
tgcli folders edit <id|name> --title "New name" --json --timeout 30s
tgcli folders delete <id|name> --json --timeout 30s
tgcli folders reorder --ids 1,2,3 --json --timeout 30s
tgcli folders chats add <id|name> --chat <chatId> --json --timeout 30s
tgcli folders chats remove <id|name> --chat <chatId> --json --timeout 30s
tgcli folders join <invite-link> --json --timeout 30s
Summary
Add full CRUD support for Telegram Chat Folders (Dialog Filters) via
tgcli folderscommands. Includes support for shared/chatlist folders.Proposed Commands
Create/Edit Options
--title--emoji--include-contacts--include-non-contacts--include-groups--include-channels--include-bots--exclude-muted--exclude-read--exclude-archived--chat--exclude-chat--pin-chatTechnical Notes
@mtcute/core:getFolders,createFolder,editFolder,deleteFolder,findFolder,setFoldersOrder,joinChatlistchannels/groups/contactscommandsshowcommand needs to resolve included peers to display chat namesImplementation Plan
Task 1: Add folder domain methods to telegram-client.js
Files: Modify
telegram-client.js(add methods after existinglistForumTopicsat ~line 1196)Methods to add:
getFolders()— list all folders with normalized outputfindFolder(idOrName)— resolve folder by numeric ID or title stringshowFolder(idOrName)— detailed folder info with filter flags and peer listscreateFolder(options)— create folder with title, emoji, filter flags, peer listseditFolder(idOrName, modification)— modify existing folder propertiesdeleteFolder(idOrName)— delete folder by ID or titlesetFoldersOrder(ids)— reorder foldersaddChatToFolder(idOrName, chatId)— append chat to folder's includePeersremoveChatFromFolder(idOrName, chatId)— remove chat from folder's includePeersjoinChatlist(link)— join shared folder via invite linkCommit:
feat(folders): add folder domain methods to telegram-clientTask 2: Add CLI subcommands in cli.js
Files: Modify
cli.js(insertfoldersblock beforedisableHelpCommand(program)at ~line 408; add handler functions)Commander subcommands:
folders list,folders show <folder>,folders create,folders edit <folder>,folders delete <folder>,folders reorder,folders join <link>,folders chats add <folder>,folders chats remove <folder>.Handler functions follow same pattern as
runGroupsList/runGroupsInfo:resolveStoreDir→acquireReadLock→createServices→ auth check → call domain method →writeJson/console output → cleanup infinally.Commit:
feat(folders): add CLI subcommands for folder managementTask 3: Add MCP tools in mcp-server.js
Files: Modify
mcp-server.js(add Zod schemas near top ~line 150, register tools near end ~line 1270)9 MCP tools:
listFolders,showFolder,createFolder,editFolder,deleteFolder,reorderFolders,addChatToFolder,removeChatFromFolder,joinChatlist.Pattern follows existing
server.tool(name, description, schema, handler)withawait telegramClient.ensureLogin()and JSON response.Commit:
feat(folders): add MCP tools for folder managementTask 4: Smoke test all commands
Task 5: Update tgcli skill documentation
Add
### Folderssection to skill file: