@hogsend/cli 0.19.0 → 0.21.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.
package/dist/bin.js CHANGED
@@ -61,13 +61,13 @@ async function request(baseUrl, key, missingKeyMessage, method, path, opts) {
61
61
  const msg = cause instanceof Error ? cause.message : String(cause);
62
62
  throw makeHttpError(`cannot reach ${baseUrl} (${msg})`, 0, void 0);
63
63
  }
64
- const text3 = await res.text();
64
+ const text4 = await res.text();
65
65
  let parsed;
66
- if (text3.length > 0) {
66
+ if (text4.length > 0) {
67
67
  try {
68
- parsed = JSON.parse(text3);
68
+ parsed = JSON.parse(text4);
69
69
  } catch {
70
- parsed = text3;
70
+ parsed = text4;
71
71
  }
72
72
  }
73
73
  if (!res.ok) {
@@ -91,6 +91,10 @@ function createAdminClient(cfg) {
91
91
  body,
92
92
  auth: true
93
93
  }),
94
+ put: (path, body) => request(cfg.baseUrl, cfg.adminKey, missing, "PUT", path, {
95
+ body,
96
+ auth: true
97
+ }),
94
98
  del: (path, body) => request(cfg.baseUrl, cfg.adminKey, missing, "DELETE", path, {
95
99
  body,
96
100
  auth: true
@@ -145,7 +149,7 @@ function renderTable(rows, columns) {
145
149
  const widths = cols.map(
146
150
  (c) => Math.max(c.length, ...rows.map((r) => cell(r[c]).length))
147
151
  );
148
- const pad = (text3, width) => text3 + " ".repeat(width - text3.length);
152
+ const pad = (text4, width) => text4 + " ".repeat(width - text4.length);
149
153
  const header = cols.map((c, i) => color.bold(pad(c, widths[i] ?? 0))).join(" ");
150
154
  const sep4 = cols.map((_, i) => "-".repeat(widths[i] ?? 0)).join(" ");
151
155
  const body = rows.map((r) => cols.map((c, i) => pad(cell(r[c]), widths[i] ?? 0)).join(" ")).join("\n");
@@ -459,16 +463,825 @@ async function run(ctx) {
459
463
  );
460
464
  }
461
465
  }
462
- var campaignsCommand = {
463
- name: "campaigns",
464
- summary: "Queue a broadcast to a list/bucket, or check its status",
465
- usage,
466
- run
466
+ var campaignsCommand = {
467
+ name: "campaigns",
468
+ summary: "Queue a broadcast to a list/bucket, or check its status",
469
+ usage,
470
+ run
471
+ };
472
+
473
+ // src/commands/connect.ts
474
+ import { parseArgs as parseArgs2 } from "util";
475
+ import { confirm, select, text } from "@clack/prompts";
476
+
477
+ // src/lib/browser.ts
478
+ import { spawn } from "child_process";
479
+ function openBrowser(url) {
480
+ const platform = process.platform;
481
+ const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
482
+ const args = platform === "win32" ? ["/c", "start", "", url] : [url];
483
+ try {
484
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
485
+ child.on("error", () => {
486
+ });
487
+ child.unref();
488
+ return true;
489
+ } catch {
490
+ return false;
491
+ }
492
+ }
493
+
494
+ // src/lib/loopback.ts
495
+ import { createServer } from "http";
496
+
497
+ // src/lib/oauth.ts
498
+ import { createHash, randomBytes } from "crypto";
499
+ var POSTHOG_CLIENT_ID = "https://hogsend.com/.well-known/hogsend-posthog-client.json";
500
+ var POSTHOG_SCOPES = "person:read person:write project:read organization:read hog_function:read hog_function:write feature_flag:read cohort:read cohort:write query:read insight:read event_definition:read property_definition:read";
501
+ var LOOPBACK_PORTS = [8423, 8424, 8425];
502
+ var CALLBACK_PATH = "/callback";
503
+ var CALLBACK_TIMEOUT_MS = 3e5;
504
+ var REQUIRED_ACCESS_LEVEL = "team";
505
+ var DISCOVERY_TIMEOUT_MS = 1e4;
506
+ function computeChallenge(verifier) {
507
+ return createHash("sha256").update(verifier, "ascii").digest("base64url");
508
+ }
509
+ function generatePkce() {
510
+ const verifier = randomBytes(32).toString("base64url");
511
+ return { verifier, challenge: computeChallenge(verifier), method: "S256" };
512
+ }
513
+ function generateState() {
514
+ return randomBytes(16).toString("base64url");
515
+ }
516
+ async function discoverOAuthServer(opts) {
517
+ const fetchImpl = opts.fetchImpl ?? fetch;
518
+ const url = `${opts.privateHost}/.well-known/oauth-authorization-server`;
519
+ let res;
520
+ try {
521
+ res = await fetchImpl(url, {
522
+ headers: { Accept: "application/json" },
523
+ signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS)
524
+ });
525
+ } catch (cause) {
526
+ const msg = cause instanceof Error ? cause.message : String(cause);
527
+ return { status: "error", message: `OAuth discovery failed: ${msg}` };
528
+ }
529
+ if (res.status === 404 || res.status === 410) {
530
+ return { status: "unsupported" };
531
+ }
532
+ if (!res.ok) {
533
+ return {
534
+ status: "error",
535
+ message: `OAuth discovery failed (HTTP ${res.status})`
536
+ };
537
+ }
538
+ let doc;
539
+ try {
540
+ doc = await res.json();
541
+ } catch {
542
+ return {
543
+ status: "error",
544
+ message: "malformed discovery document (not JSON)"
545
+ };
546
+ }
547
+ if (typeof doc !== "object" || doc === null || typeof doc.issuer !== "string" || typeof doc.authorization_endpoint !== "string" || typeof doc.token_endpoint !== "string") {
548
+ return {
549
+ status: "error",
550
+ message: "malformed discovery document (missing issuer / authorization_endpoint / token_endpoint)"
551
+ };
552
+ }
553
+ return { status: "ok", metadata: doc };
554
+ }
555
+ function buildAuthorizeUrl(opts) {
556
+ const url = new URL(opts.authorizationEndpoint);
557
+ url.searchParams.set("response_type", "code");
558
+ url.searchParams.set("client_id", opts.clientId);
559
+ url.searchParams.set("redirect_uri", opts.redirectUri);
560
+ url.searchParams.set("scope", opts.scope);
561
+ url.searchParams.set("state", opts.state);
562
+ url.searchParams.set("code_challenge", opts.pkce.challenge);
563
+ url.searchParams.set("code_challenge_method", opts.pkce.method);
564
+ if (opts.requiredAccessLevel !== void 0) {
565
+ url.searchParams.set("required_access_level", opts.requiredAccessLevel);
566
+ }
567
+ return url.toString();
568
+ }
569
+ async function exchangeCode(opts) {
570
+ const fetchImpl = opts.fetchImpl ?? fetch;
571
+ const res = await fetchImpl(opts.tokenEndpoint, {
572
+ method: "POST",
573
+ headers: {
574
+ // URLSearchParams sets this automatically; explicit for clarity.
575
+ "Content-Type": "application/x-www-form-urlencoded",
576
+ Accept: "application/json"
577
+ },
578
+ body: new URLSearchParams({
579
+ grant_type: "authorization_code",
580
+ code: opts.code,
581
+ redirect_uri: opts.redirectUri,
582
+ client_id: opts.clientId,
583
+ code_verifier: opts.codeVerifier
584
+ })
585
+ });
586
+ if (!res.ok) {
587
+ const text4 = await res.text().catch(() => "");
588
+ let detail = text4;
589
+ try {
590
+ const parsed = JSON.parse(text4);
591
+ if (typeof parsed === "object" && parsed !== null && typeof parsed.error === "string") {
592
+ detail = parsed.error;
593
+ }
594
+ } catch {
595
+ }
596
+ throw new Error(
597
+ `token exchange failed (${res.status})${detail ? `: ${detail}` : ""}`
598
+ );
599
+ }
600
+ const json2 = await res.json();
601
+ if (typeof json2.access_token !== "string" || json2.access_token === "") {
602
+ throw new Error("token response missing access_token");
603
+ }
604
+ if (typeof json2.refresh_token !== "string" || json2.refresh_token === "") {
605
+ throw new Error(
606
+ "token response missing refresh_token \u2014 cannot store a long-lived credential"
607
+ );
608
+ }
609
+ return json2;
610
+ }
611
+
612
+ // src/lib/loopback.ts
613
+ var LoopbackError = class extends Error {
614
+ reason;
615
+ detail;
616
+ constructor(reason, message, detail) {
617
+ super(message);
618
+ this.name = "LoopbackError";
619
+ this.reason = reason;
620
+ this.detail = detail;
621
+ }
622
+ };
623
+ var page = (title, body) => `<!doctype html><html><head><meta charset="utf-8"><title>Hogsend</title><style>body{font-family:system-ui,sans-serif;max-width:32rem;margin:6rem auto;padding:0 1rem;color:#1a1a1a}</style></head><body><h1>${title}</h1><p>${body}</p></body></html>`;
624
+ var SUCCESS_HTML = page(
625
+ "Connected",
626
+ "You can close this tab and return to your terminal."
627
+ );
628
+ var DENIED_HTML = page(
629
+ "Not connected",
630
+ "Authorization was denied. Close this tab and re-run the command if that was a mistake."
631
+ );
632
+ var MISMATCH_HTML = page(
633
+ "Not connected",
634
+ "State mismatch. Close this tab and re-run the command."
635
+ );
636
+ var NO_CODE_HTML = page(
637
+ "Not connected",
638
+ "The callback carried no authorization code. Close this tab and re-run the command."
639
+ );
640
+ function tryListen(port, handler) {
641
+ return new Promise((resolve3, reject) => {
642
+ const server = createServer(handler);
643
+ const onError = (err) => {
644
+ server.close();
645
+ if (err.code === "EADDRINUSE") resolve3(null);
646
+ else reject(err);
647
+ };
648
+ server.once("error", onError);
649
+ server.listen({ host: "127.0.0.1", port }, () => {
650
+ server.removeListener("error", onError);
651
+ resolve3(server);
652
+ });
653
+ });
654
+ }
655
+ async function startLoopbackServer(opts) {
656
+ const callbackPath = opts.callbackPath ?? CALLBACK_PATH;
657
+ let settled = false;
658
+ let settleResolve;
659
+ let settleReject;
660
+ const settlePromise = new Promise((resolve3, reject) => {
661
+ settleResolve = resolve3;
662
+ settleReject = reject;
663
+ });
664
+ settlePromise.catch(() => {
665
+ });
666
+ const handler = (req, res) => {
667
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
668
+ if (url.pathname !== callbackPath) {
669
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
670
+ res.end("Not found");
671
+ return;
672
+ }
673
+ if (settled) {
674
+ res.writeHead(410, { "content-type": "text/plain; charset=utf-8" });
675
+ res.end("This callback was already handled \u2014 return to your terminal.");
676
+ return;
677
+ }
678
+ settled = true;
679
+ const respond = (status, html) => {
680
+ res.writeHead(status, { "content-type": "text/html; charset=utf-8" });
681
+ res.end(html);
682
+ };
683
+ const error = url.searchParams.get("error");
684
+ if (error !== null) {
685
+ respond(200, DENIED_HTML);
686
+ const detail = url.searchParams.get("error_description") ?? error;
687
+ settleReject(
688
+ error === "access_denied" ? new LoopbackError(
689
+ "consent_denied",
690
+ "authorization was denied in PostHog",
691
+ detail
692
+ ) : new LoopbackError(
693
+ "oauth_error",
694
+ `the OAuth callback returned an error: ${error}`,
695
+ detail
696
+ )
697
+ );
698
+ return;
699
+ }
700
+ const state = url.searchParams.get("state");
701
+ if (state === null || state !== opts.state) {
702
+ respond(400, MISMATCH_HTML);
703
+ settleReject(
704
+ new LoopbackError(
705
+ "state_mismatch",
706
+ "state mismatch on the OAuth callback \u2014 possible CSRF; retry the command"
707
+ )
708
+ );
709
+ return;
710
+ }
711
+ const code = url.searchParams.get("code");
712
+ if (code === null || code === "") {
713
+ respond(400, NO_CODE_HTML);
714
+ settleReject(
715
+ new LoopbackError(
716
+ "oauth_error",
717
+ "the OAuth callback carried no authorization code"
718
+ )
719
+ );
720
+ return;
721
+ }
722
+ respond(200, SUCCESS_HTML);
723
+ settleResolve({ code });
724
+ };
725
+ let server = null;
726
+ for (const port2 of opts.ports) {
727
+ server = await tryListen(port2, handler);
728
+ if (server) break;
729
+ }
730
+ if (!server) {
731
+ throw new LoopbackError(
732
+ "ports_busy",
733
+ `ports ${opts.ports.join(", ")} on 127.0.0.1 are all in use`
734
+ );
735
+ }
736
+ const address = server.address();
737
+ const port = address !== null && typeof address === "object" ? address.port : 0;
738
+ const bound = server;
739
+ return {
740
+ port,
741
+ redirectUri: `http://127.0.0.1:${port}${callbackPath}`,
742
+ waitForCallback(waitOpts) {
743
+ const timeoutMs = waitOpts?.timeoutMs ?? CALLBACK_TIMEOUT_MS;
744
+ let timer2;
745
+ const timeout = new Promise((_, reject) => {
746
+ timer2 = setTimeout(() => {
747
+ reject(
748
+ new LoopbackError(
749
+ "timeout",
750
+ "timed out waiting for the OAuth callback (5 minutes)"
751
+ )
752
+ );
753
+ }, timeoutMs);
754
+ });
755
+ return Promise.race([settlePromise, timeout]).finally(() => {
756
+ clearTimeout(timer2);
757
+ });
758
+ },
759
+ close() {
760
+ return new Promise((resolve3) => {
761
+ bound.closeAllConnections();
762
+ bound.close(() => resolve3());
763
+ });
764
+ }
765
+ };
766
+ }
767
+
768
+ // src/lib/connect-flow.ts
769
+ var ConnectError = class extends Error {
770
+ verdict;
771
+ hint;
772
+ constructor(verdict, message, hint) {
773
+ super(message);
774
+ this.name = "ConnectError";
775
+ this.verdict = verdict;
776
+ this.hint = hint;
777
+ }
778
+ };
779
+ var HINT_NOT_CONFIGURED = "Pass --posthog-host https://eu.posthog.com (or https://us.posthog.com, or your self-hosted app URL) to pick the region to authorize against. Alternatively set POSTHOG_HOST on the instance, redeploy, then re-run.";
780
+ var POSTHOG_EU_HOST = "https://eu.posthog.com";
781
+ var POSTHOG_US_HOST = "https://us.posthog.com";
782
+ var normalizeHost = (host) => host.replace(/\/+$/, "");
783
+ var hintOauthUnsupported = (privateHost) => `${privateHost} doesn't advertise an OAuth server (discovery returned 404).
784
+ Self-hosted PostHog builds may not ship OAuth. Use a personal API key instead:
785
+
786
+ 1. In PostHog: Settings -> User -> Personal API keys -> create a key scoped
787
+ person:read, person:write, project:read, hog_function:write
788
+ 2. Set POSTHOG_PERSONAL_API_KEY=<key> on your Hogsend instance (api + worker)
789
+ 3. Redeploy \u2014 person reads and loop provisioning use the key automatically.`;
790
+ var HINT_PORTS = "Ports 8423-8425 on 127.0.0.1 are all in use \u2014 free one and re-run. The OAuth callback must land on one of these fixed ports; they are registered in Hogsend's OAuth client document.";
791
+ var SSH_NOTE = `The consent page must open in a browser on THIS machine \u2014 the OAuth callback
792
+ returns to 127.0.0.1 here. On a remote/SSH session this cannot complete: run
793
+ the command from your laptop instead and point --url at the instance (the CLI
794
+ never needs to run on the server).`;
795
+ function isLoopbackUrl(publicUrl) {
796
+ try {
797
+ const host = new URL(publicUrl).hostname.toLowerCase();
798
+ return host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host === "[::1]" || host === "::1" || host.endsWith(".localhost");
799
+ } catch {
800
+ return false;
801
+ }
802
+ }
803
+ var LOOPBACK_URL_NOTE = `Credential stored \u2014 but this instance's API_PUBLIC_URL is a loopback
804
+ address, so PostHog Cloud cannot deliver webhooks to it. Provisioning was
805
+ skipped (a destination pointing at localhost would be unreachable).
806
+
807
+ Once deployed, wire the loop against the real instance:
808
+
809
+ hogsend connect posthog --provision-only --url https://your-instance`;
810
+ var errMsg = (err) => err instanceof Error ? err.message : String(err);
811
+ var httpErrorBody = (err) => {
812
+ if (!isHttpError(err)) return void 0;
813
+ const body = err.body;
814
+ if (body && typeof body === "object" && "error" in body && typeof body.error === "string") {
815
+ return body.error;
816
+ }
817
+ return void 0;
818
+ };
819
+ function fromLoopbackError(err) {
820
+ switch (err.reason) {
821
+ case "consent_denied":
822
+ return new ConnectError(
823
+ "consent_denied",
824
+ "authorization was denied in PostHog \u2014 re-run the command if that was a mistake"
825
+ );
826
+ case "state_mismatch":
827
+ return new ConnectError(
828
+ "state_mismatch",
829
+ "state mismatch on the OAuth callback \u2014 possible CSRF; retry the command"
830
+ );
831
+ case "timeout":
832
+ return new ConnectError(
833
+ "callback_timeout",
834
+ "timed out waiting for the OAuth callback (5 minutes) \u2014 re-run when you're ready to approve in the browser"
835
+ );
836
+ case "ports_busy":
837
+ return new ConnectError("port_unavailable", err.message, HINT_PORTS);
838
+ case "oauth_error":
839
+ return new ConnectError("exchange_failed", err.message);
840
+ }
841
+ }
842
+ async function runProvisionOnly(deps, info, base) {
843
+ if (isLoopbackUrl(info.apiPublicUrl)) {
844
+ deps.out.note(LOOPBACK_URL_NOTE, "Instance not publicly reachable");
845
+ throw new ConnectError(
846
+ "api_public_url_unreachable",
847
+ `API_PUBLIC_URL is ${info.apiPublicUrl} \u2014 PostHog cannot deliver webhooks to a loopback address`
848
+ );
849
+ }
850
+ let result;
851
+ try {
852
+ result = await deps.out.step(
853
+ `POST ${base}/v1/admin/analytics/provision-loop`,
854
+ () => deps.http.post(
855
+ "/v1/admin/analytics/provision-loop",
856
+ {}
857
+ )
858
+ );
859
+ } catch (err) {
860
+ if (isHttpError(err) && err.status === 409 && httpErrorBody(err) === "no_posthog_credential") {
861
+ throw new ConnectError(
862
+ "no_credential",
863
+ "no PostHog credential is stored on this instance",
864
+ "run `hogsend connect posthog` first"
865
+ );
866
+ }
867
+ throw new ConnectError("provision_failed", errMsg(err));
868
+ }
869
+ printProvisioned(deps.out, result);
870
+ return {
871
+ verdict: "connected",
872
+ providerId: "posthog",
873
+ instance: base,
874
+ posthog: null,
875
+ credential: { stored: false },
876
+ provision: {
877
+ attempted: true,
878
+ ok: true,
879
+ created: result.created === true,
880
+ hogFunctionId: result.hogFunctionId ?? "",
881
+ webhookUrl: result.webhookUrl ?? ""
882
+ }
883
+ };
884
+ }
885
+ function printProvisioned(out, result) {
886
+ out.note(
887
+ [
888
+ "PostHog -> Hogsend loop provisioned",
889
+ ` webhookUrl ${result.webhookUrl ?? "(unknown)"}`,
890
+ ` hogFunctionId ${result.hogFunctionId ?? "(unknown)"}`,
891
+ ` created ${result.created === true ? "yes" : "no (existing function adopted)"}`
892
+ ].join("\n")
893
+ );
894
+ }
895
+ async function resolvePrivateHost(deps, info, opts) {
896
+ if (info.privateHost !== null) {
897
+ return info.privateHost;
898
+ }
899
+ if (opts.posthogHost) {
900
+ return normalizeHost(opts.posthogHost);
901
+ }
902
+ if (deps.interactive) {
903
+ if (deps.selectRegion) {
904
+ return normalizeHost(await deps.selectRegion());
905
+ }
906
+ const useUs = await deps.confirm(
907
+ `Use PostHog US Cloud (${POSTHOG_US_HOST})? (No selects PostHog EU Cloud, ${POSTHOG_EU_HOST})`
908
+ );
909
+ return useUs ? POSTHOG_US_HOST : POSTHOG_EU_HOST;
910
+ }
911
+ throw new ConnectError(
912
+ "not_configured",
913
+ "this instance has no PostHog configuration",
914
+ HINT_NOT_CONFIGURED
915
+ );
916
+ }
917
+ async function runConnectPosthog(deps, opts) {
918
+ const base = deps.http.cfg.baseUrl;
919
+ const info = await deps.out.step(
920
+ `GET ${base}/v1/admin/analytics/connect-info`,
921
+ () => deps.http.get("/v1/admin/analytics/connect-info")
922
+ );
923
+ if (opts.provisionOnly) {
924
+ return runProvisionOnly(deps, info, base);
925
+ }
926
+ const privateHost = await resolvePrivateHost(deps, info, opts);
927
+ if (info.privateHost !== null && info.hostExplicit === false) {
928
+ if (deps.interactive) {
929
+ const proceed = await deps.confirm(
930
+ `No POSTHOG_HOST set on the instance \u2014 assume PostHog US Cloud (${privateHost})?`
931
+ );
932
+ if (!proceed) {
933
+ throw new ConnectError(
934
+ "not_configured",
935
+ "set POSTHOG_HOST on the instance to pick the right region"
936
+ );
937
+ }
938
+ } else {
939
+ deps.out.log(
940
+ `warning: no POSTHOG_HOST set on the instance \u2014 assuming PostHog US Cloud (${privateHost}).`
941
+ );
942
+ }
943
+ }
944
+ if (info.personalKeyConfigured === true) {
945
+ deps.out.log(
946
+ "note: POSTHOG_PERSONAL_API_KEY is set on the instance; the OAuth credential will take precedence once stored."
947
+ );
948
+ }
949
+ const metadata = await deps.out.step(
950
+ `OAuth discovery at ${privateHost}`,
951
+ async () => {
952
+ const result = await deps.discover({ privateHost });
953
+ if (result.status === "unsupported") {
954
+ throw new ConnectError(
955
+ "oauth_unsupported",
956
+ `${privateHost} doesn't advertise an OAuth server (discovery returned 404)`,
957
+ hintOauthUnsupported(privateHost)
958
+ );
959
+ }
960
+ if (result.status === "error") {
961
+ throw new ConnectError("discovery_failed", result.message);
962
+ }
963
+ return result.metadata;
964
+ }
965
+ );
966
+ try {
967
+ if (new URL(metadata.issuer).origin !== new URL(privateHost).origin) {
968
+ deps.out.log(
969
+ `warning: discovery issuer ${metadata.issuer} differs from ${privateHost} \u2014 continuing.`
970
+ );
971
+ }
972
+ } catch {
973
+ }
974
+ const pkce = generatePkce();
975
+ const state = generateState();
976
+ let server;
977
+ try {
978
+ server = await deps.startLoopback({ ports: LOOPBACK_PORTS, state });
979
+ } catch (err) {
980
+ if (err instanceof LoopbackError) throw fromLoopbackError(err);
981
+ throw err;
982
+ }
983
+ let code;
984
+ try {
985
+ const authorizeUrl = buildAuthorizeUrl({
986
+ authorizationEndpoint: metadata.authorization_endpoint,
987
+ clientId: POSTHOG_CLIENT_ID,
988
+ redirectUri: server.redirectUri,
989
+ scope: POSTHOG_SCOPES,
990
+ state,
991
+ pkce,
992
+ requiredAccessLevel: REQUIRED_ACCESS_LEVEL
993
+ });
994
+ deps.out.note(
995
+ [
996
+ "About to authorize Hogsend against PostHog",
997
+ ` instance ${base}`,
998
+ ` posthog ${privateHost}`,
999
+ ` scopes ${POSTHOG_SCOPES}`,
1000
+ ` callback ${server.redirectUri}`
1001
+ ].join("\n")
1002
+ );
1003
+ const opened = opts.noBrowser ? false : deps.openBrowser(authorizeUrl);
1004
+ deps.out.log(
1005
+ opened ? "Opening your browser. If nothing happens, open this URL yourself:" : "Open this URL in a browser on THIS machine:"
1006
+ );
1007
+ deps.out.log(` ${authorizeUrl}`);
1008
+ if (!opened) {
1009
+ deps.out.note(SSH_NOTE);
1010
+ }
1011
+ const callback = await deps.out.step(
1012
+ "Waiting for PostHog authorization (Ctrl-C aborts)",
1013
+ () => server.waitForCallback({ timeoutMs: opts.timeoutMs })
1014
+ );
1015
+ code = callback.code;
1016
+ } catch (err) {
1017
+ if (err instanceof LoopbackError) throw fromLoopbackError(err);
1018
+ throw err;
1019
+ } finally {
1020
+ await server.close();
1021
+ }
1022
+ const tokenEndpoint = metadata.token_endpoint;
1023
+ let tokens;
1024
+ try {
1025
+ tokens = await deps.out.step(
1026
+ `Exchanging code at ${tokenEndpoint}`,
1027
+ () => deps.exchangeCode({
1028
+ tokenEndpoint,
1029
+ clientId: POSTHOG_CLIENT_ID,
1030
+ code,
1031
+ codeVerifier: pkce.verifier,
1032
+ redirectUri: server.redirectUri
1033
+ })
1034
+ );
1035
+ } catch (err) {
1036
+ throw new ConnectError("exchange_failed", errMsg(err));
1037
+ }
1038
+ const expiresAt = new Date(
1039
+ deps.now().getTime() + tokens.expires_in * 1e3
1040
+ ).toISOString();
1041
+ const scopes = (tokens.scope ?? POSTHOG_SCOPES).split(" ");
1042
+ const scopedTeams = tokens.scoped_teams ?? [];
1043
+ const scopedOrganizations = tokens.scoped_organizations ?? [];
1044
+ try {
1045
+ await deps.out.step(
1046
+ `PUT ${base}/v1/admin/provider-credentials/posthog`,
1047
+ () => deps.http.put("/v1/admin/provider-credentials/posthog", {
1048
+ kind: "oauth",
1049
+ payload: {
1050
+ accessToken: tokens.access_token,
1051
+ refreshToken: tokens.refresh_token,
1052
+ expiresAt,
1053
+ tokenEndpoint,
1054
+ clientId: POSTHOG_CLIENT_ID,
1055
+ scopes,
1056
+ scopedTeams,
1057
+ scopedOrganizations
1058
+ }
1059
+ })
1060
+ );
1061
+ } catch (err) {
1062
+ throw new ConnectError("store_failed", errMsg(err));
1063
+ }
1064
+ const stored = {
1065
+ providerId: "posthog",
1066
+ instance: base,
1067
+ posthog: {
1068
+ privateHost,
1069
+ issuer: metadata.issuer,
1070
+ scopes: scopes.join(" "),
1071
+ scopedTeams,
1072
+ scopedOrganizations
1073
+ },
1074
+ credential: { stored: true, expiresAt }
1075
+ };
1076
+ const requestedScopes = POSTHOG_SCOPES.split(" ");
1077
+ const missingScopes = requestedScopes.filter((s) => !scopes.includes(s));
1078
+ if (missingScopes.length > 0) {
1079
+ deps.out.log(
1080
+ `note: PostHog granted ${scopes.length}/${requestedScopes.length} requested scope(s); missing: ${missingScopes.join(", ")}. Re-run \`hogsend connect posthog\` to grant the full set.`
1081
+ );
1082
+ }
1083
+ if (opts.noProvision) {
1084
+ return {
1085
+ verdict: "connected_no_provision",
1086
+ ...stored,
1087
+ provision: { attempted: false, skipped: "no_provision_flag" }
1088
+ };
1089
+ }
1090
+ if (isLoopbackUrl(info.apiPublicUrl)) {
1091
+ deps.out.note(LOOPBACK_URL_NOTE, "Instance not publicly reachable");
1092
+ return {
1093
+ verdict: "connected_no_provision",
1094
+ ...stored,
1095
+ provision: { attempted: false, skipped: "api_public_url_unreachable" }
1096
+ };
1097
+ }
1098
+ try {
1099
+ const result = await deps.out.step(
1100
+ `POST ${base}/v1/admin/analytics/provision-loop`,
1101
+ () => deps.http.post(
1102
+ "/v1/admin/analytics/provision-loop",
1103
+ {}
1104
+ )
1105
+ );
1106
+ printProvisioned(deps.out, result);
1107
+ return {
1108
+ verdict: "connected",
1109
+ ...stored,
1110
+ provision: {
1111
+ attempted: true,
1112
+ ok: true,
1113
+ created: result.created === true,
1114
+ hogFunctionId: result.hogFunctionId ?? "",
1115
+ webhookUrl: result.webhookUrl ?? ""
1116
+ }
1117
+ };
1118
+ } catch (err) {
1119
+ const message = errMsg(err);
1120
+ deps.out.log(
1121
+ `The credential is stored, but provisioning the event loop failed: ${message}. Re-run with: hogsend connect posthog --provision-only`
1122
+ );
1123
+ return {
1124
+ verdict: "connected_no_provision",
1125
+ ...stored,
1126
+ provision: { attempted: true, ok: false, error: message }
1127
+ };
1128
+ }
1129
+ }
1130
+
1131
+ // src/lib/prompt.ts
1132
+ import { cancel as cancel2, isCancel } from "@clack/prompts";
1133
+ function bail(value) {
1134
+ if (isCancel(value)) {
1135
+ cancel2("Cancelled.");
1136
+ process.exit(0);
1137
+ }
1138
+ return value;
1139
+ }
1140
+
1141
+ // src/commands/connect.ts
1142
+ var usage2 = `hogsend connect <provider> [--posthog-host <url>] [--provision-only] [--no-provision] [--no-browser] [--json]
1143
+
1144
+ Connect this Hogsend instance to an analytics provider via OAuth. Providers:
1145
+
1146
+ posthog Authorize Hogsend against your PostHog region (PKCE, loopback
1147
+ callback on 127.0.0.1), store the refresh token on the instance,
1148
+ then provision the PostHog -> Hogsend event loop (a PostHog
1149
+ destination posting to /v1/webhooks/posthog).
1150
+
1151
+ The browser consent must happen on THIS machine (the OAuth callback lands on
1152
+ 127.0.0.1). The target instance can be anywhere \u2014 point --url at it and run
1153
+ this command from your laptop, not from an SSH session on the server.
1154
+
1155
+ Options:
1156
+ --posthog-host PostHog app/private host to authorize against, e.g.
1157
+ https://eu.posthog.com or https://us.posthog.com (NOT the
1158
+ i. ingestion host). Required when the instance has no
1159
+ PostHog config and you're running non-interactively.
1160
+ --provision-only Skip OAuth; (re-)provision the event loop using the
1161
+ already-stored credential.
1162
+ --no-provision Stop after storing the credential.
1163
+ --no-browser Don't spawn a browser; just print the authorize URL.
1164
+ --url, --admin-key, --json, -h, --help Global flags as usual.
1165
+
1166
+ Exit code: 0 when a credential is stored (even if provisioning was skipped),
1167
+ 1 otherwise.`;
1168
+ async function run2(ctx) {
1169
+ const { values: values2, positionals } = parseArgs2({
1170
+ args: ctx.argv,
1171
+ allowPositionals: true,
1172
+ strict: false,
1173
+ options: {
1174
+ "posthog-host": { type: "string" },
1175
+ "provision-only": { type: "boolean", default: false },
1176
+ "no-provision": { type: "boolean", default: false },
1177
+ "no-browser": { type: "boolean", default: false },
1178
+ help: { type: "boolean", short: "h", default: false }
1179
+ }
1180
+ });
1181
+ if (values2.help) {
1182
+ ctx.out.log(usage2);
1183
+ return;
1184
+ }
1185
+ const provider = positionals[0];
1186
+ if (!provider) {
1187
+ ctx.out.fail("missing provider \u2014 try: hogsend connect posthog");
1188
+ }
1189
+ if (provider !== "posthog") {
1190
+ ctx.out.fail(`unknown provider "${provider}" \u2014 supported: posthog`);
1191
+ }
1192
+ if (values2["provision-only"] && values2["no-provision"]) {
1193
+ ctx.out.fail("--provision-only and --no-provision are mutually exclusive");
1194
+ }
1195
+ try {
1196
+ const target = new URL(ctx.cfg.baseUrl);
1197
+ if (target.protocol === "http:" && target.hostname !== "localhost" && target.hostname !== "127.0.0.1") {
1198
+ ctx.out.log(
1199
+ color.yellow(
1200
+ `warning: ${ctx.cfg.baseUrl} is plain http \u2014 OAuth tokens will be sent to it unencrypted; use https for remote instances.`
1201
+ )
1202
+ );
1203
+ }
1204
+ } catch {
1205
+ }
1206
+ ctx.out.intro(`${color.bgMagenta(color.black(" hogsend "))} connect`);
1207
+ const deps = {
1208
+ http: ctx.http,
1209
+ out: ctx.out,
1210
+ interactive: ctx.out.interactive,
1211
+ discover: discoverOAuthServer,
1212
+ startLoopback: startLoopbackServer,
1213
+ exchangeCode,
1214
+ openBrowser,
1215
+ confirm: async (message) => bail(await confirm({ message })),
1216
+ selectRegion: async () => {
1217
+ const choice = bail(
1218
+ await select({
1219
+ message: "Which PostHog region should Hogsend authorize against?",
1220
+ options: [
1221
+ { value: "https://eu.posthog.com", label: "PostHog EU Cloud" },
1222
+ { value: "https://us.posthog.com", label: "PostHog US Cloud" },
1223
+ { value: "custom", label: "Custom / self-hosted" }
1224
+ ]
1225
+ })
1226
+ );
1227
+ if (choice !== "custom") return choice;
1228
+ return bail(
1229
+ await text({
1230
+ message: "PostHog app/private host URL (e.g. https://posthog.example.com)",
1231
+ placeholder: "https://posthog.example.com"
1232
+ })
1233
+ );
1234
+ },
1235
+ now: () => /* @__PURE__ */ new Date()
1236
+ };
1237
+ try {
1238
+ const result = await runConnectPosthog(deps, {
1239
+ provisionOnly: Boolean(values2["provision-only"]),
1240
+ noProvision: Boolean(values2["no-provision"]),
1241
+ noBrowser: Boolean(values2["no-browser"]),
1242
+ posthogHost: typeof values2["posthog-host"] === "string" ? values2["posthog-host"] : void 0
1243
+ });
1244
+ if (ctx.json) {
1245
+ ctx.out.json({ ok: true, ...result });
1246
+ return;
1247
+ }
1248
+ ctx.out.outro(
1249
+ color.green(
1250
+ `connect: posthog ${result.verdict === "connected" ? "connected" : "connected (loop not provisioned)"}`
1251
+ )
1252
+ );
1253
+ } catch (error) {
1254
+ if (error instanceof ConnectError) {
1255
+ if (ctx.json) {
1256
+ ctx.out.json({
1257
+ ok: false,
1258
+ verdict: error.verdict,
1259
+ error: error.message,
1260
+ hint: error.hint
1261
+ });
1262
+ process.exit(1);
1263
+ }
1264
+ ctx.out.note(
1265
+ error.hint ? `${error.message}
1266
+
1267
+ ${error.hint}` : error.message
1268
+ );
1269
+ ctx.out.outro(color.red(`connect: ${error.verdict}`));
1270
+ process.exit(1);
1271
+ }
1272
+ throw error;
1273
+ }
1274
+ }
1275
+ var connectCommand = {
1276
+ name: "connect",
1277
+ summary: "Connect an analytics provider via OAuth (posthog)",
1278
+ usage: usage2,
1279
+ run: run2
467
1280
  };
468
1281
 
469
1282
  // src/commands/contacts.ts
470
- import { parseArgs as parseArgs2 } from "util";
471
- var usage2 = `hogsend contacts <subcommand> [options]
1283
+ import { parseArgs as parseArgs3 } from "util";
1284
+ var usage3 = `hogsend contacts <subcommand> [options]
472
1285
 
473
1286
  Inspect contacts via the admin API (/v1/admin/contacts) and upsert them via the
474
1287
  data plane (PUT /v1/contacts).
@@ -573,7 +1386,7 @@ async function fetchOrFail(ctx, label, fn) {
573
1386
  }
574
1387
  }
575
1388
  async function runList(ctx, argv) {
576
- const { values: values2 } = parseArgs2({
1389
+ const { values: values2 } = parseArgs3({
577
1390
  args: argv,
578
1391
  allowPositionals: true,
579
1392
  options: {
@@ -584,7 +1397,7 @@ async function runList(ctx, argv) {
584
1397
  }
585
1398
  });
586
1399
  if (values2.help) {
587
- ctx.out.log(usage2);
1400
+ ctx.out.log(usage3);
588
1401
  return;
589
1402
  }
590
1403
  const query = {
@@ -616,7 +1429,7 @@ async function runList(ctx, argv) {
616
1429
  );
617
1430
  }
618
1431
  async function runGet(ctx, argv) {
619
- const { values: values2, positionals } = parseArgs2({
1432
+ const { values: values2, positionals } = parseArgs3({
620
1433
  args: argv,
621
1434
  allowPositionals: true,
622
1435
  options: {
@@ -624,7 +1437,7 @@ async function runGet(ctx, argv) {
624
1437
  }
625
1438
  });
626
1439
  if (values2.help) {
627
- ctx.out.log(usage2);
1440
+ ctx.out.log(usage3);
628
1441
  return;
629
1442
  }
630
1443
  const id = positionals[1];
@@ -671,7 +1484,7 @@ async function runGet(ctx, argv) {
671
1484
  ctx.out.outro(`Contact ${color.cyan(contact.externalId)}`);
672
1485
  }
673
1486
  async function runTimeline(ctx, argv) {
674
- const { values: values2, positionals } = parseArgs2({
1487
+ const { values: values2, positionals } = parseArgs3({
675
1488
  args: argv,
676
1489
  allowPositionals: true,
677
1490
  options: {
@@ -682,7 +1495,7 @@ async function runTimeline(ctx, argv) {
682
1495
  }
683
1496
  });
684
1497
  if (values2.help) {
685
- ctx.out.log(usage2);
1498
+ ctx.out.log(usage3);
686
1499
  return;
687
1500
  }
688
1501
  const id = positionals[1];
@@ -736,7 +1549,7 @@ function summarizeTimelineEntry(entry) {
736
1549
  return `${subject} [${String(d.status ?? "")}]`;
737
1550
  }
738
1551
  async function runUpsert(ctx, argv) {
739
- const { values: values2 } = parseArgs2({
1552
+ const { values: values2 } = parseArgs3({
740
1553
  args: argv,
741
1554
  allowPositionals: true,
742
1555
  options: {
@@ -750,7 +1563,7 @@ async function runUpsert(ctx, argv) {
750
1563
  }
751
1564
  });
752
1565
  if (values2.help) {
753
- ctx.out.log(usage2);
1566
+ ctx.out.log(usage3);
754
1567
  return;
755
1568
  }
756
1569
  const email = values2.email;
@@ -790,7 +1603,7 @@ async function runUpsert(ctx, argv) {
790
1603
  const verb = res.created ? "created" : "updated";
791
1604
  ctx.out.outro(`Contact ${color.cyan(res.id)} ${verb}.`);
792
1605
  }
793
- async function run2(ctx) {
1606
+ async function run3(ctx) {
794
1607
  const sub = ctx.argv[0];
795
1608
  switch (sub) {
796
1609
  case "list":
@@ -815,21 +1628,21 @@ async function run2(ctx) {
815
1628
  var contactsCommand = {
816
1629
  name: "contacts",
817
1630
  summary: "List, inspect, trace, and upsert contacts",
818
- usage: usage2,
819
- run: run2
1631
+ usage: usage3,
1632
+ run: run3
820
1633
  };
821
1634
 
822
1635
  // src/commands/dev.ts
823
1636
  import { spawnSync as spawnSync2 } from "child_process";
824
1637
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
825
1638
  import { join as join3, resolve } from "path";
826
- import { parseArgs as parseArgs5 } from "util";
1639
+ import { parseArgs as parseArgs6 } from "util";
827
1640
 
828
1641
  // src/lib/proc.ts
829
- import { spawn } from "child_process";
1642
+ import { spawn as spawn2 } from "child_process";
830
1643
  import { createInterface } from "readline";
831
1644
  function spawnManaged(opts) {
832
- const child = spawn(opts.cmd, opts.args, {
1645
+ const child = spawn2(opts.cmd, opts.args, {
833
1646
  cwd: opts.cwd,
834
1647
  env: { ...process.env, FORCE_COLOR: "1", ...opts.env },
835
1648
  stdio: ["ignore", "pipe", "pipe"],
@@ -930,7 +1743,7 @@ async function waitForHttp(url, timeoutMs) {
930
1743
 
931
1744
  // src/lib/setup-steps.ts
932
1745
  import { spawnSync } from "child_process";
933
- import { randomBytes } from "crypto";
1746
+ import { randomBytes as randomBytes2 } from "crypto";
934
1747
  import { copyFileSync, existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
935
1748
  import { connect } from "net";
936
1749
  import { join as join2 } from "path";
@@ -938,10 +1751,10 @@ import { join as join2 } from "path";
938
1751
  // src/lib/config.ts
939
1752
  import { existsSync, readFileSync } from "fs";
940
1753
  import { join } from "path";
941
- import { parseArgs as parseArgs3 } from "util";
1754
+ import { parseArgs as parseArgs4 } from "util";
942
1755
  var DEFAULT_BASE_URL = "http://localhost:3002";
943
1756
  function parseGlobalFlags(argv) {
944
- const { values: values2, tokens } = parseArgs3({
1757
+ const { values: values2, tokens } = parseArgs4({
945
1758
  args: argv,
946
1759
  allowPositionals: true,
947
1760
  strict: false,
@@ -1022,7 +1835,7 @@ var SECRET_KEY = "BETTER_AUTH_SECRET";
1022
1835
  var PLACEHOLDER_PREFIX = "change-me";
1023
1836
  var PLACEHOLDER_PREFIXES = [PLACEHOLDER_PREFIX, "REPLACE_ME"];
1024
1837
  function generateSecret() {
1025
- return randomBytes(32).toString("hex");
1838
+ return randomBytes2(32).toString("hex");
1026
1839
  }
1027
1840
  var COMPOSE_FILES = [
1028
1841
  "docker-compose.yml",
@@ -1215,8 +2028,8 @@ async function detectRunningInfra(cwd) {
1215
2028
  }
1216
2029
 
1217
2030
  // src/commands/events.ts
1218
- import { parseArgs as parseArgs4 } from "util";
1219
- var usage3 = `hogsend events <userId> [options]
2031
+ import { parseArgs as parseArgs5 } from "util";
2032
+ var usage4 = `hogsend events <userId> [options]
1220
2033
  hogsend events send <name> [options]
1221
2034
 
1222
2035
  Read a single user's event history (admin API), or send an event into the data
@@ -1262,14 +2075,14 @@ Examples:
1262
2075
  hogsend events user_123 --from 2026-01-01T00:00:00Z --json
1263
2076
  hogsend events send signup --user-id user_123 --prop plan=pro
1264
2077
  hogsend events send purchase --email a@b.com --props '{"amount":49}' --json`;
1265
- async function run3(ctx) {
2078
+ async function run4(ctx) {
1266
2079
  if (ctx.argv[0] === "send") {
1267
2080
  return runSend2(ctx, ctx.argv.slice(1));
1268
2081
  }
1269
2082
  return runRead(ctx, ctx.argv);
1270
2083
  }
1271
2084
  async function runRead(ctx, argv) {
1272
- const { values: values2, positionals } = parseArgs4({
2085
+ const { values: values2, positionals } = parseArgs5({
1273
2086
  args: argv,
1274
2087
  allowPositionals: true,
1275
2088
  options: {
@@ -1282,7 +2095,7 @@ async function runRead(ctx, argv) {
1282
2095
  }
1283
2096
  });
1284
2097
  if (values2.help) {
1285
- ctx.out.log(usage3);
2098
+ ctx.out.log(usage4);
1286
2099
  return;
1287
2100
  }
1288
2101
  const userId = positionals[0];
@@ -1338,7 +2151,7 @@ async function runRead(ctx, argv) {
1338
2151
  );
1339
2152
  }
1340
2153
  async function runSend2(ctx, argv) {
1341
- const { values: values2, positionals } = parseArgs4({
2154
+ const { values: values2, positionals } = parseArgs5({
1342
2155
  args: argv,
1343
2156
  allowPositionals: true,
1344
2157
  options: {
@@ -1356,7 +2169,7 @@ async function runSend2(ctx, argv) {
1356
2169
  }
1357
2170
  });
1358
2171
  if (values2.help) {
1359
- ctx.out.log(usage3);
2172
+ ctx.out.log(usage4);
1360
2173
  return;
1361
2174
  }
1362
2175
  const name = positionals[0];
@@ -1494,12 +2307,12 @@ function summarizeProps(props) {
1494
2307
  var eventsCommand = {
1495
2308
  name: "events",
1496
2309
  summary: "Stream a user's event history, or send an event",
1497
- usage: usage3,
1498
- run: run3
2310
+ usage: usage4,
2311
+ run: run4
1499
2312
  };
1500
2313
 
1501
2314
  // src/commands/dev.ts
1502
- var usage4 = `hogsend dev [options]
2315
+ var usage5 = `hogsend dev [options]
1503
2316
  hogsend dev --fire <event> [event-send options]
1504
2317
 
1505
2318
  Run the full local stack for a Hogsend app from one command:
@@ -1582,7 +2395,7 @@ function extractFire(argv) {
1582
2395
  }
1583
2396
  async function runFire(ctx, event, rest) {
1584
2397
  if (rest.includes("-h") || rest.includes("--help")) {
1585
- ctx.out.log(usage4);
2398
+ ctx.out.log(usage5);
1586
2399
  return;
1587
2400
  }
1588
2401
  try {
@@ -1690,7 +2503,7 @@ async function prepareInfra(ctx, cwd, pkg) {
1690
2503
  ctx.out.log(color.dim(" no db:migrate script \u2014 skipping migrations"));
1691
2504
  }
1692
2505
  }
1693
- async function run4(ctx) {
2506
+ async function run5(ctx) {
1694
2507
  const fire = extractFire(ctx.argv);
1695
2508
  if (fire && "error" in fire) {
1696
2509
  ctx.out.fail(fire.error);
@@ -1699,7 +2512,7 @@ async function run4(ctx) {
1699
2512
  await runFire(ctx, fire.event, fire.rest);
1700
2513
  return;
1701
2514
  }
1702
- const { values: values2 } = parseArgs5({
2515
+ const { values: values2 } = parseArgs6({
1703
2516
  args: ctx.argv,
1704
2517
  allowPositionals: true,
1705
2518
  options: {
@@ -1710,7 +2523,7 @@ async function run4(ctx) {
1710
2523
  }
1711
2524
  });
1712
2525
  if (values2.help) {
1713
- ctx.out.log(usage4);
2526
+ ctx.out.log(usage5);
1714
2527
  return;
1715
2528
  }
1716
2529
  const cwd = resolve(values2.cwd ?? process.cwd());
@@ -1817,12 +2630,12 @@ ${color.dim("Shutting down\u2026")}`);
1817
2630
  var devCommand = {
1818
2631
  name: "dev",
1819
2632
  summary: "Run the full local stack: infra, API + worker, health, URLs",
1820
- usage: usage4,
1821
- run: run4
2633
+ usage: usage5,
2634
+ run: run5
1822
2635
  };
1823
2636
 
1824
2637
  // src/commands/doctor.ts
1825
- import { parseArgs as parseArgs6 } from "util";
2638
+ import { parseArgs as parseArgs7 } from "util";
1826
2639
 
1827
2640
  // src/lib/skills.ts
1828
2641
  import {
@@ -1973,7 +2786,7 @@ function analyticsNudge(ctx) {
1973
2786
  "PostHog person reads disabled"
1974
2787
  );
1975
2788
  }
1976
- var usage5 = `hogsend doctor [--url <baseUrl>] [--admin-key <key>] [--json]
2789
+ var usage6 = `hogsend doctor [--url <baseUrl>] [--admin-key <key>] [--json]
1977
2790
 
1978
2791
  Probe a running Hogsend instance via GET /v1/health and report its health:
1979
2792
  component status (database, redis), two-track schema state (engine + client),
@@ -2015,8 +2828,8 @@ function trackLine(name, track) {
2015
2828
  const required = track.required ?? color.dim("none");
2016
2829
  return `${color.bold(name.padEnd(7))} applied ${applied} -> required ${required} ${sync}`;
2017
2830
  }
2018
- async function run5(ctx) {
2019
- const { values: values2 } = parseArgs6({
2831
+ async function run6(ctx) {
2832
+ const { values: values2 } = parseArgs7({
2020
2833
  args: ctx.argv,
2021
2834
  allowPositionals: true,
2022
2835
  options: {
@@ -2026,7 +2839,7 @@ async function run5(ctx) {
2026
2839
  strict: false
2027
2840
  });
2028
2841
  if (values2.help) {
2029
- ctx.out.log(usage5);
2842
+ ctx.out.log(usage6);
2030
2843
  return;
2031
2844
  }
2032
2845
  const { baseUrl } = ctx.http.cfg;
@@ -2117,13 +2930,13 @@ async function run5(ctx) {
2117
2930
  var doctorCommand = {
2118
2931
  name: "doctor",
2119
2932
  summary: "Probe a running instance's health (GET /v1/health)",
2120
- usage: usage5,
2121
- run: run5
2933
+ usage: usage6,
2934
+ run: run6
2122
2935
  };
2123
2936
 
2124
2937
  // src/commands/domain.ts
2125
- import { parseArgs as parseArgs7 } from "util";
2126
- import { confirm } from "@clack/prompts";
2938
+ import { parseArgs as parseArgs8 } from "util";
2939
+ import { confirm as confirm2 } from "@clack/prompts";
2127
2940
 
2128
2941
  // src/lib/dns.ts
2129
2942
  import { resolveNs as nodeResolveNs } from "dns/promises";
@@ -2417,18 +3230,8 @@ async function applyRecords(opts) {
2417
3230
  return applyVercel(opts, fetchImpl);
2418
3231
  }
2419
3232
 
2420
- // src/lib/prompt.ts
2421
- import { cancel as cancel2, isCancel } from "@clack/prompts";
2422
- function bail(value) {
2423
- if (isCancel(value)) {
2424
- cancel2("Cancelled.");
2425
- process.exit(0);
2426
- }
2427
- return value;
2428
- }
2429
-
2430
3233
  // src/commands/domain.ts
2431
- var usage6 = `hogsend domain <subcommand> [options]
3234
+ var usage7 = `hogsend domain <subcommand> [options]
2432
3235
 
2433
3236
  Manage the sending domain through the RUNNING instance's admin routes
2434
3237
  (/v1/admin/domain) \u2014 provider API keys never touch the CLI. Requires an admin
@@ -2536,7 +3339,7 @@ function renderStatus(ctx, status) {
2536
3339
  }
2537
3340
  }
2538
3341
  async function runAdd(ctx, argv) {
2539
- const { values: values2, positionals } = parseArgs7({
3342
+ const { values: values2, positionals } = parseArgs8({
2540
3343
  args: argv,
2541
3344
  allowPositionals: true,
2542
3345
  options: {
@@ -2546,7 +3349,7 @@ async function runAdd(ctx, argv) {
2546
3349
  }
2547
3350
  });
2548
3351
  if (values2.help) {
2549
- ctx.out.log(usage6);
3352
+ ctx.out.log(usage7);
2550
3353
  return;
2551
3354
  }
2552
3355
  const domain = positionals[0];
@@ -2572,7 +3375,7 @@ async function runAdd(ctx, argv) {
2572
3375
  const autoAvailable = canAutoApply(host.id, process.env);
2573
3376
  if (autoAvailable && records.length > 0) {
2574
3377
  const doApply = values2.apply ? true : values2["no-apply"] ? false : ctx.out.interactive ? bail(
2575
- await confirm({
3378
+ await confirm2({
2576
3379
  message: `Apply these records via the ${host.label} API?`,
2577
3380
  initialValue: true
2578
3381
  })
@@ -2621,7 +3424,7 @@ async function runAdd(ctx, argv) {
2621
3424
  );
2622
3425
  }
2623
3426
  async function runCheck(ctx, argv) {
2624
- const { values: values2, positionals } = parseArgs7({
3427
+ const { values: values2, positionals } = parseArgs8({
2625
3428
  args: argv,
2626
3429
  allowPositionals: true,
2627
3430
  options: {
@@ -2631,7 +3434,7 @@ async function runCheck(ctx, argv) {
2631
3434
  }
2632
3435
  });
2633
3436
  if (values2.help) {
2634
- ctx.out.log(usage6);
3437
+ ctx.out.log(usage7);
2635
3438
  return;
2636
3439
  }
2637
3440
  const timeoutSecs = Number(values2.timeout);
@@ -2703,7 +3506,7 @@ async function runCheck(ctx, argv) {
2703
3506
  }
2704
3507
  }
2705
3508
  async function runStatus2(ctx, argv) {
2706
- const { values: values2 } = parseArgs7({
3509
+ const { values: values2 } = parseArgs8({
2707
3510
  args: argv,
2708
3511
  allowPositionals: false,
2709
3512
  options: {
@@ -2712,7 +3515,7 @@ async function runStatus2(ctx, argv) {
2712
3515
  }
2713
3516
  });
2714
3517
  if (values2.help) {
2715
- ctx.out.log(usage6);
3518
+ ctx.out.log(usage7);
2716
3519
  return;
2717
3520
  }
2718
3521
  const status = await getStatus(ctx, { refresh: values2.refresh });
@@ -2732,11 +3535,11 @@ async function runStatus2(ctx, argv) {
2732
3535
  }
2733
3536
  ctx.out.outro("Done.");
2734
3537
  }
2735
- async function run6(ctx) {
3538
+ async function run7(ctx) {
2736
3539
  const sub = ctx.argv[0];
2737
3540
  const rest = ctx.argv.slice(1);
2738
3541
  if (!sub || sub === "--help" || sub === "-h" || sub === "help") {
2739
- ctx.out.log(usage6);
3542
+ ctx.out.log(usage7);
2740
3543
  return;
2741
3544
  }
2742
3545
  switch (sub) {
@@ -2755,15 +3558,15 @@ async function run6(ctx) {
2755
3558
  var domainCommand = {
2756
3559
  name: "domain",
2757
3560
  summary: "Set up + verify the sending domain (DNS records, auto-apply)",
2758
- usage: usage6,
2759
- run: run6
3561
+ usage: usage7,
3562
+ run: run7
2760
3563
  };
2761
3564
 
2762
3565
  // src/commands/eject.ts
2763
3566
  import { existsSync as existsSync6, realpathSync } from "fs";
2764
3567
  import { createRequire as createRequire2 } from "module";
2765
3568
  import { dirname, join as join6, sep as sep2 } from "path";
2766
- import { parseArgs as parseArgs8 } from "util";
3569
+ import { parseArgs as parseArgs9 } from "util";
2767
3570
 
2768
3571
  // src/eject.ts
2769
3572
  import { existsSync as existsSync5 } from "fs";
@@ -2877,7 +3680,7 @@ async function countFiles(dir) {
2877
3680
  }
2878
3681
 
2879
3682
  // src/commands/eject.ts
2880
- var usage7 = `hogsend eject <package> [--force] [--cwd <dir>]
3683
+ var usage8 = `hogsend eject <package> [--force] [--cwd <dir>]
2881
3684
 
2882
3685
  Copy a @hogsend/* package's source into vendor/<name> and rewrite the consumer
2883
3686
  dependency to file:./vendor/<name>. Every other dependency keeps upgrading.
@@ -2905,8 +3708,8 @@ function resolveSourceDir(pkg, consumerRoot) {
2905
3708
  }
2906
3709
  return null;
2907
3710
  }
2908
- async function run7(ctx) {
2909
- const { values: values2, positionals } = parseArgs8({
3711
+ async function run8(ctx) {
3712
+ const { values: values2, positionals } = parseArgs9({
2910
3713
  args: ctx.argv,
2911
3714
  allowPositionals: true,
2912
3715
  options: {
@@ -2916,7 +3719,7 @@ async function run7(ctx) {
2916
3719
  }
2917
3720
  });
2918
3721
  if (values2.help) {
2919
- ctx.out.log(usage7);
3722
+ ctx.out.log(usage8);
2920
3723
  return;
2921
3724
  }
2922
3725
  const pkg = positionals[0];
@@ -2960,13 +3763,13 @@ async function run7(ctx) {
2960
3763
  var ejectCommand = {
2961
3764
  name: "eject",
2962
3765
  summary: "Vendor a @hogsend/* package into vendor/<name>",
2963
- usage: usage7,
2964
- run: run7
3766
+ usage: usage8,
3767
+ run: run8
2965
3768
  };
2966
3769
 
2967
3770
  // src/commands/emails.ts
2968
- import { parseArgs as parseArgs9 } from "util";
2969
- var usage8 = `hogsend emails <subcommand> [options]
3771
+ import { parseArgs as parseArgs10 } from "util";
3772
+ var usage9 = `hogsend emails <subcommand> [options]
2970
3773
 
2971
3774
  Send a transactional email through the data plane (POST /v1/emails). The send
2972
3775
  runs through the full preferences + tracking pipeline (link-click + open).
@@ -3044,7 +3847,7 @@ function statusColor2(status) {
3044
3847
  }
3045
3848
  }
3046
3849
  async function runSend3(ctx, argv) {
3047
- const { values: values2, positionals } = parseArgs9({
3850
+ const { values: values2, positionals } = parseArgs10({
3048
3851
  args: argv,
3049
3852
  allowPositionals: true,
3050
3853
  options: {
@@ -3062,7 +3865,7 @@ async function runSend3(ctx, argv) {
3062
3865
  }
3063
3866
  });
3064
3867
  if (values2.help) {
3065
- ctx.out.log(usage8);
3868
+ ctx.out.log(usage9);
3066
3869
  return;
3067
3870
  }
3068
3871
  const template = positionals[0];
@@ -3118,7 +3921,7 @@ async function runSend3(ctx, argv) {
3118
3921
  );
3119
3922
  ctx.out.outro(`${template} \u2192 ${statusColor2(res.status)}.`);
3120
3923
  }
3121
- async function run8(ctx) {
3924
+ async function run9(ctx) {
3122
3925
  const sub = ctx.argv[0];
3123
3926
  switch (sub) {
3124
3927
  case "send":
@@ -3135,12 +3938,12 @@ async function run8(ctx) {
3135
3938
  var emailsCommand = {
3136
3939
  name: "emails",
3137
3940
  summary: "Send a transactional email through the data plane",
3138
- usage: usage8,
3139
- run: run8
3941
+ usage: usage9,
3942
+ run: run9
3140
3943
  };
3141
3944
 
3142
3945
  // src/commands/hatchet.ts
3143
- import { parseArgs as parseArgs10 } from "util";
3946
+ import { parseArgs as parseArgs11 } from "util";
3144
3947
 
3145
3948
  // src/lib/hatchet-token.ts
3146
3949
  var SLUG_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
@@ -3311,7 +4114,7 @@ async function mintHatchetToken(opts) {
3311
4114
  }
3312
4115
 
3313
4116
  // src/commands/hatchet.ts
3314
- var usage9 = `hogsend hatchet token [options]
4117
+ var usage10 = `hogsend hatchet token [options]
3315
4118
 
3316
4119
  Mint a Hatchet API token (HATCHET_CLIENT_TOKEN) headlessly against a
3317
4120
  hatchet-lite instance. Registers the account if the instance still allows
@@ -3349,7 +4152,7 @@ public URL can create an account. On a public deployment set
3349
4152
  SERVER_ALLOW_SIGNUP=false (plus a real ADMIN_EMAIL/ADMIN_PASSWORD, which
3350
4153
  hatchet-lite seeds at boot); this command then logs in with those credentials.`;
3351
4154
  function parseTokenFlags(argv) {
3352
- const { values: values2 } = parseArgs10({
4155
+ const { values: values2 } = parseArgs11({
3353
4156
  args: argv,
3354
4157
  allowPositionals: true,
3355
4158
  strict: false,
@@ -3415,15 +4218,15 @@ async function runToken(ctx, argv) {
3415
4218
  throw err;
3416
4219
  }
3417
4220
  }
3418
- async function run9(ctx) {
4221
+ async function run10(ctx) {
3419
4222
  const sub = ctx.argv[0];
3420
4223
  const rest = ctx.argv.slice(1);
3421
4224
  if (!sub || sub === "--help" || sub === "-h" || sub === "help") {
3422
- ctx.out.log(usage9);
4225
+ ctx.out.log(usage10);
3423
4226
  return;
3424
4227
  }
3425
4228
  if (rest.includes("-h") || rest.includes("--help")) {
3426
- ctx.out.log(usage9);
4229
+ ctx.out.log(usage10);
3427
4230
  return;
3428
4231
  }
3429
4232
  switch (sub) {
@@ -3436,13 +4239,13 @@ async function run9(ctx) {
3436
4239
  var hatchetCommand = {
3437
4240
  name: "hatchet",
3438
4241
  summary: "Hatchet helpers \u2014 mint a HATCHET_CLIENT_TOKEN headlessly",
3439
- usage: usage9,
3440
- run: run9
4242
+ usage: usage10,
4243
+ run: run10
3441
4244
  };
3442
4245
 
3443
4246
  // src/commands/journeys.ts
3444
- import { parseArgs as parseArgs11 } from "util";
3445
- var usage10 = `hogsend journeys <subcommand> [options]
4247
+ import { parseArgs as parseArgs12 } from "util";
4248
+ var usage11 = `hogsend journeys <subcommand> [options]
3446
4249
 
3447
4250
  Inspect and toggle journeys via the admin API (/v1/admin/journeys).
3448
4251
 
@@ -3471,7 +4274,7 @@ function statusColor3(enabled) {
3471
4274
  return enabled ? color.green("enabled") : color.yellow("disabled");
3472
4275
  }
3473
4276
  async function runList2(ctx) {
3474
- const { values: values2 } = parseArgs11({
4277
+ const { values: values2 } = parseArgs12({
3475
4278
  args: ctx.argv,
3476
4279
  allowPositionals: true,
3477
4280
  options: {
@@ -3482,7 +4285,7 @@ async function runList2(ctx) {
3482
4285
  }
3483
4286
  });
3484
4287
  if (values2.help) {
3485
- ctx.out.log(usage10);
4288
+ ctx.out.log(usage11);
3486
4289
  return;
3487
4290
  }
3488
4291
  if (values2.enabled !== void 0 && !["true", "false"].includes(values2.enabled)) {
@@ -3618,7 +4421,7 @@ async function runToggle(ctx, id, enabled) {
3618
4421
  );
3619
4422
  ctx.out.outro(`${j.id} is now ${statusColor3(j.enabled)}.`);
3620
4423
  }
3621
- async function run10(ctx) {
4424
+ async function run11(ctx) {
3622
4425
  const sub = ctx.argv[0];
3623
4426
  const rest = ctx.argv.slice(1);
3624
4427
  const subCtx = { ...ctx, argv: rest };
@@ -3630,7 +4433,7 @@ async function run10(ctx) {
3630
4433
  case "get": {
3631
4434
  const id = rest.find((a) => !a.startsWith("-"));
3632
4435
  if (rest.includes("--help") || rest.includes("-h")) {
3633
- ctx.out.log(usage10);
4436
+ ctx.out.log(usage11);
3634
4437
  return;
3635
4438
  }
3636
4439
  await runGet2(subCtx, id);
@@ -3638,7 +4441,7 @@ async function run10(ctx) {
3638
4441
  }
3639
4442
  case "enable": {
3640
4443
  if (rest.includes("--help") || rest.includes("-h")) {
3641
- ctx.out.log(usage10);
4444
+ ctx.out.log(usage11);
3642
4445
  return;
3643
4446
  }
3644
4447
  await runToggle(
@@ -3650,7 +4453,7 @@ async function run10(ctx) {
3650
4453
  }
3651
4454
  case "disable": {
3652
4455
  if (rest.includes("--help") || rest.includes("-h")) {
3653
- ctx.out.log(usage10);
4456
+ ctx.out.log(usage11);
3654
4457
  return;
3655
4458
  }
3656
4459
  await runToggle(
@@ -3684,14 +4487,14 @@ async function run10(ctx) {
3684
4487
  var journeysCommand = {
3685
4488
  name: "journeys",
3686
4489
  summary: "List, inspect, enable, and disable journeys",
3687
- usage: usage10,
3688
- run: run10
4490
+ usage: usage11,
4491
+ run: run11
3689
4492
  };
3690
4493
 
3691
4494
  // src/commands/patch.ts
3692
4495
  import { spawnSync as spawnSync3 } from "child_process";
3693
- import { parseArgs as parseArgs12 } from "util";
3694
- var usage11 = `hogsend patch <package> [--cwd <dir>]
4496
+ import { parseArgs as parseArgs13 } from "util";
4497
+ var usage12 = `hogsend patch <package> [--cwd <dir>]
3695
4498
 
3696
4499
  Thin wrapper over pnpm's native patch flow. Runs \`pnpm patch <package>\`, which
3697
4500
  extracts the package into a temp dir and prints the path to edit. After editing,
@@ -3702,8 +4505,8 @@ This does NOT replace scripts/patch-check.sh (the patch re-apply contract).
3702
4505
  Options:
3703
4506
  --cwd <dir> Project root to run pnpm in (defaults to current directory).
3704
4507
  -h, --help Show this help.`;
3705
- async function run11(ctx) {
3706
- const { values: values2, positionals } = parseArgs12({
4508
+ async function run12(ctx) {
4509
+ const { values: values2, positionals } = parseArgs13({
3707
4510
  args: ctx.argv,
3708
4511
  allowPositionals: true,
3709
4512
  options: {
@@ -3712,7 +4515,7 @@ async function run11(ctx) {
3712
4515
  }
3713
4516
  });
3714
4517
  if (values2.help) {
3715
- ctx.out.log(usage11);
4518
+ ctx.out.log(usage12);
3716
4519
  return;
3717
4520
  }
3718
4521
  const pkg = positionals[0];
@@ -3752,16 +4555,16 @@ async function run11(ctx) {
3752
4555
  var patchCommand = {
3753
4556
  name: "patch",
3754
4557
  summary: "Patch a package via pnpm's native patch flow",
3755
- usage: usage11,
3756
- run: run11
4558
+ usage: usage12,
4559
+ run: run12
3757
4560
  };
3758
4561
 
3759
4562
  // src/commands/setup.ts
3760
4563
  import { existsSync as existsSync7 } from "fs";
3761
4564
  import { join as join7 } from "path";
3762
- import { parseArgs as parseArgs13 } from "util";
3763
- import { confirm as confirm2 } from "@clack/prompts";
3764
- var usage12 = `hogsend setup [--cwd <dir>] [--yes] [--json]
4565
+ import { parseArgs as parseArgs14 } from "util";
4566
+ import { confirm as confirm3 } from "@clack/prompts";
4567
+ var usage13 = `hogsend setup [--cwd <dir>] [--yes] [--json]
3765
4568
 
3766
4569
  Interactive local onboarding for a scaffolded Hogsend app. Mirrors the
3767
4570
  create-hogsend "next steps":
@@ -3778,8 +4581,8 @@ Options:
3778
4581
  -h, --help Show this help.
3779
4582
 
3780
4583
  Run ${color.cyan("hogsend doctor")} afterwards to verify the instance is healthy.`;
3781
- async function run12(ctx) {
3782
- const { values: values2 } = parseArgs13({
4584
+ async function run13(ctx) {
4585
+ const { values: values2 } = parseArgs14({
3783
4586
  args: ctx.argv,
3784
4587
  allowPositionals: true,
3785
4588
  options: {
@@ -3789,7 +4592,7 @@ async function run12(ctx) {
3789
4592
  }
3790
4593
  });
3791
4594
  if (values2.help) {
3792
- ctx.out.log(usage12);
4595
+ ctx.out.log(usage13);
3793
4596
  return;
3794
4597
  }
3795
4598
  const cwd = values2.cwd ?? process.cwd();
@@ -3807,7 +4610,7 @@ async function run12(ctx) {
3807
4610
  }
3808
4611
  if (ctx.out.interactive && !skipConfirm) {
3809
4612
  const proceed = bail(
3810
- await confirm2({
4613
+ await confirm3({
3811
4614
  message: `Set up local infra in ${color.cyan(cwd)}? (docker compose up, .env, db:migrate)`
3812
4615
  })
3813
4616
  );
@@ -3904,16 +4707,16 @@ async function run12(ctx) {
3904
4707
  var setupCommand = {
3905
4708
  name: "setup",
3906
4709
  summary: "Local onboarding: docker compose up, gen secret, db:migrate",
3907
- usage: usage12,
3908
- run: run12
4710
+ usage: usage13,
4711
+ run: run13
3909
4712
  };
3910
4713
 
3911
4714
  // src/commands/skills.ts
3912
4715
  import { existsSync as existsSync8 } from "fs";
3913
4716
  import { join as join8 } from "path";
3914
- import { parseArgs as parseArgs14 } from "util";
4717
+ import { parseArgs as parseArgs15 } from "util";
3915
4718
  import { multiselect } from "@clack/prompts";
3916
- var usage13 = `hogsend skills <subcommand> [options]
4719
+ var usage14 = `hogsend skills <subcommand> [options]
3917
4720
 
3918
4721
  Manage the Claude Code skills bundled with @hogsend/cli. Bundled skills teach
3919
4722
  agents how to drive the hogsend CLI; \`add\` copies them into your project's
@@ -3974,7 +4777,7 @@ function runList3(ctx) {
3974
4777
  );
3975
4778
  }
3976
4779
  async function runAdd2(ctx, argv) {
3977
- const { values: values2, positionals } = parseArgs14({
4780
+ const { values: values2, positionals } = parseArgs15({
3978
4781
  args: argv,
3979
4782
  allowPositionals: true,
3980
4783
  options: {
@@ -3984,7 +4787,7 @@ async function runAdd2(ctx, argv) {
3984
4787
  }
3985
4788
  });
3986
4789
  if (values2.help) {
3987
- ctx.out.log(usage13);
4790
+ ctx.out.log(usage14);
3988
4791
  return;
3989
4792
  }
3990
4793
  const cwd = process.cwd();
@@ -4052,7 +4855,7 @@ async function runAdd2(ctx, argv) {
4052
4855
  `Installed ${installedCount} skill${installedCount === 1 ? "" : "s"}` + (skippedCount > 0 ? `, skipped ${skippedCount}.` : ".")
4053
4856
  );
4054
4857
  }
4055
- async function run13(ctx) {
4858
+ async function run14(ctx) {
4056
4859
  const sub = ctx.argv[0];
4057
4860
  switch (sub) {
4058
4861
  case "list":
@@ -4064,7 +4867,7 @@ async function run13(ctx) {
4064
4867
  case void 0:
4065
4868
  case "-h":
4066
4869
  case "--help":
4067
- ctx.out.log(usage13);
4870
+ ctx.out.log(usage14);
4068
4871
  return;
4069
4872
  default:
4070
4873
  ctx.out.fail(
@@ -4075,13 +4878,13 @@ async function run13(ctx) {
4075
4878
  var skillsCommand = {
4076
4879
  name: "skills",
4077
4880
  summary: "List + install bundled Claude Code skills into .claude/skills",
4078
- usage: usage13,
4079
- run: run13
4881
+ usage: usage14,
4882
+ run: run14
4080
4883
  };
4081
4884
 
4082
4885
  // src/commands/stats.ts
4083
- import { parseArgs as parseArgs15 } from "util";
4084
- var usage14 = `hogsend stats [--json]
4886
+ import { parseArgs as parseArgs16 } from "util";
4887
+ var usage15 = `hogsend stats [--json]
4085
4888
 
4086
4889
  Show system-wide overview metrics from a running Hogsend instance.
4087
4890
  Wraps GET /v1/admin/metrics/overview.
@@ -4103,8 +4906,8 @@ Options:
4103
4906
  function pct(rate) {
4104
4907
  return `${(rate * 100).toFixed(2)}%`;
4105
4908
  }
4106
- async function run14(ctx) {
4107
- const { values: values2 } = parseArgs15({
4909
+ async function run15(ctx) {
4910
+ const { values: values2 } = parseArgs16({
4108
4911
  args: ctx.argv,
4109
4912
  allowPositionals: true,
4110
4913
  options: {
@@ -4112,7 +4915,7 @@ async function run14(ctx) {
4112
4915
  }
4113
4916
  });
4114
4917
  if (values2.help) {
4115
- ctx.out.log(usage14);
4918
+ ctx.out.log(usage15);
4116
4919
  return;
4117
4920
  }
4118
4921
  const metrics = await ctx.out.step(
@@ -4141,21 +4944,20 @@ async function run14(ctx) {
4141
4944
  var statsCommand = {
4142
4945
  name: "stats",
4143
4946
  summary: "Show system-wide overview metrics",
4144
- usage: usage14,
4145
- run: run14
4947
+ usage: usage15,
4948
+ run: run15
4146
4949
  };
4147
4950
 
4148
4951
  // src/commands/studio.ts
4149
- import { spawn as spawn2 } from "child_process";
4150
4952
  import { createReadStream, existsSync as existsSync9, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
4151
- import { createServer } from "http";
4953
+ import { createServer as createServer2 } from "http";
4152
4954
  import { extname, join as join9, normalize, resolve as resolve2, sep as sep3 } from "path";
4153
4955
  import { fileURLToPath as fileURLToPath2 } from "url";
4154
- import { parseArgs as parseArgs17 } from "util";
4956
+ import { parseArgs as parseArgs18 } from "util";
4155
4957
 
4156
4958
  // src/commands/studio-admin.ts
4157
- import { parseArgs as parseArgs16 } from "util";
4158
- import { password as passwordPrompt, text as text2 } from "@clack/prompts";
4959
+ import { parseArgs as parseArgs17 } from "util";
4960
+ import { password as passwordPrompt, text as text3 } from "@clack/prompts";
4159
4961
 
4160
4962
  // ../../node_modules/.pnpm/postgres@3.4.9/node_modules/postgres/src/index.js
4161
4963
  import os from "os";
@@ -4461,7 +5263,7 @@ function values(first, rest, parameters, types2, options) {
4461
5263
  const columns = rest.length ? rest.flat() : Object.keys(multi ? first[0] : first);
4462
5264
  return valuesBuilder(multi ? first : [first], parameters, types2, columns, options);
4463
5265
  }
4464
- function select(first, rest, parameters, types2, options) {
5266
+ function select2(first, rest, parameters, types2, options) {
4465
5267
  typeof first === "string" && (first = [first].concat(rest));
4466
5268
  if (Array.isArray(first))
4467
5269
  return escapeIdentifiers(first, options);
@@ -4478,10 +5280,10 @@ var builders = Object.entries({
4478
5280
  const x = values(...xs);
4479
5281
  return x === "()" ? "(null)" : x;
4480
5282
  },
4481
- select,
4482
- as: select,
4483
- returning: select,
4484
- "\\(": select,
5283
+ select: select2,
5284
+ as: select2,
5285
+ returning: select2,
5286
+ "\\(": select2,
4485
5287
  update(first, rest, parameters, types2, options) {
4486
5288
  return (rest.length ? rest.flat() : Object.keys(first)).map(
4487
5289
  (x) => escapeIdentifier(options.transform.column.to ? options.transform.column.to(x) : x) + "=" + stringifyValue("values", first[x], parameters, types2, options)
@@ -8829,7 +9631,7 @@ var PgText = class extends PgColumn {
8829
9631
  return "text";
8830
9632
  }
8831
9633
  };
8832
- function text(a, b2 = {}) {
9634
+ function text2(a, b2 = {}) {
8833
9635
  const { name, config } = getColumnNameAndConfig(a, b2);
8834
9636
  return new PgTextBuilder(name, config);
8835
9637
  }
@@ -9151,7 +9953,7 @@ function getPgColumnBuilders() {
9151
9953
  serial,
9152
9954
  smallint,
9153
9955
  smallserial,
9154
- text,
9956
+ text: text2,
9155
9957
  time,
9156
9958
  timestamp,
9157
9959
  uuid,
@@ -10160,14 +10962,14 @@ var PgDialect = class {
10160
10962
  const offsetSql = offset ? sql` offset ${offset}` : void 0;
10161
10963
  return sql`${leftChunk}${operatorChunk}${rightChunk}${orderBySql}${limitSql}${offsetSql}`;
10162
10964
  }
10163
- buildInsertQuery({ table, values: valuesOrSelect, onConflict, returning, withList, select: select2, overridingSystemValue_ }) {
10965
+ buildInsertQuery({ table, values: valuesOrSelect, onConflict, returning, withList, select: select3, overridingSystemValue_ }) {
10164
10966
  const valuesSqlList = [];
10165
10967
  const columns = table[Table.Symbol.Columns];
10166
10968
  const colEntries = Object.entries(columns).filter(([_, col]) => !col.shouldDisableInsert());
10167
10969
  const insertOrder = colEntries.map(
10168
10970
  ([, column]) => sql.identifier(this.casing.getColumnCasing(column))
10169
10971
  );
10170
- if (select2) {
10972
+ if (select3) {
10171
10973
  const select22 = valuesOrSelect;
10172
10974
  if (is(select22, SQL)) {
10173
10975
  valuesSqlList.push(select22);
@@ -11740,10 +12542,10 @@ var PgSelectBase = class extends PgSelectQueryBuilderBase {
11740
12542
  applyMixins(PgSelectBase, [QueryPromise]);
11741
12543
  function createSetOperator(type, isAll) {
11742
12544
  return (leftSelect, rightSelect, ...restSelects) => {
11743
- const setOperators = [rightSelect, ...restSelects].map((select2) => ({
12545
+ const setOperators = [rightSelect, ...restSelects].map((select3) => ({
11744
12546
  type,
11745
12547
  isAll,
11746
- rightSelect: select2
12548
+ rightSelect: select3
11747
12549
  }));
11748
12550
  for (const setOperator of setOperators) {
11749
12551
  if (!haveSameKeys(leftSelect.getSelectedFields(), setOperator.rightSelect.getSelectedFields())) {
@@ -11799,7 +12601,7 @@ var QueryBuilder = class {
11799
12601
  };
11800
12602
  with(...queries) {
11801
12603
  const self = this;
11802
- function select2(fields) {
12604
+ function select3(fields) {
11803
12605
  return new PgSelectBuilder({
11804
12606
  fields: fields ?? void 0,
11805
12607
  session: void 0,
@@ -11823,7 +12625,7 @@ var QueryBuilder = class {
11823
12625
  distinct: { on }
11824
12626
  });
11825
12627
  }
11826
- return { select: select2, selectDistinct, selectDistinctOn };
12628
+ return { select: select3, selectDistinct, selectDistinctOn };
11827
12629
  }
11828
12630
  select(fields) {
11829
12631
  return new PgSelectBuilder({
@@ -12012,21 +12814,21 @@ var PgInsertBuilder = class {
12012
12814
  ).setToken(this.authToken);
12013
12815
  }
12014
12816
  select(selectQuery) {
12015
- const select2 = typeof selectQuery === "function" ? selectQuery(new QueryBuilder()) : selectQuery;
12016
- if (!is(select2, SQL) && !haveSameKeys(this.table[Columns], select2._.selectedFields)) {
12817
+ const select3 = typeof selectQuery === "function" ? selectQuery(new QueryBuilder()) : selectQuery;
12818
+ if (!is(select3, SQL) && !haveSameKeys(this.table[Columns], select3._.selectedFields)) {
12017
12819
  throw new Error(
12018
12820
  "Insert select error: selected fields are not the same or are in a different order compared to the table definition"
12019
12821
  );
12020
12822
  }
12021
- return new PgInsertBase(this.table, select2, this.session, this.dialect, this.withList, true);
12823
+ return new PgInsertBase(this.table, select3, this.session, this.dialect, this.withList, true);
12022
12824
  }
12023
12825
  };
12024
12826
  var PgInsertBase = class extends QueryPromise {
12025
- constructor(table, values2, session2, dialect, withList, select2, overridingSystemValue_) {
12827
+ constructor(table, values2, session2, dialect, withList, select3, overridingSystemValue_) {
12026
12828
  super();
12027
12829
  this.session = session2;
12028
12830
  this.dialect = dialect;
12029
- this.config = { table, values: values2, withList, select: select2, overridingSystemValue_ };
12831
+ this.config = { table, values: values2, withList, select: select3, overridingSystemValue_ };
12030
12832
  }
12031
12833
  static [entityKind] = "PgInsert";
12032
12834
  config;
@@ -12729,7 +13531,7 @@ var PgDatabase = class {
12729
13531
  */
12730
13532
  with(...queries) {
12731
13533
  const self = this;
12732
- function select2(fields) {
13534
+ function select3(fields) {
12733
13535
  return new PgSelectBuilder({
12734
13536
  fields: fields ?? void 0,
12735
13537
  session: self.session,
@@ -12764,7 +13566,7 @@ var PgDatabase = class {
12764
13566
  function delete_(table) {
12765
13567
  return new PgDeleteBase(table, self.session, self.dialect, queries);
12766
13568
  }
12767
- return { select: select2, selectDistinct, selectDistinctOn, update, insert, delete: delete_ };
13569
+ return { select: select3, selectDistinct, selectDistinctOn, update, insert, delete: delete_ };
12768
13570
  }
12769
13571
  select(fields) {
12770
13572
  return new PgSelectBuilder({
@@ -13342,6 +14144,7 @@ __export(schema_exports, {
13342
14144
  memberRelations: () => memberRelations,
13343
14145
  organization: () => organization,
13344
14146
  organizationRelations: () => organizationRelations,
14147
+ providerCredentials: () => providerCredentials,
13345
14148
  session: () => session,
13346
14149
  sessionRelations: () => sessionRelations,
13347
14150
  trackedLinks: () => trackedLinks,
@@ -13427,7 +14230,7 @@ var alertRules = pgTable(
13427
14230
  "alert_rules",
13428
14231
  {
13429
14232
  id: uuid("id").defaultRandom().primaryKey(),
13430
- name: text("name").notNull(),
14233
+ name: text2("name").notNull(),
13431
14234
  type: alertRuleTypeEnum("type").notNull(),
13432
14235
  threshold: jsonb("threshold").$type().notNull(),
13433
14236
  channel: alertChannelEnum("channel").notNull(),
@@ -13452,8 +14255,8 @@ var alertHistory = pgTable(
13452
14255
  id: uuid("id").defaultRandom().primaryKey(),
13453
14256
  alertRuleId: uuid("alert_rule_id").notNull().references(() => alertRules.id),
13454
14257
  payload: jsonb("payload").$type(),
13455
- deliveryStatus: text("delivery_status").notNull(),
13456
- error: text("error"),
14258
+ deliveryStatus: text2("delivery_status").notNull(),
14259
+ error: text2("error"),
13457
14260
  ...timestamps
13458
14261
  },
13459
14262
  (table) => [
@@ -13467,12 +14270,12 @@ var apiKeys = pgTable(
13467
14270
  "api_keys",
13468
14271
  {
13469
14272
  id: uuid("id").defaultRandom().primaryKey(),
13470
- organizationId: text("organization_id"),
13471
- name: text("name").notNull(),
13472
- keyPrefix: text("key_prefix").notNull(),
13473
- keyHash: text("key_hash").notNull().unique(),
14273
+ organizationId: text2("organization_id"),
14274
+ name: text2("name").notNull(),
14275
+ keyPrefix: text2("key_prefix").notNull(),
14276
+ keyHash: text2("key_hash").notNull().unique(),
13474
14277
  scopes: jsonb("scopes").$type().notNull().default(["read"]),
13475
- createdBy: text("created_by"),
14278
+ createdBy: text2("created_by"),
13476
14279
  lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
13477
14280
  revokedAt: timestamp("revoked_at", { withTimezone: true }),
13478
14281
  expiresAt: timestamp("expires_at", { withTimezone: true }),
@@ -13489,13 +14292,13 @@ var auditLogs = pgTable(
13489
14292
  "audit_logs",
13490
14293
  {
13491
14294
  id: uuid("id").defaultRandom().primaryKey(),
13492
- actor: text("actor").notNull(),
14295
+ actor: text2("actor").notNull(),
13493
14296
  actorKeyId: uuid("actor_key_id"),
13494
- action: text("action").notNull(),
13495
- resource: text("resource").notNull(),
13496
- resourceId: text("resource_id"),
14297
+ action: text2("action").notNull(),
14298
+ resource: text2("resource").notNull(),
14299
+ resourceId: text2("resource_id"),
13497
14300
  detail: jsonb("detail").$type(),
13498
- ipAddress: text("ip_address"),
14301
+ ipAddress: text2("ip_address"),
13499
14302
  ...timestamps
13500
14303
  },
13501
14304
  (table) => [
@@ -13507,71 +14310,71 @@ var auditLogs = pgTable(
13507
14310
 
13508
14311
  // ../db/src/schema/auth.ts
13509
14312
  var user = pgTable("user", {
13510
- id: text("id").primaryKey(),
13511
- name: text("name").notNull(),
13512
- email: text("email").notNull().unique(),
14313
+ id: text2("id").primaryKey(),
14314
+ name: text2("name").notNull(),
14315
+ email: text2("email").notNull().unique(),
13513
14316
  emailVerified: boolean("email_verified").notNull().default(false),
13514
- image: text("image"),
14317
+ image: text2("image"),
13515
14318
  ...timestamps
13516
14319
  });
13517
14320
  var session = pgTable("session", {
13518
- id: text("id").primaryKey(),
14321
+ id: text2("id").primaryKey(),
13519
14322
  expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
13520
- token: text("token").notNull().unique(),
13521
- ipAddress: text("ip_address"),
13522
- userAgent: text("user_agent"),
13523
- userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
13524
- activeOrganizationId: text("active_organization_id"),
14323
+ token: text2("token").notNull().unique(),
14324
+ ipAddress: text2("ip_address"),
14325
+ userAgent: text2("user_agent"),
14326
+ userId: text2("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
14327
+ activeOrganizationId: text2("active_organization_id"),
13525
14328
  ...timestamps
13526
14329
  });
13527
14330
  var account = pgTable("account", {
13528
- id: text("id").primaryKey(),
13529
- accountId: text("account_id").notNull(),
13530
- providerId: text("provider_id").notNull(),
13531
- userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
13532
- accessToken: text("access_token"),
13533
- refreshToken: text("refresh_token"),
13534
- idToken: text("id_token"),
14331
+ id: text2("id").primaryKey(),
14332
+ accountId: text2("account_id").notNull(),
14333
+ providerId: text2("provider_id").notNull(),
14334
+ userId: text2("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
14335
+ accessToken: text2("access_token"),
14336
+ refreshToken: text2("refresh_token"),
14337
+ idToken: text2("id_token"),
13535
14338
  accessTokenExpiresAt: timestamp("access_token_expires_at", {
13536
14339
  withTimezone: true
13537
14340
  }),
13538
14341
  refreshTokenExpiresAt: timestamp("refresh_token_expires_at", {
13539
14342
  withTimezone: true
13540
14343
  }),
13541
- scope: text("scope"),
13542
- password: text("password"),
14344
+ scope: text2("scope"),
14345
+ password: text2("password"),
13543
14346
  ...timestamps
13544
14347
  });
13545
14348
  var verification = pgTable("verification", {
13546
- id: text("id").primaryKey(),
13547
- identifier: text("identifier").notNull(),
13548
- value: text("value").notNull(),
14349
+ id: text2("id").primaryKey(),
14350
+ identifier: text2("identifier").notNull(),
14351
+ value: text2("value").notNull(),
13549
14352
  expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
13550
14353
  ...timestamps
13551
14354
  });
13552
14355
  var organization = pgTable("organization", {
13553
- id: text("id").primaryKey(),
13554
- name: text("name").notNull(),
13555
- slug: text("slug").unique(),
13556
- logo: text("logo"),
13557
- metadata: text("metadata"),
14356
+ id: text2("id").primaryKey(),
14357
+ name: text2("name").notNull(),
14358
+ slug: text2("slug").unique(),
14359
+ logo: text2("logo"),
14360
+ metadata: text2("metadata"),
13558
14361
  ...timestamps
13559
14362
  });
13560
14363
  var member = pgTable("member", {
13561
- id: text("id").primaryKey(),
13562
- organizationId: text("organization_id").notNull().references(() => organization.id, { onDelete: "cascade" }),
13563
- userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
13564
- role: text("role").notNull().default("member"),
14364
+ id: text2("id").primaryKey(),
14365
+ organizationId: text2("organization_id").notNull().references(() => organization.id, { onDelete: "cascade" }),
14366
+ userId: text2("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
14367
+ role: text2("role").notNull().default("member"),
13565
14368
  ...timestamps
13566
14369
  });
13567
14370
  var invitation = pgTable("invitation", {
13568
- id: text("id").primaryKey(),
13569
- organizationId: text("organization_id").notNull().references(() => organization.id, { onDelete: "cascade" }),
13570
- email: text("email").notNull(),
13571
- role: text("role"),
13572
- status: text("status").notNull().default("pending"),
14371
+ id: text2("id").primaryKey(),
14372
+ organizationId: text2("organization_id").notNull().references(() => organization.id, { onDelete: "cascade" }),
14373
+ email: text2("email").notNull(),
14374
+ role: text2("role"),
14375
+ status: text2("status").notNull().default("pending"),
13573
14376
  expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
13574
- inviterId: text("inviter_id").notNull().references(() => user.id, { onDelete: "cascade" }),
14377
+ inviterId: text2("inviter_id").notNull().references(() => user.id, { onDelete: "cascade" }),
13575
14378
  ...timestamps
13576
14379
  });
13577
14380
 
@@ -13580,12 +14383,12 @@ var bucketConfigs = pgTable(
13580
14383
  "bucket_configs",
13581
14384
  {
13582
14385
  id: uuid("id").defaultRandom().primaryKey(),
13583
- bucketId: text("bucket_id").notNull(),
14386
+ bucketId: text2("bucket_id").notNull(),
13584
14387
  enabled: boolean("enabled").notNull().default(true),
13585
14388
  // Stable hash of the normalized ConditionEval, written at boot. Diffed on the
13586
14389
  // next boot to detect a CRITERIA CHANGE and enqueue the re-evaluation job
13587
14390
  // (Section 6.6 B). Nullable until the first registration.
13588
- criteriaHash: text("criteria_hash"),
14391
+ criteriaHash: text2("criteria_hash"),
13589
14392
  ...timestamps
13590
14393
  },
13591
14394
  (table) => [uniqueIndex("bucket_configs_bucket_id_idx").on(table.bucketId)]
@@ -13597,13 +14400,13 @@ var bucketMemberships = pgTable(
13597
14400
  {
13598
14401
  id: uuid("id").defaultRandom().primaryKey(),
13599
14402
  // multi-tenant insurance (nullable today, NOT in the unique key — see note)
13600
- organizationId: text("organization_id"),
14403
+ organizationId: text2("organization_id"),
13601
14404
  // logical join to contacts.externalId — NO FK (matches userEvents /
13602
14405
  // journeyStates; membership rows can predate a contacts row).
13603
- userId: text("user_id").notNull(),
13604
- userEmail: text("user_email"),
14406
+ userId: text2("user_id").notNull(),
14407
+ userEmail: text2("user_email"),
13605
14408
  // denormalized so emitted events carry it
13606
- bucketId: text("bucket_id").notNull(),
14409
+ bucketId: text2("bucket_id").notNull(),
13607
14410
  status: bucketMembershipStatusEnum("status").notNull().default("active"),
13608
14411
  enteredAt: timestamp("entered_at", { withTimezone: true }).defaultNow().notNull(),
13609
14412
  leftAt: timestamp("left_at", { withTimezone: true }),
@@ -13616,7 +14419,7 @@ var bucketMemberships = pgTable(
13616
14419
  maxDwellAt: timestamp("max_dwell_at", { withTimezone: true }),
13617
14420
  lastEvaluatedAt: timestamp("last_evaluated_at", { withTimezone: true }),
13618
14421
  entryCount: integer("entry_count").notNull().default(1),
13619
- source: text("source"),
14422
+ source: text2("source"),
13620
14423
  // "event" | "reconcile" | "backfill" | "manual"
13621
14424
  context: jsonb("context").$type().default({}),
13622
14425
  // Per-membership dwell bookkeeping. JSON map keyed by dwellLabel → ISO of
@@ -13684,18 +14487,18 @@ var campaigns = pgTable(
13684
14487
  "campaigns",
13685
14488
  {
13686
14489
  id: uuid("id").defaultRandom().primaryKey(),
13687
- organizationId: text("organization_id"),
13688
- name: text("name").notNull(),
14490
+ organizationId: text2("organization_id"),
14491
+ name: text2("name").notNull(),
13689
14492
  // queued | sending | sent | failed
13690
- status: text("status").notNull().default("queued"),
14493
+ status: text2("status").notNull().default("queued"),
13691
14494
  // "list" | "bucket"
13692
- audienceKind: text("audience_kind").notNull(),
14495
+ audienceKind: text2("audience_kind").notNull(),
13693
14496
  // the list id (ListRegistry) or bucket id (BucketRegistry)
13694
- audienceId: text("audience_id").notNull(),
13695
- templateKey: text("template_key").notNull(),
14497
+ audienceId: text2("audience_id").notNull(),
14498
+ templateKey: text2("template_key").notNull(),
13696
14499
  props: jsonb("props").$type().default({}),
13697
- fromEmail: text("from_email"),
13698
- subject: text("subject"),
14500
+ fromEmail: text2("from_email"),
14501
+ subject: text2("subject"),
13699
14502
  /**
13700
14503
  * Optional client-supplied idempotency key (POST /v1/campaigns
13701
14504
  * `Idempotency-Key` header / body field). A retried create with the same key
@@ -13704,7 +14507,7 @@ var campaigns = pgTable(
13704
14507
  * idempotency key, double-sending the blast). Uniqueness is enforced by the
13705
14508
  * partial-unique index below (NULL keys are unconstrained).
13706
14509
  */
13707
- idempotencyKey: text("idempotency_key"),
14510
+ idempotencyKey: text2("idempotency_key"),
13708
14511
  totalRecipients: integer("total_recipients").notNull().default(0),
13709
14512
  sentCount: integer("sent_count").notNull().default(0),
13710
14513
  skippedCount: integer("skipped_count").notNull().default(0),
@@ -13728,7 +14531,7 @@ var contacts = pgTable(
13728
14531
  "contacts",
13729
14532
  {
13730
14533
  id: uuid("id").defaultRandom().primaryKey(),
13731
- organizationId: text("organization_id"),
14534
+ organizationId: text2("organization_id"),
13732
14535
  /**
13733
14536
  * Stable external/distinct id (= the `user_id` text key joined by every
13734
14537
  * contact-referencing table). NULLABLE since D1: contacts can be email-only
@@ -13737,21 +14540,21 @@ var contacts = pgTable(
13737
14540
  * — a soft-deleted loser row must be able to keep its stale external_id
13738
14541
  * until a merge re-points it.
13739
14542
  */
13740
- externalId: text("external_id"),
13741
- email: text("email"),
14543
+ externalId: text2("external_id"),
14544
+ email: text2("email"),
13742
14545
  /**
13743
14546
  * Stable anonymous/distinct id for the future anonymous→identified path.
13744
14547
  * NULLABLE. Like external_id, uniqueness is enforced by a partial-unique
13745
14548
  * index scoped to live, non-deleted rows.
13746
14549
  */
13747
- anonymousId: text("anonymous_id"),
14550
+ anonymousId: text2("anonymous_id"),
13748
14551
  /**
13749
14552
  * Opportunistic IANA-timezone cache (e.g. "America/New_York"). Populated
13750
14553
  * best-effort when a tz is resolved from PostHog person props. PostHog and
13751
14554
  * `properties` jsonb remain authoritative sources — this column sits below
13752
14555
  * them in the resolution precedence, so nothing is blocked on it.
13753
14556
  */
13754
- timezone: text("timezone"),
14557
+ timezone: text2("timezone"),
13755
14558
  properties: jsonb("properties").$type().default({}),
13756
14559
  firstSeenAt: timestamp("first_seen_at", { withTimezone: true }).defaultNow().notNull(),
13757
14560
  lastSeenAt: timestamp("last_seen_at", { withTimezone: true }).defaultNow().notNull(),
@@ -13782,15 +14585,15 @@ var contactAliases = pgTable(
13782
14585
  // The SURVIVOR a stale key resolves TO.
13783
14586
  contactId: uuid("contact_id").notNull().references(() => contacts.id, { onDelete: "cascade" }),
13784
14587
  // 'email' | 'external' | 'anonymous'
13785
- aliasKind: text("alias_kind").notNull(),
14588
+ aliasKind: text2("alias_kind").notNull(),
13786
14589
  // The stale key value (the loser's old external_id / normalized email /
13787
14590
  // anonymous_id).
13788
- aliasValue: text("alias_value").notNull(),
14591
+ aliasValue: text2("alias_value").notNull(),
13789
14592
  // Provenance: the loser contact id this alias came from (nullable — a
13790
14593
  // 'promote' alias may have no distinct loser row).
13791
14594
  fromContactId: uuid("from_contact_id"),
13792
14595
  // 'merge' | 'promote'
13793
- reason: text("reason").notNull(),
14596
+ reason: text2("reason").notNull(),
13794
14597
  ...timestamps
13795
14598
  },
13796
14599
  (table) => [
@@ -13808,10 +14611,10 @@ var deadLetterQueue = pgTable(
13808
14611
  "dead_letter_queue",
13809
14612
  {
13810
14613
  id: uuid("id").defaultRandom().primaryKey(),
13811
- source: text("source").notNull(),
13812
- sourceId: text("source_id"),
14614
+ source: text2("source").notNull(),
14615
+ sourceId: text2("source_id"),
13813
14616
  payload: jsonb("payload").$type().notNull(),
13814
- error: text("error").notNull(),
14617
+ error: text2("error").notNull(),
13815
14618
  retryCount: integer("retry_count").notNull().default(0),
13816
14619
  status: dlqStatusEnum("status").notNull().default("pending"),
13817
14620
  retriedAt: timestamp("retried_at", { withTimezone: true }),
@@ -13829,8 +14632,8 @@ var emailPreferences = pgTable(
13829
14632
  "email_preferences",
13830
14633
  {
13831
14634
  id: uuid("id").defaultRandom().primaryKey(),
13832
- userId: text("user_id").notNull(),
13833
- email: text("email").notNull(),
14635
+ userId: text2("user_id").notNull(),
14636
+ email: text2("email").notNull(),
13834
14637
  unsubscribedAll: boolean("unsubscribed_all").notNull().default(false),
13835
14638
  suppressed: boolean("suppressed").notNull().default(false),
13836
14639
  bounceCount: integer("bounce_count").notNull().default(0),
@@ -13852,15 +14655,15 @@ var journeyStates = pgTable(
13852
14655
  "journey_states",
13853
14656
  {
13854
14657
  id: uuid("id").defaultRandom().primaryKey(),
13855
- organizationId: text("organization_id"),
13856
- userId: text("user_id").notNull(),
13857
- userEmail: text("user_email").notNull(),
13858
- journeyId: text("journey_id").notNull(),
13859
- currentNodeId: text("current_node_id").notNull(),
14658
+ organizationId: text2("organization_id"),
14659
+ userId: text2("user_id").notNull(),
14660
+ userEmail: text2("user_email").notNull(),
14661
+ journeyId: text2("journey_id").notNull(),
14662
+ currentNodeId: text2("current_node_id").notNull(),
13860
14663
  status: journeyStatusEnum("status").notNull().default("active"),
13861
- hatchetRunId: text("hatchet_run_id"),
14664
+ hatchetRunId: text2("hatchet_run_id"),
13862
14665
  context: jsonb("context").$type().default({}),
13863
- errorMessage: text("error_message"),
14666
+ errorMessage: text2("error_message"),
13864
14667
  entryCount: integer("entry_count").notNull().default(1),
13865
14668
  completedAt: timestamp("completed_at", { withTimezone: true }),
13866
14669
  exitedAt: timestamp("exited_at", { withTimezone: true }),
@@ -13898,19 +14701,19 @@ var emailSends = pgTable(
13898
14701
  "email_sends",
13899
14702
  {
13900
14703
  id: uuid("id").defaultRandom().primaryKey(),
13901
- organizationId: text("organization_id"),
14704
+ organizationId: text2("organization_id"),
13902
14705
  journeyStateId: uuid("journey_state_id").references(() => journeyStates.id),
13903
14706
  // Denormalized recipient identity, set at send time. Lets reporting attribute
13904
14707
  // a send to a contact without joining journey_states, and captures journeyless
13905
14708
  // (raw/batch) sends that have no journey linkage. Both nullable.
13906
- userId: text("user_id"),
13907
- userEmail: text("user_email"),
13908
- templateKey: text("template_key"),
13909
- messageId: text("message_id"),
13910
- fromEmail: text("from_email").notNull(),
13911
- toEmail: text("to_email").notNull(),
13912
- subject: text("subject").notNull(),
13913
- category: text("category"),
14709
+ userId: text2("user_id"),
14710
+ userEmail: text2("user_email"),
14711
+ templateKey: text2("template_key"),
14712
+ messageId: text2("message_id"),
14713
+ fromEmail: text2("from_email").notNull(),
14714
+ toEmail: text2("to_email").notNull(),
14715
+ subject: text2("subject").notNull(),
14716
+ category: text2("category"),
13914
14717
  status: emailSendStatusEnum("status").notNull().default("queued"),
13915
14718
  sentAt: timestamp("sent_at", { withTimezone: true }),
13916
14719
  deliveredAt: timestamp("delivered_at", { withTimezone: true }),
@@ -13919,13 +14722,13 @@ var emailSends = pgTable(
13919
14722
  bouncedAt: timestamp("bounced_at", { withTimezone: true }),
13920
14723
  complainedAt: timestamp("complained_at", { withTimezone: true }),
13921
14724
  // Bounce classification from the Resend webhook (hard/soft/transient + reason).
13922
- bounceType: text("bounce_type"),
13923
- bounceReason: text("bounce_reason"),
14725
+ bounceType: text2("bounce_type"),
14726
+ bounceReason: text2("bounce_reason"),
13924
14727
  // Caller-supplied idempotency key (POST /v1/emails). A retry with the same
13925
14728
  // key short-circuits to the prior send instead of dispatching a duplicate —
13926
14729
  // mirrors the user_events idempotency pattern. Nullable: journey/system sends
13927
14730
  // don't set it.
13928
- idempotencyKey: text("idempotency_key"),
14731
+ idempotencyKey: text2("idempotency_key"),
13929
14732
  // Free-form per-send annotations. Set ONLY by test-mode redirected sends
13930
14733
  // today — `{ testMode: true, originalTo: <real recipient> }` — so Studio can
13931
14734
  // flag a TEST row and show who the mail was REALLY for. Nullable: normal
@@ -13961,8 +14764,8 @@ var importJobs = pgTable(
13961
14764
  "import_jobs",
13962
14765
  {
13963
14766
  id: uuid("id").defaultRandom().primaryKey(),
13964
- fileName: text("file_name"),
13965
- format: text("format").notNull(),
14767
+ fileName: text2("file_name"),
14768
+ format: text2("format").notNull(),
13966
14769
  status: importJobStatusEnum("status").notNull().default("pending"),
13967
14770
  totalRows: integer("total_rows"),
13968
14771
  processedRows: integer("processed_rows").notNull().default(0),
@@ -13978,7 +14781,7 @@ var journeyConfigs = pgTable(
13978
14781
  "journey_configs",
13979
14782
  {
13980
14783
  id: uuid("id").defaultRandom().primaryKey(),
13981
- journeyId: text("journey_id").notNull(),
14784
+ journeyId: text2("journey_id").notNull(),
13982
14785
  enabled: boolean("enabled").notNull().default(true),
13983
14786
  ...timestamps
13984
14787
  },
@@ -13993,9 +14796,9 @@ var journeyLogs = pgTable(
13993
14796
  {
13994
14797
  id: uuid("id").defaultRandom().primaryKey(),
13995
14798
  journeyStateId: uuid("journey_state_id").notNull().references(() => journeyStates.id, { onDelete: "cascade" }),
13996
- fromNodeId: text("from_node_id"),
13997
- toNodeId: text("to_node_id"),
13998
- action: text("action").notNull(),
14799
+ fromNodeId: text2("from_node_id"),
14800
+ toNodeId: text2("to_node_id"),
14801
+ action: text2("action").notNull(),
13999
14802
  detail: jsonb("detail").$type(),
14000
14803
  ...timestamps
14001
14804
  },
@@ -14010,12 +14813,12 @@ var trackedLinks = pgTable(
14010
14813
  {
14011
14814
  id: uuid("id").defaultRandom().primaryKey(),
14012
14815
  emailSendId: uuid("email_send_id").notNull().references(() => emailSends.id, { onDelete: "cascade" }),
14013
- originalUrl: text("original_url").notNull(),
14816
+ originalUrl: text2("original_url").notNull(),
14014
14817
  clickCount: integer("click_count").notNull().default(0),
14015
14818
  // Semantic link metadata, lifted from the template's data-hs-* attributes
14016
14819
  // at send time. NULL for plain tracked links. `event` is the consumer event
14017
14820
  // name emitted at click time; `eventProperties` its scalar payload.
14018
- event: text("event"),
14821
+ event: text2("event"),
14019
14822
  eventProperties: jsonb("event_properties").$type(),
14020
14823
  // Set exactly once by the click route when the semantic event is emitted —
14021
14824
  // the per-link emit-once gate today, and the provisional-then-confirm
@@ -14034,8 +14837,8 @@ var linkClicks = pgTable(
14034
14837
  {
14035
14838
  id: uuid("id").defaultRandom().primaryKey(),
14036
14839
  trackedLinkId: uuid("tracked_link_id").notNull().references(() => trackedLinks.id, { onDelete: "cascade" }),
14037
- ipAddress: text("ip_address"),
14038
- userAgent: text("user_agent"),
14840
+ ipAddress: text2("ip_address"),
14841
+ userAgent: text2("user_agent"),
14039
14842
  clickedAt: timestamp("clicked_at", { withTimezone: true }).defaultNow().notNull()
14040
14843
  },
14041
14844
  (table) => [
@@ -14044,16 +14847,40 @@ var linkClicks = pgTable(
14044
14847
  ]
14045
14848
  );
14046
14849
 
14850
+ // ../db/src/schema/provider-credentials.ts
14851
+ var providerCredentials = pgTable(
14852
+ "provider_credentials",
14853
+ {
14854
+ id: uuid("id").defaultRandom().primaryKey(),
14855
+ // e.g. "posthog" — matches an AnalyticsProvider meta.id by convention,
14856
+ // but deliberately NOT foreign-keyed: providers are code-defined.
14857
+ providerId: text2("provider_id").notNull(),
14858
+ // "oauth" today; "api_key" is the anticipated future kind.
14859
+ kind: text2("kind").notNull().default("oauth"),
14860
+ // Encrypted JSON: base64url(iv || ciphertext || gcmTag).
14861
+ payload: text2("payload").notNull(),
14862
+ ...timestamps
14863
+ },
14864
+ (table) => [
14865
+ // unique-per-kind: one oauth credential per provider; a future api_key
14866
+ // credential for the same provider coexists.
14867
+ uniqueIndex("provider_credentials_provider_kind_idx").on(
14868
+ table.providerId,
14869
+ table.kind
14870
+ )
14871
+ ]
14872
+ );
14873
+
14047
14874
  // ../db/src/schema/user-events.ts
14048
14875
  var userEvents = pgTable(
14049
14876
  "user_events",
14050
14877
  {
14051
14878
  id: uuid("id").defaultRandom().primaryKey(),
14052
- organizationId: text("organization_id"),
14053
- userId: text("user_id").notNull(),
14054
- event: text("event").notNull(),
14879
+ organizationId: text2("organization_id"),
14880
+ userId: text2("user_id").notNull(),
14881
+ event: text2("event").notNull(),
14055
14882
  properties: jsonb("properties").$type(),
14056
- idempotencyKey: text("idempotency_key"),
14883
+ idempotencyKey: text2("idempotency_key"),
14057
14884
  occurredAt: timestamp("occurred_at", { withTimezone: true }).defaultNow().notNull()
14058
14885
  },
14059
14886
  (table) => [
@@ -14074,15 +14901,15 @@ var webhookEndpoints = pgTable(
14074
14901
  "webhook_endpoints",
14075
14902
  {
14076
14903
  id: uuid("id").defaultRandom().primaryKey(),
14077
- organizationId: text("organization_id"),
14078
- url: text("url").notNull(),
14079
- description: text("description"),
14904
+ organizationId: text2("organization_id"),
14905
+ url: text2("url").notNull(),
14906
+ description: text2("description"),
14080
14907
  // The delivery adapter selector. "webhook" (the default) is the signed
14081
14908
  // Standard-Webhooks POST that existing subscribers receive — byte-identical
14082
14909
  // to before this column existed. Any other value (e.g. "posthog") selects a
14083
14910
  // delivery-time TRANSFORM adapter that reuses the same durable delivery
14084
14911
  // machinery but rewrites url/headers/body for a vendor destination.
14085
- kind: text("kind").notNull().default("webhook"),
14912
+ kind: text2("kind").notNull().default("webhook"),
14086
14913
  // Per-destination configuration for keyed adapters (e.g. PostHog's
14087
14914
  // `{ apiKey, host }`). Null for `kind="webhook"` (it reads `secret` instead).
14088
14915
  // Keyed destinations keep their credentials HERE, not in a fake `whsec_`.
@@ -14091,9 +14918,9 @@ var webhookEndpoints = pgTable(
14091
14918
  // Nullable: only `kind="webhook"` carries a signing secret; keyed
14092
14919
  // destinations authenticate via `config` and the webhook adapter is the only
14093
14920
  // reader of this column.
14094
- secret: text("secret"),
14921
+ secret: text2("secret"),
14095
14922
  // e.g. "whsec_AbCd" — safe to show on list/get. Nullable alongside `secret`.
14096
- secretPrefix: text("secret_prefix"),
14923
+ secretPrefix: text2("secret_prefix"),
14097
14924
  eventTypes: jsonb("event_types").$type().notNull().default([]),
14098
14925
  disabled: boolean("disabled").notNull().default(false),
14099
14926
  // written by the delivery task on a successful (2xx) delivery.
@@ -14114,13 +14941,13 @@ var webhookDeliveries = pgTable(
14114
14941
  id: uuid("id").defaultRandom().primaryKey(),
14115
14942
  endpointId: uuid("endpoint_id").notNull().references(() => webhookEndpoints.id, { onDelete: "cascade" }),
14116
14943
  // denormalized, nullable (MT deferred).
14117
- organizationId: text("organization_id"),
14944
+ organizationId: text2("organization_id"),
14118
14945
  // == Webhook-Id header; ONE per logical event, shared across endpoints +
14119
14946
  // reused across retries.
14120
- webhookId: text("webhook_id").notNull(),
14121
- eventType: text("event_type").notNull(),
14947
+ webhookId: text2("webhook_id").notNull(),
14948
+ eventType: text2("event_type").notNull(),
14122
14949
  // producer-side dedup (idempotencyKey/stateId/emailSendId/...).
14123
- dedupeKey: text("dedupe_key"),
14950
+ dedupeKey: text2("dedupe_key"),
14124
14951
  // the EXACT signed envelope { id, type, timestamp, data }.
14125
14952
  payload: jsonb("payload").$type().notNull(),
14126
14953
  status: webhookDeliveryStatusEnum("status").notNull().default("pending"),
@@ -14129,9 +14956,9 @@ var webhookDeliveries = pgTable(
14129
14956
  lastAttemptAt: timestamp("last_attempt_at", { withTimezone: true }),
14130
14957
  responseStatus: integer("response_status"),
14131
14958
  // truncated to ≤1KB in app.
14132
- responseBodySnippet: text("response_body_snippet"),
14959
+ responseBodySnippet: text2("response_body_snippet"),
14133
14960
  deliveredAt: timestamp("delivered_at", { withTimezone: true }),
14134
- lastError: text("last_error"),
14961
+ lastError: text2("last_error"),
14135
14962
  ...timestamps
14136
14963
  },
14137
14964
  (table) => [
@@ -14539,7 +15366,7 @@ Examples:
14539
15366
  Security: passwords are written ONLY via better-auth (scrypt) \u2014 never raw SQL,
14540
15367
  never plaintext at rest, never logged. Prefer the masked prompt over --password.`;
14541
15368
  function parseAdminFlags(argv) {
14542
- const { values: values2 } = parseArgs16({
15369
+ const { values: values2 } = parseArgs17({
14543
15370
  args: argv,
14544
15371
  allowPositionals: true,
14545
15372
  strict: false,
@@ -14588,7 +15415,7 @@ async function resolveEmail(ctx, flags, defaultEmail) {
14588
15415
  ctx.out.fail("--email is required (no TTY to prompt).");
14589
15416
  }
14590
15417
  const value = bail(
14591
- await text2({
15418
+ await text3({
14592
15419
  message: "Admin email",
14593
15420
  placeholder: defaultEmail ?? "admin@example.com",
14594
15421
  initialValue: defaultEmail,
@@ -14740,7 +15567,7 @@ async function runStudioAdmin(ctx, argv) {
14740
15567
  }
14741
15568
 
14742
15569
  // src/commands/studio.ts
14743
- var usage15 = `hogsend studio [options]
15570
+ var usage16 = `hogsend studio [options]
14744
15571
 
14745
15572
  Subcommands:
14746
15573
  admin create | reset | list Shell-gated Studio admin recovery (DB + secret).
@@ -14818,24 +15645,12 @@ function indexHtml(distPath, baseUrl) {
14818
15645
  }
14819
15646
  return `${inject}${raw}`;
14820
15647
  }
14821
- function openBrowser(url) {
14822
- const platform = process.platform;
14823
- const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
14824
- const args = platform === "win32" ? ["/c", "start", "", url] : [url];
14825
- try {
14826
- const child = spawn2(cmd, args, { stdio: "ignore", detached: true });
14827
- child.on("error", () => {
14828
- });
14829
- child.unref();
14830
- } catch {
14831
- }
14832
- }
14833
- async function run15(ctx) {
15648
+ async function run16(ctx) {
14834
15649
  if (ctx.argv[0] === "admin") {
14835
15650
  await runStudioAdmin(ctx, ctx.argv.slice(1));
14836
15651
  return;
14837
15652
  }
14838
- const { values: values2, positionals } = parseArgs17({
15653
+ const { values: values2, positionals } = parseArgs18({
14839
15654
  args: ctx.argv,
14840
15655
  allowPositionals: true,
14841
15656
  strict: false,
@@ -14848,7 +15663,7 @@ async function run15(ctx) {
14848
15663
  }
14849
15664
  });
14850
15665
  if (values2.help) {
14851
- ctx.out.log(usage15);
15666
+ ctx.out.log(usage16);
14852
15667
  return;
14853
15668
  }
14854
15669
  const port = Number(values2.port ?? "3333");
@@ -14866,7 +15681,7 @@ async function run15(ctx) {
14866
15681
  }
14867
15682
  const cleanBase = baseUrl ? baseUrl.replace(/\/+$/, "") : void 0;
14868
15683
  const index2 = indexHtml(distPath, cleanBase);
14869
- const server = createServer((req, res) => {
15684
+ const server = createServer2((req, res) => {
14870
15685
  const urlPath = decodeURIComponent((req.url ?? "/").split("?")[0] ?? "/");
14871
15686
  const rel = urlPath.replace(/^\/studio/, "");
14872
15687
  if (rel === "" || rel === "/") {
@@ -14934,17 +15749,17 @@ async function run15(ctx) {
14934
15749
  var studioCommand = {
14935
15750
  name: "studio",
14936
15751
  summary: "Serve the bundled Hogsend Studio admin SPA locally",
14937
- usage: usage15,
14938
- run: run15
15752
+ usage: usage16,
15753
+ run: run16
14939
15754
  };
14940
15755
 
14941
15756
  // src/commands/upgrade.ts
14942
15757
  import { spawnSync as spawnSync4 } from "child_process";
14943
15758
  import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
14944
15759
  import { join as join10 } from "path";
14945
- import { parseArgs as parseArgs18 } from "util";
14946
- import { confirm as confirm3 } from "@clack/prompts";
14947
- var usage16 = `hogsend upgrade [--cwd <dir>] [--pm <pnpm|npm|yarn|bun>] [options]
15760
+ import { parseArgs as parseArgs19 } from "util";
15761
+ import { confirm as confirm4 } from "@clack/prompts";
15762
+ var usage17 = `hogsend upgrade [--cwd <dir>] [--pm <pnpm|npm|yarn|bun>] [options]
14948
15763
 
14949
15764
  Upgrade a scaffolded Hogsend app in one step:
14950
15765
  1. bump every @hogsend/* dependency to latest (or --to <version>), then
@@ -14980,8 +15795,8 @@ function hogsendDeps(cwd) {
14980
15795
  function addArgs(pm, specs) {
14981
15796
  return [pm === "npm" ? "install" : "add", ...specs];
14982
15797
  }
14983
- async function run16(ctx) {
14984
- const { values: values2 } = parseArgs18({
15798
+ async function run17(ctx) {
15799
+ const { values: values2 } = parseArgs19({
14985
15800
  args: ctx.argv,
14986
15801
  allowPositionals: true,
14987
15802
  options: {
@@ -14995,7 +15810,7 @@ async function run16(ctx) {
14995
15810
  }
14996
15811
  });
14997
15812
  if (values2.help) {
14998
- ctx.out.log(usage16);
15813
+ ctx.out.log(usage17);
14999
15814
  return;
15000
15815
  }
15001
15816
  if (values2["deps-only"] && values2["skills-only"]) {
@@ -15039,7 +15854,7 @@ async function run16(ctx) {
15039
15854
  doSkills ? "refresh .claude/skills" : null
15040
15855
  ].filter(Boolean).join(" + ");
15041
15856
  const proceed = bail(
15042
- await confirm3({ message: `Upgrade ${color.cyan(cwd)}: ${plan}?` })
15857
+ await confirm4({ message: `Upgrade ${color.cyan(cwd)}: ${plan}?` })
15043
15858
  );
15044
15859
  if (!proceed) {
15045
15860
  ctx.out.outro(color.dim("Nothing changed."));
@@ -15120,12 +15935,12 @@ async function run16(ctx) {
15120
15935
  var upgradeCommand = {
15121
15936
  name: "upgrade",
15122
15937
  summary: "Bump @hogsend/* deps to latest + refresh vendored skills",
15123
- usage: usage16,
15124
- run: run16
15938
+ usage: usage17,
15939
+ run: run17
15125
15940
  };
15126
15941
 
15127
15942
  // src/commands/webhooks.ts
15128
- import { parseArgs as parseArgs19 } from "util";
15943
+ import { parseArgs as parseArgs20 } from "util";
15129
15944
  var WEBHOOK_EVENT_TYPES = [
15130
15945
  "contact.created",
15131
15946
  "contact.updated",
@@ -15142,7 +15957,7 @@ var WEBHOOK_EVENT_TYPES = [
15142
15957
  "bucket.entered",
15143
15958
  "bucket.left"
15144
15959
  ];
15145
- var usage17 = `hogsend webhooks <subcommand> [options]
15960
+ var usage18 = `hogsend webhooks <subcommand> [options]
15146
15961
 
15147
15962
  Manage outbound webhook endpoints \u2014 the Svix-style signed event stream Hogsend
15148
15963
  emits to your URLs. Wraps the admin routes (/v1/admin/webhooks), so this command
@@ -15232,7 +16047,7 @@ ${color.bold(secret)}`,
15232
16047
  );
15233
16048
  }
15234
16049
  async function runList5(ctx, argv) {
15235
- const { values: values2 } = parseArgs19({
16050
+ const { values: values2 } = parseArgs20({
15236
16051
  args: argv,
15237
16052
  allowPositionals: true,
15238
16053
  options: {
@@ -15243,7 +16058,7 @@ async function runList5(ctx, argv) {
15243
16058
  }
15244
16059
  });
15245
16060
  if (values2.help) {
15246
- ctx.out.log(usage17);
16061
+ ctx.out.log(usage18);
15247
16062
  return;
15248
16063
  }
15249
16064
  const query = {
@@ -15292,13 +16107,13 @@ function renderEndpoint(ctx, ep, title) {
15292
16107
  );
15293
16108
  }
15294
16109
  async function runGet3(ctx, argv) {
15295
- const { values: values2, positionals } = parseArgs19({
16110
+ const { values: values2, positionals } = parseArgs20({
15296
16111
  args: argv,
15297
16112
  allowPositionals: true,
15298
16113
  options: { help: { type: "boolean", short: "h", default: false } }
15299
16114
  });
15300
16115
  if (values2.help) {
15301
- ctx.out.log(usage17);
16116
+ ctx.out.log(usage18);
15302
16117
  return;
15303
16118
  }
15304
16119
  const id = positionals[0];
@@ -15323,7 +16138,7 @@ async function runGet3(ctx, argv) {
15323
16138
  ctx.out.outro(`${res.url} \u2192 ${res.status}`);
15324
16139
  }
15325
16140
  async function runCreate2(ctx, argv) {
15326
- const { values: values2 } = parseArgs19({
16141
+ const { values: values2 } = parseArgs20({
15327
16142
  args: argv,
15328
16143
  allowPositionals: true,
15329
16144
  options: {
@@ -15336,7 +16151,7 @@ async function runCreate2(ctx, argv) {
15336
16151
  }
15337
16152
  });
15338
16153
  if (values2.help) {
15339
- ctx.out.log(usage17);
16154
+ ctx.out.log(usage18);
15340
16155
  return;
15341
16156
  }
15342
16157
  const url = values2.url;
@@ -15370,7 +16185,7 @@ async function runCreate2(ctx, argv) {
15370
16185
  ctx.out.outro(`${color.green("Created")} ${res.id} \u2192 ${res.url}`);
15371
16186
  }
15372
16187
  async function runUpdate(ctx, argv) {
15373
- const { values: values2, positionals } = parseArgs19({
16188
+ const { values: values2, positionals } = parseArgs20({
15374
16189
  args: argv,
15375
16190
  allowPositionals: true,
15376
16191
  options: {
@@ -15384,7 +16199,7 @@ async function runUpdate(ctx, argv) {
15384
16199
  }
15385
16200
  });
15386
16201
  if (values2.help) {
15387
- ctx.out.log(usage17);
16202
+ ctx.out.log(usage18);
15388
16203
  return;
15389
16204
  }
15390
16205
  const id = positionals[0];
@@ -15425,13 +16240,13 @@ async function runUpdate(ctx, argv) {
15425
16240
  ctx.out.outro(`${color.green("Updated")} ${res.id} \u2192 ${res.status}`);
15426
16241
  }
15427
16242
  async function runDelete(ctx, argv) {
15428
- const { values: values2, positionals } = parseArgs19({
16243
+ const { values: values2, positionals } = parseArgs20({
15429
16244
  args: argv,
15430
16245
  allowPositionals: true,
15431
16246
  options: { help: { type: "boolean", short: "h", default: false } }
15432
16247
  });
15433
16248
  if (values2.help) {
15434
- ctx.out.log(usage17);
16249
+ ctx.out.log(usage18);
15435
16250
  return;
15436
16251
  }
15437
16252
  const id = positionals[0];
@@ -15455,13 +16270,13 @@ async function runDelete(ctx, argv) {
15455
16270
  ctx.out.outro(`${color.green("Deleted")} ${id}`);
15456
16271
  }
15457
16272
  async function runRotate(ctx, argv) {
15458
- const { values: values2, positionals } = parseArgs19({
16273
+ const { values: values2, positionals } = parseArgs20({
15459
16274
  args: argv,
15460
16275
  allowPositionals: true,
15461
16276
  options: { help: { type: "boolean", short: "h", default: false } }
15462
16277
  });
15463
16278
  if (values2.help) {
15464
- ctx.out.log(usage17);
16279
+ ctx.out.log(usage18);
15465
16280
  return;
15466
16281
  }
15467
16282
  const id = positionals[0];
@@ -15490,13 +16305,13 @@ async function runRotate(ctx, argv) {
15490
16305
  );
15491
16306
  }
15492
16307
  async function runTest(ctx, argv) {
15493
- const { values: values2, positionals } = parseArgs19({
16308
+ const { values: values2, positionals } = parseArgs20({
15494
16309
  args: argv,
15495
16310
  allowPositionals: true,
15496
16311
  options: { help: { type: "boolean", short: "h", default: false } }
15497
16312
  });
15498
16313
  if (values2.help) {
15499
- ctx.out.log(usage17);
16314
+ ctx.out.log(usage18);
15500
16315
  return;
15501
16316
  }
15502
16317
  const id = positionals[0];
@@ -15522,7 +16337,7 @@ async function runTest(ctx, argv) {
15522
16337
  `${color.green("Enqueued")} a ${color.cyan(res.eventType)} delivery to ${id}.`
15523
16338
  );
15524
16339
  }
15525
- async function run17(ctx) {
16340
+ async function run18(ctx) {
15526
16341
  const sub = ctx.argv[0];
15527
16342
  switch (sub) {
15528
16343
  case "list":
@@ -15553,8 +16368,8 @@ async function run17(ctx) {
15553
16368
  var webhooksCommand = {
15554
16369
  name: "webhooks",
15555
16370
  summary: "Manage outbound webhook endpoints (create, rotate, test)",
15556
- usage: usage17,
15557
- run: run17
16371
+ usage: usage18,
16372
+ run: run18
15558
16373
  };
15559
16374
 
15560
16375
  // src/commands/index.ts
@@ -15568,6 +16383,7 @@ var commands = [
15568
16383
  campaignsCommand,
15569
16384
  webhooksCommand,
15570
16385
  domainCommand,
16386
+ connectCommand,
15571
16387
  hatchetCommand,
15572
16388
  studioCommand,
15573
16389
  devCommand,