@funnelsgrove/cli 0.1.20 → 0.1.24
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 +16 -0
- package/dist/analyticsOutput.d.ts +2 -1
- package/dist/analyticsOutput.js +35 -10
- package/dist/apiClient.d.ts +9 -1
- package/dist/apiClient.js +6 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +37 -1
- package/dist/experimentCreate.d.ts +100 -0
- package/dist/experimentCreate.js +887 -0
- package/dist/localSync.d.ts +8 -0
- package/dist/localSync.js +71 -11
- package/package.json +1 -1
- package/template_docs/.funnelsgrove-docs.json +4 -4
- package/template_docs/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
- package/template_docs/docs/funnelsgrove/recipes/add-experiment.md +156 -7
- package/template_docs/funnel-docs.config.json +1 -1
- package/template_scaffold/.funnelsgrove-docs.json +4 -4
- package/template_scaffold/.funnelsgrove-scaffold.json +9 -9
- package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
- package/template_scaffold/docs/funnelsgrove/recipes/add-experiment.md +156 -7
- package/template_scaffold/funnel-agent-docs.test.ts +27 -1
- package/template_scaffold/funnel-docs.config.json +1 -1
- package/template_scaffold/package-lock.json +4 -4
- package/template_scaffold/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,6 +41,22 @@ for the same local diff.
|
|
|
41
41
|
|
|
42
42
|
Use `fgrove env pull` from a synced folder to refresh only the ignored local `.env` file after project settings change, without replacing source files.
|
|
43
43
|
|
|
44
|
+
Create an experiment draft from a strict JSON spec:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
fgrove experiments create --spec experiment.json --dir .
|
|
48
|
+
fgrove experiments create --spec experiment.json --dir . --json
|
|
49
|
+
fgrove validate
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The spec uses a stable `id` and `type: "step"`, `"paywall"`, or `"pricing"` with two to five variants, exactly one control, unique metrics and variant keys, and integer traffic totaling 100. Pricing variants also require `offerSetKey` and must route to the top-level `stepId`. See `docs/funnelsgrove/recipes/add-experiment.md` in a synced funnel for complete valid JSON examples and remediation guidance.
|
|
53
|
+
|
|
54
|
+
Creation is draft-only: it creates no PostHog flag and activates no traffic. The experiment becomes visible in the UI after the API commits its hosted generated snapshot, and the CLI installs those exact generated bytes locally through recoverable staging. `.funnelsgrove-sync.json` stays local and ignored.
|
|
55
|
+
|
|
56
|
+
With `--json`, success output contains only `experimentId`, `experimentKey`, `draftVersionId`, and `writtenPaths`, with generated paths in canonical order.
|
|
57
|
+
|
|
58
|
+
For a non-GitHub funnel, deliver the local source with `fgrove sync up`. For a GitHub-connected funnel, commit and push the generated source with the rest of the change, then run `fgrove github pull`; do not commit `.funnelsgrove-sync.json` or use `fgrove sync up` for that diff.
|
|
59
|
+
|
|
44
60
|
GitHub sync workflow:
|
|
45
61
|
|
|
46
62
|
```bash
|
|
@@ -108,8 +108,9 @@ export type MarketingCohortPerformanceRow = {
|
|
|
108
108
|
revenueDay3: number;
|
|
109
109
|
revenueDay90: number;
|
|
110
110
|
revenueDay180: number;
|
|
111
|
+
pLtvCurrency: string | null;
|
|
111
112
|
pLtv: number | null;
|
|
112
|
-
pLtvPredictedNetRevenueDay365Minor: number;
|
|
113
|
+
pLtvPredictedNetRevenueDay365Minor: number | null;
|
|
113
114
|
pLtvPredictedCustomerCount: number;
|
|
114
115
|
campaign: string;
|
|
115
116
|
channel: string;
|
package/dist/analyticsOutput.js
CHANGED
|
@@ -78,8 +78,8 @@ const formatPercent = (value) => (typeof value === 'number' && Number.isFinite(v
|
|
|
78
78
|
const formatPercentPoints = (value) => (typeof value === 'number' && Number.isFinite(value)
|
|
79
79
|
? `${value.toFixed(2)}%`
|
|
80
80
|
: '');
|
|
81
|
-
const formatCurrency = (value) => new Intl.NumberFormat('en-US', {
|
|
82
|
-
currency
|
|
81
|
+
const formatCurrency = (value, currency = 'USD') => new Intl.NumberFormat('en-US', {
|
|
82
|
+
currency,
|
|
83
83
|
maximumFractionDigits: 2,
|
|
84
84
|
style: 'currency',
|
|
85
85
|
}).format(value);
|
|
@@ -225,11 +225,25 @@ export const formatConversionsTable = (input) => {
|
|
|
225
225
|
return `${lines.join('\n')}\n`;
|
|
226
226
|
};
|
|
227
227
|
const flattenCohortRows = (rows) => rows.flatMap((row) => [row, ...(row.segments || []), ...(row.children || [])]);
|
|
228
|
+
const normalizeCurrency = (value) => {
|
|
229
|
+
const currency = value?.trim().toUpperCase() || '';
|
|
230
|
+
return /^[A-Z]{3}$/.test(currency) ? currency : null;
|
|
231
|
+
};
|
|
228
232
|
const predictedAverageLtv = (row) => {
|
|
233
|
+
if (!normalizeCurrency(row.pLtvCurrency)) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
229
236
|
if (row.pLtvPredictedCustomerCount > 0) {
|
|
230
|
-
return row.pLtvPredictedNetRevenueDay365Minor
|
|
237
|
+
return typeof row.pLtvPredictedNetRevenueDay365Minor === 'number'
|
|
238
|
+
&& Number.isFinite(row.pLtvPredictedNetRevenueDay365Minor)
|
|
239
|
+
? row.pLtvPredictedNetRevenueDay365Minor
|
|
240
|
+
/ row.pLtvPredictedCustomerCount
|
|
241
|
+
/ 100
|
|
242
|
+
: null;
|
|
231
243
|
}
|
|
232
|
-
return Number(row.pLtv)
|
|
244
|
+
return typeof row.pLtv === 'number' && Number.isFinite(row.pLtv)
|
|
245
|
+
? row.pLtv
|
|
246
|
+
: null;
|
|
233
247
|
};
|
|
234
248
|
const ratioToSpend = (value, spend) => (Number.isFinite(value) && Number.isFinite(spend) && spend !== 0 ? value / spend : null);
|
|
235
249
|
const formatRoi = (profit, spend) => {
|
|
@@ -243,12 +257,18 @@ const formatRoas = (revenue, spend) => {
|
|
|
243
257
|
export const formatCohortTable = (input) => {
|
|
244
258
|
const lines = [
|
|
245
259
|
`Cohort\t${input.date}`,
|
|
246
|
-
'cohort\tmediaSource\tfunnelUrl\tspend\tsubscribers\taliveRate\trevenue\tpLtv\tcpa\tpredictedProfit\tpROI\tROAS\tROAS d3\tROAS d90\tROAS d180',
|
|
260
|
+
'cohort\tmediaSource\tfunnelUrl\tspend\tsubscribers\taliveRate\trevenue\tpLtv\tpLtvCurrency\tcpa\tpredictedProfit\tpROI\tROAS\tROAS d3\tROAS d90\tROAS d180',
|
|
247
261
|
];
|
|
248
262
|
for (const row of flattenCohortRows(input.cohort.rows)) {
|
|
249
263
|
const cpa = row.subscribers > 0 ? row.spend / row.subscribers : 0;
|
|
250
|
-
const
|
|
251
|
-
const
|
|
264
|
+
const averageLtv = predictedAverageLtv(row);
|
|
265
|
+
const predictedRevenue = averageLtv === null
|
|
266
|
+
? null
|
|
267
|
+
: averageLtv * row.subscribers;
|
|
268
|
+
const predictedProfit = predictedRevenue === null
|
|
269
|
+
? null
|
|
270
|
+
: predictedRevenue - row.spend;
|
|
271
|
+
const pLtvCurrency = normalizeCurrency(row.pLtvCurrency);
|
|
252
272
|
lines.push([
|
|
253
273
|
row.cohortDate,
|
|
254
274
|
row.mediaSource,
|
|
@@ -257,10 +277,15 @@ export const formatCohortTable = (input) => {
|
|
|
257
277
|
formatNumber(row.subscribers),
|
|
258
278
|
formatPercentPoints(row.subscribersAlivePercent),
|
|
259
279
|
formatCurrency(row.revenue),
|
|
260
|
-
row.pLtv === null
|
|
280
|
+
row.pLtv === null || pLtvCurrency === null
|
|
281
|
+
? ''
|
|
282
|
+
: formatCurrency(row.pLtv, pLtvCurrency),
|
|
283
|
+
pLtvCurrency || '',
|
|
261
284
|
row.subscribers > 0 ? formatCurrency(cpa) : '',
|
|
262
|
-
|
|
263
|
-
|
|
285
|
+
predictedProfit === null
|
|
286
|
+
? ''
|
|
287
|
+
: formatCurrency(predictedProfit, pLtvCurrency || 'USD'),
|
|
288
|
+
predictedProfit === null ? '' : formatRoi(predictedProfit, row.spend),
|
|
264
289
|
formatRoas(row.revenue, row.spend),
|
|
265
290
|
formatRoas(row.revenueDay3, row.spend),
|
|
266
291
|
formatRoas(row.revenueDay90, row.spend),
|
package/dist/apiClient.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FunnelContractDiagnosticCode } from '@funnelsgrove/runtime';
|
|
2
2
|
import type { SourceCandidateOperation } from './localSync.js';
|
|
3
|
+
import type { AgentExperimentSpec, ExperimentCreateResponse } from './experimentCreate.js';
|
|
3
4
|
type FetchFn = typeof fetch;
|
|
4
5
|
type TrpcProcedureType = 'query' | 'mutation';
|
|
5
6
|
export type TrpcCallInput = {
|
|
@@ -17,6 +18,12 @@ export type FunnelSourceCandidateApiContext = {
|
|
|
17
18
|
fetchFn?: FetchFn;
|
|
18
19
|
signal?: AbortSignal;
|
|
19
20
|
};
|
|
21
|
+
export type CreateFunnelExperimentFromSpecInput = {
|
|
22
|
+
workspaceId: string;
|
|
23
|
+
funnelId: string;
|
|
24
|
+
expectedDraftVersionId: string;
|
|
25
|
+
spec: AgentExperimentSpec;
|
|
26
|
+
};
|
|
20
27
|
export type FunnelSourceCandidateStatus = 'staging' | 'validating' | 'committed' | 'aborted' | 'expired' | 'failed';
|
|
21
28
|
export type BeginFunnelSourceCandidateInput = {
|
|
22
29
|
workspaceId: string;
|
|
@@ -64,7 +71,7 @@ export type FinalizeFunnelSourceCandidateResult = {
|
|
|
64
71
|
idempotent: boolean;
|
|
65
72
|
diagnostics: readonly FunnelContractApiDiagnostic[];
|
|
66
73
|
};
|
|
67
|
-
export declare const FUNNEL_CONTRACT_API_INGRESSES: readonly ["
|
|
74
|
+
export declare const FUNNEL_CONTRACT_API_INGRESSES: readonly ["cli-sync", "github-sync", "builder-authoring", "preview-publish", "production-publish", "published-artifact-serving"];
|
|
68
75
|
export type FunnelContractApiIngress = (typeof FUNNEL_CONTRACT_API_INGRESSES)[number];
|
|
69
76
|
export type FunnelContractApiDiagnostic = {
|
|
70
77
|
code: FunnelContractDiagnosticCode;
|
|
@@ -95,4 +102,5 @@ export declare const beginFunnelSourceCandidate: (context: FunnelSourceCandidate
|
|
|
95
102
|
export declare const stageFunnelSourceCandidate: (context: FunnelSourceCandidateApiContext, input: StageFunnelSourceCandidateInput) => Promise<StageFunnelSourceCandidateResult>;
|
|
96
103
|
export declare const finalizeFunnelSourceCandidate: (context: FunnelSourceCandidateApiContext, input: FinalizeFunnelSourceCandidateInput) => Promise<FinalizeFunnelSourceCandidateResult>;
|
|
97
104
|
export declare const abortFunnelSourceCandidate: (context: FunnelSourceCandidateApiContext, input: FinishFunnelSourceCandidateInput) => Promise<FunnelSourceCandidateSummary>;
|
|
105
|
+
export declare const createFunnelExperimentFromSpec: (context: FunnelSourceCandidateApiContext, input: CreateFunnelExperimentFromSpecInput) => Promise<ExperimentCreateResponse>;
|
|
98
106
|
export {};
|
package/dist/apiClient.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { CURRENT_STEP_CONTRACT_VERSION, FUNNEL_CONTRACT_DIAGNOSTIC_CODES, } from '@funnelsgrove/runtime';
|
|
2
2
|
export const FUNNEL_CONTRACT_API_INGRESSES = Object.freeze([
|
|
3
|
-
'agent-transaction',
|
|
4
3
|
'cli-sync',
|
|
5
4
|
'github-sync',
|
|
6
5
|
'builder-authoring',
|
|
@@ -276,3 +275,9 @@ export const beginFunnelSourceCandidate = (context, input) => callFunnelSourceCa
|
|
|
276
275
|
export const stageFunnelSourceCandidate = (context, input) => callFunnelSourceCandidateMutation(context, 'funnels.stageSourceCandidate', input);
|
|
277
276
|
export const finalizeFunnelSourceCandidate = (context, input) => callFunnelSourceCandidateMutation(context, 'funnels.finalizeSourceCandidate', input);
|
|
278
277
|
export const abortFunnelSourceCandidate = (context, input) => callFunnelSourceCandidateMutation(context, 'funnels.abortSourceCandidate', input);
|
|
278
|
+
export const createFunnelExperimentFromSpec = (context, input) => callTrpcProcedure({
|
|
279
|
+
...context,
|
|
280
|
+
path: 'funnelExperiments.createFromSpec',
|
|
281
|
+
type: 'mutation',
|
|
282
|
+
input,
|
|
283
|
+
});
|
package/dist/cli.d.ts
CHANGED
|
@@ -144,6 +144,7 @@ type SyncTargetIdInput = {
|
|
|
144
144
|
};
|
|
145
145
|
type GeneratedConfigKind = 'offerSets' | 'experiments';
|
|
146
146
|
export declare const SYNC_DOWN_GENERATED_CONFIG_KINDS: GeneratedConfigKind[];
|
|
147
|
+
export declare const EXPERIMENT_TRANSACTION_RECOVERY_COMMANDS: readonly ["sync down", "sync up", "experiments sync", "offer-sets sync"];
|
|
147
148
|
export declare function resolveSyncTargetIds(input: SyncTargetIdInput): {
|
|
148
149
|
workspaceId: string;
|
|
149
150
|
funnelId?: string;
|
package/dist/cli.js
CHANGED
|
@@ -8,9 +8,10 @@ import path from 'node:path';
|
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { Command } from 'commander';
|
|
10
10
|
import { CURRENT_STEP_CONTRACT_VERSION, FUNNEL_CONTRACT_DIAGNOSTIC_CODES, PREVIOUS_STEP_CONTRACT_VERSION, createFunnelContractDiagnostic, } from '@funnelsgrove/runtime';
|
|
11
|
-
import { abortFunnelSourceCandidate, beginFunnelSourceCandidate, callTrpcProcedure, finalizeFunnelSourceCandidate, stageFunnelSourceCandidate, } from './apiClient.js';
|
|
11
|
+
import { abortFunnelSourceCandidate, beginFunnelSourceCandidate, callTrpcProcedure, createFunnelExperimentFromSpec, finalizeFunnelSourceCandidate, stageFunnelSourceCandidate, } from './apiClient.js';
|
|
12
12
|
import { clearActiveContext, getDefaultAuthConfigPath, loadActiveContext, loadAuthToken, saveActiveContext, saveAuthToken, } from './authStore.js';
|
|
13
13
|
import { buildSyncManifest, buildCommittedSyncManifest, buildSourceCandidateOperations, collectChangedSourceFiles, collectSourceSnapshot, ensureGitignore, formatSyncUploadSummary, hasLocalSourceChanges, readSyncManifest, runSyncDownLocalLifecycle, runSourceCandidateSync, writeLocalEnvFile, writeSourceFiles, writeSyncManifest, } from './localSync.js';
|
|
14
|
+
import { executeExperimentCreate, formatExperimentCreateSuccess, recoverExperimentCreateTransaction, } from './experimentCreate.js';
|
|
14
15
|
import { pullEnvFile } from './envSync.js';
|
|
15
16
|
import { formatGitHubConnectRows, formatGitHubJobRows, formatGitHubStatusRows, } from './githubOutput.js';
|
|
16
17
|
import { syncGitHubDraftIfConnected } from './githubSyncFlow.js';
|
|
@@ -1059,6 +1060,13 @@ const GENERATED_CONFIG_PATH_BY_KIND = {
|
|
|
1059
1060
|
experiments: 'src/config/experiments.generated.ts',
|
|
1060
1061
|
};
|
|
1061
1062
|
export const SYNC_DOWN_GENERATED_CONFIG_KINDS = ['offerSets', 'experiments'];
|
|
1063
|
+
export const EXPERIMENT_TRANSACTION_RECOVERY_COMMANDS = [
|
|
1064
|
+
'sync down',
|
|
1065
|
+
'sync up',
|
|
1066
|
+
'experiments sync',
|
|
1067
|
+
'offer-sets sync',
|
|
1068
|
+
];
|
|
1069
|
+
const experimentTransactionRecoveryCommands = new Set(EXPERIMENT_TRANSACTION_RECOVERY_COMMANDS);
|
|
1062
1070
|
export function resolveSyncTargetIds(input) {
|
|
1063
1071
|
const workspaceId = input.explicitWorkspaceId ||
|
|
1064
1072
|
input.manifest?.workspaceId ||
|
|
@@ -1432,6 +1440,13 @@ const syncGeneratedConfigFilesForCli = async (input) => {
|
|
|
1432
1440
|
};
|
|
1433
1441
|
const addExamples = (command, examples) => command.addHelpText('after', `\nExamples:\n${examples.map((example) => ` $ ${example}`).join('\n')}`);
|
|
1434
1442
|
export const program = new Command();
|
|
1443
|
+
program.hook('preAction', async (_command, actionCommand) => {
|
|
1444
|
+
const commandPath = `${actionCommand.parent?.name() || ''} ${actionCommand.name()}`.trim();
|
|
1445
|
+
if (!experimentTransactionRecoveryCommands.has(commandPath))
|
|
1446
|
+
return;
|
|
1447
|
+
const options = actionCommand.opts();
|
|
1448
|
+
await recoverExperimentCreateTransaction(path.resolve(process.cwd(), options.dir || '.'));
|
|
1449
|
+
});
|
|
1435
1450
|
program
|
|
1436
1451
|
.name('fgrove')
|
|
1437
1452
|
.description('FunnelsGrove CLI for editing, syncing, and publishing funnels')
|
|
@@ -1654,9 +1669,30 @@ addExamples(offerSetsCommand
|
|
|
1654
1669
|
console.log(`Wrote ${formatGeneratedFileCount(generatedFiles.length)}`);
|
|
1655
1670
|
});
|
|
1656
1671
|
const experimentsCommand = addExamples(program.command('experiments').description('Manage funnel experiments'), [
|
|
1672
|
+
'fgrove experiments create --spec experiment.json --dir .',
|
|
1657
1673
|
'fgrove experiments sync',
|
|
1658
1674
|
'fgrove experiments sync --funnel claimbee-ios',
|
|
1659
1675
|
]);
|
|
1676
|
+
addExamples(experimentsCommand
|
|
1677
|
+
.command('create')
|
|
1678
|
+
.description('Create a draft experiment from a JSON spec')
|
|
1679
|
+
.requiredOption('--spec <path>', 'Experiment JSON spec')
|
|
1680
|
+
.option('--dir <path>', 'Synced local funnel directory', '.')
|
|
1681
|
+
.option('--json', 'Emit one machine-readable success object'), [
|
|
1682
|
+
'fgrove experiments create --spec experiment.json --dir .',
|
|
1683
|
+
'fgrove experiments create --spec experiment.json --dir ./claimbee-ios --json',
|
|
1684
|
+
])
|
|
1685
|
+
.action(async (options) => {
|
|
1686
|
+
const result = await executeExperimentCreate({
|
|
1687
|
+
sourceDir: path.resolve(process.cwd(), options.dir),
|
|
1688
|
+
specPath: path.resolve(process.cwd(), options.spec),
|
|
1689
|
+
createFromSpec: async (input) => createFunnelExperimentFromSpec({
|
|
1690
|
+
apiUrl: getApiUrl(),
|
|
1691
|
+
token: await readAuthToken(),
|
|
1692
|
+
}, input),
|
|
1693
|
+
});
|
|
1694
|
+
process.stdout.write(formatExperimentCreateSuccess(result, options.json === true));
|
|
1695
|
+
});
|
|
1660
1696
|
addExamples(experimentsCommand
|
|
1661
1697
|
.command('sync')
|
|
1662
1698
|
.description('Write generated experiment config for a funnel')
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { readSyncManifest, type SyncManifest } from './localSync.js';
|
|
2
|
+
export declare const EXPERIMENT_GENERATED_PATHS: readonly ["src/config/experiments.generated.ts", "src/config/experiments.ts"];
|
|
3
|
+
declare const EXPERIMENT_GENERATED_CONTENT_TYPE = "text/typescript";
|
|
4
|
+
declare const EXPERIMENT_METRICS: readonly ["step_completion", "next_step_reached", "checkout_opened", "funnel_completed", "paying_customer"];
|
|
5
|
+
export type ExperimentMetric = (typeof EXPERIMENT_METRICS)[number];
|
|
6
|
+
export type AgentExperimentVariant = {
|
|
7
|
+
variantKey: string;
|
|
8
|
+
label: string;
|
|
9
|
+
routeToStepId: string;
|
|
10
|
+
trafficPercent: number;
|
|
11
|
+
isControl: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type AgentPricingExperimentVariant = AgentExperimentVariant & {
|
|
14
|
+
offerSetKey: string;
|
|
15
|
+
};
|
|
16
|
+
type SharedAgentExperimentSpec = {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
stepId: string;
|
|
20
|
+
primaryMetric: ExperimentMetric;
|
|
21
|
+
trackedMetrics: ExperimentMetric[];
|
|
22
|
+
};
|
|
23
|
+
export type AgentExperimentSpec = SharedAgentExperimentSpec & ({
|
|
24
|
+
type: 'step' | 'paywall';
|
|
25
|
+
variants: AgentExperimentVariant[];
|
|
26
|
+
} | {
|
|
27
|
+
type: 'pricing';
|
|
28
|
+
variants: AgentPricingExperimentVariant[];
|
|
29
|
+
});
|
|
30
|
+
export type GeneratedExperimentFile = {
|
|
31
|
+
path: (typeof EXPERIMENT_GENERATED_PATHS)[number];
|
|
32
|
+
content: string;
|
|
33
|
+
contentType: typeof EXPERIMENT_GENERATED_CONTENT_TYPE;
|
|
34
|
+
};
|
|
35
|
+
export type ExperimentCreateResponse = {
|
|
36
|
+
experiment: Record<string, unknown> & {
|
|
37
|
+
id: string;
|
|
38
|
+
posthog_flag_key: string;
|
|
39
|
+
};
|
|
40
|
+
variants: Array<Record<string, unknown> & {
|
|
41
|
+
id: string;
|
|
42
|
+
experiment_id: string;
|
|
43
|
+
variant_key: string;
|
|
44
|
+
label: string;
|
|
45
|
+
route_to_step_id: string;
|
|
46
|
+
traffic_percent: number;
|
|
47
|
+
is_control: boolean;
|
|
48
|
+
}>;
|
|
49
|
+
draftVersionId: string;
|
|
50
|
+
generatedFiles: [GeneratedExperimentFile, GeneratedExperimentFile];
|
|
51
|
+
githubConnected: boolean;
|
|
52
|
+
};
|
|
53
|
+
export declare class ExperimentCreateContractError extends Error {
|
|
54
|
+
constructor(fieldPath: string, reason: string);
|
|
55
|
+
}
|
|
56
|
+
export declare function parseAgentExperimentSpec(value: unknown): AgentExperimentSpec;
|
|
57
|
+
export declare function readAgentExperimentSpec(filePath: string): Promise<AgentExperimentSpec>;
|
|
58
|
+
export declare function parseExperimentCreateResponse(value: unknown, requestedSpec: AgentExperimentSpec, expectedFunnelIdValue: string): ExperimentCreateResponse;
|
|
59
|
+
export declare function assertGeneratedExperimentFilesUnchanged(root: string, manifestValue: SyncManifest): Promise<void>;
|
|
60
|
+
export declare function buildExperimentCreateManifest(manifestValue: SyncManifest, draftVersionIdValue: string, filesValue: unknown): SyncManifest;
|
|
61
|
+
export type ExperimentCreateInstallPhase = 'prepared' | 'ready' | `generated:${GeneratedExperimentFile['path']}` | 'before-manifest' | 'manifest';
|
|
62
|
+
type ExperimentCreateInstallHooks = {
|
|
63
|
+
afterPhase?: (phase: ExperimentCreateInstallPhase) => void | Promise<void>;
|
|
64
|
+
};
|
|
65
|
+
export declare function recoverExperimentCreateTransaction(root: string): Promise<boolean>;
|
|
66
|
+
export declare function installExperimentCreateTransaction(root: string, input: {
|
|
67
|
+
generatedFiles: unknown;
|
|
68
|
+
manifest: SyncManifest;
|
|
69
|
+
}, hooks?: ExperimentCreateInstallHooks): Promise<void>;
|
|
70
|
+
export type ExecuteExperimentCreateInput = {
|
|
71
|
+
sourceDir: string;
|
|
72
|
+
specPath: string;
|
|
73
|
+
createFromSpec: (input: {
|
|
74
|
+
workspaceId: string;
|
|
75
|
+
funnelId: string;
|
|
76
|
+
expectedDraftVersionId: string;
|
|
77
|
+
spec: AgentExperimentSpec;
|
|
78
|
+
}) => Promise<unknown>;
|
|
79
|
+
};
|
|
80
|
+
export type ExperimentCreateSuccess = {
|
|
81
|
+
experimentId: string;
|
|
82
|
+
experimentKey: string;
|
|
83
|
+
draftVersionId: string;
|
|
84
|
+
writtenPaths: [...typeof EXPERIMENT_GENERATED_PATHS];
|
|
85
|
+
githubConnected: boolean;
|
|
86
|
+
};
|
|
87
|
+
type ExecuteExperimentCreateDependencies = {
|
|
88
|
+
recover: typeof recoverExperimentCreateTransaction;
|
|
89
|
+
readManifest: typeof readSyncManifest;
|
|
90
|
+
readSpec: typeof readAgentExperimentSpec;
|
|
91
|
+
assertGeneratedFilesUnchanged: typeof assertGeneratedExperimentFilesUnchanged;
|
|
92
|
+
parseResponse: typeof parseExperimentCreateResponse;
|
|
93
|
+
install: (sourceDir: string, input: {
|
|
94
|
+
generatedFiles: unknown;
|
|
95
|
+
manifest: SyncManifest;
|
|
96
|
+
}) => Promise<void>;
|
|
97
|
+
};
|
|
98
|
+
export declare function executeExperimentCreate(input: ExecuteExperimentCreateInput, dependencyOverrides?: Partial<ExecuteExperimentCreateDependencies>): Promise<ExperimentCreateSuccess>;
|
|
99
|
+
export declare function formatExperimentCreateSuccess(result: ExperimentCreateSuccess, json: boolean): string;
|
|
100
|
+
export {};
|