@supalytics/cli 0.4.1 → 0.4.3

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.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "CLI for Supalytics web analytics",
5
5
  "type": "module",
6
6
  "bin": {
package/src/api.ts CHANGED
@@ -159,7 +159,7 @@ export interface PropertyBreakdownResponse {
159
159
  */
160
160
  export async function listEvents(
161
161
  site: string,
162
- period: string = "30d",
162
+ dateRange: string | [string, string] = "30d",
163
163
  limit: number = 100,
164
164
  isDev: boolean = false
165
165
  ): Promise<EventsResponse> {
@@ -177,7 +177,13 @@ export async function listEvents(
177
177
  );
178
178
  }
179
179
 
180
- const params = new URLSearchParams({ period, limit: String(limit), domain: site });
180
+ const params = new URLSearchParams({ limit: String(limit), domain: site });
181
+ if (Array.isArray(dateRange)) {
182
+ params.set("start", dateRange[0]);
183
+ params.set("end", dateRange[1]);
184
+ } else {
185
+ params.set("period", dateRange);
186
+ }
181
187
  if (isDev) {
182
188
  params.set("is_dev", "true");
183
189
  }
@@ -199,7 +205,7 @@ export async function listEvents(
199
205
  export async function getEventProperties(
200
206
  site: string,
201
207
  eventName: string,
202
- period: string = "30d",
208
+ dateRange: string | [string, string] = "30d",
203
209
  isDev: boolean = false
204
210
  ): Promise<PropertyKeysResponse> {
205
211
  const apiKey = await getApiKeyForSite(site);
@@ -208,7 +214,13 @@ export async function getEventProperties(
208
214
  throw new Error(`Not authenticated. Run \`supalytics login\` first.`);
209
215
  }
210
216
 
211
- const params = new URLSearchParams({ period, domain: site });
217
+ const params = new URLSearchParams({ domain: site });
218
+ if (Array.isArray(dateRange)) {
219
+ params.set("start", dateRange[0]);
220
+ params.set("end", dateRange[1]);
221
+ } else {
222
+ params.set("period", dateRange);
223
+ }
212
224
  if (isDev) {
213
225
  params.set("is_dev", "true");
214
226
  }
@@ -232,7 +244,7 @@ export async function getPropertyBreakdown(
232
244
  site: string,
233
245
  eventName: string,
234
246
  propertyKey: string,
235
- period: string = "30d",
247
+ dateRange: string | [string, string] = "30d",
236
248
  limit: number = 100,
237
249
  includeRevenue: boolean = false,
238
250
  isDev: boolean = false
@@ -244,11 +256,16 @@ export async function getPropertyBreakdown(
244
256
  }
245
257
 
246
258
  const params = new URLSearchParams({
247
- period,
248
259
  limit: String(limit),
249
260
  include_revenue: String(includeRevenue),
250
261
  domain: site,
251
262
  });
263
+ if (Array.isArray(dateRange)) {
264
+ params.set("start", dateRange[0]);
265
+ params.set("end", dateRange[1]);
266
+ } else {
267
+ params.set("period", dateRange);
268
+ }
252
269
  if (isDev) {
253
270
  params.set("is_dev", "true");
254
271
  }
@@ -351,7 +368,7 @@ export interface AnnotationsResponse {
351
368
  */
352
369
  export async function listAnnotations(
353
370
  site: string,
354
- period: string = "30d"
371
+ dateRange: string | [string, string] = "30d"
355
372
  ): Promise<AnnotationsResponse> {
356
373
  const apiKey = await getApiKeyForSite(site);
357
374
 
@@ -367,36 +384,13 @@ export async function listAnnotations(
367
384
  );
368
385
  }
369
386
 
370
- // Valid periods
371
- const validPeriods = ["7d", "14d", "30d", "90d", "12mo", "all"] as const;
372
- if (!validPeriods.includes(period as typeof validPeriods[number])) {
373
- throw new Error(`Invalid period "${period}". Valid periods: ${validPeriods.join(", ")}`);
374
- }
375
-
376
- // Calculate date range from period
377
- const endDate = new Date();
378
- let startDate = new Date();
379
-
380
- if (period === "7d") {
381
- startDate.setDate(endDate.getDate() - 7);
382
- } else if (period === "14d") {
383
- startDate.setDate(endDate.getDate() - 14);
384
- } else if (period === "30d") {
385
- startDate.setDate(endDate.getDate() - 30);
386
- } else if (period === "90d") {
387
- startDate.setDate(endDate.getDate() - 90);
388
- } else if (period === "12mo") {
389
- startDate.setMonth(endDate.getMonth() - 12);
390
- } else if (period === "all") {
391
- startDate = new Date("2020-01-01");
387
+ const params = new URLSearchParams({ domain: site });
388
+ if (Array.isArray(dateRange)) {
389
+ params.set("start_date", dateRange[0]);
390
+ params.set("end_date", dateRange[1]);
392
391
  }
393
-
394
- const formatDate = (d: Date) => d.toISOString().split("T")[0];
395
- const params = new URLSearchParams({
396
- domain: site,
397
- start_date: formatDate(startDate),
398
- end_date: formatDate(endDate),
399
- });
392
+ // Annotations API only supports start_date/end_date, not period.
393
+ // String values like "all" are left without date params so the API returns everything.
400
394
 
401
395
  const response = await fetch(`${API_BASE}/v1/annotations?${params}`, {
402
396
  headers: { Authorization: `Bearer ${apiKey}` },
@@ -734,6 +728,235 @@ export async function getJourneyStats(
734
728
  return response.json();
735
729
  }
736
730
 
731
+ // Funnels API types
732
+ export interface FunnelStep {
733
+ id: string;
734
+ step_order: number;
735
+ name: string;
736
+ type: string; // 'page' | 'event' | 'purchase' | 'trial'
737
+ match_type?: string | null;
738
+ match_value?: string | null;
739
+ property_key?: string | null;
740
+ property_value?: string | null;
741
+ }
742
+
743
+ export interface FunnelItem {
744
+ id: string;
745
+ name: string;
746
+ description?: string | null;
747
+ mode: string; // 'ordered' | 'unordered'
748
+ steps: FunnelStep[];
749
+ created_at: string;
750
+ updated_at: string;
751
+ }
752
+
753
+ export interface FunnelsListResponse {
754
+ data: FunnelItem[];
755
+ meta: { domain: string };
756
+ }
757
+
758
+ export interface FunnelResponse {
759
+ data: FunnelItem;
760
+ meta: { domain: string };
761
+ }
762
+
763
+ export interface FunnelAnalysisRow {
764
+ step_order: number;
765
+ step_type: string;
766
+ match_type: string | null;
767
+ match_value: string | null;
768
+ property_key: string | null;
769
+ property_value: string | null;
770
+ visitors: number;
771
+ conversion_rate_from_start: number;
772
+ conversion_rate_from_prev: number;
773
+ }
774
+
775
+ export interface FunnelAnalysisResponse {
776
+ data: FunnelAnalysisRow[];
777
+ meta: {
778
+ domain: string;
779
+ date_range: [string, string];
780
+ query_ms: number;
781
+ };
782
+ }
783
+
784
+ export interface FunnelStepInput {
785
+ name: string;
786
+ type: string;
787
+ match_type?: string;
788
+ match_value?: string;
789
+ property_key?: string;
790
+ property_value?: string;
791
+ }
792
+
793
+ /**
794
+ * List all funnels for a site
795
+ */
796
+ export async function listFunnels(site: string): Promise<FunnelsListResponse> {
797
+ const apiKey = await getApiKeyForSite(site);
798
+
799
+ if (!apiKey) {
800
+ const sites = await getSites();
801
+ if (sites.length === 0) {
802
+ throw new Error(
803
+ "No sites configured. Run `supalytics login` to authenticate."
804
+ );
805
+ }
806
+ throw new Error(
807
+ `Not authenticated or site '${site}' not found. Available sites: ${sites.join(", ")}`
808
+ );
809
+ }
810
+
811
+ const params = new URLSearchParams({ domain: site });
812
+ const response = await fetch(`${API_BASE}/v1/funnels?${params}`, {
813
+ headers: { Authorization: `Bearer ${apiKey}` },
814
+ });
815
+
816
+ if (!response.ok) {
817
+ const error = (await response.json()) as ApiError;
818
+ throw new Error(error.message || error.error || `HTTP ${response.status}`);
819
+ }
820
+
821
+ return response.json();
822
+ }
823
+
824
+ /**
825
+ * Get a single funnel
826
+ */
827
+ export async function getFunnel(
828
+ site: string,
829
+ funnelId: string
830
+ ): Promise<FunnelResponse> {
831
+ const apiKey = await getApiKeyForSite(site);
832
+
833
+ if (!apiKey) {
834
+ throw new Error(`Not authenticated. Run \`supalytics login\` first.`);
835
+ }
836
+
837
+ const params = new URLSearchParams({ domain: site });
838
+ const response = await fetch(`${API_BASE}/v1/funnels/${funnelId}?${params}`, {
839
+ headers: { Authorization: `Bearer ${apiKey}` },
840
+ });
841
+
842
+ if (!response.ok) {
843
+ const error = (await response.json()) as ApiError;
844
+ throw new Error(error.message || error.error || `HTTP ${response.status}`);
845
+ }
846
+
847
+ return response.json();
848
+ }
849
+
850
+ /**
851
+ * Create a funnel
852
+ */
853
+ export async function createFunnel(
854
+ site: string,
855
+ data: {
856
+ name: string;
857
+ description?: string;
858
+ mode?: string;
859
+ steps: FunnelStepInput[];
860
+ }
861
+ ): Promise<FunnelResponse> {
862
+ const apiKey = await getApiKeyForSite(site);
863
+
864
+ if (!apiKey) {
865
+ throw new Error(`Not authenticated. Run \`supalytics login\` first.`);
866
+ }
867
+
868
+ const response = await fetch(`${API_BASE}/v1/funnels`, {
869
+ method: "POST",
870
+ headers: {
871
+ Authorization: `Bearer ${apiKey}`,
872
+ "Content-Type": "application/json",
873
+ },
874
+ body: JSON.stringify({ ...data, domain: site }),
875
+ });
876
+
877
+ if (!response.ok) {
878
+ const error = (await response.json()) as ApiError;
879
+ throw new Error(error.message || error.error || `HTTP ${response.status}`);
880
+ }
881
+
882
+ return response.json();
883
+ }
884
+
885
+ /**
886
+ * Update a funnel
887
+ */
888
+ export async function updateFunnel(
889
+ site: string,
890
+ funnelId: string,
891
+ data: {
892
+ name?: string;
893
+ description?: string;
894
+ mode?: string;
895
+ steps?: FunnelStepInput[];
896
+ }
897
+ ): Promise<FunnelResponse> {
898
+ const apiKey = await getApiKeyForSite(site);
899
+
900
+ if (!apiKey) {
901
+ throw new Error(`Not authenticated. Run \`supalytics login\` first.`);
902
+ }
903
+
904
+ const response = await fetch(`${API_BASE}/v1/funnels/${funnelId}`, {
905
+ method: "PUT",
906
+ headers: {
907
+ Authorization: `Bearer ${apiKey}`,
908
+ "Content-Type": "application/json",
909
+ },
910
+ body: JSON.stringify({ ...data, domain: site }),
911
+ });
912
+
913
+ if (!response.ok) {
914
+ const error = (await response.json()) as ApiError;
915
+ throw new Error(error.message || error.error || `HTTP ${response.status}`);
916
+ }
917
+
918
+ return response.json();
919
+ }
920
+
921
+ /**
922
+ * Run funnel analysis
923
+ */
924
+ export async function analyzeFunnel(
925
+ site: string,
926
+ funnelId: string,
927
+ dateRange: string | [string, string] = "30d",
928
+ isDev: boolean = false
929
+ ): Promise<FunnelAnalysisResponse> {
930
+ const apiKey = await getApiKeyForSite(site);
931
+
932
+ if (!apiKey) {
933
+ throw new Error(`Not authenticated. Run \`supalytics login\` first.`);
934
+ }
935
+
936
+ const body: Record<string, unknown> = { domain: site, is_dev: isDev };
937
+ if (Array.isArray(dateRange)) {
938
+ body.date_range = dateRange;
939
+ } else {
940
+ body.date_range = dateRange;
941
+ }
942
+
943
+ const response = await fetch(`${API_BASE}/v1/funnels/${funnelId}/analyze`, {
944
+ method: "POST",
945
+ headers: {
946
+ Authorization: `Bearer ${apiKey}`,
947
+ "Content-Type": "application/json",
948
+ },
949
+ body: JSON.stringify(body),
950
+ });
951
+
952
+ if (!response.ok) {
953
+ const error = (await response.json()) as ApiError;
954
+ throw new Error(error.message || error.error || `HTTP ${response.status}`);
955
+ }
956
+
957
+ return response.json();
958
+ }
959
+
737
960
  /**
738
961
  * Format revenue in cents to dollars
739
962
  */
@@ -2,6 +2,23 @@ import chalk from "chalk"
2
2
  import { Command } from "commander"
3
3
  import { createAnnotation, deleteAnnotation, listAnnotations } from "../api"
4
4
  import { getDefaultSite } from "../config"
5
+ import { parsePeriod } from "../ui"
6
+
7
+ /**
8
+ * Merge subcommand options with parent command options.
9
+ * Commander doesn't propagate options like -s/--site and --json
10
+ * from parent to subcommands, so we check both.
11
+ */
12
+ function mergedOpts(cmd: Command): Record<string, unknown> {
13
+ const parentOpts = cmd.parent?.opts() || {}
14
+ const ownOpts = cmd.opts()
15
+ return { ...parentOpts, ...ownOpts }
16
+ }
17
+
18
+ async function resolveSiteOption(cmd: Command): Promise<string | undefined> {
19
+ const opts = mergedOpts(cmd)
20
+ return (opts.site as string) || (await getDefaultSite())
21
+ }
5
22
 
6
23
  const annotationsExamples = `
7
24
  Examples:
@@ -23,10 +40,10 @@ Examples:
23
40
  export const annotationsCommand = new Command("annotations")
24
41
  .description("Manage chart annotations")
25
42
  .addHelpText("after", annotationsExamples)
26
- .argument("[period]", "Time period: 7d, 14d, 30d, 90d, 12mo, all", "30d")
27
43
  .option("-s, --site <site>", "Site to query")
44
+ .option("-p, --period <period>", "Time period: 7d, 14d, 30d, 90d, 12mo, all", "30d")
28
45
  .option("--json", "Output as JSON")
29
- .action(async (period, options) => {
46
+ .action(async (options) => {
30
47
  const site = options.site || (await getDefaultSite())
31
48
 
32
49
  if (!site) {
@@ -39,7 +56,7 @@ export const annotationsCommand = new Command("annotations")
39
56
  }
40
57
 
41
58
  try {
42
- const response = await listAnnotations(site, period)
59
+ const response = await listAnnotations(site, parsePeriod(options.period))
43
60
 
44
61
  if (options.json) {
45
62
  console.log(JSON.stringify(response, null, 2))
@@ -80,8 +97,9 @@ annotationsCommand
80
97
  .option("-s, --site <site>", "Site to query")
81
98
  .option("-d, --description <text>", "Optional description")
82
99
  .option("--json", "Output as JSON")
83
- .action(async (date, title, options) => {
84
- const site = options.site || (await getDefaultSite())
100
+ .action(async (date, title, _options, cmd) => {
101
+ const options = mergedOpts(cmd)
102
+ const site = await resolveSiteOption(cmd)
85
103
 
86
104
  if (!site) {
87
105
  console.error(
@@ -99,7 +117,7 @@ annotationsCommand
99
117
  }
100
118
 
101
119
  try {
102
- const annotation = await createAnnotation(site, date, title, options.description)
120
+ const annotation = await createAnnotation(site, date, title, options.description as string | undefined)
103
121
 
104
122
  if (options.json) {
105
123
  console.log(JSON.stringify(annotation, null, 2))
@@ -126,8 +144,8 @@ annotationsCommand
126
144
  .command("remove <id>")
127
145
  .description("Remove an annotation")
128
146
  .option("-s, --site <site>", "Site to query")
129
- .action(async (id, options) => {
130
- const site = options.site || (await getDefaultSite())
147
+ .action(async (id, _options, cmd) => {
148
+ const site = await resolveSiteOption(cmd)
131
149
 
132
150
  if (!site) {
133
151
  console.error(
@@ -10,7 +10,7 @@ _supalytics_completions() {
10
10
  cur="\${COMP_WORDS[COMP_CWORD]}"
11
11
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
12
12
 
13
- commands="init login logout sites default remove stats pages referrers countries trend query events realtime completions help"
13
+ commands="init login logout sites default remove stats pages referrers countries trend query events annotations realtime completions help"
14
14
 
15
15
  # Main command completion
16
16
  if [[ \${COMP_CWORD} -eq 1 ]]; then
@@ -21,7 +21,7 @@ _supalytics_completions() {
21
21
  # Subcommand options
22
22
  case "\${COMP_WORDS[1]}" in
23
23
  stats)
24
- opts="today yesterday week month year 7d 14d 30d 90d 12mo all --site --start --end --filter --all --no-revenue --json"
24
+ opts="--site --period --start --end --filter --all --no-revenue --json"
25
25
  ;;
26
26
  pages|referrers|countries)
27
27
  opts="--site --period --start --end --limit --filter --no-revenue --json"
@@ -35,6 +35,9 @@ _supalytics_completions() {
35
35
  events)
36
36
  opts="--site --period --property --limit --no-revenue --json"
37
37
  ;;
38
+ annotations)
39
+ opts="--site --period --json add remove"
40
+ ;;
38
41
  realtime)
39
42
  opts="--site --json --watch"
40
43
  ;;
@@ -86,6 +89,7 @@ _supalytics() {
86
89
  'trend:Daily visitor trend'
87
90
  'query:Flexible query with custom metrics and dimensions'
88
91
  'events:List and explore custom events'
92
+ 'annotations:Manage chart annotations'
89
93
  'realtime:Live visitors on your site right now'
90
94
  'completions:Generate shell completions'
91
95
  'help:Display help for command'
@@ -118,19 +122,14 @@ _supalytics() {
118
122
  case \$words[1] in
119
123
  stats)
120
124
  _arguments \\
121
- '1: :->period' \\
122
125
  '--site[Site to query]:domain:' \\
126
+ '--period[Time period]:period:(today yesterday week month year 7d 14d 30d 90d 12mo all)' \\
123
127
  '--start[Start date]:date:' \\
124
128
  '--end[End date]:date:' \\
125
129
  '*--filter[Filter]:filter:' \\
126
130
  '--all[Show detailed breakdown]' \\
127
131
  '--no-revenue[Exclude revenue metrics]' \\
128
132
  '--json[Output as JSON]'
129
- case \$state in
130
- period)
131
- _describe 'period' period_opts
132
- ;;
133
- esac
134
133
  ;;
135
134
  pages|referrers|countries)
136
135
  _arguments \\
@@ -180,6 +179,12 @@ _supalytics() {
180
179
  '--no-revenue[Exclude revenue]' \\
181
180
  '--json[Output as JSON]'
182
181
  ;;
182
+ annotations)
183
+ _arguments \\
184
+ '--site[Site to query]:domain:' \\
185
+ '--period[Time period]:period:(7d 14d 30d 90d 12mo all)' \\
186
+ '--json[Output as JSON]'
187
+ ;;
183
188
  realtime)
184
189
  _arguments \\
185
190
  '--site[Site to query]:domain:' \\
@@ -236,13 +241,14 @@ complete -c supalytics -n "__fish_use_subcommand" -a "countries" -d "Traffic by
236
241
  complete -c supalytics -n "__fish_use_subcommand" -a "trend" -d "Daily visitor trend"
237
242
  complete -c supalytics -n "__fish_use_subcommand" -a "query" -d "Flexible query with custom metrics"
238
243
  complete -c supalytics -n "__fish_use_subcommand" -a "events" -d "List and explore custom events"
244
+ complete -c supalytics -n "__fish_use_subcommand" -a "annotations" -d "Manage chart annotations"
239
245
  complete -c supalytics -n "__fish_use_subcommand" -a "realtime" -d "Live visitors right now"
240
246
  complete -c supalytics -n "__fish_use_subcommand" -a "completions" -d "Generate shell completions"
241
247
  complete -c supalytics -n "__fish_use_subcommand" -a "help" -d "Display help for command"
242
248
 
243
249
  # Stats options
244
- complete -c supalytics -n "__fish_seen_subcommand_from stats" -a "today yesterday week month year 7d 14d 30d 90d 12mo all"
245
250
  complete -c supalytics -n "__fish_seen_subcommand_from stats" -l site -s s -d "Site to query"
251
+ complete -c supalytics -n "__fish_seen_subcommand_from stats" -l period -s p -d "Time period" -a "today yesterday week month year 7d 14d 30d 90d 12mo all"
246
252
  complete -c supalytics -n "__fish_seen_subcommand_from stats" -l start -d "Start date"
247
253
  complete -c supalytics -n "__fish_seen_subcommand_from stats" -l end -d "End date"
248
254
  complete -c supalytics -n "__fish_seen_subcommand_from stats" -l filter -s f -d "Filter"
@@ -293,6 +299,11 @@ complete -c supalytics -n "__fish_seen_subcommand_from events" -l limit -s l -d
293
299
  complete -c supalytics -n "__fish_seen_subcommand_from events" -l no-revenue -d "Exclude revenue"
294
300
  complete -c supalytics -n "__fish_seen_subcommand_from events" -l json -d "Output as JSON"
295
301
 
302
+ # Annotations options
303
+ complete -c supalytics -n "__fish_seen_subcommand_from annotations" -l site -s s -d "Site to query"
304
+ complete -c supalytics -n "__fish_seen_subcommand_from annotations" -l period -s p -d "Time period" -a "7d 14d 30d 90d 12mo all"
305
+ complete -c supalytics -n "__fish_seen_subcommand_from annotations" -l json -d "Output as JSON"
306
+
296
307
  # Realtime options
297
308
  complete -c supalytics -n "__fish_seen_subcommand_from realtime" -l site -s s -d "Site to query"
298
309
  complete -c supalytics -n "__fish_seen_subcommand_from realtime" -l json -d "Output as JSON"
@@ -2,7 +2,7 @@ import { Command } from "commander";
2
2
  import chalk from "chalk";
3
3
  import { query, formatNumber } from "../api";
4
4
  import { getDefaultSite } from "../config";
5
- import { sparkBar, formatRevenue } from "../ui";
5
+ import { sparkBar, formatRevenue, parsePeriod } from "../ui";
6
6
 
7
7
  export const countriesCommand = new Command("countries")
8
8
  .description("Traffic by country")
@@ -25,7 +25,7 @@ export const countriesCommand = new Command("countries")
25
25
 
26
26
  const dateRange = options.start && options.end
27
27
  ? [options.start, options.end] as [string, string]
28
- : options.period;
28
+ : parsePeriod(options.period);
29
29
 
30
30
  const filters = options.filter
31
31
  ? options.filter.map((f: string) => {
@@ -8,6 +8,7 @@ import {
8
8
  type PropertyKeysResponse,
9
9
  } from "../api"
10
10
  import { getDefaultSite } from "../config"
11
+ import { parsePeriod } from "../ui"
11
12
 
12
13
  const eventsExamples = `
13
14
  Examples:
@@ -20,6 +21,10 @@ Examples:
20
21
  # Get breakdown of a property
21
22
  supalytics events signup --property plan
22
23
 
24
+ # With custom date range
25
+ supalytics events --start 2026-01-01 --end 2026-01-31
26
+ supalytics events signup --start 2026-01-01 --end 2026-01-31
27
+
23
28
  # Without revenue
24
29
  supalytics events signup --property plan --no-revenue`
25
30
 
@@ -52,6 +57,8 @@ export const eventsCommand = new Command("events")
52
57
  .argument("[event]", "Event name to explore")
53
58
  .option("-s, --site <site>", "Site to query")
54
59
  .option("-p, --period <period>", "Time period: 7d, 14d, 30d, 90d, 12mo, all", "30d")
60
+ .option("--start <date>", "Start date (YYYY-MM-DD)")
61
+ .option("--end <date>", "End date (YYYY-MM-DD)")
55
62
  .option("--property <key>", "Get breakdown for a specific property")
56
63
  .option("-l, --limit <number>", "Number of results", "20")
57
64
  .option("--no-revenue", "Exclude revenue metrics")
@@ -59,6 +66,9 @@ export const eventsCommand = new Command("events")
59
66
  .option("-t, --test", "Test mode: query localhost data instead of production")
60
67
  .action(async (event, options) => {
61
68
  const site = options.site || (await getDefaultSite())
69
+ const dateRange = options.start && options.end
70
+ ? [options.start, options.end] as [string, string]
71
+ : parsePeriod(options.period)
62
72
 
63
73
  if (!site) {
64
74
  console.error(
@@ -74,7 +84,7 @@ export const eventsCommand = new Command("events")
74
84
  if (!event) {
75
85
  const response = await listEvents(
76
86
  site,
77
- options.period,
87
+ dateRange,
78
88
  parseInt(options.limit),
79
89
  options.test || false
80
90
  )
@@ -110,7 +120,7 @@ export const eventsCommand = new Command("events")
110
120
  site,
111
121
  event,
112
122
  options.property,
113
- options.period,
123
+ dateRange,
114
124
  parseInt(options.limit),
115
125
  options.revenue !== false,
116
126
  options.test || false
@@ -146,8 +156,8 @@ export const eventsCommand = new Command("events")
146
156
 
147
157
  // If just event name, show event stats + properties
148
158
  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),
159
+ listEvents(site, dateRange, 100, options.test || false), // Get all events to find this one
160
+ getEventProperties(site, event, dateRange, options.test || false),
151
161
  ])
152
162
 
153
163
  // Find the specific event stats