ticketlens 0.10.0 → 0.10.2

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.10.0",
3
+ "version": "0.10.2",
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": {
@@ -1,4 +1,4 @@
1
- <!-- jtb-skill-version: 0.10.0 -->
1
+ <!-- jtb-skill-version: 0.10.2 -->
2
2
  ---
3
3
  name: jtb
4
4
  description: Fetch a Jira ticket's full context (description, comments, linked issues, code references) and assemble a structured TicketBrief for implementation planning. Use when user types /jtb, mentions a Jira ticket key, or wants to plan work from a Jira ticket.
@@ -108,6 +108,16 @@ export async function run(args, envOrOpts = process.env, fetcher = globalThis.fe
108
108
  const licensedFn = opts.isLicensed ?? isLicensed;
109
109
  const upgradeFn = opts.showUpgradePrompt ?? showUpgradePrompt;
110
110
 
111
+ // Fail-fast: --push requires a CLI token — check before the triage scan fires
112
+ if (pushFlag) {
113
+ const pushToken = opts.cliToken ?? readCliToken(configDir) ?? null;
114
+ if (!pushToken) {
115
+ process.stderr.write('Error: --push requires authentication. Run `ticketlens auth login` to connect your account.\n');
116
+ process.exitCode = 1;
117
+ return;
118
+ }
119
+ }
120
+
111
121
  // --save=FILE: Pro gate + validate path is not a directory
112
122
  if (saveArg) {
113
123
  if (!licensedFn('pro', configDir)) {
@@ -987,6 +987,15 @@ export async function run(args, envOrOpts = process.env, fetcher = globalThis.fe
987
987
  const licensedFn = opts.isLicensed ?? isLicensed;
988
988
  const upgradeFn = opts.showUpgradePrompt ?? showUpgradePrompt;
989
989
 
990
+ // Fail-fast: check Pro gate before any network call for Pro-only flags
991
+ if (args.includes('--summarize') || args.includes('--handoff')) {
992
+ if (!licensedFn('pro', configDir)) {
993
+ upgradeFn('pro', args.includes('--handoff') ? '--handoff' : '--summarize');
994
+ process.exitCode = 1;
995
+ return;
996
+ }
997
+ }
998
+
990
999
  const noCache = args.includes('--no-cache');
991
1000
 
992
1001
  // Resolve brief cache TTL: configurable for Pro tier only, else fixed 4h default
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  export const DEFAULT_API_BASE = 'https://api.ticketlens.app';
7
+ export const DEFAULT_SITE_BASE = 'https://ticketlens.dev';
7
8
 
8
9
  // Matches localhost, 127.0.0.1, and any hostname ending in .test or .local,
9
10
  // with an optional port — all treated as local-only addresses.
@@ -13,6 +14,10 @@ export function apiBase() {
13
14
  return process.env?.TICKETLENS_API_URL ?? DEFAULT_API_BASE;
14
15
  }
15
16
 
17
+ export function siteBase() {
18
+ return process.env?.TICKETLENS_SITE_URL ?? DEFAULT_SITE_BASE;
19
+ }
20
+
16
21
  export function warnIfInsecure(url, warn) {
17
22
  if (url.startsWith('http://') && !LOCAL_HOST_RE.test(url)) {
18
23
  warn('⚠ Warning: TICKETLENS_API_URL uses an unencrypted HTTP connection. Use https:// for production.\n');
@@ -19,6 +19,7 @@ import { DEFAULT_BRIEF_TTL } from './brief-cache.mjs';
19
19
  import { DEFAULT_CONFIG_DIR } from './config.mjs';
20
20
  import { visLen, SERVER_AUTH_TYPES, promptText, promptSecret, promptYN } from './prompt-helpers.mjs';
21
21
  import { isLicensed } from './license.mjs';
22
+ import { siteBase } from './api-utils.mjs';
22
23
 
23
24
  const RETRY_OPTIONS = [
24
25
  { label: 'Retry', sublabel: 'Try again — same credentials (e.g. VPN just connected)', value: 'retry' },
@@ -384,7 +385,7 @@ export async function run({ configDir = DEFAULT_CONFIG_DIR, profileName } = {})
384
385
  );
385
386
  cacheTtl = ttlInput || curTtl;
386
387
  } else {
387
- stream.write(` ${s.dim('○')} Brief cache TTL: ${s.dim('4h')} ${s.dim('·')} ${s.cyan('Pro')} ${s.dim('unlocks configurable TTL → ticketlens.dev/pricing')}\n`);
388
+ stream.write(` ${s.dim('○')} Brief cache TTL: ${s.dim('4h')} ${s.dim('·')} ${s.cyan('Pro')} ${s.dim(`unlocks configurable TTL → ${siteBase()}/#pricing`)}\n`);
388
389
  }
389
390
 
390
391
  // ── Save ───────────────────────────────────────────────────────────────────
@@ -4,6 +4,7 @@ import os from 'node:os';
4
4
  import crypto from 'node:crypto';
5
5
  import { DEFAULT_CONFIG_DIR } from './config.mjs';
6
6
  import { createStyler } from './ansi.mjs';
7
+ import { siteBase } from './api-utils.mjs';
7
8
 
8
9
  // Mixed into the HMAC key so that knowing the license key alone is not sufficient
9
10
  // to forge a valid signature — an attacker also needs this constant from the source.
@@ -14,7 +15,7 @@ const LICENSE_SECRET_FILE = 'license-hmac-secret.json';
14
15
  const REVALIDATION_DAYS = 7; // attempt background revalidation after this many days
15
16
  const GRACE_DAYS = 30; // treat license as invalid if not revalidated within this window
16
17
  const MS_PER_DAY = 86400000;
17
- const UPGRADE_URL = 'https://ticketlens.dev/pricing';
18
+ const upgradeUrl = () => `${siteBase()}/#pricing`;
18
19
 
19
20
  const ANSI_RE_LIC = /\x1b\[[0-9;]*m|\x1b\]8;[^\x07]*\x07/g;
20
21
  const visLen = (s) => s.replace(ANSI_RE_LIC, '').length;
@@ -138,7 +139,8 @@ export function showUpgradePrompt(requiredTier, featureFlag, { stream = process.
138
139
  const bc = (t) => s.dim(t);
139
140
 
140
141
  const featureLine = padInner(` ${s.yellow('◆')} ${s.bold(featureFlag)} requires ${s.bold(s.cyan(tier))}`, W);
141
- const upgradeLine = padInner(` ${s.dim('Upgrade:')} ${s.link(UPGRADE_URL, s.cyan(UPGRADE_URL))}`, W);
142
+ const url = upgradeUrl();
143
+ const upgradeLine = padInner(` ${s.dim('Upgrade:')} ${s.link(url, s.cyan(url))}`, W);
142
144
  const activateLine = padInner(` ${s.dim('Or run:')} ${s.dim('ticketlens activate <KEY>')}`, W);
143
145
  const blank = ' '.repeat(W);
144
146