@supalytics/cli 0.3.8 → 0.4.1
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 +1 -1
- package/src/commands/annotations.ts +128 -116
- package/src/commands/events.ts +184 -157
- package/src/commands/journeys.ts +442 -414
- package/src/commands/logout.ts +49 -13
- package/src/commands/query.ts +181 -158
- package/src/commands/update.ts +68 -52
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
+
})
|
package/src/commands/events.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
+
})
|