@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/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
- var cli_exports = {};
33
- __export(cli_exports, {
34
- main: () => main
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
- var import_promises2 = require("fs/promises");
41
- var import_node_path2 = __toESM(require("path"));
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
- var import_promises = require("fs/promises");
45
- var import_node_path = __toESM(require("path"));
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 = import_node_path.default.join(rootDir, PROVIDER_CONFIG_PATH);
17
+ const configPath = path.join(rootDir, PROVIDER_CONFIG_PATH);
49
18
  let raw;
50
19
  try {
51
- raw = await (0, import_promises.readFile)(configPath, "utf8");
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 = import_node_path2.default.join(BETS_DIR, "_logs");
106
- var EVIDENCE_DIR = import_node_path2.default.join(BETS_DIR, "_evidence");
107
- var STATE_PATH = import_node_path2.default.join(BETS_DIR, "_state.json");
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 (0, import_promises2.access)(filePath);
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 = import_node_path2.default.join(rootDir, relativeDir);
99
+ const absoluteDir = path2.join(rootDir, relativeDir);
131
100
  const existed = await pathExists(absoluteDir);
132
- await (0, import_promises2.mkdir)(absoluteDir, { recursive: true });
101
+ await mkdir(absoluteDir, { recursive: true });
133
102
  if (!existed) {
134
103
  createdPaths.push(relativeDir);
135
104
  }
136
105
  }
137
- const statePath = import_node_path2.default.join(rootDir, STATE_PATH);
106
+ const statePath = path2.join(rootDir, STATE_PATH);
138
107
  const stateExists = await pathExists(statePath);
139
108
  if (!stateExists) {
140
- await (0, import_promises2.writeFile)(statePath, `${JSON.stringify(DEFAULT_STATE, null, 2)}
109
+ await writeFile(statePath, `${JSON.stringify(DEFAULT_STATE, null, 2)}
141
110
  `, "utf8");
142
111
  createdPaths.push(STATE_PATH);
143
112
  }
144
- const providerConfigPath = import_node_path2.default.join(rootDir, PROVIDER_CONFIG_PATH);
113
+ const providerConfigPath = path2.join(rootDir, PROVIDER_CONFIG_PATH);
145
114
  const providerConfigExists = await pathExists(providerConfigPath);
146
115
  if (!providerConfigExists) {
147
- await (0, import_promises2.writeFile)(providerConfigPath, `${JSON.stringify(DEFAULT_PROVIDER_CONFIG, null, 2)}
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 = import_node_path2.default.resolve(startDir);
130
+ let currentDir = path2.resolve(startDir);
162
131
  while (true) {
163
- if (await pathExists(import_node_path2.default.join(currentDir, ".git"))) {
132
+ if (await pathExists(path2.join(currentDir, ".git"))) {
164
133
  return currentDir;
165
134
  }
166
- const parentDir = import_node_path2.default.dirname(currentDir);
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 = import_node_path2.default.join(gitRoot, GITIGNORE_PATH);
143
+ const gitignorePath = path2.join(gitRoot, GITIGNORE_PATH);
175
144
  const exists = await pathExists(gitignorePath);
176
145
  if (!exists) {
177
- await (0, import_promises2.writeFile)(gitignorePath, `${PROVIDER_GITIGNORE_ENTRY}
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 (0, import_promises2.readFile)(gitignorePath, "utf8");
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 (0, import_promises2.writeFile)(gitignorePath, `${raw}${suffix}${PROVIDER_GITIGNORE_ENTRY}
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 = import_node_path2.default.join(candidateRootDir, relativePath);
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 = import_node_path2.default.resolve(startDir);
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: import_node_path2.default.join(currentDir, BETS_DIR)
175
+ betsDir: path2.join(currentDir, BETS_DIR)
207
176
  };
208
177
  }
209
- const parentDir = import_node_path2.default.dirname(currentDir);
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
- var import_node_path5 = __toESM(require("path"));
194
+ import path5 from "path";
226
195
 
227
196
  // src/hooks/discovery.ts
228
- var import_promises3 = require("fs/promises");
229
- var import_node_path3 = __toESM(require("path"));
197
+ import { stat } from "fs/promises";
198
+ import path3 from "path";
230
199
  async function findNearestClaudeDir(startDir) {
231
- let currentDir = import_node_path3.default.resolve(startDir);
200
+ let currentDir = path3.resolve(startDir);
232
201
  while (true) {
233
- const candidate = import_node_path3.default.join(currentDir, ".claude");
202
+ const candidate = path3.join(currentDir, ".claude");
234
203
  try {
235
- const stats = await (0, import_promises3.stat)(candidate);
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 = import_node_path3.default.dirname(currentDir);
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
- var import_promises4 = require("fs/promises");
251
- var import_node_path4 = __toESM(require("path"));
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 = import_node_path4.default.join(claudeDir, CLAUDE_SETTINGS_FILE);
269
+ const settingsPath = path4.join(claudeDir, CLAUDE_SETTINGS_FILE);
301
270
  let settings = {};
302
271
  try {
303
- const raw = await (0, import_promises4.readFile)(settingsPath, "utf8");
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 (0, import_promises4.writeFile)(settingsPath, `${JSON.stringify(settings, null, 2)}
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 = import_node_path5.default.isAbsolute(argv1) ? argv1 : import_node_path5.default.resolve(startDir, argv1);
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 = import_node_path5.default.relative(startDir, installed.settingsPath) || import_node_path5.default.basename(installed.settingsPath);
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
- var import_prompts = require("@clack/prompts");
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 (0, import_prompts.confirm)({
396
+ const value = await confirm({
428
397
  message: "Install agent tracking hooks now?",
429
398
  initialValue: true
430
399
  });
431
- if ((0, import_prompts.isCancel)(value)) {
400
+ if (isCancel(value)) {
432
401
  return "cancel";
433
402
  }
434
403
  return value;
435
404
  },
436
405
  async promptAgent() {
437
- const value = await (0, import_prompts.select)({
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 ((0, import_prompts.isCancel)(value)) {
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
- var import_promises6 = require("fs/promises");
513
- var import_node_path7 = __toESM(require("path"));
514
- var import_prompts5 = require("@clack/prompts");
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
- var import_promises5 = require("fs/promises");
529
- var import_node_path6 = __toESM(require("path"));
530
- var import_gray_matter = __toESM(require("gray-matter"));
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 import_node_path6.default.join(BETS_DIR, fileName);
502
+ return path6.join(BETS_DIR, fileName);
534
503
  }
535
504
  function getBetAbsolutePath(rootDir, idOrFileName) {
536
- return import_node_path6.default.join(rootDir, getBetRelativePath(idOrFileName));
505
+ return path6.join(rootDir, getBetRelativePath(idOrFileName));
537
506
  }
538
507
  async function pathExists2(filePath) {
539
508
  try {
540
- await (0, import_promises5.access)(filePath);
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 (0, import_promises5.readFile)(absolutePath, "utf8");
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 = (0, import_gray_matter.default)(markdown);
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 = import_node_path6.default.join(rootDir, BETS_DIR);
573
- const entries = await (0, import_promises5.readdir)(betsDir, { withFileTypes: true });
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 (0, import_promises5.writeFile)(absolutePath, import_gray_matter.default.stringify(bet.content, bet.data), "utf8");
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
- var import_prompts2 = require("@clack/prompts");
551
+ import { isCancel as isCancel2, text } from "@clack/prompts";
586
552
  function createClackCheckPromptClient() {
587
553
  return {
588
554
  async promptObservedValue() {
589
- return (0, import_prompts2.text)({
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 (0, import_prompts2.text)({
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 ((0, import_prompts2.isCancel)(observed)) {
574
+ if (isCancel2(observed)) {
609
575
  return { cancelled: true };
610
576
  }
611
577
  const notes = await client.promptNotes();
612
- if ((0, import_prompts2.isCancel)(notes)) {
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
- var import_node_buffer = require("buffer");
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 = import_node_buffer.Buffer.from(credsResult.value, "utf8").toString("base64");
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 = import_node_path7.default.join(rootDir, EVIDENCE_DIR, `${id}.json`);
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 (0, import_promises6.readFile)(evidencePath, "utf8"));
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 (0, import_prompts5.select)({
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 ((0, import_prompts5.isCancel)(value)) {
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 module2 = resolveProviderModule(leadingIndicatorType);
1342
- if (!module2) {
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 = module2.adapter.parseIndicator(rawLeadingIndicator);
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 module2.adapter.runCheck(parsedIndicator.value, {
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 = import_node_path7.default.join(EVIDENCE_DIR, `${id}.json`);
1380
- const absoluteEvidencePath = import_node_path7.default.join(rootDir, relativeEvidencePath);
981
+ const relativeEvidencePath = path7.join(EVIDENCE_DIR, `${id}.json`);
982
+ const absoluteEvidencePath = path7.join(rootDir, relativeEvidencePath);
1381
983
  try {
1382
- await (0, import_promises6.writeFile)(absoluteEvidencePath, `${JSON.stringify(snapshot, null, 2)}
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
- var import_promises7 = require("fs/promises");
1431
- var import_node_path8 = __toESM(require("path"));
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
- var import_gray_matter2 = __toESM(require("gray-matter"));
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. Rationale",
1456
- "",
1457
- input.rationale,
1458
- "",
1459
- "## 3. Validation Plan",
1058
+ "## 2. Validation Plan",
1460
1059
  "",
1461
1060
  input.validationPlan,
1462
1061
  "",
1463
- "## 4. Notes",
1062
+ "## 3. Notes",
1464
1063
  "",
1465
1064
  input.notes,
1466
1065
  ""
1467
1066
  ].join("\n");
1468
- return `${import_gray_matter2.default.stringify(body, frontmatter)}`;
1067
+ return `${matter2.stringify(body, frontmatter)}`;
1469
1068
  }
1470
1069
 
1471
- // src/ui/newWizard.ts
1472
- var import_prompts6 = require("@clack/prompts");
1473
- var BACK_VALUE3 = "__back__";
1474
- var DIM3 = "\x1B[2m";
1475
- var RESET3 = "\x1B[0m";
1476
- var STEP_ORDER = [
1477
- "cap_type",
1478
- "cap_value",
1479
- "leading_indicator_type",
1480
- "leading_indicator_setup",
1481
- "primary_assumption",
1482
- "rationale",
1483
- "validation_plan",
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
- if (result.kind === "back") {
1491
- return { kind: "back" };
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
- onValue(result.value);
1494
- return { kind: "next" };
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 finalizeWizardValues(values) {
1497
- if (!values.capType || typeof values.capValue !== "number" || !values.leadingIndicator || !values.primaryAssumption || !values.rationale || !values.validationPlan || values.notes === void 0) {
1498
- return null;
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
- maxHours,
1504
- maxCalendarDays,
1505
- leadingIndicator: values.leadingIndicator,
1506
- primaryAssumption: values.primaryAssumption,
1507
- rationale: values.rationale,
1508
- validationPlan: values.validationPlan,
1509
- notes: values.notes
1226
+ title: "Config",
1227
+ fields: [
1228
+ {
1229
+ label: "provider",
1230
+ value: "Select a provider",
1231
+ empty: true
1232
+ }
1233
+ ]
1510
1234
  };
1511
1235
  }
1512
- var STEP_HANDLERS = {
1513
- async cap_type({ client, values, stepIndex }) {
1514
- const result = await client.promptCapType({
1515
- initialValue: values.capType,
1516
- allowBack: stepIndex > 0
1517
- });
1518
- return applyPromptResult(result, (value) => {
1519
- const previousCapType = values.capType;
1520
- values.capType = value;
1521
- if (previousCapType !== value) {
1522
- values.capValue = void 0;
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
- async cap_value({ client, values, stepIndex }) {
1527
- if (!values.capType) {
1528
- return { kind: "cancel" };
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
- const result = await client.promptCapValue({
1531
- field: values.capType,
1532
- initialValue: values.capValue,
1533
- allowBack: stepIndex > 0
1534
- });
1535
- return applyPromptResult(result, (value) => {
1536
- values.capValue = value;
1537
- });
1538
- },
1539
- async leading_indicator_type({ client, values, stepIndex }) {
1540
- const result = await client.promptLeadingIndicatorType({
1541
- initialValue: values.leadingIndicatorType,
1542
- allowBack: stepIndex > 0
1543
- });
1544
- return applyPromptResult(result, (value) => {
1545
- if (values.leadingIndicatorType !== value) {
1546
- values.leadingIndicator = void 0;
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
- values.leadingIndicatorType = value;
1549
- });
1550
- },
1551
- async leading_indicator_setup({ client, values, stepIndex }) {
1552
- if (!values.leadingIndicatorType) {
1553
- return { kind: "cancel" };
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
- const module2 = resolveProviderModule(values.leadingIndicatorType);
1556
- if (!module2 || !module2.setup) {
1557
- return { kind: "cancel" };
1456
+ if (key.return) {
1457
+ actions.submitText();
1458
+ return;
1558
1459
  }
1559
- const setupResult = await module2.setup.collectNewWizardInput({
1560
- allowBack: stepIndex > 0,
1561
- initialValue: values.leadingIndicator && values.leadingIndicator.type === values.leadingIndicatorType ? values.leadingIndicator : void 0,
1562
- client
1563
- });
1564
- return applyPromptResult(setupResult, (value) => {
1565
- values.leadingIndicator = value;
1566
- });
1567
- },
1568
- async primary_assumption({ client, values, stepIndex }) {
1569
- const result = await client.promptPrimaryAssumption({
1570
- initialValue: values.primaryAssumption,
1571
- allowBack: stepIndex > 0
1572
- });
1573
- return applyPromptResult(result, (value) => {
1574
- values.primaryAssumption = value;
1575
- });
1576
- },
1577
- async rationale({ client, values, stepIndex }) {
1578
- const result = await client.promptRationale({
1579
- initialValue: values.rationale,
1580
- allowBack: stepIndex > 0
1581
- });
1582
- return applyPromptResult(result, (value) => {
1583
- values.rationale = value;
1584
- });
1585
- },
1586
- async validation_plan({ client, values, stepIndex }) {
1587
- const result = await client.promptValidationPlan({
1588
- initialValue: values.validationPlan,
1589
- allowBack: stepIndex > 0
1590
- });
1591
- return applyPromptResult(result, (value) => {
1592
- values.validationPlan = value;
1593
- });
1594
- },
1595
- async notes({ client, values, stepIndex }) {
1596
- const result = await client.promptNotes({
1597
- initialValue: values.notes,
1598
- allowBack: stepIndex > 0
1599
- });
1600
- return applyPromptResult(result, (value) => {
1601
- values.notes = value;
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
- async function runNewWizard(client = createClackPromptClient(), log = console.log) {
1606
- let stepIndex = 0;
1607
- const values = {};
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
- if (flow.kind === "back") {
1619
- stepIndex = Math.max(0, stepIndex - 1);
1620
- continue;
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
- stepIndex += 1;
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
- const finalizedValues = finalizeWizardValues(values);
1625
- if (!finalizedValues) {
1626
- return { cancelled: true };
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
- cancelled: false,
1630
- values: finalizedValues
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
- function createClackPromptClient() {
1634
- const mixpanelPromptClient = createClackMixpanelSetupPromptClient();
1635
- return {
1636
- async promptCapType({ initialValue, allowBack }) {
1637
- const options = [
1638
- { label: "Cap by hours", value: "max_hours" },
1639
- { label: "Cap by calendar days", value: "max_calendar_days" }
1640
- ];
1641
- if (allowBack) {
1642
- options.unshift({ label: "Back", value: BACK_VALUE3 });
1643
- }
1644
- const value = await (0, import_prompts6.select)({
1645
- message: "Choose your exposure cap type",
1646
- options,
1647
- initialValue
1648
- });
1649
- if ((0, import_prompts6.isCancel)(value)) {
1650
- return { kind: "cancel" };
1651
- }
1652
- if (value === BACK_VALUE3) {
1653
- return { kind: "back" };
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 { kind: "value", value };
1656
- },
1657
- async promptCapValue({ field, initialValue, allowBack }) {
1658
- const label = field === "max_hours" ? "Max hours" : "Max calendar days";
1659
- const backHint = allowBack ? ` ${DIM3}(type b to go back)${RESET3}` : "";
1660
- const value = await (0, import_prompts6.text)({
1661
- message: `${label} (required).${backHint}`,
1662
- initialValue: typeof initialValue === "number" ? String(initialValue) : void 0,
1663
- validate(rawValue) {
1664
- const trimmed2 = rawValue.trim();
1665
- if (allowBack && trimmed2.toLowerCase() === "b") {
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
- const trimmed = value.trim();
1681
- if (allowBack && trimmed.toLowerCase() === "b") {
1682
- return { kind: "back" };
1683
- }
1684
- return { kind: "value", value: Number(trimmed) };
1685
- },
1686
- async promptLeadingIndicatorType({ initialValue, allowBack }) {
1687
- const options = listRegisteredProviderTypes().map((type) => ({ label: type, value: type }));
1688
- if (allowBack) {
1689
- options.unshift({ label: "Back", value: BACK_VALUE3 });
1690
- }
1691
- const value = await (0, import_prompts6.select)({
1692
- message: "Leading indicator provider type",
1693
- options,
1694
- initialValue
1695
- });
1696
- if ((0, import_prompts6.isCancel)(value)) {
1697
- return { kind: "cancel" };
1698
- }
1699
- if (value === BACK_VALUE3) {
1700
- return { kind: "back" };
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
- return { kind: "value", value };
1703
- },
1704
- async promptManualOperator({ initialValue, allowBack }) {
1705
- const options = [
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
- if (allowBack) {
1713
- options.unshift({ label: "Back", value: BACK_VALUE3 });
1714
- }
1715
- const value = await (0, import_prompts6.select)({
1716
- message: "Leading indicator comparison operator",
1717
- options,
1718
- initialValue
1719
- });
1720
- if ((0, import_prompts6.isCancel)(value)) {
1721
- return { kind: "cancel" };
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
- return { kind: "value", value: trimmed };
1838
- },
1839
- async promptNotes({ initialValue, allowBack }) {
1840
- const backHint = allowBack ? ` ${DIM3}(type b to go back)${RESET3}` : "";
1841
- const value = await (0, import_prompts6.text)({
1842
- message: `Notes (optional).${backHint}`,
1843
- initialValue,
1844
- validate(rawValue) {
1845
- const trimmed2 = (rawValue || "").trim();
1846
- if (allowBack && trimmed2.toLowerCase() === "b") {
1847
- return;
1848
- }
1849
- return void 0;
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
- return { kind: "value", value: trimmed };
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
- // src/ui/newBetName.ts
1865
- var import_prompts7 = require("@clack/prompts");
1866
- function normalizeBetName(value) {
1867
- return value.trim().replace(/\s+/g, "_").toLowerCase();
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
- async function promptNewBetName() {
1870
- const response = await (0, import_prompts7.text)({
1871
- message: "Bet name",
1872
- placeholder: "Landing page iteration",
1873
- validate(input) {
1874
- return input.trim().length > 0 ? void 0 : "Bet name is required.";
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
- if ((0, import_prompts7.isCancel)(response)) {
1878
- return { cancelled: true };
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
- cancelled: false,
1882
- value: normalizeBetName(response)
1888
+ prompt,
1889
+ step,
1890
+ draft,
1891
+ uiState,
1892
+ actions: { goBack, submitSelect, submitText, cancel, setUiState }
1883
1893
  };
1884
1894
  }
1885
1895
 
1886
- // src/commands/new.ts
1887
- async function pathExists3(filePath) {
1888
- try {
1889
- await (0, import_promises7.access)(filePath);
1890
- return true;
1891
- } catch {
1892
- return false;
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
- let id = rawId ? normalizeBetName(rawId) : void 0;
1903
- if (!id) {
1904
- if (!isInteractiveTty3()) {
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 relativePath = import_node_path8.default.join(BETS_DIR, `${id}.md`);
1928
- const absolutePath = import_node_path8.default.join(rootDir, relativePath);
1929
- if (await pathExists3(absolutePath)) {
1930
- console.error(`Bet '${id}' already exists at ${relativePath}. Choose a unique id.`);
1931
- return 1;
1932
- }
1933
- const wizardResult = await runNewWizard();
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 (0, import_promises7.writeFile)(absolutePath, markdown, { encoding: "utf8", flag: "wx" });
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
- var import_promises8 = require("fs/promises");
1965
- var import_node_path9 = __toESM(require("path"));
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 = import_node_path9.default.join(rootDir, STATE_PATH);
1991
- const raw = await (0, import_promises8.readFile)(statePath, "utf8");
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 = import_node_path9.default.join(rootDir, STATE_PATH);
1996
- await (0, import_promises8.writeFile)(statePath, `${JSON.stringify(state, null, 2)}
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
- var import_promises9 = require("fs/promises");
2075
- var import_node_path10 = __toESM(require("path"));
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 = import_node_path10.default.join(rootDir, LOGS_DIR, `${id}.jsonl`);
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 (0, import_promises9.appendFile)(logPath, serializedLogs, "utf8");
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
- var import_promises10 = require("fs/promises");
2147
- var import_node_path11 = __toESM(require("path"));
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 = import_node_path11.default.join(LOGS_DIR, `${id}.jsonl`);
2222
- const absolutePath = import_node_path11.default.join(rootDir, relativePath);
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 (0, import_promises10.readFile)(absolutePath, "utf8");
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 = import_node_path11.default.join(EVIDENCE_DIR, `${id}.json`);
2254
- const absolutePath = import_node_path11.default.join(rootDir, relativePath);
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 (0, import_promises10.readFile)(absolutePath, "utf8"));
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 = import_node_path11.default.join(rootDir, BETS_DIR);
2344
+ const betDir = path11.join(rootDir, BETS_DIR);
2290
2345
  let dirEntries;
2291
2346
  try {
2292
- dirEntries = await (0, import_promises10.readdir)(betDir, { withFileTypes: true });
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
- var import_promises14 = require("fs/promises");
2371
- var import_node_path14 = __toESM(require("path"));
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
- var import_promises11 = require("fs/promises");
2450
- var import_node_path12 = __toESM(require("path"));
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
- rationale: extractSection(content, "2\\. Rationale"),
2482
- validationPlan: extractSection(content, "3\\. Validation Plan"),
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 = import_node_path12.default.join(rootDir, LOGS_DIR, "agent-attribution.jsonl");
2555
+ const filePath = path12.join(rootDir, LOGS_DIR, "agent-attribution.jsonl");
2493
2556
  let raw;
2494
2557
  try {
2495
- raw = await (0, import_promises11.readFile)(filePath, "utf8");
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
- var import_promises12 = require("fs/promises");
2644
- var import_node_path13 = __toESM(require("path"));
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 = import_node_path13.default.join(LOGS_DIR, `${betId}.jsonl`);
2667
- const absolutePath = import_node_path13.default.join(rootDir, relativePath);
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 (0, import_promises12.readFile)(absolutePath, "utf8");
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
- var import_node_child_process = require("child_process");
2806
- var import_promises13 = require("fs/promises");
2807
- var import_node_os = __toESM(require("os"));
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 (0, import_promises13.appendFile)(
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: import_node_os.default.tmpdir(),
2915
+ cwd: os.tmpdir(),
2853
2916
  timeoutMs
2854
2917
  });
2855
- const child = (0, import_node_child_process.spawn)(
2918
+ const child = spawn(
2856
2919
  "claude",
2857
2920
  ["--print", "--output-format", "json", "--model", "sonnet", "--setting-sources", ""],
2858
2921
  {
2859
- cwd: import_node_os.default.tmpdir(),
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 buildPrompt(context) {
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 = buildPrompt(context);
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: import_promises14.appendFile,
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 = import_node_path14.default.join(found.rootDir, LOGS_DIR, "hook_debug.log");
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 = import_node_path14.default.join(found.rootDir, LOGS_DIR, "agent-attribution.jsonl");
3282
- const sessionPath = import_node_path14.default.join(found.rootDir, LOGS_DIR, "agent-sessions.jsonl");
3283
- const blocksPath = import_node_path14.default.join(found.rootDir, LOGS_DIR, "agent-blocks.jsonl");
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 import_commander.Command();
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 (idParts) => {
3316
- const id = idParts && idParts.length > 0 ? ["new", ...idParts].join(" ") : void 0;
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
- if (require.main === module) {
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
- // Annotate the CommonJS export names for ESM import in node:
3346
- 0 && (module.exports = {
3415
+ export {
3347
3416
  main
3348
- });
3417
+ };
3349
3418
  //# sourceMappingURL=cli.js.map