@wirecat/cli-core 0.476.0 → 0.478.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 (66) hide show
  1. package/CHANGELOG.md +41 -0
  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 +16 -0
  7. package/dist/cli/messenger/catch-up-answers.js +2 -1
  8. package/dist/cli/messenger/chat-answers.d.ts +8 -2
  9. package/dist/cli/messenger/chat-answers.js +43 -0
  10. package/dist/cli/messenger/contacts-command.js +20 -25
  11. package/dist/cli/messenger/context.d.ts +2 -0
  12. package/dist/cli/messenger/conversation-answers.d.ts +21 -0
  13. package/dist/cli/messenger/message-answers.d.ts +21 -0
  14. package/dist/cli/messenger/message-items.d.ts +1 -1
  15. package/dist/cli/messenger/message-items.js +10 -2
  16. package/dist/cli/messenger/message-writes.d.ts +42 -0
  17. package/dist/cli/messenger/people-queries.d.ts +2 -0
  18. package/dist/cli/messenger/people-queries.js +7 -1
  19. package/dist/cli/messenger/reactions-command.js +38 -2
  20. package/dist/cli/messenger/search-answers.js +5 -1
  21. package/dist/cli/messenger/serve-relay.d.ts +1 -0
  22. package/dist/domain/models.d.ts +24 -3
  23. package/dist/domain/models.js +2 -0
  24. package/dist/entities/attachment.d.ts +8 -0
  25. package/dist/entities/attachment.js +9 -1
  26. package/dist/entities/contact-context.d.ts +42 -0
  27. package/dist/entities/contact-read.d.ts +15 -25
  28. package/dist/entities/contact-read.js +4 -15
  29. package/dist/entities/contact.d.ts +103 -134
  30. package/dist/entities/contact.js +13 -2
  31. package/dist/entities/conversation-results.d.ts +42 -0
  32. package/dist/entities/evidence.d.ts +168 -0
  33. package/dist/entities/hit.d.ts +2 -0
  34. package/dist/entities/hit.js +2 -0
  35. package/dist/entities/index.d.ts +519 -202
  36. package/dist/entities/index.js +6 -3
  37. package/dist/entities/listener.d.ts +84 -0
  38. package/dist/entities/message-context.d.ts +63 -0
  39. package/dist/entities/message-write.d.ts +84 -0
  40. package/dist/entities/message.d.ts +21 -0
  41. package/dist/entities/reaction.d.ts +12 -0
  42. package/dist/entities/reaction.js +9 -0
  43. package/dist/entities/store-records.d.ts +21 -0
  44. package/dist/errors/reasons.js +2 -0
  45. package/dist/meetings/mcp/index.d.ts +15 -15
  46. package/dist/ports/messenger.d.ts +13 -2
  47. package/dist/render/markdown.js +4 -0
  48. package/dist/render/messages.js +7 -2
  49. package/dist/services/account.d.ts +1 -1
  50. package/dist/services/account.js +1 -2
  51. package/dist/services/people.d.ts +8 -5
  52. package/dist/services/people.js +6 -3
  53. package/dist/services/person-profile.d.ts +4 -3
  54. package/dist/services/person-profile.js +14 -3
  55. package/dist/services/pictures.d.ts +2 -1
  56. package/dist/services/pictures.js +2 -0
  57. package/dist/services/search-all.d.ts +3 -1
  58. package/dist/services/search-all.js +2 -0
  59. package/dist/store/sqlite/manifest.js +1 -0
  60. package/dist/store/sqlite/messages.js +5 -1
  61. package/dist/store/sqlite/migrations.generated.js +6 -0
  62. package/dist/store/sqlite/reads.js +1 -0
  63. package/dist/store/sqlite/schema.d.ts +16 -0
  64. package/dist/store/sqlite/schema.js +1 -0
  65. package/package.json +1 -1
  66. package/parity.json +20 -10
