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.
@@ -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`, 0, 1),
247
- y: finiteInRange(y, `${name} y`, 0, 1),
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 === 'portrait' || raw === 'portrait_upside_down' || raw === 'landscape_left' || raw === 'landscape_right') {
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', 50, 30_000);
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 < 2 || interaction.steps.length > 1_000) {
298
- throw new Error('--steps must contain from 2 to 1000 gesture steps');
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 !== 1 && step.points.length !== 2) {
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', 0, 1);
319
- finiteInRange(String(point.y), 'gesture y', 0, 1);
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', 0, 5_000);
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 > 100_000)
336
- throw new Error('clipboard text must contain at most 100000 UTF-16 code units');
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 < 1 || interaction.text.length > 10_000) {
340
- throw new Error('text must contain from 1 to 10000 characters');
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', 20, 30_000);
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`, 0, 5_000);
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', 100, 60_000, DEFAULT_INTERACTION_TIMEOUT_MS);
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>', 'acknowledgement timeout in milliseconds (100-60000)', String(DEFAULT_INTERACTION_TIMEOUT_MS))
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>', 'swipe duration in milliseconds (50-30000)')).action((id, fromX, fromY, toX, toY, opts) => action(() => runSimulatorInteraction(platform, id, {
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', 50, 30_000) }),
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>', '1-10000 US ASCII characters; quote text containing spaces')).action((id, text, opts) => action(() => runSimulatorInteraction(platform, id, { action: 'typeText', text }, opts), opts));
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>', 'key hold duration in milliseconds (20-30000)')
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 = ['shift', 'control', 'alt', 'meta']
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', 20, 30_000) }),
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>', 'button hold duration in milliseconds (20-30000)')).action((id, rawButton, opts) => action(() => runSimulatorInteraction(platform, id, {
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', 20, 30_000) }),
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>', 'horizontal delta from -1 to 1')
1302
- .argument('<delta-y>', 'vertical delta from -1 to 1')
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', -1, 1),
1313
- deltaY: finiteInRange(deltaY, 'delta-y', -1, 1),
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>', 'nonzero crown delta from -10000 to 10000')).action((id, delta, opts) => action(() => runSimulatorInteraction(platform, id, {
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', -10_000, 10_000),
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.196';
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.196",
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
Binary file