@wirecat/cli-core 0.475.0 → 0.477.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/dist/calendar/mcp/index.d.ts +12 -12
  3. package/dist/cli/index.d.ts +1 -1
  4. package/dist/cli/index.js +1 -1
  5. package/dist/cli/messenger/account-command.js +10 -8
  6. package/dist/cli/messenger/catch-up-answers.d.ts +14 -0
  7. package/dist/cli/messenger/chat-answers.d.ts +8 -2
  8. package/dist/cli/messenger/chat-answers.js +43 -0
  9. package/dist/cli/messenger/contacts-command.js +20 -25
  10. package/dist/cli/messenger/context.d.ts +2 -0
  11. package/dist/cli/messenger/conversation-answers.d.ts +21 -0
  12. package/dist/cli/messenger/message-answers.d.ts +21 -0
  13. package/dist/cli/messenger/message-items.js +8 -0
  14. package/dist/cli/messenger/message-writes.d.ts +42 -0
  15. package/dist/cli/messenger/people-queries.d.ts +2 -0
  16. package/dist/cli/messenger/people-queries.js +7 -1
  17. package/dist/cli/messenger/reactions-command.js +38 -2
  18. package/dist/cli/messenger/serve-relay.d.ts +1 -0
  19. package/dist/domain/models.d.ts +22 -3
  20. package/dist/entities/attachment.d.ts +8 -0
  21. package/dist/entities/attachment.js +9 -1
  22. package/dist/entities/contact-context.d.ts +42 -0
  23. package/dist/entities/contact-read.d.ts +15 -25
  24. package/dist/entities/contact-read.js +4 -15
  25. package/dist/entities/contact.d.ts +103 -134
  26. package/dist/entities/contact.js +13 -2
  27. package/dist/entities/conversation-results.d.ts +42 -0
  28. package/dist/entities/evidence.d.ts +168 -0
  29. package/dist/entities/index.d.ts +511 -201
  30. package/dist/entities/index.js +4 -3
  31. package/dist/entities/listener.d.ts +84 -0
  32. package/dist/entities/message-context.d.ts +63 -0
  33. package/dist/entities/message-write.d.ts +84 -0
  34. package/dist/entities/message.d.ts +21 -0
  35. package/dist/entities/reaction.d.ts +12 -0
  36. package/dist/entities/reaction.js +9 -0
  37. package/dist/entities/store-records.d.ts +21 -0
  38. package/dist/errors/reasons.js +2 -0
  39. package/dist/meetings/mcp/index.d.ts +15 -15
  40. package/dist/oauth/storage.js +14 -9
  41. package/dist/ports/messenger.d.ts +13 -2
  42. package/dist/render/markdown.js +4 -0
  43. package/dist/render/messages.js +7 -2
  44. package/dist/services/account.d.ts +1 -1
  45. package/dist/services/account.js +1 -2
  46. package/dist/services/people.d.ts +8 -5
  47. package/dist/services/people.js +6 -3
  48. package/dist/services/person-profile.d.ts +4 -3
  49. package/dist/services/person-profile.js +14 -3
  50. package/dist/store/sqlite/manifest.js +1 -0
  51. package/dist/store/sqlite/messages.js +5 -1
  52. package/dist/store/sqlite/migrations.generated.js +6 -0
  53. package/dist/store/sqlite/reads.js +1 -0
  54. package/dist/store/sqlite/schema.d.ts +16 -0
  55. package/dist/store/sqlite/schema.js +1 -0
  56. package/package.json +1 -1
  57. package/parity.json +20 -10
