@wirecat/cli-core 0.400.0 → 0.402.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 +39 -0
- package/README.md +1 -1
- package/dist/cli/bot/api.js +5 -2
- package/dist/cli/bot/store.js +1 -1
- package/dist/cli/config-command.js +13 -7
- package/dist/cli/context.d.ts +2 -0
- package/dist/cli/index.d.ts +2 -1
- package/dist/cli/index.js +2 -1
- package/dist/cli/meeting-evidence-answer.d.ts +46 -0
- package/dist/cli/meeting-evidence-answer.js +22 -0
- package/dist/cli/meeting-preparation-answer.js +1 -13
- package/dist/cli/messenger/account-sessions-command.js +0 -2
- package/dist/cli/messenger/archive-commands.js +1 -1
- package/dist/cli/messenger/doctor-command.js +0 -1
- package/dist/cli/messenger/message-writes.d.ts +192 -2
- package/dist/cli/messenger/message-writes.js +6 -2
- package/dist/cli/messenger/messages-send-command.js +9 -6
- package/dist/cli/messenger/models-command.js +0 -3
- package/dist/cli/messenger/records-command.js +0 -2
- package/dist/cli/messenger/review.js +1 -1
- package/dist/cli/messenger/schedule-command.js +2 -2
- package/dist/cli/messenger/search-answers.js +2 -0
- package/dist/cli/messenger/server-command.js +0 -3
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/messenger/store-maintenance-command.js +7 -7
- package/dist/cli/messenger/stored.js +4 -1
- package/dist/cli/messenger/tasks-command.js +10 -6
- package/dist/cli/program.js +5 -1
- package/dist/documents/index.d.ts +1 -0
- package/dist/documents/index.js +1 -0
- package/dist/entities/hit.d.ts +4 -0
- package/dist/entities/hit.js +4 -0
- package/dist/entities/index.d.ts +285 -0
- package/dist/entities/index.js +5 -1
- package/dist/entities/meeting-evidence.d.ts +86 -0
- package/dist/entities/meeting-evidence.js +25 -0
- package/dist/entities/meeting-preparation-parts.d.ts +14 -0
- package/dist/entities/meeting-preparation-parts.js +15 -13
- package/dist/entities/meeting.d.ts +1 -1
- package/dist/entities/meeting.js +1 -1
- package/dist/entities/message-write.d.ts +187 -0
- package/dist/entities/message-write.js +2 -0
- package/dist/errors/reasons.js +2 -0
- package/dist/mcp/agent.js +12 -6
- package/dist/meetings/cli/index.js +3 -1
- package/dist/meetings/index.d.ts +1 -1
- package/dist/meetings/index.js +1 -1
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/meetings/read-answers.d.ts +18 -0
- package/dist/meetings/read-answers.js +30 -0
- package/dist/ports/messenger.d.ts +10 -1
- package/dist/search/dictionary.d.ts +20 -0
- package/dist/search/dictionary.js +106 -0
- package/dist/sends/guard.d.ts +1 -1
- package/dist/sends/guard.js +9 -5
- package/dist/sends/journal.d.ts +1 -1
- package/dist/sends/permissions.d.ts +10 -1
- package/dist/sends/permissions.js +43 -1
- package/dist/sends/upload.d.ts +4 -4
- package/dist/sends/upload.js +24 -20
- package/dist/services/documents/mirrors.d.ts +98 -0
- package/dist/services/documents/mirrors.js +144 -0
- package/dist/services/messages.js +1 -0
- package/dist/services/search-all-meetings.js +21 -8
- package/dist/services/task-rules.d.ts +1 -1
- package/dist/services/tasks.d.ts +2 -2
- package/dist/services/tasks.js +1 -1
- package/dist/store/sqlite/drizzle/bun.js +3 -3
- package/dist/store/sqlite/drizzle/bun.js.map +1 -1
- package/dist/store/sqlite/drizzle/core.js +2 -2
- package/dist/store/sqlite/drizzle/core.js.map +1 -1
- package/dist/store/sqlite/drizzle/node.js +3 -3
- package/dist/store/sqlite/drizzle/node.js.map +1 -1
- package/dist/store/sqlite/drizzle/{shared-O7CTKP45.js → shared-HKXF6HKX.js} +57 -57
- package/dist/store/sqlite/drizzle/{shared-O7CTKP45.js.map → shared-HKXF6HKX.js.map} +1 -1
- package/dist/store/sqlite/transcript-query.js +15 -5
- package/package.json +4 -2
- package/parity.json +6 -3
|
@@ -38,6 +38,7 @@ export declare const meetingTools: ({
|
|
|
38
38
|
max_read_bytes?: undefined;
|
|
39
39
|
cursor?: undefined;
|
|
40
40
|
} | {
|
|
41
|
+
limit?: undefined;
|
|
41
42
|
event_id?: undefined;
|
|
42
43
|
series_id?: undefined;
|
|
43
44
|
page?: undefined;
|
|
@@ -53,11 +54,11 @@ export declare const meetingTools: ({
|
|
|
53
54
|
format?: undefined;
|
|
54
55
|
transcript_id?: undefined;
|
|
55
56
|
query?: undefined;
|
|
56
|
-
limit?: undefined;
|
|
57
57
|
scan_limit?: undefined;
|
|
58
58
|
max_read_bytes?: undefined;
|
|
59
59
|
cursor?: undefined;
|
|
60
60
|
} | {
|
|
61
|
+
limit?: undefined;
|
|
61
62
|
event_id?: undefined;
|
|
62
63
|
series_id?: undefined;
|
|
63
64
|
page?: undefined;
|
|
@@ -75,11 +76,11 @@ export declare const meetingTools: ({
|
|
|
75
76
|
format?: undefined;
|
|
76
77
|
transcript_id?: undefined;
|
|
77
78
|
query?: undefined;
|
|
78
|
-
limit?: undefined;
|
|
79
79
|
scan_limit?: undefined;
|
|
80
80
|
max_read_bytes?: undefined;
|
|
81
81
|
cursor?: undefined;
|
|
82
82
|
} | {
|
|
83
|
+
limit?: undefined;
|
|
83
84
|
event_id?: undefined;
|
|
84
85
|
series_id?: undefined;
|
|
85
86
|
page?: undefined;
|
|
@@ -101,7 +102,6 @@ export declare const meetingTools: ({
|
|
|
101
102
|
minimum: number;
|
|
102
103
|
};
|
|
103
104
|
query?: undefined;
|
|
104
|
-
limit?: undefined;
|
|
105
105
|
scan_limit?: undefined;
|
|
106
106
|
max_read_bytes?: undefined;
|
|
107
107
|
cursor?: undefined;
|
|
@@ -188,6 +188,7 @@ export declare const meetingTools: ({
|
|
|
188
188
|
max_read_bytes?: undefined;
|
|
189
189
|
cursor?: undefined;
|
|
190
190
|
} | {
|
|
191
|
+
limit?: undefined;
|
|
191
192
|
event_id?: undefined;
|
|
192
193
|
series_id?: undefined;
|
|
193
194
|
page?: undefined;
|
|
@@ -203,11 +204,11 @@ export declare const meetingTools: ({
|
|
|
203
204
|
format?: undefined;
|
|
204
205
|
transcript_id?: undefined;
|
|
205
206
|
query?: undefined;
|
|
206
|
-
limit?: undefined;
|
|
207
207
|
scan_limit?: undefined;
|
|
208
208
|
max_read_bytes?: undefined;
|
|
209
209
|
cursor?: undefined;
|
|
210
210
|
} | {
|
|
211
|
+
limit?: undefined;
|
|
211
212
|
event_id?: undefined;
|
|
212
213
|
series_id?: undefined;
|
|
213
214
|
page?: undefined;
|
|
@@ -225,11 +226,11 @@ export declare const meetingTools: ({
|
|
|
225
226
|
format?: undefined;
|
|
226
227
|
transcript_id?: undefined;
|
|
227
228
|
query?: undefined;
|
|
228
|
-
limit?: undefined;
|
|
229
229
|
scan_limit?: undefined;
|
|
230
230
|
max_read_bytes?: undefined;
|
|
231
231
|
cursor?: undefined;
|
|
232
232
|
} | {
|
|
233
|
+
limit?: undefined;
|
|
233
234
|
event_id?: undefined;
|
|
234
235
|
series_id?: undefined;
|
|
235
236
|
page?: undefined;
|
|
@@ -251,7 +252,6 @@ export declare const meetingTools: ({
|
|
|
251
252
|
minimum: number;
|
|
252
253
|
};
|
|
253
254
|
query?: undefined;
|
|
254
|
-
limit?: undefined;
|
|
255
255
|
scan_limit?: undefined;
|
|
256
256
|
max_read_bytes?: undefined;
|
|
257
257
|
cursor?: undefined;
|
|
@@ -338,6 +338,7 @@ export declare const meetingTools: ({
|
|
|
338
338
|
max_read_bytes?: undefined;
|
|
339
339
|
cursor?: undefined;
|
|
340
340
|
} | {
|
|
341
|
+
limit?: undefined;
|
|
341
342
|
event_id?: undefined;
|
|
342
343
|
series_id?: undefined;
|
|
343
344
|
page?: undefined;
|
|
@@ -353,11 +354,11 @@ export declare const meetingTools: ({
|
|
|
353
354
|
format?: undefined;
|
|
354
355
|
transcript_id?: undefined;
|
|
355
356
|
query?: undefined;
|
|
356
|
-
limit?: undefined;
|
|
357
357
|
scan_limit?: undefined;
|
|
358
358
|
max_read_bytes?: undefined;
|
|
359
359
|
cursor?: undefined;
|
|
360
360
|
} | {
|
|
361
|
+
limit?: undefined;
|
|
361
362
|
event_id?: undefined;
|
|
362
363
|
series_id?: undefined;
|
|
363
364
|
page?: undefined;
|
|
@@ -375,11 +376,11 @@ export declare const meetingTools: ({
|
|
|
375
376
|
format?: undefined;
|
|
376
377
|
transcript_id?: undefined;
|
|
377
378
|
query?: undefined;
|
|
378
|
-
limit?: undefined;
|
|
379
379
|
scan_limit?: undefined;
|
|
380
380
|
max_read_bytes?: undefined;
|
|
381
381
|
cursor?: undefined;
|
|
382
382
|
} | {
|
|
383
|
+
limit?: undefined;
|
|
383
384
|
event_id?: undefined;
|
|
384
385
|
series_id?: undefined;
|
|
385
386
|
page?: undefined;
|
|
@@ -401,7 +402,6 @@ export declare const meetingTools: ({
|
|
|
401
402
|
minimum: number;
|
|
402
403
|
};
|
|
403
404
|
query?: undefined;
|
|
404
|
-
limit?: undefined;
|
|
405
405
|
scan_limit?: undefined;
|
|
406
406
|
max_read_bytes?: undefined;
|
|
407
407
|
cursor?: undefined;
|
|
@@ -488,6 +488,7 @@ export declare const meetingTools: ({
|
|
|
488
488
|
max_read_bytes?: undefined;
|
|
489
489
|
cursor?: undefined;
|
|
490
490
|
} | {
|
|
491
|
+
limit?: undefined;
|
|
491
492
|
event_id?: undefined;
|
|
492
493
|
series_id?: undefined;
|
|
493
494
|
page?: undefined;
|
|
@@ -503,11 +504,11 @@ export declare const meetingTools: ({
|
|
|
503
504
|
format?: undefined;
|
|
504
505
|
transcript_id?: undefined;
|
|
505
506
|
query?: undefined;
|
|
506
|
-
limit?: undefined;
|
|
507
507
|
scan_limit?: undefined;
|
|
508
508
|
max_read_bytes?: undefined;
|
|
509
509
|
cursor?: undefined;
|
|
510
510
|
} | {
|
|
511
|
+
limit?: undefined;
|
|
511
512
|
event_id?: undefined;
|
|
512
513
|
series_id?: undefined;
|
|
513
514
|
page?: undefined;
|
|
@@ -525,11 +526,11 @@ export declare const meetingTools: ({
|
|
|
525
526
|
format?: undefined;
|
|
526
527
|
transcript_id?: undefined;
|
|
527
528
|
query?: undefined;
|
|
528
|
-
limit?: undefined;
|
|
529
529
|
scan_limit?: undefined;
|
|
530
530
|
max_read_bytes?: undefined;
|
|
531
531
|
cursor?: undefined;
|
|
532
532
|
} | {
|
|
533
|
+
limit?: undefined;
|
|
533
534
|
event_id?: undefined;
|
|
534
535
|
series_id?: undefined;
|
|
535
536
|
page?: undefined;
|
|
@@ -551,7 +552,6 @@ export declare const meetingTools: ({
|
|
|
551
552
|
minimum: number;
|
|
552
553
|
};
|
|
553
554
|
query?: undefined;
|
|
554
|
-
limit?: undefined;
|
|
555
555
|
scan_limit?: undefined;
|
|
556
556
|
max_read_bytes?: undefined;
|
|
557
557
|
cursor?: undefined;
|
|
@@ -638,6 +638,7 @@ export declare const meetingTools: ({
|
|
|
638
638
|
max_read_bytes?: undefined;
|
|
639
639
|
cursor?: undefined;
|
|
640
640
|
} | {
|
|
641
|
+
limit?: undefined;
|
|
641
642
|
event_id?: undefined;
|
|
642
643
|
series_id?: undefined;
|
|
643
644
|
page?: undefined;
|
|
@@ -653,11 +654,11 @@ export declare const meetingTools: ({
|
|
|
653
654
|
format?: undefined;
|
|
654
655
|
transcript_id?: undefined;
|
|
655
656
|
query?: undefined;
|
|
656
|
-
limit?: undefined;
|
|
657
657
|
scan_limit?: undefined;
|
|
658
658
|
max_read_bytes?: undefined;
|
|
659
659
|
cursor?: undefined;
|
|
660
660
|
} | {
|
|
661
|
+
limit?: undefined;
|
|
661
662
|
event_id?: undefined;
|
|
662
663
|
series_id?: undefined;
|
|
663
664
|
page?: undefined;
|
|
@@ -675,11 +676,11 @@ export declare const meetingTools: ({
|
|
|
675
676
|
format?: undefined;
|
|
676
677
|
transcript_id?: undefined;
|
|
677
678
|
query?: undefined;
|
|
678
|
-
limit?: undefined;
|
|
679
679
|
scan_limit?: undefined;
|
|
680
680
|
max_read_bytes?: undefined;
|
|
681
681
|
cursor?: undefined;
|
|
682
682
|
} | {
|
|
683
|
+
limit?: undefined;
|
|
683
684
|
event_id?: undefined;
|
|
684
685
|
series_id?: undefined;
|
|
685
686
|
page?: undefined;
|
|
@@ -701,7 +702,6 @@ export declare const meetingTools: ({
|
|
|
701
702
|
minimum: number;
|
|
702
703
|
};
|
|
703
704
|
query?: undefined;
|
|
704
|
-
limit?: undefined;
|
|
705
705
|
scan_limit?: undefined;
|
|
706
706
|
max_read_bytes?: undefined;
|
|
707
707
|
cursor?: undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
2
|
import type { MeetingLink } from "./links.js";
|
|
3
|
+
import type { Event } from "./model.js";
|
|
3
4
|
import type { MeetingReviewItem } from "./review.js";
|
|
4
5
|
import type { MeetingSpeakerStats, MeetingStats } from "./stats.js";
|
|
5
6
|
/** One meeting in `review`: what the store holds about it, never transcript text. */
|
|
@@ -165,3 +166,20 @@ export declare const meetingPassage: v.StrictObjectSchema<{
|
|
|
165
166
|
readonly revision: v.PicklistSchema<["current", "superseded"], undefined>;
|
|
166
167
|
}, undefined>, undefined>;
|
|
167
168
|
}, undefined>;
|
|
169
|
+
/** A stored event a meeting belongs to; `id` is what `meetings update --event-id` takes. */
|
|
170
|
+
export declare const event: v.StrictObjectSchema<{
|
|
171
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
172
|
+
readonly event_series_id: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
173
|
+
readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
174
|
+
readonly description: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
175
|
+
readonly location: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
176
|
+
readonly starts_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
177
|
+
readonly ends_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
178
|
+
readonly timezone: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
179
|
+
/** `auto`: made from a meeting; `owner`: made by hand. */
|
|
180
|
+
readonly origin: v.PicklistSchema<["auto", "owner"], undefined>;
|
|
181
|
+
readonly deleted_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
182
|
+
readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
183
|
+
readonly updated_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
184
|
+
}, undefined>;
|
|
185
|
+
export declare const eventItem: (one: Event) => v.InferOutput<typeof event>;
|
|
@@ -181,3 +181,33 @@ export const meetingPassage = v.strictObject({
|
|
|
181
181
|
revision,
|
|
182
182
|
})),
|
|
183
183
|
});
|
|
184
|
+
/** A stored event a meeting belongs to; `id` is what `meetings update --event-id` takes. */
|
|
185
|
+
export const event = v.strictObject({
|
|
186
|
+
id: v.pipe(v.string(), v.minLength(1)),
|
|
187
|
+
event_series_id: v.nullable(text),
|
|
188
|
+
title: v.nullable(text),
|
|
189
|
+
description: v.nullable(text),
|
|
190
|
+
location: v.nullable(text),
|
|
191
|
+
starts_at: v.nullable(isoTime),
|
|
192
|
+
ends_at: v.nullable(isoTime),
|
|
193
|
+
timezone: v.nullable(text),
|
|
194
|
+
/** `auto`: made from a meeting; `owner`: made by hand. */
|
|
195
|
+
origin: v.picklist(["auto", "owner"]),
|
|
196
|
+
deleted_at: v.nullable(isoTime),
|
|
197
|
+
created_at: isoTime,
|
|
198
|
+
updated_at: isoTime,
|
|
199
|
+
});
|
|
200
|
+
export const eventItem = (one) => ({
|
|
201
|
+
id: String(one.id),
|
|
202
|
+
event_series_id: one.eventSeriesId === null ? null : String(one.eventSeriesId),
|
|
203
|
+
title: one.title,
|
|
204
|
+
description: one.description,
|
|
205
|
+
location: one.location,
|
|
206
|
+
starts_at: iso(one.startsAt),
|
|
207
|
+
ends_at: iso(one.endsAt),
|
|
208
|
+
timezone: one.timezone,
|
|
209
|
+
origin: one.origin,
|
|
210
|
+
deleted_at: iso(one.deletedAt),
|
|
211
|
+
created_at: new Date(one.createdAt).toISOString(),
|
|
212
|
+
updated_at: new Date(one.updatedAt).toISOString(),
|
|
213
|
+
});
|
|
@@ -41,7 +41,10 @@ export interface SendOptions {
|
|
|
41
41
|
formatting?: TextSpan[];
|
|
42
42
|
/** ISO time: the messenger holds it and sends it then, under a new id. */
|
|
43
43
|
at?: string;
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Sent in one message, `text` as the caption. More than one is an album: photos first, then files, each in the
|
|
46
|
+
* order given. An adapter whose messenger cannot send one refuses it with `validation_error` before anything goes.
|
|
47
|
+
*/
|
|
45
48
|
attachments?: Upload[];
|
|
46
49
|
/** One sticker by id, alone: no text, no attachment. Only where `Messenger.stickers` is on. */
|
|
47
50
|
sticker?: Id;
|
|
@@ -82,7 +85,13 @@ export interface NewPoll {
|
|
|
82
85
|
closeAfter?: number;
|
|
83
86
|
}
|
|
84
87
|
export interface Sent {
|
|
88
|
+
/** The message sent; for an album, its first. */
|
|
85
89
|
message: Message;
|
|
90
|
+
/**
|
|
91
|
+
* For an album, every message it made, in order, `message` among them. Unset for a single message, and where an
|
|
92
|
+
* adapter has not learned to answer with them yet.
|
|
93
|
+
*/
|
|
94
|
+
messages?: Message[];
|
|
86
95
|
/** The send's identity. Repeat it after an unknown outcome, never a new one. */
|
|
87
96
|
sendId: string;
|
|
88
97
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** The Hunspell dictionary that tells a real word from a garbled one, for each language the store stems. */
|
|
2
|
+
export declare const DICTIONARIES: {
|
|
3
|
+
readonly russian: "dictionary-ru";
|
|
4
|
+
readonly english: "dictionary-en";
|
|
5
|
+
readonly spanish: "dictionary-es";
|
|
6
|
+
};
|
|
7
|
+
export type DictionaryLanguage = keyof typeof DICTIONARIES;
|
|
8
|
+
/**
|
|
9
|
+
* `known`: whether a folded word the index stores is real in one of `languages` that write in its alphabet.
|
|
10
|
+
* `covers`: whether any of them does; without one, a garbled spelling cannot be told from a real word.
|
|
11
|
+
*/
|
|
12
|
+
export declare const knownWord: (languages: readonly DictionaryLanguage[]) => {
|
|
13
|
+
covers: (word: string) => boolean;
|
|
14
|
+
known: (word: string) => boolean;
|
|
15
|
+
};
|
|
16
|
+
/** The dictionaries of the store's stemmers: Russian for Cyrillic, each named Latin stemmer for Latin. */
|
|
17
|
+
export declare const dictionaryLanguages: (stemmers: {
|
|
18
|
+
cyrillic: string;
|
|
19
|
+
latin: string;
|
|
20
|
+
}) => DictionaryLanguage[];
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { fold } from "../store/normalize.js";
|
|
3
|
+
/** The Hunspell dictionary that tells a real word from a garbled one, for each language the store stems. */
|
|
4
|
+
export const DICTIONARIES = { russian: "dictionary-ru", english: "dictionary-en", spanish: "dictionary-es" };
|
|
5
|
+
/** `fold`, without Unicode normalisation for plain Russian letters: it would double the time Russian loads in. */
|
|
6
|
+
const folded = (word) => /^[а-яё'-]*$/.test(word) ? word.replaceAll("ё", "е").replaceAll("й", "и") : fold(word);
|
|
7
|
+
const loaded = new Map();
|
|
8
|
+
/**
|
|
9
|
+
* Hunspell's own check, an affix at a time, instead of nspell's: nspell expands Russian's 146,269 entries into
|
|
10
|
+
* 1.44 million forms (1.4 s, 172 MB) where this loads in about 0.2 s. The index stores words folded as `normalize`
|
|
11
|
+
* folds them, so entries, affixes and conditions are folded too; a condition such as `[^ейоь]` then refuses и as
|
|
12
|
+
* well, which changed none of 12,769 sample words. Continuation classes and compounds are not followed: a word
|
|
13
|
+
* only they make counts as unknown and may widen.
|
|
14
|
+
*/
|
|
15
|
+
const dictionary = (language) => {
|
|
16
|
+
const cached = loaded.get(language);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
const file = (name) => readFileSync(new URL(name, import.meta.resolve(DICTIONARIES[language])), "utf8");
|
|
20
|
+
const words = new Map();
|
|
21
|
+
for (const line of file("index.dic").split("\n").slice(1)) {
|
|
22
|
+
const [entry, flags = ""] = line.trim().split("/");
|
|
23
|
+
if (!entry)
|
|
24
|
+
continue;
|
|
25
|
+
// Names and abbreviations are capitalised in the dictionary; the index is lower case.
|
|
26
|
+
const spelled = entry.toLowerCase();
|
|
27
|
+
const key = folded(spelled);
|
|
28
|
+
words.set(key, (words.get(key) ?? "") + flags);
|
|
29
|
+
}
|
|
30
|
+
const suffixes = new Map();
|
|
31
|
+
const prefixes = new Map();
|
|
32
|
+
const crosses = new Set();
|
|
33
|
+
let longest = 0;
|
|
34
|
+
for (const line of file("index.aff").split("\n")) {
|
|
35
|
+
const [kind, flag, strip, add, condition] = line.trim().split(/\s+/);
|
|
36
|
+
if ((kind !== "SFX" && kind !== "PFX") || flag === undefined || strip === undefined || add === undefined)
|
|
37
|
+
continue;
|
|
38
|
+
if (!condition) {
|
|
39
|
+
if (strip === "Y")
|
|
40
|
+
crosses.add(`${kind}${flag}`);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const affix = folded(add.split("/")[0].replace(/^0$/, ""));
|
|
44
|
+
const pattern = condition === "." ? "" : folded(condition);
|
|
45
|
+
const into = kind === "SFX" ? suffixes : prefixes;
|
|
46
|
+
const rules = into.get(affix) ?? [];
|
|
47
|
+
rules.push({
|
|
48
|
+
flag,
|
|
49
|
+
strip: strip === "0" ? "" : folded(strip),
|
|
50
|
+
condition: new RegExp(kind === "SFX" ? `${pattern}$` : `^${pattern}`),
|
|
51
|
+
cross: crosses.has(`${kind}${flag}`),
|
|
52
|
+
});
|
|
53
|
+
into.set(affix, rules);
|
|
54
|
+
longest = Math.max(longest, affix.length);
|
|
55
|
+
}
|
|
56
|
+
const made = { words, suffixes, prefixes, longest };
|
|
57
|
+
loaded.set(language, made);
|
|
58
|
+
return made;
|
|
59
|
+
};
|
|
60
|
+
const entry = ({ words }, key, rule, also) => {
|
|
61
|
+
const flags = words.get(key) ?? "";
|
|
62
|
+
return flags.includes(rule.flag) && (also === undefined || flags.includes(also)) && rule.condition.test(key);
|
|
63
|
+
};
|
|
64
|
+
/** A suffix on a dictionary word; `prefix` is the flag of a prefix already taken off, which both must allow. */
|
|
65
|
+
const suffixed = (found, word, prefix) => {
|
|
66
|
+
for (let cut = 0; cut <= Math.min(word.length - 1, found.longest); cut++)
|
|
67
|
+
for (const rule of found.suffixes.get(word.slice(word.length - cut)) ?? [])
|
|
68
|
+
if ((prefix === undefined || rule.cross) &&
|
|
69
|
+
entry(found, word.slice(0, word.length - cut) + rule.strip, rule, prefix))
|
|
70
|
+
return true;
|
|
71
|
+
return false;
|
|
72
|
+
};
|
|
73
|
+
const spelled = (language, word) => {
|
|
74
|
+
const found = dictionary(language);
|
|
75
|
+
if (found.words.has(word) || suffixed(found, word))
|
|
76
|
+
return true;
|
|
77
|
+
for (let cut = 1; cut <= Math.min(word.length - 1, found.longest); cut++)
|
|
78
|
+
for (const rule of found.prefixes.get(word.slice(0, cut)) ?? []) {
|
|
79
|
+
const rest = rule.strip + word.slice(cut);
|
|
80
|
+
if (entry(found, rest, rule) || (rule.cross && suffixed(found, rest, rule.flag)))
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
};
|
|
85
|
+
const CYRILLIC = /\p{Script=Cyrillic}/u;
|
|
86
|
+
const SCRIPT_OF = {
|
|
87
|
+
russian: "cyrillic",
|
|
88
|
+
english: "latin",
|
|
89
|
+
spanish: "latin",
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* `known`: whether a folded word the index stores is real in one of `languages` that write in its alphabet.
|
|
93
|
+
* `covers`: whether any of them does; without one, a garbled spelling cannot be told from a real word.
|
|
94
|
+
*/
|
|
95
|
+
export const knownWord = (languages) => {
|
|
96
|
+
const byScript = (word) => {
|
|
97
|
+
const script = CYRILLIC.test(word) ? "cyrillic" : "latin";
|
|
98
|
+
return languages.filter((language) => SCRIPT_OF[language] === script);
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
covers: (word) => byScript(word).length > 0,
|
|
102
|
+
known: (word) => byScript(word).some((language) => spelled(language, word)),
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
/** The dictionaries of the store's stemmers: Russian for Cyrillic, each named Latin stemmer for Latin. */
|
|
106
|
+
export const dictionaryLanguages = (stemmers) => [stemmers.cyrillic, ...stemmers.latin.split(",")].filter((name) => Object.hasOwn(DICTIONARIES, name));
|
package/dist/sends/guard.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface GuardRequest {
|
|
|
12
12
|
chatId: Id | null;
|
|
13
13
|
kind?: SendKind;
|
|
14
14
|
action?: ChatAction | AccountAction;
|
|
15
|
-
/** Messages a deletion names, or people a chat change adds. */
|
|
15
|
+
/** Messages a deletion names or an album sends, or people a chat change adds. */
|
|
16
16
|
count?: number;
|
|
17
17
|
sendId?: string;
|
|
18
18
|
sendAs?: Id;
|
package/dist/sends/guard.js
CHANGED
|
@@ -40,11 +40,11 @@ const countsTowardLimit = ({ kind = "message", action, notify }) => kind === "me
|
|
|
40
40
|
action === "join" ||
|
|
41
41
|
action === "contact-import";
|
|
42
42
|
/**
|
|
43
|
-
* Each deleted message, each person added to a group or let in by a
|
|
44
|
-
* counts as one.
|
|
43
|
+
* Each deleted message, each photo or file of an album (`NEED-1660`), each person added to a group or let in by a
|
|
44
|
+
* request, and each imported number counts as one.
|
|
45
45
|
*/
|
|
46
|
-
const weightOf = ({ kind, action, count, people }) => kind === "delete"
|
|
47
|
-
? (count ?? 1)
|
|
46
|
+
const weightOf = ({ kind = "message", action, count, people }) => kind === "delete" || (kind === "message" && action === undefined)
|
|
47
|
+
? Math.max(1, count ?? 1)
|
|
48
48
|
: action === "create" || action === "members.add" || action === "requests.accept" || action === "contact-import"
|
|
49
49
|
? Math.max(1, people ?? count ?? 1)
|
|
50
50
|
: 1;
|
|
@@ -180,7 +180,11 @@ export const sendGuard = ({ profile, recipients, command = recipients.command, r
|
|
|
180
180
|
reservation: id,
|
|
181
181
|
...(kind ? { kind } : {}),
|
|
182
182
|
...(action ? { action } : {}),
|
|
183
|
-
...(asked.people === undefined
|
|
183
|
+
...(asked.people === undefined
|
|
184
|
+
? {}
|
|
185
|
+
: kind === "delete" || kind === "message" || kind === undefined
|
|
186
|
+
? { count: asked.people }
|
|
187
|
+
: { people: asked.people }),
|
|
184
188
|
...(sendId === undefined ? {} : { sendId }),
|
|
185
189
|
...(operationId === undefined ? {} : { operationId }),
|
|
186
190
|
...(scheduledFor ? { scheduledFor } : {}),
|
package/dist/sends/journal.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export interface SendEntry {
|
|
|
23
23
|
messageId?: Id;
|
|
24
24
|
/** The command a write belongs to, where it named one: `polls.create` sets a poll apart from a message. */
|
|
25
25
|
key?: string;
|
|
26
|
-
/** How many messages a `delete` entry named — each one counts toward the hourly limit. */
|
|
26
|
+
/** How many messages a `delete` entry named, or an album sent — each one counts toward the hourly limit. */
|
|
27
27
|
count?: number;
|
|
28
28
|
forEveryone?: boolean;
|
|
29
29
|
/**
|
|
@@ -21,8 +21,17 @@ export declare const DEFAULT_PERMISSIONS: Readonly<Record<PermissionKey, Level>>
|
|
|
21
21
|
export declare const AUTHORITY_KEY: PermissionKey;
|
|
22
22
|
/** Whether these levels let an agent run a command that changes what a later run may do; `ask` does, as for any write. */
|
|
23
23
|
export declare const authorityAllowed: (levels: Readonly<Record<PermissionKey, Level>>) => boolean;
|
|
24
|
+
/** Lets an MCP server offer `bot api`, whose calls skip the send guard and the bot's recipient list (NEED-1679). */
|
|
25
|
+
export declare const BOT_API_KEY: PermissionKey;
|
|
26
|
+
export declare const isBotApi: (path: readonly string[]) => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Deny by default for an MCP server only: the key stays out of `DEFAULT_PERMISSIONS` because the command line's
|
|
29
|
+
* `bot api` is checked per operation (`bot.api.<operation>`), and a default there would deny it for the owner too.
|
|
30
|
+
* A broader `bot: allow` does not lift it; `ask` goes ahead over MCP, as for any write.
|
|
31
|
+
*/
|
|
32
|
+
export declare const botApiAllowed: (levels: Readonly<Record<PermissionKey, Level>>) => boolean;
|
|
24
33
|
/** The resources at the top of the command tree, which `readOnly` and `allow` turn read-only as a whole. */
|
|
25
|
-
export declare const RESOURCES: readonly ["messages", "reactions", "polls", "topics", "chats", "contacts", "account", "bot", "conversations", "tags", "mentions", "search", "searches", "tasks", "replies", "attachments", "stats", "store", "metadata", "mcp"];
|
|
34
|
+
export declare const RESOURCES: readonly ["messages", "reactions", "polls", "topics", "chats", "contacts", "account", "bot", "conversations", "tags", "mentions", "search", "searches", "tasks", "replies", "attachments", "stats", "store", "metadata", "mcp", "server", "models", "schedule", "doctor", "images"];
|
|
26
35
|
/** The command path an old `allow` word stood for, as one key — what an MCP tool's `permission` is checked against. */
|
|
27
36
|
export declare const keyOfWord: (word: Permission) => PermissionKey;
|
|
28
37
|
/**
|
|
@@ -79,7 +79,27 @@ export const DEFAULT_PERMISSIONS = {
|
|
|
79
79
|
"replies.send": "deny",
|
|
80
80
|
// NEED-1662: an agent that changes settings, schedules or recipients could lift its own limits; the owner opts in.
|
|
81
81
|
"mcp.authority": "deny",
|
|
82
|
+
// NEED-1663: writes no level stopped before, so MCP could not offer them; over MCP `ask` goes ahead.
|
|
83
|
+
"store.backup": "ask",
|
|
84
|
+
"store.restore": "ask",
|
|
85
|
+
"store.migrate": "ask",
|
|
86
|
+
"store.repair": "ask",
|
|
87
|
+
"store.reset": "ask",
|
|
88
|
+
"store.clear": "ask",
|
|
89
|
+
"store.decrypt": "ask",
|
|
90
|
+
"store.copies.delete": "ask",
|
|
91
|
+
"server.start": "ask",
|
|
92
|
+
"server.stop": "ask",
|
|
93
|
+
"server.restart": "ask",
|
|
94
|
+
"models.audio.download": "ask",
|
|
95
|
+
"models.text.download": "ask",
|
|
96
|
+
"images.models.download": "ask",
|
|
97
|
+
"schedule.run": "ask",
|
|
98
|
+
"schedule.exec": "ask",
|
|
99
|
+
"doctor.report.create": "ask",
|
|
82
100
|
};
|
|
101
|
+
/** Commands under a housekeeping word, or store maintenance, that still have a key of their own: their path. */
|
|
102
|
+
const KEYED_UNDER_HOUSEKEEPING = new Set(Object.keys(DEFAULT_PERMISSIONS).filter((key) => /^(store|server|models|images|schedule|doctor)\./.test(key)));
|
|
83
103
|
/** Lets an MCP server offer the commands that change what a later run may do (`grantsAuthority`). */
|
|
84
104
|
export const AUTHORITY_KEY = "mcp.authority";
|
|
85
105
|
/** Whether these levels let an agent run a command that changes what a later run may do; `ask` does, as for any write. */
|
|
@@ -87,6 +107,18 @@ export const authorityAllowed = (levels) => {
|
|
|
87
107
|
const { level } = levelFor(levels, AUTHORITY_KEY);
|
|
88
108
|
return level === "allow" || level === "ask";
|
|
89
109
|
};
|
|
110
|
+
/** Lets an MCP server offer `bot api`, whose calls skip the send guard and the bot's recipient list (NEED-1679). */
|
|
111
|
+
export const BOT_API_KEY = "bot.api";
|
|
112
|
+
export const isBotApi = (path) => path[0] === "bot" && path[1] === "api";
|
|
113
|
+
/**
|
|
114
|
+
* Deny by default for an MCP server only: the key stays out of `DEFAULT_PERMISSIONS` because the command line's
|
|
115
|
+
* `bot api` is checked per operation (`bot.api.<operation>`), and a default there would deny it for the owner too.
|
|
116
|
+
* A broader `bot: allow` does not lift it; `ask` goes ahead over MCP, as for any write.
|
|
117
|
+
*/
|
|
118
|
+
export const botApiAllowed = (levels) => {
|
|
119
|
+
const { level } = levelFor(levels, BOT_API_KEY, { ...DEFAULT_PERMISSIONS, [BOT_API_KEY]: "deny" });
|
|
120
|
+
return level === "allow" || level === "ask";
|
|
121
|
+
};
|
|
90
122
|
/** The resources at the top of the command tree, which `readOnly` and `allow` turn read-only as a whole. */
|
|
91
123
|
export const RESOURCES = [
|
|
92
124
|
"messages",
|
|
@@ -109,6 +141,11 @@ export const RESOURCES = [
|
|
|
109
141
|
"store",
|
|
110
142
|
"metadata",
|
|
111
143
|
"mcp",
|
|
144
|
+
"server",
|
|
145
|
+
"models",
|
|
146
|
+
"schedule",
|
|
147
|
+
"doctor",
|
|
148
|
+
"images",
|
|
112
149
|
];
|
|
113
150
|
const OLD_WORDS = {
|
|
114
151
|
send: ["messages.send", "polls.create", "chats.start"],
|
|
@@ -375,7 +412,7 @@ const HOUSEKEEPING = new Set([
|
|
|
375
412
|
]);
|
|
376
413
|
/** Under `bot`: its token, its names, its lists and its MCP server. */
|
|
377
414
|
const BOT_HOUSEKEEPING = new Set(["auth", "list", "sends", "recipients", "mcp"]);
|
|
378
|
-
const STORE_MAINTENANCE = new Set(["info", "check", "
|
|
415
|
+
const STORE_MAINTENANCE = new Set(["info", "check", "copies"]);
|
|
379
416
|
/** Commands outside `messages` that print what people wrote, so `deny messages` reaches them too. */
|
|
380
417
|
const SHOW_MESSAGES = new Set(["inbox", "review", "watch", "serve", "store", "conversations"]);
|
|
381
418
|
/** Before every search moved under `search` (2026-10-09). */
|
|
@@ -405,6 +442,9 @@ export const readKeysForCommand = (path) => {
|
|
|
405
442
|
return [key, "messages"];
|
|
406
443
|
if (path[0] === "store" && path[1] === "gaps" && path[2] === "repair")
|
|
407
444
|
return [key, "messages"];
|
|
445
|
+
// Checked as `messages` before they had keys of their own: `deny messages` keeps stopping them.
|
|
446
|
+
if (path[0] === "store" && ["reset", "clear", "decrypt"].includes(path[1] ?? ""))
|
|
447
|
+
return [key, "messages"];
|
|
408
448
|
// A search shows what people wrote, so `deny messages` reaches every leaf that reads messages.
|
|
409
449
|
if (path[0] === "search") {
|
|
410
450
|
if (path[1] === "notes")
|
|
@@ -438,6 +478,8 @@ export const keyForCommand = (path) => {
|
|
|
438
478
|
const inner = keyForCommand(path.slice(1));
|
|
439
479
|
return `bot.${inner ?? path.slice(1).join(".")}`;
|
|
440
480
|
}
|
|
481
|
+
if (KEYED_UNDER_HOUSEKEEPING.has(path.join(".")))
|
|
482
|
+
return path.join(".");
|
|
441
483
|
if (top === undefined || HOUSEKEEPING.has(top) || (top === "search" && next === "runs"))
|
|
442
484
|
return null;
|
|
443
485
|
// Picture models moved from `models image` (0.270.0); a model is housekeeping wherever it is listed.
|
package/dist/sends/upload.d.ts
CHANGED
|
@@ -18,12 +18,12 @@ export declare const readUpload: (kind: UploadKind, path: string, { app, env, an
|
|
|
18
18
|
photoTypes?: readonly string[];
|
|
19
19
|
}) => Promise<Upload>;
|
|
20
20
|
/**
|
|
21
|
-
* What a send attaches, as `messages send` and its tool take it.
|
|
22
|
-
* messengers' own apps send one.
|
|
21
|
+
* What a send attaches, as `messages send` and its tool take it. More than one photo or file is an album: photos
|
|
22
|
+
* first, then files, each in the order given. A voice message goes alone, as the messengers' own apps send one.
|
|
23
23
|
*/
|
|
24
24
|
export declare const readAttachments: ({ photo, file, voice, asFile, filename, text, }: {
|
|
25
|
-
photo?: string;
|
|
26
|
-
file?: string;
|
|
25
|
+
photo?: string | readonly string[];
|
|
26
|
+
file?: string | readonly string[];
|
|
27
27
|
voice?: string;
|
|
28
28
|
asFile?: boolean;
|
|
29
29
|
filename?: string;
|
package/dist/sends/upload.js
CHANGED
|
@@ -197,37 +197,41 @@ export const readUpload = async (kind, path, { app, env = process.env, anyFile =
|
|
|
197
197
|
}
|
|
198
198
|
return { kind, name: basename(path), source: { kind: "file", path: resolve(path), size } };
|
|
199
199
|
};
|
|
200
|
-
const checkFilename = (filename,
|
|
201
|
-
if (
|
|
200
|
+
const checkFilename = (filename, files) => {
|
|
201
|
+
if (files.length === 0)
|
|
202
202
|
throw new CliError("validation_error", "--filename names a --file; give one");
|
|
203
|
+
if (files.length > 1)
|
|
204
|
+
throw new CliError("validation_error", "--filename names one --file; an album keeps its names");
|
|
203
205
|
if (filename.trim() === "" || /[/\\\0]/.test(filename) || filename.length > 255)
|
|
204
206
|
throw new CliError("validation_error", "--filename is a name, not a path: no / or \\, at most 255 characters");
|
|
205
207
|
};
|
|
208
|
+
const listOf = (value) => value === undefined ? [] : typeof value === "string" ? [value] : value;
|
|
206
209
|
/**
|
|
207
|
-
* What a send attaches, as `messages send` and its tool take it.
|
|
208
|
-
* messengers' own apps send one.
|
|
210
|
+
* What a send attaches, as `messages send` and its tool take it. More than one photo or file is an album: photos
|
|
211
|
+
* first, then files, each in the order given. A voice message goes alone, as the messengers' own apps send one.
|
|
209
212
|
*/
|
|
210
213
|
export const readAttachments = async ({ photo, file, voice, asFile, filename, text, }, read) => {
|
|
211
|
-
|
|
214
|
+
const photos = listOf(photo);
|
|
215
|
+
const files = listOf(file);
|
|
216
|
+
if (voice !== undefined && (photos.length > 0 || files.length > 0 || (text ?? "").trim() !== "")) {
|
|
212
217
|
throw new CliError("validation_error", "a voice message goes alone — no text, no file, no photo beside it");
|
|
213
218
|
}
|
|
214
|
-
if (asFile &&
|
|
219
|
+
if (asFile && files.length === 0)
|
|
215
220
|
throw new CliError("validation_error", "--as-file is about a --file; give one");
|
|
216
221
|
if (filename !== undefined)
|
|
217
|
-
checkFilename(filename,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
];
|
|
222
|
+
checkFilename(filename, files);
|
|
223
|
+
const uploads = [];
|
|
224
|
+
for (const one of photos)
|
|
225
|
+
uploads.push(await readUpload("photo", one, read));
|
|
226
|
+
for (const one of files)
|
|
227
|
+
uploads.push({
|
|
228
|
+
...(await readUpload("file", one, read)),
|
|
229
|
+
...(filename === undefined ? {} : { name: filename }),
|
|
230
|
+
...(asFile ? { asFile: true } : {}),
|
|
231
|
+
});
|
|
232
|
+
if (voice !== undefined)
|
|
233
|
+
uploads.push(await readUpload("voice", voice, read));
|
|
234
|
+
return uploads;
|
|
231
235
|
};
|
|
232
236
|
/** A sticker to send from a file: a WebP image of at most `maxBytes`, behind the same file guard as `--file`. */
|
|
233
237
|
export const readStickerFile = async (path, maxBytes, read) => {
|