ticketlens 0.4.6 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ticketlens",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "Jira CLI for developers — fetch ticket context, triage your queue, and stop tab-switching. Zero dependencies, all local.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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, readLicense } from './lib/license.mjs';
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 { readLicense } = await import('./lib/license.mjs');
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 ${licenseKey}`,
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
 
@@ -2,9 +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, cyan } from './ansi.mjs';
5
+ import { red, green, yellow, cyan } from './ansi.mjs';
6
+ import { apiBase, warnIfInsecure } from './api-utils.mjs';
6
7
 
7
- const SCHEDULE_URL = 'https://api.ticketlens.dev/v1/schedule';
8
+ const SCHEDULE_PATH = '/v1/schedule';
8
9
 
9
10
  /**
10
11
  * Build a macOS LaunchAgent plist string.
@@ -59,7 +60,9 @@ export async function runScheduleWizard({
59
60
  writeLocalJob = defaultWriteLocalJob,
60
61
  timeoutMs = 10_000,
61
62
  print = s => process.stdout.write(s),
63
+ warn = s => process.stderr.write(s),
62
64
  }) {
65
+ warnIfInsecure(apiBase(), warn);
63
66
  if (!cliToken) {
64
67
  print(` ${red('✗')} schedule requires Console access. Run ${cyan('ticketlens login')} first.\n`);
65
68
  return { ok: false };
@@ -69,32 +72,44 @@ export async function runScheduleWizard({
69
72
  const hour = parseInt(hourStr, 10);
70
73
  const minute = parseInt(minuteStr, 10);
71
74
 
72
- const res = await fetcher(SCHEDULE_URL, {
73
- method: 'POST',
74
- signal: AbortSignal.timeout(timeoutMs),
75
- headers: {
76
- 'Content-Type': 'application/json',
77
- 'Authorization': `Bearer ${cliToken}`,
78
- },
79
- body: JSON.stringify({ email, timezone, deliverAt: time }),
80
- });
81
-
82
- if (!res.ok) {
83
- const err = new Error(`Schedule API error ${res.status}`);
84
- err.status = res.status;
85
- throw err;
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 };
86
112
  }
87
-
88
- const data = await res.json();
89
-
90
- const ticketlensBin = resolveTicketlensBin();
91
- const content = platform === 'darwin'
92
- ? buildPlist({ hour, minute, ticketlensBin })
93
- : buildCronLine({ hour, minute, ticketlensBin });
94
-
95
- writeLocalJob(content, platform);
96
-
97
- return data;
98
113
  }
99
114
 
100
115
  export async function runScheduleStop({
@@ -102,17 +117,28 @@ export async function runScheduleStop({
102
117
  cliToken,
103
118
  platform = osPlatform(),
104
119
  print = s => process.stdout.write(s),
120
+ warn = s => process.stderr.write(s),
105
121
  }) {
122
+ warnIfInsecure(apiBase(), warn);
106
123
  if (!cliToken) {
107
124
  print(` ${red('✗')} schedule requires Console access. Run ${cyan('ticketlens login')} first.\n`);
108
125
  return;
109
126
  }
110
- const res = await fetcher(SCHEDULE_URL, {
111
- method: 'DELETE',
112
- signal: AbortSignal.timeout(10_000),
113
- headers: { 'Authorization': `Bearer ${cliToken}` },
114
- });
115
- if (!res.ok) throw new Error(`Schedule API error ${res.status}`);
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`);
140
+ return;
141
+ }
116
142
 
117
143
  if (platform === 'darwin') {
118
144
  const plistPath = join(homedir(), 'Library', 'LaunchAgents', 'io.ticketlens.digest.plist');
@@ -133,18 +159,26 @@ export async function runScheduleStatus({
133
159
  fetcher = globalThis.fetch,
134
160
  cliToken,
135
161
  print = s => process.stdout.write(s),
162
+ warn = s => process.stderr.write(s),
136
163
  }) {
164
+ warnIfInsecure(apiBase(), warn);
137
165
  if (!cliToken) {
138
166
  print(` ${red('✗')} schedule requires Console access. Run ${cyan('ticketlens login')} first.\n`);
139
167
  return;
140
168
  }
141
- const res = await fetcher(SCHEDULE_URL, {
142
- method: 'GET',
143
- signal: AbortSignal.timeout(10_000),
144
- headers: { 'Authorization': `Bearer ${cliToken}` },
145
- });
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`);
178
+ return;
179
+ }
146
180
  if (!res.ok) {
147
- process.stdout.write('No active digest schedule found.\n');
181
+ print(' No active digest schedule found.\n');
148
182
  return;
149
183
  }
150
184
  const data = await res.json();
@@ -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(CLOUD_URL, {
138
+ const res = await fetcher(`${apiBase()}/v1/summarize`, {
138
139
  method: 'POST',
139
140
  signal: AbortSignal.timeout(timeoutMs),
140
141
  headers: {