@newly/cli 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": "@newly/cli",
3
- "version": "0.2.196",
3
+ "version": "0.2.197",
4
4
  "description": "Develop, provision, and deploy Newly backends from your terminal",
5
5
  "homepage": "https://newly.app",
6
6
  "repository": {