@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 +5 -5
- package/dist/commands/traffic.d.ts +3 -0
- package/dist/commands/traffic.d.ts.map +1 -0
- package/dist/commands/traffic.js +243 -0
- package/dist/commands/traffic.js.map +1 -0
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/traffic.ts +354 -0
- package/src/index.ts +13 -1
package/README.md
CHANGED
|
@@ -40,11 +40,11 @@ bi-cli auth logout
|
|
|
40
40
|
|
|
41
41
|
### Environment Configuration
|
|
42
42
|
|
|
43
|
-
| Environment | Auth URL
|
|
44
|
-
|
|
45
|
-
| production
|
|
46
|
-
| stage
|
|
47
|
-
| development | auth.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 @@
|
|
|
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(
|
|
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;
|
|
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
|
@@ -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(
|
|
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')
|