@wirecat/cli-core 0.399.0 → 0.401.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 (259) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/README.md +1 -1
  3. package/dist/analysis/consents.d.ts +11 -6
  4. package/dist/analysis/consents.js +5 -2
  5. package/dist/cli/bot/mcp.js +2 -1
  6. package/dist/cli/bot/store.js +1 -1
  7. package/dist/cli/config-command.js +20 -1
  8. package/dist/cli/context.d.ts +2 -0
  9. package/dist/cli/context.js +1 -1
  10. package/dist/cli/effects.d.ts +4 -1
  11. package/dist/cli/effects.js +10 -2
  12. package/dist/cli/index.d.ts +3 -2
  13. package/dist/cli/index.js +3 -2
  14. package/dist/cli/json-fields.js +12 -1
  15. package/dist/cli/meeting-evidence-answer.d.ts +46 -0
  16. package/dist/cli/meeting-evidence-answer.js +22 -0
  17. package/dist/cli/meeting-preparation-answer.js +1 -13
  18. package/dist/cli/messenger/account-command.js +15 -6
  19. package/dist/cli/messenger/account-profile-query.d.ts +2 -0
  20. package/dist/cli/messenger/account-profile-query.js +13 -0
  21. package/dist/cli/messenger/account-sessions-command.js +1 -2
  22. package/dist/cli/messenger/admin-commands.js +12 -12
  23. package/dist/cli/messenger/admin-contacts-command.js +6 -3
  24. package/dist/cli/messenger/admin-folders-command.js +5 -5
  25. package/dist/cli/messenger/admin-moderation-command.js +4 -4
  26. package/dist/cli/messenger/admin-statistics-command.js +6 -14
  27. package/dist/cli/messenger/analysis-consents-query.d.ts +5 -0
  28. package/dist/cli/messenger/analysis-consents-query.js +20 -0
  29. package/dist/cli/messenger/archive-commands.js +3 -3
  30. package/dist/cli/messenger/attachments-command.js +3 -0
  31. package/dist/cli/messenger/backfill-command.js +5 -4
  32. package/dist/cli/messenger/chat-queries.d.ts +1 -0
  33. package/dist/cli/messenger/chat-queries.js +12 -2
  34. package/dist/cli/messenger/chats-command.js +2 -2
  35. package/dist/cli/messenger/chats-documents-command.js +7 -5
  36. package/dist/cli/messenger/chats-members-command.js +7 -3
  37. package/dist/cli/messenger/chats-read-command.js +1 -1
  38. package/dist/cli/messenger/chats-tracking-command.js +14 -6
  39. package/dist/cli/messenger/contacts-command.js +9 -5
  40. package/dist/cli/messenger/conversations-command.js +20 -16
  41. package/dist/cli/messenger/doctor-command.js +4 -1
  42. package/dist/cli/messenger/document-links-query.d.ts +2 -0
  43. package/dist/cli/messenger/document-links-query.js +9 -0
  44. package/dist/cli/messenger/download-command.js +1 -0
  45. package/dist/cli/messenger/flood-command.d.ts +2 -2
  46. package/dist/cli/messenger/flood-command.js +3 -3
  47. package/dist/cli/messenger/gaps-command.js +2 -2
  48. package/dist/cli/messenger/guard-commands.js +35 -13
  49. package/dist/cli/messenger/images-command.js +1 -0
  50. package/dist/cli/messenger/media-archive-command.js +2 -0
  51. package/dist/cli/messenger/media-archive-move-command.js +1 -0
  52. package/dist/cli/messenger/media-archive-timer-command.js +1 -1
  53. package/dist/cli/messenger/mentions-command.js +15 -2
  54. package/dist/cli/messenger/message-writes.d.ts +192 -2
  55. package/dist/cli/messenger/message-writes.js +6 -2
  56. package/dist/cli/messenger/messages-command.js +2 -0
  57. package/dist/cli/messenger/messages-delete-command.js +1 -0
  58. package/dist/cli/messenger/messages-edit-command.js +1 -0
  59. package/dist/cli/messenger/messages-evidence-command.js +3 -1
  60. package/dist/cli/messenger/messages-forward-command.js +1 -0
  61. package/dist/cli/messenger/messages-pin-command.js +3 -0
  62. package/dist/cli/messenger/messages-send-command.js +10 -6
  63. package/dist/cli/messenger/metadata-command.js +6 -1
  64. package/dist/cli/messenger/models-command.js +10 -5
  65. package/dist/cli/messenger/polls-command.js +4 -0
  66. package/dist/cli/messenger/private-people-command.js +3 -3
  67. package/dist/cli/messenger/rankings-command.js +6 -11
  68. package/dist/cli/messenger/reactions-command.js +2 -0
  69. package/dist/cli/messenger/recipients-query.d.ts +5 -0
  70. package/dist/cli/messenger/recipients-query.js +18 -0
  71. package/dist/cli/messenger/records-command.js +13 -8
  72. package/dist/cli/messenger/review.js +1 -1
  73. package/dist/cli/messenger/schedule-command.js +2 -2
  74. package/dist/cli/messenger/search-answers.js +2 -0
  75. package/dist/cli/messenger/search-command.js +3 -1
  76. package/dist/cli/messenger/searches-command.js +5 -1
  77. package/dist/cli/messenger/sends-query.d.ts +5 -0
  78. package/dist/cli/messenger/sends-query.js +33 -0
  79. package/dist/cli/messenger/serve-command.js +1 -0
  80. package/dist/cli/messenger/server-command.js +0 -3
  81. package/dist/cli/messenger/statistics-query.d.ts +14 -0
  82. package/dist/cli/messenger/statistics-query.js +143 -0
  83. package/dist/cli/messenger/stats-command.js +5 -5
  84. package/dist/cli/messenger/store-maintenance-command.js +10 -10
  85. package/dist/cli/messenger/stored.js +4 -1
  86. package/dist/cli/messenger/tags-command.js +7 -1
  87. package/dist/cli/messenger/tasks-command.js +57 -14
  88. package/dist/cli/messenger/transcribe-command.js +1 -0
  89. package/dist/cli/messenger/watch-answers.d.ts +6 -0
  90. package/dist/cli/messenger/watch-answers.js +69 -0
  91. package/dist/cli/messenger/watch-command.d.ts +1 -1
  92. package/dist/cli/messenger/watch-command.js +56 -7
  93. package/dist/cli/program.js +5 -1
  94. package/dist/cli/runs/command.js +25 -38
  95. package/dist/cli/runs/query.d.ts +5 -0
  96. package/dist/cli/runs/query.js +32 -0
  97. package/dist/cli/runs/search-command.d.ts +4 -0
  98. package/dist/cli/runs/search-command.js +27 -0
  99. package/dist/cli/runs/search-query.d.ts +4 -0
  100. package/dist/cli/runs/search-query.js +50 -0
  101. package/dist/cli/runs/search.d.ts +2 -1
  102. package/dist/cli/runs/search.js +6 -6
  103. package/dist/documents/index.d.ts +1 -0
  104. package/dist/documents/index.js +1 -0
  105. package/dist/domain/attachments.js +9 -1
  106. package/dist/domain/rankings-options.js +3 -3
  107. package/dist/entities/account-change.d.ts +89 -0
  108. package/dist/entities/account-change.js +20 -0
  109. package/dist/entities/account-profile.d.ts +7 -0
  110. package/dist/entities/account-profile.js +8 -0
  111. package/dist/entities/analysis-consent.d.ts +9 -0
  112. package/dist/entities/analysis-consent.js +4 -0
  113. package/dist/entities/attachment-results.d.ts +96 -0
  114. package/dist/entities/attachment-results.js +31 -0
  115. package/dist/entities/batch-result.d.ts +27 -0
  116. package/dist/entities/batch-result.js +18 -0
  117. package/dist/entities/chat-admin.d.ts +234 -0
  118. package/dist/entities/chat-admin.js +84 -0
  119. package/dist/entities/chat-change.d.ts +25 -0
  120. package/dist/entities/chat-change.js +11 -0
  121. package/dist/entities/chat-metadata.d.ts +34 -0
  122. package/dist/entities/chat-metadata.js +15 -0
  123. package/dist/entities/chat-moderation.d.ts +44 -0
  124. package/dist/entities/chat-moderation.js +37 -0
  125. package/dist/entities/chat-tracking.d.ts +26 -0
  126. package/dist/entities/chat-tracking.js +23 -0
  127. package/dist/entities/contact-activity.d.ts +52 -0
  128. package/dist/entities/contact-activity.js +42 -0
  129. package/dist/entities/contact-context.d.ts +1568 -0
  130. package/dist/entities/contact-context.js +191 -0
  131. package/dist/entities/contact-write.d.ts +22 -0
  132. package/dist/entities/contact-write.js +6 -0
  133. package/dist/entities/conversation-preparation.d.ts +63 -0
  134. package/dist/entities/conversation-preparation.js +23 -0
  135. package/dist/entities/conversation-results.d.ts +567 -0
  136. package/dist/entities/conversation-results.js +94 -0
  137. package/dist/entities/document-link.d.ts +13 -0
  138. package/dist/entities/document-link.js +11 -0
  139. package/dist/entities/extraction-report.d.ts +95 -0
  140. package/dist/entities/extraction-report.js +34 -0
  141. package/dist/entities/hit.d.ts +4 -0
  142. package/dist/entities/hit.js +4 -0
  143. package/dist/entities/image-embedding.d.ts +9 -0
  144. package/dist/entities/image-embedding.js +10 -0
  145. package/dist/entities/index.d.ts +8284 -1921
  146. package/dist/entities/index.js +178 -3
  147. package/dist/entities/listener.d.ts +2150 -0
  148. package/dist/entities/listener.js +46 -0
  149. package/dist/entities/media-archive.d.ts +43 -0
  150. package/dist/entities/media-archive.js +32 -0
  151. package/dist/entities/meeting-evidence.d.ts +86 -0
  152. package/dist/entities/meeting-evidence.js +25 -0
  153. package/dist/entities/meeting-preparation-parts.d.ts +14 -0
  154. package/dist/entities/meeting-preparation-parts.js +15 -13
  155. package/dist/entities/meeting-task-proposal.d.ts +14 -0
  156. package/dist/entities/meeting-task-proposal.js +19 -0
  157. package/dist/entities/meeting.d.ts +1 -1
  158. package/dist/entities/meeting.js +1 -1
  159. package/dist/entities/mention.d.ts +63 -0
  160. package/dist/entities/mention.js +44 -0
  161. package/dist/entities/message-change.d.ts +19 -0
  162. package/dist/entities/message-change.js +6 -0
  163. package/dist/entities/message-context.d.ts +617 -0
  164. package/dist/entities/message-context.js +20 -0
  165. package/dist/entities/message-read.d.ts +73 -0
  166. package/dist/entities/message-read.js +58 -0
  167. package/dist/entities/message-transfer.d.ts +379 -0
  168. package/dist/entities/message-transfer.js +83 -0
  169. package/dist/entities/message-write.d.ts +378 -0
  170. package/dist/entities/message-write.js +10 -0
  171. package/dist/entities/model-key.d.ts +14 -0
  172. package/dist/entities/model-key.js +10 -0
  173. package/dist/entities/poll.d.ts +40 -0
  174. package/dist/entities/poll.js +16 -0
  175. package/dist/entities/private-contact.d.ts +10 -0
  176. package/dist/entities/private-contact.js +4 -0
  177. package/dist/entities/reaction-change.d.ts +7 -0
  178. package/dist/entities/reaction-change.js +8 -0
  179. package/dist/entities/recipient.d.ts +29 -0
  180. package/dist/entities/recipient.js +11 -0
  181. package/dist/entities/recorded-run.d.ts +103 -0
  182. package/dist/entities/recorded-run.js +44 -0
  183. package/dist/entities/saved-search.d.ts +4 -0
  184. package/dist/entities/saved-search.js +2 -0
  185. package/dist/entities/send-attempt.d.ts +31 -0
  186. package/dist/entities/send-attempt.js +29 -0
  187. package/dist/entities/store-maintenance.d.ts +319 -0
  188. package/dist/entities/store-maintenance.js +145 -0
  189. package/dist/entities/store-records.d.ts +442 -0
  190. package/dist/entities/store-records.js +99 -0
  191. package/dist/entities/tag-change.d.ts +74 -0
  192. package/dist/entities/tag-change.js +31 -0
  193. package/dist/entities/task-pair.d.ts +31 -0
  194. package/dist/entities/task-pair.js +23 -0
  195. package/dist/entities/task-results.d.ts +524 -0
  196. package/dist/entities/task-results.js +142 -0
  197. package/dist/errors/reasons.js +3 -0
  198. package/dist/mcp/agent.d.ts +3 -1
  199. package/dist/mcp/agent.js +17 -10
  200. package/dist/mcp/bot/server.d.ts +2 -0
  201. package/dist/mcp/bot/server.js +1 -0
  202. package/dist/mcp/server.js +3 -1
  203. package/dist/mcp/tools/attachments.js +1 -0
  204. package/dist/meetings/cli/index.js +4 -2
  205. package/dist/meetings/cli/passage.js +1 -0
  206. package/dist/meetings/index.d.ts +2 -1
  207. package/dist/meetings/index.js +1 -0
  208. package/dist/meetings/links.d.ts +16 -0
  209. package/dist/meetings/read-answers.d.ts +185 -0
  210. package/dist/meetings/read-answers.js +213 -0
  211. package/dist/ports/messenger.d.ts +10 -1
  212. package/dist/runs/run.d.ts +10 -2
  213. package/dist/runs/run.js +11 -3
  214. package/dist/sends/guard.d.ts +1 -1
  215. package/dist/sends/guard.js +9 -5
  216. package/dist/sends/journal.d.ts +9 -2
  217. package/dist/sends/journal.js +9 -2
  218. package/dist/sends/permissions.d.ts +5 -1
  219. package/dist/sends/permissions.js +42 -2
  220. package/dist/sends/recipients.d.ts +8 -0
  221. package/dist/sends/recipients.js +9 -0
  222. package/dist/sends/upload.d.ts +4 -4
  223. package/dist/sends/upload.js +24 -20
  224. package/dist/services/admin-statistics.js +3 -3
  225. package/dist/services/chat-documents.d.ts +1 -0
  226. package/dist/services/chat-documents.js +9 -3
  227. package/dist/services/chats.d.ts +1 -1
  228. package/dist/services/chats.js +5 -2
  229. package/dist/services/documents/mirrors.d.ts +98 -0
  230. package/dist/services/documents/mirrors.js +144 -0
  231. package/dist/services/meeting-links.d.ts +2 -17
  232. package/dist/services/messages.js +1 -0
  233. package/dist/services/search-all-meetings.js +21 -8
  234. package/dist/services/task-rules.d.ts +1 -1
  235. package/dist/services/tasks.d.ts +2 -2
  236. package/dist/services/tasks.js +1 -1
  237. package/dist/store/exports.d.ts +1 -1
  238. package/dist/store/exports.js +1 -1
  239. package/dist/store/messages.d.ts +3 -1
  240. package/dist/store/messages.js +2 -2
  241. package/dist/store/roster.d.ts +4 -1
  242. package/dist/store/roster.js +2 -2
  243. package/dist/store/sqlite/chats.d.ts +12 -0
  244. package/dist/store/sqlite/chats.js +1 -0
  245. package/dist/store/sqlite/document-links.d.ts +1 -1
  246. package/dist/store/sqlite/document-links.js +5 -5
  247. package/dist/store/sqlite/drizzle/bun.js +3 -3
  248. package/dist/store/sqlite/drizzle/bun.js.map +1 -1
  249. package/dist/store/sqlite/drizzle/core.js +2 -2
  250. package/dist/store/sqlite/drizzle/core.js.map +1 -1
  251. package/dist/store/sqlite/drizzle/node.js +3 -3
  252. package/dist/store/sqlite/drizzle/node.js.map +1 -1
  253. package/dist/store/sqlite/drizzle/{shared-O7CTKP45.js → shared-HKXF6HKX.js} +57 -57
  254. package/dist/store/sqlite/drizzle/{shared-O7CTKP45.js.map → shared-HKXF6HKX.js.map} +1 -1
  255. package/dist/store/sqlite/involvements.js +2 -1
  256. package/dist/store/sqlite/roster.d.ts +2 -1
  257. package/dist/store/sqlite/roster.js +3 -2
  258. package/package.json +2 -2
  259. package/parity.json +14 -69
