@levr-one/auth 1.0.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.
Files changed (70) hide show
  1. package/README.md +37 -0
  2. package/dist/config-watcher.d.ts +65 -0
  3. package/dist/config-watcher.d.ts.map +1 -0
  4. package/dist/config-watcher.js +140 -0
  5. package/dist/config-watcher.js.map +1 -0
  6. package/dist/config-watcher.test.d.ts +2 -0
  7. package/dist/config-watcher.test.d.ts.map +1 -0
  8. package/dist/config-watcher.test.js +116 -0
  9. package/dist/config-watcher.test.js.map +1 -0
  10. package/dist/config.d.ts +58 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +234 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/config.test.d.ts +2 -0
  15. package/dist/config.test.d.ts.map +1 -0
  16. package/dist/config.test.js +221 -0
  17. package/dist/config.test.js.map +1 -0
  18. package/dist/file-lock.d.ts +41 -0
  19. package/dist/file-lock.d.ts.map +1 -0
  20. package/dist/file-lock.js +110 -0
  21. package/dist/file-lock.js.map +1 -0
  22. package/dist/index.d.ts +43 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +43 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/oauth-client.d.ts +156 -0
  27. package/dist/oauth-client.d.ts.map +1 -0
  28. package/dist/oauth-client.js +572 -0
  29. package/dist/oauth-client.js.map +1 -0
  30. package/dist/oauth-client.test.d.ts +2 -0
  31. package/dist/oauth-client.test.d.ts.map +1 -0
  32. package/dist/oauth-client.test.js +290 -0
  33. package/dist/oauth-client.test.js.map +1 -0
  34. package/dist/test-utils.d.ts +10 -0
  35. package/dist/test-utils.d.ts.map +1 -0
  36. package/dist/test-utils.js +45 -0
  37. package/dist/test-utils.js.map +1 -0
  38. package/dist/token-store.d.ts +78 -0
  39. package/dist/token-store.d.ts.map +1 -0
  40. package/dist/token-store.js +299 -0
  41. package/dist/token-store.js.map +1 -0
  42. package/dist/token-store.test.d.ts +2 -0
  43. package/dist/token-store.test.d.ts.map +1 -0
  44. package/dist/token-store.test.js +239 -0
  45. package/dist/token-store.test.js.map +1 -0
  46. package/dist/token-watcher.d.ts +82 -0
  47. package/dist/token-watcher.d.ts.map +1 -0
  48. package/dist/token-watcher.js +185 -0
  49. package/dist/token-watcher.js.map +1 -0
  50. package/dist/types.d.ts +70 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +5 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/workspace-store.d.ts +60 -0
  55. package/dist/workspace-store.d.ts.map +1 -0
  56. package/dist/workspace-store.js +147 -0
  57. package/dist/workspace-store.js.map +1 -0
  58. package/dist/workspace-store.test.d.ts +2 -0
  59. package/dist/workspace-store.test.d.ts.map +1 -0
  60. package/dist/workspace-store.test.js +169 -0
  61. package/dist/workspace-store.test.js.map +1 -0
  62. package/dist/workspaces.d.ts +60 -0
  63. package/dist/workspaces.d.ts.map +1 -0
  64. package/dist/workspaces.js +80 -0
  65. package/dist/workspaces.js.map +1 -0
  66. package/dist/workspaces.test.d.ts +2 -0
  67. package/dist/workspaces.test.d.ts.map +1 -0
  68. package/dist/workspaces.test.js +111 -0
  69. package/dist/workspaces.test.js.map +1 -0
  70. package/package.json +58 -0
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Secure token storage for OAuth tokens
3
+ *
4
+ * Stores tokens in ~/.tq/oauth-tokens.json as a map keyed by backend URL:
5
+ * {
6
+ * "https://api.levr.now": { accessToken, refreshToken, expiresAt, apiBaseUrl },
7
+ * "http://localhost:8080": { ... }
8
+ * }
9
+ *
10
+ * Backward-compatible: if the file contains the legacy single-token format
11
+ * (flat object with accessToken at the top level), it is migrated in-place
12
+ * to the new map format on first read.
13
+ */
14
+ import * as fs from 'fs';
15
+ import * as path from 'path';
16
+ import * as os from 'os';
17
+ /**
18
+ * Token storage directory and file path
19
+ */
20
+ const TQ_DIR = path.join(os.homedir(), '.tq');
21
+ const TOKEN_FILE = path.join(TQ_DIR, 'oauth-tokens.json');
22
+ /**
23
+ * Map of environment names to their backend URLs.
24
+ */
25
+ // internal D15: unified levr.* origins. Legacy testquality.* URLs are
26
+ // retired during the DEC-11 7-day window; tokens minted under the
27
+ // legacy URLs are migrated by the legacy-key fallback in readTokenMap()
28
+ // (the apiBaseUrl on the StoredTokens itself was set when the token
29
+ // was originally issued, so old token files keep working transparently).
30
+ const ENV_URLS = {
31
+ local: 'http://localhost:8080',
32
+ staging: 'https://api.levr.now',
33
+ production: 'https://api.levr.one',
34
+ };
35
+ /**
36
+ * Resolve an environment name from a backend URL.
37
+ * Returns undefined if the URL doesn't match any known preset.
38
+ */
39
+ export function resolveEnvFromUrl(url) {
40
+ const normalized = url.replace(/\/+$/, '');
41
+ for (const [env, envUrl] of Object.entries(ENV_URLS)) {
42
+ if (envUrl === normalized)
43
+ return env;
44
+ }
45
+ return undefined;
46
+ }
47
+ /**
48
+ * Resolve a backend URL from an environment name.
49
+ */
50
+ export function resolveUrlFromEnv(env) {
51
+ return ENV_URLS[env];
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // Internal: read/write the token map
55
+ // ---------------------------------------------------------------------------
56
+ /**
57
+ * Read the raw token map from disk.
58
+ * Handles migration from legacy single-token format.
59
+ */
60
+ function readTokenMap() {
61
+ try {
62
+ if (!fs.existsSync(TOKEN_FILE))
63
+ return {};
64
+ const content = fs.readFileSync(TOKEN_FILE, 'utf-8');
65
+ const parsed = JSON.parse(content);
66
+ // Detect legacy single-token format (has accessToken at top level)
67
+ if ('accessToken' in parsed && 'refreshToken' in parsed) {
68
+ const legacy = parsed;
69
+ const key = legacy.apiBaseUrl ?? 'https://api.levr.now';
70
+ const map = { [key]: legacy };
71
+ // Migrate in place
72
+ writeTokenMap(map);
73
+ return map;
74
+ }
75
+ return parsed;
76
+ }
77
+ catch (error) {
78
+ console.error('[tq-oauth] Failed to load stored tokens:', error);
79
+ return {};
80
+ }
81
+ }
82
+ /**
83
+ * Write the full token map to disk with atomic write.
84
+ */
85
+ function writeTokenMap(map) {
86
+ if (!fs.existsSync(TQ_DIR)) {
87
+ fs.mkdirSync(TQ_DIR, { recursive: true, mode: 0o700 });
88
+ }
89
+ const tempPath = TOKEN_FILE + '.tmp';
90
+ fs.writeFileSync(tempPath, JSON.stringify(map, null, 2), {
91
+ mode: 0o600,
92
+ });
93
+ fs.renameSync(tempPath, TOKEN_FILE);
94
+ }
95
+ function isValidToken(t) {
96
+ const obj = t;
97
+ return !!(obj?.accessToken && obj?.refreshToken && obj?.expiresAt);
98
+ }
99
+ // ---------------------------------------------------------------------------
100
+ // Public API — drop-in compatible with the old single-token interface
101
+ // ---------------------------------------------------------------------------
102
+ /**
103
+ * Load the token for the current environment.
104
+ * Reads the config to determine which backend URL is active,
105
+ * then returns the matching entry from the token map.
106
+ *
107
+ * Falls back to first token in the map ONLY when no config exists
108
+ * (backward compat for first-time migration). When the user has
109
+ * explicitly set an environment, only that environment's token is returned.
110
+ */
111
+ export function loadTokens() {
112
+ const map = readTokenMap();
113
+ const keys = Object.keys(map);
114
+ if (keys.length === 0)
115
+ return null;
116
+ // Try to match the current environment
117
+ const currentUrl = getCurrentApiUrl();
118
+ if (currentUrl) {
119
+ const entry = map[currentUrl];
120
+ if (entry && isValidToken(entry))
121
+ return entry;
122
+ // Config is set but no token for this env — don't fall back
123
+ return null;
124
+ }
125
+ // No config at all — fall back to first valid entry (backward compat)
126
+ for (const key of keys) {
127
+ if (isValidToken(map[key]))
128
+ return map[key];
129
+ }
130
+ return null;
131
+ }
132
+ /**
133
+ * Load tokens for a specific environment by name.
134
+ */
135
+ export function loadTokensForEnv(env) {
136
+ const map = readTokenMap();
137
+ const url = ENV_URLS[env];
138
+ const entry = map[url];
139
+ if (entry && isValidToken(entry))
140
+ return entry;
141
+ return null;
142
+ }
143
+ /**
144
+ * Load tokens for an arbitrary backend URL (custom URL not in ENV_URLS).
145
+ *
146
+ * Used by the qinetic sync setup command, which needs to talk to a remote
147
+ * backend that may not be one of the named presets — the operator passes
148
+ * `--target <url>` and the CLI fetches the matching token entry by URL.
149
+ */
150
+ export function loadTokensForUrl(url) {
151
+ const map = readTokenMap();
152
+ const normalized = url.replace(/\/$/, '');
153
+ // Try exact match first, then trailing-slash variant.
154
+ const entry = map[url] ?? map[normalized] ?? map[`${normalized}/`];
155
+ if (entry && isValidToken(entry))
156
+ return entry;
157
+ return null;
158
+ }
159
+ /**
160
+ * Save tokens for the current environment.
161
+ * Upserts the entry keyed by the token's apiBaseUrl.
162
+ */
163
+ export function saveTokens(tokens) {
164
+ try {
165
+ const map = readTokenMap();
166
+ const key = tokens.apiBaseUrl ?? getCurrentApiUrl() ?? ENV_URLS.staging;
167
+ map[key] = tokens;
168
+ writeTokenMap(map);
169
+ }
170
+ catch (error) {
171
+ console.error('[tq-oauth] Failed to save tokens:', error);
172
+ throw error;
173
+ }
174
+ }
175
+ /**
176
+ * Save tokens for a specific environment by name.
177
+ */
178
+ export function saveTokensForEnv(env, tokens) {
179
+ const map = readTokenMap();
180
+ map[ENV_URLS[env]] = tokens;
181
+ writeTokenMap(map);
182
+ }
183
+ /**
184
+ * Clear stored tokens for the current environment.
185
+ * Moves the entire file to .bak before removing the entry.
186
+ */
187
+ export function clearTokens() {
188
+ try {
189
+ if (!fs.existsSync(TOKEN_FILE))
190
+ return;
191
+ // Backup entire file before modifying
192
+ const backupPath = TOKEN_FILE + '.bak';
193
+ try {
194
+ fs.copyFileSync(TOKEN_FILE, backupPath);
195
+ fs.chmodSync(backupPath, 0o600);
196
+ }
197
+ catch {
198
+ // Backup failed — still proceed with clear
199
+ }
200
+ const map = readTokenMap();
201
+ const currentUrl = getCurrentApiUrl();
202
+ if (currentUrl && map[currentUrl]) {
203
+ delete map[currentUrl];
204
+ if (Object.keys(map).length === 0) {
205
+ fs.unlinkSync(TOKEN_FILE);
206
+ }
207
+ else {
208
+ writeTokenMap(map);
209
+ }
210
+ }
211
+ else if (!currentUrl) {
212
+ // No current URL known — clear entire file (legacy behavior)
213
+ fs.unlinkSync(TOKEN_FILE);
214
+ }
215
+ }
216
+ catch (error) {
217
+ console.error('[tq-oauth] Failed to clear tokens:', error);
218
+ }
219
+ }
220
+ /**
221
+ * Attempt to restore tokens from backup (.bak file)
222
+ * Returns true if backup was restored successfully.
223
+ */
224
+ export function restoreTokensFromBackup() {
225
+ const backupPath = TOKEN_FILE + '.bak';
226
+ try {
227
+ if (fs.existsSync(backupPath) && !fs.existsSync(TOKEN_FILE)) {
228
+ const content = fs.readFileSync(backupPath, 'utf-8');
229
+ const parsed = JSON.parse(content);
230
+ // Handle both legacy and map format in backup
231
+ if ('accessToken' in parsed && parsed.refreshToken) {
232
+ const legacy = parsed;
233
+ const key = legacy.apiBaseUrl ?? ENV_URLS.staging;
234
+ writeTokenMap({ [key]: legacy });
235
+ console.error('[tq-oauth] Restored tokens from backup — refresh token recovered');
236
+ return true;
237
+ }
238
+ // Map format — write as-is if it has any entry with a refresh token
239
+ const map = parsed;
240
+ const hasRefresh = Object.values(map).some((t) => t.refreshToken);
241
+ if (hasRefresh) {
242
+ writeTokenMap(map);
243
+ console.error('[tq-oauth] Restored tokens from backup — refresh token recovered');
244
+ return true;
245
+ }
246
+ }
247
+ }
248
+ catch (error) {
249
+ console.error('[tq-oauth] Failed to restore tokens from backup:', error);
250
+ }
251
+ return false;
252
+ }
253
+ /**
254
+ * Check if token file exists and has any entries
255
+ */
256
+ export function hasStoredTokens() {
257
+ if (!fs.existsSync(TOKEN_FILE))
258
+ return false;
259
+ const map = readTokenMap();
260
+ return Object.keys(map).length > 0;
261
+ }
262
+ /**
263
+ * Get token file path (for debugging and watching)
264
+ */
265
+ export function getTokenFilePath() {
266
+ return TOKEN_FILE;
267
+ }
268
+ /**
269
+ * Get TQ directory path
270
+ */
271
+ export function getTqDir() {
272
+ return TQ_DIR;
273
+ }
274
+ // ---------------------------------------------------------------------------
275
+ // Helpers
276
+ // ---------------------------------------------------------------------------
277
+ /**
278
+ * Read the current API URL from ~/.tq/config.json without importing
279
+ * config.ts (avoids circular dependency since config.ts imports us).
280
+ */
281
+ function getCurrentApiUrl() {
282
+ try {
283
+ // Env var takes precedence
284
+ if (process.env.TQ_BACKEND_URL) {
285
+ return process.env.TQ_BACKEND_URL.replace(/\/+$/, '');
286
+ }
287
+ const configPath = path.join(TQ_DIR, 'config.json');
288
+ if (fs.existsSync(configPath)) {
289
+ const raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
290
+ if (raw.apiUrl)
291
+ return raw.apiUrl.replace(/\/+$/, '');
292
+ }
293
+ }
294
+ catch {
295
+ // Fall through
296
+ }
297
+ return undefined;
298
+ }
299
+ //# sourceMappingURL=token-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.js","sourceRoot":"","sources":["../src/token-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=token-store.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.test.d.ts","sourceRoot":"","sources":["../src/token-store.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,239 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+ // Mutable ref so the mock can read the current tmpDir
6
+ const state = vi.hoisted(() => ({ tmpDir: '' }));
7
+ // Mock os.homedir() so token-store's module-level TQ_DIR uses our temp dir
8
+ vi.mock('os', async (importOriginal) => {
9
+ const original = await importOriginal();
10
+ return { ...original, homedir: () => state.tmpDir };
11
+ });
12
+ // Re-import the module each test so module-level constants re-evaluate
13
+ let loadTokens;
14
+ let loadTokensForEnv;
15
+ let saveTokens;
16
+ let saveTokensForEnv;
17
+ let clearTokens;
18
+ let hasStoredTokens;
19
+ let getTqDir;
20
+ let getTokenFilePath;
21
+ let resolveEnvFromUrl;
22
+ let resolveUrlFromEnv;
23
+ const stagingTokens = {
24
+ accessToken: 'staging-access',
25
+ refreshToken: 'staging-refresh',
26
+ expiresAt: Date.now() + 3600000,
27
+ apiBaseUrl: 'https://api.levr.now',
28
+ };
29
+ const localTokens = {
30
+ accessToken: 'local-access',
31
+ refreshToken: 'local-refresh',
32
+ expiresAt: Date.now() + 3600000,
33
+ apiBaseUrl: 'http://localhost:8080',
34
+ };
35
+ beforeEach(async () => {
36
+ state.tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tq-oauth-test-'));
37
+ // Reset module cache so TQ_DIR re-evaluates with the new homedir mock
38
+ vi.resetModules();
39
+ const mod = await import('./token-store.js');
40
+ loadTokens = mod.loadTokens;
41
+ loadTokensForEnv = mod.loadTokensForEnv;
42
+ saveTokens = mod.saveTokens;
43
+ saveTokensForEnv = mod.saveTokensForEnv;
44
+ clearTokens = mod.clearTokens;
45
+ hasStoredTokens = mod.hasStoredTokens;
46
+ getTqDir = mod.getTqDir;
47
+ getTokenFilePath = mod.getTokenFilePath;
48
+ resolveEnvFromUrl = mod.resolveEnvFromUrl;
49
+ resolveUrlFromEnv = mod.resolveUrlFromEnv;
50
+ });
51
+ afterEach(() => {
52
+ delete process.env.TQ_BACKEND_URL;
53
+ fs.rmSync(state.tmpDir, { recursive: true, force: true });
54
+ });
55
+ describe('getTqDir', () => {
56
+ it('returns ~/.tq path based on homedir', () => {
57
+ expect(getTqDir()).toBe(path.join(state.tmpDir, '.tq'));
58
+ });
59
+ });
60
+ describe('getTokenFilePath', () => {
61
+ it('returns path to oauth-tokens.json', () => {
62
+ expect(getTokenFilePath()).toBe(path.join(state.tmpDir, '.tq', 'oauth-tokens.json'));
63
+ });
64
+ });
65
+ describe('resolveEnvFromUrl', () => {
66
+ it('resolves local URL', () => {
67
+ expect(resolveEnvFromUrl('http://localhost:8080')).toBe('local');
68
+ });
69
+ it('resolves staging URL', () => {
70
+ expect(resolveEnvFromUrl('https://api.levr.now')).toBe('staging');
71
+ });
72
+ it('resolves production URL', () => {
73
+ expect(resolveEnvFromUrl('https://api.levr.one')).toBe('production');
74
+ });
75
+ it('strips trailing slashes', () => {
76
+ expect(resolveEnvFromUrl('http://localhost:8080/')).toBe('local');
77
+ });
78
+ it('returns undefined for unknown URLs', () => {
79
+ expect(resolveEnvFromUrl('https://custom.example.com')).toBeUndefined();
80
+ });
81
+ });
82
+ describe('resolveUrlFromEnv', () => {
83
+ it('returns correct URL for each env', () => {
84
+ expect(resolveUrlFromEnv('local')).toBe('http://localhost:8080');
85
+ expect(resolveUrlFromEnv('staging')).toBe('https://api.levr.now');
86
+ expect(resolveUrlFromEnv('production')).toBe('https://api.levr.one');
87
+ });
88
+ });
89
+ describe('saveTokens + loadTokens roundtrip', () => {
90
+ it('saves and loads tokens correctly', () => {
91
+ saveTokens(stagingTokens);
92
+ // loadTokens reads the entry matching the current env;
93
+ // without config.json it falls back to first entry
94
+ const loaded = loadTokens();
95
+ expect(loaded).toEqual(stagingTokens);
96
+ });
97
+ it('creates ~/.tq directory if missing', () => {
98
+ expect(fs.existsSync(path.join(state.tmpDir, '.tq'))).toBe(false);
99
+ saveTokens(stagingTokens);
100
+ expect(fs.existsSync(path.join(state.tmpDir, '.tq'))).toBe(true);
101
+ });
102
+ it('overwrites existing tokens for the same env', () => {
103
+ saveTokens(stagingTokens);
104
+ const updated = {
105
+ ...stagingTokens,
106
+ accessToken: 'new-token',
107
+ };
108
+ saveTokens(updated);
109
+ const loaded = loadTokensForEnv('staging');
110
+ expect(loaded?.accessToken).toBe('new-token');
111
+ });
112
+ });
113
+ describe('token map: multiple environments', () => {
114
+ it('stores tokens for multiple environments in one file', () => {
115
+ saveTokens(stagingTokens);
116
+ saveTokens(localTokens);
117
+ expect(loadTokensForEnv('staging')?.accessToken).toBe('staging-access');
118
+ expect(loadTokensForEnv('local')?.accessToken).toBe('local-access');
119
+ expect(loadTokensForEnv('production')).toBeNull();
120
+ });
121
+ it('loadTokens returns entry matching current env via TQ_BACKEND_URL', () => {
122
+ saveTokens(stagingTokens);
123
+ saveTokens(localTokens);
124
+ process.env.TQ_BACKEND_URL = 'http://localhost:8080';
125
+ const loaded = loadTokens();
126
+ expect(loaded?.accessToken).toBe('local-access');
127
+ });
128
+ it('loadTokens returns entry matching config.json', () => {
129
+ saveTokens(stagingTokens);
130
+ saveTokens(localTokens);
131
+ // Write a config.json pointing to staging
132
+ const tqDir = path.join(state.tmpDir, '.tq');
133
+ fs.writeFileSync(path.join(tqDir, 'config.json'), JSON.stringify({ apiUrl: 'https://api.levr.now' }));
134
+ const loaded = loadTokens();
135
+ expect(loaded?.accessToken).toBe('staging-access');
136
+ });
137
+ it('loadTokens falls back to first entry when no config', () => {
138
+ saveTokens(stagingTokens);
139
+ const loaded = loadTokens();
140
+ expect(loaded?.accessToken).toBe('staging-access');
141
+ });
142
+ });
143
+ describe('backward compatibility: legacy single-token format', () => {
144
+ it('migrates legacy format on first read', () => {
145
+ const tqDir = path.join(state.tmpDir, '.tq');
146
+ fs.mkdirSync(tqDir, { recursive: true });
147
+ // Write legacy format (flat object)
148
+ fs.writeFileSync(path.join(tqDir, 'oauth-tokens.json'), JSON.stringify(stagingTokens));
149
+ const loaded = loadTokens();
150
+ expect(loaded?.accessToken).toBe('staging-access');
151
+ // File should now be in map format
152
+ const raw = JSON.parse(fs.readFileSync(path.join(tqDir, 'oauth-tokens.json'), 'utf-8'));
153
+ expect(raw['https://api.levr.now']).toBeDefined();
154
+ expect(raw['https://api.levr.now'].accessToken).toBe('staging-access');
155
+ });
156
+ it('migrates legacy format without apiBaseUrl using staging default', () => {
157
+ const tqDir = path.join(state.tmpDir, '.tq');
158
+ fs.mkdirSync(tqDir, { recursive: true });
159
+ const legacy = {
160
+ accessToken: 'a',
161
+ refreshToken: 'r',
162
+ expiresAt: 9999999999999,
163
+ };
164
+ fs.writeFileSync(path.join(tqDir, 'oauth-tokens.json'), JSON.stringify(legacy));
165
+ const loaded = loadTokens();
166
+ expect(loaded?.accessToken).toBe('a');
167
+ });
168
+ });
169
+ describe('loadTokens', () => {
170
+ it('returns null when no token file exists', () => {
171
+ expect(loadTokens()).toBeNull();
172
+ });
173
+ it('returns null for malformed JSON', () => {
174
+ const tqDir = path.join(state.tmpDir, '.tq');
175
+ fs.mkdirSync(tqDir, { recursive: true });
176
+ fs.writeFileSync(path.join(tqDir, 'oauth-tokens.json'), 'not-json');
177
+ expect(loadTokens()).toBeNull();
178
+ });
179
+ });
180
+ describe('loadTokensForEnv', () => {
181
+ it('returns null when no matching token exists', () => {
182
+ saveTokens(stagingTokens);
183
+ expect(loadTokensForEnv('local')).toBeNull();
184
+ });
185
+ it('returns the correct env token', () => {
186
+ saveTokens(stagingTokens);
187
+ saveTokens(localTokens);
188
+ expect(loadTokensForEnv('staging')?.accessToken).toBe('staging-access');
189
+ expect(loadTokensForEnv('local')?.accessToken).toBe('local-access');
190
+ });
191
+ });
192
+ describe('clearTokens', () => {
193
+ it('removes the token for the current environment', () => {
194
+ saveTokens(stagingTokens);
195
+ saveTokens(localTokens);
196
+ // Clear staging (current env via TQ_BACKEND_URL)
197
+ process.env.TQ_BACKEND_URL = 'https://api.levr.now';
198
+ clearTokens();
199
+ expect(loadTokensForEnv('staging')).toBeNull();
200
+ expect(loadTokensForEnv('local')?.accessToken).toBe('local-access');
201
+ });
202
+ it('removes entire file when last token cleared', () => {
203
+ saveTokens(stagingTokens);
204
+ process.env.TQ_BACKEND_URL = 'https://api.levr.now';
205
+ clearTokens();
206
+ expect(hasStoredTokens()).toBe(false);
207
+ });
208
+ it('does not throw when no token file exists', () => {
209
+ expect(() => clearTokens()).not.toThrow();
210
+ });
211
+ it('creates a backup before clearing', () => {
212
+ saveTokens(stagingTokens);
213
+ process.env.TQ_BACKEND_URL = 'https://api.levr.now';
214
+ clearTokens();
215
+ const backupPath = getTokenFilePath() + '.bak';
216
+ expect(fs.existsSync(backupPath)).toBe(true);
217
+ });
218
+ });
219
+ describe('hasStoredTokens', () => {
220
+ it('returns false when no file exists', () => {
221
+ expect(hasStoredTokens()).toBe(false);
222
+ });
223
+ it('returns true after saving tokens', () => {
224
+ saveTokens(stagingTokens);
225
+ expect(hasStoredTokens()).toBe(true);
226
+ });
227
+ });
228
+ describe('saveTokensForEnv', () => {
229
+ it('saves and retrieves by env name', () => {
230
+ saveTokensForEnv('production', {
231
+ ...stagingTokens,
232
+ accessToken: 'prod-access',
233
+ apiBaseUrl: 'https://api.levr.one',
234
+ });
235
+ const loaded = loadTokensForEnv('production');
236
+ expect(loaded?.accessToken).toBe('prod-access');
237
+ });
238
+ });
239
+ //# sourceMappingURL=token-store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.test.js","sourceRoot":"","sources":["../src/token-store.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAGzB,sDAAsD;AACtD,MAAM,KAAK,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;AAEjD,2EAA2E;AAC3E,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE;IACrC,MAAM,QAAQ,GAAG,MAAM,cAAc,EAAuB,CAAC;IAC7D,OAAO,EAAE,GAAG,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;AACtD,CAAC,CAAC,CAAC;AAEH,uEAAuE;AACvE,IAAI,UAAwD,CAAC;AAC7D,IAAI,gBAAoE,CAAC;AACzE,IAAI,UAAwD,CAAC;AAC7D,IAAI,gBAAoE,CAAC;AACzE,IAAI,WAA0D,CAAC;AAC/D,IAAI,eAAkE,CAAC;AACvE,IAAI,QAAoD,CAAC;AACzD,IAAI,gBAAoE,CAAC;AACzE,IAAI,iBAAsE,CAAC;AAC3E,IAAI,iBAAsE,CAAC;AAE3E,MAAM,aAAa,GAAiB;IAClC,WAAW,EAAE,gBAAgB;IAC7B,YAAY,EAAE,iBAAiB;IAC/B,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;IAC/B,UAAU,EAAE,sBAAsB;CACnC,CAAC;AAEF,MAAM,WAAW,GAAiB;IAChC,WAAW,EAAE,cAAc;IAC3B,YAAY,EAAE,eAAe;IAC7B,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;IAC/B,UAAU,EAAE,uBAAuB;CACpC,CAAC;AAEF,UAAU,CAAC,KAAK,IAAI,EAAE;IACpB,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACxE,sEAAsE;IACtE,EAAE,CAAC,YAAY,EAAE,CAAC;IAClB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;IAC7C,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC;IAC5B,gBAAgB,GAAG,GAAG,CAAC,gBAAgB,CAAC;IACxC,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC;IAC5B,gBAAgB,GAAG,GAAG,CAAC,gBAAgB,CAAC;IACxC,WAAW,GAAG,GAAG,CAAC,WAAW,CAAC;IAC9B,eAAe,GAAG,GAAG,CAAC,eAAe,CAAC;IACtC,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;IACxB,gBAAgB,GAAG,GAAG,CAAC,gBAAgB,CAAC;IACxC,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,CAAC;IAC1C,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,CAAC;AAC5C,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAClC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AAC5D,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;IACxB,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,CAAC,gBAAgB,EAAE,CAAC,CAAC,IAAI,CAC7B,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,mBAAmB,CAAC,CACpD,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC5B,MAAM,CAAC,iBAAiB,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACjC,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACjC,MAAM,CAAC,iBAAiB,CAAC,wBAAwB,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,iBAAiB,CAAC,4BAA4B,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC1E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QACjE,MAAM,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QAClE,MAAM,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mCAAmC,EAAE,GAAG,EAAE;IACjD,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,UAAU,CAAC,aAAa,CAAC,CAAC;QAC1B,uDAAuD;QACvD,mDAAmD;QACnD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClE,UAAU,CAAC,aAAa,CAAC,CAAC;QAC1B,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,UAAU,CAAC,aAAa,CAAC,CAAC;QAC1B,MAAM,OAAO,GAAiB;YAC5B,GAAG,aAAa;YAChB,WAAW,EAAE,WAAW;SACzB,CAAC;QACF,UAAU,CAAC,OAAO,CAAC,CAAC;QACpB,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAC3C,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kCAAkC,EAAE,GAAG,EAAE;IAChD,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,UAAU,CAAC,aAAa,CAAC,CAAC;QAC1B,UAAU,CAAC,WAAW,CAAC,CAAC;QAExB,MAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACxE,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACpE,MAAM,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,UAAU,CAAC,aAAa,CAAC,CAAC;QAC1B,UAAU,CAAC,WAAW,CAAC,CAAC;QAExB,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,uBAAuB,CAAC;QACrD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,UAAU,CAAC,aAAa,CAAC,CAAC;QAC1B,UAAU,CAAC,WAAW,CAAC,CAAC;QAExB,0CAA0C;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC7C,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,aAAa,CAAC,EAC/B,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC,CACnD,CAAC;QAEF,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,UAAU,CAAC,aAAa,CAAC,CAAC;QAC1B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oDAAoD,EAAE,GAAG,EAAE;IAClE,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC7C,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,oCAAoC;QACpC,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,mBAAmB,CAAC,EACrC,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,CAC9B,CAAC;QAEF,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAEnD,mCAAmC;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,mBAAmB,CAAC,EAAE,OAAO,CAAC,CAChC,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAClD,MAAM,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC7C,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,MAAM,MAAM,GAAiB;YAC3B,WAAW,EAAE,GAAG;YAChB,YAAY,EAAE,GAAG;YACjB,SAAS,EAAE,aAAa;SACzB,CAAC;QACF,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,mBAAmB,CAAC,EACrC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CACvB,CAAC;QAEF,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC7C,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,mBAAmB,CAAC,EAAE,UAAU,CAAC,CAAC;QACpE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;IAClC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,UAAU,CAAC,aAAa,CAAC,CAAC;QAC1B,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,UAAU,CAAC,aAAa,CAAC,CAAC;QAC1B,UAAU,CAAC,WAAW,CAAC,CAAC;QACxB,MAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACxE,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,UAAU,CAAC,aAAa,CAAC,CAAC;QAC1B,UAAU,CAAC,WAAW,CAAC,CAAC;QAExB,iDAAiD;QACjD,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,sBAAsB,CAAC;QACpD,WAAW,EAAE,CAAC;QAEd,MAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC/C,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,UAAU,CAAC,aAAa,CAAC,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,sBAAsB,CAAC;QACpD,WAAW,EAAE,CAAC;QACd,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,UAAU,CAAC,aAAa,CAAC,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,sBAAsB,CAAC;QACpD,WAAW,EAAE,CAAC;QACd,MAAM,UAAU,GAAG,gBAAgB,EAAE,GAAG,MAAM,CAAC;QAC/C,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,UAAU,CAAC,aAAa,CAAC,CAAC;QAC1B,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,gBAAgB,CAAC,YAAY,EAAE;YAC7B,GAAG,aAAa;YAChB,WAAW,EAAE,aAAa;YAC1B,UAAU,EAAE,sBAAsB;SACnC,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,gBAAgB,CAAC,YAAY,CAAC,CAAC;QAC9C,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * TokenWatcher - Self-Healing Token Management
3
+ *
4
+ * Watches the token file for changes and notifies subscribers.
5
+ * This enables MCP servers to auto-recover when:
6
+ * - Tokens appear after startup (user runs authorize)
7
+ * - Tokens are refreshed externally
8
+ * - Token file is fixed after corruption
9
+ */
10
+ import type { StoredTokens, TokenChangeCallback, AuthStatus } from './types.js';
11
+ /**
12
+ * Options for TokenWatcher
13
+ */
14
+ export interface TokenWatcherOptions {
15
+ /** Polling interval in milliseconds (backup if fs.watch misses events) */
16
+ pollInterval?: number;
17
+ /** Minutes before expiration to consider token as expiring soon */
18
+ expirationWarningMinutes?: number;
19
+ }
20
+ /**
21
+ * TokenWatcher for self-healing token management
22
+ *
23
+ * Usage:
24
+ * ```typescript
25
+ * const watcher = new TokenWatcher();
26
+ * watcher.onTokenChange((tokens) => {
27
+ * if (tokens) {
28
+ * console.log('Tokens available:', tokens.expiresAt > Date.now() ? 'valid' : 'expired');
29
+ * } else {
30
+ * console.log('No tokens available');
31
+ * }
32
+ * });
33
+ * watcher.start();
34
+ * ```
35
+ */
36
+ export declare class TokenWatcher {
37
+ private watcher;
38
+ private pollInterval;
39
+ private lastTokens;
40
+ private lastTokensJson;
41
+ private callbacks;
42
+ private options;
43
+ private isRunning;
44
+ constructor(options?: TokenWatcherOptions);
45
+ /**
46
+ * Start watching for token changes
47
+ */
48
+ start(): void;
49
+ /**
50
+ * Stop watching for token changes
51
+ */
52
+ stop(): void;
53
+ /**
54
+ * Register a callback for token changes
55
+ */
56
+ onTokenChange(callback: TokenChangeCallback): void;
57
+ /**
58
+ * Remove a token change callback
59
+ */
60
+ offTokenChange(callback: TokenChangeCallback): void;
61
+ /**
62
+ * Get current tokens (synchronous)
63
+ */
64
+ getCurrentTokens(): StoredTokens | null;
65
+ /**
66
+ * Get current auth status based on tokens
67
+ */
68
+ getAuthStatus(): AuthStatus;
69
+ /**
70
+ * Check if tokens are valid (not expired)
71
+ */
72
+ isTokenValid(): boolean;
73
+ /**
74
+ * Force a token check (useful after manual refresh)
75
+ */
76
+ forceCheck(): void;
77
+ /**
78
+ * Internal: Check tokens and notify if changed
79
+ */
80
+ private checkTokens;
81
+ }
82
+ //# sourceMappingURL=token-watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-watcher.d.ts","sourceRoot":"","sources":["../src/token-watcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEhF;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AAOD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,YAAY,CAA+B;IACnD,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,SAAS,CAAuC;IACxD,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,SAAS,CAAkB;gBAEvB,OAAO,CAAC,EAAE,mBAAmB;IAIzC;;OAEG;IACH,KAAK,IAAI,IAAI;IA+Cb;;OAEG;IACH,IAAI,IAAI,IAAI;IAcZ;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IASlD;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAInD;;OAEG;IACH,gBAAgB,IAAI,YAAY,GAAG,IAAI;IAIvC;;OAEG;IACH,aAAa,IAAI,UAAU;IAoB3B;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;OAEG;IACH,OAAO,CAAC,WAAW;CA2BpB"}