runcloud 0.2.196 → 0.2.197

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.
@@ -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.197';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runcloud",
3
- "version": "0.2.196",
3
+ "version": "0.2.197",
4
4
  "description": "Create and control run.cloud remote mobile simulators and cloud sandboxes",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
Binary file
Binary file