ticketlens 0.21.0 → 0.21.1
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
|
@@ -25,13 +25,37 @@ export function readCliToken(configDir = DEFAULT_CONFIG_DIR) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
// Server-asserted tier (personal or team-seat) synced alongside the token on
|
|
29
|
+
// every login/sync — separate from the signed, offline license.json file.
|
|
30
|
+
// Lets team-invited members get Pro-equivalent CLI access without a personal
|
|
31
|
+
// TL- key, mirroring what the manager already pays for.
|
|
32
|
+
export function readCliTokenTier(configDir = DEFAULT_CONFIG_DIR) {
|
|
33
|
+
const p = cliTokenPath(configDir);
|
|
34
|
+
if (!existsSync(p)) return null;
|
|
35
|
+
try {
|
|
36
|
+
const data = JSON.parse(readFileSync(p, 'utf8'));
|
|
37
|
+
return typeof data.tier === 'string' ? data.tier : null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function saveCliToken(token, configDir = DEFAULT_CONFIG_DIR, tier = undefined) {
|
|
29
44
|
mkdirSync(configDir, { recursive: true });
|
|
30
45
|
const p = cliTokenPath(configDir);
|
|
31
|
-
|
|
46
|
+
const payload = tier === undefined ? { token } : { token, tier };
|
|
47
|
+
writeFileSync(p, JSON.stringify(payload, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 });
|
|
32
48
|
chmodSync(p, 0o600);
|
|
33
49
|
}
|
|
34
50
|
|
|
51
|
+
// Updates just the cached tier without touching the token — used by `tl sync`
|
|
52
|
+
// to keep entitlement fresh between logins.
|
|
53
|
+
export function saveCliTokenTier(tier, configDir = DEFAULT_CONFIG_DIR) {
|
|
54
|
+
const token = readCliToken(configDir);
|
|
55
|
+
if (!token) return;
|
|
56
|
+
saveCliToken(token, configDir, tier);
|
|
57
|
+
}
|
|
58
|
+
|
|
35
59
|
export function deleteCliToken(configDir = DEFAULT_CONFIG_DIR) {
|
|
36
60
|
const p = cliTokenPath(configDir);
|
|
37
61
|
if (existsSync(p)) {
|
|
@@ -5,6 +5,7 @@ import crypto from 'node:crypto';
|
|
|
5
5
|
import { DEFAULT_CONFIG_DIR } from './config.mjs';
|
|
6
6
|
import { createStyler } from './ansi.mjs';
|
|
7
7
|
import { siteBase } from './api-utils.mjs';
|
|
8
|
+
import { readCliTokenTier } from './cli-auth.mjs';
|
|
8
9
|
|
|
9
10
|
// Mixed into the HMAC key so that knowing the license key alone is not sufficient
|
|
10
11
|
// to forge a valid signature — an attacker also needs this constant from the source.
|
|
@@ -100,6 +101,15 @@ export function isLicensed(tier, configDir = DEFAULT_CONFIG_DIR) {
|
|
|
100
101
|
if (required === 0) return true;
|
|
101
102
|
if (process.env.TICKETLENS_SKIP_LICENSE === 'true') return true;
|
|
102
103
|
|
|
104
|
+
if (checkOwnLicense(required, configDir)) return true;
|
|
105
|
+
|
|
106
|
+
// Team-seat entitlement, server-asserted and synced via login/`tl sync` —
|
|
107
|
+
// covers invited team members who never activated a personal TL- key.
|
|
108
|
+
const syncedTier = LICENSE_TIERS[readCliTokenTier(configDir)] ?? 0;
|
|
109
|
+
return syncedTier >= required;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function checkOwnLicense(required, configDir) {
|
|
103
113
|
const license = readLicense(configDir);
|
|
104
114
|
if (!license) return false;
|
|
105
115
|
|
|
@@ -83,7 +83,12 @@ export async function runLogin({
|
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
let tier;
|
|
87
|
+
try {
|
|
88
|
+
tier = (await res.json())?.tier;
|
|
89
|
+
} catch { /* tier is best-effort — token is still valid */ }
|
|
90
|
+
|
|
91
|
+
saveCliTokenFn(token, undefined, tier);
|
|
87
92
|
stream.write(`\x1b[A\r\x1b[2K ${s.green('✔')} Logged in.\n`);
|
|
88
93
|
|
|
89
94
|
// Flow 1: pull team Jira config for Pro/Team members (silently skipped for Free)
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Server wins on all non-credential fields (shape, prefixes, URLs, etc.).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { readCliToken } from './cli-auth.mjs';
|
|
8
|
+
import { readCliToken, saveCliTokenTier } from './cli-auth.mjs';
|
|
9
9
|
import {
|
|
10
10
|
loadProfiles,
|
|
11
11
|
loadCredentials,
|
|
@@ -88,6 +88,8 @@ export async function syncProfiles({
|
|
|
88
88
|
let json;
|
|
89
89
|
try { json = await res.json(); } catch { return { error: 'invalid-json' }; }
|
|
90
90
|
|
|
91
|
+
if (typeof json?.tier === 'string') saveCliTokenTier(json.tier, configDir);
|
|
92
|
+
|
|
91
93
|
const remoteProfiles = Array.isArray(json?.profiles) ? json.profiles : [];
|
|
92
94
|
const localConfig = loadProfiles(configDir) || { profiles: {} };
|
|
93
95
|
const localCreds = loadCredentials(configDir);
|