@optima-chat/bi-cli 0.3.2 → 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.
package/README.md CHANGED
@@ -40,11 +40,11 @@ bi-cli auth logout
40
40
 
41
41
  ### Environment Configuration
42
42
 
43
- | Environment | Auth URL | Backend URL |
44
- |-------------|----------|-------------|
45
- | production | auth.optima.onl | bi-api.optima.onl |
46
- | stage | auth.stage.optima.onl | bi-api.stage.optima.onl |
47
- | development | auth.optima.chat | bi-api.optima.chat |
43
+ | Environment | Auth URL | Backend URL |
44
+ | ----------- | --------------------- | ----------------------- |
45
+ | production | auth.optima.onl | bi-api.optima.onl |
46
+ | stage | auth.stage.optima.onl | bi-api.stage.optima.onl |
47
+ | development | auth.optima.chat | bi-api.optima.chat |
48
48
 
49
49
  ### Token Priority
50
50
 
@@ -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
@@ -1,15 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
+ import { readFileSync } from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
3
6
  import { createAuthCommand } from './commands/auth.js';
4
7
  import { createSalesCommand } from './commands/sales.js';
5
8
  import { createProductCommand } from './commands/product.js';
6
9
  import { createTrendsCommand } from './commands/trends.js';
7
10
  import { createAnalyticsCommand } from './commands/analytics.js';
11
+ import { createTrafficCommand } from './commands/traffic.js';
12
+ // Read version from package.json
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
8
16
  const program = new Command();
9
17
  program
10
18
  .name('bi-cli')
11
19
  .description('Optima BI CLI - AI-friendly business intelligence tool')
12
- .version('0.2.0');
20
+ .version(pkg.version);
13
21
  // Auth commands
14
22
  program.addCommand(createAuthCommand());
15
23
  // Sales commands
@@ -20,6 +28,8 @@ program.addCommand(createProductCommand());
20
28
  program.addCommand(createTrendsCommand());
21
29
  // Analytics commands
22
30
  program.addCommand(createAnalyticsCommand());
31
+ // Traffic commands
32
+ program.addCommand(createTrafficCommand());
23
33
  // Config commands (placeholder)
24
34
  program
25
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,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,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,wDAAwD,CAAC;KACrE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,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.2",
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
@@ -1,18 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { Command } from 'commander';
4
+ import { readFileSync } from 'fs';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname, join } from 'path';
4
7
  import { createAuthCommand } from './commands/auth.js';
5
8
  import { createSalesCommand } from './commands/sales.js';
6
9
  import { createProductCommand } from './commands/product.js';
7
10
  import { createTrendsCommand } from './commands/trends.js';
8
11
  import { createAnalyticsCommand } from './commands/analytics.js';
12
+ import { createTrafficCommand } from './commands/traffic.js';
13
+
14
+ // Read version from package.json
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
9
18
 
10
19
  const program = new Command();
11
20
 
12
21
  program
13
22
  .name('bi-cli')
14
23
  .description('Optima BI CLI - AI-friendly business intelligence tool')
15
- .version('0.2.0');
24
+ .version(pkg.version);
16
25
 
17
26
  // Auth commands
18
27
  program.addCommand(createAuthCommand());
@@ -29,6 +38,9 @@ program.addCommand(createTrendsCommand());
29
38
  // Analytics commands
30
39
  program.addCommand(createAnalyticsCommand());
31
40
 
41
+ // Traffic commands
42
+ program.addCommand(createTrafficCommand());
43
+
32
44
  // Config commands (placeholder)
33
45
  program
34
46
  .command('config')