@optima-chat/bi-cli 0.3.3 → 0.3.4

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.
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createTrafficCommand(): Command;
3
+ //# sourceMappingURL=traffic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traffic.d.ts","sourceRoot":"","sources":["../../src/commands/traffic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsEpC,wBAAgB,oBAAoB,IAAI,OAAO,CA2Q9C"}
@@ -0,0 +1,243 @@
1
+ import { Command } from 'commander';
2
+ import axios from 'axios';
3
+ import { getConfig } from '../config/index.js';
4
+ import { outputJson, outputPretty, error, OutputFormat } from '../utils/output.js';
5
+ function formatPercent(value) {
6
+ return `${(value * 100).toFixed(1)}%`;
7
+ }
8
+ function formatChange(value) {
9
+ const sign = value >= 0 ? '+' : '';
10
+ return `${sign}${(value * 100).toFixed(1)}%`;
11
+ }
12
+ function formatDuration(seconds) {
13
+ const mins = Math.floor(seconds / 60);
14
+ const secs = seconds % 60;
15
+ return mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
16
+ }
17
+ export function createTrafficCommand() {
18
+ const traffic = new Command('traffic').description('Traffic analytics');
19
+ // traffic overview
20
+ traffic
21
+ .command('overview')
22
+ .description('Get traffic overview')
23
+ .option('--days <number>', 'Number of days', '30')
24
+ .option('--product <id>', 'Filter by product ID')
25
+ .option('--pretty', 'Output in pretty format (default: JSON)')
26
+ .action(async (options) => {
27
+ const cfg = getConfig();
28
+ if (!cfg.accessToken) {
29
+ error('Not logged in. Run: bi-cli auth login');
30
+ process.exit(1);
31
+ }
32
+ const days = parseInt(options.days, 10);
33
+ if (isNaN(days) || days < 1 || days > 365) {
34
+ error('Days must be between 1 and 365');
35
+ process.exit(1);
36
+ }
37
+ const format = options.pretty ? OutputFormat.PRETTY : OutputFormat.JSON;
38
+ try {
39
+ const response = await axios.get(`${cfg.backendUrl}/api/v1/analytics/traffic/overview`, {
40
+ params: { days, product_id: options.product },
41
+ headers: { Authorization: `Bearer ${cfg.accessToken}` },
42
+ });
43
+ const data = response.data;
44
+ if (format === OutputFormat.JSON) {
45
+ outputJson(data);
46
+ }
47
+ else {
48
+ console.log('\nšŸ“Š Traffic Overview\n');
49
+ console.log(`Period: ${data.period.start} to ${data.period.end} (${data.period.days} days)\n`);
50
+ outputPretty({
51
+ 'Page Views': `${data.summary.page_views.toLocaleString()} (${formatChange(data.comparison.page_views_change)})`,
52
+ 'Unique Visitors': `${data.summary.unique_visitors.toLocaleString()} (${formatChange(data.comparison.unique_visitors_change)})`,
53
+ Sessions: `${data.summary.sessions.toLocaleString()} (${formatChange(data.comparison.sessions_change)})`,
54
+ 'Avg Session Duration': formatDuration(data.summary.avg_session_duration),
55
+ 'Bounce Rate': formatPercent(data.summary.bounce_rate),
56
+ });
57
+ }
58
+ }
59
+ catch (err) {
60
+ handleError(err);
61
+ }
62
+ });
63
+ // traffic sources
64
+ traffic
65
+ .command('sources')
66
+ .description('Get traffic sources')
67
+ .option('--days <number>', 'Number of days', '30')
68
+ .option('--limit <number>', 'Limit results', '10')
69
+ .option('--pretty', 'Output in pretty format (default: JSON)')
70
+ .action(async (options) => {
71
+ const cfg = getConfig();
72
+ if (!cfg.accessToken) {
73
+ error('Not logged in. Run: bi-cli auth login');
74
+ process.exit(1);
75
+ }
76
+ const days = parseInt(options.days, 10);
77
+ const limit = parseInt(options.limit, 10);
78
+ const format = options.pretty ? OutputFormat.PRETTY : OutputFormat.JSON;
79
+ try {
80
+ const response = await axios.get(`${cfg.backendUrl}/api/v1/analytics/traffic/sources`, {
81
+ params: { days, limit },
82
+ headers: { Authorization: `Bearer ${cfg.accessToken}` },
83
+ });
84
+ const { sources } = response.data;
85
+ if (format === OutputFormat.JSON) {
86
+ outputJson(response.data);
87
+ }
88
+ else {
89
+ console.log('\nšŸ”— Traffic Sources\n');
90
+ outputPretty(sources.map((s) => ({
91
+ Source: s.source,
92
+ Medium: s.medium,
93
+ Visitors: s.visitors.toLocaleString(),
94
+ 'Page Views': s.page_views.toLocaleString(),
95
+ Share: formatPercent(s.percentage),
96
+ })), ['Source', 'Medium', 'Visitors', 'Page Views', 'Share']);
97
+ }
98
+ }
99
+ catch (err) {
100
+ handleError(err);
101
+ }
102
+ });
103
+ // traffic funnel
104
+ traffic
105
+ .command('funnel')
106
+ .description('Get conversion funnel')
107
+ .option('--days <number>', 'Number of days', '30')
108
+ .option('--product <id>', 'Filter by product ID')
109
+ .option('--pretty', 'Output in pretty format (default: JSON)')
110
+ .action(async (options) => {
111
+ const cfg = getConfig();
112
+ if (!cfg.accessToken) {
113
+ error('Not logged in. Run: bi-cli auth login');
114
+ process.exit(1);
115
+ }
116
+ const days = parseInt(options.days, 10);
117
+ const format = options.pretty ? OutputFormat.PRETTY : OutputFormat.JSON;
118
+ try {
119
+ const response = await axios.get(`${cfg.backendUrl}/api/v1/analytics/traffic/funnel`, {
120
+ params: { days, product_id: options.product },
121
+ headers: { Authorization: `Bearer ${cfg.accessToken}` },
122
+ });
123
+ const { funnel } = response.data;
124
+ if (format === OutputFormat.JSON) {
125
+ outputJson(response.data);
126
+ }
127
+ else {
128
+ console.log('\nšŸ”» Conversion Funnel\n');
129
+ outputPretty(funnel.map((f) => ({
130
+ Step: f.step.replace(/_/g, ' '),
131
+ Users: f.count.toLocaleString(),
132
+ 'From Start': formatPercent(f.rate),
133
+ Conversion: formatPercent(f.conversion),
134
+ })), ['Step', 'Users', 'From Start', 'Conversion']);
135
+ }
136
+ }
137
+ catch (err) {
138
+ handleError(err);
139
+ }
140
+ });
141
+ // traffic search
142
+ traffic
143
+ .command('search')
144
+ .description('Get search analytics')
145
+ .option('--days <number>', 'Number of days', '30')
146
+ .option('--limit <number>', 'Limit results', '20')
147
+ .option('--zero-results', 'Show only zero results queries')
148
+ .option('--pretty', 'Output in pretty format (default: JSON)')
149
+ .action(async (options) => {
150
+ const cfg = getConfig();
151
+ if (!cfg.accessToken) {
152
+ error('Not logged in. Run: bi-cli auth login');
153
+ process.exit(1);
154
+ }
155
+ const days = parseInt(options.days, 10);
156
+ const limit = parseInt(options.limit, 10);
157
+ const format = options.pretty ? OutputFormat.PRETTY : OutputFormat.JSON;
158
+ try {
159
+ const response = await axios.get(`${cfg.backendUrl}/api/v1/analytics/traffic/search`, {
160
+ params: { days, limit, zero_results: options.zeroResults },
161
+ headers: { Authorization: `Bearer ${cfg.accessToken}` },
162
+ });
163
+ const { searches, zero_results } = response.data;
164
+ if (format === OutputFormat.JSON) {
165
+ outputJson(response.data);
166
+ }
167
+ else {
168
+ console.log('\nšŸ” Top Searches\n');
169
+ outputPretty(searches.map((s) => ({
170
+ Query: s.query,
171
+ Searches: s.count.toLocaleString(),
172
+ 'Avg Results': s.avg_results,
173
+ 'Click Rate': formatPercent(s.click_rate),
174
+ })), ['Query', 'Searches', 'Avg Results', 'Click Rate']);
175
+ if (zero_results.length > 0) {
176
+ console.log('\nāŒ Zero Results Queries\n');
177
+ outputPretty(zero_results.map((z) => ({
178
+ Query: z.query,
179
+ Count: z.count.toLocaleString(),
180
+ })), ['Query', 'Count']);
181
+ }
182
+ }
183
+ }
184
+ catch (err) {
185
+ handleError(err);
186
+ }
187
+ });
188
+ // traffic pages
189
+ traffic
190
+ .command('pages')
191
+ .description('Get top pages')
192
+ .option('--days <number>', 'Number of days', '30')
193
+ .option('--limit <number>', 'Limit results', '20')
194
+ .option('--pretty', 'Output in pretty format (default: JSON)')
195
+ .action(async (options) => {
196
+ const cfg = getConfig();
197
+ if (!cfg.accessToken) {
198
+ error('Not logged in. Run: bi-cli auth login');
199
+ process.exit(1);
200
+ }
201
+ const days = parseInt(options.days, 10);
202
+ const limit = parseInt(options.limit, 10);
203
+ const format = options.pretty ? OutputFormat.PRETTY : OutputFormat.JSON;
204
+ try {
205
+ const response = await axios.get(`${cfg.backendUrl}/api/v1/analytics/traffic/pages`, {
206
+ params: { days, limit },
207
+ headers: { Authorization: `Bearer ${cfg.accessToken}` },
208
+ });
209
+ const { pages } = response.data;
210
+ if (format === OutputFormat.JSON) {
211
+ outputJson(response.data);
212
+ }
213
+ else {
214
+ console.log('\nšŸ“„ Top Pages\n');
215
+ outputPretty(pages.map((p) => ({
216
+ Path: p.path.length > 40 ? p.path.substring(0, 37) + '...' : p.path,
217
+ 'Page Views': p.page_views.toLocaleString(),
218
+ Visitors: p.unique_visitors.toLocaleString(),
219
+ Sessions: p.sessions.toLocaleString(),
220
+ })), ['Path', 'Page Views', 'Visitors', 'Sessions']);
221
+ }
222
+ }
223
+ catch (err) {
224
+ handleError(err);
225
+ }
226
+ });
227
+ return traffic;
228
+ }
229
+ function handleError(err) {
230
+ const axiosError = err;
231
+ const errorObj = err;
232
+ if (axiosError.response?.status === 401) {
233
+ error('Authentication failed. Please login again: bi-cli auth login');
234
+ }
235
+ else if (axiosError.response?.data?.error) {
236
+ error(`Error: ${axiosError.response.data.error}`);
237
+ }
238
+ else {
239
+ error(`Request failed: ${errorObj.message || 'Unknown error'}`);
240
+ }
241
+ process.exit(1);
242
+ }
243
+ //# sourceMappingURL=traffic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traffic.js","sourceRoot":"","sources":["../../src/commands/traffic.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { createSalesCommand } from './commands/sales.js';
8
8
  import { createProductCommand } from './commands/product.js';
