@samboyd/bep-cli 0.1.0 → 0.1.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/README.md +1 -1
- package/dist/cli.js +1022 -953
- package/dist/cli.js.map +1 -1
- package/package.json +10 -4
package/dist/cli.js
CHANGED
|
@@ -1,54 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __export = (target, all) => {
|
|
10
|
-
for (var name in all)
|
|
11
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
-
};
|
|
13
|
-
var __copyProps = (to, from, except, desc) => {
|
|
14
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
-
for (let key of __getOwnPropNames(from))
|
|
16
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
-
}
|
|
19
|
-
return to;
|
|
20
|
-
};
|
|
21
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
-
mod
|
|
28
|
-
));
|
|
29
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
2
|
|
|
31
3
|
// src/cli.ts
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
});
|
|
36
|
-
module.exports = __toCommonJS(cli_exports);
|
|
37
|
-
var import_commander = require("commander");
|
|
4
|
+
import path15 from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { Command } from "commander";
|
|
38
7
|
|
|
39
8
|
// src/fs/init.ts
|
|
40
|
-
|
|
41
|
-
|
|
9
|
+
import { access, mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
10
|
+
import path2 from "path";
|
|
42
11
|
|
|
43
12
|
// src/providers/config.ts
|
|
44
|
-
|
|
45
|
-
|
|
13
|
+
import { readFile } from "fs/promises";
|
|
14
|
+
import path from "path";
|
|
46
15
|
var PROVIDER_CONFIG_PATH = ".bep.providers.json";
|
|
47
16
|
async function readProviderConfig(rootDir) {
|
|
48
|
-
const configPath =
|
|
17
|
+
const configPath = path.join(rootDir, PROVIDER_CONFIG_PATH);
|
|
49
18
|
let raw;
|
|
50
19
|
try {
|
|
51
|
-
raw = await
|
|
20
|
+
raw = await readFile(configPath, "utf8");
|
|
52
21
|
} catch (error) {
|
|
53
22
|
if (error.code === "ENOENT") {
|
|
54
23
|
return {
|
|
@@ -102,9 +71,9 @@ function getMixpanelServiceAccountCreds(config) {
|
|
|
102
71
|
|
|
103
72
|
// src/fs/init.ts
|
|
104
73
|
var BETS_DIR = "bets";
|
|
105
|
-
var LOGS_DIR =
|
|
106
|
-
var EVIDENCE_DIR =
|
|
107
|
-
var STATE_PATH =
|
|
74
|
+
var LOGS_DIR = path2.join(BETS_DIR, "_logs");
|
|
75
|
+
var EVIDENCE_DIR = path2.join(BETS_DIR, "_evidence");
|
|
76
|
+
var STATE_PATH = path2.join(BETS_DIR, "_state.json");
|
|
108
77
|
var GITIGNORE_PATH = ".gitignore";
|
|
109
78
|
var PROVIDER_GITIGNORE_ENTRY = ".bep.providers.json";
|
|
110
79
|
var DEFAULT_STATE = {
|
|
@@ -118,7 +87,7 @@ var DEFAULT_PROVIDER_CONFIG = {
|
|
|
118
87
|
var REQUIRED_INIT_PATHS = [BETS_DIR, LOGS_DIR, EVIDENCE_DIR, STATE_PATH];
|
|
119
88
|
async function pathExists(filePath) {
|
|
120
89
|
try {
|
|
121
|
-
await
|
|
90
|
+
await access(filePath);
|
|
122
91
|
return true;
|
|
123
92
|
} catch {
|
|
124
93
|
return false;
|
|
@@ -127,24 +96,24 @@ async function pathExists(filePath) {
|
|
|
127
96
|
async function initRepo(rootDir) {
|
|
128
97
|
const createdPaths = [];
|
|
129
98
|
for (const relativeDir of [BETS_DIR, LOGS_DIR, EVIDENCE_DIR]) {
|
|
130
|
-
const absoluteDir =
|
|
99
|
+
const absoluteDir = path2.join(rootDir, relativeDir);
|
|
131
100
|
const existed = await pathExists(absoluteDir);
|
|
132
|
-
await
|
|
101
|
+
await mkdir(absoluteDir, { recursive: true });
|
|
133
102
|
if (!existed) {
|
|
134
103
|
createdPaths.push(relativeDir);
|
|
135
104
|
}
|
|
136
105
|
}
|
|
137
|
-
const statePath =
|
|
106
|
+
const statePath = path2.join(rootDir, STATE_PATH);
|
|
138
107
|
const stateExists = await pathExists(statePath);
|
|
139
108
|
if (!stateExists) {
|
|
140
|
-
await
|
|
109
|
+
await writeFile(statePath, `${JSON.stringify(DEFAULT_STATE, null, 2)}
|
|
141
110
|
`, "utf8");
|
|
142
111
|
createdPaths.push(STATE_PATH);
|
|
143
112
|
}
|
|
144
|
-
const providerConfigPath =
|
|
113
|
+
const providerConfigPath = path2.join(rootDir, PROVIDER_CONFIG_PATH);
|
|
145
114
|
const providerConfigExists = await pathExists(providerConfigPath);
|
|
146
115
|
if (!providerConfigExists) {
|
|
147
|
-
await
|
|
116
|
+
await writeFile(providerConfigPath, `${JSON.stringify(DEFAULT_PROVIDER_CONFIG, null, 2)}
|
|
148
117
|
`, "utf8");
|
|
149
118
|
createdPaths.push(PROVIDER_CONFIG_PATH);
|
|
150
119
|
}
|
|
@@ -158,12 +127,12 @@ async function initRepo(rootDir) {
|
|
|
158
127
|
};
|
|
159
128
|
}
|
|
160
129
|
async function findGitRepoRoot(startDir) {
|
|
161
|
-
let currentDir =
|
|
130
|
+
let currentDir = path2.resolve(startDir);
|
|
162
131
|
while (true) {
|
|
163
|
-
if (await pathExists(
|
|
132
|
+
if (await pathExists(path2.join(currentDir, ".git"))) {
|
|
164
133
|
return currentDir;
|
|
165
134
|
}
|
|
166
|
-
const parentDir =
|
|
135
|
+
const parentDir = path2.dirname(currentDir);
|
|
167
136
|
if (parentDir === currentDir) {
|
|
168
137
|
return null;
|
|
169
138
|
}
|
|
@@ -171,26 +140,26 @@ async function findGitRepoRoot(startDir) {
|
|
|
171
140
|
}
|
|
172
141
|
}
|
|
173
142
|
async function ensureGitignoreEntry(gitRoot, createdPaths) {
|
|
174
|
-
const gitignorePath =
|
|
143
|
+
const gitignorePath = path2.join(gitRoot, GITIGNORE_PATH);
|
|
175
144
|
const exists = await pathExists(gitignorePath);
|
|
176
145
|
if (!exists) {
|
|
177
|
-
await
|
|
146
|
+
await writeFile(gitignorePath, `${PROVIDER_GITIGNORE_ENTRY}
|
|
178
147
|
`, "utf8");
|
|
179
148
|
createdPaths.push(GITIGNORE_PATH);
|
|
180
149
|
return;
|
|
181
150
|
}
|
|
182
|
-
const raw = await (
|
|
151
|
+
const raw = await readFile2(gitignorePath, "utf8");
|
|
183
152
|
const lines = raw.split(/\r?\n/);
|
|
184
153
|
if (lines.includes(PROVIDER_GITIGNORE_ENTRY)) {
|
|
185
154
|
return;
|
|
186
155
|
}
|
|
187
156
|
const suffix = raw.length === 0 || raw.endsWith("\n") ? "" : "\n";
|
|
188
|
-
await
|
|
157
|
+
await writeFile(gitignorePath, `${raw}${suffix}${PROVIDER_GITIGNORE_ENTRY}
|
|
189
158
|
`, "utf8");
|
|
190
159
|
}
|
|
191
160
|
async function isInitializedRepoRoot(candidateRootDir) {
|
|
192
161
|
for (const relativePath of REQUIRED_INIT_PATHS) {
|
|
193
|
-
const absolutePath =
|
|
162
|
+
const absolutePath = path2.join(candidateRootDir, relativePath);
|
|
194
163
|
if (!await pathExists(absolutePath)) {
|
|
195
164
|
return false;
|
|
196
165
|
}
|
|
@@ -198,15 +167,15 @@ async function isInitializedRepoRoot(candidateRootDir) {
|
|
|
198
167
|
return true;
|
|
199
168
|
}
|
|
200
169
|
async function findInitializedRepo(startDir) {
|
|
201
|
-
let currentDir =
|
|
170
|
+
let currentDir = path2.resolve(startDir);
|
|
202
171
|
while (true) {
|
|
203
172
|
if (await isInitializedRepoRoot(currentDir)) {
|
|
204
173
|
return {
|
|
205
174
|
rootDir: currentDir,
|
|
206
|
-
betsDir:
|
|
175
|
+
betsDir: path2.join(currentDir, BETS_DIR)
|
|
207
176
|
};
|
|
208
177
|
}
|
|
209
|
-
const parentDir =
|
|
178
|
+
const parentDir = path2.dirname(currentDir);
|
|
210
179
|
if (parentDir === currentDir) {
|
|
211
180
|
return null;
|
|
212
181
|
}
|
|
@@ -222,23 +191,23 @@ async function ensureInitializedRepo(startDir) {
|
|
|
222
191
|
}
|
|
223
192
|
|
|
224
193
|
// src/hooks/install.ts
|
|
225
|
-
|
|
194
|
+
import path5 from "path";
|
|
226
195
|
|
|
227
196
|
// src/hooks/discovery.ts
|
|
228
|
-
|
|
229
|
-
|
|
197
|
+
import { stat } from "fs/promises";
|
|
198
|
+
import path3 from "path";
|
|
230
199
|
async function findNearestClaudeDir(startDir) {
|
|
231
|
-
let currentDir =
|
|
200
|
+
let currentDir = path3.resolve(startDir);
|
|
232
201
|
while (true) {
|
|
233
|
-
const candidate =
|
|
202
|
+
const candidate = path3.join(currentDir, ".claude");
|
|
234
203
|
try {
|
|
235
|
-
const stats = await
|
|
204
|
+
const stats = await stat(candidate);
|
|
236
205
|
if (stats.isDirectory()) {
|
|
237
206
|
return candidate;
|
|
238
207
|
}
|
|
239
208
|
} catch {
|
|
240
209
|
}
|
|
241
|
-
const parentDir =
|
|
210
|
+
const parentDir = path3.dirname(currentDir);
|
|
242
211
|
if (parentDir === currentDir) {
|
|
243
212
|
return null;
|
|
244
213
|
}
|
|
@@ -247,8 +216,8 @@ async function findNearestClaudeDir(startDir) {
|
|
|
247
216
|
}
|
|
248
217
|
|
|
249
218
|
// src/hooks/claude.ts
|
|
250
|
-
|
|
251
|
-
|
|
219
|
+
import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
220
|
+
import path4 from "path";
|
|
252
221
|
var CLAUDE_SETTINGS_FILE = "settings.json";
|
|
253
222
|
var HOOK_EVENTS = [
|
|
254
223
|
{ event: "UserPromptSubmit", suffix: "user-prompt-submit" },
|
|
@@ -297,10 +266,10 @@ function ensureCommand(settings, event, command) {
|
|
|
297
266
|
return true;
|
|
298
267
|
}
|
|
299
268
|
async function installClaudeCodeHooks(claudeDir, hookCommandBase) {
|
|
300
|
-
const settingsPath =
|
|
269
|
+
const settingsPath = path4.join(claudeDir, CLAUDE_SETTINGS_FILE);
|
|
301
270
|
let settings = {};
|
|
302
271
|
try {
|
|
303
|
-
const raw = await (
|
|
272
|
+
const raw = await readFile3(settingsPath, "utf8");
|
|
304
273
|
settings = parseSettings(raw);
|
|
305
274
|
} catch (error) {
|
|
306
275
|
const code = error.code;
|
|
@@ -315,7 +284,7 @@ async function installClaudeCodeHooks(claudeDir, hookCommandBase) {
|
|
|
315
284
|
addedCommands += 1;
|
|
316
285
|
}
|
|
317
286
|
}
|
|
318
|
-
await (
|
|
287
|
+
await writeFile2(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
319
288
|
`, "utf8");
|
|
320
289
|
return {
|
|
321
290
|
claudeDir,
|
|
@@ -373,7 +342,7 @@ function resolveHookCommandBase(startDir) {
|
|
|
373
342
|
if (!argv1) {
|
|
374
343
|
return "bep";
|
|
375
344
|
}
|
|
376
|
-
const resolved =
|
|
345
|
+
const resolved = path5.isAbsolute(argv1) ? argv1 : path5.resolve(startDir, argv1);
|
|
377
346
|
return quoteShellArg(resolved);
|
|
378
347
|
}
|
|
379
348
|
async function installAgentHooks(startDir, agent) {
|
|
@@ -390,7 +359,7 @@ async function installAgentHooks(startDir, agent) {
|
|
|
390
359
|
}
|
|
391
360
|
const hookCommandBase = resolveHookCommandBase(startDir);
|
|
392
361
|
const installed = await installClaudeCodeHooks(claudeDir, hookCommandBase);
|
|
393
|
-
const settingsPathRelative =
|
|
362
|
+
const settingsPathRelative = path5.relative(startDir, installed.settingsPath) || path5.basename(installed.settingsPath);
|
|
394
363
|
return {
|
|
395
364
|
ok: true,
|
|
396
365
|
agent: resolved.value,
|
|
@@ -400,7 +369,7 @@ async function installAgentHooks(startDir, agent) {
|
|
|
400
369
|
}
|
|
401
370
|
|
|
402
371
|
// src/ui/initHooks.ts
|
|
403
|
-
|
|
372
|
+
import { confirm, isCancel, select } from "@clack/prompts";
|
|
404
373
|
var COMING_SOON_AGENTS = ["cursor", "codex", "windsurf"];
|
|
405
374
|
async function runInitHookPrompt(client = createInitHookPromptClient()) {
|
|
406
375
|
const shouldInstall = await client.promptInstallNow();
|
|
@@ -424,17 +393,17 @@ async function runInitHookPrompt(client = createInitHookPromptClient()) {
|
|
|
424
393
|
function createInitHookPromptClient() {
|
|
425
394
|
return {
|
|
426
395
|
async promptInstallNow() {
|
|
427
|
-
const value = await
|
|
396
|
+
const value = await confirm({
|
|
428
397
|
message: "Install agent tracking hooks now?",
|
|
429
398
|
initialValue: true
|
|
430
399
|
});
|
|
431
|
-
if (
|
|
400
|
+
if (isCancel(value)) {
|
|
432
401
|
return "cancel";
|
|
433
402
|
}
|
|
434
403
|
return value;
|
|
435
404
|
},
|
|
436
405
|
async promptAgent() {
|
|
437
|
-
const value = await
|
|
406
|
+
const value = await select({
|
|
438
407
|
message: "Choose an agent",
|
|
439
408
|
options: [
|
|
440
409
|
{ label: "Claude Code", value: "claude-code" },
|
|
@@ -444,7 +413,7 @@ function createInitHookPromptClient() {
|
|
|
444
413
|
],
|
|
445
414
|
initialValue: "claude-code"
|
|
446
415
|
});
|
|
447
|
-
if (
|
|
416
|
+
if (isCancel(value)) {
|
|
448
417
|
return "cancel";
|
|
449
418
|
}
|
|
450
419
|
return value;
|
|
@@ -509,9 +478,9 @@ async function runInit(options = {}) {
|
|
|
509
478
|
}
|
|
510
479
|
|
|
511
480
|
// src/commands/check.ts
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
481
|
+
import { readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
482
|
+
import path7 from "path";
|
|
483
|
+
import { isCancel as isCancel3, select as select2 } from "@clack/prompts";
|
|
515
484
|
|
|
516
485
|
// src/bep/id.ts
|
|
517
486
|
var BET_ID_REGEX = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/;
|
|
@@ -525,19 +494,19 @@ function normalizeValidationStatus(value) {
|
|
|
525
494
|
}
|
|
526
495
|
|
|
527
496
|
// src/fs/bets.ts
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
497
|
+
import { access as access2, readdir, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
498
|
+
import path6 from "path";
|
|
499
|
+
import matter from "gray-matter";
|
|
531
500
|
function getBetRelativePath(idOrFileName) {
|
|
532
501
|
const fileName = idOrFileName.endsWith(".md") ? idOrFileName : `${idOrFileName}.md`;
|
|
533
|
-
return
|
|
502
|
+
return path6.join(BETS_DIR, fileName);
|
|
534
503
|
}
|
|
535
504
|
function getBetAbsolutePath(rootDir, idOrFileName) {
|
|
536
|
-
return
|
|
505
|
+
return path6.join(rootDir, getBetRelativePath(idOrFileName));
|
|
537
506
|
}
|
|
538
507
|
async function pathExists2(filePath) {
|
|
539
508
|
try {
|
|
540
|
-
await (
|
|
509
|
+
await access2(filePath);
|
|
541
510
|
return true;
|
|
542
511
|
} catch {
|
|
543
512
|
return false;
|
|
@@ -548,13 +517,13 @@ async function readBetFile(rootDir, idOrFileName) {
|
|
|
548
517
|
const absolutePath = getBetAbsolutePath(rootDir, idOrFileName);
|
|
549
518
|
let markdown;
|
|
550
519
|
try {
|
|
551
|
-
markdown = await (
|
|
520
|
+
markdown = await readFile4(absolutePath, "utf8");
|
|
552
521
|
} catch (error) {
|
|
553
522
|
throw new Error(`Failed to parse BEP file at ${relativePath}: ${error.message}`);
|
|
554
523
|
}
|
|
555
524
|
let parsed;
|
|
556
525
|
try {
|
|
557
|
-
parsed = (
|
|
526
|
+
parsed = matter(markdown);
|
|
558
527
|
} catch (error) {
|
|
559
528
|
throw new Error(`Failed to parse BEP file at ${relativePath}: ${error.message}`);
|
|
560
529
|
}
|
|
@@ -569,24 +538,21 @@ async function readBetFile(rootDir, idOrFileName) {
|
|
|
569
538
|
};
|
|
570
539
|
}
|
|
571
540
|
async function listBetMarkdownFiles(rootDir) {
|
|
572
|
-
const betsDir =
|
|
573
|
-
const entries = await
|
|
541
|
+
const betsDir = path6.join(rootDir, BETS_DIR);
|
|
542
|
+
const entries = await readdir(betsDir, { withFileTypes: true });
|
|
574
543
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md") && !entry.name.startsWith("_")).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
|
|
575
544
|
}
|
|
576
545
|
async function writeBetFile(rootDir, idOrFileName, bet) {
|
|
577
546
|
const absolutePath = getBetAbsolutePath(rootDir, idOrFileName);
|
|
578
|
-
await (
|
|
547
|
+
await writeFile3(absolutePath, matter.stringify(bet.content, bet.data), "utf8");
|
|
579
548
|
}
|
|
580
549
|
|
|
581
|
-
// src/providers/manual.ts
|
|
582
|
-
var import_prompts3 = require("@clack/prompts");
|
|
583
|
-
|
|
584
550
|
// src/ui/checkPrompt.ts
|
|
585
|
-
|
|
551
|
+
import { isCancel as isCancel2, text } from "@clack/prompts";
|
|
586
552
|
function createClackCheckPromptClient() {
|
|
587
553
|
return {
|
|
588
554
|
async promptObservedValue() {
|
|
589
|
-
return
|
|
555
|
+
return text({
|
|
590
556
|
message: "Observed value (required, numeric)",
|
|
591
557
|
validate(rawValue) {
|
|
592
558
|
const trimmed = rawValue.trim();
|
|
@@ -597,7 +563,7 @@ function createClackCheckPromptClient() {
|
|
|
597
563
|
});
|
|
598
564
|
},
|
|
599
565
|
async promptNotes() {
|
|
600
|
-
return
|
|
566
|
+
return text({
|
|
601
567
|
message: "Notes (optional)"
|
|
602
568
|
});
|
|
603
569
|
}
|
|
@@ -605,11 +571,11 @@ function createClackCheckPromptClient() {
|
|
|
605
571
|
}
|
|
606
572
|
async function runCheckPrompt(client = createClackCheckPromptClient()) {
|
|
607
573
|
const observed = await client.promptObservedValue();
|
|
608
|
-
if ((
|
|
574
|
+
if (isCancel2(observed)) {
|
|
609
575
|
return { cancelled: true };
|
|
610
576
|
}
|
|
611
577
|
const notes = await client.promptNotes();
|
|
612
|
-
if ((
|
|
578
|
+
if (isCancel2(notes)) {
|
|
613
579
|
return { cancelled: true };
|
|
614
580
|
}
|
|
615
581
|
const observedValue = Number(observed.trim());
|
|
@@ -622,9 +588,6 @@ async function runCheckPrompt(client = createClackCheckPromptClient()) {
|
|
|
622
588
|
}
|
|
623
589
|
|
|
624
590
|
// src/providers/manual.ts
|
|
625
|
-
var BACK_VALUE = "__back__";
|
|
626
|
-
var DIM = "\x1B[2m";
|
|
627
|
-
var RESET = "\x1B[0m";
|
|
628
591
|
function isManualComparisonOperator(value) {
|
|
629
592
|
return value === "lt" || value === "lte" || value === "eq" || value === "gte" || value === "gt";
|
|
630
593
|
}
|
|
@@ -681,58 +644,6 @@ function formatManualComparisonOperator(operator) {
|
|
|
681
644
|
}
|
|
682
645
|
return ">";
|
|
683
646
|
}
|
|
684
|
-
function createClackManualSetupPromptClient() {
|
|
685
|
-
return {
|
|
686
|
-
async promptManualOperator({ initialValue, allowBack }) {
|
|
687
|
-
const options = [
|
|
688
|
-
{ label: "lt (less than)", value: "lt" },
|
|
689
|
-
{ label: "lte (less than or equal)", value: "lte" },
|
|
690
|
-
{ label: "eq (equal)", value: "eq" },
|
|
691
|
-
{ label: "gte (greater than or equal)", value: "gte" },
|
|
692
|
-
{ label: "gt (greater than)", value: "gt" }
|
|
693
|
-
];
|
|
694
|
-
if (allowBack) {
|
|
695
|
-
options.unshift({ label: "Back", value: BACK_VALUE });
|
|
696
|
-
}
|
|
697
|
-
const value = await (0, import_prompts3.select)({
|
|
698
|
-
message: "Leading indicator comparison operator",
|
|
699
|
-
options,
|
|
700
|
-
initialValue
|
|
701
|
-
});
|
|
702
|
-
if ((0, import_prompts3.isCancel)(value)) {
|
|
703
|
-
return { kind: "cancel" };
|
|
704
|
-
}
|
|
705
|
-
if (value === BACK_VALUE) {
|
|
706
|
-
return { kind: "back" };
|
|
707
|
-
}
|
|
708
|
-
return { kind: "value", value };
|
|
709
|
-
},
|
|
710
|
-
async promptManualTarget({ initialValue, allowBack }) {
|
|
711
|
-
const backHint = allowBack ? ` ${DIM}(type b to go back)${RESET}` : "";
|
|
712
|
-
const value = await (0, import_prompts3.text)({
|
|
713
|
-
message: `Leading indicator numeric target (required).${backHint}`,
|
|
714
|
-
initialValue: typeof initialValue === "number" ? String(initialValue) : void 0,
|
|
715
|
-
validate(rawValue) {
|
|
716
|
-
const trimmed2 = rawValue.trim();
|
|
717
|
-
if (allowBack && trimmed2.toLowerCase() === "b") {
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
|
-
if (trimmed2.length === 0 || !Number.isFinite(Number(trimmed2))) {
|
|
721
|
-
return "Enter a valid number.";
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
});
|
|
725
|
-
if ((0, import_prompts3.isCancel)(value)) {
|
|
726
|
-
return { kind: "cancel" };
|
|
727
|
-
}
|
|
728
|
-
const trimmed = value.trim();
|
|
729
|
-
if (allowBack && trimmed.toLowerCase() === "b") {
|
|
730
|
-
return { kind: "back" };
|
|
731
|
-
}
|
|
732
|
-
return { kind: "value", value: Number(trimmed) };
|
|
733
|
-
}
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
647
|
var manualAdapter = {
|
|
737
648
|
type: "manual",
|
|
738
649
|
parseIndicator(input) {
|
|
@@ -750,81 +661,13 @@ var manualAdapter = {
|
|
|
750
661
|
};
|
|
751
662
|
}
|
|
752
663
|
};
|
|
753
|
-
function getManualSetupClient(ctx) {
|
|
754
|
-
if (ctx.client && typeof ctx.client === "object") {
|
|
755
|
-
const candidate = ctx.client;
|
|
756
|
-
if (typeof candidate.promptManualOperator === "function" && typeof candidate.promptManualTarget === "function") {
|
|
757
|
-
return candidate;
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
return createClackManualSetupPromptClient();
|
|
761
|
-
}
|
|
762
|
-
var manualSetup = {
|
|
763
|
-
type: "manual",
|
|
764
|
-
async collectNewWizardInput(ctx) {
|
|
765
|
-
const client = getManualSetupClient(ctx);
|
|
766
|
-
let stepIndex = 0;
|
|
767
|
-
const values = {
|
|
768
|
-
type: "manual",
|
|
769
|
-
operator: ctx.initialValue?.operator,
|
|
770
|
-
target: ctx.initialValue?.target
|
|
771
|
-
};
|
|
772
|
-
while (stepIndex < 2) {
|
|
773
|
-
if (stepIndex === 0) {
|
|
774
|
-
const result2 = await client.promptManualOperator({
|
|
775
|
-
initialValue: values.operator,
|
|
776
|
-
allowBack: ctx.allowBack
|
|
777
|
-
});
|
|
778
|
-
if (result2.kind === "cancel") {
|
|
779
|
-
return { kind: "cancel" };
|
|
780
|
-
}
|
|
781
|
-
if (result2.kind === "back") {
|
|
782
|
-
return { kind: "back" };
|
|
783
|
-
}
|
|
784
|
-
values.operator = result2.value;
|
|
785
|
-
stepIndex += 1;
|
|
786
|
-
continue;
|
|
787
|
-
}
|
|
788
|
-
const result = await client.promptManualTarget({
|
|
789
|
-
initialValue: values.target,
|
|
790
|
-
allowBack: true
|
|
791
|
-
});
|
|
792
|
-
if (result.kind === "cancel") {
|
|
793
|
-
return { kind: "cancel" };
|
|
794
|
-
}
|
|
795
|
-
if (result.kind === "back") {
|
|
796
|
-
stepIndex = Math.max(0, stepIndex - 1);
|
|
797
|
-
continue;
|
|
798
|
-
}
|
|
799
|
-
values.target = result.value;
|
|
800
|
-
stepIndex += 1;
|
|
801
|
-
}
|
|
802
|
-
if (!values.operator || typeof values.target !== "number") {
|
|
803
|
-
return { kind: "cancel" };
|
|
804
|
-
}
|
|
805
|
-
return {
|
|
806
|
-
kind: "value",
|
|
807
|
-
value: {
|
|
808
|
-
type: "manual",
|
|
809
|
-
operator: values.operator,
|
|
810
|
-
target: values.target
|
|
811
|
-
}
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
};
|
|
815
664
|
var manualProviderModule = {
|
|
816
|
-
adapter: manualAdapter
|
|
817
|
-
setup: manualSetup
|
|
665
|
+
adapter: manualAdapter
|
|
818
666
|
};
|
|
819
667
|
|
|
820
668
|
// src/providers/mixpanel.ts
|
|
821
|
-
|
|
822
|
-
var import_prompts4 = require("@clack/prompts");
|
|
823
|
-
var BACK_VALUE2 = "__back__";
|
|
824
|
-
var DIM2 = "\x1B[2m";
|
|
825
|
-
var RESET2 = "\x1B[0m";
|
|
669
|
+
import { Buffer } from "buffer";
|
|
826
670
|
var MIXPANEL_REPORT_ENDPOINT = "https://mixpanel.com/api/query/insights";
|
|
827
|
-
var MIXPANEL_URL_HINT = "Values come from report URL: /project/<PROJECT_ID>/view/<WORKSPACE_ID>/...#...report-<BOOKMARK_ID>.";
|
|
828
671
|
function isManualComparisonOperator2(value) {
|
|
829
672
|
return value === "lt" || value === "lte" || value === "eq" || value === "gte" || value === "gt";
|
|
830
673
|
}
|
|
@@ -863,139 +706,6 @@ function parseMixpanelLeadingIndicator(input) {
|
|
|
863
706
|
}
|
|
864
707
|
};
|
|
865
708
|
}
|
|
866
|
-
function getMixpanelSetupClient(ctx) {
|
|
867
|
-
if (ctx.client && typeof ctx.client === "object") {
|
|
868
|
-
const candidate = ctx.client;
|
|
869
|
-
if (typeof candidate.promptMixpanelProjectId === "function" && typeof candidate.promptMixpanelWorkspaceId === "function" && typeof candidate.promptMixpanelBookmarkId === "function" && typeof candidate.promptMixpanelOperator === "function" && typeof candidate.promptMixpanelTarget === "function") {
|
|
870
|
-
return candidate;
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
return createClackMixpanelSetupPromptClient();
|
|
874
|
-
}
|
|
875
|
-
function createClackMixpanelSetupPromptClient() {
|
|
876
|
-
return {
|
|
877
|
-
async promptMixpanelProjectId({ initialValue, allowBack }) {
|
|
878
|
-
const backHint = allowBack ? ` ${DIM2}(type b to go back)${RESET2}` : "";
|
|
879
|
-
const value = await (0, import_prompts4.text)({
|
|
880
|
-
message: `Mixpanel project id (required). ${MIXPANEL_URL_HINT}${backHint}`,
|
|
881
|
-
initialValue,
|
|
882
|
-
validate(rawValue) {
|
|
883
|
-
const trimmed2 = rawValue.trim();
|
|
884
|
-
if (allowBack && trimmed2.toLowerCase() === "b") {
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
if (trimmed2.length === 0) {
|
|
888
|
-
return "Enter a project id.";
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
});
|
|
892
|
-
if ((0, import_prompts4.isCancel)(value)) {
|
|
893
|
-
return { kind: "cancel" };
|
|
894
|
-
}
|
|
895
|
-
const trimmed = value.trim();
|
|
896
|
-
if (allowBack && trimmed.toLowerCase() === "b") {
|
|
897
|
-
return { kind: "back" };
|
|
898
|
-
}
|
|
899
|
-
return { kind: "value", value: trimmed };
|
|
900
|
-
},
|
|
901
|
-
async promptMixpanelWorkspaceId({ initialValue, allowBack }) {
|
|
902
|
-
const backHint = allowBack ? ` ${DIM2}(type b to go back)${RESET2}` : "";
|
|
903
|
-
const value = await (0, import_prompts4.text)({
|
|
904
|
-
message: `Mixpanel workspace id (required). ${MIXPANEL_URL_HINT}${backHint}`,
|
|
905
|
-
initialValue,
|
|
906
|
-
validate(rawValue) {
|
|
907
|
-
const trimmed2 = rawValue.trim();
|
|
908
|
-
if (allowBack && trimmed2.toLowerCase() === "b") {
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
if (trimmed2.length === 0) {
|
|
912
|
-
return "Enter a workspace id.";
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
});
|
|
916
|
-
if ((0, import_prompts4.isCancel)(value)) {
|
|
917
|
-
return { kind: "cancel" };
|
|
918
|
-
}
|
|
919
|
-
const trimmed = value.trim();
|
|
920
|
-
if (allowBack && trimmed.toLowerCase() === "b") {
|
|
921
|
-
return { kind: "back" };
|
|
922
|
-
}
|
|
923
|
-
return { kind: "value", value: trimmed };
|
|
924
|
-
},
|
|
925
|
-
async promptMixpanelBookmarkId({ initialValue, allowBack }) {
|
|
926
|
-
const backHint = allowBack ? ` ${DIM2}(type b to go back)${RESET2}` : "";
|
|
927
|
-
const value = await (0, import_prompts4.text)({
|
|
928
|
-
message: `Mixpanel bookmark id (required). ${MIXPANEL_URL_HINT}${backHint}`,
|
|
929
|
-
initialValue,
|
|
930
|
-
validate(rawValue) {
|
|
931
|
-
const trimmed2 = rawValue.trim();
|
|
932
|
-
if (allowBack && trimmed2.toLowerCase() === "b") {
|
|
933
|
-
return;
|
|
934
|
-
}
|
|
935
|
-
if (trimmed2.length === 0) {
|
|
936
|
-
return "Enter a bookmark id.";
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
});
|
|
940
|
-
if ((0, import_prompts4.isCancel)(value)) {
|
|
941
|
-
return { kind: "cancel" };
|
|
942
|
-
}
|
|
943
|
-
const trimmed = value.trim();
|
|
944
|
-
if (allowBack && trimmed.toLowerCase() === "b") {
|
|
945
|
-
return { kind: "back" };
|
|
946
|
-
}
|
|
947
|
-
return { kind: "value", value: trimmed };
|
|
948
|
-
},
|
|
949
|
-
async promptMixpanelOperator({ initialValue, allowBack }) {
|
|
950
|
-
const options = [
|
|
951
|
-
{ label: "lt (less than)", value: "lt" },
|
|
952
|
-
{ label: "lte (less than or equal)", value: "lte" },
|
|
953
|
-
{ label: "eq (equal)", value: "eq" },
|
|
954
|
-
{ label: "gte (greater than or equal)", value: "gte" },
|
|
955
|
-
{ label: "gt (greater than)", value: "gt" }
|
|
956
|
-
];
|
|
957
|
-
if (allowBack) {
|
|
958
|
-
options.unshift({ label: "Back", value: BACK_VALUE2 });
|
|
959
|
-
}
|
|
960
|
-
const value = await (0, import_prompts4.select)({
|
|
961
|
-
message: "Mixpanel comparison operator",
|
|
962
|
-
options,
|
|
963
|
-
initialValue
|
|
964
|
-
});
|
|
965
|
-
if ((0, import_prompts4.isCancel)(value)) {
|
|
966
|
-
return { kind: "cancel" };
|
|
967
|
-
}
|
|
968
|
-
if (value === BACK_VALUE2) {
|
|
969
|
-
return { kind: "back" };
|
|
970
|
-
}
|
|
971
|
-
return { kind: "value", value };
|
|
972
|
-
},
|
|
973
|
-
async promptMixpanelTarget({ initialValue, allowBack }) {
|
|
974
|
-
const backHint = allowBack ? ` ${DIM2}(type b to go back)${RESET2}` : "";
|
|
975
|
-
const value = await (0, import_prompts4.text)({
|
|
976
|
-
message: `Mixpanel target value (required).${backHint}`,
|
|
977
|
-
initialValue: typeof initialValue === "number" ? String(initialValue) : void 0,
|
|
978
|
-
validate(rawValue) {
|
|
979
|
-
const trimmed2 = rawValue.trim();
|
|
980
|
-
if (allowBack && trimmed2.toLowerCase() === "b") {
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
983
|
-
if (trimmed2.length === 0 || !Number.isFinite(Number(trimmed2))) {
|
|
984
|
-
return "Enter a valid number.";
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
});
|
|
988
|
-
if ((0, import_prompts4.isCancel)(value)) {
|
|
989
|
-
return { kind: "cancel" };
|
|
990
|
-
}
|
|
991
|
-
const trimmed = value.trim();
|
|
992
|
-
if (allowBack && trimmed.toLowerCase() === "b") {
|
|
993
|
-
return { kind: "back" };
|
|
994
|
-
}
|
|
995
|
-
return { kind: "value", value: Number(trimmed) };
|
|
996
|
-
}
|
|
997
|
-
};
|
|
998
|
-
}
|
|
999
709
|
function collectNumericLeaves(value) {
|
|
1000
710
|
const numbers = [];
|
|
1001
711
|
const stack = [value];
|
|
@@ -1060,7 +770,7 @@ var mixpanelAdapter = {
|
|
|
1060
770
|
bookmark_id: indicator.bookmark_id
|
|
1061
771
|
});
|
|
1062
772
|
const url = `${MIXPANEL_REPORT_ENDPOINT}?${params.toString()}`;
|
|
1063
|
-
const encodedCreds =
|
|
773
|
+
const encodedCreds = Buffer.from(credsResult.value, "utf8").toString("base64");
|
|
1064
774
|
let response;
|
|
1065
775
|
try {
|
|
1066
776
|
response = await fetch(url, {
|
|
@@ -1103,116 +813,8 @@ var mixpanelAdapter = {
|
|
|
1103
813
|
};
|
|
1104
814
|
}
|
|
1105
815
|
};
|
|
1106
|
-
var mixpanelSetup = {
|
|
1107
|
-
type: "mixpanel",
|
|
1108
|
-
async collectNewWizardInput(ctx) {
|
|
1109
|
-
const client = getMixpanelSetupClient(ctx);
|
|
1110
|
-
let stepIndex = 0;
|
|
1111
|
-
const values = {
|
|
1112
|
-
type: "mixpanel",
|
|
1113
|
-
project_id: ctx.initialValue?.project_id,
|
|
1114
|
-
workspace_id: ctx.initialValue?.workspace_id,
|
|
1115
|
-
bookmark_id: ctx.initialValue?.bookmark_id,
|
|
1116
|
-
operator: ctx.initialValue?.operator,
|
|
1117
|
-
target: ctx.initialValue?.target
|
|
1118
|
-
};
|
|
1119
|
-
while (stepIndex < 5) {
|
|
1120
|
-
if (stepIndex === 0) {
|
|
1121
|
-
const result2 = await client.promptMixpanelProjectId({
|
|
1122
|
-
initialValue: values.project_id,
|
|
1123
|
-
allowBack: ctx.allowBack
|
|
1124
|
-
});
|
|
1125
|
-
if (result2.kind === "cancel") {
|
|
1126
|
-
return { kind: "cancel" };
|
|
1127
|
-
}
|
|
1128
|
-
if (result2.kind === "back") {
|
|
1129
|
-
return { kind: "back" };
|
|
1130
|
-
}
|
|
1131
|
-
values.project_id = result2.value;
|
|
1132
|
-
stepIndex += 1;
|
|
1133
|
-
continue;
|
|
1134
|
-
}
|
|
1135
|
-
if (stepIndex === 1) {
|
|
1136
|
-
const result2 = await client.promptMixpanelWorkspaceId({
|
|
1137
|
-
initialValue: values.workspace_id,
|
|
1138
|
-
allowBack: true
|
|
1139
|
-
});
|
|
1140
|
-
if (result2.kind === "cancel") {
|
|
1141
|
-
return { kind: "cancel" };
|
|
1142
|
-
}
|
|
1143
|
-
if (result2.kind === "back") {
|
|
1144
|
-
stepIndex = Math.max(0, stepIndex - 1);
|
|
1145
|
-
continue;
|
|
1146
|
-
}
|
|
1147
|
-
values.workspace_id = result2.value;
|
|
1148
|
-
stepIndex += 1;
|
|
1149
|
-
continue;
|
|
1150
|
-
}
|
|
1151
|
-
if (stepIndex === 2) {
|
|
1152
|
-
const result2 = await client.promptMixpanelBookmarkId({
|
|
1153
|
-
initialValue: values.bookmark_id,
|
|
1154
|
-
allowBack: true
|
|
1155
|
-
});
|
|
1156
|
-
if (result2.kind === "cancel") {
|
|
1157
|
-
return { kind: "cancel" };
|
|
1158
|
-
}
|
|
1159
|
-
if (result2.kind === "back") {
|
|
1160
|
-
stepIndex = Math.max(0, stepIndex - 1);
|
|
1161
|
-
continue;
|
|
1162
|
-
}
|
|
1163
|
-
values.bookmark_id = result2.value;
|
|
1164
|
-
stepIndex += 1;
|
|
1165
|
-
continue;
|
|
1166
|
-
}
|
|
1167
|
-
if (stepIndex === 3) {
|
|
1168
|
-
const result2 = await client.promptMixpanelOperator({
|
|
1169
|
-
initialValue: values.operator,
|
|
1170
|
-
allowBack: true
|
|
1171
|
-
});
|
|
1172
|
-
if (result2.kind === "cancel") {
|
|
1173
|
-
return { kind: "cancel" };
|
|
1174
|
-
}
|
|
1175
|
-
if (result2.kind === "back") {
|
|
1176
|
-
stepIndex = Math.max(0, stepIndex - 1);
|
|
1177
|
-
continue;
|
|
1178
|
-
}
|
|
1179
|
-
values.operator = result2.value;
|
|
1180
|
-
stepIndex += 1;
|
|
1181
|
-
continue;
|
|
1182
|
-
}
|
|
1183
|
-
const result = await client.promptMixpanelTarget({
|
|
1184
|
-
initialValue: values.target,
|
|
1185
|
-
allowBack: true
|
|
1186
|
-
});
|
|
1187
|
-
if (result.kind === "cancel") {
|
|
1188
|
-
return { kind: "cancel" };
|
|
1189
|
-
}
|
|
1190
|
-
if (result.kind === "back") {
|
|
1191
|
-
stepIndex = Math.max(0, stepIndex - 1);
|
|
1192
|
-
continue;
|
|
1193
|
-
}
|
|
1194
|
-
values.target = result.value;
|
|
1195
|
-
stepIndex += 1;
|
|
1196
|
-
}
|
|
1197
|
-
if (!values.project_id || !values.workspace_id || !values.bookmark_id || !values.operator || typeof values.target !== "number") {
|
|
1198
|
-
return { kind: "cancel" };
|
|
1199
|
-
}
|
|
1200
|
-
return {
|
|
1201
|
-
kind: "value",
|
|
1202
|
-
value: {
|
|
1203
|
-
type: "mixpanel",
|
|
1204
|
-
project_id: values.project_id,
|
|
1205
|
-
workspace_id: values.workspace_id,
|
|
1206
|
-
bookmark_id: values.bookmark_id,
|
|
1207
|
-
operator: values.operator,
|
|
1208
|
-
target: values.target
|
|
1209
|
-
}
|
|
1210
|
-
};
|
|
1211
|
-
}
|
|
1212
|
-
};
|
|
1213
816
|
var mixpanelProviderModule = {
|
|
1214
|
-
adapter: mixpanelAdapter
|
|
1215
|
-
setup: mixpanelSetup
|
|
817
|
+
adapter: mixpanelAdapter
|
|
1216
818
|
};
|
|
1217
819
|
|
|
1218
820
|
// src/providers/registry.ts
|
|
@@ -1258,13 +860,13 @@ function hasPassedStatusInFrontmatter(markdown) {
|
|
|
1258
860
|
return frontmatterLines.some((line) => /^status:\s*passed\s*$/i.test(line.trim()));
|
|
1259
861
|
}
|
|
1260
862
|
async function hasPassingEvidence(rootDir, id) {
|
|
1261
|
-
const evidencePath =
|
|
863
|
+
const evidencePath = path7.join(rootDir, EVIDENCE_DIR, `${id}.json`);
|
|
1262
864
|
if (!await pathExists2(evidencePath)) {
|
|
1263
865
|
return false;
|
|
1264
866
|
}
|
|
1265
867
|
let parsed;
|
|
1266
868
|
try {
|
|
1267
|
-
parsed = JSON.parse(await (
|
|
869
|
+
parsed = JSON.parse(await readFile5(evidencePath, "utf8"));
|
|
1268
870
|
} catch {
|
|
1269
871
|
return false;
|
|
1270
872
|
}
|
|
@@ -1277,7 +879,7 @@ function isInteractiveTty2() {
|
|
|
1277
879
|
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
1278
880
|
}
|
|
1279
881
|
async function maybePromptUnpass(id) {
|
|
1280
|
-
const value = await (
|
|
882
|
+
const value = await select2({
|
|
1281
883
|
message: `Bet '${id}' is currently status: passed, but the forced check FAILED. Update status?`,
|
|
1282
884
|
options: [
|
|
1283
885
|
{ label: "Keep status: passed", value: "keep" },
|
|
@@ -1285,7 +887,7 @@ async function maybePromptUnpass(id) {
|
|
|
1285
887
|
],
|
|
1286
888
|
initialValue: "keep"
|
|
1287
889
|
});
|
|
1288
|
-
if ((
|
|
890
|
+
if (isCancel3(value)) {
|
|
1289
891
|
return "cancel";
|
|
1290
892
|
}
|
|
1291
893
|
return value;
|
|
@@ -1338,22 +940,22 @@ async function runCheck(id, options = {}) {
|
|
|
1338
940
|
console.error("Bet has invalid leading_indicator: missing string field 'type'.");
|
|
1339
941
|
return 1;
|
|
1340
942
|
}
|
|
1341
|
-
const
|
|
1342
|
-
if (!
|
|
943
|
+
const module = resolveProviderModule(leadingIndicatorType);
|
|
944
|
+
if (!module) {
|
|
1343
945
|
const knownTypes = listRegisteredProviderTypes().join(", ");
|
|
1344
946
|
console.error(
|
|
1345
947
|
`Bet has unsupported leading_indicator.type '${leadingIndicatorType}'. Supported types: ${knownTypes}.`
|
|
1346
948
|
);
|
|
1347
949
|
return 1;
|
|
1348
950
|
}
|
|
1349
|
-
const parsedIndicator =
|
|
951
|
+
const parsedIndicator = module.adapter.parseIndicator(rawLeadingIndicator);
|
|
1350
952
|
if (!parsedIndicator.ok) {
|
|
1351
953
|
console.error(`Bet '${id}' has invalid leading_indicator: ${parsedIndicator.error}`);
|
|
1352
954
|
return 1;
|
|
1353
955
|
}
|
|
1354
956
|
let checkResult;
|
|
1355
957
|
try {
|
|
1356
|
-
checkResult = await
|
|
958
|
+
checkResult = await module.adapter.runCheck(parsedIndicator.value, {
|
|
1357
959
|
rootDir,
|
|
1358
960
|
betId: id,
|
|
1359
961
|
nowIso: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1376,10 +978,10 @@ async function runCheck(id, options = {}) {
|
|
|
1376
978
|
notes: checkResult.notes,
|
|
1377
979
|
meta: checkResult.meta
|
|
1378
980
|
};
|
|
1379
|
-
const relativeEvidencePath =
|
|
1380
|
-
const absoluteEvidencePath =
|
|
981
|
+
const relativeEvidencePath = path7.join(EVIDENCE_DIR, `${id}.json`);
|
|
982
|
+
const absoluteEvidencePath = path7.join(rootDir, relativeEvidencePath);
|
|
1381
983
|
try {
|
|
1382
|
-
await (
|
|
984
|
+
await writeFile4(absoluteEvidencePath, `${JSON.stringify(snapshot, null, 2)}
|
|
1383
985
|
`, "utf8");
|
|
1384
986
|
} catch (error) {
|
|
1385
987
|
console.error(`Failed to write evidence at ${relativeEvidencePath}: ${error.message}`);
|
|
@@ -1427,11 +1029,12 @@ async function runCheck(id, options = {}) {
|
|
|
1427
1029
|
}
|
|
1428
1030
|
|
|
1429
1031
|
// src/commands/new.ts
|
|
1430
|
-
|
|
1431
|
-
|
|
1032
|
+
import { existsSync } from "fs";
|
|
1033
|
+
import { writeFile as writeFile5 } from "fs/promises";
|
|
1034
|
+
import path8 from "path";
|
|
1432
1035
|
|
|
1433
1036
|
// src/bep/template.ts
|
|
1434
|
-
|
|
1037
|
+
import matter2 from "gray-matter";
|
|
1435
1038
|
function renderNewBetMarkdown(input) {
|
|
1436
1039
|
const frontmatter = {
|
|
1437
1040
|
id: input.id,
|
|
@@ -1452,446 +1055,900 @@ function renderNewBetMarkdown(input) {
|
|
|
1452
1055
|
"",
|
|
1453
1056
|
input.primaryAssumption,
|
|
1454
1057
|
"",
|
|
1455
|
-
"## 2.
|
|
1456
|
-
"",
|
|
1457
|
-
input.rationale,
|
|
1458
|
-
"",
|
|
1459
|
-
"## 3. Validation Plan",
|
|
1058
|
+
"## 2. Validation Plan",
|
|
1460
1059
|
"",
|
|
1461
1060
|
input.validationPlan,
|
|
1462
1061
|
"",
|
|
1463
|
-
"##
|
|
1062
|
+
"## 3. Notes",
|
|
1464
1063
|
"",
|
|
1465
1064
|
input.notes,
|
|
1466
1065
|
""
|
|
1467
1066
|
].join("\n");
|
|
1468
|
-
return `${
|
|
1067
|
+
return `${matter2.stringify(body, frontmatter)}`;
|
|
1469
1068
|
}
|
|
1470
1069
|
|
|
1471
|
-
// src/ui/newWizard.
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
var
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
"
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
"notes"
|
|
1485
|
-
];
|
|
1486
|
-
function applyPromptResult(result, onValue) {
|
|
1487
|
-
if (result.kind === "cancel") {
|
|
1488
|
-
return { kind: "cancel" };
|
|
1070
|
+
// src/ui/ink/newWizard/renderNewWizardInk.tsx
|
|
1071
|
+
import { render } from "ink";
|
|
1072
|
+
import { createElement } from "react";
|
|
1073
|
+
|
|
1074
|
+
// src/ui/ink/newWizard/betCardPreview.ts
|
|
1075
|
+
var EMPTY_VALUE = "\u2014";
|
|
1076
|
+
var EMPTY_TEXT = "Not entered yet";
|
|
1077
|
+
function isSelectPrompt(prompt) {
|
|
1078
|
+
return "options" in prompt;
|
|
1079
|
+
}
|
|
1080
|
+
function getHighlightedSelectValue(prompt, uiState) {
|
|
1081
|
+
if (!isSelectPrompt(prompt)) {
|
|
1082
|
+
return void 0;
|
|
1489
1083
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1084
|
+
const option = prompt.options[uiState.selectIndex];
|
|
1085
|
+
return option?.value;
|
|
1086
|
+
}
|
|
1087
|
+
function normalizePreviewText(value) {
|
|
1088
|
+
if (value === void 0) {
|
|
1089
|
+
return void 0;
|
|
1492
1090
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1091
|
+
return value.length > 0 ? value : void 0;
|
|
1092
|
+
}
|
|
1093
|
+
function buildEffectivePreviewState(draft, step, prompt, uiState) {
|
|
1094
|
+
const highlightedValue = getHighlightedSelectValue(prompt, uiState);
|
|
1095
|
+
const effective = {
|
|
1096
|
+
betName: draft.betName,
|
|
1097
|
+
primaryAssumption: draft.primaryAssumption,
|
|
1098
|
+
validationPlan: draft.validationPlan,
|
|
1099
|
+
leadingIndicatorType: draft.leadingIndicatorType,
|
|
1100
|
+
capType: draft.capType,
|
|
1101
|
+
capValueText: draft.capValue === void 0 ? void 0 : String(draft.capValue),
|
|
1102
|
+
manualOperator: draft.manualOperator,
|
|
1103
|
+
manualTargetText: draft.manualTarget === void 0 ? void 0 : String(draft.manualTarget),
|
|
1104
|
+
mixpanelProjectId: draft.mixpanelProjectId,
|
|
1105
|
+
mixpanelWorkspaceId: draft.mixpanelWorkspaceId,
|
|
1106
|
+
mixpanelBookmarkId: draft.mixpanelBookmarkId,
|
|
1107
|
+
mixpanelOperator: draft.mixpanelOperator,
|
|
1108
|
+
mixpanelTargetText: draft.mixpanelTarget === void 0 ? void 0 : String(draft.mixpanelTarget)
|
|
1109
|
+
};
|
|
1110
|
+
if (step === "bet_name" && !isSelectPrompt(prompt)) {
|
|
1111
|
+
effective.betName = normalizePreviewText(uiState.textValue);
|
|
1112
|
+
} else if (step === "primary_assumption" && !isSelectPrompt(prompt)) {
|
|
1113
|
+
effective.primaryAssumption = normalizePreviewText(uiState.textValue);
|
|
1114
|
+
} else if (step === "validation_plan" && !isSelectPrompt(prompt)) {
|
|
1115
|
+
effective.validationPlan = normalizePreviewText(uiState.textValue);
|
|
1116
|
+
} else if (step === "leading_indicator_type" && highlightedValue) {
|
|
1117
|
+
if (highlightedValue === "manual" || highlightedValue === "mixpanel") {
|
|
1118
|
+
effective.leadingIndicatorType = highlightedValue;
|
|
1119
|
+
}
|
|
1120
|
+
} else if (step === "cap_type" && highlightedValue) {
|
|
1121
|
+
if (highlightedValue === "max_hours" || highlightedValue === "max_calendar_days") {
|
|
1122
|
+
effective.capType = highlightedValue;
|
|
1123
|
+
}
|
|
1124
|
+
} else if (step === "cap_value" && !isSelectPrompt(prompt)) {
|
|
1125
|
+
effective.capValueText = normalizePreviewText(uiState.textValue);
|
|
1126
|
+
} else if (step === "manual_operator" && highlightedValue) {
|
|
1127
|
+
if (isComparisonOperator(highlightedValue)) {
|
|
1128
|
+
effective.manualOperator = highlightedValue;
|
|
1129
|
+
}
|
|
1130
|
+
} else if (step === "manual_target" && !isSelectPrompt(prompt)) {
|
|
1131
|
+
effective.manualTargetText = normalizePreviewText(uiState.textValue);
|
|
1132
|
+
} else if (step === "mixpanel_project_id" && !isSelectPrompt(prompt)) {
|
|
1133
|
+
effective.mixpanelProjectId = normalizePreviewText(uiState.textValue);
|
|
1134
|
+
} else if (step === "mixpanel_workspace_id" && !isSelectPrompt(prompt)) {
|
|
1135
|
+
effective.mixpanelWorkspaceId = normalizePreviewText(uiState.textValue);
|
|
1136
|
+
} else if (step === "mixpanel_bookmark_id" && !isSelectPrompt(prompt)) {
|
|
1137
|
+
effective.mixpanelBookmarkId = normalizePreviewText(uiState.textValue);
|
|
1138
|
+
} else if (step === "mixpanel_operator" && highlightedValue) {
|
|
1139
|
+
if (isComparisonOperator(highlightedValue)) {
|
|
1140
|
+
effective.mixpanelOperator = highlightedValue;
|
|
1141
|
+
}
|
|
1142
|
+
} else if (step === "mixpanel_target" && !isSelectPrompt(prompt)) {
|
|
1143
|
+
effective.mixpanelTargetText = normalizePreviewText(uiState.textValue);
|
|
1144
|
+
}
|
|
1145
|
+
return effective;
|
|
1146
|
+
}
|
|
1147
|
+
function isComparisonOperator(value) {
|
|
1148
|
+
return value === "lt" || value === "lte" || value === "eq" || value === "gte" || value === "gt";
|
|
1495
1149
|
}
|
|
1496
|
-
function
|
|
1497
|
-
if (!
|
|
1498
|
-
return
|
|
1150
|
+
function valueField(value) {
|
|
1151
|
+
if (!value) {
|
|
1152
|
+
return { value: EMPTY_TEXT, empty: true };
|
|
1153
|
+
}
|
|
1154
|
+
return { value, empty: false };
|
|
1155
|
+
}
|
|
1156
|
+
function inlineValue(value) {
|
|
1157
|
+
return value && value.length > 0 ? value : EMPTY_VALUE;
|
|
1158
|
+
}
|
|
1159
|
+
function providerLabel(type) {
|
|
1160
|
+
if (type === "mixpanel") {
|
|
1161
|
+
return "Mixpanel";
|
|
1162
|
+
}
|
|
1163
|
+
if (type === "manual") {
|
|
1164
|
+
return "Manual";
|
|
1165
|
+
}
|
|
1166
|
+
return type;
|
|
1167
|
+
}
|
|
1168
|
+
function buildValidationRuleSummary(effective) {
|
|
1169
|
+
if (effective.leadingIndicatorType === "mixpanel") {
|
|
1170
|
+
const operator = effective.mixpanelOperator ? formatManualComparisonOperator(effective.mixpanelOperator) : EMPTY_VALUE;
|
|
1171
|
+
const target = inlineValue(effective.mixpanelTargetText);
|
|
1172
|
+
return `Mixpanel metric ${operator} ${target}`;
|
|
1173
|
+
}
|
|
1174
|
+
if (effective.leadingIndicatorType === "manual") {
|
|
1175
|
+
const operator = effective.manualOperator ? formatManualComparisonOperator(effective.manualOperator) : EMPTY_VALUE;
|
|
1176
|
+
const target = inlineValue(effective.manualTargetText);
|
|
1177
|
+
return `Manual observed value ${operator} ${target}`;
|
|
1178
|
+
}
|
|
1179
|
+
return "Select a provider to define validation rule";
|
|
1180
|
+
}
|
|
1181
|
+
function buildCapSummary(effective) {
|
|
1182
|
+
const capValue = effective.capValueText;
|
|
1183
|
+
if (!effective.capType || !capValue) {
|
|
1184
|
+
return "Set time cap type and value";
|
|
1185
|
+
}
|
|
1186
|
+
return effective.capType === "max_calendar_days" ? `${capValue} calendar day${capValue === "1" ? "" : "s"}` : `${capValue} hour${capValue === "1" ? "" : "s"}`;
|
|
1187
|
+
}
|
|
1188
|
+
function buildProviderConfig(effective) {
|
|
1189
|
+
if (effective.leadingIndicatorType === "mixpanel") {
|
|
1190
|
+
return {
|
|
1191
|
+
title: "Mixpanel config",
|
|
1192
|
+
fields: [
|
|
1193
|
+
{
|
|
1194
|
+
label: "workspace id",
|
|
1195
|
+
...valueField(effective.mixpanelWorkspaceId)
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
label: "project id",
|
|
1199
|
+
...valueField(effective.mixpanelProjectId)
|
|
1200
|
+
},
|
|
1201
|
+
{
|
|
1202
|
+
label: "bookmark id",
|
|
1203
|
+
...valueField(effective.mixpanelBookmarkId)
|
|
1204
|
+
}
|
|
1205
|
+
]
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
if (effective.leadingIndicatorType === "manual") {
|
|
1209
|
+
return {
|
|
1210
|
+
title: "Manual config",
|
|
1211
|
+
fields: [
|
|
1212
|
+
{
|
|
1213
|
+
label: "operator",
|
|
1214
|
+
value: effective.manualOperator ? formatManualComparisonOperator(effective.manualOperator) : EMPTY_VALUE,
|
|
1215
|
+
empty: !effective.manualOperator
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
label: "target",
|
|
1219
|
+
value: inlineValue(effective.manualTargetText),
|
|
1220
|
+
empty: !effective.manualTargetText
|
|
1221
|
+
}
|
|
1222
|
+
]
|
|
1223
|
+
};
|
|
1499
1224
|
}
|
|
1500
|
-
const maxHours = values.capType === "max_hours" ? values.capValue : void 0;
|
|
1501
|
-
const maxCalendarDays = values.capType === "max_calendar_days" ? values.capValue : void 0;
|
|
1502
1225
|
return {
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1226
|
+
title: "Config",
|
|
1227
|
+
fields: [
|
|
1228
|
+
{
|
|
1229
|
+
label: "provider",
|
|
1230
|
+
value: "Select a provider",
|
|
1231
|
+
empty: true
|
|
1232
|
+
}
|
|
1233
|
+
]
|
|
1510
1234
|
};
|
|
1511
1235
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1236
|
+
function buildBetCardPreviewModel(args) {
|
|
1237
|
+
const effective = buildEffectivePreviewState(args.draft, args.step, args.prompt, args.uiState);
|
|
1238
|
+
const providerConfig = buildProviderConfig(effective);
|
|
1239
|
+
return {
|
|
1240
|
+
betName: {
|
|
1241
|
+
label: "Bet name",
|
|
1242
|
+
...valueField(effective.betName)
|
|
1243
|
+
},
|
|
1244
|
+
primaryAssumption: valueField(effective.primaryAssumption),
|
|
1245
|
+
validationPlan: valueField(effective.validationPlan),
|
|
1246
|
+
providers: args.providerTypes.map((type) => ({
|
|
1247
|
+
type,
|
|
1248
|
+
label: providerLabel(type),
|
|
1249
|
+
selected: type === effective.leadingIndicatorType
|
|
1250
|
+
})),
|
|
1251
|
+
selectedProviderType: effective.leadingIndicatorType,
|
|
1252
|
+
providerConfigTitle: providerConfig.title,
|
|
1253
|
+
providerConfigFields: providerConfig.fields,
|
|
1254
|
+
validationRuleSummary: buildValidationRuleSummary(effective),
|
|
1255
|
+
capSummary: buildCapSummary(effective)
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// src/ui/ink/newWizard/components/BetCard.tsx
|
|
1260
|
+
import { Box, Text, measureElement } from "ink";
|
|
1261
|
+
import { useEffect, useRef, useState } from "react";
|
|
1262
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1263
|
+
function useMeasuredSize() {
|
|
1264
|
+
const ref = useRef(null);
|
|
1265
|
+
const [size, setSize] = useState({ width: 0, height: 0 });
|
|
1266
|
+
useEffect(() => {
|
|
1267
|
+
const node = ref.current;
|
|
1268
|
+
if (!node) {
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const update = () => {
|
|
1272
|
+
const next = measureElement(node);
|
|
1273
|
+
setSize(
|
|
1274
|
+
(previous) => previous.width === next.width && previous.height === next.height ? previous : next
|
|
1275
|
+
);
|
|
1276
|
+
};
|
|
1277
|
+
update();
|
|
1278
|
+
node.onRender = update;
|
|
1279
|
+
return () => {
|
|
1280
|
+
if (node.onRender === update) {
|
|
1281
|
+
node.onRender = void 0;
|
|
1523
1282
|
}
|
|
1524
|
-
}
|
|
1525
|
-
},
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1283
|
+
};
|
|
1284
|
+
}, []);
|
|
1285
|
+
return { ref, size };
|
|
1286
|
+
}
|
|
1287
|
+
function HorizontalDivider() {
|
|
1288
|
+
const { ref, size } = useMeasuredSize();
|
|
1289
|
+
const width = Math.max(1, size.width);
|
|
1290
|
+
return /* @__PURE__ */ jsx(Box, { ref, width: "100%", overflow: "hidden", children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: Array.from({ length: width }, () => "\u2500").join("") }) });
|
|
1291
|
+
}
|
|
1292
|
+
function VerticalDivider() {
|
|
1293
|
+
const { ref, size } = useMeasuredSize();
|
|
1294
|
+
const height = Math.max(1, size.height);
|
|
1295
|
+
return /* @__PURE__ */ jsx(Box, { ref, height: "100%", alignSelf: "stretch", flexDirection: "column", overflow: "hidden", children: Array.from({ length: height }, (_, index) => /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }, index)) });
|
|
1296
|
+
}
|
|
1297
|
+
function Section({
|
|
1298
|
+
title,
|
|
1299
|
+
children,
|
|
1300
|
+
flexGrow,
|
|
1301
|
+
width
|
|
1302
|
+
}) {
|
|
1303
|
+
return /* @__PURE__ */ jsxs(
|
|
1304
|
+
Box,
|
|
1305
|
+
{
|
|
1306
|
+
flexDirection: "column",
|
|
1307
|
+
paddingX: 1,
|
|
1308
|
+
paddingY: 1,
|
|
1309
|
+
flexGrow,
|
|
1310
|
+
width,
|
|
1311
|
+
children: [
|
|
1312
|
+
title && title !== "" ? /* @__PURE__ */ jsx(Text, { bold: true, children: title }) : null,
|
|
1313
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", children })
|
|
1314
|
+
]
|
|
1529
1315
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
})
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
})
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
function FieldLine({ label, value, empty }) {
|
|
1319
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
1320
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1321
|
+
label,
|
|
1322
|
+
": "
|
|
1323
|
+
] }),
|
|
1324
|
+
empty ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: value }) : value
|
|
1325
|
+
] });
|
|
1326
|
+
}
|
|
1327
|
+
function BetCard({ model }) {
|
|
1328
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", paddingX: 1, paddingY: 0, children: [
|
|
1329
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Bet Card Preview" }),
|
|
1330
|
+
/* @__PURE__ */ jsx(Section, { children: model.betName.empty ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: model.betName.value }) : /* @__PURE__ */ jsx(Text, { children: model.betName.value }) }),
|
|
1331
|
+
/* @__PURE__ */ jsx(HorizontalDivider, {}),
|
|
1332
|
+
/* @__PURE__ */ jsxs(Section, { children: [
|
|
1333
|
+
/* @__PURE__ */ jsx(FieldLine, { label: "Assumption", value: model.primaryAssumption.value, empty: model.primaryAssumption.empty }),
|
|
1334
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(FieldLine, { label: "Validation plan", value: model.validationPlan.value, empty: model.validationPlan.empty }) })
|
|
1335
|
+
] }),
|
|
1336
|
+
/* @__PURE__ */ jsx(HorizontalDivider, {}),
|
|
1337
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
1338
|
+
/* @__PURE__ */ jsx(Box, { marginRight: 1, children: /* @__PURE__ */ jsx(Section, { title: "Providers", width: 24, children: model.providers.map((provider) => /* @__PURE__ */ jsxs(Text, { inverse: provider.selected, color: provider.selected ? "cyan" : void 0, children: [
|
|
1339
|
+
provider.selected ? "\u203A" : " ",
|
|
1340
|
+
" ",
|
|
1341
|
+
provider.label
|
|
1342
|
+
] }, provider.type)) }) }),
|
|
1343
|
+
/* @__PURE__ */ jsx(VerticalDivider, {}),
|
|
1344
|
+
/* @__PURE__ */ jsxs(Section, { title: model.providerConfigTitle, flexGrow: 1, children: [
|
|
1345
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: model.providerConfigFields.map((field) => /* @__PURE__ */ jsx(FieldLine, { label: field.label, value: field.value, empty: field.empty }, field.label)) }),
|
|
1346
|
+
/* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
|
|
1347
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Validation passes when:" }),
|
|
1348
|
+
/* @__PURE__ */ jsx(Text, { children: model.validationRuleSummary })
|
|
1349
|
+
] }),
|
|
1350
|
+
/* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
|
|
1351
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Time cap:" }),
|
|
1352
|
+
/* @__PURE__ */ jsx(Text, { children: model.capSummary })
|
|
1353
|
+
] })
|
|
1354
|
+
] })
|
|
1355
|
+
] })
|
|
1356
|
+
] });
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// src/ui/ink/newWizard/components/WizardFrame.tsx
|
|
1360
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
1361
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1362
|
+
function WizardFrame({
|
|
1363
|
+
title,
|
|
1364
|
+
helpText,
|
|
1365
|
+
error,
|
|
1366
|
+
children
|
|
1367
|
+
}) {
|
|
1368
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 2, paddingY: 0, children: [
|
|
1369
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, children: title }),
|
|
1370
|
+
helpText ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: helpText }) : null,
|
|
1371
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, flexDirection: "column", children }),
|
|
1372
|
+
error ? /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
|
|
1373
|
+
"Error: ",
|
|
1374
|
+
error
|
|
1375
|
+
] }) }) : null
|
|
1376
|
+
] });
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// src/ui/ink/newWizard/components/SelectStep.tsx
|
|
1380
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
1381
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1382
|
+
function SelectStep({
|
|
1383
|
+
prompt,
|
|
1384
|
+
selectedIndex
|
|
1385
|
+
}) {
|
|
1386
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1387
|
+
prompt.options.map((option, index) => {
|
|
1388
|
+
const selected = index === selectedIndex;
|
|
1389
|
+
return /* @__PURE__ */ jsxs3(Text3, { color: selected ? "cyan" : void 0, inverse: selected, children: [
|
|
1390
|
+
selected ? "\u203A" : " ",
|
|
1391
|
+
" ",
|
|
1392
|
+
option.label
|
|
1393
|
+
] }, option.value);
|
|
1394
|
+
}),
|
|
1395
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Up/Down: move selection" }) })
|
|
1396
|
+
] });
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// src/ui/ink/newWizard/components/TextStep.tsx
|
|
1400
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
1401
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1402
|
+
function TextStep({
|
|
1403
|
+
prompt,
|
|
1404
|
+
value
|
|
1405
|
+
}) {
|
|
1406
|
+
const showPlaceholder = value.length === 0 && Boolean(prompt.placeholder);
|
|
1407
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
1408
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1409
|
+
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: ">" }),
|
|
1410
|
+
" ",
|
|
1411
|
+
value,
|
|
1412
|
+
/* @__PURE__ */ jsx4(Text4, { inverse: true, children: " " }),
|
|
1413
|
+
showPlaceholder ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: prompt.placeholder }) : null
|
|
1414
|
+
] }),
|
|
1415
|
+
prompt.optional ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Optional field." }) : null
|
|
1416
|
+
] });
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// src/ui/ink/newWizard/useWizardInput.ts
|
|
1420
|
+
import { useInput } from "ink";
|
|
1421
|
+
function useWizardInput(prompt, uiState, actions) {
|
|
1422
|
+
useInput((input, key) => {
|
|
1423
|
+
if (key.ctrl && input === "c") {
|
|
1424
|
+
actions.cancel();
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
if (key.escape) {
|
|
1428
|
+
actions.goBack();
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
if ("options" in prompt) {
|
|
1432
|
+
if (key.downArrow || input === "j") {
|
|
1433
|
+
actions.setUiState((previous) => ({
|
|
1434
|
+
...previous,
|
|
1435
|
+
selectIndex: Math.min(prompt.options.length - 1, previous.selectIndex + 1),
|
|
1436
|
+
error: void 0
|
|
1437
|
+
}));
|
|
1438
|
+
return;
|
|
1547
1439
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1440
|
+
if (key.upArrow || input === "k") {
|
|
1441
|
+
actions.setUiState((previous) => ({
|
|
1442
|
+
...previous,
|
|
1443
|
+
selectIndex: Math.max(0, previous.selectIndex - 1),
|
|
1444
|
+
error: void 0
|
|
1445
|
+
}));
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
if (key.return) {
|
|
1449
|
+
const option = prompt.options[uiState.selectIndex];
|
|
1450
|
+
if (option) {
|
|
1451
|
+
actions.submitSelect(option.value);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
return;
|
|
1554
1455
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
return
|
|
1456
|
+
if (key.return) {
|
|
1457
|
+
actions.submitText();
|
|
1458
|
+
return;
|
|
1558
1459
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1460
|
+
if (key.backspace || key.delete) {
|
|
1461
|
+
actions.setUiState((previous) => ({
|
|
1462
|
+
...previous,
|
|
1463
|
+
textValue: previous.textValue.slice(0, -1),
|
|
1464
|
+
error: void 0
|
|
1465
|
+
}));
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
if (input.length > 0 && !key.tab) {
|
|
1469
|
+
actions.setUiState((previous) => ({
|
|
1470
|
+
...previous,
|
|
1471
|
+
textValue: previous.textValue + input,
|
|
1472
|
+
error: void 0
|
|
1473
|
+
}));
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// src/ui/ink/newWizard/useWizardState.ts
|
|
1479
|
+
import { useEffect as useEffect2, useMemo, useState as useState2 } from "react";
|
|
1480
|
+
|
|
1481
|
+
// src/ui/newBetName.ts
|
|
1482
|
+
function normalizeBetName(value) {
|
|
1483
|
+
return value.trim().replace(/\s+/g, "_").toLowerCase();
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
// src/ui/ink/newWizard/flow.ts
|
|
1487
|
+
function createInitialWizardDraft() {
|
|
1488
|
+
return {};
|
|
1489
|
+
}
|
|
1490
|
+
function getWizardSteps(providerType) {
|
|
1491
|
+
const steps = ["bet_name", "primary_assumption", "validation_plan", "leading_indicator_type"];
|
|
1492
|
+
if (providerType === "manual") {
|
|
1493
|
+
steps.push("manual_operator", "manual_target");
|
|
1494
|
+
} else if (providerType === "mixpanel") {
|
|
1495
|
+
steps.push("mixpanel_workspace_id", "mixpanel_project_id", "mixpanel_bookmark_id", "mixpanel_operator", "mixpanel_target");
|
|
1496
|
+
}
|
|
1497
|
+
steps.push("cap_type", "cap_value", "notes");
|
|
1498
|
+
return steps;
|
|
1499
|
+
}
|
|
1500
|
+
function applySelectStepValue(draft, step, value) {
|
|
1501
|
+
if (step === "cap_type") {
|
|
1502
|
+
const nextCapType = value;
|
|
1503
|
+
return {
|
|
1504
|
+
...draft,
|
|
1505
|
+
capType: nextCapType,
|
|
1506
|
+
capValue: draft.capType !== nextCapType ? void 0 : draft.capValue
|
|
1507
|
+
};
|
|
1603
1508
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
while (stepIndex < STEP_ORDER.length) {
|
|
1609
|
-
const step = STEP_ORDER[stepIndex];
|
|
1610
|
-
const handler = STEP_HANDLERS[step];
|
|
1611
|
-
if (!handler) {
|
|
1612
|
-
throw new Error(`No wizard step handler registered for '${step}'.`);
|
|
1613
|
-
}
|
|
1614
|
-
const flow = await handler({ client, values, stepIndex });
|
|
1615
|
-
if (flow.kind === "cancel") {
|
|
1616
|
-
return { cancelled: true };
|
|
1509
|
+
if (step === "leading_indicator_type") {
|
|
1510
|
+
const nextProviderType = value;
|
|
1511
|
+
if (draft.leadingIndicatorType === nextProviderType) {
|
|
1512
|
+
return { ...draft, leadingIndicatorType: nextProviderType };
|
|
1617
1513
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1514
|
+
return {
|
|
1515
|
+
...draft,
|
|
1516
|
+
leadingIndicatorType: nextProviderType,
|
|
1517
|
+
manualOperator: void 0,
|
|
1518
|
+
manualTarget: void 0,
|
|
1519
|
+
mixpanelProjectId: void 0,
|
|
1520
|
+
mixpanelWorkspaceId: void 0,
|
|
1521
|
+
mixpanelBookmarkId: void 0,
|
|
1522
|
+
mixpanelOperator: void 0,
|
|
1523
|
+
mixpanelTarget: void 0
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
if (step === "manual_operator") {
|
|
1527
|
+
return { ...draft, manualOperator: value };
|
|
1528
|
+
}
|
|
1529
|
+
if (step === "mixpanel_operator") {
|
|
1530
|
+
return { ...draft, mixpanelOperator: value };
|
|
1531
|
+
}
|
|
1532
|
+
return draft;
|
|
1533
|
+
}
|
|
1534
|
+
function applyTextStepValue(draft, step, rawValue) {
|
|
1535
|
+
if (step === "bet_name") {
|
|
1536
|
+
return { ...draft, betName: normalizeBetName(rawValue) };
|
|
1537
|
+
}
|
|
1538
|
+
if (step === "cap_value") {
|
|
1539
|
+
return { ...draft, capValue: Number(rawValue.trim()) };
|
|
1540
|
+
}
|
|
1541
|
+
if (step === "manual_target") {
|
|
1542
|
+
return { ...draft, manualTarget: Number(rawValue.trim()) };
|
|
1543
|
+
}
|
|
1544
|
+
if (step === "mixpanel_target") {
|
|
1545
|
+
return { ...draft, mixpanelTarget: Number(rawValue.trim()) };
|
|
1546
|
+
}
|
|
1547
|
+
if (step === "mixpanel_project_id") {
|
|
1548
|
+
return { ...draft, mixpanelProjectId: rawValue.trim() };
|
|
1549
|
+
}
|
|
1550
|
+
if (step === "mixpanel_workspace_id") {
|
|
1551
|
+
return { ...draft, mixpanelWorkspaceId: rawValue.trim() };
|
|
1552
|
+
}
|
|
1553
|
+
if (step === "mixpanel_bookmark_id") {
|
|
1554
|
+
return { ...draft, mixpanelBookmarkId: rawValue.trim() };
|
|
1555
|
+
}
|
|
1556
|
+
if (step === "primary_assumption") {
|
|
1557
|
+
return { ...draft, primaryAssumption: rawValue.trim() };
|
|
1558
|
+
}
|
|
1559
|
+
if (step === "validation_plan") {
|
|
1560
|
+
return { ...draft, validationPlan: rawValue.trim() };
|
|
1561
|
+
}
|
|
1562
|
+
if (step === "notes") {
|
|
1563
|
+
return { ...draft, notes: rawValue.trim() };
|
|
1564
|
+
}
|
|
1565
|
+
return draft;
|
|
1566
|
+
}
|
|
1567
|
+
function buildLeadingIndicatorFromDraft(draft) {
|
|
1568
|
+
if (draft.leadingIndicatorType === "manual") {
|
|
1569
|
+
if (!draft.manualOperator || typeof draft.manualTarget !== "number") {
|
|
1570
|
+
return null;
|
|
1571
|
+
}
|
|
1572
|
+
return {
|
|
1573
|
+
type: "manual",
|
|
1574
|
+
operator: draft.manualOperator,
|
|
1575
|
+
target: draft.manualTarget
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
if (draft.leadingIndicatorType === "mixpanel") {
|
|
1579
|
+
if (!draft.mixpanelProjectId || !draft.mixpanelWorkspaceId || !draft.mixpanelBookmarkId || !draft.mixpanelOperator || typeof draft.mixpanelTarget !== "number") {
|
|
1580
|
+
return null;
|
|
1621
1581
|
}
|
|
1622
|
-
|
|
1582
|
+
return {
|
|
1583
|
+
type: "mixpanel",
|
|
1584
|
+
project_id: draft.mixpanelProjectId,
|
|
1585
|
+
workspace_id: draft.mixpanelWorkspaceId,
|
|
1586
|
+
bookmark_id: draft.mixpanelBookmarkId,
|
|
1587
|
+
operator: draft.mixpanelOperator,
|
|
1588
|
+
target: draft.mixpanelTarget
|
|
1589
|
+
};
|
|
1623
1590
|
}
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1591
|
+
return null;
|
|
1592
|
+
}
|
|
1593
|
+
function finalizeWizardDraft(draft) {
|
|
1594
|
+
if (!draft.betName || !draft.capType || typeof draft.capValue !== "number") {
|
|
1595
|
+
return null;
|
|
1596
|
+
}
|
|
1597
|
+
const leadingIndicator = buildLeadingIndicatorFromDraft(draft);
|
|
1598
|
+
if (!leadingIndicator) {
|
|
1599
|
+
return null;
|
|
1600
|
+
}
|
|
1601
|
+
if (!draft.primaryAssumption || !draft.validationPlan || draft.notes === void 0) {
|
|
1602
|
+
return null;
|
|
1627
1603
|
}
|
|
1628
1604
|
return {
|
|
1629
|
-
|
|
1630
|
-
|
|
1605
|
+
betName: draft.betName,
|
|
1606
|
+
maxHours: draft.capType === "max_hours" ? draft.capValue : void 0,
|
|
1607
|
+
maxCalendarDays: draft.capType === "max_calendar_days" ? draft.capValue : void 0,
|
|
1608
|
+
leadingIndicator,
|
|
1609
|
+
primaryAssumption: draft.primaryAssumption,
|
|
1610
|
+
validationPlan: draft.validationPlan,
|
|
1611
|
+
notes: draft.notes
|
|
1631
1612
|
};
|
|
1632
1613
|
}
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1614
|
+
|
|
1615
|
+
// src/ui/ink/newWizard/promptUtils.ts
|
|
1616
|
+
function getInitialSelectIndex(options, initialValue) {
|
|
1617
|
+
if (!initialValue) {
|
|
1618
|
+
return 0;
|
|
1619
|
+
}
|
|
1620
|
+
const index = options.findIndex((option) => option.value === initialValue);
|
|
1621
|
+
return index >= 0 ? index : 0;
|
|
1622
|
+
}
|
|
1623
|
+
function classifyTextSubmission(rawValue, options) {
|
|
1624
|
+
const error = options.validate?.(rawValue);
|
|
1625
|
+
if (error) {
|
|
1626
|
+
return { kind: "invalid", message: error };
|
|
1627
|
+
}
|
|
1628
|
+
return { kind: "value", value: rawValue };
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
// src/ui/ink/newWizard/useWizardState.ts
|
|
1632
|
+
var BACK_OPTION = "__back__";
|
|
1633
|
+
var MIXPANEL_URL_HINT = "Values come from report URL: /project/<PROJECT_ID>/view/<WORKSPACE_ID>/...#...report-<BOOKMARK_ID>.";
|
|
1634
|
+
var WIZARD_GUIDANCE_COPY = {
|
|
1635
|
+
primary_assumption: {
|
|
1636
|
+
title: "What must be true for this bet to work?",
|
|
1637
|
+
helpText: "Write the core assumption you are betting on. Focus on one claim that could be proven wrong.",
|
|
1638
|
+
placeholder: "Example: Users who start onboarding from the pricing page convert better because they already understand the value."
|
|
1639
|
+
},
|
|
1640
|
+
validation_plan: {
|
|
1641
|
+
title: "How will you validate whether the bet worked?",
|
|
1642
|
+
helpText: "Describe the metric(s), comparison, and decision rule you will use. Include what outcome would count as success or failure.",
|
|
1643
|
+
placeholder: "Example: Compare signup-to-activation rate for users exposed to variant B vs control for 14 days; consider the bet validated if activation improves by >=10% with no drop in trial starts."
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
function isProviderType(value) {
|
|
1647
|
+
return value === "manual" || value === "mixpanel";
|
|
1648
|
+
}
|
|
1649
|
+
function buildPrompt(step, draft, options) {
|
|
1650
|
+
const requiredText = (title, initialValue, emptyMessage, helpText, placeholder) => ({
|
|
1651
|
+
title,
|
|
1652
|
+
initialValue,
|
|
1653
|
+
helpText,
|
|
1654
|
+
placeholder,
|
|
1655
|
+
validate(rawValue) {
|
|
1656
|
+
if (rawValue.trim().length === 0) {
|
|
1657
|
+
return emptyMessage;
|
|
1654
1658
|
}
|
|
1655
|
-
return
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
return;
|
|
1667
|
-
}
|
|
1668
|
-
if (trimmed2.length === 0) {
|
|
1669
|
-
return "Enter a positive number.";
|
|
1670
|
-
}
|
|
1671
|
-
const parsed = Number(trimmed2);
|
|
1672
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
1673
|
-
return "Enter a positive number.";
|
|
1674
|
-
}
|
|
1659
|
+
return void 0;
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
if (step === "bet_name") {
|
|
1663
|
+
return {
|
|
1664
|
+
title: "Bet name (required).",
|
|
1665
|
+
initialValue: draft.betName ?? options.initialBetName,
|
|
1666
|
+
validate(rawValue) {
|
|
1667
|
+
const normalizedName = normalizeBetName(rawValue);
|
|
1668
|
+
if (normalizedName.length === 0) {
|
|
1669
|
+
return "Enter a bet name.";
|
|
1675
1670
|
}
|
|
1676
|
-
|
|
1677
|
-
if ((0, import_prompts6.isCancel)(value)) {
|
|
1678
|
-
return { kind: "cancel" };
|
|
1671
|
+
return options.validateBetName(normalizedName);
|
|
1679
1672
|
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
})
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
if (step === "cap_type") {
|
|
1676
|
+
return {
|
|
1677
|
+
title: "Choose your time cap type",
|
|
1678
|
+
initialValue: draft.capType,
|
|
1679
|
+
helpText: "Set the maximum amount of time you will allow this bet to run before you must stop, evaluate, or extend it.",
|
|
1680
|
+
options: [
|
|
1681
|
+
{ label: "Cap by hours", value: "max_hours" },
|
|
1682
|
+
{ label: "Cap by calendar days", value: "max_calendar_days" }
|
|
1683
|
+
]
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
if (step === "cap_value") {
|
|
1687
|
+
return {
|
|
1688
|
+
title: `${draft.capType === "max_calendar_days" ? "Max calendar days" : "Max hours"} (required).`,
|
|
1689
|
+
helpText: draft.capType === "max_calendar_days" ? "Enter the maximum number of calendar days this bet is allowed to run before review." : "Enter the maximum number of hours this bet is allowed to run before review.",
|
|
1690
|
+
initialValue: typeof draft.capValue === "number" ? String(draft.capValue) : void 0,
|
|
1691
|
+
validate(rawValue) {
|
|
1692
|
+
const trimmed = rawValue.trim();
|
|
1693
|
+
if (trimmed.length === 0) {
|
|
1694
|
+
return "Enter a positive number.";
|
|
1695
|
+
}
|
|
1696
|
+
const parsed = Number(trimmed);
|
|
1697
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
1698
|
+
return "Enter a positive number.";
|
|
1699
|
+
}
|
|
1700
|
+
return void 0;
|
|
1701
1701
|
}
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
if (step === "leading_indicator_type") {
|
|
1705
|
+
return {
|
|
1706
|
+
title: "Leading indicator provider type",
|
|
1707
|
+
initialValue: draft.leadingIndicatorType,
|
|
1708
|
+
options: listRegisteredProviderTypes().filter(isProviderType).map((type) => ({ label: type, value: type }))
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
if (step === "manual_operator") {
|
|
1712
|
+
return {
|
|
1713
|
+
title: "Leading indicator comparison operator",
|
|
1714
|
+
initialValue: draft.manualOperator,
|
|
1715
|
+
options: [
|
|
1706
1716
|
{ label: "lt (less than)", value: "lt" },
|
|
1707
1717
|
{ label: "lte (less than or equal)", value: "lte" },
|
|
1708
1718
|
{ label: "eq (equal)", value: "eq" },
|
|
1709
1719
|
{ label: "gte (greater than or equal)", value: "gte" },
|
|
1710
1720
|
{ label: "gt (greater than)", value: "gt" }
|
|
1711
|
-
]
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
}
|
|
1723
|
-
if (value === BACK_VALUE3) {
|
|
1724
|
-
return { kind: "back" };
|
|
1725
|
-
}
|
|
1726
|
-
return { kind: "value", value };
|
|
1727
|
-
},
|
|
1728
|
-
async promptManualTarget({ initialValue, allowBack }) {
|
|
1729
|
-
const backHint = allowBack ? ` ${DIM3}(type b to go back)${RESET3}` : "";
|
|
1730
|
-
const value = await (0, import_prompts6.text)({
|
|
1731
|
-
message: `Leading indicator numeric target (required).${backHint}`,
|
|
1732
|
-
initialValue: typeof initialValue === "number" ? String(initialValue) : void 0,
|
|
1733
|
-
validate(rawValue) {
|
|
1734
|
-
const trimmed2 = rawValue.trim();
|
|
1735
|
-
if (allowBack && trimmed2.toLowerCase() === "b") {
|
|
1736
|
-
return;
|
|
1737
|
-
}
|
|
1738
|
-
if (trimmed2.length === 0 || !Number.isFinite(Number(trimmed2))) {
|
|
1739
|
-
return "Enter a valid number.";
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
});
|
|
1743
|
-
if ((0, import_prompts6.isCancel)(value)) {
|
|
1744
|
-
return { kind: "cancel" };
|
|
1745
|
-
}
|
|
1746
|
-
const trimmed = value.trim();
|
|
1747
|
-
if (allowBack && trimmed.toLowerCase() === "b") {
|
|
1748
|
-
return { kind: "back" };
|
|
1749
|
-
}
|
|
1750
|
-
return { kind: "value", value: Number(trimmed) };
|
|
1751
|
-
},
|
|
1752
|
-
async promptMixpanelProjectId({ initialValue, allowBack }) {
|
|
1753
|
-
return mixpanelPromptClient.promptMixpanelProjectId({ initialValue, allowBack });
|
|
1754
|
-
},
|
|
1755
|
-
async promptMixpanelWorkspaceId({ initialValue, allowBack }) {
|
|
1756
|
-
return mixpanelPromptClient.promptMixpanelWorkspaceId({ initialValue, allowBack });
|
|
1757
|
-
},
|
|
1758
|
-
async promptMixpanelBookmarkId({ initialValue, allowBack }) {
|
|
1759
|
-
return mixpanelPromptClient.promptMixpanelBookmarkId({ initialValue, allowBack });
|
|
1760
|
-
},
|
|
1761
|
-
async promptMixpanelOperator({ initialValue, allowBack }) {
|
|
1762
|
-
return mixpanelPromptClient.promptMixpanelOperator({ initialValue, allowBack });
|
|
1763
|
-
},
|
|
1764
|
-
async promptMixpanelTarget({ initialValue, allowBack }) {
|
|
1765
|
-
return mixpanelPromptClient.promptMixpanelTarget({ initialValue, allowBack });
|
|
1766
|
-
},
|
|
1767
|
-
async promptPrimaryAssumption({ initialValue, allowBack }) {
|
|
1768
|
-
const backHint = allowBack ? ` ${DIM3}(type b to go back)${RESET3}` : "";
|
|
1769
|
-
const value = await (0, import_prompts6.text)({
|
|
1770
|
-
message: `Primary assumption (required).${backHint}`,
|
|
1771
|
-
initialValue,
|
|
1772
|
-
validate(rawValue) {
|
|
1773
|
-
const trimmed2 = rawValue.trim();
|
|
1774
|
-
if (allowBack && trimmed2.toLowerCase() === "b") {
|
|
1775
|
-
return;
|
|
1776
|
-
}
|
|
1777
|
-
if (trimmed2.length === 0) {
|
|
1778
|
-
return "Enter a value.";
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
});
|
|
1782
|
-
if ((0, import_prompts6.isCancel)(value)) {
|
|
1783
|
-
return { kind: "cancel" };
|
|
1784
|
-
}
|
|
1785
|
-
const trimmed = value.trim();
|
|
1786
|
-
if (allowBack && trimmed.toLowerCase() === "b") {
|
|
1787
|
-
return { kind: "back" };
|
|
1788
|
-
}
|
|
1789
|
-
return { kind: "value", value: trimmed };
|
|
1790
|
-
},
|
|
1791
|
-
async promptRationale({ initialValue, allowBack }) {
|
|
1792
|
-
const backHint = allowBack ? ` ${DIM3}(type b to go back)${RESET3}` : "";
|
|
1793
|
-
const value = await (0, import_prompts6.text)({
|
|
1794
|
-
message: `Rationale (required).${backHint}`,
|
|
1795
|
-
initialValue,
|
|
1796
|
-
validate(rawValue) {
|
|
1797
|
-
const trimmed2 = rawValue.trim();
|
|
1798
|
-
if (allowBack && trimmed2.toLowerCase() === "b") {
|
|
1799
|
-
return;
|
|
1800
|
-
}
|
|
1801
|
-
if (trimmed2.length === 0) {
|
|
1802
|
-
return "Enter a value.";
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
});
|
|
1806
|
-
if ((0, import_prompts6.isCancel)(value)) {
|
|
1807
|
-
return { kind: "cancel" };
|
|
1808
|
-
}
|
|
1809
|
-
const trimmed = value.trim();
|
|
1810
|
-
if (allowBack && trimmed.toLowerCase() === "b") {
|
|
1811
|
-
return { kind: "back" };
|
|
1812
|
-
}
|
|
1813
|
-
return { kind: "value", value: trimmed };
|
|
1814
|
-
},
|
|
1815
|
-
async promptValidationPlan({ initialValue, allowBack }) {
|
|
1816
|
-
const backHint = allowBack ? ` ${DIM3}(type b to go back)${RESET3}` : "";
|
|
1817
|
-
const value = await (0, import_prompts6.text)({
|
|
1818
|
-
message: `Validation plan (required).${backHint}`,
|
|
1819
|
-
initialValue,
|
|
1820
|
-
validate(rawValue) {
|
|
1821
|
-
const trimmed2 = rawValue.trim();
|
|
1822
|
-
if (allowBack && trimmed2.toLowerCase() === "b") {
|
|
1823
|
-
return;
|
|
1824
|
-
}
|
|
1825
|
-
if (trimmed2.length === 0) {
|
|
1826
|
-
return "Enter a value.";
|
|
1827
|
-
}
|
|
1721
|
+
]
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
if (step === "manual_target") {
|
|
1725
|
+
return {
|
|
1726
|
+
title: "Leading indicator numeric target (required).",
|
|
1727
|
+
initialValue: typeof draft.manualTarget === "number" ? String(draft.manualTarget) : void 0,
|
|
1728
|
+
validate(rawValue) {
|
|
1729
|
+
const trimmed = rawValue.trim();
|
|
1730
|
+
if (trimmed.length === 0 || !Number.isFinite(Number(trimmed))) {
|
|
1731
|
+
return "Enter a valid number.";
|
|
1828
1732
|
}
|
|
1829
|
-
|
|
1830
|
-
if ((0, import_prompts6.isCancel)(value)) {
|
|
1831
|
-
return { kind: "cancel" };
|
|
1832
|
-
}
|
|
1833
|
-
const trimmed = value.trim();
|
|
1834
|
-
if (allowBack && trimmed.toLowerCase() === "b") {
|
|
1835
|
-
return { kind: "back" };
|
|
1733
|
+
return void 0;
|
|
1836
1734
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
if (step === "mixpanel_project_id") {
|
|
1738
|
+
return requiredText("Mixpanel project id (required).", draft.mixpanelProjectId, "Enter a project id.", MIXPANEL_URL_HINT);
|
|
1739
|
+
}
|
|
1740
|
+
if (step === "mixpanel_workspace_id") {
|
|
1741
|
+
return requiredText(
|
|
1742
|
+
"Mixpanel workspace id (required).",
|
|
1743
|
+
draft.mixpanelWorkspaceId,
|
|
1744
|
+
"Enter a workspace id.",
|
|
1745
|
+
MIXPANEL_URL_HINT
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
if (step === "mixpanel_bookmark_id") {
|
|
1749
|
+
return requiredText(
|
|
1750
|
+
"Mixpanel bookmark id (required).",
|
|
1751
|
+
draft.mixpanelBookmarkId,
|
|
1752
|
+
"Enter a bookmark id.",
|
|
1753
|
+
MIXPANEL_URL_HINT
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1756
|
+
if (step === "mixpanel_operator") {
|
|
1757
|
+
return {
|
|
1758
|
+
title: "Mixpanel comparison operator",
|
|
1759
|
+
initialValue: draft.mixpanelOperator,
|
|
1760
|
+
options: [
|
|
1761
|
+
{ label: "lt (less than)", value: "lt" },
|
|
1762
|
+
{ label: "lte (less than or equal)", value: "lte" },
|
|
1763
|
+
{ label: "eq (equal)", value: "eq" },
|
|
1764
|
+
{ label: "gte (greater than or equal)", value: "gte" },
|
|
1765
|
+
{ label: "gt (greater than)", value: "gt" }
|
|
1766
|
+
]
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
if (step === "mixpanel_target") {
|
|
1770
|
+
return {
|
|
1771
|
+
title: "Mixpanel target value (required).",
|
|
1772
|
+
initialValue: typeof draft.mixpanelTarget === "number" ? String(draft.mixpanelTarget) : void 0,
|
|
1773
|
+
validate(rawValue) {
|
|
1774
|
+
const trimmed = rawValue.trim();
|
|
1775
|
+
if (trimmed.length === 0 || !Number.isFinite(Number(trimmed))) {
|
|
1776
|
+
return "Enter a valid number.";
|
|
1850
1777
|
}
|
|
1851
|
-
|
|
1852
|
-
if ((0, import_prompts6.isCancel)(value)) {
|
|
1853
|
-
return { kind: "cancel" };
|
|
1854
|
-
}
|
|
1855
|
-
const trimmed = (value || "").trim();
|
|
1856
|
-
if (allowBack && trimmed.toLowerCase() === "b") {
|
|
1857
|
-
return { kind: "back" };
|
|
1778
|
+
return void 0;
|
|
1858
1779
|
}
|
|
1859
|
-
|
|
1860
|
-
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
if (step === "primary_assumption") {
|
|
1783
|
+
return requiredText(
|
|
1784
|
+
WIZARD_GUIDANCE_COPY.primary_assumption.title,
|
|
1785
|
+
draft.primaryAssumption,
|
|
1786
|
+
"Enter a value.",
|
|
1787
|
+
WIZARD_GUIDANCE_COPY.primary_assumption.helpText,
|
|
1788
|
+
WIZARD_GUIDANCE_COPY.primary_assumption.placeholder
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
if (step === "validation_plan") {
|
|
1792
|
+
return requiredText(
|
|
1793
|
+
WIZARD_GUIDANCE_COPY.validation_plan.title,
|
|
1794
|
+
draft.validationPlan,
|
|
1795
|
+
"Enter a value.",
|
|
1796
|
+
WIZARD_GUIDANCE_COPY.validation_plan.helpText,
|
|
1797
|
+
WIZARD_GUIDANCE_COPY.validation_plan.placeholder
|
|
1798
|
+
);
|
|
1799
|
+
}
|
|
1800
|
+
return {
|
|
1801
|
+
title: "Notes (optional).",
|
|
1802
|
+
initialValue: draft.notes,
|
|
1803
|
+
optional: true,
|
|
1804
|
+
validate: () => void 0
|
|
1861
1805
|
};
|
|
1862
1806
|
}
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1807
|
+
function buildUiState(prompt) {
|
|
1808
|
+
if ("options" in prompt) {
|
|
1809
|
+
return {
|
|
1810
|
+
textValue: "",
|
|
1811
|
+
selectIndex: getInitialSelectIndex(prompt.options, prompt.initialValue),
|
|
1812
|
+
error: void 0
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
return {
|
|
1816
|
+
textValue: prompt.initialValue ?? "",
|
|
1817
|
+
selectIndex: 0,
|
|
1818
|
+
error: void 0
|
|
1819
|
+
};
|
|
1868
1820
|
}
|
|
1869
|
-
|
|
1870
|
-
const
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1821
|
+
function useWizardState(onComplete, options) {
|
|
1822
|
+
const [draft, setDraft] = useState2(() => createInitialWizardDraft());
|
|
1823
|
+
const [stepIndex, setStepIndex] = useState2(0);
|
|
1824
|
+
const [uiState, setUiState] = useState2({ textValue: "", selectIndex: 0 });
|
|
1825
|
+
const steps = useMemo(() => getWizardSteps(draft.leadingIndicatorType), [draft.leadingIndicatorType]);
|
|
1826
|
+
const safeStepIndex = Math.min(stepIndex, Math.max(0, steps.length - 1));
|
|
1827
|
+
const step = steps[safeStepIndex] ?? steps[0];
|
|
1828
|
+
const prompt = useMemo(() => buildPrompt(step, draft, options), [step, draft, options]);
|
|
1829
|
+
useEffect2(() => {
|
|
1830
|
+
if (safeStepIndex !== stepIndex) {
|
|
1831
|
+
setStepIndex(safeStepIndex);
|
|
1832
|
+
}
|
|
1833
|
+
}, [safeStepIndex, stepIndex]);
|
|
1834
|
+
useEffect2(() => {
|
|
1835
|
+
setUiState(buildUiState(prompt));
|
|
1836
|
+
}, [step]);
|
|
1837
|
+
const completeIfDone = (nextDraft, nextStepIndex) => {
|
|
1838
|
+
const nextSteps = getWizardSteps(nextDraft.leadingIndicatorType);
|
|
1839
|
+
if (nextStepIndex < nextSteps.length) {
|
|
1840
|
+
return false;
|
|
1875
1841
|
}
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
return
|
|
1879
|
-
}
|
|
1842
|
+
const finalized = finalizeWizardDraft(nextDraft);
|
|
1843
|
+
onComplete(finalized ? { cancelled: false, values: finalized } : { cancelled: true });
|
|
1844
|
+
return true;
|
|
1845
|
+
};
|
|
1846
|
+
const goBack = () => {
|
|
1847
|
+
setUiState((previous) => ({ ...previous, error: void 0 }));
|
|
1848
|
+
setStepIndex((previous) => Math.max(0, previous - 1));
|
|
1849
|
+
};
|
|
1850
|
+
const submitSelect = (selectedValue) => {
|
|
1851
|
+
if (selectedValue === BACK_OPTION) {
|
|
1852
|
+
goBack();
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
const nextStep = safeStepIndex + 1;
|
|
1856
|
+
const nextDraft = applySelectStepValue(draft, step, selectedValue);
|
|
1857
|
+
setDraft(nextDraft);
|
|
1858
|
+
if (!completeIfDone(nextDraft, nextStep)) {
|
|
1859
|
+
setStepIndex(nextStep);
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1862
|
+
const submitText = () => {
|
|
1863
|
+
if ("options" in prompt) {
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
const submission = classifyTextSubmission(uiState.textValue, {
|
|
1867
|
+
validate: prompt.validate
|
|
1868
|
+
});
|
|
1869
|
+
if (submission.kind === "invalid") {
|
|
1870
|
+
setUiState((previous) => ({ ...previous, error: submission.message }));
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
if (submission.kind === "back") {
|
|
1874
|
+
goBack();
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
const nextStep = safeStepIndex + 1;
|
|
1878
|
+
const nextDraft = applyTextStepValue(draft, step, submission.value);
|
|
1879
|
+
setDraft(nextDraft);
|
|
1880
|
+
if (!completeIfDone(nextDraft, nextStep)) {
|
|
1881
|
+
setStepIndex(nextStep);
|
|
1882
|
+
}
|
|
1883
|
+
};
|
|
1884
|
+
const cancel = () => {
|
|
1885
|
+
onComplete({ cancelled: true });
|
|
1886
|
+
};
|
|
1880
1887
|
return {
|
|
1881
|
-
|
|
1882
|
-
|
|
1888
|
+
prompt,
|
|
1889
|
+
step,
|
|
1890
|
+
draft,
|
|
1891
|
+
uiState,
|
|
1892
|
+
actions: { goBack, submitSelect, submitText, cancel, setUiState }
|
|
1883
1893
|
};
|
|
1884
1894
|
}
|
|
1885
1895
|
|
|
1886
|
-
// src/
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
}
|
|
1896
|
+
// src/ui/ink/newWizard/App.tsx
|
|
1897
|
+
import { Text as Text5 } from "ink";
|
|
1898
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1899
|
+
function InkNewWizardApp({
|
|
1900
|
+
onComplete,
|
|
1901
|
+
options
|
|
1902
|
+
}) {
|
|
1903
|
+
const { prompt, step, draft, uiState, actions } = useWizardState(onComplete, options);
|
|
1904
|
+
useWizardInput(prompt, uiState, actions);
|
|
1905
|
+
const betCardModel = buildBetCardPreviewModel({
|
|
1906
|
+
draft,
|
|
1907
|
+
step,
|
|
1908
|
+
prompt,
|
|
1909
|
+
uiState,
|
|
1910
|
+
providerTypes: listRegisteredProviderTypes()
|
|
1911
|
+
});
|
|
1912
|
+
return /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1913
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Create a new bet:" }),
|
|
1914
|
+
/* @__PURE__ */ jsx5(BetCard, { model: betCardModel }),
|
|
1915
|
+
/* @__PURE__ */ jsx5(WizardFrame, { title: prompt.title, helpText: prompt.helpText, error: uiState.error, children: "options" in prompt ? /* @__PURE__ */ jsx5(SelectStep, { prompt, selectedIndex: uiState.selectIndex }) : /* @__PURE__ */ jsx5(TextStep, { prompt, value: uiState.textValue }) })
|
|
1916
|
+
] });
|
|
1894
1917
|
}
|
|
1918
|
+
|
|
1919
|
+
// src/ui/ink/newWizard/renderNewWizardInk.tsx
|
|
1920
|
+
async function renderNewWizardInk(options) {
|
|
1921
|
+
return await new Promise((resolve, reject) => {
|
|
1922
|
+
let settled = false;
|
|
1923
|
+
const app = render(
|
|
1924
|
+
createElement(InkNewWizardApp, {
|
|
1925
|
+
options,
|
|
1926
|
+
onComplete(result) {
|
|
1927
|
+
if (settled) {
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
settled = true;
|
|
1931
|
+
resolve(result);
|
|
1932
|
+
app.unmount();
|
|
1933
|
+
}
|
|
1934
|
+
})
|
|
1935
|
+
);
|
|
1936
|
+
app.waitUntilExit().catch((error) => {
|
|
1937
|
+
if (settled) {
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
settled = true;
|
|
1941
|
+
reject(error);
|
|
1942
|
+
});
|
|
1943
|
+
});
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// src/ui/newWizard.ts
|
|
1947
|
+
async function runNewWizard(options) {
|
|
1948
|
+
return renderNewWizardInk(options);
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
// src/commands/new.ts
|
|
1895
1952
|
function isInteractiveTty3() {
|
|
1896
1953
|
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
1897
1954
|
}
|
|
@@ -1899,21 +1956,9 @@ function invalidIdError(id) {
|
|
|
1899
1956
|
return `Invalid bet id '${id}'. Use id format like 'landing-page' or 'landing_page'.`;
|
|
1900
1957
|
}
|
|
1901
1958
|
async function runNew(rawId) {
|
|
1902
|
-
|
|
1903
|
-
if (!
|
|
1904
|
-
|
|
1905
|
-
console.error("Missing bet name. Run 'bep new <name>' or use an interactive terminal.");
|
|
1906
|
-
return 1;
|
|
1907
|
-
}
|
|
1908
|
-
const nameResult = await promptNewBetName();
|
|
1909
|
-
if (nameResult.cancelled) {
|
|
1910
|
-
console.error("Cancelled. No files were created.");
|
|
1911
|
-
return 1;
|
|
1912
|
-
}
|
|
1913
|
-
id = normalizeBetName(nameResult.value);
|
|
1914
|
-
}
|
|
1915
|
-
if (!isValidBetId(id)) {
|
|
1916
|
-
console.error(invalidIdError(id));
|
|
1959
|
+
const initialBetName = rawId ? normalizeBetName(rawId) : void 0;
|
|
1960
|
+
if (!initialBetName && !isInteractiveTty3()) {
|
|
1961
|
+
console.error("Missing bet name. Run 'bep new <name>' or use an interactive terminal.");
|
|
1917
1962
|
return 1;
|
|
1918
1963
|
}
|
|
1919
1964
|
let rootDir;
|
|
@@ -1924,17 +1969,28 @@ async function runNew(rawId) {
|
|
|
1924
1969
|
console.error(error.message);
|
|
1925
1970
|
return 1;
|
|
1926
1971
|
}
|
|
1927
|
-
const
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1972
|
+
const validateBetName = (normalizedName) => {
|
|
1973
|
+
if (!isValidBetId(normalizedName)) {
|
|
1974
|
+
return invalidIdError(normalizedName);
|
|
1975
|
+
}
|
|
1976
|
+
const relativePath2 = path8.join(BETS_DIR, `${normalizedName}.md`);
|
|
1977
|
+
const absolutePath2 = path8.join(rootDir, relativePath2);
|
|
1978
|
+
if (existsSync(absolutePath2)) {
|
|
1979
|
+
return `Bet '${normalizedName}' already exists at ${relativePath2}. Choose a unique id.`;
|
|
1980
|
+
}
|
|
1981
|
+
return void 0;
|
|
1982
|
+
};
|
|
1983
|
+
const wizardResult = await runNewWizard({
|
|
1984
|
+
initialBetName,
|
|
1985
|
+
validateBetName
|
|
1986
|
+
});
|
|
1934
1987
|
if (wizardResult.cancelled) {
|
|
1935
1988
|
console.error("Cancelled. No files were created.");
|
|
1936
1989
|
return 1;
|
|
1937
1990
|
}
|
|
1991
|
+
const id = wizardResult.values.betName;
|
|
1992
|
+
const relativePath = path8.join(BETS_DIR, `${id}.md`);
|
|
1993
|
+
const absolutePath = path8.join(rootDir, relativePath);
|
|
1938
1994
|
const markdown = renderNewBetMarkdown({
|
|
1939
1995
|
id,
|
|
1940
1996
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1942,12 +1998,11 @@ async function runNew(rawId) {
|
|
|
1942
1998
|
maxHours: wizardResult.values.maxHours,
|
|
1943
1999
|
maxCalendarDays: wizardResult.values.maxCalendarDays,
|
|
1944
2000
|
primaryAssumption: wizardResult.values.primaryAssumption,
|
|
1945
|
-
rationale: wizardResult.values.rationale,
|
|
1946
2001
|
validationPlan: wizardResult.values.validationPlan,
|
|
1947
2002
|
notes: wizardResult.values.notes
|
|
1948
2003
|
});
|
|
1949
2004
|
try {
|
|
1950
|
-
await (
|
|
2005
|
+
await writeFile5(absolutePath, markdown, { encoding: "utf8", flag: "wx" });
|
|
1951
2006
|
} catch (error) {
|
|
1952
2007
|
if (error.code === "EEXIST") {
|
|
1953
2008
|
console.error(`Bet '${id}' already exists at ${relativePath}. Choose a unique id.`);
|
|
@@ -1961,8 +2016,8 @@ Created ${relativePath}.`);
|
|
|
1961
2016
|
}
|
|
1962
2017
|
|
|
1963
2018
|
// src/state/state.ts
|
|
1964
|
-
|
|
1965
|
-
|
|
2019
|
+
import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
2020
|
+
import path9 from "path";
|
|
1966
2021
|
function isValidActiveSession(value) {
|
|
1967
2022
|
if (!value || typeof value !== "object") {
|
|
1968
2023
|
return false;
|
|
@@ -1987,13 +2042,13 @@ function parseState(raw) {
|
|
|
1987
2042
|
return { active };
|
|
1988
2043
|
}
|
|
1989
2044
|
async function readState(rootDir) {
|
|
1990
|
-
const statePath =
|
|
1991
|
-
const raw = await (
|
|
2045
|
+
const statePath = path9.join(rootDir, STATE_PATH);
|
|
2046
|
+
const raw = await readFile6(statePath, "utf8");
|
|
1992
2047
|
return parseState(raw);
|
|
1993
2048
|
}
|
|
1994
2049
|
async function writeState(rootDir, state) {
|
|
1995
|
-
const statePath =
|
|
1996
|
-
await (
|
|
2050
|
+
const statePath = path9.join(rootDir, STATE_PATH);
|
|
2051
|
+
await writeFile6(statePath, `${JSON.stringify(state, null, 2)}
|
|
1997
2052
|
`, "utf8");
|
|
1998
2053
|
}
|
|
1999
2054
|
function addActiveSession(state, id, startedAt) {
|
|
@@ -2071,8 +2126,8 @@ async function runStart(id) {
|
|
|
2071
2126
|
}
|
|
2072
2127
|
|
|
2073
2128
|
// src/commands/stop.ts
|
|
2074
|
-
|
|
2075
|
-
|
|
2129
|
+
import { appendFile } from "fs/promises";
|
|
2130
|
+
import path10 from "path";
|
|
2076
2131
|
async function runStop(id) {
|
|
2077
2132
|
if (!isValidBetId(id)) {
|
|
2078
2133
|
console.error(`Invalid bet id '${id}'. Use lowercase id format like 'landing-page' or 'landing_page'.`);
|
|
@@ -2126,10 +2181,10 @@ async function runStop(id) {
|
|
|
2126
2181
|
return 1;
|
|
2127
2182
|
}
|
|
2128
2183
|
}
|
|
2129
|
-
const logPath =
|
|
2184
|
+
const logPath = path10.join(rootDir, LOGS_DIR, `${id}.jsonl`);
|
|
2130
2185
|
const serializedLogs = logs.map((line) => JSON.stringify(line)).join("\n").concat("\n");
|
|
2131
2186
|
try {
|
|
2132
|
-
await
|
|
2187
|
+
await appendFile(logPath, serializedLogs, "utf8");
|
|
2133
2188
|
await writeState(rootDir, next.state);
|
|
2134
2189
|
} catch (error) {
|
|
2135
2190
|
console.error(`Failed to stop bet '${id}': ${error.message}`);
|
|
@@ -2143,8 +2198,8 @@ async function runStop(id) {
|
|
|
2143
2198
|
}
|
|
2144
2199
|
|
|
2145
2200
|
// src/commands/status.ts
|
|
2146
|
-
|
|
2147
|
-
|
|
2201
|
+
import { readFile as readFile7, readdir as readdir2 } from "fs/promises";
|
|
2202
|
+
import path11 from "path";
|
|
2148
2203
|
var STATUS_COLUMNS = [
|
|
2149
2204
|
"id",
|
|
2150
2205
|
"status",
|
|
@@ -2218,12 +2273,12 @@ function formatValidation(snapshot) {
|
|
|
2218
2273
|
return `${resultLabel} ${observedValue}`;
|
|
2219
2274
|
}
|
|
2220
2275
|
async function sumLoggedExposureSeconds(rootDir, id) {
|
|
2221
|
-
const relativePath =
|
|
2222
|
-
const absolutePath =
|
|
2276
|
+
const relativePath = path11.join(LOGS_DIR, `${id}.jsonl`);
|
|
2277
|
+
const absolutePath = path11.join(rootDir, relativePath);
|
|
2223
2278
|
if (!await pathExists2(absolutePath)) {
|
|
2224
2279
|
return 0;
|
|
2225
2280
|
}
|
|
2226
|
-
const raw = await (
|
|
2281
|
+
const raw = await readFile7(absolutePath, "utf8");
|
|
2227
2282
|
if (raw.trim().length === 0) {
|
|
2228
2283
|
return 0;
|
|
2229
2284
|
}
|
|
@@ -2250,14 +2305,14 @@ async function sumLoggedExposureSeconds(rootDir, id) {
|
|
|
2250
2305
|
return total;
|
|
2251
2306
|
}
|
|
2252
2307
|
async function readValidationLabel(rootDir, id) {
|
|
2253
|
-
const relativePath =
|
|
2254
|
-
const absolutePath =
|
|
2308
|
+
const relativePath = path11.join(EVIDENCE_DIR, `${id}.json`);
|
|
2309
|
+
const absolutePath = path11.join(rootDir, relativePath);
|
|
2255
2310
|
if (!await pathExists2(absolutePath)) {
|
|
2256
2311
|
return "N/A";
|
|
2257
2312
|
}
|
|
2258
2313
|
let parsed;
|
|
2259
2314
|
try {
|
|
2260
|
-
parsed = JSON.parse(await (
|
|
2315
|
+
parsed = JSON.parse(await readFile7(absolutePath, "utf8"));
|
|
2261
2316
|
} catch (error) {
|
|
2262
2317
|
throw new Error(`Failed to parse evidence file at ${relativePath}: ${error.message}`);
|
|
2263
2318
|
}
|
|
@@ -2286,10 +2341,10 @@ async function runStatus() {
|
|
|
2286
2341
|
console.error(error.message);
|
|
2287
2342
|
return 1;
|
|
2288
2343
|
}
|
|
2289
|
-
const betDir =
|
|
2344
|
+
const betDir = path11.join(rootDir, BETS_DIR);
|
|
2290
2345
|
let dirEntries;
|
|
2291
2346
|
try {
|
|
2292
|
-
dirEntries = await (
|
|
2347
|
+
dirEntries = await readdir2(betDir, { withFileTypes: true });
|
|
2293
2348
|
} catch (error) {
|
|
2294
2349
|
console.error(`Failed to read bets directory at ${BETS_DIR}: ${error.message}`);
|
|
2295
2350
|
return 1;
|
|
@@ -2367,8 +2422,8 @@ async function runStatus() {
|
|
|
2367
2422
|
}
|
|
2368
2423
|
|
|
2369
2424
|
// src/commands/hook.ts
|
|
2370
|
-
|
|
2371
|
-
|
|
2425
|
+
import { appendFile as appendFile3 } from "fs/promises";
|
|
2426
|
+
import path14 from "path";
|
|
2372
2427
|
|
|
2373
2428
|
// src/hooks/events.ts
|
|
2374
2429
|
var MAX_FIELD_LENGTH = 2e3;
|
|
@@ -2446,8 +2501,8 @@ async function readHookStdin() {
|
|
|
2446
2501
|
}
|
|
2447
2502
|
|
|
2448
2503
|
// src/tracking/context.ts
|
|
2449
|
-
|
|
2450
|
-
|
|
2504
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
2505
|
+
import path12 from "path";
|
|
2451
2506
|
var MAX_BET_SUMMARY_CHARS = 800;
|
|
2452
2507
|
var MAX_HISTORY_ENTRIES = 20;
|
|
2453
2508
|
function summarizeContent(content) {
|
|
@@ -2465,6 +2520,15 @@ function extractSection(content, heading) {
|
|
|
2465
2520
|
}
|
|
2466
2521
|
return summarizeContent(match[1]);
|
|
2467
2522
|
}
|
|
2523
|
+
function extractSectionAny(content, headings) {
|
|
2524
|
+
for (const heading of headings) {
|
|
2525
|
+
const value = extractSection(content, heading);
|
|
2526
|
+
if (value) {
|
|
2527
|
+
return value;
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
return void 0;
|
|
2531
|
+
}
|
|
2468
2532
|
async function readBetCatalog(rootDir) {
|
|
2469
2533
|
const files = await listBetMarkdownFiles(rootDir);
|
|
2470
2534
|
const result = [];
|
|
@@ -2478,9 +2542,8 @@ async function readBetCatalog(rootDir) {
|
|
|
2478
2542
|
id,
|
|
2479
2543
|
status,
|
|
2480
2544
|
assumption: extractSection(content, "1\\. Primary Assumption"),
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
notes: extractSection(content, "4\\. Notes"),
|
|
2545
|
+
validationPlan: extractSectionAny(content, ["2\\. Validation Plan", "3\\. Validation Plan"]),
|
|
2546
|
+
notes: extractSectionAny(content, ["3\\. Notes", "4\\. Notes"]),
|
|
2484
2547
|
summary: summarizeContent(content)
|
|
2485
2548
|
});
|
|
2486
2549
|
} catch {
|
|
@@ -2489,10 +2552,10 @@ async function readBetCatalog(rootDir) {
|
|
|
2489
2552
|
return result;
|
|
2490
2553
|
}
|
|
2491
2554
|
async function readRecentAttribution(rootDir) {
|
|
2492
|
-
const filePath =
|
|
2555
|
+
const filePath = path12.join(rootDir, LOGS_DIR, "agent-attribution.jsonl");
|
|
2493
2556
|
let raw;
|
|
2494
2557
|
try {
|
|
2495
|
-
raw = await (
|
|
2558
|
+
raw = await readFile8(filePath, "utf8");
|
|
2496
2559
|
} catch {
|
|
2497
2560
|
return [];
|
|
2498
2561
|
}
|
|
@@ -2640,8 +2703,8 @@ async function applySelectionDecision(context, rawDecision, deps = defaultDeps)
|
|
|
2640
2703
|
}
|
|
2641
2704
|
|
|
2642
2705
|
// src/tracking/enforcement.ts
|
|
2643
|
-
|
|
2644
|
-
|
|
2706
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
2707
|
+
import path13 from "path";
|
|
2645
2708
|
function parsePositiveNumber(value) {
|
|
2646
2709
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
2647
2710
|
return null;
|
|
@@ -2663,12 +2726,12 @@ function parseCreatedAtMs2(value) {
|
|
|
2663
2726
|
return millis;
|
|
2664
2727
|
}
|
|
2665
2728
|
async function sumLoggedExposureSeconds2(rootDir, betId) {
|
|
2666
|
-
const relativePath =
|
|
2667
|
-
const absolutePath =
|
|
2729
|
+
const relativePath = path13.join(LOGS_DIR, `${betId}.jsonl`);
|
|
2730
|
+
const absolutePath = path13.join(rootDir, relativePath);
|
|
2668
2731
|
if (!await pathExists2(absolutePath)) {
|
|
2669
2732
|
return 0;
|
|
2670
2733
|
}
|
|
2671
|
-
const raw = await (
|
|
2734
|
+
const raw = await readFile9(absolutePath, "utf8");
|
|
2672
2735
|
if (raw.trim().length === 0) {
|
|
2673
2736
|
return 0;
|
|
2674
2737
|
}
|
|
@@ -2802,9 +2865,9 @@ async function evaluateCapGate(rootDir, context, decision) {
|
|
|
2802
2865
|
}
|
|
2803
2866
|
|
|
2804
2867
|
// src/tracking/selector.ts
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2868
|
+
import { spawn } from "child_process";
|
|
2869
|
+
import { appendFile as appendFile2 } from "fs/promises";
|
|
2870
|
+
import os from "os";
|
|
2808
2871
|
var DEBUG = true;
|
|
2809
2872
|
var SELECTION_TIMEOUT_MS = 6e4;
|
|
2810
2873
|
var OUTPUT_FORMAT_INSTRUCTION = "Return only one JSON object. Do not include markdown fences, commentary, or extra keys.";
|
|
@@ -2822,7 +2885,7 @@ async function writeDebug(debugLogPath, entry) {
|
|
|
2822
2885
|
return;
|
|
2823
2886
|
}
|
|
2824
2887
|
try {
|
|
2825
|
-
await (
|
|
2888
|
+
await appendFile2(
|
|
2826
2889
|
debugLogPath,
|
|
2827
2890
|
`${JSON.stringify({
|
|
2828
2891
|
...entry,
|
|
@@ -2849,14 +2912,14 @@ async function defaultRunClaude({ prompt, timeoutMs, onDebug }) {
|
|
|
2849
2912
|
void onDebug?.("spawn_start", "Starting claude selector subprocess.", {
|
|
2850
2913
|
command: "claude",
|
|
2851
2914
|
args: ["--print", "--output-format", "json", "--model", "sonnet", "--setting-sources", ""],
|
|
2852
|
-
cwd:
|
|
2915
|
+
cwd: os.tmpdir(),
|
|
2853
2916
|
timeoutMs
|
|
2854
2917
|
});
|
|
2855
|
-
const child =
|
|
2918
|
+
const child = spawn(
|
|
2856
2919
|
"claude",
|
|
2857
2920
|
["--print", "--output-format", "json", "--model", "sonnet", "--setting-sources", ""],
|
|
2858
2921
|
{
|
|
2859
|
-
cwd:
|
|
2922
|
+
cwd: os.tmpdir(),
|
|
2860
2923
|
env: stripGitEnv(process.env),
|
|
2861
2924
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2862
2925
|
}
|
|
@@ -2959,12 +3022,11 @@ function toDecision(raw, knownIds) {
|
|
|
2959
3022
|
reason: reason.trim()
|
|
2960
3023
|
};
|
|
2961
3024
|
}
|
|
2962
|
-
function
|
|
3025
|
+
function buildPrompt2(context) {
|
|
2963
3026
|
const bets = context.bets.map((bet) => ({
|
|
2964
3027
|
id: bet.id,
|
|
2965
3028
|
status: bet.status,
|
|
2966
3029
|
assumption: bet.assumption,
|
|
2967
|
-
rationale: bet.rationale,
|
|
2968
3030
|
validation_plan: bet.validationPlan,
|
|
2969
3031
|
notes: bet.notes,
|
|
2970
3032
|
summary: bet.summary
|
|
@@ -3056,7 +3118,7 @@ async function selectBetWithClaude(context, runClaudeOrOptions = defaultRunClaud
|
|
|
3056
3118
|
rawText: ""
|
|
3057
3119
|
};
|
|
3058
3120
|
}
|
|
3059
|
-
const prompt =
|
|
3121
|
+
const prompt = buildPrompt2(context);
|
|
3060
3122
|
await debugLog("build_prompt", "Built Claude selector prompt.", {
|
|
3061
3123
|
prompt,
|
|
3062
3124
|
activeBetIds: context.activeBetIds,
|
|
@@ -3172,7 +3234,7 @@ var defaultDeps2 = {
|
|
|
3172
3234
|
readInput: readHookStdin,
|
|
3173
3235
|
select: selectBetWithClaude,
|
|
3174
3236
|
apply: applySelectionDecision,
|
|
3175
|
-
append:
|
|
3237
|
+
append: appendFile3,
|
|
3176
3238
|
writeOutput: (output) => process.stdout.write(output)
|
|
3177
3239
|
};
|
|
3178
3240
|
function isHookEvent(value) {
|
|
@@ -3218,7 +3280,7 @@ async function runHook(agent, event, deps = defaultDeps2) {
|
|
|
3218
3280
|
let blockLine = null;
|
|
3219
3281
|
try {
|
|
3220
3282
|
const context = await buildBetSelectionContext(found.rootDir, event, payload);
|
|
3221
|
-
const debugLogPath =
|
|
3283
|
+
const debugLogPath = path14.join(found.rootDir, LOGS_DIR, "hook_debug.log");
|
|
3222
3284
|
selection = await deps.select(context, { debugLogPath });
|
|
3223
3285
|
if (selection.ok) {
|
|
3224
3286
|
const gate = await evaluateCapGate(found.rootDir, context, selection.decision);
|
|
@@ -3278,9 +3340,9 @@ async function runHook(agent, event, deps = defaultDeps2) {
|
|
|
3278
3340
|
confidence: decision.confidence,
|
|
3279
3341
|
applied: applied?.applied ?? false
|
|
3280
3342
|
};
|
|
3281
|
-
const attributionPath =
|
|
3282
|
-
const sessionPath =
|
|
3283
|
-
const blocksPath =
|
|
3343
|
+
const attributionPath = path14.join(found.rootDir, LOGS_DIR, "agent-attribution.jsonl");
|
|
3344
|
+
const sessionPath = path14.join(found.rootDir, LOGS_DIR, "agent-sessions.jsonl");
|
|
3345
|
+
const blocksPath = path14.join(found.rootDir, LOGS_DIR, "agent-blocks.jsonl");
|
|
3284
3346
|
if (blockLine) {
|
|
3285
3347
|
await deps.append(blocksPath, `${JSON.stringify(blockLine)}
|
|
3286
3348
|
`, "utf8");
|
|
@@ -3303,7 +3365,7 @@ async function runHook(agent, event, deps = defaultDeps2) {
|
|
|
3303
3365
|
|
|
3304
3366
|
// src/cli.ts
|
|
3305
3367
|
async function main(argv) {
|
|
3306
|
-
const program = new
|
|
3368
|
+
const program = new Command();
|
|
3307
3369
|
program.name("bep").description("Budgeted Engineering Proposals CLI");
|
|
3308
3370
|
program.command("init").description("Initialize BEP directories in the current repository").option("--install-hooks", "Install agent tracking hooks").option("--no-install-hooks", "Skip agent tracking hook setup").option("--agent <agent>", "Agent target for hook setup (currently: claude-code)").action(async (options) => {
|
|
3309
3371
|
const exitCode = await runInit({
|
|
@@ -3312,8 +3374,8 @@ async function main(argv) {
|
|
|
3312
3374
|
});
|
|
3313
3375
|
process.exitCode = exitCode;
|
|
3314
3376
|
});
|
|
3315
|
-
program.command("new [id...]").description("Create a new BEP markdown file").action(async (
|
|
3316
|
-
const id =
|
|
3377
|
+
program.command("new [id...]").description("Create a new BEP markdown file").action(async (args) => {
|
|
3378
|
+
const id = args && args.length > 0 ? [...args].join(" ") : void 0;
|
|
3317
3379
|
const exitCode = await runNew(id);
|
|
3318
3380
|
process.exitCode = exitCode;
|
|
3319
3381
|
});
|
|
@@ -3339,11 +3401,18 @@ async function main(argv) {
|
|
|
3339
3401
|
});
|
|
3340
3402
|
await program.parseAsync(argv);
|
|
3341
3403
|
}
|
|
3342
|
-
|
|
3404
|
+
function isEntrypoint() {
|
|
3405
|
+
const argvPath = process.argv[1];
|
|
3406
|
+
if (!argvPath) {
|
|
3407
|
+
return false;
|
|
3408
|
+
}
|
|
3409
|
+
const modulePath = fileURLToPath(import.meta.url);
|
|
3410
|
+
return path15.resolve(argvPath) === path15.resolve(modulePath);
|
|
3411
|
+
}
|
|
3412
|
+
if (isEntrypoint()) {
|
|
3343
3413
|
void main(process.argv);
|
|
3344
3414
|
}
|
|
3345
|
-
|
|
3346
|
-
0 && (module.exports = {
|
|
3415
|
+
export {
|
|
3347
3416
|
main
|
|
3348
|
-
}
|
|
3417
|
+
};
|
|
3349
3418
|
//# sourceMappingURL=cli.js.map
|