ticketlens 0.4.5 → 0.4.7
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 +3 -2
- package/package.json +1 -1
- package/skills/jtb/scripts/fetch-my-tickets.mjs +7 -7
- package/skills/jtb/scripts/lib/run-collisions.mjs +6 -6
- package/skills/jtb/scripts/lib/schedule-wizard.mjs +80 -45
- package/skills/jtb/scripts/lib/summarizer.mjs +3 -2
- package/skills/jtb/scripts/lib/triage-push.mjs +7 -6
- package/skills/jtb/scripts/lib/triage-share.mjs +7 -6
package/bin/ticketlens.mjs
CHANGED
|
@@ -284,8 +284,9 @@ switch (command) {
|
|
|
284
284
|
const result = await runScheduleWizard({ answers, cliToken: readCliToken() });
|
|
285
285
|
if (!result.ok) { process.exitCode = 1; break; }
|
|
286
286
|
|
|
287
|
-
|
|
288
|
-
process.stdout.write(`
|
|
287
|
+
const s = createStyler({ isTTY: process.stdout.isTTY });
|
|
288
|
+
process.stdout.write(` ${s.green('✔')} Digest scheduled for ${s.cyan(answers.time)} ${answers.timezone}\n`);
|
|
289
|
+
process.stdout.write(` Next delivery: ${s.cyan(result.nextDelivery)}\n`);
|
|
289
290
|
break;
|
|
290
291
|
}
|
|
291
292
|
|
package/package.json
CHANGED
|
@@ -17,20 +17,19 @@ import { runInteractiveList } from './lib/interactive-list.mjs';
|
|
|
17
17
|
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
|
-
import { isLicensed, showUpgradePrompt, revalidateIfStale
|
|
20
|
+
import { isLicensed, showUpgradePrompt, revalidateIfStale } from './lib/license.mjs';
|
|
21
21
|
import { readCliToken } from './lib/cli-auth.mjs';
|
|
22
|
+
import { apiBase } from './lib/api-utils.mjs';
|
|
22
23
|
|
|
23
24
|
const DEFAULT_STATUSES = ['In Progress', 'Code Review', 'QA'];
|
|
24
25
|
|
|
25
|
-
async function defaultDigestDeliverer(payload) {
|
|
26
|
-
const
|
|
27
|
-
const licenseKey = readLicense()?.key;
|
|
28
|
-
const res = await fetch('https://api.ticketlens.dev/v1/digest/deliver', {
|
|
26
|
+
async function defaultDigestDeliverer(payload, { cliToken } = {}) {
|
|
27
|
+
const res = await fetch(`${apiBase()}/v1/digest/deliver`, {
|
|
29
28
|
method: 'POST',
|
|
30
29
|
signal: AbortSignal.timeout(10_000),
|
|
31
30
|
headers: {
|
|
32
31
|
'Content-Type': 'application/json',
|
|
33
|
-
'Authorization': `Bearer ${
|
|
32
|
+
'Authorization': `Bearer ${cliToken}`,
|
|
34
33
|
},
|
|
35
34
|
body: JSON.stringify(payload),
|
|
36
35
|
});
|
|
@@ -297,6 +296,7 @@ export async function run(args, envOrOpts = process.env, fetcher = globalThis.fe
|
|
|
297
296
|
return;
|
|
298
297
|
}
|
|
299
298
|
const deliverer = opts.digestDeliverer ?? defaultDigestDeliverer;
|
|
299
|
+
const digestCliToken = opts.cliToken ?? readCliToken(configDir) ?? null;
|
|
300
300
|
|
|
301
301
|
// Triage history delta (non-fatal — wrapped in try/catch)
|
|
302
302
|
let delta = null;
|
|
@@ -321,7 +321,7 @@ export async function run(args, envOrOpts = process.env, fetcher = globalThis.fe
|
|
|
321
321
|
},
|
|
322
322
|
tickets: sorted,
|
|
323
323
|
delta,
|
|
324
|
-
});
|
|
324
|
+
}, { cliToken: digestCliToken });
|
|
325
325
|
return;
|
|
326
326
|
}
|
|
327
327
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readCliToken } from './cli-auth.mjs';
|
|
2
2
|
import { formatCollisions } from './collision-reporter.mjs';
|
|
3
3
|
import { DEFAULT_CONFIG_DIR } from './config.mjs';
|
|
4
|
-
|
|
5
4
|
import { apiBase, warnIfInsecure } from './api-utils.mjs';
|
|
5
|
+
import { red, yellow, bold, cyan } from './ansi.mjs';
|
|
6
6
|
|
|
7
7
|
const COLLISIONS_PATH = '/v1/triage/collisions';
|
|
8
8
|
|
|
@@ -29,7 +29,7 @@ export async function runCollisions(args = [], opts = {}) {
|
|
|
29
29
|
|
|
30
30
|
const cliToken = readCliTokenFn(configDir) ?? null;
|
|
31
31
|
if (!cliToken) {
|
|
32
|
-
print('✗ collisions requires Console access. Run ticketlens login first.\n
|
|
32
|
+
print(` ${red('✗')} ${bold('collisions')} requires Console access. Run ${cyan('ticketlens login')} first.\n`);
|
|
33
33
|
return { ok: false };
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -41,14 +41,14 @@ export async function runCollisions(args = [], opts = {}) {
|
|
|
41
41
|
|
|
42
42
|
if (!res.ok) {
|
|
43
43
|
if (res.status === 401) {
|
|
44
|
-
print('✗ Session expired. Run ticketlens login to reconnect.\n
|
|
44
|
+
print(` ${red('✗')} Session expired. Run ${cyan('ticketlens login')} to reconnect.\n`);
|
|
45
45
|
return { ok: false, status: 401 };
|
|
46
46
|
}
|
|
47
47
|
if (res.status === 403) {
|
|
48
|
-
print('✗ collisions requires a Team license\n
|
|
48
|
+
print(` ${red('✗')} ${bold('collisions')} requires a Team license\n`);
|
|
49
49
|
return { ok: false, status: 403 };
|
|
50
50
|
}
|
|
51
|
-
print(
|
|
51
|
+
print(` ${yellow('⚠')} Failed to fetch collisions (${res.status})\n`);
|
|
52
52
|
return { ok: false, status: res.status };
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -67,7 +67,7 @@ export async function runCollisions(args = [], opts = {}) {
|
|
|
67
67
|
|
|
68
68
|
return { ok: true };
|
|
69
69
|
} catch {
|
|
70
|
-
print('⚠ Failed to fetch collisions (network error)\n
|
|
70
|
+
print(` ${yellow('⚠')} Failed to fetch collisions (network error)\n`);
|
|
71
71
|
return { ok: false };
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -2,8 +2,10 @@ import { writeFileSync, mkdirSync, unlinkSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { homedir, platform as osPlatform } from 'node:os';
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { red, green, yellow, cyan } from './ansi.mjs';
|
|
6
|
+
import { apiBase, warnIfInsecure } from './api-utils.mjs';
|
|
5
7
|
|
|
6
|
-
const
|
|
8
|
+
const SCHEDULE_PATH = '/v1/schedule';
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Build a macOS LaunchAgent plist string.
|
|
@@ -58,9 +60,11 @@ export async function runScheduleWizard({
|
|
|
58
60
|
writeLocalJob = defaultWriteLocalJob,
|
|
59
61
|
timeoutMs = 10_000,
|
|
60
62
|
print = s => process.stdout.write(s),
|
|
63
|
+
warn = s => process.stderr.write(s),
|
|
61
64
|
}) {
|
|
65
|
+
warnIfInsecure(apiBase(), warn);
|
|
62
66
|
if (!cliToken) {
|
|
63
|
-
print('✗ schedule requires Console access. Run ticketlens login first.\n
|
|
67
|
+
print(` ${red('✗')} schedule requires Console access. Run ${cyan('ticketlens login')} first.\n`);
|
|
64
68
|
return { ok: false };
|
|
65
69
|
}
|
|
66
70
|
const { time, email, timezone } = answers;
|
|
@@ -68,32 +72,44 @@ export async function runScheduleWizard({
|
|
|
68
72
|
const hour = parseInt(hourStr, 10);
|
|
69
73
|
const minute = parseInt(minuteStr, 10);
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
try {
|
|
76
|
+
const res = await fetcher(`${apiBase()}${SCHEDULE_PATH}`, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
79
|
+
headers: {
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
'Authorization': `Bearer ${cliToken}`,
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({ email, timezone, deliverAt: time }),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (!res.ok) {
|
|
87
|
+
if (res.status === 401) {
|
|
88
|
+
print(` ${red('✗')} Session expired. Run ${cyan('ticketlens login')} to reconnect.\n`);
|
|
89
|
+
return { ok: false, status: 401 };
|
|
90
|
+
}
|
|
91
|
+
if (res.status === 403) {
|
|
92
|
+
print(` ${red('✗')} schedule requires a Pro license\n`);
|
|
93
|
+
return { ok: false, status: 403 };
|
|
94
|
+
}
|
|
95
|
+
print(` ${yellow('⚠')} Schedule API error (${res.status}) — try again later\n`);
|
|
96
|
+
return { ok: false, status: res.status };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const data = await res.json();
|
|
100
|
+
|
|
101
|
+
const ticketlensBin = resolveTicketlensBin();
|
|
102
|
+
const content = platform === 'darwin'
|
|
103
|
+
? buildPlist({ hour, minute, ticketlensBin })
|
|
104
|
+
: buildCronLine({ hour, minute, ticketlensBin });
|
|
105
|
+
|
|
106
|
+
writeLocalJob(content, platform);
|
|
107
|
+
|
|
108
|
+
return data;
|
|
109
|
+
} catch {
|
|
110
|
+
print(` ${yellow('⚠')} Schedule API error (network error) — try again later\n`);
|
|
111
|
+
return { ok: false };
|
|
85
112
|
}
|
|
86
|
-
|
|
87
|
-
const data = await res.json();
|
|
88
|
-
|
|
89
|
-
const ticketlensBin = resolveTicketlensBin();
|
|
90
|
-
const content = platform === 'darwin'
|
|
91
|
-
? buildPlist({ hour, minute, ticketlensBin })
|
|
92
|
-
: buildCronLine({ hour, minute, ticketlensBin });
|
|
93
|
-
|
|
94
|
-
writeLocalJob(content, platform);
|
|
95
|
-
|
|
96
|
-
return data;
|
|
97
113
|
}
|
|
98
114
|
|
|
99
115
|
export async function runScheduleStop({
|
|
@@ -101,17 +117,28 @@ export async function runScheduleStop({
|
|
|
101
117
|
cliToken,
|
|
102
118
|
platform = osPlatform(),
|
|
103
119
|
print = s => process.stdout.write(s),
|
|
120
|
+
warn = s => process.stderr.write(s),
|
|
104
121
|
}) {
|
|
122
|
+
warnIfInsecure(apiBase(), warn);
|
|
105
123
|
if (!cliToken) {
|
|
106
|
-
print('✗ schedule requires Console access. Run ticketlens login first.\n
|
|
124
|
+
print(` ${red('✗')} schedule requires Console access. Run ${cyan('ticketlens login')} first.\n`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
let res;
|
|
128
|
+
try {
|
|
129
|
+
res = await fetcher(`${apiBase()}${SCHEDULE_PATH}`, {
|
|
130
|
+
method: 'DELETE',
|
|
131
|
+
signal: AbortSignal.timeout(10_000),
|
|
132
|
+
headers: { 'Authorization': `Bearer ${cliToken}` },
|
|
133
|
+
});
|
|
134
|
+
} catch {
|
|
135
|
+
print(` ${yellow('⚠')} Schedule API error (network error) — try again later\n`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (!res.ok) {
|
|
139
|
+
print(` ${yellow('⚠')} Schedule API error (${res.status}) — try again later\n`);
|
|
107
140
|
return;
|
|
108
141
|
}
|
|
109
|
-
const res = await fetcher(SCHEDULE_URL, {
|
|
110
|
-
method: 'DELETE',
|
|
111
|
-
signal: AbortSignal.timeout(10_000),
|
|
112
|
-
headers: { 'Authorization': `Bearer ${cliToken}` },
|
|
113
|
-
});
|
|
114
|
-
if (!res.ok) throw new Error(`Schedule API error ${res.status}`);
|
|
115
142
|
|
|
116
143
|
if (platform === 'darwin') {
|
|
117
144
|
const plistPath = join(homedir(), 'Library', 'LaunchAgents', 'io.ticketlens.digest.plist');
|
|
@@ -125,31 +152,39 @@ export async function runScheduleStop({
|
|
|
125
152
|
spawnSync('crontab', [tmp], { encoding: 'utf8' });
|
|
126
153
|
}
|
|
127
154
|
|
|
128
|
-
|
|
155
|
+
print(` ${green('✔')} Digest schedule removed.\n`);
|
|
129
156
|
}
|
|
130
157
|
|
|
131
158
|
export async function runScheduleStatus({
|
|
132
159
|
fetcher = globalThis.fetch,
|
|
133
160
|
cliToken,
|
|
134
161
|
print = s => process.stdout.write(s),
|
|
162
|
+
warn = s => process.stderr.write(s),
|
|
135
163
|
}) {
|
|
164
|
+
warnIfInsecure(apiBase(), warn);
|
|
136
165
|
if (!cliToken) {
|
|
137
|
-
print('✗ schedule requires Console access. Run ticketlens login first.\n
|
|
166
|
+
print(` ${red('✗')} schedule requires Console access. Run ${cyan('ticketlens login')} first.\n`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
let res;
|
|
170
|
+
try {
|
|
171
|
+
res = await fetcher(`${apiBase()}${SCHEDULE_PATH}`, {
|
|
172
|
+
method: 'GET',
|
|
173
|
+
signal: AbortSignal.timeout(10_000),
|
|
174
|
+
headers: { 'Authorization': `Bearer ${cliToken}` },
|
|
175
|
+
});
|
|
176
|
+
} catch {
|
|
177
|
+
print(` ${yellow('⚠')} Schedule API error (network error) — try again later\n`);
|
|
138
178
|
return;
|
|
139
179
|
}
|
|
140
|
-
const res = await fetcher(SCHEDULE_URL, {
|
|
141
|
-
method: 'GET',
|
|
142
|
-
signal: AbortSignal.timeout(10_000),
|
|
143
|
-
headers: { 'Authorization': `Bearer ${cliToken}` },
|
|
144
|
-
});
|
|
145
180
|
if (!res.ok) {
|
|
146
|
-
|
|
181
|
+
print(' No active digest schedule found.\n');
|
|
147
182
|
return;
|
|
148
183
|
}
|
|
149
184
|
const data = await res.json();
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
185
|
+
print(` ${green('✔')} Digest schedule: ${cyan(data.deliverAt)} ${data.timezone}\n`);
|
|
186
|
+
print(` Last delivered: ${data.lastDeliveredAt ?? 'never'}\n`);
|
|
187
|
+
print(` Next delivery: ${cyan(data.nextDelivery)}\n`);
|
|
153
188
|
}
|
|
154
189
|
|
|
155
190
|
function resolveTicketlensBin() {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { apiBase } from './api-utils.mjs';
|
|
2
|
+
|
|
1
3
|
const ANTHROPIC_URL = 'https://api.anthropic.com/v1/messages';
|
|
2
4
|
const OPENAI_URL = 'https://api.openai.com/v1/chat/completions';
|
|
3
5
|
const GROQ_URL = 'https://api.groq.com/openai/v1/chat/completions';
|
|
4
|
-
const CLOUD_URL = 'https://api.ticketlens.dev/v1/summarize';
|
|
5
6
|
const DEFAULT_PROMPT = 'Summarize this Jira ticket in 3 sentences. Focus on what matters most for implementation. Be concrete.\n\n';
|
|
6
7
|
const DEFAULT_MAX_TOKENS = 256;
|
|
7
8
|
|
|
@@ -134,7 +135,7 @@ async function callGroq({ brief, apiKey, fetcher, timeoutMs, prompt, maxTokens }
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
async function cloud({ brief, licenseKey, fetcher, timeoutMs }) {
|
|
137
|
-
const res = await fetcher(
|
|
138
|
+
const res = await fetcher(`${apiBase()}/v1/summarize`, {
|
|
138
139
|
method: 'POST',
|
|
139
140
|
signal: AbortSignal.timeout(timeoutMs),
|
|
140
141
|
headers: {
|
|
@@ -8,6 +8,7 @@ import { isLicensed } from './license.mjs';
|
|
|
8
8
|
import { DEFAULT_CONFIG_DIR } from './config.mjs';
|
|
9
9
|
import { apiBase, warnIfInsecure } from './api-utils.mjs';
|
|
10
10
|
import { buildTicketPayload } from './ticket-payload.mjs';
|
|
11
|
+
import { red, green, yellow, bold, cyan } from './ansi.mjs';
|
|
11
12
|
|
|
12
13
|
const PUSH_PATH = '/v1/triage/push';
|
|
13
14
|
|
|
@@ -54,7 +55,7 @@ export async function pushTriageSnapshot({
|
|
|
54
55
|
} = {}) {
|
|
55
56
|
warnIfInsecure(apiBase(), warn);
|
|
56
57
|
if (!cliToken) {
|
|
57
|
-
print('✗ --push requires Console access. Run ticketlens login first.\n
|
|
58
|
+
print(` ${red('✗')} ${bold('--push')} requires Console access. Run ${cyan('ticketlens login')} first.\n`);
|
|
58
59
|
return { ok: false };
|
|
59
60
|
}
|
|
60
61
|
|
|
@@ -100,24 +101,24 @@ export async function pushTriageSnapshot({
|
|
|
100
101
|
});
|
|
101
102
|
|
|
102
103
|
if (res.ok) {
|
|
103
|
-
print(
|
|
104
|
+
print(` ${green('✓')} Queue updated — view at ${queueUrl(apiBase())}\n`);
|
|
104
105
|
return { ok: true, status: res.status };
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
if (res.status === 401) {
|
|
108
|
-
print('✗ Session expired. Run ticketlens login to reconnect.\n
|
|
109
|
+
print(` ${red('✗')} Session expired. Run ${cyan('ticketlens login')} to reconnect.\n`);
|
|
109
110
|
return { ok: false, status: res.status };
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
if (res.status === 403) {
|
|
113
|
-
print('✗ --push requires a Team license\n
|
|
114
|
+
print(` ${red('✗')} ${bold('--push')} requires a Team license\n`);
|
|
114
115
|
return { ok: false, status: res.status };
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
print(
|
|
118
|
+
print(` ${yellow('⚠')} Push failed (${res.status}) — triage output unaffected\n`);
|
|
118
119
|
return { ok: false, status: res.status };
|
|
119
120
|
} catch {
|
|
120
|
-
print('⚠ Push failed (network error) — triage output unaffected\n
|
|
121
|
+
print(` ${yellow('⚠')} Push failed (network error) — triage output unaffected\n`);
|
|
121
122
|
return { ok: false };
|
|
122
123
|
}
|
|
123
124
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { apiBase, warnIfInsecure } from './api-utils.mjs';
|
|
7
|
+
import { red, green, yellow, bold, cyan } from './ansi.mjs';
|
|
7
8
|
|
|
8
9
|
const SHARE_PATH = '/v1/triage/share';
|
|
9
10
|
|
|
@@ -48,7 +49,7 @@ export async function shareTriageSnapshot({
|
|
|
48
49
|
} = {}) {
|
|
49
50
|
warnIfInsecure(apiBase(), warn);
|
|
50
51
|
if (!cliToken) {
|
|
51
|
-
print('✗ --share requires Console access. Run ticketlens login first.\n
|
|
52
|
+
print(` ${red('✗')} ${bold('--share')} requires Console access. Run ${cyan('ticketlens login')} first.\n`);
|
|
52
53
|
return { ok: false };
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -71,24 +72,24 @@ export async function shareTriageSnapshot({
|
|
|
71
72
|
|
|
72
73
|
if (res.ok) {
|
|
73
74
|
const data = await res.json();
|
|
74
|
-
print(
|
|
75
|
+
print(` ${green('✓')} Share link (expires in 24h):\n ${data.url}\n`);
|
|
75
76
|
return { ok: true, status: res.status };
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
if (res.status === 401) {
|
|
79
|
-
print('✗ Session expired. Run ticketlens login to reconnect.\n
|
|
80
|
+
print(` ${red('✗')} Session expired. Run ${cyan('ticketlens login')} to reconnect.\n`);
|
|
80
81
|
return { ok: false, status: res.status };
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
if (res.status === 403) {
|
|
84
|
-
print('✗ --share requires a Team license\n
|
|
85
|
+
print(` ${red('✗')} ${bold('--share')} requires a Team license\n`);
|
|
85
86
|
return { ok: false, status: res.status };
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
print(
|
|
89
|
+
print(` ${yellow('⚠')} Share failed (${res.status}) — triage output unaffected\n`);
|
|
89
90
|
return { ok: false, status: res.status };
|
|
90
91
|
} catch {
|
|
91
|
-
print('⚠ Share failed (network error) — triage output unaffected\n
|
|
92
|
+
print(` ${yellow('⚠')} Share failed (network error) — triage output unaffected\n`);
|
|
92
93
|
return { ok: false };
|
|
93
94
|
}
|
|
94
95
|
}
|