@rackbops/ac-plugin-contract 2.0.0-alpha.33
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/AUTHORING.md +934 -0
- package/LICENSE +201 -0
- package/README.md +29 -0
- package/dist/action-options.d.ts +49 -0
- package/dist/action-options.js +9 -0
- package/dist/attention.d.ts +43 -0
- package/dist/attention.js +12 -0
- package/dist/host-api.d.ts +365 -0
- package/dist/host-api.js +10 -0
- package/dist/index-envelope.d.ts +36 -0
- package/dist/index-envelope.js +2 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +9 -0
- package/dist/issue-filter.d.ts +122 -0
- package/dist/issue-filter.js +152 -0
- package/dist/manifest.d.ts +217 -0
- package/dist/manifest.js +9 -0
- package/dist/ov-widgets.d.ts +21 -0
- package/dist/ov-widgets.js +33 -0
- package/dist/slots.d.ts +139 -0
- package/dist/slots.js +17 -0
- package/dist/version.d.ts +31 -0
- package/dist/version.js +19 -0
- package/dist/views.d.ts +262 -0
- package/dist/views.js +2 -0
- package/host.d.ts +60 -0
- package/package.json +47 -0
- package/schema.json +495 -0
package/schema.json
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$ref": "#/definitions/AcPluginManifest",
|
|
4
|
+
"definitions": {
|
|
5
|
+
"AcPluginManifest": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"id": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Permanent id: state key, route namespace (`/api/x/<id>/`), log prefix. Never rename."
|
|
11
|
+
},
|
|
12
|
+
"hostApiVersion": {
|
|
13
|
+
"type": "number",
|
|
14
|
+
"description": "Integer, equal to the host's HOST_API_VERSION."
|
|
15
|
+
},
|
|
16
|
+
"kind": {
|
|
17
|
+
"$ref": "#/definitions/PluginKind"
|
|
18
|
+
},
|
|
19
|
+
"server": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Server entry exporting `createPlugin(host)`; required for `kind: \"in-process\"`."
|
|
22
|
+
},
|
|
23
|
+
"frontend": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Frontend ESM entry; omitted by a plugin that contributes no UI."
|
|
26
|
+
},
|
|
27
|
+
"sidecar": {
|
|
28
|
+
"$ref": "#/definitions/SidecarSpec",
|
|
29
|
+
"description": "Image + health path; required when `kind: \"sidecar\"`."
|
|
30
|
+
},
|
|
31
|
+
"capabilities": {
|
|
32
|
+
"type": "array",
|
|
33
|
+
"items": {
|
|
34
|
+
"type": "string"
|
|
35
|
+
},
|
|
36
|
+
"description": "Host-agent capabilities the plugin may use, each granted per machine in Admin (E4)."
|
|
37
|
+
},
|
|
38
|
+
"contributes": {
|
|
39
|
+
"$ref": "#/definitions/Contributes"
|
|
40
|
+
},
|
|
41
|
+
"env": {
|
|
42
|
+
"type": "array",
|
|
43
|
+
"items": {
|
|
44
|
+
"$ref": "#/definitions/EnvVar"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"required": [
|
|
49
|
+
"id",
|
|
50
|
+
"hostApiVersion",
|
|
51
|
+
"kind"
|
|
52
|
+
],
|
|
53
|
+
"additionalProperties": false,
|
|
54
|
+
"description": "The `acPlugin` block of a plugin's package.json — everything the host needs before it loads any code. The host validates this against the generated JSON Schema (issue #26). `hostApiVersion` is an integer that must equal the host's `HOST_API_VERSION`."
|
|
55
|
+
},
|
|
56
|
+
"PluginKind": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"enum": [
|
|
59
|
+
"in-process",
|
|
60
|
+
"sidecar"
|
|
61
|
+
],
|
|
62
|
+
"description": "How the host runs a plugin."
|
|
63
|
+
},
|
|
64
|
+
"SidecarSpec": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"properties": {
|
|
67
|
+
"image": {
|
|
68
|
+
"type": "string"
|
|
69
|
+
},
|
|
70
|
+
"healthPath": {
|
|
71
|
+
"type": "string"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"required": [
|
|
75
|
+
"image",
|
|
76
|
+
"healthPath"
|
|
77
|
+
],
|
|
78
|
+
"additionalProperties": false,
|
|
79
|
+
"description": "A sidecar plugin's image and health path. Present when `kind === \"sidecar\"`."
|
|
80
|
+
},
|
|
81
|
+
"Contributes": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"properties": {
|
|
84
|
+
"panels": {
|
|
85
|
+
"type": "array",
|
|
86
|
+
"items": {
|
|
87
|
+
"$ref": "#/definitions/PanelContribution"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"cards": {
|
|
91
|
+
"type": "array",
|
|
92
|
+
"items": {
|
|
93
|
+
"$ref": "#/definitions/CardContribution"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"actions": {
|
|
97
|
+
"type": "array",
|
|
98
|
+
"items": {
|
|
99
|
+
"$ref": "#/definitions/ActionContribution"
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"jobs": {
|
|
103
|
+
"type": "array",
|
|
104
|
+
"items": {
|
|
105
|
+
"$ref": "#/definitions/JobContribution"
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"routes": {
|
|
109
|
+
"type": "array",
|
|
110
|
+
"items": {
|
|
111
|
+
"$ref": "#/definitions/RouteContribution"
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"settings": {
|
|
115
|
+
"type": "array",
|
|
116
|
+
"items": {
|
|
117
|
+
"$ref": "#/definitions/SettingContribution"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"themes": {
|
|
121
|
+
"type": "array",
|
|
122
|
+
"items": {
|
|
123
|
+
"$ref": "#/definitions/ThemeContribution"
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"provides": {
|
|
127
|
+
"type": "array",
|
|
128
|
+
"items": {
|
|
129
|
+
"$ref": "#/definitions/SlotRef"
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"consumes": {
|
|
133
|
+
"type": "array",
|
|
134
|
+
"items": {
|
|
135
|
+
"$ref": "#/definitions/SlotRef"
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"attention": {
|
|
139
|
+
"type": "boolean",
|
|
140
|
+
"const": true,
|
|
141
|
+
"description": "#78: the plugin serves `GET /api/x/<id>/attention` -> `{ ok: true, items: AttentionItem[] }` (`attention.ts`). The `overview` plugin's rollup card discovers contributors via `PluginView.attention` (`views.ts`) rather than a second manifest read. Additive; always `true` when present (a boolean, not a route list, since the path is fixed)."
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"additionalProperties": false,
|
|
145
|
+
"description": "The contribution points a plugin offers. Every field is optional; a plugin contributes a subset."
|
|
146
|
+
},
|
|
147
|
+
"PanelContribution": {
|
|
148
|
+
"type": "object",
|
|
149
|
+
"properties": {
|
|
150
|
+
"id": {
|
|
151
|
+
"type": "string"
|
|
152
|
+
},
|
|
153
|
+
"title": {
|
|
154
|
+
"type": "string"
|
|
155
|
+
},
|
|
156
|
+
"route": {
|
|
157
|
+
"type": "string",
|
|
158
|
+
"description": "Route the panel mounts at, e.g. \"/board\"."
|
|
159
|
+
},
|
|
160
|
+
"nav": {
|
|
161
|
+
"type": "string",
|
|
162
|
+
"description": "Named nav slot this panel wants to own (e.g. \"issues\"); optional."
|
|
163
|
+
},
|
|
164
|
+
"order": {
|
|
165
|
+
"type": "number",
|
|
166
|
+
"description": "Where this panel wants to sit among the tabs (#472): a finite number, lower first, after the fixed `Home` tab. Unset keeps the panel after every ordered one, in the existing order (plugin directory name, then declaration order), so declaring `order` on one panel never moves a panel that did not. A hint, not a claim: the operator's `tab_order` setting outranks it, and among the ordered panels an `installed` plugin's never precedes a `bundled` plugin's, so a third-party package declaring a small number cannot jump ahead of the bundled ordered panels (it still precedes every panel that declares no `order`). Additive; a manifest without it validates as before."
|
|
167
|
+
},
|
|
168
|
+
"export": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"description": "The export name in the plugin's frontend bundle."
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"required": [
|
|
174
|
+
"id",
|
|
175
|
+
"title",
|
|
176
|
+
"route",
|
|
177
|
+
"export"
|
|
178
|
+
],
|
|
179
|
+
"additionalProperties": false,
|
|
180
|
+
"description": "A contributed nav tab (panel)."
|
|
181
|
+
},
|
|
182
|
+
"CardContribution": {
|
|
183
|
+
"type": "object",
|
|
184
|
+
"properties": {
|
|
185
|
+
"id": {
|
|
186
|
+
"type": "string"
|
|
187
|
+
},
|
|
188
|
+
"title": {
|
|
189
|
+
"type": "string"
|
|
190
|
+
},
|
|
191
|
+
"sizes": {
|
|
192
|
+
"type": "array",
|
|
193
|
+
"items": {
|
|
194
|
+
"type": "string"
|
|
195
|
+
},
|
|
196
|
+
"description": "Allowed grid sizes, e.g. [\"2x1\", \"3x1\"]."
|
|
197
|
+
},
|
|
198
|
+
"export": {
|
|
199
|
+
"type": "string",
|
|
200
|
+
"description": "The export name in the plugin's frontend bundle."
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
"required": [
|
|
204
|
+
"id",
|
|
205
|
+
"title",
|
|
206
|
+
"sizes",
|
|
207
|
+
"export"
|
|
208
|
+
],
|
|
209
|
+
"additionalProperties": false,
|
|
210
|
+
"description": "A contributed Overview card."
|
|
211
|
+
},
|
|
212
|
+
"ActionContribution": {
|
|
213
|
+
"type": "object",
|
|
214
|
+
"properties": {
|
|
215
|
+
"id": {
|
|
216
|
+
"type": "string"
|
|
217
|
+
},
|
|
218
|
+
"title": {
|
|
219
|
+
"type": "string",
|
|
220
|
+
"description": "Button/label text; the shell falls back to `id` when omitted."
|
|
221
|
+
},
|
|
222
|
+
"scope": {
|
|
223
|
+
"type": "string",
|
|
224
|
+
"description": "What the action operates on, e.g. \"issue\" | \"report\" | \"global\"."
|
|
225
|
+
},
|
|
226
|
+
"confirm": {
|
|
227
|
+
"type": "boolean"
|
|
228
|
+
},
|
|
229
|
+
"danger": {
|
|
230
|
+
"type": "boolean"
|
|
231
|
+
},
|
|
232
|
+
"fields": {
|
|
233
|
+
"type": "array",
|
|
234
|
+
"items": {
|
|
235
|
+
"$ref": "#/definitions/ActionField"
|
|
236
|
+
},
|
|
237
|
+
"description": "The form the shell builds the action's modal from; omit for a no-input action."
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
"required": [
|
|
241
|
+
"id",
|
|
242
|
+
"scope"
|
|
243
|
+
],
|
|
244
|
+
"additionalProperties": false,
|
|
245
|
+
"description": "A confirm-gated action; the shell builds its button + modal from these fields (#34)."
|
|
246
|
+
},
|
|
247
|
+
"ActionField": {
|
|
248
|
+
"type": "object",
|
|
249
|
+
"properties": {
|
|
250
|
+
"name": {
|
|
251
|
+
"type": "string",
|
|
252
|
+
"description": "Key the submitted value is collected under."
|
|
253
|
+
},
|
|
254
|
+
"label": {
|
|
255
|
+
"type": "string"
|
|
256
|
+
},
|
|
257
|
+
"type": {
|
|
258
|
+
"$ref": "#/definitions/ActionFieldType"
|
|
259
|
+
},
|
|
260
|
+
"options": {
|
|
261
|
+
"type": "array",
|
|
262
|
+
"items": {
|
|
263
|
+
"anyOf": [
|
|
264
|
+
{
|
|
265
|
+
"type": "string"
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"type": "object",
|
|
269
|
+
"properties": {
|
|
270
|
+
"value": {
|
|
271
|
+
"type": "string"
|
|
272
|
+
},
|
|
273
|
+
"label": {
|
|
274
|
+
"type": "string"
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
"required": [
|
|
278
|
+
"value",
|
|
279
|
+
"label"
|
|
280
|
+
],
|
|
281
|
+
"additionalProperties": false
|
|
282
|
+
}
|
|
283
|
+
]
|
|
284
|
+
},
|
|
285
|
+
"description": "Choices for a `select`. A bare string is both value and label; the object form allows a distinct display label (so a later value≠label select is additive, not a breaking `string[]`→object change)."
|
|
286
|
+
},
|
|
287
|
+
"optionsFrom": {
|
|
288
|
+
"$ref": "#/definitions/ActionOptionsSource",
|
|
289
|
+
"description": "Choices fetched from a plugin route instead (#463), for a `select` or `multiselect` only -- the validator rejects it on any other type, and beside a non-empty `options` (one source of choices, not two). When the route cannot be loaded the shell degrades the field to a text box (a `multiselect`'s text is a comma-separated list) and shows the reason, so an action is never blocked by its picker. Additive."
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
"required": [
|
|
293
|
+
"name",
|
|
294
|
+
"label",
|
|
295
|
+
"type"
|
|
296
|
+
],
|
|
297
|
+
"additionalProperties": false,
|
|
298
|
+
"description": "One field of an action's form; the shell builds a labelled control from it."
|
|
299
|
+
},
|
|
300
|
+
"ActionFieldType": {
|
|
301
|
+
"type": "string",
|
|
302
|
+
"enum": [
|
|
303
|
+
"string",
|
|
304
|
+
"text",
|
|
305
|
+
"number",
|
|
306
|
+
"boolean",
|
|
307
|
+
"select",
|
|
308
|
+
"multiselect",
|
|
309
|
+
"hidden"
|
|
310
|
+
],
|
|
311
|
+
"description": "Input control kind for an action field; the shell renders each `fields` entry by its `type`. `multiselect` (#463) is a set of choices whose submitted value is a `string[]`; `hidden` (#463) is a value with no control at all -- a snapshot the operator must not type into (the seed, the stored draft or the default is submitted unchanged). Both are additive; every earlier type behaves as before."
|
|
312
|
+
},
|
|
313
|
+
"ActionOptionsSource": {
|
|
314
|
+
"type": "object",
|
|
315
|
+
"properties": {
|
|
316
|
+
"route": {
|
|
317
|
+
"type": "string",
|
|
318
|
+
"description": "The route, relative to the declaring plugin's own namespace: the shell fetches `/api/x/<plugin-id><route>`. It must start with `/`, carry no query string, fragment, `..`, `//`, `%` or `\\`, and be one of the plugin's own declared `GET` routes (`contributes.routes`)."
|
|
319
|
+
},
|
|
320
|
+
"params": {
|
|
321
|
+
"type": "array",
|
|
322
|
+
"items": {
|
|
323
|
+
"type": "string"
|
|
324
|
+
},
|
|
325
|
+
"description": "Names of OTHER fields of the same action whose current values are sent as query parameters (so a route can answer for the repo the operator has typed, say). The route is not fetched while any of them is empty, and is fetched again when one changes."
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
"required": [
|
|
329
|
+
"route"
|
|
330
|
+
],
|
|
331
|
+
"additionalProperties": false,
|
|
332
|
+
"description": "Where a `select` / `multiselect` field's choices come from when they are not a fixed `options` list (#463): a `GET` route the DECLARING plugin itself registers, answering an `ActionOptionsResponse` (`action-options.ts`). The host validates it at manifest-load time (`plugins/validate.ts`)."
|
|
333
|
+
},
|
|
334
|
+
"JobContribution": {
|
|
335
|
+
"type": "object",
|
|
336
|
+
"properties": {
|
|
337
|
+
"id": {
|
|
338
|
+
"type": "string"
|
|
339
|
+
},
|
|
340
|
+
"schedule": {
|
|
341
|
+
"type": "string",
|
|
342
|
+
"description": "5-field cron expression."
|
|
343
|
+
},
|
|
344
|
+
"timeoutSec": {
|
|
345
|
+
"type": "number",
|
|
346
|
+
"description": "Per-run timeout in seconds."
|
|
347
|
+
},
|
|
348
|
+
"runOn": {
|
|
349
|
+
"$ref": "#/definitions/JobRunOn"
|
|
350
|
+
},
|
|
351
|
+
"minAgentVersion": {
|
|
352
|
+
"type": "string",
|
|
353
|
+
"description": "A `runOn: \"agent\"` job is not dispatched to a machine whose paired agent version compares lower than this (#372, #80); the run row records the skip with its reason instead. Ignored for a `runOn: \"host\"` job. Additive; absent means every paired machine is always dispatched, as before this field existed."
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
"required": [
|
|
357
|
+
"id",
|
|
358
|
+
"schedule",
|
|
359
|
+
"timeoutSec",
|
|
360
|
+
"runOn"
|
|
361
|
+
],
|
|
362
|
+
"additionalProperties": false,
|
|
363
|
+
"description": "A scheduled job."
|
|
364
|
+
},
|
|
365
|
+
"JobRunOn": {
|
|
366
|
+
"type": "string",
|
|
367
|
+
"enum": [
|
|
368
|
+
"host",
|
|
369
|
+
"agent"
|
|
370
|
+
],
|
|
371
|
+
"description": "Where a contributed job runs: in the host, or fanned out to the paired agents (E4)."
|
|
372
|
+
},
|
|
373
|
+
"RouteContribution": {
|
|
374
|
+
"type": "object",
|
|
375
|
+
"properties": {
|
|
376
|
+
"method": {
|
|
377
|
+
"$ref": "#/definitions/HttpMethod"
|
|
378
|
+
},
|
|
379
|
+
"path": {
|
|
380
|
+
"type": "string",
|
|
381
|
+
"description": "Path under the plugin's namespace, e.g. \"/feed\"."
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
"required": [
|
|
385
|
+
"method",
|
|
386
|
+
"path"
|
|
387
|
+
],
|
|
388
|
+
"additionalProperties": false,
|
|
389
|
+
"description": "An HTTP route; the host mounts it under `/api/x/<plugin-id>/<path>`."
|
|
390
|
+
},
|
|
391
|
+
"HttpMethod": {
|
|
392
|
+
"type": "string",
|
|
393
|
+
"enum": [
|
|
394
|
+
"GET",
|
|
395
|
+
"POST",
|
|
396
|
+
"PUT",
|
|
397
|
+
"PATCH",
|
|
398
|
+
"DELETE"
|
|
399
|
+
],
|
|
400
|
+
"description": "HTTP method for a contributed route."
|
|
401
|
+
},
|
|
402
|
+
"SettingContribution": {
|
|
403
|
+
"type": "object",
|
|
404
|
+
"properties": {
|
|
405
|
+
"key": {
|
|
406
|
+
"type": "string"
|
|
407
|
+
},
|
|
408
|
+
"type": {
|
|
409
|
+
"$ref": "#/definitions/SettingType"
|
|
410
|
+
},
|
|
411
|
+
"default": {
|
|
412
|
+
"type": [
|
|
413
|
+
"string",
|
|
414
|
+
"number",
|
|
415
|
+
"boolean"
|
|
416
|
+
]
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
"required": [
|
|
420
|
+
"key",
|
|
421
|
+
"type"
|
|
422
|
+
],
|
|
423
|
+
"additionalProperties": false,
|
|
424
|
+
"description": "A plugin setting."
|
|
425
|
+
},
|
|
426
|
+
"SettingType": {
|
|
427
|
+
"type": "string",
|
|
428
|
+
"enum": [
|
|
429
|
+
"string",
|
|
430
|
+
"integer",
|
|
431
|
+
"number",
|
|
432
|
+
"boolean"
|
|
433
|
+
],
|
|
434
|
+
"description": "JSON scalar type of a plugin setting (rendered by Admin)."
|
|
435
|
+
},
|
|
436
|
+
"ThemeContribution": {
|
|
437
|
+
"type": "object",
|
|
438
|
+
"properties": {
|
|
439
|
+
"id": {
|
|
440
|
+
"type": "string"
|
|
441
|
+
},
|
|
442
|
+
"title": {
|
|
443
|
+
"type": "string"
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
"required": [
|
|
447
|
+
"id"
|
|
448
|
+
],
|
|
449
|
+
"additionalProperties": false,
|
|
450
|
+
"description": "A theme contributed as token JSON only (no code)."
|
|
451
|
+
},
|
|
452
|
+
"SlotRef": {
|
|
453
|
+
"type": "object",
|
|
454
|
+
"properties": {
|
|
455
|
+
"slot": {
|
|
456
|
+
"type": "string",
|
|
457
|
+
"description": "The slot name, one of the `SLOTS` ids, e.g. \"issues\"."
|
|
458
|
+
},
|
|
459
|
+
"version": {
|
|
460
|
+
"type": "number",
|
|
461
|
+
"description": "Integer slot version; the host resolves provider/consumer by exact equality."
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
"required": [
|
|
465
|
+
"slot",
|
|
466
|
+
"version"
|
|
467
|
+
],
|
|
468
|
+
"additionalProperties": false,
|
|
469
|
+
"description": "A named, versioned slot this plugin provides or consumes."
|
|
470
|
+
},
|
|
471
|
+
"EnvVar": {
|
|
472
|
+
"type": "object",
|
|
473
|
+
"properties": {
|
|
474
|
+
"key": {
|
|
475
|
+
"type": "string"
|
|
476
|
+
},
|
|
477
|
+
"format": {
|
|
478
|
+
"type": "string",
|
|
479
|
+
"description": "Anchored regex the value must match, e.g. \"^[0-9]+$\"."
|
|
480
|
+
},
|
|
481
|
+
"required": {
|
|
482
|
+
"type": "boolean"
|
|
483
|
+
},
|
|
484
|
+
"secret": {
|
|
485
|
+
"type": "boolean"
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
"required": [
|
|
489
|
+
"key"
|
|
490
|
+
],
|
|
491
|
+
"additionalProperties": false,
|
|
492
|
+
"description": "A declared environment variable (field names shared verbatim with the discord-bot index)."
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|