package/CHANGELOG.md CHANGED
@@ -5,7 +5,46 @@ Notable changes to `@wirecat/cli-core` (`@wirecat/cli-messaging` up to 0.261.0,
5
5
  minor version may break callers, and says how under "Changed — may break callers". `pnpm docs:check` checks the
6
6
  shape of this file.
7
7
 
8
- ## Unreleased
8
+ ## 0.477.0 — 29.10.2026
9
+
10
+ ### Added
11
+
12
+ - **A shared contact card is an attachment with a `contact` field** (store version 36). `Attachment.contact`
13
+ (`SharedContact`: `name`, `firstName?`, `lastName?`, `phone?`, `userId?`) and, in every message answer,
14
+ `attachments[].contact` (`name`, `first_name?`, `last_name?`, `phone?`, `user_id?`), the phone whole. The store keeps it in
15
+ the new column `attachments.contact`, so a card read with `--offline` is the card the messenger sent; a later copy of the
16
+ message without it keeps the stored one. The terminal shows `👤 name · phone`. Additive: `minCompatible` stays 34.
17
+ What an adapter does: set `contact` on the attachment instead of putting a masked phone in `title`.
18
+ - **`--hide-phone`** on `account show`, `account update` and `contacts show`: the answer without the phone number.
19
+ - **`reactions list <chat> <message> [reaction:<emoji>] [--limit <n>] [--cursor <cursor>]`** (read) shows who reacted
20
+ to a message, newest first, as `{ person: { id, name, username }, reaction, reacted_at }` rows paged by cursor. A
21
+ messenger turns it on with `Messenger.reactionsList` and `MessageReactions.reactors`; where the chat hides the list
22
+ it refuses with `reactions.list-hidden` (or `reactions.broadcast` for a channel) instead of answering with none. The
23
+ permission key is `reactions.list`. New entity `reaction`, new type `Reactor`.
24
+
25
+ ### Changed — may break callers
26
+
27
+ - **Phone numbers are shown whole.** `account show`, `account update` and `contacts show` print the number the messenger
28
+ gave, in the CLI and over MCP; they are the owner's own data. `--show-phone` is gone (a script that passes it is refused
29
+ as an unknown option; drop it), and so is `maskedAccount` from `/cli`: `withoutPhone(answer)` is what `--hide-phone` uses.
30
+ - **`contacts info` is folded into `contacts show`, with no alias.** `contacts show <person>` returns the card as before (alias,
31
+ shared chats, `--with-notes`) and, with it, the messenger's profile: `usernames`, `birthday`, `phone`, `flags`, `seen`,
32
+ `contact`, `mutual_contact`, `common_chats_count`, `registered`, `has_photo` and `aliases` (earlier names), and in each shared
33
+ chat `their_messages`, `first_at`, `last_at` and `complete`. The profile's `bio` is `description`. `--offline` answers from
34
+ the store alone, as every read does. The entity `contact_profile` and the MCP tool `contacts info` are gone, and `PeopleService.profile`
35
+ takes the card as an optional second argument. Permission keys are read only as written (0.421.0): a saved
36
+ `contacts.info` is refused as a key no command checks, with the `config unset` that removes it; `contacts.show` now covers
37
+ the profile too.
38
+
39
+ ## 0.476.0 — 29.10.2026
40
+
41
+ ### Fixed
42
+
43
+ - **A sign-in that cannot be read says why under `--debug` and in the run record.** `credentialStorage` threw
44
+ `configuration_error` (`oauth.not-saved`, "could not reach the saved sign-in") and dropped the keyring's own error,
45
+ so a locked keyring, a missing Secret Service and an unreachable D-Bus looked the same. The keyring's message is now
46
+ the error's `cause`: `--debug` and `runs show` print it, the JSON answer still does not. A secret the keyring echoes
47
+ back on a failed write is masked first.
9
48
 
10
49
  ## 0.475.0 — 29.10.2026
11
50
 
