@posthog/wizard 2.22.1 → 2.24.0

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.
Files changed (56) hide show
  1. package/README.md +61 -2
  2. package/dist/{AiOptInRequiredScreen-B8mgZbe5.js → AiOptInRequiredScreen-DPn1CCeD.js} +12 -577
  3. package/dist/AiOptInRequiredScreen-DPn1CCeD.js.map +1 -0
  4. package/dist/{add-mcp-server-to-clients-lCxOS1A1.js → add-mcp-server-to-clients-BU8Owthq.js} +4 -4
  5. package/dist/{add-mcp-server-to-clients-lCxOS1A1.js.map → add-mcp-server-to-clients-BU8Owthq.js.map} +1 -1
  6. package/dist/{agent-interface-BPCzPvK-.js → agent-interface-CysYcZl5.js} +6 -7
  7. package/dist/agent-interface-CysYcZl5.js.map +1 -0
  8. package/dist/{agent-runner-DgzF2mga.js → agent-runner-Br0OxBxd.js} +8 -8
  9. package/dist/{agent-runner-DgzF2mga.js.map → agent-runner-Br0OxBxd.js.map} +1 -1
  10. package/dist/{analytics-BZv-cKyn.js → analytics-BOWrR4qd.js} +22 -2
  11. package/dist/analytics-BOWrR4qd.js.map +1 -0
  12. package/dist/{api-BkLZ8BWm.js → api-RXTR8yZo.js} +3 -3
  13. package/dist/{api-BkLZ8BWm.js.map → api-RXTR8yZo.js.map} +1 -1
  14. package/dist/bin.js +875 -423
  15. package/dist/bin.js.map +1 -1
  16. package/dist/{ci-install-DBARq5LX.js → ci-install-BscZ60Ec.js} +5 -5
  17. package/dist/{ci-install-DBARq5LX.js.map → ci-install-BscZ60Ec.js.map} +1 -1
  18. package/dist/{debug-Ckumcrye.js → debug-BUdVZP84.js} +2 -2
  19. package/dist/{debug-Ckumcrye.js.map → debug-BUdVZP84.js.map} +1 -1
  20. package/dist/{debug-DLsuyfln.js → debug-BgH07S-e.js} +1 -1
  21. package/dist/{environment-DUh_8hqW.js → environment-G0Hv6_JI.js} +3 -3
  22. package/dist/{environment-DUh_8hqW.js.map → environment-G0Hv6_JI.js.map} +1 -1
  23. package/dist/{interactive-DeiHgviS.js → interactive-fh2iyewb.js} +3 -3
  24. package/dist/{interactive-DeiHgviS.js.map → interactive-fh2iyewb.js.map} +1 -1
  25. package/dist/{mcp-prompt-streaming-kEJgmB30.js → mcp-prompt-streaming-DUtbxnNy.js} +4 -4
  26. package/dist/{mcp-prompt-streaming-kEJgmB30.js.map → mcp-prompt-streaming-DUtbxnNy.js.map} +1 -1
  27. package/dist/{non-interactive-I4ifOSau.js → non-interactive-BfqXlY8R.js} +2 -2
  28. package/dist/{non-interactive-I4ifOSau.js.map → non-interactive-BfqXlY8R.js.map} +1 -1
  29. package/dist/{package-manager-fUeLORHg.js → package-manager-Ca1maxU-.js} +2 -2
  30. package/dist/{package-manager-fUeLORHg.js.map → package-manager-Ca1maxU-.js.map} +1 -1
  31. package/dist/{playground-DLLIz4Ql.js → playground-4sqLVVJL.js} +5 -10
  32. package/dist/{playground-DLLIz4Ql.js.map → playground-4sqLVVJL.js.map} +1 -1
  33. package/dist/{posthog-integration-COcPewVt.js → posthog-integration-Bz3HWkHn.js} +11 -11
  34. package/dist/{posthog-integration-COcPewVt.js.map → posthog-integration-Bz3HWkHn.js.map} +1 -1
  35. package/dist/{provisioning-7xU12_S9.js → provisioning-CgwvlsIl.js} +3 -3
  36. package/dist/{provisioning-7xU12_S9.js.map → provisioning-CgwvlsIl.js.map} +1 -1
  37. package/dist/{registry-YwaF-aD8.js → registry-CEnQVctL.js} +4 -4
  38. package/dist/{registry-YwaF-aD8.js.map → registry-CEnQVctL.js.map} +1 -1
  39. package/dist/{setup-utils-Cr4FxJDl.js → setup-utils-Doh69vo4.js} +8 -8
  40. package/dist/{setup-utils-Cr4FxJDl.js.map → setup-utils-Doh69vo4.js.map} +1 -1
  41. package/dist/{start-tui--E4PXdwG.js → start-tui-CywbSvZE.js} +54 -1030
  42. package/dist/start-tui-CywbSvZE.js.map +1 -0
  43. package/dist/{steps-CxUxdK4V.js → steps-DJojDYQ-.js} +6 -6
  44. package/dist/{steps-CxUxdK4V.js.map → steps-DJojDYQ-.js.map} +1 -1
  45. package/dist/{telemetry-BS7yw3TP.js → telemetry-8zMpaIuK.js} +3 -3
  46. package/dist/{telemetry-BS7yw3TP.js.map → telemetry-8zMpaIuK.js.map} +1 -1
  47. package/dist/{urls-BW23_XbC.js → urls-BUfvQmU4.js} +2 -2
  48. package/dist/{urls-BW23_XbC.js.map → urls-BUfvQmU4.js.map} +1 -1
  49. package/dist/{wizard-abort-E66_R4S7.js → wizard-abort-BdGW4Tvi.js} +3 -3
  50. package/dist/{wizard-abort-E66_R4S7.js.map → wizard-abort-BdGW4Tvi.js.map} +1 -1
  51. package/dist/{wizard-abort-BPsnXKY5.js → wizard-abort-Ni-mKJ58.js} +1 -1
  52. package/package.json +2 -2
  53. package/dist/AiOptInRequiredScreen-B8mgZbe5.js.map +0 -1
  54. package/dist/agent-interface-BPCzPvK-.js.map +0 -1
  55. package/dist/analytics-BZv-cKyn.js.map +0 -1
  56. package/dist/start-tui--E4PXdwG.js.map +0 -1
package/dist/bin.js CHANGED
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { $ as VERSION, P as POSTHOG_DOCS_URL, Y as WIZARD_USER_AGENT, _ as SIGNUP_WIZARD_READINESS_CONFIG, a as getLogFilePath, h as LoggingUI, m as setUI, p as getUI, s as logToFile, v as evaluateWizardReadiness, y as getBlockingServiceKeys } from "./debug-Ckumcrye.js";
3
- import { t as analytics } from "./analytics-BZv-cKyn.js";
4
- import { r as setEntryCommand } from "./telemetry-BS7yw3TP.js";
5
- import { n as isUsingTypeScript, t as getOrAskForProjectData } from "./setup-utils-Cr4FxJDl.js";
6
- import { a as getUiHostFromHost, n as getCloudUrlFromRegion } from "./urls-BW23_XbC.js";
7
- import { o as handleApiError } from "./api-BkLZ8BWm.js";
2
+ import { $ as VERSION, P as POSTHOG_DOCS_URL, Q as getSkillsBaseUrl, Y as WIZARD_USER_AGENT, _ as SIGNUP_WIZARD_READINESS_CONFIG, a as getLogFilePath, h as LoggingUI, m as setUI, p as getUI, s as logToFile, v as evaluateWizardReadiness, y as getBlockingServiceKeys } from "./debug-BUdVZP84.js";
3
+ import { t as analytics } from "./analytics-BOWrR4qd.js";
4
+ import { r as setEntryCommand } from "./telemetry-8zMpaIuK.js";
5
+ import { n as isUsingTypeScript, t as getOrAskForProjectData } from "./setup-utils-Doh69vo4.js";
6
+ import { a as getUiHostFromHost, n as getCloudUrlFromRegion } from "./urls-BUfvQmU4.js";
7
+ import { o as handleApiError } from "./api-RXTR8yZo.js";
8
8
  import "./wizard-session-G3VWD6hv.js";
9
- import { r as runCleanups } from "./wizard-abort-E66_R4S7.js";
10
- import { n as isNonInteractiveEnvironment } from "./environment-DUh_8hqW.js";
11
- import { _ as AUDIT_CHECKS_KEY, f as WIZARD_TOOL_NAMES, g as AUDIT_CHECKS_FILE, l as AgentSignals, s as recoverOrphanedSettingsBackups, v as AUDIT_REPORT_FILE } from "./agent-interface-BPCzPvK-.js";
12
- import { i as SPINNER_MESSAGE } from "./registry-YwaF-aD8.js";
13
- import { a as PRODUCT_SUITE_BLOCK, f as Colors, i as LINE_CHART_BLOCK, l as isClearBlock, m as HEALTH_CHECK_STEP, n as posthogIntegrationConfig, o as StatusPeekTrigger, r as FUNNEL_BLOCK } from "./posthog-integration-COcPewVt.js";
9
+ import { r as runCleanups } from "./wizard-abort-BdGW4Tvi.js";
10
+ import { n as isNonInteractiveEnvironment } from "./environment-G0Hv6_JI.js";
11
+ import { _ as AUDIT_CHECKS_KEY, f as WIZARD_TOOL_NAMES, g as AUDIT_CHECKS_FILE, l as AgentSignals, m as fetchSkillMenu, s as recoverOrphanedSettingsBackups, v as AUDIT_REPORT_FILE } from "./agent-interface-CysYcZl5.js";
12
+ import { i as SPINNER_MESSAGE } from "./registry-CEnQVctL.js";
13
+ import { a as PRODUCT_SUITE_BLOCK, f as Colors, i as LINE_CHART_BLOCK, l as isClearBlock, m as HEALTH_CHECK_STEP, n as posthogIntegrationConfig, o as StatusPeekTrigger, p as Icons, r as FUNNEL_BLOCK } from "./posthog-integration-Bz3HWkHn.js";
14
14
  import { t as IGNORED_DIRS } from "./file-utils-VAXoyXVA.js";
15
15
  import { n as readApiKeyFromEnv } from "./env-api-key-MlzJYAvt.js";
16
16
  import { satisfies } from "semver";
@@ -20,7 +20,8 @@ import fs, { existsSync, readFileSync, readdirSync, statSync } from "fs";
20
20
  import path, { join, relative } from "path";
21
21
  import axios from "axios";
22
22
  import { z } from "zod";
23
- import { Text } from "ink";
23
+ import { Box, Text, render, useInput } from "ink";
24
+ import { createContext, createElement, useCallback, useContext, useEffect, useRef, useState } from "react";
24
25
  import { jsx, jsxs } from "react/jsx-runtime";
25
26
  //#region src/commands/command.ts
26
27
  /** Extract the bare command word(s) from a yargs name spec, dropping positionals and aliases' arg syntax. */
