ticketlens 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/ticketlens.mjs
CHANGED
|
@@ -17,9 +17,16 @@ import { run as runConfig } from '../skills/jtb/scripts/lib/config-wizard.mjs';
|
|
|
17
17
|
import { activateLicense, checkLicense, revalidateIfStale, isLicensed, showUpgradePrompt, readLicense } from '../skills/jtb/scripts/lib/license.mjs';
|
|
18
18
|
import { deleteProfile, loadProfiles } from '../skills/jtb/scripts/lib/profile-resolver.mjs';
|
|
19
19
|
import { run as runCache } from '../skills/jtb/scripts/lib/cache-manager.mjs';
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
printHelp, printProfiles,
|
|
22
|
+
printLoginHelp, printLogoutHelp, printSyncHelp,
|
|
23
|
+
printActivateHelp, printLicenseHelp, printDeleteHelp,
|
|
24
|
+
printProfilesHelp, printScheduleHelp,
|
|
25
|
+
printInitHelp, printSwitchHelp, printConfigHelp,
|
|
26
|
+
} from '../skills/jtb/scripts/lib/help.mjs';
|
|
21
27
|
import { createStyler } from '../skills/jtb/scripts/lib/ansi.mjs';
|
|
22
28
|
import { readCliToken, saveCliToken, deleteCliToken } from '../skills/jtb/scripts/lib/cli-auth.mjs';
|
|
29
|
+
import { browserLogin } from '../skills/jtb/scripts/lib/browser-login.mjs';
|
|
23
30
|
import { syncProfiles, getApiBase, getConsoleBase } from '../skills/jtb/scripts/lib/sync.mjs';
|
|
24
31
|
import { promptSecret, promptText } from '../skills/jtb/scripts/lib/prompt-helpers.mjs';
|
|
25
32
|
|
|
@@ -45,6 +52,7 @@ switch (command) {
|
|
|
45
52
|
break;
|
|
46
53
|
|
|
47
54
|
case 'init':
|
|
55
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) { printInitHelp(); break; }
|
|
48
56
|
runInit().catch(err => {
|
|
49
57
|
process.stderr.write(`Error: ${err.message}\n`);
|
|
50
58
|
process.exitCode = 1;
|
|
@@ -52,6 +60,7 @@ switch (command) {
|
|
|
52
60
|
break;
|
|
53
61
|
|
|
54
62
|
case 'switch':
|
|
63
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) { printSwitchHelp(); break; }
|
|
55
64
|
runSwitch().catch(err => {
|
|
56
65
|
process.stderr.write(`Error: ${err.message}\n`);
|
|
57
66
|
process.exitCode = 1;
|
|
@@ -59,6 +68,7 @@ switch (command) {
|
|
|
59
68
|
break;
|
|
60
69
|
|
|
61
70
|
case 'config': {
|
|
71
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) { printConfigHelp(); break; }
|
|
62
72
|
const profileArg = cmdArgs.find(a => a.startsWith('--profile='));
|
|
63
73
|
const profileName = profileArg ? profileArg.split('=')[1] : undefined;
|
|
64
74
|
runConfig({ profileName }).catch(err => {
|
|
@@ -69,6 +79,7 @@ switch (command) {
|
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
case 'activate': {
|
|
82
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) { printActivateHelp(); break; }
|
|
72
83
|
const s = createStyler({ isTTY: process.stdout.isTTY });
|
|
73
84
|
const key = cmdArgs.find(a => !a.startsWith('--'));
|
|
74
85
|
if (!key) {
|
|
@@ -90,6 +101,7 @@ switch (command) {
|
|
|
90
101
|
}
|
|
91
102
|
|
|
92
103
|
case 'license': {
|
|
104
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) { printLicenseHelp(); break; }
|
|
93
105
|
const s = createStyler({ isTTY: process.stdout.isTTY });
|
|
94
106
|
const status = checkLicense();
|
|
95
107
|
const daysSinceVal = status.validatedAt
|
|
@@ -131,6 +143,7 @@ switch (command) {
|
|
|
131
143
|
}
|
|
132
144
|
|
|
133
145
|
case 'delete': {
|
|
146
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) { printDeleteHelp(); break; }
|
|
134
147
|
const s = createStyler({ isTTY: process.stderr.isTTY });
|
|
135
148
|
const profileName = cmdArgs.find(a => !a.startsWith('--'));
|
|
136
149
|
if (!profileName) {
|
|
@@ -182,6 +195,7 @@ switch (command) {
|
|
|
182
195
|
}
|
|
183
196
|
|
|
184
197
|
case 'profiles': {
|
|
198
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) { printProfilesHelp(); break; }
|
|
185
199
|
const plain = cmdArgs.includes('--plain');
|
|
186
200
|
const config = loadProfiles();
|
|
187
201
|
printProfiles({ config, plain });
|
|
@@ -203,6 +217,7 @@ switch (command) {
|
|
|
203
217
|
}
|
|
204
218
|
|
|
205
219
|
case 'schedule': {
|
|
220
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) { printScheduleHelp(); break; }
|
|
206
221
|
const subCmd = cmdArgs[0];
|
|
207
222
|
|
|
208
223
|
if (subCmd === '--stop') {
|
|
@@ -262,21 +277,46 @@ switch (command) {
|
|
|
262
277
|
break;
|
|
263
278
|
|
|
264
279
|
case 'login': {
|
|
280
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) { printLoginHelp(); break; }
|
|
281
|
+
|
|
282
|
+
const useManual = cmdArgs.includes('--manual');
|
|
283
|
+
|
|
265
284
|
(async () => {
|
|
266
285
|
const s = createStyler({ isTTY: process.stderr.isTTY });
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
process.stderr.write(` ${s.
|
|
275
|
-
process.
|
|
276
|
-
|
|
286
|
+
|
|
287
|
+
let token;
|
|
288
|
+
|
|
289
|
+
if (useManual) {
|
|
290
|
+
// ── manual paste flow (CI / headless environments) ──────────────────
|
|
291
|
+
process.stderr.write(`\n ${s.bold('TicketLens Login')}\n`);
|
|
292
|
+
process.stderr.write(` ${s.dim('─'.repeat(44))}\n`);
|
|
293
|
+
process.stderr.write(` ${s.dim(`Generate a CLI token at ${s.cyan(`${getConsoleBase()}/console/account`)}`)}\n`);
|
|
294
|
+
process.stderr.write(` ${s.dim('then paste it below.')}\n\n`);
|
|
295
|
+
|
|
296
|
+
token = await promptSecret(`CLI Token ${s.dim('(tl_…)')}:`, { stream: process.stderr });
|
|
297
|
+
if (!token.startsWith('tl_')) {
|
|
298
|
+
process.stderr.write(` ${s.red('✖')} Token must start with ${s.dim('tl_')}\n`);
|
|
299
|
+
process.exitCode = 1;
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
} else {
|
|
303
|
+
// ── browser flow (default) ────────────────────────────────────────
|
|
304
|
+
process.stderr.write(`\n ${s.bold('TicketLens Login')}\n`);
|
|
305
|
+
process.stderr.write(` ${s.dim('─'.repeat(44))}\n`);
|
|
306
|
+
process.stderr.write(` Opening browser to authorize…\n\n`);
|
|
307
|
+
process.stderr.write(` ${s.dim('○ Waiting for authorization (120s)…')}\n`);
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
token = await browserLogin();
|
|
311
|
+
} catch (err) {
|
|
312
|
+
process.stderr.write(`\x1b[A\r\x1b[2K ${s.red('✖')} ${err.message}\n`);
|
|
313
|
+
process.stderr.write(`\n ${s.dim(`Try ${s.cyan('ticketlens login --manual')} to paste a token instead.`)}\n\n`);
|
|
314
|
+
process.exitCode = 1;
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
277
317
|
}
|
|
278
318
|
|
|
279
|
-
//
|
|
319
|
+
// ── verify token against API (both flows) ─────────────────────────
|
|
280
320
|
process.stderr.write(`\n ${s.dim('○ Verifying token…')}\n`);
|
|
281
321
|
let res;
|
|
282
322
|
try {
|
|
@@ -284,7 +324,7 @@ switch (command) {
|
|
|
284
324
|
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
|
|
285
325
|
signal: AbortSignal.timeout(15000),
|
|
286
326
|
});
|
|
287
|
-
} catch
|
|
327
|
+
} catch {
|
|
288
328
|
process.stderr.write(`\x1b[A\r\x1b[2K ${s.red('✖')} Could not reach ${getApiBase()} — check your connection.\n`);
|
|
289
329
|
process.exitCode = 1;
|
|
290
330
|
return;
|
|
@@ -312,6 +352,7 @@ switch (command) {
|
|
|
312
352
|
}
|
|
313
353
|
|
|
314
354
|
case 'logout': {
|
|
355
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) { printLogoutHelp(); break; }
|
|
315
356
|
const s = createStyler({ isTTY: process.stderr.isTTY });
|
|
316
357
|
deleteCliToken();
|
|
317
358
|
process.stderr.write(` ${s.green('✔')} CLI token removed.\n`);
|
|
@@ -319,6 +360,7 @@ switch (command) {
|
|
|
319
360
|
}
|
|
320
361
|
|
|
321
362
|
case 'sync': {
|
|
363
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) { printSyncHelp(); break; }
|
|
322
364
|
(async () => {
|
|
323
365
|
const s = createStyler({ isTTY: process.stderr.isTTY });
|
|
324
366
|
process.stderr.write(`\n ${s.dim('Syncing from TicketLens console…')}\n`);
|
package/package.json
CHANGED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { hostname as osHostname } from 'node:os';
|
|
5
|
+
import { getConsoleBase } from './sync.mjs';
|
|
6
|
+
|
|
7
|
+
const PORT_MIN = 49152;
|
|
8
|
+
const PORT_MAX = 65535;
|
|
9
|
+
const TIMEOUT_MS = 120_000;
|
|
10
|
+
|
|
11
|
+
export const generateState = () => randomBytes(16).toString('hex');
|
|
12
|
+
|
|
13
|
+
export const pickPort = () =>
|
|
14
|
+
Math.floor(Math.random() * (PORT_MAX - PORT_MIN + 1)) + PORT_MIN;
|
|
15
|
+
|
|
16
|
+
export function openBrowser(url) {
|
|
17
|
+
const cmd = process.platform === 'win32' ? 'cmd'
|
|
18
|
+
: process.platform === 'darwin' ? 'open'
|
|
19
|
+
: 'xdg-open';
|
|
20
|
+
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
21
|
+
spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Start a one-shot local HTTP server that waits for the CLI auth callback.
|
|
26
|
+
* Resolves with the token string on success; rejects on state mismatch,
|
|
27
|
+
* missing/invalid token, or timeout.
|
|
28
|
+
*/
|
|
29
|
+
export function startLocalServer(port, expectedState, timeoutMs = TIMEOUT_MS) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
let settled = false;
|
|
32
|
+
|
|
33
|
+
const settle = (fn, value) => {
|
|
34
|
+
if (settled) return;
|
|
35
|
+
settled = true;
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
server.close();
|
|
38
|
+
fn(value);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const server = createServer((req, res) => {
|
|
42
|
+
const url = new URL(req.url, `http://127.0.0.1:${port}`);
|
|
43
|
+
|
|
44
|
+
if (url.pathname !== '/callback') {
|
|
45
|
+
res.writeHead(404);
|
|
46
|
+
res.end();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const token = url.searchParams.get('token') ?? '';
|
|
51
|
+
const state = url.searchParams.get('state') ?? '';
|
|
52
|
+
|
|
53
|
+
if (state !== expectedState) {
|
|
54
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
55
|
+
res.end('State mismatch — authorization rejected.');
|
|
56
|
+
settle(reject, new Error('State mismatch'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!token.startsWith('tl_')) {
|
|
61
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
62
|
+
res.end('Invalid token received.');
|
|
63
|
+
settle(reject, new Error('Invalid token'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
68
|
+
res.end(
|
|
69
|
+
'<html><body style="font-family:sans-serif;text-align:center;padding:60px;background:#0d1117;color:#cdd9e5">'
|
|
70
|
+
+ '<h2 style="color:#3fb950">✓ Authorized</h2>'
|
|
71
|
+
+ '<p style="color:#8b949e">You can close this tab and return to the terminal.</p>'
|
|
72
|
+
+ '</body></html>',
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
settle(resolve, token);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
server.on('error', (err) => settle(reject, err));
|
|
79
|
+
|
|
80
|
+
const timer = setTimeout(
|
|
81
|
+
() => settle(reject, new Error('Authorization timed out after 120 seconds')),
|
|
82
|
+
timeoutMs,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
server.listen(port, '127.0.0.1');
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Full browser login flow. Opens the authorize page in the default browser,
|
|
91
|
+
* waits for the callback, and returns the plaintext CLI token.
|
|
92
|
+
*/
|
|
93
|
+
export async function browserLogin() {
|
|
94
|
+
const state = generateState();
|
|
95
|
+
const port = pickPort();
|
|
96
|
+
const hostname = osHostname();
|
|
97
|
+
|
|
98
|
+
const consoleBase = getConsoleBase();
|
|
99
|
+
const url = `${consoleBase}/console/auth/cli`
|
|
100
|
+
+ `?port=${port}`
|
|
101
|
+
+ `&state=${encodeURIComponent(state)}`
|
|
102
|
+
+ `&hostname=${encodeURIComponent(hostname)}`;
|
|
103
|
+
|
|
104
|
+
const tokenPromise = startLocalServer(port, state);
|
|
105
|
+
openBrowser(url);
|
|
106
|
+
|
|
107
|
+
return tokenPromise;
|
|
108
|
+
}
|
|
@@ -215,6 +215,288 @@ export function printProfiles({ stream = process.stdout, config, plain = false }
|
|
|
215
215
|
stream.write(lines.join('\n') + '\n');
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
export function printLoginHelp({ stream = process.stdout } = {}) {
|
|
219
|
+
const s = createStyler({ isTTY: stream.isTTY });
|
|
220
|
+
const lines = [
|
|
221
|
+
'',
|
|
222
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('login')} ${s.dim('[--manual]')}`,
|
|
223
|
+
'',
|
|
224
|
+
` Connect the CLI to your TicketLens account.`,
|
|
225
|
+
` Opens a browser window to authorize — no copy-pasting required.`,
|
|
226
|
+
'',
|
|
227
|
+
` ${s.bold('HOW IT WORKS')}`,
|
|
228
|
+
'',
|
|
229
|
+
` 1. Run ${s.cyan('ticketlens login')} — your browser opens the authorize page`,
|
|
230
|
+
` 2. Click ${s.bold('Authorize TicketLens CLI')} while logged in to the Console`,
|
|
231
|
+
` 3. The terminal confirms login automatically`,
|
|
232
|
+
` 4. Run ${s.cyan('ticketlens sync')} to pull your tracker connections`,
|
|
233
|
+
'',
|
|
234
|
+
` ${s.bold('OPTIONS')}`,
|
|
235
|
+
'',
|
|
236
|
+
` ${s.brand('--manual')} Paste a token instead of using the browser`,
|
|
237
|
+
` ${s.dim('Useful for CI, SSH sessions, or headless environments.')}`,
|
|
238
|
+
` ${s.dim(`Generate a token at ${s.cyan('<console-url>/console/account')}`)}`,
|
|
239
|
+
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
240
|
+
'',
|
|
241
|
+
` ${s.bold('EXAMPLES')}`,
|
|
242
|
+
'',
|
|
243
|
+
` ${s.dim('$')} ticketlens login ${s.dim('# opens browser (default)')}`,
|
|
244
|
+
` ${s.dim('$')} ticketlens login --manual ${s.dim('# paste token (CI / headless)')}`,
|
|
245
|
+
` ${s.dim('$')} ticketlens sync ${s.dim('# after login, pull connections')}`,
|
|
246
|
+
'',
|
|
247
|
+
` ${s.bold('FILES')}`,
|
|
248
|
+
'',
|
|
249
|
+
` ${s.dim('Token saved to:')} ~/.ticketlens/cli-token ${s.dim('(written by ticketlens login)')}`,
|
|
250
|
+
'',
|
|
251
|
+
];
|
|
252
|
+
stream.write(lines.join('\n') + '\n');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function printLogoutHelp({ stream = process.stdout } = {}) {
|
|
256
|
+
const s = createStyler({ isTTY: stream.isTTY });
|
|
257
|
+
const lines = [
|
|
258
|
+
'',
|
|
259
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('logout')}`,
|
|
260
|
+
'',
|
|
261
|
+
` Remove the stored CLI token, disconnecting this machine from your`,
|
|
262
|
+
` TicketLens account. Local profiles and credentials are kept intact.`,
|
|
263
|
+
'',
|
|
264
|
+
` ${s.bold('OPTIONS')}`,
|
|
265
|
+
'',
|
|
266
|
+
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
267
|
+
'',
|
|
268
|
+
` ${s.bold('EXAMPLES')}`,
|
|
269
|
+
'',
|
|
270
|
+
` ${s.dim('$')} ticketlens logout`,
|
|
271
|
+
` ${s.dim('$')} ticketlens login ${s.dim('# re-authenticate')}`,
|
|
272
|
+
'',
|
|
273
|
+
];
|
|
274
|
+
stream.write(lines.join('\n') + '\n');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function printSyncHelp({ stream = process.stdout } = {}) {
|
|
278
|
+
const s = createStyler({ isTTY: stream.isTTY });
|
|
279
|
+
const lines = [
|
|
280
|
+
'',
|
|
281
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('sync')}`,
|
|
282
|
+
'',
|
|
283
|
+
` Pull tracker connections from the TicketLens console and write them`,
|
|
284
|
+
` to ${s.dim('~/.ticketlens/profiles.json')}. Requires ${s.cyan('ticketlens login')} first.`,
|
|
285
|
+
'',
|
|
286
|
+
` Profiles that need credentials will be listed with a reminder to`,
|
|
287
|
+
` run ${s.cyan('ticketlens config --profile=NAME')} to add them.`,
|
|
288
|
+
'',
|
|
289
|
+
` ${s.bold('OPTIONS')}`,
|
|
290
|
+
'',
|
|
291
|
+
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
292
|
+
'',
|
|
293
|
+
` ${s.bold('EXAMPLES')}`,
|
|
294
|
+
'',
|
|
295
|
+
` ${s.dim('$')} ticketlens login`,
|
|
296
|
+
` ${s.dim('$')} ticketlens sync`,
|
|
297
|
+
` ${s.dim('$')} ticketlens profiles ${s.dim('# verify pulled connections')}`,
|
|
298
|
+
'',
|
|
299
|
+
];
|
|
300
|
+
stream.write(lines.join('\n') + '\n');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function printActivateHelp({ stream = process.stdout } = {}) {
|
|
304
|
+
const s = createStyler({ isTTY: stream.isTTY });
|
|
305
|
+
const lines = [
|
|
306
|
+
'',
|
|
307
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('activate')} ${s.dim('<LICENSE-KEY>')}`,
|
|
308
|
+
'',
|
|
309
|
+
` Activate a Pro or Team license key to unlock paid features.`,
|
|
310
|
+
` Validates the key online and writes the result to ${s.dim('~/.ticketlens/license.json')}.`,
|
|
311
|
+
'',
|
|
312
|
+
` ${s.bold('ARGUMENTS')}`,
|
|
313
|
+
'',
|
|
314
|
+
` ${s.brand('<LICENSE-KEY>')} Your LemonSqueezy license key`,
|
|
315
|
+
'',
|
|
316
|
+
` ${s.bold('OPTIONS')}`,
|
|
317
|
+
'',
|
|
318
|
+
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
319
|
+
'',
|
|
320
|
+
` ${s.bold('EXAMPLES')}`,
|
|
321
|
+
'',
|
|
322
|
+
` ${s.dim('$')} ticketlens activate tl_abc123xxxx`,
|
|
323
|
+
` ${s.dim('$')} ticketlens license ${s.dim('# verify activation')}`,
|
|
324
|
+
'',
|
|
325
|
+
];
|
|
326
|
+
stream.write(lines.join('\n') + '\n');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function printLicenseHelp({ stream = process.stdout } = {}) {
|
|
330
|
+
const s = createStyler({ isTTY: stream.isTTY });
|
|
331
|
+
const lines = [
|
|
332
|
+
'',
|
|
333
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('license')}`,
|
|
334
|
+
'',
|
|
335
|
+
` Show current license status: tier, email, and last validation date.`,
|
|
336
|
+
` License is re-validated automatically in the background every 7 days.`,
|
|
337
|
+
'',
|
|
338
|
+
` ${s.bold('OPTIONS')}`,
|
|
339
|
+
'',
|
|
340
|
+
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
341
|
+
'',
|
|
342
|
+
` ${s.bold('EXAMPLES')}`,
|
|
343
|
+
'',
|
|
344
|
+
` ${s.dim('$')} ticketlens license`,
|
|
345
|
+
` ${s.dim('$')} ticketlens activate ${s.dim('<KEY>')} ${s.dim('# activate or renew')}`,
|
|
346
|
+
'',
|
|
347
|
+
];
|
|
348
|
+
stream.write(lines.join('\n') + '\n');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function printDeleteHelp({ stream = process.stdout } = {}) {
|
|
352
|
+
const s = createStyler({ isTTY: stream.isTTY });
|
|
353
|
+
const lines = [
|
|
354
|
+
'',
|
|
355
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('delete')} ${s.dim('<PROFILE-NAME>')}`,
|
|
356
|
+
'',
|
|
357
|
+
` Permanently remove a locally configured profile. In TTY mode, prompts`,
|
|
358
|
+
` for confirmation before deleting. Pass ${s.cyan('--yes')} to skip the prompt.`,
|
|
359
|
+
'',
|
|
360
|
+
` ${s.bold('ARGUMENTS')}`,
|
|
361
|
+
'',
|
|
362
|
+
` ${s.brand('<PROFILE-NAME>')} Name of the profile to remove`,
|
|
363
|
+
'',
|
|
364
|
+
` ${s.bold('OPTIONS')}`,
|
|
365
|
+
'',
|
|
366
|
+
` ${s.brand('--yes')}, ${s.brand('-y')} Skip confirmation prompt`,
|
|
367
|
+
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
368
|
+
'',
|
|
369
|
+
` ${s.bold('EXAMPLES')}`,
|
|
370
|
+
'',
|
|
371
|
+
` ${s.dim('$')} ticketlens delete myprofile`,
|
|
372
|
+
` ${s.dim('$')} ticketlens delete myprofile --yes`,
|
|
373
|
+
` ${s.dim('$')} ticketlens profiles ${s.dim('# list remaining profiles')}`,
|
|
374
|
+
'',
|
|
375
|
+
];
|
|
376
|
+
stream.write(lines.join('\n') + '\n');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function printProfilesHelp({ stream = process.stdout } = {}) {
|
|
380
|
+
const s = createStyler({ isTTY: stream.isTTY });
|
|
381
|
+
const lines = [
|
|
382
|
+
'',
|
|
383
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('profiles')} ${s.dim('[--plain]')}`,
|
|
384
|
+
'',
|
|
385
|
+
` List all locally configured Jira profiles and their active status.`,
|
|
386
|
+
` Also available as ${s.cyan('ticketlens ls')}.`,
|
|
387
|
+
'',
|
|
388
|
+
` ${s.bold('OPTIONS')}`,
|
|
389
|
+
'',
|
|
390
|
+
` ${s.brand('--plain')} Tab-separated output ${s.dim('(for scripting)')}`,
|
|
391
|
+
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
392
|
+
'',
|
|
393
|
+
` ${s.bold('EXAMPLES')}`,
|
|
394
|
+
'',
|
|
395
|
+
` ${s.dim('$')} ticketlens profiles`,
|
|
396
|
+
` ${s.dim('$')} ticketlens ls`,
|
|
397
|
+
` ${s.dim('$')} ticketlens profiles --plain`,
|
|
398
|
+
'',
|
|
399
|
+
];
|
|
400
|
+
stream.write(lines.join('\n') + '\n');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export function printScheduleHelp({ stream = process.stdout } = {}) {
|
|
404
|
+
const s = createStyler({ isTTY: stream.isTTY });
|
|
405
|
+
const lines = [
|
|
406
|
+
'',
|
|
407
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('schedule')} ${s.dim('[--stop|--status]')} ${s.dim('[Pro]')}`,
|
|
408
|
+
'',
|
|
409
|
+
` Set up a recurring digest email with your triage results. ${s.dim('[Pro]')}`,
|
|
410
|
+
` Runs an interactive wizard to configure day, time, and timezone.`,
|
|
411
|
+
'',
|
|
412
|
+
` ${s.bold('OPTIONS')}`,
|
|
413
|
+
'',
|
|
414
|
+
` ${s.brand('--stop')} Cancel the active digest schedule`,
|
|
415
|
+
` ${s.brand('--status')} Show current schedule configuration`,
|
|
416
|
+
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
417
|
+
'',
|
|
418
|
+
` ${s.bold('EXAMPLES')}`,
|
|
419
|
+
'',
|
|
420
|
+
` ${s.dim('$')} ticketlens schedule`,
|
|
421
|
+
` ${s.dim('$')} ticketlens schedule --status`,
|
|
422
|
+
` ${s.dim('$')} ticketlens schedule --stop`,
|
|
423
|
+
'',
|
|
424
|
+
];
|
|
425
|
+
stream.write(lines.join('\n') + '\n');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function printInitHelp({ stream = process.stdout } = {}) {
|
|
429
|
+
const s = createStyler({ isTTY: stream.isTTY });
|
|
430
|
+
const lines = [
|
|
431
|
+
'',
|
|
432
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('init')}`,
|
|
433
|
+
'',
|
|
434
|
+
` Configure a new Jira connection locally using an interactive wizard.`,
|
|
435
|
+
` Supports Jira Cloud ${s.dim('(Basic auth)')} and Jira Server/DC ${s.dim('(Bearer PAT or Basic)')}`,
|
|
436
|
+
'',
|
|
437
|
+
` After setup, run ${s.cyan('ticketlens PROJ-123')} to fetch your first ticket.`,
|
|
438
|
+
'',
|
|
439
|
+
` ${s.bold('OPTIONS')}`,
|
|
440
|
+
'',
|
|
441
|
+
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
442
|
+
'',
|
|
443
|
+
` ${s.bold('EXAMPLES')}`,
|
|
444
|
+
'',
|
|
445
|
+
` ${s.dim('$')} ticketlens init`,
|
|
446
|
+
` ${s.dim('$')} ticketlens profiles ${s.dim('# verify the new profile')}`,
|
|
447
|
+
'',
|
|
448
|
+
` ${s.dim('Tip: use')} ${s.cyan('ticketlens sync')} ${s.dim('instead to pull connections from the console.')}`,
|
|
449
|
+
'',
|
|
450
|
+
];
|
|
451
|
+
stream.write(lines.join('\n') + '\n');
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function printSwitchHelp({ stream = process.stdout } = {}) {
|
|
455
|
+
const s = createStyler({ isTTY: stream.isTTY });
|
|
456
|
+
const lines = [
|
|
457
|
+
'',
|
|
458
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('switch')}`,
|
|
459
|
+
'',
|
|
460
|
+
` Interactively select which profile is active by default.`,
|
|
461
|
+
` The chosen profile is used when no ${s.cyan('--profile')} flag is given.`,
|
|
462
|
+
'',
|
|
463
|
+
` ${s.bold('OPTIONS')}`,
|
|
464
|
+
'',
|
|
465
|
+
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
466
|
+
'',
|
|
467
|
+
` ${s.bold('EXAMPLES')}`,
|
|
468
|
+
'',
|
|
469
|
+
` ${s.dim('$')} ticketlens switch`,
|
|
470
|
+
` ${s.dim('$')} ticketlens profiles ${s.dim('# confirm new active profile')}`,
|
|
471
|
+
'',
|
|
472
|
+
];
|
|
473
|
+
stream.write(lines.join('\n') + '\n');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export function printConfigHelp({ stream = process.stdout } = {}) {
|
|
477
|
+
const s = createStyler({ isTTY: stream.isTTY });
|
|
478
|
+
const lines = [
|
|
479
|
+
'',
|
|
480
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('config')} ${s.dim('[--profile=NAME]')}`,
|
|
481
|
+
'',
|
|
482
|
+
` Edit settings for an existing profile using an interactive wizard.`,
|
|
483
|
+
` Without ${s.cyan('--profile')}, edits the currently active profile.`,
|
|
484
|
+
'',
|
|
485
|
+
` ${s.bold('OPTIONS')}`,
|
|
486
|
+
'',
|
|
487
|
+
` ${s.brand('--profile')}=${s.dim('NAME')} Profile to configure`,
|
|
488
|
+
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
489
|
+
'',
|
|
490
|
+
` ${s.bold('EXAMPLES')}`,
|
|
491
|
+
'',
|
|
492
|
+
` ${s.dim('$')} ticketlens config`,
|
|
493
|
+
` ${s.dim('$')} ticketlens config --profile=work`,
|
|
494
|
+
` ${s.dim('$')} ticketlens config --profile=acme`,
|
|
495
|
+
'',
|
|
496
|
+
];
|
|
497
|
+
stream.write(lines.join('\n') + '\n');
|
|
498
|
+
}
|
|
499
|
+
|
|
218
500
|
export function printTriageHelp({ stream = process.stdout } = {}) {
|
|
219
501
|
const s = createStyler({ isTTY: stream.isTTY });
|
|
220
502
|
const lines = [
|