@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,290 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ // Mock token-store so OAuthClient constructor doesn't hit disk
3
+ vi.mock('./token-store.js', () => ({
4
+ loadTokens: () => null,
5
+ saveTokens: vi.fn(),
6
+ clearTokens: vi.fn(),
7
+ restoreTokensFromBackup: () => false,
8
+ getTqDir: () => '/tmp/.tq',
9
+ }));
10
+ // Mock workspace-store so setTokens() can exercise saveWorkspace() without disk
11
+ vi.mock('./workspace-store.js', () => ({
12
+ saveWorkspace: vi.fn(),
13
+ loadWorkspace: () => null,
14
+ clearWorkspace: vi.fn(),
15
+ getWorkspaceFilePath: () => '/tmp/.tq/workspace.json',
16
+ }));
17
+ // Mock config so loadConfig() returns a deterministic value
18
+ vi.mock('./config.js', () => ({
19
+ loadConfig: () => ({
20
+ environment: 'staging',
21
+ apiUrl: 'https://api.test.com',
22
+ authUrl: 'https://api.test.com',
23
+ clientUrl: 'https://app.test.com',
24
+ oauthClientId: '2',
25
+ }),
26
+ }));
27
+ import { OAuthClient } from './oauth-client.js';
28
+ import { saveTokens } from './token-store.js';
29
+ import { saveWorkspace } from './workspace-store.js';
30
+ const mockFetch = vi.fn();
31
+ vi.stubGlobal('fetch', mockFetch);
32
+ const deviceResponse = {
33
+ device_code: 'dev-code-123',
34
+ user_code: 'ABCD-1234',
35
+ verification_uri: 'https://api.test.com/v1/oauth/device',
36
+ verification_uri_complete: 'https://api.test.com/v1/oauth/device?user_code=ABCD-1234',
37
+ expires_in: 300,
38
+ interval: 1,
39
+ };
40
+ const tokenResponse = {
41
+ access_token: 'access-123',
42
+ token_type: 'bearer',
43
+ expires_in: 86400,
44
+ refresh_token: 'refresh-456',
45
+ scope: 'read:own write:own',
46
+ };
47
+ describe('OAuthClient.authorizeDevice', () => {
48
+ let client;
49
+ beforeEach(() => {
50
+ vi.useFakeTimers();
51
+ client = new OAuthClient({
52
+ clientId: '2',
53
+ authServerUrl: 'https://api.test.com',
54
+ });
55
+ });
56
+ afterEach(() => {
57
+ mockFetch.mockReset();
58
+ vi.useRealTimers();
59
+ });
60
+ /**
61
+ * Helper: start authorizeDevice(), advance fake timers until it resolves.
62
+ * Each tick advances past the polling interval so setTimeout unblocks.
63
+ */
64
+ async function drainPolling(promise) {
65
+ // Advance in small increments, yielding to microtasks between each,
66
+ // so fetch mocks resolve and the while-loop progresses.
67
+ for (let i = 0; i < 20; i++) {
68
+ await vi.advanceTimersByTimeAsync(6_000);
69
+ }
70
+ return promise;
71
+ }
72
+ it('should request device code and return tokens on immediate approval', async () => {
73
+ mockFetch
74
+ .mockResolvedValueOnce({
75
+ ok: true,
76
+ json: () => Promise.resolve(deviceResponse),
77
+ })
78
+ .mockResolvedValueOnce({
79
+ ok: true,
80
+ json: () => Promise.resolve(tokenResponse),
81
+ });
82
+ const p = client.authorizeDevice();
83
+ await drainPolling(p);
84
+ expect(mockFetch).toHaveBeenCalledTimes(2);
85
+ const [deviceUrl, deviceOpts] = mockFetch.mock.calls[0];
86
+ expect(deviceUrl).toBe('https://api.test.com/v1/oauth/device/authorize');
87
+ expect(deviceOpts.body).toBeDefined();
88
+ expect(deviceOpts.body.toString()).toContain('client_id=2');
89
+ const [tokenUrl, tokenOpts] = mockFetch.mock.calls[1];
90
+ expect(tokenUrl).toBe('https://api.test.com/v1/oauth/token');
91
+ expect(tokenOpts.body).toBeDefined();
92
+ expect(tokenOpts.body.toString()).toContain('device_code=dev-code-123');
93
+ expect(saveTokens).toHaveBeenCalledWith(expect.objectContaining({
94
+ accessToken: 'access-123',
95
+ refreshToken: 'refresh-456',
96
+ }));
97
+ });
98
+ it('should poll on authorization_pending then succeed', async () => {
99
+ mockFetch
100
+ .mockResolvedValueOnce({
101
+ ok: true,
102
+ json: () => Promise.resolve(deviceResponse),
103
+ })
104
+ .mockResolvedValueOnce({
105
+ ok: false,
106
+ text: () => Promise.resolve(JSON.stringify({ error: 'authorization_pending' })),
107
+ })
108
+ .mockResolvedValueOnce({
109
+ ok: true,
110
+ json: () => Promise.resolve(tokenResponse),
111
+ });
112
+ const p = client.authorizeDevice();
113
+ await drainPolling(p);
114
+ expect(mockFetch).toHaveBeenCalledTimes(3);
115
+ expect(saveTokens).toHaveBeenCalled();
116
+ });
117
+ it('should increase interval on slow_down then succeed', async () => {
118
+ mockFetch
119
+ .mockResolvedValueOnce({
120
+ ok: true,
121
+ json: () => Promise.resolve(deviceResponse),
122
+ })
123
+ .mockResolvedValueOnce({
124
+ ok: false,
125
+ text: () => Promise.resolve(JSON.stringify({ error: 'slow_down' })),
126
+ })
127
+ .mockResolvedValueOnce({
128
+ ok: true,
129
+ json: () => Promise.resolve(tokenResponse),
130
+ });
131
+ const p = client.authorizeDevice();
132
+ await drainPolling(p);
133
+ expect(mockFetch).toHaveBeenCalledTimes(3);
134
+ });
135
+ it('should throw on access_denied', async () => {
136
+ mockFetch
137
+ .mockResolvedValueOnce({
138
+ ok: true,
139
+ json: () => Promise.resolve(deviceResponse),
140
+ })
141
+ .mockResolvedValueOnce({
142
+ ok: false,
143
+ text: () => Promise.resolve(JSON.stringify({ error: 'access_denied' })),
144
+ });
145
+ const p = client.authorizeDevice();
146
+ // Attach rejection handler before advancing timers to avoid unhandled rejection
147
+ const assertion = expect(p).rejects.toThrow('Authorization denied by user.');
148
+ await drainPolling(p).catch(() => { });
149
+ await assertion;
150
+ });
151
+ it('should throw on expired_token', async () => {
152
+ mockFetch
153
+ .mockResolvedValueOnce({
154
+ ok: true,
155
+ json: () => Promise.resolve(deviceResponse),
156
+ })
157
+ .mockResolvedValueOnce({
158
+ ok: false,
159
+ text: () => Promise.resolve(JSON.stringify({ error: 'expired_token' })),
160
+ });
161
+ const p = client.authorizeDevice();
162
+ const assertion = expect(p).rejects.toThrow('Device code expired');
163
+ await drainPolling(p).catch(() => { });
164
+ await assertion;
165
+ });
166
+ it('should handle non-JSON error response', async () => {
167
+ mockFetch
168
+ .mockResolvedValueOnce({
169
+ ok: true,
170
+ json: () => Promise.resolve(deviceResponse),
171
+ })
172
+ .mockResolvedValueOnce({
173
+ ok: false,
174
+ status: 502,
175
+ text: () => Promise.resolve('Bad Gateway'),
176
+ });
177
+ const p = client.authorizeDevice();
178
+ const assertion = expect(p).rejects.toThrow('Device flow polling failed');
179
+ await drainPolling(p).catch(() => { });
180
+ await assertion;
181
+ });
182
+ it('should throw when device authorize request fails', async () => {
183
+ mockFetch.mockResolvedValueOnce({
184
+ ok: false,
185
+ status: 400,
186
+ text: () => Promise.resolve('Bad Request'),
187
+ });
188
+ await expect(client.authorizeDevice()).rejects.toThrow('Device authorization request failed (400): Bad Request');
189
+ });
190
+ });
191
+ describe('OAuthClient.setTokens workspace persistence', () => {
192
+ let client;
193
+ beforeEach(() => {
194
+ vi.useFakeTimers();
195
+ vi.mocked(saveWorkspace).mockClear();
196
+ client = new OAuthClient({
197
+ clientId: '2',
198
+ authServerUrl: 'https://api.test.com',
199
+ });
200
+ });
201
+ afterEach(() => {
202
+ mockFetch.mockReset();
203
+ vi.useRealTimers();
204
+ });
205
+ it('persists workspace when device grant carries user.workspace_id', async () => {
206
+ const wsId = 'ws-device-abc';
207
+ mockFetch
208
+ .mockResolvedValueOnce({
209
+ ok: true,
210
+ json: () => Promise.resolve(deviceResponse),
211
+ })
212
+ .mockResolvedValueOnce({
213
+ ok: true,
214
+ json: () => Promise.resolve({
215
+ ...tokenResponse,
216
+ user: { workspace_id: wsId },
217
+ // Backend includes workspaces[] in the grant response but
218
+ // tq-oauth does not cache it locally — left here to simulate
219
+ // the full payload without asserting anything on it.
220
+ workspaces: [
221
+ {
222
+ id: wsId,
223
+ name: 'My Workspace',
224
+ url_key: 'my-ws',
225
+ is_primary: true,
226
+ },
227
+ ],
228
+ }),
229
+ });
230
+ const p = client.authorizeDevice();
231
+ for (let i = 0; i < 20; i++) {
232
+ await vi.advanceTimersByTimeAsync(6_000);
233
+ }
234
+ await p;
235
+ expect(saveWorkspace).toHaveBeenCalledTimes(1);
236
+ expect(saveWorkspace).toHaveBeenCalledWith(wsId);
237
+ });
238
+ it('does NOT persist workspace when grant omits user.workspace_id (refresh-like)', async () => {
239
+ mockFetch
240
+ .mockResolvedValueOnce({
241
+ ok: true,
242
+ json: () => Promise.resolve(deviceResponse),
243
+ })
244
+ .mockResolvedValueOnce({
245
+ ok: true,
246
+ json: () => Promise.resolve(tokenResponse),
247
+ });
248
+ const p = client.authorizeDevice();
249
+ for (let i = 0; i < 20; i++) {
250
+ await vi.advanceTimersByTimeAsync(6_000);
251
+ }
252
+ await p;
253
+ expect(saveWorkspace).not.toHaveBeenCalled();
254
+ });
255
+ });
256
+ describe('OAuthClient.refresh workspace behavior', () => {
257
+ let client;
258
+ beforeEach(() => {
259
+ vi.mocked(saveWorkspace).mockClear();
260
+ client = new OAuthClient({
261
+ clientId: '2',
262
+ authServerUrl: 'https://api.test.com',
263
+ });
264
+ // Seed a refresh token so refresh() proceeds to the fetch call
265
+ client.setTokensFromStorage({
266
+ accessToken: 'expired-access',
267
+ refreshToken: 'refresh-seed',
268
+ expiresAt: Date.now() - 1000,
269
+ apiBaseUrl: 'https://api.test.com',
270
+ });
271
+ });
272
+ afterEach(() => {
273
+ mockFetch.mockReset();
274
+ });
275
+ it('refresh response without user field does NOT save workspace', async () => {
276
+ mockFetch.mockResolvedValueOnce({
277
+ ok: true,
278
+ json: () => Promise.resolve({
279
+ access_token: 'new-access',
280
+ token_type: 'bearer',
281
+ expires_in: 3600,
282
+ refresh_token: 'new-refresh',
283
+ scope: 'read:own',
284
+ }),
285
+ });
286
+ await client.refresh();
287
+ expect(saveWorkspace).not.toHaveBeenCalled();
288
+ });
289
+ });
290
+ //# sourceMappingURL=oauth-client.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-client.test.js","sourceRoot":"","sources":["../src/oauth-client.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared test utilities for tq-oauth tests.
3
+ */
4
+ /**
5
+ * Temporarily simulate a non-test environment so loadConfig()
6
+ * exercises the config-file reading path. loadConfig() skips
7
+ * ~/.tq/config.json when VITEST/JEST env vars are set.
8
+ */
9
+ export declare function withNonTestEnv<T>(fn: () => T): T;
10
+ //# sourceMappingURL=test-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CA2BhD"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Shared test utilities for tq-oauth tests.
3
+ */
4
+ /**
5
+ * Temporarily simulate a non-test environment so loadConfig()
6
+ * exercises the config-file reading path. loadConfig() skips
7
+ * ~/.tq/config.json when VITEST/JEST env vars are set.
8
+ */
9
+ export function withNonTestEnv(fn) {
10
+ const saved = {
11
+ VITEST: process.env.VITEST,
12
+ NODE_ENV: process.env.NODE_ENV,
13
+ JEST_WORKER_ID: process.env.JEST_WORKER_ID,
14
+ // DEV_TLS gates isLocalTlsMode() in config.ts. Clearing it forces the
15
+ // cert-file probe path, which deterministically returns false in the
16
+ // tmpDir filesystem the test harness uses.
17
+ DEV_TLS: process.env.DEV_TLS,
18
+ };
19
+ delete process.env.VITEST;
20
+ process.env.NODE_ENV = 'development';
21
+ delete process.env.JEST_WORKER_ID;
22
+ delete process.env.DEV_TLS;
23
+ try {
24
+ return fn();
25
+ }
26
+ finally {
27
+ if (saved.VITEST !== undefined)
28
+ process.env.VITEST = saved.VITEST;
29
+ else
30
+ delete process.env.VITEST;
31
+ if (saved.NODE_ENV !== undefined)
32
+ process.env.NODE_ENV = saved.NODE_ENV;
33
+ else
34
+ delete process.env.NODE_ENV;
35
+ if (saved.JEST_WORKER_ID !== undefined)
36
+ process.env.JEST_WORKER_ID = saved.JEST_WORKER_ID;
37
+ else
38
+ delete process.env.JEST_WORKER_ID;
39
+ if (saved.DEV_TLS !== undefined)
40
+ process.env.DEV_TLS = saved.DEV_TLS;
41
+ else
42
+ delete process.env.DEV_TLS;
43
+ }
44
+ }
45
+ //# sourceMappingURL=test-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-utils.js","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAI,EAAW;IAC3C,MAAM,KAAK,GAAG;QACZ,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM;QAC1B,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ;QAC9B,cAAc,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;QAC1C,sEAAsE;QACtE,qEAAqE;QACrE,2CAA2C;QAC3C,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO;KAC7B,CAAC;IACF,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;IAC1B,OAAO,CAAC,GAAG,CAAC,QAAQ,GAAG,aAAa,CAAC;IACrC,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAClC,OAAO,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;IAC3B,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;YAAS,CAAC;QACT,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;;YAC7D,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;QAC/B,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;;YACnE,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;QACjC,IAAI,KAAK,CAAC,cAAc,KAAK,SAAS;YACpC,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,KAAK,CAAC,cAAc,CAAC;;YAC/C,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;QACvC,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;;YAChE,OAAO,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;IAClC,CAAC;AACH,CAAC"}
@@ -0,0 +1,78 @@
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 type { StoredTokens } from './types.js';
15
+ export type EnvName = 'local' | 'staging' | 'production';
16
+ /**
17
+ * Resolve an environment name from a backend URL.
18
+ * Returns undefined if the URL doesn't match any known preset.
19
+ */
20
+ export declare function resolveEnvFromUrl(url: string): EnvName | undefined;
21
+ /**
22
+ * Resolve a backend URL from an environment name.
23
+ */
24
+ export declare function resolveUrlFromEnv(env: EnvName): string;
25
+ /**
26
+ * Load the token for the current environment.
27
+ * Reads the config to determine which backend URL is active,
28
+ * then returns the matching entry from the token map.
29
+ *
30
+ * Falls back to first token in the map ONLY when no config exists
31
+ * (backward compat for first-time migration). When the user has
32
+ * explicitly set an environment, only that environment's token is returned.
33
+ */
34
+ export declare function loadTokens(): StoredTokens | null;
35
+ /**
36
+ * Load tokens for a specific environment by name.
37
+ */
38
+ export declare function loadTokensForEnv(env: EnvName): StoredTokens | null;
39
+ /**
40
+ * Load tokens for an arbitrary backend URL (custom URL not in ENV_URLS).
41
+ *
42
+ * Used by the qinetic sync setup command, which needs to talk to a remote
43
+ * backend that may not be one of the named presets — the operator passes
44
+ * `--target <url>` and the CLI fetches the matching token entry by URL.
45
+ */
46
+ export declare function loadTokensForUrl(url: string): StoredTokens | null;
47
+ /**
48
+ * Save tokens for the current environment.
49
+ * Upserts the entry keyed by the token's apiBaseUrl.
50
+ */
51
+ export declare function saveTokens(tokens: StoredTokens): void;
52
+ /**
53
+ * Save tokens for a specific environment by name.
54
+ */
55
+ export declare function saveTokensForEnv(env: EnvName, tokens: StoredTokens): void;
56
+ /**
57
+ * Clear stored tokens for the current environment.
58
+ * Moves the entire file to .bak before removing the entry.
59
+ */
60
+ export declare function clearTokens(): void;
61
+ /**
62
+ * Attempt to restore tokens from backup (.bak file)
63
+ * Returns true if backup was restored successfully.
64
+ */
65
+ export declare function restoreTokensFromBackup(): boolean;
66
+ /**
67
+ * Check if token file exists and has any entries
68
+ */
69
+ export declare function hasStoredTokens(): boolean;
70
+ /**
71
+ * Get token file path (for debugging and watching)
72
+ */
73
+ export declare function getTokenFilePath(): string;
74
+ /**
75
+ * Get TQ directory path
76
+ */
77
+ export declare function getTqDir(): string;
78
+ //# sourceMappingURL=token-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../src/token-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,YAAY,CAAC;AAyBzD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAMlE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAEtD;AAwDD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,IAAI,YAAY,GAAG,IAAI,CAmBhD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAMlE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAOjE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAUrD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAIzE;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,IAAI,CA6BlC;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,OAAO,CAiCjD;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAIzC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;GAEG;AACH,wBAAgB,QAAQ,IAAI,MAAM,CAEjC"}