zalo-agent-cli 1.0.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.
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Account commands — multi-account management with per-account proxy.
3
+ * Includes export for headless/CI credential transfer.
4
+ */
5
+
6
+ import { writeFileSync, chmodSync } from "fs";
7
+ import { resolve } from "path";
8
+ import { loginWithQR, loginWithCredentials, extractCredentials, clearSession } from "../core/zalo-client.js";
9
+ import { saveCredentials, loadCredentials } from "../core/credentials.js";
10
+ import { listAccounts, getActive, setActive, addAccount, removeAccount, getAccount } from "../core/accounts.js";
11
+ import { maskProxy } from "../utils/proxy-helpers.js";
12
+ import { displayQR, getQRPath } from "../utils/qr-display.js";
13
+ import { startQrServer } from "../utils/qr-http-server.js";
14
+ import { success, error, info, warning, output } from "../utils/output.js";
15
+
16
+ export function registerAccountCommands(program) {
17
+ const account = program.command("account").description("Manage multiple Zalo accounts with proxy");
18
+
19
+ account
20
+ .command("list")
21
+ .description("List all registered accounts")
22
+ .action(() => {
23
+ const accounts = listAccounts();
24
+ const safe = accounts.map((a) => ({ ...a, proxy: maskProxy(a.proxy) }));
25
+ output(safe, program.opts().json, () => {
26
+ if (!accounts.length) {
27
+ info("No accounts. Use: zalo-agent account login");
28
+ return;
29
+ }
30
+ console.log(` ${"Active".padEnd(8)} ${"Owner ID".padEnd(22)} ${"Name".padEnd(20)} Proxy`);
31
+ console.log(` ${"─".repeat(75)}`);
32
+ for (const a of accounts) {
33
+ const marker = a.active ? " ★" : " ";
34
+ console.log(
35
+ ` ${marker.padEnd(8)} ${a.ownId.padEnd(22)} ${(a.name || "").padEnd(20)} ${maskProxy(a.proxy)}`,
36
+ );
37
+ }
38
+ });
39
+ });
40
+
41
+ account
42
+ .command("login")
43
+ .description("Login a new Zalo account via QR code")
44
+ .option("-p, --proxy <url>", "Dedicated proxy URL for this account")
45
+ .option("-n, --name <label>", "Friendly label", "")
46
+ .option("--qr-url", "Start local HTTP server to view QR in browser (for VPS/headless)")
47
+ .action(async (opts) => {
48
+ if (opts.proxy) info(`Using proxy: ${maskProxy(opts.proxy)}`);
49
+ info("Generating QR code... Scan with Zalo mobile app.");
50
+
51
+ let qrServer = null;
52
+ try {
53
+ const { ownId } = await loginWithQR(opts.proxy, (event) => {
54
+ displayQR(event);
55
+ if (!qrServer) {
56
+ qrServer = startQrServer(getQRPath());
57
+ }
58
+ });
59
+
60
+ // Fetch display name from Zalo profile
61
+ let displayName = opts.name || "";
62
+ try {
63
+ const { getApi } = await import("../core/zalo-client.js");
64
+ const accountInfo = await getApi().fetchAccountInfo();
65
+ displayName = accountInfo?.profile?.displayName || displayName || ownId;
66
+ } catch {}
67
+
68
+ const creds = extractCredentials();
69
+ saveCredentials(ownId, creds);
70
+ addAccount(ownId, displayName, opts.proxy);
71
+ success(
72
+ `Account logged in: ${displayName} (${ownId})${opts.proxy ? ` via ${maskProxy(opts.proxy)}` : ""}`,
73
+ );
74
+ } catch (e) {
75
+ error(`Login failed: ${e.message}`);
76
+ } finally {
77
+ if (qrServer) qrServer.close();
78
+ }
79
+ });
80
+
81
+ account
82
+ .command("switch <ownerId>")
83
+ .description("Switch active account (restarts connection with account proxy)")
84
+ .action(async (ownerId) => {
85
+ let acc = getAccount(ownerId);
86
+ if (!acc) {
87
+ const all = listAccounts();
88
+ const matches = all.filter((a) => a.ownId.includes(ownerId) || (a.name || "").includes(ownerId));
89
+ if (matches.length === 1) {
90
+ ownerId = matches[0].ownId;
91
+ acc = matches[0];
92
+ } else {
93
+ error(`Account not found: ${ownerId}`);
94
+ return;
95
+ }
96
+ }
97
+
98
+ const creds = loadCredentials(ownerId);
99
+ if (!creds) {
100
+ error(`No credentials for ${ownerId}. Re-login needed.`);
101
+ return;
102
+ }
103
+
104
+ info(`Switching to ${ownerId} (${acc?.name || ""})`);
105
+ if (acc?.proxy) info(`Proxy: ${maskProxy(acc.proxy)}`);
106
+
107
+ clearSession();
108
+ try {
109
+ await loginWithCredentials(creds, acc?.proxy || null);
110
+ setActive(ownerId);
111
+ success(`Switched to ${ownerId}`);
112
+ } catch (e) {
113
+ error(`Switch failed: ${e.message}`);
114
+ }
115
+ });
116
+
117
+ account
118
+ .command("remove <ownerId>")
119
+ .description("Remove account and delete its credentials")
120
+ .action((ownerId) => {
121
+ if (removeAccount(ownerId)) {
122
+ success(`Account ${ownerId} removed`);
123
+ } else {
124
+ error(`Account not found: ${ownerId}`);
125
+ }
126
+ });
127
+
128
+ account
129
+ .command("info")
130
+ .description("Show currently active account")
131
+ .action(() => {
132
+ const active = getActive();
133
+ const safe = active ? { ...active, proxy: maskProxy(active.proxy) } : null;
134
+ output(safe, program.opts().json, () => {
135
+ if (!active) {
136
+ info("No active account.");
137
+ return;
138
+ }
139
+ console.log(` Owner ID: ${active.ownId}`);
140
+ console.log(` Name: ${active.name || "-"}`);
141
+ console.log(` Proxy: ${maskProxy(active.proxy)}`);
142
+ console.log(` Active: yes`);
143
+ });
144
+ });
145
+
146
+ account
147
+ .command("export [ownerId]")
148
+ .description("Export account credentials for transfer to another machine")
149
+ .option("-o, --output <path>", "Output file path", "./zalo-creds.json")
150
+ .action((ownerId, opts) => {
151
+ const acc = ownerId ? getAccount(ownerId) : getActive();
152
+ if (!acc) {
153
+ error("No account found to export.");
154
+ return;
155
+ }
156
+
157
+ const creds = loadCredentials(acc.ownId);
158
+ if (!creds) {
159
+ error(`No credentials for ${acc.ownId}.`);
160
+ return;
161
+ }
162
+
163
+ const exportData = {
164
+ ...creds,
165
+ proxy: acc.proxy || null,
166
+ ownId: acc.ownId,
167
+ name: acc.name || "",
168
+ };
169
+
170
+ const outPath = resolve(opts.output);
171
+ writeFileSync(outPath, JSON.stringify(exportData, null, 2), "utf-8");
172
+ chmodSync(outPath, 0o600);
173
+
174
+ success(`Exported to ${outPath}`);
175
+ warning("This file contains login credentials. Keep it secure and do not commit to git.");
176
+ info(`Import on another machine: zalo-agent login --credentials ${opts.output}`);
177
+ });
178
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Conversation commands — pinned, archived, mute, unmute, read, unread, delete.
3
+ */
4
+
5
+ import { getApi } from "../core/zalo-client.js";
6
+ import { success, error, output } from "../utils/output.js";
7
+
8
+ export function registerConvCommands(program) {
9
+ const conv = program.command("conv").description("Manage conversations");
10
+
11
+ conv.command("pinned")
12
+ .description("List pinned conversations")
13
+ .action(async () => {
14
+ try {
15
+ const result = await getApi().getPinnedConversations();
16
+ output(result, program.opts().json);
17
+ } catch (e) {
18
+ error(e.message);
19
+ }
20
+ });
21
+
22
+ conv.command("archived")
23
+ .description("List archived conversations")
24
+ .action(async () => {
25
+ try {
26
+ const result = await getApi().getArchivedConversations();
27
+ output(result, program.opts().json);
28
+ } catch (e) {
29
+ error(e.message);
30
+ }
31
+ });
32
+
33
+ conv.command("mute <threadId>")
34
+ .description("Mute a conversation")
35
+ .option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
36
+ .option("-d, --duration <secs>", "Duration in seconds (-1 = forever)", "-1")
37
+ .action(async (threadId, opts) => {
38
+ try {
39
+ const result = await getApi().setMute(threadId, Number(opts.type), Number(opts.duration));
40
+ output(result, program.opts().json, () => success("Conversation muted"));
41
+ } catch (e) {
42
+ error(e.message);
43
+ }
44
+ });
45
+
46
+ conv.command("unmute <threadId>")
47
+ .description("Unmute a conversation")
48
+ .option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
49
+ .action(async (threadId, opts) => {
50
+ try {
51
+ const result = await getApi().setMute(threadId, Number(opts.type), 0);
52
+ output(result, program.opts().json, () => success("Conversation unmuted"));
53
+ } catch (e) {
54
+ error(e.message);
55
+ }
56
+ });
57
+
58
+ conv.command("read <threadId>")
59
+ .description("Mark conversation as read")
60
+ .option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
61
+ .action(async (threadId, opts) => {
62
+ try {
63
+ const result = await getApi().sendSeenEvent(threadId, Number(opts.type));
64
+ output(result, program.opts().json, () => success("Marked as read"));
65
+ } catch (e) {
66
+ error(e.message);
67
+ }
68
+ });
69
+
70
+ conv.command("unread <threadId>")
71
+ .description("Mark conversation as unread")
72
+ .option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
73
+ .action(async (threadId, opts) => {
74
+ try {
75
+ const result = await getApi().markAsUnread(threadId, Number(opts.type));
76
+ output(result, program.opts().json, () => success("Marked as unread"));
77
+ } catch (e) {
78
+ error(e.message);
79
+ }
80
+ });
81
+
82
+ conv.command("delete <threadId>")
83
+ .description("Delete conversation history")
84
+ .option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
85
+ .action(async (threadId, opts) => {
86
+ try {
87
+ const result = await getApi().deleteConversation(threadId, Number(opts.type));
88
+ output(result, program.opts().json, () => success("Conversation deleted"));
89
+ } catch (e) {
90
+ error(e.message);
91
+ }
92
+ });
93
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Friend commands — list, find, info, add, accept, remove, block, unblock, last-online, online.
3
+ */
4
+
5
+ import { getApi } from "../core/zalo-client.js";
6
+ import { success, error, info, output } from "../utils/output.js";
7
+
8
+ export function registerFriendCommands(program) {
9
+ const friend = program.command("friend").description("Manage friends and contacts");
10
+
11
+ friend
12
+ .command("list")
13
+ .description("List all friends")
14
+ .action(async () => {
15
+ try {
16
+ const result = await getApi().getAllFriends();
17
+ output(result, program.opts().json, () => {
18
+ const profiles = result?.changed_profiles || result || {};
19
+ const entries = Object.entries(profiles);
20
+ info(`${entries.length} friends`);
21
+ for (const [uid, p] of entries) {
22
+ console.log(` ${uid} ${p.displayName || p.zaloName || "?"}`);
23
+ }
24
+ });
25
+ } catch (e) {
26
+ error(e.message);
27
+ }
28
+ });
29
+
30
+ friend
31
+ .command("online")
32
+ .description("List currently online friends")
33
+ .action(async () => {
34
+ try {
35
+ const result = await getApi().getFriendOnlines();
36
+ output(result, program.opts().json);
37
+ } catch (e) {
38
+ error(e.message);
39
+ }
40
+ });
41
+
42
+ friend
43
+ .command("find <query>")
44
+ .description("Find user by phone number or Zalo ID")
45
+ .action(async (query) => {
46
+ try {
47
+ const result = await getApi().findUser(query);
48
+ output(result, program.opts().json, () => {
49
+ const u = result?.uid ? result : result?.data || result;
50
+ info(`User ID: ${u.uid || "?"}`);
51
+ info(`Name: ${u.displayName || u.zaloName || "?"}`);
52
+ });
53
+ } catch (e) {
54
+ error(e.message);
55
+ }
56
+ });
57
+
58
+ friend
59
+ .command("info <userId>")
60
+ .description("Get user profile information")
61
+ .action(async (userId) => {
62
+ try {
63
+ const result = await getApi().getUserInfo(userId);
64
+ output(result, program.opts().json, () => {
65
+ const profiles = result?.changed_profiles || {};
66
+ const p = profiles[userId] || {};
67
+ info(`Name: ${p.displayName || p.zaloName || "?"}`);
68
+ info(`Phone: ${p.phoneNumber || "?"}`);
69
+ info(`Avatar: ${p.avatar || "?"}`);
70
+ });
71
+ } catch (e) {
72
+ error(e.message);
73
+ }
74
+ });
75
+
76
+ friend
77
+ .command("add <userId>")
78
+ .description("Send a friend request")
79
+ .option("-m, --msg <text>", "Message to include", "")
80
+ .action(async (userId, opts) => {
81
+ try {
82
+ const result = await getApi().sendFriendRequest(userId, opts.msg);
83
+ output(result, program.opts().json, () => success("Friend request sent"));
84
+ } catch (e) {
85
+ error(e.message);
86
+ }
87
+ });
88
+
89
+ friend
90
+ .command("accept <userId>")
91
+ .description("Accept a friend request")
92
+ .action(async (userId) => {
93
+ try {
94
+ const result = await getApi().acceptFriendRequest(userId);
95
+ output(result, program.opts().json, () => success("Friend request accepted"));
96
+ } catch (e) {
97
+ error(e.message);
98
+ }
99
+ });
100
+
101
+ friend
102
+ .command("remove <userId>")
103
+ .description("Remove a friend")
104
+ .action(async (userId) => {
105
+ try {
106
+ const result = await getApi().removeFriend(userId);
107
+ output(result, program.opts().json, () => success("Friend removed"));
108
+ } catch (e) {
109
+ error(e.message);
110
+ }
111
+ });
112
+
113
+ friend
114
+ .command("block <userId>")
115
+ .description("Block a user")
116
+ .action(async (userId) => {
117
+ try {
118
+ const result = await getApi().blockUser(userId);
119
+ output(result, program.opts().json, () => success("User blocked"));
120
+ } catch (e) {
121
+ error(e.message);
122
+ }
123
+ });
124
+
125
+ friend
126
+ .command("unblock <userId>")
127
+ .description("Unblock a user")
128
+ .action(async (userId) => {
129
+ try {
130
+ const result = await getApi().unblockUser(userId);
131
+ output(result, program.opts().json, () => success("User unblocked"));
132
+ } catch (e) {
133
+ error(e.message);
134
+ }
135
+ });
136
+
137
+ friend
138
+ .command("last-online <userId>")
139
+ .description("Check when user was last online")
140
+ .action(async (userId) => {
141
+ try {
142
+ const result = await getApi().getLastOnline(userId);
143
+ output(result, program.opts().json);
144
+ } catch (e) {
145
+ error(e.message);
146
+ }
147
+ });
148
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Group commands — list, create, info, members, add/remove-member, rename, leave, join.
3
+ */
4
+
5
+ import { getApi } from "../core/zalo-client.js";
6
+ import { success, error, info, output } from "../utils/output.js";
7
+
8
+ export function registerGroupCommands(program) {
9
+ const group = program.command("group").description("Manage groups");
10
+
11
+ group
12
+ .command("list")
13
+ .description("List all groups")
14
+ .action(async () => {
15
+ try {
16
+ const result = await getApi().getAllGroups();
17
+ output(result, program.opts().json);
18
+ } catch (e) {
19
+ error(e.message);
20
+ }
21
+ });
22
+
23
+ group
24
+ .command("create <name> <memberIds...>")
25
+ .description("Create a new group")
26
+ .action(async (name, memberIds) => {
27
+ try {
28
+ const result = await getApi().createGroup({ members: memberIds, name });
29
+ output(result, program.opts().json, () => success(`Group "${name}" created`));
30
+ } catch (e) {
31
+ error(e.message);
32
+ }
33
+ });
34
+
35
+ group
36
+ .command("info <groupId>")
37
+ .description("Show group details")
38
+ .action(async (groupId) => {
39
+ try {
40
+ const result = await getApi().getGroupInfo(groupId);
41
+ output(result, program.opts().json);
42
+ } catch (e) {
43
+ error(e.message);
44
+ }
45
+ });
46
+
47
+ group
48
+ .command("members <groupId>")
49
+ .description("List group members")
50
+ .action(async (groupId) => {
51
+ try {
52
+ const result = await getApi().getGroupInfo(groupId);
53
+ const members = result?.gridInfoMap?.[groupId]?.memberIds || {};
54
+ output(members, program.opts().json, () => {
55
+ const ids = Object.keys(members);
56
+ info(`${ids.length} members`);
57
+ ids.forEach((id) => console.log(` ${id}`));
58
+ });
59
+ } catch (e) {
60
+ error(e.message);
61
+ }
62
+ });
63
+
64
+ group
65
+ .command("add-member <groupId> <userIds...>")
66
+ .description("Add members to a group")
67
+ .action(async (groupId, userIds) => {
68
+ try {
69
+ const result = await getApi().addUserToGroup(userIds, groupId);
70
+ output(result, program.opts().json, () => success("Member(s) added"));
71
+ } catch (e) {
72
+ error(e.message);
73
+ }
74
+ });
75
+
76
+ group
77
+ .command("remove-member <groupId> <userIds...>")
78
+ .description("Remove members from a group")
79
+ .action(async (groupId, userIds) => {
80
+ try {
81
+ const result = await getApi().removeUserFromGroup(userIds, groupId);
82
+ output(result, program.opts().json, () => success("Member(s) removed"));
83
+ } catch (e) {
84
+ error(e.message);
85
+ }
86
+ });
87
+
88
+ group
89
+ .command("rename <groupId> <name>")
90
+ .description("Rename a group")
91
+ .action(async (groupId, name) => {
92
+ try {
93
+ const result = await getApi().changeGroupName(groupId, name);
94
+ output(result, program.opts().json, () => success(`Group renamed to "${name}"`));
95
+ } catch (e) {
96
+ error(e.message);
97
+ }
98
+ });
99
+
100
+ group
101
+ .command("leave <groupId>")
102
+ .description("Leave a group")
103
+ .action(async (groupId) => {
104
+ try {
105
+ const result = await getApi().leaveGroup(groupId);
106
+ output(result, program.opts().json, () => success("Left group"));
107
+ } catch (e) {
108
+ error(e.message);
109
+ }
110
+ });
111
+
112
+ group
113
+ .command("join <link>")
114
+ .description("Join a group via invite link")
115
+ .action(async (link) => {
116
+ try {
117
+ const result = await getApi().joinGroup(link);
118
+ output(result, program.opts().json, () => success("Joined group"));
119
+ } catch (e) {
120
+ error(e.message);
121
+ }
122
+ });
123
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Login commands — QR login, credential login, logout, status, whoami.
3
+ */
4
+
5
+ import { readFileSync, unlinkSync } from "fs";
6
+ import {
7
+ loginWithQR,
8
+ loginWithCredentials,
9
+ extractCredentials,
10
+ clearSession,
11
+ isLoggedIn,
12
+ getApi,
13
+ getOwnId,
14
+ } from "../core/zalo-client.js";
15
+ import { saveCredentials, deleteCredentials } from "../core/credentials.js";
16
+ import { addAccount, getActive, removeAccount } from "../core/accounts.js";
17
+ import { maskProxy } from "../utils/proxy-helpers.js";
18
+ import { displayQR, getQRPath } from "../utils/qr-display.js";
19
+ import { startQrServer } from "../utils/qr-http-server.js";
20
+ import { success, error, info, output } from "../utils/output.js";
21
+
22
+ export function registerLoginCommands(program) {
23
+ program
24
+ .command("login")
25
+ .description("Login to Zalo via QR code scan or from exported credentials")
26
+ .option("-p, --proxy <url>", "Proxy URL (http/https/socks5://[user:pass@]host:port)")
27
+ .option("-n, --name <label>", "Friendly name for this account", "")
28
+ .option("--qr-url", "Start local HTTP server to view QR in browser (for VPS/headless)")
29
+ .option("--credentials <path>", "Login from exported credentials file (skip QR)")
30
+ .action(async (opts) => {
31
+ // Credential-based login (headless/CI)
32
+ if (opts.credentials) {
33
+ try {
34
+ const raw = JSON.parse(readFileSync(opts.credentials, "utf-8"));
35
+ const proxy = opts.proxy || raw.proxy || null;
36
+ if (proxy) info(`Using proxy: ${maskProxy(proxy)}`);
37
+
38
+ const { ownId } = await loginWithCredentials(raw, proxy);
39
+
40
+ let displayName = opts.name || raw.name || "";
41
+ try {
42
+ const accountInfo = await getApi().fetchAccountInfo();
43
+ displayName = accountInfo?.profile?.displayName || displayName || ownId;
44
+ } catch {}
45
+
46
+ const creds = extractCredentials();
47
+ saveCredentials(ownId, creds);
48
+ addAccount(ownId, displayName, proxy);
49
+ success(`Logged in as ${displayName} (${ownId})`);
50
+ } catch (e) {
51
+ error(`Login from credentials failed: ${e.message}`);
52
+ process.exit(1);
53
+ }
54
+ return;
55
+ }
56
+
57
+ // QR-based login
58
+ if (opts.proxy) info(`Using proxy: ${maskProxy(opts.proxy)}`);
59
+ info("Generating QR code... Scan with Zalo mobile app.");
60
+
61
+ let qrServer = null;
62
+ try {
63
+ const { ownId } = await loginWithQR(opts.proxy, (event) => {
64
+ displayQR(event);
65
+
66
+ // Always start HTTP server for QR scanning (no flag needed)
67
+ if (!qrServer) {
68
+ qrServer = startQrServer(getQRPath());
69
+ }
70
+ });
71
+
72
+ // Fetch display name from Zalo profile
73
+ let displayName = opts.name || "";
74
+ try {
75
+ const accountInfo = await getApi().fetchAccountInfo();
76
+ displayName = accountInfo?.profile?.displayName || displayName || ownId;
77
+ } catch {}
78
+
79
+ const creds = extractCredentials();
80
+ saveCredentials(ownId, creds);
81
+ addAccount(ownId, displayName, opts.proxy);
82
+ success(`Logged in as ${displayName} (${ownId})`);
83
+ } catch (e) {
84
+ error(`Login failed: ${e.message}`);
85
+ process.exit(1);
86
+ } finally {
87
+ if (qrServer) qrServer.close();
88
+ }
89
+ });
90
+
91
+ program
92
+ .command("logout")
93
+ .description("Logout current account (keeps credentials for auto-login)")
94
+ .option("--purge", "Also delete saved credentials (must QR login again)")
95
+ .action((opts) => {
96
+ const active = getActive();
97
+ clearSession();
98
+
99
+ if (opts.purge && active) {
100
+ deleteCredentials(active.ownId);
101
+ removeAccount(active.ownId);
102
+ // Also remove QR image
103
+ try {
104
+ unlinkSync(getQRPath());
105
+ } catch {}
106
+ success(`Logged out and purged credentials for ${active.name || active.ownId}`);
107
+ } else {
108
+ success("Logged out (credentials kept — will auto-login on next command)");
109
+ if (active) info(`To fully remove: zalo-agent account remove ${active.ownId}`);
110
+ }
111
+ });
112
+
113
+ program
114
+ .command("status")
115
+ .description("Show current login status")
116
+ .action(() => {
117
+ const active = getActive();
118
+ const data = {
119
+ loggedIn: isLoggedIn(),
120
+ ownId: getOwnId(),
121
+ activeAccount: active
122
+ ? { ownId: active.ownId, name: active.name, proxy: maskProxy(active.proxy) }
123
+ : null,
124
+ };
125
+ output(data, program.opts().json, () => {
126
+ if (data.loggedIn) {
127
+ success(`Logged in as ${data.ownId}`);
128
+ if (active) info(`Account: ${active.name || active.ownId} | Proxy: ${maskProxy(active.proxy)}`);
129
+ } else {
130
+ info("Not logged in");
131
+ if (active)
132
+ info(`Active account: ${active.name || active.ownId} (will auto-login on next command)`);
133
+ }
134
+ });
135
+ });
136
+
137
+ program
138
+ .command("whoami")
139
+ .description("Show current user profile")
140
+ .action(async () => {
141
+ try {
142
+ const api = getApi();
143
+ const accountInfo = await api.fetchAccountInfo();
144
+ output(accountInfo, program.opts().json, () => {
145
+ const p = accountInfo?.profile || {};
146
+ info(`Name: ${p.displayName || "?"}`);
147
+ info(`ID: ${p.userId || getOwnId()}`);
148
+ info(`Phone: ${p.phoneNumber || "?"}`);
149
+ });
150
+ } catch (e) {
151
+ error(e.message);
152
+ }
153
+ });
154
+ }