@wirecat/zoom-cli 0.56.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/zm.js +35 -5
  3. package/dist/bundle/all-profiles-EKHWTGOY.js +25 -0
  4. package/dist/bundle/answer-policy-KH2IFXPN.js +96 -0
  5. package/dist/bundle/anthropic-VL54IUGF.js +70 -0
  6. package/dist/bundle/bun-ADJVI4HY.js +169 -0
  7. package/dist/bundle/bun-sqlite-2SDA3YOT.js +69 -0
  8. package/dist/bundle/chunk-2IKY7VBH.js +9 -0
  9. package/dist/bundle/chunk-2WOGZDZG.js +431 -0
  10. package/dist/bundle/chunk-4R7HYNZS.js +73 -0
  11. package/dist/bundle/chunk-4VCA7QU3.js +538 -0
  12. package/dist/bundle/chunk-4YLS4YLS.js +29 -0
  13. package/dist/bundle/chunk-5D4AEUVA.js +24783 -0
  14. package/dist/bundle/chunk-5F7IOESV.js +281 -0
  15. package/dist/bundle/chunk-5QESMCQK.js +122 -0
  16. package/dist/bundle/chunk-6M6FJV5D.js +138 -0
  17. package/dist/bundle/chunk-6MX5T6AQ.js +17 -0
  18. package/dist/bundle/chunk-6P3YHT54.js +385 -0
  19. package/dist/bundle/chunk-7A6PZUDR.js +938 -0
  20. package/dist/bundle/chunk-7GUG6UZS.js +884 -0
  21. package/dist/bundle/chunk-7JZJMHQY.js +487 -0
  22. package/dist/bundle/chunk-A4ETXPMZ.js +98 -0
  23. package/dist/bundle/chunk-ADLS6FVG.js +24600 -0
  24. package/dist/bundle/chunk-AE4BOXH7.js +5292 -0
  25. package/dist/bundle/chunk-AN3GVZTU.js +76 -0
  26. package/dist/bundle/chunk-BAYNCCNM.js +215 -0
  27. package/dist/bundle/chunk-BW2WPOXP.js +166 -0
  28. package/dist/bundle/chunk-BW52AJ4R.js +3387 -0
  29. package/dist/bundle/chunk-CB5UTASG.js +18 -0
  30. package/dist/bundle/chunk-CLMXBW53.js +1028 -0
  31. package/dist/bundle/chunk-DCTXOMOR.js +35 -0
  32. package/dist/bundle/chunk-DFDN6AUB.js +21 -0
  33. package/dist/bundle/chunk-EOQFWJJD.js +1095 -0
  34. package/dist/bundle/chunk-G4R4ZIGD.js +3569 -0
  35. package/dist/bundle/chunk-G4VZDINN.js +161 -0
  36. package/dist/bundle/chunk-GARCOIB6.js +409 -0
  37. package/dist/bundle/chunk-GKMGS7G5.js +1938 -0
  38. package/dist/bundle/chunk-HCNMFFG3.js +82 -0
  39. package/dist/bundle/chunk-HG5KRV55.js +1970 -0
  40. package/dist/bundle/chunk-HKYLSIZ2.js +82 -0
  41. package/dist/bundle/chunk-HTQTIKWP.js +928 -0
  42. package/dist/bundle/chunk-I4VQDXFR.js +100 -0
  43. package/dist/bundle/chunk-ICSKEDPI.js +36139 -0
  44. package/dist/bundle/chunk-IP4OK7RQ.js +24 -0
  45. package/dist/bundle/chunk-IVQ6342B.js +2 -0
  46. package/dist/bundle/chunk-K72SEMYI.js +160 -0
  47. package/dist/bundle/chunk-LHRCJYFC.js +19 -0
  48. package/dist/bundle/chunk-LPTIQLDW.js +192 -0
  49. package/dist/bundle/chunk-LQ3T2ZIX.js +124 -0
  50. package/dist/bundle/chunk-LSVI3A5X.js +836 -0
  51. package/dist/bundle/chunk-LW2G4U6W.js +570 -0
  52. package/dist/bundle/chunk-LXB6ONWC.js +3517 -0
  53. package/dist/bundle/chunk-LY26NW3I.js +6848 -0
  54. package/dist/bundle/chunk-MAEFLDHG.js +6092 -0
  55. package/dist/bundle/chunk-MZH4GDR4.js +45 -0
  56. package/dist/bundle/chunk-NN5B24KP.js +1109 -0
  57. package/dist/bundle/chunk-NO7CJEYV.js +22 -0
  58. package/dist/bundle/chunk-NTDWAAGL.js +18435 -0
  59. package/dist/bundle/chunk-NW3X3G5X.js +264 -0
  60. package/dist/bundle/chunk-O66V2WHK.js +48 -0
  61. package/dist/bundle/chunk-OCGPZHXW.js +25 -0
  62. package/dist/bundle/chunk-OIHDCGV4.js +151 -0
  63. package/dist/bundle/chunk-OVSWVPXL.js +8190 -0
  64. package/dist/bundle/chunk-PIN5RZ3D.js +63 -0
  65. package/dist/bundle/chunk-QLIB3PEC.js +1146 -0
  66. package/dist/bundle/chunk-QTR6D5HT.js +58 -0
  67. package/dist/bundle/chunk-QUJPQMHJ.js +2315 -0
  68. package/dist/bundle/chunk-R3HPOCZD.js +188 -0
  69. package/dist/bundle/chunk-RMV4C2YL.js +482 -0
  70. package/dist/bundle/chunk-RRRBMQ2E.js +844 -0
  71. package/dist/bundle/chunk-S4S7E54J.js +60 -0
  72. package/dist/bundle/chunk-SB6WOSHG.js +1109 -0
  73. package/dist/bundle/chunk-SE75DPBY.js +31 -0
  74. package/dist/bundle/chunk-SQJ7EH2D.js +97 -0
  75. package/dist/bundle/chunk-TKCHUT2H.js +422 -0
  76. package/dist/bundle/chunk-TLQUKHEM.js +193 -0
  77. package/dist/bundle/chunk-TZ2MKETI.js +100 -0
  78. package/dist/bundle/chunk-UC44ZMQV.js +78 -0
  79. package/dist/bundle/chunk-UI5UTMSZ.js +275 -0
  80. package/dist/bundle/chunk-VPPYYWOH.js +630 -0
  81. package/dist/bundle/chunk-WJHK5D7Z.js +35612 -0
  82. package/dist/bundle/chunk-WQ24UC44.js +239 -0
  83. package/dist/bundle/chunk-X5DFDGWS.js +2 -0
  84. package/dist/bundle/chunk-Y5E2WNWB.js +35 -0
  85. package/dist/bundle/chunk-YB4DMKYX.js +119 -0
  86. package/dist/bundle/chunk-Z4TZ6TAF.js +288 -0
  87. package/dist/bundle/chunk-ZVVMGPBJ.js +50 -0
  88. package/dist/bundle/cli-72EQATJF.js +825 -0
  89. package/dist/bundle/command-contract-4MHSZUN3.js +38 -0
  90. package/dist/bundle/config-GFIJK6G2.js +79 -0
  91. package/dist/bundle/dist-IBNHCQXT.js +136 -0
  92. package/dist/bundle/dist-XUFQCJXW.js +7577 -0
  93. package/dist/bundle/echarts-RFTK737T.js +42501 -0
  94. package/dist/bundle/embed-7QYA2ZZE.js +35 -0
  95. package/dist/bundle/embedder-YNN6PCHG.js +108 -0
  96. package/dist/bundle/first-run-EVSRQKZJ.js +23 -0
  97. package/dist/bundle/first-screen-RG4GUKW7.js +36 -0
  98. package/dist/bundle/folders-JEPOMYM4.js +13 -0
  99. package/dist/bundle/instructions-FHNL7CMW.js +9 -0
  100. package/dist/bundle/main-NYGJXPH7.js +130 -0
  101. package/dist/bundle/mcp-IXFC2VVR.js +165 -0
  102. package/dist/bundle/mcp-VGSZDCKK.js +189 -0
  103. package/dist/bundle/mcp-XKP2PXLS.js +92 -0
  104. package/dist/bundle/meetings-IBPI4MM4.js +215 -0
  105. package/dist/bundle/node-P4E63KXA.js +2340 -0
  106. package/dist/bundle/node-TLZSIHIJ.js +21 -0
  107. package/dist/bundle/node-ZMCACJVS.js +176 -0
  108. package/dist/bundle/node-sqlite-ZNGOBA5Q.js +45 -0
  109. package/dist/bundle/oauth-Q6LKF7DB.js +17 -0
  110. package/dist/bundle/open-LJ332Y5O.js +19 -0
  111. package/dist/bundle/output-file-RYQZ3RTE.js +63 -0
  112. package/dist/bundle/process-ZPTLNVQS.js +91 -0
  113. package/dist/bundle/program-IBRQBQ6W.js +5845 -0
  114. package/dist/bundle/prompt-RT7GUUXH.js +12 -0
  115. package/dist/bundle/recognize-QXGE7YJH.js +190 -0
  116. package/dist/bundle/remote-FD4MZQV3.js +26 -0
  117. package/dist/bundle/rerank-V6AVV755.js +49 -0
  118. package/dist/bundle/run-log-RE2EI3UU.js +111 -0
  119. package/dist/bundle/serve-HIJTFAW7.js +19 -0
  120. package/dist/bundle/server-3QOXOYYL.js +53 -0
  121. package/dist/bundle/server-WLJCU7KK.js +812 -0
  122. package/dist/bundle/services-KC5KQFBS.js +330 -0
  123. package/dist/bundle/speech-5JOXLYDA.js +44 -0
  124. package/dist/bundle/state-PPGRB3T6.js +33 -0
  125. package/dist/bundle/store-LODVDPK3.js +141 -0
  126. package/dist/bundle/store-WB5BICTG.js +124 -0
  127. package/dist/bundle/store-connection-EG6Z5ITP.js +76 -0
  128. package/dist/bundle/store-maintenance-command-Q5WMFUZ2.js +54 -0
  129. package/dist/bundle/streaming-UGFFNOXL.js +15 -0
  130. package/dist/bundle/tokens-AF5CPY2F.js +17 -0
  131. package/dist/bundle/zm.js +8 -0
  132. package/dist/contract-overrides.d.ts.map +1 -1
  133. package/dist/contract-overrides.js +18 -16
  134. package/dist/contract-overrides.js.map +1 -1
  135. package/dist/mcp-command.d.ts +3 -2
  136. package/dist/mcp-command.d.ts.map +1 -1
  137. package/dist/mcp-command.js +62 -10
  138. package/dist/mcp-command.js.map +1 -1
  139. package/dist/program-bindings.d.ts.map +1 -1
  140. package/dist/program-bindings.js +1 -0
  141. package/dist/program-bindings.js.map +1 -1
  142. package/dist/version.d.ts +1 -1
  143. package/dist/version.js +1 -1
  144. package/package.json +7 -5
  145. package/skills/zoom-cli/SKILL.md +2 -0
