@wirecat/mail-cli 0.0.0-stage → 0.1.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 +50 -0
- package/LICENSE +204 -0
- package/README.md +141 -2
- package/dist/bin/mail.d.ts +3 -0
- package/dist/bin/mail.d.ts.map +1 -0
- package/dist/bin/mail.js +36 -0
- package/dist/bin/mail.js.map +1 -0
- package/dist/bundle/all-profiles-TKMBELRZ.js +286 -0
- package/dist/bundle/anthropic-7LMUAEIZ.js +72 -0
- package/dist/bundle/app-server-JGMIJDD4.js +229 -0
- package/dist/bundle/bun-XRO6XJHQ.js +169 -0
- package/dist/bundle/bun-sqlite-Z54LEHVM.js +71 -0
- package/dist/bundle/chunk-2LLNPNMB.js +24 -0
- package/dist/bundle/chunk-3E3WA2L6.js +1932 -0
- package/dist/bundle/chunk-3KEVLSNN.js +138 -0
- package/dist/bundle/chunk-3KOA73LS.js +3416 -0
- package/dist/bundle/chunk-4E7BN3TC.js +122 -0
- package/dist/bundle/chunk-4FEMINO2.js +409 -0
- package/dist/bundle/chunk-5BVIWP3S.js +71 -0
- package/dist/bundle/chunk-5D4AEUVA.js +24783 -0
- package/dist/bundle/chunk-5NYMNSSO.js +264 -0
- package/dist/bundle/chunk-5WGEUDE4.js +27 -0
- package/dist/bundle/chunk-6MAZWSC3.js +132 -0
- package/dist/bundle/chunk-75CWWETH.js +379 -0
- package/dist/bundle/chunk-7SXGLT6T.js +444 -0
- package/dist/bundle/chunk-BTQX2ZSZ.js +288 -0
- package/dist/bundle/chunk-CEW6KIFA.js +1193 -0
- package/dist/bundle/chunk-CH6RXYAX.js +82 -0
- package/dist/bundle/chunk-D72URWJE.js +532 -0
- package/dist/bundle/chunk-DFDN6AUB.js +21 -0
- package/dist/bundle/chunk-EOEDICIE.js +6848 -0
- package/dist/bundle/chunk-F6D6PJ34.js +100 -0
- package/dist/bundle/chunk-FDCGTODC.js +78 -0
- package/dist/bundle/chunk-FZ5FXIG6.js +18 -0
- package/dist/bundle/chunk-GAH7N7HO.js +8181 -0
- package/dist/bundle/chunk-GK4M2SUP.js +195 -0
- package/dist/bundle/chunk-GN62YOT5.js +161 -0
- package/dist/bundle/chunk-GQQJOH7A.js +7050 -0
- package/dist/bundle/chunk-GWDCKUBI.js +781 -0
- package/dist/bundle/chunk-HTQTIKWP.js +928 -0
- package/dist/bundle/chunk-IK5TH3O7.js +217 -0
- package/dist/bundle/chunk-J3QXTBD2.js +2742 -0
- package/dist/bundle/chunk-J6MSJLIW.js +45 -0
- package/dist/bundle/chunk-JBBZCTTF.js +76 -0
- package/dist/bundle/chunk-JVMVLITS.js +260 -0
- package/dist/bundle/chunk-K4FVKPDQ.js +525 -0
- package/dist/bundle/chunk-KHTO2Q6E.js +886 -0
- package/dist/bundle/chunk-KM7Q67Z3.js +63 -0
- package/dist/bundle/chunk-LQ37ALDJ.js +2416 -0
- package/dist/bundle/chunk-MNOIRJEO.js +2315 -0
- package/dist/bundle/chunk-MS53RALV.js +168 -0
- package/dist/bundle/chunk-NK7EB63L.js +36225 -0
- package/dist/bundle/chunk-NPGVOZVK.js +2 -0
- package/dist/bundle/chunk-OTUOZ33L.js +82 -0
- package/dist/bundle/chunk-P7P5MNAU.js +46 -0
- package/dist/bundle/chunk-QLEUNU6G.js +537 -0
- package/dist/bundle/chunk-QQB7WT7R.js +216 -0
- package/dist/bundle/chunk-QX7RCLRS.js +23 -0
- package/dist/bundle/chunk-T6464KYA.js +35130 -0
- package/dist/bundle/chunk-TE5U5OAM.js +73 -0
- package/dist/bundle/chunk-TI6VBZKJ.js +166 -0
- package/dist/bundle/chunk-TQFCPUIM.js +53 -0
- package/dist/bundle/chunk-TVREDYX4.js +3574 -0
- package/dist/bundle/chunk-TWPDVUBR.js +106 -0
- package/dist/bundle/chunk-TWTATDM7.js +461 -0
- package/dist/bundle/chunk-UH4DDRHA.js +2989 -0
- package/dist/bundle/chunk-VEIZZRQ5.js +630 -0
- package/dist/bundle/chunk-WLFLBWNH.js +54 -0
- package/dist/bundle/chunk-WXAXYJJ7.js +486 -0
- package/dist/bundle/chunk-XQUUANA2.js +24600 -0
- package/dist/bundle/chunk-Y5E2WNWB.js +35 -0
- package/dist/bundle/chunk-YO4CUH5E.js +33 -0
- package/dist/bundle/chunk-YPQQ5J4X.js +849 -0
- package/dist/bundle/chunk-Z34ICZA3.js +1028 -0
- package/dist/bundle/chunk-ZANYM66S.js +58 -0
- package/dist/bundle/chunk-ZD3WUGR2.js +142 -0
- package/dist/bundle/chunk-ZVVMGPBJ.js +50 -0
- package/dist/bundle/command-contract-WU434ZUL.js +34 -0
- package/dist/bundle/decode-YB3ZDTES.js +162 -0
- package/dist/bundle/dispatch-EUKKT3PD.js +411 -0
- package/dist/bundle/dist-A6GKSKFC.js +2072 -0
- package/dist/bundle/dist-X36THCOF.js +7577 -0
- package/dist/bundle/echarts-YNGCOYHW.js +42501 -0
- package/dist/bundle/embed-LWNHIRBQ.js +37 -0
- package/dist/bundle/embedder-2LYD3U37.js +110 -0
- package/dist/bundle/failure-OAIC5VAP.js +24 -0
- package/dist/bundle/first-run-N3HJKGU7.js +27 -0
- package/dist/bundle/first-screen-4U2DMV3G.js +32 -0
- package/dist/bundle/instructions-GJHHFV64.js +11 -0
- package/dist/bundle/mail.js +166 -0
- package/dist/bundle/mcp-L7SRVDRN.js +167 -0
- package/dist/bundle/node-GFOH6FKP.js +176 -0
- package/dist/bundle/node-KM3CUABH.js +2340 -0
- package/dist/bundle/node-TLZSIHIJ.js +21 -0
- package/dist/bundle/node-sqlite-NBXQXBIU.js +63 -0
- package/dist/bundle/oauth-56EFO5AQ.js +19 -0
- package/dist/bundle/open-2V7QZQJP.js +21 -0
- package/dist/bundle/openai-V3SVACVL.js +251 -0
- package/dist/bundle/process-XVDX4SPD.js +91 -0
- package/dist/bundle/program-ILID6EAT.js +35542 -0
- package/dist/bundle/prompt-YKWAI222.js +14 -0
- package/dist/bundle/recognize-PAR66KVM.js +25 -0
- package/dist/bundle/remote-CE43TKXC.js +28 -0
- package/dist/bundle/rerank-M7S7P2TW.js +51 -0
- package/dist/bundle/serve-ENPMIV6J.js +197 -0
- package/dist/bundle/server-JRTHUCVY.js +63 -0
- package/dist/bundle/server-MBEZLVKZ.js +189 -0
- package/dist/bundle/sqlite-runtime-HVGDHBJJ.js +18 -0
- package/dist/bundle/state-QZSOSKBD.js +35 -0
- package/dist/bundle/store-2BUX6LVX.js +124 -0
- package/dist/bundle/store-UJ7SNKXI.js +141 -0
- package/dist/bundle/store-maintenance-command-I7V5C44K.js +52 -0
- package/dist/commands/account.d.ts +3 -0
- package/dist/commands/account.d.ts.map +1 -0
- package/dist/commands/account.js +47 -0
- package/dist/commands/account.js.map +1 -0
- package/dist/commands/context.d.ts +41 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +52 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/doctor.d.ts +77 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +74 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/folders.d.ts +3 -0
- package/dist/commands/folders.d.ts.map +1 -0
- package/dist/commands/folders.js +36 -0
- package/dist/commands/folders.js.map +1 -0
- package/dist/commands/login.d.ts +5 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +21 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/mailbox.d.ts +16 -0
- package/dist/commands/mailbox.d.ts.map +1 -0
- package/dist/commands/mailbox.js +54 -0
- package/dist/commands/mailbox.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +97 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/session.d.ts +3 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +211 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/setup.d.ts +3 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +156 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +89 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts +3 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +190 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/config.d.ts +190 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +46 -0
- package/dist/config.js.map +1 -0
- package/dist/discovery.d.ts +42 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +179 -0
- package/dist/discovery.js.map +1 -0
- package/dist/identity.d.ts +5 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +16 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/mail/imap-errors.d.ts +4 -0
- package/dist/mail/imap-errors.d.ts.map +1 -0
- package/dist/mail/imap-errors.js +41 -0
- package/dist/mail/imap-errors.js.map +1 -0
- package/dist/mail/imap-messages.d.ts +18 -0
- package/dist/mail/imap-messages.d.ts.map +1 -0
- package/dist/mail/imap-messages.js +66 -0
- package/dist/mail/imap-messages.js.map +1 -0
- package/dist/mail/imap.d.ts +67 -0
- package/dist/mail/imap.d.ts.map +1 -0
- package/dist/mail/imap.js +348 -0
- package/dist/mail/imap.js.map +1 -0
- package/dist/mail/parse.d.ts +18 -0
- package/dist/mail/parse.d.ts.map +1 -0
- package/dist/mail/parse.js +31 -0
- package/dist/mail/parse.js.map +1 -0
- package/dist/mail/port.d.ts +50 -0
- package/dist/mail/port.d.ts.map +1 -0
- package/dist/mail/port.js +4 -0
- package/dist/mail/port.js.map +1 -0
- package/dist/mcp.d.ts +29 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +98 -0
- package/dist/mcp.js.map +1 -0
- package/dist/profile.d.ts +16 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +31 -0
- package/dist/profile.js.map +1 -0
- package/dist/program.d.ts +6 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +52 -0
- package/dist/program.js.map +1 -0
- package/dist/providers.d.ts +53 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +106 -0
- package/dist/providers.js.map +1 -0
- package/dist/secret.d.ts +30 -0
- package/dist/secret.d.ts.map +1 -0
- package/dist/secret.js +68 -0
- package/dist/secret.js.map +1 -0
- package/dist/store.d.ts +12 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +25 -0
- package/dist/store.js.map +1 -0
- package/dist/sync.d.ts +26 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +75 -0
- package/dist/sync.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +83 -4
- package/skills/mail-cli/SKILL.md +27 -0
|
@@ -0,0 +1,1932 @@
|
|
|
1
|
+
import { createRequire as __bundleRequire } from "node:module";
|
|
2
|
+
const require = __bundleRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
OFFLINE,
|
|
5
|
+
capability,
|
|
6
|
+
chatIdIn,
|
|
7
|
+
refuseLacking,
|
|
8
|
+
servicesFor,
|
|
9
|
+
throughWrapper
|
|
10
|
+
} from "./chunk-T6464KYA.js";
|
|
11
|
+
import {
|
|
12
|
+
foldName,
|
|
13
|
+
isId,
|
|
14
|
+
pickPerson
|
|
15
|
+
} from "./chunk-WXAXYJJ7.js";
|
|
16
|
+
import {
|
|
17
|
+
LOG_ATTRIBUTES,
|
|
18
|
+
MESSENGER_CHANGE_REFUSAL,
|
|
19
|
+
asFirstWord,
|
|
20
|
+
baseContext,
|
|
21
|
+
codeOf,
|
|
22
|
+
currentOperation,
|
|
23
|
+
currentRun,
|
|
24
|
+
environmentOf,
|
|
25
|
+
fromLogAttributes,
|
|
26
|
+
issueMessage,
|
|
27
|
+
logAttributes,
|
|
28
|
+
permissionIssues,
|
|
29
|
+
publicIssue,
|
|
30
|
+
readOnlyRefusal,
|
|
31
|
+
recalledAccount,
|
|
32
|
+
rememberAccount,
|
|
33
|
+
rootOf2 as rootOf
|
|
34
|
+
} from "./chunk-GQQJOH7A.js";
|
|
35
|
+
import {
|
|
36
|
+
AUTHORITY_KEY,
|
|
37
|
+
CEILING_KEY,
|
|
38
|
+
DEFAULT_PERMISSIONS,
|
|
39
|
+
WRITE_KEYS,
|
|
40
|
+
keyForCommand,
|
|
41
|
+
keyForWrite,
|
|
42
|
+
levelFor,
|
|
43
|
+
readKeysForCommand,
|
|
44
|
+
skipFlagFor
|
|
45
|
+
} from "./chunk-QLEUNU6G.js";
|
|
46
|
+
import {
|
|
47
|
+
isCliFailure,
|
|
48
|
+
providerErrorKey
|
|
49
|
+
} from "./chunk-7SXGLT6T.js";
|
|
50
|
+
import {
|
|
51
|
+
servingProfiles
|
|
52
|
+
} from "./chunk-TE5U5OAM.js";
|
|
53
|
+
import {
|
|
54
|
+
openStore
|
|
55
|
+
} from "./chunk-NK7EB63L.js";
|
|
56
|
+
import {
|
|
57
|
+
logRecordOf,
|
|
58
|
+
realSleep,
|
|
59
|
+
resolvePaths,
|
|
60
|
+
writeSecurely
|
|
61
|
+
} from "./chunk-J3QXTBD2.js";
|
|
62
|
+
import {
|
|
63
|
+
CliError
|
|
64
|
+
} from "./chunk-5BVIWP3S.js";
|
|
65
|
+
import {
|
|
66
|
+
__name
|
|
67
|
+
} from "./chunk-ZVVMGPBJ.js";
|
|
68
|
+
|
|
69
|
+
// node_modules/.pnpm/@wirecat+cli-core@0.507.0_@wirecat+cli-base@0.68.1_commander@15.0.0_esbuild@0.28.2_type_d5beb0101c7a90701cc33879a5be8d28/node_modules/@wirecat/cli-core/dist/sends/guard.js
|
|
70
|
+
import { randomUUID } from "node:crypto";
|
|
71
|
+
|
|
72
|
+
// node_modules/.pnpm/@wirecat+cli-core@0.507.0_@wirecat+cli-base@0.68.1_commander@15.0.0_esbuild@0.28.2_type_d5beb0101c7a90701cc33879a5be8d28/node_modules/@wirecat/cli-core/dist/sends/flood.js
|
|
73
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
74
|
+
import { dirname, join } from "node:path";
|
|
75
|
+
var MOST_DEADLINES = 50;
|
|
76
|
+
var LIMITED_HOLD_MS = 60 * 60 * 1e3;
|
|
77
|
+
var FROZEN_HOLD_MS = 24 * 60 * 60 * 1e3;
|
|
78
|
+
var NO_END = "9999-12-31T23:59:59.999Z";
|
|
79
|
+
var holdEnd = /* @__PURE__ */ __name((block, show = (iso) => iso) => block.until === null ? "until lifted" : `until ${show(block.until)}`, "holdEnd");
|
|
80
|
+
var floodPathFor = /* @__PURE__ */ __name((app, profile, env = process.env) => join(resolvePaths({ appName: app.appName, prefix: app.envPrefix, env }).state, "flood", `${profile}.json`), "floodPathFor");
|
|
81
|
+
var FloodMemory = class {
|
|
82
|
+
static {
|
|
83
|
+
__name(this, "FloodMemory");
|
|
84
|
+
}
|
|
85
|
+
path;
|
|
86
|
+
now;
|
|
87
|
+
constructor(path, now = Date.now) {
|
|
88
|
+
this.path = path;
|
|
89
|
+
this.now = now;
|
|
90
|
+
}
|
|
91
|
+
/** What is still in force; anything expired is left out. */
|
|
92
|
+
read() {
|
|
93
|
+
const now = this.now();
|
|
94
|
+
let raw;
|
|
95
|
+
try {
|
|
96
|
+
raw = JSON.parse(readFileSync(this.path, "utf8"));
|
|
97
|
+
} catch {
|
|
98
|
+
return { deadlines: [] };
|
|
99
|
+
}
|
|
100
|
+
const alive = /* @__PURE__ */ __name((until) => typeof until === "string" && Date.parse(until) > now, "alive");
|
|
101
|
+
const deadlines = Array.isArray(raw.deadlines) ? raw.deadlines.filter((one) => typeof one?.operation === "string" && alive(one.until)) : [];
|
|
102
|
+
const block = raw.sendBlock && (raw.sendBlock.until === NO_END ? { ...raw.sendBlock, until: null } : raw.sendBlock);
|
|
103
|
+
const held = block && (block.until === null || alive(block.until));
|
|
104
|
+
return { deadlines, ...held ? { sendBlock: block } : {} };
|
|
105
|
+
}
|
|
106
|
+
/** The deadline this call is still under: one for its chat, or one for the call in every chat. */
|
|
107
|
+
owed(operation, chatId) {
|
|
108
|
+
return this.read().deadlines.filter((one) => one.operation === operation && (one.chatId === void 0 || one.chatId === chatId)).sort((a, b) => b.until.localeCompare(a.until))[0];
|
|
109
|
+
}
|
|
110
|
+
remember(deadline) {
|
|
111
|
+
const { waitMs, ...rest } = deadline;
|
|
112
|
+
const kept = { ...rest, until: new Date(this.now() + waitMs).toISOString() };
|
|
113
|
+
const state = this.read();
|
|
114
|
+
const others = state.deadlines.filter((one) => one.operation !== kept.operation || one.chatId !== kept.chatId);
|
|
115
|
+
const deadlines = [...others, kept].sort((a, b) => b.until.localeCompare(a.until)).slice(0, MOST_DEADLINES);
|
|
116
|
+
this.#write({ ...state, deadlines });
|
|
117
|
+
return kept;
|
|
118
|
+
}
|
|
119
|
+
sendBlock() {
|
|
120
|
+
return this.read().sendBlock;
|
|
121
|
+
}
|
|
122
|
+
/** A ban the messenger gave no end for holds until lifted; a spam limit or a freeze without one, for a while. */
|
|
123
|
+
block(block) {
|
|
124
|
+
const now = this.now();
|
|
125
|
+
const holdMs = { limited: LIMITED_HOLD_MS, frozen: FROZEN_HOLD_MS, banned: void 0 }[block.state];
|
|
126
|
+
const kept = {
|
|
127
|
+
...block,
|
|
128
|
+
since: new Date(now).toISOString(),
|
|
129
|
+
until: block.until ?? (holdMs === void 0 ? null : new Date(now + holdMs).toISOString())
|
|
130
|
+
};
|
|
131
|
+
this.#write({ ...this.read(), sendBlock: kept });
|
|
132
|
+
return kept;
|
|
133
|
+
}
|
|
134
|
+
/** Lifts only a block of this state: an account found not frozen may still be limited for spam. */
|
|
135
|
+
unblock(state) {
|
|
136
|
+
const { sendBlock, ...rest } = this.read();
|
|
137
|
+
if (sendBlock?.state === state)
|
|
138
|
+
this.#write(rest);
|
|
139
|
+
}
|
|
140
|
+
/** Forgets every wait and lifts the hold, and answers with what was still in force. */
|
|
141
|
+
clear() {
|
|
142
|
+
const cleared = this.read();
|
|
143
|
+
this.#write({ deadlines: [] });
|
|
144
|
+
return cleared;
|
|
145
|
+
}
|
|
146
|
+
#write({ sendBlock, ...rest }) {
|
|
147
|
+
const state = sendBlock ? { ...rest, sendBlock: { ...sendBlock, until: sendBlock.until ?? NO_END } } : rest;
|
|
148
|
+
mkdirSync(dirname(this.path), { recursive: true, mode: 448 });
|
|
149
|
+
const temporary = `${this.path}.${process.pid}.tmp`;
|
|
150
|
+
writeFileSync(temporary, `${JSON.stringify(state)}
|
|
151
|
+
`, { mode: 384 });
|
|
152
|
+
renameSync(temporary, this.path);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// node_modules/.pnpm/@wirecat+cli-core@0.507.0_@wirecat+cli-base@0.68.1_commander@15.0.0_esbuild@0.28.2_type_d5beb0101c7a90701cc33879a5be8d28/node_modules/@wirecat/cli-core/dist/sends/journal.js
|
|
157
|
+
import { appendFileSync, mkdirSync as mkdirSync3, readFileSync as readFileSync2 } from "node:fs";
|
|
158
|
+
import { dirname as dirname3, join as join2 } from "node:path";
|
|
159
|
+
|
|
160
|
+
// node_modules/.pnpm/@wirecat+cli-core@0.507.0_@wirecat+cli-base@0.68.1_commander@15.0.0_esbuild@0.28.2_type_d5beb0101c7a90701cc33879a5be8d28/node_modules/@wirecat/cli-core/dist/sends/file-lock.js
|
|
161
|
+
import { closeSync, mkdirSync as mkdirSync2, openSync, statSync, unlinkSync } from "node:fs";
|
|
162
|
+
import { dirname as dirname2 } from "node:path";
|
|
163
|
+
var LOCK_WAIT_MS = 5e3;
|
|
164
|
+
var LOCK_STALE_MS = 3e4;
|
|
165
|
+
var RETRY_MS = 20;
|
|
166
|
+
var pause = new Int32Array(new SharedArrayBuffer(4));
|
|
167
|
+
var withFileLock = /* @__PURE__ */ __name((path, what2, body, waitMs = LOCK_WAIT_MS) => {
|
|
168
|
+
const lock = lockFor(path);
|
|
169
|
+
const deadline = Date.now() + waitMs;
|
|
170
|
+
while (!taken(lock)) {
|
|
171
|
+
if (Date.now() >= deadline)
|
|
172
|
+
throw lockTimeout(what2, waitMs, lock);
|
|
173
|
+
Atomics.wait(pause, 0, 0, RETRY_MS);
|
|
174
|
+
}
|
|
175
|
+
return holding(lock, body);
|
|
176
|
+
}, "withFileLock");
|
|
177
|
+
var withFileLockAsync = /* @__PURE__ */ __name(async (path, what2, body, waitMs = LOCK_WAIT_MS, sleep = realSleep) => {
|
|
178
|
+
const lock = lockFor(path);
|
|
179
|
+
const deadline = Date.now() + waitMs;
|
|
180
|
+
while (!taken(lock)) {
|
|
181
|
+
if (Date.now() >= deadline)
|
|
182
|
+
throw lockTimeout(what2, waitMs, lock);
|
|
183
|
+
await sleep(RETRY_MS);
|
|
184
|
+
}
|
|
185
|
+
return holding(lock, body);
|
|
186
|
+
}, "withFileLockAsync");
|
|
187
|
+
var lockFor = /* @__PURE__ */ __name((path) => {
|
|
188
|
+
mkdirSync2(dirname2(path), { recursive: true, mode: 448 });
|
|
189
|
+
return `${path}.lock`;
|
|
190
|
+
}, "lockFor");
|
|
191
|
+
var taken = /* @__PURE__ */ __name((lock) => {
|
|
192
|
+
for (; ; ) {
|
|
193
|
+
try {
|
|
194
|
+
closeSync(openSync(lock, "wx", 384));
|
|
195
|
+
return true;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (error.code !== "EEXIST")
|
|
198
|
+
throw error;
|
|
199
|
+
if (Date.now() - lockTime(lock) <= LOCK_STALE_MS)
|
|
200
|
+
return false;
|
|
201
|
+
try {
|
|
202
|
+
unlinkSync(lock);
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}, "taken");
|
|
208
|
+
var holding = /* @__PURE__ */ __name((lock, body) => {
|
|
209
|
+
try {
|
|
210
|
+
return body();
|
|
211
|
+
} finally {
|
|
212
|
+
try {
|
|
213
|
+
unlinkSync(lock);
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}, "holding");
|
|
218
|
+
var lockTimeout = /* @__PURE__ */ __name((what2, waitMs, lock) => new CliError("timeout", `${what2} stayed locked for ${waitMs / 1e3}s (${lock})`), "lockTimeout");
|
|
219
|
+
var lockTime = /* @__PURE__ */ __name((lock) => {
|
|
220
|
+
try {
|
|
221
|
+
return statSync(lock).mtimeMs;
|
|
222
|
+
} catch {
|
|
223
|
+
return Date.now();
|
|
224
|
+
}
|
|
225
|
+
}, "lockTime");
|
|
226
|
+
|
|
227
|
+
// node_modules/.pnpm/@wirecat+cli-core@0.507.0_@wirecat+cli-base@0.68.1_commander@15.0.0_esbuild@0.28.2_type_d5beb0101c7a90701cc33879a5be8d28/node_modules/@wirecat/cli-core/dist/sends/journal.js
|
|
228
|
+
var sendsPathFor = /* @__PURE__ */ __name((app, profile, env = process.env) => join2(resolvePaths({ appName: app.appName, prefix: app.envPrefix, env }).state, "sends", `${profile}.jsonl`), "sendsPathFor");
|
|
229
|
+
var SendJournal = class {
|
|
230
|
+
static {
|
|
231
|
+
__name(this, "SendJournal");
|
|
232
|
+
}
|
|
233
|
+
path;
|
|
234
|
+
resource;
|
|
235
|
+
constructor(path, resource = { service: "unknown", version: "unknown" }) {
|
|
236
|
+
this.path = path;
|
|
237
|
+
this.resource = resource;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Runs `body` with the journal to itself, across processes: two sends at the limit must not both
|
|
241
|
+
* read "one left". A lock file opened with `wx`, because `flock` is not there on Windows.
|
|
242
|
+
*/
|
|
243
|
+
locked(body) {
|
|
244
|
+
return withFileLock(this.path, "the send journal", body);
|
|
245
|
+
}
|
|
246
|
+
/** `locked` without blocking the thread while another process holds the journal: what `serve` uses. */
|
|
247
|
+
lockedAsync(body) {
|
|
248
|
+
return withFileLockAsync(this.path, "the send journal", body);
|
|
249
|
+
}
|
|
250
|
+
append(entry) {
|
|
251
|
+
const { at, ...attributes } = entry;
|
|
252
|
+
const { runId, command, traceId } = currentRun();
|
|
253
|
+
const record = logRecordOf({
|
|
254
|
+
severity: entry.outcome === "sent" || entry.outcome === "reserved" ? "info" : "warn",
|
|
255
|
+
event: logAttributes({ event: "send.recorded", ...attributes }),
|
|
256
|
+
time: at,
|
|
257
|
+
runId,
|
|
258
|
+
...traceId === void 0 ? {} : { traceId },
|
|
259
|
+
command,
|
|
260
|
+
resource: this.resource,
|
|
261
|
+
allow: LOG_ATTRIBUTES
|
|
262
|
+
});
|
|
263
|
+
mkdirSync3(dirname3(this.path), { recursive: true, mode: 448 });
|
|
264
|
+
appendFileSync(this.path, `${JSON.stringify(record)}
|
|
265
|
+
`, { mode: 384 });
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Oldest first, each reservation folded into its outcome; one with no outcome yet is in flight,
|
|
269
|
+
* or its process died. A line that does not parse is skipped: a torn last write must not block sending.
|
|
270
|
+
*/
|
|
271
|
+
entries(filter) {
|
|
272
|
+
const lines = this.#lines();
|
|
273
|
+
const settled2 = new Set(lines.filter((entry) => entry.outcome !== "reserved").map((entry) => entry.reservation));
|
|
274
|
+
const entries = lines.filter((entry) => entry.outcome !== "reserved" || !settled2.has(entry.reservation)).map(({ reservation, ...entry }) => entry.outcome === "reserved" ? { ...entry, reservation } : entry).map(withSendId);
|
|
275
|
+
return filter === void 0 ? entries : entries.filter((entry) => (filter.outcome === void 0 || entry.outcome === filter.outcome) && (filter.chatId === void 0 || entry.chatId === filter.chatId) && (filter.operationId === void 0 || entry.operationId === filter.operationId) && (filter.errorCode === void 0 || entry.errorCode === filter.errorCode) && (filter.key === void 0 || entry.key === filter.key));
|
|
276
|
+
}
|
|
277
|
+
#lines() {
|
|
278
|
+
let text;
|
|
279
|
+
try {
|
|
280
|
+
text = readFileSync2(this.path, "utf8");
|
|
281
|
+
} catch {
|
|
282
|
+
return [];
|
|
283
|
+
}
|
|
284
|
+
return text.split("\n").filter((line) => line.trim() !== "").flatMap((line) => {
|
|
285
|
+
try {
|
|
286
|
+
return [entryOf(JSON.parse(line))];
|
|
287
|
+
} catch {
|
|
288
|
+
return [];
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
var entryOf = /* @__PURE__ */ __name((line) => {
|
|
294
|
+
if (!("attributes" in line))
|
|
295
|
+
return line;
|
|
296
|
+
const { event: _, ...attributes } = line.attributes;
|
|
297
|
+
return { at: line.time, ...fromLogAttributes(attributes) };
|
|
298
|
+
}, "entryOf");
|
|
299
|
+
var withSendId = /* @__PURE__ */ __name((entry) => {
|
|
300
|
+
const { cid, ...rest } = entry;
|
|
301
|
+
return cid === void 0 || rest.sendId !== void 0 ? rest : { ...rest, sendId: String(cid) };
|
|
302
|
+
}, "withSendId");
|
|
303
|
+
|
|
304
|
+
// node_modules/.pnpm/@wirecat+cli-core@0.507.0_@wirecat+cli-base@0.68.1_commander@15.0.0_esbuild@0.28.2_type_d5beb0101c7a90701cc33879a5be8d28/node_modules/@wirecat/cli-core/dist/sends/recipients.js
|
|
305
|
+
import { existsSync, readFileSync as readFileSync3, rmSync } from "node:fs";
|
|
306
|
+
import { join as join3 } from "node:path";
|
|
307
|
+
var recipientsPathFor = /* @__PURE__ */ __name((app, profile, env = process.env) => join3(resolvePaths({ appName: app.appName, prefix: app.envPrefix, env }).state, "profiles", `${profile}.recipients.json`), "recipientsPathFor");
|
|
308
|
+
var RecipientList = class {
|
|
309
|
+
static {
|
|
310
|
+
__name(this, "RecipientList");
|
|
311
|
+
}
|
|
312
|
+
path;
|
|
313
|
+
command;
|
|
314
|
+
lockWaitMs;
|
|
315
|
+
/** `command` names the program in the hint that turns the list off. */
|
|
316
|
+
constructor(path, command, lockWaitMs) {
|
|
317
|
+
this.path = path;
|
|
318
|
+
this.command = command;
|
|
319
|
+
this.lockWaitMs = lockWaitMs;
|
|
320
|
+
}
|
|
321
|
+
/** `undefined` when the list is off. A file that cannot be read refuses rather than opening up. */
|
|
322
|
+
read() {
|
|
323
|
+
if (!existsSync(this.path))
|
|
324
|
+
return void 0;
|
|
325
|
+
try {
|
|
326
|
+
const parsed = JSON.parse(readFileSync3(this.path, "utf8"));
|
|
327
|
+
if (!Array.isArray(parsed.chats))
|
|
328
|
+
throw new Error("no `chats` array");
|
|
329
|
+
return parsed.chats;
|
|
330
|
+
} catch (error) {
|
|
331
|
+
throw new CliError("configuration_error", `the recipient list ${this.path} cannot be read (${error instanceof Error ? error.message : String(error)}) \u2014 fix the file or turn the list off with \`${this.command} recipients clear\``);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
list(filter = {}) {
|
|
335
|
+
const chats = this.read();
|
|
336
|
+
return {
|
|
337
|
+
state: chats === void 0 ? "off" : chats.length === 0 ? "empty" : "on",
|
|
338
|
+
items: (chats ?? []).filter((chat) => (filter.id === void 0 || chat.id === filter.id) && (filter.title === void 0 || chat.title?.toLocaleLowerCase() === filter.title.toLocaleLowerCase()))
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
/** Whether it was new. Adding one again fills in a partner an older entry was stored without. */
|
|
342
|
+
add(recipient) {
|
|
343
|
+
return this.#locked(() => this.#added(recipient));
|
|
344
|
+
}
|
|
345
|
+
/** `add` waiting for the lock on timers: `recipients add` connects, so it runs inside `serve` when one holds the account. */
|
|
346
|
+
addAsync(recipient) {
|
|
347
|
+
return withFileLockAsync(this.path, "the recipient list", () => this.#added(recipient), this.lockWaitMs);
|
|
348
|
+
}
|
|
349
|
+
#added(recipient) {
|
|
350
|
+
const chats = this.read() ?? [];
|
|
351
|
+
const known = chats.find((chat) => chat.id === recipient.id);
|
|
352
|
+
if (known) {
|
|
353
|
+
if (recipient.partnerId && !known.partnerId) {
|
|
354
|
+
this.#write(chats.map((chat) => chat === known ? { ...known, partnerId: recipient.partnerId } : chat));
|
|
355
|
+
}
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
this.#write([...chats, recipient]);
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
/** The id or the title as it was stored; `undefined` when neither matched. */
|
|
362
|
+
remove(reference) {
|
|
363
|
+
return this.#locked(() => {
|
|
364
|
+
const chats = this.read() ?? [];
|
|
365
|
+
const wanted = reference.trim();
|
|
366
|
+
const gone = chats.find((chat) => chat.id === wanted) ?? chats.find((chat) => chat.title?.toLocaleLowerCase() === wanted.toLocaleLowerCase());
|
|
367
|
+
if (gone)
|
|
368
|
+
this.#write(chats.filter((chat) => chat !== gone));
|
|
369
|
+
return gone;
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* The chat the messenger now names `to`: its entry, and a partner of that id, follow it. Only an entry already there
|
|
374
|
+
* changes, and no list is created, so the list never opens up. Whether anything changed.
|
|
375
|
+
*/
|
|
376
|
+
async rekey(from, to) {
|
|
377
|
+
if (from === to)
|
|
378
|
+
return false;
|
|
379
|
+
return withFileLockAsync(this.path, "the recipient list", () => {
|
|
380
|
+
const chats = this.read();
|
|
381
|
+
if (!chats?.some((chat) => chat.id === from || chat.partnerId === from))
|
|
382
|
+
return false;
|
|
383
|
+
const held = chats.some((chat) => chat.id === to);
|
|
384
|
+
this.#write(chats.filter((chat) => !(held && chat.id === from)).map((chat) => ({
|
|
385
|
+
...chat,
|
|
386
|
+
...chat.id === from ? { id: to } : {},
|
|
387
|
+
...chat.partnerId === from ? { partnerId: to } : {}
|
|
388
|
+
})));
|
|
389
|
+
return true;
|
|
390
|
+
}, this.lockWaitMs);
|
|
391
|
+
}
|
|
392
|
+
/** Whether there was a list to turn off. */
|
|
393
|
+
off() {
|
|
394
|
+
return this.#locked(() => {
|
|
395
|
+
if (!existsSync(this.path))
|
|
396
|
+
return false;
|
|
397
|
+
rmSync(this.path);
|
|
398
|
+
return true;
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Every change reads and writes under one lock: a `serve` re-keying while the owner removes a chat must not write
|
|
403
|
+
* the removed chat back. A lock held too long refuses the change and leaves the list as it was. Blocks the thread
|
|
404
|
+
* while it waits: for callers that cannot be async, and `recipients remove` and `clear`, which never
|
|
405
|
+
* connect and so never run inside `serve`.
|
|
406
|
+
*/
|
|
407
|
+
#locked(body) {
|
|
408
|
+
return withFileLock(this.path, "the recipient list", body, this.lockWaitMs);
|
|
409
|
+
}
|
|
410
|
+
#write(chats) {
|
|
411
|
+
writeSecurely(this.path, `${JSON.stringify({ chats }, null, 2)}
|
|
412
|
+
`, 384);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// node_modules/.pnpm/@wirecat+cli-core@0.507.0_@wirecat+cli-base@0.68.1_commander@15.0.0_esbuild@0.28.2_type_d5beb0101c7a90701cc33879a5be8d28/node_modules/@wirecat/cli-core/dist/sends/guard.js
|
|
417
|
+
var HOUR_MS = 60 * 60 * 1e3;
|
|
418
|
+
var READS_UNDER_HOLD = /* @__PURE__ */ new Set([
|
|
419
|
+
"attachments.extract",
|
|
420
|
+
"conversations.embeddings",
|
|
421
|
+
"conversations.links",
|
|
422
|
+
"messages.server-search",
|
|
423
|
+
"messages.sync-first",
|
|
424
|
+
"stats.contacts.top.sync-first",
|
|
425
|
+
"stats.messages.counters.sync",
|
|
426
|
+
"stats.messages.show.sync-first",
|
|
427
|
+
"stats.messages.top.sync-first",
|
|
428
|
+
"store.gaps.repair"
|
|
429
|
+
]);
|
|
430
|
+
var countsTowardLimit = /* @__PURE__ */ __name(({ kind = "message", action, notify }) => kind === "message" || kind === "forward" || kind === "edit" || kind === "delete" || kind === "pin" && notify === true || action === "create" || action === "members.add" || action === "requests.accept" || action === "join" || action === "contact-import", "countsTowardLimit");
|
|
431
|
+
var weightOf = /* @__PURE__ */ __name(({ kind = "message", action, count, people }) => kind === "delete" || kind === "message" && action === void 0 ? Math.max(1, count ?? 1) : action === "create" || action === "members.add" || action === "requests.accept" || action === "contact-import" ? Math.max(1, people ?? count ?? 1) : 1, "weightOf");
|
|
432
|
+
var timeOf = /* @__PURE__ */ __name((entry) => Date.parse(entry.scheduledFor ?? entry.at), "timeOf");
|
|
433
|
+
var ceilingRefusal = /* @__PURE__ */ __name((key, command) => new CliError("permission_error", `${key} is not allowed: setup's answer keeps every WireCat tool read-only; the owner changes it at a terminal with \`${command} setup\``, { permission: key }), "ceilingRefusal");
|
|
434
|
+
var levelRefusal = /* @__PURE__ */ __name((refused, fix = `${refused.command} ${refused.profile} config set permissions.${refused.key} allow`) => refused.named === CEILING_KEY ? ceilingRefusal(refused.key, refused.command) : new CliError("permission_error", `profile ${refused.profile} does not let ${refused.key} write (permissions.${refused.named} is ${refused.level}, from the ${refused.permissionSources[refused.named ?? ""] ?? "default"}); to allow it: ${fix}`, { permission: refused.key }), "levelRefusal");
|
|
435
|
+
var sendGuard = /* @__PURE__ */ __name(({ profile, recipients, command = recipients.command, readOnlyLock, permissions, permissionSources = {}, permissionDefaults, permissionFix, ask = refuseToAsk, sendsPerHour, journal, flood, warn, now = /* @__PURE__ */ __name(() => /* @__PURE__ */ new Date(), "now") }) => {
|
|
436
|
+
const currentPermissions = /* @__PURE__ */ __name(() => typeof permissions === "function" ? permissions() : permissions, "currentPermissions");
|
|
437
|
+
let reservation;
|
|
438
|
+
const answered = /* @__PURE__ */ new WeakSet();
|
|
439
|
+
const refuseReadOnly = /* @__PURE__ */ __name(() => {
|
|
440
|
+
if (readOnlyLock)
|
|
441
|
+
throw readOnlyRefusal(readOnlyLock, MESSENGER_CHANGE_REFUSAL);
|
|
442
|
+
}, "refuseReadOnly");
|
|
443
|
+
const refuseHeld = /* @__PURE__ */ __name(({ key }) => {
|
|
444
|
+
if (key !== void 0 && READS_UNDER_HOLD.has(key))
|
|
445
|
+
return;
|
|
446
|
+
const block = flood?.sendBlock();
|
|
447
|
+
if (!block)
|
|
448
|
+
return;
|
|
449
|
+
throw new CliError("permission_error", `profile ${profile} holds its writes ${holdEnd(block)}: ${block.hint} \u2014 reads still work; do not retry: the owner checks the account with \`${command} ${profile} doctor --online\`, and lifts the hold with \`${command} ${profile} flood clear\` once it is over`, { sendBlock: block, standing: { state: block.state, hint: block.hint } });
|
|
450
|
+
}, "refuseHeld");
|
|
451
|
+
const permitted = /* @__PURE__ */ __name((request) => {
|
|
452
|
+
const { chatId, kind = "message", action, personIds } = request;
|
|
453
|
+
refuseReadOnly();
|
|
454
|
+
const levels = currentPermissions();
|
|
455
|
+
if (levels !== void 0) {
|
|
456
|
+
const key = request.key ?? keyForWrite(kind, action);
|
|
457
|
+
const { level, key: named } = levelFor(levels, key, permissionDefaults);
|
|
458
|
+
if (level === "deny" || level === "readonly")
|
|
459
|
+
throw levelRefusal({ profile, command, key, level, named, permissionSources }, permissionFix?.(request, key));
|
|
460
|
+
if (level === "ask" && !answered.has(request)) {
|
|
461
|
+
throw new CliError("confirmation_required", `${key} asks before it acts, and it was not asked`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const allowed = recipients.read();
|
|
465
|
+
if (chatId !== null && allowed && !allowed.some((chat) => chat.id === chatId)) {
|
|
466
|
+
throw new CliError("confirmation_required", `chat ${chatId} is not on the recipient list of profile ${profile} \u2014 the owner adds it with \`${command} ${profile} recipients add ${chatId}\``, { chat_id: chatId });
|
|
467
|
+
}
|
|
468
|
+
const strangers = allowed ? (personIds ?? []).filter((id) => !allowed.some((chat) => chat.partnerId === id)) : [];
|
|
469
|
+
if (strangers.length > 0) {
|
|
470
|
+
throw new CliError("confirmation_required", `${strangers.join(", ")} ${strangers.length === 1 ? "is" : "are"} not on the recipient list of profile ${profile} \u2014 the owner adds the one-to-one chat with each person (\`${command} ${profile} recipients add <chat>\`); a chat added to the list earlier is added again to be recognised`, { person_ids: strangers });
|
|
471
|
+
}
|
|
472
|
+
}, "permitted");
|
|
473
|
+
const withinLimit = /* @__PURE__ */ __name((request, entries) => {
|
|
474
|
+
const { chatId, sendId } = request;
|
|
475
|
+
const asked = { ...request, people: request.personIds?.length ?? request.count };
|
|
476
|
+
const count = weightOf(asked);
|
|
477
|
+
if (count > sendsPerHour) {
|
|
478
|
+
throw new CliError("rate_limited", `${count} at once is more than the hourly limit of profile ${profile} (sendsPerHour ${sendsPerHour}) \u2014 do fewer at a time`);
|
|
479
|
+
}
|
|
480
|
+
const at = request.scheduledFor ? Date.parse(request.scheduledFor) : now().getTime();
|
|
481
|
+
const counted = entries.filter(countsTowardLimit).filter((entry) => entry.outcome === "sent" || entry.outcome === "outcome_unknown" || entry.outcome === "reserved").filter((entry) => Math.abs(timeOf(entry) - at) < HOUR_MS);
|
|
482
|
+
const unsure = /* @__PURE__ */ __name((entry) => entry.outcome === "outcome_unknown", "unsure");
|
|
483
|
+
const same = /* @__PURE__ */ __name((a, b) => a.sendId !== void 0 && a.sendId === b.sendId && a.chatId === b.chatId, "same");
|
|
484
|
+
const last = [...counted].reverse().find((entry) => same(entry, { chatId, sendId }));
|
|
485
|
+
if (sendId !== void 0 && last && unsure(last)) {
|
|
486
|
+
if (last.sendAs !== request.sendAs) {
|
|
487
|
+
throw new CliError("validation_error", "this send id was tried under another identity; repeat it with the same --send-as, or it may post twice", { operation_id: sendId, retryable: false });
|
|
488
|
+
}
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
const recent = counted.filter((entry, index) => !(unsure(entry) && counted.slice(index + 1).some((later) => same(later, entry)))).map((entry) => ({ time: timeOf(entry), weight: weightOf(entry) })).sort((a, b) => a.time - b.time);
|
|
492
|
+
const used = recent.reduce((sum, { weight }) => sum + weight, 0);
|
|
493
|
+
if (used + count > sendsPerHour) {
|
|
494
|
+
let freed = 0;
|
|
495
|
+
const opening = recent.find(({ weight }) => {
|
|
496
|
+
freed += weight;
|
|
497
|
+
return used - freed + count <= sendsPerHour;
|
|
498
|
+
});
|
|
499
|
+
const nextMs = (opening?.time ?? 0) + HOUR_MS;
|
|
500
|
+
const next = new Date(nextMs).toISOString();
|
|
501
|
+
throw new CliError("rate_limited", `profile ${profile} has sent ${used} messages in the hour around ${new Date(at).toISOString()}, and its limit is ${sendsPerHour} (sendsPerHour) \u2014 the next send is possible at ${next}`, { retry_after_ms: nextMs - now().getTime(), retry_at: next });
|
|
502
|
+
}
|
|
503
|
+
return true;
|
|
504
|
+
}, "withinLimit");
|
|
505
|
+
const counting = /* @__PURE__ */ __name((request) => {
|
|
506
|
+
permitted(request);
|
|
507
|
+
refuseHeld(request);
|
|
508
|
+
const asked = { ...request, people: request.personIds?.length ?? request.count };
|
|
509
|
+
const creatingTopic = request.kind === "chat" && request.action === "topic-create";
|
|
510
|
+
if (!countsTowardLimit(asked) && !creatingTopic)
|
|
511
|
+
return void 0;
|
|
512
|
+
const checked = /* @__PURE__ */ __name((entries) => {
|
|
513
|
+
if (!creatingTopic)
|
|
514
|
+
return withinLimit(request, entries);
|
|
515
|
+
if (request.sendId === void 0)
|
|
516
|
+
throw new CliError("validation_error", "a topic creation needs an attempt id");
|
|
517
|
+
if (entries.some((entry) => entry.action === "topic-create" && entry.sendId === request.sendId && ["sent", "outcome_unknown", "reserved"].includes(entry.outcome))) {
|
|
518
|
+
throw new CliError("validation_error", "this topic creation was already attempted; check topics list and do not repeat an unknown creation", { operation_id: request.sendId, retryable: false });
|
|
519
|
+
}
|
|
520
|
+
return true;
|
|
521
|
+
}, "checked");
|
|
522
|
+
const reserving = /* @__PURE__ */ __name(() => {
|
|
523
|
+
if (!checked(journal.entries()))
|
|
524
|
+
return;
|
|
525
|
+
const id = randomUUID();
|
|
526
|
+
const { chatId, kind, action, sendId, operationId, scheduledFor, notify } = request;
|
|
527
|
+
journal.append({
|
|
528
|
+
at: now().toISOString(),
|
|
529
|
+
profile,
|
|
530
|
+
chatId,
|
|
531
|
+
outcome: "reserved",
|
|
532
|
+
reservation: id,
|
|
533
|
+
...kind ? { kind } : {},
|
|
534
|
+
...action ? { action } : {},
|
|
535
|
+
...asked.people === void 0 ? {} : kind === "delete" || kind === "message" || kind === void 0 ? { count: asked.people } : { people: asked.people },
|
|
536
|
+
...sendId === void 0 ? {} : { sendId },
|
|
537
|
+
...operationId === void 0 ? {} : { operationId },
|
|
538
|
+
...scheduledFor ? { scheduledFor } : {},
|
|
539
|
+
...notify === void 0 ? {} : { notify }
|
|
540
|
+
});
|
|
541
|
+
reservation = id;
|
|
542
|
+
}, "reserving");
|
|
543
|
+
return { checked, reserving };
|
|
544
|
+
}, "counting");
|
|
545
|
+
return {
|
|
546
|
+
ask: /* @__PURE__ */ __name(async (request) => {
|
|
547
|
+
refuseReadOnly();
|
|
548
|
+
const levels = currentPermissions();
|
|
549
|
+
if (levels === void 0)
|
|
550
|
+
return;
|
|
551
|
+
const key = request.key ?? keyForWrite(request.kind ?? "message", request.action);
|
|
552
|
+
if (levelFor(levels, key, permissionDefaults).level !== "ask")
|
|
553
|
+
return;
|
|
554
|
+
refuseHeld(request);
|
|
555
|
+
await ask(key, request);
|
|
556
|
+
answered.add(request);
|
|
557
|
+
}, "ask"),
|
|
558
|
+
check: /* @__PURE__ */ __name((request, { reserve = true } = {}) => {
|
|
559
|
+
const counted = counting(request);
|
|
560
|
+
if (!counted)
|
|
561
|
+
return;
|
|
562
|
+
if (reserve)
|
|
563
|
+
journal.locked(counted.reserving);
|
|
564
|
+
else
|
|
565
|
+
counted.checked(journal.entries());
|
|
566
|
+
}, "check"),
|
|
567
|
+
reserve: /* @__PURE__ */ __name(async (request) => {
|
|
568
|
+
const counted = counting(request);
|
|
569
|
+
if (counted)
|
|
570
|
+
await journal.lockedAsync(counted.reserving);
|
|
571
|
+
}, "reserve"),
|
|
572
|
+
// After a send, a failure to write here must not become the command's answer: the message is
|
|
573
|
+
// already with a person, and an error would invite the caller to send it again.
|
|
574
|
+
record: /* @__PURE__ */ __name((entry) => {
|
|
575
|
+
const { personIds: _, ...kept } = entry;
|
|
576
|
+
const settles = entry.outcome === "refused" ? void 0 : reservation;
|
|
577
|
+
reservation = void 0;
|
|
578
|
+
try {
|
|
579
|
+
journal.append({ at: now().toISOString(), profile, ...kept, ...settles ? { reservation: settles } : {} });
|
|
580
|
+
} catch (error) {
|
|
581
|
+
warn(`this send is not in the send journal: ${journalFailure(error)}`, {
|
|
582
|
+
event: "warning",
|
|
583
|
+
code: "send_not_journaled",
|
|
584
|
+
operation: "sends.journal"
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
}, "record")
|
|
588
|
+
};
|
|
589
|
+
}, "sendGuard");
|
|
590
|
+
var refuseToAsk = /* @__PURE__ */ __name(async (key) => {
|
|
591
|
+
throw new CliError("confirmation_required", `${key} asks before it acts (its permission level is ask), and nobody is here to answer \u2014 add ${skipFlagFor(key)} to go ahead`);
|
|
592
|
+
}, "refuseToAsk");
|
|
593
|
+
var guardFor = /* @__PURE__ */ __name((app, settings, warn, env = process.env, ask) => sendGuard({
|
|
594
|
+
profile: settings.profile,
|
|
595
|
+
command: app.command,
|
|
596
|
+
...settings.readOnlyLock ? { readOnlyLock: settings.readOnlyLock } : {},
|
|
597
|
+
permissions: /* @__PURE__ */ __name(() => settings.permissions, "permissions"),
|
|
598
|
+
permissionSources: settings.permissionSources,
|
|
599
|
+
...settings.permissionDefaults ? { permissionDefaults: settings.permissionDefaults } : {},
|
|
600
|
+
...ask ? { ask } : {},
|
|
601
|
+
sendsPerHour: settings.sendsPerHour,
|
|
602
|
+
journal: new SendJournal(sendsPathFor(app, settings.profile, env), { service: app.command, version: app.version }),
|
|
603
|
+
recipients: new RecipientList(recipientsPathFor(app, settings.profile, env), app.command),
|
|
604
|
+
flood: new FloodMemory(floodPathFor(app, settings.profile, env)),
|
|
605
|
+
warn
|
|
606
|
+
}), "guardFor");
|
|
607
|
+
var journalFailure = /* @__PURE__ */ __name((error) => error?.code ?? (error instanceof Error ? error.name : "error"), "journalFailure");
|
|
608
|
+
|
|
609
|
+
// node_modules/.pnpm/@wirecat+cli-core@0.507.0_@wirecat+cli-base@0.68.1_commander@15.0.0_esbuild@0.28.2_type_d5beb0101c7a90701cc33879a5be8d28/node_modules/@wirecat/cli-core/dist/cli/messenger/ask.js
|
|
610
|
+
import { openSync as openSync2 } from "node:fs";
|
|
611
|
+
import { createInterface } from "node:readline";
|
|
612
|
+
import { ReadStream } from "node:tty";
|
|
613
|
+
var fromTerminal = /* @__PURE__ */ __name(async (question) => {
|
|
614
|
+
if (!process.stdin.isTTY || !process.stderr.isTTY)
|
|
615
|
+
return null;
|
|
616
|
+
let input;
|
|
617
|
+
try {
|
|
618
|
+
input = new ReadStream(openSync2("/dev/tty", "r"));
|
|
619
|
+
} catch {
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
const reader = createInterface({ input, output: process.stderr, terminal: true });
|
|
623
|
+
try {
|
|
624
|
+
return await new Promise((resolve, reject) => {
|
|
625
|
+
const cancel = /* @__PURE__ */ __name(() => reject(new CliError("cancelled", "cancelled \u2014 nothing was done")), "cancel");
|
|
626
|
+
reader.once("SIGINT", cancel);
|
|
627
|
+
reader.once("close", cancel);
|
|
628
|
+
reader.question(question, resolve);
|
|
629
|
+
});
|
|
630
|
+
} finally {
|
|
631
|
+
reader.close();
|
|
632
|
+
input.destroy();
|
|
633
|
+
}
|
|
634
|
+
}, "fromTerminal");
|
|
635
|
+
var what = /* @__PURE__ */ __name((key, { chatId, count, personIds, forEveryone }) => [
|
|
636
|
+
key,
|
|
637
|
+
forEveryone ? "for everyone" : void 0,
|
|
638
|
+
count !== void 0 ? `${count} ${count === 1 ? "item" : "items"}` : void 0,
|
|
639
|
+
personIds?.length ? `${personIds.length} ${personIds.length === 1 ? "person" : "people"}` : void 0,
|
|
640
|
+
chatId !== null ? `in chat ${chatId}` : void 0
|
|
641
|
+
].filter(Boolean).join(", "), "what");
|
|
642
|
+
var answerOf = /* @__PURE__ */ __name(async (command, question) => {
|
|
643
|
+
const given = command.optsWithGlobals();
|
|
644
|
+
if (given.json === true || given.jsonl === true)
|
|
645
|
+
return null;
|
|
646
|
+
return (environmentOf(command).answer ?? fromTerminal)(question);
|
|
647
|
+
}, "answerOf");
|
|
648
|
+
var terminalAsker = /* @__PURE__ */ __name((command) => async (key, request) => {
|
|
649
|
+
if (environmentOf(command).agent)
|
|
650
|
+
return;
|
|
651
|
+
const flag = skipFlagFor(key);
|
|
652
|
+
const given = command.optsWithGlobals();
|
|
653
|
+
if (flag === "--yes" ? given.yes === true : given.allowDangerous === true)
|
|
654
|
+
return;
|
|
655
|
+
const machine = given.json === true || given.jsonl === true;
|
|
656
|
+
const answer = machine ? null : await (environmentOf(command).answer ?? fromTerminal)(`${what(key, request)} \u2014 go ahead? [y/N] `);
|
|
657
|
+
if (answer === null) {
|
|
658
|
+
throw new CliError("confirmation_required", `${key} asks before it acts (its permission level is ask), and nobody is at a terminal to answer \u2014 add ${flag} to go ahead`, { permission: key });
|
|
659
|
+
}
|
|
660
|
+
if (!/^\s*y(es)?\s*$/i.test(answer))
|
|
661
|
+
throw new CliError("cancelled", `${key}: not done \u2014 the answer was no`);
|
|
662
|
+
}, "terminalAsker");
|
|
663
|
+
|
|
664
|
+
// node_modules/.pnpm/@wirecat+cli-core@0.507.0_@wirecat+cli-base@0.68.1_commander@15.0.0_esbuild@0.28.2_type_d5beb0101c7a90701cc33879a5be8d28/node_modules/@wirecat/cli-core/dist/sends/pace.js
|
|
665
|
+
import { readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
666
|
+
import { join as join4 } from "node:path";
|
|
667
|
+
var DEFAULT_PACE = { perMinute: 60, burst: 20 };
|
|
668
|
+
var pacePathFor = /* @__PURE__ */ __name((app, profile, env = process.env) => join4(resolvePaths({ appName: app.appName, prefix: app.envPrefix, env }).state, "pace", `${profile}.json`), "pacePathFor");
|
|
669
|
+
var Pacer = class {
|
|
670
|
+
static {
|
|
671
|
+
__name(this, "Pacer");
|
|
672
|
+
}
|
|
673
|
+
path;
|
|
674
|
+
rate;
|
|
675
|
+
now;
|
|
676
|
+
constructor(path, rate, now = Date.now) {
|
|
677
|
+
this.path = path;
|
|
678
|
+
this.rate = rate;
|
|
679
|
+
this.now = now;
|
|
680
|
+
}
|
|
681
|
+
get interval() {
|
|
682
|
+
return this.rate.perMinute > 0 ? 6e4 / this.rate.perMinute : 0;
|
|
683
|
+
}
|
|
684
|
+
/** When this call may go; reserved at once, so the next caller in any process lines up behind it. */
|
|
685
|
+
async reserve() {
|
|
686
|
+
const now = this.now();
|
|
687
|
+
if (this.interval === 0)
|
|
688
|
+
return now;
|
|
689
|
+
return withFileLockAsync(this.path, "the request pace", () => {
|
|
690
|
+
const tat = Math.max(this.read(), now);
|
|
691
|
+
this.write(tat + this.interval);
|
|
692
|
+
return Math.max(now, tat - this.rate.burst * this.interval);
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
/** The messenger asked every caller to wait: nothing goes before `now + waitMs`, in any process. */
|
|
696
|
+
async holdFor(waitMs) {
|
|
697
|
+
if (this.interval === 0)
|
|
698
|
+
return;
|
|
699
|
+
await withFileLockAsync(this.path, "the request pace", () => {
|
|
700
|
+
const hold = this.now() + waitMs + this.rate.burst * this.interval;
|
|
701
|
+
this.write(Math.max(this.read(), hold));
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
/** The owner's `flood clear`: every process may go again at once. */
|
|
705
|
+
async reset() {
|
|
706
|
+
await withFileLockAsync(this.path, "the request pace", () => this.write(0));
|
|
707
|
+
}
|
|
708
|
+
read() {
|
|
709
|
+
try {
|
|
710
|
+
const tat = Number(JSON.parse(readFileSync4(this.path, "utf8")).tat);
|
|
711
|
+
return Number.isFinite(tat) ? tat : 0;
|
|
712
|
+
} catch {
|
|
713
|
+
return 0;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
write(tat) {
|
|
717
|
+
const temporary = `${this.path}.${process.pid}.tmp`;
|
|
718
|
+
writeFileSync2(temporary, JSON.stringify({ tat }), { mode: 384 });
|
|
719
|
+
renameSync2(temporary, this.path);
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
// node_modules/.pnpm/@wirecat+cli-core@0.507.0_@wirecat+cli-base@0.68.1_commander@15.0.0_esbuild@0.28.2_type_d5beb0101c7a90701cc33879a5be8d28/node_modules/@wirecat/cli-core/dist/cli/permission-keys.js
|
|
724
|
+
var HOUSEKEEPING = /* @__PURE__ */ new Set([
|
|
725
|
+
"help",
|
|
726
|
+
"guide",
|
|
727
|
+
"config",
|
|
728
|
+
"session",
|
|
729
|
+
"doctor",
|
|
730
|
+
"commands",
|
|
731
|
+
"complete",
|
|
732
|
+
"upgrade",
|
|
733
|
+
"skill",
|
|
734
|
+
"mcp",
|
|
735
|
+
"runs"
|
|
736
|
+
]);
|
|
737
|
+
var commandPathKey = /* @__PURE__ */ __name((path) => path[0] === void 0 || HOUSEKEEPING.has(path[0]) ? null : path.join("."), "commandPathKey");
|
|
738
|
+
var commandPermissions = /* @__PURE__ */ __name(({ keyOf = commandPathKey, extra = [], defaults = {} } = {}) => ({
|
|
739
|
+
keyOf,
|
|
740
|
+
extra: [AUTHORITY_KEY, ...extra],
|
|
741
|
+
defaults: { ...DEFAULT_PERMISSIONS, ...defaults }
|
|
742
|
+
}), "commandPermissions");
|
|
743
|
+
var pathOf = /* @__PURE__ */ __name((command) => {
|
|
744
|
+
const words = [];
|
|
745
|
+
for (let at = command; at?.parent; at = at.parent)
|
|
746
|
+
words.unshift(at.name());
|
|
747
|
+
return words;
|
|
748
|
+
}, "pathOf");
|
|
749
|
+
var rootOf2 = /* @__PURE__ */ __name((command) => {
|
|
750
|
+
let at = command;
|
|
751
|
+
while (at.parent)
|
|
752
|
+
at = at.parent;
|
|
753
|
+
return at;
|
|
754
|
+
}, "rootOf");
|
|
755
|
+
var everyCommand = /* @__PURE__ */ __name((command) => [command, ...command.commands.flatMap(everyCommand)], "everyCommand");
|
|
756
|
+
var knownPermissionKeys = /* @__PURE__ */ __name((command, keyOf = (one) => keyForCommand(pathOf(one)), extra = []) => {
|
|
757
|
+
const known = /* @__PURE__ */ new Set();
|
|
758
|
+
const add = /* @__PURE__ */ __name((key) => {
|
|
759
|
+
for (let parts = key.split("."); parts.length > 0; parts = parts.slice(0, -1))
|
|
760
|
+
known.add(parts.join("."));
|
|
761
|
+
}, "add");
|
|
762
|
+
if (typeof keyOf !== "function") {
|
|
763
|
+
for (const one of everyCommand(rootOf2(command))) {
|
|
764
|
+
const key = keyOf.keyOf(pathOf(one));
|
|
765
|
+
if (key)
|
|
766
|
+
add(key);
|
|
767
|
+
}
|
|
768
|
+
for (const key of [...keyOf.extra, ...extra])
|
|
769
|
+
add(key);
|
|
770
|
+
return known;
|
|
771
|
+
}
|
|
772
|
+
for (const one of everyCommand(rootOf2(command))) {
|
|
773
|
+
const own = keyOf(one);
|
|
774
|
+
const key = own === void 0 ? keyForCommand(pathOf(one)) : own;
|
|
775
|
+
if (key)
|
|
776
|
+
add(key);
|
|
777
|
+
}
|
|
778
|
+
for (const key of [...WRITE_KEYS, ...extra])
|
|
779
|
+
add(key);
|
|
780
|
+
return known;
|
|
781
|
+
}, "knownPermissionKeys");
|
|
782
|
+
var knownBeside = /* @__PURE__ */ __name((key, known) => {
|
|
783
|
+
const parent = key.split(".").slice(0, -1).join(".");
|
|
784
|
+
const depth = parent === "" ? 1 : parent.split(".").length + 1;
|
|
785
|
+
return [...known].filter((one) => one.split(".").length === depth && (parent === "" || one.startsWith(`${parent}.`))).sort();
|
|
786
|
+
}, "knownBeside");
|
|
787
|
+
var unknownPermissionKeys = /* @__PURE__ */ __name((levels, known) => Object.keys(levels).filter((key) => key !== CEILING_KEY && !known.has(key)), "unknownPermissionKeys");
|
|
788
|
+
var unknownPermissionIssues = /* @__PURE__ */ __name((command, settings, tool, keyOf) => {
|
|
789
|
+
const saved = settings.savedPermissions ?? Object.keys(settings.permissions).filter((key) => key !== CEILING_KEY && settings.permissionSources[key] !== "flag").map((key) => ({ key, at: ["profiles", settings.profile, "permissions", key] }));
|
|
790
|
+
return permissionIssues(saved, knownPermissionKeys(command, keyOf), { file: settings.configPath, tool }).map(publicIssue);
|
|
791
|
+
}, "unknownPermissionIssues");
|
|
792
|
+
var refuseUnknownPermissionKeys = /* @__PURE__ */ __name((command, settings, tool, keyOf) => {
|
|
793
|
+
const issues = unknownPermissionIssues(command, settings, tool, keyOf);
|
|
794
|
+
if (issues.length > 0)
|
|
795
|
+
throw new CliError("configuration_error", issueMessage(issues), { permission: issues[0]?.key });
|
|
796
|
+
const flagged = Object.keys(settings.permissions).filter((key2) => settings.permissionSources[key2] === "flag");
|
|
797
|
+
const [key] = unknownPermissionKeys(Object.fromEntries(flagged.map((one) => [one, true])), knownPermissionKeys(command, keyOf));
|
|
798
|
+
if (key !== void 0)
|
|
799
|
+
throw new CliError("validation_error", `--permission ${key} names no command`, { permission: key });
|
|
800
|
+
}, "refuseUnknownPermissionKeys");
|
|
801
|
+
|
|
802
|
+
// node_modules/.pnpm/@wirecat+cli-core@0.507.0_@wirecat+cli-base@0.68.1_commander@15.0.0_esbuild@0.28.2_type_d5beb0101c7a90701cc33879a5be8d28/node_modules/@wirecat/cli-core/dist/cli/messenger/flooded.js
|
|
803
|
+
var LONG_LIVED = /* @__PURE__ */ new Set(["self", "newSendId", "close", "formatMarkdown", "formatHtml", "storedChats"]);
|
|
804
|
+
var LISTENING = /* @__PURE__ */ new Set(["watch", "feed", "listen", "listenHistory"]);
|
|
805
|
+
var BLOCKING = /* @__PURE__ */ new Set(["frozen", "limited", "banned"]);
|
|
806
|
+
var ANNOUNCE_WAIT_MS = 5e3;
|
|
807
|
+
var LONGEST_PACE_WAIT_MS = 5 * 6e4;
|
|
808
|
+
var chatOf = /* @__PURE__ */ __name((args) => typeof args[0] === "string" && /^-?\d+$/.test(args[0]) ? args[0] : void 0, "chatOf");
|
|
809
|
+
var flooded = /* @__PURE__ */ __name((messenger, memory, { name, warn, pacer }) => {
|
|
810
|
+
const kept = /* @__PURE__ */ __name(async (operation, what2) => {
|
|
811
|
+
try {
|
|
812
|
+
await what2();
|
|
813
|
+
} catch (error) {
|
|
814
|
+
warn(`could not remember ${name}'s wait (${error instanceof Error ? error.message : String(error)})`, {
|
|
815
|
+
event: "warning",
|
|
816
|
+
code: "wait_not_remembered",
|
|
817
|
+
operation
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
}, "kept");
|
|
821
|
+
const noted = /* @__PURE__ */ __name(async (operation, chatId, error) => {
|
|
822
|
+
if (!isCliFailure(error))
|
|
823
|
+
return;
|
|
824
|
+
const details = error.details ?? {};
|
|
825
|
+
const standing = details.standing;
|
|
826
|
+
if (standing && BLOCKING.has(standing.state)) {
|
|
827
|
+
await kept(operation, () => {
|
|
828
|
+
const block = memory.block({
|
|
829
|
+
state: standing.state,
|
|
830
|
+
hint: standing.hint ?? error.message,
|
|
831
|
+
...standing.until ? { until: standing.until } : {}
|
|
832
|
+
});
|
|
833
|
+
warn(`writes from this profile are held ${holdEnd(block)}: ${block.hint}`, {
|
|
834
|
+
event: "warning",
|
|
835
|
+
code: `account_${block.state}`,
|
|
836
|
+
operation
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
const waitMs = Number(details.retry_after_ms);
|
|
842
|
+
if (error.code !== "rate_limited" || details.remembered === true || !Number.isFinite(waitMs) || waitMs <= 0)
|
|
843
|
+
return;
|
|
844
|
+
const providerError = providerErrorKey(details.provider_error);
|
|
845
|
+
await kept(operation, () => {
|
|
846
|
+
const deadline = memory.remember({
|
|
847
|
+
operation,
|
|
848
|
+
...chatId ? { chatId } : {},
|
|
849
|
+
...providerError ? { providerError } : {},
|
|
850
|
+
waitMs
|
|
851
|
+
});
|
|
852
|
+
warn(`${name} asked to wait before ${operation} again \u2014 until ${deadline.until}`, {
|
|
853
|
+
event: "warning",
|
|
854
|
+
code: "flood_wait",
|
|
855
|
+
operation,
|
|
856
|
+
counts: { seconds: Math.ceil(waitMs / 1e3) }
|
|
857
|
+
});
|
|
858
|
+
});
|
|
859
|
+
if (pacer)
|
|
860
|
+
await kept(operation, () => pacer.holdFor(waitMs));
|
|
861
|
+
}, "noted");
|
|
862
|
+
const turn = /* @__PURE__ */ __name(async (operation) => {
|
|
863
|
+
if (!pacer)
|
|
864
|
+
return;
|
|
865
|
+
let at = Date.now();
|
|
866
|
+
await kept(operation, async () => {
|
|
867
|
+
at = await pacer.reserve();
|
|
868
|
+
});
|
|
869
|
+
const waitMs = at - Date.now();
|
|
870
|
+
if (waitMs <= 0)
|
|
871
|
+
return;
|
|
872
|
+
if (waitMs > LONGEST_PACE_WAIT_MS) {
|
|
873
|
+
throw new CliError("rate_limited", `${name} asked this profile to wait, ${Math.ceil(waitMs / 1e3)} s are left \u2014 nothing was sent to ${name}`, { retry_after_ms: waitMs, retry_at: new Date(at).toISOString(), remembered: true });
|
|
874
|
+
}
|
|
875
|
+
if (waitMs >= ANNOUNCE_WAIT_MS) {
|
|
876
|
+
const seconds = Math.ceil(waitMs / 1e3);
|
|
877
|
+
warn(`waiting ${seconds} s to keep this profile's pace with ${name}`, {
|
|
878
|
+
event: "warning",
|
|
879
|
+
code: "pace_wait",
|
|
880
|
+
operation,
|
|
881
|
+
counts: { seconds }
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
885
|
+
}, "turn");
|
|
886
|
+
return throughWrapper(messenger, {}, (operation, call) => {
|
|
887
|
+
if (LONG_LIVED.has(operation))
|
|
888
|
+
return call;
|
|
889
|
+
if (LISTENING.has(operation))
|
|
890
|
+
return (async (...args) => {
|
|
891
|
+
try {
|
|
892
|
+
return await call(...args);
|
|
893
|
+
} catch (error) {
|
|
894
|
+
await noted(operation, void 0, error);
|
|
895
|
+
throw error;
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
return (async (...args) => {
|
|
899
|
+
const chatId = chatOf(args);
|
|
900
|
+
const owed = memory.owed(operation, chatId);
|
|
901
|
+
if (owed) {
|
|
902
|
+
const waitMs = Math.max(0, Date.parse(owed.until) - Date.now());
|
|
903
|
+
throw new CliError("rate_limited", `${name} asked to wait before ${operation} again, ${Math.ceil(waitMs / 1e3)} s are left \u2014 nothing was sent to ${name}`, {
|
|
904
|
+
retry_after_ms: waitMs,
|
|
905
|
+
retry_at: owed.until,
|
|
906
|
+
remembered: true,
|
|
907
|
+
...owed.providerError ? { provider_error: owed.providerError } : {}
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
await turn(operation);
|
|
911
|
+
try {
|
|
912
|
+
return await call(...args);
|
|
913
|
+
} catch (error) {
|
|
914
|
+
await noted(operation, chatId, error);
|
|
915
|
+
throw error;
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
});
|
|
919
|
+
}, "flooded");
|
|
920
|
+
|
|
921
|
+
// node_modules/.pnpm/@wirecat+cli-core@0.507.0_@wirecat+cli-base@0.68.1_commander@15.0.0_esbuild@0.28.2_type_d5beb0101c7a90701cc33879a5be8d28/node_modules/@wirecat/cli-core/dist/cli/messenger/observed.js
|
|
922
|
+
var observed = /* @__PURE__ */ __name((messenger, events) => {
|
|
923
|
+
const timed = /* @__PURE__ */ __name(async (operation, named, work, after) => {
|
|
924
|
+
const write = currentOperation();
|
|
925
|
+
const marked = /* @__PURE__ */ __name((part = {}) => write === void 0 ? part : { ...part, ids: { ...part.ids, operation: write } }, "marked");
|
|
926
|
+
events({ event: "request", operation, level: "operation", ...marked(named) });
|
|
927
|
+
const started = performance.now();
|
|
928
|
+
const durationMs = /* @__PURE__ */ __name(() => Math.round(performance.now() - started), "durationMs");
|
|
929
|
+
try {
|
|
930
|
+
const answer = await work();
|
|
931
|
+
events({
|
|
932
|
+
event: "response",
|
|
933
|
+
operation,
|
|
934
|
+
level: "operation",
|
|
935
|
+
durationMs: durationMs(),
|
|
936
|
+
outcome: "ok",
|
|
937
|
+
...marked(after?.(answer))
|
|
938
|
+
});
|
|
939
|
+
return answer;
|
|
940
|
+
} catch (error) {
|
|
941
|
+
const providerError = isCliFailure(error) ? providerErrorKey(error.details?.provider_error) : void 0;
|
|
942
|
+
events({
|
|
943
|
+
event: "response",
|
|
944
|
+
operation,
|
|
945
|
+
level: "operation",
|
|
946
|
+
durationMs: durationMs(),
|
|
947
|
+
outcome: "error",
|
|
948
|
+
errorCode: isCliFailure(error) ? error.code : "generic_failure",
|
|
949
|
+
...providerError ? { providerError } : {},
|
|
950
|
+
...marked()
|
|
951
|
+
});
|
|
952
|
+
throw error;
|
|
953
|
+
}
|
|
954
|
+
}, "timed");
|
|
955
|
+
const counted = /* @__PURE__ */ __name(async (operation, onItem, work, count) => {
|
|
956
|
+
const counts = {};
|
|
957
|
+
await timed(operation, {}, () => work((item) => {
|
|
958
|
+
count(item, counts);
|
|
959
|
+
onItem(item);
|
|
960
|
+
}), () => ({ counts }));
|
|
961
|
+
}, "counted");
|
|
962
|
+
const mint = messenger.newSendId;
|
|
963
|
+
const { chats, history, contact, around, watch, feed, listen, listenHistory } = messenger;
|
|
964
|
+
return throughWrapper(messenger, {
|
|
965
|
+
self: /* @__PURE__ */ __name(() => messenger.self(), "self"),
|
|
966
|
+
...mint ? { newSendId: /* @__PURE__ */ __name(() => mint.call(messenger), "newSendId") } : {},
|
|
967
|
+
me: /* @__PURE__ */ __name(() => timed("account.me", {}, () => messenger.me()), "me"),
|
|
968
|
+
...chats ? {
|
|
969
|
+
chats: /* @__PURE__ */ __name((window) => timed("chats.list", {}, () => chats.call(messenger, window), (page) => ({ counts: { chats: page.items.length } })), "chats")
|
|
970
|
+
} : {},
|
|
971
|
+
...history ? {
|
|
972
|
+
history: /* @__PURE__ */ __name((reference, options) => timed("messages.list", {}, () => history.call(messenger, reference, options), (page) => ({
|
|
973
|
+
...page.items[0] ? { ids: { chat: page.items[0].chatId } } : {},
|
|
974
|
+
counts: { messages: page.items.length }
|
|
975
|
+
})), "history")
|
|
976
|
+
} : {},
|
|
977
|
+
resolve: /* @__PURE__ */ __name((reference) => timed("chats.resolve", {}, () => messenger.resolve(reference), (chat) => ({ ids: { chat: chat.id } })), "resolve"),
|
|
978
|
+
chat: /* @__PURE__ */ __name((reference) => timed("chats.show", {}, () => messenger.chat(reference), (card) => ({ ids: { chat: card.id }, ...card.members ? { counts: { members: card.members.length } } : {} })), "chat"),
|
|
979
|
+
...contact ? {
|
|
980
|
+
contact: /* @__PURE__ */ __name((reference) => timed("contacts.show", {}, () => contact.call(messenger, reference), (card) => ({ ids: { person: card.id }, counts: { chats: card.chats.length } })), "contact")
|
|
981
|
+
} : {},
|
|
982
|
+
...around ? {
|
|
983
|
+
around: /* @__PURE__ */ __name((reference, messageId, window) => timed("messages.around", { ids: { message_id: messageId } }, () => around.call(messenger, reference, messageId, window), (items) => ({
|
|
984
|
+
...items[0] ? { ids: { chat: items[0].chatId, message_id: messageId } } : {},
|
|
985
|
+
counts: { messages: items.length }
|
|
986
|
+
})), "around")
|
|
987
|
+
} : {},
|
|
988
|
+
send: /* @__PURE__ */ __name((chatId, text, options) => timed("messages.send", {
|
|
989
|
+
ids: {
|
|
990
|
+
chat: chatId,
|
|
991
|
+
send: options.sendId,
|
|
992
|
+
...options.replyTo ? { reply: options.replyTo } : {},
|
|
993
|
+
...options.threadId ? { thread: options.threadId } : {}
|
|
994
|
+
}
|
|
995
|
+
}, () => messenger.send(chatId, text, options), (sent) => ({ ids: { chat: sent.message.chatId || chatId, message_id: sent.message.id } })), "send"),
|
|
996
|
+
...watch ? {
|
|
997
|
+
watch: /* @__PURE__ */ __name((onEvent, signal, onReady) => counted("messages.watch", onEvent, (counting) => watch.call(messenger, counting, signal, onReady), byEvent), "watch")
|
|
998
|
+
} : {},
|
|
999
|
+
...feed ? { feed: /* @__PURE__ */ __name((onBatch, signal) => timed("history.feed", {}, () => feed.call(messenger, onBatch, signal)), "feed") } : {},
|
|
1000
|
+
...listen ? {
|
|
1001
|
+
listen: /* @__PURE__ */ __name((onEvent, signal) => counted("messages.listen", onEvent, (counting) => listen.call(messenger, counting, signal), byEvent), "listen")
|
|
1002
|
+
} : {},
|
|
1003
|
+
...listenHistory ? {
|
|
1004
|
+
listenHistory: /* @__PURE__ */ __name((onBatch, signal) => counted("history.listen", onBatch, (counting) => listenHistory.call(messenger, counting, signal), byPart), "listenHistory")
|
|
1005
|
+
} : {},
|
|
1006
|
+
logout: /* @__PURE__ */ __name(() => timed("session.logout", {}, () => messenger.logout()), "logout"),
|
|
1007
|
+
close: /* @__PURE__ */ __name(() => messenger.close(), "close")
|
|
1008
|
+
}, (name, call) => (...args) => timed(`adapter.${name}`, {}, () => call(...args)));
|
|
1009
|
+
}, "observed");
|
|
1010
|
+
var byEvent = /* @__PURE__ */ __name((event, counts) => {
|
|
1011
|
+
counts[event.event] = (counts[event.event] ?? 0) + 1;
|
|
1012
|
+
}, "byEvent");
|
|
1013
|
+
var byPart = /* @__PURE__ */ __name((batch, counts) => {
|
|
1014
|
+
counts.batches = (counts.batches ?? 0) + 1;
|
|
1015
|
+
for (const [part, items] of Object.entries(batch))
|
|
1016
|
+
if (Array.isArray(items) && items.length > 0)
|
|
1017
|
+
counts[part] = (counts[part] ?? 0) + items.length;
|
|
1018
|
+
}, "byPart");
|
|
1019
|
+
|
|
1020
|
+
// node_modules/.pnpm/@wirecat+cli-core@0.507.0_@wirecat+cli-base@0.68.1_commander@15.0.0_esbuild@0.28.2_type_d5beb0101c7a90701cc33879a5be8d28/node_modules/@wirecat/cli-core/dist/cli/messenger/stored.js
|
|
1021
|
+
var REKEY_PACING = { lockMs: 50, pauseMs: 25 };
|
|
1022
|
+
var stored = /* @__PURE__ */ __name((messenger, { account, store, warn, events, pending, announcing, deletedWithoutChat, profile, rekeyed, rekeyPacing = REKEY_PACING, closing, namesFromStore, recipientIds, countsUnread = false }) => {
|
|
1023
|
+
let warned = false;
|
|
1024
|
+
let recorded = profile === void 0;
|
|
1025
|
+
const notWritten = /* @__PURE__ */ __name((operation, error) => {
|
|
1026
|
+
const warning = { event: "warning", code: "store_not_written", operation };
|
|
1027
|
+
events(warning);
|
|
1028
|
+
if (!warned)
|
|
1029
|
+
warn(`not saved to the local store: ${error instanceof Error ? error.message : String(error)}`, warning);
|
|
1030
|
+
warned = true;
|
|
1031
|
+
}, "notWritten");
|
|
1032
|
+
const attempt = /* @__PURE__ */ __name(async (operation, write) => {
|
|
1033
|
+
try {
|
|
1034
|
+
const opened = await store();
|
|
1035
|
+
if (opened)
|
|
1036
|
+
await write(opened);
|
|
1037
|
+
if (opened && !recorded && profile) {
|
|
1038
|
+
recorded = true;
|
|
1039
|
+
await Promise.resolve().then(() => opened.accounts.saveProfile(profile.app, profile.name, account)).catch(() => {
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
notWritten(operation, error);
|
|
1044
|
+
}
|
|
1045
|
+
}, "attempt");
|
|
1046
|
+
const save = /* @__PURE__ */ __name((operation, writing) => {
|
|
1047
|
+
const saving = attempt(operation, writing);
|
|
1048
|
+
pending?.add(saving);
|
|
1049
|
+
return saving.finally(() => pending?.delete(saving));
|
|
1050
|
+
}, "save");
|
|
1051
|
+
const { chats, history, around, people } = messenger;
|
|
1052
|
+
const followed = /* @__PURE__ */ __name(async (opened, scoped, held) => {
|
|
1053
|
+
let answered;
|
|
1054
|
+
try {
|
|
1055
|
+
answered = await messenger.resolve(held.id);
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
if (codeOf(error) === "not_found")
|
|
1058
|
+
return void 0;
|
|
1059
|
+
throw error;
|
|
1060
|
+
}
|
|
1061
|
+
if (answered.id === held.id)
|
|
1062
|
+
return void 0;
|
|
1063
|
+
await attempt("history.rekey", async (store2) => {
|
|
1064
|
+
await store2.chats.rekeyChat(scoped, held.id, answered.id);
|
|
1065
|
+
await rekeyed?.(held.id, answered.id);
|
|
1066
|
+
});
|
|
1067
|
+
return (await storedChat(opened, scoped, namesFromStore ?? {}, answered.id)).held ?? answered;
|
|
1068
|
+
}, "followed");
|
|
1069
|
+
const titles = /* @__PURE__ */ new Map();
|
|
1070
|
+
const storedPeople = /* @__PURE__ */ __name(async () => {
|
|
1071
|
+
try {
|
|
1072
|
+
return await (await store())?.people.people(account.provider, { account: account.account });
|
|
1073
|
+
} catch {
|
|
1074
|
+
return void 0;
|
|
1075
|
+
}
|
|
1076
|
+
}, "storedPeople");
|
|
1077
|
+
let rekeysQueued = 0;
|
|
1078
|
+
let rekeyRound = 0;
|
|
1079
|
+
const rekeysOnly = /* @__PURE__ */ new Set();
|
|
1080
|
+
const giveUpRekeys = /* @__PURE__ */ __name(() => {
|
|
1081
|
+
rekeyRound += 1;
|
|
1082
|
+
const count = rekeysQueued;
|
|
1083
|
+
rekeysQueued = 0;
|
|
1084
|
+
for (const saving of rekeysOnly)
|
|
1085
|
+
pending?.delete(saving);
|
|
1086
|
+
if (count === 0)
|
|
1087
|
+
return;
|
|
1088
|
+
const warning = {
|
|
1089
|
+
event: "warning",
|
|
1090
|
+
code: "rekeys_not_applied",
|
|
1091
|
+
operation: "history.rekey",
|
|
1092
|
+
counts: { rekeys: count }
|
|
1093
|
+
};
|
|
1094
|
+
events(warning);
|
|
1095
|
+
warn(`${count} chat re-key${count === 1 ? "" : "s"} not applied yet; the next serve applies them`, warning);
|
|
1096
|
+
}, "giveUpRekeys");
|
|
1097
|
+
closing?.addEventListener("abort", giveUpRekeys, { once: true });
|
|
1098
|
+
const saveRekeys = /* @__PURE__ */ __name(async (pairs, round) => {
|
|
1099
|
+
let rest = pairs;
|
|
1100
|
+
while (rest.length > 0 && round === rekeyRound) {
|
|
1101
|
+
let pass;
|
|
1102
|
+
try {
|
|
1103
|
+
pass = await (await store())?.chats.rekeyChats(account, rest, rekeyPacing.lockMs);
|
|
1104
|
+
} catch {
|
|
1105
|
+
if (round === rekeyRound)
|
|
1106
|
+
giveUpRekeys();
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
if (round !== rekeyRound)
|
|
1110
|
+
return;
|
|
1111
|
+
const taken2 = pass?.taken ?? rest.length;
|
|
1112
|
+
rekeysQueued -= taken2;
|
|
1113
|
+
for (const { from, to } of pass?.applied ?? [])
|
|
1114
|
+
await Promise.resolve().then(() => rekeyed?.(from, to)).catch((error) => notWritten("history.rekey", error));
|
|
1115
|
+
for (const { error } of pass?.refused ?? [])
|
|
1116
|
+
notWritten("history.rekey", error);
|
|
1117
|
+
rest = rest.slice(taken2);
|
|
1118
|
+
if (rest.length > 0)
|
|
1119
|
+
await new Promise((resolve) => setTimeout(resolve, rekeyPacing.pauseMs));
|
|
1120
|
+
}
|
|
1121
|
+
}, "saveRekeys");
|
|
1122
|
+
const saveBatches = /* @__PURE__ */ __name((operation, onBatch) => {
|
|
1123
|
+
let previous = Promise.resolve();
|
|
1124
|
+
return (batch) => {
|
|
1125
|
+
if (namesFromStore) {
|
|
1126
|
+
for (const { from, to } of batch.rekeys ?? []) {
|
|
1127
|
+
const title = titles.get(from);
|
|
1128
|
+
titles.delete(from);
|
|
1129
|
+
if (title && !titles.has(to))
|
|
1130
|
+
titles.set(to, title);
|
|
1131
|
+
}
|
|
1132
|
+
for (const chat of batch.chats ?? [])
|
|
1133
|
+
if (chat.title)
|
|
1134
|
+
titles.set(chat.id, chat.title);
|
|
1135
|
+
}
|
|
1136
|
+
const rekeys = batch.rekeys ?? [];
|
|
1137
|
+
const round = rekeyRound;
|
|
1138
|
+
rekeysQueued += rekeys.length;
|
|
1139
|
+
const saving = previous.then(async () => {
|
|
1140
|
+
await saveRekeys(rekeys, round);
|
|
1141
|
+
await attempt(operation, (opened) => fill(opened, account, batch));
|
|
1142
|
+
});
|
|
1143
|
+
previous = saving;
|
|
1144
|
+
pending?.add(saving);
|
|
1145
|
+
if (onlyRekeys(batch))
|
|
1146
|
+
rekeysOnly.add(saving);
|
|
1147
|
+
void saving.finally(() => {
|
|
1148
|
+
pending?.delete(saving);
|
|
1149
|
+
rekeysOnly.delete(saving);
|
|
1150
|
+
});
|
|
1151
|
+
onBatch(batch);
|
|
1152
|
+
};
|
|
1153
|
+
}, "saveBatches");
|
|
1154
|
+
return throughWrapper(messenger, {
|
|
1155
|
+
...messenger.forumState ? {
|
|
1156
|
+
forumState: /* @__PURE__ */ __name(async (chatId) => {
|
|
1157
|
+
const state = await capability(messenger, "forumState", "read forum settings")(chatId);
|
|
1158
|
+
await save("forum.read", (opened) => opened.chats.saveChats(account, [state.chat]));
|
|
1159
|
+
return state;
|
|
1160
|
+
}, "forumState")
|
|
1161
|
+
} : {},
|
|
1162
|
+
...messenger.upgradeForum ? {
|
|
1163
|
+
upgradeForum: /* @__PURE__ */ __name(async (chatId) => {
|
|
1164
|
+
const state = await capability(messenger, "upgradeForum", "upgrade a group")(chatId);
|
|
1165
|
+
await save("forum.upgrade", (opened) => opened.chats.saveChats(account, [state.chat]));
|
|
1166
|
+
return state;
|
|
1167
|
+
}, "upgradeForum")
|
|
1168
|
+
} : {},
|
|
1169
|
+
...messenger.enableForum ? {
|
|
1170
|
+
enableForum: /* @__PURE__ */ __name(async (chatId) => {
|
|
1171
|
+
const state = await capability(messenger, "enableForum", "enable forum topics")(chatId);
|
|
1172
|
+
await save("forum.enable", (opened) => opened.chats.saveChats(account, [state.chat]));
|
|
1173
|
+
return state;
|
|
1174
|
+
}, "enableForum")
|
|
1175
|
+
} : {},
|
|
1176
|
+
self: /* @__PURE__ */ __name(() => messenger.self(), "self"),
|
|
1177
|
+
resolve: /* @__PURE__ */ __name(async (reference) => {
|
|
1178
|
+
if (isId(reference) && !namesFromStore)
|
|
1179
|
+
return messenger.resolve(reference);
|
|
1180
|
+
let accountId;
|
|
1181
|
+
try {
|
|
1182
|
+
accountId = account.account;
|
|
1183
|
+
} catch {
|
|
1184
|
+
return messenger.resolve(reference);
|
|
1185
|
+
}
|
|
1186
|
+
const scoped = { provider: account.provider, account: accountId };
|
|
1187
|
+
const opened = await store().catch(() => void 0);
|
|
1188
|
+
if (!opened)
|
|
1189
|
+
return messenger.resolve(reference);
|
|
1190
|
+
const { id, held } = namesFromStore ? await storedChat(opened, scoped, namesFromStore, reference) : {};
|
|
1191
|
+
if (!isId(reference)) {
|
|
1192
|
+
const people2 = await opened.people.people(scoped.provider, { account: scoped.account });
|
|
1193
|
+
const wanted = foldName(reference.trim());
|
|
1194
|
+
if (people2.all().some((person) => person.alias && foldName(person.alias).includes(wanted))) {
|
|
1195
|
+
const person = pickPerson(reference, people2);
|
|
1196
|
+
const chats2 = (await opened.chats.chatsWith(scoped, person.id)).filter((chat) => chat.kind === "dialog");
|
|
1197
|
+
if (chats2.length !== 1 || !chats2[0])
|
|
1198
|
+
throw new CliError("validation_error", "the alias has no unique stored direct chat; use a chat id");
|
|
1199
|
+
let remote;
|
|
1200
|
+
if (namesFromStore)
|
|
1201
|
+
remote = held;
|
|
1202
|
+
else
|
|
1203
|
+
try {
|
|
1204
|
+
remote = await messenger.resolve(reference);
|
|
1205
|
+
} catch (error) {
|
|
1206
|
+
if (!(error instanceof CliError) || error.code !== "not_found")
|
|
1207
|
+
throw error;
|
|
1208
|
+
}
|
|
1209
|
+
if (remote && remote.id !== chats2[0].id)
|
|
1210
|
+
throw new CliError("validation_error", "the local alias also names another messenger chat; use a chat id", {
|
|
1211
|
+
candidates: [{ id: chats2[0].id }, { id: remote.id }]
|
|
1212
|
+
});
|
|
1213
|
+
return remote ?? (namesFromStore ? chats2[0] : messenger.resolve(chats2[0].id));
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
if (held && held.id === reference.trim())
|
|
1217
|
+
return await followed(opened, scoped, held) ?? held;
|
|
1218
|
+
return held ?? messenger.resolve(id ?? reference);
|
|
1219
|
+
}, "resolve"),
|
|
1220
|
+
chat: /* @__PURE__ */ __name((reference) => messenger.chat(reference), "chat"),
|
|
1221
|
+
...people && namesFromStore ? {
|
|
1222
|
+
people: /* @__PURE__ */ __name(async (references) => {
|
|
1223
|
+
const known = await storedPeople();
|
|
1224
|
+
const found = references.map((reference) => known ? personIn(known, reference) : void 0);
|
|
1225
|
+
const asked = references.filter((_, at) => found[at] === void 0);
|
|
1226
|
+
const answered = asked.length === 0 ? [] : await people.call(messenger, asked);
|
|
1227
|
+
const ids = [];
|
|
1228
|
+
for (const id of found) {
|
|
1229
|
+
const one = id ?? answered.shift();
|
|
1230
|
+
if (one === void 0)
|
|
1231
|
+
throw new CliError("not_found", "the messenger found fewer people than it was asked");
|
|
1232
|
+
ids.push(one);
|
|
1233
|
+
}
|
|
1234
|
+
return ids;
|
|
1235
|
+
}, "people")
|
|
1236
|
+
} : {},
|
|
1237
|
+
logout: /* @__PURE__ */ __name(() => messenger.logout(), "logout"),
|
|
1238
|
+
close: /* @__PURE__ */ __name(() => messenger.close(), "close"),
|
|
1239
|
+
me: /* @__PURE__ */ __name(async () => {
|
|
1240
|
+
const me = await messenger.me();
|
|
1241
|
+
await save("account.me", (opened) => opened.accounts.saveAccount(account, { name: me.name }));
|
|
1242
|
+
return me;
|
|
1243
|
+
}, "me"),
|
|
1244
|
+
...chats ? {
|
|
1245
|
+
chats: /* @__PURE__ */ __name(async (window) => {
|
|
1246
|
+
const page = await chats.call(messenger, window);
|
|
1247
|
+
const complete = window.offset === 0 && !page.hasMore && page.items.length > 0;
|
|
1248
|
+
await save("chats.list", async (opened) => {
|
|
1249
|
+
await opened.chats.saveChats(account, page.items);
|
|
1250
|
+
const ids = page.items.map((chat) => chat.id);
|
|
1251
|
+
if (complete)
|
|
1252
|
+
await opened.chats.markChatsLeft(account, ids);
|
|
1253
|
+
else
|
|
1254
|
+
await opened.chats.rejoinChats(account, ids);
|
|
1255
|
+
});
|
|
1256
|
+
return page;
|
|
1257
|
+
}, "chats")
|
|
1258
|
+
} : {},
|
|
1259
|
+
...history ? {
|
|
1260
|
+
history: /* @__PURE__ */ __name(async (reference, options) => {
|
|
1261
|
+
const seenAt = Date.now();
|
|
1262
|
+
const page = await history.call(messenger, reference, options);
|
|
1263
|
+
const chatId = page.items[0]?.chatId;
|
|
1264
|
+
if (chatId !== void 0) {
|
|
1265
|
+
await save("messages.list", (opened) => opened.messages.saveMessages(account, chatId, page.items, { via: "history", seenAt }));
|
|
1266
|
+
}
|
|
1267
|
+
return page;
|
|
1268
|
+
}, "history")
|
|
1269
|
+
} : {},
|
|
1270
|
+
...around ? {
|
|
1271
|
+
around: /* @__PURE__ */ __name(async (reference, messageId, window) => {
|
|
1272
|
+
const seenAt = Date.now();
|
|
1273
|
+
const items = await around.call(messenger, reference, messageId, window);
|
|
1274
|
+
const chatId = items[0]?.chatId;
|
|
1275
|
+
if (chatId !== void 0) {
|
|
1276
|
+
const plain = items.map(({ anchor, ...message }) => message);
|
|
1277
|
+
await save("messages.around", (opened) => opened.messages.saveMessages(account, chatId, plain, { via: "context", seenAt }));
|
|
1278
|
+
}
|
|
1279
|
+
return items;
|
|
1280
|
+
}, "around")
|
|
1281
|
+
} : {},
|
|
1282
|
+
...messenger.watch ? {
|
|
1283
|
+
watch: /* @__PURE__ */ __name(async (onEvent, signal, onReady) => {
|
|
1284
|
+
if (namesFromStore)
|
|
1285
|
+
await rememberTitles(store, account, titles);
|
|
1286
|
+
return messenger.watch?.((event) => {
|
|
1287
|
+
const seenAt = Date.now();
|
|
1288
|
+
void save("messages.watch", (opened) => keep(opened, account, event, seenAt, deletedWithoutChat, countsUnread));
|
|
1289
|
+
onEvent(namesFromStore ? titled(event, titles) : event);
|
|
1290
|
+
}, signal, onReady) ?? Promise.resolve();
|
|
1291
|
+
}, "watch")
|
|
1292
|
+
} : {},
|
|
1293
|
+
...messenger.listen ? {
|
|
1294
|
+
listen: /* @__PURE__ */ __name((onEvent, signal) => messenger.listen?.((event) => {
|
|
1295
|
+
void save("messages.listen", (opened) => keep(opened, account, event, Date.now(), deletedWithoutChat, countsUnread));
|
|
1296
|
+
onEvent(event);
|
|
1297
|
+
}, signal) ?? Promise.resolve(), "listen")
|
|
1298
|
+
} : {},
|
|
1299
|
+
...messenger.feed ? {
|
|
1300
|
+
feed: /* @__PURE__ */ __name((onBatch, signal) => {
|
|
1301
|
+
if (messenger.storedChats) {
|
|
1302
|
+
const announced = announceStoredChats(messenger, store, account, events, warn, recipientIds);
|
|
1303
|
+
announcing?.add(announced);
|
|
1304
|
+
void announced.finally(() => announcing?.delete(announced));
|
|
1305
|
+
}
|
|
1306
|
+
return messenger.feed?.(saveBatches("history.feed", onBatch), signal) ?? Promise.resolve();
|
|
1307
|
+
}, "feed")
|
|
1308
|
+
} : {},
|
|
1309
|
+
...messenger.listenHistory ? {
|
|
1310
|
+
listenHistory: /* @__PURE__ */ __name((onBatch, signal) => messenger.listenHistory?.(saveBatches("history.listen", onBatch), signal) ?? Promise.resolve(), "listenHistory")
|
|
1311
|
+
} : {},
|
|
1312
|
+
...messenger.edit ? {
|
|
1313
|
+
edit: /* @__PURE__ */ __name(async (chatId, messageId, text, options) => {
|
|
1314
|
+
const seenAt = Date.now();
|
|
1315
|
+
const edited = await capability(messenger, "edit", "edit")(chatId, messageId, text, options);
|
|
1316
|
+
await save("messages.edit", async (opened) => {
|
|
1317
|
+
const held = await opened.messages.message(account, messageId, { chatId }) !== void 0;
|
|
1318
|
+
await saveSent(opened, account, chatId, edited, { via: "update", seenAt }, held ? chatId : void 0);
|
|
1319
|
+
});
|
|
1320
|
+
return edited;
|
|
1321
|
+
}, "edit")
|
|
1322
|
+
} : {},
|
|
1323
|
+
...messenger.createPoll ? {
|
|
1324
|
+
// A messenger that never echoes a device's own message back would otherwise never hold the poll.
|
|
1325
|
+
createPoll: /* @__PURE__ */ __name(async (chatId, poll, options) => {
|
|
1326
|
+
const sent = await capability(messenger, "createPoll", "create a poll")(chatId, poll, options);
|
|
1327
|
+
await save("polls.create", (opened) => saveSent(opened, account, chatId, sent.message, { via: "send" }));
|
|
1328
|
+
return sent;
|
|
1329
|
+
}, "createPoll")
|
|
1330
|
+
} : {},
|
|
1331
|
+
...messenger.vote ? {
|
|
1332
|
+
vote: /* @__PURE__ */ __name(async (chatId, messageId, answerIds) => {
|
|
1333
|
+
const poll = await capability(messenger, "vote", "vote in a poll")(chatId, messageId, answerIds);
|
|
1334
|
+
const votedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1335
|
+
await save("polls.vote", (opened) => opened.messages.saveVote(account, chatId, messageId, {
|
|
1336
|
+
person: { id: account.account, name: null, username: null },
|
|
1337
|
+
answers: answerIds,
|
|
1338
|
+
votedAt
|
|
1339
|
+
}));
|
|
1340
|
+
return poll;
|
|
1341
|
+
}, "vote")
|
|
1342
|
+
} : {},
|
|
1343
|
+
...messenger.star ? {
|
|
1344
|
+
// The messenger may not echo the owner's own star back, and `messages list starred:true` reads the store.
|
|
1345
|
+
star: /* @__PURE__ */ __name(async (chatId, messageId, starred) => {
|
|
1346
|
+
await capability(messenger, "star", "star a message")(chatId, messageId, starred);
|
|
1347
|
+
await save("messages.star", (opened) => opened.messages.saveStar(account, chatId, messageId, starred));
|
|
1348
|
+
}, "star")
|
|
1349
|
+
} : {},
|
|
1350
|
+
...messenger.forward ? {
|
|
1351
|
+
forward: /* @__PURE__ */ __name(async (fromChatId, messageId, toChatId, options) => {
|
|
1352
|
+
const copy = await capability(messenger, "forward", "forward")(fromChatId, messageId, toChatId, options);
|
|
1353
|
+
await save("messages.forward", (opened) => saveSent(opened, account, toChatId, copy, { via: "send" }));
|
|
1354
|
+
return copy;
|
|
1355
|
+
}, "forward")
|
|
1356
|
+
} : {},
|
|
1357
|
+
// Without this, a message deleted here would still be found by `search messages` and read `--offline`.
|
|
1358
|
+
...messenger.delete ? {
|
|
1359
|
+
delete: /* @__PURE__ */ __name(async (chatId, messageIds, options) => {
|
|
1360
|
+
await capability(messenger, "delete", "delete")(chatId, messageIds, options);
|
|
1361
|
+
await save("messages.delete", (opened) => opened.messages.markDeleted(account, messageIds, { chatId }));
|
|
1362
|
+
}, "delete")
|
|
1363
|
+
} : {},
|
|
1364
|
+
send: /* @__PURE__ */ __name(async (chatId, text, options) => {
|
|
1365
|
+
const sent = await messenger.send(chatId, text, options);
|
|
1366
|
+
if (options.at === void 0) {
|
|
1367
|
+
await save("messages.send", async (opened) => {
|
|
1368
|
+
for (const message of sent.messages ?? [sent.message])
|
|
1369
|
+
await saveSent(opened, account, chatId, message, { via: "send" });
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
return sent;
|
|
1373
|
+
}, "send")
|
|
1374
|
+
});
|
|
1375
|
+
}, "stored");
|
|
1376
|
+
var saveSent = /* @__PURE__ */ __name((opened, account, typed, message, options, into = message.chatId || typed) => opened.messages.saveMessages(account, into, [message], { ...options, typedChatId: typed }), "saveSent");
|
|
1377
|
+
var storedChat = /* @__PURE__ */ __name(async (store, account, traits, reference) => {
|
|
1378
|
+
const bound = store.forAccount(account);
|
|
1379
|
+
const id = await chatIdIn(traits, reference, bound).catch((error) => {
|
|
1380
|
+
if (codeOf(error) === "not_found")
|
|
1381
|
+
return void 0;
|
|
1382
|
+
throw error;
|
|
1383
|
+
});
|
|
1384
|
+
if (id === void 0)
|
|
1385
|
+
return {};
|
|
1386
|
+
const held = (await bound.chats.chats({})).items.find((chat) => chat.id === id);
|
|
1387
|
+
return held ? { id, held } : { id };
|
|
1388
|
+
}, "storedChat");
|
|
1389
|
+
var personIn = /* @__PURE__ */ __name((people, reference) => {
|
|
1390
|
+
try {
|
|
1391
|
+
return pickPerson(reference, people).id;
|
|
1392
|
+
} catch (error) {
|
|
1393
|
+
if (codeOf(error) === "not_found")
|
|
1394
|
+
return void 0;
|
|
1395
|
+
throw error;
|
|
1396
|
+
}
|
|
1397
|
+
}, "personIn");
|
|
1398
|
+
var announceStoredChats = /* @__PURE__ */ __name(async (messenger, store, account, events, warn, recipientIds) => {
|
|
1399
|
+
let listed = [];
|
|
1400
|
+
try {
|
|
1401
|
+
listed = recipientIds?.() ?? [];
|
|
1402
|
+
} catch (error) {
|
|
1403
|
+
warn(`the messenger was not told the recipient list: ${error instanceof Error ? error.message : String(error)}`, {
|
|
1404
|
+
event: "warning",
|
|
1405
|
+
code: "recipients_not_read",
|
|
1406
|
+
operation: "history.stored_chats"
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
try {
|
|
1410
|
+
const opened = await store();
|
|
1411
|
+
const held = opened ? (await opened.forAccount(account).chats.chats({})).items.map((chat) => chat.id) : [];
|
|
1412
|
+
const recipients = [...new Set(listed)].filter((id) => !held.includes(id));
|
|
1413
|
+
if (recipients.length > 0)
|
|
1414
|
+
await messenger.storedChats?.(held, recipients);
|
|
1415
|
+
else if (opened)
|
|
1416
|
+
await messenger.storedChats?.(held);
|
|
1417
|
+
} catch (error) {
|
|
1418
|
+
const warning = { event: "warning", code: "stored_chats_failed", operation: "history.stored_chats" };
|
|
1419
|
+
events(warning);
|
|
1420
|
+
warn(`the messenger was not told the stored chats: ${error instanceof Error ? error.message : String(error)}`, warning);
|
|
1421
|
+
}
|
|
1422
|
+
}, "announceStoredChats");
|
|
1423
|
+
var rememberTitles = /* @__PURE__ */ __name(async (store, account, titles) => {
|
|
1424
|
+
try {
|
|
1425
|
+
const opened = await store();
|
|
1426
|
+
for (const chat of (await opened?.forAccount(account).chats.chats({}))?.items ?? [])
|
|
1427
|
+
if (chat.title && !titles.has(chat.id))
|
|
1428
|
+
titles.set(chat.id, chat.title);
|
|
1429
|
+
} catch {
|
|
1430
|
+
}
|
|
1431
|
+
}, "rememberTitles");
|
|
1432
|
+
var titled = /* @__PURE__ */ __name((event, titles) => {
|
|
1433
|
+
if (event.event === "message" || event.event === "edit") {
|
|
1434
|
+
const title2 = event.message.chatTitle ?? titles.get(event.message.chatId);
|
|
1435
|
+
return title2 ? { ...event, message: { ...event.message, chatTitle: title2 } } : event;
|
|
1436
|
+
}
|
|
1437
|
+
if (event.event === "chat_event" && event.change.event === "title" && event.change.title)
|
|
1438
|
+
titles.set(event.chatId, event.change.title);
|
|
1439
|
+
if (event.chatTitle !== null || event.chatId === null)
|
|
1440
|
+
return event;
|
|
1441
|
+
const title = titles.get(event.chatId);
|
|
1442
|
+
return title ? { ...event, chatTitle: title } : event;
|
|
1443
|
+
}, "titled");
|
|
1444
|
+
var onlyRekeys = /* @__PURE__ */ __name((batch) => Object.entries(batch).every(([part, items]) => part === "rekeys" === (items?.length ?? 0) > 0), "onlyRekeys");
|
|
1445
|
+
var fill = /* @__PURE__ */ __name(async (store, account, batch) => {
|
|
1446
|
+
if (batch.chats?.length)
|
|
1447
|
+
await store.chats.saveChats(account, batch.chats);
|
|
1448
|
+
if (batch.people?.length)
|
|
1449
|
+
await store.people.savePeople(account, batch.people);
|
|
1450
|
+
const byChat = /* @__PURE__ */ new Map();
|
|
1451
|
+
const of = /* @__PURE__ */ __name((chatId) => {
|
|
1452
|
+
const held = byChat.get(chatId) ?? { messages: [], votes: [], reactions: [], stars: [] };
|
|
1453
|
+
byChat.set(chatId, held);
|
|
1454
|
+
return held;
|
|
1455
|
+
}, "of");
|
|
1456
|
+
for (const message of batch.messages ?? [])
|
|
1457
|
+
of(message.chatId).messages.push(message);
|
|
1458
|
+
for (const { chatId, messageId, vote } of batch.votes ?? [])
|
|
1459
|
+
of(chatId).votes.push({ messageId, vote });
|
|
1460
|
+
for (const { chatId, messageId, reaction } of batch.reactions ?? [])
|
|
1461
|
+
of(chatId).reactions.push({ messageId, reaction });
|
|
1462
|
+
for (const { chatId, messageId, starred } of batch.stars ?? [])
|
|
1463
|
+
of(chatId).stars.push({ messageId, starred });
|
|
1464
|
+
for (const [chatId, { messages, votes, reactions, stars }] of byChat)
|
|
1465
|
+
await store.messages.saveMessages(account, chatId, messages, { via: "history", votes, reactions, stars });
|
|
1466
|
+
const deletions = /* @__PURE__ */ new Map();
|
|
1467
|
+
for (const { chatId, messageId } of batch.deletions ?? [])
|
|
1468
|
+
deletions.set(chatId, [...deletions.get(chatId) ?? [], messageId]);
|
|
1469
|
+
for (const [chatId, ids] of deletions)
|
|
1470
|
+
await store.messages.markDeleted(account, ids, { chatId });
|
|
1471
|
+
const changes = /* @__PURE__ */ new Map();
|
|
1472
|
+
for (const { chatId, change } of batch.events ?? [])
|
|
1473
|
+
changes.set(chatId, [...changes.get(chatId) ?? [], change]);
|
|
1474
|
+
for (const [chatId, events] of changes)
|
|
1475
|
+
await store.roster.saveChatEvents(account, chatId, events);
|
|
1476
|
+
}, "fill");
|
|
1477
|
+
var keep = /* @__PURE__ */ __name(async (store, account, event, seenAt, among, countsUnread) => {
|
|
1478
|
+
switch (event.event) {
|
|
1479
|
+
case "message":
|
|
1480
|
+
case "edit": {
|
|
1481
|
+
const { chatTitle, ...message } = event.message;
|
|
1482
|
+
await store.messages.saveMessages(account, message.chatId, [message], {
|
|
1483
|
+
via: "update",
|
|
1484
|
+
seenAt,
|
|
1485
|
+
...countsUnread && event.event === "message" ? { countUnread: true } : {}
|
|
1486
|
+
});
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
case "delete":
|
|
1490
|
+
await store.messages.markDeleted(account, [event.messageId], event.chatId === null ? among ? { among } : {} : { chatId: event.chatId });
|
|
1491
|
+
return;
|
|
1492
|
+
case "vote":
|
|
1493
|
+
await store.messages.saveVote(account, event.chatId, event.messageId, event.vote);
|
|
1494
|
+
return;
|
|
1495
|
+
case "person_reaction":
|
|
1496
|
+
await store.messages.saveReaction(account, event.chatId, event.messageId, event.reaction);
|
|
1497
|
+
return;
|
|
1498
|
+
case "star":
|
|
1499
|
+
await store.messages.saveStar(account, event.chatId, event.messageId, event.starred);
|
|
1500
|
+
return;
|
|
1501
|
+
case "delivery":
|
|
1502
|
+
await store.messages.saveDelivery(account, event.chatId, event.messageId, event.delivery);
|
|
1503
|
+
return;
|
|
1504
|
+
case "chat_event":
|
|
1505
|
+
await store.roster.saveChatEvents(account, event.chatId, [event.change]);
|
|
1506
|
+
return;
|
|
1507
|
+
case "reaction":
|
|
1508
|
+
await store.messages.saveReactions(account, event.chatId, event.messageId, event.reactions, event.counterObservation ? { observation: event.counterObservation } : {});
|
|
1509
|
+
}
|
|
1510
|
+
}, "keep");
|
|
1511
|
+
|
|
1512
|
+
// node_modules/.pnpm/@wirecat+cli-core@0.507.0_@wirecat+cli-base@0.68.1_commander@15.0.0_esbuild@0.28.2_type_d5beb0101c7a90701cc33879a5be8d28/node_modules/@wirecat/cli-core/dist/cli/messenger/context.js
|
|
1513
|
+
var lazyStore = /* @__PURE__ */ __name((env, command, version) => {
|
|
1514
|
+
let opened;
|
|
1515
|
+
return {
|
|
1516
|
+
open: /* @__PURE__ */ __name(() => {
|
|
1517
|
+
opened ??= openStore({ env, command, ...version ? { version } : {} });
|
|
1518
|
+
return opened;
|
|
1519
|
+
}, "open"),
|
|
1520
|
+
close: /* @__PURE__ */ __name(async () => {
|
|
1521
|
+
if (opened)
|
|
1522
|
+
await (await opened.catch(() => void 0))?.close();
|
|
1523
|
+
}, "close")
|
|
1524
|
+
};
|
|
1525
|
+
}, "lazyStore");
|
|
1526
|
+
var storedMessages = /* @__PURE__ */ __name((provider, store, account) => {
|
|
1527
|
+
const key = /* @__PURE__ */ __name(() => {
|
|
1528
|
+
const self = account();
|
|
1529
|
+
return self ? { provider, account: self } : void 0;
|
|
1530
|
+
}, "key");
|
|
1531
|
+
return {
|
|
1532
|
+
message: /* @__PURE__ */ __name(async (chatId, messageId) => {
|
|
1533
|
+
const at = key();
|
|
1534
|
+
return at && (await store()).messages.message(at, messageId, { chatId });
|
|
1535
|
+
}, "message"),
|
|
1536
|
+
threadMessages: /* @__PURE__ */ __name(async (chatId, threadId, window) => {
|
|
1537
|
+
const owner = key();
|
|
1538
|
+
return owner ? (await store()).messages.messages(owner, chatId, { ...window, threadId }) : { items: [], hasMore: false };
|
|
1539
|
+
}, "threadMessages"),
|
|
1540
|
+
newestIncoming: /* @__PURE__ */ __name(async (chatId, before) => {
|
|
1541
|
+
const at = key();
|
|
1542
|
+
if (!at)
|
|
1543
|
+
return void 0;
|
|
1544
|
+
const page = await (await store()).messages.messages(at, chatId, {
|
|
1545
|
+
limit: 1,
|
|
1546
|
+
outgoing: false,
|
|
1547
|
+
...before === void 0 ? {} : { before }
|
|
1548
|
+
});
|
|
1549
|
+
return page.items.at(-1);
|
|
1550
|
+
}, "newestIncoming"),
|
|
1551
|
+
anchor: /* @__PURE__ */ __name(async (chatId, at) => {
|
|
1552
|
+
const owner = key();
|
|
1553
|
+
if (!owner)
|
|
1554
|
+
return void 0;
|
|
1555
|
+
const window = at === void 0 ? { at: (/* @__PURE__ */ new Date(0)).toISOString(), before: 0, after: 1 } : { at, before: 1, after: 0 };
|
|
1556
|
+
return (await (await store()).messages.messagesWindow(owner, chatId, window))[0];
|
|
1557
|
+
}, "anchor"),
|
|
1558
|
+
wroteTo: /* @__PURE__ */ __name(async (personId) => {
|
|
1559
|
+
const owner = key();
|
|
1560
|
+
if (!owner)
|
|
1561
|
+
return false;
|
|
1562
|
+
const opened = await store();
|
|
1563
|
+
const dialogs = /* @__PURE__ */ new Set([
|
|
1564
|
+
personId,
|
|
1565
|
+
...(await opened.chats.chatsWith(owner, personId)).filter((chat) => chat.kind === "dialog").map((chat) => chat.id)
|
|
1566
|
+
]);
|
|
1567
|
+
for (const chatId of dialogs)
|
|
1568
|
+
if ((await opened.messages.messages(owner, chatId, { limit: 1, outgoing: true })).items.length > 0)
|
|
1569
|
+
return true;
|
|
1570
|
+
return false;
|
|
1571
|
+
}, "wroteTo"),
|
|
1572
|
+
pollVotes: /* @__PURE__ */ __name(async (chatId, messageId) => {
|
|
1573
|
+
const owner = key();
|
|
1574
|
+
return owner ? (await store()).messages.pollVotes(owner, chatId, messageId) : [];
|
|
1575
|
+
}, "pollVotes"),
|
|
1576
|
+
reactors: /* @__PURE__ */ __name(async (chatId, messageId) => {
|
|
1577
|
+
const owner = key();
|
|
1578
|
+
return owner ? (await store()).messages.reactors(owner, chatId, messageId) : [];
|
|
1579
|
+
}, "reactors")
|
|
1580
|
+
};
|
|
1581
|
+
}, "storedMessages");
|
|
1582
|
+
var connectFor = /* @__PURE__ */ __name((command, messenger, base, options = {}) => {
|
|
1583
|
+
const { relayed } = environmentOf(command);
|
|
1584
|
+
if (!relayed)
|
|
1585
|
+
return messenger.connect(command, base, options);
|
|
1586
|
+
if (base.settings.profile !== relayed.profile) {
|
|
1587
|
+
return Promise.reject(new CliError("permission_error", `this serve holds profile ${relayed.profile}; it does not run commands for profile ${base.settings.profile}`, { sent: false }));
|
|
1588
|
+
}
|
|
1589
|
+
return Promise.resolve(relayed.connection());
|
|
1590
|
+
}, "connectFor");
|
|
1591
|
+
var SAVES_WAIT_MS = 5e3;
|
|
1592
|
+
var REKEY_LOCK_WAIT_MS = 250;
|
|
1593
|
+
var settled = /* @__PURE__ */ __name(async (pending, ms) => {
|
|
1594
|
+
if (pending.size === 0)
|
|
1595
|
+
return true;
|
|
1596
|
+
let timer;
|
|
1597
|
+
const late = new Promise((resolve) => {
|
|
1598
|
+
timer = setTimeout(() => resolve(false), ms);
|
|
1599
|
+
});
|
|
1600
|
+
try {
|
|
1601
|
+
return await Promise.race([Promise.all([...pending]).then(() => true), late]);
|
|
1602
|
+
} finally {
|
|
1603
|
+
clearTimeout(timer);
|
|
1604
|
+
}
|
|
1605
|
+
}, "settled");
|
|
1606
|
+
var listened = /* @__PURE__ */ __name(({ run, operation, what: what2 }, events, warn, waitMs) => {
|
|
1607
|
+
const abort = new AbortController();
|
|
1608
|
+
let closing = false;
|
|
1609
|
+
const ended = Promise.resolve().then(() => run(abort.signal)).catch((error) => {
|
|
1610
|
+
if (closing)
|
|
1611
|
+
return;
|
|
1612
|
+
const warning = { event: "warning", code: "listen_failed", operation };
|
|
1613
|
+
events(warning);
|
|
1614
|
+
warn(`${what2} arriving during this command are not saved: ${error instanceof Error ? error.message : String(error)}`, warning);
|
|
1615
|
+
});
|
|
1616
|
+
return {
|
|
1617
|
+
operation,
|
|
1618
|
+
what: what2,
|
|
1619
|
+
closing: /* @__PURE__ */ __name(() => {
|
|
1620
|
+
closing = true;
|
|
1621
|
+
}, "closing"),
|
|
1622
|
+
stop: /* @__PURE__ */ __name(() => {
|
|
1623
|
+
abort.abort();
|
|
1624
|
+
return settled(/* @__PURE__ */ new Set([ended]), waitMs);
|
|
1625
|
+
}, "stop")
|
|
1626
|
+
};
|
|
1627
|
+
}, "listened");
|
|
1628
|
+
var passivesOf = /* @__PURE__ */ __name((adapter) => [
|
|
1629
|
+
...adapter.listen ? [
|
|
1630
|
+
{
|
|
1631
|
+
run: /* @__PURE__ */ __name((signal) => adapter.listen?.(() => {
|
|
1632
|
+
}, signal), "run"),
|
|
1633
|
+
operation: "messages.listen",
|
|
1634
|
+
what: "messages"
|
|
1635
|
+
}
|
|
1636
|
+
] : [],
|
|
1637
|
+
...adapter.listenHistory ? [
|
|
1638
|
+
{
|
|
1639
|
+
run: /* @__PURE__ */ __name((signal) => adapter.listenHistory?.(() => {
|
|
1640
|
+
}, signal), "run"),
|
|
1641
|
+
operation: "history.listen",
|
|
1642
|
+
what: "history batches"
|
|
1643
|
+
}
|
|
1644
|
+
] : []
|
|
1645
|
+
], "passivesOf");
|
|
1646
|
+
var rekeyRecipients = /* @__PURE__ */ __name(async (app, profile, env, warn, from, to) => {
|
|
1647
|
+
try {
|
|
1648
|
+
await new RecipientList(recipientsPathFor(app, profile, env), app.command, REKEY_LOCK_WAIT_MS).rekey(from, to);
|
|
1649
|
+
} catch (error) {
|
|
1650
|
+
warn(`the recipient list still names ${from}: ${error instanceof Error ? error.message : String(error)}`, {
|
|
1651
|
+
event: "warning",
|
|
1652
|
+
code: "recipients_not_rekeyed",
|
|
1653
|
+
operation: "recipients.rekey"
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
}, "rekeyRecipients");
|
|
1657
|
+
var waitsOf = /* @__PURE__ */ __name((command) => {
|
|
1658
|
+
const { closeWaitMs, rekeyPacing } = environmentOf(command);
|
|
1659
|
+
return { ...closeWaitMs === void 0 ? {} : { closeWaitMs }, ...rekeyPacing ? { rekeyPacing } : {} };
|
|
1660
|
+
}, "waitsOf");
|
|
1661
|
+
var connected = /* @__PURE__ */ __name((connection, { app, provider, name, deletedWithoutChat, pace, history, savedChatId, unreadCounts }, { settings, env, renderer }, events, store = lazyStore(env, app.command, app.version), { listen: watching = false, closeWaitMs = SAVES_WAIT_MS, rekeyPacing } = {}) => {
|
|
1662
|
+
const self = connection.self();
|
|
1663
|
+
if (self !== null)
|
|
1664
|
+
rememberAccount(app, settings.profile, self, env);
|
|
1665
|
+
const memory = new FloodMemory(floodPathFor(app, settings.profile, env));
|
|
1666
|
+
const rate = {
|
|
1667
|
+
...DEFAULT_PACE,
|
|
1668
|
+
...pace,
|
|
1669
|
+
...settings.requestsPerMinute === void 0 ? {} : { perMinute: settings.requestsPerMinute }
|
|
1670
|
+
};
|
|
1671
|
+
const pacer = new Pacer(pacePathFor(app, settings.profile, env), rate);
|
|
1672
|
+
const adapter = observed(flooded(connection, memory, { name: name ?? app.command, warn: renderer.warn, pacer }), events);
|
|
1673
|
+
const pending = /* @__PURE__ */ new Set();
|
|
1674
|
+
const announcing = /* @__PURE__ */ new Set();
|
|
1675
|
+
const closing = new AbortController();
|
|
1676
|
+
const saving = self === null ? adapter : stored(adapter, {
|
|
1677
|
+
account: { provider, account: self },
|
|
1678
|
+
profile: { app: app.command, name: settings.profile },
|
|
1679
|
+
store: store.open,
|
|
1680
|
+
warn: renderer.warn,
|
|
1681
|
+
events,
|
|
1682
|
+
pending,
|
|
1683
|
+
announcing,
|
|
1684
|
+
closing: closing.signal,
|
|
1685
|
+
...deletedWithoutChat ? { deletedWithoutChat } : {},
|
|
1686
|
+
...rekeyPacing ? { rekeyPacing } : {},
|
|
1687
|
+
rekeyed: /* @__PURE__ */ __name((from, to) => rekeyRecipients(app, settings.profile, env, renderer.warn, from, to), "rekeyed"),
|
|
1688
|
+
recipientIds: /* @__PURE__ */ __name(() => (new RecipientList(recipientsPathFor(app, settings.profile, env), app.command).read() ?? []).flatMap((recipient) => [recipient.id, ...recipient.partnerId ? [recipient.partnerId] : []]), "recipientIds"),
|
|
1689
|
+
...history === "store" ? { namesFromStore: savedChatId ? { savedChatId } : {} } : {},
|
|
1690
|
+
...unreadCounts === "store" ? { countsUnread: true } : {}
|
|
1691
|
+
});
|
|
1692
|
+
const passives = history === "store" && self !== null && !watching ? passivesOf(saving).map((passive) => listened(passive, events, renderer.warn, closeWaitMs)) : [];
|
|
1693
|
+
return {
|
|
1694
|
+
adapter: saving,
|
|
1695
|
+
close: /* @__PURE__ */ __name(async () => {
|
|
1696
|
+
for (const passive of passives)
|
|
1697
|
+
passive.closing();
|
|
1698
|
+
try {
|
|
1699
|
+
await connection.close();
|
|
1700
|
+
} finally {
|
|
1701
|
+
try {
|
|
1702
|
+
const ended = await Promise.all(passives.map((passive) => passive.stop()));
|
|
1703
|
+
const warn = renderer.warn;
|
|
1704
|
+
const warned = /* @__PURE__ */ __name((warning, text) => {
|
|
1705
|
+
events(warning);
|
|
1706
|
+
warn(text, warning);
|
|
1707
|
+
}, "warned");
|
|
1708
|
+
passives.forEach(({ operation, what: what2 }, at) => {
|
|
1709
|
+
if (ended[at])
|
|
1710
|
+
return;
|
|
1711
|
+
warned({ event: "warning", code: "store_not_written", operation }, `not saved to the local store: the messenger was still handing on ${what2} at exit`);
|
|
1712
|
+
});
|
|
1713
|
+
await settled(/* @__PURE__ */ new Set([...pending, ...announcing]), closeWaitMs);
|
|
1714
|
+
closing.abort();
|
|
1715
|
+
if (pending.size > 0)
|
|
1716
|
+
warned({ event: "warning", code: "store_not_written", operation: "close" }, "not saved to the local store: the last messages were still being written at exit");
|
|
1717
|
+
if (announcing.size > 0)
|
|
1718
|
+
warned({ event: "warning", code: "stored_chats_failed", operation: "close" }, "the messenger's stored-chats hook did not finish and was abandoned at exit; nothing is lost");
|
|
1719
|
+
} finally {
|
|
1720
|
+
await store.close();
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
}, "close")
|
|
1724
|
+
};
|
|
1725
|
+
}, "connected");
|
|
1726
|
+
var connectedFor = /* @__PURE__ */ __name(async (command, messenger, base, options) => {
|
|
1727
|
+
const store = lazyStore(base.env, messenger.app.command, messenger.app.version);
|
|
1728
|
+
let connection;
|
|
1729
|
+
try {
|
|
1730
|
+
connection = await connectFor(command, messenger, base, {
|
|
1731
|
+
...options,
|
|
1732
|
+
stored: storedMessages(messenger.provider, store.open, () => connection?.self())
|
|
1733
|
+
});
|
|
1734
|
+
} catch (error) {
|
|
1735
|
+
await store.close();
|
|
1736
|
+
throw error;
|
|
1737
|
+
}
|
|
1738
|
+
return {
|
|
1739
|
+
connection,
|
|
1740
|
+
...connected(connection, messenger, base, options.events, store, {
|
|
1741
|
+
...waitsOf(command),
|
|
1742
|
+
...options.listen ? { listen: true } : {}
|
|
1743
|
+
})
|
|
1744
|
+
};
|
|
1745
|
+
}, "connectedFor");
|
|
1746
|
+
var markedProfile = /* @__PURE__ */ __name((messenger, profile, env) => messenger.profileState?.(profile, env) === "signed-out" ? `${profile} (not signed in)` : profile, "markedProfile");
|
|
1747
|
+
var unrecorded = /* @__PURE__ */ __name((command, messenger, profile, { offline, profilesHere }, env) => {
|
|
1748
|
+
if (!profilesHere.includes(profile)) {
|
|
1749
|
+
return new CliError("not_found", `no profile named "${profile}" \u2014 profiles here: ${profilesHere.length > 0 ? profilesHere.map((one) => markedProfile(messenger, one, env)).join(", ") : "none"}`, { profiles: profilesHere });
|
|
1750
|
+
}
|
|
1751
|
+
const words = `${messenger.app.command} ${asFirstWord(profile)}`;
|
|
1752
|
+
const setup = rootOf(command).commands.some((one) => one.name() === "setup") ? `\`${words}setup\`` : void 0;
|
|
1753
|
+
if (messenger.history === "store")
|
|
1754
|
+
return new CliError("not_found", `nothing recorded for profile "${profile}" yet \u2014 ${setup ? `link it with ${setup}, then ` : ""}run \`${words}serve\` or \`${words}watch\` once to fill the local store`);
|
|
1755
|
+
const ways = [
|
|
1756
|
+
...setup ? [`log in with ${setup}`] : [],
|
|
1757
|
+
...offline ? ["run the command once without --offline"] : []
|
|
1758
|
+
];
|
|
1759
|
+
return new CliError("not_found", `nothing recorded for profile "${profile}" yet \u2014 ${ways.length > 0 ? ways.join(", or ") : "log in to it first"}`);
|
|
1760
|
+
}, "unrecorded");
|
|
1761
|
+
var warnUnserved = /* @__PURE__ */ __name(async (messenger, profile, { env, renderer }, store) => {
|
|
1762
|
+
if (servingProfiles(messenger.app, env).includes(profile))
|
|
1763
|
+
return;
|
|
1764
|
+
const account = recalledAccount(messenger.app, messenger.provider, profile, env);
|
|
1765
|
+
if (!account)
|
|
1766
|
+
return;
|
|
1767
|
+
const newest = (await store.chats.chatStats(account)).map((one) => one.newestAt).reduce((latest, at) => at !== null && (latest === null || at > latest) ? at : latest, null);
|
|
1768
|
+
renderer.warn(`no \`${messenger.app.command} ${profile} serve\` keeps profile "${profile}" up to date \u2014 ` + (newest === null ? "the local store holds no messages yet" : `the newest stored message is from ${newest}`));
|
|
1769
|
+
}, "warnUnserved");
|
|
1770
|
+
var pathOf2 = /* @__PURE__ */ __name((command) => {
|
|
1771
|
+
const words = [];
|
|
1772
|
+
for (let at = command; at?.parent; at = at.parent)
|
|
1773
|
+
words.unshift(at.name());
|
|
1774
|
+
return words;
|
|
1775
|
+
}, "pathOf");
|
|
1776
|
+
var refuseDenied = /* @__PURE__ */ __name((command, settings) => {
|
|
1777
|
+
for (const key of readKeysForCommand(pathOf2(command))) {
|
|
1778
|
+
const { level, key: named } = levelFor(settings.permissions, key);
|
|
1779
|
+
if (level !== "deny")
|
|
1780
|
+
continue;
|
|
1781
|
+
throw new CliError("permission_error", `profile ${settings.profile} denies ${key} (permissions.${named} is deny, from the ${settings.permissionSources[named ?? ""] ?? "default"})`, { permission: key });
|
|
1782
|
+
}
|
|
1783
|
+
}, "refuseDenied");
|
|
1784
|
+
var refuseLocalWrite = /* @__PURE__ */ __name((context, command, permission) => {
|
|
1785
|
+
const { settings } = context;
|
|
1786
|
+
const { level, key } = levelFor(settings.permissions, permission);
|
|
1787
|
+
if (level === "allow" || level === "ask" && context.agent)
|
|
1788
|
+
return;
|
|
1789
|
+
if (key === CEILING_KEY)
|
|
1790
|
+
throw ceilingRefusal(permission, command);
|
|
1791
|
+
throw new CliError(level === "ask" ? "confirmation_required" : "permission_error", `profile ${settings.profile} does not let ${permission} write (permissions.${key} is ${level}, from the ${settings.permissionSources[key ?? ""] ?? "default"}); to allow it: ${command} ${settings.profile} config set permissions.${permission} allow`, { permission });
|
|
1792
|
+
}, "refuseLocalWrite");
|
|
1793
|
+
var messengerContext = /* @__PURE__ */ __name((command, messenger, resolving = {}) => {
|
|
1794
|
+
const { app, provider } = messenger;
|
|
1795
|
+
const base = baseContext(command, (flags, options) => messenger.resolveSettings(flags, { ...options, ...resolving }));
|
|
1796
|
+
const { profile } = base.settings;
|
|
1797
|
+
refuseDenied(command, base.settings);
|
|
1798
|
+
refuseLacking(messenger, pathOf2(command));
|
|
1799
|
+
refuseUnknownPermissionKeys(command, base.settings, app.command, messenger.permissionKey);
|
|
1800
|
+
const warn = /* @__PURE__ */ __name((text, warning) => base.renderer.warn(text, warning), "warn");
|
|
1801
|
+
const accountFor = /* @__PURE__ */ __name(() => {
|
|
1802
|
+
const account = messenger.accountOf?.(command) ?? recalledAccount(app, provider, profile, base.env);
|
|
1803
|
+
if (!account)
|
|
1804
|
+
throw unrecorded(command, messenger, profile, base.settings, base.env);
|
|
1805
|
+
return account;
|
|
1806
|
+
}, "accountFor");
|
|
1807
|
+
const guard = messenger.guard?.(command, base.settings, warn) ?? guardFor(app, base.settings, warn, base.env, terminalAsker(command));
|
|
1808
|
+
const { embedders } = environmentOf(command);
|
|
1809
|
+
return {
|
|
1810
|
+
...base,
|
|
1811
|
+
agent: environmentOf(command).agent !== void 0,
|
|
1812
|
+
profile,
|
|
1813
|
+
stdin: environmentOf(command).stdin ?? process.stdin,
|
|
1814
|
+
...environmentOf(command).recognizer ? { recognizer: environmentOf(command).recognizer } : {},
|
|
1815
|
+
guard,
|
|
1816
|
+
withMessenger: /* @__PURE__ */ __name((work, options = {}) => base.run(async (events) => {
|
|
1817
|
+
if (base.settings.offline)
|
|
1818
|
+
throw new CliError("validation_error", OFFLINE);
|
|
1819
|
+
const { connection, adapter, close } = await connectedFor(command, messenger, base, { ...options, events });
|
|
1820
|
+
base.track(connection);
|
|
1821
|
+
try {
|
|
1822
|
+
return await work(adapter);
|
|
1823
|
+
} finally {
|
|
1824
|
+
await close();
|
|
1825
|
+
}
|
|
1826
|
+
}, { unbounded: options.listen === true }), "withMessenger"),
|
|
1827
|
+
withStore: /* @__PURE__ */ __name((work, { name } = {}) => base.run(async () => {
|
|
1828
|
+
const account = accountFor();
|
|
1829
|
+
const store = await openStore({ env: base.env, command: app.command, version: app.version });
|
|
1830
|
+
try {
|
|
1831
|
+
return await work(store, account);
|
|
1832
|
+
} finally {
|
|
1833
|
+
await store.close();
|
|
1834
|
+
}
|
|
1835
|
+
}, name === void 0 ? {} : { name }), "withStore"),
|
|
1836
|
+
withServices: /* @__PURE__ */ __name((work, { name } = {}) => base.run(async (events) => {
|
|
1837
|
+
let held;
|
|
1838
|
+
let store;
|
|
1839
|
+
let warned;
|
|
1840
|
+
let released;
|
|
1841
|
+
const releaseConnection = /* @__PURE__ */ __name(() => released ??= held?.close() ?? Promise.resolve(), "releaseConnection");
|
|
1842
|
+
const openOnce = /* @__PURE__ */ __name(() => store ??= openStore({ env: base.env, command: app.command, version: app.version }), "openOnce");
|
|
1843
|
+
const deps = {
|
|
1844
|
+
messenger,
|
|
1845
|
+
profile,
|
|
1846
|
+
env: base.env,
|
|
1847
|
+
offline: base.settings.offline,
|
|
1848
|
+
reads: messenger.history ?? "server",
|
|
1849
|
+
// Turning recording off by name (--no-record, or record: false) keeps no search history either.
|
|
1850
|
+
history: base.settings.record,
|
|
1851
|
+
searchCatchUp: base.settings.searchCatchUp,
|
|
1852
|
+
pictures: base.settings.pictureSearch,
|
|
1853
|
+
reranker: base.settings.searchReranker,
|
|
1854
|
+
guard,
|
|
1855
|
+
...embedders ? { embedders } : {},
|
|
1856
|
+
connection: /* @__PURE__ */ __name(async () => {
|
|
1857
|
+
if (base.settings.offline)
|
|
1858
|
+
throw new CliError("validation_error", OFFLINE);
|
|
1859
|
+
if (released)
|
|
1860
|
+
throw new CliError("validation_error", "the read connection has already been released");
|
|
1861
|
+
if (!held) {
|
|
1862
|
+
let connection;
|
|
1863
|
+
connection = await connectFor(command, messenger, base, {
|
|
1864
|
+
events,
|
|
1865
|
+
stored: storedMessages(messenger.provider, openOnce, () => connection?.self())
|
|
1866
|
+
});
|
|
1867
|
+
if (released) {
|
|
1868
|
+
await connection.close();
|
|
1869
|
+
throw new CliError("validation_error", "the read connection has already been released");
|
|
1870
|
+
}
|
|
1871
|
+
base.track(connection);
|
|
1872
|
+
held = connected(connection, messenger, base, events, void 0, waitsOf(command));
|
|
1873
|
+
}
|
|
1874
|
+
return held.adapter;
|
|
1875
|
+
}, "connection"),
|
|
1876
|
+
account: /* @__PURE__ */ __name(async () => accountFor(), "account"),
|
|
1877
|
+
// The connect's stored lookup opens the store too; only a service reading its answer there warns.
|
|
1878
|
+
store: /* @__PURE__ */ __name(() => {
|
|
1879
|
+
warned ??= openOnce().then(async (opened) => {
|
|
1880
|
+
if (messenger.history === "store")
|
|
1881
|
+
await warnUnserved(messenger, profile, base, opened);
|
|
1882
|
+
return opened;
|
|
1883
|
+
});
|
|
1884
|
+
return warned;
|
|
1885
|
+
}, "store")
|
|
1886
|
+
};
|
|
1887
|
+
try {
|
|
1888
|
+
return await work(servicesFor(deps), async (read) => {
|
|
1889
|
+
try {
|
|
1890
|
+
return await read(await deps.connection());
|
|
1891
|
+
} finally {
|
|
1892
|
+
await releaseConnection();
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
} finally {
|
|
1896
|
+
try {
|
|
1897
|
+
await releaseConnection();
|
|
1898
|
+
} finally {
|
|
1899
|
+
if (store)
|
|
1900
|
+
await (await store.catch(() => void 0))?.close();
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}, name === void 0 ? {} : { name }), "withServices")
|
|
1904
|
+
};
|
|
1905
|
+
}, "messengerContext");
|
|
1906
|
+
|
|
1907
|
+
export {
|
|
1908
|
+
holdEnd,
|
|
1909
|
+
floodPathFor,
|
|
1910
|
+
FloodMemory,
|
|
1911
|
+
sendsPathFor,
|
|
1912
|
+
SendJournal,
|
|
1913
|
+
recipientsPathFor,
|
|
1914
|
+
RecipientList,
|
|
1915
|
+
ceilingRefusal,
|
|
1916
|
+
levelRefusal,
|
|
1917
|
+
sendGuard,
|
|
1918
|
+
guardFor,
|
|
1919
|
+
commandPermissions,
|
|
1920
|
+
knownPermissionKeys,
|
|
1921
|
+
knownBeside,
|
|
1922
|
+
unknownPermissionIssues,
|
|
1923
|
+
refuseUnknownPermissionKeys,
|
|
1924
|
+
answerOf,
|
|
1925
|
+
terminalAsker,
|
|
1926
|
+
DEFAULT_PACE,
|
|
1927
|
+
pacePathFor,
|
|
1928
|
+
Pacer,
|
|
1929
|
+
connectedFor,
|
|
1930
|
+
refuseLocalWrite,
|
|
1931
|
+
messengerContext
|
|
1932
|
+
};
|