9
9
  import { createTrendsCommand } from './commands/trends.js';
10
10
  import { createAnalyticsCommand } from './commands/analytics.js';
11
+ import { createTrafficCommand } from './commands/traffic.js';
11
12
  // Read version from package.json
12
13
  const __filename = fileURLToPath(import.meta.url);
13
14
  const __dirname = dirname(__filename);
@@ -27,6 +28,8 @@ program.addCommand(createProductCommand());
27
28
  program.addCommand(createTrendsCommand());
28
29
  // Analytics commands
29
30
  program.addCommand(createAnalyticsCommand());
31
+ // Traffic commands
32
+ program.addCommand(createTrafficCommand());
30
33
  // Config commands (placeholder)
31
34
  program
32
35
  .command('config')
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,iCAAiC;AACjC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AACtC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AAErF,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,wDAAwD,CAAC;KACrE,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAExB,gBAAgB;AAChB,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AAExC,iBAAiB;AACjB,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;AAEzC,mBAAmB;AACnB,OAAO,CAAC,UAAU,CAAC,oBAAoB,EAAE,CAAC,CAAC;AAE3C,kBAAkB;AAClB,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAE1C,qBAAqB;AACrB,OAAO,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAE7C,gCAAgC;AAChC,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,sBAAsB,CAAC;KACnC,MAAM,CAAC,GAAG,EAAE;IACX,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC;AAEL,kCAAkC;AAClC,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,oBAAoB,CAAC;KACjC,MAAM,CAAC,GAAG,EAAE;IACX,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEL,mCAAmC;AACnC,OAAO;KACJ,OAAO,CAAC,WAAW,CAAC;KACpB,WAAW,CAAC,qBAAqB,CAAC;KAClC,MAAM,CAAC,GAAG,EAAE;IACX,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;AACpD,CAAC,CAAC,CAAC;AAEL,+CAA+C;AAC/C,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,iCAAiC,CAAC;KAC9C,MAAM,CAAC,GAAG,EAAE;IACX,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,iCAAiC;AACjC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AACtC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AAErF,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,wDAAwD,CAAC;KACrE,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAExB,gBAAgB;AAChB,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AAExC,iBAAiB;AACjB,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;AAEzC,mBAAmB;AACnB,OAAO,CAAC,UAAU,CAAC,oBAAoB,EAAE,CAAC,CAAC;AAE3C,kBAAkB;AAClB,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAE1C,qBAAqB;AACrB,OAAO,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAE7C,mBAAmB;AACnB,OAAO,CAAC,UAAU,CAAC,oBAAoB,EAAE,CAAC,CAAC;AAE3C,gCAAgC;AAChC,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,sBAAsB,CAAC;KACnC,MAAM,CAAC,GAAG,EAAE;IACX,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC;AAEL,kCAAkC;AAClC,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,oBAAoB,CAAC;KACjC,MAAM,CAAC,GAAG,EAAE;IACX,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEL,mCAAmC;AACnC,OAAO;KACJ,OAAO,CAAC,WAAW,CAAC;KACpB,WAAW,CAAC,qBAAqB,CAAC;KAClC,MAAM,CAAC,GAAG,EAAE;IACX,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;AACpD,CAAC,CAAC,CAAC;AAEL,+CAA+C;AAC/C,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,iCAAiC,CAAC;KAC9C,MAAM,CAAC,GAAG,EAAE;IACX,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optima-chat/bi-cli",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "type": "module",
5
5
  "description": "Optima BI CLI - AI-friendly business intelligence tool",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,354 @@
