ticketlens 0.4.4 → 0.4.5
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/README.md +4 -0
- package/bin/ticketlens.mjs +4 -3
- package/package.json +1 -1
- package/skills/jtb/scripts/fetch-my-tickets.mjs +23 -22
- package/skills/jtb/scripts/lib/run-collisions.mjs +16 -13
- package/skills/jtb/scripts/lib/schedule-wizard.mjs +28 -6
- package/skills/jtb/scripts/lib/ticket-payload.mjs +19 -0
- package/skills/jtb/scripts/lib/triage-push.mjs +13 -25
- package/skills/jtb/scripts/lib/triage-share.mjs +10 -5
package/README.md
CHANGED
|
@@ -350,8 +350,12 @@ ticketlens sync # Pull your latest tracker profiles from the Console
|
|
|
350
350
|
|
|
351
351
|
Use `--manual` when there is no GUI (CI runners, SSH sessions, containers).
|
|
352
352
|
|
|
353
|
+
`ticketlens logout` removes the stored Console auth token and disconnects this machine from your TicketLens account. Local Jira profiles and credentials are kept intact — re-run `ticketlens login` to reconnect.
|
|
354
|
+
|
|
353
355
|
`ticketlens sync` pulls any tracker profiles you have configured in the Console and writes them locally, keeping your CLI in sync with your team settings without re-running `init`.
|
|
354
356
|
|
|
357
|
+
> **Console features** — `ticketlens triage --push`, `--share`, `ticketlens collisions`, and `ticketlens schedule` all require an active Console session. Run `ticketlens login` once and the token is stored automatically.
|
|
358
|
+
|
|
355
359
|
---
|
|
356
360
|
|
|
357
361
|
### License
|
package/bin/ticketlens.mjs
CHANGED
|
@@ -262,13 +262,13 @@ switch (command) {
|
|
|
262
262
|
|
|
263
263
|
if (subCmd === '--stop') {
|
|
264
264
|
const { runScheduleStop } = await import('../skills/jtb/scripts/lib/schedule-wizard.mjs');
|
|
265
|
-
await runScheduleStop({
|
|
265
|
+
await runScheduleStop({ cliToken: readCliToken() });
|
|
266
266
|
break;
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
if (subCmd === '--status') {
|
|
270
270
|
const { runScheduleStatus } = await import('../skills/jtb/scripts/lib/schedule-wizard.mjs');
|
|
271
|
-
await runScheduleStatus({
|
|
271
|
+
await runScheduleStatus({ cliToken: readCliToken() });
|
|
272
272
|
break;
|
|
273
273
|
}
|
|
274
274
|
|
|
@@ -281,7 +281,8 @@ switch (command) {
|
|
|
281
281
|
const { runScheduleWizard } = await import('../skills/jtb/scripts/lib/schedule-wizard.mjs');
|
|
282
282
|
const { promptScheduleAnswers } = await import('../skills/jtb/scripts/lib/prompt-helpers.mjs');
|
|
283
283
|
const answers = await promptScheduleAnswers(cmdArgs);
|
|
284
|
-
const result = await runScheduleWizard({ answers,
|
|
284
|
+
const result = await runScheduleWizard({ answers, cliToken: readCliToken() });
|
|
285
|
+
if (!result.ok) { process.exitCode = 1; break; }
|
|
285
286
|
|
|
286
287
|
process.stdout.write(`✔ Digest scheduled for ${answers.time} ${answers.timezone}\n`);
|
|
287
288
|
process.stdout.write(` Next delivery: ${result.nextDelivery}\n`);
|
package/package.json
CHANGED
|
@@ -18,6 +18,7 @@ import { promptProfileSelect } from './lib/profile-picker.mjs';
|
|
|
18
18
|
import { printTriageHelp } from './lib/help.mjs';
|
|
19
19
|
import { handleUnknownFlags } from './lib/arg-validator.mjs';
|
|
20
20
|
import { isLicensed, showUpgradePrompt, revalidateIfStale, readLicense } from './lib/license.mjs';
|
|
21
|
+
import { readCliToken } from './lib/cli-auth.mjs';
|
|
21
22
|
|
|
22
23
|
const DEFAULT_STATUSES = ['In Progress', 'Code Review', 'QA'];
|
|
23
24
|
|
|
@@ -334,37 +335,20 @@ export async function run(args, envOrOpts = process.env, fetcher = globalThis.fe
|
|
|
334
335
|
return;
|
|
335
336
|
}
|
|
336
337
|
|
|
337
|
-
//
|
|
338
|
-
const wantInteractive = process.stdout.isTTY && !args.includes('--plain') && !args.includes('--static');
|
|
339
|
-
if (wantInteractive && process.stdin.setRawMode) {
|
|
340
|
-
const result = await runInteractiveList(sorted, { baseUrl: conn.baseUrl, staleDays, styled: true });
|
|
341
|
-
if (result === 'switch') {
|
|
342
|
-
const cleanArgs = args.filter(a => !a.startsWith('--profile=') && !a.startsWith('--project='));
|
|
343
|
-
return run(cleanArgs, { ...opts, env, fetcher, configDir });
|
|
344
|
-
}
|
|
345
|
-
// Fall through to push/share if those flags were passed
|
|
346
|
-
if (!pushFlag && !shareFlag) return;
|
|
347
|
-
} else {
|
|
348
|
-
const useStyled = args.includes('--styled') || (!args.includes('--plain') && process.stdout.isTTY);
|
|
349
|
-
const summary = useStyled
|
|
350
|
-
? styleTriageSummary(sorted, { styled: true, staleDays, baseUrl: conn.baseUrl })
|
|
351
|
-
: assembleTriageSummary(sorted, { staleDays, baseUrl: conn.baseUrl });
|
|
352
|
-
process.stdout.write(summary + '\n');
|
|
353
|
-
}
|
|
354
|
-
|
|
338
|
+
// push/share run first so their result is visible before (or instead of) the TUI
|
|
355
339
|
if (pushFlag) {
|
|
356
340
|
const { pushTriageSnapshot } = await import('./lib/triage-push.mjs');
|
|
357
341
|
const { scanCurrentBranch } = await import('./lib/branch-scanner.mjs');
|
|
358
342
|
const pushFn = opts.pushFn ?? pushTriageSnapshot;
|
|
359
343
|
const scanFn = opts.scanFn ?? scanCurrentBranch;
|
|
360
|
-
const
|
|
344
|
+
const cliToken = opts.cliToken ?? readCliToken(configDir) ?? null;
|
|
361
345
|
const printFn = opts.print ?? ((s) => process.stdout.write(s));
|
|
362
346
|
await pushFn({
|
|
363
347
|
sorted,
|
|
364
348
|
rawTicketMap,
|
|
365
349
|
profile: profileName ?? 'default',
|
|
366
350
|
baseUrl: conn.baseUrl,
|
|
367
|
-
|
|
351
|
+
cliToken,
|
|
368
352
|
gitBranches: scanFn(),
|
|
369
353
|
fetcher,
|
|
370
354
|
print: printFn,
|
|
@@ -374,18 +358,35 @@ export async function run(args, envOrOpts = process.env, fetcher = globalThis.fe
|
|
|
374
358
|
if (shareFlag) {
|
|
375
359
|
const { shareTriageSnapshot } = await import('./lib/triage-share.mjs');
|
|
376
360
|
const shareFn = opts.shareFn ?? shareTriageSnapshot;
|
|
377
|
-
const
|
|
361
|
+
const cliToken = opts.cliToken ?? readCliToken(configDir) ?? null;
|
|
378
362
|
const printFn = opts.print ?? ((s) => process.stdout.write(s));
|
|
379
363
|
await shareFn({
|
|
380
364
|
sorted,
|
|
381
365
|
rawTicketMap,
|
|
382
366
|
profile: profileName ?? 'default',
|
|
383
367
|
baseUrl: conn.baseUrl,
|
|
384
|
-
|
|
368
|
+
cliToken,
|
|
385
369
|
fetcher,
|
|
386
370
|
print: printFn,
|
|
387
371
|
});
|
|
388
372
|
}
|
|
373
|
+
|
|
374
|
+
// Interactive mode: TTY + not --plain + not --static
|
|
375
|
+
const wantInteractive = process.stdout.isTTY && !args.includes('--plain') && !args.includes('--static');
|
|
376
|
+
if (wantInteractive && process.stdin.setRawMode) {
|
|
377
|
+
const result = await runInteractiveList(sorted, { baseUrl: conn.baseUrl, staleDays, styled: true });
|
|
378
|
+
if (result === 'switch') {
|
|
379
|
+
const cleanArgs = args.filter(a => !a.startsWith('--profile=') && !a.startsWith('--project='));
|
|
380
|
+
return run(cleanArgs, { ...opts, env, fetcher, configDir });
|
|
381
|
+
}
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const useStyled = args.includes('--styled') || (!args.includes('--plain') && process.stdout.isTTY);
|
|
386
|
+
const summary = useStyled
|
|
387
|
+
? styleTriageSummary(sorted, { styled: true, staleDays, baseUrl: conn.baseUrl })
|
|
388
|
+
: assembleTriageSummary(sorted, { staleDays, baseUrl: conn.baseUrl });
|
|
389
|
+
process.stdout.write(summary + '\n');
|
|
389
390
|
}
|
|
390
391
|
|
|
391
392
|
// Run if invoked directly
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readCliToken } from './cli-auth.mjs';
|
|
2
2
|
import { formatCollisions } from './collision-reporter.mjs';
|
|
3
|
+
import { DEFAULT_CONFIG_DIR } from './config.mjs';
|
|
3
4
|
|
|
4
5
|
import { apiBase, warnIfInsecure } from './api-utils.mjs';
|
|
5
6
|
|
|
@@ -10,35 +11,37 @@ const COLLISIONS_PATH = '/v1/triage/collisions';
|
|
|
10
11
|
*
|
|
11
12
|
* @param {string[]} args CLI argument array (e.g. ['--json', '--plain'])
|
|
12
13
|
* @param {object} [opts]
|
|
13
|
-
* @param {Function} [opts.fetcher]
|
|
14
|
-
* @param {Function} [opts.print]
|
|
15
|
-
* @param {Function} [opts.
|
|
14
|
+
* @param {Function} [opts.fetcher] Injectable fetch
|
|
15
|
+
* @param {Function} [opts.print] Output function
|
|
16
|
+
* @param {Function} [opts.readCliTokenFn] Injectable CLI token reader
|
|
17
|
+
* @param {string} [opts.configDir] Config directory override
|
|
16
18
|
* @returns {Promise<{ ok: boolean, status?: number }>}
|
|
17
19
|
*/
|
|
18
20
|
export async function runCollisions(args = [], opts = {}) {
|
|
19
21
|
const jsonFlag = args.includes('--json');
|
|
20
22
|
const plainFlag = args.includes('--plain');
|
|
21
|
-
const fetcher = opts.fetcher
|
|
22
|
-
const print = opts.print
|
|
23
|
-
const warn = opts.warn
|
|
24
|
-
const
|
|
23
|
+
const fetcher = opts.fetcher ?? globalThis.fetch;
|
|
24
|
+
const print = opts.print ?? ((s) => process.stdout.write(s));
|
|
25
|
+
const warn = opts.warn ?? ((s) => process.stderr.write(s));
|
|
26
|
+
const configDir = opts.configDir ?? DEFAULT_CONFIG_DIR;
|
|
27
|
+
const readCliTokenFn = opts.readCliTokenFn ?? ((dir) => readCliToken(dir));
|
|
25
28
|
warnIfInsecure(apiBase(), warn);
|
|
26
29
|
|
|
27
|
-
const
|
|
28
|
-
if (!
|
|
29
|
-
print('✗ collisions requires
|
|
30
|
+
const cliToken = readCliTokenFn(configDir) ?? null;
|
|
31
|
+
if (!cliToken) {
|
|
32
|
+
print('✗ collisions requires Console access. Run ticketlens login first.\n');
|
|
30
33
|
return { ok: false };
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
try {
|
|
34
37
|
const res = await fetcher(`${apiBase()}${COLLISIONS_PATH}`, {
|
|
35
|
-
headers: { Authorization: `Bearer ${
|
|
38
|
+
headers: { Authorization: `Bearer ${cliToken}` },
|
|
36
39
|
signal: AbortSignal.timeout(15_000),
|
|
37
40
|
});
|
|
38
41
|
|
|
39
42
|
if (!res.ok) {
|
|
40
43
|
if (res.status === 401) {
|
|
41
|
-
print('✗
|
|
44
|
+
print('✗ Session expired. Run ticketlens login to reconnect.\n');
|
|
42
45
|
return { ok: false, status: 401 };
|
|
43
46
|
}
|
|
44
47
|
if (res.status === 403) {
|
|
@@ -52,12 +52,17 @@ export function buildCronLine({ hour, minute, ticketlensBin }) {
|
|
|
52
52
|
export async function runScheduleWizard({
|
|
53
53
|
answers,
|
|
54
54
|
fetcher = globalThis.fetch,
|
|
55
|
-
|
|
55
|
+
cliToken,
|
|
56
56
|
configDir,
|
|
57
57
|
platform = osPlatform(),
|
|
58
58
|
writeLocalJob = defaultWriteLocalJob,
|
|
59
59
|
timeoutMs = 10_000,
|
|
60
|
+
print = s => process.stdout.write(s),
|
|
60
61
|
}) {
|
|
62
|
+
if (!cliToken) {
|
|
63
|
+
print('✗ schedule requires Console access. Run ticketlens login first.\n');
|
|
64
|
+
return { ok: false };
|
|
65
|
+
}
|
|
61
66
|
const { time, email, timezone } = answers;
|
|
62
67
|
const [hourStr, minuteStr] = time.split(':');
|
|
63
68
|
const hour = parseInt(hourStr, 10);
|
|
@@ -68,7 +73,7 @@ export async function runScheduleWizard({
|
|
|
68
73
|
signal: AbortSignal.timeout(timeoutMs),
|
|
69
74
|
headers: {
|
|
70
75
|
'Content-Type': 'application/json',
|
|
71
|
-
'Authorization': `Bearer ${
|
|
76
|
+
'Authorization': `Bearer ${cliToken}`,
|
|
72
77
|
},
|
|
73
78
|
body: JSON.stringify({ email, timezone, deliverAt: time }),
|
|
74
79
|
});
|
|
@@ -91,11 +96,20 @@ export async function runScheduleWizard({
|
|
|
91
96
|
return data;
|
|
92
97
|
}
|
|
93
98
|
|
|
94
|
-
export async function runScheduleStop({
|
|
99
|
+
export async function runScheduleStop({
|
|
100
|
+
fetcher = globalThis.fetch,
|
|
101
|
+
cliToken,
|
|
102
|
+
platform = osPlatform(),
|
|
103
|
+
print = s => process.stdout.write(s),
|
|
104
|
+
}) {
|
|
105
|
+
if (!cliToken) {
|
|
106
|
+
print('✗ schedule requires Console access. Run ticketlens login first.\n');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
95
109
|
const res = await fetcher(SCHEDULE_URL, {
|
|
96
110
|
method: 'DELETE',
|
|
97
111
|
signal: AbortSignal.timeout(10_000),
|
|
98
|
-
headers: { 'Authorization': `Bearer ${
|
|
112
|
+
headers: { 'Authorization': `Bearer ${cliToken}` },
|
|
99
113
|
});
|
|
100
114
|
if (!res.ok) throw new Error(`Schedule API error ${res.status}`);
|
|
101
115
|
|
|
@@ -114,11 +128,19 @@ export async function runScheduleStop({ fetcher = globalThis.fetch, licenseKey,
|
|
|
114
128
|
process.stdout.write('✔ Digest schedule removed.\n');
|
|
115
129
|
}
|
|
116
130
|
|
|
117
|
-
export async function runScheduleStatus({
|
|
131
|
+
export async function runScheduleStatus({
|
|
132
|
+
fetcher = globalThis.fetch,
|
|
133
|
+
cliToken,
|
|
134
|
+
print = s => process.stdout.write(s),
|
|
135
|
+
}) {
|
|
136
|
+
if (!cliToken) {
|
|
137
|
+
print('✗ schedule requires Console access. Run ticketlens login first.\n');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
118
140
|
const res = await fetcher(SCHEDULE_URL, {
|
|
119
141
|
method: 'GET',
|
|
120
142
|
signal: AbortSignal.timeout(10_000),
|
|
121
|
-
headers: { 'Authorization': `Bearer ${
|
|
143
|
+
headers: { 'Authorization': `Bearer ${cliToken}` },
|
|
122
144
|
});
|
|
123
145
|
if (!res.ok) {
|
|
124
146
|
process.stdout.write('No active digest schedule found.\n');
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared ticket payload builder used by triage-push and triage-share.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function buildTicketPayload(scored, rawMap, baseUrl) {
|
|
6
|
+
const raw = rawMap?.get(scored.ticketKey);
|
|
7
|
+
return {
|
|
8
|
+
key: scored.ticketKey,
|
|
9
|
+
summary: scored.summary ?? null,
|
|
10
|
+
status: scored.status ?? null,
|
|
11
|
+
assignee: raw?.assignee ?? null,
|
|
12
|
+
attention_score: null,
|
|
13
|
+
flags: scored.urgency === 'clear' ? [] : [scored.urgency],
|
|
14
|
+
compliance_coverage: null,
|
|
15
|
+
compliance_status: 'unknown',
|
|
16
|
+
url: baseUrl ? `${baseUrl}/browse/${scored.ticketKey}` : null,
|
|
17
|
+
last_updated: raw?.updated ?? null,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import { readLedger } from './ledger.mjs';
|
|
7
7
|
import { isLicensed } from './license.mjs';
|
|
8
8
|
import { DEFAULT_CONFIG_DIR } from './config.mjs';
|
|
9
|
-
|
|
10
9
|
import { apiBase, warnIfInsecure } from './api-utils.mjs';
|
|
10
|
+
import { buildTicketPayload } from './ticket-payload.mjs';
|
|
11
11
|
|
|
12
12
|
const PUSH_PATH = '/v1/triage/push';
|
|
13
13
|
|
|
@@ -16,27 +16,10 @@ const PUSH_PATH = '/v1/triage/push';
|
|
|
16
16
|
// https://api.ticketlens.com → https://app.ticketlens.com/console/queue
|
|
17
17
|
export function queueUrl(base) {
|
|
18
18
|
const noApi = base.replace(/^(https?:\/\/)api\./, '$1');
|
|
19
|
-
|
|
20
|
-
const console = /ticketlens\.com/.test(noApi)
|
|
19
|
+
const consoleBase = /ticketlens\.com/.test(noApi)
|
|
21
20
|
? noApi.replace('://', '://app.')
|
|
22
21
|
: noApi;
|
|
23
|
-
return
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function buildTicketPayload(scored, rawMap, baseUrl) {
|
|
27
|
-
const raw = rawMap?.get(scored.ticketKey);
|
|
28
|
-
return {
|
|
29
|
-
key: scored.ticketKey,
|
|
30
|
-
summary: scored.summary ?? null,
|
|
31
|
-
status: scored.status ?? null,
|
|
32
|
-
assignee: raw?.assignee ?? null,
|
|
33
|
-
attention_score: null,
|
|
34
|
-
flags: scored.urgency === 'clear' ? [] : [scored.urgency],
|
|
35
|
-
compliance_coverage: null,
|
|
36
|
-
compliance_status: 'unknown',
|
|
37
|
-
url: baseUrl ? `${baseUrl}/browse/${scored.ticketKey}` : null,
|
|
38
|
-
last_updated: raw?.updated ?? null,
|
|
39
|
-
};
|
|
22
|
+
return consoleBase + '/console/queue';
|
|
40
23
|
}
|
|
41
24
|
|
|
42
25
|
/**
|
|
@@ -47,7 +30,7 @@ function buildTicketPayload(scored, rawMap, baseUrl) {
|
|
|
47
30
|
* @param {Map} [opts.rawTicketMap] - Map<key, normalizedTicket>
|
|
48
31
|
* @param {string} [opts.profile] - Resolved profile name (max 100 chars)
|
|
49
32
|
* @param {string} [opts.baseUrl] - Jira base URL for URL construction
|
|
50
|
-
* @param {string} [opts.
|
|
33
|
+
* @param {string} [opts.cliToken] - CLI session token for the API
|
|
51
34
|
* @param {string} [opts.capturedAt] - ISO 8601 timestamp (defaults to now)
|
|
52
35
|
* @param {Array} [opts.gitBranches] - Branch metadata from scanCurrentBranch (null = not in git repo)
|
|
53
36
|
* @param {Function} [opts.fetcher] - Injectable fetch (default: globalThis.fetch)
|
|
@@ -59,7 +42,7 @@ export async function pushTriageSnapshot({
|
|
|
59
42
|
rawTicketMap = new Map(),
|
|
60
43
|
profile,
|
|
61
44
|
baseUrl,
|
|
62
|
-
|
|
45
|
+
cliToken,
|
|
63
46
|
capturedAt,
|
|
64
47
|
gitBranches,
|
|
65
48
|
fetcher = globalThis.fetch,
|
|
@@ -70,8 +53,8 @@ export async function pushTriageSnapshot({
|
|
|
70
53
|
configDir = DEFAULT_CONFIG_DIR,
|
|
71
54
|
} = {}) {
|
|
72
55
|
warnIfInsecure(apiBase(), warn);
|
|
73
|
-
if (!
|
|
74
|
-
print('✗ --push requires
|
|
56
|
+
if (!cliToken) {
|
|
57
|
+
print('✗ --push requires Console access. Run ticketlens login first.\n');
|
|
75
58
|
return { ok: false };
|
|
76
59
|
}
|
|
77
60
|
|
|
@@ -110,7 +93,7 @@ export async function pushTriageSnapshot({
|
|
|
110
93
|
method: 'POST',
|
|
111
94
|
headers: {
|
|
112
95
|
'Content-Type': 'application/json',
|
|
113
|
-
'Authorization': `Bearer ${
|
|
96
|
+
'Authorization': `Bearer ${cliToken}`,
|
|
114
97
|
},
|
|
115
98
|
body: JSON.stringify(payload),
|
|
116
99
|
signal: AbortSignal.timeout(15_000),
|
|
@@ -121,6 +104,11 @@ export async function pushTriageSnapshot({
|
|
|
121
104
|
return { ok: true, status: res.status };
|
|
122
105
|
}
|
|
123
106
|
|
|
107
|
+
if (res.status === 401) {
|
|
108
|
+
print('✗ Session expired. Run ticketlens login to reconnect.\n');
|
|
109
|
+
return { ok: false, status: res.status };
|
|
110
|
+
}
|
|
111
|
+
|
|
124
112
|
if (res.status === 403) {
|
|
125
113
|
print('✗ --push requires a Team license\n');
|
|
126
114
|
return { ok: false, status: res.status };
|
|
@@ -29,7 +29,7 @@ function buildTicketPayload(scored, rawMap, baseUrl) {
|
|
|
29
29
|
* @param {Map} [opts.rawTicketMap] - Map<key, normalizedTicket>
|
|
30
30
|
* @param {string} [opts.profile] - Resolved profile name (max 100 chars)
|
|
31
31
|
* @param {string} [opts.baseUrl] - Jira base URL
|
|
32
|
-
* @param {string} [opts.
|
|
32
|
+
* @param {string} [opts.cliToken] - CLI session token
|
|
33
33
|
* @param {string} [opts.capturedAt] - ISO 8601 timestamp
|
|
34
34
|
* @param {Function} [opts.fetcher] - Injectable fetch
|
|
35
35
|
* @param {Function} [opts.print] - Output fn
|
|
@@ -40,15 +40,15 @@ export async function shareTriageSnapshot({
|
|
|
40
40
|
rawTicketMap = new Map(),
|
|
41
41
|
profile,
|
|
42
42
|
baseUrl,
|
|
43
|
-
|
|
43
|
+
cliToken,
|
|
44
44
|
capturedAt,
|
|
45
45
|
fetcher = globalThis.fetch,
|
|
46
46
|
print = (s) => process.stdout.write(s),
|
|
47
47
|
warn = (s) => process.stderr.write(s),
|
|
48
48
|
} = {}) {
|
|
49
49
|
warnIfInsecure(apiBase(), warn);
|
|
50
|
-
if (!
|
|
51
|
-
print('✗ --share requires
|
|
50
|
+
if (!cliToken) {
|
|
51
|
+
print('✗ --share requires Console access. Run ticketlens login first.\n');
|
|
52
52
|
return { ok: false };
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -63,7 +63,7 @@ export async function shareTriageSnapshot({
|
|
|
63
63
|
method: 'POST',
|
|
64
64
|
headers: {
|
|
65
65
|
'Content-Type': 'application/json',
|
|
66
|
-
'Authorization': `Bearer ${
|
|
66
|
+
'Authorization': `Bearer ${cliToken}`,
|
|
67
67
|
},
|
|
68
68
|
body: JSON.stringify(payload),
|
|
69
69
|
signal: AbortSignal.timeout(15_000),
|
|
@@ -75,6 +75,11 @@ export async function shareTriageSnapshot({
|
|
|
75
75
|
return { ok: true, status: res.status };
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
if (res.status === 401) {
|
|
79
|
+
print('✗ Session expired. Run ticketlens login to reconnect.\n');
|
|
80
|
+
return { ok: false, status: res.status };
|
|
81
|
+
}
|
|
82
|
+
|
|
78
83
|
if (res.status === 403) {
|
|
79
84
|
print('✗ --share requires a Team license\n');
|
|
80
85
|
return { ok: false, status: res.status };
|