@posthog/wizard 2.25.0 → 2.27.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 (65) hide show
  1. package/README.md +14 -1
  2. package/dist/{add-mcp-server-to-clients-t0xe8gn1.js → add-mcp-server-to-clients-D2XNlVgw.js} +4 -4
  3. package/dist/{add-mcp-server-to-clients-t0xe8gn1.js.map → add-mcp-server-to-clients-D2XNlVgw.js.map} +1 -1
  4. package/dist/{agent-interface-BsuUUPle.js → agent-interface-DpkR1mbC.js} +39 -12
  5. package/dist/agent-interface-DpkR1mbC.js.map +1 -0
  6. package/dist/{agent-runner-L_-kJ3y3.js → agent-runner-D7hIITUf.js} +176 -167
  7. package/dist/agent-runner-D7hIITUf.js.map +1 -0
  8. package/dist/{analytics-CDOujOSQ.js → analytics-B7-uRKIJ.js} +2 -2
  9. package/dist/{analytics-CDOujOSQ.js.map → analytics-B7-uRKIJ.js.map} +1 -1
  10. package/dist/{api-DNS-L-1U.js → api-2zPZQONC.js} +9 -5
  11. package/dist/api-2zPZQONC.js.map +1 -0
  12. package/dist/bin.js +1160 -120
  13. package/dist/bin.js.map +1 -1
  14. package/dist/{ci-install-_9A7tL36.js → ci-install-CpGSFNDi.js} +5 -5
  15. package/dist/{ci-install-_9A7tL36.js.map → ci-install-CpGSFNDi.js.map} +1 -1
  16. package/dist/{debug-BwC7UkGH.js → debug-Br_xCc9s.js} +3 -2
  17. package/dist/{debug-BwC7UkGH.js.map → debug-Br_xCc9s.js.map} +1 -1
  18. package/dist/{debug-CZQcMAJT.js → debug-CDLYQOQh.js} +1 -1
  19. package/dist/{environment-DQPoj9sU.js → environment-CFXsie0G.js} +3 -3
  20. package/dist/{environment-DQPoj9sU.js.map → environment-CFXsie0G.js.map} +1 -1
  21. package/dist/file-utils-CHAj73KM.js +116 -0
  22. package/dist/file-utils-CHAj73KM.js.map +1 -0
  23. package/dist/{interactive-DT5dLd7N.js → interactive-lfAs6vF7.js} +3 -3
  24. package/dist/{interactive-DT5dLd7N.js.map → interactive-lfAs6vF7.js.map} +1 -1
  25. package/dist/{mcp-prompt-streaming-CBMr458Q.js → mcp-prompt-streaming-BHdAwwob.js} +4 -4
  26. package/dist/{mcp-prompt-streaming-CBMr458Q.js.map → mcp-prompt-streaming-BHdAwwob.js.map} +1 -1
  27. package/dist/{non-interactive-csP4yGdA.js → non-interactive--4CK1bkn.js} +2 -2
  28. package/dist/{non-interactive-csP4yGdA.js.map → non-interactive--4CK1bkn.js.map} +1 -1
  29. package/dist/{package-manager-CB4c2euf.js → package-manager-BlogZvIK.js} +2 -2
  30. package/dist/{package-manager-CB4c2euf.js.map → package-manager-BlogZvIK.js.map} +1 -1
  31. package/dist/{playground-C-lpKoKC.js → playground-De_BxaCh.js} +145 -48
  32. package/dist/playground-De_BxaCh.js.map +1 -0
  33. package/dist/{posthog-integration-BL8-vC0V.js → posthog-integration-DWs8JM8J.js} +12 -12
  34. package/dist/{posthog-integration-BL8-vC0V.js.map → posthog-integration-DWs8JM8J.js.map} +1 -1
  35. package/dist/{provisioning-DLOiFSM9.js → provisioning-CUwxxByi.js} +10 -6
  36. package/dist/{provisioning-DLOiFSM9.js.map → provisioning-CUwxxByi.js.map} +1 -1
  37. package/dist/{registry-BbRzCV5l.js → registry-CIjJsxDE.js} +4 -4
  38. package/dist/{registry-BbRzCV5l.js.map → registry-CIjJsxDE.js.map} +1 -1
  39. package/dist/{setup-utils-D87CyNkw.js → setup-utils-CjKjaKcG.js} +81 -16
  40. package/dist/setup-utils-CjKjaKcG.js.map +1 -0
  41. package/dist/{start-tui-DnAG57vY.js → start-tui-Cbw0kVr3.js} +471 -54
  42. package/dist/start-tui-Cbw0kVr3.js.map +1 -0
  43. package/dist/{steps-JaxH6u0f.js → steps-DUz5lHWu.js} +7 -6
  44. package/dist/{steps-JaxH6u0f.js.map → steps-DUz5lHWu.js.map} +1 -1
  45. package/dist/{telemetry-DL28cCwY.js → telemetry-D3CnLknq.js} +3 -3
  46. package/dist/{telemetry-DL28cCwY.js.map → telemetry-D3CnLknq.js.map} +1 -1
  47. package/dist/{AiOptInRequiredScreen-C-D9tN6r.js → terminal-DwAdsRPX.js} +1047 -85
  48. package/dist/terminal-DwAdsRPX.js.map +1 -0
  49. package/dist/{urls-vkJ5c0ix.js → urls-JN8mo6lU.js} +2 -2
  50. package/dist/{urls-vkJ5c0ix.js.map → urls-JN8mo6lU.js.map} +1 -1
  51. package/dist/{wizard-abort-BRXKRL4F.js → wizard-abort-BPr0xo7i.js} +1 -1
  52. package/dist/{wizard-abort-CLGgMAEe.js → wizard-abort-gHZ7kHYo.js} +3 -3
  53. package/dist/{wizard-abort-CLGgMAEe.js.map → wizard-abort-gHZ7kHYo.js.map} +1 -1
  54. package/dist/wizard-session-G3VWD6hv.js.map +1 -1
  55. package/dist/wizard-ui-WZ48rUgr.js.map +1 -1
  56. package/package.json +1 -1
  57. package/dist/AiOptInRequiredScreen-C-D9tN6r.js.map +0 -1
  58. package/dist/agent-interface-BsuUUPle.js.map +0 -1
  59. package/dist/agent-runner-L_-kJ3y3.js.map +0 -1
  60. package/dist/api-DNS-L-1U.js.map +0 -1
  61. package/dist/file-utils-VAXoyXVA.js +0 -38
  62. package/dist/file-utils-VAXoyXVA.js.map +0 -1
  63. package/dist/playground-C-lpKoKC.js.map +0 -1
  64. package/dist/setup-utils-D87CyNkw.js.map +0 -1
  65. package/dist/start-tui-DnAG57vY.js.map +0 -1
package/dist/bin.js CHANGED
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
- import { $ as getSkillsBaseUrl, P as POSTHOG_DOCS_URL, X as WIZARD_USER_AGENT, _ as SIGNUP_WIZARD_READINESS_CONFIG, a as getLogFilePath, et as VERSION, h as LoggingUI, m as setUI, p as getUI, r as debug, s as logToFile, v as evaluateWizardReadiness, y as getBlockingServiceKeys } from "./debug-BwC7UkGH.js";
3
- import { t as analytics } from "./analytics-CDOujOSQ.js";
4
- import { r as setEntryCommand } from "./telemetry-DL28cCwY.js";
5
- import { n as isUsingTypeScript } from "./setup-utils-D87CyNkw.js";
6
- import { a as getUiHostFromHost, n as getCloudUrlFromRegion } from "./urls-vkJ5c0ix.js";
7
- import { o as handleApiError } from "./api-DNS-L-1U.js";
2
+ import { $ as getSkillsBaseUrl, P as POSTHOG_DOCS_URL, X as WIZARD_USER_AGENT, _ as SIGNUP_WIZARD_READINESS_CONFIG, a as getLogFilePath, et as VERSION, h as LoggingUI, m as setUI, p as getUI, r as debug, s as logToFile, v as evaluateWizardReadiness, y as getBlockingServiceKeys } from "./debug-Br_xCc9s.js";
3
+ import { t as analytics } from "./analytics-B7-uRKIJ.js";
4
+ import { r as setEntryCommand } from "./telemetry-D3CnLknq.js";
5
+ import { n as isUsingTypeScript } from "./setup-utils-CjKjaKcG.js";
6
+ import { a as getUiHostFromHost, n as getCloudUrlFromRegion } from "./urls-JN8mo6lU.js";
7
+ import { o as handleApiError } from "./api-2zPZQONC.js";
8
8
  import "./wizard-session-G3VWD6hv.js";