@@ -57,7 +57,7 @@ export const readProfilePhoto = (messenger, path, env) => readUpload("photo", pa
57
57
  });
58
58
  const updateCommand = (messenger) => {
59
59
  const keys = messenger.groupSettings ?? GROUP_SETTINGS;
60
- const update = annotate(new Command("update"), { effects: REMOTE_WRITE })
60
+ const update = annotate(new Command("update"), { output: { entity: "chat_group_change" }, effects: REMOTE_WRITE })
61
61
  .summary("change a group or channel's profile and settings")
62
62
  .description("rename a group or channel, change its description, or turn one of its settings on or off")
63
63
  .argument("<chat>", messenger.chatArgument)
@@ -93,7 +93,7 @@ const updateCommand = (messenger) => {
93
93
  };
94
94
  const linkCommand = (messenger) => {
95
95
  const link = new Command("link").description("a group's invite link");
96
- annotate(link.command("show"), { effects: REMOTE_READ })
96
+ annotate(link.command("show"), { output: { entity: "chat_invite" }, effects: REMOTE_READ })
97
97
  .description("the invite link, if you may see it")
98
98
  .argument("<chat>", messenger.chatArgument)
99
99
  .action(async function (chat) {
@@ -101,7 +101,7 @@ const linkCommand = (messenger) => {
101
101
  renderItem(this, context, await context.withServices((services) => services.admin.link(chat)));
102
102
  });
103
103
  if (messenger.inviteLinkCreate !== false)
104
- link.addCommand(annotate(new Command("create"), { effects: REMOTE_WRITE })
104
+ link.addCommand(annotate(new Command("create"), { output: { entity: "chat_invite_change" }, effects: REMOTE_WRITE })
105
105
  .description("make another invite link; nobody is told until you share it")
106
106
  .argument("<chat>", messenger.chatArgument)
107
107
  .option("--approval", "who joins by it asks first, and an admin lets them in; it then has no use limit")
@@ -132,7 +132,7 @@ const linkCommand = (messenger) => {
132
132
  ].join(" ")));
133
133
  }));