@@ -0,0 +1,188 @@
1
+ import { createRequire as __bundleRequire } from "node:module";
2
+ const require = __bundleRequire(import.meta.url);
3
+ import {
4
+ checkCancelled,
5
+ object
6
+ } from "./chunk-A4ETXPMZ.js";
7
+ import {
8
+ CliError,
9
+ isCliError
10
+ } from "./chunk-AE4BOXH7.js";
11
+ import {
12
+ __name
13
+ } from "./chunk-ZVVMGPBJ.js";
14
+
15
+ // dist/tokens.js
16
+ var TOKEN_LOG_ALLOW = ["rotated", "saved", "pid"];
17
+ var TOKEN_LOG_EVENT = "zoom.token.exchange";
18
+ var abortReason = /* @__PURE__ */ __name((parent) => !parent?.aborted ? "timeout" : isCliError(parent.reason) ? parent.reason.code : "cancelled", "abortReason");
19
+ var TokenManager = class _TokenManager {
20
+ static {
21
+ __name(this, "TokenManager");
22
+ }
23
+ options;
24
+ refreshState = /* @__PURE__ */ new Map();
25
+ constructor(options) {
26
+ this.options = options;
27
+ }
28
+ /** The login of one profile: `<client id>:<profile>` in the keyring. */
29
+ forProfile(profile) {
30
+ const parent = this.options;
31
+ const scoped = new _TokenManager({
32
+ get clientId() {
33
+ return parent.clientId;
34
+ },
35
+ get account() {
36
+ return `${parent.clientId}:${profile}`;
37
+ },
38
+ keyring: parent.keyring,
39
+ service: parent.service,
40
+ fetch: parent.fetch,
41
+ now: parent.now,
42
+ log: parent.log,
43
+ signal: parent.signal
44
+ });
45
+ scoped.refreshState = this.refreshState;
46
+ return scoped;
47
+ }
48
+ withSignal(signal, fetcher = this.options.fetch) {
49
+ const parent = this.options;
50
+ let login;
51
+ const pinned = /* @__PURE__ */ __name(() => login ??= { clientId: parent.clientId, account: parent.account }, "pinned");
52
+ const scoped = new _TokenManager({
53
+ get clientId() {
54
+ return pinned().clientId;
55
+ },
56
+ get account() {
57
+ return pinned().account;
58
+ },
59
+ keyring: parent.keyring,
60
+ service: parent.service,
61
+ fetch: fetcher,
62
+ now: parent.now,
63
+ log: parent.log,
64
+ signal: signal && parent.signal ? AbortSignal.any([signal, parent.signal]) : signal ?? parent.signal
65
+ });
66
+ scoped.refreshState = this.refreshState;
67
+ return scoped;
68
+ }
69
+ accountOf() {
70
+ return this.options.account ?? this.options.clientId;
71
+ }
72
+ read(account) {
73
+ try {
74
+ return this.options.keyring.get(this.options.service, account);
75
+ } catch {
76
+ throw new CliError("configuration_error", "system keyring is unavailable");
77
+ }
78
+ }
79
+ load(account = this.accountOf()) {
80
+ const saved = this.read(account);
81
+ if (!saved)
82
+ return null;
83
+ try {
84
+ const value = object(JSON.parse(saved));
85
+ if (typeof value.accessToken !== "string" || !value.accessToken || typeof value.refreshToken !== "string" || !value.refreshToken || typeof value.expiresAt !== "number" || !Number.isFinite(value.expiresAt) || !Array.isArray(value.scopes) || !value.scopes.every((s) => typeof s === "string"))
86
+ throw new Error("invalid token entry");
87
+ return {
88
+ accessToken: value.accessToken,
89
+ refreshToken: value.refreshToken,
90
+ expiresAt: value.expiresAt,
91
+ scopes: value.scopes
92
+ };
93
+ } catch (error) {
94
+ if (isCliError(error) && error.code === "configuration_error")
95
+ throw error;
96
+ throw new CliError("authentication_error", "saved login is invalid; run zm <profile> session start again, or sign in from WireCat Desktop (Tools \u2192 Zoom \u2192 Manage)");
97
+ }
98
+ }
99
+ save(tokens, account = this.accountOf()) {
100
+ try {
101
+ this.options.keyring.set(this.options.service, account, JSON.stringify(tokens));
102
+ } catch {
103
+ throw new CliError("configuration_error", "could not save login to the system keyring");
104
+ }
105
+ }
106
+ async exchange(params, previous, clientId = this.options.clientId, account = this.options.account ?? clientId) {
107
+ checkCancelled(this.options.signal);
108
+ const signal = this.options.signal ? AbortSignal.any([this.options.signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4);
109
+ const parent = this.options.signal;
110
+ const record = /* @__PURE__ */ __name((level, fields) => this.options.log?.[level]({
111
+ event: TOKEN_LOG_EVENT,
112
+ operation: params.grant_type ?? "unknown",
113
+ pid: process.pid,
114
+ ...signal.aborted ? { reason: abortReason(parent) } : {},
115
+ ...fields
116
+ }), "record");
117
+ let response;
118
+ try {
119
+ response = await this.options.fetch("https://zoom.us/oauth/token", {
120
+ method: "POST",
121
+ headers: { "content-type": "application/x-www-form-urlencoded" },
122
+ body: new URLSearchParams({ ...params, client_id: clientId }),
123
+ signal,
124
+ redirect: "error"
125
+ });
126
+ } catch {
127
+ record("warn", { outcome: "no_response" });
128
+ throw new CliError("outcome_unknown", "Zoom token exchange did not finish; run zm <profile> session start again before retrying");
129
+ }
130
+ if (!response.ok) {
131
+ record("warn", { outcome: "rejected", status: response.status });
132
+ throw new CliError("authentication_error", `Zoom token request failed (HTTP ${response.status}); run zm <profile> session start again`);
133
+ }
134
+ let body;
135
+ try {
136
+ body = object(await response.json());
137
+ } catch {
138
+ record("error", { outcome: "unreadable_response", status: response.status });
139
+ throw new CliError("outcome_unknown", "Zoom returned an invalid token response; run zm <profile> session start again before retrying");
140
+ }
141
+ const refreshToken = typeof body.refresh_token === "string" ? body.refresh_token : previous?.refreshToken;
142
+ if (typeof body.access_token !== "string" || !body.access_token || !refreshToken || typeof body.expires_in !== "number" || !Number.isFinite(body.expires_in) || body.expires_in <= 0 || !Number.isFinite((this.options.now?.() ?? Date.now()) + body.expires_in * 1e3) || body.scope !== void 0 && typeof body.scope !== "string" || body.refresh_token !== void 0 && typeof body.refresh_token !== "string") {
143
+ record("error", { outcome: "invalid_response", status: response.status });
144
+ throw new CliError("outcome_unknown", "Zoom returned an invalid token response; run zm <profile> session start again before retrying");
145
+ }
146
+ const rotated = previous ? { rotated: refreshToken !== previous.refreshToken } : {};
147
+ const tokens = {
148
+ accessToken: body.access_token,
149
+ refreshToken,
150
+ expiresAt: (this.options.now?.() ?? Date.now()) + body.expires_in * 1e3,
151
+ scopes: typeof body.scope === "string" ? body.scope.split(/\s+/).filter(Boolean) : previous?.scopes ?? []
152
+ };
153
+ try {
154
+ this.save(tokens, account);
155
+ } catch (error) {
156
+ record("error", { outcome: "save_failed", status: response.status, ...rotated, saved: false });
157
+ throw error;
158
+ }
159
+ record("info", { outcome: "saved", status: response.status, ...rotated, saved: true });
160
+ return tokens;
161
+ }
162
+ async tokens() {
163
+ checkCancelled(this.options.signal);
164
+ const clientId = this.options.clientId;
165
+ const account = this.options.account ?? clientId;
166
+ const saved = this.load(account);
167
+ if (!saved)
168
+ throw new CliError("authentication_error", "run zm <profile> session start first, or sign in from WireCat Desktop (Tools \u2192 Zoom \u2192 Manage)");
169
+ if (saved.expiresAt > (this.options.now?.() ?? Date.now()) + 3e4)
170
+ return saved;
171
+ let refreshing = this.refreshState.get(account);
172
+ if (!refreshing) {
173
+ refreshing = this.exchange({ grant_type: "refresh_token", refresh_token: saved.refreshToken }, saved, clientId, account).finally(() => {
174
+ this.refreshState.delete(account);
175
+ });
176
+ this.refreshState.set(account, refreshing);
177
+ }
178
+ const refreshed = await refreshing;
179
+ checkCancelled(this.options.signal);
180
+ return refreshed;
181
+ }
182
+ };
183
+
184
+ export {
185
+ TOKEN_LOG_ALLOW,
186
+ TOKEN_LOG_EVENT,
187
+ TokenManager
188
+ };
@@ -0,0 +1,482 @@
1
+ import { createRequire as __bundleRequire } from "node:module";
2
+ const require = __bundleRequire(import.meta.url);
3
+ import {
4
+ LEVELS
5
+ } from "./chunk-TKCHUT2H.js";
6
+ import {
7
+ CliError,
8
+ array,
9
+ getDotPath,
10
+ integer,
11
+ minLength,
12
+ minValue,
13
+ nullable,
14
+ number,
15
+ optional,
16
+ picklist,
17
+ pipe,
18
+ record,
19
+ regex,
20
+ resolvePaths,
21
+ safeParse,
22
+ singleLine,
23
+ strictObject,
24
+ string,
25
+ transform,
26
+ unknown,
27
+ visibleControls,
28
+ writeSecurely
29
+ } from "./chunk-AE4BOXH7.js";
30
+ import {
31
+ __name
32
+ } from "./chunk-ZVVMGPBJ.js";
33
+
34
+ // node_modules/.pnpm/@wirecat+cli-core@0.469.0_@wirecat+cli-base@0.65.0_commander@15.0.0_typescript@7.0.2_vi_80b586dbc8b099637a6e461856c2ee6b/node_modules/@wirecat/cli-core/dist/resolve.js
35
+ var isId = /* @__PURE__ */ __name((reference) => /^-?\d+$/.test(reference.trim()), "isId");
36
+ var foldName = /* @__PURE__ */ __name((name) => name.normalize("NFC").replace(/[\uFE0E\uFE0F\u200D]/g, "").toLowerCase(), "foldName");
37
+ var pickChat = /* @__PURE__ */ __name((reference, chats) => {
38
+ const wanted = foldName(reference.trim());
39
+ const matches = chats.filter((chat) => chat.title !== null && foldName(chat.title).includes(wanted));
40
+ if (matches.length === 1 && matches[0])
41
+ return matches[0];
42
+ if (matches.length === 0)
43
+ throw new CliError("not_found", `no chat matches "${reference}"`);
44
+ throw ambiguous(reference, "chats", matches.map((chat) => ({ id: chat.id, title: chat.title })), ({ title }) => singleLine(String(title)));
45
+ }, "pickChat");
46
+ var pickPerson = /* @__PURE__ */ __name((reference, people) => {
47
+ const trimmed = reference.trim();
48
+ const known = people.get(trimmed);
49
+ if (known)
50
+ return known;
51
+ if (isId(trimmed))
52
+ throw new CliError("not_found", `no person ${trimmed} in what this account has seen`);
53
+ const wanted = foldName(trimmed.replace(/^@/, ""));
54
+ const everyone = people.all().toSorted((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
55
+ const fields = /* @__PURE__ */ __name((person) => [person.alias, person.name, person.username].filter(isPresent).map(foldName), "fields");
56
+ const exact = everyone.filter((person) => fields(person).includes(wanted));
57
+ const matches = exact.length > 0 ? exact : everyone.filter((person) => fields(person).some((one) => one.includes(wanted)));
58
+ if (matches.length === 1 && matches[0])
59
+ return matches[0];
60
+ if (matches.length === 0)
61
+ throw new CliError("not_found", `nobody matches "${reference}"`);
62
+ throw ambiguous(reference, "people", matches.map(({ id, name, username }) => ({ id, name, username })), ({ name, username }) => singleLine([name, username && `@${username}`].filter(isPresent).join(" ")));
63
+ }, "pickPerson");
64
+ var ambiguous = /* @__PURE__ */ __name((reference, what, candidates, label) => {
65
+ const width = Math.max(...candidates.map(({ id }) => id.length));
66
+ const lines = candidates.map((candidate) => ` ${candidate.id.padEnd(width)} ${label(candidate)}`).join("\n");
67
+ return new CliError("validation_error", `"${singleLine(reference)}" matches ${candidates.length} ${what} \u2014 name one by its id:
68
+ ${lines}`, { candidates });
69
+ }, "ambiguous");
70
+ var isPresent = /* @__PURE__ */ __name((value) => value !== null && value !== void 0, "isPresent");
71
+
72
+ // node_modules/.pnpm/@wirecat+cli-core@0.469.0_@wirecat+cli-base@0.65.0_commander@15.0.0_typescript@7.0.2_vi_80b586dbc8b099637a6e461856c2ee6b/node_modules/@wirecat/cli-core/dist/domain/formatting.js
73
+ var invalid = /* @__PURE__ */ __name(() => new CliError("validation_error", "the messenger formatter returned invalid text spans"), "invalid");
74
+ var validateFormattedText = /* @__PURE__ */ __name((formatted) => {
75
+ const { text, spans } = formatted;
76
+ if (typeof text !== "string" || !Array.isArray(spans) || spans.length > 100)
77
+ throw invalid();
78
+ const boundary = /* @__PURE__ */ __name((at) => !(at > 0 && at < text.length && /[\uD800-\uDBFF]/u.test(text[at - 1] ?? "") && /[\uDC00-\uDFFF]/u.test(text[at] ?? "")), "boundary");
79
+ for (const span of spans) {
80
+ if (![
81
+ "bold",
82
+ "italic",
83
+ "strike",
84
+ "underline",
85
+ "code",
86
+ "pre",
87
+ "link",
88
+ "blockquote",
89
+ "spoiler",
90
+ "highlight",
91
+ "heading"
92
+ ].includes(span.type) || !Number.isInteger(span.from) || !Number.isInteger(span.length) || span.from < 0 || span.length <= 0 || span.from + span.length > text.length || !boundary(span.from) || !boundary(span.from + span.length))
93
+ throw invalid();
94
+ if (span.type === "link") {
95
+ if (!span.url || Array.from(span.url).some((char) => char.charCodeAt(0) <= 32 || char.charCodeAt(0) === 127))
96
+ throw new CliError("validation_error", "a Markdown link needs an absolute http, https or mailto URL");
97
+ let url;
98
+ try {
99
+ url = new URL(span.url);
100
+ } catch {
101
+ throw new CliError("validation_error", "a Markdown link needs an absolute http, https or mailto URL");
102
+ }
103
+ if (!["http:", "https:", "mailto:"].includes(url.protocol))
104
+ throw new CliError("validation_error", "Markdown links support http, https and mailto URLs");
105
+ }
106
+ }
107
+ return formatted;
108
+ }, "validateFormattedText");
109
+ var visibleFormattedText = /* @__PURE__ */ __name((formatted) => {
110
+ validateFormattedText(formatted);
111
+ const text = visibleControls(formatted.text);
112
+ const positions = /* @__PURE__ */ new Map([[0, 0]]);
113
+ let original = 0;
114
+ let visible = 0;
115
+ for (const character of formatted.text) {
116
+ const rendered = text.startsWith(character, visible) ? character : visibleControls(character);
117
+ original += character.length;
118
+ visible += rendered.length;
119
+ positions.set(original, visible);
120
+ }
121
+ return {
122
+ text,
123
+ spans: formatted.spans.map((span) => ({
124
+ ...span,
125
+ from: positions.get(span.from),
126
+ length: positions.get(span.from + span.length) - positions.get(span.from),
127
+ ...span.url === void 0 ? {} : { url: visibleControls(span.url) }
128
+ }))
129
+ };
130
+ }, "visibleFormattedText");
131
+
132
+ // node_modules/.pnpm/@wirecat+cli-core@0.469.0_@wirecat+cli-base@0.65.0_commander@15.0.0_typescript@7.0.2_vi_80b586dbc8b099637a6e461856c2ee6b/node_modules/@wirecat/cli-core/dist/moderation/check.js
133
+ var INVITE = /max\.ru\/join\/|(t|telegram)\.me\/(\+|joinchat\/)|tg:\/\/join/i;
134
+ var LINK = /https?:\/\/\S+/i;
135
+ var STRENGTH = { report: 0, delete: 1, remove: 2 };
136
+ var judge = /* @__PURE__ */ __name(({ rules, messages, joined, answerers, service, now, invites }) => {
137
+ const invite = invites ? new RegExp(`${INVITE.source}|${invites.source}`, "i") : INVITE;
138
+ const trusted = new Set(rules.trusted);
139
+ const blocked = /* @__PURE__ */ __name((id, name) => id !== null && rules.blocked.includes(id) || name !== null && rules.blockedNames.some((part) => name.toLocaleLowerCase().includes(part.toLocaleLowerCase())), "blocked");
140
+ const young = /* @__PURE__ */ __name((member) => rules.newAccount.days > 0 && typeof member.registeredAt === "string" && now - Date.parse(member.registeredAt) < rules.newAccount.days * 864e5, "young");
141
+ const found = [];
142
+ const add = /* @__PURE__ */ __name((finding) => {
143
+ if (finding.action === "remove" && found.some((f) => f.action === "remove" && f.personId === finding.personId)) {
144
+ return;
145
+ }
146
+ found.push(finding);
147
+ }, "add");
148
+ for (const member of joined) {
149
+ if (trusted.has(member.id) || answerers.has(member.id))
150
+ continue;
151
+ const person = { kind: "member", personId: member.id, personName: member.name };
152
+ if (blocked(member.id, member.name)) {
153
+ add({ ...person, rule: "blocked", action: rules.blockedPeople === "remove" ? "remove" : "report" });
154
+ } else if (young(member)) {
155
+ add({ ...person, rule: "newAccount", action: rules.newAccount.action });
156
+ }
157
+ }
158
+ const flooding = floodOf(messages, rules.flood);
159
+ for (const message of messages) {
160
+ const id = message.senderId;
161
+ if (id === null || message.outgoing === true || answerers.has(id) || trusted.has(id))
162
+ continue;
163
+ if (service.has(message.id) || message.attachments.some((attachment) => attachment.kind === "control"))
164
+ continue;
165
+ const base = { kind: "message", personId: id, personName: message.senderName, messageId: message.id };
166
+ const text = message.text;
167
+ const broken2 = [];
168
+ if (blocked(id, message.senderName))
169
+ broken2.push(["blocked", rules.blockedPeople]);
170
+ if (invite.test(text))
171
+ broken2.push(["invites", rules.invites]);
172
+ if (LINK.test(text) || message.attachments.some((attachment) => attachment.kind === "share")) {
173
+ broken2.push(["links", rules.links]);
174
+ }
175
+ if (message.forwardedFrom !== null)
176
+ broken2.push(["forwards", rules.forwards]);
177
+ if (flooding.has(message.id))
178
+ broken2.push(["flood", rules.flood.action]);
179
+ const strongest = broken2.reduce((best, rule) => best === void 0 || STRENGTH[rule[1]] > STRENGTH[best[1]] ? rule : best, void 0);
180
+ if (strongest)
181
+ add({ ...base, rule: strongest[0], action: strongest[1] });
182
+ }
183
+ return found;
184
+ }, "judge");
185
+ var floodOf = /* @__PURE__ */ __name((messages, flood) => {
186
+ const window = flood.minutes * 6e4;
187
+ const over = /* @__PURE__ */ new Set();
188
+ const bySender = /* @__PURE__ */ new Map();
189
+ for (const message of messages) {
190
+ if (message.senderId === null)
191
+ continue;
192
+ const times = bySender.get(message.senderId) ?? [];
193
+ const at = Date.parse(message.timestamp);
194
+ times.push(at);
195
+ bySender.set(message.senderId, times);
196
+ if (times.filter((time) => at - time < window).length > flood.messages)
197
+ over.add(message.id);
198
+ }
199
+ return over;
200
+ }, "floodOf");
201
+ var act = /* @__PURE__ */ __name(async (moderator, findings, options) => {
202
+ const { chatId, rules, allowDangerous, dryRun, maxActions, confirm, command } = options;
203
+ const typed = options.commandFor ?? ((chat, finding) => commandFor(command, chat, finding));
204
+ const rows = [];
205
+ let acted = 0;
206
+ let stopped;
207
+ for (const finding of findings) {
208
+ if (finding.action === "report") {
209
+ rows.push({ ...finding, outcome: "reported" });
210
+ continue;
211
+ }
212
+ const consent = finding.action;
213
+ const level2 = rules.consent[consent];
214
+ const row = /* @__PURE__ */ __name((outcome, reason) => ({
215
+ ...finding,
216
+ outcome,
217
+ ...reason ? { reason } : {},
218
+ ...outcome === "done" || outcome === "reported" ? {} : { command: typed(chatId, finding) }
219
+ }), "row");
220
+ if (level2 === "deny") {
221
+ rows.push(row("forbidden", `consent.${consent} is deny`));
222
+ continue;
223
+ }
224
+ if (level2 === "readonly") {
225
+ rows.push(row("reported", `consent.${consent} is readonly`));
226
+ continue;
227
+ }
228
+ if (dryRun) {
229
+ rows.push(row("planned", "--dry-run"));
230
+ continue;
231
+ }
232
+ if (stopped || acted >= maxActions) {
233
+ rows.push(row("skipped", stopped ?? `over the limit of ${maxActions} actions per check`));
234
+ continue;
235
+ }
236
+ if (level2 === "ask" && !allowDangerous) {
237
+ const yes = confirm ? await confirm(finding) : void 0;
238
+ if (yes === void 0) {
239
+ rows.push(row("planned", `consent.${consent} is ask, and nobody is there to answer \u2014 run with --allow-dangerous`));
240
+ continue;
241
+ }
242
+ if (!yes) {
243
+ rows.push(row("declined", "the answer was no"));
244
+ continue;
245
+ }
246
+ }
247
+ acted += 1;
248
+ try {
249
+ if (finding.action === "delete" && finding.messageId) {
250
+ await moderator.deleteMessage(chatId, finding.messageId);
251
+ } else {
252
+ await moderator.removePerson(chatId, finding.personId);
253
+ }
254
+ rows.push(row("done"));
255
+ } catch (error) {
256
+ const failure = asCliError(error);
257
+ if (failure.code === "rate_limited")
258
+ stopped = failure.message;
259
+ const refused = ["rate_limited", "permission_error", "confirmation_required"].includes(failure.code);
260
+ rows.push(row(refused ? "refused" : "failed", failure.message));
261
+ }
262
+ }
263
+ return rows;
264
+ }, "act");
265
+ var asCliError = /* @__PURE__ */ __name((error) => error instanceof CliError ? error : new CliError("provider_error", error instanceof Error ? error.message : String(error)), "asCliError");
266
+ var who = /* @__PURE__ */ __name((finding) => finding.personName ?? finding.personId, "who");
267
+ var describe = /* @__PURE__ */ __name((finding) => finding.action === "delete" ? `delete message ${finding.messageId} from ${who(finding)} for everyone (${finding.rule})` : `remove ${who(finding)} from the group (${finding.rule})`, "describe");
268
+ var commandFor = /* @__PURE__ */ __name((command, chatId, finding) => finding.action === "delete" ? `${command} messages delete ${chatId} ${finding.messageId} --for-everyone --allow-dangerous` : finding.action === "remove" ? `${command} chats members remove ${chatId} ${finding.personId}` : "", "commandFor");
269
+ var CHECK_READS = 1e3;
270
+ var PAGE = 100;
271
+ var gather = /* @__PURE__ */ __name(async (adapter, chatId, since) => {
272
+ const notes = [];
273
+ const historyAfter = adapter.historyAfter;
274
+ if (!historyAfter)
275
+ throw new CliError("validation_error", "this messenger cannot read a chat forward from a time");
276
+ const messages = [];
277
+ let more = true;
278
+ while (more && messages.length < CHECK_READS) {
279
+ const last = messages.at(-1);
280
+ const page = await historyAfter.call(adapter, chatId, {
281
+ limit: PAGE,
282
+ after: last ? { id: last.id } : { time: since }
283
+ });
284
+ messages.push(...page.items);
285
+ more = page.hasMore && page.items.length > 0;
286
+ }
287
+ const scanned = adapter.chatEvents ? (await adapter.chatEvents(chatId, { since })).events : [];
288
+ const until = messages.at(-1)?.timestamp ?? null;
289
+ const events = more && until !== null ? scanned.filter((event) => Date.parse(event.timestamp) <= Date.parse(until)) : scanned;
290
+ if (!adapter.chatEvents)
291
+ notes.push("this messenger does not say who joined, so only messages are judged");
292
+ const joinedIds = new Set(events.filter((one) => one.event === "join" || one.event === "add").flatMap((one) => one.people.length > 0 ? one.people.map((person) => person.id) : one.by.id ? [one.by.id] : []));
293
+ const joined = joinedIds.size === 0 || !adapter.members ? [] : (await adapter.members(chatId, { offset: 0 })).items.filter((member) => joinedIds.has(member.id));
294
+ const admins = adapter.admins ? await adapter.admins(chatId) : null;
295
+ if (admins === null)
296
+ notes.push("the group's admins are not known, so only your own messages are exempt");
297
+ return {
298
+ messages,
299
+ joined,
300
+ answerers: new Set(admins ?? []),
301
+ service: new Set(events.map((one) => one.messageId)),
302
+ until,
303
+ more,
304
+ notes
305
+ };
306
+ }, "gather");
307
+ var MAX_ACTIONS = 10;
308
+ var undone = /* @__PURE__ */ __name((row) => ["planned", "skipped", "failed"].includes(row.outcome), "undone");
309
+ var nextPoint = /* @__PURE__ */ __name((rows, found) => {
310
+ const waiting = rows.filter(undone);
311
+ if (waiting.some((row) => row.messageId === void 0))
312
+ return null;
313
+ const times = waiting.map((row) => found.messages.find((message) => message.id === row.messageId)?.timestamp).filter((time) => time !== void 0).map(Date.parse);
314
+ if (times.length === 0)
315
+ return found.until;
316
+ return new Date(Math.min(...times) - 1).toISOString();
317
+ }, "nextPoint");
318
+ var someUndone = /* @__PURE__ */ __name((rows) => rows.some(undone), "someUndone");
319
+
320
+ // node_modules/.pnpm/@wirecat+cli-core@0.469.0_@wirecat+cli-base@0.65.0_commander@15.0.0_typescript@7.0.2_vi_80b586dbc8b099637a6e461856c2ee6b/node_modules/@wirecat/cli-core/dist/moderation/rules.js
321
+ import { existsSync, readFileSync } from "node:fs";
322
+ import { join } from "node:path";
323
+ var RULE_ACTIONS = ["report", "delete", "remove"];
324
+ var OLD_LEVELS = { forbid: "deny", flag: "ask", confirm: "ask", allow: "allow" };
325
+ var level = pipe(picklist([...LEVELS, ...Object.keys(OLD_LEVELS)]), transform((typed) => OLD_LEVELS[typed] ?? typed));
326
+ var action = picklist(RULE_ACTIONS);
327
+ var personIds = array(pipe(string(), regex(/^-?\d+$/, "a person id is digits")));
328
+ var atLeast = /* @__PURE__ */ __name((min) => pipe(number(), integer(), minValue(min)), "atLeast");
329
+ var groupRules = pipe(strictObject({
330
+ title: nullable(string()),
331
+ trusted: personIds,
332
+ blocked: personIds,
333
+ blockedNames: array(pipe(string(), minLength(1))),
334
+ links: action,
335
+ invites: action,
336
+ forwards: action,
337
+ blockedPeople: action,
338
+ flood: strictObject({ messages: atLeast(1), minutes: atLeast(1), action }),
339
+ /** 0 days turns the rule off. */
340
+ newAccount: strictObject({ days: atLeast(0), action: picklist(["report", "remove"]) }),
341
+ requests: optional(unknown()),
342
+ consent: strictObject({
343
+ delete: level,
344
+ remove: level,
345
+ accept: optional(unknown()),
346
+ decline: optional(unknown())
347
+ })
348
+ }), transform(({ requests: _, consent: { accept: _accept, decline: _decline, ...consent }, ...rules }) => ({
349
+ ...rules,
350
+ consent
351
+ })));
352
+ var file = strictObject({
353
+ groups: record(string(), groupRules),
354
+ /** Where each group's next `chats moderate` starts, ISO 8601. */
355
+ checkedUntil: optional(record(string(), string()))
356
+ });
357
+ var defaultRules = /* @__PURE__ */ __name((title) => ({
358
+ title,
359
+ trusted: [],
360
+ blocked: [],
361
+ blockedNames: [],
362
+ links: "report",
363
+ invites: "report",
364
+ forwards: "report",
365
+ blockedPeople: "report",
366
+ flood: { messages: 5, minutes: 1, action: "report" },
367
+ newAccount: { days: 7, action: "report" },
368
+ consent: { delete: "ask", remove: "ask" }
369
+ }), "defaultRules");
370
+ var list = /* @__PURE__ */ __name((value) => value.split(",").map((item) => item.trim()).filter(Boolean), "list");
371
+ var whole = /* @__PURE__ */ __name((value) => /^\d+$/.test(value.trim()) ? Number(value) : Number.NaN, "whole");
372
+ var KEYS = {
373
+ trusted: { help: "person ids, comma-separated; never acted on", parse: list },
374
+ blocked: { help: "person ids, comma-separated", parse: list },
375
+ blockedNames: { help: "parts of a name, comma-separated, any case", parse: list },
376
+ links: { help: RULE_ACTIONS.join("|"), parse: String },
377
+ invites: { help: RULE_ACTIONS.join("|"), parse: String },
378
+ forwards: { help: RULE_ACTIONS.join("|"), parse: String },
379
+ blockedPeople: { help: RULE_ACTIONS.join("|"), parse: String },
380
+ "flood.messages": { help: "a whole number, 1 or more", parse: whole },
381
+ "flood.minutes": { help: "a whole number, 1 or more", parse: whole },
382
+ "flood.action": { help: RULE_ACTIONS.join("|"), parse: String },
383
+ "newAccount.days": { help: "a whole number; 0 turns it off", parse: whole },
384
+ "newAccount.action": { help: "report|remove", parse: String },
385
+ "consent.delete": { help: LEVELS.join("|"), parse: String },
386
+ "consent.remove": { help: LEVELS.join("|"), parse: String }
387
+ };
388
+ var RULE_KEYS = Object.keys(KEYS);
389
+ var moderationPathFor = /* @__PURE__ */ __name((app, profile, env = process.env) => join(resolvePaths({ appName: app.appName, prefix: app.envPrefix, env }).state, "profiles", `${profile}.moderation.json`), "moderationPathFor");
390
+ var ModerationRules = class {
391
+ static {
392
+ __name(this, "ModerationRules");
393
+ }
394
+ path;
395
+ constructor(path) {
396
+ this.path = path;
397
+ }
398
+ /** `undefined` when this group has no section yet. */
399
+ read(chatId) {
400
+ return this.#file().groups[chatId];
401
+ }
402
+ /** Writes the group's whole section — the defaults first, if it had none — with one key changed. */
403
+ set(chatId, title, key, value) {
404
+ const known = KEYS[key];
405
+ if (!known)
406
+ throw invalid2(`no rule ${key} \u2014 one of: ${RULE_KEYS.join(", ")}`);
407
+ const current = this.read(chatId) ?? defaultRules(title);
408
+ const checked = safeParse(groupRules, assign(current, key, known.parse(value)));
409
+ if (!checked.success)
410
+ throw invalid2(`${key} ${JSON.stringify(value)} is not valid \u2014 ${known.help}`);
411
+ const saved = this.#file();
412
+ this.#write({ ...saved, groups: { ...saved.groups, [chatId]: checked.output } });
413
+ return checked.output;
414
+ }
415
+ /** Puts one key back to its default. */
416
+ unset(chatId, title, key) {
417
+ if (!KEYS[key])
418
+ throw invalid2(`no rule ${key} \u2014 one of: ${RULE_KEYS.join(", ")}`);
419
+ const current = this.read(chatId) ?? defaultRules(title);
420
+ const changed = assign(current, key, lookup(defaultRules(title), key));
421
+ const saved = this.#file();
422
+ this.#write({ ...saved, groups: { ...saved.groups, [chatId]: changed } });
423
+ return changed;
424
+ }
425
+ checkedUntil(chatId) {
426
+ return this.#file().checkedUntil?.[chatId];
427
+ }
428
+ markChecked(chatId, at) {
429
+ const saved = this.#file();
430
+ this.#write({ ...saved, checkedUntil: { ...saved.checkedUntil, [chatId]: at } });
431
+ }
432
+ #file() {
433
+ if (!existsSync(this.path))
434
+ return { groups: {} };
435
+ let parsed;
436
+ try {
437
+ parsed = JSON.parse(readFileSync(this.path, "utf8"));
438
+ } catch (error) {
439
+ throw broken(this.path, error instanceof Error ? error.message : String(error));
440
+ }
441
+ const checked = safeParse(file, parsed);
442
+ if (!checked.success) {
443
+ const problems = checked.issues.map((issue) => `${getDotPath(issue) ?? "file"}: ${issue.message}`);
444
+ throw broken(this.path, problems.join("; "));
445
+ }
446
+ return checked.output;
447
+ }
448
+ #write(content) {
449
+ writeSecurely(this.path, `${JSON.stringify(content, null, 2)}
450
+ `, 384);
451
+ }
452
+ };
453
+ var invalid2 = /* @__PURE__ */ __name((message) => new CliError("validation_error", message), "invalid");
454
+ var broken = /* @__PURE__ */ __name((path, why) => new CliError("configuration_error", `the moderation rules ${path} cannot be read (${why}) \u2014 fix the file`), "broken");
455
+ var lookup = /* @__PURE__ */ __name((rules, key) => key.split(".").reduce((value, part) => value[part], rules), "lookup");
456
+ var assign = /* @__PURE__ */ __name((rules, key, value) => {
457
+ const [head, tail] = key.split(".");
458
+ if (tail === void 0)
459
+ return { ...rules, [head]: value };
460
+ return { ...rules, [head]: { ...rules[head], [tail]: value } };
461
+ }, "assign");
462
+
463
+ export {
464
+ isId,
465
+ foldName,
466
+ pickChat,
467
+ pickPerson,
468
+ validateFormattedText,
469
+ visibleFormattedText,
470
+ judge,
471
+ act,
472
+ describe,
473
+ CHECK_READS,
474
+ gather,
475
+ MAX_ACTIONS,
476
+ nextPoint,
477
+ someUndone,
478
+ defaultRules,
479
+ RULE_KEYS,
480
+ moderationPathFor,
481
+ ModerationRules
482
+ };