acuity-mcp-server 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 (99) hide show
  1. package/README.md +541 -0
  2. package/dist/auth/device-flow.d.ts +46 -0
  3. package/dist/auth/device-flow.d.ts.map +1 -0
  4. package/dist/auth/device-flow.js +141 -0
  5. package/dist/auth/device-flow.js.map +1 -0
  6. package/dist/auth/http-auth.d.ts +25 -0
  7. package/dist/auth/http-auth.d.ts.map +1 -0
  8. package/dist/auth/http-auth.js +101 -0
  9. package/dist/auth/http-auth.js.map +1 -0
  10. package/dist/auth/jwt-validator.d.ts +20 -0
  11. package/dist/auth/jwt-validator.d.ts.map +1 -0
  12. package/dist/auth/jwt-validator.js +83 -0
  13. package/dist/auth/jwt-validator.js.map +1 -0
  14. package/dist/auth/token-storage.d.ts +88 -0
  15. package/dist/auth/token-storage.d.ts.map +1 -0
  16. package/dist/auth/token-storage.js +273 -0
  17. package/dist/auth/token-storage.js.map +1 -0
  18. package/dist/clients/hasura-client.d.ts +33 -0
  19. package/dist/clients/hasura-client.d.ts.map +1 -0
  20. package/dist/clients/hasura-client.js +79 -0
  21. package/dist/clients/hasura-client.js.map +1 -0
  22. package/dist/config/environments.d.ts +51 -0
  23. package/dist/config/environments.d.ts.map +1 -0
  24. package/dist/config/environments.js +183 -0
  25. package/dist/config/environments.js.map +1 -0
  26. package/dist/index.d.ts +7 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +593 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/server/http-server.d.ts +14 -0
  31. package/dist/server/http-server.d.ts.map +1 -0
  32. package/dist/server/http-server.js +167 -0
  33. package/dist/server/http-server.js.map +1 -0
  34. package/dist/server/mcp-core.d.ts +12 -0
  35. package/dist/server/mcp-core.d.ts.map +1 -0
  36. package/dist/server/mcp-core.js +200 -0
  37. package/dist/server/mcp-core.js.map +1 -0
  38. package/dist/tools/acuity-init.d.ts +84 -0
  39. package/dist/tools/acuity-init.d.ts.map +1 -0
  40. package/dist/tools/acuity-init.js +239 -0
  41. package/dist/tools/acuity-init.js.map +1 -0
  42. package/dist/tools/get-dashboard-summary.d.ts +96 -0
  43. package/dist/tools/get-dashboard-summary.d.ts.map +1 -0
  44. package/dist/tools/get-dashboard-summary.js +264 -0
  45. package/dist/tools/get-dashboard-summary.js.map +1 -0
  46. package/dist/tools/get-issue.d.ts +62 -0
  47. package/dist/tools/get-issue.d.ts.map +1 -0
  48. package/dist/tools/get-issue.js +150 -0
  49. package/dist/tools/get-issue.js.map +1 -0
  50. package/dist/tools/get-lesson-learned.d.ts +53 -0
  51. package/dist/tools/get-lesson-learned.d.ts.map +1 -0
  52. package/dist/tools/get-lesson-learned.js +117 -0
  53. package/dist/tools/get-lesson-learned.js.map +1 -0
  54. package/dist/tools/get-lookup-values.d.ts +41 -0
  55. package/dist/tools/get-lookup-values.d.ts.map +1 -0
  56. package/dist/tools/get-lookup-values.js +127 -0
  57. package/dist/tools/get-lookup-values.js.map +1 -0
  58. package/dist/tools/get-project.d.ts +131 -0
  59. package/dist/tools/get-project.d.ts.map +1 -0
  60. package/dist/tools/get-project.js +340 -0
  61. package/dist/tools/get-project.js.map +1 -0
  62. package/dist/tools/get-risk.d.ts +65 -0
  63. package/dist/tools/get-risk.d.ts.map +1 -0
  64. package/dist/tools/get-risk.js +173 -0
  65. package/dist/tools/get-risk.js.map +1 -0
  66. package/dist/tools/get-status-reports.d.ts +46 -0
  67. package/dist/tools/get-status-reports.d.ts.map +1 -0
  68. package/dist/tools/get-status-reports.js +151 -0
  69. package/dist/tools/get-status-reports.js.map +1 -0
  70. package/dist/tools/init-auth.d.ts +47 -0
  71. package/dist/tools/init-auth.d.ts.map +1 -0
  72. package/dist/tools/init-auth.js +213 -0
  73. package/dist/tools/init-auth.js.map +1 -0
  74. package/dist/tools/list-issues.d.ts +134 -0
  75. package/dist/tools/list-issues.d.ts.map +1 -0
  76. package/dist/tools/list-issues.js +285 -0
  77. package/dist/tools/list-issues.js.map +1 -0
  78. package/dist/tools/list-lessons-learned.d.ts +79 -0
  79. package/dist/tools/list-lessons-learned.d.ts.map +1 -0
  80. package/dist/tools/list-lessons-learned.js +155 -0
  81. package/dist/tools/list-lessons-learned.js.map +1 -0
  82. package/dist/tools/list-projects.d.ts +200 -0
  83. package/dist/tools/list-projects.d.ts.map +1 -0
  84. package/dist/tools/list-projects.js +396 -0
  85. package/dist/tools/list-projects.js.map +1 -0
  86. package/dist/tools/list-risks.d.ts +166 -0
  87. package/dist/tools/list-risks.d.ts.map +1 -0
  88. package/dist/tools/list-risks.js +356 -0
  89. package/dist/tools/list-risks.js.map +1 -0
  90. package/dist/tools/search-projects.d.ts +90 -0
  91. package/dist/tools/search-projects.d.ts.map +1 -0
  92. package/dist/tools/search-projects.js +191 -0
  93. package/dist/tools/search-projects.js.map +1 -0
  94. package/dist/utils/formatters.d.ts +12 -0
  95. package/dist/utils/formatters.d.ts.map +1 -0
  96. package/dist/utils/formatters.js +28 -0
  97. package/dist/utils/formatters.js.map +1 -0
  98. package/openapi.yaml +194 -0
  99. package/package.json +68 -0
