@moxxy/plugin-channel-slack 0.27.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/LICENSE +21 -0
- package/dist/channel/slack-client.d.ts +53 -0
- package/dist/channel/slack-client.d.ts.map +1 -0
- package/dist/channel/slack-client.js +82 -0
- package/dist/channel/slack-client.js.map +1 -0
- package/dist/channel/turn-runner.d.ts +39 -0
- package/dist/channel/turn-runner.d.ts.map +1 -0
- package/dist/channel/turn-runner.js +81 -0
- package/dist/channel/turn-runner.js.map +1 -0
- package/dist/channel.d.ts +100 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +286 -0
- package/dist/channel.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +207 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +50 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +78 -0
- package/dist/keys.js.map +1 -0
- package/dist/pair-flow.d.ts +18 -0
- package/dist/pair-flow.d.ts.map +1 -0
- package/dist/pair-flow.js +106 -0
- package/dist/pair-flow.js.map +1 -0
- package/dist/permission.d.ts +27 -0
- package/dist/permission.d.ts.map +1 -0
- package/dist/permission.js +33 -0
- package/dist/permission.js.map +1 -0
- package/dist/server/dedupe.d.ts +8 -0
- package/dist/server/dedupe.d.ts.map +1 -0
- package/dist/server/dedupe.js +8 -0
- package/dist/server/dedupe.js.map +1 -0
- package/dist/server/ingest-server.d.ts +80 -0
- package/dist/server/ingest-server.d.ts.map +1 -0
- package/dist/server/ingest-server.js +142 -0
- package/dist/server/ingest-server.js.map +1 -0
- package/dist/server/schema.d.ts +257 -0
- package/dist/server/schema.d.ts.map +1 -0
- package/dist/server/schema.js +69 -0
- package/dist/server/schema.js.map +1 -0
- package/dist/server/verify.d.ts +39 -0
- package/dist/server/verify.d.ts.map +1 -0
- package/dist/server/verify.js +73 -0
- package/dist/server/verify.js.map +1 -0
- package/dist/setup-wizard.d.ts +17 -0
- package/dist/setup-wizard.d.ts.map +1 -0
- package/dist/setup-wizard.js +92 -0
- package/dist/setup-wizard.js.map +1 -0
- package/package.json +97 -0
- package/src/channel/slack-client.ts +123 -0
- package/src/channel/turn-runner.test.ts +143 -0
- package/src/channel/turn-runner.ts +107 -0
- package/src/channel.ts +378 -0
- package/src/index.ts +254 -0
- package/src/keys.ts +96 -0
- package/src/pair-flow.ts +119 -0
- package/src/permission.test.ts +65 -0
- package/src/permission.ts +42 -0
- package/src/server/dedupe.ts +7 -0
- package/src/server/ingest-server.test.ts +280 -0
- package/src/server/ingest-server.ts +205 -0
- package/src/server/schema.test.ts +67 -0
- package/src/server/schema.ts +77 -0
- package/src/server/verify.test.ts +132 -0
- package/src/server/verify.ts +88 -0
- package/src/setup-wizard.ts +121 -0
- package/src/subcommands.test.ts +189 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { z } from '@moxxy/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* zod schemas for the Slack Events API request bodies we accept. Every inbound
|
|
4
|
+
* body is validated against these BEFORE any field is read or the session is
|
|
5
|
+
* touched (AGENTS.md A8: validate inbound frames with zod first). Unknown event
|
|
6
|
+
* subtypes parse to the permissive envelope so we never throw on a Slack event
|
|
7
|
+
* type we don't subscribe to — we just ignore it.
|
|
8
|
+
*/
|
|
9
|
+
/** The URL-verification handshake Slack sends once when you set the Request URL. */
|
|
10
|
+
export declare const urlVerificationSchema: z.ZodObject<{
|
|
11
|
+
type: z.ZodLiteral<"url_verification">;
|
|
12
|
+
token: z.ZodOptional<z.ZodString>;
|
|
13
|
+
challenge: z.ZodString;
|
|
14
|
+
}, "strip", z.ZodTypeAny, {
|
|
15
|
+
type: "url_verification";
|
|
16
|
+
challenge: string;
|
|
17
|
+
token?: string | undefined;
|
|
18
|
+
}, {
|
|
19
|
+
type: "url_verification";
|
|
20
|
+
challenge: string;
|
|
21
|
+
token?: string | undefined;
|
|
22
|
+
}>;
|
|
23
|
+
export type SlackUrlVerification = z.infer<typeof urlVerificationSchema>;
|
|
24
|
+
/** A `message` or `app_mention` inner event. Loose on the many optional fields. */
|
|
25
|
+
export declare const messageEventSchema: z.ZodObject<{
|
|
26
|
+
type: z.ZodEnum<["message", "app_mention"]>;
|
|
27
|
+
/** Author user id. Absent for some bot/system messages. */
|
|
28
|
+
user: z.ZodOptional<z.ZodString>;
|
|
29
|
+
/** Set for messages posted by a bot integration (including our own). */
|
|
30
|
+
bot_id: z.ZodOptional<z.ZodString>;
|
|
31
|
+
/** Message text (may be empty for attachment-only messages). */
|
|
32
|
+
text: z.ZodOptional<z.ZodString>;
|
|
33
|
+
/** Channel the event occurred in. */
|
|
34
|
+
channel: z.ZodOptional<z.ZodString>;
|
|
35
|
+
/** Message timestamp (also the thread root when no `thread_ts`). */
|
|
36
|
+
ts: z.ZodOptional<z.ZodString>;
|
|
37
|
+
/** Present when the message is inside a thread. */
|
|
38
|
+
thread_ts: z.ZodOptional<z.ZodString>;
|
|
39
|
+
/** `message_changed` / `message_deleted` etc. — we ignore edited/system subtypes. */
|
|
40
|
+
subtype: z.ZodOptional<z.ZodString>;
|
|
41
|
+
}, "strip", z.ZodTypeAny, {
|
|
42
|
+
type: "message" | "app_mention";
|
|
43
|
+
channel?: string | undefined;
|
|
44
|
+
text?: string | undefined;
|
|
45
|
+
thread_ts?: string | undefined;
|
|
46
|
+
ts?: string | undefined;
|
|
47
|
+
user?: string | undefined;
|
|
48
|
+
bot_id?: string | undefined;
|
|
49
|
+
subtype?: string | undefined;
|
|
50
|
+
}, {
|
|
51
|
+
type: "message" | "app_mention";
|
|
52
|
+
channel?: string | undefined;
|
|
53
|
+
text?: string | undefined;
|
|
54
|
+
thread_ts?: string | undefined;
|
|
55
|
+
ts?: string | undefined;
|
|
56
|
+
user?: string | undefined;
|
|
57
|
+
bot_id?: string | undefined;
|
|
58
|
+
subtype?: string | undefined;
|
|
59
|
+
}>;
|
|
60
|
+
export type SlackMessageEvent = z.infer<typeof messageEventSchema>;
|
|
61
|
+
/** The `event_callback` envelope wrapping an inner event. */
|
|
62
|
+
export declare const eventCallbackSchema: z.ZodObject<{
|
|
63
|
+
type: z.ZodLiteral<"event_callback">;
|
|
64
|
+
/** Workspace/team id — the unit we pair against. */
|
|
65
|
+
team_id: z.ZodOptional<z.ZodString>;
|
|
66
|
+
api_app_id: z.ZodOptional<z.ZodString>;
|
|
67
|
+
/** Stable per-event id used for at-least-once dedupe. */
|
|
68
|
+
event_id: z.ZodOptional<z.ZodString>;
|
|
69
|
+
event_time: z.ZodOptional<z.ZodNumber>;
|
|
70
|
+
/** The actual event. We only act on message/app_mention; others pass through. */
|
|
71
|
+
event: z.ZodObject<{
|
|
72
|
+
type: z.ZodString;
|
|
73
|
+
user: z.ZodOptional<z.ZodString>;
|
|
74
|
+
bot_id: z.ZodOptional<z.ZodString>;
|
|
75
|
+
text: z.ZodOptional<z.ZodString>;
|
|
76
|
+
channel: z.ZodOptional<z.ZodString>;
|
|
77
|
+
ts: z.ZodOptional<z.ZodString>;
|
|
78
|
+
thread_ts: z.ZodOptional<z.ZodString>;
|
|
79
|
+
subtype: z.ZodOptional<z.ZodString>;
|
|
80
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
81
|
+
type: z.ZodString;
|
|
82
|
+
user: z.ZodOptional<z.ZodString>;
|
|
83
|
+
bot_id: z.ZodOptional<z.ZodString>;
|
|
84
|
+
text: z.ZodOptional<z.ZodString>;
|
|
85
|
+
channel: z.ZodOptional<z.ZodString>;
|
|
86
|
+
ts: z.ZodOptional<z.ZodString>;
|
|
87
|
+
thread_ts: z.ZodOptional<z.ZodString>;
|
|
88
|
+
subtype: z.ZodOptional<z.ZodString>;
|
|
89
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
90
|
+
type: z.ZodString;
|
|
91
|
+
user: z.ZodOptional<z.ZodString>;
|
|
92
|
+
bot_id: z.ZodOptional<z.ZodString>;
|
|
93
|
+
text: z.ZodOptional<z.ZodString>;
|
|
94
|
+
channel: z.ZodOptional<z.ZodString>;
|
|
95
|
+
ts: z.ZodOptional<z.ZodString>;
|
|
96
|
+
thread_ts: z.ZodOptional<z.ZodString>;
|
|
97
|
+
subtype: z.ZodOptional<z.ZodString>;
|
|
98
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
99
|
+
/** Bot user ids this event was authorized for — lets us detect self-authored messages. */
|
|
100
|
+
authorizations: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
101
|
+
user_id: z.ZodOptional<z.ZodString>;
|
|
102
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
103
|
+
user_id: z.ZodOptional<z.ZodString>;
|
|
104
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
105
|
+
user_id: z.ZodOptional<z.ZodString>;
|
|
106
|
+
}, z.ZodTypeAny, "passthrough">>, "many">>;
|
|
107
|
+
}, "strip", z.ZodTypeAny, {
|
|
108
|
+
type: "event_callback";
|
|
109
|
+
event: {
|
|
110
|
+
type: string;
|
|
111
|
+
channel?: string | undefined;
|
|
112
|
+
text?: string | undefined;
|
|
113
|
+
thread_ts?: string | undefined;
|
|
114
|
+
ts?: string | undefined;
|
|
115
|
+
user?: string | undefined;
|
|
116
|
+
bot_id?: string | undefined;
|
|
117
|
+
subtype?: string | undefined;
|
|
118
|
+
} & {
|
|
119
|
+
[k: string]: unknown;
|
|
120
|
+
};
|
|
121
|
+
team_id?: string | undefined;
|
|
122
|
+
api_app_id?: string | undefined;
|
|
123
|
+
event_id?: string | undefined;
|
|
124
|
+
event_time?: number | undefined;
|
|
125
|
+
authorizations?: z.objectOutputType<{
|
|
126
|
+
user_id: z.ZodOptional<z.ZodString>;
|
|
127
|
+
}, z.ZodTypeAny, "passthrough">[] | undefined;
|
|
128
|
+
}, {
|
|
129
|
+
type: "event_callback";
|
|
130
|
+
event: {
|
|
131
|
+
type: string;
|
|
132
|
+
channel?: string | undefined;
|
|
133
|
+
text?: string | undefined;
|
|
134
|
+
thread_ts?: string | undefined;
|
|
135
|
+
ts?: string | undefined;
|
|
136
|
+
user?: string | undefined;
|
|
137
|
+
bot_id?: string | undefined;
|
|
138
|
+
subtype?: string | undefined;
|
|
139
|
+
} & {
|
|
140
|
+
[k: string]: unknown;
|
|
141
|
+
};
|
|
142
|
+
team_id?: string | undefined;
|
|
143
|
+
api_app_id?: string | undefined;
|
|
144
|
+
event_id?: string | undefined;
|
|
145
|
+
event_time?: number | undefined;
|
|
146
|
+
authorizations?: z.objectInputType<{
|
|
147
|
+
user_id: z.ZodOptional<z.ZodString>;
|
|
148
|
+
}, z.ZodTypeAny, "passthrough">[] | undefined;
|
|
149
|
+
}>;
|
|
150
|
+
export type SlackEventCallback = z.infer<typeof eventCallbackSchema>;
|
|
151
|
+
/**
|
|
152
|
+
* The top-level Slack request envelope: either the one-off url_verification
|
|
153
|
+
* handshake or an event_callback. A discriminated union on `type` so a body
|
|
154
|
+
* that is neither is rejected with a clear error.
|
|
155
|
+
*/
|
|
156
|
+
export declare const slackEnvelopeSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
157
|
+
type: z.ZodLiteral<"url_verification">;
|
|
158
|
+
token: z.ZodOptional<z.ZodString>;
|
|
159
|
+
challenge: z.ZodString;
|
|
160
|
+
}, "strip", z.ZodTypeAny, {
|
|
161
|
+
type: "url_verification";
|
|
162
|
+
challenge: string;
|
|
163
|
+
token?: string | undefined;
|
|
164
|
+
}, {
|
|
165
|
+
type: "url_verification";
|
|
166
|
+
challenge: string;
|
|
167
|
+
token?: string | undefined;
|
|
168
|
+
}>, z.ZodObject<{
|
|
169
|
+
type: z.ZodLiteral<"event_callback">;
|
|
170
|
+
/** Workspace/team id — the unit we pair against. */
|
|
171
|
+
team_id: z.ZodOptional<z.ZodString>;
|
|
172
|
+
api_app_id: z.ZodOptional<z.ZodString>;
|
|
173
|
+
/** Stable per-event id used for at-least-once dedupe. */
|
|
174
|
+
event_id: z.ZodOptional<z.ZodString>;
|
|
175
|
+
event_time: z.ZodOptional<z.ZodNumber>;
|
|
176
|
+
/** The actual event. We only act on message/app_mention; others pass through. */
|
|
177
|
+
event: z.ZodObject<{
|
|
178
|
+
type: z.ZodString;
|
|
179
|
+
user: z.ZodOptional<z.ZodString>;
|
|
180
|
+
bot_id: z.ZodOptional<z.ZodString>;
|
|
181
|
+
text: z.ZodOptional<z.ZodString>;
|
|
182
|
+
channel: z.ZodOptional<z.ZodString>;
|
|
183
|
+
ts: z.ZodOptional<z.ZodString>;
|
|
184
|
+
thread_ts: z.ZodOptional<z.ZodString>;
|
|
185
|
+
subtype: z.ZodOptional<z.ZodString>;
|
|
186
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
187
|
+
type: z.ZodString;
|
|
188
|
+
user: z.ZodOptional<z.ZodString>;
|
|
189
|
+
bot_id: z.ZodOptional<z.ZodString>;
|
|
190
|
+
text: z.ZodOptional<z.ZodString>;
|
|
191
|
+
channel: z.ZodOptional<z.ZodString>;
|
|
192
|
+
ts: z.ZodOptional<z.ZodString>;
|
|
193
|
+
thread_ts: z.ZodOptional<z.ZodString>;
|
|
194
|
+
subtype: z.ZodOptional<z.ZodString>;
|
|
195
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
196
|
+
type: z.ZodString;
|
|
197
|
+
user: z.ZodOptional<z.ZodString>;
|
|
198
|
+
bot_id: z.ZodOptional<z.ZodString>;
|
|
199
|
+
text: z.ZodOptional<z.ZodString>;
|
|
200
|
+
channel: z.ZodOptional<z.ZodString>;
|
|
201
|
+
ts: z.ZodOptional<z.ZodString>;
|
|
202
|
+
thread_ts: z.ZodOptional<z.ZodString>;
|
|
203
|
+
subtype: z.ZodOptional<z.ZodString>;
|
|
204
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
205
|
+
/** Bot user ids this event was authorized for — lets us detect self-authored messages. */
|
|
206
|
+
authorizations: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
207
|
+
user_id: z.ZodOptional<z.ZodString>;
|
|
208
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
209
|
+
user_id: z.ZodOptional<z.ZodString>;
|
|
210
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
211
|
+
user_id: z.ZodOptional<z.ZodString>;
|
|
212
|
+
}, z.ZodTypeAny, "passthrough">>, "many">>;
|
|
213
|
+
}, "strip", z.ZodTypeAny, {
|
|
214
|
+
type: "event_callback";
|
|
215
|
+
event: {
|
|
216
|
+
type: string;
|
|
217
|
+
channel?: string | undefined;
|
|
218
|
+
text?: string | undefined;
|
|
219
|
+
thread_ts?: string | undefined;
|
|
220
|
+
ts?: string | undefined;
|
|
221
|
+
user?: string | undefined;
|
|
222
|
+
bot_id?: string | undefined;
|
|
223
|
+
subtype?: string | undefined;
|
|
224
|
+
} & {
|
|
225
|
+
[k: string]: unknown;
|
|
226
|
+
};
|
|
227
|
+
team_id?: string | undefined;
|
|
228
|
+
api_app_id?: string | undefined;
|
|
229
|
+
event_id?: string | undefined;
|
|
230
|
+
event_time?: number | undefined;
|
|
231
|
+
authorizations?: z.objectOutputType<{
|
|
232
|
+
user_id: z.ZodOptional<z.ZodString>;
|
|
233
|
+
}, z.ZodTypeAny, "passthrough">[] | undefined;
|
|
234
|
+
}, {
|
|
235
|
+
type: "event_callback";
|
|
236
|
+
event: {
|
|
237
|
+
type: string;
|
|
238
|
+
channel?: string | undefined;
|
|
239
|
+
text?: string | undefined;
|
|
240
|
+
thread_ts?: string | undefined;
|
|
241
|
+
ts?: string | undefined;
|
|
242
|
+
user?: string | undefined;
|
|
243
|
+
bot_id?: string | undefined;
|
|
244
|
+
subtype?: string | undefined;
|
|
245
|
+
} & {
|
|
246
|
+
[k: string]: unknown;
|
|
247
|
+
};
|
|
248
|
+
team_id?: string | undefined;
|
|
249
|
+
api_app_id?: string | undefined;
|
|
250
|
+
event_id?: string | undefined;
|
|
251
|
+
event_time?: number | undefined;
|
|
252
|
+
authorizations?: z.objectInputType<{
|
|
253
|
+
user_id: z.ZodOptional<z.ZodString>;
|
|
254
|
+
}, z.ZodTypeAny, "passthrough">[] | undefined;
|
|
255
|
+
}>]>;
|
|
256
|
+
export type SlackEnvelope = z.infer<typeof slackEnvelopeSchema>;
|
|
257
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/server/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAE/B;;;;;;GAMG;AAEH,oFAAoF;AACpF,eAAO,MAAM,qBAAqB;;;;;;;;;;;;EAIhC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEzE,mFAAmF;AACnF,eAAO,MAAM,kBAAkB;;IAE7B,2DAA2D;;IAE3D,wEAAwE;;IAExE,gEAAgE;;IAEhE,qCAAqC;;IAErC,oEAAoE;;IAEpE,mDAAmD;;IAEnD,qFAAqF;;;;;;;;;;;;;;;;;;;;EAErF,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEnE,6DAA6D;AAC7D,eAAO,MAAM,mBAAmB;;IAE9B,oDAAoD;;;IAGpD,yDAAyD;;;IAGzD,iFAAiF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAajF,0FAA0F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAI1F,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAErE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;IA/B9B,oDAAoD;;;IAGpD,yDAAyD;;;IAGzD,iFAAiF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAajF,0FAA0F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAe1F,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from '@moxxy/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* zod schemas for the Slack Events API request bodies we accept. Every inbound
|
|
4
|
+
* body is validated against these BEFORE any field is read or the session is
|
|
5
|
+
* touched (AGENTS.md A8: validate inbound frames with zod first). Unknown event
|
|
6
|
+
* subtypes parse to the permissive envelope so we never throw on a Slack event
|
|
7
|
+
* type we don't subscribe to — we just ignore it.
|
|
8
|
+
*/
|
|
9
|
+
/** The URL-verification handshake Slack sends once when you set the Request URL. */
|
|
10
|
+
export const urlVerificationSchema = z.object({
|
|
11
|
+
type: z.literal('url_verification'),
|
|
12
|
+
token: z.string().optional(),
|
|
13
|
+
challenge: z.string().min(1),
|
|
14
|
+
});
|
|
15
|
+
/** A `message` or `app_mention` inner event. Loose on the many optional fields. */
|
|
16
|
+
export const messageEventSchema = z.object({
|
|
17
|
+
type: z.enum(['message', 'app_mention']),
|
|
18
|
+
/** Author user id. Absent for some bot/system messages. */
|
|
19
|
+
user: z.string().optional(),
|
|
20
|
+
/** Set for messages posted by a bot integration (including our own). */
|
|
21
|
+
bot_id: z.string().optional(),
|
|
22
|
+
/** Message text (may be empty for attachment-only messages). */
|
|
23
|
+
text: z.string().optional(),
|
|
24
|
+
/** Channel the event occurred in. */
|
|
25
|
+
channel: z.string().optional(),
|
|
26
|
+
/** Message timestamp (also the thread root when no `thread_ts`). */
|
|
27
|
+
ts: z.string().optional(),
|
|
28
|
+
/** Present when the message is inside a thread. */
|
|
29
|
+
thread_ts: z.string().optional(),
|
|
30
|
+
/** `message_changed` / `message_deleted` etc. — we ignore edited/system subtypes. */
|
|
31
|
+
subtype: z.string().optional(),
|
|
32
|
+
});
|
|
33
|
+
/** The `event_callback` envelope wrapping an inner event. */
|
|
34
|
+
export const eventCallbackSchema = z.object({
|
|
35
|
+
type: z.literal('event_callback'),
|
|
36
|
+
/** Workspace/team id — the unit we pair against. */
|
|
37
|
+
team_id: z.string().optional(),
|
|
38
|
+
api_app_id: z.string().optional(),
|
|
39
|
+
/** Stable per-event id used for at-least-once dedupe. */
|
|
40
|
+
event_id: z.string().optional(),
|
|
41
|
+
event_time: z.number().optional(),
|
|
42
|
+
/** The actual event. We only act on message/app_mention; others pass through. */
|
|
43
|
+
event: z
|
|
44
|
+
.object({
|
|
45
|
+
type: z.string(),
|
|
46
|
+
user: z.string().optional(),
|
|
47
|
+
bot_id: z.string().optional(),
|
|
48
|
+
text: z.string().optional(),
|
|
49
|
+
channel: z.string().optional(),
|
|
50
|
+
ts: z.string().optional(),
|
|
51
|
+
thread_ts: z.string().optional(),
|
|
52
|
+
subtype: z.string().optional(),
|
|
53
|
+
})
|
|
54
|
+
.passthrough(),
|
|
55
|
+
/** Bot user ids this event was authorized for — lets us detect self-authored messages. */
|
|
56
|
+
authorizations: z
|
|
57
|
+
.array(z.object({ user_id: z.string().optional() }).passthrough())
|
|
58
|
+
.optional(),
|
|
59
|
+
});
|
|
60
|
+
/**
|
|
61
|
+
* The top-level Slack request envelope: either the one-off url_verification
|
|
62
|
+
* handshake or an event_callback. A discriminated union on `type` so a body
|
|
63
|
+
* that is neither is rejected with a clear error.
|
|
64
|
+
*/
|
|
65
|
+
export const slackEnvelopeSchema = z.discriminatedUnion('type', [
|
|
66
|
+
urlVerificationSchema,
|
|
67
|
+
eventCallbackSchema,
|
|
68
|
+
]);
|
|
69
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/server/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack request-signature verification.
|
|
3
|
+
*
|
|
4
|
+
* Slack signs each request with HMAC-SHA256 over the string
|
|
5
|
+
* `v0:{X-Slack-Request-Timestamp}:{rawBody}`, hex-encoded and prefixed `v0=`,
|
|
6
|
+
* delivered in the `X-Slack-Signature` header. This is structurally identical
|
|
7
|
+
* to the Stripe HMAC scheme in `@moxxy/plugin-webhooks/src/verify.ts` — a
|
|
8
|
+
* timestamped HMAC over the raw bytes with a replay window — so the logic here
|
|
9
|
+
* mirrors it: verify over the EXACT raw body bytes (never the reserialized
|
|
10
|
+
* JSON), constant-time compare, and reject deliveries outside a ±5-minute
|
|
11
|
+
* window to bound replay.
|
|
12
|
+
*
|
|
13
|
+
* Always returns a structured verdict; the caller decides whether to log the
|
|
14
|
+
* reason (useful in dev) or hide it (preferable on a public endpoint).
|
|
15
|
+
*
|
|
16
|
+
* See: https://api.slack.com/authentication/verifying-requests-from-slack
|
|
17
|
+
*/
|
|
18
|
+
/** Slack's documented replay window: reject requests older than 5 minutes. */
|
|
19
|
+
export declare const SLACK_REPLAY_WINDOW_SEC: number;
|
|
20
|
+
export type SlackVerifyResult = {
|
|
21
|
+
readonly ok: true;
|
|
22
|
+
} | {
|
|
23
|
+
readonly ok: false;
|
|
24
|
+
readonly reason: string;
|
|
25
|
+
};
|
|
26
|
+
export interface VerifySlackSignatureInput {
|
|
27
|
+
readonly rawBody: Buffer;
|
|
28
|
+
readonly headers: Record<string, string | string[] | undefined>;
|
|
29
|
+
readonly signingSecret: string;
|
|
30
|
+
/** Epoch-ms used to enforce the replay window. Defaults to `Date.now()`. */
|
|
31
|
+
readonly nowMs?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Verify a Slack request signature against the raw body. Pass the EXACT bytes
|
|
35
|
+
* read off the socket, BEFORE `JSON.parse` — reserializing the body changes
|
|
36
|
+
* whitespace/key-order and breaks the HMAC.
|
|
37
|
+
*/
|
|
38
|
+
export declare function verifySlackSignature(input: VerifySlackSignatureInput): SlackVerifyResult;
|
|
39
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/server/verify.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;CAAE,GACrB;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAuBpD,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IAChE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,GAAG,iBAAiB,CA0BxF"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Slack request-signature verification.
|
|
4
|
+
*
|
|
5
|
+
* Slack signs each request with HMAC-SHA256 over the string
|
|
6
|
+
* `v0:{X-Slack-Request-Timestamp}:{rawBody}`, hex-encoded and prefixed `v0=`,
|
|
7
|
+
* delivered in the `X-Slack-Signature` header. This is structurally identical
|
|
8
|
+
* to the Stripe HMAC scheme in `@moxxy/plugin-webhooks/src/verify.ts` — a
|
|
9
|
+
* timestamped HMAC over the raw bytes with a replay window — so the logic here
|
|
10
|
+
* mirrors it: verify over the EXACT raw body bytes (never the reserialized
|
|
11
|
+
* JSON), constant-time compare, and reject deliveries outside a ±5-minute
|
|
12
|
+
* window to bound replay.
|
|
13
|
+
*
|
|
14
|
+
* Always returns a structured verdict; the caller decides whether to log the
|
|
15
|
+
* reason (useful in dev) or hide it (preferable on a public endpoint).
|
|
16
|
+
*
|
|
17
|
+
* See: https://api.slack.com/authentication/verifying-requests-from-slack
|
|
18
|
+
*/
|
|
19
|
+
/** Slack's documented replay window: reject requests older than 5 minutes. */
|
|
20
|
+
export const SLACK_REPLAY_WINDOW_SEC = 60 * 5;
|
|
21
|
+
function lower(headers, name) {
|
|
22
|
+
const v = headers[name.toLowerCase()];
|
|
23
|
+
if (Array.isArray(v))
|
|
24
|
+
return v[0] ?? null;
|
|
25
|
+
return v ?? null;
|
|
26
|
+
}
|
|
27
|
+
/** Constant-time compare of two `v0=…` hex signatures. */
|
|
28
|
+
function safeEqual(a, b) {
|
|
29
|
+
const ab = Buffer.from(a);
|
|
30
|
+
const bb = Buffer.from(b);
|
|
31
|
+
if (ab.length !== bb.length)
|
|
32
|
+
return false;
|
|
33
|
+
try {
|
|
34
|
+
return timingSafeEqual(ab, bb);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Verify a Slack request signature against the raw body. Pass the EXACT bytes
|
|
42
|
+
* read off the socket, BEFORE `JSON.parse` — reserializing the body changes
|
|
43
|
+
* whitespace/key-order and breaks the HMAC.
|
|
44
|
+
*/
|
|
45
|
+
export function verifySlackSignature(input) {
|
|
46
|
+
const { rawBody, headers, signingSecret } = input;
|
|
47
|
+
if (!signingSecret)
|
|
48
|
+
return { ok: false, reason: 'no signing secret configured' };
|
|
49
|
+
const timestamp = lower(headers, 'x-slack-request-timestamp');
|
|
50
|
+
const signature = lower(headers, 'x-slack-signature');
|
|
51
|
+
if (!timestamp)
|
|
52
|
+
return { ok: false, reason: 'missing X-Slack-Request-Timestamp' };
|
|
53
|
+
if (!signature)
|
|
54
|
+
return { ok: false, reason: 'missing X-Slack-Signature' };
|
|
55
|
+
const tsNum = Number(timestamp);
|
|
56
|
+
if (!Number.isFinite(tsNum))
|
|
57
|
+
return { ok: false, reason: 'non-numeric timestamp' };
|
|
58
|
+
const now = input.nowMs ?? Date.now();
|
|
59
|
+
const driftSec = Math.abs(now / 1000 - tsNum);
|
|
60
|
+
if (driftSec > SLACK_REPLAY_WINDOW_SEC) {
|
|
61
|
+
return { ok: false, reason: `timestamp drift ${Math.round(driftSec)}s exceeds replay window` };
|
|
62
|
+
}
|
|
63
|
+
// HMAC over `v0:{ts}:{rawBody}` — note the raw bytes, not a reserialized JSON.
|
|
64
|
+
const base = Buffer.concat([
|
|
65
|
+
Buffer.from(`v0:${timestamp}:`, 'utf8'),
|
|
66
|
+
rawBody,
|
|
67
|
+
]);
|
|
68
|
+
const computed = `v0=${createHmac('sha256', signingSecret).update(base).digest('hex')}`;
|
|
69
|
+
if (safeEqual(computed, signature))
|
|
70
|
+
return { ok: true };
|
|
71
|
+
return { ok: false, reason: 'signature mismatch' };
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/server/verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE1D;;;;;;;;;;;;;;;;GAgBG;AAEH,8EAA8E;AAC9E,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,GAAG,CAAC,CAAC;AAM9C,SAAS,KAAK,CACZ,OAAsD,EACtD,IAAY;IAEZ,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IACtC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAC1C,OAAO,CAAC,IAAI,IAAI,CAAC;AACnB,CAAC;AAED,0DAA0D;AAC1D,SAAS,SAAS,CAAC,CAAS,EAAE,CAAS;IACrC,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1B,IAAI,EAAE,CAAC,MAAM,KAAK,EAAE,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1C,IAAI,CAAC;QACH,OAAO,eAAe,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAUD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAgC;IACnE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,KAAK,CAAC;IAClD,IAAI,CAAC,aAAa;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,8BAA8B,EAAE,CAAC;IAEjF,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,EAAE,2BAA2B,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IACtD,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,mCAAmC,EAAE,CAAC;IAClF,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC;IAE1E,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAChC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;IAEnF,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;IACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,IAAI,GAAG,KAAK,CAAC,CAAC;IAC9C,IAAI,QAAQ,GAAG,uBAAuB,EAAE,CAAC;QACvC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,yBAAyB,EAAE,CAAC;IACjG,CAAC;IAED,+EAA+E;IAC/E,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC,MAAM,SAAS,GAAG,EAAE,MAAM,CAAC;QACvC,OAAO;KACR,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IACxF,IAAI,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACxD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ChannelSubcommandContext } from '@moxxy/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Interactive Slack setup wizard (the channel's `interactiveCommand`).
|
|
4
|
+
*
|
|
5
|
+
* Walks the operator through:
|
|
6
|
+
* 1. paste the bot token (`xoxb-…`) into the vault,
|
|
7
|
+
* 2. paste the signing secret into the vault,
|
|
8
|
+
* 3. validate the token via `auth.test`,
|
|
9
|
+
* 4. choose the working folder + the autonomous tool allow-list,
|
|
10
|
+
* 5. start the channel (opens the proxy tunnel) and PRINT the public Request
|
|
11
|
+
* URL to paste into the Slack app's Event Subscriptions.
|
|
12
|
+
*
|
|
13
|
+
* Headless invocations bypass this and start the channel directly (the dispatch
|
|
14
|
+
* caller decides; this is only reached on a TTY).
|
|
15
|
+
*/
|
|
16
|
+
export declare function runSlackWizard(ctx: ChannelSubcommandContext): Promise<number>;
|
|
17
|
+
//# sourceMappingURL=setup-wizard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-wizard.d.ts","sourceRoot":"","sources":["../src/setup-wizard.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAc3D;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiFnF"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { cancel, intro, isCancel, log, note, outro, password, spinner, text, } from '@clack/prompts';
|
|
2
|
+
import { SLACK_BOT_TOKEN_KEY, SLACK_BOT_TOKEN_RE, SLACK_SIGNING_SECRET_KEY, slackSigningSecretSchema, } from './keys.js';
|
|
3
|
+
import { SlackClient } from './channel/slack-client.js';
|
|
4
|
+
const ANSI = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
5
|
+
const bold = (s) => (ANSI ? `\x1b[1m${s}\x1b[22m` : s);
|
|
6
|
+
const dim = (s) => (ANSI ? `\x1b[2m${s}\x1b[22m` : s);
|
|
7
|
+
/**
|
|
8
|
+
* Interactive Slack setup wizard (the channel's `interactiveCommand`).
|
|
9
|
+
*
|
|
10
|
+
* Walks the operator through:
|
|
11
|
+
* 1. paste the bot token (`xoxb-…`) into the vault,
|
|
12
|
+
* 2. paste the signing secret into the vault,
|
|
13
|
+
* 3. validate the token via `auth.test`,
|
|
14
|
+
* 4. choose the working folder + the autonomous tool allow-list,
|
|
15
|
+
* 5. start the channel (opens the proxy tunnel) and PRINT the public Request
|
|
16
|
+
* URL to paste into the Slack app's Event Subscriptions.
|
|
17
|
+
*
|
|
18
|
+
* Headless invocations bypass this and start the channel directly (the dispatch
|
|
19
|
+
* caller decides; this is only reached on a TTY).
|
|
20
|
+
*/
|
|
21
|
+
export async function runSlackWizard(ctx) {
|
|
22
|
+
const vault = ctx.deps.vault;
|
|
23
|
+
intro(bold('moxxy slack setup'));
|
|
24
|
+
note('Create a Slack app at https://api.slack.com/apps → "From scratch".\n' +
|
|
25
|
+
'• OAuth & Permissions → add the bot scopes app_mentions:read, chat:write,\n' +
|
|
26
|
+
' channels:history → Install to Workspace → copy the Bot User OAuth Token (xoxb-…).\n' +
|
|
27
|
+
'• Basic Information → App Credentials → copy the Signing Secret.\n' +
|
|
28
|
+
'Both go straight into the moxxy vault — no env vars needed.', 'create a Slack app');
|
|
29
|
+
const token = await password({
|
|
30
|
+
message: 'Paste the Bot User OAuth Token',
|
|
31
|
+
mask: '•',
|
|
32
|
+
validate: (v) => {
|
|
33
|
+
if (!v || !v.trim())
|
|
34
|
+
return 'required';
|
|
35
|
+
if (!SLACK_BOT_TOKEN_RE.test(v.trim()))
|
|
36
|
+
return 'expected a token like "xoxb-…"';
|
|
37
|
+
return undefined;
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
if (isCancel(token)) {
|
|
41
|
+
cancel('cancelled.');
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
const secret = await password({
|
|
45
|
+
message: 'Paste the Signing Secret',
|
|
46
|
+
mask: '•',
|
|
47
|
+
validate: (v) => {
|
|
48
|
+
const parsed = slackSigningSecretSchema.safeParse(v);
|
|
49
|
+
return parsed.success ? undefined : parsed.error.issues[0]?.message ?? 'invalid';
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
if (isCancel(secret)) {
|
|
53
|
+
cancel('cancelled.');
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
// Validate the token before persisting anything the operator can act on.
|
|
57
|
+
const spin = spinner();
|
|
58
|
+
spin.start('Validating the bot token (auth.test)…');
|
|
59
|
+
try {
|
|
60
|
+
const client = new SlackClient({ token: String(token).trim() });
|
|
61
|
+
const auth = await client.authTest();
|
|
62
|
+
spin.stop(`Token OK — bot user ${auth.botUserId}${auth.team ? ` on ${auth.team}` : ''}.`);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
spin.stop('Token validation failed.');
|
|
66
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
await vault.set(SLACK_BOT_TOKEN_KEY, String(token).trim(), ['slack']);
|
|
70
|
+
await vault.set(SLACK_SIGNING_SECRET_KEY, String(secret).trim(), ['slack']);
|
|
71
|
+
log.success('Stored bot token + signing secret in the vault.');
|
|
72
|
+
const allow = await text({
|
|
73
|
+
message: 'Autonomous tool allow-list (comma-separated; "*" = all, blank = read-only)',
|
|
74
|
+
placeholder: 'Read, Grep, Glob',
|
|
75
|
+
});
|
|
76
|
+
if (isCancel(allow)) {
|
|
77
|
+
cancel('cancelled.');
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
const allowedTools = String(allow)
|
|
81
|
+
.split(',')
|
|
82
|
+
.map((s) => s.trim())
|
|
83
|
+
.filter(Boolean);
|
|
84
|
+
note('Starting the channel — it opens a proxy tunnel and prints a public Request URL.\n' +
|
|
85
|
+
'Paste that URL into your Slack app → Event Subscriptions → Request URL, then\n' +
|
|
86
|
+
'subscribe to the bot event app_mention. Mention the bot in a channel to pair.', 'next steps');
|
|
87
|
+
log.info('Starting the bot. Press Ctrl+C to stop.');
|
|
88
|
+
outro(dim('handing off to the channel…'));
|
|
89
|
+
// Start in pairing mode so the first @mention establishes trust automatically.
|
|
90
|
+
return ctx.startChannel({ pair: true, allowedTools });
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=setup-wizard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-wizard.js","sourceRoot":"","sources":["../src/setup-wizard.ts"],"names":[],"mappings":"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"}
|