134
134
  if (messenger.inviteLinkRevoke !== false)
135
- link.addCommand(annotate(new Command("revoke"), { effects: REMOTE_WRITE })
135
+ link.addCommand(annotate(new Command("revoke"), { output: { entity: "chat_invite_change" }, effects: REMOTE_WRITE })
136
136
  .description("stop one link; for the group's own link, the answer is the new one")
137
137
  .argument("<chat>", messenger.chatArgument)
138
138
  .argument("<link>", "the link, as `chats link list` shows it")
@@ -141,7 +141,7 @@ const linkCommand = (messenger) => {
141
141
  renderItem(this, context, await context.withServices((services) => services.admin.revokeLink(chat, link)));
142
142
  }));
143
143
  if (messenger.inviteLinkUpdate === true)
144
- link.addCommand(annotate(new Command("update"), { effects: REMOTE_WRITE })
144
+ link.addCommand(annotate(new Command("update"), { output: { entity: "chat_invite_change" }, effects: REMOTE_WRITE })
145
145
  .description("change one of your invite links, the group's own one too")
146
146
  .argument("<chat>", messenger.chatArgument)
147
147
  .argument("<link>", "the link, as `chats link list` shows it")
@@ -158,7 +158,7 @@ const linkCommand = (messenger) => {
158
158
  ...(maxUses === undefined ? {} : { maxUses }),
159
159
  })));
160
160
  }));