@@ -34,16 +35,17 @@ function toCommandModule(cmd, parentPath) {
34
35
  describe: cmd.description,
35
36
  builder: (y) => {
36
37
  let next = cmd.options ? y.options(cmd.options) : y;
38
+ for (const [key, opts] of Object.entries(cmd.positionals ?? {})) next = next.positional(key, opts);
37
39
  if (cmd.check) next = next.check(cmd.check);
38
40
  for (const [usage, description] of cmd.examples ?? []) next = next.example(usage, description);
39
41
  const ownPath = [...parentPath, commandKeys(cmd.name)[0]];
40
42
  for (const child of cmd.children ?? []) next = next.command(toCommandModule(child, ownPath));
41
- if (cmd.children?.length && !cmd.handler) next = next.demandCommand(1);
43
+ if (cmd.children?.length && !cmd.handler && !cmd.interactiveDefault) next = next.demandCommand(1);
42
44
  return next;
43
45
  },
44
46
  handler: (argv) => {
45
47
  if (entryCommand) setEntryCommand(entryCommand);
46
- cmd.handler?.(argv);
48
+ (cmd.handler ?? cmd.interactiveDefault ?? (() => void 0))(argv);
47
49
  }
48
50
  };
49
51
  }
@@ -52,6 +54,10 @@ function toCommandModule(cmd, parentPath) {
52
54
  /**
53
55
  * Global yargs options applied to every command. These are read from the
54
56
  * `POSTHOG_WIZARD` env prefix as well as flags.
57
+ *
58
+ * Options with `hidden: true` are "internal modes" — they don't show up in
59
+ * `--help` but are still accepted on every command. The catalog of internal
60
+ * flags and what each one does lives in CONTRIBUTING.md.
55
61
  */
56
62
  const GLOBAL_OPTIONS = {
57
63
  debug: {
@@ -69,11 +75,6 @@ const GLOBAL_OPTIONS = {
69
75
  describe: "Create a new PostHog account during setup\nenv: POSTHOG_WIZARD_SIGNUP",
70
76
  type: "boolean"
71
77
  },
72
- "local-mcp": {
73
- default: false,
74
- describe: "Use local MCP server at http://localhost:8787/mcp\nenv: POSTHOG_WIZARD_LOCAL_MCP",
75
- type: "boolean"
76
- },
77
78
  telemetry: {
78
79
  default: true,
79
80
  describe: "Send wizard run state to PostHog (pass --no-telemetry to disable)\nenv: POSTHOG_WIZARD_TELEMETRY",
@@ -90,16 +91,33 @@ const GLOBAL_OPTIONS = {
90
91
  email: {
91
92
  describe: "Email address for signup (used with --signup)\nenv: POSTHOG_WIZARD_EMAIL",
92
93
  type: "string"
94
+ },
95
+ "local-mcp": {
96
+ default: false,
97
+ describe: "Use local MCP server at http://localhost:8787/mcp\nenv: POSTHOG_WIZARD_LOCAL_MCP",
98
+ type: "boolean",
99
+ hidden: true
100
+ },
101
+ benchmark: {
102
+ default: false,
103
+ describe: "Run in benchmark mode with per-phase token tracking\nenv: POSTHOG_WIZARD_BENCHMARK",
104
+ type: "boolean",
105
+ hidden: true
106
+ },
107
+ "yara-report": {
108
+ default: false,
109
+ describe: "Print YARA scanner summary after the agent run\nenv: POSTHOG_WIZARD_YARA_REPORT",
110
+ type: "boolean",
111
+ hidden: true
93
112
  }
94
113
  };
95
114
  var Wizard = class Wizard {
96
115
  cli;
97
116
  constructor() {
98
117
  let cli = yargs(hideBin(process.argv)).env("POSTHOG_WIZARD").options(GLOBAL_OPTIONS);
99
- this.cli = cli.strictOptions().fail((msg, err, parser) => {
118
+ this.cli = cli.strictOptions().strictCommands().fail((msg, err) => {
100
119
  const text = msg || err && err.message || "Invalid arguments";
101
- process.stderr.write(`\n\x1b[1;91m✖ ${text}\x1b[0m\n\n`);
102
- parser.showHelp();
120
+ process.stderr.write(`\n\x1b[1;91m✖ ${text}\x1b[0m\n Run \`wizard --help\` to see available commands and options.\n\n`);
103
121
  process.exit(1);
104
122
  }).help().alias("help", "h").version().alias("version", "v");
105
123
  }
@@ -166,7 +184,7 @@ function runProvision(argv) {
166
184
  }
167
185
  async function provision({ email, region, name, jsonMode }) {
168
186
  try {
169
- const { provisionNewAccount } = await import("./provisioning-7xU12_S9.js").then((n) => n.n);
187
+ const { provisionNewAccount } = await import("./provisioning-CgwvlsIl.js").then((n) => n.n);
170
188
  if (!jsonMode) getUI().log.info(`Provisioning account for ${email} in ${region}...`);
171
189
  emitResult(await provisionNewAccount(email, name, region), jsonMode);
172
190
  process.exit(0);
@@ -212,25 +230,15 @@ const basicIntegrationCommand = {
212
230
  describe: "Directory to install PostHog in\nenv: POSTHOG_WIZARD_INSTALL_DIR",
213
231
  type: "string"
214
232
  },
233
+ name: {
234
+ describe: "Name for account creation with --ci --signup\nenv: POSTHOG_WIZARD_NAME",
235
+ type: "string"
236
+ },
215
237
  playground: {
216
238
  default: false,
217
239
  describe: "Launch the TUI primitives playground",
218
- type: "boolean"
219
- },
220
- benchmark: {
221
- default: false,
222
- describe: "Run in benchmark mode with per-phase token tracking\nenv: POSTHOG_WIZARD_BENCHMARK",
223
- type: "boolean"
224
- },
225
- "yara-report": {
226
- default: false,
227
- describe: "Print YARA scanner summary after the agent run\nenv: POSTHOG_WIZARD_YARA_REPORT",
228
240
  type: "boolean",
229
241
  hidden: true
230
- },
231
- name: {
232
- describe: "Name for account creation with --ci --signup\nenv: POSTHOG_WIZARD_NAME",
233
- type: "string"
234
242
  }
235
243
  },
236
244
  check: (argv) => {
@@ -241,18 +249,18 @@ const basicIntegrationCommand = {
241
249
  setEntryCommand("integrate");
242
250
  (async () => {
243
251
  if (argv.ci) {
244
- const { runCIInstall } = await import("./ci-install-DBARq5LX.js");
252
+ const { runCIInstall } = await import("./ci-install-BscZ60Ec.js");
245
253
  return runCIInstall(argv);
246
254
  }
247
255
  if (isNonInteractiveEnvironment()) {
248
- const { failNonInteractive } = await import("./non-interactive-I4ifOSau.js");
256
+ const { failNonInteractive } = await import("./non-interactive-BfqXlY8R.js");
249
257
  return failNonInteractive();
250
258
  }
251
259
  if (argv.playground) {
252
- const { runPlayground } = await import("./playground-DLLIz4Ql.js");
260
+ const { runPlayground } = await import("./playground-4sqLVVJL.js");
253
261
  return runPlayground();
254
262
  }
255
- const { runInteractive } = await import("./interactive-DeiHgviS.js");
263
+ const { runInteractive } = await import("./interactive-fh2iyewb.js");
256
264
  runInteractive(argv);
257
265
  })();
258
266
  }
@@ -473,9 +481,10 @@ const getContentBlocks$2 = (store) => {
473
481
  //#endregion
474
482
  //#region src/lib/programs/revenue-analytics/index.ts
475
483
  const revenueAnalyticsConfig = {
476
- command: "revenue",
484
+ command: "revenue-analytics",
477
485
  description: "Set up PostHog revenue analytics (e.g. Stripe integration)",
478
486
  id: "revenue-analytics-setup",
487
+ skillId: "revenue-analytics-setup",
479
488
  steps: REVENUE_ANALYTICS_PROGRAM,
480
489
  getContentBlocks: getContentBlocks$2,
481
490
  allowedTools: ["Agent"],
@@ -678,7 +687,7 @@ const withAuditScreens = (steps) => steps.map((step) => {
678
687
  } : step;
679
688
  });
680
689
  const auditSteps = withAuditScreens(AGENT_SKILL_STEPS);
681
- const baseConfig$1 = createSkillProgram({
690
+ const baseConfig = createSkillProgram({
682
691
  skillId: "audit",
683
692
  command: "audit",
684
693
  id: "audit",
@@ -695,8 +704,8 @@ const baseConfig$1 = createSkillProgram({
695
704
  });
696
705
  const auditRun = async (session) => {
697
706
  seedBeforeAuditRun(session);
698
- if (!baseConfig$1.run) throw new Error("Audit program has no run configuration.");
699
- const baseRun = typeof baseConfig$1.run === "function" ? await baseConfig$1.run(session) : baseConfig$1.run;
707
+ if (!baseConfig.run) throw new Error("Audit program has no run configuration.");
708
+ const baseRun = typeof baseConfig.run === "function" ? await baseConfig.run(session) : baseConfig.run;
700
709
  return {
701
710
  ...baseRun,
702
711
  buildOutroData: (sess, _credentials, cloudRegion) => {
@@ -715,7 +724,7 @@ const auditRun = async (session) => {
715
724
  };
716
725
  };
717
726
  const auditConfig = {
718
- ...baseConfig$1,
727
+ ...baseConfig,
719
728
  steps: auditSteps,
720
729
  run: auditRun,
721
730
  allowedTools: ["Agent"],
@@ -882,207 +891,6 @@ Project context:
882
891
  }
883
892
  };
884
893
  //#endregion
885
- //#region src/lib/programs/audit-3000/index.ts
886
- const AUDIT3000_REPORT_FILE = "posthog-audit-3000-report.md";
887
- const AUDIT3000_EXTRA_CHECKS = [
888
- {
889
- id: "event-naming-standardization",
890
- area: "Event Quality",
891
- label: "Event naming convention is consistent",
892
- status: "pending"
893
- },
894
- {
895
- id: "event-duplicates-and-bloat",
896
- area: "Event Quality",
897
- label: "No duplicate or bloated event capture",
898
- status: "pending"
899
- },
900
- {
901
- id: "event-quality-context-review",
902
- area: "Event Quality",
903
- label: "Event property context reviewed",
904
- status: "pending"
905
- },
906
- {
907
- id: "event-usage-coverage",
908
- area: "Event Quality",
909
- label: "Captured events match insights / dashboards usage",
910
- status: "pending"
911
- },
912
- {
913
- id: "stale-feature-flags-reviewed",
914
- area: "Feature Flags",
915
- label: "Stale feature flags reviewed",
916
- status: "pending"
917
- },
918
- {
919
- id: "replay-minimum-duration-set",
920
- area: "Session Replay",
921
- label: "Minimum duration set on init",
922
- status: "pending"
923
- },
924
- {
925
- id: "replay-mask-config",
926
- area: "Session Replay",
927
- label: "Mask config covers sensitive surfaces",
928
- status: "pending"
929
- },
930
- {
931
- id: "replay-disabled-in-test-envs",
932
- area: "Session Replay",
933
- label: "Disabled in test / CI environments",
934
- status: "pending"
935
- },
936
- {
937
- id: "replay-strict-minimum-duration",
938
- area: "Session Replay",
939
- label: "Strict minimum duration enforced",
940
- status: "pending"
941
- },
942
- {
943
- id: "replay-sampling-rate",
944
- area: "Session Replay — Optimize",
945
- label: "Sampling rate tuned for cost",
946
- status: "pending"
947
- },
948
- {
949
- id: "replay-triggers-configured",
950
- area: "Session Replay — Optimize",
951
- label: "Triggers configured (event / URL / flag)",
952
- status: "pending"
953
- },
954
- {
955
- id: "replay-network-recording-filtered",
956
- area: "Session Replay — Optimize",
957
- label: "Network recording filtered",
958
- status: "pending"
959
- },
960
- {
961
- id: "replay-mobile-sampling",
962
- area: "Session Replay — Optimize",
963
- label: "Mobile sampling configured",
964
- status: "pending"
965
- },
966
- {
967
- id: "expansion-product-analytics",
968
- area: "Use Case: Expansion",
969
- label: "Product analytics coverage",
970
- status: "pending"
971
- },
972
- {
973
- id: "expansion-error-tracking",
974
- area: "Use Case: Expansion",
975
- label: "Error tracking coverage",
976
- status: "pending"
977
- },
978
- {
979
- id: "expansion-llm-observability",
980
- area: "Use Case: Expansion",
981
- label: "LLM observability coverage",
982
- status: "pending"
983
- },
984
- {
985
- id: "expansion-session-replay",
986
- area: "Use Case: Expansion",
987
- label: "Session replay coverage",
988
- status: "pending"
989
- },
990
- {
991
- id: "expansion-feature-flags",
992
- area: "Use Case: Expansion",
993
- label: "Feature flags coverage",
994
- status: "pending"
995
- },
996
- {
997
- id: "expansion-surveys",
998
- area: "Use Case: Expansion",
999
- label: "Surveys coverage",
1000
- status: "pending"
1001
- },
1002
- {
1003
- id: "expansion-logs",
1004
- area: "Use Case: Expansion",
1005
- label: "Logs coverage",
1006
- status: "pending"
1007
- },
1008
- {
1009
- id: "expansion-web-analytics",
1010
- area: "Use Case: Expansion",
1011
- label: "Web analytics coverage",
1012
- status: "pending"
1013
- },
1014
- {
1015
- id: "customer-enrichment",
1016
- area: "Additional Sections",
1017
- label: "Customer enrichment (Harmonic + PDL)",
1018
- status: "pending"
1019
- },
1020
- {
1021
- id: "use-case-match",
1022
- area: "Additional Sections",
1023
- label: "Use-case match",
1024
- status: "pending"
1025
- },
1026
- {
1027
- id: "final-report",
1028
- area: "Additional Sections",
1029
- label: "Final audit report written",
1030
- status: "pending"
1031
- }
1032
- ];
1033
- const AUDIT3000_SEED_CHECKS = [...AUDIT_SEED_CHECKS, ...AUDIT3000_EXTRA_CHECKS];
1034
- const AUDIT3000_SCREEN_BY_STEP = {
1035
- intro: "audit-3000-intro",
1036
- run: "audit-3000-run",
1037
- outro: "audit-3000-outro"
1038
- };
1039
- const seedAudit3000Ledger = (installDir) => {
1040
- const target = path.join(installDir, AUDIT_CHECKS_FILE);
1041
- const tmp = `${target}.tmp`;
1042
- fs.writeFileSync(tmp, JSON.stringify(AUDIT3000_SEED_CHECKS, null, 2), "utf8");
1043
- fs.renameSync(tmp, target);
1044
- logToFile(`seedAudit3000Ledger: wrote ${AUDIT3000_SEED_CHECKS.length} entries to ${target}`);
1045
- };
1046
- const seedBeforeAudit3000Run = (session) => {
1047
- seedAudit3000Ledger(session.installDir);
1048
- session.frameworkContext[AUDIT_CHECKS_KEY] = AUDIT3000_SEED_CHECKS;
1049
- };
1050
- const withAudit3000Screens = (steps) => steps.map((step) => {
1051
- const override = AUDIT3000_SCREEN_BY_STEP[step.id];
1052
- return override ? {
1053
- ...step,
1054
- screenId: override
1055
- } : step;
1056
- });
1057
- const audit3000Steps = withAudit3000Screens(AGENT_SKILL_STEPS);
1058
- const baseConfig = createSkillProgram({
1059
- skillId: "audit-3000",
1060
- command: "audit-3000",
1061
- id: "audit-3000",
1062
- description: "Audit an existing PostHog integration (v3000 — adds event quality, stale-flag hygiene, customer enrichment, use-case match)",
1063
- integrationLabel: "audit-3000",
1064
- customPrompt: "Run the audit-3000 skill end-to-end. Follow the step chain starting at references/1-version.md. Do not modify any project files — only create the final audit report and (when enrichment is enabled) the enrichment report.",
1065
- successMessage: `Audit complete! View the report at ./${AUDIT3000_REPORT_FILE}`,
1066
- reportFile: AUDIT3000_REPORT_FILE,
1067
- docsUrl: "https://posthog.com/docs/product-analytics/best-practices",
1068
- spinnerMessage: "Running PostHog Audit 3000...",
1069
- estimatedDurationMinutes: 6,
1070
- requires: ["posthog-integration"],
1071
- abortCases: AUDIT_ABORT_CASES
1072
- });
1073
- const audit3000Run = async (session) => {
1074
- seedBeforeAudit3000Run(session);
1075
- if (!baseConfig.run) throw new Error("audit-3000 program has no run configuration.");
1076
- return typeof baseConfig.run === "function" ? baseConfig.run(session) : baseConfig.run;
1077
- };
1078
- const audit3000Config = {
1079
- ...baseConfig,
1080
- steps: audit3000Steps,
1081
- run: audit3000Run,
1082
- allowedTools: ["Agent"],
1083
- disallowedTools: [WIZARD_TOOL_NAMES.wizardAsk]
1084
- };
1085
- //#endregion
1086
894
  //#region src/lib/programs/posthog-doctor/steps.ts
1087
895
  const POSTHOG_DOCTOR_PROGRAM = [
1088
896
  {
@@ -1845,32 +1653,19 @@ const MIGRATION_ABORT_CASES = [{
1845
1653
  message: "No source-SDK calls found",
1846
1654
  body: "The migration needs an existing third-party SDK to migrate from. No calls to the source SDK appear anywhere in this project. If you haven't installed PostHog yet, you don't need this command — run `npx @posthog/wizard@latest` to add PostHog from scratch."
1847
1655
  }];
1848
- /**
1849
- * Map each `--product=<id>` choice to the context-mill skill ID that handles
1850
- * it. Adding a variant: drop a new row here. The CLI `choices` and the
1851
- * runtime lookup both read from this map, so the two stay in sync.
1852
- */
1853
- const PRODUCT_TO_SKILL_ID = { statsig: "migrate-statsig" };
1854
- const MIGRATE_PRODUCTS = Object.keys(PRODUCT_TO_SKILL_ID);
1656
+ const DEFAULT_MIGRATE_SKILL_ID = "migrate-statsig";
1855
1657
  const migrationConfig = {
1856
1658
  command: "migrate",
1857
1659
  description: "Migrate to PostHog from another analytics provider",
1858
1660
  id: "migration",
1859
- skillId: PRODUCT_TO_SKILL_ID.statsig,
1661
+ skillId: DEFAULT_MIGRATE_SKILL_ID,
1860
1662
  steps: MIGRATION_PROGRAM,
1861
1663
  reportFile: MIGRATION_REPORT_FILE,
1862
1664
  getContentBlocks: getContentBlocks$1,
1863
1665
  allowedTools: ["Agent"],
1864
1666
  disallowedTools: [WIZARD_TOOL_NAMES.wizardAsk],
1865
- cliOptions: { product: {
1866
- describe: "Source SDK to migrate from",
1867
- type: "string",
1868
- choices: MIGRATE_PRODUCTS,
1869
- demandOption: true
1870
- } },
1871
- mapCliOptions: (argv) => ({ skillId: PRODUCT_TO_SKILL_ID[argv.product] }),
1872
1667
  run: {
1873
- skillId: PRODUCT_TO_SKILL_ID.statsig,
1668
+ skillId: DEFAULT_MIGRATE_SKILL_ID,
1874
1669
  integrationLabel: "migration",
1875
1670
  customPrompt: () => `Migrate this project from its existing third-party analytics, feature-flag, and observability tools to PostHog. Run the \`migrate\` skill end-to-end: follow the step chain starting at references/1-presence.md. Only replace existing source-SDK call sites with PostHog equivalents — make zero unrelated changes and no net-new instrumentation. The final report is written to ./${MIGRATION_REPORT_FILE}.`,
1876
1671
  successMessage: `Migration complete! View the report at ./${MIGRATION_REPORT_FILE}`,
@@ -2796,7 +2591,19 @@ const agentSkillConfig = {
2796
2591
  description: "Run an arbitrary context-mill skill",
2797
2592
  steps: AGENT_SKILL_STEPS,
2798
2593
  getContentBlocks: getContentBlocks$2,
2799
- allowedTools: ["Agent"]
2594
+ allowedTools: ["Agent"],
2595
+ run: (session) => {
2596
+ const skillId = session.skillId ?? "agent-skill";
2597
+ return Promise.resolve({
2598
+ skillId,
2599
+ integrationLabel: skillId,
2600
+ spinnerMessage: `Running ${skillId}...`,
2601
+ successMessage: `${skillId} complete!`,
2602
+ estimatedDurationMinutes: 5,
2603
+ reportFile: `posthog-${skillId}-report.md`,
2604
+ docsUrl: POSTHOG_DOCS_URL
2605
+ });
2606
+ }
2800
2607
  };
2801
2608
  const PROGRAM_REGISTRY = [
2802
2609
  posthogIntegrationConfig,
@@ -2804,7 +2611,6 @@ const PROGRAM_REGISTRY = [
2804
2611
  errorTrackingUploadSourceMapsConfig,
2805
2612
  auditConfig,
2806
2613
  eventsAuditConfig,
2807
- audit3000Config,
2808
2614
  posthogDoctorConfig,
2809
2615
  webAnalyticsDoctorConfig,
2810
2616
  migrationConfig,
@@ -2826,7 +2632,6 @@ const Program = {
2826
2632
  Migration: migrationConfig.id,
2827
2633
  Audit: auditConfig.id,
2828
2634
  EventsAudit: eventsAuditConfig.id,
2829
- Audit3000: audit3000Config.id,
2830
2635
  PosthogDoctor: posthogDoctorConfig.id,
2831
2636
  WebAnalyticsDoctor: webAnalyticsDoctorConfig.id,
2832
2637
  AgentSkill: agentSkillConfig.id,
@@ -2873,7 +2678,7 @@ function runMcpAdd(argv) {
2873
2678
  const debug = argv.debug;
2874
2679
  const localMcp = argv.local;
2875
2680
  try {
2876
- const { startTUI } = await import("./start-tui--E4PXdwG.js");
2681
+ const { startTUI } = await import("./start-tui-CywbSvZE.js");
2877
2682
  const { buildSession } = await import("./wizard-session-wPJtNl4c.js");
2878
2683
  const tui = startTUI(VERSION, Program.McpAdd);
2879
2684
  tui.store.session = buildSession({
@@ -2885,7 +2690,7 @@ function runMcpAdd(argv) {
2885
2690
  } catch (error) {
2886
2691
  if (!isTUIUnavailable(error)) throw error;
2887
2692
  setUI(new LoggingUI());
2888
- const { addMCPServerToClientsStep } = await import("./add-mcp-server-to-clients-lCxOS1A1.js").then((n) => n.r);
2693
+ const { addMCPServerToClientsStep } = await import("./add-mcp-server-to-clients-BU8Owthq.js").then((n) => n.r);
2889
2694
  await addMCPServerToClientsStep({
2890
2695
  local: localMcp,
2891
2696
  features,
@@ -2924,7 +2729,7 @@ function runMcpRemove(argv) {
2924
2729
  const debug = argv.debug;
2925
2730
  const localMcp = argv.local;
2926
2731
  try {
2927
- const { startTUI } = await import("./start-tui--E4PXdwG.js");
2732
+ const { startTUI } = await import("./start-tui-CywbSvZE.js");
2928
2733
  const { buildSession } = await import("./wizard-session-wPJtNl4c.js");
2929
2734
  const tui = startTUI(VERSION, Program.McpRemove);
2930
2735
  tui.store.session = buildSession({
@@ -2933,7 +2738,7 @@ function runMcpRemove(argv) {
2933
2738
  });
2934
2739
  } catch {
2935
2740
  setUI(new LoggingUI());
2936
- const { removeMCPServerFromClientsStep } = await import("./add-mcp-server-to-clients-lCxOS1A1.js").then((n) => n.r);
2741
+ const { removeMCPServerFromClientsStep } = await import("./add-mcp-server-to-clients-BU8Owthq.js").then((n) => n.r);
2937
2742
  await removeMCPServerFromClientsStep({ local: localMcp });
2938
2743
  }
2939
2744
  })();
@@ -2955,7 +2760,7 @@ function runMcpTutorial(argv) {
2955
2760
  const debug = argv.debug;
2956
2761
  const localMcp = argv.local;
2957
2762
  try {
2958
- const { startTUI } = await import("./start-tui--E4PXdwG.js");
2763
+ const { startTUI } = await import("./start-tui-CywbSvZE.js");
2959
2764
  const { buildSession } = await import("./wizard-session-wPJtNl4c.js");
2960
2765
  const tui = startTUI(VERSION, Program.McpTutorial);
2961
2766
  tui.store.session = buildSession({
@@ -3010,7 +2815,7 @@ function runWizard(config, options) {
3010
2815
  (async () => {
3011
2816
  try {
3012
2817
  const installDir = options.installDir || process.cwd();
3013
- const { startTUI } = await import("./start-tui--E4PXdwG.js");
2818
+ const { startTUI } = await import("./start-tui-CywbSvZE.js");
3014
2819
  const { buildSession, RunPhase } = await import("./wizard-session-wPJtNl4c.js");
3015
2820
  const { TaskStreamPush } = await import("./task-stream-CZawuzlz.js");
3016
2821
  const { PostHogDestination } = await import("./posthog-Cr37rnla.js");
@@ -3066,7 +2871,7 @@ function runWizard(config, options) {
3066
2871
  await activeTui.store.getGate("health-check");
3067
2872
  const skipAgent = config.run == null;
3068
2873
  if (skipAgent) {
3069
- const { getOrAskForProjectData } = await import("./setup-utils-Cr4FxJDl.js").then((n) => n.r);
2874
+ const { getOrAskForProjectData } = await import("./setup-utils-Doh69vo4.js").then((n) => n.r);
3070
2875
  const { projectApiKey, host, accessToken, projectId } = await getOrAskForProjectData({
3071
2876
  signup: session.signup,
3072
2877
  ci: session.ci,
@@ -3081,7 +2886,7 @@ function runWizard(config, options) {
3081
2886
  projectId
3082
2887
  });
3083
2888
  } else {
3084
- const { runAgent } = await import("./agent-runner-DgzF2mga.js");
2889
+ const { runAgent } = await import("./agent-runner-Br0OxBxd.js");
3085
2890
  await runAgent(config, activeTui.store.session);
3086
2891
  }
3087
2892
  const isDone = () => skipAgent ? activeTui.store.session.outroDismissed : activeTui.store.session.skillsComplete;
@@ -3158,10 +2963,10 @@ function runWizardCI(config, options) {
3158
2963
  (async () => {
3159
2964
  const path = await import("path");
3160
2965
  const { buildSession } = await import("./wizard-session-wPJtNl4c.js");
3161
- const { readEnvironment } = await import("./environment-DUh_8hqW.js").then((n) => n.t);
2966
+ const { readEnvironment } = await import("./environment-G0Hv6_JI.js").then((n) => n.t);
3162
2967
  const { readApiKeyFromEnv } = await import("./env-api-key-MlzJYAvt.js").then((n) => n.t);
3163
- const { configureLogFileFromEnvironment, logToFile } = await import("./debug-DLsuyfln.js");
3164
- const { wizardAbort, WizardError } = await import("./wizard-abort-BPsnXKY5.js");
2968
+ const { configureLogFileFromEnvironment, logToFile } = await import("./debug-BgH07S-e.js");
2969
+ const { wizardAbort, WizardError } = await import("./wizard-abort-Ni-mKJ58.js");
3165
2970
  configureLogFileFromEnvironment();
3166
2971
  const env = readEnvironment();
3167
2972
  const apiKey = options.apiKey ?? readApiKeyFromEnv() ?? void 0;
@@ -3212,7 +3017,7 @@ function runWizardCI(config, options) {
3212
3017
  })
3213
3018
  });
3214
3019
  }
3215
- const { runAgent } = await import("./agent-runner-DgzF2mga.js");
3020
+ const { runAgent } = await import("./agent-runner-Br0OxBxd.js");
3216
3021
  await runAgent(config, session);
3217
3022
  } catch (error) {
3218
3023
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3231,107 +3036,732 @@ function runWizardCI(config, options) {
3231
3036
  }
3232
3037
  //#endregion
3233
3038
  //#region src/commands/skill-program-options.ts
3234
- /** Flags shared by every skill-based program command (integrate, audit, …). */
3235
- const skillProgramOptions = {
3236
- debug: {
3237
- default: false,
3238
- describe: "Enable verbose logging",
3239
- type: "boolean"
3240
- },
3241
- "install-dir": {
3242
- describe: "Directory to install in",
3243
- type: "string"
3244
- },
3245
- "local-mcp": {
3246
- default: false,
3247
- describe: "Use local MCP server",
3248
- type: "boolean"
3249
- },
3250
- benchmark: {
3251
- default: false,
3252
- describe: "Run in benchmark mode",
3253
- type: "boolean"
3254
- },
3255
- "yara-report": {
3256
- default: false,
3257
- describe: "Print YARA scanner summary",
3258
- type: "boolean",
3259
- hidden: true
3260
- }
3261
- };
3262
- //#endregion
3263
- //#region src/commands/integrate.ts
3264
- const integrateCommand = {
3265
- name: "integrate",
3266
- description: posthogIntegrationConfig.description,
3267
- options: {
3268
- ...skillProgramOptions,
3269
- ...posthogIntegrationConfig.cliOptions ?? {}
3270
- },
3271
- handler: (argv) => {
3272
- const extras = posthogIntegrationConfig.mapCliOptions?.(argv) ?? {};
3273
- const options = {
3274
- ...argv,
3275
- ...extras
3276
- };
3277
- if (options.ci) runWizardCI(posthogIntegrationConfig, options);
3278
- else runWizard(posthogIntegrationConfig, options);
3279
- }
3280
- };
3039
+ /**
3040
+ * Per-command options shared by every skill-based program command
3041
+ * (`audit events`, `migrate statsig`, `revenue`, `source-maps`, …).
3042
+ *
3043
+ * Only flags that are unique to skill commands live here. Global flags
3044
+ * (`--debug`, `--local-mcp`, `--benchmark`, `--yara-report`, `--ci`) are
3045
+ * declared once in `wizard.ts::GLOBAL_OPTIONS` and apply automatically
3046
+ * across every command — no need to repeat them per subcommand.
3047
+ */
3048
+ const skillProgramOptions = { "install-dir": {
3049
+ describe: "Directory to install in",
3050
+ type: "string"
3051
+ } };
3281
3052
  //#endregion
3282
- //#region src/commands/audit.ts
3283
- const dispatchProgram = (config, argv) => {
3284
- const extras = config.mapCliOptions?.(argv) ?? {};
3053
+ //#region src/commands/factories/shared.ts
3054
+ /**
3055
+ * Dispatch a parsed yargs invocation to the wizard runner. Applies the
3056
+ * program's `mapCliOptions` transform, then routes to `runWizard` or
3057
+ * `runWizardCI` based on the `--ci` flag.
3058
+ *
3059
+ * Every command file used to inline this; the factories call it instead.
3060
+ */
3061
+ /**
3062
+ * Run a command's async body as fire-and-forget while still surfacing failures.
3063
+ * yargs handlers are synchronous, so async work kicks off a detached promise —
3064
+ * without this, a rejection becomes an unhandled promise rejection (no message,
3065
+ * wrong exit code). This awaits the work and turns any error into a clean
3066
+ * message + non-zero exit.
3067
+ */
3068
+ function runCommandHandler(work) {
3069
+ (async () => {
3070
+ try {
3071
+ await work();
3072
+ } catch (err) {
3073
+ const msg = err instanceof Error ? err.message : String(err);
3074
+ process.stderr.write(`\n\x1b[1;91m✖ ${msg}\x1b[0m\n\n`);
3075
+ process.exit(1);
3076
+ }
3077
+ })();
3078
+ }
3079
+ function dispatchProgram(config, argv) {
3080
+ const argvRecord = argv;
3081
+ const extras = config.mapCliOptions?.(argvRecord) ?? {};
3285
3082
  const options = {
3286
- ...argv,
3083
+ ...argvRecord,
3287
3084
  ...extras
3288
3085
  };
3289
3086
  if (options.ci) runWizardCI(config, options);
3290
3087
  else runWizard(config, options);
3291
- };
3292
- const webAnalyticsCommand = {
3293
- name: webAnalyticsDoctorConfig.command,
3294
- description: webAnalyticsDoctorConfig.description,
3295
- options: {
3088
+ }
3089
+ /**
3090
+ * Merge the standard skill-program flags (`--debug`, `--install-dir`, etc.)
3091
+ * with any program-specific options declared on `cliOptions`.
3092
+ *
3093
+ * Program-specific options shadow the standard ones — that's intentional, so
3094
+ * a program can override a default flag if it ever needs to.
3095
+ */
3096
+ function mergeCommandOptions(config) {
3097
+ return {
3296
3098
  ...skillProgramOptions,
3297
- ...webAnalyticsDoctorConfig.cliOptions ?? {}
3298
- },
3299
- handler: (argv) => {
3300
- dispatchProgram(webAnalyticsDoctorConfig, argv);
3099
+ ...config.cliOptions ?? {}
3100
+ };
3101
+ }
3102
+ //#endregion
3103
+ //#region src/lib/programs/dispatch-family.ts
3104
+ /**
3105
+ * Capture a CLI dispatch error, flush analytics, and exit. The wizard never
3106
+ * starts a run from these paths — use flush() (not shutdown()) so we don't
3107
+ * fire a "setup wizard finished" event for a parse error that didn't run.
3108
+ */
3109
+ async function exitDispatchError(reason, properties, message, code = 1) {
3110
+ analytics.wizardCapture("cli dispatch error", {
3111
+ reason,
3112
+ ...properties
3113
+ });
3114
+ try {
3115
+ await analytics.flush();
3116
+ } catch {}
3117
+ process.stderr.write(message);
3118
+ return process.exit(code);
3119
+ }
3120
+ /**
3121
+ * Family commands (`wizard audit`, `wizard migrate`, ...) resolve their
3122
+ * subcommands at runtime against the published `cliEntries` inside
3123
+ * `skill-menu.json`. Adding a subcommand is a context-mill release — no
3124
+ * wizard release needed.
3125
+ *
3126
+ * Wizard-native subcommands (programs that aren't backed by a single skill,
3127
+ * e.g. `wizard audit web-analytics`) live here in code, dispatched directly
3128
+ * without touching the registry. Adding a native is a wizard PR.
3129
+ */
3130
+ /** Wizard-native subcommands keyed by family. */
3131
+ const NATIVE_HANDLERS = { audit: { "web-analytics": webAnalyticsDoctorConfig } };
3132
+ /**
3133
+ * Resolve a fetched CliEntry to the ProgramConfig that actually runs it.
3134
+ * Most entries run via the generic agent-skill program with the entry's
3135
+ * `skillId` injected. The comprehensive `audit all` is the one exception —
3136
+ * skillId 'audit' triggers the specialized auditConfig (custom hooks,
3137
+ * content blocks, screens).
3138
+ */
3139
+ function configForCliEntry(entry) {
3140
+ if (entry.skillId === "audit") return auditConfig;
3141
+ return {
3142
+ ...agentSkillConfig,
3143
+ skillId: entry.skillId
3144
+ };
3145
+ }
3146
+ function familyEntries(family, entries) {
3147
+ return entries.filter((e) => e.role === "command" && e.parentCommand === family && Boolean(e.command));
3148
+ }
3149
+ /**
3150
+ * Dispatch `wizard <family> <sub>` to the right program.
3151
+ *
3152
+ * Order:
3153
+ * 1. Native handler for (family, sub) — runs immediately, no network.
3154
+ * 2. Fetched CliEntry — runs the resolved skill.
3155
+ * 3. Unknown — prints the available list and exits non-zero.
3156
+ */
3157
+ async function dispatchFamily(family, argv) {
3158
+ const sub = argv.skill?.trim();
3159
+ if (!sub) return exitDispatchError("missing subcommand", { family }, `\n\x1b[1;91m✖ \`wizard ${family}\` requires a subcommand.\x1b[0m\n Pass one (e.g. \`wizard ${family} <subcommand>\`), or run it in an interactive terminal to pick from a menu.\n\n`);
3160
+ const native = NATIVE_HANDLERS[family]?.[sub];
3161
+ if (native) {
3162
+ dispatchProgram(native, argv);
3163
+ return;
3164
+ }
3165
+ const skillsBaseUrl = getSkillsBaseUrl(Boolean(argv["local-mcp"]));
3166
+ const menu = await fetchSkillMenu(skillsBaseUrl);
3167
+ if (!menu) return exitDispatchError("registry unreachable", {
3168
+ family,
3169
+ sub,
3170
+ skillsBaseUrl
3171
+ }, `\n\x1b[1;91m✖ Couldn't reach the skill registry at ${skillsBaseUrl}.\x1b[0m\n Check your network connection and try again.\n\n`);
3172
+ const entries = menu.cliEntries ?? [];
3173
+ const entry = familyEntries(family, entries).find((e) => e.command === sub);
3174
+ if (entry) {
3175
+ dispatchProgram(configForCliEntry(entry), argv);
3176
+ return;
3301
3177
  }
3178
+ const available = [...Object.keys(NATIVE_HANDLERS[family] ?? {}), ...familyEntries(family, entries).map((e) => e.command)].sort();
3179
+ return exitDispatchError("unknown subcommand", {
3180
+ family,
3181
+ sub,
3182
+ available
3183
+ }, `\n\x1b[1;91m✖ Unknown subcommand "${sub}" under \`${family}\`.\x1b[0m\n` + (available.length ? ` Available: ${available.join(", ")}\n\n` : ` No subcommands published for "${family}" yet.\n\n`));
3184
+ }
3185
+ /**
3186
+ * Build the children list shown in the family's interactive picker.
3187
+ * Combines native handlers with skill-backed entries from the live registry.
3188
+ * Used by `familyCommandFactory`'s `interactiveDefault`.
3189
+ */
3190
+ function buildFamilyPickerChildren(family, entries) {
3191
+ const natives = Object.entries(NATIVE_HANDLERS[family] ?? {}).map(([cmd, program]) => ({
3192
+ name: cmd,
3193
+ description: program.description,
3194
+ handler: (argv) => dispatchProgram(program, argv)
3195
+ }));
3196
+ const live = familyEntries(family, entries).map((entry) => ({
3197
+ name: entry.command,
3198
+ description: entry.description,
3199
+ handler: (argv) => {
3200
+ dispatchFamily(family, {
3201
+ ...argv,
3202
+ skill: entry.command
3203
+ });
3204
+ },
3205
+ default: entry.default
3206
+ }));
3207
+ return [...natives, ...live];
3208
+ }
3209
+ /**
3210
+ * The children the family picker shows **today**: only the leaf marked
3211
+ * `default` (e.g. `audit events`). Every other subcommand stays runnable
3212
+ * directly (`wizard audit <name>`) — they just aren't listed in the picker yet.
3213
+ * Falls back to all children when nothing is marked `default`.
3214
+ *
3215
+ * Temporary: when we're ready to surface the full menu, return `children`
3216
+ * unchanged (and delete this note).
3217
+ */
3218
+ function pickerChildrenToShow(children) {
3219
+ const defaults = children.filter((c) => c.default);
3220
+ return defaults.length > 0 ? [...defaults] : [...children];
3221
+ }
3222
+ //#endregion
3223
+ //#region src/ui/tui/primitives/PromptLabel.tsx
3224
+ /**
3225
+ * PromptLabel — Compact inline label for input prompts.
3226
+ *
3227
+ * Renders: [!] message
3228
+ * where [!] is black text on accent background.
3229
+ */
3230
+ const PromptLabel = ({ message }) => {
3231
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, {
3232
+ bold: true,
3233
+ color: Colors.accent,
3234
+ children: [" ", message]
3235
+ }) });
3302
3236
  };
3303
- const auditCommand = {
3304
- name: "audit",
3305
- description: auditConfig.description,
3306
- children: [webAnalyticsCommand],
3307
- options: {
3308
- ...skillProgramOptions,
3309
- ...auditConfig.cliOptions ?? {}
3310
- },
3311
- handler: (argv) => {
3312
- dispatchProgram(auditConfig, argv);
3237
+ //#endregion
3238
+ //#region src/ui/tui/hooks/keyboard-hints-utils.ts
3239
+ /** Default priorities by key type. */
3240
+ const DEFAULT_PRIORITY = {
3241
+ ["upArrow"]: 0,
3242
+ ["downArrow"]: 0,
3243
+ ["leftArrow"]: 1,
3244
+ ["rightArrow"]: 1,
3245
+ ["space"]: 10,
3246
+ ["escape"]: 20,
3247
+ ["return"]: 21
3248
+ };
3249
+ /** Get the default display priority for a key match. */
3250
+ function getDefaultPriority(match) {
3251
+ return DEFAULT_PRIORITY[Array.isArray(match) ? match[0] : match] ?? 15;
3252
+ }
3253
+ /** Check if a KeyMatchOrChar matches the given input string and key flags. */
3254
+ function matchesKey(m, input, key) {
3255
+ switch (m) {
3256
+ case "upArrow": return !!key.upArrow;
3257
+ case "downArrow": return !!key.downArrow;
3258
+ case "leftArrow": return !!key.leftArrow;
3259
+ case "rightArrow": return !!key.rightArrow;
3260
+ case "return": return !!key.return;
3261
+ case "escape": return !!key.escape;
3262
+ case "space": return input === " ";
3263
+ default: return input === m;
3264
+ }
3265
+ }
3266
+ /** Serialize hints for comparison. */
3267
+ function hintsKey(hints) {
3268
+ return hints.map((h) => `${h.label}:${h.action}`).join("|");
3269
+ }
3270
+ /** Deduplicate hints by label+action and sort by priority. */
3271
+ function deduplicateAndSort(hints) {
3272
+ const seen = /* @__PURE__ */ new Set();
3273
+ const deduped = [];
3274
+ for (const hint of hints) {
3275
+ const k = `${hint.label}:${hint.action}`;
3276
+ if (!seen.has(k)) {
3277
+ seen.add(k);
3278
+ deduped.push(hint);
3279
+ }
3313
3280
  }
3281
+ deduped.sort((a, b) => a.priority - b.priority);
3282
+ return deduped;
3283
+ }
3284
+ //#endregion
3285
+ //#region src/ui/tui/hooks/useKeyboardHints.tsx
3286
+ /**
3287
+ * KeyboardHintsProvider — Context for collecting and displaying keyboard hints.
3288
+ *
3289
+ * Input components register their hints via useKeyBindings. The provider
3290
+ * flattens, deduplicates, and sorts them. The hints bar stays visible for as
3291
+ * long as a screen has registered hints — it never auto-dismisses.
3292
+ */
3293
+ const KeyboardHintsContext = createContext({
3294
+ register: () => void 0,
3295
+ unregister: () => void 0,
3296
+ hints: []
3297
+ });
3298
+ const useKeyboardHintsContext = () => useContext(KeyboardHintsContext);
3299
+ const KeyboardHintsProvider = ({ children }) => {
3300
+ const registrationsRef = useRef(/* @__PURE__ */ new Map());
3301
+ const [hints, setHints] = useState([]);
3302
+ const prevHintsKeyRef = useRef("");
3303
+ const recompute = useCallback(() => {
3304
+ const all = [];
3305
+ for (const h of registrationsRef.current.values()) all.push(...h);
3306
+ const deduped = deduplicateAndSort(all);
3307
+ const newKey = hintsKey(deduped);
3308
+ if (newKey !== prevHintsKeyRef.current) {
3309
+ prevHintsKeyRef.current = newKey;
3310
+ setHints(deduped);
3311
+ }
3312
+ }, []);
3313
+ const register = useCallback((id, h) => {
3314
+ registrationsRef.current.set(id, h);
3315
+ recompute();
3316
+ }, [recompute]);
3317
+ const unregister = useCallback((id) => {
3318
+ registrationsRef.current.delete(id);
3319
+ recompute();
3320
+ }, [recompute]);
3321
+ return /* @__PURE__ */ jsx(KeyboardHintsContext.Provider, {
3322
+ value: {
3323
+ register,
3324
+ unregister,
3325
+ hints
3326
+ },
3327
+ children
3328
+ });
3314
3329
  };
3315
3330
  //#endregion
3316
- //#region src/commands/audit-3000.ts
3317
- const audit3000Command = {
3318
- name: "audit-3000",
3319
- description: audit3000Config.description,
3320
- options: {
3321
- ...skillProgramOptions,
3322
- ...audit3000Config.cliOptions ?? {}
3323
- },
3324
- handler: (argv) => {
3325
- const extras = audit3000Config.mapCliOptions?.(argv) ?? {};
3326
- const options = {
3327
- ...argv,
3328
- ...extras
3329
- };
3330
- if (options.ci) runWizardCI(audit3000Config, options);
3331
- else runWizard(audit3000Config, options);
3331
+ //#region src/ui/tui/hooks/useKeyBindings.ts
3332
+ /**
3333
+ * useKeyBindings — Declarative keyboard input + automatic hint registration.
3334
+ *
3335
+ * Replaces raw `useInput` in input components. Define bindings as data;
3336
+ * the hook wires up the Ink input handler AND registers hints in the
3337
+ * KeyboardHintsProvider. One source of truth for keys and their labels.
3338
+ */
3339
+ /**
3340
+ * Declarative key bindings hook. Replaces `useInput` in input components.
3341
+ * Registers hints automatically with the KeyboardHintsProvider.
3342
+ *
3343
+ * @param id Unique identifier for this component's hints registration
3344
+ * @param bindings Array of key binding definitions
3345
+ */
3346
+ function useKeyBindings(id, bindings) {
3347
+ const ctx = useKeyboardHintsContext();
3348
+ const hintsRef = useRef("");
3349
+ const hints = bindings.map((b) => ({
3350
+ label: b.label,
3351
+ action: b.action,
3352
+ priority: b.priority ?? getDefaultPriority(b.match)
3353
+ }));
3354
+ const serialized = hints.map((h) => `${h.label}:${h.action}:${h.priority}`).join("|");
3355
+ useEffect(() => {
3356
+ if (serialized !== hintsRef.current) {
3357
+ hintsRef.current = serialized;
3358
+ ctx.register(id, hints);
3359
+ }
3360
+ return () => ctx.unregister(id);
3361
+ }, [id, serialized]);
3362
+ useInput((input, key) => {
3363
+ for (const binding of bindings) if ((Array.isArray(binding.match) ? binding.match : [binding.match]).some((m) => matchesKey(m, input, key))) {
3364
+ binding.handler(input, key);
3365
+ return;
3366
+ }
3367
+ });
3368
+ }
3369
+ //#endregion
3370
+ //#region src/ui/tui/primitives/PickerMenu.tsx
3371
+ /**
3372
+ * PickerMenu — Single and multi select.
3373
+ * Single mode: custom renderer with small triangle indicator.
3374
+ * Multi mode: checkbox glyphs with space to toggle.
3375
+ *
3376
+ * Key bindings are declared via useKeyBindings, which auto-registers
3377
+ * hints in the KeyboardHintsBar.
3378
+ */
3379
+ /**
3380
+ * Step through a column's options in `dir`, wrapping, until an enabled
3381
+ * option is found. Returns `from` unchanged if the column is entirely
3382
+ * disabled.
3383
+ */
3384
+ function stepEnabled(options, rows, from, dir) {
3385
+ const colStart = Math.floor(from / rows) * rows;
3386
+ const colLen = Math.min(rows, options.length - colStart);
3387
+ let row = from % rows;
3388
+ for (let i = 0; i < colLen; i++) {
3389
+ row = (row + dir + colLen) % colLen;
3390
+ const idx = colStart + row;
3391
+ if (!options[idx]?.disabled) return idx;
3332
3392
  }
3393
+ return from;
3394
+ }
3395
+ /** Index of the first enabled option, for the initial focus. */
3396
+ function firstEnabled(options) {
3397
+ const idx = options.findIndex((o) => !o.disabled);
3398
+ return idx === -1 ? 0 : idx;
3399
+ }
3400
+ const PickerMenu = ({ message, options, mode = "single", centered = false, columns = 1, optionMarginBottom = 0, onSelect }) => {
3401
+ if (mode === "multi") return /* @__PURE__ */ jsx(MultiPickerMenu, {
3402
+ message,
3403
+ options,
3404
+ centered,
3405
+ columns,
3406
+ optionMarginBottom,
3407
+ onSelect
3408
+ });
3409
+ return /* @__PURE__ */ jsx(SinglePickerMenu, {
3410
+ message,
3411
+ options,
3412
+ centered,
3413
+ columns,
3414
+ optionMarginBottom,
3415
+ onSelect
3416
+ });
3417
+ };
3418
+ /** Custom single-select with triangle indicator and accent highlight. */
3419
+ const SinglePickerMenu = ({ message, options, centered = false, columns = 1, optionMarginBottom = 0, onSelect }) => {
3420
+ const [focused, setFocused] = useState(() => firstEnabled(options));
3421
+ const rows = Math.ceil(options.length / columns);
3422
+ useEffect(() => {
3423
+ if (focused >= options.length || options[focused]?.disabled) setFocused(firstEnabled(options));
3424
+ }, [options, focused]);
3425
+ const bindings = [{
3426
+ match: ["upArrow", "downArrow"],
3427
+ label: "↑↓",
3428
+ action: "navigate",
3429
+ handler: (_input, key) => {
3430
+ if (key.upArrow) setFocused(stepEnabled(options, rows, focused, -1));
3431
+ if (key.downArrow) setFocused(stepEnabled(options, rows, focused, 1));
3432
+ }
3433
+ }, {
3434
+ match: "return",
3435
+ label: "enter",
3436
+ action: "select",
3437
+ handler: () => {
3438
+ const selected = options[focused];
3439
+ if (selected && !selected.disabled) onSelect(selected.value);
3440
+ }
3441
+ }];
3442
+ if (columns > 1) bindings.splice(1, 0, {
3443
+ match: ["leftArrow", "rightArrow"],
3444
+ label: "←→",
3445
+ action: "navigate",
3446
+ handler: (_input, key) => {
3447
+ const col = Math.floor(focused / rows);
3448
+ const row = focused % rows;
3449
+ let next = focused;
3450
+ if (key.leftArrow) {
3451
+ const prevCol = col > 0 ? col - 1 : columns - 1;
3452
+ next = Math.min(prevCol * rows + row, options.length - 1);
3453
+ }
3454
+ if (key.rightArrow) {
3455
+ const nextCol = col < columns - 1 ? col + 1 : 0;
3456
+ next = Math.min(nextCol * rows + row, options.length - 1);
3457
+ }
3458
+ if (options[next]?.disabled) next = stepEnabled(options, rows, next, 1);
3459
+ setFocused(next);
3460
+ }
3461
+ });
3462
+ useKeyBindings("single-picker", bindings);
3463
+ const columnArrays = [];
3464
+ for (let c = 0; c < columns; c++) columnArrays.push(options.slice(c * rows, c * rows + rows));
3465
+ return /* @__PURE__ */ jsxs(Box, {
3466
+ flexDirection: "column",
3467
+ alignItems: centered ? "center" : void 0,
3468
+ children: [/* @__PURE__ */ jsx(PromptLabel, { message }), /* @__PURE__ */ jsx(Box, {
3469
+ flexDirection: "row",
3470
+ gap: 4,
3471
+ children: columnArrays.map((colOpts, colIdx) => /* @__PURE__ */ jsx(Box, {
3472
+ flexDirection: "column",
3473
+ children: colOpts.map((opt, rowIdx) => {
3474
+ const flatIdx = colIdx * rows + rowIdx;
3475
+ const isFocused = flatIdx === focused;
3476
+ const label = opt.hint ? `${opt.label} (${opt.hint})` : opt.label;
3477
+ return /* @__PURE__ */ jsxs(Box, {
3478
+ gap: 1,
3479
+ marginBottom: optionMarginBottom,
3480
+ children: [
3481
+ /* @__PURE__ */ jsx(Text, {
3482
+ color: isFocused ? Colors.accent : void 0,
3483
+ dimColor: !isFocused,
3484
+ children: isFocused ? Icons.triangleSmallRight : " "
3485
+ }),
3486
+ opt.icon && /* @__PURE__ */ jsx(Text, {
3487
+ color: opt.icon.color,
3488
+ children: opt.icon.glyph
3489
+ }),
3490
+ /* @__PURE__ */ jsx(Text, {
3491
+ color: opt.disabled ? Colors.muted : isFocused ? Colors.accent : void 0,
3492
+ bold: isFocused && !opt.disabled,
3493
+ dimColor: !isFocused || opt.disabled,
3494
+ children: label
3495
+ })
3496
+ ]
3497
+ }, flatIdx);
3498
+ })
3499
+ }, colIdx))
3500
+ })]
3501
+ });
3502
+ };
3503
+ /** Custom multi-select with checkbox glyphs and accent highlight. */
3504
+ const MultiPickerMenu = ({ message, options, centered = false, columns = 1, optionMarginBottom = 0, onSelect }) => {
3505
+ const [focused, setFocused] = useState(() => firstEnabled(options));
3506
+ const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
3507
+ const rows = Math.ceil(options.length / columns);
3508
+ useEffect(() => {
3509
+ if (focused >= options.length || options[focused]?.disabled) setFocused(firstEnabled(options));
3510
+ }, [options, focused]);
3511
+ const bindings = [
3512
+ {
3513
+ match: ["upArrow", "downArrow"],
3514
+ label: "↑↓",
3515
+ action: "navigate",
3516
+ handler: (_input, key) => {
3517
+ if (key.upArrow) setFocused(stepEnabled(options, rows, focused, -1));
3518
+ if (key.downArrow) setFocused(stepEnabled(options, rows, focused, 1));
3519
+ }
3520
+ },
3521
+ {
3522
+ match: "space",
3523
+ label: "space",
3524
+ action: "toggle",
3525
+ handler: () => {
3526
+ if (options[focused]?.disabled) return;
3527
+ setSelected((prev) => {
3528
+ const next = new Set(prev);
3529
+ if (next.has(focused)) {
3530
+ next.delete(focused);
3531
+ return next;
3532
+ }
3533
+ if (options[focused]?.exclusive) return new Set([focused]);
3534
+ for (const i of next) if (options[i]?.exclusive) next.delete(i);
3535
+ next.add(focused);
3536
+ return next;
3537
+ });
3538
+ }
3539
+ },
3540
+ {
3541
+ match: "return",
3542
+ label: "enter",
3543
+ action: "confirm",
3544
+ handler: () => {
3545
+ if (selected.size === 0) {
3546
+ const hovered = options[focused];
3547
+ if (hovered && !hovered.disabled) onSelect(hovered.value);
3548
+ } else onSelect([...selected].sort().map((i) => options[i].value));
3549
+ }
3550
+ }
3551
+ ];
3552
+ if (columns > 1) bindings.splice(1, 0, {
3553
+ match: ["leftArrow", "rightArrow"],
3554
+ label: "←→",
3555
+ action: "navigate",
3556
+ handler: (_input, key) => {
3557
+ const col = Math.floor(focused / rows);
3558
+ const row = focused % rows;
3559
+ let next = focused;
3560
+ if (key.leftArrow) {
3561
+ const prevCol = col > 0 ? col - 1 : columns - 1;
3562
+ next = Math.min(prevCol * rows + row, options.length - 1);
3563
+ }
3564
+ if (key.rightArrow) {
3565
+ const nextCol = col < columns - 1 ? col + 1 : 0;
3566
+ next = Math.min(nextCol * rows + row, options.length - 1);
3567
+ }
3568
+ if (options[next]?.disabled) next = stepEnabled(options, rows, next, 1);
3569
+ setFocused(next);
3570
+ }
3571
+ });
3572
+ useKeyBindings("multi-picker", bindings);
3573
+ const columnArrays = [];
3574
+ for (let c = 0; c < columns; c++) columnArrays.push(options.slice(c * rows, c * rows + rows));
3575
+ return /* @__PURE__ */ jsxs(Box, {
3576
+ flexDirection: "column",
3577
+ alignItems: centered ? "center" : void 0,
3578
+ children: [/* @__PURE__ */ jsx(PromptLabel, { message }), /* @__PURE__ */ jsx(Box, {
3579
+ flexDirection: "row",
3580
+ gap: 4,
3581
+ marginLeft: centered ? 0 : 2,
3582
+ marginTop: 1,
3583
+ children: columnArrays.map((colOpts, colIdx) => /* @__PURE__ */ jsx(Box, {
3584
+ flexDirection: "column",
3585
+ children: colOpts.map((opt, rowIdx) => {
3586
+ const flatIdx = colIdx * rows + rowIdx;
3587
+ const isFocused = flatIdx === focused;
3588
+ const isSelected = selected.has(flatIdx);
3589
+ const label = opt.hint ? `${opt.label} (${opt.hint})` : opt.label;
3590
+ const checkbox = isSelected ? Icons.squareFilled : Icons.squareOpen;
3591
+ return /* @__PURE__ */ jsxs(Box, {
3592
+ gap: 1,
3593
+ marginBottom: optionMarginBottom,
3594
+ children: [
3595
+ /* @__PURE__ */ jsx(Text, {
3596
+ color: isSelected ? "white" : Colors.muted,
3597
+ dimColor: !isFocused && !isSelected,
3598
+ children: checkbox
3599
+ }),
3600
+ opt.icon && /* @__PURE__ */ jsx(Text, {
3601
+ color: opt.icon.color,
3602
+ children: opt.icon.glyph
3603
+ }),
3604
+ /* @__PURE__ */ jsx(Text, {
3605
+ color: opt.disabled ? Colors.muted : isFocused ? Colors.accent : void 0,
3606
+ bold: isFocused && !opt.disabled,
3607
+ dimColor: !isFocused || opt.disabled,
3608
+ children: label
3609
+ })
3610
+ ]
3611
+ }, flatIdx);
3612
+ })
3613
+ }, colIdx))
3614
+ })]
3615
+ });
3333
3616
  };
3334
3617
  //#endregion
3618
+ //#region src/commands/factories/family-picker.tsx
3619
+ function FamilyPickerApp(props) {
3620
+ return createElement(Box, {
3621
+ flexDirection: "column",
3622
+ paddingX: 1,
3623
+ paddingY: 1
3624
+ }, createElement(Text, {
3625
+ bold: true,
3626
+ color: Colors.accent
3627
+ }, props.parentLabel), createElement(Box, { height: 1 }), createElement(PickerMenu, {
3628
+ message: "Pick a subcommand",
3629
+ options: props.options,
3630
+ optionMarginBottom: 1,
3631
+ onSelect: (value) => {
3632
+ const cmd = Array.isArray(value) ? value[0] : value;
3633
+ if (cmd) props.onSelect(cmd);
3634
+ }
3635
+ }));
3636
+ }
3637
+ function describe(child) {
3638
+ return commandKeys(child.name)[0] ?? "";
3639
+ }
3640
+ /**
3641
+ * Reorder children so the `default`-marked entry is first, while
3642
+ * preserving the relative order of the rest. The picker's initial
3643
+ * focus is index 0, so this is what makes "press Enter on
3644
+ * `wizard audit`" run the default leaf (today `audit events`).
3645
+ *
3646
+ * Exported for testability — the ordering logic stays pure and
3647
+ * inspectable without mounting Ink.
3648
+ */
3649
+ function orderFamilyChildren(children) {
3650
+ const selectable = children.filter((c) => c.handler || c.children?.length);
3651
+ const defaults = selectable.filter((c) => c.default);
3652
+ const rest = selectable.filter((c) => !c.default);
3653
+ return [...defaults, ...rest];
3654
+ }
3655
+ /**
3656
+ * Render the picker. Resolves once the user has selected a child;
3657
+ * dispatching the child's handler is the caller's responsibility (so this
3658
+ * function stays pure-UI and easy to test by stubbing `render`).
3659
+ */
3660
+ function chooseFamilyChild(parentLabel, children) {
3661
+ const ordered = orderFamilyChildren(children);
3662
+ if (ordered.length === 0) return Promise.resolve(null);
3663
+ const options = ordered.map((child) => ({
3664
+ label: describe(child),
3665
+ value: child,
3666
+ hint: child.description
3667
+ }));
3668
+ return new Promise((resolve) => {
3669
+ let app = null;
3670
+ const handleSelect = (cmd) => {
3671
+ app?.unmount();
3672
+ resolve(cmd);
3673
+ };
3674
+ app = render(createElement(FamilyPickerApp, {
3675
+ parentLabel,
3676
+ options,
3677
+ onSelect: handleSelect
3678
+ }));
3679
+ });
3680
+ }
3681
+ /**
3682
+ * Returns an `interactiveDefault` handler for a family parent's no-leaf
3683
+ * invocation. Always opens the picker; the `default`-marked child is
3684
+ * shown first (pre-highlighted), so a single Enter keystroke runs it.
3685
+ *
3686
+ * Discovery + consent in one extra keystroke vs. auto-running silently.
3687
+ *
3688
+ * Wire onto a family parent:
3689
+ * export const auditCommand: Command = {
3690
+ * name: 'audit',
3691
+ * description: '...',
3692
+ * children: [...],
3693
+ * interactiveDefault: createFamilyPickerDefault('audit', auditChildren),
3694
+ * };
3695
+ */
3696
+ function createFamilyPickerDefault(parentLabel, children, chooser = chooseFamilyChild) {
3697
+ return async (argv) => {
3698
+ const chosen = await chooser(parentLabel, children);
3699
+ if (!chosen) return;
3700
+ await Promise.resolve(chosen.handler?.(argv));
3701
+ };
3702
+ }
3703
+ //#endregion
3704
+ //#region src/commands/factories/family-command-factory.ts
3705
+ /**
3706
+ * Build a yargs `Command` for a family parent (`wizard audit`, etc.).
3707
+ *
3708
+ * - `wizard <family> <sub>` — `dispatchFamily` resolves `<sub>` against
3709
+ * native handlers first, then the live `cliEntries` from
3710
+ * `skill-menu.json`. Unknown subs error with the available list.
3711
+ * - `wizard <family>` (no positional) — in an interactive terminal, runs the
3712
+ * family's single shown entry directly (today `audit events`, so the user
3713
+ * lands on its intro screen); opens the picker once a family shows more than
3714
+ * one. In non-TTY/CI, falls through to `dispatchFamily`, which prints
3715
+ * "requires a subcommand" rather than running something unprompted.
3716
+ *
3717
+ * No static yargs children. New skill-backed subcommands appear after a
3718
+ * context-mill release without a wizard release. New *native* subcommands
3719
+ * (rare) are added by updating `NATIVE_HANDLERS` in `dispatch-family.ts`.
3720
+ */
3721
+ function familyCommandFactory({ family, description, optionsFrom }) {
3722
+ const openFamilyEntry = async (argv) => {
3723
+ const toShow = pickerChildrenToShow(buildFamilyPickerChildren(family, (await fetchSkillMenu(getSkillsBaseUrl(Boolean(argv["local-mcp"]))))?.cliEntries ?? []));
3724
+ if (toShow.length === 1 && toShow[0]?.handler) {
3725
+ await Promise.resolve(toShow[0].handler(argv));
3726
+ return;
3727
+ }
3728
+ await createFamilyPickerDefault(`wizard ${family}`, toShow)(argv);
3729
+ };
3730
+ return {
3731
+ name: `${family} [skill]`,
3732
+ description,
3733
+ options: mergeCommandOptions(optionsFrom),
3734
+ positionals: { skill: {
3735
+ type: "string",
3736
+ describe: "Subcommand to run (omit to run the default)"
3737
+ } },
3738
+ handler: (argv) => {
3739
+ const sub = argv.skill?.trim();
3740
+ runCommandHandler(() => sub || !process.stdout.isTTY ? dispatchFamily(family, argv) : openFamilyEntry(argv));
3741
+ },
3742
+ interactiveDefault: openFamilyEntry
3743
+ };
3744
+ }
3745
+ //#endregion
3746
+ //#region src/commands/audit.ts
3747
+ /**
3748
+ * The `wizard audit` family.
3749
+ *
3750
+ * Subcommands are resolved at runtime: the wizard fetches `cliEntries` from
3751
+ * `skill-menu.json` and dispatches based on `parentCommand: 'audit'`. The
3752
+ * wizard-native handler for `web-analytics` lives in `NATIVE_HANDLERS` over
3753
+ * in `dispatch-family.ts`. `wizard audit` with no positional opens the
3754
+ * family picker, which combines native + live entries.
3755
+ *
3756
+ * Adding a new skill-backed audit subcommand is a context-mill release —
3757
+ * no wizard release needed.
3758
+ */
3759
+ const auditCommand = familyCommandFactory({
3760
+ family: "audit",
3761
+ description: auditConfig.description,
3762
+ optionsFrom: auditConfig
3763
+ });
3764
+ //#endregion
3335
3765
  //#region src/commands/doctor.ts
3336
3766
  const doctorCommand = {
3337
3767
  name: "doctor",
@@ -3366,7 +3796,7 @@ async function runDoctorCI(options) {
3366
3796
  getUI().intro("Welcome to the PostHog setup wizard");
3367
3797
  getUI().log.info("Running posthog-doctor in CI mode");
3368
3798
  try {
3369
- const { getOrAskForProjectData } = await import("./setup-utils-Cr4FxJDl.js").then((n) => n.r);
3799
+ const { getOrAskForProjectData } = await import("./setup-utils-Doh69vo4.js").then((n) => n.r);
3370
3800
  const { host, accessToken, projectId } = await getOrAskForProjectData({
3371
3801
  signup: false,
3372
3802
  ci: true,
@@ -3383,69 +3813,53 @@ async function runDoctorCI(options) {
3383
3813
  for (const issue of sorted) getUI().log.info(` • [${issue.severity}] ${getKindMeta(issue.kind).title}`);
3384
3814
  process.exit(1);
3385
3815
  } catch (error) {
3386
- const { ApiError } = await import("./api-BkLZ8BWm.js").then((n) => n.n);
3816
+ const { ApiError } = await import("./api-RXTR8yZo.js").then((n) => n.n);
3387
3817
  const message = error instanceof ApiError && error.statusCode === 401 ? "Your PostHog API key is invalid or expired." : error instanceof Error ? error.message : String(error);
3388
3818
  getUI().log.error(`Doctor failed: ${message}`);
3389
3819
  process.exit(1);
3390
3820
  }
3391
3821
  }
3392
3822
  //#endregion
3393
- //#region src/commands/migrate.ts
3394
- const migrateCommand = {
3395
- name: "migrate",
3396
- description: migrationConfig.description,
3397
- options: {
3398
- ...skillProgramOptions,
3399
- ...migrationConfig.cliOptions ?? {}
3400
- },
3401
- handler: (argv) => {
3402
- const extras = migrationConfig.mapCliOptions?.(argv) ?? {};
3403
- const options = {
3404
- ...argv,
3405
- ...extras
3406
- };
3407
- if (options.ci) runWizardCI(migrationConfig, options);
3408
- else runWizard(migrationConfig, options);
3409
- }
3410
- };
3823
+ //#region src/commands/factories/native-command-factory.ts
3824
+ /**
3825
+ * Build a yargs `Command` from a wizard-native `ProgramConfig`.
3826
+ *
3827
+ * Collapses the previously duplicated boilerplate (read `config.command`,
3828
+ * merge skill-program flags with program-specific options, dispatch via
3829
+ * `runWizard` / `runWizardCI`) into a single call.
3830
+ */
3831
+ function nativeCommandFactory(config, opts = {}) {
3832
+ if (!config.command) throw new Error(`nativeCommandFactory: program "${config.id}" has no \`command\` — wizard-native programs must declare a CLI name`);
3833
+ return {
3834
+ name: config.command,
3835
+ description: config.description,
3836
+ options: mergeCommandOptions(config),
3837
+ children: opts.children,
3838
+ handler: (argv) => dispatchProgram(config, argv)
3839
+ };
3840
+ }
3411
3841
  //#endregion
3412
- //#region src/commands/events-audit.ts
3413
- const eventsAuditCommand = {
3414
- name: "events-audit",
3415
- description: eventsAuditConfig.description,
3416
- options: {
3417
- ...skillProgramOptions,
3418
- ...eventsAuditConfig.cliOptions ?? {}
3419
- },
3420
- handler: (argv) => {
3421
- const extras = eventsAuditConfig.mapCliOptions?.(argv) ?? {};
3422
- const options = {
3423
- ...argv,
3424
- ...extras
3425
- };
3426
- if (options.ci) runWizardCI(eventsAuditConfig, options);
3427
- else runWizard(eventsAuditConfig, options);
3428
- }
3429
- };
3842
+ //#region src/commands/migrate.ts
3843
+ /**
3844
+ * `wizard migrate` — flat skill command, Statsig today.
3845
+ *
3846
+ * Stays flat while there's only one vendor. When a second vendor lands,
3847
+ * restructure into a family with `familyCommandFactory` and publish each
3848
+ * vendor as a `cliEntries` entry with `parentCommand: 'migrate'` from
3849
+ * context-mill. That move is a deliberate breaking change for users
3850
+ * (`wizard migrate` stops running Statsig directly), so do it explicitly
3851
+ * when the second vendor arrives, not pre-emptively.
3852
+ */
3853
+ const migrateCommand = nativeCommandFactory(migrationConfig);
3430
3854
  //#endregion
3431
3855
  //#region src/commands/revenue.ts
3432
- const revenueCommand = {
3433
- name: "revenue",
3434
- description: revenueAnalyticsConfig.description,
3435
- options: {
3436
- ...skillProgramOptions,
3437
- ...revenueAnalyticsConfig.cliOptions ?? {}
3438
- },
3439
- handler: (argv) => {
3440
- const extras = revenueAnalyticsConfig.mapCliOptions?.(argv) ?? {};
3441
- const options = {
3442
- ...argv,
3443
- ...extras
3444
- };
3445
- if (options.ci) runWizardCI(revenueAnalyticsConfig, options);
3446
- else runWizard(revenueAnalyticsConfig, options);
3447
- }
3448
- };
3856
+ /**
3857
+ * `wizard revenue-analytics` — flat skill command, Stripe today.
3858
+ *
3859
+ * Stays flat while there's only one provider. Restructure into a family
3860
+ * if/when a second provider lands.
3861
+ */
3862
+ const revenueCommand = nativeCommandFactory(revenueAnalyticsConfig);
3449
3863
  //#endregion
3450
3864
  //#region src/commands/slack.ts
3451
3865
  const slackCommand = {
@@ -3462,7 +3876,7 @@ function runSlackConnect(argv) {
3462
3876
  (async () => {
3463
3877
  const debug = argv.debug;
3464
3878
  try {
3465
- const { startTUI } = await import("./start-tui--E4PXdwG.js");
3879
+ const { startTUI } = await import("./start-tui-CywbSvZE.js");
3466
3880
  const { buildSession } = await import("./wizard-session-wPJtNl4c.js");
3467
3881
  const tui = startTUI(VERSION, Program.SlackConnect);
3468
3882
  tui.store.session = buildSession({ debug });
@@ -3522,8 +3936,14 @@ function runSkillMode(argv) {
3522
3936
  function readSkillName(argv) {
3523
3937
  return String(argv.skillName ?? argv["skill-name"] ?? "").trim();
3524
3938
  }
3939
+ const BROWSABLE_ROLES = new Set(["command", "skill"]);
3940
+ function formatEntry(entry) {
3941
+ const path = entry.parentCommand ? `wizard ${entry.parentCommand} ${entry.command}` : entry.command ? `wizard ${entry.command}` : `wizard skill ${entry.skillId}`;
3942
+ return ` ${entry.skillId.padEnd(38)} ${path.padEnd(36)} ${entry.description}`;
3943
+ }
3525
3944
  /**
3526
3945
  * `wizard skill <skill-name>` — run a single context-mill skill by id.
3946
+ * `wizard skill list` — list every browsable skill in the catalog.
3527
3947
  *
3528
3948
  * Replaces the old `--skill=<id>` flag on the default command. The skill id
3529
3949
  * is fetched from context-mill's release at runtime (same mechanism the flag
@@ -3531,9 +3951,41 @@ function readSkillName(argv) {
3531
3951
  */
3532
3952
  const skillCommand = {
3533
3953
  name: "skill <skill-name>",
3534
- description: "Run a specific context-mill skill by name",
3954
+ description: "Run a specific context-mill skill by name (or `list` them)",
3955
+ children: [{
3956
+ name: "list",
3957
+ description: "List every browsable skill in the catalog",
3958
+ handler: (argv) => {
3959
+ runCommandHandler(async () => {
3960
+ const skillsBaseUrl = getSkillsBaseUrl(Boolean(argv["local-mcp"]));
3961
+ const menu = await fetchSkillMenu(skillsBaseUrl);
3962
+ if (!menu) {
3963
+ analytics.wizardCapture("cli dispatch error", {
3964
+ reason: "registry unreachable",
3965
+ family: "skill",
3966
+ sub: "list",
3967
+ skillsBaseUrl
3968
+ });
3969
+ try {
3970
+ await analytics.flush();
3971
+ } catch {}
3972
+ process.stderr.write("\n\x1B[1;91m✖ Couldn't reach the skill registry.\x1B[0m\n Check your network connection and try again.\n\n");
3973
+ process.exit(1);
3974
+ }
3975
+ const entries = (menu.cliEntries ?? []).filter((e) => BROWSABLE_ROLES.has(e.role));
3976
+ if (entries.length === 0) {
3977
+ process.stdout.write("No skills found.\n");
3978
+ return;
3979
+ }
3980
+ process.stdout.write(`${entries.length} skill${entries.length === 1 ? "" : "s"}:\n`);
3981
+ process.stdout.write(` ${"SKILL ID".padEnd(38)} ${"COMMAND".padEnd(36)} DESCRIPTION\n`);
3982
+ for (const entry of entries) process.stdout.write(`${formatEntry(entry)}\n`);
3983
+ });
3984
+ }
3985
+ }],
3535
3986
  options: { ...skillProgramOptions },
3536
3987
  check: (argv) => {
3988
+ if (argv.skillName == null && argv["skill-name"] == null) return true;
3537
3989
  if (!readSkillName(argv)) throw new Error("skill needs a skill name, e.g. `wizard skill audit-events`");
3538
3990
  return true;
3539
3991
  },
@@ -3560,8 +4012,8 @@ function resolveInstallDir() {
3560
4012
  if (inline) return inline.slice(14);
3561
4013
  return process.env.POSTHOG_WIZARD_INSTALL_DIR ?? process.cwd();
3562
4014
  }
3563
- Wizard.use(basicIntegrationCommand).use(mcpCommand).use(integrateCommand).use(auditCommand).use(audit3000Command).use(doctorCommand).use(migrateCommand).use(eventsAuditCommand).use(revenueCommand).use(slackCommand).use(uploadSourcemapsCommand).use(skillCommand).init();
4015
+ Wizard.use(basicIntegrationCommand).use(mcpCommand).use(auditCommand).use(doctorCommand).use(migrateCommand).use(revenueCommand).use(slackCommand).use(uploadSourcemapsCommand).use(skillCommand).init();
3564
4016
  //#endregion
3565
- export { getProgramConfig as a, getContentBlocks$1 as c, getContentBlocks$2 as d, POSTHOG_SDKS$1 as f, Program as i, getKindMeta as l, runWizard as n, DISPLAY_NAME as o, STRIPE_SDKS as p, PROGRAM_REGISTRY as r, SOURCE_MAPS_CONTEXT_KEYS as s, runWizardCI as t, fetchHealthIssues as u };
4017
+ export { POSTHOG_SDKS$1 as _, PromptLabel as a, PROGRAM_REGISTRY as c, DISPLAY_NAME as d, SOURCE_MAPS_CONTEXT_KEYS as f, getContentBlocks$2 as g, fetchHealthIssues as h, useKeyboardHintsContext as i, Program as l, getKindMeta as m, useKeyBindings as n, runWizardCI as o, getContentBlocks$1 as p, KeyboardHintsProvider as r, runWizard as s, PickerMenu as t, getProgramConfig as u, STRIPE_SDKS as v };
3566
4018
 
3567
4019
  //# sourceMappingURL=bin.js.map