1
+ import { Command } from 'commander';
2
+ import axios from 'axios';
3
+ import { getConfig } from '../config/index.js';
4
+ import { outputJson, outputPretty, error, OutputFormat } from '../utils/output.js';
5
+
6
+ interface TrafficOverview {
7
+ period: {
8
+ start: string;
9
+ end: string;
10
+ days: number;
11
+ };
12
+ summary: {
13
+ page_views: number;
14
+ unique_visitors: number;
15
+ sessions: number;
16
+ avg_session_duration: number;
17
+ bounce_rate: number;
18
+ };
19
+ comparison: {
20
+ page_views_change: number;
21
+ unique_visitors_change: number;
22
+ sessions_change: number;
23
+ };
24
+ }
25
+
26
+ interface TrafficSource {
27
+ source: string;
28
+ medium: string;
29
+ visitors: number;
30
+ page_views: number;
31
+ percentage: number;
32
+ }
33
+
34
+ interface FunnelStep {
35
+ step: string;
36
+ count: number;
37
+ rate: number;
38
+ conversion: number;
39
+ }
40
+
41
+ interface SearchItem {
42
+ query: string;
43
+ count: number;
44
+ unique_searchers: number;
45
+ avg_results: number;
46
+ click_rate: number;
47
+ }
48
+
49
+ interface PageItem {
50
+ path: string;
51
+ page_views: number;
52
+ unique_visitors: number;
53
+ sessions: number;
54
+ }
55
+
56
+ function formatPercent(value: number): string {
57
+ return `${(value * 100).toFixed(1)}%`;
58
+ }
59
+
60
+ function formatChange(value: number): string {
61
+ const sign = value >= 0 ? '+' : '';
62
+ return `${sign}${(value * 100).toFixed(1)}%`;
63
+ }
64
+
65
+ function formatDuration(seconds: number): string {
66
+ const mins = Math.floor(seconds / 60);
67
+ const secs = seconds % 60;
68
+ return mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
69
+ }
70
+
71
+ export function createTrafficCommand(): Command {
72
+ const traffic = new Command('traffic').description('Traffic analytics');
73
+
74
+ // traffic overview
75
+ traffic
76
+ .command('overview')
77
+ .description('Get traffic overview')
78
+ .option('--days <number>', 'Number of days', '30')
79
+ .option('--product <id>', 'Filter by product ID')
80
+ .option('--pretty', 'Output in pretty format (default: JSON)')
81
+ .action(async (options) => {
82
+ const cfg = getConfig();
83
+
84
+ if (!cfg.accessToken) {
85
+ error('Not logged in. Run: bi-cli auth login');
86
+ process.exit(1);
87
+ }
88
+
89
+ const days = parseInt(options.days, 10);
90
+ if (isNaN(days) || days < 1 || days > 365) {
91
+ error('Days must be between 1 and 365');
92
+ process.exit(1);
93
+ }
94
+
95
+ const format = options.pretty ? OutputFormat.PRETTY : OutputFormat.JSON;
96
+
97
+ try {
98
+ const response = await axios.get<TrafficOverview>(
99
+ `${cfg.backendUrl}/api/v1/analytics/traffic/overview`,
100
+ {
101
+ params: { days, product_id: options.product },
102
+ headers: { Authorization: `Bearer ${cfg.accessToken}` },
103
+ }
104
+ );
105
+
106
+ const data = response.data;
107
+
108
+ if (format === OutputFormat.JSON) {
109
+ outputJson(data);
110
+ } else {
111
+ console.log('\nšŸ“Š Traffic Overview\n');
112
+ console.log(
113
+ `Period: ${data.period.start} to ${data.period.end} (${data.period.days} days)\n`
114
+ );
115
+
116
+ outputPretty({
117
+ 'Page Views': `${data.summary.page_views.toLocaleString()} (${formatChange(data.comparison.page_views_change)})`,
118
+ 'Unique Visitors': `${data.summary.unique_visitors.toLocaleString()} (${formatChange(data.comparison.unique_visitors_change)})`,
119
+ Sessions: `${data.summary.sessions.toLocaleString()} (${formatChange(data.comparison.sessions_change)})`,
120
+ 'Avg Session Duration': formatDuration(data.summary.avg_session_duration),
121
+ 'Bounce Rate': formatPercent(data.summary.bounce_rate),
122
+ });
123
+ }
124
+ } catch (err: unknown) {
125
+ handleError(err);
126
+ }
127
+ });
128
+
129
+ // traffic sources
130
+ traffic
131
+ .command('sources')
132
+ .description('Get traffic sources')
133
+ .option('--days <number>', 'Number of days', '30')
134
+ .option('--limit <number>', 'Limit results', '10')
135
+ .option('--pretty', 'Output in pretty format (default: JSON)')
136
+ .action(async (options) => {
137
+ const cfg = getConfig();
138
+
139
+ if (!cfg.accessToken) {
140
+ error('Not logged in. Run: bi-cli auth login');
141
+ process.exit(1);
142
+ }
143
+
144
+ const days = parseInt(options.days, 10);
145
+ const limit = parseInt(options.limit, 10);
146
+ const format = options.pretty ? OutputFormat.PRETTY : OutputFormat.JSON;
147
+
148
+ try {
149
+ const response = await axios.get<{ sources: TrafficSource[] }>(
150
+ `${cfg.backendUrl}/api/v1/analytics/traffic/sources`,
151
+ {
152
+ params: { days, limit },
153
+ headers: { Authorization: `Bearer ${cfg.accessToken}` },
154
+ }
155
+ );
156
+
157
+ const { sources } = response.data;
158
+
159
+ if (format === OutputFormat.JSON) {
160
+ outputJson(response.data);
161
+ } else {
162
+ console.log('\nšŸ”— Traffic Sources\n');
163
+ outputPretty(
164
+ sources.map((s) => ({
165
+ Source: s.source,
166
+ Medium: s.medium,
167
+ Visitors: s.visitors.toLocaleString(),
168
+ 'Page Views': s.page_views.toLocaleString(),
169
+ Share: formatPercent(s.percentage),
170
+ })),
171
+ ['Source', 'Medium', 'Visitors', 'Page Views', 'Share']
172
+ );
173
+ }
174
+ } catch (err: unknown) {
175
+ handleError(err);
176
+ }
177
+ });
178
+
179
+ // traffic funnel
180
+ traffic
181
+ .command('funnel')
182
+ .description('Get conversion funnel')
183
+ .option('--days <number>', 'Number of days', '30')
184
+ .option('--product <id>', 'Filter by product ID')
185
+ .option('--pretty', 'Output in pretty format (default: JSON)')
186
+ .action(async (options) => {
187
+ const cfg = getConfig();
188
+
189
+ if (!cfg.accessToken) {
190
+ error('Not logged in. Run: bi-cli auth login');
191
+ process.exit(1);
192
+ }
193
+
194
+ const days = parseInt(options.days, 10);
195
+ const format = options.pretty ? OutputFormat.PRETTY : OutputFormat.JSON;
196
+
197
+ try {
198
+ const response = await axios.get<{ funnel: FunnelStep[] }>(
199
+ `${cfg.backendUrl}/api/v1/analytics/traffic/funnel`,
200
+ {
201
+ params: { days, product_id: options.product },
202
+ headers: { Authorization: `Bearer ${cfg.accessToken}` },
203
+ }
204
+ );
205
+
206
+ const { funnel } = response.data;
207
+
208
+ if (format === OutputFormat.JSON) {
209
+ outputJson(response.data);
210
+ } else {
211
+ console.log('\nšŸ”» Conversion Funnel\n');
212
+ outputPretty(
213
+ funnel.map((f) => ({
214
+ Step: f.step.replace(/_/g, ' '),
215
+ Users: f.count.toLocaleString(),
216
+ 'From Start': formatPercent(f.rate),
217
+ Conversion: formatPercent(f.conversion),
218
+ })),
219
+ ['Step', 'Users', 'From Start', 'Conversion']
220
+ );
221
+ }
222
+ } catch (err: unknown) {
223
+ handleError(err);
224
+ }
225
+ });
226
+
227
+ // traffic search
228
+ traffic
229
+ .command('search')
230
+ .description('Get search analytics')
231
+ .option('--days <number>', 'Number of days', '30')
232
+ .option('--limit <number>', 'Limit results', '20')
233
+ .option('--zero-results', 'Show only zero results queries')
234
+ .option('--pretty', 'Output in pretty format (default: JSON)')
235
+ .action(async (options) => {
236
+ const cfg = getConfig();
237
+
238
+ if (!cfg.accessToken) {
239
+ error('Not logged in. Run: bi-cli auth login');
240
+ process.exit(1);
241
+ }
242
+
243
+ const days = parseInt(options.days, 10);
244
+ const limit = parseInt(options.limit, 10);
245
+ const format = options.pretty ? OutputFormat.PRETTY : OutputFormat.JSON;
246
+
247
+ try {
248
+ const response = await axios.get<{
249
+ searches: SearchItem[];
250
+ zero_results: { query: string; count: number }[];
251
+ }>(`${cfg.backendUrl}/api/v1/analytics/traffic/search`, {
252
+ params: { days, limit, zero_results: options.zeroResults },
253
+ headers: { Authorization: `Bearer ${cfg.accessToken}` },
254
+ });
255
+
256
+ const { searches, zero_results } = response.data;
257
+
258
+ if (format === OutputFormat.JSON) {
259
+ outputJson(response.data);
260
+ } else {
261
+ console.log('\nšŸ” Top Searches\n');
262
+ outputPretty(
263
+ searches.map((s) => ({
264
+ Query: s.query,
265
+ Searches: s.count.toLocaleString(),
266
+ 'Avg Results': s.avg_results,
267
+ 'Click Rate': formatPercent(s.click_rate),
268
+ })),
269
+ ['Query', 'Searches', 'Avg Results', 'Click Rate']
270
+ );
271
+
272
+ if (zero_results.length > 0) {
273
+ console.log('\nāŒ Zero Results Queries\n');
274
+ outputPretty(
275
+ zero_results.map((z) => ({
276
+ Query: z.query,
277
+ Count: z.count.toLocaleString(),
278
+ })),
279
+ ['Query', 'Count']
280
+ );
281
+ }
282
+ }
283
+ } catch (err: unknown) {
284
+ handleError(err);
285
+ }
286
+ });
287
+
288
+ // traffic pages
289
+ traffic
290
+ .command('pages')
291
+ .description('Get top pages')
292
+ .option('--days <number>', 'Number of days', '30')
293
+ .option('--limit <number>', 'Limit results', '20')
294
+ .option('--pretty', 'Output in pretty format (default: JSON)')
295
+ .action(async (options) => {
296
+ const cfg = getConfig();
297
+
298
+ if (!cfg.accessToken) {
299
+ error('Not logged in. Run: bi-cli auth login');
300
+ process.exit(1);
301
+ }
302
+
303
+ const days = parseInt(options.days, 10);
304
+ const limit = parseInt(options.limit, 10);
305
+ const format = options.pretty ? OutputFormat.PRETTY : OutputFormat.JSON;
306
+
307
+ try {
308
+ const response = await axios.get<{ pages: PageItem[] }>(
309
+ `${cfg.backendUrl}/api/v1/analytics/traffic/pages`,
310
+ {
311
+ params: { days, limit },
312
+ headers: { Authorization: `Bearer ${cfg.accessToken}` },
313
+ }
314
+ );
315
+
316
+ const { pages } = response.data;
317
+
318
+ if (format === OutputFormat.JSON) {
319
+ outputJson(response.data);
320
+ } else {
321
+ console.log('\nšŸ“„ Top Pages\n');
322
+ outputPretty(
323
+ pages.map((p) => ({
324
+ Path: p.path.length > 40 ? p.path.substring(0, 37) + '...' : p.path,
325
+ 'Page Views': p.page_views.toLocaleString(),
326
+ Visitors: p.unique_visitors.toLocaleString(),
327
+ Sessions: p.sessions.toLocaleString(),
328
+ })),
329
+ ['Path', 'Page Views', 'Visitors', 'Sessions']
330
+ );
331
+ }
332
+ } catch (err: unknown) {
333
+ handleError(err);
334
+ }
335
+ });
336
+
337
+ return traffic;
338
+ }
339
+
340
+ function handleError(err: unknown): never {
341
+ const axiosError = err as {
342
+ response?: { status?: number; data?: { error?: string } };
343
+ };
344
+ const errorObj = err as Error;
345
+
346
+ if (axiosError.response?.status === 401) {
347
+ error('Authentication failed. Please login again: bi-cli auth login');
348
+ } else if (axiosError.response?.data?.error) {
349
+ error(`Error: ${axiosError.response.data.error}`);
350
+ } else {
351
+ error(`Request failed: ${errorObj.message || 'Unknown error'}`);
352
+ }
353
+ process.exit(1);
354
+ }
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ import { createSalesCommand } from './commands/sales.js';
9
9
  import { createProductCommand } from './commands/product.js';
10
10
  import { createTrendsCommand } from './commands/trends.js';
11
11
  import { createAnalyticsCommand } from './commands/analytics.js';
12
+ import { createTrafficCommand } from './commands/traffic.js';
12
13
 
13
14
  // Read version from package.json
14
15
  const __filename = fileURLToPath(import.meta.url);
@@ -37,6 +38,9 @@ program.addCommand(createTrendsCommand());
37
38
  // Analytics commands
38
39
  program.addCommand(createAnalyticsCommand());
39
40
 
41
+ // Traffic commands
42
+ program.addCommand(createTrafficCommand());
43
+
40
44
  // Config commands (placeholder)
41
45
  program
42
46
  .command('config')