@funnelsgrove/cli 0.1.10 → 0.1.11

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
@@ -39,6 +39,23 @@ fgrove publish --funnel claimbee-general --env preview
39
39
 
40
40
  The GitHub commands use the FunnelsGrove API only. When GitHub is connected, `fgrove sync up` pushes the resulting draft to GitHub before returning, and `fgrove publish` waits for the current draft to reach GitHub before publishing. Local `.env*` files remain CLI-local runtime material from `sync down`; they are not sent to GitHub sync.
41
41
 
42
+ Analytics workflow:
43
+
44
+ ```bash
45
+ fgrove analytics conversions --project claimbee --funnel claimbee-ios --date 2026-06-11
46
+ fgrove analytics funnel-path --project claimbee --funnel claimbee-ios --date 2026-06-11
47
+ fgrove analytics transitions --project claimbee --funnel claimbee-ios --date 2026-06-11
48
+ fgrove analytics cohort --project claimbee --date 2026-06-11
49
+ ```
50
+
51
+ Each analytics command accepts `--format table|json`, `--out <path>`, `--workspace`, and `--timezone`. Use JSON for agents and automations:
52
+
53
+ ```bash
54
+ fgrove analytics conversions --project claimbee --funnel claimbee-ios --date 2026-06-11 --format json --out conversions.json
55
+ ```
56
+
57
+ `conversions` downloads the one-day conversion totals, primary conversion metrics, full funnel path rows, and step transitions. `funnel-path` focuses on the ordered path report. `transitions` focuses on step-by-step advanced/drop-off counts. `cohort` downloads synced marketing cohort economics for the day. If the requested day has no synced data, or cohort source data is incomplete, the CLI exits non-zero with a human-readable explanation.
58
+
42
59
  The package also keeps the longer `funnelsgrove` command as a compatibility alias.
43
60
  Use `--api-url` or `FUNNELSGROVE_API_URL` for non-production APIs.
44
61
  Use `--config` or `FUNNELSGROVE_CONFIG` to keep test credentials separate from the default `~/.funnelsgrove/config.json`.