9
- import { r as runCleanups } from "./wizard-abort-CLGgMAEe.js";
10
- import { n as isNonInteractiveEnvironment } from "./environment-DQPoj9sU.js";
11
- import { S as AUDIT_REPORT_FILE, b as AUDIT_CHECKS_FILE, c as recoverOrphanedSettingsBackups, h as fetchSkillMenu, p as WIZARD_TOOL_NAMES, u as AgentSignals, x as AUDIT_CHECKS_KEY } from "./agent-interface-BsuUUPle.js";
12
- import { i as SPINNER_MESSAGE } from "./registry-BbRzCV5l.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-BL8-vC0V.js";
14
- import { t as IGNORED_DIRS } from "./file-utils-VAXoyXVA.js";
9
+ import { r as runCleanups } from "./wizard-abort-gHZ7kHYo.js";
10
+ import { n as isNonInteractiveEnvironment } from "./environment-CFXsie0G.js";
11
+ import { S as AUDIT_REPORT_FILE, b as AUDIT_CHECKS_FILE, c as recoverOrphanedSettingsBackups, h as fetchSkillMenu, p as WIZARD_TOOL_NAMES, u as AgentSignals, x as AUDIT_CHECKS_KEY } from "./agent-interface-DpkR1mbC.js";
12
+ import { i as SPINNER_MESSAGE } from "./registry-CIjJsxDE.js";
13
+ import { _ as parseRequirementsTxt, a as PRODUCT_SUITE_BLOCK, f as Colors, g as parsePyprojectToml, h as parsePipfile, 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-DWs8JM8J.js";
14
+ import { n as safeReadFile, r as walkProjectFiles, t as IGNORED_DIRS } from "./file-utils-CHAj73KM.js";
15
15
  import { n as readApiKeyFromEnv } from "./env-api-key-MlzJYAvt.js";
16
16
  import { satisfies } from "semver";
17
17
  import yargs from "yargs";
@@ -27,6 +27,7 @@ import { z } from "zod";
27
27
  import { Box, Text, render, useInput } from "ink";
28
28
  import { createContext, createElement, useCallback, useContext, useEffect, useRef, useState } from "react";
29
29
  import { jsx, jsxs } from "react/jsx-runtime";
30
+ import { access, rm } from "node:fs/promises";
30
31
  import * as readline from "node:readline/promises";
31
32
  //#region src/commands/command.ts
32
33
  /** Extract the bare command word(s) from a yargs name spec, dropping positionals and aliases' arg syntax. */
@@ -189,7 +190,7 @@ function runProvision(argv) {
189
190
  }
190
191
  async function provision({ email, region, name, jsonMode }) {
191
192
  try {
192
- const { provisionNewAccount } = await import("./provisioning-DLOiFSM9.js").then((n) => n.n);
193
+ const { provisionNewAccount } = await import("./provisioning-CUwxxByi.js").then((n) => n.n);
193
194
  if (!jsonMode) getUI().log.info(`Provisioning account for ${email} in ${region}...`);
194
195
  emitResult(await provisionNewAccount(email, name, region), jsonMode);
195
196
  process.exit(0);
@@ -254,18 +255,18 @@ const basicIntegrationCommand = {
254
255
  setEntryCommand("integrate");
255
256
  (async () => {
256
257
  if (argv.ci) {
257
- const { runCIInstall } = await import("./ci-install-_9A7tL36.js");
258
+ const { runCIInstall } = await import("./ci-install-CpGSFNDi.js");
258
259
  return runCIInstall(argv);
259
260
  }
260
261
  if (isNonInteractiveEnvironment()) {
261
- const { failNonInteractive } = await import("./non-interactive-csP4yGdA.js");
262
+ const { failNonInteractive } = await import("./non-interactive--4CK1bkn.js");
262
263
  return failNonInteractive();
263
264
  }
264
265
  if (argv.playground) {
265
- const { runPlayground } = await import("./playground-C-lpKoKC.js");
266
+ const { runPlayground } = await import("./playground-De_BxaCh.js");
266
267
  return runPlayground();
267
268
  }
268
- const { runInteractive } = await import("./interactive-DT5dLd7N.js");
269
+ const { runInteractive } = await import("./interactive-lfAs6vF7.js");
269
270
  runInteractive(argv);
270
271
  })();
271
272
  }
@@ -508,6 +509,571 @@ const revenueAnalyticsConfig = {
508
509
  requires: ["posthog-integration"]
509
510
  };
510
511
  //#endregion
512
+ //#region src/lib/warehouse-sources/registry.ts
513
+ const SOURCE_DETECTORS = [
514
+ {
515
+ kind: "Postgres",
516
+ label: "PostgreSQL",
517
+ mode: "in-cli",
518
+ signals: {
519
+ npm: [
520
+ "pg",
521
+ "postgres",
522
+ "postgres.js",
523
+ "knex",
524
+ "sequelize"
525
+ ],
526
+ python: [
527
+ "psycopg",
528
+ "psycopg2",
529
+ "psycopg2-binary",
530
+ "asyncpg"
531
+ ],
532
+ ruby: ["pg"],
533
+ envKeys: [
534
+ /^DATABASE_URL$/,
535
+ /^POSTGRES_/,
536
+ /^PG(HOST|DATABASE|USER|PORT)$/
537
+ ]
538
+ }
539
+ },
540
+ {
541
+ kind: "MySQL",
542
+ label: "MySQL",
543
+ mode: "in-cli",
544
+ signals: {
545
+ npm: ["mysql", "mysql2"],
546
+ python: [
547
+ "pymysql",
548
+ "mysqlclient",
549
+ "mysql-connector-python"
550
+ ],
551
+ ruby: ["mysql2"],
552
+ envKeys: [/^MYSQL_/]
553
+ }
554
+ },
555
+ {
556
+ kind: "MongoDB",
557
+ label: "MongoDB",
558
+ mode: "in-cli",
559
+ signals: {
560
+ npm: ["mongodb", "mongoose"],
561
+ python: ["pymongo", "motor"],
562
+ ruby: ["mongo", "mongoid"],
563
+ envKeys: [/^MONGO(DB)?_/]
564
+ }
565
+ },
566
+ {
567
+ kind: "Snowflake",
568
+ label: "Snowflake",
569
+ mode: "in-cli",
570
+ signals: {
571
+ npm: ["snowflake-sdk"],
572
+ python: ["snowflake-connector-python", "snowflake-sqlalchemy"],
573
+ envKeys: [/^SNOWFLAKE_/]
574
+ }
575
+ },
576
+ {
577
+ kind: "BigQuery",
578
+ label: "BigQuery",
579
+ mode: "in-cli",
580
+ signals: {
581
+ npm: ["@google-cloud/bigquery"],
582
+ python: ["google-cloud-bigquery"],
583
+ envKeys: [/^BIGQUERY_/]
584
+ }
585
+ },
586
+ {
587
+ kind: "Redshift",
588
+ label: "Redshift",
589
+ mode: "in-cli",
590
+ signals: {
591
+ npm: ["node-redshift"],
592
+ python: ["redshift-connector"],
593
+ envKeys: [/^REDSHIFT_/]
594
+ }
595
+ },
596
+ {
597
+ kind: "MSSQL",
598
+ label: "SQL Server",
599
+ mode: "in-cli",
600
+ signals: {
601
+ npm: ["mssql", "tedious"],
602
+ python: ["pyodbc", "pymssql"],
603
+ envKeys: [/^MSSQL_/]
604
+ }
605
+ },
606
+ {
607
+ kind: "Supabase",
608
+ label: "Supabase",
609
+ mode: "in-cli",
610
+ signals: {
611
+ npm: ["@supabase/supabase-js"],
612
+ python: ["supabase"],
613
+ envKeys: [/^SUPABASE_/]
614
+ }
615
+ },
616
+ {
617
+ kind: "ClickHouse",
618
+ label: "ClickHouse",
619
+ mode: "in-cli",
620
+ signals: {
621
+ npm: ["@clickhouse/client"],
622
+ python: ["clickhouse-connect", "clickhouse-driver"],
623
+ envKeys: [/^CLICKHOUSE_/]
624
+ }
625
+ },
626
+ {
627
+ kind: "Convex",
628
+ label: "Convex",
629
+ mode: "in-cli",
630
+ signals: {
631
+ npm: ["convex"],
632
+ python: ["convex"],
633
+ envKeys: [/^CONVEX_/, /^NEXT_PUBLIC_CONVEX_URL$/]
634
+ }
635
+ },
636
+ {
637
+ kind: "Stripe",
638
+ label: "Stripe",
639
+ mode: "in-cli",
640
+ signals: {
641
+ npm: [
642
+ "stripe",
643
+ "@stripe/stripe-js",
644
+ "@stripe/react-stripe-js"
645
+ ],
646
+ python: ["stripe"],
647
+ ruby: ["stripe"],
648
+ envKeys: [/^STRIPE_(SECRET|API)_KEY$/]
649
+ }
650
+ },
651
+ {
652
+ kind: "Clerk",
653
+ label: "Clerk",
654
+ mode: "in-cli",
655
+ signals: {
656
+ npm: [
657
+ "@clerk/nextjs",
658
+ "@clerk/clerk-react",
659
+ "@clerk/backend",
660
+ "@clerk/express",
661
+ "@clerk/fastify",
662
+ "@clerk/remix"
663
+ ],
664
+ envKeys: [/^CLERK_SECRET_KEY$/, /^NEXT_PUBLIC_CLERK_/]
665
+ }
666
+ },
667
+ {
668
+ kind: "Resend",
669
+ label: "Resend",
670
+ mode: "in-cli",
671
+ signals: {
672
+ npm: ["resend"],
673
+ python: ["resend"],
674
+ envKeys: [/^RESEND_API_KEY$/]
675
+ }
676
+ },
677
+ {
678
+ kind: "Shopify",
679
+ label: "Shopify",
680
+ mode: "in-cli",
681
+ signals: {
682
+ npm: ["@shopify/shopify-api", "shopify-api-node"],
683
+ python: ["shopifyapi"],
684
+ envKeys: [/^SHOPIFY_/]
685
+ }
686
+ },
687
+ {
688
+ kind: "Klaviyo",
689
+ label: "Klaviyo",
690
+ mode: "in-cli",
691
+ signals: {
692
+ npm: ["klaviyo-api"],
693
+ python: ["klaviyo-api"],
694
+ envKeys: [/^KLAVIYO_/]
695
+ }
696
+ },
697
+ {
698
+ kind: "Chargebee",
699
+ label: "Chargebee",
700
+ mode: "in-cli",
701
+ signals: {
702
+ npm: ["chargebee"],
703
+ python: ["chargebee"],
704
+ envKeys: [/^CHARGEBEE_/]
705
+ }
706
+ },
707
+ {
708
+ kind: "Paddle",
709
+ label: "Paddle",
710
+ mode: "in-cli",
711
+ signals: {
712
+ npm: ["@paddle/paddle-node-sdk", "@paddle/paddle-js"],
713
+ envKeys: [/^PADDLE_/]
714
+ }
715
+ },
716
+ {
717
+ kind: "Polar",
718
+ label: "Polar",
719
+ mode: "in-cli",
720
+ signals: {
721
+ npm: ["@polar-sh/sdk", "@polar-sh/nextjs"],
722
+ envKeys: [/^POLAR_/]
723
+ }
724
+ },
725
+ {
726
+ kind: "Mailchimp",
727
+ label: "Mailchimp",
728
+ mode: "in-cli",
729
+ signals: {
730
+ npm: ["@mailchimp/mailchimp_marketing"],
731
+ python: ["mailchimp-marketing"],
732
+ envKeys: [/^MAILCHIMP_/]
733
+ }
734
+ },
735
+ {
736
+ kind: "CustomerIO",
737
+ label: "Customer.io",
738
+ mode: "in-cli",
739
+ signals: {
740
+ npm: ["customerio-node"],
741
+ python: ["customerio"],
742
+ envKeys: [/^CUSTOMER_?IO_/]
743
+ }
744
+ },
745
+ {
746
+ kind: "Typeform",
747
+ label: "Typeform",
748
+ mode: "in-cli",
749
+ signals: {
750
+ npm: ["@typeform/api-client"],
751
+ envKeys: [/^TYPEFORM_/]
752
+ }
753
+ },
754
+ {
755
+ kind: "Sentry",
756
+ label: "Sentry",
757
+ mode: "in-cli",
758
+ signals: {
759
+ npm: [
760
+ "@sentry/node",
761
+ "@sentry/browser",
762
+ "@sentry/react",
763
+ "@sentry/nextjs"
764
+ ],
765
+ python: ["sentry-sdk"],
766
+ ruby: ["sentry-ruby"]
767
+ }
768
+ },
769
+ {
770
+ kind: "Salesforce",
771
+ label: "Salesforce",
772
+ mode: "deep-link",
773
+ signals: {
774
+ npm: ["jsforce"],
775
+ python: ["simple-salesforce"],
776
+ envKeys: [/^SALESFORCE_/]
777
+ }
778
+ },
779
+ {
780
+ kind: "Hubspot",
781
+ label: "HubSpot",
782
+ mode: "deep-link",
783
+ signals: {
784
+ npm: ["@hubspot/api-client"],
785
+ python: ["hubspot-api-client"],
786
+ envKeys: [/^HUBSPOT_/]
787
+ }
788
+ },
789
+ {
790
+ kind: "Zendesk",
791
+ label: "Zendesk",
792
+ mode: "deep-link",
793
+ signals: {
794
+ npm: ["node-zendesk"],
795
+ python: ["zenpy"],
796
+ envKeys: [/^ZENDESK_/]
797
+ }
798
+ },
799
+ {
800
+ kind: "Intercom",
801
+ label: "Intercom",
802
+ mode: "deep-link",
803
+ signals: {
804
+ npm: ["intercom-client", "@intercom/messenger-js-sdk"],
805
+ python: ["python-intercom"],
806
+ envKeys: [/^INTERCOM_/]
807
+ }
808
+ },
809
+ {
810
+ kind: "Linear",
811
+ label: "Linear",
812
+ mode: "deep-link",
813
+ signals: {
814
+ npm: ["@linear/sdk"],
815
+ envKeys: [/^LINEAR_API_KEY$/]
816
+ }
817
+ }
818
+ ];
819
+ //#endregion
820
+ //#region src/lib/warehouse-sources/detect.ts
821
+ /**
822
+ * Data warehouse source detection.
823
+ *
824
+ * Scans a project for codebase signals (npm/python/ruby deps, `.env` key
825
+ * names) and matches them against the `SOURCE_DETECTORS` registry. Pure
826
+ * function: no store mutations, no UI calls. Reads files locally — only the
827
+ * wizard process does this; the agent never sees secret values.
828
+ *
829
+ * Note we only read `.env` KEY NAMES, never values.
830
+ */
831
+ const MAX_DEPTH = 3;
832
+ /**
833
+ * Detect which warehouse sources the project at `installDir` appears to use.
834
+ * Returns one `DetectedSource` per matched registry entry (kinds are unique,
835
+ * so the result is naturally deduped).
836
+ */
837
+ function detectWarehouseSources(installDir) {
838
+ const signals = collectSignals$1(installDir);
839
+ const detected = [];
840
+ for (const detector of SOURCE_DETECTORS) {
841
+ const match = matchDetector(detector, signals);
842
+ if (match) detected.push({
843
+ kind: detector.kind,
844
+ label: detector.label,
845
+ mode: detector.mode,
846
+ matchedSignal: match
847
+ });
848
+ }
849
+ return detected;
850
+ }
851
+ /** Returns a human-readable description of the first matching signal, or null. */
852
+ function matchDetector(detector, signals) {
853
+ const { npm, python, ruby, envKeys } = detector.signals;
854
+ const npmHit = npm?.find((dep) => signals.npm.has(dep));
855
+ if (npmHit) return `found \`${npmHit}\` in package.json`;
856
+ const pyHit = python?.find((dep) => signals.python.has(dep));
857
+ if (pyHit) return `found \`${pyHit}\` in Python dependencies`;
858
+ const rubyHit = ruby?.find((gem) => signals.ruby.has(gem));
859
+ if (rubyHit) return `found \`${rubyHit}\` in Gemfile`;
860
+ if (envKeys) {
861
+ for (const key of signals.envKeys) if (envKeys.some((re) => re.test(key))) return `found \`${key}\` in .env`;
862
+ }
863
+ return null;
864
+ }
865
+ function collectSignals$1(installDir) {
866
+ const signals = {
867
+ npm: /* @__PURE__ */ new Set(),
868
+ python: /* @__PURE__ */ new Set(),
869
+ ruby: /* @__PURE__ */ new Set(),
870
+ envKeys: /* @__PURE__ */ new Set()
871
+ };
872
+ walkProjectFiles(installDir, (name, fullPath) => ingestFile(name, fullPath, signals), MAX_DEPTH);
873
+ return signals;
874
+ }
875
+ /**
876
+ * Route a file to the right parser BY NAME, reading its contents only when the
877
+ * name matches one of the ~6 manifests we care about. Checking the name first
878
+ * avoids slurping every file in the tree (lockfiles, binaries, assets) into
879
+ * memory just to discard it.
880
+ */
881
+ function ingestFile(name, fullPath, signals) {
882
+ const ingest = ingestorFor(name);
883
+ if (!ingest) return;
884
+ const content = safeReadFile(fullPath);
885
+ if (content === null) return;
886
+ ingest(content, signals);
887
+ }
888
+ /** Pick the parser for a manifest filename, or null if it's not one we read. */
889
+ function ingestorFor(name) {
890
+ if (name === "package.json") return addNpmDeps;
891
+ if (name === "requirements.txt") return (c, s) => parseRequirementsTxt(c).forEach((d) => s.python.add(d));
892
+ if (name === "pyproject.toml") return (c, s) => parsePyprojectToml(c).forEach((d) => s.python.add(d));
893
+ if (name === "Pipfile") return (c, s) => parsePipfile(c).forEach((d) => s.python.add(d));
894
+ if (name === "Gemfile") return (c, s) => parseGemfile(c).forEach((g) => s.ruby.add(g));
895
+ if (name.startsWith(".env")) return (c, s) => parseEnvKeys(c).forEach((k) => s.envKeys.add(k));
896
+ return null;
897
+ }
898
+ function addNpmDeps(content, signals) {
899
+ try {
900
+ const pkg = JSON.parse(content);
901
+ for (const dep of Object.keys({
902
+ ...pkg.dependencies,
903
+ ...pkg.devDependencies
904
+ })) signals.npm.add(dep);
905
+ } catch (error) {
906
+ analytics.captureException(error instanceof Error ? error : new Error(String(error)), { step: "detectWarehouseSources.parsePackageJson" });
907
+ }
908
+ }
909
+ /** Extract gem names from `gem 'name'` declarations. */
910
+ function parseGemfile(content) {
911
+ const gems = [];
912
+ for (const match of content.matchAll(/^\s*gem\s+['"]([^'"]+)['"]/gm)) gems.push(match[1]);
913
+ return gems;
914
+ }
915
+ /** Extract KEY NAMES from a dotenv file. Values are intentionally discarded. */
916
+ function parseEnvKeys(content) {
917
+ const keys = [];
918
+ for (const line of content.split("\n")) {
919
+ const trimmed = line.trim();
920
+ if (!trimmed || trimmed.startsWith("#")) continue;
921
+ const match = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/);
922
+ if (match) keys.push(match[1]);
923
+ }
924
+ return keys;
925
+ }
926
+ //#endregion
927
+ //#region src/lib/programs/warehouse-source/detect.ts
928
+ /**
929
+ * Warehouse-source program detection step.
930
+ *
931
+ * Thin adapter over `detectWarehouseSources` that writes results into
932
+ * frameworkContext for the intro screen, plus the `[ABORT]` cases the
933
+ * data-warehouse-source-setup skill can emit.
934
+ */
935
+ /** frameworkContext key holding the detected sources (set on success). */
936
+ const DETECTED_WAREHOUSE_SOURCES_KEY = "detectedWarehouseSources";
937
+ /**
938
+ * Read the detected sources out of frameworkContext. Single accessor shared by
939
+ * the intro screen and the prompt builder so the key + cast live in one place.
940
+ */
941
+ function getDetectedWarehouseSources(session) {
942
+ return session.frameworkContext["detectedWarehouseSources"] ?? [];
943
+ }
944
+ /** `[ABORT] <reason>` cases the skill can emit. */
945
+ const WAREHOUSE_ABORT_CASES = [{
946
+ match: /^no data sources? detected\.?$/i,
947
+ message: "No data source detected",
948
+ body: "The agent could not confirm a data warehouse source to connect. Run this command from a project that uses a supported source (a database, Stripe, etc.).",
949
+ docsUrl: "https://posthog.com/docs/data-warehouse"
950
+ }, {
951
+ match: /^source creation failed\.?$/i,
952
+ message: "Source creation failed",
953
+ body: "PostHog could not create the data warehouse source with the credentials provided. Double-check the connection details and try again, or set the source up directly in the PostHog app.",
954
+ docsUrl: "https://posthog.com/docs/data-warehouse"
955
+ }];
956
+ /**
957
+ * Scan `session.installDir` for warehouse-source signals. Writes the detected
958
+ * sources (or a `detectError`) into frameworkContext for the intro screen.
959
+ */
960
+ function detectWarehousePrerequisites(session, setFrameworkContext) {
961
+ const fail = (error) => setFrameworkContext("detectError", error);
962
+ const installDir = session.installDir;
963
+ if (!existsSync(installDir)) {
964
+ fail({
965
+ kind: "bad-directory",
966
+ path: installDir,
967
+ reason: "missing"
968
+ });
969
+ return;
970
+ }
971
+ try {
972
+ if (!statSync(installDir).isDirectory()) {
973
+ fail({
974
+ kind: "bad-directory",
975
+ path: installDir,
976
+ reason: "not-dir"
977
+ });
978
+ return;
979
+ }
980
+ } catch (error) {
981
+ analytics.captureException(error instanceof Error ? error : new Error(String(error)), {
982
+ step: "detectWarehousePrerequisites.stat",
983
+ path: installDir
984
+ });
985
+ fail({
986
+ kind: "bad-directory",
987
+ path: installDir,
988
+ reason: "unreadable"
989
+ });
990
+ return;
991
+ }
992
+ const sources = detectWarehouseSources(installDir);
993
+ if (sources.length === 0) {
994
+ fail({ kind: "no-sources" });
995
+ return;
996
+ }
997
+ setFrameworkContext(DETECTED_WAREHOUSE_SOURCES_KEY, sources);
998
+ }
999
+ //#endregion
1000
+ //#region src/lib/programs/warehouse-source/steps.ts
1001
+ const WAREHOUSE_SOURCE_PROGRAM = [
1002
+ {
1003
+ id: "detect",
1004
+ label: "Detecting data sources",
1005
+ onReady: (ctx) => detectWarehousePrerequisites(ctx.session, ctx.setFrameworkContext)
1006
+ },
1007
+ {
1008
+ id: "intro",
1009
+ label: "Welcome",
1010
+ screenId: "warehouse-intro",
1011
+ gate: (session) => session.setupConfirmed
1012
+ },
1013
+ {
1014
+ id: "auth",
1015
+ label: "Authentication",
1016
+ screenId: "auth",
1017
+ isComplete: (session) => session.credentials !== null
1018
+ },
1019
+ {
1020
+ id: "run",
1021
+ label: "Data warehouse",
1022
+ screenId: "run",
1023
+ isComplete: (session) => session.runPhase === "completed" || session.runPhase === "error"
1024
+ },
1025
+ {
1026
+ id: "outro",
1027
+ label: "Done",
1028
+ screenId: "outro",
1029
+ isComplete: (session) => session.outroDismissed
1030
+ },
1031
+ {
1032
+ id: "skills",
1033
+ label: "Skills",
1034
+ screenId: "keep-skills"
1035
+ }
1036
+ ];
1037
+ //#endregion
1038
+ //#region src/lib/programs/warehouse-source/index.ts
1039
+ /**
1040
+ * Inject the detected sources (and their creation mode) into the prompt so the
1041
+ * skill knows what to set up. The *how* — in-CLI creation vs deep-link, field
1042
+ * collection, validation — lives in the skill, not here.
1043
+ */
1044
+ function buildPrompt(session) {
1045
+ const sources = getDetectedWarehouseSources(session);
1046
+ if (sources.length === 0) return "Set up a data warehouse source for this project.";
1047
+ return [
1048
+ "The wizard detected the following data warehouse sources in this project:",
1049
+ ...sources.map((s) => `- ${s.label} (kind: ${s.kind}, mode: ${s.mode}) — ${s.matchedSignal}`),
1050
+ "",
1051
+ "Set these up in PostHog following the skill instructions: create `in-cli` sources directly via the PostHog MCP after collecting credentials; for `deep-link` sources, provide the user the pre-filled new-source URL."
1052
+ ].join("\n");
1053
+ }
1054
+ const warehouseSourceConfig = {
1055
+ command: "warehouse",
1056
+ description: "Detect and connect a data warehouse source (Postgres, Stripe, …)",
1057
+ id: "warehouse-source",
1058
+ skillId: "data-warehouse-source-setup",
1059
+ steps: WAREHOUSE_SOURCE_PROGRAM,
1060
+ getContentBlocks: getContentBlocks$2,
1061
+ reportFile: "posthog-warehouse-report.md",
1062
+ allowedTools: ["Agent"],
1063
+ run: (session) => Promise.resolve({
1064
+ skillId: "data-warehouse-source-setup",
1065
+ integrationLabel: "data-warehouse-source-setup",
1066
+ customPrompt: () => buildPrompt(session),
1067
+ successMessage: "Data warehouse source connected!",
1068
+ reportFile: "posthog-warehouse-report.md",
1069
+ docsUrl: "https://posthog.com/docs/data-warehouse",
1070
+ spinnerMessage: "Connecting your data source...",
1071
+ estimatedDurationMinutes: 5,
1072
+ abortCases: WAREHOUSE_ABORT_CASES
1073
+ }),
1074
+ requires: ["posthog-integration"]
1075
+ };
1076
+ //#endregion
511
1077
  //#region src/lib/programs/agent-skill/steps.ts
512
1078
  const AGENT_SKILL_STEPS = [
513
1079
  {
@@ -846,7 +1412,7 @@ const EVENTS_AUDIT_SEED_CHECKS = [
846
1412
  //#endregion
847
1413
  //#region src/lib/programs/events-audit/index.ts
848
1414
  const SETUP_REPORT_FILE = "posthog-events-audit-report.md";
849
- const DOCS_URL$2 = "https://posthog.com/docs/product-analytics/best-practices";
1415
+ const DOCS_URL$3 = "https://posthog.com/docs/product-analytics/best-practices";
850
1416
  const eventsAuditConfig = {
851
1417
  command: "events-audit",
852
1418
  description: "Audit PostHog event tracking in this project",
@@ -868,7 +1434,7 @@ const eventsAuditConfig = {
868
1434
  successMessage: "Events audit complete! You can view the report at ./posthog-events-audit-report.md",
869
1435
  estimatedDurationMinutes: 5,
870
1436
  reportFile: SETUP_REPORT_FILE,
871
- docsUrl: DOCS_URL$2,
1437
+ docsUrl: DOCS_URL$3,
872
1438
  errorMessage: "Events audit failed",
873
1439
  additionalFeatureQueue: session.additionalFeatureQueue,
874
1440
  customPrompt: (ctx) => `Audit PostHog event capture in this project. Do not modify any project files — produce a read-only report only.
@@ -886,7 +1452,7 @@ Project context:
886
1452
  message: "Your events audit was successful",
887
1453
  reportFile: SETUP_REPORT_FILE,
888
1454
  changes: [],
889
- docsUrl: DOCS_URL$2,
1455
+ docsUrl: DOCS_URL$3,
890
1456
  continueUrl: sess.signup && cloudUrl ? `${cloudUrl}/products?source=wizard` : void 0,
891
1457
  dashboardUrl: sess.dashboardUrl ?? (cloudUrl ? `${cloudUrl}/dashboard` : void 0),
892
1458
  notebookUrl: sess.notebookUrl ?? void 0
@@ -2415,15 +2981,15 @@ const getContentBlocks = (store) => pace([
2415
2981
  ]);
2416
2982
  //#endregion
2417
2983
  //#region src/lib/programs/error-tracking-upload-source-maps/index.ts
2418
- const REPORT_FILE = "posthog-source-maps-report.md";
2419
- const DOCS_URL = "https://posthog.com/docs/error-tracking/upload-source-maps";
2984
+ const REPORT_FILE$1 = "posthog-source-maps-report.md";
2985
+ const DOCS_URL$1 = "https://posthog.com/docs/error-tracking/upload-source-maps";
2420
2986
  const errorTrackingUploadSourceMapsConfig = {
2421
2987
  command: "upload-source-maps",
2422
2988
  description: "Upload source maps to PostHog Error Tracking",
2423
2989
  id: "error-tracking-upload-source-maps",
2424
2990
  requiresAi: true,
2425
2991
  steps: ERROR_TRACKING_UPLOAD_SOURCE_MAPS_PROGRAM,
2426
- reportFile: REPORT_FILE,
2992
+ reportFile: REPORT_FILE$1,
2427
2993
  getContentBlocks,
2428
2994
  requires: ["posthog-integration"],
2429
2995
  run: (session) => {
@@ -2433,8 +2999,8 @@ const errorTrackingUploadSourceMapsConfig = {
2433
2999
  return Promise.resolve({
2434
3000
  integrationLabel: "error-tracking-upload-source-maps",
2435
3001
  successMessage: "Source maps wired up!",
2436
- reportFile: REPORT_FILE,
2437
- docsUrl: DOCS_URL,
3002
+ reportFile: REPORT_FILE$1,
3003
+ docsUrl: DOCS_URL$1,
2438
3004
  spinnerMessage: "Wiring up source maps...",
2439
3005
  estimatedDurationMinutes: 3,
2440
3006
  abortCases: SOURCE_MAPS_ABORT_CASES,
@@ -2460,14 +3026,363 @@ const errorTrackingUploadSourceMapsConfig = {
2460
3026
  return {
2461
3027
  kind: "success",
2462
3028
  message: "Source maps wired up!",
2463
- reportFile: REPORT_FILE,
2464
- docsUrl: DOCS_URL
3029
+ reportFile: REPORT_FILE$1,
3030
+ docsUrl: DOCS_URL$1
2465
3031
  };
2466
3032
  }
2467
3033
  });
2468
3034
  }
2469
3035
  };
2470
3036
  //#endregion
3037
+ //#region src/lib/programs/self-driving/detect.ts
3038
+ /**
3039
+ * Self-driving prerequisite detection + abort vocabulary.
3040
+ *
3041
+ * The only thing worth verifying before auth is local and cheap: that
3042
+ * `session.installDir` is a real, readable directory. We deliberately do
3043
+ * NOT require the base posthog-integration report to be present — it is a
3044
+ * report many users never commit, and `requires: ['posthog-integration']`
3045
+ * is metadata, not a hard runtime gate. Real readiness (integration state
3046
+ * + beta access) is established by the agent's STEP 1 Signals API probe at
3047
+ * the start of the run. The beta gates (the `product-autonomy` access flag
3048
+ * and `signals-scout` enrollment — PostHog-side flag names, unchanged by
3049
+ * the wizard-side "self-driving" rename) are PostHog-internal flags with no
3050
+ * customer-facing read API, which is why that probe lives in the run and
3051
+ * emits a structured `[ABORT]` when the product is not available.
3052
+ */
3053
+ /**
3054
+ * `[ABORT] <reason>` cases the self-driving skill can emit. The
3055
+ * reason strings are part of the skill contract — the context-mill
3056
+ * `self-driving-setup` skill emits these exact strings.
3057
+ */
3058
+ const SELF_DRIVING_ABORT_CASES = [
3059
+ {
3060
+ match: /^self-driving is not available for this project$/i,
3061
+ message: "PostHog Self-driving is not available for this project",
3062
+ body: "Self-driving is in beta and is enabled per team by PostHog. This project does not appear to have access yet. Reach out to your PostHog contact (or wizard@posthog.com) to join the beta, then run the wizard again."
3063
+ },
3064
+ {
3065
+ match: /^github connection declined$/i,
3066
+ message: "GitHub connection required",
3067
+ body: "Self-driving needs GitHub access to research issues in your code and open fixes, so setup cannot finish without it. Nothing was left half-configured. When you are ready to install the PostHog GitHub App, run the wizard again."
3068
+ },
3069
+ {
3070
+ match: /^requires-interactive-mode$/i,
3071
+ message: "Interactive terminal required",
3072
+ body: "Self-driving setup asks questions along the way (GitHub and issue trackers), so it needs an interactive terminal. Run the wizard outside CI / non-interactive mode."
3073
+ },
3074
+ {
3075
+ match: /^requirements-incomplete$/i,
3076
+ message: "Setup needs your input",
3077
+ body: "The wizard could not collect the answers this setup needs (the environment was non-interactive, or the question budget ran out). Nothing was left half-configured. Run the wizard again in an interactive terminal."
3078
+ }
3079
+ ];
3080
+ /**
3081
+ * Verify `session.installDir` is a readable directory. Writes a
3082
+ * `SelfDrivingDetectError` to frameworkContext on failure — the intro
3083
+ * screen renders it and blocks.
3084
+ */
3085
+ function detectSelfDrivingPrerequisites(session, setFrameworkContext) {
3086
+ const fail = (error) => setFrameworkContext("detectError", error);
3087
+ const installDir = session.installDir;
3088
+ if (!existsSync(installDir)) {
3089
+ fail({
3090
+ kind: "bad-directory",
3091
+ path: installDir,
3092
+ reason: "missing"
3093
+ });
3094
+ return;
3095
+ }
3096
+ try {
3097
+ if (!statSync(installDir).isDirectory()) {
3098
+ fail({
3099
+ kind: "bad-directory",
3100
+ path: installDir,
3101
+ reason: "not-dir"
3102
+ });
3103
+ return;
3104
+ }
3105
+ } catch {
3106
+ fail({
3107
+ kind: "bad-directory",
3108
+ path: installDir,
3109
+ reason: "unreadable"
3110
+ });
3111
+ return;
3112
+ }
3113
+ }
3114
+ //#endregion
3115
+ //#region src/lib/programs/self-driving/steps.ts
3116
+ const SELF_DRIVING_PROGRAM = [
3117
+ {
3118
+ id: "detect",
3119
+ label: "Detecting prerequisites",
3120
+ onReady: (ctx) => detectSelfDrivingPrerequisites(ctx.session, ctx.setFrameworkContext)
3121
+ },
3122
+ {
3123
+ id: "intro",
3124
+ label: "Welcome",
3125
+ screenId: "self-driving-intro",
3126
+ gate: (session) => session.setupConfirmed
3127
+ },
3128
+ HEALTH_CHECK_STEP,
3129
+ {
3130
+ id: "auth",
3131
+ label: "Authentication",
3132
+ screenId: "auth",
3133
+ isComplete: (session) => session.credentials !== null
3134
+ },
3135
+ {
3136
+ id: "run",
3137
+ label: "Self-driving",
3138
+ screenId: "run",
3139
+ isComplete: (session) => session.runPhase === "completed" || session.runPhase === "error"
3140
+ },
3141
+ {
3142
+ id: "outro",
3143
+ label: "Done",
3144
+ screenId: "outro",
3145
+ isComplete: (session) => session.outroDismissed
3146
+ }
3147
+ ];
3148
+ //#endregion
3149
+ //#region src/lib/programs/self-driving/prompt.ts
3150
+ /**
3151
+ * Build the self-driving run prompt. The installed
3152
+ * `self-driving-setup` skill is the source of truth for the HOW of
3153
+ * every step (which MCP tools to call, which sources/scouts apply, how
3154
+ * to verify); this prompt carries the order, the wizard-specific
3155
+ * mechanics (wizard_ask, abort signals), and the project URLs.
3156
+ */
3157
+ function buildSelfDrivingPrompt(ctx) {
3158
+ const uiHost = getUiHostFromHost(ctx.host).replace(/\/$/, "");
3159
+ const projectBase = `${uiHost}/project/${ctx.projectId}`;
3160
+ const integrationsSettingsUrl = `${projectBase}/settings/environment-integrations`;
3161
+ const orgAiSettingsUrl = `${uiHost}/settings/organization#organization-ai-consent`;
3162
+ const newWarehouseSourceUrl = `${projectBase}/pipeline/new/source`;
3163
+ const inboxUrl = `${projectBase}/inbox`;
3164
+ const optIn = (value) => value === true ? "ON" : value === false ? "OFF" : "unknown";
3165
+ const optIns = ctx.teamProductOptIns;
3166
+ return `You are setting up PostHog Self-driving for this project: you will enable the right signal sources, make sure GitHub is connected, tune the scout fleet, design custom scouts for what this product uniquely needs, and hand the user a configured inbox.
3167
+
3168
+ Project URLs:
3169
+ - Integrations settings (GitHub App install): ${integrationsSettingsUrl}
3170
+ - Organization AI settings: ${orgAiSettingsUrl}
3171
+ - New data warehouse source (Linear / Zendesk / GitHub issues / pganalyze): ${newWarehouseSourceUrl}
3172
+ - Self-driving inbox: ${inboxUrl}
3173
+
3174
+ Project state read at auth time (PostHog project settings — authoritative
3175
+ for whether a product is enabled, regardless of what this repo
3176
+ instruments; products are often instrumented from other repos or the
3177
+ snippet, so repo evidence may rule a product IN but never OUT):
3178
+ - Session replay recording: ${optIn(optIns?.sessionReplay)}
3179
+ - Exception autocapture (error tracking): ${optIn(optIns?.exceptionAutocapture)}
3180
+ - Surveys: ${optIn(optIns?.surveys)}
3181
+
3182
+ The installed skill is the source of truth for the HOW of every step:
3183
+ which MCP tools to call, which sources and scouts apply to this product,
3184
+ and how to verify each change. The STEPS below give the order and the
3185
+ wizard-specific mechanics — read the matching skill reference before
3186
+ doing the work, and do not invent steps the skill doesn't describe.
3187
+
3188
+ Before doing any work, create your FULL task list in a single TaskCreate
3189
+ call so the user can follow your progress in the TUI. Use exactly these
3190
+ tasks, in this order:
3191
+ 1. Check Self-driving access
3192
+ 2. Read project and current Self-driving state
3193
+ 3. Connect GitHub (required)
3194
+ 4. Enable signal sources
3195
+ 5. Offer issue-tracker integrations
3196
+ 6. Configure the scout fleet
3197
+ 7. Design custom scouts
3198
+ 8. Write report and hand off
3199
+ Drive the list with TaskUpdate — mark a task in_progress when you start
3200
+ it and completed when done. If a step turns out to be a no-op (e.g.
3201
+ GitHub is already connected), still mark its task completed.
3202
+
3203
+ Wizard mechanics:
3204
+ - Ask the user things ONLY with the wizard_ask MCP tool, and batch
3205
+ related questions (e.g. one multi-select for all issue trackers, not
3206
+ one ask per tool). The per-run ask budget is limited.
3207
+ - If wizard_ask is unavailable (CI / non-interactive), emit
3208
+ ${AgentSignals.ABORT} requires-interactive-mode and halt.
3209
+ - When a step requires the user to do something in the browser, give
3210
+ them the exact URL inside the wizard_ask prompt text — do not try to
3211
+ open a browser yourself.
3212
+ - Emit ${AgentSignals.STATUS} lines as you complete each step so the
3213
+ user sees progress.
3214
+
3215
+ Follow these steps IN ORDER. Do not skip or reorder.
3216
+
3217
+ STEP 1 — Check Self-driving access. (skill: "Check access")
3218
+ Probe the Signals API as the skill describes. If the API is not
3219
+ available for this project (permission or not-found errors), emit
3220
+ ${AgentSignals.ABORT} self-driving is not available for this project
3221
+ and halt.
3222
+
3223
+ STEP 2 — Read project and current Signals state. (skill: "Read context")
3224
+ If ./posthog-setup-report.md exists, read it as a strong hint for what
3225
+ THIS repo instruments — but it is often absent (users frequently don't
3226
+ commit it), so do NOT depend on it. Combine whatever you find with the
3227
+ project-state block above and the skill's server-side usage probes —
3228
+ repo evidence rules products in, never out. Do a light scan ONLY for
3229
+ what neither covers. List the currently enabled signal sources so every
3230
+ later write is idempotent.
3231
+
3232
+ STEP 3 — Connect GitHub. REQUIRED. (skill: "Connect GitHub")
3233
+ Signals cannot research or fix issues without code access. Check for
3234
+ an existing GitHub integration first; if absent, send the user to
3235
+ ${integrationsSettingsUrl} via wizard_ask and verify the connection
3236
+ after they confirm. If the user cannot connect now, emit
3237
+ ${AgentSignals.ABORT} github connection declined
3238
+ and halt — never finish setup without GitHub.
3239
+
3240
+ STEP 4 — Enable signal sources. (skill: "Enable sources")
3241
+ Enable the sources that match what this product actually uses, per
3242
+ the skill. Never enable a source for a tool the user hasn't
3243
+ confirmed they use.
3244
+
3245
+ STEP 5 — Offer issue-tracker integrations. (skill: "Connected tools")
3246
+ One batched multi-select wizard_ask for the external tools the skill
3247
+ lists. The run auto-connects the ones it can (GitHub Issues, and
3248
+ Linear via a one-click OAuth link), verifying each with a single
3249
+ silent check — never nudge. It arms the rest as dormant responders to
3250
+ finish later: for tools it can't auto-connect (Zendesk, pganalyze) it
3251
+ never sends the user to paste credentials and never re-prompts. Enable
3252
+ a source only for a tool the user picked.
3253
+
3254
+ STEP 6 — Configure the scout fleet. (skill: "Scouts")
3255
+ Materialize the fleet and disable the scouts whose product surface
3256
+ this project lacks, per the skill.
3257
+
3258
+ STEP 7 — Design custom scouts for this product. (skill: "Custom scouts")
3259
+ You are the only actor that has read this repo — turn that into
3260
+ coverage per the skill: a real gap analysis of the project's
3261
+ watchable surfaces against what the canonical fleet already covers,
3262
+ then custom scouts for the uncovered ones. Keep scout bodies
3263
+ high-level: describe the behavior and signal conditions to watch,
3264
+ referencing repo evidence by file/function name — never paste raw
3265
+ source, secrets, env values, or customer data into a scout body.
3266
+ Never edit canonical scout bodies. Propose all candidates in ONE
3267
+ batched wizard_ask
3268
+ before creating anything; the user declining everything (or finding
3269
+ no gap at all) is a valid outcome, not an abort. Mark the task
3270
+ completed either way.
3271
+
3272
+ STEP 8 — Write the report and hand off. (skill: "Report")
3273
+ Write the report per the skill, including follow-ups for anything
3274
+ deferred. Tell the user findings will start appearing in their inbox
3275
+ at ${inboxUrl} within about 30 minutes.`;
3276
+ }
3277
+ //#endregion
3278
+ //#region src/lib/programs/self-driving/content/tips.ts
3279
+ const SELF_DRIVING_TIPS = [
3280
+ {
3281
+ id: "what-is-a-signal-source",
3282
+ title: "What's a signal source?",
3283
+ description: "A signal source is a PostHog product or connected tool — errors, session replays, support, GitHub or Linear issues — that feeds findings into your Self-driving inbox."
3284
+ },
3285
+ {
3286
+ id: "what-is-a-scout",
3287
+ title: "What's a scout?",
3288
+ description: "Scouts are scheduled checks that scan your data and flag issues — a spike in errors, a dropping funnel — straight to your inbox."
3289
+ },
3290
+ {
3291
+ id: "findings-in-inbox",
3292
+ title: "Findings land in your inbox",
3293
+ description: "Once setup finishes, PostHog starts scanning within ~30 minutes and surfaces what it finds in your Self-driving inbox — grouped, researched, and ready to act on."
3294
+ }
3295
+ ];
3296
+ const getTips = () => SELF_DRIVING_TIPS;
3297
+ //#endregion
3298
+ //#region src/lib/programs/self-driving/index.ts
3299
+ const SELF_DRIVING_SKILL_ID = "self-driving-setup";
3300
+ const REPORT_FILE = "posthog-self-driving-report.md";
3301
+ const DOCS_URL = "https://posthog.com/docs";
3302
+ const SUCCESS_MESSAGE = "Self-driving is on! PostHog will start scanning within ~30 minutes and surface findings in your inbox.";
3303
+ const WIZARD_MARKER = ".posthog-wizard";
3304
+ /**
3305
+ * Remove the installed setup skill. It is transient orchestration
3306
+ * knowledge (unlike integration skills such as the Next.js one, which
3307
+ * are worth keeping for the user's coding agents), so the program
3308
+ * cleans it up instead of showing the keep-skills prompt. Marker-
3309
+ * guarded: only directories the wizard installed are touched.
3310
+ */
3311
+ async function removeInstalledSkill(installDir) {
3312
+ const skillDir = join(installDir, ".claude", "skills", SELF_DRIVING_SKILL_ID);
3313
+ try {
3314
+ await access(join(skillDir, WIZARD_MARKER));
3315
+ } catch {
3316
+ return;
3317
+ }
3318
+ await rm(skillDir, {
3319
+ recursive: true,
3320
+ force: true
3321
+ }).catch(() => void 0);
3322
+ }
3323
+ const run = {
3324
+ skillId: SELF_DRIVING_SKILL_ID,
3325
+ integrationLabel: SELF_DRIVING_SKILL_ID,
3326
+ customPrompt: buildSelfDrivingPrompt,
3327
+ successMessage: SUCCESS_MESSAGE,
3328
+ reportFile: REPORT_FILE,
3329
+ docsUrl: DOCS_URL,
3330
+ spinnerMessage: "Setting up PostHog Self-driving...",
3331
+ estimatedDurationMinutes: 10,
3332
+ abortCases: SELF_DRIVING_ABORT_CASES,
3333
+ maxQuestions: 13,
3334
+ richLinks: true,
3335
+ askTimeoutMs: 1800 * 1e3,
3336
+ postRun: async (session) => {
3337
+ await removeInstalledSkill(session.installDir);
3338
+ },
3339
+ buildOutroData: (_session, credentials) => {
3340
+ return {
3341
+ kind: "success",
3342
+ message: "Self-driving is on. PostHog is scanning your project — first findings hit your inbox within ~30 minutes.",
3343
+ primaryLink: {
3344
+ label: "Your Self-driving inbox",
3345
+ url: `${getUiHostFromHost(credentials.host).replace(/\/$/, "")}/project/${credentials.projectId}/inbox`
3346
+ },
3347
+ nextSteps: {
3348
+ heading: "In your inbox you can:",
3349
+ items: [
3350
+ "Review the findings PostHog surfaces",
3351
+ "Triage what matters and dismiss the noise",
3352
+ "Kick off fixes and open issues"
3353
+ ]
3354
+ },
3355
+ reportFile: REPORT_FILE
3356
+ };
3357
+ }
3358
+ };
3359
+ const selfDrivingConfig = {
3360
+ ...createSkillProgram({
3361
+ skillId: SELF_DRIVING_SKILL_ID,
3362
+ command: "self-driving",
3363
+ id: "self-driving",
3364
+ description: "Set up PostHog Self-driving for this project",
3365
+ integrationLabel: SELF_DRIVING_SKILL_ID,
3366
+ successMessage: SUCCESS_MESSAGE,
3367
+ reportFile: REPORT_FILE,
3368
+ docsUrl: DOCS_URL,
3369
+ spinnerMessage: "Setting up PostHog Self-driving...",
3370
+ estimatedDurationMinutes: 10,
3371
+ requires: ["posthog-integration"],
3372
+ abortCases: SELF_DRIVING_ABORT_CASES
3373
+ }),
3374
+ steps: SELF_DRIVING_PROGRAM,
3375
+ run,
3376
+ getTips,
3377
+ getContentBlocks: (store) => {
3378
+ const blocks = getContentBlocks$2(store);
3379
+ return blocks.map((b, i) => i === blocks.length - 1 && typeof b === "object" ? {
3380
+ ...b,
3381
+ pause: 5e3
3382
+ } : b);
3383
+ }
3384
+ };
3385
+ //#endregion
2471
3386
  //#region src/lib/programs/mcp/index.ts
2472
3387
  const mcpAddConfig = {
2473
3388
  id: "mcp-add",
@@ -2583,12 +3498,14 @@ const agentSkillConfig = {
2583
3498
  const PROGRAM_REGISTRY = [
2584
3499
  posthogIntegrationConfig,
2585
3500
  revenueAnalyticsConfig,
3501
+ warehouseSourceConfig,
2586
3502
  errorTrackingUploadSourceMapsConfig,
2587
3503
  auditConfig,
2588
3504
  eventsAuditConfig,
2589
3505
  posthogDoctorConfig,
2590
3506
  webAnalyticsDoctorConfig,
2591
3507
  migrationConfig,
3508
+ selfDrivingConfig,
2592
3509
  agentSkillConfig,
2593
3510
  mcpAddConfig,
2594
3511
  mcpRemoveConfig,
@@ -2603,12 +3520,14 @@ const PROGRAM_REGISTRY = [
2603
3520
  const Program = {
2604
3521
  PostHogIntegration: posthogIntegrationConfig.id,
2605
3522
  RevenueAnalyticsSetup: revenueAnalyticsConfig.id,
3523
+ WarehouseSource: warehouseSourceConfig.id,
2606
3524
  ErrorTrackingUploadSourceMaps: errorTrackingUploadSourceMapsConfig.id,
2607
3525
  Migration: migrationConfig.id,
2608
3526
  Audit: auditConfig.id,
2609
3527
  EventsAudit: eventsAuditConfig.id,
2610
3528
  PosthogDoctor: posthogDoctorConfig.id,
2611
3529
  WebAnalyticsDoctor: webAnalyticsDoctorConfig.id,
3530
+ SelfDriving: selfDrivingConfig.id,
2612
3531
  AgentSkill: agentSkillConfig.id,
2613
3532
  McpAdd: mcpAddConfig.id,
2614
3533
  McpRemove: mcpRemoveConfig.id,
@@ -2653,7 +3572,7 @@ function runMcpAdd(argv) {
2653
3572
  const debug = argv.debug;
2654
3573
  const localMcp = argv.local;
2655
3574
  try {
2656
- const { startTUI } = await import("./start-tui-DnAG57vY.js");
3575
+ const { startTUI } = await import("./start-tui-Cbw0kVr3.js");
2657
3576
  const { buildSession } = await import("./wizard-session-wPJtNl4c.js");
2658
3577
  const tui = startTUI(VERSION, Program.McpAdd);
2659
3578
  tui.store.session = buildSession({
@@ -2665,7 +3584,7 @@ function runMcpAdd(argv) {
2665
3584
  } catch (error) {
2666
3585
  if (!isTUIUnavailable(error)) throw error;
2667
3586
  setUI(new LoggingUI());
2668
- const { addMCPServerToClientsStep } = await import("./add-mcp-server-to-clients-t0xe8gn1.js").then((n) => n.r);
3587
+ const { addMCPServerToClientsStep } = await import("./add-mcp-server-to-clients-D2XNlVgw.js").then((n) => n.r);
2669
3588
  await addMCPServerToClientsStep({
2670
3589
  local: localMcp,
2671
3590
  features,
@@ -2704,7 +3623,7 @@ function runMcpRemove(argv) {
2704
3623
  const debug = argv.debug;
2705
3624
  const localMcp = argv.local;
2706
3625
  try {
2707
- const { startTUI } = await import("./start-tui-DnAG57vY.js");
3626
+ const { startTUI } = await import("./start-tui-Cbw0kVr3.js");
2708
3627
  const { buildSession } = await import("./wizard-session-wPJtNl4c.js");
2709
3628
  const tui = startTUI(VERSION, Program.McpRemove);
2710
3629
  tui.store.session = buildSession({
@@ -2713,7 +3632,7 @@ function runMcpRemove(argv) {
2713
3632
  });
2714
3633
  } catch {
2715
3634
  setUI(new LoggingUI());
2716
- const { removeMCPServerFromClientsStep } = await import("./add-mcp-server-to-clients-t0xe8gn1.js").then((n) => n.r);
3635
+ const { removeMCPServerFromClientsStep } = await import("./add-mcp-server-to-clients-D2XNlVgw.js").then((n) => n.r);
2717
3636
  await removeMCPServerFromClientsStep({ local: localMcp });
2718
3637
  }
2719
3638
  })();
@@ -2735,7 +3654,7 @@ function runMcpTutorial(argv) {
2735
3654
  const debug = argv.debug;
2736
3655
  const localMcp = argv.local;
2737
3656
  try {
2738
- const { startTUI } = await import("./start-tui-DnAG57vY.js");
3657
+ const { startTUI } = await import("./start-tui-Cbw0kVr3.js");
2739
3658
  const { buildSession } = await import("./wizard-session-wPJtNl4c.js");
2740
3659
  const tui = startTUI(VERSION, Program.McpTutorial);
2741
3660
  tui.store.session = buildSession({
@@ -2790,7 +3709,7 @@ function runWizard(config, options) {
2790
3709
  (async () => {
2791
3710
  try {
2792
3711
  const installDir = options.installDir || process.cwd();
2793
- const { startTUI } = await import("./start-tui-DnAG57vY.js");
3712
+ const { startTUI } = await import("./start-tui-Cbw0kVr3.js");
2794
3713
  const { buildSession, RunPhase } = await import("./wizard-session-wPJtNl4c.js");
2795
3714
  const { TaskStreamPush } = await import("./task-stream-BQNSp0qR.js");
2796
3715
  const { PostHogDestination } = await import("./posthog-Cr37rnla.js");
@@ -2846,7 +3765,7 @@ function runWizard(config, options) {
2846
3765
  await activeTui.store.getGate("health-check");
2847
3766
  const skipAgent = config.run == null;
2848
3767
  if (skipAgent) {
2849
- const { getOrAskForProjectData } = await import("./setup-utils-D87CyNkw.js").then((n) => n.r);
3768
+ const { getOrAskForProjectData } = await import("./setup-utils-CjKjaKcG.js").then((n) => n.r);
2850
3769
  const { projectApiKey, host, accessToken, projectId } = await getOrAskForProjectData({
2851
3770
  signup: session.signup,
2852
3771
  ci: session.ci,
@@ -2861,7 +3780,7 @@ function runWizard(config, options) {
2861
3780
  projectId
2862
3781
  });
2863
3782
  } else {
2864
- const { runAgent } = await import("./agent-runner-L_-kJ3y3.js");
3783
+ const { runAgent } = await import("./agent-runner-D7hIITUf.js");
2865
3784
  await runAgent(config, activeTui.store.session);
2866
3785
  }
2867
3786
  const isDone = () => skipAgent ? activeTui.store.session.outroDismissed : activeTui.store.session.skillsComplete;
@@ -2938,10 +3857,10 @@ function runWizardCI(config, options) {
2938
3857
  (async () => {
2939
3858
  const path = await import("path");
2940
3859
  const { buildSession } = await import("./wizard-session-wPJtNl4c.js");
2941
- const { readEnvironment } = await import("./environment-DQPoj9sU.js").then((n) => n.t);
3860
+ const { readEnvironment } = await import("./environment-CFXsie0G.js").then((n) => n.t);
2942
3861
  const { readApiKeyFromEnv } = await import("./env-api-key-MlzJYAvt.js").then((n) => n.t);
2943
- const { configureLogFileFromEnvironment, logToFile } = await import("./debug-CZQcMAJT.js");
2944
- const { wizardAbort, WizardError } = await import("./wizard-abort-BRXKRL4F.js");
3862
+ const { configureLogFileFromEnvironment, logToFile } = await import("./debug-CDLYQOQh.js");
3863
+ const { wizardAbort, WizardError } = await import("./wizard-abort-BPr0xo7i.js");
2945
3864
  configureLogFileFromEnvironment();
2946
3865
  const env = readEnvironment();
2947
3866
  const apiKey = options.apiKey ?? readApiKeyFromEnv() ?? void 0;
@@ -2992,7 +3911,7 @@ function runWizardCI(config, options) {
2992
3911
  })
2993
3912
  });
2994
3913
  }
2995
- const { runAgent } = await import("./agent-runner-L_-kJ3y3.js");
3914
+ const { runAgent } = await import("./agent-runner-D7hIITUf.js");
2996
3915
  await runAgent(config, session);
2997
3916
  } catch (error) {
2998
3917
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3210,6 +4129,34 @@ const PromptLabel = ({ message }) => {
3210
4129
  }) });
3211
4130
  };
3212
4131
  //#endregion
4132
+ //#region src/ui/tui/primitives/ConfirmButton.tsx
4133
+ /**
4134
+ * ConfirmButton — the confirm row used to submit a selection.
4135
+ *
4136
+ * Pure render. Multi-select menus (PickerMenu mode="multi", GroupedPickerMenu)
4137
+ * append this below their options as the final focusable row: the user toggles
4138
+ * options with enter, then arrows down onto this row and presses enter to
4139
+ * submit. This replaces the older "enter anywhere submits" pattern, which
4140
+ * confused people who expected enter to toggle the focused item.
4141
+ *
4142
+ * Renders flat, mirroring the option rows — a focus triangle and the label,
4143
+ * accent and bold when focused, dimmed otherwise — so it lines up under the
4144
+ * options instead of sitting in a separate boxed target.
4145
+ */
4146
+ const ConfirmButton = ({ label = "Confirm", focused, count }) => {
4147
+ const text = count && count > 0 ? `${label} (${count})` : label;
4148
+ return /* @__PURE__ */ jsxs(Text, {
4149
+ color: focused ? Colors.accent : void 0,
4150
+ bold: focused,
4151
+ dimColor: !focused,
4152
+ children: [
4153
+ focused ? Icons.triangleSmallRight : " ",
4154
+ " ",
4155
+ text
4156
+ ]
4157
+ });
4158
+ };
4159
+ //#endregion
3213
4160
  //#region src/ui/tui/hooks/keyboard-hints-utils.ts
3214
4161
  /** Default priorities by key type. */
3215
4162
  const DEFAULT_PRIORITY = {
@@ -3345,8 +4292,10 @@ function useKeyBindings(id, bindings) {
3345
4292
  //#region src/ui/tui/primitives/PickerMenu.tsx
3346
4293
  /**
3347
4294
  * PickerMenu — Single and multi select.
3348
- * Single mode: custom renderer with small triangle indicator.
3349
- * Multi mode: checkbox glyphs with space to toggle.
4295
+ * Single mode: custom renderer with small triangle indicator; enter selects.
4296
+ * Multi mode: checkbox glyphs toggled with enter, plus a focusable
4297
+ * Confirm button below the options. The cursor moves onto the button and
4298
+ * enter submits — see MultiPickerMenu for the rationale.
3350
4299
  *
3351
4300
  * Key bindings are declared via useKeyBindings, which auto-registers
3352
4301
  * hints in the KeyboardHintsBar.
@@ -3372,6 +4321,12 @@ function firstEnabled(options) {
3372
4321
  const idx = options.findIndex((o) => !o.disabled);
3373
4322
  return idx === -1 ? 0 : idx;
3374
4323
  }
4324
+ /** Index of the last enabled option, for wrapping from the button onto
4325
+ * the bottom of the grid. */
4326
+ function lastEnabled(options) {
4327
+ for (let i = options.length - 1; i >= 0; i--) if (!options[i]?.disabled) return i;
4328
+ return options.length - 1;
4329
+ }
3375
4330
  const PickerMenu = ({ message, options, mode = "single", centered = false, columns = 1, optionMarginBottom = 0, onSelect }) => {
3376
4331
  if (mode === "multi") return /* @__PURE__ */ jsx(MultiPickerMenu, {
3377
4332
  message,
@@ -3475,60 +4430,90 @@ const SinglePickerMenu = ({ message, options, centered = false, columns = 1, opt
3475
4430
  })]
3476
4431
  });
3477
4432
  };
3478
- /** Custom multi-select with checkbox glyphs and accent highlight. */
4433
+ /**
4434
+ * Custom multi-select with checkbox glyphs and accent highlight.
4435
+ *
4436
+ * Interaction model (shared with GroupedPickerMenu):
4437
+ * - \u2191\u2193 move the cursor through the options AND onto the Confirm button,
4438
+ * which lives just past the last option.
4439
+ * - enter toggles the focused option (no more "space toggles but enter
4440
+ * advances" split that tripped people up). Space is kept as an
4441
+ * undocumented alias, but the hints bar advertises only enter.
4442
+ * - moving onto the Confirm button and pressing enter submits the
4443
+ * current selection.
4444
+ */
3479
4445
  const MultiPickerMenu = ({ message, options, centered = false, columns = 1, optionMarginBottom = 0, onSelect }) => {
3480
4446
  const [focused, setFocused] = useState(() => firstEnabled(options));
4447
+ const [onButton, setOnButton] = useState(false);
3481
4448
  const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
3482
4449
  const rows = Math.ceil(options.length / columns);
3483
4450
  useEffect(() => {
3484
4451
  if (focused >= options.length || options[focused]?.disabled) setFocused(firstEnabled(options));
3485
4452
  }, [options, focused]);
3486
- const bindings = [
3487
- {
3488
- match: ["upArrow", "downArrow"],
3489
- label: "↑↓",
3490
- action: "navigate",
3491
- handler: (_input, key) => {
3492
- if (key.upArrow) setFocused(stepEnabled(options, rows, focused, -1));
3493
- if (key.downArrow) setFocused(stepEnabled(options, rows, focused, 1));
4453
+ const confirm = () => {
4454
+ onSelect([...selected].sort((a, b) => a - b).map((i) => options[i].value));
4455
+ };
4456
+ const bindings = [{
4457
+ match: ["upArrow", "downArrow"],
4458
+ label: "↑↓",
4459
+ action: "navigate",
4460
+ handler: (_input, key) => {
4461
+ if (key.upArrow) {
4462
+ if (onButton) {
4463
+ setOnButton(false);
4464
+ setFocused(lastEnabled(options));
4465
+ return;
4466
+ }
4467
+ const col = Math.floor(focused / rows);
4468
+ let r = focused % rows - 1;
4469
+ while (r >= 0 && options[col * rows + r]?.disabled) r--;
4470
+ if (r >= 0) setFocused(col * rows + r);
4471
+ else setOnButton(true);
3494
4472
  }
3495
- },
3496
- {
3497
- match: "space",
3498
- label: "space",
3499
- action: "toggle",
3500
- handler: () => {
3501
- if (options[focused]?.disabled) return;
3502
- setSelected((prev) => {
3503
- const next = new Set(prev);
3504
- if (next.has(focused)) {
3505
- next.delete(focused);
3506
- return next;
3507
- }
3508
- if (options[focused]?.exclusive) return new Set([focused]);
3509
- for (const i of next) if (options[i]?.exclusive) next.delete(i);
3510
- next.add(focused);
3511
- return next;
3512
- });
4473
+ if (key.downArrow) {
4474
+ if (onButton) {
4475
+ setOnButton(false);
4476
+ setFocused(firstEnabled(options));
4477
+ return;
4478
+ }
4479
+ const col = Math.floor(focused / rows);
4480
+ const row = focused % rows;
4481
+ const colLen = Math.min(rows, options.length - col * rows);
4482
+ let r = row + 1;
4483
+ while (r < colLen && options[col * rows + r]?.disabled) r++;
4484
+ if (r < colLen) setFocused(col * rows + r);
4485
+ else setOnButton(true);
3513
4486
  }
3514
- },
3515
- {
3516
- match: "return",
3517
- label: "enter",
3518
- action: "confirm",
3519
- handler: () => {
3520
- if (selected.size === 0) {
3521
- const hovered = options[focused];
3522
- if (hovered && !hovered.disabled) onSelect(hovered.value);
3523
- } else onSelect([...selected].sort().map((i) => options[i].value));
4487
+ }
4488
+ }, {
4489
+ match: ["space", "return"],
4490
+ label: "enter",
4491
+ action: "select",
4492
+ handler: () => {
4493
+ if (onButton) {
4494
+ confirm();
4495
+ return;
3524
4496
  }
4497
+ if (options[focused]?.disabled) return;
4498
+ setSelected((prev) => {
4499
+ const next = new Set(prev);
4500
+ if (next.has(focused)) {
4501
+ next.delete(focused);
4502
+ return next;
4503
+ }
4504
+ if (options[focused]?.exclusive) return new Set([focused]);
4505
+ for (const i of next) if (options[i]?.exclusive) next.delete(i);
4506
+ next.add(focused);
4507
+ return next;
4508
+ });
3525
4509
  }
3526
- ];
4510
+ }];
3527
4511
  if (columns > 1) bindings.splice(1, 0, {
3528
4512
  match: ["leftArrow", "rightArrow"],
3529
4513
  label: "←→",
3530
4514
  action: "navigate",
3531
4515
  handler: (_input, key) => {
4516
+ if (onButton) return;
3532
4517
  const col = Math.floor(focused / rows);
3533
4518
  const row = focused % rows;
3534
4519
  let next = focused;
@@ -3550,43 +4535,65 @@ const MultiPickerMenu = ({ message, options, centered = false, columns = 1, opti
3550
4535
  return /* @__PURE__ */ jsxs(Box, {
3551
4536
  flexDirection: "column",
3552
4537
  alignItems: centered ? "center" : void 0,
3553
- children: [/* @__PURE__ */ jsx(PromptLabel, { message }), /* @__PURE__ */ jsx(Box, {
3554
- flexDirection: "row",
3555
- gap: 4,
3556
- marginLeft: centered ? 0 : 2,
3557
- marginTop: 1,
3558
- children: columnArrays.map((colOpts, colIdx) => /* @__PURE__ */ jsx(Box, {
3559
- flexDirection: "column",
3560
- children: colOpts.map((opt, rowIdx) => {
3561
- const flatIdx = colIdx * rows + rowIdx;
3562
- const isFocused = flatIdx === focused;
3563
- const isSelected = selected.has(flatIdx);
3564
- const label = opt.hint ? `${opt.label} (${opt.hint})` : opt.label;
3565
- const checkbox = isSelected ? Icons.squareFilled : Icons.squareOpen;
3566
- return /* @__PURE__ */ jsxs(Box, {
3567
- gap: 1,
3568
- marginBottom: optionMarginBottom,
3569
- children: [
3570
- /* @__PURE__ */ jsx(Text, {
3571
- color: isSelected ? "white" : Colors.muted,
3572
- dimColor: !isFocused && !isSelected,
3573
- children: checkbox
3574
- }),
3575
- opt.icon && /* @__PURE__ */ jsx(Text, {
3576
- color: opt.icon.color,
3577
- children: opt.icon.glyph
3578
- }),
3579
- /* @__PURE__ */ jsx(Text, {
3580
- color: opt.disabled ? Colors.muted : isFocused ? Colors.accent : void 0,
3581
- bold: isFocused && !opt.disabled,
3582
- dimColor: !isFocused || opt.disabled,
3583
- children: label
3584
- })
3585
- ]
3586
- }, flatIdx);
4538
+ children: [
4539
+ /* @__PURE__ */ jsx(PromptLabel, { message }),
4540
+ /* @__PURE__ */ jsx(Box, {
4541
+ flexDirection: "row",
4542
+ gap: 4,
4543
+ marginLeft: centered ? 0 : 2,
4544
+ marginTop: 1,
4545
+ children: columnArrays.map((colOpts, colIdx) => /* @__PURE__ */ jsx(Box, {
4546
+ flexDirection: "column",
4547
+ children: colOpts.map((opt, rowIdx) => {
4548
+ const flatIdx = colIdx * rows + rowIdx;
4549
+ const isFocused = !onButton && flatIdx === focused;
4550
+ const isSelected = selected.has(flatIdx);
4551
+ const label = opt.hint ? `${opt.label} (${opt.hint})` : opt.label;
4552
+ const checkbox = isSelected ? Icons.squareFilled : Icons.squareOpen;
4553
+ return /* @__PURE__ */ jsxs(Box, {
4554
+ flexDirection: "column",
4555
+ marginBottom: optionMarginBottom,
4556
+ children: [/* @__PURE__ */ jsxs(Box, {
4557
+ gap: 1,
4558
+ children: [
4559
+ /* @__PURE__ */ jsx(Text, {
4560
+ color: isSelected ? "white" : Colors.muted,
4561
+ dimColor: !isFocused && !isSelected,
4562
+ children: checkbox
4563
+ }),
4564
+ opt.icon && /* @__PURE__ */ jsx(Text, {
4565
+ color: opt.icon.color,
4566
+ children: opt.icon.glyph
4567
+ }),
4568
+ /* @__PURE__ */ jsx(Text, {
4569
+ color: opt.disabled ? Colors.muted : isFocused ? Colors.accent : void 0,
4570
+ bold: isFocused && !opt.disabled,
4571
+ dimColor: !isFocused || opt.disabled,
4572
+ children: label
4573
+ })
4574
+ ]
4575
+ }), opt.description && /* @__PURE__ */ jsx(Box, {
4576
+ marginLeft: 4,
4577
+ width: 56,
4578
+ children: /* @__PURE__ */ jsx(Text, {
4579
+ dimColor: true,
4580
+ wrap: "wrap",
4581
+ children: opt.description
4582
+ })
4583
+ })]
4584
+ }, flatIdx);
4585
+ })
4586
+ }, colIdx))
4587
+ }),
4588
+ /* @__PURE__ */ jsx(Box, {
4589
+ marginTop: 1,
4590
+ marginLeft: centered ? 0 : 2,
4591
+ children: /* @__PURE__ */ jsx(ConfirmButton, {
4592
+ focused: onButton,
4593
+ count: selected.size
3587
4594
  })
3588
- }, colIdx))
3589
- })]
4595
+ })
4596
+ ]
3590
4597
  });
3591
4598
  };
3592
4599
  //#endregion
@@ -3771,7 +4778,7 @@ async function runDoctorCI(options) {
3771
4778
  getUI().intro("Welcome to the PostHog setup wizard");
3772
4779
  getUI().log.info("Running posthog-doctor in CI mode");
3773
4780
  try {
3774
- const { getOrAskForProjectData } = await import("./setup-utils-D87CyNkw.js").then((n) => n.r);
4781
+ const { getOrAskForProjectData } = await import("./setup-utils-CjKjaKcG.js").then((n) => n.r);
3775
4782
  const { host, accessToken, projectId } = await getOrAskForProjectData({
3776
4783
  signup: false,
3777
4784
  ci: true,
@@ -3788,7 +4795,7 @@ async function runDoctorCI(options) {
3788
4795
  for (const issue of sorted) getUI().log.info(` • [${issue.severity}] ${getKindMeta(issue.kind).title}`);
3789
4796
  process.exit(1);
3790
4797
  } catch (error) {
3791
- const { ApiError } = await import("./api-DNS-L-1U.js").then((n) => n.n);
4798
+ const { ApiError } = await import("./api-2zPZQONC.js").then((n) => n.n);
3792
4799
  const message = error instanceof ApiError && error.statusCode === 401 ? "Your PostHog API key is invalid or expired." : error instanceof Error ? error.message : String(error);
3793
4800
  getUI().log.error(`Doctor failed: ${message}`);
3794
4801
  process.exit(1);
@@ -3836,6 +4843,39 @@ const migrateCommand = nativeCommandFactory(migrationConfig);
3836
4843
  */
3837
4844
  const revenueCommand = nativeCommandFactory(revenueAnalyticsConfig);
3838
4845
  //#endregion
4846
+ //#region src/commands/warehouse.ts
4847
+ /**
4848
+ * `wizard warehouse` — detect and connect a data warehouse source.
4849
+ *
4850
+ * Mirrors `revenue-analytics`: flat skill command driven by the
4851
+ * warehouse-source program.
4852
+ */
4853
+ const warehouseCommand = nativeCommandFactory(warehouseSourceConfig);
4854
+ //#endregion
4855
+ //#region src/commands/self-driving.ts
4856
+ const selfDrivingCommand = {
4857
+ name: "self-driving",
4858
+ description: selfDrivingConfig.description,
4859
+ options: {
4860
+ ...skillProgramOptions,
4861
+ ...selfDrivingConfig.cliOptions ?? {}
4862
+ },
4863
+ check: (argv) => {
4864
+ if (argv.signup) throw new Error("`self-driving` cannot run with --signup. It builds on an existing PostHog integration — run the base `wizard` to create your account and set up PostHog first, then run `wizard self-driving`.");
4865
+ if (argv.ci) throw new Error("`self-driving` cannot run in CI mode — it requires interactive steps (GitHub connect, issue-tracker selection, custom-scout approval).");
4866
+ return true;
4867
+ },
4868
+ handler: (argv) => {
4869
+ const extras = selfDrivingConfig.mapCliOptions?.(argv) ?? {};
4870
+ const options = {
4871
+ ...argv,
4872
+ ...extras
4873
+ };
4874
+ if (options.ci) runWizardCI(selfDrivingConfig, options);
4875
+ else runWizard(selfDrivingConfig, options);
4876
+ }
4877
+ };
4878
+ //#endregion
3839
4879
  //#region src/commands/slack.ts
3840
4880
  const slackCommand = {
3841
4881
  name: "slack",
@@ -3851,7 +4891,7 @@ function runSlackConnect(argv) {
3851
4891
  (async () => {
3852
4892
  const debug = argv.debug;
3853
4893
  try {
3854
- const { startTUI } = await import("./start-tui-DnAG57vY.js");
4894
+ const { startTUI } = await import("./start-tui-Cbw0kVr3.js");
3855
4895
  const { buildSession } = await import("./wizard-session-wPJtNl4c.js");
3856
4896
  const tui = startTUI(VERSION, Program.SlackConnect);
3857
4897
  tui.store.session = buildSession({ debug });
@@ -4228,8 +5268,8 @@ function resolveInstallDir() {
4228
5268
  if (inline) return inline.slice(14);
4229
5269
  return process.env.POSTHOG_WIZARD_INSTALL_DIR ?? process.cwd();
4230
5270
  }
4231
- Wizard.use(basicIntegrationCommand).use(mcpCommand).use(cliCommand).use(auditCommand).use(doctorCommand).use(migrateCommand).use(revenueCommand).use(slackCommand).use(uploadSourcemapsCommand).use(skillCommand).init();
5271
+ Wizard.use(basicIntegrationCommand).use(mcpCommand).use(cliCommand).use(auditCommand).use(doctorCommand).use(migrateCommand).use(revenueCommand).use(warehouseCommand).use(selfDrivingCommand).use(slackCommand).use(uploadSourcemapsCommand).use(skillCommand).init();
4232
5272
  //#endregion
4233
- 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 };
5273
+ export { getContentBlocks$2 as _, ConfirmButton as a, runWizard as c, getProgramConfig as d, DISPLAY_NAME as f, getDetectedWarehouseSources as g, fetchHealthIssues as h, useKeyboardHintsContext as i, PROGRAM_REGISTRY as l, getKindMeta as m, useKeyBindings as n, PromptLabel as o, SOURCE_MAPS_CONTEXT_KEYS as p, KeyboardHintsProvider as r, runWizardCI as s, PickerMenu as t, Program as u, POSTHOG_SDKS$1 as v, STRIPE_SDKS as y };
4234
5274
 
4235
5275
  //# sourceMappingURL=bin.js.map