@supalytics/cli 0.3.7 → 0.4.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supalytics/cli",
3
- "version": "0.3.7",
3
+ "version": "0.4.0",
4
4
  "description": "CLI for Supalytics web analytics",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,10 +1,9 @@
1
- import { Command } from "commander";
2
- import chalk from "chalk";
3
- import { listAnnotations, createAnnotation, deleteAnnotation } from "../api";
4
- import { getDefaultSite } from "../config";
5
-
6
- const annotationsDescription = `Manage chart annotations.
1
+ import chalk from "chalk"
2
+ import { Command } from "commander"
3
+ import { createAnnotation, deleteAnnotation, listAnnotations } from "../api"
4
+ import { getDefaultSite } from "../config"
7
5
 
6
+ const annotationsExamples = `
8
7
  Examples:
9
8
  # List all annotations
10
9
  supalytics annotations
@@ -19,121 +18,134 @@ Examples:
19
18
  supalytics annotations add 2025-01-15 "Launched v2.0" -d "Major redesign with new dashboard"
20
19
 
21
20
  # Remove an annotation
22
- supalytics annotations remove <id>`;
21
+ supalytics annotations remove <id>`
23
22
 
24
23
  export const annotationsCommand = new Command("annotations")
25
- .description(annotationsDescription)
26
- .argument("[period]", "Time period: 7d, 14d, 30d, 90d, 12mo, all", "30d")
27
- .option("-s, --site <site>", "Site to query")
28
- .option("--json", "Output as JSON")
29
- .action(async (period, options) => {
30
- const site = options.site || (await getDefaultSite());
31
-
32
- if (!site) {
33
- console.error(chalk.red("Error: No site specified. Use --site or set a default with `supalytics login --site`"));
34
- process.exit(1);
35
- }
36
-
37
- try {
38
- const response = await listAnnotations(site, period);
39
-
40
- if (options.json) {
41
- console.log(JSON.stringify(response, null, 2));
42
- return;
43
- }
44
-
45
- const [startDate, endDate] = response.meta.date_range;
46
- console.log();
47
- console.log(chalk.bold("Annotations"), chalk.dim(`${startDate} → ${endDate}`));
48
- console.log();
49
-
50
- if (response.data.length === 0) {
51
- console.log(chalk.dim(" No annotations found"));
52
- console.log();
53
- console.log(chalk.dim(" Add one with: supalytics annotations add <date> <title>"));
54
- console.log();
55
- return;
56
- }
57
-
58
- for (const annotation of response.data) {
59
- console.log(` ${chalk.cyan(annotation.date)} ${annotation.title}`);
60
- if (annotation.description) {
61
- console.log(` ${chalk.dim(annotation.description)}`);
62
- }
63
- console.log(chalk.dim(` id: ${annotation.id}`));
64
- }
65
- console.log();
66
- } catch (error) {
67
- console.error(chalk.red(`Error: ${(error as Error).message}`));
68
- process.exit(1);
69
- }
70
- });
24
+ .description("Manage chart annotations")
25
+ .addHelpText("after", annotationsExamples)
26
+ .argument("[period]", "Time period: 7d, 14d, 30d, 90d, 12mo, all", "30d")
27
+ .option("-s, --site <site>", "Site to query")
28
+ .option("--json", "Output as JSON")
29
+ .action(async (period, options) => {
30
+ const site = options.site || (await getDefaultSite())
31
+
32
+ if (!site) {
33
+ console.error(
34
+ chalk.red(
35
+ "Error: No site specified. Use --site or set a default with `supalytics login --site`"
36
+ )
37
+ )
38
+ process.exit(1)
39
+ }
40
+
41
+ try {
42
+ const response = await listAnnotations(site, period)
43
+
44
+ if (options.json) {
45
+ console.log(JSON.stringify(response, null, 2))
46
+ return
47
+ }
48
+
49
+ const [startDate, endDate] = response.meta.date_range
50
+ console.log()
51
+ console.log(chalk.bold("Annotations"), chalk.dim(`${startDate} → ${endDate}`))
52
+ console.log()
53
+
54
+ if (response.data.length === 0) {
55
+ console.log(chalk.dim(" No annotations found"))
56
+ console.log()
57
+ console.log(chalk.dim(" Add one with: supalytics annotations add <date> <title>"))
58
+ console.log()
59
+ return
60
+ }
61
+
62
+ for (const annotation of response.data) {
63
+ console.log(` ${chalk.cyan(annotation.date)} ${annotation.title}`)
64
+ if (annotation.description) {
65
+ console.log(` ${chalk.dim(annotation.description)}`)
66
+ }
67
+ console.log(chalk.dim(` id: ${annotation.id}`))
68
+ }
69
+ console.log()
70
+ } catch (error) {
71
+ console.error(chalk.red(`Error: ${(error as Error).message}`))
72
+ process.exit(1)
73
+ }
74
+ })
71
75
 