@@ -10,13 +10,13 @@ export declare const calendarTools: ({
10
10
  inputSchema: {
11
11
  type: string;
12
12
  properties: {
13
- limit?: undefined;
14
13
  account_id: {
15
14
  type: string;
16
15
  minimum: number;
17
16
  };
18
17
  words?: undefined;
19
18
  timezone?: undefined;
19
+ limit?: undefined;
20
20
  event?: undefined;
21
21
  } | {
22
22
  account_id: {
@@ -38,9 +38,9 @@ export declare const calendarTools: ({
38
38
  };
39
39
  event?: undefined;
40
40
  } | {
41
- limit?: undefined;
42
41
  words?: undefined;
43
42
  timezone?: undefined;
43
+ limit?: undefined;
44
44
  event: {
45
45
  type: string;
46
46
  minLength: number;
@@ -50,9 +50,9 @@ export declare const calendarTools: ({
50
50
  minimum: number;
51
51
  };
52
52
  } | {
53
- limit?: undefined;
54
53
  words?: undefined;
55
54
  timezone?: undefined;
55
+ limit?: undefined;
56
56
  event?: undefined;
57
57
  account_id?: undefined;
58
58
  };
@@ -71,13 +71,13 @@ export declare const calendarTools: ({
71
71
  inputSchema: {
72
72
  type: string;
73
73
  properties: {
74
- limit?: undefined;
75
74
  account_id: {
76
75
  type: string;
77
76
  minimum: number;
78
77
  };
79
78
  words?: undefined;
80
79
  timezone?: undefined;
80
+ limit?: undefined;
81
81
  event?: undefined;
82
82
  } | {
83
83
  account_id: {
@@ -99,9 +99,9 @@ export declare const calendarTools: ({
99
99
  };
100
100
  event?: undefined;
101
101
  } | {
102
- limit?: undefined;
103
102
  words?: undefined;
104
103
  timezone?: undefined;
104
+ limit?: undefined;
105
105
  event: {
106
106
  type: string;
107
107
  minLength: number;
@@ -111,9 +111,9 @@ export declare const calendarTools: ({
111
111
  minimum: number;
112
112
  };
113
113
  } | {
114
- limit?: undefined;
115
114
  words?: undefined;
116
115
  timezone?: undefined;
116
+ limit?: undefined;
117
117
  event?: undefined;
118
118
  account_id?: undefined;
119
119
  };
@@ -132,13 +132,13 @@ export declare const calendarTools: ({
132
132
  inputSchema: {
133
133
  type: string;
134
134
  properties: {
135
- limit?: undefined;
136
135
  account_id: {
137
136
  type: string;
138
137
  minimum: number;
139
138
  };
140
139
  words?: undefined;
141
140
  timezone?: undefined;
141
+ limit?: undefined;
142
142
  event?: undefined;
143
143
  } | {
144
144
  account_id: {
@@ -160,9 +160,9 @@ export declare const calendarTools: ({
160
160
  };
161
161
  event?: undefined;
162
162
  } | {
163
- limit?: undefined;
164
163
  words?: undefined;
165
164
  timezone?: undefined;
165
+ limit?: undefined;
166
166
  event: {
167
167
  type: string;
168
168
  minLength: number;
@@ -172,9 +172,9 @@ export declare const calendarTools: ({
172
172
  minimum: number;
173
173
  };
174
174
  } | {
175
- limit?: undefined;
176
175
  words?: undefined;
177
176
  timezone?: undefined;
177
+ limit?: undefined;
178
178
  event?: undefined;
179
179
  account_id?: undefined;
180
180
  };
@@ -193,13 +193,13 @@ export declare const calendarTools: ({
193
193
  inputSchema: {
194
194
  type: string;
195
195
  properties: {
196
- limit?: undefined;
197
196
  account_id: {
198
197
  type: string;
199
198
  minimum: number;
200
199
  };
201
200
  words?: undefined;
202
201
  timezone?: undefined;
202
+ limit?: undefined;
203
203
  event?: undefined;
204
204
  } | {
205
205
  account_id: {
@@ -221,9 +221,9 @@ export declare const calendarTools: ({
221
221
  };
222
222
  event?: undefined;
223
223
  } | {
224
- limit?: undefined;
225
224
  words?: undefined;
226
225
  timezone?: undefined;
226
+ limit?: undefined;
227
227
  event: {
228
228
  type: string;
229
229
  minLength: number;
@@ -233,9 +233,9 @@ export declare const calendarTools: ({
233
233
  minimum: number;
234
234
  };
235
235
  } | {
236
- limit?: undefined;
237
236
  words?: undefined;
238
237
  timezone?: undefined;
238
+ limit?: undefined;
239
239
  event?: undefined;
240
240
  account_id?: undefined;
241
241
  };
@@ -33,7 +33,7 @@ export { guardedClose, guardedCreatePoll, guardedVote } from "../sends/polls.js"
33
33
  export type { InboxReader } from "../services/inbox.js";
34
34
  export { INBOX_CHATS, newIn, REVIEW_CHATS, reviewIn, unreadIn } from "../services/inbox.js";
35
35
  export { type DateWindow, dateWindow, momentOf } from "../services/moment.js";
36
- export { maskedAccount } from "../services/people.js";
36
+ export { withoutPhone } from "../services/people.js";
37
37
  export { unanswered } from "../services/questions.js";
38
38
  export { openRequestTask } from "../services/task-rules.js";
39
39
  export { TASK_PAIR_QUERY, TASK_QUERY } from "../services/tasks.js";
package/dist/cli/index.js CHANGED
@@ -28,7 +28,7 @@ export { guardedApp, guardedPress, guardedStart } from "../sends/buttons.js";
28
28
  export { guardedClose, guardedCreatePoll, guardedVote } from "../sends/polls.js";
29
29
  export { INBOX_CHATS, newIn, REVIEW_CHATS, reviewIn, unreadIn } from "../services/inbox.js";
30
30
  export { dateWindow, momentOf } from "../services/moment.js";
31
- export { maskedAccount } from "../services/people.js";
31
+ export { withoutPhone } from "../services/people.js";
32
32
  export { unanswered } from "../services/questions.js";
33
33
  export { openRequestTask } from "../services/task-rules.js";
34
34
  export { TASK_PAIR_QUERY, TASK_QUERY } from "../services/tasks.js";
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { annotate } from "@wirecat/cli-base/commands";
3
3
  import { Command } from "commander";
4
- import { maskedAccount } from "../../services/people.js";
4
+ import { withoutPhone } from "../../services/people.js";
5
5
  import { storePath } from "../../store/path.js";
6
6
  import { openStore } from "../../store/store.js";
7
7
  import { REMOTE_READ, REMOTE_WRITE } from "../effects.js";
@@ -61,13 +61,13 @@ export const accountCommand = (messenger) => {
61
61
  output: { entity: "account" },
62
62
  examples: [`${tool} account show`],
63
63
  })
64
- .description("who this profile is logged in as; the phone number shows its last four digits")
65
- .option("--show-phone", "print the whole phone number")
64
+ .description("who this profile is logged in as, with the phone number")
65
+ .option("--hide-phone", "leave the phone number out")
66
66
  .action(async function () {
67
- const whole = this.opts().showPhone === true;
67
+ const hide = this.opts().hidePhone === true;
68
68
  const context = messengerContext(this, messenger);
69
69
  const account = await context.withMessenger((connection) => connection.me());
70
- renderAnswer(this, context, itemOf(accountAnswer(messenger.provider, whole ? account : maskedAccount(account))));
70
+ renderAnswer(this, context, itemOf(accountAnswer(messenger.provider, hide ? withoutPhone(account) : account)));
71
71
  }))
72
72
  .addCommand(annotate(new Command("update"), {
73
73
  effects: REMOTE_WRITE,
@@ -79,16 +79,18 @@ export const accountCommand = (messenger) => {
79
79
  .option("--last-name <name>", "your last name")
80
80
  .option("--description <text>", "about you")
81
81
  .option("--photo <file>", `a new profile photo — ${photoHelp(messenger)}`)
82
+ .option("--hide-phone", "leave the phone number out of the answer")
82
83
  .action(async function () {
83
84
  const context = messengerContext(this, messenger);
84
- const { firstName, lastName, description, photo } = this.opts();
85
+ const { firstName, lastName, description, photo, hidePhone } = this.opts();
85
86
  const upload = photo === undefined ? undefined : await readProfilePhoto(messenger, photo, context.env);
86
- renderItem(this, context, await context.withServices((services) => services.account.update({
87
+ const changed = await context.withServices((services) => services.account.update({
87
88
  ...(firstName === undefined ? {} : { firstName }),
88
89
  ...(lastName === undefined ? {} : { lastName }),
89
90
  ...(description === undefined ? {} : { description }),
90
91
  ...(upload === undefined ? {} : { photo: upload }),
91
- })));
92
+ }));
93
+ renderItem(this, context, hidePhone ? { ...changed, account: withoutPhone(changed.account) } : changed);
92
94
  }))
93
95
  .addCommand(accountSessionsCommand(messenger));
94
96
  if (messenger.privacy === true)
@@ -43,6 +43,13 @@ export declare const inboxAnswer: (inbox: Inbox, hearing: Hearing | undefined, r
43
43
  error?: string | undefined;
44
44
  } | undefined;
45
45
  local_path?: string | undefined;
46
+ contact?: {
47
+ name: string;
48
+ first_name?: string | undefined;
49
+ last_name?: string | undefined;
50
+ phone?: string | undefined;
51
+ user_id?: string | undefined;
52
+ } | undefined;
46
53
  buttons?: {
47
54
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
48
55
  text: string;
@@ -90,6 +97,13 @@ export declare const reviewAnswer: (found: Review, hearing: Hearing | undefined,
90
97
  error?: string | undefined;
91
98
  } | undefined;
92
99
  local_path?: string | undefined;
100
+ contact?: {
101
+ name: string;
102
+ first_name?: string | undefined;
103
+ last_name?: string | undefined;
104
+ phone?: string | undefined;
105
+ user_id?: string | undefined;
106
+ } | undefined;
93
107
  buttons?: {
94
108
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
95
109
  text: string;
@@ -2,8 +2,8 @@ import { type QueryAst } from "@wirecat/cli-base/query";
2
2
  import type { NextStep } from "@wirecat/cli-base/standards";
3
3
  import type { Command } from "commander";
4
4
  import type * as v from "valibot";
5
- import type { Account, AccountSession, Chat, ChatCard, ChatEvent, Contact, Folder, GroupCard, GroupMember, InviteLink, JoinRequest, Member, PersonCard, PollVote, Topic } from "../../domain/models.js";
6
- import type { account as accountSchema, chatEvent as chatEventSchema, chat as chatSchema, contact as contactSchema, folder as folderSchema, inviteLink as inviteLinkSchema, joinRequest as joinRequestSchema, member as memberSchema, pollVote as pollVoteSchema, topic as topicSchema } from "../../entities/index.js";
5
+ import type { Account, AccountSession, Chat, ChatCard, ChatEvent, Contact, Folder, GroupCard, GroupMember, InviteLink, JoinRequest, Member, PersonCard, PersonProfile, PollVote, Reactor, Topic } from "../../domain/models.js";
6
+ import type { account as accountSchema, chatEvent as chatEventSchema, chat as chatSchema, contact as contactSchema, folder as folderSchema, inviteLink as inviteLinkSchema, joinRequest as joinRequestSchema, member as memberSchema, pollVote as pollVoteSchema, reaction as reactionSchema, topic as topicSchema } from "../../entities/index.js";
7
7
  import type { ChatDocument } from "../../services/chat-documents.js";
8
8
  import type { FolderShown } from "../../services/folders.js";
9
9
  import type { MemberEvent } from "../../services/member-history.js";
@@ -21,11 +21,17 @@ export declare const chatAnswer: (account: AccountKey, chat: ShownChat) => Out<t
21
21
  export declare const memberAnswer: (member: Member | GroupMember | AuditedMember) => Out<typeof memberSchema>;
22
22
  export declare const contactRef: (provider: string, id: string) => string;
23
23
  export declare const contactAnswer: (account: AccountKey, contact: Contact | PersonCard) => Out<typeof contactSchema>;
24
+ /**
25
+ * `contacts show`: the card (the owner's alias and notes, the chats shared) with what the messenger says about the
26
+ * person, and in each shared chat how much of their writing the store holds.
27
+ */
28
+ export declare const contactShown: (account: AccountKey, card: PersonCard, profile: PersonProfile) => Out<typeof contactSchema>;
24
29
  export declare const chatEventAnswer: (chatId: string, event: ChatEvent) => Out<typeof chatEventSchema>;
25
30
  export declare const memberEventAnswer: (chatId: string, event: MemberEvent) => Out<typeof chatEventSchema>;
26
31
  export declare const inviteLinkAnswer: (link: InviteLink) => Out<typeof inviteLinkSchema>;
27
32
  export declare const joinRequestAnswer: (request: JoinRequest) => Out<typeof joinRequestSchema>;
28
33
  export declare const pollVoteAnswer: (vote: PollVote) => Out<typeof pollVoteSchema>;
34
+ export declare const reactorAnswer: (reactor: Reactor) => Out<typeof reactionSchema>;
29
35
  export declare const topicAnswer: (topic: Topic) => Out<typeof topicSchema>;
30
36
  export declare const folderAnswer: (folder: Folder | FolderShown) => Out<typeof folderSchema>;
31
37
  /** A person's view of a list: one line per item. */
@@ -88,6 +88,44 @@ export const contactAnswer = (account, contact) => {
88
88
  })),
89
89
  });
90
90
  };
91
+ /**
92
+ * `contacts show`: the card (the owner's alias and notes, the chats shared) with what the messenger says about the
93
+ * person, and in each shared chat how much of their writing the store holds.
94
+ */
95
+ export const contactShown = (account, card, profile) => {
96
+ const { id: _id, name: _name, bio, chats, ...facts } = profile;
97
+ const listed = new Map(card.chats.map((chat) => [chat.id, chat]));
98
+ const active = new Set(chats.map(({ id }) => id));
99
+ const shared = [
100
+ ...chats.map((chat) => ({
101
+ ref: chatRef(account, chat.id),
102
+ id: chat.id,
103
+ title: chat.title,
104
+ kind: chat.kind,
105
+ last_message_at: listed.get(chat.id)?.lastMessageAt ?? chat.lastAt,
106
+ their_messages: chat.theirMessages,
107
+ first_at: chat.firstAt,
108
+ last_at: chat.lastAt,
109
+ complete: chat.complete,
110
+ })),
111
+ ...card.chats
112
+ .filter(({ id }) => !active.has(id))
113
+ .map((chat) => ({
114
+ ref: chatRef(account, chat.id),
115
+ id: chat.id,
116
+ title: chat.title,
117
+ kind: chat.kind,
118
+ last_message_at: chat.lastMessageAt,
119
+ })),
120
+ ];
121
+ const answer = contactAnswer(account, card);
122
+ return {
123
+ ...answer,
124
+ ...snakeKeys(facts),
125
+ description: answer.description ?? bio,
126
+ chats: shared,
127
+ };
128
+ };
91
129
  export const chatEventAnswer = (chatId, event) => defined({
92
130
  chat_id: chatId,
93
131
  time: event.timestamp,
@@ -130,6 +168,11 @@ export const pollVoteAnswer = (vote) => ({
130
168
  answers: vote.answers,
131
169
  voted_at: vote.votedAt,
132
170
  });
171
+ export const reactorAnswer = (reactor) => ({
172
+ person: { id: reactor.person.id, name: reactor.person.name, username: reactor.person.username },
173
+ reaction: reactor.reaction,
174
+ reacted_at: reactor.reactedAt,
175
+ });
133
176
  export const topicAnswer = (topic) => defined({
134
177
  id: topic.id,
135
178
  title: topic.title,
@@ -7,7 +7,7 @@ import { secretInput } from "../../runs/run-context.js";
7
7
  import { levelFor } from "../../sends/permissions.js";
8
8
  import { phoneOf } from "../../services/index.js";
9
9
  import { dateWindow } from "../../services/moment.js";
10
- import { maskedAccount } from "../../services/people.js";
10
+ import { withoutPhone } from "../../services/people.js";
11
11
  import { CHAT_MESSAGES, CONTEXT_BYTES, CONTEXT_MESSAGES } from "../../services/person-context.js";
12
12
  import { SCOPES, TIMELINE_ITEMS } from "../../services/person-timeline.js";
13
13
  import { readSecret } from "../../terminal/prompt.js";
@@ -19,7 +19,7 @@ import { positiveCount, window, withPaging } from "../paging.js";
19
19
  import { casKey } from "../registry-keys.js";
20
20
  import { commandAt, nextStep } from "../steps.js";
21
21
  import { contactWriteCommands } from "./admin-contacts-command.js";
22
- import { contactAnswer, lines, pageOf } from "./chat-answers.js";
22
+ import { contactAnswer, contactShown, lines, pageOf } from "./chat-answers.js";
23
23
  import { messengerContext } from "./context.js";
24
24
  import { hideNever } from "./lacks.js";
25
25
  import { CONTACTS_QUERY, notesAllowed } from "./people-queries.js";
@@ -71,37 +71,32 @@ export const contactsCommand = (messenger) => {
71
71
  }), lines(({ id, name, username, last_messaged_at }) => [id, name ?? "", username ? `@${username}` : "", last_messaged_at ?? ""].join(" ")));
72
72
  }));
73
73
  contacts.addCommand(annotate(annotate(new Command("show"), { effects: REMOTE_READ })
74
- .description("one person and the chats you share with them")
74
+ .summary("one person's profile, your alias and notes, and the chats you share")
75
+ .description("one person: what the messenger says about them — handles, flags, last seen, phone, when they registered — " +
76
+ "your alias, the chats you share, how many of their messages the store holds in each, and the earlier " +
77
+ "names and usernames the store saw them with; --offline answers from the store alone")
75
78
  .argument("<person>", "their id, @username, or part of their name")
76
- .option("--with-notes", "include your private notes, subject to contacts.notes.list permission"), { output: { entity: "contact" } }).action(async function (person) {
79
+ .option("--with-notes", "include your private notes, subject to contacts.notes.list permission")
80
+ .option("--hide-phone", "leave the phone number out"), { output: { entity: "contact" } }).action(async function (person) {
77
81
  const context = messengerContext(this, messenger);
78
- const { withNotes } = this.opts();
82
+ const { withNotes, hidePhone } = this.opts();
79
83
  if (withNotes && levelFor(context.settings.permissions, "contacts.notes.list").level === "deny")
80
84
  throw new CliError("permission_error", "profile denies contacts.notes.list");
81
- const { card, account } = await context.withServices(async (services) => ({
82
- card: await services.people.show(person, { notes: withNotes }),
83
- account: await services.chats.account(),
84
- }));
85
- renderAnswer(this, context, itemOf(contactAnswer(account, card)));
86
- }));
87
- annotate(contacts.command("info"), { effects: REMOTE_READ, output: { entity: "contact_profile" } })
88
- .summary("a person's profile from the messenger")
89
- .description("everything the messenger says about one person — handles, flags, last seen, when they registered — and " +
90
- "how many of their messages the store holds in each chat you share, the first and the last, and the " +
91
- "earlier names and usernames the store saw them with")
92
- .argument("<person>", "their id, @username, or part of their name")
93
- .option("--show-phone", "print the whole phone number")
94
- .action(async function (person) {
95
- const whole = this.opts().showPhone === true;
96
- const context = messengerContext(this, messenger);
97
- const found = await context.withServices((services) => services.people.profile(person));
98
- const partial = found.chats.filter(({ complete }) => !complete);
99
- renderAnswer(this, context, itemOf(snakeKeys(whole ? found : maskedAccount(found)), {
85
+ const { card, profile, account } = await context.withServices(async (services) => {
86
+ const card = await services.people.show(person, { notes: withNotes });
87
+ return {
88
+ card,
89
+ profile: await services.people.profile(person, card),
90
+ account: await services.chats.account(),
91
+ };
92
+ });
93
+ const partial = profile.chats.filter(({ complete }) => !complete);
94
+ renderAnswer(this, context, itemOf(contactShown(account, card, hidePhone ? withoutPhone(profile) : profile), {
100
95
  next: syncsFirst(messenger)
101
96
  ? partial.map((one) => nextStep("messages.sync", { args: { chat: one.id } }))
102
97
  : [],
103
98
  }));
104
- });
99
+ }));
105
100
  const contextCommand = annotate(contacts.command("context"), {
106
101
  effects: REMOTE_READ,
107
102
  output: { entity: "contact_context" },
@@ -80,6 +80,8 @@ export interface Messenger extends ServiceMessenger {
80
80
  pollQuiz?: boolean;
81
81
  /** How many seconds after sending a poll may close by itself — `polls create --close-time`; none when unset. */
82
82
  pollCloseSeconds?: readonly [min: number, max: number];
83
+ /** Whether `reactions list` shows who reacted to a message; no when unset. */
84
+ reactionsList?: boolean;
83
85
  /** Whether `polls voters` lists who voted for what; no when unset. */
84
86
  pollVoters?: boolean;
85
87
  /** Whether a poll can be closed — `polls close`; yes when unset. WhatsApp's stay open. */
@@ -96,6 +96,13 @@ export declare const conversationShown: (account: AccountKey, { summary, message
96
96
  error?: string | undefined;
97
97
  } | undefined;
98
98
  local_path?: string | undefined;
99
+ contact?: {
100
+ name: string;
101
+ first_name?: string | undefined;
102
+ last_name?: string | undefined;
103
+ phone?: string | undefined;
104
+ user_id?: string | undefined;
105
+ } | undefined;
99
106
  buttons?: {
100
107
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
101
108
  text: string;
@@ -128,6 +135,13 @@ export declare const conversationShown: (account: AccountKey, { summary, message
128
135
  error?: string | undefined;
129
136
  } | undefined;
130
137
  local_path?: string | undefined;
138
+ contact?: {
139
+ name: string;
140
+ first_name?: string | undefined;
141
+ last_name?: string | undefined;
142
+ phone?: string | undefined;
143
+ user_id?: string | undefined;
144
+ } | undefined;
131
145
  buttons?: {
132
146
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
133
147
  text: string;
@@ -163,6 +177,13 @@ export declare const conversationShown: (account: AccountKey, { summary, message
163
177
  error?: string | undefined;
164
178
  } | undefined;
165
179
  local_path?: string | undefined;
180
+ contact?: {
181
+ name: string;
182
+ first_name?: string | undefined;
183
+ last_name?: string | undefined;
184
+ phone?: string | undefined;
185
+ user_id?: string | undefined;
186
+ } | undefined;
166
187
  buttons?: {
167
188
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
168
189
  text: string;
@@ -73,6 +73,13 @@ export declare const threadAnswer: ({ items, ...thread }: ThreadContext, reader:
73
73
  error?: string | undefined;
74
74
  } | undefined;
75
75
  local_path?: string | undefined;
76
+ contact?: {
77
+ name: string;
78
+ first_name?: string | undefined;
79
+ last_name?: string | undefined;
80
+ phone?: string | undefined;
81
+ user_id?: string | undefined;
82
+ } | undefined;
76
83
  buttons?: {
77
84
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
78
85
  text: string;
@@ -105,6 +112,13 @@ export declare const threadAnswer: ({ items, ...thread }: ThreadContext, reader:
105
112
  error?: string | undefined;
106
113
  } | undefined;
107
114
  local_path?: string | undefined;
115
+ contact?: {
116
+ name: string;
117
+ first_name?: string | undefined;
118
+ last_name?: string | undefined;
119
+ phone?: string | undefined;
120
+ user_id?: string | undefined;
121
+ } | undefined;
108
122
  buttons?: {
109
123
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
110
124
  text: string;
@@ -140,6 +154,13 @@ export declare const threadAnswer: ({ items, ...thread }: ThreadContext, reader:
140
154
  error?: string | undefined;
141
155
  } | undefined;
142
156
  local_path?: string | undefined;
157
+ contact?: {
158
+ name: string;
159
+ first_name?: string | undefined;
160
+ last_name?: string | undefined;
161
+ phone?: string | undefined;
162
+ user_id?: string | undefined;
163
+ } | undefined;
143
164
  buttons?: {
144
165
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
145
166
  text: string;
@@ -11,6 +11,14 @@ const attachmentItem = (attachment) => defined({
11
11
  width: attachment.width,
12
12
  height: attachment.height,
13
13
  duration: attachment.duration,
14
+ contact: attachment.contact &&
15
+ defined({
16
+ name: attachment.contact.name,
17
+ first_name: attachment.contact.firstName,
18
+ last_name: attachment.contact.lastName,
19
+ phone: attachment.contact.phone,
20
+ user_id: attachment.contact.userId,
21
+ }),
14
22
  buttons: attachment.buttons,
15
23
  });
16
24
  const quotedItem = (quoted) => quoted === null
@@ -77,6 +77,13 @@ export declare const writtenAnswer: ({ operationId, sendId, message, messages, s
77
77
  error?: string | undefined;
78
78
  } | undefined;
79
79
  local_path?: string | undefined;
80
+ contact?: {
81
+ name: string;
82
+ first_name?: string | undefined;
83
+ last_name?: string | undefined;
84
+ phone?: string | undefined;
85
+ user_id?: string | undefined;
86
+ } | undefined;
80
87
  buttons?: {
81
88
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
82
89
  text: string;
@@ -109,6 +116,13 @@ export declare const writtenAnswer: ({ operationId, sendId, message, messages, s
109
116
  error?: string | undefined;
110
117
  } | undefined;
111
118
  local_path?: string | undefined;
119
+ contact?: {
120
+ name: string;
121
+ first_name?: string | undefined;
122
+ last_name?: string | undefined;
123
+ phone?: string | undefined;
124
+ user_id?: string | undefined;
125
+ } | undefined;
112
126
  buttons?: {
113
127
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
114
128
  text: string;
@@ -144,6 +158,13 @@ export declare const writtenAnswer: ({ operationId, sendId, message, messages, s
144
158
  error?: string | undefined;
145
159
  } | undefined;
146
160
  local_path?: string | undefined;
161
+ contact?: {
162
+ name: string;
163
+ first_name?: string | undefined;
164
+ last_name?: string | undefined;
165
+ phone?: string | undefined;
166
+ user_id?: string | undefined;
167
+ } | undefined;
147
168
  buttons?: {
148
169
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
149
170
  text: string;
@@ -268,6 +289,13 @@ export declare const writtenAnswer: ({ operationId, sendId, message, messages, s
268
289
  error?: string | undefined;
269
290
  } | undefined;
270
291
  local_path?: string | undefined;
292
+ contact?: {
293
+ name: string;
294
+ first_name?: string | undefined;
295
+ last_name?: string | undefined;
296
+ phone?: string | undefined;
297
+ user_id?: string | undefined;
298
+ } | undefined;
271
299
  buttons?: {
272
300
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
273
301
  text: string;
@@ -300,6 +328,13 @@ export declare const writtenAnswer: ({ operationId, sendId, message, messages, s
300
328
  error?: string | undefined;
301
329
  } | undefined;
302
330
  local_path?: string | undefined;
331
+ contact?: {
332
+ name: string;
333
+ first_name?: string | undefined;
334
+ last_name?: string | undefined;
335
+ phone?: string | undefined;
336
+ user_id?: string | undefined;
337
+ } | undefined;
303
338
  buttons?: {
304
339
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
305
340
  text: string;
@@ -335,6 +370,13 @@ export declare const writtenAnswer: ({ operationId, sendId, message, messages, s
335
370
  error?: string | undefined;
336
371
  } | undefined;
337
372
  local_path?: string | undefined;
373
+ contact?: {
374
+ name: string;
375
+ first_name?: string | undefined;
376
+ last_name?: string | undefined;
377
+ phone?: string | undefined;
378
+ user_id?: string | undefined;
379
+ } | undefined;
338
380
  buttons?: {
339
381
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
340
382
  text: string;
@@ -9,3 +9,5 @@ export declare const CONTACTS_QUERY: QueryRegistry;
9
9
  export declare const notesAllowed: (permissions: Parameters<typeof levelFor>[0], notes: Predicate | undefined) => void;
10
10
  /** The messenger keeps the voters of one answer; one answer id at a time. */
11
11
  export declare const VOTERS_QUERY: QueryRegistry;
12
+ /** Telegram keeps the people who chose one reaction; one emoji at a time. */
13
+ export declare const REACTORS_QUERY: QueryRegistry;