msteams-mcp 0.28.0 → 0.29.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 (82) hide show
  1. package/README.md +1 -1
  2. package/dist/api/csa-api.js +8 -8
  3. package/dist/auth/crypto.d.ts +4 -0
  4. package/dist/auth/crypto.js +12 -0
  5. package/dist/auth/crypto.test.js +18 -1
  6. package/dist/auth/session-store.d.ts +6 -9
  7. package/dist/auth/session-store.js +88 -12
  8. package/dist/auth/token-extractor.d.ts +74 -31
  9. package/dist/auth/token-extractor.js +432 -171
  10. package/dist/auth/token-refresh-http.d.ts +10 -3
  11. package/dist/auth/token-refresh-http.js +219 -155
  12. package/dist/auth/token-refresh-http.test.js +19 -125
  13. package/dist/auth/token-refresh.d.ts +21 -6
  14. package/dist/auth/token-refresh.js +103 -62
  15. package/dist/browser/auth.d.ts +35 -0
  16. package/dist/browser/auth.js +57 -18
  17. package/dist/browser/context.d.ts +14 -4
  18. package/dist/browser/context.js +18 -7
  19. package/dist/cli.js +1 -1
  20. package/dist/constants.d.ts +12 -11
  21. package/dist/constants.js +15 -11
  22. package/dist/index.js +0 -0
  23. package/dist/server.d.ts +53 -1
  24. package/dist/server.js +167 -10
  25. package/dist/tools/auth-tools.js +77 -36
  26. package/dist/tools/file-tools.d.ts +3 -24
  27. package/dist/tools/file-tools.js +4 -31
  28. package/dist/tools/index.d.ts +8 -2
  29. package/dist/tools/index.js +0 -1
  30. package/dist/tools/meeting-tools.js +2 -2
  31. package/dist/tools/message-tools.d.ts +6 -6
  32. package/dist/tools/message-tools.js +21 -21
  33. package/dist/tools/people-tools.js +4 -4
  34. package/dist/tools/registry.d.ts +4 -4
  35. package/dist/tools/registry.js +3 -5
  36. package/dist/tools/registry.test.js +14 -4
  37. package/dist/tools/search-tools.js +4 -4
  38. package/dist/tools/tag-tools.js +1 -1
  39. package/dist/types/errors.d.ts +0 -4
  40. package/dist/types/errors.js +0 -11
  41. package/dist/utils/api-config.d.ts +0 -40
  42. package/dist/utils/api-config.js +0 -50
  43. package/dist/utils/auth-guards.d.ts +16 -23
  44. package/dist/utils/auth-guards.js +65 -81
  45. package/dist/utils/auth-guards.test.js +2 -37
  46. package/package.json +1 -1
  47. package/dist/api/assignments-api.d.ts +0 -124
  48. package/dist/api/assignments-api.js +0 -327
  49. package/dist/api/assignments-api.test.d.ts +0 -7
  50. package/dist/api/assignments-api.test.js +0 -302
  51. package/dist/api/graph-files-api.d.ts +0 -34
  52. package/dist/api/graph-files-api.js +0 -169
  53. package/dist/api/graph-files-api.test.d.ts +0 -1
  54. package/dist/api/graph-files-api.test.js +0 -152
  55. package/dist/api/index.d.ts +0 -10
  56. package/dist/api/index.js +0 -10
  57. package/dist/auth/assignments-refresh.test.d.ts +0 -1
  58. package/dist/auth/assignments-refresh.test.js +0 -43
  59. package/dist/auth/assignments-token.test.d.ts +0 -1
  60. package/dist/auth/assignments-token.test.js +0 -36
  61. package/dist/auth/index.d.ts +0 -7
  62. package/dist/auth/index.js +0 -7
  63. package/dist/auth/token-refresh.test.d.ts +0 -1
  64. package/dist/auth/token-refresh.test.js +0 -31
  65. package/dist/research/auth-research.d.ts +0 -10
  66. package/dist/research/auth-research.js +0 -175
  67. package/dist/research/explore.d.ts +0 -11
  68. package/dist/research/explore.js +0 -287
  69. package/dist/research/search-research.d.ts +0 -17
  70. package/dist/research/search-research.js +0 -317
  71. package/dist/test/debug-search.d.ts +0 -10
  72. package/dist/test/debug-search.js +0 -147
  73. package/dist/test/mcp-harness.d.ts +0 -17
  74. package/dist/test/mcp-harness.js +0 -476
  75. package/dist/tools/assignment-tools.d.ts +0 -90
  76. package/dist/tools/assignment-tools.js +0 -151
  77. package/dist/tools/assignment-tools.test.d.ts +0 -1
  78. package/dist/tools/assignment-tools.test.js +0 -13
  79. package/dist/utils/assignments-auth.test.d.ts +0 -1
  80. package/dist/utils/assignments-auth.test.js +0 -57
  81. package/dist/utils/auth-guards-assignments.test.d.ts +0 -5
  82. package/dist/utils/auth-guards-assignments.test.js +0 -89