72
76
  // Subcommand: add
73
77
  annotationsCommand
74
- .command("add <date> <title>")
75
- .description("Add an annotation")
76
- .option("-s, --site <site>", "Site to query")
77
- .option("-d, --description <text>", "Optional description")
78
- .option("--json", "Output as JSON")
79
- .action(async (date, title, options) => {
80
- const site = options.site || (await getDefaultSite());
81
-
82
- if (!site) {
83
- console.error(chalk.red("Error: No site specified. Use --site or set a default with `supalytics login --site`"));
84
- process.exit(1);
85
- }
86
-
87
- // Validate date format
88
- if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
89
- console.error(chalk.red("Error: Date must be in YYYY-MM-DD format"));
90
- process.exit(1);
91
- }
92
-
93
- try {
94
- const annotation = await createAnnotation(site, date, title, options.description);
95
-
96
- if (options.json) {
97
- console.log(JSON.stringify(annotation, null, 2));
98
- return;
99
- }
100
-
101
- console.log();
102
- console.log(chalk.green("Annotation added"));
103
- console.log();
104
- console.log(` ${chalk.cyan(annotation.date)} ${annotation.title}`);
105
- if (annotation.description) {
106
- console.log(` ${chalk.dim(annotation.description)}`);
107
- }
108
- console.log(chalk.dim(` id: ${annotation.id}`));
109
- console.log();
110
- } catch (error) {
111
- console.error(chalk.red(`Error: ${(error as Error).message}`));
112
- process.exit(1);
113
- }
114
- });
78
+ .command("add <date> <title>")
79
+ .description("Add an annotation")
80
+ .option("-s, --site <site>", "Site to query")
81
+ .option("-d, --description <text>", "Optional description")
82
+ .option("--json", "Output as JSON")
83
+ .action(async (date, title, options) => {
84
+ const site = options.site || (await getDefaultSite())
85
+
86
+ if (!site) {
87
+ console.error(
88
+ chalk.red(
89
+ "Error: No site specified. Use --site or set a default with `supalytics login --site`"
90
+ )
91
+ )
92
+ process.exit(1)
93
+ }
94
+
95
+ // Validate date format
96
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
97
+ console.error(chalk.red("Error: Date must be in YYYY-MM-DD format"))
98
+ process.exit(1)
99
+ }
100
+
101
+ try {
102
+ const annotation = await createAnnotation(site, date, title, options.description)
103
+
104
+ if (options.json) {
105
+ console.log(JSON.stringify(annotation, null, 2))
106
+ return
107
+ }
108
+
109
+ console.log()
110
+ console.log(chalk.green("Annotation added"))
111
+ console.log()
112
+ console.log(` ${chalk.cyan(annotation.date)} ${annotation.title}`)
113
+ if (annotation.description) {
114
+ console.log(` ${chalk.dim(annotation.description)}`)
115
+ }
116
+ console.log(chalk.dim(` id: ${annotation.id}`))
117
+ console.log()
118
+ } catch (error) {
119
+ console.error(chalk.red(`Error: ${(error as Error).message}`))
120
+ process.exit(1)
121
+ }
122
+ })
115
123
 
116
124
  // Subcommand: remove
117
125
  annotationsCommand
