@mintlify/cli 4.0.1097 → 4.0.1099

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.
@@ -12,6 +12,8 @@ import {
12
12
 
13
13
  vi.mock('../../src/keyring.js', () => ({
14
14
  getAccessToken: vi.fn().mockResolvedValue(null),
15
+ getRefreshToken: vi.fn().mockResolvedValue(null),
16
+ storeCredentials: vi.fn().mockResolvedValue(undefined),
15
17
  }));
16
18
 
17
19
  const mockFetch = vi.fn();
@@ -30,6 +32,7 @@ afterEach(() => {
30
32
  function mockOk(data: unknown) {
31
33
  mockFetch.mockResolvedValueOnce({
32
34
  ok: true,
35
+ status: 200,
33
36
  json: () => Promise.resolve(data),
34
37
  });
35
38
  }
@@ -43,8 +46,13 @@ function mockError(status: number, body: string) {
43
46
  });
44
47
  }
45
48
 
46
- function calledUrl(): URL {
47
- return mockFetch.mock.calls[0]![0] as URL;
49
+ function calledUrl(): string {
50
+ const arg = mockFetch.mock.calls[0]![0];
51
+ return typeof arg === 'string' ? arg : String(arg);
52
+ }
53
+
54
+ function calledUrlObj(): URL {
55
+ return new URL(calledUrl());
48
56
  }
49
57
 
50
58
  describe('client auth', () => {
@@ -59,7 +67,7 @@ describe('client auth', () => {
59
67
  mockOk({ feedback: [], nextCursor: null, hasMore: false });
60
68
  await getFeedback({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'test');
61
69
  expect(mockFetch).toHaveBeenCalledWith(
62
- expect.any(URL),
70
+ expect.any(String),
63
71
  expect.objectContaining({
64
72
  headers: expect.objectContaining({
65
73
  Authorization: 'Bearer test-token',
@@ -80,14 +88,14 @@ describe('client request handling', () => {
80
88
  it('passes subdomain as query param when provided', async () => {
81
89
  mockOk({ feedback: [], nextCursor: null, hasMore: false });
82
90
  await getFeedback({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'my-docs');
83
- expect(calledUrl().searchParams.get('subdomain')).toBe('my-docs');
84
- expect(calledUrl().pathname).toBe('/api/cli/analytics/feedback');
91
+ expect(calledUrlObj().searchParams.get('subdomain')).toBe('my-docs');
92
+ expect(calledUrlObj().pathname).toBe('/api/cli/analytics/feedback');
85
93
  });
86
94
 
87
95
  it('omits subdomain param when not provided', async () => {
88
96
  mockOk({ feedback: [], nextCursor: null, hasMore: false });
89
97
  await getFeedback({ dateFrom: '2024-01-01', dateTo: '2024-01-31' });
90
- expect(calledUrl().searchParams.has('subdomain')).toBe(false);
98
+ expect(calledUrlObj().searchParams.has('subdomain')).toBe(false);
91
99
  });
92
100
 
93
101
  it('sets query params and omits undefined values', async () => {
@@ -98,9 +106,9 @@ describe('client request handling', () => {
98
106
  limit: 10,
99
107
  cursor: undefined,
100
108
  });
101
- expect(calledUrl().searchParams.get('dateFrom')).toBe('2024-01-01');
102
- expect(calledUrl().searchParams.get('limit')).toBe('10');
103
- expect(calledUrl().searchParams.has('cursor')).toBe(false);
109
+ expect(calledUrlObj().searchParams.get('dateFrom')).toBe('2024-01-01');
110
+ expect(calledUrlObj().searchParams.get('limit')).toBe('10');
111
+ expect(calledUrlObj().searchParams.has('cursor')).toBe(false);
104
112
  });
105
113
  });
106
114
 
@@ -108,51 +116,51 @@ describe('endpoint paths', () => {
108
116
  it('getKpi', async () => {
109
117
  mockOk({ humanVisitors: 0 });
110
118
  await getKpi({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'docs');
111
- expect(calledUrl().pathname).toBe('/api/cli/analytics/kpi');
112
- expect(calledUrl().searchParams.get('subdomain')).toBe('docs');
119
+ expect(calledUrlObj().pathname).toBe('/api/cli/analytics/kpi');
120
+ expect(calledUrlObj().searchParams.get('subdomain')).toBe('docs');
113
121
  });
114
122
 
115
123
  it('getFeedbackByPage', async () => {
116
124
  mockOk({ feedback: [], hasMore: false });
117
125
  await getFeedbackByPage({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'docs');
118
- expect(calledUrl().pathname).toBe('/api/cli/analytics/feedback/by-page');
126
+ expect(calledUrlObj().pathname).toBe('/api/cli/analytics/feedback/by-page');
119
127
  });
120
128
 
121
129
  it('getConversations', async () => {
122
130
  mockOk({ conversations: [], nextCursor: null, hasMore: false });
123
131
  await getConversations({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'docs');
124
- expect(calledUrl().pathname).toBe('/api/cli/analytics/assistant');
132
+ expect(calledUrlObj().pathname).toBe('/api/cli/analytics/assistant');
125
133
  });
126
134
 
127
135
  it('getSearches', async () => {
128
136
  mockOk({ searches: [], totalSearches: 0, nextCursor: null });
129
137
  await getSearches({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'docs');
130
- expect(calledUrl().pathname).toBe('/api/cli/analytics/searches');
138
+ expect(calledUrlObj().pathname).toBe('/api/cli/analytics/searches');
131
139
  });
132
140
 
133
141
  it('getViews', async () => {
134
142
  mockOk({ totals: {}, views: [], hasMore: false });
135
143
  await getViews({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'docs');
136
- expect(calledUrl().pathname).toBe('/api/cli/analytics/views');
144
+ expect(calledUrlObj().pathname).toBe('/api/cli/analytics/views');
137
145
  });
138
146
 
139
147
  it('getVisitors', async () => {
140
148
  mockOk({ totals: {}, visitors: [], hasMore: false });
141
149
  await getVisitors({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'docs');
142
- expect(calledUrl().pathname).toBe('/api/cli/analytics/visitors');
150
+ expect(calledUrlObj().pathname).toBe('/api/cli/analytics/visitors');
143
151
  });
144
152
 
145
153
  it('getBuckets', async () => {
146
154
  mockOk({ data: [], pagination: { total: 0 } });
147
155
  await getBuckets({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'docs');
148
- expect(calledUrl().pathname).toBe('/api/cli/analytics/conversations/buckets');
149
- expect(calledUrl().searchParams.get('subdomain')).toBe('docs');
156
+ expect(calledUrlObj().pathname).toBe('/api/cli/analytics/conversations/buckets');
157
+ expect(calledUrlObj().searchParams.get('subdomain')).toBe('docs');
150
158
  });
151
159
 
152
160
  it('getBucketThreads', async () => {
153
161
  mockOk({ data: [], pagination: { total: 0, hasMore: false, nextCursor: null } });
154
162
  await getBucketThreads('bucket-123', { dateFrom: '2024-01-01' }, 'docs');
155
- expect(calledUrl().pathname).toBe('/api/cli/analytics/conversations/buckets/bucket-123');
156
- expect(calledUrl().searchParams.get('subdomain')).toBe('docs');
163
+ expect(calledUrlObj().pathname).toBe('/api/cli/analytics/conversations/buckets/bucket-123');
164
+ expect(calledUrlObj().searchParams.get('subdomain')).toBe('docs');
157
165
  });
158
166
  });
@@ -0,0 +1,182 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+
3
+ import { authenticatedFetch } from '../src/authenticatedFetch.js';
4
+
5
+ const mockGetAccessToken = vi.fn();
6
+ const mockGetRefreshToken = vi.fn();
7
+ const mockStoreCredentials = vi.fn();
8
+
9
+ vi.mock('../src/keyring.js', () => ({
10
+ getAccessToken: (...args: unknown[]) => mockGetAccessToken(...args),
11
+ getRefreshToken: (...args: unknown[]) => mockGetRefreshToken(...args),
12
+ storeCredentials: (...args: unknown[]) => mockStoreCredentials(...args),
13
+ }));
14
+
15
+ const mockFetch = vi.fn();
16
+ global.fetch = mockFetch;
17
+
18
+ function makeResponse(status: number, body: unknown = {}) {
19
+ return {
20
+ ok: status >= 200 && status < 300,
21
+ status,
22
+ statusText: status === 401 ? 'Unauthorized' : 'OK',
23
+ json: () => Promise.resolve(body),
24
+ text: () => Promise.resolve(JSON.stringify(body)),
25
+ };
26
+ }
27
+
28
+ describe('authenticatedFetch', () => {
29
+ beforeEach(() => {
30
+ vi.clearAllMocks();
31
+ mockFetch.mockReset();
32
+ vi.stubEnv('MINTLIFY_SESSION_TOKEN', '');
33
+ });
34
+
35
+ afterEach(() => {
36
+ vi.unstubAllEnvs();
37
+ });
38
+
39
+ it('sends access token from keyring', async () => {
40
+ mockGetAccessToken.mockResolvedValue('access-123');
41
+ mockFetch.mockResolvedValueOnce(makeResponse(200, { ok: true }));
42
+
43
+ await authenticatedFetch('http://test/api');
44
+
45
+ expect(mockFetch).toHaveBeenCalledWith('http://test/api', {
46
+ headers: { Authorization: 'Bearer access-123' },
47
+ });
48
+ });
49
+
50
+ it('falls back to MINTLIFY_SESSION_TOKEN env var', async () => {
51
+ mockGetAccessToken.mockResolvedValue(null);
52
+ vi.stubEnv('MINTLIFY_SESSION_TOKEN', 'env-token');
53
+ mockFetch.mockResolvedValueOnce(makeResponse(200));
54
+
55
+ await authenticatedFetch('http://test/api');
56
+
57
+ expect(mockFetch).toHaveBeenCalledWith('http://test/api', {
58
+ headers: { Authorization: 'Bearer env-token' },
59
+ });
60
+ });
61
+
62
+ it('throws when no token is available', async () => {
63
+ mockGetAccessToken.mockResolvedValue(null);
64
+
65
+ await expect(authenticatedFetch('http://test/api')).rejects.toThrow('Not authenticated');
66
+ });
67
+
68
+ it('refreshes token on 401 and retries', async () => {
69
+ mockGetAccessToken.mockResolvedValue('expired-token');
70
+ mockGetRefreshToken.mockResolvedValue('refresh-123');
71
+ mockStoreCredentials.mockResolvedValue(undefined);
72
+
73
+ mockFetch
74
+ .mockResolvedValueOnce(makeResponse(401))
75
+ .mockResolvedValueOnce(
76
+ makeResponse(200, {
77
+ access_token: 'new-access',
78
+ refresh_token: 'new-refresh',
79
+ token_type: 'bearer',
80
+ expires_in: 3600,
81
+ })
82
+ )
83
+ .mockResolvedValueOnce(makeResponse(200, { ok: true }));
84
+
85
+ const res = await authenticatedFetch('http://test/api');
86
+
87
+ expect(res.status).toBe(200);
88
+ expect(mockFetch).toHaveBeenCalledTimes(3);
89
+ expect(mockFetch).toHaveBeenLastCalledWith('http://test/api', {
90
+ headers: { Authorization: 'Bearer new-access' },
91
+ });
92
+ });
93
+
94
+ it('falls back to env token when refresh fails', async () => {
95
+ mockGetAccessToken.mockResolvedValue('expired-token');
96
+ mockGetRefreshToken.mockResolvedValue('bad-refresh');
97
+ vi.stubEnv('MINTLIFY_SESSION_TOKEN', 'env-token');
98
+
99
+ mockFetch
100
+ .mockResolvedValueOnce(makeResponse(401))
101
+ .mockResolvedValueOnce(makeResponse(400, { error: 'invalid_grant' }))
102
+ .mockResolvedValueOnce(makeResponse(200, { ok: true }));
103
+
104
+ const res = await authenticatedFetch('http://test/api');
105
+
106
+ expect(res.status).toBe(200);
107
+ expect(mockFetch).toHaveBeenCalledTimes(3);
108
+ expect(mockFetch).toHaveBeenLastCalledWith('http://test/api', {
109
+ headers: { Authorization: 'Bearer env-token' },
110
+ });
111
+ });
112
+
113
+ it('returns original 401 when refresh fails and no env token', async () => {
114
+ mockGetAccessToken.mockResolvedValue('expired-token');
115
+ mockGetRefreshToken.mockResolvedValue('bad-refresh');
116
+
117
+ mockFetch
118
+ .mockResolvedValueOnce(makeResponse(401))
119
+ .mockResolvedValueOnce(makeResponse(400, { error: 'invalid_grant' }));
120
+
121
+ const res = await authenticatedFetch('http://test/api');
122
+
123
+ expect(res.status).toBe(401);
124
+ expect(mockFetch).toHaveBeenCalledTimes(2);
125
+ });
126
+
127
+ it('falls back to env token when no refresh token exists', async () => {
128
+ mockGetAccessToken.mockResolvedValue('expired-token');
129
+ mockGetRefreshToken.mockResolvedValue(null);
130
+ vi.stubEnv('MINTLIFY_SESSION_TOKEN', 'env-token');
131
+
132
+ mockFetch
133
+ .mockResolvedValueOnce(makeResponse(401))
134
+ .mockResolvedValueOnce(makeResponse(200, { ok: true }));
135
+
136
+ const res = await authenticatedFetch('http://test/api');
137
+
138
+ expect(res.status).toBe(200);
139
+ expect(mockFetch).toHaveBeenCalledTimes(2);
140
+ expect(mockFetch).toHaveBeenLastCalledWith('http://test/api', {
141
+ headers: { Authorization: 'Bearer env-token' },
142
+ });
143
+ });
144
+
145
+ it('returns original 401 when no refresh token and no env token', async () => {
146
+ mockGetAccessToken.mockResolvedValue('expired-token');
147
+ mockGetRefreshToken.mockResolvedValue(null);
148
+
149
+ mockFetch.mockResolvedValueOnce(makeResponse(401));
150
+
151
+ const res = await authenticatedFetch('http://test/api');
152
+
153
+ expect(res.status).toBe(401);
154
+ expect(mockFetch).toHaveBeenCalledTimes(1);
155
+ });
156
+
157
+ it('does not attempt refresh when only env token is available', async () => {
158
+ mockGetAccessToken.mockResolvedValue(null);
159
+ vi.stubEnv('MINTLIFY_SESSION_TOKEN', 'env-token');
160
+
161
+ mockFetch.mockResolvedValueOnce(makeResponse(401));
162
+
163
+ const res = await authenticatedFetch('http://test/api');
164
+
165
+ expect(res.status).toBe(401);
166
+ expect(mockGetRefreshToken).not.toHaveBeenCalled();
167
+ expect(mockFetch).toHaveBeenCalledTimes(1);
168
+ });
169
+
170
+ it('merges custom headers with auth header', async () => {
171
+ mockGetAccessToken.mockResolvedValue('token-123');
172
+ mockFetch.mockResolvedValueOnce(makeResponse(200));
173
+
174
+ await authenticatedFetch('http://test/api', {
175
+ headers: { Accept: 'application/json' },
176
+ });
177
+
178
+ expect(mockFetch).toHaveBeenCalledWith('http://test/api', {
179
+ headers: { Accept: 'application/json', Authorization: 'Bearer token-123' },
180
+ });
181
+ });
182
+ });
@@ -33,6 +33,13 @@ describe('keyring', () => {
33
33
  expect(mockKeytar.getPassword).toHaveBeenCalledWith('mintlify', 'access_token');
34
34
  });
35
35
 
36
+ it('retrieves the refresh token via keytar', async () => {
37
+ const { getRefreshToken } = await import('../src/keyring.js');
38
+ const token = await getRefreshToken();
39
+ expect(token).toBe('test-token');
40
+ expect(mockKeytar.getPassword).toHaveBeenCalledWith('mintlify', 'refresh_token');
41
+ });
42
+
36
43
  it('deletes both tokens via keytar', async () => {
37
44
  const { clearCredentials } = await import('../src/keyring.js');
38
45
  await clearCredentials();
@@ -7,24 +7,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
+ import { authenticatedFetch } from '../authenticatedFetch.js';
10
11
  import { API_URL } from '../constants.js';
11
- function getAuthHeaders() {
12
- return __awaiter(this, void 0, void 0, function* () {
13
- try {
14
- const { getAccessToken } = yield import('../keyring.js');
15
- const token = yield getAccessToken();
16
- if (token) {
17
- return { Authorization: `Bearer ${token}` };
18
- }
19
- }
20
- catch (_a) { }
21
- const envToken = process.env.MINTLIFY_SESSION_TOKEN;
22
- if (envToken) {
23
- return { Authorization: `Bearer ${envToken}` };
24
- }
25
- throw new Error('Not authenticated. Run `mint login` to authenticate.');
26
- });
27
- }
28
12
  function request(path_1) {
29
13
  return __awaiter(this, arguments, void 0, function* (path, params = {}) {
30
14
  const url = new URL(`${API_URL}/api/cli/analytics${path}`);
@@ -32,9 +16,8 @@ function request(path_1) {
32
16
  if (value !== undefined)
33
17
  url.searchParams.set(key, String(value));
34
18
  }
35
- const authHeaders = yield getAuthHeaders();
36
- const res = yield fetch(url, {
37
- headers: Object.assign(Object.assign({}, authHeaders), { Accept: 'application/json' }),
19
+ const res = yield authenticatedFetch(url.toString(), {
20
+ headers: { Accept: 'application/json' },
38
21
  });
39
22
  if (!res.ok) {
40
23
  const body = yield res.text().catch(() => '');
@@ -0,0 +1,46 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { getAccessToken } from './keyring.js';
11
+ import { refreshAccessToken } from './tokenRefresh.js';
12
+ function getKeyringToken() {
13
+ return __awaiter(this, void 0, void 0, function* () {
14
+ try {
15
+ return yield getAccessToken();
16
+ }
17
+ catch (_a) {
18
+ return null;
19
+ }
20
+ });
21
+ }
22
+ export function authenticatedFetch(url, init) {
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ const keyringToken = yield getKeyringToken();
25
+ const envToken = process.env.MINTLIFY_SESSION_TOKEN;
26
+ const token = keyringToken !== null && keyringToken !== void 0 ? keyringToken : envToken;
27
+ if (!token) {
28
+ throw new Error('Not authenticated. Run `mint login` to authenticate.');
29
+ }
30
+ const makeRequest = (t) => fetch(url, Object.assign(Object.assign({}, init), { headers: Object.assign(Object.assign({}, init === null || init === void 0 ? void 0 : init.headers), { Authorization: `Bearer ${t}` }) }));
31
+ const res = yield makeRequest(token);
32
+ if (res.status !== 401)
33
+ return res;
34
+ // If the keyring token expired, try refreshing it
35
+ if (keyringToken) {
36
+ const refreshed = yield refreshAccessToken();
37
+ if (refreshed)
38
+ return makeRequest(refreshed);
39
+ }
40
+ // Fall back to the env session token if it wasn't already used
41
+ if (envToken && token !== envToken) {
42
+ return makeRequest(envToken);
43
+ }
44
+ return res;
45
+ });
46
+ }
package/bin/keyring.js CHANGED
@@ -38,6 +38,12 @@ export function getAccessToken() {
38
38
  return keytar.getPassword(SERVICE, ACCESS_TOKEN_ACCOUNT);
39
39
  });
40
40
  }
41
+ export function getRefreshToken() {
42
+ return __awaiter(this, void 0, void 0, function* () {
43
+ const keytar = yield getKeytar();
44
+ return keytar.getPassword(SERVICE, REFRESH_TOKEN_ACCOUNT);
45
+ });
46
+ }
41
47
  export function clearCredentials() {
42
48
  return __awaiter(this, void 0, void 0, function* () {
43
49
  const keytar = yield getKeytar();
package/bin/login.js CHANGED
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  import { input } from '@inquirer/prompts';
12
- import { addLog } from '@mintlify/previewing';
12
+ import { addLog, ErrorLog, SuccessLog } from '@mintlify/previewing';
13
13
  import chalk from 'chalk';
14
14
  import { Box, Text } from 'ink';
15
15
  import open from 'open';
@@ -32,7 +32,7 @@ export function login() {
32
32
  const url = authorizeUrl.toString();
33
33
  void trackLoginAttempt();
34
34
  const { codePromise, close: closeServer } = yield startCallbackServer();
35
- addLog(_jsxs(Box, { flexDirection: "column", gap: 1, paddingY: 1, children: [_jsxs(Text, { bold: true, children: [_jsx(Text, { color: "green", children: "\u25C6 " }), "A browser window will open for Mintlify authentication"] }), _jsxs(Box, { flexDirection: "column", paddingLeft: 3, gap: 1, children: [_jsx(Text, { dimColor: true, children: "If your browser doesn't open automatically, copy this URL:" }), _jsx(Text, { color: "cyan", children: url })] })] }));
35
+ addLog(_jsxs(Box, { flexDirection: "column", gap: 1, paddingY: 1, children: [_jsxs(Text, { bold: true, children: [_jsx(Text, { color: "green", children: "\u25C6 " }), "A browser window will open for Mintlify authentication"] }), _jsxs(Box, { flexDirection: "column", paddingLeft: 3, gap: 1, children: [_jsx(Text, { dimColor: true, children: "If your browser doesn't open automatically, copy this URL:" }), _jsx(Text, { dimColor: true, children: url })] })] }));
36
36
  open(url).catch(() => { });
37
37
  addLog(_jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "\u256D\u2500 Paste the authorization code from your browser" }), _jsx(Text, { dimColor: true, children: "\u2502" })] }));
38
38
  // Let ink finish rendering before inquirer takes over stdout
@@ -53,7 +53,7 @@ export function login() {
53
53
  catch (_c) {
54
54
  closeServer();
55
55
  inputPromise.cancel();
56
- addLog(_jsx(Text, { color: "red", children: "\u2716 Login cancelled." }));
56
+ addLog(_jsx(ErrorLog, { message: "login cancelled" }));
57
57
  return;
58
58
  }
59
59
  closeServer();
@@ -73,12 +73,12 @@ export function login() {
73
73
  if (!res.ok) {
74
74
  const reason = (_b = (_a = body.error_message) !== null && _a !== void 0 ? _a : body.error) !== null && _b !== void 0 ? _b : 'unknown error';
75
75
  void trackLoginFailed(reason);
76
- addLog(_jsxs(Text, { color: "red", children: ["\u2716 Login failed: ", reason] }));
76
+ addLog(_jsx(ErrorLog, { message: `login failed: ${reason}` }));
77
77
  return;
78
78
  }
79
79
  const token = body;
80
80
  yield storeCredentials(token.access_token, token.refresh_token);
81
81
  void trackLoginSuccess();
82
- addLog(_jsx(Text, { color: "green", children: "\u2714 Logged in successfully." }));
82
+ addLog(_jsx(SuccessLog, { message: "logged in successfully" }));
83
83
  });
84
84
  }
package/bin/logout.js CHANGED
@@ -8,12 +8,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { jsx as _jsx } from "react/jsx-runtime";
11
- import { addLog } from '@mintlify/previewing';
12
- import { Text } from 'ink';
11
+ import { addLog, SuccessLog } from '@mintlify/previewing';
13
12
  import { clearCredentials } from './keyring.js';
14
13
  export function logout() {
15
14
  return __awaiter(this, void 0, void 0, function* () {
16
15
  yield clearCredentials();
17
- addLog(_jsx(Text, { color: "green", children: "Logged out successfully." }));
16
+ addLog(_jsx(SuccessLog, { message: "logged out successfully" }));
18
17
  });
19
18
  }
package/bin/status.js CHANGED
@@ -8,10 +8,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
- import { addLog } from '@mintlify/previewing';
12
- import { Text } from 'ink';
11
+ import { addLog, ErrorLog } from '@mintlify/previewing';
12
+ import { Box, Text } from 'ink';
13
13
  import { z } from 'zod';
14
+ import { authenticatedFetch } from './authenticatedFetch.js';
15
+ import { getConfigValue } from './config.js';
14
16
  import { API_URL } from './constants.js';
17
+ import { getCliVersion } from './helpers.js';
15
18
  import { getAccessToken } from './keyring.js';
16
19
  const StatusResponseSchema = z.object({
17
20
  user: z.object({ email: z.string() }),
@@ -38,30 +41,31 @@ export function getCliSubdomain(accessToken) {
38
41
  }
39
42
  export function status() {
40
43
  return __awaiter(this, void 0, void 0, function* () {
44
+ var _a, _b;
41
45
  const accessToken = yield getAccessToken();
42
46
  if (!accessToken) {
43
- addLog(_jsx(Text, { color: "red", children: "Not logged in. Run `mint login` to authenticate." }));
47
+ addLog(_jsx(ErrorLog, { message: "not logged in. Run `mint login` to authenticate." }));
44
48
  return;
45
49
  }
46
50
  try {
47
- const res = yield fetch(`${API_URL}/api/cli/status`, {
48
- headers: { Authorization: `Bearer ${accessToken}` },
49
- });
51
+ const res = yield authenticatedFetch(`${API_URL}/api/cli/status`);
50
52
  if (!res.ok) {
51
- addLog(_jsx(Text, { color: "red", children: "Not logged in. Run `mint login` to authenticate." }));
53
+ addLog(_jsx(ErrorLog, { message: "not logged in. Run `mint login` to authenticate." }));
52
54
  return;
53
55
  }
54
56
  const json = yield res.json().catch(() => null);
55
57
  const parsed = StatusResponseSchema.safeParse(json);
56
58
  if (!parsed.success) {
57
- addLog(_jsx(Text, { color: "red", children: "Unexpected response from server. Please try again." }));
59
+ addLog(_jsx(ErrorLog, { message: "unexpected response from server. please try again." }));
58
60
  return;
59
61
  }
60
- const { user, org } = parsed.data;
61
- addLog(_jsxs(Text, { children: ["Logged in as ", _jsx(Text, { color: "green", children: user.email }), " in org", ' ', _jsx(Text, { color: "green", children: org.name })] }));
62
+ const { user, org, subdomain: apiSubdomain } = parsed.data;
63
+ const version = getCliVersion();
64
+ const subdomain = (_b = (_a = getConfigValue('subdomain')) !== null && _a !== void 0 ? _a : apiSubdomain) !== null && _b !== void 0 ? _b : null;
65
+ addLog(_jsxs(Box, { flexDirection: "column", paddingY: 1, children: [version && (_jsxs(Box, { children: [_jsx(Box, { minWidth: 16, children: _jsx(Text, { dimColor: true, children: "Version" }) }), _jsx(Text, { children: version })] })), _jsxs(Box, { children: [_jsx(Box, { minWidth: 16, children: _jsx(Text, { dimColor: true, children: "Email" }) }), _jsx(Text, { children: user.email })] }), _jsxs(Box, { children: [_jsx(Box, { minWidth: 16, children: _jsx(Text, { dimColor: true, children: "Organization" }) }), _jsx(Text, { children: org.name })] }), subdomain && (_jsxs(Box, { children: [_jsx(Box, { minWidth: 16, children: _jsx(Text, { dimColor: true, children: "Subdomain" }) }), _jsx(Text, { children: subdomain })] }))] }));
62
66
  }
63
67
  catch (e) {
64
- addLog(_jsx(Text, { color: "red", children: "Unexpected response from server. Please try again." }));
68
+ addLog(_jsx(ErrorLog, { message: "unexpected response from server. please try again." }));
65
69
  }
66
70
  });
67
71
  }
@@ -0,0 +1,50 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { STYTCH_CLIENT_ID, TOKEN_ENDPOINT } from './constants.js';
11
+ import { getRefreshToken, storeCredentials } from './keyring.js';
12
+ let inflightRefresh = null;
13
+ export function refreshAccessToken() {
14
+ return __awaiter(this, void 0, void 0, function* () {
15
+ if (inflightRefresh)
16
+ return inflightRefresh;
17
+ inflightRefresh = doRefresh().finally(() => {
18
+ inflightRefresh = null;
19
+ });
20
+ return inflightRefresh;
21
+ });
22
+ }
23
+ function doRefresh() {
24
+ return __awaiter(this, void 0, void 0, function* () {
25
+ try {
26
+ const refreshToken = yield getRefreshToken();
27
+ if (!refreshToken)
28
+ return null;
29
+ const res = yield fetch(TOKEN_ENDPOINT, {
30
+ method: 'POST',
31
+ headers: { 'Content-Type': 'application/json' },
32
+ body: JSON.stringify({
33
+ client_id: STYTCH_CLIENT_ID,
34
+ grant_type: 'refresh_token',
35
+ refresh_token: refreshToken,
36
+ }),
37
+ });
38
+ if (!res.ok)
39
+ return null;
40
+ const body = (yield res.json().catch(() => null));
41
+ if (!(body === null || body === void 0 ? void 0 : body.access_token) || !body.refresh_token)
42
+ return null;
43
+ yield storeCredentials(body.access_token, body.refresh_token);
44
+ return body.access_token;
45
+ }
46
+ catch (_a) {
47
+ return null;
48
+ }
49
+ });
50
+ }