ticketlens 0.1.0
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/LICENSE +21 -0
- package/README.md +457 -0
- package/bin/ticketlens.mjs +376 -0
- package/package.json +37 -0
- package/skills/jtb/scripts/fetch-my-tickets.mjs +377 -0
- package/skills/jtb/scripts/fetch-ticket.mjs +682 -0
- package/skills/jtb/scripts/lib/adapters/github-adapter.mjs +112 -0
- package/skills/jtb/scripts/lib/adapters/jira-adapter.mjs +19 -0
- package/skills/jtb/scripts/lib/adf-converter.mjs +67 -0
- package/skills/jtb/scripts/lib/ansi.mjs +87 -0
- package/skills/jtb/scripts/lib/arg-validator.mjs +178 -0
- package/skills/jtb/scripts/lib/attachment-downloader.mjs +123 -0
- package/skills/jtb/scripts/lib/attention-scorer.mjs +152 -0
- package/skills/jtb/scripts/lib/banner.mjs +201 -0
- package/skills/jtb/scripts/lib/brief-assembler.mjs +137 -0
- package/skills/jtb/scripts/lib/brief-cache.mjs +137 -0
- package/skills/jtb/scripts/lib/budget-pruner.mjs +242 -0
- package/skills/jtb/scripts/lib/cache-manager.mjs +499 -0
- package/skills/jtb/scripts/lib/cli-auth.mjs +40 -0
- package/skills/jtb/scripts/lib/cli.mjs +87 -0
- package/skills/jtb/scripts/lib/code-ref-parser.mjs +113 -0
- package/skills/jtb/scripts/lib/commit-linker.mjs +42 -0
- package/skills/jtb/scripts/lib/compliance-checker.mjs +92 -0
- package/skills/jtb/scripts/lib/config-wizard.mjs +392 -0
- package/skills/jtb/scripts/lib/config.mjs +63 -0
- package/skills/jtb/scripts/lib/diff-analyzer.mjs +66 -0
- package/skills/jtb/scripts/lib/drift-tracker.mjs +120 -0
- package/skills/jtb/scripts/lib/error-classifier.mjs +119 -0
- package/skills/jtb/scripts/lib/help.mjs +253 -0
- package/skills/jtb/scripts/lib/hook-installer.mjs +81 -0
- package/skills/jtb/scripts/lib/init-wizard.mjs +508 -0
- package/skills/jtb/scripts/lib/interactive-list.mjs +257 -0
- package/skills/jtb/scripts/lib/jira-client.mjs +169 -0
- package/skills/jtb/scripts/lib/ledger.mjs +96 -0
- package/skills/jtb/scripts/lib/license.mjs +195 -0
- package/skills/jtb/scripts/lib/pr-assembler.mjs +186 -0
- package/skills/jtb/scripts/lib/profile-picker.mjs +216 -0
- package/skills/jtb/scripts/lib/profile-resolver.mjs +236 -0
- package/skills/jtb/scripts/lib/profile-switcher.mjs +147 -0
- package/skills/jtb/scripts/lib/prompt-helpers.mjs +122 -0
- package/skills/jtb/scripts/lib/requirement-extractor.mjs +52 -0
- package/skills/jtb/scripts/lib/resolve-adapter.mjs +28 -0
- package/skills/jtb/scripts/lib/schedule-wizard.mjs +153 -0
- package/skills/jtb/scripts/lib/select-prompt.mjs +106 -0
- package/skills/jtb/scripts/lib/spinner.mjs +44 -0
- package/skills/jtb/scripts/lib/styled-assembler.mjs +183 -0
- package/skills/jtb/scripts/lib/summarizer.mjs +109 -0
- package/skills/jtb/scripts/lib/sync.mjs +119 -0
- package/skills/jtb/scripts/lib/table-formatter.mjs +48 -0
- package/skills/jtb/scripts/lib/triage-exporter.mjs +93 -0
- package/skills/jtb/scripts/lib/triage-history.mjs +166 -0
- package/skills/jtb/scripts/lib/triage-push.mjs +98 -0
- package/skills/jtb/scripts/lib/usage-tracker.mjs +54 -0
- package/skills/jtb/scripts/lib/vcs-detector.mjs +12 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TicketLens CLI — Developer toolkit for Jira ticket context.
|
|
5
|
+
* Usage:
|
|
6
|
+
* ticketlens TICKET-KEY [--depth=N] [--profile=NAME]
|
|
7
|
+
* ticketlens triage [--stale=N] [--status=X,Y] [--profile=NAME]
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
import { parseCommand } from '../skills/jtb/scripts/lib/cli.mjs';
|
|
12
|
+
import { run as runFetch } from '../skills/jtb/scripts/fetch-ticket.mjs';
|
|
13
|
+
import { run as runTriage } from '../skills/jtb/scripts/fetch-my-tickets.mjs';
|
|
14
|
+
import { run as runInit } from '../skills/jtb/scripts/lib/init-wizard.mjs';
|
|
15
|
+
import { runSwitch } from '../skills/jtb/scripts/lib/profile-switcher.mjs';
|
|
16
|
+
import { run as runConfig } from '../skills/jtb/scripts/lib/config-wizard.mjs';
|
|
17
|
+
import { activateLicense, checkLicense, revalidateIfStale, isLicensed, showUpgradePrompt, readLicense } from '../skills/jtb/scripts/lib/license.mjs';
|
|
18
|
+
import { deleteProfile, loadProfiles } from '../skills/jtb/scripts/lib/profile-resolver.mjs';
|
|
19
|
+
import { run as runCache } from '../skills/jtb/scripts/lib/cache-manager.mjs';
|
|
20
|
+
import { printHelp, printProfiles } from '../skills/jtb/scripts/lib/help.mjs';
|
|
21
|
+
import { createStyler } from '../skills/jtb/scripts/lib/ansi.mjs';
|
|
22
|
+
import { readCliToken, saveCliToken, deleteCliToken } from '../skills/jtb/scripts/lib/cli-auth.mjs';
|
|
23
|
+
import { syncProfiles, getApiBase } from '../skills/jtb/scripts/lib/sync.mjs';
|
|
24
|
+
import { promptSecret, promptText } from '../skills/jtb/scripts/lib/prompt-helpers.mjs';
|
|
25
|
+
|
|
26
|
+
const args = process.argv.slice(2);
|
|
27
|
+
const { command, args: cmdArgs } = parseCommand(args);
|
|
28
|
+
|
|
29
|
+
// Fire-and-forget: silently refresh license.json at startup if >7 days since last validation
|
|
30
|
+
revalidateIfStale();
|
|
31
|
+
|
|
32
|
+
switch (command) {
|
|
33
|
+
case 'fetch':
|
|
34
|
+
runFetch(cmdArgs).catch(err => {
|
|
35
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
36
|
+
process.exitCode = 1;
|
|
37
|
+
});
|
|
38
|
+
break;
|
|
39
|
+
|
|
40
|
+
case 'triage':
|
|
41
|
+
runTriage(cmdArgs).catch(err => {
|
|
42
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
});
|
|
45
|
+
break;
|
|
46
|
+
|
|
47
|
+
case 'init':
|
|
48
|
+
runInit().catch(err => {
|
|
49
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
});
|
|
52
|
+
break;
|
|
53
|
+
|
|
54
|
+
case 'switch':
|
|
55
|
+
runSwitch().catch(err => {
|
|
56
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
});
|
|
59
|
+
break;
|
|
60
|
+
|
|
61
|
+
case 'config': {
|
|
62
|
+
const profileArg = cmdArgs.find(a => a.startsWith('--profile='));
|
|
63
|
+
const profileName = profileArg ? profileArg.split('=')[1] : undefined;
|
|
64
|
+
runConfig({ profileName }).catch(err => {
|
|
65
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
});
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
case 'activate': {
|
|
72
|
+
const s = createStyler({ isTTY: process.stdout.isTTY });
|
|
73
|
+
const key = cmdArgs.find(a => !a.startsWith('--'));
|
|
74
|
+
if (!key) {
|
|
75
|
+
process.stderr.write(`${s.red('✖')} Missing license key.\n ${s.dim('Usage:')} ticketlens activate ${s.dim('<LICENSE-KEY>')}\n`);
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
activateLicense(key).then(result => {
|
|
80
|
+
if (result.success) {
|
|
81
|
+
process.stdout.write(`\n ${s.green('✔')} License activated\n\n`);
|
|
82
|
+
process.stdout.write(` ${s.dim('Tier:')} ${s.bold(s.cyan(result.tier))}\n`);
|
|
83
|
+
process.stdout.write(` ${s.dim('Email:')} ${result.email}\n\n`);
|
|
84
|
+
} else {
|
|
85
|
+
process.stderr.write(`\n ${s.red('✖')} Activation failed: ${result.error}\n\n`);
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
case 'license': {
|
|
93
|
+
const s = createStyler({ isTTY: process.stdout.isTTY });
|
|
94
|
+
const status = checkLicense();
|
|
95
|
+
const daysSinceVal = status.validatedAt
|
|
96
|
+
? (Date.now() - new Date(status.validatedAt)) / 86400000
|
|
97
|
+
: Infinity;
|
|
98
|
+
// Grace period: treat as inactive if not revalidated within 30 days
|
|
99
|
+
const graceExpired = daysSinceVal > 30;
|
|
100
|
+
process.stdout.write('\n');
|
|
101
|
+
if (status.active && !graceExpired) {
|
|
102
|
+
process.stdout.write(` ${s.green('●')} ${s.bold('License active')}\n\n`);
|
|
103
|
+
process.stdout.write(` ${s.dim('Tier:')} ${s.bold(s.cyan(status.tier))}\n`);
|
|
104
|
+
process.stdout.write(` ${s.dim('Email:')} ${status.email}\n`);
|
|
105
|
+
if (status.validatedAt) {
|
|
106
|
+
const date = status.validatedAt.split('T')[0];
|
|
107
|
+
process.stdout.write(` ${s.dim('Validated:')} ${date}\n`);
|
|
108
|
+
if (daysSinceVal > 7) {
|
|
109
|
+
const days = Math.floor(daysSinceVal);
|
|
110
|
+
process.stdout.write(` ${s.yellow('⚠')} ${s.dim(`Revalidation pending — last checked ${days} day${days === 1 ? '' : 's'} ago`)}\n`);
|
|
111
|
+
process.stdout.write(` ${s.dim('Run:')} ticketlens activate ${s.dim('<KEY>')} ${s.dim('to refresh')}\n`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} else if (graceExpired) {
|
|
115
|
+
process.stdout.write(` ${s.red('●')} ${s.bold('License inactive')}\n\n`);
|
|
116
|
+
process.stdout.write(` ${s.dim('Tier:')} ${s.bold(status.tier)}\n`);
|
|
117
|
+
process.stdout.write(` ${s.dim('Email:')} ${status.email}\n\n`);
|
|
118
|
+
process.stdout.write(` ${s.dim('Not revalidated in 30+ days. Run:')} ticketlens activate ${s.dim('<KEY>')}\n`);
|
|
119
|
+
} else if (status.expired) {
|
|
120
|
+
process.stdout.write(` ${s.yellow('●')} ${s.bold('License expired')}\n\n`);
|
|
121
|
+
process.stdout.write(` ${s.dim('Tier:')} ${s.bold(status.tier)}\n`);
|
|
122
|
+
process.stdout.write(` ${s.dim('Email:')} ${status.email}\n\n`);
|
|
123
|
+
process.stdout.write(` ${s.dim('Renew:')} ticketlens activate ${s.dim('<LICENSE-KEY>')}\n`);
|
|
124
|
+
} else {
|
|
125
|
+
process.stdout.write(` ${s.dim('●')} ${s.bold('Free tier')}\n\n`);
|
|
126
|
+
process.stdout.write(` ${s.dim('Unlock Pro features with a license key:')}\n`);
|
|
127
|
+
process.stdout.write(` ${s.cyan('ticketlens activate')} ${s.dim('<LICENSE-KEY>')}\n`);
|
|
128
|
+
}
|
|
129
|
+
process.stdout.write('\n');
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
case 'delete': {
|
|
134
|
+
const s = createStyler({ isTTY: process.stderr.isTTY });
|
|
135
|
+
const profileName = cmdArgs.find(a => !a.startsWith('--'));
|
|
136
|
+
if (!profileName) {
|
|
137
|
+
process.stderr.write(`${s.red('✖')} Missing profile name.\n ${s.dim('Usage:')} ticketlens delete ${s.dim('<PROFILE-NAME>')}\n`);
|
|
138
|
+
process.exitCode = 1;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
const profiles = loadProfiles();
|
|
142
|
+
if (!profiles?.profiles[profileName]) {
|
|
143
|
+
process.stderr.write(`${s.red('✖')} Profile "${profileName}" not found.\n`);
|
|
144
|
+
const names = Object.keys(profiles?.profiles || {});
|
|
145
|
+
if (names.length > 0) process.stderr.write(` ${s.dim('Profiles:')} ${names.join(', ')}\n`);
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
const forceYes = cmdArgs.includes('--yes') || cmdArgs.includes('-y');
|
|
150
|
+
// Confirmation prompt on TTY
|
|
151
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
152
|
+
process.stderr.write(` Delete profile ${s.cyan(s.bold(profileName))}? This cannot be undone. ${s.dim('y/N')} `);
|
|
153
|
+
const answer = await new Promise(res => {
|
|
154
|
+
process.stdin.setRawMode(true);
|
|
155
|
+
process.stdin.resume();
|
|
156
|
+
process.stdin.setEncoding('utf8');
|
|
157
|
+
process.stdin.once('data', char => {
|
|
158
|
+
process.stdin.setRawMode(false);
|
|
159
|
+
process.stdin.pause();
|
|
160
|
+
process.stderr.write('\n');
|
|
161
|
+
if (char === '\x03') process.exit(0);
|
|
162
|
+
res(char === 'y' || char === 'Y');
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
if (!answer) {
|
|
166
|
+
process.stderr.write(` ${s.dim('Cancelled.')}\n`);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
} else if (!forceYes) {
|
|
170
|
+
process.stderr.write(`${s.red('✖')} Non-interactive mode: pass ${s.cyan('--yes')} to confirm deletion without a prompt.\n`);
|
|
171
|
+
process.exitCode = 1;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
const result = deleteProfile(profileName);
|
|
175
|
+
if (result.deleted) {
|
|
176
|
+
process.stdout.write(` ${s.green('✔')} Profile ${s.bold(profileName)} deleted.\n`);
|
|
177
|
+
} else {
|
|
178
|
+
process.stderr.write(`${s.red('✖')} Could not delete profile "${profileName}".\n`);
|
|
179
|
+
process.exitCode = 1;
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
case 'profiles': {
|
|
185
|
+
const plain = cmdArgs.includes('--plain');
|
|
186
|
+
const config = loadProfiles();
|
|
187
|
+
printProfiles({ config, plain });
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
case 'cache':
|
|
192
|
+
runCache(cmdArgs).catch(err => {
|
|
193
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
194
|
+
process.exitCode = 1;
|
|
195
|
+
});
|
|
196
|
+
break;
|
|
197
|
+
|
|
198
|
+
case 'version': {
|
|
199
|
+
const require = createRequire(import.meta.url);
|
|
200
|
+
const pkg = require('../package.json');
|
|
201
|
+
process.stdout.write(`ticketlens v${pkg.version}\n`);
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
case 'schedule': {
|
|
206
|
+
const subCmd = cmdArgs[0];
|
|
207
|
+
|
|
208
|
+
if (subCmd === '--stop') {
|
|
209
|
+
const { runScheduleStop } = await import('../skills/jtb/scripts/lib/schedule-wizard.mjs');
|
|
210
|
+
await runScheduleStop({ licenseKey: readLicense()?.key });
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (subCmd === '--status') {
|
|
215
|
+
const { runScheduleStatus } = await import('../skills/jtb/scripts/lib/schedule-wizard.mjs');
|
|
216
|
+
await runScheduleStatus({ licenseKey: readLicense()?.key });
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!isLicensed('pro')) {
|
|
221
|
+
showUpgradePrompt('pro', 'ticketlens schedule');
|
|
222
|
+
process.exitCode = 1;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const { runScheduleWizard } = await import('../skills/jtb/scripts/lib/schedule-wizard.mjs');
|
|
227
|
+
const { promptScheduleAnswers } = await import('../skills/jtb/scripts/lib/prompt-helpers.mjs');
|
|
228
|
+
const answers = await promptScheduleAnswers(cmdArgs);
|
|
229
|
+
const result = await runScheduleWizard({ answers, licenseKey: readLicense()?.key });
|
|
230
|
+
|
|
231
|
+
process.stdout.write(`✔ Digest scheduled for ${answers.time} ${answers.timezone}\n`);
|
|
232
|
+
process.stdout.write(` Next delivery: ${result.nextDelivery}\n`);
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
case 'install-hooks':
|
|
237
|
+
runFetch(['install-hooks', ...cmdArgs]).catch(err => {
|
|
238
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
239
|
+
process.exitCode = 1;
|
|
240
|
+
});
|
|
241
|
+
break;
|
|
242
|
+
|
|
243
|
+
case 'pr':
|
|
244
|
+
runFetch(['pr', ...cmdArgs]).catch(err => {
|
|
245
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
246
|
+
process.exitCode = 1;
|
|
247
|
+
});
|
|
248
|
+
break;
|
|
249
|
+
|
|
250
|
+
case 'ledger':
|
|
251
|
+
runFetch(['ledger', ...cmdArgs]).catch(err => {
|
|
252
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
253
|
+
process.exitCode = 1;
|
|
254
|
+
});
|
|
255
|
+
break;
|
|
256
|
+
|
|
257
|
+
case 'compliance':
|
|
258
|
+
runFetch(['compliance', ...cmdArgs]).catch(err => {
|
|
259
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
260
|
+
process.exitCode = 1;
|
|
261
|
+
});
|
|
262
|
+
break;
|
|
263
|
+
|
|
264
|
+
case 'login': {
|
|
265
|
+
(async () => {
|
|
266
|
+
const s = createStyler({ isTTY: process.stderr.isTTY });
|
|
267
|
+
process.stderr.write(`\n ${s.bold('TicketLens Login')}\n`);
|
|
268
|
+
process.stderr.write(` ${s.dim('─'.repeat(44))}\n`);
|
|
269
|
+
process.stderr.write(` ${s.dim(`Generate a CLI token at ${s.cyan(`${getApiBase()}/console/account`)}`)}\n`);
|
|
270
|
+
process.stderr.write(` ${s.dim('then paste it below.')}\n\n`);
|
|
271
|
+
|
|
272
|
+
const token = await promptSecret(`CLI Token ${s.dim('(tl_…)')}:`, { stream: process.stderr });
|
|
273
|
+
if (!token.startsWith('tl_')) {
|
|
274
|
+
process.stderr.write(` ${s.red('✖')} Token must start with ${s.dim('tl_')}\n`);
|
|
275
|
+
process.exitCode = 1;
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Validate against the API before saving
|
|
280
|
+
process.stderr.write(`\n ${s.dim('○ Verifying token…')}\n`);
|
|
281
|
+
let res;
|
|
282
|
+
try {
|
|
283
|
+
res = await fetch(`${getApiBase()}/v1/profiles`, {
|
|
284
|
+
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
|
|
285
|
+
signal: AbortSignal.timeout(15000),
|
|
286
|
+
});
|
|
287
|
+
} catch (err) {
|
|
288
|
+
process.stderr.write(`\x1b[A\r\x1b[2K ${s.red('✖')} Could not reach ${getApiBase()} — check your connection.\n`);
|
|
289
|
+
process.exitCode = 1;
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (res.status === 401) {
|
|
294
|
+
process.stderr.write(`\x1b[A\r\x1b[2K ${s.red('✖')} Invalid token — check the value and try again.\n`);
|
|
295
|
+
process.exitCode = 1;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (!res.ok) {
|
|
299
|
+
process.stderr.write(`\x1b[A\r\x1b[2K ${s.red('✖')} Server returned ${res.status}. Try again later.\n`);
|
|
300
|
+
process.exitCode = 1;
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
saveCliToken(token);
|
|
305
|
+
process.stderr.write(`\x1b[A\r\x1b[2K ${s.green('✔')} Logged in.\n`);
|
|
306
|
+
process.stderr.write(`\n Run ${s.cyan('ticketlens sync')} to pull your connections.\n\n`);
|
|
307
|
+
})().catch(err => {
|
|
308
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
309
|
+
process.exitCode = 1;
|
|
310
|
+
});
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
case 'logout': {
|
|
315
|
+
const s = createStyler({ isTTY: process.stderr.isTTY });
|
|
316
|
+
deleteCliToken();
|
|
317
|
+
process.stderr.write(` ${s.green('✔')} CLI token removed.\n`);
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
case 'sync': {
|
|
322
|
+
(async () => {
|
|
323
|
+
const s = createStyler({ isTTY: process.stderr.isTTY });
|
|
324
|
+
process.stderr.write(`\n ${s.dim('Syncing from TicketLens console…')}\n`);
|
|
325
|
+
|
|
326
|
+
const result = await syncProfiles();
|
|
327
|
+
|
|
328
|
+
if (result.error === 'no-token') {
|
|
329
|
+
process.stderr.write(`\x1b[A\r\x1b[2K ${s.red('✖')} Not logged in. Run ${s.cyan('ticketlens login')} first.\n\n`);
|
|
330
|
+
process.exitCode = 1;
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (result.error === 'unauthorized') {
|
|
334
|
+
process.stderr.write(`\x1b[A\r\x1b[2K ${s.red('✖')} Token expired or revoked. Run ${s.cyan('ticketlens login')} to re-authenticate.\n\n`);
|
|
335
|
+
process.exitCode = 1;
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (result.error) {
|
|
339
|
+
process.stderr.write(`\x1b[A\r\x1b[2K ${s.red('✖')} Sync failed: ${result.error}\n\n`);
|
|
340
|
+
process.exitCode = 1;
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const { added, updated, unchanged, needsCredentials } = result;
|
|
345
|
+
const total = added.length + updated.length + unchanged.length;
|
|
346
|
+
|
|
347
|
+
process.stderr.write(`\x1b[A\r\x1b[2K ${s.green('✔')} Sync complete`);
|
|
348
|
+
if (total === 0) {
|
|
349
|
+
process.stderr.write(` — no profiles on console yet.\n`);
|
|
350
|
+
} else {
|
|
351
|
+
process.stderr.write(`\n`);
|
|
352
|
+
if (added.length) process.stderr.write(` ${s.dim('+')} ${added.length} added: ${added.map(n => s.cyan(n)).join(', ')}\n`);
|
|
353
|
+
if (updated.length) process.stderr.write(` ${s.dim('↑')} ${updated.length} updated: ${updated.map(n => s.cyan(n)).join(', ')}\n`);
|
|
354
|
+
if (unchanged.length) process.stderr.write(` ${s.dim('○')} ${unchanged.length} unchanged\n`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (needsCredentials.length > 0) {
|
|
358
|
+
process.stderr.write(`\n ${s.yellow('!')} These profiles need credentials before they can be used:\n`);
|
|
359
|
+
for (const name of needsCredentials) {
|
|
360
|
+
process.stderr.write(` ${s.dim('○')} ${s.cyan(name)} — run: ${s.bold(`ticketlens config --profile=${name}`)}\n`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
process.stderr.write('\n');
|
|
365
|
+
})().catch(err => {
|
|
366
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
367
|
+
process.exitCode = 1;
|
|
368
|
+
});
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
case 'help':
|
|
373
|
+
default:
|
|
374
|
+
printHelp();
|
|
375
|
+
break;
|
|
376
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ticketlens",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Jira CLI for developers — fetch ticket context, triage your queue, and stop tab-switching. Zero dependencies, all local.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ticketlens": "bin/ticketlens.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"skills/jtb/scripts/lib/",
|
|
12
|
+
"skills/jtb/scripts/fetch-ticket.mjs",
|
|
13
|
+
"skills/jtb/scripts/fetch-my-tickets.mjs"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test skills/jtb/scripts/test/*.test.mjs"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"jira",
|
|
20
|
+
"cli",
|
|
21
|
+
"developer-tools",
|
|
22
|
+
"productivity",
|
|
23
|
+
"ticket",
|
|
24
|
+
"triage",
|
|
25
|
+
"context"
|
|
26
|
+
],
|
|
27
|
+
"author": "Ralph Moran",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/ralphmoran/ticket-lens.git"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/ralphmoran/ticket-lens",
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|