118
- .command("remove <id>")
119
- .description("Remove an annotation")
120
- .option("-s, --site <site>", "Site to query")
121
- .action(async (id, options) => {
122
- const site = options.site || (await getDefaultSite());
123
-
124
- if (!site) {
125
- console.error(chalk.red("Error: No site specified. Use --site or set a default with `supalytics login --site`"));
126
- process.exit(1);
127
- }
128
-
129
- try {
130
- await deleteAnnotation(site, id);
131
-
132
- console.log();
133
- console.log(chalk.green("Annotation removed"));
134
- console.log();
135
- } catch (error) {
136
- console.error(chalk.red(`Error: ${(error as Error).message}`));
137
- process.exit(1);
138
- }
139
- });
126
+ .command("remove <id>")
127
+ .description("Remove an annotation")
128
+ .option("-s, --site <site>", "Site to query")
129
+ .action(async (id, options) => {
130
+ const site = options.site || (await getDefaultSite())
131
+
132
+ if (!site) {
133
+ console.error(
134
+ chalk.red(
135
+ "Error: No site specified. Use --site or set a default with `supalytics login --site`"
136
+ )
137
+ )
138
+ process.exit(1)
139
+ }
140
+
141
+ try {
142
+ await deleteAnnotation(site, id)
143
+
144
+ console.log()
145
+ console.log(chalk.green("Annotation removed"))
146
+ console.log()
147
+ } catch (error) {
148
+ console.error(chalk.red(`Error: ${(error as Error).message}`))
149
+ process.exit(1)
150
+ }
151
+ })
@@ -1,10 +1,15 @@
1
- import { Command } from "commander";
2
- import chalk from "chalk";
3
- import { listEvents, getEventProperties, getPropertyBreakdown, formatNumber, PropertyKeysResponse } from "../api";
4
- import { getDefaultSite } from "../config";
5
-
6
- const eventsDescription = `List and explore custom events.
7
-
1
+ import chalk from "chalk"
2
+ import { Command } from "commander"
3
+ import {
4
+ formatNumber,
5
+ getEventProperties,
6
+ getPropertyBreakdown,
7
+ listEvents,
8
+ type PropertyKeysResponse,
9
+ } from "../api"
10
+ import { getDefaultSite } from "../config"
11
+
12
+ const eventsExamples = `
8
13
  Examples:
9
14
  # List all events
10
15
  supalytics events
@@ -16,158 +21,180 @@ Examples:
16
21
  supalytics events signup --property plan
17
22
 
18
23
  # Without revenue
19
- supalytics events signup --property plan --no-revenue`;
24
+ supalytics events signup --property plan --no-revenue`
20
25
 
21
26
  function displayPropertyKeys(response: PropertyKeysResponse, event: string, json: boolean) {
22
- if (json) {
23
- console.log(JSON.stringify(response, null, 2));
24
- return;
25
- }
26
-
27
- console.log();
28
- console.log(chalk.bold(`Properties for "${event}"`));
29
- console.log();
30
-
31
- if (response.data.length === 0) {
32
- console.log(chalk.dim(" No properties found"));
33
- return;
34
- }
35
-
36
- for (const key of response.data) {
37
- console.log(` ${chalk.cyan(key)}`);
38
- }
39
- console.log();
40
- console.log(chalk.dim(` Use: supalytics events ${event} --property <key>`));
41
- console.log();
27
+ if (json) {
28
+ console.log(JSON.stringify(response, null, 2))
29
+ return
30
+ }
31
+
32
+ console.log()
33
+ console.log(chalk.bold(`Properties for "${event}"`))
34
+ console.log()
35
+
36
+ if (response.data.length === 0) {
37
+ console.log(chalk.dim(" No properties found"))
38
+ return
39
+ }
40
+
41
+ for (const key of response.data) {
42
+ console.log(` ${chalk.cyan(key)}`)
43
+ }
44
+ console.log()
45
+ console.log(chalk.dim(` Use: supalytics events ${event} --property <key>`))
46
+ console.log()
42
47
  }
43
48
 
44
49
  export const eventsCommand = new Command("events")
