wldx-cli 0.1.1 → 1.1.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/README.md +281 -37
- package/package.json +9 -6
- package/src/chat/format.mjs +129 -0
- package/src/chat/lib.mjs +210 -0
- package/src/chat/watch.mjs +282 -0
- package/src/cli.mjs +13 -132
- package/src/commands/calls.mjs +470 -0
- package/src/commands/channel.mjs +404 -0
- package/src/commands/chat.mjs +444 -61
- package/src/commands/conversations.mjs +368 -0
- package/src/commands/file.mjs +80 -0
- package/src/commands/inbox.mjs +108 -0
- package/src/commands/index.mjs +33 -12
- package/src/commands/live.mjs +462 -0
- package/src/commands/meetings.mjs +386 -0
- package/src/commands/phone.mjs +564 -0
- package/src/commands/reports.mjs +606 -0
- package/src/commands/section.mjs +101 -0
- package/src/commands/user.mjs +159 -0
- package/src/history/text.mjs +86 -0
- package/src/history/wda.mjs +171 -0
- package/src/live/socket.mjs +139 -0
- package/src/live/stats.mjs +243 -0
- package/src/live/text.mjs +318 -0
- package/src/live/views.mjs +391 -0
- package/src/phone/control.mjs +437 -0
- package/src/phone/events.mjs +220 -0
- package/src/phone/pbx.mjs +158 -0
- package/src/phone/sip.mjs +386 -0
- package/src/phone/xml.mjs +57 -0
- package/src/stream.mjs +12 -14
- package/src/version.mjs +0 -1
- package/src/auth.mjs +0 -128
- package/src/commands/auth.mjs +0 -32
- package/src/completion.mjs +0 -105
- package/src/errors.mjs +0 -30
- package/src/http.mjs +0 -47
package/README.md
CHANGED
|
@@ -1,34 +1,257 @@
|
|
|
1
1
|
# wldx-cli — `wx`, a command line for Wildix
|
|
2
2
|
|
|
3
|
-
**Version
|
|
3
|
+
**Version 1.1.0** · unofficial · MIT
|
|
4
4
|
|
|
5
|
-
`wx` works with Wildix as **the logged-in user**:
|
|
6
|
-
|
|
5
|
+
`wx` works with Wildix as **the logged-in user**: chats, calls and meetings on record, queue reports, what is going
|
|
6
|
+
on now, and your phone. It is the command-line twin of the
|
|
7
|
+
[Wildix agent skills](https://github.com/gorets/wildix-skills) and shares their login.
|
|
7
8
|
|
|
8
9
|
```sh
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
npm i -g wldx-cli # the command is `wx`
|
|
11
|
+
wx login you@company.com # a code comes by e-mail
|
|
12
|
+
wx status
|
|
13
|
+
wx chat list
|
|
14
|
+
|
|
15
|
+
npx wldx-cli chat list # without installing
|
|
11
16
|
```
|
|
12
17
|
|
|
13
|
-
Requires Node.js 22 or newer.
|
|
18
|
+
Requires Node.js 22 or newer. One dependency: [`wldx-core`](https://github.com/gorets/wildix-cli/tree/main/packages/core), the core it shares with `wx-admin`, from the
|
|
19
|
+
same repository and of the same version.
|
|
20
|
+
|
|
21
|
+
Unofficial: not made or supported by Wildix. It uses the same internal APIs as the Wildix apps, which may change.
|
|
14
22
|
|
|
15
23
|
## Commands
|
|
16
24
|
|
|
25
|
+
Every command has `-h`, takes `--profile EMAIL` (which login to use, see [Profiles](#profiles)), and prints text or, with `--output json`, JSON. `wx <group> -h`
|
|
26
|
+
lists the commands of a group.
|
|
27
|
+
|
|
28
|
+
<!-- commands:start (generated by tools/readme-commands.mjs) -->
|
|
29
|
+
### wx chat
|
|
30
|
+
|
|
31
|
+
Chats: channels, messages, reactions, mentions, search, the live feed.
|
|
32
|
+
|
|
17
33
|
| Command | What it does |
|
|
18
34
|
| --- | --- |
|
|
19
35
|
| `wx chat list [--filter unread\|archived] [--section ID] [--cursor C]` | your channels, newest activity first |
|
|
20
36
|
| `wx chat read <channel> [--unread] [--from SEQ] [--to SEQ]` | messages of a channel, newest first, 30 per page |
|
|
21
|
-
| `wx chat send <channel> <text\|-> [--markdown] [--reply-to MESSAGE]
|
|
22
|
-
| `wx
|
|
37
|
+
| `wx chat send <channel> <text\|-> [--markdown] [--reply-to MESSAGE] [--mention USERID]... [--file PATH]...` | send a message to a channel ("-" reads the text from stdin) |
|
|
38
|
+
| `wx chat get <channel> <message>` | one message |
|
|
39
|
+
| `wx chat edit <channel> <message> <text\|-> [--markdown]` | edit your own message ("-" reads the new text from stdin) |
|
|
40
|
+
| `wx chat delete <channel> <message> --yes` | delete your own message, for everyone (needs --yes) |
|
|
41
|
+
| `wx chat pin <channel> <message>` | pin a message in its channel (clients then show it as edited) |
|
|
42
|
+
| `wx chat unpin <channel> <message>` | unpin a message |
|
|
43
|
+
| `wx chat react <channel> <message> <type>` | react to a message (eyes, white_check_mark, thumbsup, heart…) |
|
|
44
|
+
| `wx chat unreact <channel> <message> <type>` | take your reaction back |
|
|
45
|
+
| `wx chat reactions <channel> <message> [--type TYPE] [--users]` | who reacted to a message: one entry per person and type |
|
|
46
|
+
| `wx chat mentions [--unread] [--cursor C]` | messages that mention you |
|
|
47
|
+
| `wx chat search <query> [--channel C] [--sender USERID]... [--attachments] [--links] [--size 1-50] [--cursor C] [--sort relevance\|recency\|recency_asc]` | search messages in your channels |
|
|
48
|
+
| `wx chat watch [--channel C]... [--from USERID]... [--type TYPE]... [--mine] [--count N] [--for SECONDS] [--raw]` | follow your chats live: messages, reactions, reads, members, channel changes |
|
|
49
|
+
|
|
50
|
+
### wx channel
|
|
51
|
+
|
|
52
|
+
Chat channels: get, search, create, 1:1, update, join, leave, delete, members.
|
|
53
|
+
|
|
54
|
+
| Command | What it does |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `wx channel get <channel>` | one channel |
|
|
57
|
+
| `wx channel query <channel>...` | several channels by id (not implemented by the service yet) |
|
|
58
|
+
| `wx channel search <query> [--type direct\|group] [--member USERID] [--size 1-50] [--cursor C] [--sort relevance\|recency\|recency_asc]` | search your channels by subject or member |
|
|
59
|
+
| `wx channel create --subject S [--description D] [--access private\|public] [--member USERID]... [--invite EMAIL]... [--id 8-4-4-4-8] [--auto-transcribe \| --no-auto-transcribe]` | create a group channel |
|
|
60
|
+
| `wx channel direct (--user USERID \| --email EMAIL)` | the 1:1 channel with a person (created when it does not exist yet) |
|
|
61
|
+
| `wx channel direct-id (--user USERID \| --email EMAIL)` | only the id of the 1:1 channel with a person (nothing is created) |
|
|
62
|
+
| `wx channel update <channel> [--subject S] [--description D] [--access private\|public] [--unset FIELD]...` | change the subject, description or access of a channel (not implemented by the service yet) |
|
|
63
|
+
| `wx channel join <channel>` | join a public channel (not implemented by the service yet) |
|
|
64
|
+
| `wx channel leave <channel>` | leave a channel |
|
|
65
|
+
| `wx channel delete <channel> --yes` | delete a channel for everyone (needs --yes) |
|
|
66
|
+
| `wx channel members <channel> [--limit 1-100] [--offset 0-1000]` | members of a channel |
|
|
67
|
+
| `wx channel member <channel> <userId>` | one member of a channel (not implemented by the service yet) |
|
|
68
|
+
| `wx channel add <channel> (--user USERID \| --invite EMAIL)... [--hide-history]` | add people to a channel |
|
|
69
|
+
| `wx channel remove <channel> <userId>` | remove a person from a channel |
|
|
70
|
+
| `wx channel read-status <channel>` | who has read a channel up to where (channels with up to 100 members) |
|
|
71
|
+
|
|
72
|
+
### wx inbox
|
|
73
|
+
|
|
74
|
+
Your inbox: unread, mark read or unread, mute, pin, hide (only for you).
|
|
75
|
+
|
|
76
|
+
| Command | What it does |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| `wx inbox get` | your inbox: recent channels with your state, and the counters of the sections |
|
|
79
|
+
| `wx inbox mark-read <channel> [--message MESSAGE]` | mark a channel read, up to the latest message (or up to a message) |
|
|
80
|
+
| `wx inbox mark-unread <channel> [--message MESSAGE] [--force]` | mark a channel unread from a message on |
|
|
81
|
+
| `wx inbox mute <channel> [--until ISO8601]` | mute a channel (no notifications), for good or until a time |
|
|
82
|
+
| `wx inbox unmute <channel>` | unmute a channel |
|
|
83
|
+
| `wx inbox pin <channel>` | pin a channel at the top of your inbox |
|
|
84
|
+
| `wx inbox unpin <channel>` | unpin a channel |
|
|
85
|
+
| `wx inbox hide <channel>` | archive a channel (hide it from your inbox) |
|
|
86
|
+
| `wx inbox show <channel>` | restore an archived channel |
|
|
87
|
+
|
|
88
|
+
### wx file
|
|
89
|
+
|
|
90
|
+
Files in chat channels: upload, info, download.
|
|
91
|
+
|
|
92
|
+
| Command | What it does |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| `wx file upload <channel> <path>` | upload a file to a channel and print the attachment object (to put into a message) |
|
|
95
|
+
| `wx file info <channel> <fsId>` | name, type and size of an uploaded file |
|
|
96
|
+
| `wx file download-url <channel> <message> <fsId>` | a temporary download link of a file of a message |
|
|
97
|
+
| `wx file download <channel> <message> <fsId> --to PATH` | save a file of a message (never overwrites a file) |
|
|
98
|
+
|
|
99
|
+
### wx user
|
|
100
|
+
|
|
101
|
+
People: you, a colleague by name, e-mail or phone.
|
|
102
|
+
|
|
103
|
+
| Command | What it does |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `wx user me` | the logged-in user: id, name, e-mail, extension |
|
|
106
|
+
| `wx user find <query> [--mode auto\|name\|email\|phone] [--kind human\|bot\|all] [--with-extension] [--min-confidence 0..1] [--brief] [--size 1-50] [--cursor C]` | find a colleague by name (any script, typos, as it sounds), e-mail or phone |
|
|
107
|
+
| `wx user get [<id\|email\|phone>...] [--id USERID]... [--email EMAIL]... [--phone PHONE]...` | people by user id, e-mail or phone (exact lookup) |
|
|
108
|
+
|
|
109
|
+
### wx section
|
|
110
|
+
|
|
111
|
+
Your inbox sections (not implemented by the service yet, except list).
|
|
112
|
+
|
|
113
|
+
| Command | What it does |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| `wx section list` | your inbox sections with their counters |
|
|
116
|
+
| `wx section create <sectionId> <name>` | create a section (not implemented by the service yet) |
|
|
117
|
+
| `wx section rename <sectionId> <name>` | rename a section (not implemented by the service yet) |
|
|
118
|
+
| `wx section delete <sectionId>` | delete a section (not implemented by the service yet) |
|
|
119
|
+
| `wx section add <channel> <sectionId>` | put a channel into a section (not implemented by the service yet) |
|
|
120
|
+
| `wx section remove <channel> <sectionId>` | take a channel out of a section (not implemented by the service yet) |
|
|
121
|
+
|
|
122
|
+
### wx calls
|
|
123
|
+
|
|
124
|
+
Your call history and calls after the fact: record, transcript, summary, insights.
|
|
125
|
+
|
|
126
|
+
| Command | What it does |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| `wx calls list [--direction INTERNAL\|INBOUND\|OUTBOUND] [--status COMPLETED\|MISSED] [--flag FAX\|VOICEMAIL]... [--from ISO] [--to ISO] [--search TEXT] [--tag TAG]... [--has-attachment] [--user-direction INCOMING\|OUTGOING] [--limit 1-100] [--offset N] [--brief]` | your own call history, newest first, 50 per page |
|
|
129
|
+
| `wx calls missed [the options of list, without --status]` | your missed calls, newest first (list --status MISSED) |
|
|
130
|
+
| `wx calls get <callId> [flow]` | the record of a call: who called whom, times, queue, trunk, recordings |
|
|
131
|
+
| `wx calls brief <callId> [flow]` | a call as one readable object: record, summary, insights |
|
|
132
|
+
| `wx calls transcript <callId> [flow] [--to FILE] [--structured]` | the dialogue of a call (exit 3 when there is none) |
|
|
133
|
+
| `wx calls annotations <callId> [flow]` | the annotations of a call (404 when there are none) |
|
|
134
|
+
| `wx calls insights <callId> [flow] [--raw]` | the AI insights that were generated: {projection: {field: value}} |
|
|
135
|
+
| `wx calls summary <callId> [flow]` | the AI title, brief and the structured summary of a call |
|
|
136
|
+
| `wx calls characteristics <callId> [flow]` | talk time, interruptions, sentiment and talk speed of a call |
|
|
137
|
+
| `wx calls state <callId> [flow]` | your own state of a call: playlists, playback position |
|
|
138
|
+
| `wx calls recordings <callId> [flow]` | the recordings of a call: file names and their links on the PBX |
|
|
139
|
+
| `wx calls schedule-summary <callId> [flow]` | ask the service to generate the summary of a call again (only when asked: it does work) |
|
|
140
|
+
|
|
141
|
+
### wx meetings
|
|
142
|
+
|
|
143
|
+
Meetings (conferences) after the fact: record, transcript, summary, insights, recording.
|
|
144
|
+
|
|
145
|
+
| Command | What it does |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| `wx meetings get <conference>` | the record of a meeting: subject, times, participants, recordings, transcription status |
|
|
148
|
+
| `wx meetings brief <conference>...` | a meeting as one readable card: record, participants, summary, insights (several: oldest first) |
|
|
149
|
+
| `wx meetings transcript <conference> [--to FILE] [--structured]` | the dialogue of a meeting with a header (exit 3 when there is none) |
|
|
150
|
+
| `wx meetings annotations <conference>` | the annotations of a meeting (404 when there are none) |
|
|
151
|
+
| `wx meetings insights <conference> [--raw]` | the AI insights that were generated: {projection: {field: value}} |
|
|
152
|
+
| `wx meetings summary <conference>` | the AI title, brief and the structured summary of a meeting |
|
|
153
|
+
| `wx meetings state <conference>` | your own state of a meeting: playlists, playback position |
|
|
154
|
+
| `wx meetings timeframes <conference>` | who spoke when in a meeting |
|
|
155
|
+
| `wx meetings recording-url --file-id ID --category channels\|recordings\|wizyconfRecordings [--download]` | a link to download or play a recording of a meeting |
|
|
156
|
+
| `wx meetings schedule-summary <conference>` | ask the service to generate the summary of a meeting again (only when asked: it does work) |
|
|
157
|
+
|
|
158
|
+
### wx conversations
|
|
159
|
+
|
|
160
|
+
Search calls, meetings and chats with their AI summary; counts and statistics.
|
|
161
|
+
|
|
162
|
+
| Command | What it does |
|
|
163
|
+
| --- | --- |
|
|
164
|
+
| `wx conversations find [--entity call\|conference\|chat]... [--email E]... [--phone P]... [--extension X]... [--everyone] [--transcribed] [--recorded] [--direction inbound\|outbound\|internal] [--from ISO --to ISO [--tz ZONE]] [--when PERIOD] [--tag T]... [--language L]... [--cursor KEY] [--brief]` | the search over conversations with the summary of each, 10 per page |
|
|
165
|
+
| `wx conversations count [the filter of find, without --cursor and --brief]` | how many conversations match the filter |
|
|
166
|
+
| `wx conversations aggregate (--by DIM[,DIM...] \| --by DIM --then DIM [--then DIM] \| --projections) [the filter of find] [--tz ZONE] [--top N] [--raw]` | how many conversations fall into each value of a dimension (the data of a chart) |
|
|
167
|
+
| `wx conversations tags <prefix> [--limit 1-20]` | the tags that start with a prefix |
|
|
168
|
+
|
|
169
|
+
### wx reports
|
|
170
|
+
|
|
171
|
+
Reports of the call queues over days: service level, abandoned, lost, waits, agents.
|
|
172
|
+
|
|
173
|
+
| Command | What it does |
|
|
174
|
+
| --- | --- |
|
|
175
|
+
| `wx reports list [--name TEXT] [--pbx SERIAL]` | the queues of the company: id, name, PBX (and how many on each PBX) |
|
|
176
|
+
| `wx reports summary [filter]` | every queue call of the filter: offered, answered, abandoned, lost, waits, talk, ring, the SLA counts |
|
|
177
|
+
| `wx reports by-queue [filter]` | the same figures per queue |
|
|
178
|
+
| `wx reports by-period --by hour\|day\|week\|month\|weekday [filter]` | the same figures as a series (hour: the 24 hours of the day over the window) |
|
|
179
|
+
| `wx reports answered [--by queue\|agent\|user\|reason\|talk-time] [--band S --steps N] [filter]` | the answered calls: summary, or grouped by queue, agent, user, reason or talk time |
|
|
180
|
+
| `wx reports unanswered [--by queue\|reason] [filter]` | the abandoned and unanswered calls: summary (lost, overflow), or grouped by queue or reason |
|
|
181
|
+
| `wx reports service-level answered\|unanswered [--band S] [--steps N] [filter]` | how many calls were answered (or given up) within 20, 40, … seconds |
|
|
182
|
+
| `wx reports calls [--limit N] [--pages N] [--cursor C] [--call CALLID] [--full] [filter]` | the queue calls one by one, newest first: queue, times, wait, talk, outcome, who answered |
|
|
183
|
+
| `wx reports events <callId> <flowIndex> [--queue ID] [--full]` | one call in one queue: its row and its events (INIT, RING, CANCEL, ANSWER, END) with the agents rung |
|
|
184
|
+
|
|
185
|
+
### wx live
|
|
186
|
+
|
|
187
|
+
What is going on now: queues and agents, calls and chats in progress, presence, live transcripts.
|
|
188
|
+
|
|
189
|
+
| Command | What it does |
|
|
190
|
+
| --- | --- |
|
|
191
|
+
| `wx live queues [--name TEXT] [--pbx SERIAL] [--busy] [--paused] [--active] [--stats] [--window W] [--sla S] [--full]` | the call queues of the company: strategy, calls in them now, agents with their status (Premium) |
|
|
192
|
+
| `wx live conversations [--calls] [--chats] [--conferences] [--mine] [--status S] [--limit N] [--full]` | the calls, customer chats and conferences going on now, newest first, with their live insights (Premium) |
|
|
193
|
+
| `wx live presence <extension>... [--full]` | status, status message, "until" and phone registration of colleagues (is someone available) |
|
|
194
|
+
| `wx live transcript <callId\|conference:ID> [--follow] [--for SECONDS] [--full]` | the live transcript of a call or conference in progress; --follow prints each new line until it ends |
|
|
195
|
+
| `wx live watch [--conversations] [--calls] [--mine] [--queues] [--stats] [--window W] [--sla S] [--presence EXT]... [--for SECONDS] [--full]` | a live feed: one line per change of the conversations, queues or presence until stopped |
|
|
196
|
+
|
|
197
|
+
### wx phone
|
|
198
|
+
|
|
199
|
+
Live calls on your PBX: place, answer, hold, transfer, DTMF, record; your devices; watch live.
|
|
200
|
+
|
|
201
|
+
| Command | What it does |
|
|
202
|
+
| --- | --- |
|
|
203
|
+
| `wx phone me` | who the calls are made as: extension, name, e-mail, role, PBX (from the login, no request) |
|
|
204
|
+
| `wx phone list [--sip]` | your current calls: sipCallId, numbers, names, state, seconds (--sip: as SIP sees them) |
|
|
205
|
+
| `wx phone call <number> [--mobile \| --any \| --device CONTACT]` | place a call from your device: it rings first and dials when you pick up, or starts at once on --device |
|
|
206
|
+
| `wx phone answer <callId> [--device CONTACT]` | answer a ringing call (on a device that can answer remotely: a Wildix phone, x-bees) |
|
|
207
|
+
| `wx phone hold <callId>` | put a call on hold |
|
|
208
|
+
| `wx phone unhold <callId>` | resume a held call |
|
|
209
|
+
| `wx phone hangup <callId>` | hang up a call |
|
|
210
|
+
| `wx phone transfer <callId> <number>` | blind transfer: the call goes to <number> and leaves you |
|
|
211
|
+
| `wx phone attended-transfer <callId> <otherCallId>` | join a call to another current call of yours (consultation done), then leave both |
|
|
212
|
+
| `wx phone dtmf <callId> <digits>` | send DTMF tones: 0-9, * and # (longer sequences in parts of 10) |
|
|
213
|
+
| `wx phone show-as <callId> [--name NAME] [--number NUMBER]` | change the name and/or number your own phone shows for the other party (they see nothing) |
|
|
214
|
+
| `wx phone devices` | your registered devices: contact, user agent, which one is active (the SIP watcher marked) |
|
|
215
|
+
| `wx phone use <contact> \| --mobile \| --any` | choose what rings and places your calls: a registered device, the mobile, or all devices |
|
|
216
|
+
| `wx phone watch [--calls-only] [--for SECONDS]` | follow your calls, devices, presence and ringing mode live, one line per event, until stopped |
|
|
217
|
+
| `wx phone record <callId> on\|off\|pause\|unpause` | start, stop, pause or resume the recording of a call (SIP; needs your right to record) |
|
|
218
|
+
| `wx phone conference-transfer <callId> <number>` | join the call and <number> in a conference; when you hang up the other two stay (SIP) |
|
|
219
|
+
|
|
220
|
+
### wx profile
|
|
221
|
+
|
|
222
|
+
Your profiles (the e-mails you are logged in with) and the default one.
|
|
223
|
+
|
|
224
|
+
| Command | What it does |
|
|
225
|
+
| --- | --- |
|
|
226
|
+
| `wx profile list` | your profiles; * marks the default one |
|
|
227
|
+
| `wx profile use <e-mail>` | make a profile the default one (used when no --profile is given) |
|
|
228
|
+
|
|
229
|
+
### Login and profiles
|
|
230
|
+
|
|
231
|
+
| Command | What it does |
|
|
232
|
+
| --- | --- |
|
|
233
|
+
| `wx login [<e-mail>] [--send-code \| --code CODE]` | log in with a one-time code sent to your e-mail |
|
|
234
|
+
| `wx logout [--profile EMAIL] [--everywhere]` | log out: revoke the session of this device, or of every device |
|
|
235
|
+
| `wx status [--profile EMAIL] [--check]` | who you are logged in as, until when, and the version of wx |
|
|
23
236
|
| `wx completion [bash\|zsh\|fish]` | the shell completion script (default: the shell of `$SHELL`) |
|
|
237
|
+
<!-- commands:end -->
|
|
24
238
|
|
|
25
|
-
A channel is an id or an inbox link; a message is an id or an inbox link to it
|
|
26
|
-
|
|
239
|
+
A channel is an id or an inbox link; a message is an id or an inbox link to it; a meeting is an id or an x-bees
|
|
240
|
+
link. Commands that reach other people (send, call, transfer, invite) do only what their arguments say; those that
|
|
241
|
+
destroy something for everyone need `--yes`. The service does not implement some chat operations yet: their help
|
|
242
|
+
says so.
|
|
243
|
+
|
|
244
|
+
## Output
|
|
245
|
+
|
|
246
|
+
As in the internal Wildix CLI: **text** for people by default (one line per item, local times), **`--output json`**
|
|
247
|
+
for scripts and agents (the data of the service; `--pretty` indents it), `--no-color` for plain text. These options
|
|
248
|
+
may stand anywhere: `wx --output json chat list` or `wx chat list --output json | jq`. Streaming commands (`watch`)
|
|
249
|
+
print one line per event: text, or one JSON object per line. Messages for people (progress, prompts) go to stderr.
|
|
27
250
|
|
|
28
251
|
## Completion
|
|
29
252
|
|
|
30
|
-
Tab completes commands, options and option values (`--filter unread|archived`, `--
|
|
31
|
-
|
|
253
|
+
Tab completes commands, options and option values (`--filter unread|archived`, `--profile` from your
|
|
254
|
+
profiles); zsh and fish show a description next to each. Turn it on once, then open a new terminal:
|
|
32
255
|
|
|
33
256
|
```sh
|
|
34
257
|
echo 'eval "$(wx completion zsh)"' >> ~/.zshrc # zsh (after compinit, if your .zshrc runs it)
|
|
@@ -41,54 +264,75 @@ candidates on every Tab, so it follows new commands without being regenerated; i
|
|
|
41
264
|
|
|
42
265
|
## Login
|
|
43
266
|
|
|
44
|
-
`wx
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
267
|
+
`wx login you@company.com` sends a one-time code to the e-mail and asks for it. Without a terminal (scripts,
|
|
268
|
+
agents), sign in in two steps: `wx login you@company.com --send-code`, then within 3 minutes
|
|
269
|
+
`wx login you@company.com --code 123456`.
|
|
270
|
+
|
|
271
|
+
The session is one file per profile (e-mail) in `~/.wildix/tokens` (`$WILDIX_CONFIG_DIR/tokens`), the same as the
|
|
272
|
+
`wildix-auth` agent skill uses: log in with either, both work. An expired token is renewed and written back in the
|
|
273
|
+
same format. `wx logout` revokes the session of this device; `--everywhere` signs you out of every device and
|
|
274
|
+
app.
|
|
275
|
+
|
|
276
|
+
## Profiles
|
|
277
|
+
|
|
278
|
+
Each e-mail you are logged in with is a profile. Which one a command uses:
|
|
279
|
+
|
|
280
|
+
1. `--profile EMAIL` (anywhere on the command line),
|
|
281
|
+
2. else `WILDIX_EMAIL`,
|
|
282
|
+
3. else the default profile,
|
|
283
|
+
4. else the only one logged in.
|
|
284
|
+
|
|
285
|
+
The first `wx login` makes its profile the default; logging in with another e-mail does not change it.
|
|
286
|
+
`wx profile list` shows them (`*` is the default), `wx profile use EMAIL` changes the default, and `wx status` says
|
|
287
|
+
which profile it used and why. The default is kept in `~/.wildix/wx.json`.
|
|
48
288
|
|
|
49
289
|
## Environment
|
|
50
290
|
|
|
51
291
|
| Variable | Meaning |
|
|
52
292
|
| --- | --- |
|
|
53
|
-
| `WILDIX_EMAIL` | the
|
|
293
|
+
| `WILDIX_EMAIL` | the profile to use, before the default one |
|
|
54
294
|
| `WILDIX_CONFIG_DIR` | where the sessions are (default `~/.wildix`) |
|
|
55
295
|
| `WILDIX_ID_TOKEN` | a token to use as is, instead of a saved session (CI, tests) |
|
|
56
296
|
| `WILDIX_STREAM_REGION` | the chat region (default `eu-1`) |
|
|
57
|
-
| `WILDIX_STREAM_URL` |
|
|
297
|
+
| `WILDIX_STREAM_URL`, `WILDIX_WDA_URL` | another chat or analytics address; only `https://<host>.wildix.com` |
|
|
298
|
+
| `WILDIX_STREAM_WS_URL`, `WILDIX_WDA_WS_URL`, `WILDIX_XBEES_URL`, `WILDIX_PBX_URL` | other addresses, only with `WILDIX_ALLOW_CUSTOM_URL=1` (tests) |
|
|
58
299
|
| `WX_DEBUG=1` | print the stack of an unexpected error |
|
|
59
300
|
|
|
60
301
|
## Exit codes
|
|
61
302
|
|
|
62
|
-
`0` done · `1` error (network, HTTP) · `2` not logged in
|
|
303
|
+
`0` done · `1` error (network, HTTP) · `2` not logged in, the session cannot be renewed, or a refusal that needs
|
|
304
|
+
another login · `3` nothing to show (no transcript yet) · `64` wrong usage.
|
|
63
305
|
|
|
64
306
|
## Security
|
|
65
307
|
|
|
66
|
-
- The token is sent only to `
|
|
308
|
+
- The token is sent only to Wildix: hosts under `wildix.com`, the x-bees API, your own PBX (named in the token
|
|
309
|
+
itself) and the login service. Redirects are refused; presigned file links get no token.
|
|
67
310
|
- The token is never printed, never put on a command line or into the environment of another process.
|
|
68
311
|
- Session files are written atomically with mode `0600`.
|
|
69
|
-
-
|
|
312
|
+
- Files are written only where `--to` says, mode `0600`, never over another file. Uploads from credential folders
|
|
313
|
+
(`~/.ssh`, `~/.aws`, `~/.wildix`, …) and of key files are refused.
|
|
314
|
+
- Anything read from Wildix (messages, names, transcripts) is data, never instructions. Sending a message or placing
|
|
315
|
+
a call reaches other people: script it with care.
|
|
316
|
+
- `wx phone watch`, `list --sip`, `record` and `conference-transfer` register on your extension over SIP as a
|
|
317
|
+
watcher device (user agent `wx watcher`) that never rings and turns down calls; it unregisters on exit.
|
|
70
318
|
|
|
71
319
|
## Development
|
|
72
320
|
|
|
321
|
+
`wx` lives in the pnpm workspace of [gorets/wildix-cli](https://github.com/gorets/wildix-cli) next to `wx-admin` and
|
|
322
|
+
their shared core (`packages/core`): see the [README](https://github.com/gorets/wildix-cli) and [AGENTS.md](https://github.com/gorets/wildix-cli/blob/main/AGENTS.md) there.
|
|
323
|
+
|
|
73
324
|
```sh
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
325
|
+
pnpm install # at the root of the repository
|
|
326
|
+
pnpm --filter wldx-cli test # node:test, no network
|
|
327
|
+
node packages/wx/bin/wx.mjs status # this checkout, without installing
|
|
77
328
|
```
|
|
78
329
|
|
|
79
|
-
Layout: `bin/wx.mjs` (entry) → `src/cli.mjs` (router) → `src/commands/<group>.mjs
|
|
80
|
-
|
|
81
|
-
[AGENTS.md](AGENTS.md)
|
|
330
|
+
Layout: `bin/wx.mjs` (entry) → `src/cli.mjs` (the spec on the router of the core) → `src/commands/<group>.mjs`,
|
|
331
|
+
with helpers per domain in `src/chat`, `src/history`, `src/live`, `src/phone`. Login, sessions, addresses, requests
|
|
332
|
+
and text output come from `wldx-core`. See [AGENTS.md](https://github.com/gorets/wildix-cli/blob/main/packages/wx/AGENTS.md). `node test/dev.mjs <module> <args>` runs a command
|
|
333
|
+
module that is not listed yet; after changing commands, regenerate the reference: `node tools/readme-commands.mjs`.
|
|
82
334
|
|
|
83
335
|
## Releasing
|
|
84
336
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
the tests and **stages** the version with provenance through npm trusted publishing (no npm token in the
|
|
88
|
-
repository).
|
|
89
|
-
3. Approve it with 2FA, which makes it public: `npm stage list wldx-cli`, then `npm stage approve <stage-id>`
|
|
90
|
-
(`npm stage download <stage-id>` to look at the tarball first).
|
|
91
|
-
|
|
92
|
-
The trusted publisher on npmjs.com (package settings → Trusted publishing): GitHub Actions, `gorets/wildix-cli`,
|
|
93
|
-
workflow `release.yml`, permission "npm stage publish". Version 0.1.0 was published by hand, because a trusted
|
|
94
|
-
publisher can be configured only for a package that exists.
|
|
337
|
+
Together with `wx-admin` and the core, one version for the three: see Releasing in the
|
|
338
|
+
[README of the repository](https://github.com/gorets/wildix-cli#releasing).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wldx-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Unofficial command line for Wildix (x-bees chats and more), acting as the logged-in user",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wildix",
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
"author": "Vladimir Gorobets",
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
|
-
"url": "git+https://github.com/gorets/wildix-cli.git"
|
|
15
|
+
"url": "git+https://github.com/gorets/wildix-cli.git",
|
|
16
|
+
"directory": "packages/wx"
|
|
16
17
|
},
|
|
17
|
-
"homepage": "https://github.com/gorets/wildix-cli#readme",
|
|
18
|
+
"homepage": "https://github.com/gorets/wildix-cli/tree/main/packages/wx#readme",
|
|
18
19
|
"bugs": "https://github.com/gorets/wildix-cli/issues",
|
|
19
20
|
"type": "module",
|
|
20
21
|
"bin": {
|
|
@@ -27,8 +28,10 @@
|
|
|
27
28
|
"engines": {
|
|
28
29
|
"node": ">=22"
|
|
29
30
|
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"wldx-core": "1.1.0"
|
|
33
|
+
},
|
|
30
34
|
"scripts": {
|
|
31
|
-
"test": "node --test test/*.test.mjs"
|
|
32
|
-
"check": "npm test && npm pack --dry-run"
|
|
35
|
+
"test": "node --test test/*.test.mjs"
|
|
33
36
|
}
|
|
34
|
-
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Text output of the chat commands: one line per channel, message, member or person, local times, names of
|
|
2
|
+
// people instead of their ids (looked up by the command, see withNames in lib.mjs).
|
|
3
|
+
import {localTime, oneLine, paint, table} from 'wldx-core/format';
|
|
4
|
+
import {displayName, nameIn} from './lib.mjs';
|
|
5
|
+
|
|
6
|
+
const reactions = (counts) => {
|
|
7
|
+
const parts = Object.entries(counts ?? {})
|
|
8
|
+
.filter(([, n]) => n > 0)
|
|
9
|
+
.map(([t, n]) => `${t} ${n}`);
|
|
10
|
+
return parts.length ? `[${parts.join(', ')}]` : '';
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// The text of a message on one line, with what goes with it: files, reply, edits, reactions.
|
|
14
|
+
export function messageText(m, data, {max = 2000} = {}) {
|
|
15
|
+
if (!m) return '';
|
|
16
|
+
if (m.messageType === 'deleted' || m.deletedAt) return '(deleted)';
|
|
17
|
+
const parts = [];
|
|
18
|
+
if (m.system) parts.push('[system]');
|
|
19
|
+
if (m.quote) parts.push(`(reply to ${nameIn(data, m.quote.userId)})`);
|
|
20
|
+
if (m.forward) parts.push('(forwarded)');
|
|
21
|
+
if (m.text) parts.push(oneLine(m.text, max));
|
|
22
|
+
for (const f of m.attachments ?? []) parts.push(`[file: ${oneLine(f.name, 80)}]`);
|
|
23
|
+
if (m.giphy) parts.push('[giphy]');
|
|
24
|
+
if (m.pollId) parts.push('[poll]');
|
|
25
|
+
if (m.editedAt) parts.push('(edited)');
|
|
26
|
+
if (m.pinned) parts.push('(pinned)');
|
|
27
|
+
const r = reactions(m.reactionCounts);
|
|
28
|
+
if (r) parts.push(r);
|
|
29
|
+
return parts.join(' ');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Messages as a transcript, oldest first: time, author, text, and the message id.
|
|
33
|
+
export function messageLines(messages, data, ctx) {
|
|
34
|
+
const c = paint(ctx);
|
|
35
|
+
const sorted = [...(messages ?? [])].sort((a, b) => (a.timelineSeq ?? 0) - (b.timelineSeq ?? 0));
|
|
36
|
+
return table(
|
|
37
|
+
sorted.map((m) => [
|
|
38
|
+
c.dim(localTime(m.createdAt)),
|
|
39
|
+
c.bold(nameIn(data, m.userId) || '?'),
|
|
40
|
+
`${messageText(m, data)} ${c.dim(m.messageId)}`,
|
|
41
|
+
]),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// The title of a channel: its subject, or for a 1:1 channel the name of the other person.
|
|
46
|
+
export const channelTitle = (ch, data) =>
|
|
47
|
+
ch?.subject ||
|
|
48
|
+
(ch?.peerId ? nameIn(data, ch.peerId) : '') ||
|
|
49
|
+
(ch?.channelType === 'direct' ? '(direct)' : '(no subject)');
|
|
50
|
+
|
|
51
|
+
// The people a channel line names: the other person of a 1:1 channel, the author of the last message.
|
|
52
|
+
export const channelUserIds = (ch) => [ch?.peerId, ch?.lastMessage?.userId];
|
|
53
|
+
|
|
54
|
+
function flags(ch, c) {
|
|
55
|
+
const i = ch.inbox;
|
|
56
|
+
if (!i) return '';
|
|
57
|
+
const f = [];
|
|
58
|
+
if (i.unreadCount) f.push(c.yellow(`${i.unreadCount} new`));
|
|
59
|
+
if (i.mentionUnreadCount) f.push(c.yellow(`@${i.mentionUnreadCount}`));
|
|
60
|
+
if (i.pinned) f.push('pinned');
|
|
61
|
+
if (i.muted) f.push('muted');
|
|
62
|
+
if (i.hidden) f.push('archived');
|
|
63
|
+
return f.join(' ');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const preview = (ch, data) => {
|
|
67
|
+
const m = ch.lastMessage;
|
|
68
|
+
if (!m) return '';
|
|
69
|
+
const text = m.messageType === 'deleted' ? '(deleted)' : oneLine(m.text ?? (m.pollId ? '[poll]' : ''), 80);
|
|
70
|
+
return `${nameIn(data, m.userId)}: ${text}`;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Channels: last activity, id, inbox flags, title, the last message.
|
|
74
|
+
export function channelLines(channels, data, ctx) {
|
|
75
|
+
const c = paint(ctx);
|
|
76
|
+
return table(
|
|
77
|
+
(channels ?? []).map((ch) => [
|
|
78
|
+
c.dim(localTime(ch.lastActivityAt)),
|
|
79
|
+
ch.channelId,
|
|
80
|
+
flags(ch, c),
|
|
81
|
+
c.bold(oneLine(channelTitle(ch, data), 50)),
|
|
82
|
+
c.dim(preview(ch, data)),
|
|
83
|
+
]),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function channelDetails(ch, data, ctx) {
|
|
88
|
+
const c = paint(ctx);
|
|
89
|
+
if (!ch) return [];
|
|
90
|
+
const rows = [
|
|
91
|
+
['Channel', ch.channelId],
|
|
92
|
+
['Title', c.bold(channelTitle(ch, data))],
|
|
93
|
+
[
|
|
94
|
+
'Type',
|
|
95
|
+
[ch.channelType, ch.access, ch.broadcast && 'broadcast', ch.external && 'external'].filter(Boolean).join(' · '),
|
|
96
|
+
],
|
|
97
|
+
];
|
|
98
|
+
if (ch.description) rows.push(['Description', oneLine(ch.description, 300)]);
|
|
99
|
+
if (ch.peerId) rows.push(['With', `${nameIn(data, ch.peerId)} (${ch.peerId})`]);
|
|
100
|
+
rows.push(['Members', String(ch.membersCount ?? '')]);
|
|
101
|
+
rows.push(['Created', `${localTime(ch.createdAt)}${ch.createdBy ? ` by ${nameIn(data, ch.createdBy)}` : ''}`]);
|
|
102
|
+
rows.push(['Last activity', localTime(ch.lastActivityAt)]);
|
|
103
|
+
if (ch.pinnedMessageIds?.length) rows.push(['Pinned', ch.pinnedMessageIds.join(', ')]);
|
|
104
|
+
if (ch.autoRecord !== undefined || ch.autoTranscribe !== undefined) {
|
|
105
|
+
rows.push([
|
|
106
|
+
'Conferences',
|
|
107
|
+
[ch.autoRecord && 'auto record', ch.autoTranscribe && 'auto transcribe'].filter(Boolean).join(', ') || 'manual',
|
|
108
|
+
]);
|
|
109
|
+
}
|
|
110
|
+
if (ch.inbox) rows.push(['Inbox', flags(ch, c) || 'read']);
|
|
111
|
+
return table(rows);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// A person on one line: name, id, e-mail, extension, bot.
|
|
115
|
+
export function userLine(u, c) {
|
|
116
|
+
return [
|
|
117
|
+
c.bold(displayName(u) ?? ''),
|
|
118
|
+
u.id,
|
|
119
|
+
u.email ?? '',
|
|
120
|
+
u.pbxExtension ? `ext ${u.pbxExtension}` : '',
|
|
121
|
+
u.bot ? 'bot' : '',
|
|
122
|
+
u.deactivatedAt ? c.dim('deactivated') : '',
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const nextPage = (cursor, c) => (cursor ? [c.dim(`More: --cursor ${cursor}`)] : []);
|
|
127
|
+
|
|
128
|
+
// The text of a command that changes something: what was done (the JSON output keeps the answer of the service).
|
|
129
|
+
export const done = (what) => (r, ctx) => (typeof what === 'function' ? what(r, ctx) : what);
|