@@ -0,0 +1,141 @@
1
+ /**
2
+ * OAuth 2.0 Device Authorization Flow for Auth0
3
+ *
4
+ * Implements the Device Code flow for CLI/MCP authentication
5
+ * Reference: https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow
6
+ */
7
+ import open from 'open';
8
+ /**
9
+ * Request a device code from Auth0
10
+ */
11
+ export async function requestDeviceCode(config) {
12
+ const url = `https://${config.domain}/oauth/device/code`;
13
+ const body = new URLSearchParams({
14
+ client_id: config.clientId,
15
+ scope: config.scope || 'openid profile email offline_access',
16
+ });
17
+ if (config.audience) {
18
+ body.append('audience', config.audience);
19
+ }
20
+ const response = await fetch(url, {
21
+ method: 'POST',
22
+ headers: {
23
+ 'Content-Type': 'application/x-www-form-urlencoded',
24
+ },
25
+ body: body.toString(),
26
+ });
27
+ if (!response.ok) {
28
+ const error = await response.json().catch(() => ({}));
29
+ throw new Error(`Failed to get device code: ${error.error_description || response.statusText}`);
30
+ }
31
+ return response.json();
32
+ }
33
+ /**
34
+ * Poll for access token
35
+ * Returns token when user completes authorization, or throws on timeout/error
36
+ */
37
+ export async function pollForToken(config, deviceCode, interval, expiresIn, onPoll) {
38
+ const url = `https://${config.domain}/oauth/token`;
39
+ const startTime = Date.now();
40
+ const timeout = expiresIn * 1000;
41
+ while (Date.now() - startTime < timeout) {
42
+ // Wait for the specified interval before polling
43
+ await sleep(interval * 1000);
44
+ if (onPoll) {
45
+ onPoll();
46
+ }
47
+ const body = new URLSearchParams({
48
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
49
+ device_code: deviceCode,
50
+ client_id: config.clientId,
51
+ });
52
+ const response = await fetch(url, {
53
+ method: 'POST',
54
+ headers: {
55
+ 'Content-Type': 'application/x-www-form-urlencoded',
56
+ },
57
+ body: body.toString(),
58
+ });
59
+ const data = await response.json();
60
+ if (response.ok) {
61
+ return data;
62
+ }
63
+ // Handle expected errors during polling
64
+ switch (data.error) {
65
+ case 'authorization_pending':
66
+ // User hasn't authorized yet, continue polling
67
+ continue;
68
+ case 'slow_down':
69
+ // Increase polling interval
70
+ interval += 5;
71
+ continue;
72
+ case 'expired_token':
73
+ throw new Error('Device code expired. Please try again.');
74
+ case 'access_denied':
75
+ throw new Error('Authorization was denied by the user.');
76
+ default:
77
+ throw new Error(`Token request failed: ${data.error_description || data.error || 'Unknown error'}`);
78
+ }
79
+ }
80
+ throw new Error('Authorization timed out. Please try again.');
81
+ }
82
+ /**
83
+ * Refresh an access token using a refresh token
84
+ */
85
+ export async function refreshAccessToken(config, refreshToken) {
86
+ const url = `https://${config.domain}/oauth/token`;
87
+ const body = new URLSearchParams({
88
+ grant_type: 'refresh_token',
89
+ client_id: config.clientId,
90
+ refresh_token: refreshToken,
91
+ });
92
+ const response = await fetch(url, {
93
+ method: 'POST',
94
+ headers: {
95
+ 'Content-Type': 'application/x-www-form-urlencoded',
96
+ },
97
+ body: body.toString(),
98
+ });
99
+ if (!response.ok) {
100
+ const error = await response.json().catch(() => ({}));
101
+ throw new Error(`Failed to refresh token: ${error.error_description || response.statusText}`);
102
+ }
103
+ return response.json();
104
+ }
105
+ /**
106
+ * Run the complete Device Authorization Flow
107
+ */
108
+ export async function runDeviceFlow(config) {
109
+ // Step 1: Request device code
110
+ console.error('\n[Auth] Initiating Device Authorization Flow...\n');
111
+ const deviceCodeResponse = await requestDeviceCode(config);
112
+ // Step 2: Display instructions to user
113
+ console.error('To authorize this device, please:');
114
+ console.error(`\n 1. Visit: ${deviceCodeResponse.verification_uri}`);
115
+ console.error(` 2. Enter code: ${deviceCodeResponse.user_code}\n`);
116
+ console.error(`Or open this URL directly: ${deviceCodeResponse.verification_uri_complete}\n`);
117
+ // Step 3: Open browser automatically
118
+ console.error('[Auth] Opening browser...\n');
119
+ try {
120
+ await open(deviceCodeResponse.verification_uri_complete);
121
+ }
122
+ catch {
123
+ console.error('[Auth] Could not open browser automatically. Please visit the URL manually.\n');
124
+ }
125
+ // Step 4: Poll for token
126
+ console.error('[Auth] Waiting for authorization...');
127
+ let dots = 0;
128
+ const token = await pollForToken(config, deviceCodeResponse.device_code, deviceCodeResponse.interval, deviceCodeResponse.expires_in, () => {
129
+ dots = (dots + 1) % 4;
130
+ process.stderr.write(`\r[Auth] Waiting for authorization${'.'.repeat(dots)}${' '.repeat(3 - dots)}`);
131
+ });
132
+ console.error('\n\n[Auth] Authorization successful!\n');
133
+ return token;
134
+ }
135
+ /**
136
+ * Helper: Sleep for specified milliseconds
137
+ */
138
+ function sleep(ms) {
139
+ return new Promise((resolve) => setTimeout(resolve, ms));
140
+ }
141
+ //# sourceMappingURL=device-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-flow.js","sourceRoot":"","sources":["../../src/auth/device-flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * HTTP Authentication Middleware
3
+ * Validates OAuth 2.0 Bearer tokens for HTTP requests
4
+ */
5
+ import { Request, Response, NextFunction } from 'express';
6
+ /**
7
+ * Extended Request type with authenticated user info
8
+ */
9
+ export interface AuthenticatedRequest extends Request {
10
+ userEmail: string;
11
+ userToken: string;
12
+ }
13
+ /**
14
+ * Express middleware to validate OAuth 2.0 Bearer tokens
15
+ *
16
+ * Extracts JWT from Authorization header, validates it using Auth0 JWKS,
17
+ * and attaches user email to the request for downstream use.
18
+ */
19
+ export declare function validateHttpAuth(req: Request, res: Response, next: NextFunction): Promise<void>;
20
+ /**
21
+ * Optional middleware for endpoints that don't require auth
22
+ * but should use auth if provided
23
+ */
24
+ export declare function optionalHttpAuth(req: Request, _res: Response, next: NextFunction): Promise<void>;
25
+ //# sourceMappingURL=http-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-auth.d.ts","sourceRoot":"","sources":["../../src/auth/http-auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG1D;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,OAAO;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAkBD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CA8Df;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CAgBf"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * HTTP Authentication Middleware
3
+ * Validates OAuth 2.0 Bearer tokens for HTTP requests
4
+ */
5
+ import { validateJWT } from './jwt-validator.js';
6
+ /**
7
+ * Extract Bearer token from Authorization header
8
+ */
9
+ function extractBearerToken(authHeader) {
10
+ if (!authHeader) {
11
+ return null;
12
+ }
13
+ const parts = authHeader.split(' ');
14
+ if (parts.length !== 2 || parts[0].toLowerCase() !== 'bearer') {
15
+ return null;
16
+ }
17
+ return parts[1];
18
+ }
19
+ /**
20
+ * Express middleware to validate OAuth 2.0 Bearer tokens
21
+ *
22
+ * Extracts JWT from Authorization header, validates it using Auth0 JWKS,
23
+ * and attaches user email to the request for downstream use.
24
+ */
25
+ export async function validateHttpAuth(req, res, next) {
26
+ try {
27
+ // Check for API key first (simpler auth for testing/internal use)
28
+ const apiKey = req.headers['x-api-key'];
29
+ if (apiKey && process.env.MCP_API_KEY && apiKey === process.env.MCP_API_KEY) {
30
+ // API key auth - use configured service account email
31
+ const serviceEmail = process.env.MCP_SERVICE_EMAIL || 'mcp-service@acuityppm.com';
32
+ req.userEmail = serviceEmail;
33
+ req.userToken = apiKey;
34
+ next();
35
+ return;
36
+ }
37
+ // Extract Bearer token
38
+ const token = extractBearerToken(req.headers.authorization);
39
+ if (!token) {
40
+ res.status(401).json({
41
+ jsonrpc: '2.0',
42
+ error: {
43
+ code: -32001,
44
+ message: 'Authentication required. Provide Authorization: Bearer <token> header.'
45
+ },
46
+ id: null
47
+ });
48
+ return;
49
+ }
50
+ // Validate JWT and extract email
51
+ const { email } = await validateJWT(token);
52
+ if (!email) {
53
+ res.status(401).json({
54
+ jsonrpc: '2.0',
55
+ error: {
56
+ code: -32001,
57
+ message: 'Invalid token: could not extract user email.'
58
+ },
59
+ id: null
60
+ });
61
+ return;
62
+ }
63
+ // Attach user info to request
64
+ req.userEmail = email;
65
+ req.userToken = token;
66
+ next();
67
+ }
68
+ catch (error) {
69
+ console.error('[HTTP Auth] Token validation failed:', error);
70
+ const message = error instanceof Error ? error.message : 'Token validation failed';
71
+ res.status(401).json({
72
+ jsonrpc: '2.0',
73
+ error: {
74
+ code: -32001,
75
+ message: `Authentication failed: ${message}`
76
+ },
77
+ id: null
78
+ });
79
+ }
80
+ }
81
+ /**
82
+ * Optional middleware for endpoints that don't require auth
83
+ * but should use auth if provided
84
+ */
85
+ export async function optionalHttpAuth(req, _res, next) {
86
+ try {
87
+ const token = extractBearerToken(req.headers.authorization);
88
+ if (token) {
89
+ const { email } = await validateJWT(token);
90
+ if (email) {
91
+ req.userEmail = email;
92
+ req.userToken = token;
93
+ }
94
+ }
95
+ }
96
+ catch {
97
+ // Ignore auth errors for optional auth
98
+ }
99
+ next();
100
+ }
101
+ //# sourceMappingURL=http-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-auth.js","sourceRoot":"","sources":["../../src/auth/http-auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * JWT Validator for Auth0 tokens
3
+ * Based on rails_api/app/lib/json_web_token.rb
4
+ */
5
+ export interface JWTPayload {
6
+ email: string;
7
+ sub?: string;
8
+ aud?: string;
9
+ iss?: string;
10
+ exp?: number;
11
+ }
12
+ /**
13
+ * Validates a JWT token from Auth0
14
+ *
15
+ * @param token - The JWT token string
16
+ * @returns Promise resolving to payload with email
17
+ * @throws Error if token is invalid or email is missing
18
+ */
19
+ export declare function validateJWT(token: string): Promise<JWTPayload>;
20
+ //# sourceMappingURL=jwt-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt-validator.d.ts","sourceRoot":"","sources":["../../src/auth/jwt-validator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAwCD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAkDpE"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * JWT Validator for Auth0 tokens
3
+ * Based on rails_api/app/lib/json_web_token.rb
4
+ */
5
+ import jwt from 'jsonwebtoken';
6
+ import jwksClient from 'jwks-rsa';
7
+ /**
8
+ * Creates a JWKS client for Auth0 public key retrieval
9
+ */
10
+ function createJWKSClient() {
11
+ const managementDomain = process.env.AUTH0_MANAGEMENT_DOMAIN;
12
+ if (!managementDomain) {
13
+ throw new Error('AUTH0_MANAGEMENT_DOMAIN environment variable is required');
14
+ }
15
+ return jwksClient({
16
+ jwksUri: `https://${managementDomain}/.well-known/jwks.json`,
17
+ cache: true,
18
+ cacheMaxAge: 86400000, // 24 hours
19
+ rateLimit: true,
20
+ jwksRequestsPerMinute: 10
21
+ });
22
+ }
23
+ /**
24
+ * Get signing key from Auth0 JWKS
25
+ */
26
+ function getKey(header, callback) {
27
+ const client = createJWKSClient();
28
+ client.getSigningKey(header.kid, (err, key) => {
29
+ if (err) {
30
+ callback(err);
31
+ return;
32
+ }
33
+ const signingKey = key?.getPublicKey();
34
+ callback(null, signingKey);
35
+ });
36
+ }
37
+ /**
38
+ * Validates a JWT token from Auth0
39
+ *
40
+ * @param token - The JWT token string
41
+ * @returns Promise resolving to payload with email
42
+ * @throws Error if token is invalid or email is missing
43
+ */
44
+ export async function validateJWT(token) {
45
+ const auth0Domain = process.env.REACT_APP_AUTH0_DOMAIN || process.env.AUTH0_DOMAIN;
46
+ const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID;
47
+ if (!auth0Domain) {
48
+ throw new Error('AUTH0_DOMAIN or REACT_APP_AUTH0_DOMAIN environment variable is required');
49
+ }
50
+ return new Promise((resolve, reject) => {
51
+ jwt.verify(token, getKey, {
52
+ algorithms: ['RS256', 'RS512'],
53
+ issuer: `https://${auth0Domain}/`,
54
+ audience: clientId,
55
+ ignoreExpiration: false
56
+ }, (err, decoded) => {
57
+ if (err) {
58
+ reject(new Error(`Invalid JWT token: ${err.message}`));
59
+ return;
60
+ }
61
+ if (!decoded || typeof decoded === 'string') {
62
+ reject(new Error('Invalid JWT token payload'));
63
+ return;
64
+ }
65
+ // Extract email from various possible claim locations
66
+ const email = decoded.email ||
67
+ decoded['https://acuity.com/email'] ||
68
+ decoded['https://acuityppm.com/email'];
69
+ if (!email || typeof email !== 'string') {
70
+ reject(new Error('Email not found in JWT token claims'));
71
+ return;
72
+ }
73
+ resolve({
74
+ email: email.toLowerCase(),
75
+ sub: decoded.sub,
76
+ aud: decoded.aud,
77
+ iss: decoded.iss,
78
+ exp: decoded.exp
79
+ });
80
+ });
81
+ });
82
+ }
83
+ //# sourceMappingURL=jwt-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt-validator.js","sourceRoot":"","sources":["../../src/auth/jwt-validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Secure Token Storage using System Keychain
3
+ *
4
+ * Uses keytar for cross-platform keychain access:
5
+ * - macOS: Keychain Access
6
+ * - Windows: Credential Manager
7
+ * - Linux: libsecret
8
+ *
9
+ * If keytar is not available (e.g., no native build tools),
10
+ * falls back to no-op storage and users must use USER_JWT env var.
11
+ */
12
+ import type { TokenResponse } from './device-flow.js';
13
+ /**
14
+ * Check if keytar is available
15
+ */
16
+ export declare function isKeytarAvailable(): Promise<boolean>;
17
+ /**
18
+ * Get the error message if keytar failed to load
19
+ */
20
+ export declare function getKeytarLoadError(): string | null;
21
+ export interface PendingDeviceCode {
22
+ deviceCode: string;
23
+ userCode: string;
24
+ verificationUri: string;
25
+ interval: number;
26
+ expiresAt: number;
27
+ }
28
+ export interface StoredCredentials {
29
+ accessToken: string;
30
+ refreshToken?: string;
31
+ idToken?: string;
32
+ expiresAt: number;
33
+ email?: string;
34
+ }
35
+ /**
36
+ * Save credentials to the system keychain
37
+ * Returns false if keytar is not available
38
+ */
39
+ export declare function saveCredentials(credentials: StoredCredentials): Promise<boolean>;
40
+ /**
41
+ * Load credentials from the system keychain
42
+ * Returns null if keytar is not available or no credentials stored
43
+ */
44
+ export declare function loadCredentials(): Promise<StoredCredentials | null>;
45
+ /**
46
+ * Clear credentials from the system keychain
47
+ * Returns false if keytar is not available
48
+ */
49
+ export declare function clearCredentials(): Promise<boolean>;
50
+ /**
51
+ * Check if credentials exist in the keychain
52
+ */
53
+ export declare function hasCredentials(): Promise<boolean>;
54
+ /**
55
+ * Check if the stored access token is expired
56
+ * Returns true if expired or no credentials exist
57
+ */
58
+ export declare function isTokenExpired(bufferSeconds?: number): Promise<boolean>;
59
+ /**
60
+ * Convert TokenResponse from Device Flow to StoredCredentials
61
+ */
62
+ export declare function tokenResponseToCredentials(token: TokenResponse, email?: string): StoredCredentials;
63
+ /**
64
+ * Get the access token, refreshing if necessary
65
+ * Throws if no valid token available and refresh fails
66
+ */
67
+ export declare function getValidAccessToken(refreshFn?: (refreshToken: string) => Promise<TokenResponse>): Promise<string>;
68
+ /**
69
+ * Save pending device code during two-phase login
70
+ * Returns false if keytar is not available
71
+ */
72
+ export declare function savePendingDeviceCode(pending: PendingDeviceCode): Promise<boolean>;
73
+ /**
74
+ * Load pending device code
75
+ * Returns null if keytar is not available or no pending code
76
+ */
77
+ export declare function loadPendingDeviceCode(): Promise<PendingDeviceCode | null>;
78
+ /**
79
+ * Clear pending device code
80
+ * Returns false if keytar is not available
81
+ */
82
+ export declare function clearPendingDeviceCode(): Promise<boolean>;
83
+ /**
84
+ * Get the ID token (JWT) for Hasura authentication, refreshing if necessary
85
+ * Hasura requires a JWT, not an opaque access token
86
+ */
87
+ export declare function getValidIdToken(refreshFn?: (refreshToken: string) => Promise<TokenResponse>): Promise<string>;
88
+ //# sourceMappingURL=token-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-storage.d.ts","sourceRoot":"","sources":["../../src/auth/token-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AA4CtD;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,CAG1D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,IAAI,CAElD;AAUD,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,WAAW,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,CAStF;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAezE;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAOzD;AAED;;GAEG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAGvD;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,aAAa,GAAE,MAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAQjF;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,aAAa,EACpB,KAAK,CAAC,EAAE,MAAM,GACb,iBAAiB,CAUnB;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,GAC3D,OAAO,CAAC,MAAM,CAAC,CA0CjB;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,CASxF;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAwB/E;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO/D;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,SAAS,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,GAC3D,OAAO,CAAC,MAAM,CAAC,CAkDjB"}