45
- .description(eventsDescription)
46
- .argument("[event]", "Event name to explore")
47
- .option("-s, --site <site>", "Site to query")
48
- .option("-p, --period <period>", "Time period: 7d, 14d, 30d, 90d, 12mo, all", "30d")
49
- .option("--property <key>", "Get breakdown for a specific property")
50
- .option("-l, --limit <number>", "Number of results", "20")
51
- .option("--no-revenue", "Exclude revenue metrics")
52
- .option("--json", "Output as JSON")
53
- .option("-t, --test", "Test mode: query localhost data instead of production")
54
- .action(async (event, options) => {
55
- const site = options.site || (await getDefaultSite());
56
-
57
- if (!site) {
58
- console.error(chalk.red("Error: No site specified. Use --site or set a default with `supalytics login --site`"));
59
- process.exit(1);
60
- }
61
-
62
- try {
63
- // If no event specified, list all events
64
- if (!event) {
65
- const response = await listEvents(site, options.period, parseInt(options.limit), options.test || false);
66
-
67
- if (options.json) {
68
- console.log(JSON.stringify(response, null, 2));
69
- return;
70
- }
71
-
72
- const [startDate, endDate] = response.meta.date_range;
73
- console.log();
74
- console.log(chalk.bold("Events"), chalk.dim(`${startDate} → ${endDate}`));
75
- console.log();
76
-
77
- if (response.data.length === 0) {
78
- console.log(chalk.dim(" No events found"));
79
- return;
80
- }
81
-
82
- for (const e of response.data) {
83
- const props = e.has_properties ? chalk.dim(" [has properties]") : "";
84
- console.log(` ${chalk.cyan(e.name)} ${formatNumber(e.visitors)} visitors ${formatNumber(e.count)} events${props}`);
85
- }
86
- console.log();
87
- return;
88
- }
89
-
90
- // If --property flag, get breakdown
91
- if (options.property) {
92
- const response = await getPropertyBreakdown(
93
- site,
94
- event,
95
- options.property,
96
- options.period,
97
- parseInt(options.limit),
98
- options.revenue !== false,
99
- options.test || false
100
- );
101
-
102
- if (options.json) {
103
- console.log(JSON.stringify(response, null, 2));
104
- return;
105
- }
106
-
107
- console.log();
108
- console.log(chalk.bold(`${event}.${options.property}`), chalk.dim(`${response.meta.date_range[0]} → ${response.meta.date_range[1]}`));
109
- console.log();
110
-
111
- if (response.data.length === 0) {
112
- console.log(chalk.dim(" No values found"));
113
- return;
114
- }
115
-
116
- for (const v of response.data) {
117
- let line = ` ${chalk.cyan(v.value)} ${formatNumber(v.visitors)} visitors ${formatNumber(v.count)} events`;
118
- if (options.revenue !== false && v.revenue !== null) {
119
- line += ` ${chalk.green("$" + (v.revenue / 100).toFixed(2))}`;
120
- }
121
- console.log(line);
122
- }
123
- console.log();
124
- return;
125
- }
126
-
127
- // If just event name, show event stats + properties
128
- const [eventsResponse, propsResponse] = await Promise.all([
129
- listEvents(site, options.period, 100, options.test || false), // Get all events to find this one
130
- getEventProperties(site, event, options.period, options.test || false),
131
- ]);
132
-
133
- // Find the specific event stats
134
- const eventData = eventsResponse.data.find((e) => e.name === event);
135
-
136
- if (options.json) {
137
- console.log(JSON.stringify({
138
- event: eventData || { name: event, count: 0, visitors: 0, has_properties: false },
139
- properties: propsResponse.data,
140
- meta: propsResponse.meta,
141
- }, null, 2));
142
- return;
143
- }
144
-
145
- const [startDate, endDate] = propsResponse.meta.date_range;
146
- console.log();
147
- console.log(chalk.bold(event), chalk.dim(`${startDate} → ${endDate}`));
148
- console.log();
149
-
150
- // Show event stats
151
- if (eventData) {
152
- console.log(` ${formatNumber(eventData.visitors)} visitors ${formatNumber(eventData.count)} events`);
153
- } else {
154
- console.log(chalk.dim(" No data for this event"));
155
- }
156
- console.log();
157
-
158
- // Show properties
159
- if (propsResponse.data.length > 0) {
160
- console.log(chalk.dim(" PROPERTIES"));
161
- for (const key of propsResponse.data) {
162
- console.log(` ${chalk.cyan(key)}`);
163
- }
164
- console.log();
165
- console.log(chalk.dim(` Use: supalytics events ${event} --property <key>`));
166
- }
167
- console.log();
168
-
169
- } catch (error) {
170
- console.error(chalk.red(`Error: ${(error as Error).message}`));
171
- process.exit(1);
172
- }
173
- });
50
+ .description("List and explore custom events")
51
+ .addHelpText("after", eventsExamples)
52
+ .argument("[event]", "Event name to explore")
53
+ .option("-s, --site <site>", "Site to query")
54
+ .option("-p, --period <period>", "Time period: 7d, 14d, 30d, 90d, 12mo, all", "30d")
55
+ .option("--property <key>", "Get breakdown for a specific property")
56
+ .option("-l, --limit <number>", "Number of results", "20")
57
+ .option("--no-revenue", "Exclude revenue metrics")
58
+ .option("--json", "Output as JSON")
59
+ .option("-t, --test", "Test mode: query localhost data instead of production")
60
+ .action(async (event, options) => {
61
+ const site = options.site || (await getDefaultSite())
62
+
63
+ if (!site) {
64
+ console.error(
65
+ chalk.red(
66
+ "Error: No site specified. Use --site or set a default with `supalytics login --site`"
67
+ )
68
+ )
69
+ process.exit(1)
70
+ }
71
+
72
+ try {
73
+ // If no event specified, list all events
74
+ if (!event) {
75
+ const response = await listEvents(
76
+ site,
77
+ options.period,
78
+ parseInt(options.limit),
79
+ options.test || false
80
+ )
81
+
82
+ if (options.json) {
83
+ console.log(JSON.stringify(response, null, 2))
84
+ return
85
+ }
86
+
87
+ const [startDate, endDate] = response.meta.date_range
88
+ console.log()
89
+ console.log(chalk.bold("Events"), chalk.dim(`${startDate} ${endDate}`))
90
+ console.log()
91
+
92
+ if (response.data.length === 0) {
93
+ console.log(chalk.dim(" No events found"))
94
+ return
95
+ }
96
+
97
+ for (const e of response.data) {
98
+ const props = e.has_properties ? chalk.dim(" [has properties]") : ""
99
+ console.log(
100
+ ` ${chalk.cyan(e.name)} ${formatNumber(e.visitors)} visitors ${formatNumber(e.count)} events${props}`
101
+ )
102
+ }
103
+ console.log()
104
+ return
105
+ }
106
+
107
+ // If --property flag, get breakdown
108
+ if (options.property) {
109
+ const response = await getPropertyBreakdown(
110
+ site,
111
+ event,
112
+ options.property,
113
+ options.period,
114
+ parseInt(options.limit),
115
+ options.revenue !== false,
116
+ options.test || false
117
+ )
118
+
119
+ if (options.json) {
120
+ console.log(JSON.stringify(response, null, 2))
121
+ return
122
+ }
123
+
124
+ console.log()
125
+ console.log(
126
+ chalk.bold(`${event}.${options.property}`),
127
+ chalk.dim(`${response.meta.date_range[0]} → ${response.meta.date_range[1]}`)
128
+ )
129
+ console.log()
130
+
131
+ if (response.data.length === 0) {
132
+ console.log(chalk.dim(" No values found"))
133
+ return
134
+ }
135
+
136
+ for (const v of response.data) {
137
+ let line = ` ${chalk.cyan(v.value)} ${formatNumber(v.visitors)} visitors ${formatNumber(v.count)} events`
138
+ if (options.revenue !== false && v.revenue !== null) {
139
+ line += ` ${chalk.green("$" + (v.revenue / 100).toFixed(2))}`
140
+ }
141
+ console.log(line)
142
+ }
143
+ console.log()
144
+ return
145
+ }
146
+
147
+ // If just event name, show event stats + properties
148
+ const [eventsResponse, propsResponse] = await Promise.all([
149
+ listEvents(site, options.period, 100, options.test || false), // Get all events to find this one
150
+ getEventProperties(site, event, options.period, options.test || false),
151
+ ])
152
+
153
+ // Find the specific event stats
154
+ const eventData = eventsResponse.data.find((e) => e.name === event)
155
+
156
+ if (options.json) {
157
+ console.log(
158
+ JSON.stringify(
159
+ {
160
+ event: eventData || { name: event, count: 0, visitors: 0, has_properties: false },
161
+ properties: propsResponse.data,
162
+ meta: propsResponse.meta,
163
+ },
164
+ null,
165
+ 2
166
+ )
167
+ )
168
+ return
169
+ }
170
+
171
+ const [startDate, endDate] = propsResponse.meta.date_range
172
+ console.log()
173
+ console.log(chalk.bold(event), chalk.dim(`${startDate} → ${endDate}`))
174
+ console.log()
175
+
176
+ // Show event stats
177
+ if (eventData) {
178
+ console.log(
179
+ ` ${formatNumber(eventData.visitors)} visitors ${formatNumber(eventData.count)} events`
180
+ )
181
+ } else {
182
+ console.log(chalk.dim(" No data for this event"))
183
+ }
184
+ console.log()
185
+
186
+ // Show properties
187
+ if (propsResponse.data.length > 0) {
188
+ console.log(chalk.dim(" PROPERTIES"))
189
+ for (const key of propsResponse.data) {
190
+ console.log(` ${chalk.cyan(key)}`)
191
+ }
192
+ console.log()
193
+ console.log(chalk.dim(` Use: supalytics events ${event} --property <key>`))
194
+ }
195
+ console.log()
196
+ } catch (error) {
197
+ console.error(chalk.red(`Error: ${(error as Error).message}`))
198
+ process.exit(1)
199
+ }
200
+ })