161
- link.addCommand(annotate(new Command("reset"), { effects: REMOTE_WRITE })
161
+ link.addCommand(annotate(new Command("reset"), { output: { entity: "chat_group_change" }, effects: REMOTE_WRITE })
162
162
  .description("replace the invite link; the old one stops working")
163
163
  .argument("<chat>", messenger.chatArgument)
164
164
  .action(async function (chat) {
@@ -171,7 +171,7 @@ const adminsCommand = (messenger) => {
171
171
  const offered = messenger.adminRights ?? ADMIN_RIGHTS;
172
172
  const admins = new Command("admins").description("give or take back a member's admin rights");
173
173
  const picks = messenger.pickAdminRights !== false;
174
- const add = annotate(new Command("add"), { effects: REMOTE_WRITE })
174
+ const add = annotate(new Command("add"), { output: { entity: "chat_admin_change" }, effects: REMOTE_WRITE })
175
175
  .summary(picks ? "make a member an admin with these rights" : "make a member an admin, with every right")
176
176
  .description(picks
177
177
  ? "make a member an admin with these rights"
@@ -185,7 +185,7 @@ const adminsCommand = (messenger) => {
185
185
  const rights = picks ? rightsOf(offered, this.opts().can) : [...offered];
186
186
  renderItem(this, context, await context.withServices((services) => services.admin.addAdmin(chat, person, rights)));
187
187
  }));
188
- admins.addCommand(annotate(new Command("remove"), { effects: REMOTE_WRITE })
188
+ admins.addCommand(annotate(new Command("remove"), { output: { entity: "chat_admin_change" }, effects: REMOTE_WRITE })
189
189
  .description("take an admin's rights back; they stay a member")
190
190
  .argument("<chat>", messenger.chatArgument)
191
191
  .argument("<person>", "an id, or part of a name")
@@ -223,7 +223,7 @@ const requestsCommand = (messenger) => {
223
223
  ["accept", true, "let them in; the group sees them join"],
224
224
  ["decline", false, "turn the request away"],
225
225
  ]) {
226
- const answer = annotate(new Command(verb), { effects: REMOTE_WRITE })
226
+ const answer = annotate(new Command(verb), { output: { entity: "chat_request_change" }, effects: REMOTE_WRITE })
227
227
  .description(help)
228
228
  .argument("<chat>", messenger.chatArgument)
229
229
  .argument(everyone ? "[person]" : "<person>", "who asked: an id from `chats requests list`");
@@ -247,7 +247,7 @@ const requestsCommand = (messenger) => {
247
247
  };
248
248
  /** `chats create`, `join`, `leave`, `update` and `link`: the help text is max-cli's, the one both tools say (standard, help rule 2). */
249
249
  export const groupCommands = (messenger) => [
250
- annotate(new Command("create"), { effects: REMOTE_WRITE })
250
+ annotate(new Command("create"), { output: { entity: "chat_group_change" }, effects: REMOTE_WRITE })
251
251
  .description("create a group or a channel; the people added are told")
252
252
  .argument("<title>", "the group's name")
253
253
  .argument("[person...]", "people to add: an id, or part of a name")
@@ -257,14 +257,14 @@ export const groupCommands = (messenger) => [
257
257
  const { channel } = this.opts();
258
258
  renderItem(this, context, await context.withServices((services) => services.admin.create({ title, people, channel: channel === true })));
259
259
  }),
260
- annotate(new Command("join"), { effects: REMOTE_WRITE })
260
+ annotate(new Command("join"), { output: { entity: "chat_group_change" }, effects: REMOTE_WRITE })
261
261
  .description("join a group or channel by its link; the others in it see that you joined")
262
262
  .argument("<link>", `an invite link, or a public one`)
263
263
  .action(async function (link) {
264
264
  const context = messengerContext(this, messenger);
265
265
  renderItem(this, context, await context.withServices((services) => services.admin.join(link)));
266
266
  }),
267
- annotate(new Command("leave"), { effects: REMOTE_WRITE })
267
+ annotate(new Command("leave"), { output: { entity: "chat_removal" }, effects: REMOTE_WRITE })
268
268
  .description("leave a group or channel; the others in it see that you left")
269
269
  .argument("<chat>", messenger.chatArgument)
270
270
  .action(async function (chat) {
@@ -18,7 +18,10 @@ export const contactWriteCommands = (messenger) => {
18
18
  ["remove", "remove a person from your contacts; the chat stays, a name you gave them may not"],
19
19
  ["block", "stop a person from writing to you — they need not be a contact"],
20
20
  ["unblock", "let a blocked person write to you again"],
21
- ].map(([name, description, summary]) => annotate(new Command(name), { effects: REMOTE_WRITE })
21
+ ].map(([name, description, summary]) => annotate(new Command(name), {
22
+ effects: REMOTE_WRITE,
23
+ output: { entity: name === "add" ? "contact_update" : "contact_state_change" },
24
+ })
22
25
  .summary(summary ?? description)
23
26
  .description(description)
24
27
  .argument("<person>", PERSON)
@@ -27,7 +30,7 @@ export const contactWriteCommands = (messenger) => {
27
30
  const done = await context.withServices((services) => services.people[name](person));
28
31
  renderAnswer(this, context, itemOf(snakeKeys(done)));
29
32
  }));
30
- const rename = annotate(new Command("rename"), { effects: REMOTE_WRITE })
33
+ const rename = annotate(new Command("rename"), { effects: REMOTE_WRITE, output: { entity: "contact_update" } })
31
34
  .summary("change a contact's name in the messenger address book")
32
35
  .description("rename the contact in the messenger address book; use contacts alias for a private local name")
33
36
  .argument("<person>", PERSON)
@@ -39,7 +42,7 @@ export const contactWriteCommands = (messenger) => {
39
42
  renderAnswer(this, context, itemOf(snakeKeys(renamed)));
40
43
  });
41
44
  /** A file rather than argv: these are phone numbers, and argv is read by `ps` and kept by the shell. */
42
- const importing = annotate(new Command("import"), { effects: REMOTE_WRITE })
45
+ const importing = annotate(new Command("import"), { effects: REMOTE_WRITE, output: { entity: "contact_import" } })
43
46
  .description("upload phone numbers and add the people the messenger has under them")
44
47
  .argument("<file>", "one person per line: number, then a comma, a tab or a semicolon, then the name")
45
48
  .action(async function (file) {
@@ -57,7 +57,7 @@ export const foldersCommand = (messenger) => {
57
57
  const found = await context.withServices((services) => services.folders.show(folder));
58
58
  renderAnswer(this, context, itemOf(folderAnswer(found)));
59
59
  }));
60
- folders.addCommand(withRules(annotate(new Command("create"), { effects: REMOTE_WRITE })
60
+ folders.addCommand(withRules(annotate(new Command("create"), { output: { entity: "chat_folder_change" }, effects: REMOTE_WRITE })
61
61
  .description("create a chat folder")
62
62
  .argument("<title>", "the folder's name; the app may refuse a long one")
63
63
  .option("--chat <chat>", "a chat to put in it, by id or name; repeat it for more", collect), messenger, false).action(async function (title) {
@@ -66,7 +66,7 @@ export const foldersCommand = (messenger) => {
66
66
  const rules = rulesOf(this);
67
67
  renderItem(this, context, await context.withServices((services) => services.folders.create(title, chat, rules)));
68
68
  }));
69
- folders.addCommand(withRules(annotate(new Command("update"), { effects: REMOTE_WRITE })
69
+ folders.addCommand(withRules(annotate(new Command("update"), { output: { entity: "chat_folder_change" }, effects: REMOTE_WRITE })
70
70
  .description("rename a folder, or change which chats are in it")
71
71
  .argument("<folder>", "folder id, or its title exactly")
72
72
  .option("--title <title>", "a new name")
@@ -82,7 +82,7 @@ export const foldersCommand = (messenger) => {
82
82
  };
83
83
  renderItem(this, context, await context.withServices((services) => services.folders.update(folder, edit)));
84
84
  }));
85
- folders.addCommand(annotate(new Command("delete"), { effects: REMOTE_WRITE })
85
+ folders.addCommand(annotate(new Command("delete"), { output: { entity: "chat_folder_removal" }, effects: REMOTE_WRITE })
86
86
  .description("delete a folder; the chats in it stay")
87
87
  .argument("<folder>", "folder id, or its title exactly")
88
88
  .action(async function (folder) {
@@ -90,7 +90,7 @@ export const foldersCommand = (messenger) => {
90
90
  renderItem(this, context, await context.withServices((services) => services.folders.delete(folder)));
91
91
  }));
92
92
  if (messenger.folderOrder !== false)
93
- folders.addCommand(annotate(new Command("order"), { effects: REMOTE_WRITE })
93
+ folders.addCommand(annotate(new Command("order"), { output: { entity: "chat_folder_order" }, effects: REMOTE_WRITE })
94
94
  .description("put folders in this order; the ones not named keep theirs after them")
95
95
  .argument("<folders...>", "folder ids, or titles exactly, first one first")
96
96
  .action(async function (typed) {
@@ -98,7 +98,7 @@ export const foldersCommand = (messenger) => {
98
98
  renderItem(this, context, await context.withServices((services) => services.folders.order(typed)));
99
99
  }));
100
100
  if (messenger.folderJoin !== false)
101
- folders.addCommand(annotate(new Command("join"), { effects: REMOTE_WRITE })
101
+ folders.addCommand(annotate(new Command("join"), { output: { entity: "chat_folder_change" }, effects: REMOTE_WRITE })
102
102
  .description("add a folder someone shared by a link; joins every chat in it, and the others there see you joined")
103
103
  .argument("<link>", "the folder's link, as t.me/addlist/…")
104
104
  .action(async function (link) {
@@ -12,14 +12,14 @@ import { messengerContext } from "./context.js";
12
12
  /** `chats rules show|set|unset`: the rules live in a file of this profile, and only the chat's id is asked of the messenger. */
13
13
  export const rulesCommand = (messenger) => {
14
14
  const rules = new Command("rules").description("what `chats moderate` judges a group by, kept in a file of this profile");
15
- annotate(rules.command("show"), { effects: LOCAL_READ })
15
+ annotate(rules.command("show"), { output: { entity: "chat_rules" }, effects: LOCAL_READ })
16
16
  .description("the group's rules; the defaults, marked not saved, if it has none yet")
17
17
  .argument("<chat>", messenger.chatArgument)
18
18
  .action(async function (chat) {
19
19
  const context = messengerContext(this, messenger);
20
20
  renderItem(this, context, await context.withServices((services) => services.moderation.rules(chat)));
21
21
  });
22
- rules.addCommand(grantsAuthority(annotate(new Command("set"), { effects: LOCAL_WRITE }))
22
+ rules.addCommand(grantsAuthority(annotate(new Command("set"), { output: { entity: "chat_rules" }, effects: LOCAL_WRITE }))
23
23
  .description("change one rule; the group's first change writes every rule with its default")
24
24
  .argument("<chat>", messenger.chatArgument)
25
25
  .argument("<key>", `one of: ${RULE_KEYS.join(", ")}`)
@@ -28,7 +28,7 @@ export const rulesCommand = (messenger) => {
28
28
  const context = messengerContext(this, messenger);
29
29
  renderItem(this, context, await context.withServices((services) => services.moderation.set(chat, key, value)));
30
30
  }));
31
- rules.addCommand(grantsAuthority(annotate(new Command("unset"), { effects: LOCAL_WRITE }))
31
+ rules.addCommand(grantsAuthority(annotate(new Command("unset"), { output: { entity: "chat_rules" }, effects: LOCAL_WRITE }))
32
32
  .description("put one rule back to its default")
33
33
  .argument("<chat>", messenger.chatArgument)
34
34
  .argument("<key>", `one of: ${RULE_KEYS.join(", ")}`)
@@ -43,7 +43,7 @@ export const rulesCommand = (messenger) => {
43
43
  * for what the rules name, within each action's level in the group's rules. Nothing watches groups
44
44
  * in the background.
45
45
  */
46
- export const moderateCommand = (messenger) => annotate(new Command("moderate"), { effects: REMOTE_WRITE })
46
+ export const moderateCommand = (messenger) => annotate(new Command("moderate"), { output: { entity: "chat_moderation" }, effects: REMOTE_WRITE })
47
47
  .description("judge a group's new messages and members by its rules, and act as they allow")
48
48
  .argument("<chat>", messenger.chatArgument)
49
49
  .option("--since-time <time>", "judge what came after this ISO 8601 time, or 2h / 1d ago; the saved point stays")
@@ -5,6 +5,7 @@ import { REMOTE_READ } from "../effects.js";
5
5
  import { renderAnswer } from "../envelope.js";
6
6
  import { positiveCount } from "../paging.js";
7
7
  import { messengerContext } from "./context.js";
8
+ import { statisticsQuery, withStatisticsQuery } from "./statistics-query.js";
8
9
  import { adminAnswer, refuseJsonl } from "./stats-answers.js";
9
10
  const collect = (value, previous = []) => [...previous, value];
10
11
  export const adminStatisticsCommand = (messenger, report) => {
@@ -27,22 +28,17 @@ export const adminStatisticsCommand = (messenger, report) => {
27
28
  else
28
29
  command
29
30
  .argument("[query...]", "a strict Lucene query; none selects every stored message")
30
- .option("--chat <chat>", `only this chat; ${messenger.chatArgument}`)
31
- .option("--source <accounts>", "every held account of this messenger; personal, bots or all")
32
- .option("--exact", "bare words match exact forms rather than stems");
31
+ .option("--source <accounts>", "every held account of this messenger; personal, bots or all");
33
32
  command.option("--saved <name|id>", "run a saved report of this kind; typed report options replace stored options");
34
33
  command
35
34
  .option("--timezone <zone>", "the IANA timezone for calendar date boundaries")
36
35
  .option("--limit <n>", "report rows, 1–100; 20 if not given", positiveCount("--limit"));
37
36
  if (report !== "discussion")
38
37
  command.option("--answerer <person>", "stored name, alias, @username, ID or person:provider/account/id; ambiguous names require a choice; repeat for more", collect);
39
- if (report === "unanswered")
40
- command.option("--older-than <duration>", "minimum age of a question without an observed qualifying answer");
41
- if (report === "discussion")
42
- command
43
- .option("--min-views <n>", "minimum known cumulative views", Number)
44
- .option("--max-replies <n>", "maximum observed discussion replies", Number);
38
+ if (report !== "newcomers")
39
+ withStatisticsQuery(command, report);
45
40
  command.action(async function (argument) {
41
+ const filters = report === "newcomers" ? {} : statisticsQuery(argument, report);
46
42
  const context = messengerContext(this, messenger);
47
43
  refuseJsonl(context.format);
48
44
  const { answerer, ...options } = this.opts();
@@ -50,11 +46,7 @@ export const adminStatisticsCommand = (messenger, report) => {
50
46
  ...options,
51
47
  limit: options.limit ?? 20,
52
48
  ...(answerer ? { answerers: answerer } : {}),
53
- ...(report === "newcomers"
54
- ? { chat: argument }
55
- : argument.length
56
- ? { text: argument.join(" ") }
57
- : {}),
49
+ ...(report === "newcomers" ? { chat: argument } : filters),
58
50
  signal: environmentOf(this).commandSignal ?? environmentOf(this).signal,
59
51
  }));
60
52
  renderAnswer(this, context, adminAnswer(found));
@@ -0,0 +1,5 @@
1
+ import { type QueryRegistry } from "@wirecat/cli-base/query";
2
+ import type { AnalysisConsentFilter } from "../../analysis/consents.js";
3
+ import { type QueryWords } from "../list-query.js";
4
+ export declare const ANALYSIS_CONSENTS_QUERY: QueryRegistry;
5
+ export declare const analysisConsentFilter: (words: QueryWords) => AnalysisConsentFilter | undefined;
@@ -0,0 +1,20 @@
1
+ import { queryFieldsOf } from "@wirecat/cli-base/query";
2
+ import { analysisConsent } from "../../entities/index.js";
3
+ import { queryTerms } from "../list-query.js";
4
+ export const ANALYSIS_CONSENTS_QUERY = {
5
+ entity: "analysis_consent",
6
+ backend: "native",
7
+ fields: queryFieldsOf(analysisConsent, {
8
+ native: ["chat", "provider"],
9
+ examples: { chat: "chat:7", provider: 'provider:"openai:https://example.test/v1"' },
10
+ }).map((field) => field.native
11
+ ? { ...field, operators: field.operators.filter((operator) => operator === "term" || operator === "phrase") }
12
+ : field),
13
+ };
14
+ export const analysisConsentFilter = (words) => {
15
+ const terms = queryTerms(words, ANALYSIS_CONSENTS_QUERY, "conversations consents list");
16
+ if (terms.size === 0)
17
+ return undefined;
18
+ const chat = terms.get("chat"), provider = terms.get("provider");
19
+ return { ...(chat === undefined ? {} : { chat }), ...(provider === undefined ? {} : { provider }) };
20
+ };
@@ -64,7 +64,7 @@ export const storeCommand = (messenger) => {
64
64
  * What the local store holds, per chat — read from the store alone. Whether a fetch reached a
65
65
  * chat's very first message is not recorded, so it is not claimed: the stretches held are shown.
66
66
  */
67
- const statusCommand = (messenger) => annotate(new Command("status"), { effects: LOCAL_READ })
67
+ const statusCommand = (messenger) => annotate(new Command("status"), { output: { entity: "store_chat_status" }, effects: LOCAL_READ })
68
68
  .summary("what the local archive holds and what is missing")
69
69
  .description("per chat: messages stored, the oldest and newest, and the stretches held completely")
70
70
  .argument("[chat]", messenger.chatArgument)
@@ -93,7 +93,7 @@ const leftLines = (left) => {
93
93
  * store is not offered. The refusal names the chats to a person but keeps them out of its message,
94
94
  * which a scheduled run's `last.json` keeps.
95
95
  */
96
- const clearCommand = (messenger) => annotate(new Command("clear"), { effects: LOCAL_WRITE, mcp: false })
96
+ const clearCommand = (messenger) => annotate(new Command("clear"), { output: { entity: "store_clear" }, effects: LOCAL_WRITE })
97
97
  .description("delete from the store the chats this account has left, with their messages")
98
98
  .option("--left", "the chats this account has left — the only thing this clears")
99
99
  .option("--allow-dangerous", "yes, delete — it cannot be undone, and a chat you left cannot be fetched again")
@@ -132,7 +132,7 @@ const clearCommand = (messenger) => annotate(new Command("clear"), { effects: LO
132
132
  * `--format markdown` for a transcript a person reads. With `--output-dir`, any number of chats into a
133
133
  * folder, and each later run into the same folder adds only what changed.
134
134
  */
135
- const exportCommand = (messenger) => destroysNothing(annotate(new Command("export"), { effects: LOCAL_WRITE }))
135
+ const exportCommand = (messenger) => destroysNothing(annotate(new Command("export"), { output: { entity: "store_export" }, effects: LOCAL_WRITE }))
136
136
  .summary("export stored messages to files")
137
137
  .description("a chat's stored messages, oldest first: --jsonl one message per line, --json the list envelope of messages; never asks the messenger")
138
138
  .argument("[chat...]", `${messenger.chatArgument}; several with --output-dir`)
@@ -36,6 +36,7 @@ const summary = (run) => [
36
36
  .filter(Boolean)
37
37
  .join(", ");
38
38
  const extractCommand = (messenger) => annotate(new Command("extract"), {
39
+ output: { entity: "attachment_extraction" },
39
40
  effects: FETCH,
40
41
  examples: [`${messenger.app.command} personal attachments extract --chat "Book club" --limit 10`],
41
42
  })
@@ -138,6 +139,7 @@ const listCommand = (messenger) => withQuery(withPaging(annotate(new Command("li
138
139
  renderAnswer(this, context, pagedList("attachments list", context.settings, found), () => found.items.length === 0 ? "" : `${found.items.map(row).join("\n")}\n`);
139
140
  });
140
141
  const showCommand = (messenger) => annotate(new Command("show"), {
142
+ output: { entity: "attachment_bytes" },
141
143
  effects: REMOTE_READ,
142
144
  examples: [`${messenger.app.command} personal attachments show "Book club" 101 --attachment 1`],
143
145
  })
@@ -173,6 +175,7 @@ const showCommand = (messenger) => annotate(new Command("show"), {
173
175
  const textCommand = (messenger) => {
174
176
  const text = new Command("text").description("the text of one file, as an agent read it");
175
177
  readsStdin(annotate(text.command("set"), {
178
+ output: { entity: "attachment_text_change" },
176
179
  effects: LOCAL_WRITE,
177
180
  examples: [`${messenger.app.command} personal attachments text set "Book club" 101 --text-file ./transcript.txt`],
178
181
  }), {
@@ -35,6 +35,7 @@ const ALL_CHATS = "--all";
35
35
  export const syncCommand = (messenger) => {
36
36
  const fetching = messenger.fetching ?? FETCHING;
37
37
  return annotate(new Command("sync"), {
38
+ output: { entity: "message_sync" },
38
39
  examples: [`${messenger.app.command} personal messages sync "Book club" --last 100`],
39
40
  effects: FETCH,
40
41
  cooperative: true,
@@ -224,7 +225,7 @@ export const jobsCommand = (messenger) => {
224
225
  if (jobs.length === 0 && context.format === "pretty")
225
226
  context.renderer.note(`no ${query ? "matching " : ""}background sync jobs for this profile`);
226
227
  });
227
- annotate(command.command("show"), { effects: LOCAL_READ })
228
+ annotate(command.command("show"), { output: { entity: "store_job_details" }, effects: LOCAL_READ })
228
229
  .summary("one archive job's status and log")
229
230
  .description("one background job — the newest when none is named — and what the store now holds of its chat")
230
231
  .argument("[job]", "the job id `messages sync --background` printed")
@@ -237,7 +238,7 @@ export const jobsCommand = (messenger) => {
237
238
  : await context.withServices((services) => services.archive.held(chatId)).catch(() => undefined);
238
239
  renderAnswer(this, context, itemOf(snakeKeys({ ...jobDetails(job), ...(held ? { held } : {}), log: job.log })));
239
240
  });
240
- destroysNothing(annotate(command.command("cancel"), { effects: LOCAL_WRITE }))
241
+ destroysNothing(annotate(command.command("cancel"), { output: { entity: "store_job_cancel" }, effects: LOCAL_WRITE }))
241
242
  .summary("cancel a queued or running archive job")
242
243
  .description("stop a running background job after its current page; a later sync resumes where it stopped")
243
244
  .argument("<job>", "the job id")
@@ -251,7 +252,7 @@ export const jobsCommand = (messenger) => {
251
252
  process.kill(job.pid, "SIGTERM");
252
253
  renderAnswer(this, context, itemOf({ job: job.id, cancelled: true }));
253
254
  });
254
- destroysNothing(annotate(command.command("retry"), { effects: LOCAL_WRITE }))
255
+ destroysNothing(annotate(command.command("retry"), { output: { entity: "store_job_retry" }, effects: LOCAL_WRITE }))
255
256
  .summary("retry an archive job")
256
257
  .description("start a partial, failed or died job again, as a new job; the sync resumes where the store stopped")
257
258
  .argument("[job]", "the job id")
@@ -292,7 +293,7 @@ export const jobsCommand = (messenger) => {
292
293
  if (items.length === 0 && context.format === "pretty")
293
294
  context.renderer.note("no partial, failed or died jobs to retry");
294
295
  });
295
- destroysNothing(annotate(command.command("clear"), { effects: LOCAL_WRITE }))
296
+ destroysNothing(annotate(command.command("clear"), { output: { entity: "store_job_clear" }, effects: LOCAL_WRITE }))
296
297
  .description("forget finished jobs and remove their logs; a running job is kept")
297
298
  .action(function () {
298
299
  const context = messengerContext(this, messenger);
@@ -4,6 +4,7 @@ import { type QueryRegistry } from "@wirecat/cli-base/query";
4
4
  * there; `--offline`, or a messenger whose history is the store, answers them.
5
5
  */
6
6
  export declare const CHATS_QUERY: QueryRegistry;
7
+ export declare const TRACKING_QUERY: QueryRegistry;
7
8
  /** The messenger reads a chat's events back to a time; it cannot pick them by type. */
8
9
  export declare const EVENTS_QUERY: QueryRegistry;
9
10
  /** The store's member history is read back to a time. */
@@ -1,8 +1,8 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  import { queryError, queryFieldsOf } from "@wirecat/cli-base/query";
3
- import { chat, chatEvent, inviteLink, joinRequest, member } from "../../entities/index.js";
3
+ import { chat, chatEvent, ENTITIES, inviteLink, joinRequest, member } from "../../entities/index.js";
4
4
  import { AUDIT_MIN_SCORE } from "../../services/members-audit.js";
5
- import { CHAT_COLUMNS, titlePart } from "../../store/store.js";
5
+ import { CHAT_COLUMNS, TRACKED_CHAT_COLUMNS, titlePart } from "../../store/store.js";
6
6
  import { unfilteredQuery } from "../list-query.js";
7
7
  const titleWords = (node) => {
8
8
  const part = titlePart(node.value);
@@ -25,6 +25,16 @@ export const CHATS_QUERY = {
25
25
  validate: { title: titleWords },
26
26
  }),
27
27
  };
28
+ export const TRACKING_QUERY = {
29
+ entity: "tracked_chat",
30
+ backend: "sql",
31
+ fields: queryFieldsOf(ENTITIES.tracked_chat, {
32
+ exclude: Object.keys(ENTITIES.tracked_chat.entries).filter((name) => !(name in TRACKED_CHAT_COLUMNS)),
33
+ text: ["title"],
34
+ examples: { chat_id: "chat_id:7", title: "title:book" },
35
+ validate: { title: titleWords },
36
+ }),
37
+ };
28
38
  /** The messenger reads a chat's events back to a time; it cannot pick them by type. */
29
39
  export const EVENTS_QUERY = {
30
40
  entity: "chat_event",
@@ -48,7 +48,7 @@ export const chatsCommand = (messenger) => {
48
48
  if (body.meta?.truncated)
49
49
  context.renderer.note("more history than one run reads; a later time bound reads less");
50
50
  }));
51
- annotate(chats.command("inspect"), { effects: REMOTE_READ, offline: false })
51
+ annotate(chats.command("inspect"), { output: { entity: "chat_inspection" }, effects: REMOTE_READ, offline: false })
52
52
  .description("what an invite or public link leads to, without joining it")
53
53
  .argument("<link>", "an invite link or a public one")
54
54
  .action(async function (link) {
@@ -69,7 +69,7 @@ export const chatsCommand = (messenger) => {
69
69
  context.renderer.note(`you left this chat; its stored messages are kept — \`${typedCommand(messenger.app.command, "store clear --left --dry-run", context.settings.profile)}\` lists the chats you left and deletes nothing`);
70
70
  }));
71
71
  if (messenger.sendAs !== false)
72
- annotate(chats.command("send-as"), { effects: REMOTE_READ, offline: false })
72
+ annotate(chats.command("send-as"), { output: { entity: "chat_sender" }, effects: REMOTE_READ, offline: false })
73
73
  .description("who this account may post as in a chat; changes no saved choice")
74
74
  .argument("<chat>", messenger.chatArgument)
75
75
  .action(async function (chat) {
@@ -2,26 +2,28 @@ import { annotate } from "@wirecat/cli-base/commands";
2
2
  import { Command } from "commander";
3
3
  import { REMOTE_READ } from "../effects.js";
4
4
  import { listOf, renderAnswer } from "../envelope.js";
5
+ import { queryTerms, typedQuery, withQuery } from "../list-query.js";
5
6
  import { positiveCount, window } from "../paging.js";
6
7
  import { nextStep } from "../steps.js";
7
8
  import { documentLinkAnswer, lines, pageOf } from "./chat-answers.js";
8
9
  import { messengerContext } from "./context.js";
10
+ import { DOCUMENT_LINKS_QUERY } from "./document-links-query.js";
9
11
  import { syncsFirst } from "./search-sync-options.js";
10
12
  /** `chats documents …`: the Google documents linked in a chat's stored messages. */
11
13
  export const documentsCommand = (messenger) => {
12
14
  const documents = new Command("documents").description("the Google Docs and Drive documents linked in a chat's stored messages");
13
- documents.addCommand(annotate(new Command("list"), { effects: REMOTE_READ })
15
+ documents.addCommand(withQuery(annotate(new Command("list"), { effects: REMOTE_READ })
14
16
  .description("every document link in the chat, newest first, and whether the store has it")
15
17
  .argument("<chat>", messenger.chatArgument)
16
18
  .option("--limit <n>", "how many to show", positiveCount("--limit"))
17
- .option("--page <n>", "which page, starting at 1", positiveCount("--page"))
18
- .action(async function (chat) {
19
+ .option("--page <n>", "which page, starting at 1", positiveCount("--page")), DOCUMENT_LINKS_QUERY).action(async function (chat, words) {
20
+ const ref = queryTerms(words, DOCUMENT_LINKS_QUERY, "chats documents list").get("ref");
19
21
  const context = messengerContext(this, messenger);
20
- const found = await context.withServices((services) => services.chatDocuments.list(chat, window(context.settings)));
22
+ const found = await context.withServices((services) => services.chatDocuments.list(chat, { ...window(context.settings), ...(ref === undefined ? {} : { ref }) }));
21
23
  renderAnswer(this, context, listOf("chats documents list", {
22
24
  items: found.items.map(documentLinkAnswer),
23
25
  ...pageOf(context.settings, found),
24
- args: { chat },
26
+ args: { chat, ...(words.length ? { query: typedQuery(words) } : {}) },
25
27
  ...(found.empty && syncsFirst(messenger)
26
28
  ? { next: [nextStep("messages.sync", { args: { chat: found.chatId } })] }
27
29
  : {}),
@@ -71,7 +71,11 @@ export const membersCommand = (messenger) => {
71
71
  before ? [before.name, before.username ? `@${before.username}` : null].filter(Boolean).join(" ") : "",
72
72
  ].join(" ")));
73
73
  }));
74
- const fetch = annotate(new Command("fetch"), { effects: FETCH, offline: false })
74
+ const fetch = annotate(new Command("fetch"), {
75
+ output: { entity: "chat_members_fetch" },
76
+ effects: FETCH,
77
+ offline: false,
78
+ })
75
79
  .summary("save the group's current membership")
76
80
  .description("read a group's whole member list into the local store's member history: who joined, who left, daily " +
77
81
  "counts and profile changes; someone is recorded as gone only when every member was read")
@@ -96,7 +100,7 @@ export const membersCommand = (messenger) => {
96
100
  renderAnswer(this, context, itemOf(snakeKeys(fetched)), () => `${fetched.read} members read; ${fetched.joined.length} new, ${fetched.gone.length} gone, ` +
97
101
  `${fetched.changed.length} changed their profile${fetched.tracked ? "; tracked daily" : ""}\n`);
98
102
  }));
99
- const add = annotate(new Command("add"), { effects: REMOTE_WRITE })
103
+ const add = annotate(new Command("add"), { output: { entity: "chat_members_change" }, effects: REMOTE_WRITE })
100
104
  .description("add people; they are told")
101
105
  .argument("<chat>", messenger.chatArgument)
102
106
  .argument("<person...>", "an id, or part of a name");
@@ -109,7 +113,7 @@ export const membersCommand = (messenger) => {
109
113
  const added = await context.withServices((services) => services.admin.addMembers(chat, people, history ? { history } : {}));
110
114
  renderAnswer(this, context, itemOf(snakeKeys(added)));
111
115
  }));
112
- members.addCommand(annotate(new Command("remove"), { effects: REMOTE_WRITE })
116
+ members.addCommand(annotate(new Command("remove"), { output: { entity: "chat_members_change" }, effects: REMOTE_WRITE })
113
117
  .description("remove people; their messages stay")
114
118
  .argument("<chat>", messenger.chatArgument)
115
119
  .argument("<person...>", "an id, or part of a name")
@@ -4,7 +4,7 @@ import { REMOTE_WRITE } from "../effects.js";
4
4
  import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
5
5
  import { messengerContext } from "./context.js";
6
6
  export const markReadCommand = (messenger) => {
7
- const read = annotate(new Command("mark-read"), { effects: REMOTE_WRITE })
7
+ const read = annotate(new Command("mark-read"), { output: { entity: "chat_read" }, effects: REMOTE_WRITE })
8
8
  .description("mark a chat read; the other side sees that you read it")
9
9
  .argument("<chat>", messenger.chatArgument)
10
10
  .option("--until-id <id>", "only up to this message id; the newest by default");
@@ -3,19 +3,27 @@ import { Command } from "commander";
3
3
  import { TRACKED_DAYS } from "../../services/chats.js";
4
4
  import { LOCAL_READ, LOCAL_WRITE } from "../effects.js";
5
5
  import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
6
+ import { listQuery, typedQuery, withQuery } from "../list-query.js";
6
7
  import { lines, wholeList } from "./chat-answers.js";
8
+ import { TRACKING_QUERY } from "./chat-queries.js";
7
9
  import { messengerContext, refuseLocalWrite } from "./context.js";
8
10
  /** The chats whose member lists `serve` fetches daily; kept in the local store, never sent. */
9
11
  export const trackingCommand = (messenger) => {
10
12
  const tracking = new Command("tracking")
11
13
  .summary("chats whose member lists are fetched daily")
12
14
  .description("the chats whose member lists serve fetches daily into the local store — chats members fetch --track adds one");
13
- annotate(tracking.command("list"), { effects: LOCAL_READ })
15
+ withQuery(annotate(tracking.command("list"), { effects: LOCAL_READ }), TRACKING_QUERY)
14
16
  .description("every tracked chat: since when, and its last member count")
15
- .action(async function () {
17
+ .action(async function (words) {
18
+ const query = listQuery(typedQuery(words), TRACKING_QUERY, "chats tracking list");
16
19
  const context = messengerContext(this, messenger);
17
- const found = await context.withServices((services) => services.chats.tracked());
18
- renderAnswer(this, context, wholeList("chats tracking list", found.map((one) => snakeKeys(one))), lines(({ chat_id, title, tracked_at, last_count }) => [
20
+ const found = await context.withServices((services) => services.chats.tracked(query));
21
+ renderAnswer(this, context, wholeList("chats tracking list", found.map(({ chatId, title, trackedAt, lastCount }) => ({
22
+ chat_id: chatId,
23
+ title,
24
+ tracked_at: trackedAt,
25
+ last_count: lastCount,
26
+ }))), lines(({ chat_id, title, tracked_at, last_count }) => [
19
27
  title ? `${title} (${chat_id})` : chat_id,
20
28
  tracked_at.slice(0, 10),
21
29
  last_count
@@ -23,7 +31,7 @@ export const trackingCommand = (messenger) => {
23
31
  : "",
24
32
  ].join(" ")));
25
33
  });
26
- annotate(tracking.command("show"), { effects: LOCAL_READ })
34
+ annotate(tracking.command("show"), { effects: LOCAL_READ, output: { entity: "tracked_chat_history" } })
27
35
  .summary("one chat's member tracking and daily counts")
28
36
  .description(`one chat: whether it is tracked, and its member count per day for the last ${TRACKED_DAYS} days`)
29
37
  .argument("<chat>", messenger.chatArgument)
@@ -35,7 +43,7 @@ export const trackingCommand = (messenger) => {
35
43
  ["add", true, "fetch this chat's member list daily while serve runs, from its next run"],
36
44
  ["remove", false, "stop fetching it daily; the history already kept stays"],
37
45
  ]) {
38
- annotate(tracking.command(verb), { effects: LOCAL_WRITE })
46
+ annotate(tracking.command(verb), { effects: LOCAL_WRITE, output: { entity: "chat_tracking" } })
39
47
  .description(description)
40
48
  .argument("<chat>", messenger.chatArgument)
41
49
  .action(async function (chat) {