package/CHANGELOG.md CHANGED
@@ -7,6 +7,47 @@ shape of this file.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.478.0 — 29.10.2026
11
+
12
+ ### Fixed
13
+
14
+ - **`sender_is_me` on `inbox`, `review`, `search messages` and `search all`**, as 0.473.0 promised. The catch-up
15
+ answers and the search hits are built field by field, not from `messageItem`, and left it out; only `messages list`
16
+ and the other `messageItem` answers had it. A `search all` hit carries it for a message or a mail, and the picture
17
+ lane's hits of `search messages` carry it too. Every answer applies the one rule: `outgoing`, or the sender id is
18
+ the reading account's own.
19
+
20
+ ## 0.477.0 — 29.10.2026
21
+
22
+ ### Added
23
+
24
+ - **A shared contact card is an attachment with a `contact` field** (store version 36). `Attachment.contact`
25
+ (`SharedContact`: `name`, `firstName?`, `lastName?`, `phone?`, `userId?`) and, in every message answer,
26
+ `attachments[].contact` (`name`, `first_name?`, `last_name?`, `phone?`, `user_id?`), the phone whole. The store keeps it in
27
+ the new column `attachments.contact`, so a card read with `--offline` is the card the messenger sent; a later copy of the
28
+ message without it keeps the stored one. The terminal shows `👤 name · phone`. Additive: `minCompatible` stays 34.
29
+ What an adapter does: set `contact` on the attachment instead of putting a masked phone in `title`.
30
+ - **`--hide-phone`** on `account show`, `account update` and `contacts show`: the answer without the phone number.
31
+ - **`reactions list <chat> <message> [reaction:<emoji>] [--limit <n>] [--cursor <cursor>]`** (read) shows who reacted
32
+ to a message, newest first, as `{ person: { id, name, username }, reaction, reacted_at }` rows paged by cursor. A
33
+ messenger turns it on with `Messenger.reactionsList` and `MessageReactions.reactors`; where the chat hides the list
34
+ it refuses with `reactions.list-hidden` (or `reactions.broadcast` for a channel) instead of answering with none. The
35
+ permission key is `reactions.list`. New entity `reaction`, new type `Reactor`.
36
+
37
+ ### Changed — may break callers
38
+
39
+ - **Phone numbers are shown whole.** `account show`, `account update` and `contacts show` print the number the messenger
40
+ 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
41
+ as an unknown option; drop it), and so is `maskedAccount` from `/cli`: `withoutPhone(answer)` is what `--hide-phone` uses.
42
+ - **`contacts info` is folded into `contacts show`, with no alias.** `contacts show <person>` returns the card as before (alias,
43
+ shared chats, `--with-notes`) and, with it, the messenger's profile: `usernames`, `birthday`, `phone`, `flags`, `seen`,
44
+ `contact`, `mutual_contact`, `common_chats_count`, `registered`, `has_photo` and `aliases` (earlier names), and in each shared
45
+ chat `their_messages`, `first_at`, `last_at` and `complete`. The profile's `bio` is `description`. `--offline` answers from
46
+ the store alone, as every read does. The entity `contact_profile` and the MCP tool `contacts info` are gone, and `PeopleService.profile`
47
+ takes the card as an optional second argument. Permission keys are read only as written (0.421.0): a saved
48
+ `contacts.info` is refused as a key no command checks, with the `config unset` that removes it; `contacts.show` now covers
49
+ the profile too.
50
+
10
51
  ## 0.476.0 — 29.10.2026
11
52
 
12
53
  ### Fixed
