@wirecat/cli-core 0.530.0 → 0.532.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +1 -1
- package/dist/cli/list-query.d.ts +0 -4
- package/dist/cli/list-query.js +19 -1
- package/dist/cli/messenger/attachments-command.d.ts +1 -0
- package/dist/cli/messenger/attachments-command.js +1 -1
- package/dist/cli/messenger/download-command.js +37 -10
- package/dist/cli/messenger/schedule-command.d.ts +2 -19
- package/dist/cli/messenger/schedule-command.js +2 -231
- package/dist/cli/messenger/search-answers.js +7 -2
- package/dist/cli/messenger/topics-command.js +22 -5
- package/dist/cli/schedule/schedule-command.d.ts +19 -0
- package/dist/cli/schedule/schedule-command.js +261 -0
- package/dist/cli/shared-command-exports.d.ts +1 -1
- package/dist/cli/shared-command-exports.js +2 -1
- package/dist/domain/attachments.d.ts +9 -1
- package/dist/domain/attachments.js +12 -0
- package/dist/domain/message-link.d.ts +14 -0
- package/dist/domain/message-link.js +8 -0
- package/dist/domain/models.d.ts +2 -0
- package/dist/entities/attachment.d.ts +7 -0
- package/dist/entities/attachment.js +2 -0
- package/dist/entities/hit.d.ts +7 -0
- package/dist/entities/hit.js +3 -0
- package/dist/entities/index.d.ts +61 -2
- package/dist/entities/index.js +6 -3
- package/dist/entities/list-results.d.ts +3 -0
- package/dist/entities/list-results.js +1 -0
- package/dist/entities/message-link.d.ts +8 -0
- package/dist/entities/message-link.js +9 -1
- package/dist/entities/task-results.d.ts +30 -0
- package/dist/entities/task-results.js +1 -0
- package/dist/entities/task.d.ts +7 -0
- package/dist/entities/task.js +2 -0
- package/dist/errors/reasons.js +1 -0
- package/dist/mail/account-key.d.ts +2 -0
- package/dist/mail/account-key.js +1 -0
- package/dist/mail/cli/index.js +10 -10
- package/dist/mail/index.d.ts +1 -0
- package/dist/mail/index.js +1 -0
- package/dist/mail/retry.js +2 -1
- package/dist/mail/rule-stored.js +2 -1
- package/dist/mail/sender-scores.js +2 -1
- package/dist/mail/sync.js +3 -2
- package/dist/ports/messenger.d.ts +3 -0
- package/dist/services/attachments.d.ts +11 -0
- package/dist/services/attachments.js +45 -2
- package/dist/services/deps.d.ts +1 -1
- package/dist/services/deps.js +1 -1
- package/dist/services/messages.d.ts +2 -3
- package/dist/services/messages.js +27 -8
- package/dist/services/search-all.d.ts +3 -0
- package/dist/services/search-all.js +2 -0
- package/dist/services/tasks.d.ts +4 -2
- package/dist/services/tasks.js +2 -2
- package/dist/services/topics.d.ts +3 -0
- package/dist/services/topics.js +20 -6
- package/dist/store/index.d.ts +1 -0
- package/dist/store/sqlite/task-board.js +3 -1
- package/dist/store/sqlite/task-comments.d.ts +23 -0
- package/dist/store/sqlite/task-comments.js +41 -0
- package/dist/store/sqlite/things.js +8 -1
- package/dist/store/sqlite/work-tasks.d.ts +13 -2
- package/dist/store/sqlite/work-tasks.js +52 -1
- package/package.json +1 -1
- package/parity.json +3 -0
package/dist/entities/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { accountSession } from "./account-session.js";
|
|
|
8
8
|
import { adminReport } from "./admin-report.js";
|
|
9
9
|
import { agenda } from "./agenda.js";
|
|
10
10
|
import { analysisConsent, analysisConsentRevocation } from "./analysis-consent.js";
|
|
11
|
-
import { attachment } from "./attachment.js";
|
|
11
|
+
import { attachment, hitFile } from "./attachment.js";
|
|
12
12
|
import { attachmentBytes, attachmentExtraction, attachmentTextChange } from "./attachment-results.js";
|
|
13
13
|
import { chart } from "./chart.js";
|
|
14
14
|
import { chat } from "./chat.js";
|
|
@@ -42,7 +42,7 @@ import { inviteLink } from "./invite-link.js";
|
|
|
42
42
|
import { job } from "./job.js";
|
|
43
43
|
import { joinRequest } from "./join-request.js";
|
|
44
44
|
import { organization, project, reminder } from "./knowledge-lists.js";
|
|
45
|
-
import { botAdmin, botMenuEntry, botProfile, botSeenChat, botTemplate, botWebhook, callRecord, scheduledJob, stickerInventory, } from "./list-results.js";
|
|
45
|
+
import { botAdmin, botMenuEntry, botProfile, botSeenChat, botTemplate, botWebhook, callRecord, scheduledJob, stickerInventory, topicIcon, } from "./list-results.js";
|
|
46
46
|
import { messageEvent, serveSummary, watchedMessage, watchItem } from "./listener.js";
|
|
47
47
|
import { mediaArchive, mediaArchiveCheck, mediaArchiveMove, mediaArchiveTimer } from "./media-archive.js";
|
|
48
48
|
import { event, meeting, meetingDetails, meetingHit, meetingLink, meetingParticipants, meetingPassage, meetingProjectEvidence, meetingReview, meetingSpeaker, meetingStats, meetingSummary, meetingTranscript, } from "./meeting.js";
|
|
@@ -94,7 +94,7 @@ import { topic } from "./topic.js";
|
|
|
94
94
|
* subset of its fields, never a renamed one. Commands move onto these one by one; until then, a schema
|
|
95
95
|
* is the target, not a description of today's output.
|
|
96
96
|
*/
|
|
97
|
-
export { account, accountChange, accountPrivacy, accountPrivacyChange, accountProfile, accountSession, adminReport, agenda, analysisConsent, analysisConsentRevocation, attachment, attachmentBytes, attachmentExtraction, attachmentTextChange, automaticTags, botAdmin, botMenuEntry, botProfile, botSeenChat, botTemplate, botWebhook, cachedChatMetadata, callRecord, chart, chat, chatAdminChange, chatArchive, chatEvent, chatFolderChange, chatFolderOrder, chatFolderRemoval, chatGroupChange, chatInspection, chatInvite, chatInviteChange, chatMembersChange, chatMembersFetch, chatMetadata, chatMetadataRefresh, chatModeration, chatMuting, chatPin, chatRead, chatRemoval, chatRequestChange, chatRules, chatSender, chatStats, chatTracking, chatUnread, contact, contactActivity, contactAlias, contactCheck, contactContext, contactImport, contactLookup, contactNote, contactNoteRemoval, contactPreparation, contactStateChange, contactSync, contactUpdate, conversation, conversationBatch, conversationBatchStatus, conversationBuildResult, conversationChange, conversationDetails, conversationEmbedding, conversationEmbeddingStatus, conversationHit, conversationPreparation, conversationReadiness, counterRefresh, counters, document, documentLink, endedAccountSessions, evidence, evidencePacket, fact, folder, hit, identity, imageEmbedding, inviteLink, job, joinRequest, linkedPerson, mediaArchive, mediaArchiveCheck, mediaArchiveMove, mediaArchiveTimer, meeting, meetingProjectCandidates, meetingSeriesInventory, member, memory, mention, mentionDecision, mentionDetails, mentionDomain, mentionRebuild, message, messageContext, messageCounts, messageDeletion, messageDownload, messageEvent, messageLink, messageLinks, messagePin, messageStar, messageSync, messageTranscript, messageWrite, model, modelKey, note, officialStats, organization, person, place, poll, pollChange, pollVote, project, ranking, reaction, reactionChange, recipient, recipientAdded, recipientChange, recipientOff, recipientRemoved, recordedRun, reference, relationship, reminder, removedModelKey, retention, runDetails, runHit, runLocation, savedSearch, scheduledJob, searchHistoryClear, sendAttempt, serveSummary, stickerInventory, storeBackup, storeChatStatus, storeCheck, storeClear, storeCopyRemoval, storeDecrypt, storedModelKey, storeExport, storeGapPlan, storeGapRepair, storeInfo, storeJobCancel, storeJobClear, storeJobDetails, storeJobRetry, storeMigration, storeReindex, storeRepair, storeReset, storeRestore, storeTable, tagChange, tagging, task, taskCard, taskCategories, taskClosed, taskCreated, taskLinks, taskMerge, taskPair, taskScan, taskSimilar, taskStats, taskUnmerge, topic, trackedChat, trackedChatHistory, watchedMessage, watchItem, };
|
|
97
|
+
export { account, accountChange, accountPrivacy, accountPrivacyChange, accountProfile, accountSession, adminReport, agenda, analysisConsent, analysisConsentRevocation, attachment, attachmentBytes, attachmentExtraction, attachmentTextChange, automaticTags, botAdmin, botMenuEntry, botProfile, botSeenChat, botTemplate, botWebhook, cachedChatMetadata, callRecord, chart, chat, chatAdminChange, chatArchive, chatEvent, chatFolderChange, chatFolderOrder, chatFolderRemoval, chatGroupChange, chatInspection, chatInvite, chatInviteChange, chatMembersChange, chatMembersFetch, chatMetadata, chatMetadataRefresh, chatModeration, chatMuting, chatPin, chatRead, chatRemoval, chatRequestChange, chatRules, chatSender, chatStats, chatTracking, chatUnread, contact, contactActivity, contactAlias, contactCheck, contactContext, contactImport, contactLookup, contactNote, contactNoteRemoval, contactPreparation, contactStateChange, contactSync, contactUpdate, conversation, conversationBatch, conversationBatchStatus, conversationBuildResult, conversationChange, conversationDetails, conversationEmbedding, conversationEmbeddingStatus, conversationHit, conversationPreparation, conversationReadiness, counterRefresh, counters, document, documentLink, endedAccountSessions, evidence, evidencePacket, fact, folder, hit, identity, imageEmbedding, inviteLink, job, joinRequest, linkedPerson, mediaArchive, mediaArchiveCheck, mediaArchiveMove, mediaArchiveTimer, meeting, meetingProjectCandidates, meetingSeriesInventory, member, memory, mention, mentionDecision, mentionDetails, mentionDomain, mentionRebuild, message, messageContext, messageCounts, messageDeletion, messageDownload, messageEvent, messageLink, messageLinks, messagePin, messageStar, messageSync, messageTranscript, messageWrite, model, modelKey, note, officialStats, organization, person, place, poll, pollChange, pollVote, project, ranking, reaction, reactionChange, recipient, recipientAdded, recipientChange, recipientOff, recipientRemoved, recordedRun, reference, relationship, reminder, removedModelKey, retention, runDetails, runHit, runLocation, savedSearch, scheduledJob, searchHistoryClear, sendAttempt, serveSummary, stickerInventory, storeBackup, storeChatStatus, storeCheck, storeClear, storeCopyRemoval, storeDecrypt, storedModelKey, storeExport, storeGapPlan, storeGapRepair, storeInfo, storeJobCancel, storeJobClear, storeJobDetails, storeJobRetry, storeMigration, storeReindex, storeRepair, storeReset, storeRestore, storeTable, tagChange, tagging, task, taskCard, taskCategories, taskClosed, taskCreated, taskLinks, taskMerge, taskPair, taskScan, taskSimilar, taskStats, taskUnmerge, topic, topicIcon, trackedChat, trackedChatHistory, watchedMessage, watchItem, };
|
|
98
98
|
export const ENTITIES = {
|
|
99
99
|
sync_progress: syncProgress,
|
|
100
100
|
...MEETING_RESULT_ENTITIES,
|
|
@@ -120,6 +120,8 @@ export const ENTITIES = {
|
|
|
120
120
|
"follow_ups",
|
|
121
121
|
"email",
|
|
122
122
|
]).entries,
|
|
123
|
+
/** The files the message carries; absent when it has none. */
|
|
124
|
+
attachments: v.optional(v.array(hitFile)),
|
|
123
125
|
untrusted,
|
|
124
126
|
}),
|
|
125
127
|
"message.summary": v.pick(message, [
|
|
@@ -183,6 +185,7 @@ export const ENTITIES = {
|
|
|
183
185
|
bot_seen_chat: botSeenChat,
|
|
184
186
|
bot_admin: botAdmin,
|
|
185
187
|
bot_menu_entry: botMenuEntry,
|
|
188
|
+
topic_icon: topicIcon,
|
|
186
189
|
bot_webhook: botWebhook,
|
|
187
190
|
scheduled_job: scheduledJob,
|
|
188
191
|
call_record: callRecord,
|
|
@@ -37,6 +37,9 @@ export declare const botMenuEntry: v.StrictObjectSchema<{
|
|
|
37
37
|
readonly name: v.StringSchema<undefined>;
|
|
38
38
|
readonly description: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
39
39
|
}, undefined>;
|
|
40
|
+
export declare const topicIcon: v.StrictObjectSchema<{
|
|
41
|
+
readonly emoji: v.StringSchema<undefined>;
|
|
42
|
+
}, undefined>;
|
|
40
43
|
export declare const botWebhook: v.StrictObjectSchema<{
|
|
41
44
|
readonly url: v.StringSchema<undefined>;
|
|
42
45
|
readonly types: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
@@ -26,6 +26,7 @@ export const botAdmin = v.strictObject({
|
|
|
26
26
|
title: v.nullable(text),
|
|
27
27
|
});
|
|
28
28
|
export const botMenuEntry = v.strictObject({ name: text, description: v.nullable(text) });
|
|
29
|
+
export const topicIcon = v.strictObject({ emoji: text });
|
|
29
30
|
export const botWebhook = v.strictObject({ url: text, types: v.optional(v.array(text)) });
|
|
30
31
|
export const scheduledJob = v.strictObject({
|
|
31
32
|
name: text,
|
|
@@ -6,4 +6,12 @@ export declare const messageLink: v.StrictObjectSchema<{
|
|
|
6
6
|
readonly access: v.PicklistSchema<["public", "restricted", "unknown", "unavailable"], undefined>;
|
|
7
7
|
/** Why there is no link. */
|
|
8
8
|
readonly reason: v.NullableSchema<v.PicklistSchema<["unsupported_chat", "unsupported_provider", "offline"], undefined>, undefined>;
|
|
9
|
+
/** The message's files, each with its own link where the messenger gives one; absent when it has none. */
|
|
10
|
+
readonly files: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
|
|
11
|
+
readonly position: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
12
|
+
readonly kind: v.StringSchema<undefined>;
|
|
13
|
+
readonly name: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
14
|
+
readonly url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
15
|
+
readonly reason: v.NullableSchema<v.PicklistSchema<["unsupported_provider"], undefined>, undefined>;
|
|
16
|
+
}, undefined>, undefined>, undefined>;
|
|
9
17
|
}, undefined>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
|
-
import { reference, text } from "./fields.js";
|
|
2
|
+
import { count, reference, text } from "./fields.js";
|
|
3
3
|
/** Where a message can be opened: its locator always, a provider link when there is one and who that link reaches. */
|
|
4
4
|
export const messageLink = v.strictObject({
|
|
5
5
|
ref: reference,
|
|
@@ -7,4 +7,12 @@ export const messageLink = v.strictObject({
|
|
|
7
7
|
access: v.picklist(["public", "restricted", "unknown", "unavailable"]),
|
|
8
8
|
/** Why there is no link. */
|
|
9
9
|
reason: v.nullable(v.picklist(["unsupported_chat", "unsupported_provider", "offline"])),
|
|
10
|
+
/** The message's files, each with its own link where the messenger gives one; absent when it has none. */
|
|
11
|
+
files: v.optional(v.array(v.strictObject({
|
|
12
|
+
position: count,
|
|
13
|
+
kind: text,
|
|
14
|
+
name: v.optional(text),
|
|
15
|
+
url: v.nullable(text),
|
|
16
|
+
reason: v.nullable(v.picklist(["unsupported_provider"])),
|
|
17
|
+
}))),
|
|
10
18
|
});
|
|
@@ -6,6 +6,12 @@ export declare const taskCard: v.StrictObjectSchema<{
|
|
|
6
6
|
readonly project: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
7
7
|
readonly project_key: v.StringSchema<undefined>;
|
|
8
8
|
readonly project_name: v.StringSchema<undefined>;
|
|
9
|
+
readonly created_by: v.NullableSchema<v.StrictObjectSchema<{
|
|
10
|
+
readonly type: v.PicklistSchema<["person", "bot"], undefined>;
|
|
11
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
12
|
+
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
13
|
+
readonly owner: v.BooleanSchema<undefined>;
|
|
14
|
+
}, undefined>, undefined>;
|
|
9
15
|
readonly title: v.StringSchema<undefined>;
|
|
10
16
|
readonly description: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
11
17
|
readonly type: v.PicklistSchema<readonly ["bug", "feature", "chore", "question", "request", "mention", "promise"], undefined>;
|
|
@@ -112,6 +118,12 @@ export declare const taskCreated: v.StrictObjectSchema<{
|
|
|
112
118
|
readonly project: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
113
119
|
readonly project_key: v.StringSchema<undefined>;
|
|
114
120
|
readonly project_name: v.StringSchema<undefined>;
|
|
121
|
+
readonly created_by: v.NullableSchema<v.StrictObjectSchema<{
|
|
122
|
+
readonly type: v.PicklistSchema<["person", "bot"], undefined>;
|
|
123
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
124
|
+
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
125
|
+
readonly owner: v.BooleanSchema<undefined>;
|
|
126
|
+
}, undefined>, undefined>;
|
|
115
127
|
readonly title: v.StringSchema<undefined>;
|
|
116
128
|
readonly description: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
117
129
|
readonly type: v.PicklistSchema<readonly ["bug", "feature", "chore", "question", "request", "mention", "promise"], undefined>;
|
|
@@ -219,6 +231,12 @@ export declare const taskLinks: v.UnionSchema<[v.StrictObjectSchema<{
|
|
|
219
231
|
readonly project: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
220
232
|
readonly project_key: v.StringSchema<undefined>;
|
|
221
233
|
readonly project_name: v.StringSchema<undefined>;
|
|
234
|
+
readonly created_by: v.NullableSchema<v.StrictObjectSchema<{
|
|
235
|
+
readonly type: v.PicklistSchema<["person", "bot"], undefined>;
|
|
236
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
237
|
+
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
238
|
+
readonly owner: v.BooleanSchema<undefined>;
|
|
239
|
+
}, undefined>, undefined>;
|
|
222
240
|
readonly title: v.StringSchema<undefined>;
|
|
223
241
|
readonly description: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
224
242
|
readonly type: v.PicklistSchema<readonly ["bug", "feature", "chore", "question", "request", "mention", "promise"], undefined>;
|
|
@@ -354,6 +372,12 @@ export declare const taskMerge: v.StrictObjectSchema<{
|
|
|
354
372
|
readonly project: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
355
373
|
readonly project_key: v.StringSchema<undefined>;
|
|
356
374
|
readonly project_name: v.StringSchema<undefined>;
|
|
375
|
+
readonly created_by: v.NullableSchema<v.StrictObjectSchema<{
|
|
376
|
+
readonly type: v.PicklistSchema<["person", "bot"], undefined>;
|
|
377
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
378
|
+
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
379
|
+
readonly owner: v.BooleanSchema<undefined>;
|
|
380
|
+
}, undefined>, undefined>;
|
|
357
381
|
readonly title: v.StringSchema<undefined>;
|
|
358
382
|
readonly description: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
359
383
|
readonly type: v.PicklistSchema<readonly ["bug", "feature", "chore", "question", "request", "mention", "promise"], undefined>;
|
|
@@ -454,6 +478,12 @@ export declare const taskUnmerge: v.StrictObjectSchema<{
|
|
|
454
478
|
readonly project: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
455
479
|
readonly project_key: v.StringSchema<undefined>;
|
|
456
480
|
readonly project_name: v.StringSchema<undefined>;
|
|
481
|
+
readonly created_by: v.NullableSchema<v.StrictObjectSchema<{
|
|
482
|
+
readonly type: v.PicklistSchema<["person", "bot"], undefined>;
|
|
483
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
484
|
+
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
485
|
+
readonly owner: v.BooleanSchema<undefined>;
|
|
486
|
+
}, undefined>, undefined>;
|
|
457
487
|
readonly title: v.StringSchema<undefined>;
|
|
458
488
|
readonly description: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
459
489
|
readonly type: v.PicklistSchema<readonly ["bug", "feature", "chore", "question", "request", "mention", "promise"], undefined>;
|
|
@@ -27,6 +27,7 @@ export const taskCard = v.strictObject({
|
|
|
27
27
|
project: reference,
|
|
28
28
|
project_key: text,
|
|
29
29
|
project_name: text,
|
|
30
|
+
created_by: v.nullable(v.strictObject({ type: v.picklist(["person", "bot"]), id, name: v.nullable(text), owner: v.boolean() })),
|
|
30
31
|
title: text,
|
|
31
32
|
description: v.nullable(text),
|
|
32
33
|
type: v.picklist(TASK_TYPES),
|
package/dist/entities/task.d.ts
CHANGED
|
@@ -22,6 +22,13 @@ export declare const task: v.StrictObjectSchema<{
|
|
|
22
22
|
/** The project's key and name, as `tasks counts` names it. */
|
|
23
23
|
readonly project_key: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
24
24
|
readonly project_name: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
25
|
+
/** Who added it: the owner, or an agent by its bot name. */
|
|
26
|
+
readonly created_by: v.OptionalSchema<v.NullableSchema<v.StrictObjectSchema<{
|
|
27
|
+
readonly type: v.PicklistSchema<["person", "bot"], undefined>;
|
|
28
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
29
|
+
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
30
|
+
readonly owner: v.BooleanSchema<undefined>;
|
|
31
|
+
}, undefined>, undefined>, undefined>;
|
|
25
32
|
readonly account: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
26
33
|
/** The chat it came from, as a `chat:` reference. */
|
|
27
34
|
readonly chat: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>, undefined>;
|
package/dist/entities/task.js
CHANGED
|
@@ -25,6 +25,8 @@ export const task = v.strictObject({
|
|
|
25
25
|
/** The project's key and name, as `tasks counts` names it. */
|
|
26
26
|
project_key: v.optional(v.nullable(text)),
|
|
27
27
|
project_name: v.optional(v.nullable(text)),
|
|
28
|
+
/** Who added it: the owner, or an agent by its bot name. */
|
|
29
|
+
created_by: v.optional(v.nullable(v.strictObject({ type: v.picklist(["person", "bot"]), id, name: v.nullable(text), owner: v.boolean() }))),
|
|
28
30
|
account: v.optional(text),
|
|
29
31
|
/** The chat it came from, as a `chat:` reference. */
|
|
30
32
|
chat: v.optional(v.nullable(reference)),
|
package/dist/errors/reasons.js
CHANGED
|
@@ -133,6 +133,7 @@ const OTHERS = [
|
|
|
133
133
|
["task.stale", "configuration_error", "the task changed since it was read; read it again"],
|
|
134
134
|
validation("task.column", "the task beside which it moves is in another column than the status asked for"),
|
|
135
135
|
validation("task.dependency-cycle", "the task would depend on itself"),
|
|
136
|
+
validation("task.has-subtasks", "the task has subtasks; move or detach them before it moves to another project"),
|
|
136
137
|
validation("task.duplicate-candidates", "open tasks may be the same job: attach to one, or add it as new"),
|
|
137
138
|
validation("task.certainty-required", "an agent closes a task with its certainty, 0 to 1"),
|
|
138
139
|
["task.pair-decided", "permission_error", "the owner already decided the pair; an agent does not change it"],
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const mailAccountKey = (address) => ({ provider: "email", account: address.toLowerCase() });
|
package/dist/mail/cli/index.js
CHANGED
|
@@ -84,9 +84,9 @@ const searchCommand = (mail) => {
|
|
|
84
84
|
partial: true,
|
|
85
85
|
output: { entity: "message", view: "hit" },
|
|
86
86
|
examples: [
|
|
87
|
-
`${mail.app.command}
|
|
88
|
-
`${mail.app.command}
|
|
89
|
-
`${mail.app.command}
|
|
87
|
+
`${mail.app.command} personal search messages invoice`,
|
|
88
|
+
`${mail.app.command} personal search messages 'from:ann@example.com has:attachment date>30d'`,
|
|
89
|
+
`${mail.app.command} personal search messages 'list:github.com is:unread' --newest`,
|
|
90
90
|
],
|
|
91
91
|
})
|
|
92
92
|
.summary(mail.server ? "find emails by words and fields" : "find stored emails by words and fields")
|
|
@@ -103,7 +103,7 @@ const searchCommand = (mail) => {
|
|
|
103
103
|
.option("--timezone <zone>", "the IANA timezone for calendar date boundaries")
|
|
104
104
|
.addHelpText("after", `
|
|
105
105
|
Fields: from: to: cc: bcc: subject: body: mailbox: (or label:) list: (a List-Id or its domain) is:unread|read|starred
|
|
106
|
-
has:attachment, filename:, content: (the text of attachments), size>, date: — \`${mail.app.command}
|
|
106
|
+
has:attachment, filename:, content: (the text of attachments), size>, date: — \`${mail.app.command} personal search messages 'content:invoice'\`.
|
|
107
107
|
chat: names a thread by its subject. Mail sent to many is in the mailbox bulk; NOT mailbox:bulk leaves it out.
|
|
108
108
|
Mail from senders with signs of fraud comes after the rest of the page; --plain-order keeps it in place.
|
|
109
109
|
--json: the list is items; each hit's reference is ref (msg:email/…), which messages show reads.${mail.server
|
|
@@ -194,8 +194,8 @@ const messagesCommand = (mail) => {
|
|
|
194
194
|
annotate(messages.command("show"), {
|
|
195
195
|
effects: LOCAL_READ,
|
|
196
196
|
examples: [
|
|
197
|
-
`${mail.app.command}
|
|
198
|
-
`${mail.app.command}
|
|
197
|
+
`${mail.app.command} personal messages show 'msg:email/me%40example.com/thread-1/%3Cid%40example.com%3E'`,
|
|
198
|
+
`${mail.app.command} personal messages show 'msg:email/me%40example.com/thread-1/%3Cid%40example.com%3E' --before-n 2`,
|
|
199
199
|
],
|
|
200
200
|
})
|
|
201
201
|
.summary("one stored email whole, with the text of its attachments")
|
|
@@ -243,8 +243,8 @@ const conversationsCommand = (mail) => {
|
|
|
243
243
|
const list = withQuery(annotate(conversations.command("list"), {
|
|
244
244
|
effects: LOCAL_READ,
|
|
245
245
|
examples: [
|
|
246
|
-
`${mail.app.command}
|
|
247
|
-
`${mail.app.command}
|
|
246
|
+
`${mail.app.command} personal conversations list`,
|
|
247
|
+
`${mail.app.command} personal conversations list subject:invoice emails_count>1`,
|
|
248
248
|
],
|
|
249
249
|
})
|
|
250
250
|
.summary("stored email threads, newest first")
|
|
@@ -291,8 +291,8 @@ const conversationsCommand = (mail) => {
|
|
|
291
291
|
annotate(conversations.command("show"), {
|
|
292
292
|
effects: LOCAL_READ,
|
|
293
293
|
examples: [
|
|
294
|
-
`${mail.app.command}
|
|
295
|
-
`${mail.app.command}
|
|
294
|
+
`${mail.app.command} personal conversations show 'chat:email/me%40example.com/thread-1'`,
|
|
295
|
+
`${mail.app.command} personal conversations show 'msg:email/me%40example.com/thread-1/%3Cid%40example.com%3E'`,
|
|
296
296
|
],
|
|
297
297
|
})
|
|
298
298
|
.summary("one email thread, every email oldest first")
|
package/dist/mail/index.d.ts
CHANGED
package/dist/mail/index.js
CHANGED
package/dist/mail/retry.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { mailAccountKey } from "./account-key.js";
|
|
2
3
|
import { addressOf, FETCH_CHUNK, readAttachments } from "./attachments.js";
|
|
3
4
|
import { mailFolders } from "./folders.js";
|
|
4
5
|
import { mailHeadersOf } from "./headers.js";
|
|
@@ -12,7 +13,7 @@ export const RETRYABLE_ATTACHMENTS = ["engine-missing"];
|
|
|
12
13
|
*/
|
|
13
14
|
export const retryUnread = async ({ store, source, account, since, max, maxBodyChars, maxAttachmentChars, now, loadEngine, index, }) => {
|
|
14
15
|
const startedAt = now();
|
|
15
|
-
const key =
|
|
16
|
+
const key = mailAccountKey(account.address);
|
|
16
17
|
const find = source.find;
|
|
17
18
|
if (find === undefined)
|
|
18
19
|
throw new CliError("validation_error", "--retry-attachments reads mail again by its Gmail id; an mbox file or a plain IMAP account has none");
|
package/dist/mail/rule-stored.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { mailAccountKey } from "./account-key.js";
|
|
2
3
|
import { PRESET_NAMES } from "./presets.js";
|
|
3
4
|
const ACTION_OF = { full: "keep", envelope: "envelope", skip: "skip" };
|
|
4
5
|
const KINDS = {
|
|
@@ -56,7 +57,7 @@ export const storedMailRule = (stored) => {
|
|
|
56
57
|
export const storedMailRuleSet = async (store, account, query) => {
|
|
57
58
|
let stored;
|
|
58
59
|
try {
|
|
59
|
-
const { id } = await store.accounts.storedAccount(
|
|
60
|
+
const { id } = await store.accounts.storedAccount(mailAccountKey(account.address));
|
|
60
61
|
stored = await store.mail.rules.list(id, query);
|
|
61
62
|
}
|
|
62
63
|
catch (error) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { mailAccountKey } from "./account-key.js";
|
|
2
3
|
import { domainsToAge, groupKey, probeMail } from "./probe.js";
|
|
3
4
|
import { decide } from "./rules.js";
|
|
4
5
|
import { FRAUD_SIGNALS, FREE_MAIL, registrableOf, WEIGHTS } from "./score.js";
|
|
@@ -18,7 +19,7 @@ const RECENT = 2 * 365 * DAY;
|
|
|
18
19
|
* looked up first, for senders heard from in the last two years.
|
|
19
20
|
*/
|
|
20
21
|
export const scoreSenders = async (store, address, ages, now = Date.now(), rules = []) => {
|
|
21
|
-
const key =
|
|
22
|
+
const key = mailAccountKey(address);
|
|
22
23
|
const last = (await store.sync.syncState(key, SCORED))?.value;
|
|
23
24
|
const done = last === undefined ? undefined : JSON.parse(last);
|
|
24
25
|
if (done !== undefined && done.version === SCORE_VERSION && now - done.at < DAY)
|
package/dist/mail/sync.js
CHANGED
|
@@ -2,6 +2,7 @@ import { setTimeout } from "node:timers/promises";
|
|
|
2
2
|
import { CliError } from "@wirecat/cli-base";
|
|
3
3
|
import { importEngine } from "../attachments/extract.js";
|
|
4
4
|
import { isBusy } from "../store/sqlite/transaction.js";
|
|
5
|
+
import { mailAccountKey } from "./account-key.js";
|
|
5
6
|
import { FETCH_CHUNK, MAX_ATTACHMENT_CHARS, readAttachments } from "./attachments.js";
|
|
6
7
|
import { BULK, bulkBox } from "./bulk.js";
|
|
7
8
|
import { parsedEmail } from "./email-input.js";
|
|
@@ -108,7 +109,7 @@ export const syncMail = async (source, store, options) => {
|
|
|
108
109
|
})),
|
|
109
110
|
];
|
|
110
111
|
const complete = last.complete && plan.stopped === null && unreadable.length === 0 && remaining === 0;
|
|
111
|
-
const account =
|
|
112
|
+
const account = mailAccountKey(options.account.address);
|
|
112
113
|
const indexed = plan.stopped === null ? await index?.(store, account) : undefined;
|
|
113
114
|
const deletionsSkipped = rounds.map((round) => round.deletionsSkipped).findLast((text) => text !== undefined);
|
|
114
115
|
const { unreadable: _unreadable, ...rest } = last;
|
|
@@ -155,7 +156,7 @@ const syncRound = async (source, store, { account, since, max = 200, maxBodyChar
|
|
|
155
156
|
index,
|
|
156
157
|
});
|
|
157
158
|
const startedAt = now();
|
|
158
|
-
const key =
|
|
159
|
+
const key = mailAccountKey(account.address);
|
|
159
160
|
const accountId = await store.accounts.saveAccount(key, { name: account.address });
|
|
160
161
|
const folders = await mailFolders(account, source);
|
|
161
162
|
if (folders.length < 1 || folders.length > 50)
|
|
@@ -272,6 +272,7 @@ export interface ForumControl {
|
|
|
272
272
|
enableForum(chatId: Id): Promise<ForumState>;
|
|
273
273
|
createTopic(chatId: Id, title: string, options: {
|
|
274
274
|
sendId: string;
|
|
275
|
+
icon?: string;
|
|
275
276
|
}): Promise<Topic>;
|
|
276
277
|
}
|
|
277
278
|
export interface TopicEditing {
|
|
@@ -281,6 +282,8 @@ export interface TopicEditing {
|
|
|
281
282
|
orderPinnedTopics(chatId: Id, topicIds: Id[]): Promise<void>;
|
|
282
283
|
/** Deletes the topic and every message in it, for everyone; it cannot be undone. A topic that is gone is `not_found`. */
|
|
283
284
|
deleteTopic?(chatId: Id, topicId: Id): Promise<void>;
|
|
285
|
+
/** The emojis any account may give a topic as its icon. */
|
|
286
|
+
topicIcons?(): Promise<string[]>;
|
|
284
287
|
}
|
|
285
288
|
export interface ThreadAddressing {
|
|
286
289
|
validateThread(chatId: Id, threadId: Id, options: {
|
|
@@ -9,6 +9,7 @@ import { attachment as attachmentEntity } from "../entities/attachment.js";
|
|
|
9
9
|
import { type TextOrigin } from "../store/store.js";
|
|
10
10
|
import { batchProgress } from "./batch.js";
|
|
11
11
|
import { type ServiceDeps } from "./deps.js";
|
|
12
|
+
import { type Saved } from "./file-download.js";
|
|
12
13
|
export type ExtractStatus = "extracted" | "needs-agent" | "unreadable" | "engine-missing" | "too-large" | "missing";
|
|
13
14
|
/** One file looked at. Never its text: the text stays in the local store. */
|
|
14
15
|
export interface ExtractItem {
|
|
@@ -114,6 +115,16 @@ export interface AttachmentsService {
|
|
|
114
115
|
items: AttachmentItem[];
|
|
115
116
|
has_more: boolean;
|
|
116
117
|
}>;
|
|
118
|
+
/**
|
|
119
|
+
* Copies files the store already holds to `output`, never over a file there, and never asks the messenger: one
|
|
120
|
+
* not held locally is an error before anything is written. The store keeps pointing at its own copy.
|
|
121
|
+
*/
|
|
122
|
+
saveHeld(target: {
|
|
123
|
+
chat: string;
|
|
124
|
+
message?: string;
|
|
125
|
+
attachment?: number;
|
|
126
|
+
output: string;
|
|
127
|
+
}): Promise<Saved[]>;
|
|
117
128
|
/** Keeps text an agent read from a file — a scan, a photo — replacing whatever was there. Sends nothing. */
|
|
118
129
|
setText(target: {
|
|
119
130
|
chat: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { constants } from "node:fs";
|
|
3
|
-
import { open, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { constants, mkdirSync } from "node:fs";
|
|
3
|
+
import { lstat, open, readFile, stat } from "node:fs/promises";
|
|
4
4
|
import { CliError } from "@wirecat/cli-base";
|
|
5
5
|
import { queryFieldsOf, walkQuery } from "@wirecat/cli-base/query";
|
|
6
6
|
import { directoryPaths } from "../attachments/directory.js";
|
|
@@ -18,6 +18,7 @@ import { refusedPlace } from "../sends/upload.js";
|
|
|
18
18
|
import { attachmentColumns, queryFilter, } from "../store/store.js";
|
|
19
19
|
import { batchProgress } from "./batch.js";
|
|
20
20
|
import { accountStore } from "./deps.js";
|
|
21
|
+
import { save } from "./file-download.js";
|
|
21
22
|
import { chatIdIn } from "./message-resolution.js";
|
|
22
23
|
export const ATTACHMENT_QUERY = {
|
|
23
24
|
entity: "attachment",
|
|
@@ -407,6 +408,48 @@ export const attachmentsService = (deps) => ({
|
|
|
407
408
|
...preview,
|
|
408
409
|
};
|
|
409
410
|
},
|
|
411
|
+
saveHeld: async ({ chat, message, attachment, output }) => {
|
|
412
|
+
if (attachment !== undefined && (!Number.isSafeInteger(attachment) || attachment < 1))
|
|
413
|
+
throw new CliError("validation_error", "attachment must be a position from 1");
|
|
414
|
+
const store = await accountStore(deps);
|
|
415
|
+
const { account } = store;
|
|
416
|
+
const { chatId, messageId } = await messageOf(deps, store, chat, message);
|
|
417
|
+
const ref = formatLocator({ ...account, chat: chatId, message: messageId });
|
|
418
|
+
const files = (await store.attachments.attachments({ chatId, messageId, limit: 1000 })).filter(({ kind }) => !NOT_FILES.has(kind));
|
|
419
|
+
if (files.length === 0)
|
|
420
|
+
throw new CliError("not_found", `the local store holds no file of ${ref}`);
|
|
421
|
+
const chosen = attachment === undefined ? files : files.filter(({ position }) => position === attachment - 1);
|
|
422
|
+
if (chosen.length === 0)
|
|
423
|
+
throw new CliError("not_found", `${ref} has no file at position ${attachment}; its files are at ${files.map(({ position }) => position + 1).join(", ")}`);
|
|
424
|
+
const env = deps.env ?? process.env;
|
|
425
|
+
const held = await Promise.all(chosen.map(async (file) => {
|
|
426
|
+
const missing = new CliError("not_found", `file ${file.position + 1} of ${ref} is not saved on this computer; run messages download without --offline`);
|
|
427
|
+
if (file.localPath === null)
|
|
428
|
+
throw missing;
|
|
429
|
+
if (refusedPlace(file.localPath, deps.messenger.app, env))
|
|
430
|
+
throw new CliError("validation_error", "cannot copy known credential files, the WireCat apps' own files or the message store");
|
|
431
|
+
const found = await lstat(file.localPath).catch(() => undefined);
|
|
432
|
+
if (!found)
|
|
433
|
+
throw missing;
|
|
434
|
+
if (!found.isFile())
|
|
435
|
+
throw new CliError("validation_error", "the saved attachment must be a regular file");
|
|
436
|
+
return { file, path: file.localPath };
|
|
437
|
+
}));
|
|
438
|
+
mkdirSync(output, { recursive: true });
|
|
439
|
+
const saved = [];
|
|
440
|
+
for (const { file, path } of held)
|
|
441
|
+
saved.push(await save({
|
|
442
|
+
kind: file.kind,
|
|
443
|
+
...(file.name === null ? {} : { name: file.name }),
|
|
444
|
+
...(file.mime === null ? {} : { mime: file.mime }),
|
|
445
|
+
position: file.position,
|
|
446
|
+
bytes: async function* () {
|
|
447
|
+
const handle = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
448
|
+
yield* handle.createReadStream();
|
|
449
|
+
},
|
|
450
|
+
}, output, `${messageId}-${file.position + 1}`));
|
|
451
|
+
return saved;
|
|
452
|
+
},
|
|
410
453
|
setText: async ({ chat, message, attachment, text }) => {
|
|
411
454
|
if (text.trim() === "")
|
|
412
455
|
throw new CliError("validation_error", "the text is empty — nothing to keep");
|
package/dist/services/deps.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ export declare const fromStore: (deps: Pick<ServiceDeps, "offline" | "reads">) =
|
|
|
72
72
|
export declare const nothingStored: (messenger: MessengerTraits) => string;
|
|
73
73
|
/** Where a store-mode messenger's history comes from, for what it cannot do. */
|
|
74
74
|
export declare const PUSHED = "this messenger's history is read from the local store";
|
|
75
|
-
export declare const OFFLINE = "--offline answers only from what is kept locally: `chats list|show`, `messages list|show|context`, `contacts list|show` and `review`";
|
|
75
|
+
export declare const OFFLINE = "--offline answers only from what is kept locally: `chats list|show`, `messages list|show|context|download`, `contacts list|show` and `review`";
|
|
76
76
|
/** Over a connection that is already open — an MCP session holds one for minutes. */
|
|
77
77
|
export declare const onlineDeps: (messenger: ServiceMessenger, adapter: MessengerAdapter, guard: SendGuard, where?: {
|
|
78
78
|
profile?: string;
|
package/dist/services/deps.js
CHANGED
|
@@ -55,7 +55,7 @@ export const fromStore = (deps) => deps.offline || deps.reads === "store";
|
|
|
55
55
|
export const nothingStored = (messenger) => `nothing stored for this chat yet — keep \`${typedCommand(messenger.app.command, "serve")}\` running`;
|
|
56
56
|
/** Where a store-mode messenger's history comes from, for what it cannot do. */
|
|
57
57
|
export const PUSHED = "this messenger's history is read from the local store";
|
|
58
|
-
export const OFFLINE = "--offline answers only from what is kept locally: `chats list|show`, `messages list|show|context`, `contacts list|show` and `review`";
|
|
58
|
+
export const OFFLINE = "--offline answers only from what is kept locally: `chats list|show`, `messages list|show|context|download`, `contacts list|show` and `review`";
|
|
59
59
|
/** Over a connection that is already open — an MCP session holds one for minutes. */
|
|
60
60
|
export const onlineDeps = (messenger, adapter, guard, where = {}) => ({
|
|
61
61
|
messenger,
|
|
@@ -197,7 +197,6 @@ export interface Reacted {
|
|
|
197
197
|
}
|
|
198
198
|
/** max-cli's: many at once is what a ban for automation looks like. */
|
|
199
199
|
export declare const DELETE_AT_ONCE = 10;
|
|
200
|
-
/** Every write goes through the guard: asked before it goes, told after, on every outcome. */
|
|
201
200
|
export interface MessagesService {
|
|
202
201
|
/** The account these messages are read as, which names each one's locator. */
|
|
203
202
|
reader(): Promise<AccountKey>;
|
|
@@ -212,8 +211,8 @@ export interface MessagesService {
|
|
|
212
211
|
}): Promise<Page<Message> & {
|
|
213
212
|
discussion: Discussion;
|
|
214
213
|
}>;
|
|
215
|
-
/** The files of one message
|
|
216
|
-
download(chat: string, message
|
|
214
|
+
/** The files of one message, `chat` and its id or a `msg:` locator alone. Always from the messenger. */
|
|
215
|
+
download(chat: string, message?: Id): Promise<Download>;
|
|
217
216
|
/**
|
|
218
217
|
* Records in the local store where the files went, so their text can be read later. Only a
|
|
219
218
|
* message the store holds; answers how many attachments it recorded.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { validateFormattedText, visibleFormattedText } from "../domain/formatting.js";
|
|
3
3
|
import { formatLocator } from "../domain/locator.js";
|
|
4
|
-
import { messageLinkTarget, validatePermalink } from "../domain/message-link.js";
|
|
4
|
+
import { fileLinks, messageLinkTarget, validatePermalink } from "../domain/message-link.js";
|
|
5
5
|
import { pinDurationsText } from "../domain/pin-duration.js";
|
|
6
6
|
import { threadIdOf } from "../domain/thread.js";
|
|
7
7
|
import { capability } from "../ports/messenger.js";
|
|
@@ -22,6 +22,12 @@ import { searchServer, sourceKey, } from "./server-search.js";
|
|
|
22
22
|
import { readThreadContext, threadBounds } from "./thread-context.js";
|
|
23
23
|
/** max-cli's: many at once is what a ban for automation looks like. */
|
|
24
24
|
export const DELETE_AT_ONCE = 10;
|
|
25
|
+
/** Every write goes through the guard: asked before it goes, told after, on every outcome. */
|
|
26
|
+
/** Only a message with files says what can be linked of them. */
|
|
27
|
+
const filesOf = ({ attachments }) => {
|
|
28
|
+
const files = fileLinks(attachments);
|
|
29
|
+
return files.length > 0 ? { files } : {};
|
|
30
|
+
};
|
|
25
31
|
export const messagesService = (deps) => {
|
|
26
32
|
const { guard } = deps;
|
|
27
33
|
const inStore = async (read) => read(await deps.store(), await deps.account());
|
|
@@ -185,13 +191,15 @@ export const messagesService = (deps) => {
|
|
|
185
191
|
const store = await deps.store();
|
|
186
192
|
const chat = await readChatId(deps, target.chat, store, account);
|
|
187
193
|
const found = await store.messages.around(account, chat, target.message, { before: 0, after: 0 });
|
|
188
|
-
|
|
194
|
+
const held = found.find((item) => item.id === target.message && item.chatId === chat);
|
|
195
|
+
if (!held)
|
|
189
196
|
throw new CliError("not_found", "no such message in this account's stored chat");
|
|
190
197
|
return {
|
|
191
198
|
locator: formatLocator({ ...account, chat, message: target.message }),
|
|
192
199
|
url: null,
|
|
193
200
|
access: "unavailable",
|
|
194
201
|
reason: deps.offline ? "offline" : "unsupported_provider",
|
|
202
|
+
...filesOf(held),
|
|
195
203
|
};
|
|
196
204
|
}
|
|
197
205
|
const connection = await deps.connection();
|
|
@@ -199,20 +207,31 @@ export const messagesService = (deps) => {
|
|
|
199
207
|
throw new CliError("authentication_error", "the connected account differs from the recorded profile; refresh the session before linking");
|
|
200
208
|
const { id: chat } = await connection.resolve(target.chat);
|
|
201
209
|
const locator = formatLocator({ ...account, chat, message: target.message });
|
|
202
|
-
if (connection.permalink)
|
|
203
|
-
|
|
210
|
+
if (connection.permalink) {
|
|
211
|
+
const permalink = validatePermalink(await connection.permalink(chat, target.message));
|
|
212
|
+
const held = await deps
|
|
213
|
+
.store()
|
|
214
|
+
.then((store) => store.messages.around(account, chat, target.message, { before: 0, after: 0 }))
|
|
215
|
+
.then((found) => found.find((item) => item.id === target.message && item.chatId === chat))
|
|
216
|
+
.catch(() => undefined);
|
|
217
|
+
return { locator, ...permalink, ...(held ? filesOf(held) : {}) };
|
|
218
|
+
}
|
|
204
219
|
const found = await capability(connection, "around", "read a message")(chat, target.message, {
|
|
205
220
|
before: 0,
|
|
206
221
|
after: 0,
|
|
207
222
|
});
|
|
208
|
-
|
|
223
|
+
const held = found.find((item) => item.id === target.message && item.chatId === chat);
|
|
224
|
+
if (!held)
|
|
209
225
|
throw new CliError("not_found", "no such message in this chat");
|
|
210
|
-
return { locator, url: null, access: "unavailable", reason: "unsupported_provider" };
|
|
226
|
+
return { locator, url: null, access: "unavailable", reason: "unsupported_provider", ...filesOf(held) };
|
|
211
227
|
},
|
|
212
|
-
download: async (
|
|
228
|
+
download: async (reference, message) => {
|
|
229
|
+
const target = messageLinkTarget(reference, message, deps.messenger.provider);
|
|
230
|
+
if (target.account !== undefined && target.account !== (await deps.account()).account)
|
|
231
|
+
throw new CliError("validation_error", "that locator belongs to another account; select its profile first");
|
|
213
232
|
const connection = await deps.connection();
|
|
214
233
|
const download = capability(connection, "download", "download attachments");
|
|
215
|
-
return download((await connection.resolve(chat)).id, message);
|
|
234
|
+
return download((await connection.resolve(target.chat)).id, target.message);
|
|
216
235
|
},
|
|
217
236
|
keepDownloaded: (chat, message, files) => inStore(async (store, account) => {
|
|
218
237
|
const chatId = (await storedChatId(deps.messenger, chat, store, account).catch(() => undefined)) ??
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ErrorCode } from "@wirecat/cli-base";
|
|
2
|
+
import { type HitFile } from "../domain/attachments.js";
|
|
2
3
|
import { type MailFields } from "../domain/models.js";
|
|
3
4
|
import type { MessengerTraits } from "../ports/traits.js";
|
|
4
5
|
import type { AccountKey, MessageStore } from "../store/store.js";
|
|
@@ -30,6 +31,8 @@ export interface SearchAllItem {
|
|
|
30
31
|
senderIsMe?: boolean;
|
|
31
32
|
/** A mail's people, labels and thread, as `search mail` gives them. */
|
|
32
33
|
email?: MailFields;
|
|
34
|
+
/** A message's or a mail's files, as `search messages` names them. */
|
|
35
|
+
attachments?: HitFile[];
|
|
33
36
|
/** Which search found it: words, meaning or both. */
|
|
34
37
|
foundBy: ("title" | "words" | "meaning")[];
|
|
35
38
|
}
|