@wirecat/cli-core 0.420.0 → 0.422.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 +23 -0
- package/dist/cli/bot/context.d.ts +3 -2
- package/dist/cli/bot/context.js +15 -0
- package/dist/cli/commands-command.js +1 -1
- package/dist/cli/config-command.d.ts +0 -4
- package/dist/cli/config-command.js +5 -4
- package/dist/cli/first-run.d.ts +0 -16
- package/dist/cli/first-run.js +1 -6
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +2 -2
- package/dist/cli/messenger/context.js +3 -6
- package/dist/cli/permission-keys.d.ts +2 -0
- package/dist/cli/permission-keys.js +10 -3
- package/dist/cli/preview.d.ts +2 -2
- package/dist/cli/preview.js +4 -3
- package/dist/cli/profile.d.ts +2 -0
- package/dist/cli/profile.js +10 -0
- package/dist/cli/program.js +14 -14
- package/dist/cli/settings.d.ts +0 -2
- package/dist/cli/settings.js +1 -5
- package/dist/mcp/surface.js +9 -7
- package/dist/mcp/tool.d.ts +1 -3
- package/dist/mcp/tool.js +1 -2
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/parity/manifest.js +0 -4
- package/dist/ports/app.d.ts +6 -0
- package/dist/search/dictionary.d.ts +3 -6
- package/dist/search/dictionary.js +2 -1
- package/dist/search/languages.d.ts +28 -0
- package/dist/search/languages.js +7 -0
- package/dist/search/stem.d.ts +5 -4
- package/dist/search/stem.js +3 -2
- package/dist/sends/index.d.ts +1 -1
- package/dist/sends/index.js +1 -1
- package/dist/sends/permissions.d.ts +0 -13
- package/dist/sends/permissions.js +15 -142
- package/package.json +1 -1
- package/dist/cli/moved.d.ts +0 -18
- package/dist/cli/moved.js +0 -72
- package/dist/sends/permission-aliases.d.ts +0 -5
- package/dist/sends/permission-aliases.js +0 -50
|
@@ -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;
|
package/dist/parity/manifest.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { MOVED_TO_MEMO } from "../cli/moved.js";
|
|
2
1
|
export const longName = (flags) => flags.split(/[\s,|]+/).find((part) => part.startsWith("--")) ?? flags;
|
|
3
2
|
const presence = (entry) => (typeof entry === "string" ? { in: entry } : entry);
|
|
4
3
|
/** `true` the CLI must have it, `false` it must not, `undefined` it is planned and either passes. */
|
|
@@ -93,9 +92,6 @@ export const manifestProblems = (manifest) => {
|
|
|
93
92
|
}
|
|
94
93
|
optionEntries(path, row.options ?? {});
|
|
95
94
|
}
|
|
96
|
-
for (const path of Object.keys(manifest.commands))
|
|
97
|
-
if (Object.keys(MOVED_TO_MEMO).some((moved) => path === moved || path.startsWith(`${moved} `)))
|
|
98
|
-
problems.push(`${path}: moved to memo (src/cli/moved.ts), so a source tool has no row for it`);
|
|
99
95
|
for (const [name, option] of Object.entries(manifest.options)) {
|
|
100
96
|
if (!option.meaning)
|
|
101
97
|
problems.push(`options ${name}: no meaning`);
|
package/dist/ports/app.d.ts
CHANGED
|
@@ -19,6 +19,12 @@ export interface AppIdentity {
|
|
|
19
19
|
* can stem in place of English and the system's language; the person still confirms, since the choice is shared.
|
|
20
20
|
*/
|
|
21
21
|
contentLanguages?: readonly string[];
|
|
22
|
+
/**
|
|
23
|
+
* `false` for a CLI that is not a messenger — notes, files, a calendar. The shared help, `config`, `commands info`
|
|
24
|
+
* and MCP text then name its own commands and leave out chats, messages and senders. Unset means a messenger, so
|
|
25
|
+
* tg, max and wa print what they always did.
|
|
26
|
+
*/
|
|
27
|
+
messenger?: boolean;
|
|
22
28
|
/** Error reasons and detail keys of its own — max's `max_code`, tg's `migrated_chat_id` — kept in every error it prints. */
|
|
23
29
|
errors?: AppErrors;
|
|
24
30
|
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
+
import { type Language } from "./languages.js";
|
|
1
2
|
/** The Hunspell dictionary that tells a real word from a garbled one, for each language the store stems. */
|
|
2
|
-
export declare const DICTIONARIES:
|
|
3
|
-
|
|
4
|
-
readonly english: "dictionary-en";
|
|
5
|
-
readonly spanish: "dictionary-es";
|
|
6
|
-
};
|
|
7
|
-
export type DictionaryLanguage = keyof typeof DICTIONARIES;
|
|
3
|
+
export declare const DICTIONARIES: Record<Language, string>;
|
|
4
|
+
export type DictionaryLanguage = Language;
|
|
8
5
|
/**
|
|
9
6
|
* `known`: whether a folded word the index stores is real in one of `languages` that write in its alphabet.
|
|
10
7
|
* `covers`: whether any of them does; without one, a garbled spelling cannot be told from a real word.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { fold } from "../store/normalize.js";
|
|
3
|
+
import { LANGUAGES } from "./languages.js";
|
|
3
4
|
/** The Hunspell dictionary that tells a real word from a garbled one, for each language the store stems. */
|
|
4
|
-
export const DICTIONARIES = {
|
|
5
|
+
export const DICTIONARIES = Object.fromEntries(LANGUAGES.map(({ name, dictionary }) => [name, dictionary]));
|
|
5
6
|
/** `fold`, without Unicode normalisation for plain Russian letters: it would double the time Russian loads in. */
|
|
6
7
|
const folded = (word) => /^[а-яё'-]*$/.test(word) ? word.replaceAll("ё", "е").replaceAll("й", "и") : fold(word);
|
|
7
8
|
const loaded = new Map();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** The languages search can stem and spell-check. Adding one here needs its Snowball stemmer in `stem.ts`. */
|
|
2
|
+
export declare const LANGUAGES: readonly [{
|
|
3
|
+
readonly name: "russian";
|
|
4
|
+
readonly code: "ru";
|
|
5
|
+
readonly label: "Russian";
|
|
6
|
+
readonly script: "cyrillic";
|
|
7
|
+
readonly dictionary: "dictionary-ru";
|
|
8
|
+
}, {
|
|
9
|
+
readonly name: "english";
|
|
10
|
+
readonly code: "en";
|
|
11
|
+
readonly label: "English";
|
|
12
|
+
readonly script: "latin";
|
|
13
|
+
readonly dictionary: "dictionary-en";
|
|
14
|
+
}, {
|
|
15
|
+
readonly name: "spanish";
|
|
16
|
+
readonly code: "es";
|
|
17
|
+
readonly label: "Spanish";
|
|
18
|
+
readonly script: "latin";
|
|
19
|
+
readonly dictionary: "dictionary-es";
|
|
20
|
+
}];
|
|
21
|
+
type Entry = (typeof LANGUAGES)[number];
|
|
22
|
+
export type Language = Entry["name"];
|
|
23
|
+
export type Script = Entry["script"];
|
|
24
|
+
export type LanguageOf<S extends Script> = Extract<Entry, {
|
|
25
|
+
script: S;
|
|
26
|
+
}>["name"];
|
|
27
|
+
export declare const languagesOf: <S extends Script>(script: S) => LanguageOf<S>[];
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** The languages search can stem and spell-check. Adding one here needs its Snowball stemmer in `stem.ts`. */
|
|
2
|
+
export const LANGUAGES = [
|
|
3
|
+
{ name: "russian", code: "ru", label: "Russian", script: "cyrillic", dictionary: "dictionary-ru" },
|
|
4
|
+
{ name: "english", code: "en", label: "English", script: "latin", dictionary: "dictionary-en" },
|
|
5
|
+
{ name: "spanish", code: "es", label: "Spanish", script: "latin", dictionary: "dictionary-es" },
|
|
6
|
+
];
|
|
7
|
+
export const languagesOf = (script) => LANGUAGES.filter((one) => one.script === script).map(({ name }) => name);
|
package/dist/search/stem.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { type LanguageOf } from "./languages.js";
|
|
1
2
|
/** The version of the vendored files in `./snowball/`; `bin/snowball-update` regenerates them. */
|
|
2
3
|
export declare const SNOWBALL_VERSION = "3.1.1";
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
4
|
+
export type CyrillicStemmer = LanguageOf<"cyrillic"> | "none";
|
|
5
|
+
export type LatinStemmer = LanguageOf<"latin"> | "none";
|
|
6
|
+
export declare const CYRILLIC_STEMMERS: readonly CyrillicStemmer[];
|
|
7
|
+
export declare const LATIN_STEMMERS: readonly LatinStemmer[];
|
|
7
8
|
/** `latin` is one stemmer, `none`, or several joined by commas in alphabetical order (`english,spanish`). */
|
|
8
9
|
export type Stemmers = {
|
|
9
10
|
cyrillic: CyrillicStemmer;
|
package/dist/search/stem.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { normalize } from "../store/normalize.js";
|
|
3
|
+
import { languagesOf } from "./languages.js";
|
|
3
4
|
import EnglishStemmer from "./snowball/english-stemmer.js";
|
|
4
5
|
import RussianStemmer from "./snowball/russian-stemmer.js";
|
|
5
6
|
import SpanishStemmer from "./snowball/spanish-stemmer.js";
|
|
6
7
|
/** The version of the vendored files in `./snowball/`; `bin/snowball-update` regenerates them. */
|
|
7
8
|
export const SNOWBALL_VERSION = "3.1.1";
|
|
8
|
-
export const CYRILLIC_STEMMERS = ["
|
|
9
|
-
export const LATIN_STEMMERS = ["
|
|
9
|
+
export const CYRILLIC_STEMMERS = [...languagesOf("cyrillic"), "none"];
|
|
10
|
+
export const LATIN_STEMMERS = [...languagesOf("latin"), "none"];
|
|
10
11
|
export const DEFAULT_STEMMERS = { cyrillic: "russian", latin: "english,spanish" };
|
|
11
12
|
/**
|
|
12
13
|
* Raised with every change to `DEFAULT_STEMMERS`. A default saved in the store carries it, so a newer build
|
package/dist/sends/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { type FloodDeadline, FloodMemory, type FloodState, FROZEN_HOLD_MS, flood
|
|
|
3
3
|
export { type Asker, type GuardRequest, guardFor, type SendGuard, type SendGuardOptions, sendGuard, sharedJournal, } from "./guard.js";
|
|
4
4
|
export { currentOperation, guardedWrite, type Operated } from "./guarded.js";
|
|
5
5
|
export { type AccountAction, type ChatAction, type SendEntry, SendJournal, type SendKind, type SendOutcome, sendsPathFor, } from "./journal.js";
|
|
6
|
-
export {
|
|
6
|
+
export { CEILING_KEY, DEFAULT_PERMISSIONS, keyForCommand, keyForWrite, LEVELS, type Level, layerPermissions, levelFor, type PermissionKey, permissionOverrides, readKeysForCommand, skipFlagFor, WRITE_KEYS, } from "./permissions.js";
|
|
7
7
|
export { type Recipient, RecipientList, recipientsPathFor } from "./recipients.js";
|
|
8
8
|
export { newOperationId, newSendId } from "./send-id.js";
|
|
9
9
|
export { MAX_BUFFERED_UPLOAD_BYTES, readUpload, refusedPlace, uploadBlob, uploadBytes, uploadSize, } from "./upload.js";
|
package/dist/sends/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { FloodMemory, FROZEN_HOLD_MS, floodPathFor, LIMITED_HOLD_MS, } from "./f
|
|
|
2
2
|
export { guardFor, sendGuard, sharedJournal, } from "./guard.js";
|
|
3
3
|
export { currentOperation, guardedWrite } from "./guarded.js";
|
|
4
4
|
export { SendJournal, sendsPathFor, } from "./journal.js";
|
|
5
|
-
export {
|
|
5
|
+
export { CEILING_KEY, DEFAULT_PERMISSIONS, keyForCommand, keyForWrite, LEVELS, layerPermissions, levelFor, permissionOverrides, readKeysForCommand, skipFlagFor, WRITE_KEYS, } from "./permissions.js";
|
|
6
6
|
export { RecipientList, recipientsPathFor } from "./recipients.js";
|
|
7
7
|
export { newOperationId, newSendId } from "./send-id.js";
|
|
8
8
|
export { MAX_BUFFERED_UPLOAD_BYTES, readUpload, refusedPlace, uploadBlob, uploadBytes, uploadSize, } from "./upload.js";
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
import type { AccountAction, ChatAction, SendKind } from "./journal.js";
|
|
2
|
-
/**
|
|
3
|
-
* What a profile may be allowed to do (max-cli `CLI-37`, `NEED-251`). One name per thing the owner would
|
|
4
|
-
* recognise, so `sessions` — which logs the owner out of their phone — never rides along with
|
|
5
|
-
* adding a contact. There is no `*`: "everything" is not setting `allow` at all, and a wildcard
|
|
6
|
-
* would switch on `delete` without anyone naming it.
|
|
7
|
-
*/
|
|
8
|
-
export declare const PERMISSIONS: readonly ["send", "forward", "reaction", "edit", "pin", "read", "delete", "groups", "contacts", "profile", "folders", "sessions"];
|
|
9
|
-
export type Permission = (typeof PERMISSIONS)[number];
|
|
10
|
-
export declare const permissionFor: (kind: SendKind, action?: string) => Permission;
|
|
11
2
|
/** What a profile may do with one command path — the standard's "Permissions". */
|
|
12
3
|
export declare const LEVELS: readonly ["deny", "readonly", "ask", "allow"];
|
|
13
4
|
export type Level = (typeof LEVELS)[number];
|
|
@@ -32,8 +23,6 @@ export declare const isBotApi: (path: readonly string[]) => boolean;
|
|
|
32
23
|
export declare const botApiAllowed: (levels: Readonly<Record<PermissionKey, Level>>) => boolean;
|
|
33
24
|
/** The resources at the top of the command tree, which `readOnly` and `allow` turn read-only as a whole. */
|
|
34
25
|
export declare const RESOURCES: readonly ["messages", "drafts", "reactions", "polls", "topics", "chats", "contacts", "account", "bot", "conversations", "tags", "mentions", "search", "searches", "tasks", "replies", "attachments", "stats", "store", "metadata", "mcp", "server", "models", "schedule", "doctor", "images"];
|
|
35
|
-
/** The command path an old `allow` word stood for, as one key — what an MCP tool's `permission` is checked against. */
|
|
36
|
-
export declare const keyOfWord: (word: Permission) => PermissionKey;
|
|
37
26
|
/**
|
|
38
27
|
* Layers come nearest first. A key a nearer layer sets hides that key and every key under it in the
|
|
39
28
|
* farther layers, so a profile's `messages: readonly` is not undone by `messages.delete: allow` in
|
|
@@ -67,8 +56,6 @@ export declare const writeOfKey: (key: PermissionKey) => {
|
|
|
67
56
|
};
|
|
68
57
|
/** Every key a write is checked against that a command path may not spell out. */
|
|
69
58
|
export declare const WRITE_KEYS: readonly PermissionKey[];
|
|
70
|
-
/** Statistics and searches moved; a deny under an old path would stop applying unseen, so the command refuses. */
|
|
71
|
-
export declare const assertStatsPermissionsCurrent: (path: readonly string[], permissions: Readonly<Record<string, Level>>) => void;
|
|
72
59
|
export declare const readKeysForCommand: (path: readonly string[]) => PermissionKey[];
|
|
73
60
|
/**
|
|
74
61
|
* The key a command path is checked against: `null` for housekeeping, which no level stops, and
|
|
@@ -1,66 +1,4 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
-
import { canonicalPermissionKey, canonicalPermissions } from "./permission-aliases.js";
|
|
3
|
-
/**
|
|
4
|
-
* What a profile may be allowed to do (max-cli `CLI-37`, `NEED-251`). One name per thing the owner would
|
|
5
|
-
* recognise, so `sessions` — which logs the owner out of their phone — never rides along with
|
|
6
|
-
* adding a contact. There is no `*`: "everything" is not setting `allow` at all, and a wildcard
|
|
7
|
-
* would switch on `delete` without anyone naming it.
|
|
8
|
-
*/
|
|
9
|
-
export const PERMISSIONS = [
|
|
10
|
-
"send",
|
|
11
|
-
"forward",
|
|
12
|
-
"reaction",
|
|
13
|
-
"edit",
|
|
14
|
-
"pin",
|
|
15
|
-
"read",
|
|
16
|
-
"delete",
|
|
17
|
-
"groups",
|
|
18
|
-
"contacts",
|
|
19
|
-
"profile",
|
|
20
|
-
"folders",
|
|
21
|
-
"sessions",
|
|
22
|
-
];
|
|
23
|
-
const ACCOUNT = {
|
|
24
|
-
"contact-add": "contacts",
|
|
25
|
-
"contact-remove": "contacts",
|
|
26
|
-
"contact-import": "contacts",
|
|
27
|
-
"contact-rename": "contacts",
|
|
28
|
-
"contact-block": "contacts",
|
|
29
|
-
"contact-unblock": "contacts",
|
|
30
|
-
profile: "profile",
|
|
31
|
-
"folder-create": "folders",
|
|
32
|
-
"folder-update": "folders",
|
|
33
|
-
"folder-delete": "folders",
|
|
34
|
-
"folder-order": "folders",
|
|
35
|
-
"folder-join": "folders",
|
|
36
|
-
"sessions-end": "sessions",
|
|
37
|
-
"chat-mute": "profile",
|
|
38
|
-
"chat-unmute": "profile",
|
|
39
|
-
"chat-archive": "profile",
|
|
40
|
-
"chat-unarchive": "profile",
|
|
41
|
-
"chat-pin": "profile",
|
|
42
|
-
"chat-unpin": "profile",
|
|
43
|
-
"chat-mark-unread": "profile",
|
|
44
|
-
"message-star": "profile",
|
|
45
|
-
"message-unstar": "profile",
|
|
46
|
-
privacy: "profile",
|
|
47
|
-
};
|
|
48
|
-
export const permissionFor = (kind, action) => {
|
|
49
|
-
switch (kind) {
|
|
50
|
-
case "message":
|
|
51
|
-
return "send";
|
|
52
|
-
case "chat":
|
|
53
|
-
return "groups";
|
|
54
|
-
case "account": {
|
|
55
|
-
const permission = ACCOUNT[action];
|
|
56
|
-
if (!permission)
|
|
57
|
-
throw new Error(`an account change without a known action: ${action}`);
|
|
58
|
-
return permission;
|
|
59
|
-
}
|
|
60
|
-
default:
|
|
61
|
-
return kind;
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
2
|
/** What a profile may do with one command path — the standard's "Permissions". */
|
|
65
3
|
export const LEVELS = ["deny", "readonly", "ask", "allow"];
|
|
66
4
|
/** The deletions nobody gets back: the word that skips their question is `--allow-dangerous`, never `--yes`. */
|
|
@@ -148,47 +86,6 @@ export const RESOURCES = [
|
|
|
148
86
|
"doctor",
|
|
149
87
|
"images",
|
|
150
88
|
];
|
|
151
|
-
const OLD_WORDS = {
|
|
152
|
-
send: ["messages.send", "polls.create", "chats.start"],
|
|
153
|
-
forward: ["messages.forward"],
|
|
154
|
-
reaction: ["reactions", "polls.vote", "messages.press", "chats.app"],
|
|
155
|
-
edit: ["messages.edit", "polls.close"],
|
|
156
|
-
pin: ["messages.pin", "messages.unpin"],
|
|
157
|
-
read: ["chats.mark-read"],
|
|
158
|
-
delete: ["messages.delete", "chats.delete", "chats.clear"],
|
|
159
|
-
groups: [
|
|
160
|
-
"chats.create",
|
|
161
|
-
"chats.update",
|
|
162
|
-
"chats.join",
|
|
163
|
-
"chats.leave",
|
|
164
|
-
"chats.members",
|
|
165
|
-
"chats.admins",
|
|
166
|
-
"chats.links",
|
|
167
|
-
"chats.requests",
|
|
168
|
-
"chats.moderate",
|
|
169
|
-
"topics.enable",
|
|
170
|
-
"topics.create",
|
|
171
|
-
"topics.edit",
|
|
172
|
-
"topics.delete",
|
|
173
|
-
],
|
|
174
|
-
contacts: ["contacts"],
|
|
175
|
-
profile: ["account.update", "account.privacy.set", "chats.mute", "messages.star", "messages.unstar"],
|
|
176
|
-
folders: ["chats.folders"],
|
|
177
|
-
sessions: ["account.sessions.end"],
|
|
178
|
-
};
|
|
179
|
-
/** The command path an old `allow` word stood for, as one key — what an MCP tool's `permission` is checked against. */
|
|
180
|
-
export const keyOfWord = (word) => OLD_WORDS[word][0];
|
|
181
|
-
/**
|
|
182
|
-
* A bot's old words meant its own writes: `profile` was its command menu, and `read` was taking
|
|
183
|
-
* updates, which every level but `deny` allows now.
|
|
184
|
-
*/
|
|
185
|
-
const botKeysOf = (word) => {
|
|
186
|
-
if (word === "profile")
|
|
187
|
-
return ["bot.commands"];
|
|
188
|
-
if (word === "read")
|
|
189
|
-
return [];
|
|
190
|
-
return OLD_WORDS[word].map((key) => `bot.${key}`);
|
|
191
|
-
};
|
|
192
89
|
/**
|
|
193
90
|
* Layers come nearest first. A key a nearer layer sets hides that key and every key under it in the
|
|
194
91
|
* farther layers, so a profile's `messages: readonly` is not undone by `messages.delete: allow` in
|
|
@@ -199,7 +96,7 @@ export const layerPermissions = (layers) => {
|
|
|
199
96
|
const sources = {};
|
|
200
97
|
for (const [from, layer] of layers) {
|
|
201
98
|
const nearer = Object.keys(levels);
|
|
202
|
-
for (const [key, level] of Object.entries(
|
|
99
|
+
for (const [key, level] of Object.entries(layer ?? {})) {
|
|
203
100
|
if (nearer.some((named) => key === named || key.startsWith(`${named}.`)))
|
|
204
101
|
continue;
|
|
205
102
|
levels[key] = level;
|
|
@@ -224,11 +121,6 @@ const nearest = (levels, key) => {
|
|
|
224
121
|
}
|
|
225
122
|
return undefined;
|
|
226
123
|
};
|
|
227
|
-
/**
|
|
228
|
-
* `chats unmute` was checked as `chats.mute` before it had its own key; a config that names only the old key
|
|
229
|
-
* keeps meaning it, so a `chats.mute: readonly` written then still stops an unmute.
|
|
230
|
-
*/
|
|
231
|
-
const FORMER_KEYS = { "chats.unmute": "chats.mute" };
|
|
232
124
|
/**
|
|
233
125
|
* The most specific key the owner set wins. A built-in default (`DEFAULT_PERMISSIONS`) only ever
|
|
234
126
|
* tightens: against a broader key of the owner's, the stricter of the two holds — so
|
|
@@ -236,16 +128,11 @@ const FORMER_KEYS = { "chats.unmute": "chats.mute" };
|
|
|
236
128
|
* A path nothing names is allowed.
|
|
237
129
|
*/
|
|
238
130
|
export const levelFor = (permissions, key, defaults = DEFAULT_PERMISSIONS) => {
|
|
239
|
-
permissions = canonicalPermissions(permissions);
|
|
240
|
-
key = canonicalPermissionKey(key);
|
|
241
131
|
const found = profileLevel(permissions, key, defaults);
|
|
242
132
|
const ceiling = permissions[CEILING_KEY];
|
|
243
133
|
return ceiling && ORDER[ceiling] < ORDER[found.level] ? { level: ceiling, key: CEILING_KEY } : found;
|
|
244
134
|
};
|
|
245
135
|
const profileLevel = (permissions, key, defaults) => {
|
|
246
|
-
const former = FORMER_KEYS[key];
|
|
247
|
-
if (former !== undefined && permissions[key] === undefined)
|
|
248
|
-
return profileLevel(permissions, former, defaults);
|
|
249
136
|
const own = nearest(permissions, key);
|
|
250
137
|
const builtIn = nearest(defaults, key);
|
|
251
138
|
if (!builtIn || (own && own.depth >= builtIn.depth))
|
|
@@ -254,7 +141,15 @@ const profileLevel = (permissions, key, defaults) => {
|
|
|
254
141
|
return { level: builtIn.level, key: builtIn.key };
|
|
255
142
|
return { level: own.level, key: own.key };
|
|
256
143
|
};
|
|
257
|
-
const CHAT_KEYS = {
|
|
144
|
+
const CHAT_KEYS = {
|
|
145
|
+
settings: "chats.update",
|
|
146
|
+
"requests.accept": "chats.requests.confirm",
|
|
147
|
+
"requests.decline": "chats.requests.reject",
|
|
148
|
+
"link.create": "chats.links.create",
|
|
149
|
+
"link.reset": "chats.links.reset",
|
|
150
|
+
"link.revoke": "chats.links.remove",
|
|
151
|
+
"link.update": "chats.links.update",
|
|
152
|
+
};
|
|
258
153
|
const ACCOUNT_KEYS = {
|
|
259
154
|
"contact-add": "contacts.add",
|
|
260
155
|
"contact-remove": "contacts.remove",
|
|
@@ -292,7 +187,7 @@ const KIND_KEYS = {
|
|
|
292
187
|
/** The command path a guarded write belongs to, when the caller did not name it. */
|
|
293
188
|
export const keyForWrite = (kind, action) => {
|
|
294
189
|
if (kind === "chat")
|
|
295
|
-
return
|
|
190
|
+
return CHAT_KEYS[action] ?? `chats.${action ?? "update"}`;
|
|
296
191
|
if (kind === "account") {
|
|
297
192
|
const key = ACCOUNT_KEYS[action];
|
|
298
193
|
if (!key)
|
|
@@ -303,7 +198,6 @@ export const keyForWrite = (kind, action) => {
|
|
|
303
198
|
};
|
|
304
199
|
/** `keyForWrite` read backwards, as far as a command's key says it: an unpin is a `pin`, a chat change names its action. */
|
|
305
200
|
export const writeOfKey = (key) => {
|
|
306
|
-
key = canonicalPermissionKey(key);
|
|
307
201
|
const account = Object.entries(ACCOUNT_KEYS).find(([, named]) => named === key)?.[0];
|
|
308
202
|
if (account)
|
|
309
203
|
return { kind: "account", action: account };
|
|
@@ -399,8 +293,7 @@ export const WRITE_KEYS = [
|
|
|
399
293
|
...Object.values(KIND_KEYS),
|
|
400
294
|
...Object.values(ACCOUNT_KEYS),
|
|
401
295
|
...Object.values(CHAT_KEYS),
|
|
402
|
-
|
|
403
|
-
].map(canonicalPermissionKey)),
|
|
296
|
+
]),
|
|
404
297
|
];
|
|
405
298
|
/** Commands that look after the tool and its files and never show a message: no level stops them. */
|
|
406
299
|
const HOUSEKEEPING = new Set([
|
|
@@ -430,21 +323,6 @@ const BOT_HOUSEKEEPING = new Set(["auth", "list", "sends", "recipients", "mcp"])
|
|
|
430
323
|
const STORE_MAINTENANCE = new Set(["info", "check", "copies"]);
|
|
431
324
|
/** Commands outside `messages` that print what people wrote, so `deny messages` reaches them too. */
|
|
432
325
|
const SHOW_MESSAGES = new Set(["inbox", "review", "watch", "serve", "store", "conversations"]);
|
|
433
|
-
/** Before every search moved under `search` (2026-10-09). */
|
|
434
|
-
const OLD_SEARCH_KEY = /^(bot\.)?(messages|conversations|topics)\.search(?:\.|$)/;
|
|
435
|
-
/** Statistics and searches moved; a deny under an old path would stop applying unseen, so the command refuses. */
|
|
436
|
-
export const assertStatsPermissionsCurrent = (path, permissions) => {
|
|
437
|
-
if (path[0] === "bot") {
|
|
438
|
-
assertStatsPermissionsCurrent(path.slice(1), permissions);
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
if (path[0] === "search" && Object.keys(permissions).some((key) => OLD_SEARCH_KEY.test(key)))
|
|
442
|
-
throw new CliError("configuration_error", "search permission paths have moved — rename messages.search, conversations.search and topics.search to search.messages, search.conversations and search.topics", { retryable: false });
|
|
443
|
-
if (path[0] !== "stats")
|
|
444
|
-
return;
|
|
445
|
-
if (Object.keys(permissions).some((key) => /^(bot\.)?(messages|chats|tasks)\.stats(?:\.|$)/.test(key)))
|
|
446
|
-
throw new CliError("configuration_error", "statistics permission paths have moved — rename messages.stats, chats.stats and tasks.stats to stats.messages.show, stats.chats.show and stats.tasks.show", { retryable: false });
|
|
447
|
-
};
|
|
448
326
|
export const readKeysForCommand = (path) => {
|
|
449
327
|
if (path[0] === "bot")
|
|
450
328
|
return readKeysForCommand(path.slice(1)).map((key) => `bot.${key}`);
|
|
@@ -488,7 +366,6 @@ export const readKeysForCommand = (path) => {
|
|
|
488
366
|
export const keyForCommand = (path) => {
|
|
489
367
|
if (path.length === 0)
|
|
490
368
|
return null;
|
|
491
|
-
path = canonicalPermissionKey(path.join(".")).split(".");
|
|
492
369
|
const [top, next] = path;
|
|
493
370
|
if (top === "bot") {
|
|
494
371
|
if (next === undefined || BOT_HOUSEKEEPING.has(next))
|
|
@@ -500,7 +377,7 @@ export const keyForCommand = (path) => {
|
|
|
500
377
|
return path.join(".");
|
|
501
378
|
if (top === undefined || HOUSEKEEPING.has(top) || (top === "search" && next === "runs"))
|
|
502
379
|
return null;
|
|
503
|
-
//
|
|
380
|
+
// A model is housekeeping wherever it is listed.
|
|
504
381
|
if (top === "images" && next === "models")
|
|
505
382
|
return null;
|
|
506
383
|
if (top === "attachments" && (next === "check" || next === "jobs"))
|
|
@@ -513,7 +390,7 @@ export const keyForCommand = (path) => {
|
|
|
513
390
|
if (top === "conversations" && next === "links")
|
|
514
391
|
return path[2] === "rebuild" ? "conversations.links.rebuild" : "conversations.links";
|
|
515
392
|
// Vectors are written to the local store only, and the text never leaves the machine (phase 5 E10).
|
|
516
|
-
if ((top === "conversations" && next === "embeddings") ||
|
|
393
|
+
if ((top === "conversations" && (next === "embeddings" || next === "embed")) ||
|
|
517
394
|
(top === "images" && next === "embed") ||
|
|
518
395
|
(top === "conversations" && next === "rebuild"))
|
|
519
396
|
return "conversations.embeddings";
|
|
@@ -526,8 +403,6 @@ export const keyForCommand = (path) => {
|
|
|
526
403
|
return next === "rebuild" || next === "confirm" || next === "reject" ? `mentions.${next}` : "messages";
|
|
527
404
|
if (top === "store" && (next === "gaps" || next === "jobs"))
|
|
528
405
|
return path.join(".");
|
|
529
|
-
if (top === "messages" && next === "sync")
|
|
530
|
-
return "messages";
|
|
531
406
|
if (SHOW_MESSAGES.has(top) || (top === "contacts" && (next === "context" || next === "prepare" || next === "events")))
|
|
532
407
|
return "messages";
|
|
533
408
|
if (RESOURCES.includes(top))
|
|
@@ -544,12 +419,10 @@ export const permissionOverrides = (entries = []) => {
|
|
|
544
419
|
throw new CliError("validation_error", "--permission takes a command path and level, e.g. messages.send=allow (deny, readonly, ask, allow)");
|
|
545
420
|
levels[key] = level;
|
|
546
421
|
}
|
|
547
|
-
return
|
|
422
|
+
return levels;
|
|
548
423
|
};
|
|
549
424
|
/** No startup flag grants access: an override may narrow the profile's level, never lift it or a key under it. */
|
|
550
425
|
export const assertOverridesTighten = (overrides, profile) => {
|
|
551
|
-
overrides = canonicalPermissions(overrides);
|
|
552
|
-
profile = canonicalPermissions(profile);
|
|
553
426
|
for (const [key, level] of Object.entries(overrides)) {
|
|
554
427
|
const under = Object.keys(profile).filter((named) => named.startsWith(`${key}.`));
|
|
555
428
|
const lifted = [key, ...under].find((named) => ORDER[level] > ORDER[levelFor(profile, named).level]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.422.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|