@mintlify/cli 4.0.1098 → 4.0.1100

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
  });
@@ -5,6 +5,11 @@ import {
5
5
  formatPrettyTable,
6
6
  resolveFormat,
7
7
  } from '../../src/analytics/output.js';
8
+ import * as helpers from '../../src/helpers.js';
9
+
10
+ afterEach(() => {
11
+ vi.restoreAllMocks();
12
+ });
8
13
 
9
14
  describe('num', () => {
10
15
  it('formats numbers with locale separators', () => {
@@ -81,31 +86,21 @@ describe('formatPlainTable', () => {
81
86
  });
82
87
 
83
88
  describe('resolveFormat', () => {
84
- it('returns json when agent flag is set', () => {
85
- expect(resolveFormat({ agent: true })).toBe('json');
86
- });
87
-
88
- it('returns json when CLAUDECODE env is set', () => {
89
- const prev = process.env.CLAUDECODE;
90
- process.env.CLAUDECODE = '1';
89
+ it('returns json when AI mode is active', () => {
90
+ vi.spyOn(helpers, 'isAI').mockReturnValue(true);
91
91
  expect(resolveFormat({})).toBe('json');
92
- process.env.CLAUDECODE = prev;
93
92
  });
94
93
 
95
94
  it('returns specified format', () => {
96
- const prev = process.env.CLAUDECODE;
97
- delete process.env.CLAUDECODE;
95
+ vi.spyOn(helpers, 'isAI').mockReturnValue(true);
98
96
  expect(resolveFormat({ format: 'plain' })).toBe('plain');
99
97
  expect(resolveFormat({ format: 'json' })).toBe('json');
100
98
  expect(resolveFormat({ format: 'graph' })).toBe('graph');
101
- process.env.CLAUDECODE = prev;
102
99
  });
103
100
 
104
101
  it('defaults to plain', () => {
105
- const prev = process.env.CLAUDECODE;
106
- delete process.env.CLAUDECODE;
102
+ vi.spyOn(helpers, 'isAI').mockReturnValue(false);
107
103
  expect(resolveFormat({})).toBe('plain');
108
- process.env.CLAUDECODE = prev;
109
104
  });
110
105
  });
111
106
 
@@ -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();
@@ -4,11 +4,11 @@ import os from 'os';
4
4
 
5
5
  import { isTelemetryEnabled, setTelemetryEnabled } from '../src/config.js';
6
6
  import { TELEMETRY_ASYNC_TIMEOUT_MS } from '../src/constants.js';
7
- import { getDistinctId } from '../src/telemetry/distinctId.js';
8
7
  import {
9
8
  createTelemetryMiddleware,
10
9
  getSanitizedCommandForTelemetry,
11
- } from '../src/telemetry/middleware.js';
10
+ } from '../src/middlewares/telemetryMiddleware.js';
11
+ import { getDistinctId } from '../src/telemetry/distinctId.js';
12
12
  import * as trackModule from '../src/telemetry/track.js';
13
13
  import { trackCommand, trackTelemetryPreferenceChange } from '../src/telemetry/track.js';
14
14
 
@@ -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(() => '');
@@ -13,12 +13,12 @@ import chalk from 'chalk';
13
13
  import { Text } from 'ink';
14
14
  import { getConfigValue } from '../config.js';
15
15
  import { terminate } from '../helpers.js';
16
+ import { subdomainMiddleware } from '../middlewares/subdomainMiddleware.js';
16
17
  import { getBucketThreads, getBuckets, getConversations, getFeedback, getFeedbackByPage, getKpi, getSearches, } from './client.js';
17
18
  import { num, truncate } from './format.js';
18
19
  import { formatBarChart, formatOutput, resolveFormat } from './output.js';
19
20
  const withSubdomain = (yargs) => yargs.option('subdomain', {
20
21
  type: 'string',
21
- default: getConfigValue('subdomain'),
22
22
  description: 'Documentation subdomain (default: mint config set subdomain)',
23
23
  });
24
24
  function defaultFrom() {
@@ -44,15 +44,10 @@ const withDates = (yargs) => yargs
44
44
  default: defaultTo(),
45
45
  description: 'End date (YYYY-MM-DD)',
46
46
  });
47
- const withFormat = (yargs) => yargs
48
- .option('format', {
47
+ const withFormat = (yargs) => yargs.option('format', {
49
48
  type: 'string',
50
49
  choices: ['table', 'plain', 'json', 'graph'],
51
50
  description: 'Output format (table=pretty, plain=pipeable, json=raw)',
52
- })
53
- .option('agent', {
54
- type: 'boolean',
55
- description: 'Agent-friendly output (equivalent to --format json)',
56
51
  });
57
52
  const withAll = (yargs) => withFormat(withDates(withSubdomain(yargs)));
58
53
  function output(format, text) {
@@ -63,11 +58,12 @@ function output(format, text) {
63
58
  process.stdout.write(text + '\n');
64
59
  }
65
60
  }
66
- export const analyticsBuilder = (yargs) => yargs
67
- .command('stats', 'display KPI numbers (views, visitors, searches)', (yargs) => withAll(yargs)
68
- .option('agents', { type: 'boolean', description: 'Show only agent traffic' })
69
- .option('humans', { type: 'boolean', description: 'Show only human traffic' })
70
- .option('page', { type: 'string', description: 'Filter to a specific page path' }), (argv) => __awaiter(void 0, void 0, void 0, function* () {
61
+ export const analyticsBuilder = (yargs) => withAll(yargs)
62
+ .middleware(subdomainMiddleware)
63
+ .command('stats', 'display KPI numbers (views, visitors, searches)', (yargs) => withAll(yargs).option('page', {
64
+ type: 'string',
65
+ description: 'Filter to a specific page path',
66
+ }), (argv) => __awaiter(void 0, void 0, void 0, function* () {
71
67
  var _a, _b;
72
68
  const format = resolveFormat(argv);
73
69
  try {
@@ -105,7 +101,7 @@ export const analyticsBuilder = (yargs) => yargs
105
101
  return;
106
102
  }
107
103
  if (format === 'graph') {
108
- const label = (_a = argv.subdomain) !== null && _a !== void 0 ? _a : 'default';
104
+ const label = (_a = argv.subdomain) !== null && _a !== void 0 ? _a : '';
109
105
  const lines = [];
110
106
  lines.push(chalk.bold(`\nAnalytics \u2014 ${label} (${argv.from} to ${argv.to})\n`));
111
107
  lines.push(chalk.bold(' Human vs Agent\n'));
@@ -126,31 +122,20 @@ export const analyticsBuilder = (yargs) => yargs
126
122
  yield terminate(0);
127
123
  return;
128
124
  }
129
- const agentOnly = argv.agents && !argv.humans;
130
- const humanOnly = argv.humans && !argv.agents;
131
- const showHuman = !agentOnly;
132
- const showAgent = !humanOnly;
133
- const showTotal = showHuman && showAgent;
134
125
  const lines = [];
135
- const label = (_b = argv.subdomain) !== null && _b !== void 0 ? _b : 'default';
126
+ const label = (_b = argv.subdomain) !== null && _b !== void 0 ? _b : '';
136
127
  lines.push(chalk.bold(`\nAnalytics \u2014 ${label} (${argv.from} to ${argv.to})\n`));
137
128
  if (argv.page) {
138
129
  lines.push(` Page: ${argv.page}\n`);
139
130
  }
140
131
  lines.push(chalk.bold(' Views'));
141
- if (showHuman)
142
- lines.push(` Human: ${chalk.cyan(num(kpi.humanViews).padStart(8))}`);
143
- if (showAgent)
144
- lines.push(` Agent: ${chalk.magenta(num(kpi.agentViews).padStart(8))}`);
145
- if (showTotal)
146
- lines.push(` Total: ${num(kpi.humanViews + kpi.agentViews).padStart(8)}`);
132
+ lines.push(` Human: ${chalk.cyan(num(kpi.humanViews).padStart(8))}`);
133
+ lines.push(` Agent: ${chalk.magenta(num(kpi.agentViews).padStart(8))}`);
134
+ lines.push(` Total: ${num(kpi.humanViews + kpi.agentViews).padStart(8)}`);
147
135
  lines.push(chalk.bold('\n Visitors'));
148
- if (showHuman)
149
- lines.push(` Human: ${chalk.cyan(num(kpi.humanVisitors).padStart(8))}`);
150
- if (showAgent)
151
- lines.push(` Agent: ${chalk.magenta(num(kpi.agentVisitors).padStart(8))}`);
152
- if (showTotal)
153
- lines.push(` Total: ${num(kpi.humanVisitors + kpi.agentVisitors).padStart(8)}`);
136
+ lines.push(` Human: ${chalk.cyan(num(kpi.humanVisitors).padStart(8))}`);
137
+ lines.push(` Agent: ${chalk.magenta(num(kpi.agentVisitors).padStart(8))}`);
138
+ lines.push(` Total: ${num(kpi.humanVisitors + kpi.agentVisitors).padStart(8)}`);
154
139
  lines.push(`\n Searches: ${chalk.bold(num(kpi.humanSearches))}`);
155
140
  lines.push(` Feedback: ${chalk.bold(num(kpi.humanFeedback))}`);
156
141
  lines.push(` Assistant: ${chalk.bold(num(kpi.humanAssistant))} web, ${chalk.bold(num(kpi.agentMcpSearches))} API`);
@@ -195,7 +180,7 @@ export const analyticsBuilder = (yargs) => yargs
195
180
  output(format, JSON.stringify(data, null, 2));
196
181
  }
197
182
  else if (format === 'graph') {
198
- const label = (_a = argv.subdomain) !== null && _a !== void 0 ? _a : 'default';
183
+ const label = (_a = argv.subdomain) !== null && _a !== void 0 ? _a : '';
199
184
  const lines = [];
200
185
  lines.push(chalk.bold(`\nSearch Queries \u2014 ${label} (${argv.from} to ${argv.to})\n`));
201
186
  lines.push(formatBarChart(rows.slice(0, 20).map((r) => ({
@@ -209,7 +194,7 @@ export const analyticsBuilder = (yargs) => yargs
209
194
  output(format, formatOutput(format, headers, tableRows, data));
210
195
  }
211
196
  else {
212
- const label = (_b = argv.subdomain) !== null && _b !== void 0 ? _b : 'default';
197
+ const label = (_b = argv.subdomain) !== null && _b !== void 0 ? _b : '';
213
198
  const lines = [];
214
199
  lines.push(chalk.bold(`\nSearch Analytics \u2014 ${label} (${argv.from} to ${argv.to})`));
215
200
  lines.push(`Total Searches: ${chalk.bold(num(data.totalSearches))}\n`);
@@ -256,7 +241,7 @@ export const analyticsBuilder = (yargs) => yargs
256
241
  output(format, JSON.stringify(data, null, 2));
257
242
  }
258
243
  else if (format === 'graph') {
259
- const label = (_a = argv.subdomain) !== null && _a !== void 0 ? _a : 'default';
244
+ const label = (_a = argv.subdomain) !== null && _a !== void 0 ? _a : '';
260
245
  const lines = [];
261
246
  lines.push(chalk.bold(`\nFeedback by Page \u2014 ${label} (${argv.from} to ${argv.to})\n`));
262
247
  lines.push(formatBarChart(rows.slice(0, 20).map((r) => ({
@@ -267,7 +252,7 @@ export const analyticsBuilder = (yargs) => yargs
267
252
  output('table', lines.join('\n'));
268
253
  }
269
254
  else {
270
- const label = (_b = argv.subdomain) !== null && _b !== void 0 ? _b : 'default';
255
+ const label = (_b = argv.subdomain) !== null && _b !== void 0 ? _b : '';
271
256
  const lines = [];
272
257
  if (format === 'table')
273
258
  lines.push(chalk.bold(`\nFeedback \u2014 ${label} (${argv.from} to ${argv.to})\n`));
@@ -301,7 +286,7 @@ export const analyticsBuilder = (yargs) => yargs
301
286
  output(format, JSON.stringify(data, null, 2));
302
287
  }
303
288
  else {
304
- const label = (_c = argv.subdomain) !== null && _c !== void 0 ? _c : 'default';
289
+ const label = (_c = argv.subdomain) !== null && _c !== void 0 ? _c : '';
305
290
  const lines = [];
306
291
  if (format === 'table')
307
292
  lines.push(chalk.bold(`\nFeedback \u2014 ${label} (${argv.from} to ${argv.to})\n`));
@@ -348,7 +333,7 @@ export const analyticsBuilder = (yargs) => yargs
348
333
  output(format, JSON.stringify(data, null, 2));
349
334
  }
350
335
  else {
351
- const label = (_a = argv.subdomain) !== null && _a !== void 0 ? _a : 'default';
336
+ const label = (_a = argv.subdomain) !== null && _a !== void 0 ? _a : '';
352
337
  const lines = [];
353
338
  if (format === 'table')
354
339
  lines.push(chalk.bold(`\nConversations \u2014 ${label} (${argv.from} to ${argv.to})\n`));
@@ -449,7 +434,7 @@ export const analyticsBuilder = (yargs) => yargs
449
434
  output(format, JSON.stringify(data, null, 2));
450
435
  }
451
436
  else if (format === 'graph') {
452
- const label = (_a = argv.subdomain) !== null && _a !== void 0 ? _a : 'default';
437
+ const label = (_a = argv.subdomain) !== null && _a !== void 0 ? _a : '';
453
438
  const lines = [];
454
439
  lines.push(chalk.bold(`\nConversation Buckets \u2014 ${label} (${argv.from} to ${argv.to})\n`));
455
440
  lines.push(formatBarChart(data.data.slice(0, 20).map((b) => ({
@@ -460,7 +445,7 @@ export const analyticsBuilder = (yargs) => yargs
460
445
  output('table', lines.join('\n'));
461
446
  }
462
447
  else {
463
- const label = (_b = argv.subdomain) !== null && _b !== void 0 ? _b : 'default';
448
+ const label = (_b = argv.subdomain) !== null && _b !== void 0 ? _b : '';
464
449
  const lines = [];
465
450
  if (format === 'table')
466
451
  lines.push(chalk.bold(`\nConversation Buckets \u2014 ${label} (${argv.from} to ${argv.to})\n`));
@@ -1,12 +1,13 @@
1
1
  import chalk from 'chalk';
2
+ import { isAI } from '../helpers.js';
2
3
  export function resolveFormat(argv) {
3
- if (argv.agent || process.env.CLAUDECODE === '1')
4
- return 'json';
5
4
  if (argv.format === 'table' ||
6
5
  argv.format === 'plain' ||
7
6
  argv.format === 'json' ||
8
7
  argv.format === 'graph')
9
8
  return argv.format;
9
+ if (isAI())
10
+ return 'json';
10
11
  return 'plain';
11
12
  }
12
13
  export function formatPlainTable(headers, rows) {
@@ -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
+ }