@@ -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)
@@ -20,6 +20,7 @@ export declare const inboxAnswer: (inbox: Inbox, hearing: Hearing | undefined, r
20
20
  chat_title: string | null | undefined;
21
21
  timestamp: string;
22
22
  sender_name: string | null;
23
+ sender_is_me: boolean;
23
24
  outgoing: boolean | null;
24
25
  text: string;
25
26
  transcript?: string | undefined;
@@ -43,6 +44,13 @@ export declare const inboxAnswer: (inbox: Inbox, hearing: Hearing | undefined, r
43
44
  error?: string | undefined;
44
45
  } | undefined;
45
46
  local_path?: string | undefined;
47
+ contact?: {
48
+ name: string;
49
+ first_name?: string | undefined;
50
+ last_name?: string | undefined;
51
+ phone?: string | undefined;
52
+ user_id?: string | undefined;
53
+ } | undefined;
46
54
  buttons?: {
47
55
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
48
56
  text: string;
@@ -67,6 +75,7 @@ export declare const reviewAnswer: (found: Review, hearing: Hearing | undefined,
67
75
  chat_title: string | null | undefined;
68
76
  timestamp: string;
69
77
  sender_name: string | null;
78
+ sender_is_me: boolean;
70
79
  outgoing: boolean | null;
71
80
  text: string;
72
81
  transcript?: string | undefined;
@@ -90,6 +99,13 @@ export declare const reviewAnswer: (found: Review, hearing: Hearing | undefined,
90
99
  error?: string | undefined;
91
100
  } | undefined;
92
101
  local_path?: string | undefined;
102
+ contact?: {
103
+ name: string;
104
+ first_name?: string | undefined;
105
+ last_name?: string | undefined;
106
+ phone?: string | undefined;
107
+ user_id?: string | undefined;
108
+ } | undefined;
93
109
  buttons?: {
94
110
  kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
95
111
  text: string;
@@ -34,13 +34,14 @@ export const catchUpQuery = (words, registry, command) => {
34
34
  };
35
35
  };
36
36
  const summaryOf = (message, reader) => {
37
- const { ref, chat_id, chat_title, timestamp, sender_name, outgoing, text, transcript, attachments } = messageItem(message, reader);
37
+ const { ref, chat_id, chat_title, timestamp, sender_name, sender_is_me, outgoing, text, transcript, attachments } = messageItem(message, reader);
38
38
  return {
39
39
  ref,
40
40
  chat_id,
41
41
  chat_title,
42
42
  timestamp,
43
43
  sender_name,
44
+ sender_is_me,
44
45
  outgoing,
45
46
  text,
46
47
  ...(transcript === undefined ? {} : { transcript }),
@@ -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;
@@ -1,5 +1,5 @@
1
1
  import type * as v from "valibot";
2
- import type { Message, MessageHit } from "../../domain/models.js";
2
+ import { type Message, type MessageHit } from "../../domain/models.js";
3
3
  import type { message as messageSchema } from "../../entities/message.js";
4
4
  import type { MessengerAdapter } from "../../ports/messenger.js";
5
5
  import type { AccountKey } from "../../store/store.js";
@@ -1,5 +1,6 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  import { formatLocator, parseLocator } from "../../domain/locator.js";
3
+ import { senderIsMe, } from "../../domain/models.js";
3
4
  const defined = (value) => Object.fromEntries(Object.entries(value).filter(([, one]) => one !== undefined));
4
5
  const attachmentItem = (attachment) => defined({
5
6
  kind: attachment.kind,
@@ -11,6 +12,14 @@ const attachmentItem = (attachment) => defined({
11
12
  width: attachment.width,
12
13
  height: attachment.height,
13
14
  duration: attachment.duration,
15
+ contact: attachment.contact &&
16
+ defined({
17
+ name: attachment.contact.name,
18
+ first_name: attachment.contact.firstName,
19
+ last_name: attachment.contact.lastName,
20
+ phone: attachment.contact.phone,
21
+ user_id: attachment.contact.userId,
22
+ }),
14
23
  buttons: attachment.buttons,
15
24
  });
16
25
  const quotedItem = (quoted) => quoted === null
@@ -42,8 +51,7 @@ export const messageItem = (message, reader) => {
42
51
  sender_name: message.senderName,
43
52
  sender_username: message.senderUsername,
44
53
  sender_is_chat: message.senderIsChat,
45
- // MAX reports the owner's own messages with no sender id, so `outgoing` counts as well as the id.
46
- sender_is_me: message.outgoing === true || (message.senderId !== null && String(message.senderId) === account),
54
+ sender_is_me: senderIsMe(message, account),
47
55
  mentions: message.mentions,
48
56
  timestamp: message.timestamp,
49
57
  edited_at: message.editedAt,