runcloud 0.2.196 → 0.2.198
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/dist/commands/run-cloud.js +34 -61
- package/dist/commands/support.js +273 -0
- package/dist/commands/supportChat.generated.js +1 -0
- package/dist/index.js +2 -0
- package/dist/version.js +1 -1
- package/package.json +2 -1
- package/runcloud-0.2.198.tgz +0 -0
- package/runcloud-0.2.196.tgz +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { InvalidArgumentError, Option } from 'commander';
|
|
2
|
+
import { DEFAULT_SIMULATOR_INTERACTION_TIMEOUT_MS as DEFAULT_INTERACTION_TIMEOUT_MS, SIMULATOR_BUTTONS, SIMULATOR_INTERACTION_LIMITS as LIMITS, SIMULATOR_KEY_MODIFIERS, SIMULATOR_KEYS, SIMULATOR_ORIENTATIONS, SIMULATOR_RENDER_DEBUG_OPTIONS, } from '@run-cloud/sdk';
|
|
2
3
|
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
3
4
|
import { basename, dirname, extname, join, resolve } from 'node:path';
|
|
4
5
|
import { homedir } from 'node:os';
|
|
@@ -23,31 +24,6 @@ export function runCloudIosOptions(opts) {
|
|
|
23
24
|
},
|
|
24
25
|
};
|
|
25
26
|
}
|
|
26
|
-
export const SIMULATOR_KEYS = [
|
|
27
|
-
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
|
28
|
-
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
|
29
|
-
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
30
|
-
'enter', 'escape', 'backspace', 'tab', 'space', 'minus', 'equal',
|
|
31
|
-
'bracketLeft', 'bracketRight', 'backslash', 'semicolon', 'quote', 'backquote',
|
|
32
|
-
'comma', 'period', 'slash', 'capsLock',
|
|
33
|
-
'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12',
|
|
34
|
-
'printScreen', 'scrollLock', 'pause', 'insert', 'home', 'pageUp', 'delete', 'end',
|
|
35
|
-
'pageDown', 'arrowRight', 'arrowLeft', 'arrowDown', 'arrowUp', 'numLock',
|
|
36
|
-
'numpadDivide', 'numpadMultiply', 'numpadSubtract', 'numpadAdd', 'numpadEnter',
|
|
37
|
-
'numpad0', 'numpad1', 'numpad2', 'numpad3', 'numpad4', 'numpad5', 'numpad6',
|
|
38
|
-
'numpad7', 'numpad8', 'numpad9', 'numpadDecimal',
|
|
39
|
-
];
|
|
40
|
-
export const SIMULATOR_BUTTONS = [
|
|
41
|
-
'home', 'back', 'appSwitcher', 'recents', 'power', 'volumeUp', 'volumeDown',
|
|
42
|
-
'menu', 'sideButton', 'actionButton', 'digitalCrown',
|
|
43
|
-
];
|
|
44
|
-
export const SIMULATOR_RENDER_DEBUG_OPTIONS = [
|
|
45
|
-
'colorBlendedLayers',
|
|
46
|
-
'colorCopiedImages',
|
|
47
|
-
'colorMisalignedImages',
|
|
48
|
-
'colorOffscreenRendered',
|
|
49
|
-
'slowAnimations',
|
|
50
|
-
];
|
|
51
27
|
export const SIMULATOR_INTERACTION_ERROR_CODES = [
|
|
52
28
|
'invalid_interaction',
|
|
53
29
|
'active_session_not_found',
|
|
@@ -118,7 +94,6 @@ function normalizedAccessibilityErrorCode(value) {
|
|
|
118
94
|
? value
|
|
119
95
|
: 'accessibility_unavailable';
|
|
120
96
|
}
|
|
121
|
-
const DEFAULT_INTERACTION_TIMEOUT_MS = 15_000;
|
|
122
97
|
const DEFAULT_ACCESSIBILITY_TIMEOUT_MS = 20_000;
|
|
123
98
|
const INTERACTION_RESPONSE_MARGIN_MS = 250;
|
|
124
99
|
const REQUEST_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
@@ -243,8 +218,8 @@ function finiteInRange(raw, name, min, max) {
|
|
|
243
218
|
}
|
|
244
219
|
function normalizedPoint(x, y, name) {
|
|
245
220
|
return {
|
|
246
|
-
x: finiteInRange(x, `${name} x`,
|
|
247
|
-
y: finiteInRange(y, `${name} y`,
|
|
221
|
+
x: finiteInRange(x, `${name} x`, LIMITS.coordinate.min, LIMITS.coordinate.max),
|
|
222
|
+
y: finiteInRange(y, `${name} y`, LIMITS.coordinate.min, LIMITS.coordinate.max),
|
|
248
223
|
};
|
|
249
224
|
}
|
|
250
225
|
function simulatorKey(raw) {
|
|
@@ -258,10 +233,9 @@ function simulatorButton(raw) {
|
|
|
258
233
|
throw new Error(`Unknown simulator button: ${raw}. Choose one of: ${SIMULATOR_BUTTONS.join(', ')}.`);
|
|
259
234
|
}
|
|
260
235
|
function simulatorOrientation(raw) {
|
|
261
|
-
if (raw
|
|
236
|
+
if (SIMULATOR_ORIENTATIONS.includes(raw))
|
|
262
237
|
return raw;
|
|
263
|
-
}
|
|
264
|
-
throw new Error(`Unknown orientation: ${raw}. Choose portrait, portrait_upside_down, landscape_left, or landscape_right.`);
|
|
238
|
+
throw new Error(`Unknown orientation: ${raw}. Choose one of: ${SIMULATOR_ORIENTATIONS.join(', ')}.`);
|
|
265
239
|
}
|
|
266
240
|
function renderDebugOption(raw) {
|
|
267
241
|
if (SIMULATOR_RENDER_DEBUG_OPTIONS.includes(raw)) {
|
|
@@ -290,12 +264,12 @@ function assertInteraction(interaction, timeoutMs) {
|
|
|
290
264
|
return;
|
|
291
265
|
case 'swipe':
|
|
292
266
|
if (interaction.durationMs !== undefined) {
|
|
293
|
-
integerInRange(String(interaction.durationMs), '--duration',
|
|
267
|
+
integerInRange(String(interaction.durationMs), '--duration', LIMITS.swipeDurationMs.min, LIMITS.swipeDurationMs.max);
|
|
294
268
|
}
|
|
295
269
|
return;
|
|
296
270
|
case 'gesture': {
|
|
297
|
-
if (interaction.steps.length <
|
|
298
|
-
throw new Error(
|
|
271
|
+
if (interaction.steps.length < LIMITS.gestureSteps.min || interaction.steps.length > LIMITS.gestureSteps.max) {
|
|
272
|
+
throw new Error(`--steps must contain from ${LIMITS.gestureSteps.min} to ${LIMITS.gestureSteps.max} gesture steps`);
|
|
299
273
|
}
|
|
300
274
|
let pointCount;
|
|
301
275
|
let totalDelayMs = 0;
|
|
@@ -306,7 +280,7 @@ function assertInteraction(interaction, timeoutMs) {
|
|
|
306
280
|
if ((index === interaction.steps.length - 1) !== (step.phase === 'end')) {
|
|
307
281
|
throw new Error('Gesture steps must end with end and contain no earlier end phase');
|
|
308
282
|
}
|
|
309
|
-
if (step.points.length
|
|
283
|
+
if (step.points.length < LIMITS.gesturePoints.min || step.points.length > LIMITS.gesturePoints.max) {
|
|
310
284
|
throw new Error('Each gesture step must contain one or two points');
|
|
311
285
|
}
|
|
312
286
|
if (pointCount === undefined)
|
|
@@ -315,14 +289,14 @@ function assertInteraction(interaction, timeoutMs) {
|
|
|
315
289
|
throw new Error('Every gesture step must use the same number of points');
|
|
316
290
|
}
|
|
317
291
|
for (const point of step.points) {
|
|
318
|
-
finiteInRange(String(point.x), 'gesture x',
|
|
319
|
-
finiteInRange(String(point.y), 'gesture y',
|
|
292
|
+
finiteInRange(String(point.x), 'gesture x', LIMITS.coordinate.min, LIMITS.coordinate.max);
|
|
293
|
+
finiteInRange(String(point.y), 'gesture y', LIMITS.coordinate.min, LIMITS.coordinate.max);
|
|
320
294
|
}
|
|
321
295
|
if (step.delayMs !== undefined) {
|
|
322
296
|
if (index === interaction.steps.length - 1 && step.delayMs !== 0) {
|
|
323
297
|
throw new Error('Final gesture end step delayMs must be 0 because there is no next step');
|
|
324
298
|
}
|
|
325
|
-
totalDelayMs += integerInRange(String(step.delayMs), 'gesture delayMs',
|
|
299
|
+
totalDelayMs += integerInRange(String(step.delayMs), 'gesture delayMs', LIMITS.gestureDelayMs.min, LIMITS.gestureDelayMs.max);
|
|
326
300
|
}
|
|
327
301
|
}
|
|
328
302
|
if (totalDelayMs > timeoutMs)
|
|
@@ -332,12 +306,12 @@ function assertInteraction(interaction, timeoutMs) {
|
|
|
332
306
|
case 'getClipboard':
|
|
333
307
|
return;
|
|
334
308
|
case 'setClipboard':
|
|
335
|
-
if (interaction.text.length >
|
|
336
|
-
throw new Error(
|
|
309
|
+
if (interaction.text.length > LIMITS.clipboardTextLength.max)
|
|
310
|
+
throw new Error(`clipboard text must contain at most ${LIMITS.clipboardTextLength.max} UTF-16 code units`);
|
|
337
311
|
return;
|
|
338
312
|
case 'typeText':
|
|
339
|
-
if (interaction.text.length <
|
|
340
|
-
throw new Error(
|
|
313
|
+
if (interaction.text.length < LIMITS.typeTextLength.min || interaction.text.length > LIMITS.typeTextLength.max) {
|
|
314
|
+
throw new Error(`text must contain from ${LIMITS.typeTextLength.min} to ${LIMITS.typeTextLength.max} characters`);
|
|
341
315
|
}
|
|
342
316
|
if (!isUsAsciiText(interaction.text)) {
|
|
343
317
|
throw new Error('text must contain only US ASCII characters, tabs, and line feeds');
|
|
@@ -346,7 +320,7 @@ function assertInteraction(interaction, timeoutMs) {
|
|
|
346
320
|
case 'pressKey':
|
|
347
321
|
case 'pressButton':
|
|
348
322
|
if (interaction.durationMs !== undefined) {
|
|
349
|
-
integerInRange(String(interaction.durationMs), '--duration',
|
|
323
|
+
integerInRange(String(interaction.durationMs), '--duration', LIMITS.pressDurationMs.min, LIMITS.pressDurationMs.max);
|
|
350
324
|
}
|
|
351
325
|
return;
|
|
352
326
|
case 'scroll':
|
|
@@ -395,7 +369,7 @@ export function parseSimulatorGestureSteps(raw, timeoutMs = DEFAULT_INTERACTION_
|
|
|
395
369
|
: [points[0], points[1]];
|
|
396
370
|
const delayMs = record.delayMs === undefined
|
|
397
371
|
? undefined
|
|
398
|
-
: integerInRange(String(record.delayMs), `gesture step ${index} delayMs`,
|
|
372
|
+
: integerInRange(String(record.delayMs), `gesture step ${index} delayMs`, LIMITS.gestureDelayMs.min, LIMITS.gestureDelayMs.max);
|
|
399
373
|
return {
|
|
400
374
|
phase: record.phase,
|
|
401
375
|
points: tuple,
|
|
@@ -406,7 +380,7 @@ export function parseSimulatorGestureSteps(raw, timeoutMs = DEFAULT_INTERACTION_
|
|
|
406
380
|
return steps;
|
|
407
381
|
}
|
|
408
382
|
function interactionTimeout(opts) {
|
|
409
|
-
return integerInRange(opts.timeout, '--timeout',
|
|
383
|
+
return integerInRange(opts.timeout, '--timeout', LIMITS.timeoutMs.min, LIMITS.timeoutMs.max, DEFAULT_INTERACTION_TIMEOUT_MS);
|
|
410
384
|
}
|
|
411
385
|
function interactionRequestId(opts) {
|
|
412
386
|
const requestId = opts.requestId?.trim() || `cli_${randomUUID()}`;
|
|
@@ -463,7 +437,7 @@ async function withInteractionCancellation(timeoutMs, requestId, actionName, ses
|
|
|
463
437
|
}
|
|
464
438
|
function addInteractionOptions(command) {
|
|
465
439
|
return command
|
|
466
|
-
.option('--timeout <milliseconds>',
|
|
440
|
+
.option('--timeout <milliseconds>', `acknowledgement timeout in milliseconds (${LIMITS.timeoutMs.min}-${LIMITS.timeoutMs.max})`, String(DEFAULT_INTERACTION_TIMEOUT_MS))
|
|
467
441
|
.option('--request-id <id>', 'caller-supplied correlation id')
|
|
468
442
|
.option('--json', 'output a stable JSON result or error envelope', false);
|
|
469
443
|
}
|
|
@@ -1220,13 +1194,13 @@ function registerSimulatorCommands(program, platform) {
|
|
|
1220
1194
|
.argument('<from-y>', 'start y from 0 to 1')
|
|
1221
1195
|
.argument('<to-x>', 'end x from 0 to 1')
|
|
1222
1196
|
.argument('<to-y>', 'end y from 0 to 1')
|
|
1223
|
-
.option('--duration <milliseconds>',
|
|
1197
|
+
.option('--duration <milliseconds>', `swipe duration in milliseconds (${LIMITS.swipeDurationMs.min}-${LIMITS.swipeDurationMs.max})`)).action((id, fromX, fromY, toX, toY, opts) => action(() => runSimulatorInteraction(platform, id, {
|
|
1224
1198
|
action: 'swipe',
|
|
1225
1199
|
from: normalizedPoint(fromX, fromY, 'swipe start'),
|
|
1226
1200
|
to: normalizedPoint(toX, toY, 'swipe end'),
|
|
1227
1201
|
...(opts.duration === undefined
|
|
1228
1202
|
? {}
|
|
1229
|
-
: { durationMs: integerInRange(opts.duration, '--duration',
|
|
1203
|
+
: { durationMs: integerInRange(opts.duration, '--duration', LIMITS.swipeDurationMs.min, LIMITS.swipeDurationMs.max) }),
|
|
1230
1204
|
}, opts), opts));
|
|
1231
1205
|
addInteractionOptions(simulator
|
|
1232
1206
|
.command('gesture')
|
|
@@ -1247,27 +1221,26 @@ function registerSimulatorCommands(program, platform) {
|
|
|
1247
1221
|
.command('type-text')
|
|
1248
1222
|
.description(`Type US ASCII text into ${article} ${label} session`)
|
|
1249
1223
|
.argument('<id>', 'session id')
|
|
1250
|
-
.argument('<text>',
|
|
1224
|
+
.argument('<text>', `${LIMITS.typeTextLength.min}-${LIMITS.typeTextLength.max} US ASCII characters; quote text containing spaces`)).action((id, text, opts) => action(() => runSimulatorInteraction(platform, id, { action: 'typeText', text }, opts), opts));
|
|
1251
1225
|
addInteractionOptions(simulator
|
|
1252
1226
|
.command('press-key')
|
|
1253
1227
|
.description(`Press a semantic keyboard key in ${article} ${label} session`)
|
|
1254
1228
|
.argument('<id>', 'session id')
|
|
1255
1229
|
.argument('<key>', 'semantic key, for example a, enter, arrowUp, or f1')
|
|
1256
|
-
.option('--duration <milliseconds>',
|
|
1230
|
+
.option('--duration <milliseconds>', `key hold duration in milliseconds (${LIMITS.pressDurationMs.min}-${LIMITS.pressDurationMs.max})`)
|
|
1257
1231
|
.option('--shift', 'hold the left Shift modifier', false)
|
|
1258
1232
|
.option('--control', 'hold the left Control modifier', false)
|
|
1259
1233
|
.option('--alt', 'hold the left Alt modifier', false)
|
|
1260
1234
|
.option('--meta', 'hold the left Meta/Command modifier', false)
|
|
1261
1235
|
.addHelpText('after', `\nSupported keys:\n ${SIMULATOR_KEYS.join(', ')}\n`)).action((id, rawKey, opts) => action(() => {
|
|
1262
|
-
const modifiers =
|
|
1263
|
-
.filter((modifier) => opts[modifier]);
|
|
1236
|
+
const modifiers = SIMULATOR_KEY_MODIFIERS.filter((modifier) => opts[modifier]);
|
|
1264
1237
|
return runSimulatorInteraction(platform, id, {
|
|
1265
1238
|
action: 'pressKey',
|
|
1266
1239
|
key: simulatorKey(rawKey),
|
|
1267
1240
|
...(modifiers.length ? { modifiers } : {}),
|
|
1268
1241
|
...(opts.duration === undefined
|
|
1269
1242
|
? {}
|
|
1270
|
-
: { durationMs: integerInRange(opts.duration, '--duration',
|
|
1243
|
+
: { durationMs: integerInRange(opts.duration, '--duration', LIMITS.pressDurationMs.min, LIMITS.pressDurationMs.max) }),
|
|
1271
1244
|
}, opts);
|
|
1272
1245
|
}, opts));
|
|
1273
1246
|
addInteractionOptions(simulator
|
|
@@ -1275,12 +1248,12 @@ function registerSimulatorCommands(program, platform) {
|
|
|
1275
1248
|
.description(`Press a simulator hardware or system button in ${article} ${label} session`)
|
|
1276
1249
|
.argument('<id>', 'session id')
|
|
1277
1250
|
.argument('<button>', `one of: ${SIMULATOR_BUTTONS.join(', ')}`)
|
|
1278
|
-
.option('--duration <milliseconds>',
|
|
1251
|
+
.option('--duration <milliseconds>', `button hold duration in milliseconds (${LIMITS.pressDurationMs.min}-${LIMITS.pressDurationMs.max})`)).action((id, rawButton, opts) => action(() => runSimulatorInteraction(platform, id, {
|
|
1279
1252
|
action: 'pressButton',
|
|
1280
1253
|
button: simulatorButton(rawButton),
|
|
1281
1254
|
...(opts.duration === undefined
|
|
1282
1255
|
? {}
|
|
1283
|
-
: { durationMs: integerInRange(opts.duration, '--duration',
|
|
1256
|
+
: { durationMs: integerInRange(opts.duration, '--duration', LIMITS.pressDurationMs.min, LIMITS.pressDurationMs.max) }),
|
|
1284
1257
|
}, opts), opts));
|
|
1285
1258
|
addInteractionOptions(simulator
|
|
1286
1259
|
.command('rotate')
|
|
@@ -1298,8 +1271,8 @@ function registerSimulatorCommands(program, platform) {
|
|
|
1298
1271
|
.command('scroll')
|
|
1299
1272
|
.description(`Send normalized scroll deltas to ${article} ${label} session`)
|
|
1300
1273
|
.argument('<id>', 'session id')
|
|
1301
|
-
.argument('<delta-x>',
|
|
1302
|
-
.argument('<delta-y>',
|
|
1274
|
+
.argument('<delta-x>', `horizontal delta from ${LIMITS.scrollDelta.min} to ${LIMITS.scrollDelta.max}`)
|
|
1275
|
+
.argument('<delta-y>', `vertical delta from ${LIMITS.scrollDelta.min} to ${LIMITS.scrollDelta.max}`)
|
|
1303
1276
|
.option('--x <coordinate>', 'normalized anchor x; requires --y')
|
|
1304
1277
|
.option('--y <coordinate>', 'normalized anchor y; requires --x')).action((id, deltaX, deltaY, opts) => action(() => {
|
|
1305
1278
|
if ((opts.x === undefined) !== (opts.y === undefined))
|
|
@@ -1309,8 +1282,8 @@ function registerSimulatorCommands(program, platform) {
|
|
|
1309
1282
|
: undefined;
|
|
1310
1283
|
return runSimulatorInteraction(platform, id, {
|
|
1311
1284
|
action: 'scroll',
|
|
1312
|
-
deltaX: finiteInRange(deltaX, 'delta-x',
|
|
1313
|
-
deltaY: finiteInRange(deltaY, 'delta-y',
|
|
1285
|
+
deltaX: finiteInRange(deltaX, 'delta-x', LIMITS.scrollDelta.min, LIMITS.scrollDelta.max),
|
|
1286
|
+
deltaY: finiteInRange(deltaY, 'delta-y', LIMITS.scrollDelta.min, LIMITS.scrollDelta.max),
|
|
1314
1287
|
...(anchor ?? {}),
|
|
1315
1288
|
}, opts);
|
|
1316
1289
|
}, opts));
|
|
@@ -1326,9 +1299,9 @@ function registerSimulatorCommands(program, platform) {
|
|
|
1326
1299
|
.command('rotate-digital-crown')
|
|
1327
1300
|
.description(`Request Digital Crown input for ${article} ${label} session (unsupported on current mobile sessions)`)
|
|
1328
1301
|
.argument('<id>', 'session id')
|
|
1329
|
-
.argument('<delta>',
|
|
1302
|
+
.argument('<delta>', `nonzero crown delta from ${LIMITS.digitalCrownDelta.min} to ${LIMITS.digitalCrownDelta.max}`)).action((id, delta, opts) => action(() => runSimulatorInteraction(platform, id, {
|
|
1330
1303
|
action: 'rotateDigitalCrown',
|
|
1331
|
-
delta: finiteInRange(delta, 'delta',
|
|
1304
|
+
delta: finiteInRange(delta, 'delta', LIMITS.digitalCrownDelta.min, LIMITS.digitalCrownDelta.max),
|
|
1332
1305
|
}, opts), opts));
|
|
1333
1306
|
addInteractionOptions(simulator
|
|
1334
1307
|
.command('set-render-debug')
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { ApiClient, friendlyApiError } from '../api.js';
|
|
4
|
+
import { requireCredentials, resolveProject } from '../config.js';
|
|
5
|
+
import { SUPPORT_CHAT_TSX } from './supportChat.generated.js';
|
|
6
|
+
export const SUPPORT_CONFIG = 'src/lib/newly-support.json';
|
|
7
|
+
export const SUPPORT_CHAT = 'src/components/SupportChat.tsx';
|
|
8
|
+
export function writeSupportChat(dir, force = false) {
|
|
9
|
+
const path = join(dir, SUPPORT_CHAT);
|
|
10
|
+
if (existsSync(path) && !force)
|
|
11
|
+
return { path, written: false };
|
|
12
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
13
|
+
writeFileSync(path, Buffer.from(SUPPORT_CHAT_TSX, 'base64'));
|
|
14
|
+
return { path, written: true };
|
|
15
|
+
}
|
|
16
|
+
const ENVS = ['development', 'production'];
|
|
17
|
+
const base = (projectId) => `/projects/${encodeURIComponent(projectId)}/support`;
|
|
18
|
+
export function envFrom(flag) {
|
|
19
|
+
if (flag === undefined || flag === 'dev' || flag === 'development')
|
|
20
|
+
return 'development';
|
|
21
|
+
if (flag === 'prod' || flag === 'production')
|
|
22
|
+
return 'production';
|
|
23
|
+
throw new Error('--env must be dev or prod');
|
|
24
|
+
}
|
|
25
|
+
export function supabaseAuth(url) {
|
|
26
|
+
let host;
|
|
27
|
+
try {
|
|
28
|
+
const parsed = new URL(url);
|
|
29
|
+
if (parsed.protocol !== 'https:')
|
|
30
|
+
throw new Error();
|
|
31
|
+
host = parsed.host;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new Error(`Not a Supabase project URL: ${url}`);
|
|
35
|
+
}
|
|
36
|
+
const issuer = `https://${host}/auth/v1`;
|
|
37
|
+
return { auth_issuer: issuer, auth_jwks_url: `${issuer}/.well-known/jwks.json`, auth_audience: 'authenticated' };
|
|
38
|
+
}
|
|
39
|
+
export function writeSupportConfig(dir, config) {
|
|
40
|
+
const path = join(dir, SUPPORT_CONFIG);
|
|
41
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
42
|
+
writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`);
|
|
43
|
+
return path;
|
|
44
|
+
}
|
|
45
|
+
export async function runSupportStatus(client, projectId, log) {
|
|
46
|
+
const { settings, keys } = (await client.get(`${base(projectId)}/settings`));
|
|
47
|
+
const summary = await client.get(`${base(projectId)}/summary`);
|
|
48
|
+
log(JSON.stringify({ settings, keys, summary }, null, 2));
|
|
49
|
+
}
|
|
50
|
+
export async function runSupportEnable(client, projectId, apiUrl, dir, log, opts = {}) {
|
|
51
|
+
await client.put(`${base(projectId)}/settings`, { enabled: true });
|
|
52
|
+
const { keys } = (await client.get(`${base(projectId)}/settings`));
|
|
53
|
+
const written = {};
|
|
54
|
+
for (const env of ENVS) {
|
|
55
|
+
written[env] = keys[env].publishable
|
|
56
|
+
?? (await client.post(`${base(projectId)}/keys`, { environment: env, kind: 'publishable' })).key;
|
|
57
|
+
}
|
|
58
|
+
const path = writeSupportConfig(dir, { projectId, apiUrl, keys: written });
|
|
59
|
+
const chat = writeSupportChat(dir, opts.forceChat);
|
|
60
|
+
log(`Customer support is on. Keys written to ${path}.`);
|
|
61
|
+
log(chat.written
|
|
62
|
+
? `Help chat component written to ${chat.path}. Give it a route (for example app/help.tsx rendering <SupportChat appName="..." />) and a way in from Settings.`
|
|
63
|
+
: `Kept the existing ${chat.path} (pass --force-chat to replace it).`);
|
|
64
|
+
}
|
|
65
|
+
export async function runSupportDisable(client, projectId, log) {
|
|
66
|
+
await client.put(`${base(projectId)}/settings`, { enabled: false });
|
|
67
|
+
log('Customer support is off. The help chat in the app now says support is unavailable.');
|
|
68
|
+
}
|
|
69
|
+
export async function runSupportRotate(client, projectId, apiUrl, dir, env, secret, log) {
|
|
70
|
+
const { key } = (await client.post(`${base(projectId)}/keys`, { environment: env, kind: secret ? 'secret' : 'publishable' }));
|
|
71
|
+
if (secret) {
|
|
72
|
+
log(`Secret support key for ${env} (shown once, store it on your server as NEWLY_SUPPORT_SECRET):\n${key}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const path = join(dir, SUPPORT_CONFIG);
|
|
76
|
+
let current = {};
|
|
77
|
+
try {
|
|
78
|
+
current = JSON.parse(readFileSync(path, 'utf8'));
|
|
79
|
+
}
|
|
80
|
+
catch { }
|
|
81
|
+
const keys = { development: current.keys?.development ?? '', production: current.keys?.production ?? '', [env]: key };
|
|
82
|
+
writeSupportConfig(dir, { projectId, apiUrl, keys });
|
|
83
|
+
log(`New ${env} key written to ${path}. The old key stops working now.`);
|
|
84
|
+
}
|
|
85
|
+
export async function runSupportAuth(client, projectId, opts, log) {
|
|
86
|
+
let patch;
|
|
87
|
+
if (opts.clear)
|
|
88
|
+
patch = { auth_issuer: null, auth_jwks_url: null, auth_audience: null };
|
|
89
|
+
else if (opts.supabaseUrl)
|
|
90
|
+
patch = supabaseAuth(opts.supabaseUrl);
|
|
91
|
+
else if (opts.issuer && opts.jwks && opts.audience)
|
|
92
|
+
patch = { auth_issuer: opts.issuer, auth_jwks_url: opts.jwks, auth_audience: opts.audience };
|
|
93
|
+
else
|
|
94
|
+
throw new Error('Pass --supabase-url <url>, or all of --issuer, --jwks and --audience, or --clear.');
|
|
95
|
+
await client.put(`${base(projectId)}/settings`, patch);
|
|
96
|
+
log(opts.clear ? 'Signed-in customers are no longer recognised.' : 'Signed-in customers are now recognised from their sign-in token.');
|
|
97
|
+
}
|
|
98
|
+
export async function runArticlesList(client, projectId, log) {
|
|
99
|
+
const { articles } = (await client.get(`${base(projectId)}/articles`));
|
|
100
|
+
if (!articles.length) {
|
|
101
|
+
log('No articles yet. Add one with newly support articles add --title <title> --file <path>.');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
for (const a of articles)
|
|
105
|
+
log(`${a.id} ${a.title} (updated ${a.updated_at})`);
|
|
106
|
+
}
|
|
107
|
+
const bodyOf = (opts) => {
|
|
108
|
+
if (opts.file)
|
|
109
|
+
return readFileSync(opts.file, 'utf8');
|
|
110
|
+
if (opts.body)
|
|
111
|
+
return opts.body;
|
|
112
|
+
throw new Error('Pass --body <text> or --file <path>.');
|
|
113
|
+
};
|
|
114
|
+
export async function runArticleAdd(client, projectId, opts, log) {
|
|
115
|
+
const { article } = (await client.post(`${base(projectId)}/articles`, { title: opts.title, body: bodyOf(opts) }));
|
|
116
|
+
log(`Added ${article.id} ${article.title}`);
|
|
117
|
+
}
|
|
118
|
+
export async function runArticleEdit(client, projectId, id, opts, log) {
|
|
119
|
+
const { articles } = (await client.get(`${base(projectId)}/articles`));
|
|
120
|
+
const current = articles.find((a) => a.id === id);
|
|
121
|
+
if (!current)
|
|
122
|
+
throw new Error(`No article ${id}. List them with newly support articles list.`);
|
|
123
|
+
const body = opts.body || opts.file ? bodyOf(opts) : current.body;
|
|
124
|
+
await client.put(`${base(projectId)}/articles/${encodeURIComponent(id)}`, { title: opts.title ?? current.title, body });
|
|
125
|
+
log(`Updated ${id}`);
|
|
126
|
+
}
|
|
127
|
+
export async function runArticleRemove(client, projectId, id, log) {
|
|
128
|
+
await client.delete(`${base(projectId)}/articles/${encodeURIComponent(id)}`);
|
|
129
|
+
log(`Removed ${id}`);
|
|
130
|
+
}
|
|
131
|
+
const onOff = (value, flag) => {
|
|
132
|
+
if (value === 'on')
|
|
133
|
+
return true;
|
|
134
|
+
if (value === 'off')
|
|
135
|
+
return false;
|
|
136
|
+
throw new Error(`${flag} must be on or off`);
|
|
137
|
+
};
|
|
138
|
+
const days = (value, flag, max) => {
|
|
139
|
+
if (!/^\d+$/.test(value) || Number(value) > max)
|
|
140
|
+
throw new Error(`${flag} must be a whole number from 0 to ${max}`);
|
|
141
|
+
return Number(value);
|
|
142
|
+
};
|
|
143
|
+
export function settingsPatch(flags) {
|
|
144
|
+
const patch = {};
|
|
145
|
+
if (flags.ai !== undefined)
|
|
146
|
+
patch.ai_enabled = onOff(flags.ai, '--ai');
|
|
147
|
+
if (flags.quality !== undefined) {
|
|
148
|
+
if (flags.quality !== 'standard' && flags.quality !== 'advanced')
|
|
149
|
+
throw new Error('--quality must be standard or advanced');
|
|
150
|
+
patch.ai_quality = flags.quality;
|
|
151
|
+
}
|
|
152
|
+
if (flags.dailyLimit !== undefined)
|
|
153
|
+
patch.ai_daily_cap = days(flags.dailyLimit, '--daily-limit', 5_000);
|
|
154
|
+
if (flags.autoCloseDays !== undefined)
|
|
155
|
+
patch.auto_close_days = days(flags.autoCloseDays, '--auto-close-days', 90);
|
|
156
|
+
if (flags.chatHistory !== undefined)
|
|
157
|
+
patch.chat_history = onOff(flags.chatHistory, '--chat-history');
|
|
158
|
+
return patch;
|
|
159
|
+
}
|
|
160
|
+
export async function runSupportSettings(client, projectId, flags, log) {
|
|
161
|
+
const patch = settingsPatch(flags);
|
|
162
|
+
const result = Object.keys(patch).length
|
|
163
|
+
? (await client.put(`${base(projectId)}/settings`, patch)).settings
|
|
164
|
+
: (await client.get(`${base(projectId)}/settings`)).settings;
|
|
165
|
+
log(JSON.stringify({
|
|
166
|
+
support: result.enabled ? 'on' : 'off', ai_answers: result.ai_enabled ? 'on' : 'off', quality: result.ai_quality ?? 'standard',
|
|
167
|
+
daily_limit: result.ai_daily_cap, auto_close_days: result.auto_close_days ?? 5, chat_history: result.chat_history ? 'on' : 'off',
|
|
168
|
+
}, null, 2));
|
|
169
|
+
}
|
|
170
|
+
export async function runSupportFeedback(client, projectId, log) {
|
|
171
|
+
const f = (await client.get(`${base(projectId)}/feedback`));
|
|
172
|
+
if (!f.count) {
|
|
173
|
+
log('No ratings yet.');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
log(`Average ${f.average} out of 5 from ${f.count} ${f.count === 1 ? 'rating' : 'ratings'} (5: ${f.distribution['5']}, 4: ${f.distribution['4']}, 3: ${f.distribution['3']}, 2: ${f.distribution['2']}, 1: ${f.distribution['1']})`);
|
|
177
|
+
for (const chat of f.chats.slice(0, 10))
|
|
178
|
+
log(`${chat.rating}/5 ${chat.answered_by === 'team' ? 'team' : 'AI'} ${chat.question ?? ''}`);
|
|
179
|
+
}
|
|
180
|
+
const importedLine = (r) => `Imported ${r.articles.length} ${r.articles.length === 1 ? 'article' : 'articles'} (used ${r.credits_charged} ${r.credits_charged === 1 ? 'credit' : 'credits'}):\n`
|
|
181
|
+
+ r.articles.map((a) => `${a.id} ${a.title}`).join('\n');
|
|
182
|
+
export async function runArticleImportPdf(client, projectId, path, log) {
|
|
183
|
+
if (!client.upload)
|
|
184
|
+
throw new Error('This client cannot upload files.');
|
|
185
|
+
const name = path.split('/').pop() || 'document.pdf';
|
|
186
|
+
const result = (await client.upload(`${base(projectId)}/articles/import/pdf?${new URLSearchParams({ name })}`, readFileSync(path), 'application/pdf'));
|
|
187
|
+
log(importedLine(result));
|
|
188
|
+
}
|
|
189
|
+
export async function runArticleImportUrl(client, projectId, url, refresh, log) {
|
|
190
|
+
const result = (await client.post(`${base(projectId)}/articles/import/url`, { url, replace: refresh }));
|
|
191
|
+
log(importedLine(result));
|
|
192
|
+
}
|
|
193
|
+
export function registerSupport(program) {
|
|
194
|
+
const support = program.command('support').description('Customer support inside your app: help chat, AI answers and knowledge base');
|
|
195
|
+
const ctx = (project) => {
|
|
196
|
+
const credentials = requireCredentials();
|
|
197
|
+
return { client: new ApiClient(credentials.apiUrl, credentials.token), projectId: resolveProject(process.cwd(), project), apiUrl: credentials.apiUrl };
|
|
198
|
+
};
|
|
199
|
+
const run = (work) => work().catch((error) => {
|
|
200
|
+
console.error(error instanceof Error && !('status' in error) ? error.message : friendlyApiError(error));
|
|
201
|
+
process.exitCode = 1;
|
|
202
|
+
});
|
|
203
|
+
const log = (m) => console.log(m);
|
|
204
|
+
support.command('status').option('--project <id>').action((o) => run(async () => {
|
|
205
|
+
const c = ctx(o.project);
|
|
206
|
+
await runSupportStatus(c.client, c.projectId, log);
|
|
207
|
+
}));
|
|
208
|
+
support.command('enable').description(`Turn support on, write the app's keys to ${SUPPORT_CONFIG} and the chat to ${SUPPORT_CHAT}`)
|
|
209
|
+
.option('--force-chat', `replace an existing ${SUPPORT_CHAT}`).option('--project <id>')
|
|
210
|
+
.action((o) => run(async () => {
|
|
211
|
+
const c = ctx(o.project);
|
|
212
|
+
await runSupportEnable(c.client, c.projectId, c.apiUrl, process.cwd(), log, { forceChat: Boolean(o.forceChat) });
|
|
213
|
+
}));
|
|
214
|
+
support.command('disable').option('--project <id>').action((o) => run(async () => {
|
|
215
|
+
const c = ctx(o.project);
|
|
216
|
+
await runSupportDisable(c.client, c.projectId, log);
|
|
217
|
+
}));
|
|
218
|
+
support.command('rotate').description('Replace a support key; --secret makes a server key, shown once')
|
|
219
|
+
.option('--env <environment>', 'dev or prod', 'dev').option('--secret', 'a secret key for your server').option('--project <id>')
|
|
220
|
+
.action((o) => run(async () => {
|
|
221
|
+
const c = ctx(o.project);
|
|
222
|
+
await runSupportRotate(c.client, c.projectId, c.apiUrl, process.cwd(), envFrom(o.env), Boolean(o.secret), log);
|
|
223
|
+
}));
|
|
224
|
+
support.command('auth').description("Recognise signed-in customers from your app's sign-in provider")
|
|
225
|
+
.option('--supabase-url <url>', 'your Supabase project URL').option('--issuer <url>').option('--jwks <url>').option('--audience <aud>')
|
|
226
|
+
.option('--clear').option('--project <id>')
|
|
227
|
+
.action((o) => run(async () => {
|
|
228
|
+
const c = ctx(o.project);
|
|
229
|
+
await runSupportAuth(c.client, c.projectId, { supabaseUrl: o.supabaseUrl, issuer: o.issuer, jwks: o.jwks, audience: o.audience, clear: o.clear }, log);
|
|
230
|
+
}));
|
|
231
|
+
support.command('settings').description('AI answers, quality, daily limit, auto-close and chat history; no flags prints them')
|
|
232
|
+
.option('--ai <on|off>').option('--quality <standard|advanced>').option('--daily-limit <n>')
|
|
233
|
+
.option('--auto-close-days <n>', '0 turns auto-close off').option('--chat-history <on|off>').option('--project <id>')
|
|
234
|
+
.action((o) => run(async () => {
|
|
235
|
+
const c = ctx(o.project);
|
|
236
|
+
await runSupportSettings(c.client, c.projectId, o, log);
|
|
237
|
+
}));
|
|
238
|
+
support.command('feedback').description("Customers' ratings of resolved chats").option('--project <id>')
|
|
239
|
+
.action((o) => run(async () => {
|
|
240
|
+
const c = ctx(o.project);
|
|
241
|
+
await runSupportFeedback(c.client, c.projectId, log);
|
|
242
|
+
}));
|
|
243
|
+
const articles = support.command('articles').description('The knowledge base AI answers come from');
|
|
244
|
+
articles.command('list').option('--project <id>').action((o) => run(async () => {
|
|
245
|
+
const c = ctx(o.project);
|
|
246
|
+
await runArticlesList(c.client, c.projectId, log);
|
|
247
|
+
}));
|
|
248
|
+
articles.command('add').requiredOption('--title <title>').option('--body <text>').option('--file <path>').option('--project <id>')
|
|
249
|
+
.action((o) => run(async () => {
|
|
250
|
+
const c = ctx(o.project);
|
|
251
|
+
await runArticleAdd(c.client, c.projectId, o, log);
|
|
252
|
+
}));
|
|
253
|
+
articles.command('edit <id>').option('--title <title>').option('--body <text>').option('--file <path>').option('--project <id>')
|
|
254
|
+
.action((id, o) => run(async () => {
|
|
255
|
+
const c = ctx(o.project);
|
|
256
|
+
await runArticleEdit(c.client, c.projectId, id, o, log);
|
|
257
|
+
}));
|
|
258
|
+
articles.command('import-pdf <path>').description('Turn a PDF into articles (uses 1 credit)').option('--project <id>')
|
|
259
|
+
.action((path, o) => run(async () => {
|
|
260
|
+
const c = ctx(o.project);
|
|
261
|
+
await runArticleImportPdf(c.client, c.projectId, path, log);
|
|
262
|
+
}));
|
|
263
|
+
articles.command('import-url <url>').description("Turn a docs page into articles (uses 1 credit); --refresh replaces that page's articles")
|
|
264
|
+
.option('--refresh').option('--project <id>')
|
|
265
|
+
.action((url, o) => run(async () => {
|
|
266
|
+
const c = ctx(o.project);
|
|
267
|
+
await runArticleImportUrl(c.client, c.projectId, url, Boolean(o.refresh), log);
|
|
268
|
+
}));
|
|
269
|
+
articles.command('remove <id>').option('--project <id>').action((id, o) => run(async () => {
|
|
270
|
+
const c = ctx(o.project);
|
|
271
|
+
await runArticleRemove(c.client, c.projectId, id, log);
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SUPPORT_CHAT_TSX = "aW1wb3J0IHsgdXNlQ2FsbGJhY2ssIHVzZUVmZmVjdCwgdXNlUmVmLCB1c2VTdGF0ZSB9IGZyb20gJ3JlYWN0JzsKaW1wb3J0IHsKICBBY3Rpdml0eUluZGljYXRvciwgRmxhdExpc3QsIEtleWJvYXJkQXZvaWRpbmdWaWV3LCBQbGF0Zm9ybSwgUHJlc3NhYmxlLCBTdHlsZVNoZWV0LCBUZXh0LCBUZXh0SW5wdXQsIFZpZXcsIHVzZUNvbG9yU2NoZW1lLAp9IGZyb20gJ3JlYWN0LW5hdGl2ZSc7CgppbXBvcnQgc3VwcG9ydCBmcm9tICdAL2xpYi9uZXdseS1zdXBwb3J0Lmpzb24nOwppbXBvcnQgeyBzdG9yYWdlIH0gZnJvbSAnQC9saWIvc3RvcmFnZSc7CgovLyBgbmV3bHkgc3VwcG9ydCBlbmFibGVgIHdyaXRlcyB0aGlzIGFwcCdzIHByb2plY3QsIEFQSSBvcmlnaW4gYW5kIHB1Ymxpc2hhYmxlIGtleXMgaGVyZS4gVGhlCi8vIGtleXMgYXJlIHB1YmxpYyBieSBkZXNpZ246IHRoZXkgb25seSBvcGVuIHRoaXMgYXBwJ3MgaGVscCBjaGF0Lgpjb25zdCB7IGFwaVVybCwga2V5cyB9ID0gc3VwcG9ydCBhcyB7IHByb2plY3RJZDogc3RyaW5nOyBhcGlVcmw6IHN0cmluZzsga2V5czogeyBkZXZlbG9wbWVudDogc3RyaW5nOyBwcm9kdWN0aW9uOiBzdHJpbmcgfSB9Owpjb25zdCBLRVkgPSBfX0RFVl9fID8ga2V5cy5kZXZlbG9wbWVudCA6IGtleXMucHJvZHVjdGlvbjsKY29uc3QgVE9LRU5fS0VZID0gYG5ld2x5LnN1cHBvcnQudG9rZW4uJHtfX0RFVl9fID8gJ2RldicgOiAncHJvZCd9YDsKY29uc3QgQ0hBVF9LRVkgPSBgbmV3bHkuc3VwcG9ydC5jaGF0LiR7X19ERVZfXyA/ICdkZXYnIDogJ3Byb2QnfWA7Cgp0eXBlIFN0YXR1cyA9ICdvcGVuJyB8ICd3YWl0aW5nJyB8ICdyZXNvbHZlZCc7CnR5cGUgU291cmNlID0geyBraW5kOiAnYXJ0aWNsZSc7IGlkOiBzdHJpbmc7IHRpdGxlOiBzdHJpbmcgfSB8IHsga2luZDogJ2hpc3RvcnknIH07CnR5cGUgTWVzc2FnZSA9IHsgaWQ6IHN0cmluZzsgYXV0aG9yOiAnY3VzdG9tZXInIHwgJ2FpJyB8ICdtYWtlcicgfCAnc3lzdGVtJzsgY29udGVudDogc3RyaW5nOyBzb3VyY2VzOiBTb3VyY2VbXTsgY3JlYXRlZF9hdDogc3RyaW5nIH07CnR5cGUgVGhyZWFkID0geyBzdGF0dXM6IFN0YXR1czsgcmF0aW5nOiBudW1iZXIgfCBudWxsOyBhbnN3ZXJpbmc6IGJvb2xlYW47IG1lc3NhZ2VzOiBNZXNzYWdlW10gfTsKdHlwZSBDaGF0U3VtbWFyeSA9IHsgaWQ6IHN0cmluZzsgc3RhdHVzOiBTdGF0dXM7IHVucmVhZDogbnVtYmVyOyBsYXN0X21lc3NhZ2U6IHN0cmluZyB8IG51bGw7IGxhc3RfbWVzc2FnZV9hdDogc3RyaW5nOyByYXRpbmc6IG51bWJlciB8IG51bGwgfTsKCi8qKgogKiBXaG8gaXMgYXNraW5nLiBQYXNzIGBpZFRva2VuYCAodGhlIHNpZ25lZC1pbiB1c2VyJ3MgdG9rZW4gZnJvbSBTdXBhYmFzZSwgRmlyZWJhc2UsIENsZXJrLi4uKSBzbwogKiB0aGUgYXBwJ3MgdGVhbSBzZWVzIGEgdmVyaWZpZWQgY3VzdG9tZXI7IHdpdGhvdXQgaXQgdGhlIGNoYXQgaXMgYW5vbnltb3VzIHRvIHRoaXMgZGV2aWNlLgogKi8KZXhwb3J0IHR5cGUgU3VwcG9ydEN1c3RvbWVyID0geyBpZFRva2VuPzogc3RyaW5nIHwgbnVsbDsgbmFtZT86IHN0cmluZyB8IG51bGw7IGVtYWlsPzogc3RyaW5nIHwgbnVsbCB9OwoKY29uc3QgU1RBVFVTX0xBQkVMOiBSZWNvcmQ8U3RhdHVzLCBzdHJpbmc+ID0geyBvcGVuOiAnT3BlbicsIHdhaXRpbmc6ICdXYWl0aW5nIGZvciB0aGUgdGVhbScsIHJlc29sdmVkOiAnUmVzb2x2ZWQnIH07Cgphc3luYyBmdW5jdGlvbiBjYWxsPFQ+KHBhdGg6IHN0cmluZywgaW5pdDogUmVxdWVzdEluaXQgPSB7fSk6IFByb21pc2U8VD4gewogIGNvbnN0IHJlcyA9IGF3YWl0IGZldGNoKGAke2FwaVVybH0vYXBwLXN1cHBvcnQvdjEke3BhdGh9YCwgewogICAgLi4uaW5pdCwKICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJywgLi4uKGluaXQuaGVhZGVycyA/PyB7fSkgfSwKICB9KTsKICBpZiAocmVzLnN0YXR1cyA9PT0gMjA0KSByZXR1cm4gdW5kZWZpbmVkIGFzIFQ7CiAgY29uc3QgYm9keSA9IGF3YWl0IHJlcy5qc29uKCkuY2F0Y2goKCkgPT4gKHt9KSk7CiAgaWYgKCFyZXMub2spIHRocm93IE9iamVjdC5hc3NpZ24obmV3IEVycm9yKGJvZHkuZXJyb3IgPz8gJ1N1cHBvcnQgaXMgdW5hdmFpbGFibGUgcmlnaHQgbm93LicpLCB7IHN0YXR1czogcmVzLnN0YXR1cyB9KTsKICByZXR1cm4gYm9keSBhcyBUOwp9Cgphc3luYyBmdW5jdGlvbiB2aXNpdG9yVG9rZW4oY3VzdG9tZXI6IFN1cHBvcnRDdXN0b21lciwgZnJlc2ggPSBmYWxzZSk6IFByb21pc2U8c3RyaW5nPiB7CiAgY29uc3Qgc2F2ZWQgPSBzdG9yYWdlLmdldFN0cmluZyhUT0tFTl9LRVkpOwogIGlmIChzYXZlZCAmJiAhZnJlc2gpIHJldHVybiBzYXZlZDsKICBjb25zdCB7IHRva2VuIH0gPSBhd2FpdCBjYWxsPHsgdG9rZW46IHN0cmluZyB9PignL3Zpc2l0b3JzJywgewogICAgbWV0aG9kOiAnUE9TVCcsCiAgICBoZWFkZXJzOiB7ICd4LW5ld2x5LXN1cHBvcnQta2V5JzogS0VZIH0sCiAgICBib2R5OiBKU09OLnN0cmluZ2lmeSh7IGlkVG9rZW46IGN1c3RvbWVyLmlkVG9rZW4gPz8gdW5kZWZpbmVkLCBuYW1lOiBjdXN0b21lci5uYW1lID8/IHVuZGVmaW5lZCwgZW1haWw6IGN1c3RvbWVyLmVtYWlsID8/IHVuZGVmaW5lZCB9KSwKICB9KTsKICBzdG9yYWdlLnNldChUT0tFTl9LRVksIHRva2VuKTsKICByZXR1cm4gdG9rZW47Cn0KCmNvbnN0IHRpbWVMYWJlbCA9IChpc286IHN0cmluZykgPT4gewogIGNvbnN0IG1pbnV0ZXMgPSBNYXRoLnJvdW5kKChEYXRlLm5vdygpIC0gRGF0ZS5wYXJzZShpc28pKSAvIDYwXzAwMCk7CiAgaWYgKG1pbnV0ZXMgPCAxKSByZXR1cm4gJ0p1c3Qgbm93JzsKICBpZiAobWludXRlcyA8IDYwKSByZXR1cm4gYCR7bWludXRlc30gbWluIGFnb2A7CiAgaWYgKG1pbnV0ZXMgPCA2MCAqIDI0KSByZXR1cm4gYCR7TWF0aC5yb3VuZChtaW51dGVzIC8gNjApfSBoIGFnb2A7CiAgcmV0dXJuIG5ldyBEYXRlKGlzbykudG9Mb2NhbGVEYXRlU3RyaW5nKCk7Cn07CgovKioKICogVGhlIGFwcCdzIGhlbHAgY2hhdDogdGhlIEFJIGFuc3dlcnMgZmlyc3QgYW5kIHRoZSB0ZWFtIHJlcGxpZXMgaW4gdGhlIHNhbWUgdGhyZWFkLiAiWW91ciBjaGF0cyIKICogbGlzdHMgZXZlcnkgZWFybGllciBjaGF0IHdpdGggaXRzIHN0YXR1czsgYSByZXNvbHZlZCBjaGF0IGFza3MgZm9yIGEgcmF0aW5nLCBhbmQgd3JpdGluZyBhZ2FpbgogKiBzdGFydHMgYSBuZXcgY2hhdC4KICovCmV4cG9ydCBmdW5jdGlvbiBTdXBwb3J0Q2hhdCh7IGN1c3RvbWVyID0ge30sIGFwcE5hbWUgPSAnSGVscCcgfTogeyBjdXN0b21lcj86IFN1cHBvcnRDdXN0b21lcjsgYXBwTmFtZT86IHN0cmluZyB9KSB7CiAgY29uc3QgYyA9IHVzZUNvbG9yU2NoZW1lKCkgPT09ICdkYXJrJyA/IHBhbGV0dGUuZGFyayA6IHBhbGV0dGUubGlnaHQ7CiAgY29uc3QgW3Rva2VuLCBzZXRUb2tlbl0gPSB1c2VTdGF0ZTxzdHJpbmcgfCBudWxsPihudWxsKTsKICBjb25zdCBbdmlldywgc2V0Vmlld10gPSB1c2VTdGF0ZTwnY2hhdCcgfCAnbGlzdCc+KCdjaGF0Jyk7CiAgY29uc3QgW2NoYXRJZCwgc2V0Q2hhdElkXSA9IHVzZVN0YXRlPHN0cmluZyB8IG51bGw+KCgpID0+IHN0b3JhZ2UuZ2V0U3RyaW5nKENIQVRfS0VZKSA/PyBudWxsKTsKICBjb25zdCBbdGhyZWFkLCBzZXRUaHJlYWRdID0gdXNlU3RhdGU8VGhyZWFkIHwgbnVsbD4obnVsbCk7CiAgY29uc3QgW2NoYXRzLCBzZXRDaGF0c10gPSB1c2VTdGF0ZTxDaGF0U3VtbWFyeVtdIHwgbnVsbD4obnVsbCk7CiAgY29uc3QgW2RyYWZ0LCBzZXREcmFmdF0gPSB1c2VTdGF0ZSgnJyk7CiAgY29uc3QgW2Vycm9yLCBzZXRFcnJvcl0gPSB1c2VTdGF0ZTxzdHJpbmcgfCBudWxsPihudWxsKTsKICBjb25zdCBbc2VuZGluZywgc2V0U2VuZGluZ10gPSB1c2VTdGF0ZShmYWxzZSk7CiAgY29uc3Qgc2lnbmVkSW4gPSBCb29sZWFuKGN1c3RvbWVyLmlkVG9rZW4pOwogIGNvbnN0IGxhc3RTaWduZWRJbiA9IHVzZVJlZihzaWduZWRJbik7CgogIC8vIEEgc2lnbi1pbiBvciBzaWduLW91dCBnZXRzIGEgbmV3IHZpc2l0b3IsIHNvIGEgdmVyaWZpZWQgY3VzdG9tZXIgbmV2ZXIgc2hhcmVzIGFuIGFub255bW91cyBoaXN0b3J5LgogIHVzZUVmZmVjdCgoKSA9PiB7CiAgICBjb25zdCBjaGFuZ2VkID0gbGFzdFNpZ25lZEluLmN1cnJlbnQgIT09IHNpZ25lZEluOwogICAgbGFzdFNpZ25lZEluLmN1cnJlbnQgPSBzaWduZWRJbjsKICAgIGlmIChjaGFuZ2VkKSB7CiAgICAgIHN0b3JhZ2UucmVtb3ZlKENIQVRfS0VZKTsKICAgICAgc2V0Q2hhdElkKG51bGwpOwogICAgICBzZXRUaHJlYWQobnVsbCk7CiAgICAgIHNldENoYXRzKG51bGwpOwogICAgfQogICAgdmlzaXRvclRva2VuKGN1c3RvbWVyLCBjaGFuZ2VkKS50aGVuKHNldFRva2VuLCAoZTogRXJyb3IpID0+IHNldEVycm9yKGUubWVzc2FnZSkpOwogICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIHJlYWN0LWhvb2tzL2V4aGF1c3RpdmUtZGVwcwogIH0sIFtzaWduZWRJbl0pOwoKICBjb25zdCBhdXRoZWQgPSB1c2VDYWxsYmFjayg8VCw+KHBhdGg6IHN0cmluZywgaW5pdDogUmVxdWVzdEluaXQgPSB7fSkgPT4KICAgIGNhbGw8VD4ocGF0aCwgeyAuLi5pbml0LCBoZWFkZXJzOiB7IEF1dGhvcml6YXRpb246IGBCZWFyZXIgJHt0b2tlbn1gLCAuLi4oaW5pdC5oZWFkZXJzID8/IHt9KSB9IH0pLmNhdGNoKGFzeW5jIChlOiBFcnJvciAmIHsgc3RhdHVzPzogbnVtYmVyIH0pID0+IHsKICAgICAgaWYgKGUuc3RhdHVzID09PSA0MDEpIHNldFRva2VuKGF3YWl0IHZpc2l0b3JUb2tlbihjdXN0b21lciwgdHJ1ZSkpOwogICAgICB0aHJvdyBlOwogICAgfSksIFt0b2tlbiwgY3VzdG9tZXJdKTsKCiAgY29uc3Qgb3BlbiA9IChpZDogc3RyaW5nIHwgbnVsbCkgPT4gewogICAgaWYgKGlkKSBzdG9yYWdlLnNldChDSEFUX0tFWSwgaWQpOwogICAgZWxzZSBzdG9yYWdlLnJlbW92ZShDSEFUX0tFWSk7CiAgICBzZXRDaGF0SWQoaWQpOwogICAgc2V0VGhyZWFkKG51bGwpOwogICAgc2V0VmlldygnY2hhdCcpOwogIH07CgogIGNvbnN0IGxvYWRDaGF0cyA9IHVzZUNhbGxiYWNrKGFzeW5jICgpID0+IHsKICAgIGlmICghdG9rZW4pIHJldHVybjsKICAgIHRyeSB7CiAgICAgIHNldENoYXRzKChhd2FpdCBhdXRoZWQ8eyBjb252ZXJzYXRpb25zOiBDaGF0U3VtbWFyeVtdIH0+KCcvY29udmVyc2F0aW9ucycpKS5jb252ZXJzYXRpb25zKTsKICAgIH0gY2F0Y2ggewogICAgICAvLyB0aGUgbGlzdCBzdGF5cyBhcyBpdCB3YXM7IHRoZSBjaGF0IGl0c2VsZiBrZWVwcyB3b3JraW5nCiAgICB9CiAgfSwgW3Rva2VuLCBhdXRoZWRdKTsKCiAgY29uc3QgbG9hZFRocmVhZCA9IHVzZUNhbGxiYWNrKGFzeW5jICgpID0+IHsKICAgIGlmICghdG9rZW4gfHwgIWNoYXRJZCkgcmV0dXJuOwogICAgdHJ5IHsKICAgICAgc2V0VGhyZWFkKGF3YWl0IGF1dGhlZDxUaHJlYWQ+KGAvY29udmVyc2F0aW9ucy8ke2NoYXRJZH0vbWVzc2FnZXNgKSk7CiAgICAgIGF3YWl0IGF1dGhlZChgL2NvbnZlcnNhdGlvbnMvJHtjaGF0SWR9L3JlYWRgLCB7IG1ldGhvZDogJ1BPU1QnIH0pOwogICAgfSBjYXRjaCAoZSkgewogICAgICBpZiAoKGUgYXMgeyBzdGF0dXM/OiBudW1iZXIgfSkuc3RhdHVzID09PSA0MDQpIG9wZW4obnVsbCk7CiAgICB9CiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgcmVhY3QtaG9va3MvZXhoYXVzdGl2ZS1kZXBzCiAgfSwgW3Rva2VuLCBjaGF0SWQsIGF1dGhlZF0pOwoKICB1c2VFZmZlY3QoKCkgPT4geyB2b2lkIGxvYWRDaGF0cygpOyB9LCBbbG9hZENoYXRzXSk7CgogIC8vIEZhc3RlciB3aGlsZSBhbiBhbnN3ZXIgaXMgYmVpbmcgd3JpdHRlbiwgc2xvd2VyIG90aGVyd2lzZTsgdGhlIGxpc3QgcmVmcmVzaGVzIHdoaWxlIGl0J3Mgc2hvd24uCiAgdXNlRWZmZWN0KCgpID0+IHsKICAgIGNvbnN0IHRpY2sgPSAoKSA9PiB2b2lkICh2aWV3ID09PSAnY2hhdCcgPyBsb2FkVGhyZWFkKCkgOiBsb2FkQ2hhdHMoKSk7CiAgICB0aWNrKCk7CiAgICBjb25zdCB0aW1lciA9IHNldEludGVydmFsKHRpY2ssIHZpZXcgPT09ICdjaGF0JyAmJiB0aHJlYWQ/LmFuc3dlcmluZyA/IDE1MDAgOiA0MDAwKTsKICAgIHJldHVybiAoKSA9PiBjbGVhckludGVydmFsKHRpbWVyKTsKICB9LCBbdmlldywgbG9hZFRocmVhZCwgbG9hZENoYXRzLCB0aHJlYWQ/LmFuc3dlcmluZ10pOwoKICBjb25zdCByZXNvbHZlZCA9IHRocmVhZD8uc3RhdHVzID09PSAncmVzb2x2ZWQnOwogIGNvbnN0IHNlbmQgPSBhc3luYyAoKSA9PiB7CiAgICBjb25zdCBtZXNzYWdlID0gZHJhZnQudHJpbSgpOwogICAgaWYgKCFtZXNzYWdlIHx8ICF0b2tlbiB8fCBzZW5kaW5nKSByZXR1cm47CiAgICBzZXRTZW5kaW5nKHRydWUpOwogICAgc2V0RXJyb3IobnVsbCk7CiAgICB0cnkgewogICAgICBpZiAoY2hhdElkICYmICFyZXNvbHZlZCkgewogICAgICAgIGF3YWl0IGF1dGhlZChgL2NvbnZlcnNhdGlvbnMvJHtjaGF0SWR9L21lc3NhZ2VzYCwgeyBtZXRob2Q6ICdQT1NUJywgYm9keTogSlNPTi5zdHJpbmdpZnkoeyBtZXNzYWdlIH0pIH0pOwogICAgICAgIGF3YWl0IGxvYWRUaHJlYWQoKTsKICAgICAgfSBlbHNlIHsKICAgICAgICAvLyBObyBjaGF0IHlldCwgb3IgdGhlIGxhc3Qgb25lIGlzIHJlc29sdmVkOiB0aGlzIG1lc3NhZ2Ugc3RhcnRzIGEgbmV3IGNoYXQuCiAgICAgICAgY29uc3QgeyBjb252ZXJzYXRpb24gfSA9IGF3YWl0IGF1dGhlZDx7IGNvbnZlcnNhdGlvbjogeyBpZDogc3RyaW5nIH0gfT4oJy9jb252ZXJzYXRpb25zJywgeyBtZXRob2Q6ICdQT1NUJywgYm9keTogSlNPTi5zdHJpbmdpZnkoeyBtZXNzYWdlIH0pIH0pOwogICAgICAgIG9wZW4oY29udmVyc2F0aW9uLmlkKTsKICAgICAgfQogICAgICBzZXREcmFmdCgnJyk7CiAgICB9IGNhdGNoIChlKSB7CiAgICAgIHNldEVycm9yKChlIGFzIEVycm9yKS5tZXNzYWdlKTsKICAgIH0gZmluYWxseSB7CiAgICAgIHNldFNlbmRpbmcoZmFsc2UpOwogICAgfQogIH07CgogIGNvbnN0IHJhdGUgPSBhc3luYyAocmF0aW5nOiBudW1iZXIpID0+IHsKICAgIGlmICghY2hhdElkKSByZXR1cm47CiAgICBhd2FpdCBhdXRoZWQoYC9jb252ZXJzYXRpb25zLyR7Y2hhdElkfS9yYXRpbmdgLCB7IG1ldGhvZDogJ1BPU1QnLCBib2R5OiBKU09OLnN0cmluZ2lmeSh7IHJhdGluZyB9KSB9KS5jYXRjaCgoKSA9PiB7fSk7CiAgICBhd2FpdCBsb2FkVGhyZWFkKCk7CiAgfTsKCiAgY29uc3QgdW5yZWFkRWxzZXdoZXJlID0gKGNoYXRzID8/IFtdKS5zb21lKChjaGF0KSA9PiBjaGF0LnVucmVhZCA+IDAgJiYgY2hhdC5pZCAhPT0gY2hhdElkKTsKICByZXR1cm4gKAogICAgPEtleWJvYXJkQXZvaWRpbmdWaWV3IHN0eWxlPXtbc3R5bGVzLnNjcmVlbiwgeyBiYWNrZ3JvdW5kQ29sb3I6IGMuYmcgfV19IGJlaGF2aW9yPXtQbGF0Zm9ybS5PUyA9PT0gJ2lvcycgPyAncGFkZGluZycgOiB1bmRlZmluZWR9PgogICAgICA8VmlldyBzdHlsZT17W3N0eWxlcy5oZWFkZXIsIHsgYm9yZGVyQ29sb3I6IGMuYm9yZGVyLCBiYWNrZ3JvdW5kQ29sb3I6IGMuY2FyZCB9XX0+CiAgICAgICAge3ZpZXcgPT09ICdsaXN0JyA/ICgKICAgICAgICAgIDxQcmVzc2FibGUgb25QcmVzcz17KCkgPT4gc2V0VmlldygnY2hhdCcpfSBhY2Nlc3NpYmlsaXR5Um9sZT0iYnV0dG9uIiBhY2Nlc3NpYmlsaXR5TGFiZWw9IkJhY2sgdG8gdGhlIGNoYXQiIHN0eWxlPXtzdHlsZXMuaGVhZGVyQnV0dG9ufT4KICAgICAgICAgICAgPFRleHQgc3R5bGU9e1tzdHlsZXMuaGVhZGVyQnV0dG9uVGV4dCwgeyBjb2xvcjogYy5hY2NlbnQgfV19PkJhY2s8L1RleHQ+CiAgICAgICAgICA8L1ByZXNzYWJsZT4KICAgICAgICApIDogbnVsbH0KICAgICAgICA8VmlldyBzdHlsZT17c3R5bGVzLmhlYWRlclRleHR9PgogICAgICAgICAgPFRleHQgc3R5bGU9e1tzdHlsZXMudGl0bGUsIHsgY29sb3I6IGMudGV4dCB9XX0+e3ZpZXcgPT09ICdsaXN0JyA/ICdZb3VyIGNoYXRzJyA6IGFwcE5hbWV9PC9UZXh0PgogICAgICAgICAgPFRleHQgc3R5bGU9e1tzdHlsZXMuc3VidGl0bGUsIHsgY29sb3I6IGMubXV0ZWQgfV19PgogICAgICAgICAgICB7dmlldyA9PT0gJ2xpc3QnID8gJ0V2ZXJ5IHF1ZXN0aW9uIHlvdSBhc2tlZCwgbmV3ZXN0IGZpcnN0JyA6IHJlc29sdmVkID8gJ1Jlc29sdmVkJyA6ICdBSSBhc3Npc3RhbnQsIGFuZCB0aGUgdGVhbSB3aGVuIHlvdSBuZWVkIHRoZW0nfQogICAgICAgICAgPC9UZXh0PgogICAgICAgIDwvVmlldz4KICAgICAgICB7dmlldyA9PT0gJ2NoYXQnID8gKAogICAgICAgICAgPFByZXNzYWJsZSBvblByZXNzPXsoKSA9PiB7IHNldFZpZXcoJ2xpc3QnKTsgdm9pZCBsb2FkQ2hhdHMoKTsgfX0gYWNjZXNzaWJpbGl0eVJvbGU9ImJ1dHRvbiIgYWNjZXNzaWJpbGl0eUxhYmVsPSJZb3VyIGNoYXRzIiBzdHlsZT17c3R5bGVzLmhlYWRlckJ1dHRvbn0+CiAgICAgICAgICAgIDxUZXh0IHN0eWxlPXtbc3R5bGVzLmhlYWRlckJ1dHRvblRleHQsIHsgY29sb3I6IGMuYWNjZW50IH1dfT5Zb3VyIGNoYXRze3VucmVhZEVsc2V3aGVyZSA/ICcg4oCiJyA6ICcnfTwvVGV4dD4KICAgICAgICAgIDwvUHJlc3NhYmxlPgogICAgICAgICkgOiAoCiAgICAgICAgICA8UHJlc3NhYmxlIG9uUHJlc3M9eygpID0+IG9wZW4obnVsbCl9IGFjY2Vzc2liaWxpdHlSb2xlPSJidXR0b24iIGFjY2Vzc2liaWxpdHlMYWJlbD0iTmV3IGNoYXQiIHN0eWxlPXtzdHlsZXMuaGVhZGVyQnV0dG9ufT4KICAgICAgICAgICAgPFRleHQgc3R5bGU9e1tzdHlsZXMuaGVhZGVyQnV0dG9uVGV4dCwgeyBjb2xvcjogYy5hY2NlbnQgfV19Pk5ldyBjaGF0PC9UZXh0PgogICAgICAgICAgPC9QcmVzc2FibGU+CiAgICAgICAgKX0KICAgICAgPC9WaWV3PgoKICAgICAge3ZpZXcgPT09ICdsaXN0JyA/ICgKICAgICAgICA8RmxhdExpc3QKICAgICAgICAgIGRhdGE9e2NoYXRzID8/IFtdfQogICAgICAgICAga2V5RXh0cmFjdG9yPXsoY2hhdCkgPT4gY2hhdC5pZH0KICAgICAgICAgIGNvbnRlbnRDb250YWluZXJTdHlsZT17c3R5bGVzLmxpc3R9CiAgICAgICAgICBMaXN0RW1wdHlDb21wb25lbnQ9e2NoYXRzID09PSBudWxsID8gPEFjdGl2aXR5SW5kaWNhdG9yIGNvbG9yPXtjLm11dGVkfSAvPiA6IDxUZXh0IHN0eWxlPXtbc3R5bGVzLmVtcHR5LCB7IGNvbG9yOiBjLm11dGVkIH1dfT5ObyBjaGF0cyB5ZXQuIEFzayBhbnl0aGluZyBhYm91dCB7YXBwTmFtZX0uPC9UZXh0Pn0KICAgICAgICAgIHJlbmRlckl0ZW09eyh7IGl0ZW0gfSkgPT4gKAogICAgICAgICAgICA8UHJlc3NhYmxlIG9uUHJlc3M9eygpID0+IG9wZW4oaXRlbS5pZCl9IGFjY2Vzc2liaWxpdHlSb2xlPSJidXR0b24iIGFjY2Vzc2liaWxpdHlMYWJlbD17YCR7U1RBVFVTX0xBQkVMW2l0ZW0uc3RhdHVzXX0gY2hhdGB9CiAgICAgICAgICAgICAgc3R5bGU9e1tzdHlsZXMuY2hhdFJvdywgeyBib3JkZXJDb2xvcjogYy5ib3JkZXIsIGJhY2tncm91bmRDb2xvcjogYy5jYXJkIH1dfT4KICAgICAgICAgICAgICA8VmlldyBzdHlsZT17c3R5bGVzLmNoYXRSb3dUZXh0fT4KICAgICAgICAgICAgICAgIDxUZXh0IHN0eWxlPXtbc3R5bGVzLmNoYXRQcmV2aWV3LCB7IGNvbG9yOiBjLnRleHQgfV19IG51bWJlck9mTGluZXM9ezJ9PntpdGVtLmxhc3RfbWVzc2FnZSA/PyAnTmV3IGNoYXQnfTwvVGV4dD4KICAgICAgICAgICAgICAgIDxUZXh0IHN0eWxlPXtbc3R5bGVzLnN1YnRpdGxlLCB7IGNvbG9yOiBjLm11dGVkIH1dfT57dGltZUxhYmVsKGl0ZW0ubGFzdF9tZXNzYWdlX2F0KX08L1RleHQ+CiAgICAgICAgICAgICAgPC9WaWV3PgogICAgICAgICAgICAgIDxWaWV3IHN0eWxlPXtzdHlsZXMuY2hhdFJvd1JpZ2h0fT4KICAgICAgICAgICAgICAgIDxUZXh0IHN0eWxlPXtbc3R5bGVzLnBpbGwsIGl0ZW0uc3RhdHVzID09PSAncmVzb2x2ZWQnID8geyBjb2xvcjogYy5tdXRlZCwgYm9yZGVyQ29sb3I6IGMuYm9yZGVyIH0gOiB7IGNvbG9yOiBjLmFjY2VudCwgYm9yZGVyQ29sb3I6IGMuYWNjZW50IH1dfT4KICAgICAgICAgICAgICAgICAge1NUQVRVU19MQUJFTFtpdGVtLnN0YXR1c119CiAgICAgICAgICAgICAgICA8L1RleHQ+CiAgICAgICAgICAgICAgICB7aXRlbS51bnJlYWQgPiAwID8gPFZpZXcgc3R5bGU9e1tzdHlsZXMuZG90LCB7IGJhY2tncm91bmRDb2xvcjogYy5hY2NlbnQgfV19IGFjY2Vzc2liaWxpdHlMYWJlbD0iVW5yZWFkIiAvPiA6IG51bGx9CiAgICAgICAgICAgICAgPC9WaWV3PgogICAgICAgICAgICA8L1ByZXNzYWJsZT4KICAgICAgICAgICl9CiAgICAgICAgLz4KICAgICAgKSA6ICgKICAgICAgICA8RmxhdExpc3QKICAgICAgICAgIGRhdGE9e3RocmVhZD8ubWVzc2FnZXMgPz8gW119CiAgICAgICAgICBrZXlFeHRyYWN0b3I9eyhtKSA9PiBtLmlkfQogICAgICAgICAgY29udGVudENvbnRhaW5lclN0eWxlPXtzdHlsZXMubGlzdH0KICAgICAgICAgIExpc3RFbXB0eUNvbXBvbmVudD17Y2hhdElkICYmICF0aHJlYWQgPyA8QWN0aXZpdHlJbmRpY2F0b3IgY29sb3I9e2MubXV0ZWR9IC8+IDogPFRleHQgc3R5bGU9e1tzdHlsZXMuZW1wdHksIHsgY29sb3I6IGMubXV0ZWQgfV19PkFzayBhbnl0aGluZyBhYm91dCB7YXBwTmFtZX0uIFlvdSdsbCBnZXQgYW4gYW5zd2VyIGhlcmUuPC9UZXh0Pn0KICAgICAgICAgIExpc3RGb290ZXJDb21wb25lbnQ9e3RocmVhZD8uYW5zd2VyaW5nID8gPEFjdGl2aXR5SW5kaWNhdG9yIHN0eWxlPXtzdHlsZXMudHlwaW5nfSBjb2xvcj17Yy5tdXRlZH0gLz4gOiBudWxsfQogICAgICAgICAgcmVuZGVySXRlbT17KHsgaXRlbSB9KSA9PiA8QnViYmxlIG1lc3NhZ2U9e2l0ZW19IGNvbG9ycz17Y30gLz59CiAgICAgICAgLz4KICAgICAgKX0KCiAgICAgIHt2aWV3ID09PSAnY2hhdCcgJiYgcmVzb2x2ZWQgPyAoCiAgICAgICAgPFZpZXcgc3R5bGU9e1tzdHlsZXMucmVzb2x2ZWQsIHsgYm9yZGVyQ29sb3I6IGMuYm9yZGVyLCBiYWNrZ3JvdW5kQ29sb3I6IGMuY2FyZCB9XX0gYWNjZXNzaWJpbGl0eUxpdmVSZWdpb249InBvbGl0ZSI+CiAgICAgICAgICA8VGV4dCBzdHlsZT17W3N0eWxlcy5yZXNvbHZlZFRpdGxlLCB7IGNvbG9yOiBjLnRleHQgfV19PlRoaXMgY2hhdCBpcyByZXNvbHZlZDwvVGV4dD4KICAgICAgICAgIHt0aHJlYWQ/LnJhdGluZyA9PT0gbnVsbCA/ICgKICAgICAgICAgICAgPD4KICAgICAgICAgICAgICA8VGV4dCBzdHlsZT17W3N0eWxlcy5zdWJ0aXRsZSwgeyBjb2xvcjogYy5tdXRlZCB9XX0+SG93IGRpZCB3ZSBkbz88L1RleHQ+CiAgICAgICAgICAgICAgPFZpZXcgc3R5bGU9e3N0eWxlcy5mYWNlc30+CiAgICAgICAgICAgICAgICB7Wyfwn5ieJywgJ/CfmYEnLCAn8J+YkCcsICfwn5mCJywgJ/CfmIQnXS5tYXAoKGZhY2UsIGkpID0+ICgKICAgICAgICAgICAgICAgICAgPFByZXNzYWJsZSBrZXk9e2ZhY2V9IG9uUHJlc3M9eygpID0+IHZvaWQgcmF0ZShpICsgMSl9IGFjY2Vzc2liaWxpdHlMYWJlbD17YCR7aSArIDF9IG9mIDVgfSBzdHlsZT17c3R5bGVzLmZhY2V9PgogICAgICAgICAgICAgICAgICAgIDxUZXh0IHN0eWxlPXtzdHlsZXMuZmFjZVRleHR9PntmYWNlfTwvVGV4dD4KICAgICAgICAgICAgICAgICAgPC9QcmVzc2FibGU+CiAgICAgICAgICAgICAgICApKX0KICAgICAgICAgICAgICA8L1ZpZXc+CiAgICAgICAgICAgIDwvPgogICAgICAgICAgKSA6IDxUZXh0IHN0eWxlPXtbc3R5bGVzLnN1YnRpdGxlLCB7IGNvbG9yOiBjLm11dGVkIH1dfT5UaGFua3MgZm9yIHlvdXIgcmF0aW5nLiBXcml0ZSBiZWxvdyB0byBzdGFydCBhIG5ldyBjaGF0LjwvVGV4dD59CiAgICAgICAgPC9WaWV3PgogICAgICApIDogbnVsbH0KICAgICAge2Vycm9yID8gPFRleHQgc3R5bGU9e1tzdHlsZXMuZXJyb3IsIHsgY29sb3I6IGMuZGFuZ2VyIH1dfT57ZXJyb3J9PC9UZXh0PiA6IG51bGx9CiAgICAgIHt2aWV3ID09PSAnY2hhdCcgPyAoCiAgICAgICAgPFZpZXcgc3R5bGU9e1tzdHlsZXMuY29tcG9zZXIsIHsgYm9yZGVyQ29sb3I6IGMuYm9yZGVyLCBiYWNrZ3JvdW5kQ29sb3I6IGMuY2FyZCB9XX0+CiAgICAgICAgICA8VGV4dElucHV0CiAgICAgICAgICAgIHZhbHVlPXtkcmFmdH0KICAgICAgICAgICAgb25DaGFuZ2VUZXh0PXtzZXREcmFmdH0KICAgICAgICAgICAgcGxhY2Vob2xkZXI9e3Jlc29sdmVkID8gJ0FzayBzb21ldGhpbmcgbmV3JyA6IGBNZXNzYWdlICR7YXBwTmFtZX1gfQogICAgICAgICAgICBwbGFjZWhvbGRlclRleHRDb2xvcj17Yy5tdXRlZH0KICAgICAgICAgICAgc3R5bGU9e1tzdHlsZXMuaW5wdXQsIHsgY29sb3I6IGMudGV4dCB9XX0KICAgICAgICAgICAgbXVsdGlsaW5lCiAgICAgICAgICAgIG1heExlbmd0aD17NDAwMH0KICAgICAgICAgIC8+CiAgICAgICAgICA8UHJlc3NhYmxlIG9uUHJlc3M9eygpID0+IHZvaWQgc2VuZCgpfSBkaXNhYmxlZD17IWRyYWZ0LnRyaW0oKSB8fCBzZW5kaW5nfSBzdHlsZT17W3N0eWxlcy5zZW5kLCB7IGJhY2tncm91bmRDb2xvcjogYy5hY2NlbnQsIG9wYWNpdHk6IGRyYWZ0LnRyaW0oKSA/IDEgOiAwLjQgfV19IGFjY2Vzc2liaWxpdHlMYWJlbD0iU2VuZCI+CiAgICAgICAgICAgIDxUZXh0IHN0eWxlPXtzdHlsZXMuc2VuZFRleHR9PuKGkTwvVGV4dD4KICAgICAgICAgIDwvUHJlc3NhYmxlPgogICAgICAgIDwvVmlldz4KICAgICAgKSA6IG51bGx9CiAgICA8L0tleWJvYXJkQXZvaWRpbmdWaWV3PgogICk7Cn0KCmZ1bmN0aW9uIEJ1YmJsZSh7IG1lc3NhZ2UsIGNvbG9yczogYyB9OiB7IG1lc3NhZ2U6IE1lc3NhZ2U7IGNvbG9yczogdHlwZW9mIHBhbGV0dGUubGlnaHQgfSkgewogIGlmIChtZXNzYWdlLmF1dGhvciA9PT0gJ3N5c3RlbScpIHJldHVybiA8VGV4dCBzdHlsZT17W3N0eWxlcy5zeXN0ZW0sIHsgY29sb3I6IGMubXV0ZWQgfV19PnttZXNzYWdlLmNvbnRlbnR9PC9UZXh0PjsKICBjb25zdCBtaW5lID0gbWVzc2FnZS5hdXRob3IgPT09ICdjdXN0b21lcic7CiAgcmV0dXJuICgKICAgIDxWaWV3IHN0eWxlPXtbc3R5bGVzLnJvdywgbWluZSA/IHN0eWxlcy5yaWdodCA6IHN0eWxlcy5sZWZ0XX0+CiAgICAgIHttZXNzYWdlLmF1dGhvciA9PT0gJ21ha2VyJyA/IDxUZXh0IHN0eWxlPXtbc3R5bGVzLndobywgeyBjb2xvcjogYy5tdXRlZCB9XX0+VGVhbTwvVGV4dD4gOiBudWxsfQogICAgICA8VmlldyBzdHlsZT17W3N0eWxlcy5idWJibGUsIG1pbmUgPyB7IGJhY2tncm91bmRDb2xvcjogYy5hY2NlbnQgfSA6IHsgYmFja2dyb3VuZENvbG9yOiBjLmNhcmQsIGJvcmRlckNvbG9yOiBjLmJvcmRlciwgYm9yZGVyV2lkdGg6IDEgfV19PgogICAgICAgIDxUZXh0IHN0eWxlPXt7IGNvbG9yOiBtaW5lID8gJyNmZmYnIDogYy50ZXh0LCBmb250U2l6ZTogMTYsIGxpbmVIZWlnaHQ6IDIyIH19PnttZXNzYWdlLmNvbnRlbnR9PC9UZXh0PgogICAgICA8L1ZpZXc+CiAgICAgIHttZXNzYWdlLnNvdXJjZXMubGVuZ3RoID8gKAogICAgICAgIDxWaWV3IHN0eWxlPXtzdHlsZXMuY2hpcHN9PgogICAgICAgICAge21lc3NhZ2Uuc291cmNlcy5tYXAoKHMpID0+ICgKICAgICAgICAgICAgPFRleHQga2V5PXtzLmtpbmQgPT09ICdhcnRpY2xlJyA/IHMuaWQgOiAnaGlzdG9yeSd9IHN0eWxlPXtbc3R5bGVzLmNoaXAsIHsgY29sb3I6IGMuYWNjZW50LCBib3JkZXJDb2xvcjogYy5ib3JkZXIgfV19PgogICAgICAgICAgICAgIHtzLmtpbmQgPT09ICdhcnRpY2xlJyA/IHMudGl0bGUgOiAnWW91ciBjaGF0IGhpc3RvcnknfQogICAgICAgICAgICA8L1RleHQ+CiAgICAgICAgICApKX0KICAgICAgICA8L1ZpZXc+CiAgICAgICkgOiBudWxsfQogICAgPC9WaWV3PgogICk7Cn0KCmNvbnN0IHBhbGV0dGUgPSB7CiAgbGlnaHQ6IHsgYmc6ICcjRjdGOEZBJywgY2FyZDogJyNGRkZGRkYnLCB0ZXh0OiAnIzBCMEIwRicsIG11dGVkOiAnIzU5NjQ3NCcsIGJvcmRlcjogJ3JnYmEoMTUsMjMsNDIsMC4xMCknLCBhY2NlbnQ6ICcjM0M2M0ZGJywgZGFuZ2VyOiAnI0MyNDEwQycgfSwKICBkYXJrOiB7IGJnOiAnIzBCMEIwRicsIGNhcmQ6ICcjMTYxODFEJywgdGV4dDogJyNGMkY0RjcnLCBtdXRlZDogJyM5OEEyQjMnLCBib3JkZXI6ICdyZ2JhKDI1NSwyNTUsMjU1LDAuMTApJywgYWNjZW50OiAnIzZCOEFGRicsIGRhbmdlcjogJyNGQjkyM0MnIH0sCn07Cgpjb25zdCBzdHlsZXMgPSBTdHlsZVNoZWV0LmNyZWF0ZSh7CiAgc2NyZWVuOiB7IGZsZXg6IDEgfSwKICBoZWFkZXI6IHsgZmxleERpcmVjdGlvbjogJ3JvdycsIGFsaWduSXRlbXM6ICdjZW50ZXInLCBnYXA6IDgsIHBhZGRpbmdIb3Jpem9udGFsOiAxNiwgcGFkZGluZ1ZlcnRpY2FsOiAxMiwgYm9yZGVyQm90dG9tV2lkdGg6IDEgfSwKICBoZWFkZXJUZXh0OiB7IGZsZXg6IDEgfSwKICBoZWFkZXJCdXR0b246IHsgbWluSGVpZ2h0OiA0NCwganVzdGlmeUNvbnRlbnQ6ICdjZW50ZXInLCBwYWRkaW5nSG9yaXpvbnRhbDogNCB9LAogIGhlYWRlckJ1dHRvblRleHQ6IHsgZm9udFNpemU6IDE1LCBmb250V2VpZ2h0OiAnNjAwJyB9LAogIHRpdGxlOiB7IGZvbnRTaXplOiAxNywgZm9udFdlaWdodDogJzcwMCcgfSwKICBzdWJ0aXRsZTogeyBmb250U2l6ZTogMTMsIG1hcmdpblRvcDogMiB9LAogIGxpc3Q6IHsgcGFkZGluZzogMTYsIGdhcDogMTAsIGZsZXhHcm93OiAxIH0sCiAgZW1wdHk6IHsgdGV4dEFsaWduOiAnY2VudGVyJywgbWFyZ2luVG9wOiA0MCwgZm9udFNpemU6IDE1IH0sCiAgdHlwaW5nOiB7IGFsaWduU2VsZjogJ2ZsZXgtc3RhcnQnLCBtYXJnaW5Ub3A6IDQgfSwKICBjaGF0Um93OiB7IGZsZXhEaXJlY3Rpb246ICdyb3cnLCBhbGlnbkl0ZW1zOiAnY2VudGVyJywgZ2FwOiAxMiwgcGFkZGluZzogMTQsIGJvcmRlclJhZGl1czogMTYsIGJvcmRlcldpZHRoOiAxIH0sCiAgY2hhdFJvd1RleHQ6IHsgZmxleDogMSwgZ2FwOiAyIH0sCiAgY2hhdFByZXZpZXc6IHsgZm9udFNpemU6IDE1LCBmb250V2VpZ2h0OiAnNjAwJyB9LAogIGNoYXRSb3dSaWdodDogeyBhbGlnbkl0ZW1zOiAnZmxleC1lbmQnLCBnYXA6IDYgfSwKICBwaWxsOiB7IGZvbnRTaXplOiAxMiwgZm9udFdlaWdodDogJzYwMCcsIGJvcmRlcldpZHRoOiAxLCBib3JkZXJSYWRpdXM6IDEyLCBwYWRkaW5nSG9yaXpvbnRhbDogOCwgcGFkZGluZ1ZlcnRpY2FsOiAyLCBvdmVyZmxvdzogJ2hpZGRlbicgfSwKICBkb3Q6IHsgd2lkdGg6IDgsIGhlaWdodDogOCwgYm9yZGVyUmFkaXVzOiA0IH0sCiAgcm93OiB7IG1heFdpZHRoOiAnODUlJywgZ2FwOiA2IH0sCiAgbGVmdDogeyBhbGlnblNlbGY6ICdmbGV4LXN0YXJ0JyB9LAogIHJpZ2h0OiB7IGFsaWduU2VsZjogJ2ZsZXgtZW5kJyB9LAogIHdobzogeyBmb250U2l6ZTogMTIgfSwKICBidWJibGU6IHsgYm9yZGVyUmFkaXVzOiAyMCwgcGFkZGluZ0hvcml6b250YWw6IDE0LCBwYWRkaW5nVmVydGljYWw6IDEwIH0sCiAgY2hpcHM6IHsgZmxleERpcmVjdGlvbjogJ3JvdycsIGZsZXhXcmFwOiAnd3JhcCcsIGdhcDogNiB9LAogIGNoaXA6IHsgZm9udFNpemU6IDEzLCBmb250V2VpZ2h0OiAnNjAwJywgYm9yZGVyV2lkdGg6IDEsIGJvcmRlclJhZGl1czogMTQsIHBhZGRpbmdIb3Jpem9udGFsOiAxMCwgcGFkZGluZ1ZlcnRpY2FsOiA0LCBvdmVyZmxvdzogJ2hpZGRlbicgfSwKICBzeXN0ZW06IHsgYWxpZ25TZWxmOiAnY2VudGVyJywgZm9udFNpemU6IDEzLCB0ZXh0QWxpZ246ICdjZW50ZXInLCBwYWRkaW5nSG9yaXpvbnRhbDogMjQgfSwKICByZXNvbHZlZDogeyBtYXJnaW5Ib3Jpem9udGFsOiAxMiwgbWFyZ2luVG9wOiA0LCBwYWRkaW5nOiAxNCwgYm9yZGVyUmFkaXVzOiAxNiwgYm9yZGVyV2lkdGg6IDEsIGdhcDogNiB9LAogIHJlc29sdmVkVGl0bGU6IHsgZm9udFNpemU6IDE1LCBmb250V2VpZ2h0OiAnNjAwJyB9LAogIGZhY2VzOiB7IGZsZXhEaXJlY3Rpb246ICdyb3cnLCBqdXN0aWZ5Q29udGVudDogJ3NwYWNlLWJldHdlZW4nIH0sCiAgZmFjZTogeyBtaW5XaWR0aDogNDQsIG1pbkhlaWdodDogNDQsIGFsaWduSXRlbXM6ICdjZW50ZXInLCBqdXN0aWZ5Q29udGVudDogJ2NlbnRlcicgfSwKICBmYWNlVGV4dDogeyBmb250U2l6ZTogMjYgfSwKICBlcnJvcjogeyBwYWRkaW5nSG9yaXpvbnRhbDogMTYsIHBhZGRpbmdCb3R0b206IDYsIGZvbnRTaXplOiAxMyB9LAogIGNvbXBvc2VyOiB7IGZsZXhEaXJlY3Rpb246ICdyb3cnLCBhbGlnbkl0ZW1zOiAnZmxleC1lbmQnLCBnYXA6IDgsIG1hcmdpbjogMTIsIHBhZGRpbmdMZWZ0OiAxNiwgcGFkZGluZ1JpZ2h0OiA2LCBwYWRkaW5nVmVydGljYWw6IDYsIGJvcmRlclJhZGl1czogMjQsIGJvcmRlcldpZHRoOiAxIH0sCiAgaW5wdXQ6IHsgZmxleDogMSwgZm9udFNpemU6IDE2LCBtYXhIZWlnaHQ6IDEyMCwgcGFkZGluZ1ZlcnRpY2FsOiA4IH0sCiAgc2VuZDogeyB3aWR0aDogMzgsIGhlaWdodDogMzgsIGJvcmRlclJhZGl1czogMTksIGFsaWduSXRlbXM6ICdjZW50ZXInLCBqdXN0aWZ5Q29udGVudDogJ2NlbnRlcicgfSwKICBzZW5kVGV4dDogeyBjb2xvcjogJyNmZmYnLCBmb250U2l6ZTogMTgsIGZvbnRXZWlnaHQ6ICc3MDAnIH0sCn0pOwo=";
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { registerCreds } from './commands/creds.js';
|
|
|
15
15
|
import { registerBuild } from './commands/build.js';
|
|
16
16
|
import { registerServices } from './commands/services.js';
|
|
17
17
|
import { registerEmail } from './commands/email.js';
|
|
18
|
+
import { registerSupport } from './commands/support.js';
|
|
18
19
|
import { registerWebsite } from './commands/website.js';
|
|
19
20
|
import { registerDomains } from './commands/domains.js';
|
|
20
21
|
import { registerDns } from './commands/dns.js';
|
|
@@ -44,6 +45,7 @@ registerCreds(program);
|
|
|
44
45
|
registerBuild(program);
|
|
45
46
|
registerServices(program);
|
|
46
47
|
registerEmail(program);
|
|
48
|
+
registerSupport(program);
|
|
47
49
|
registerWebsite(program);
|
|
48
50
|
registerDomains(program);
|
|
49
51
|
registerDns(program);
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = '0.2.
|
|
1
|
+
export const CLI_VERSION = '0.2.198';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runcloud",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.198",
|
|
4
4
|
"description": "Create and control run.cloud remote mobile simulators and cloud sandboxes",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"node": ">=20"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
+
"@run-cloud/sdk": "0.48.0",
|
|
47
48
|
"commander": "^12.1.0",
|
|
48
49
|
"ignore": "^7.0.5",
|
|
49
50
|
"tar": "^7.4.3",
|
|
Binary file
|
package/runcloud-0.2.196.tgz
DELETED
|
Binary file
|