@@ -0,0 +1,276 @@
1
+ export type AnalyticsOutputFormat = 'table' | 'json';
2
+ export type AnalyticsProjectSummary = {
3
+ id: string;
4
+ name?: string | null;
5
+ slug?: string | null;
6
+ };
7
+ export type AnalyticsFunnelSummary = {
8
+ id: string;
9
+ name?: string | null;
10
+ slug?: string | null;
11
+ };
12
+ export type AnalyticsOverviewResponse = {
13
+ generatedAt: string;
14
+ from: string | null;
15
+ to: string | null;
16
+ currency: string | null;
17
+ funnels: AnalyticsFunnelSummary[];
18
+ totals: Record<string, number>;
19
+ byDay: Array<{
20
+ date: string;
21
+ visitors: number;
22
+ subscriptions: number;
23
+ chargedRevenueMinor: number;
24
+ }>;
25
+ };
26
+ export type AnalyticsStepDropoffResponse = {
27
+ generatedAt: string;
28
+ from: string | null;
29
+ to: string | null;
30
+ funnel: {
31
+ id: string;
32
+ name: string;
33
+ slug: string;
34
+ };
35
+ version: {
36
+ selected: {
37
+ id: string;
38
+ seq: number;
39
+ message: string | null;
40
+ publishedAt: string | null;
41
+ isCurrent: boolean;
42
+ } | null;
43
+ options: Array<{
44
+ id: string;
45
+ seq: number;
46
+ message: string | null;
47
+ publishedAt: string | null;
48
+ isCurrent: boolean;
49
+ }>;
50
+ };
51
+ totals: Record<string, number>;
52
+ metrics: Array<{
53
+ id: string;
54
+ label: string;
55
+ numerator: number;
56
+ denominator: number;
57
+ rate: number;
58
+ numeratorLabel: string;
59
+ denominatorLabel: string;
60
+ description: string;
61
+ chartPoints: unknown[];
62
+ }>;
63
+ steps: AnalyticsStepTransition[];
64
+ flow: {
65
+ steps: Array<{
66
+ stepId: string;
67
+ stepName: string | null;
68
+ stepPath: string | null;
69
+ stepTitle: string | null;
70
+ stepType: string | null;
71
+ stepKind: string | null;
72
+ stepOrder: number;
73
+ }>;
74
+ experiments: unknown[];
75
+ branches: unknown[];
76
+ } | null;
77
+ };
78
+ export type AnalyticsStepTransition = {
79
+ stepId: string;
80
+ stepName: string | null;
81
+ stepOrder: number;
82
+ entrants: number;
83
+ completions: number;
84
+ advancedToNextStep: number;
85
+ dropoffs: number;
86
+ dropoffRate: number;
87
+ nextStepId: string | null;
88
+ nextStepName: string | null;
89
+ };
90
+ export type MarketingCohortPerformanceRow = {
91
+ id: string;
92
+ cohortDate: string;
93
+ funnelUrl: string;
94
+ mediaSource: string;
95
+ spend: number;
96
+ subscribers: number;
97
+ subscribersAlive: number;
98
+ subscribersAlivePercent: number | null;
99
+ revenue: number;
100
+ pLtv: number | null;
101
+ pLtvPredictedNetRevenueDay365Minor: number;
102
+ pLtvPredictedCustomerCount: number;
103
+ campaign: string;
104
+ channel: string;
105
+ country: string;
106
+ children?: MarketingCohortPerformanceRow[];
107
+ segments?: MarketingCohortPerformanceRow[];
108
+ };
109
+ export type MarketingCohortPerformanceReport = {
110
+ generatedAt: string;
111
+ from: string | null;
112
+ to: string | null;
113
+ rows: MarketingCohortPerformanceRow[];
114
+ sourceHealth?: {
115
+ complete: boolean;
116
+ warnings: string[];
117
+ };
118
+ };
119
+ export type ParsedAnalyticsDate = {
120
+ date: string;
121
+ fromIso: string;
122
+ toIso: string;
123
+ };
124
+ type ReportContext = {
125
+ date: string;
126
+ workspaceId: string;
127
+ project: AnalyticsProjectSummary;
128
+ };
129
+ export declare const parseAnalyticsDate: (value: string) => ParsedAnalyticsDate;
130
+ export declare const normalizeAnalyticsOutputFormat: (value: string | undefined) => AnalyticsOutputFormat;
131
+ export declare const assertHasConversionData: (date: string, overview: AnalyticsOverviewResponse, stepDropoff: AnalyticsStepDropoffResponse) => void;
132
+ export declare const assertHasTransitionData: (date: string, stepDropoff: AnalyticsStepDropoffResponse) => void;
133
+ export declare const assertHasFunnelPathData: (date: string, stepDropoff: AnalyticsStepDropoffResponse) => void;
134
+ export declare const assertHasCohortData: (date: string, report: MarketingCohortPerformanceReport) => void;
135
+ export declare const buildTransitionRows: (stepDropoff: AnalyticsStepDropoffResponse) => {
136
+ stepId: string;
137
+ step: string;
138
+ nextStep: string;
139
+ entrants: number;
140
+ advancedToNextStep: number;
141
+ dropoffs: number;
142
+ dropoffRate: number;
143
+ }[];
144
+ export declare const buildFunnelPathRows: (stepDropoff: AnalyticsStepDropoffResponse) => {
145
+ stepId: string;
146
+ path: string;
147
+ label: string;
148
+ users: number;
149
+ advancedToNextStep: number;
150
+ conversionToNextStep: number;
151
+ dropoffs: number;
152
+ dropoffRate: number;
153
+ }[];
154
+ export declare const buildConversionReportPayload: (input: ReportContext & {
155
+ overview: AnalyticsOverviewResponse;
156
+ stepDropoff: AnalyticsStepDropoffResponse;
157
+ }) => {
158
+ generatedAt: string;
159
+ date: string;
160
+ workspaceId: string;
161
+ project: AnalyticsProjectSummary;
162
+ funnel: {
163
+ id: string;
164
+ name: string;
165
+ slug: string;
166
+ };
167
+ overview: AnalyticsOverviewResponse;
168
+ totals: Record<string, number>;
169
+ metrics: {
170
+ id: string;
171
+ label: string;
172
+ numerator: number;
173
+ denominator: number;
174
+ rate: number;
175
+ numeratorLabel: string;
176
+ denominatorLabel: string;
177
+ description: string;
178
+ chartPoints: unknown[];
179
+ }[];
180
+ funnelPath: {
181
+ stepId: string;
182
+ path: string;
183
+ label: string;
184
+ users: number;
185
+ advancedToNextStep: number;
186
+ conversionToNextStep: number;
187
+ dropoffs: number;
188
+ dropoffRate: number;
189
+ }[];
190
+ transitions: {
191
+ stepId: string;
192
+ step: string;
193
+ nextStep: string;
194
+ entrants: number;
195
+ advancedToNextStep: number;
196
+ dropoffs: number;
197
+ dropoffRate: number;
198
+ }[];
199
+ };
200
+ export declare const buildFunnelPathReportPayload: (input: ReportContext & {
201
+ stepDropoff: AnalyticsStepDropoffResponse;
202
+ }) => {
203
+ generatedAt: string;
204
+ date: string;
205
+ workspaceId: string;
206
+ project: AnalyticsProjectSummary;
207
+ funnel: {
208
+ id: string;
209
+ name: string;
210
+ slug: string;
211
+ };
212
+ funnelPath: {
213
+ stepId: string;
214
+ path: string;
215
+ label: string;
216
+ users: number;
217
+ advancedToNextStep: number;
218
+ conversionToNextStep: number;
219
+ dropoffs: number;
220
+ dropoffRate: number;
221
+ }[];
222
+ };
223
+ export declare const buildTransitionReportPayload: (input: ReportContext & {
224
+ stepDropoff: AnalyticsStepDropoffResponse;
225
+ }) => {
226
+ generatedAt: string;
227
+ date: string;
228
+ workspaceId: string;
229
+ project: AnalyticsProjectSummary;
230
+ funnel: {
231
+ id: string;
232
+ name: string;
233
+ slug: string;
234
+ };
235
+ transitions: {
236
+ stepId: string;
237
+ step: string;
238
+ nextStep: string;
239
+ entrants: number;
240
+ advancedToNextStep: number;
241
+ dropoffs: number;
242
+ dropoffRate: number;
243
+ }[];
244
+ };
245
+ export declare const buildCohortReportPayload: (input: ReportContext & {
246
+ cohort: MarketingCohortPerformanceReport;
247
+ }) => {
248
+ generatedAt: string;
249
+ date: string;
250
+ workspaceId: string;
251
+ project: AnalyticsProjectSummary;
252
+ cohort: MarketingCohortPerformanceReport;
253
+ sourceHealth: {
254
+ complete: boolean;
255
+ warnings: string[];
256
+ } | null;
257
+ };
258
+ export declare const formatAnalyticsJson: (payload: unknown) => string;
259
+ export declare const formatTransitionsTable: (input: {
260
+ date: string;
261
+ stepDropoff: AnalyticsStepDropoffResponse;
262
+ }) => string;
263
+ export declare const formatFunnelPathTable: (input: {
264
+ date: string;
265
+ stepDropoff: AnalyticsStepDropoffResponse;
266
+ }) => string;
267
+ export declare const formatConversionsTable: (input: {
268
+ date: string;
269
+ overview: AnalyticsOverviewResponse;
270
+ stepDropoff: AnalyticsStepDropoffResponse;
271
+ }) => string;
272
+ export declare const formatCohortTable: (input: {
273
+ date: string;
274
+ cohort: MarketingCohortPerformanceReport;
275
+ }) => string;
276
+ export {};
@@ -0,0 +1,254 @@
1
+ export const parseAnalyticsDate = (value) => {
2
+ const trimmed = value.trim();
3
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(trimmed);
4
+ if (!match) {
5
+ throw new Error('Expected --date in YYYY-MM-DD format.');
6
+ }
7
+ const year = Number(match[1]);
8
+ const month = Number(match[2]);
9
+ const day = Number(match[3]);
10
+ const timestamp = Date.UTC(year, month - 1, day);
11
+ const parsed = new Date(timestamp);
12
+ const isRealDate = parsed.getUTCFullYear() === year &&
13
+ parsed.getUTCMonth() + 1 === month &&
14
+ parsed.getUTCDate() === day;
15
+ if (!isRealDate) {
16
+ throw new Error('Expected --date to be a real calendar date.');
17
+ }
18
+ return {
19
+ date: trimmed,
20
+ fromIso: `${trimmed}T00:00:00.000Z`,
21
+ toIso: `${trimmed}T23:59:59.999Z`,
22
+ };
23
+ };
24
+ export const normalizeAnalyticsOutputFormat = (value) => {
25
+ const normalized = (value || 'table').trim().toLowerCase();
26
+ if (normalized === 'table' || normalized === 'json') {
27
+ return normalized;
28
+ }
29
+ throw new Error('--format must be table or json.');
30
+ };
31
+ const hasPositiveNumber = (value) => {
32
+ if (typeof value === 'number') {
33
+ return Number.isFinite(value) && value > 0;
34
+ }
35
+ if (Array.isArray(value)) {
36
+ return value.some(hasPositiveNumber);
37
+ }
38
+ if (value && typeof value === 'object') {
39
+ return Object.values(value).some(hasPositiveNumber);
40
+ }
41
+ return false;
42
+ };
43
+ export const assertHasConversionData = (date, overview, stepDropoff) => {
44
+ if (hasPositiveNumber(overview.totals) ||
45
+ hasPositiveNumber(overview.byDay) ||
46
+ hasPositiveNumber(stepDropoff.totals) ||
47
+ hasPositiveNumber(stepDropoff.steps)) {
48
+ return;
49
+ }
50
+ throw new Error(`No synced conversion data found for ${date}. Analytics may not have synced yet, or the funnel had no tracked traffic that day.`);
51
+ };
52
+ export const assertHasTransitionData = (date, stepDropoff) => {
53
+ if (stepDropoff.steps.length > 0 && hasPositiveNumber(stepDropoff.steps)) {
54
+ return;
55
+ }
56
+ throw new Error(`No synced step transition data found for ${date}. Analytics may not have synced yet, or the funnel had no tracked step traffic that day.`);
57
+ };
58
+ export const assertHasFunnelPathData = (date, stepDropoff) => {
59
+ if (buildFunnelPathRows(stepDropoff).length > 0 && hasPositiveNumber(stepDropoff.steps)) {
60
+ return;
61
+ }
62
+ throw new Error(`No synced full funnel path data found for ${date}. Analytics may not have synced yet, or the funnel had no tracked path traffic that day.`);
63
+ };
64
+ export const assertHasCohortData = (date, report) => {
65
+ if (report.sourceHealth && !report.sourceHealth.complete) {
66
+ const warnings = report.sourceHealth.warnings.join(' ').trim();
67
+ throw new Error(`Cohort source data for ${date} is not fully synced. ${warnings || 'Required marketing sources have not finished syncing.'}`);
68
+ }
69
+ if (report.rows.length > 0) {
70
+ return;
71
+ }
72
+ throw new Error(`No synced cohort data found for ${date}. Marketing cohort data may not have synced yet, or there were no subscribers for that day.`);
73
+ };
74
+ const formatNumber = (value) => Math.round(value).toLocaleString('en-US');
75
+ const formatPercent = (value) => (typeof value === 'number' && Number.isFinite(value)
76
+ ? `${(value * 100).toFixed(2)}%`
77
+ : '');
78
+ const formatPercentPoints = (value) => (typeof value === 'number' && Number.isFinite(value)
79
+ ? `${value.toFixed(2)}%`
80
+ : '');
81
+ const formatCurrency = (value) => new Intl.NumberFormat('en-US', {
82
+ currency: 'USD',
83
+ maximumFractionDigits: 2,
84
+ style: 'currency',
85
+ }).format(value);
86
+ const formatMinorCurrency = (value) => formatCurrency((Number(value) || 0) / 100);
87
+ const stepDisplayName = (step) => step.stepName || step.stepId;
88
+ const flowStepDisplay = (stepDropoff, step) => {
89
+ const flowStep = stepDropoff.flow?.steps.find((candidate) => candidate.stepId === step.stepId);
90
+ return {
91
+ label: flowStep?.stepTitle || step.stepName || flowStep?.stepName || step.stepId,
92
+ path: flowStep?.stepPath || step.stepId,
93
+ };
94
+ };
95
+ export const buildTransitionRows = (stepDropoff) => [...stepDropoff.steps]
96
+ .sort((left, right) => left.stepOrder - right.stepOrder || left.stepId.localeCompare(right.stepId))
97
+ .map((step) => ({
98
+ stepId: step.stepId,
99
+ step: stepDisplayName(step),
100
+ nextStep: step.nextStepId || '',
101
+ entrants: step.entrants,
102
+ advancedToNextStep: step.advancedToNextStep,
103
+ dropoffs: step.dropoffs,
104
+ dropoffRate: step.dropoffRate,
105
+ }));
106
+ export const buildFunnelPathRows = (stepDropoff) => buildTransitionRows(stepDropoff).map((row) => {
107
+ const step = stepDropoff.steps.find((candidate) => candidate.stepId === row.stepId);
108
+ const display = flowStepDisplay(stepDropoff, step);
109
+ return {
110
+ stepId: row.stepId,
111
+ path: display.path,
112
+ label: display.label,
113
+ users: row.entrants,
114
+ advancedToNextStep: row.advancedToNextStep,
115
+ conversionToNextStep: row.entrants > 0 ? row.advancedToNextStep / row.entrants : 0,
116
+ dropoffs: row.dropoffs,
117
+ dropoffRate: row.dropoffRate,
118
+ };
119
+ });
120
+ export const buildConversionReportPayload = (input) => ({
121
+ generatedAt: new Date().toISOString(),
122
+ date: input.date,
123
+ workspaceId: input.workspaceId,
124
+ project: input.project,
125
+ funnel: input.stepDropoff.funnel,
126
+ overview: input.overview,
127
+ totals: input.stepDropoff.totals,
128
+ metrics: input.stepDropoff.metrics,
129
+ funnelPath: buildFunnelPathRows(input.stepDropoff),
130
+ transitions: buildTransitionRows(input.stepDropoff),
131
+ });
132
+ export const buildFunnelPathReportPayload = (input) => ({
133
+ generatedAt: new Date().toISOString(),
134
+ date: input.date,
135
+ workspaceId: input.workspaceId,
136
+ project: input.project,
137
+ funnel: input.stepDropoff.funnel,
138
+ funnelPath: buildFunnelPathRows(input.stepDropoff),
139
+ });
140
+ export const buildTransitionReportPayload = (input) => ({
141
+ generatedAt: new Date().toISOString(),
142
+ date: input.date,
143
+ workspaceId: input.workspaceId,
144
+ project: input.project,
145
+ funnel: input.stepDropoff.funnel,
146
+ transitions: buildTransitionRows(input.stepDropoff),
147
+ });
148
+ export const buildCohortReportPayload = (input) => ({
149
+ generatedAt: new Date().toISOString(),
150
+ date: input.date,
151
+ workspaceId: input.workspaceId,
152
+ project: input.project,
153
+ cohort: input.cohort,
154
+ sourceHealth: input.cohort.sourceHealth || null,
155
+ });
156
+ export const formatAnalyticsJson = (payload) => `${JSON.stringify(payload, null, 2)}\n`;
157
+ export const formatTransitionsTable = (input) => {
158
+ const lines = [
159
+ `Step transitions\t${input.date}\t${input.stepDropoff.funnel.name}`,
160
+ 'step\tnextStep\tentrants\tadvanced\tdropoffs\tdropoffRate',
161
+ ];
162
+ for (const row of buildTransitionRows(input.stepDropoff)) {
163
+ lines.push([
164
+ row.step,
165
+ row.nextStep,
166
+ formatNumber(row.entrants),
167
+ formatNumber(row.advancedToNextStep),
168
+ formatNumber(row.dropoffs),
169
+ formatPercent(row.dropoffRate),
170
+ ].join('\t'));
171
+ }
172
+ return `${lines.join('\n')}\n`;
173
+ };
174
+ export const formatFunnelPathTable = (input) => {
175
+ const lines = [
176
+ `Full funnel path\t${input.date}\t${input.stepDropoff.funnel.name}`,
177
+ 'step\tpath\tusers\tadvanced\tconversionToNext\tdropoffs\tdropoffRate',
178
+ ];
179
+ for (const row of buildFunnelPathRows(input.stepDropoff)) {
180
+ lines.push([
181
+ row.label,
182
+ row.path,
183
+ formatNumber(row.users),
184
+ formatNumber(row.advancedToNextStep),
185
+ formatPercent(row.conversionToNextStep),
186
+ formatNumber(row.dropoffs),
187
+ formatPercent(row.dropoffRate),
188
+ ].join('\t'));
189
+ }
190
+ return `${lines.join('\n')}\n`;
191
+ };
192
+ export const formatConversionsTable = (input) => {
193
+ const totals = input.stepDropoff.totals;
194
+ const overviewTotals = input.overview.totals;
195
+ const lines = [
196
+ `Conversions\t${input.date}\t${input.stepDropoff.funnel.name}`,
197
+ 'metric\tvalue',
198
+ ['visitors', formatNumber(totals.visitors ?? overviewTotals.visitors ?? 0)].join('\t'),
199
+ ['quizEntries', formatNumber(totals.quizEntries ?? 0)].join('\t'),
200
+ ['quizStarts', formatNumber(totals.quizStarts ?? overviewTotals.quizStarts ?? 0)].join('\t'),
201
+ ['finishedQuiz', formatNumber(totals.finishedQuiz ?? overviewTotals.finishedQuiz ?? 0)].join('\t'),
202
+ ['emailAdds', formatNumber(totals.emailAdds ?? overviewTotals.emailAdds ?? 0)].join('\t'),
203
+ ['paywallViews', formatNumber(totals.paywallViews ?? 0)].join('\t'),
204
+ ['checkoutStarts', formatNumber(totals.checkoutStarts ?? overviewTotals.checkoutStarts ?? 0)].join('\t'),
205
+ ['subscriptions', formatNumber(totals.subscriptions ?? overviewTotals.subscriptionsStarted ?? 0)].join('\t'),
206
+ ['completedRegistrations', formatNumber(totals.completedRegistrations ?? overviewTotals.completedRegistrations ?? 0)].join('\t'),
207
+ ['netRevenue', formatMinorCurrency(overviewTotals.netRevenueMinor)].join('\t'),
208
+ '',
209
+ 'conversionMetric\tnumerator\tdenominator\trate',
210
+ ];
211
+ for (const metric of input.stepDropoff.metrics) {
212
+ lines.push([
213
+ metric.label,
214
+ formatNumber(metric.numerator),
215
+ formatNumber(metric.denominator),
216
+ formatPercent(metric.rate),
217
+ ].join('\t'));
218
+ }
219
+ lines.push('', formatFunnelPathTable({
220
+ date: input.date,
221
+ stepDropoff: input.stepDropoff,
222
+ }).trimEnd());
223
+ return `${lines.join('\n')}\n`;
224
+ };
225
+ const flattenCohortRows = (rows) => rows.flatMap((row) => [row, ...(row.segments || []), ...(row.children || [])]);
226
+ const predictedAverageLtv = (row) => {
227
+ if (row.pLtvPredictedCustomerCount > 0) {
228
+ return row.pLtvPredictedNetRevenueDay365Minor / row.pLtvPredictedCustomerCount / 100;
229
+ }
230
+ return Number(row.pLtv) || 0;
231
+ };
232
+ export const formatCohortTable = (input) => {
233
+ const lines = [
234
+ `Cohort\t${input.date}`,
235
+ 'cohort\tmediaSource\tfunnelUrl\tspend\tsubscribers\taliveRate\trevenue\tpLtv\tcpa\tpredictedProfit',
236
+ ];
237
+ for (const row of flattenCohortRows(input.cohort.rows)) {
238
+ const cpa = row.subscribers > 0 ? row.spend / row.subscribers : 0;
239
+ const predictedRevenue = predictedAverageLtv(row) * row.subscribers;
240
+ lines.push([
241
+ row.cohortDate,
242
+ row.mediaSource,
243
+ row.funnelUrl,
244
+ formatCurrency(row.spend),
245
+ formatNumber(row.subscribers),
246
+ formatPercentPoints(row.subscribersAlivePercent),
247
+ formatCurrency(row.revenue),
248
+ row.pLtv === null ? '' : formatCurrency(row.pLtv),
249
+ row.subscribers > 0 ? formatCurrency(cpa) : '',
250
+ formatCurrency(predictedRevenue - row.spend),
251
+ ].join('\t'));
252
+ }
253
+ return `${lines.join('\n')}\n`;
254
+ };
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { type ActiveContext } from './authStore.js';
3
3
  import { type SyncManifest } from './localSync.js';
4
+ import { type AnalyticsOutputFormat } from './analyticsOutput.js';
5
+ type AnalyticsCommandInput = {
6
+ date: string;
7
+ from: string;
8
+ to: string;
9
+ format: AnalyticsOutputFormat;
10
+ timezone?: string;
11
+ };
4
12
  type SyncTargetIdInput = {
5
13
  explicitWorkspaceId?: string;
6
14
  explicitFunnelId?: string;
@@ -13,4 +21,9 @@ export declare function resolveSyncTargetIds(input: SyncTargetIdInput): {
13
21
  funnelId?: string;
14
22
  };
15
23
  export declare function isCliEntrypoint(invokedPath: string | undefined, modulePath: string, realpath?: (filePath: string) => string): boolean;
24
+ export declare function buildAnalyticsCommandInput(options: {
25
+ date: string;
26
+ format?: string;
27
+ timezone?: string;
28
+ }): AnalyticsCommandInput;
16
29
  export {};