package/README.md CHANGED
@@ -117,7 +117,7 @@ See [CLI Usage](#cli-usage) for commands.
117
117
  | `teams_search_email` | Search emails in your mailbox (same auth as Teams - no extra login) |
118
118
  | `teams_list_chats` | List recent conversations (1:1, group, meeting, channel) with a last-message preview |
119
119
  | `teams_get_message` | Get a single message by ID with full content (any age); includes reactions |
120
- | `teams_get_thread` | Get messages from a conversation/thread; includes reactions; `threadRootId` scopes to one channel thread; `fromUrl` accepts a Teams message deep link |
120
+ | `teams_get_thread` | Get messages from a conversation/thread; includes reactions; `threadRootId` scopes to one channel thread; `fromUrl` accepts a Teams message deep link; `cursor` pages back through older history |
121
121
  | `teams_find_channel` | Find channels by name (your teams + org-wide discovery) |
122
122
  | `teams_get_tags` | List a team's channel tags for tag @mentions |
123
123
  | `teams_get_activity` | Get activity feed (mentions, reactions, replies, notifications) |
@@ -20,8 +20,8 @@ export async function getFavorites() {
20
20
  return authResult;
21
21
  }
22
22
  const { auth, csaToken } = authResult.value;
23
- const { csaRegion, baseUrl } = getApiConfig();
24
- const url = CSA_API.conversationFolders(csaRegion, baseUrl);
23
+ const { region, baseUrl } = getApiConfig();
24
+ const url = CSA_API.conversationFolders(region, baseUrl);
25
25
  const response = await httpRequest(url, {
26
26
  method: 'GET',
27
27
  headers: getCsaHeaders(auth.skypeToken, csaToken, baseUrl),
@@ -94,7 +94,7 @@ async function modifyFavorite(conversationId, action) {
94
94
  return authResult;
95
95
  }
96
96
  const { auth, csaToken } = authResult.value;
97
- const { csaRegion, baseUrl } = getApiConfig();
97
+ const { region, baseUrl } = getApiConfig();
98
98
  // Get current folder state
99
99
  const currentState = await getFavorites();
100
100
  if (!currentState.ok) {
@@ -103,7 +103,7 @@ async function modifyFavorite(conversationId, action) {
103
103
  if (!currentState.value.folderId) {
104
104
  return err(createError(ErrorCode.NOT_FOUND, 'Could not find Favorites folder'));
105
105
  }
106
- const url = CSA_API.conversationFolders(csaRegion, baseUrl);
106
+ const url = CSA_API.conversationFolders(region, baseUrl);
107
107
  const response = await httpRequest(url, {
108
108
  method: 'POST',
109
109
  headers: getCsaHeaders(auth.skypeToken, csaToken, baseUrl),
@@ -135,8 +135,8 @@ export async function getMyTeamsAndChannels() {
135
135
  return authResult;
136
136
  }
137
137
  const { auth, csaToken } = authResult.value;
138
- const { csaRegion, baseUrl } = getApiConfig();
139
- const url = CSA_API.teamsList(csaRegion, baseUrl);
138
+ const { region, baseUrl } = getApiConfig();
139
+ const url = CSA_API.teamsList(region, baseUrl);
140
140
  const response = await httpRequest(url, {
141
141
  method: 'GET',
142
142
  headers: getCsaHeaders(auth.skypeToken, csaToken, baseUrl),
@@ -156,8 +156,8 @@ export async function getCustomEmojis() {
156
156
  return authResult;
157
157
  }
158
158
  const { auth, csaToken } = authResult.value;
159
- const { csaRegion, baseUrl } = getApiConfig();
160
- const url = CSA_API.customEmojis(csaRegion, baseUrl);
159
+ const { region, baseUrl } = getApiConfig();
160
+ const url = CSA_API.customEmojis(region, baseUrl);
161
161
  const response = await httpRequest(url, {
162
162
  method: 'GET',
163
163
  headers: getCsaHeaders(auth.skypeToken, csaToken, baseUrl),
@@ -26,3 +26,7 @@ export declare function encrypt(plaintext: string): EncryptedData;
26
26
  * @throws Error if decryption fails (wrong machine, corrupted data, etc.)
27
27
  */
28
28
  export declare function decrypt(data: EncryptedData): string;
29
+ /**
30
+ * Checks if data looks like encrypted format.
31
+ */
32
+ export declare function isEncrypted(data: unknown): data is EncryptedData;
@@ -52,3 +52,15 @@ export function decrypt(data) {
52
52
  decrypted += decipher.final('utf8');
53
53
  return decrypted;
54
54
  }
55
+ /**
56
+ * Checks if data looks like encrypted format.
57
+ */
58
+ export function isEncrypted(data) {
59
+ if (!data || typeof data !== 'object')
60
+ return false;
61
+ const obj = data;
62
+ return (typeof obj.iv === 'string' &&
63
+ typeof obj.content === 'string' &&
64
+ typeof obj.tag === 'string' &&
65
+ typeof obj.version === 'number');
66
+ }
@@ -2,7 +2,7 @@
2
2
  * Unit tests for encryption utilities.
3
3
  */
4
4
  import { describe, it, expect } from 'vitest';
5
- import { encrypt, decrypt } from './crypto.js';
5
+ import { encrypt, decrypt, isEncrypted } from './crypto.js';
6
6
  describe('encrypt/decrypt round-trip', () => {
7
7
  it('encrypts and decrypts a simple string', () => {
8
8
  const plaintext = 'hello world';
@@ -36,6 +36,23 @@ describe('encrypt/decrypt round-trip', () => {
36
36
  expect(encrypted1.content).not.toBe(encrypted2.content);
37
37
  });
38
38
  });
39
+ describe('isEncrypted', () => {
40
+ it('returns true for encrypted data structure', () => {
41
+ const encrypted = encrypt('test');
42
+ expect(isEncrypted(encrypted)).toBe(true);
43
+ });
44
+ it('returns false for plain objects', () => {
45
+ expect(isEncrypted({})).toBe(false);
46
+ expect(isEncrypted({ content: 'abc' })).toBe(false);
47
+ expect(isEncrypted({ iv: 'abc' })).toBe(false);
48
+ expect(isEncrypted(null)).toBe(false);
49
+ expect(isEncrypted(undefined)).toBe(false);
50
+ expect(isEncrypted('string')).toBe(false);
51
+ });
52
+ it('returns true for object with all required fields', () => {
53
+ expect(isEncrypted({ content: 'x', iv: 'y', tag: 'z', version: 1 })).toBe(true);
54
+ });
55
+ });
39
56
  describe('decrypt error handling', () => {
40
57
  it('throws on tampered ciphertext', () => {
41
58
  const encrypted = encrypt('secret');
@@ -9,18 +9,11 @@
9
9
  * Session files are stored in a user-specific config directory (~/.teams-mcp-server/)
10
10
  * to ensure consistency regardless of how the server is invoked (npx, global install, etc.).
11
11
  */
12
- /**
13
- * User-specific config directory for teams-mcp-server.
14
- * - Windows: %APPDATA%\teams-mcp-server\
15
- * - macOS/Linux: ~/.teams-mcp-server/
16
- */
12
+ export declare const PROJECT_ROOT: string;
17
13
  export declare const CONFIG_DIR: string;
14
+ export declare const USER_DATA_DIR: string;
18
15
  export declare const SESSION_STATE_PATH: string;
19
16
  export declare const TOKEN_CACHE_PATH: string;
20
- /**
21
- * Ensures the config directory exists with secure permissions.
22
- */
23
- export declare function ensureConfigDir(): void;
24
17
  /** Session state as stored by Playwright. */
25
18
  export interface SessionState {
26
19
  cookies: Array<{
@@ -47,6 +40,10 @@ export interface TokenCache {
47
40
  substrateTokenExpiry: number;
48
41
  extractedAt: number;
49
42
  }
43
+ /**
44
+ * Ensures the user data directory exists.
45
+ */
46
+ export declare function ensureUserDataDir(): void;
50
47
  /**
51
48
  * Checks if session state file exists.
52
49
  */
@@ -12,26 +12,89 @@
12
12
  import * as fs from 'fs';
13
13
  import * as os from 'os';
14
14
  import * as path from 'path';
15
- import { encrypt, decrypt } from './crypto.js';
15
+ import { fileURLToPath } from 'url';
16
+ import { encrypt, decrypt, isEncrypted } from './crypto.js';
16
17
  import { SESSION_EXPIRY_HOURS } from '../constants.js';
17
18
  import * as log from '../utils/logger.js';
19
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
20
  /**
19
- * User-specific config directory for teams-mcp-server.
20
- * - Windows: %APPDATA%\teams-mcp-server\
21
+ * Gets the user's home directory with fallback.
22
+ * os.homedir() can throw in rare edge cases (missing env vars, broken passwd).
23
+ */
24
+ function getHomeDirSafe() {
25
+ try {
26
+ return os.homedir();
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ /**
33
+ * Gets the user-specific config directory for teams-mcp-server.
34
+ * - Windows: %APPDATA%\teams-mcp-server\ (e.g., C:\Users\name\AppData\Roaming\teams-mcp-server\)
21
35
  * - macOS/Linux: ~/.teams-mcp-server/
36
+ * - Fallback: ./teams-mcp-server-data/ relative to package (legacy behaviour)
22
37
  */
23
- export const CONFIG_DIR = process.platform === 'win32'
24
- ? path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'teams-mcp-server')
25
- : path.join(os.homedir(), '.teams-mcp-server');
38
+ function getConfigDir() {
39
+ const homeDir = getHomeDirSafe();
40
+ if (process.platform === 'win32') {
41
+ const appData = process.env.APPDATA || (homeDir ? path.join(homeDir, 'AppData', 'Roaming') : null);
42
+ if (appData) {
43
+ return path.join(appData, 'teams-mcp-server');
44
+ }
45
+ }
46
+ else if (homeDir) {
47
+ return path.join(homeDir, '.teams-mcp-server');
48
+ }
49
+ // Fallback to package-relative directory if home directory unavailable
50
+ const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
51
+ return path.join(projectRoot, 'teams-mcp-server-data');
52
+ }
53
+ export const PROJECT_ROOT = path.resolve(__dirname, '../..');
54
+ export const CONFIG_DIR = getConfigDir();
55
+ export const USER_DATA_DIR = path.join(CONFIG_DIR, '.user-data');
26
56
  export const SESSION_STATE_PATH = path.join(CONFIG_DIR, 'session-state.json');
27
57
  export const TOKEN_CACHE_PATH = path.join(CONFIG_DIR, 'token-cache.json');
58
+ // Legacy paths for migration
59
+ const LEGACY_SESSION_PATH = path.join(PROJECT_ROOT, 'session-state.json');
60
+ const LEGACY_TOKEN_CACHE_PATH = path.join(PROJECT_ROOT, 'token-cache.json');
28
61
  /** File permission mode: owner read/write only. */
29
62
  const SECURE_FILE_MODE = 0o600;
30
63
  /**
31
64
  * Ensures the config directory exists with secure permissions.
32
65
  */
33
- export function ensureConfigDir() {
34
- fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
66
+ function ensureConfigDir() {
67
+ if (!fs.existsSync(CONFIG_DIR)) {
68
+ fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
69
+ }
70
+ }
71
+ /**
72
+ * Migrates a file from legacy location to new config directory.
73
+ * Only migrates if legacy exists and new location doesn't.
74
+ */
75
+ function migrateIfNeeded(legacyPath, newPath) {
76
+ if (fs.existsSync(legacyPath) && !fs.existsSync(newPath)) {
77
+ ensureConfigDir();
78
+ try {
79
+ fs.copyFileSync(legacyPath, newPath);
80
+ fs.chmodSync(newPath, SECURE_FILE_MODE);
81
+ // Remove legacy file after successful copy
82
+ fs.unlinkSync(legacyPath);
83
+ }
84
+ catch (error) {
85
+ // Log migration errors for debugging, but continue - will just create new session
86
+ log.warn('session-store', `Failed to migrate ${path.basename(legacyPath)}: ${error instanceof Error ? error.message : error}`);
87
+ }
88
+ }
89
+ }
90
+ /**
91
+ * Ensures the user data directory exists.
92
+ */
93
+ export function ensureUserDataDir() {
94
+ ensureConfigDir();
95
+ if (!fs.existsSync(USER_DATA_DIR)) {
96
+ fs.mkdirSync(USER_DATA_DIR, { recursive: true, mode: 0o700 });
97
+ }
35
98
  }
36
99
  /**
37
100
  * Writes data securely with encryption and file permissions.
@@ -45,16 +108,26 @@ function writeSecure(filePath, data) {
45
108
  });
46
109
  }
47
110
  /**
48
- * Reads and decrypts data. Returns null if missing or undecryptable
49
- * (different machine, corrupted).
111
+ * Reads data securely, handling both encrypted and legacy plaintext.
50
112
  */
51
113
  function readSecure(filePath) {
52
- if (!fs.existsSync(filePath))
114
+ if (!fs.existsSync(filePath)) {
53
115
  return null;
116
+ }
54
117
  try {
55
- return JSON.parse(decrypt(JSON.parse(fs.readFileSync(filePath, 'utf8'))));
118
+ const content = fs.readFileSync(filePath, 'utf8');
119
+ const parsed = JSON.parse(content);
120
+ // Check if this is encrypted data
121
+ if (isEncrypted(parsed)) {
122
+ const decrypted = decrypt(parsed);
123
+ return JSON.parse(decrypted);
124
+ }
125
+ // Legacy plaintext - migrate to encrypted
126
+ writeSecure(filePath, parsed);
127
+ return parsed;
56
128
  }
57
129
  catch (error) {
130
+ // If decryption fails (different machine, corrupted), return null
58
131
  log.error('session-store', `Failed to read ${filePath}: ${error instanceof Error ? error.message : error}`);
59
132
  return null;
60
133
  }
@@ -63,12 +136,14 @@ function readSecure(filePath) {
63
136
  * Checks if session state file exists.
64
137
  */
65
138
  export function hasSessionState() {
139
+ migrateIfNeeded(LEGACY_SESSION_PATH, SESSION_STATE_PATH);
66
140
  return fs.existsSync(SESSION_STATE_PATH);
67
141
  }
68
142
  /**
69
143
  * Reads the session state.
70
144
  */
71
145
  export function readSessionState() {
146
+ migrateIfNeeded(LEGACY_SESSION_PATH, SESSION_STATE_PATH);
72
147
  return readSecure(SESSION_STATE_PATH);
73
148
  }
74
149
  /**
@@ -110,6 +185,7 @@ export function isSessionLikelyExpired() {
110
185
  * Reads the token cache.
111
186
  */
112
187
  export function readTokenCache() {
188
+ migrateIfNeeded(LEGACY_TOKEN_CACHE_PATH, TOKEN_CACHE_PATH);
113
189
  return readSecure(TOKEN_CACHE_PATH);
114
190
  }
115
191
  /**
@@ -5,17 +5,19 @@
5
5
  * Teams stores MSAL tokens in localStorage; we parse these to get bearer tokens
6
6
  * for various APIs (Substrate search, chatsvc messaging, etc.).
7
7
  */
8
- import { type SessionState } from './session-store.js';
8
+ import { clearTokenCache, type SessionState } from './session-store.js';
9
9
  import { type UserProfile } from '../utils/parsers.js';
10
- /**
11
- * Decodes a JWT token's payload without verifying the signature.
12
- */
13
- export declare function decodeJwtPayload(token: string): Record<string, unknown> | null;
14
- /** A JWT access token and its expiry. */
10
+ /** Substrate search token (for search/people APIs). */
15
11
  export interface SubstrateTokenInfo {
16
12
  token: string;
17
13
  expiry: Date;
18
14
  }
15
+ /** Teams chat API token (for chatsvc). */
16
+ export interface TeamsTokenInfo {
17
+ token: string;
18
+ expiry: Date;
19
+ userMri: string;
20
+ }
19
21
  /** Cookie-based auth for messaging APIs. */
20
22
  export interface MessageAuthInfo {
21
23
  skypeToken: string;
@@ -25,17 +27,16 @@ export interface MessageAuthInfo {
25
27
  /**
26
28
  * Extracts the Substrate search token from session state.
27
29
  * This token is used for search and people APIs.
28
- *
29
- * The target scope containing 'SubstrateSearch' matches all known formats:
30
- * - substrate.office.com/search/SubstrateSearch (old)
31
- * - substrate.office.com/SubstrateSearch-Internal.ReadWrite (new)
32
- * - outlook.office.com/search/SubstrateSearch-Internal.ReadWrite (some Enterprise tenants)
33
30
  */
34
31
  export declare function extractSubstrateToken(state?: SessionState): SubstrateTokenInfo | null;
35
32
  /**
36
33
  * Gets a valid Substrate token, either from cache or by extracting from session.
37
34
  */
38
35
  export declare function getValidSubstrateToken(): string | null;
36
+ /**
37
+ * Checks if we have a valid Substrate token.
38
+ */
39
+ export declare function hasValidSubstrateToken(): boolean;
39
40
  /**
40
41
  * Gets Substrate token status for diagnostics.
41
42
  */
@@ -45,28 +46,13 @@ export declare function getSubstrateTokenStatus(): {
45
46
  minutesRemaining?: number;
46
47
  };
47
48
  /**
48
- * Extracts the EDU Assignments API token from session state.
49
+ * Extracts the Teams chat API token from session state.
49
50
  *
50
- * This token authenticates calls to `assignments.edu.cloud.microsoft`. It is
51
- * minted on demand by HTTP token refresh and stored in the MSAL cache.
52
- * Select AccessToken entries by the JWT audience, since Microsoft Graph uses
53
- * the same EduAssignments permission names for a different resource.
54
- */
55
- export declare function extractAssignmentsToken(state?: SessionState): SubstrateTokenInfo | null;
56
- /**
57
- * Extracts a Microsoft Graph token from session state. Teams web caches one for
58
- * its own client; the on-demand refresh (`refreshTokensViaHttp('graph')`) renews it.
59
- */
60
- export declare function extractGraphToken(state?: SessionState): SubstrateTokenInfo | null;
61
- /** Remove only the rejected credential; leave other API tokens untouched. */
62
- export declare function invalidateAccessToken(token: string): void;
63
- /**
64
- * Gets a valid EDU Assignments token from the session, or null. Refreshed on
65
- * demand by `refreshTokensViaHttp('assignments')`.
51
+ * Teams stores multiple tokens for different services. We prefer:
52
+ * 1. chatsvcagg.teams.microsoft.com (primary chat API)
53
+ * 2. api.spaces.skype.com (fallback)
66
54
  */
67
- export declare function getValidAssignmentsToken(): string | null;
68
- /** Gets a valid Microsoft Graph token from the session, or null. */
69
- export declare function getValidGraphToken(): string | null;
55
+ export declare function extractTeamsToken(state?: SessionState): TeamsTokenInfo | null;
70
56
  /**
71
57
  * Extracts the Skype Spaces API token from session state.
72
58
  *
@@ -112,6 +98,35 @@ export interface RegionConfig {
112
98
  * base URLs (e.g., teams.microsoft.us).
113
99
  */
114
100
  export declare function extractRegionConfig(state?: SessionState): RegionConfig | null;
101
+ /** User details from DISCOVER-USER-DETAILS. */
102
+ export interface UserDetails {
103
+ /** User's MRI (e.g., "8:orgid:abc..."). */
104
+ mri: string;
105
+ /** User's region (e.g., "amer", "emea"). */
106
+ region: string;
107
+ /** User's partition (e.g., "amer01"). */
108
+ userPartition: string;
109
+ /** Tenant's partition (e.g., "amer02"). */
110
+ tenantPartition: string;
111
+ /** License details. */
112
+ licenses: {
113
+ isFreemium: boolean;
114
+ isTrial: boolean;
115
+ isTeamsEnabled: boolean;
116
+ isCopilot: boolean;
117
+ isTranscriptEnabled: boolean;
118
+ isFrontline: boolean;
119
+ };
120
+ }
121
+ /**
122
+ * Extracts user details from DISCOVER-USER-DETAILS in localStorage.
123
+ *
124
+ * This provides user-specific info including:
125
+ * - User's MRI
126
+ * - Region and partition info
127
+ * - License details (Copilot, transcription, etc.)
128
+ */
129
+ export declare function extractUserDetails(state?: SessionState): UserDetails | null;
115
130
  /**
116
131
  * Extracts authentication info needed for messaging API.
117
132
  * Unlike other APIs, messaging uses cookies rather than localStorage tokens.
@@ -140,3 +155,31 @@ export declare function getUserProfile(state?: SessionState): UserProfile | null
140
155
  * Searches localStorage entries first, then falls back to JWT claims.
141
156
  */
142
157
  export declare function getUserDisplayName(state?: SessionState): string | null;
158
+ /**
159
+ * Checks if tokens in session state are expired.
160
+ */
161
+ export declare function areTokensExpired(state?: SessionState): boolean;
162
+ export { clearTokenCache };
163
+ /**
164
+ * Discovered configuration from localStorage.
165
+ * Used for debugging and understanding what config is available.
166
+ */
167
+ export interface DiscoveredConfig {
168
+ /** All DISCOVER-* keys with their parsed values. */
169
+ discoveryConfigs: Record<string, unknown>;
170
+ /** All keys that look like configuration (not tokens). */
171
+ configKeys: string[];
172
+ /** Content of interesting config keys (settings, flags). */
173
+ configContents: Record<string, unknown>;
174
+ /** Teams base URL extracted from discovery config. */
175
+ teamsBaseUrl: string | null;
176
+ /** Substrate URL if found in any config. */
177
+ substrateUrl: string | null;
178
+ /** All unique hosts found in config URLs. */
179
+ uniqueHosts: string[];
180
+ }
181
+ /**
182
+ * Extracts all configuration data from localStorage for debugging.
183
+ * This helps discover what config is available from different tenants.
184
+ */
185
+ export declare function discoverConfig(state?: SessionState): DiscoveredConfig | null;