@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 +1154 -338
- package/dist/bin.js.map +1 -1
- package/package.json +4 -4
- package/skills/hogsend-deploy/references/env-and-secrets.md +8 -0
- package/src/__tests__/connect-command.test.ts +104 -0
- package/src/__tests__/connect-flow.test.ts +559 -0
- package/src/__tests__/dev.test.ts +1 -0
- package/src/__tests__/domain-command.test.ts +1 -0
- package/src/__tests__/loopback.test.ts +159 -0
- package/src/__tests__/oauth.test.ts +230 -0
- package/src/commands/connect.ts +178 -0
- package/src/commands/index.ts +2 -0
- package/src/commands/studio.ts +1 -16
- package/src/lib/browser.ts +17 -0
- package/src/lib/connect-flow.ts +641 -0
- package/src/lib/http.ts +6 -0
- package/src/lib/loopback.ts +223 -0
- package/src/lib/oauth.ts +265 -0
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
|
|
64
|
+
const text4 = await res.text();
|
|
65
65
|
let parsed;
|
|
66
|
-
if (
|
|
66
|
+
if (text4.length > 0) {
|
|
67
67
|
try {
|
|
68
|
-
parsed = JSON.parse(
|
|
68
|
+
parsed = JSON.parse(text4);
|
|
69
69
|
} catch {
|
|
70
|
-
parsed =
|
|
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 = (
|
|
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
|
|
471
|
-
var
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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
|
|
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:
|
|
819
|
-
run:
|
|
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
|
|
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 =
|
|
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
|
|
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 } =
|
|
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
|
|
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
|
|
1219
|
-
var
|
|
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
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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:
|
|
1498
|
-
run:
|
|
2310
|
+
usage: usage4,
|
|
2311
|
+
run: run4
|
|
1499
2312
|
};
|
|
1500
2313
|
|
|
1501
2314
|
// src/commands/dev.ts
|
|
1502
|
-
var
|
|
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(
|
|
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
|
|
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 } =
|
|
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(
|
|
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:
|
|
1821
|
-
run:
|
|
2633
|
+
usage: usage5,
|
|
2634
|
+
run: run5
|
|
1822
2635
|
};
|
|
1823
2636
|
|
|
1824
2637
|
// src/commands/doctor.ts
|
|
1825
|
-
import { parseArgs as
|
|
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
|
|
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
|
|
2019
|
-
const { values: values2 } =
|
|
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(
|
|
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:
|
|
2121
|
-
run:
|
|
2933
|
+
usage: usage6,
|
|
2934
|
+
run: run6
|
|
2122
2935
|
};
|
|
2123
2936
|
|
|
2124
2937
|
// src/commands/domain.ts
|
|
2125
|
-
import { parseArgs as
|
|
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
|
|
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 } =
|
|
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(
|
|
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
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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
|
|
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(
|
|
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:
|
|
2759
|
-
run:
|
|
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
|
|
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
|
|
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
|
|
2909
|
-
const { values: values2, positionals } =
|
|
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(
|
|
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:
|
|
2964
|
-
run:
|
|
3766
|
+
usage: usage8,
|
|
3767
|
+
run: run8
|
|
2965
3768
|
};
|
|
2966
3769
|
|
|
2967
3770
|
// src/commands/emails.ts
|
|
2968
|
-
import { parseArgs as
|
|
2969
|
-
var
|
|
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 } =
|
|
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(
|
|
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
|
|
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:
|
|
3139
|
-
run:
|
|
3941
|
+
usage: usage9,
|
|
3942
|
+
run: run9
|
|
3140
3943
|
};
|
|
3141
3944
|
|
|
3142
3945
|
// src/commands/hatchet.ts
|
|
3143
|
-
import { parseArgs as
|
|
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
|
|
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 } =
|
|
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
|
|
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(
|
|
4225
|
+
ctx.out.log(usage10);
|
|
3423
4226
|
return;
|
|
3424
4227
|
}
|
|
3425
4228
|
if (rest.includes("-h") || rest.includes("--help")) {
|
|
3426
|
-
ctx.out.log(
|
|
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:
|
|
3440
|
-
run:
|
|
4242
|
+
usage: usage10,
|
|
4243
|
+
run: run10
|
|
3441
4244
|
};
|
|
3442
4245
|
|
|
3443
4246
|
// src/commands/journeys.ts
|
|
3444
|
-
import { parseArgs as
|
|
3445
|
-
var
|
|
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 } =
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
3688
|
-
run:
|
|
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
|
|
3694
|
-
var
|
|
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
|
|
3706
|
-
const { values: values2, positionals } =
|
|
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(
|
|
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:
|
|
3756
|
-
run:
|
|
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
|
|
3763
|
-
import { confirm as
|
|
3764
|
-
var
|
|
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
|
|
3782
|
-
const { values: values2 } =
|
|
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(
|
|
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
|
|
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:
|
|
3908
|
-
run:
|
|
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
|
|
4717
|
+
import { parseArgs as parseArgs15 } from "util";
|
|
3915
4718
|
import { multiselect } from "@clack/prompts";
|
|
3916
|
-
var
|
|
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 } =
|
|
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(
|
|
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
|
|
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(
|
|
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:
|
|
4079
|
-
run:
|
|
4881
|
+
usage: usage14,
|
|
4882
|
+
run: run14
|
|
4080
4883
|
};
|
|
4081
4884
|
|
|
4082
4885
|
// src/commands/stats.ts
|
|
4083
|
-
import { parseArgs as
|
|
4084
|
-
var
|
|
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
|
|
4107
|
-
const { values: values2 } =
|
|
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(
|
|
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:
|
|
4145
|
-
run:
|
|
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
|
|
4956
|
+
import { parseArgs as parseArgs18 } from "util";
|
|
4155
4957
|
|
|
4156
4958
|
// src/commands/studio-admin.ts
|
|
4157
|
-
import { parseArgs as
|
|
4158
|
-
import { password as passwordPrompt, text as
|
|
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
|
|
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:
|
|
4483
|
-
returning:
|
|
4484
|
-
"\\(":
|
|
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
|
|
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:
|
|
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 (
|
|
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((
|
|
12545
|
+
const setOperators = [rightSelect, ...restSelects].map((select3) => ({
|
|
11744
12546
|
type,
|
|
11745
12547
|
isAll,
|
|
11746
|
-
rightSelect:
|
|
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
|
|
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:
|
|
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
|
|
12016
|
-
if (!is(
|
|
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,
|
|
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,
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
13456
|
-
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:
|
|
13471
|
-
name:
|
|
13472
|
-
keyPrefix:
|
|
13473
|
-
keyHash:
|
|
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:
|
|
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:
|
|
14295
|
+
actor: text2("actor").notNull(),
|
|
13493
14296
|
actorKeyId: uuid("actor_key_id"),
|
|
13494
|
-
action:
|
|
13495
|
-
resource:
|
|
13496
|
-
resourceId:
|
|
14297
|
+
action: text2("action").notNull(),
|
|
14298
|
+
resource: text2("resource").notNull(),
|
|
14299
|
+
resourceId: text2("resource_id"),
|
|
13497
14300
|
detail: jsonb("detail").$type(),
|
|
13498
|
-
ipAddress:
|
|
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:
|
|
13511
|
-
name:
|
|
13512
|
-
email:
|
|
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:
|
|
14317
|
+
image: text2("image"),
|
|
13515
14318
|
...timestamps
|
|
13516
14319
|
});
|
|
13517
14320
|
var session = pgTable("session", {
|
|
13518
|
-
id:
|
|
14321
|
+
id: text2("id").primaryKey(),
|
|
13519
14322
|
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
13520
|
-
token:
|
|
13521
|
-
ipAddress:
|
|
13522
|
-
userAgent:
|
|
13523
|
-
userId:
|
|
13524
|
-
activeOrganizationId:
|
|
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:
|
|
13529
|
-
accountId:
|
|
13530
|
-
providerId:
|
|
13531
|
-
userId:
|
|
13532
|
-
accessToken:
|
|
13533
|
-
refreshToken:
|
|
13534
|
-
idToken:
|
|
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:
|
|
13542
|
-
password:
|
|
14344
|
+
scope: text2("scope"),
|
|
14345
|
+
password: text2("password"),
|
|
13543
14346
|
...timestamps
|
|
13544
14347
|
});
|
|
13545
14348
|
var verification = pgTable("verification", {
|
|
13546
|
-
id:
|
|
13547
|
-
identifier:
|
|
13548
|
-
value:
|
|
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:
|
|
13554
|
-
name:
|
|
13555
|
-
slug:
|
|
13556
|
-
logo:
|
|
13557
|
-
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:
|
|
13562
|
-
organizationId:
|
|
13563
|
-
userId:
|
|
13564
|
-
role:
|
|
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:
|
|
13569
|
-
organizationId:
|
|
13570
|
-
email:
|
|
13571
|
-
role:
|
|
13572
|
-
status:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
13604
|
-
userEmail:
|
|
14406
|
+
userId: text2("user_id").notNull(),
|
|
14407
|
+
userEmail: text2("user_email"),
|
|
13605
14408
|
// denormalized so emitted events carry it
|
|
13606
|
-
bucketId:
|
|
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:
|
|
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:
|
|
13688
|
-
name:
|
|
14490
|
+
organizationId: text2("organization_id"),
|
|
14491
|
+
name: text2("name").notNull(),
|
|
13689
14492
|
// queued | sending | sent | failed
|
|
13690
|
-
status:
|
|
14493
|
+
status: text2("status").notNull().default("queued"),
|
|
13691
14494
|
// "list" | "bucket"
|
|
13692
|
-
audienceKind:
|
|
14495
|
+
audienceKind: text2("audience_kind").notNull(),
|
|
13693
14496
|
// the list id (ListRegistry) or bucket id (BucketRegistry)
|
|
13694
|
-
audienceId:
|
|
13695
|
-
templateKey:
|
|
14497
|
+
audienceId: text2("audience_id").notNull(),
|
|
14498
|
+
templateKey: text2("template_key").notNull(),
|
|
13696
14499
|
props: jsonb("props").$type().default({}),
|
|
13697
|
-
fromEmail:
|
|
13698
|
-
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:
|
|
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:
|
|
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:
|
|
13741
|
-
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
13812
|
-
sourceId:
|
|
14614
|
+
source: text2("source").notNull(),
|
|
14615
|
+
sourceId: text2("source_id"),
|
|
13813
14616
|
payload: jsonb("payload").$type().notNull(),
|
|
13814
|
-
error:
|
|
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:
|
|
13833
|
-
email:
|
|
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:
|
|
13856
|
-
userId:
|
|
13857
|
-
userEmail:
|
|
13858
|
-
journeyId:
|
|
13859
|
-
currentNodeId:
|
|
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:
|
|
14664
|
+
hatchetRunId: text2("hatchet_run_id"),
|
|
13862
14665
|
context: jsonb("context").$type().default({}),
|
|
13863
|
-
errorMessage:
|
|
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:
|
|
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:
|
|
13907
|
-
userEmail:
|
|
13908
|
-
templateKey:
|
|
13909
|
-
messageId:
|
|
13910
|
-
fromEmail:
|
|
13911
|
-
toEmail:
|
|
13912
|
-
subject:
|
|
13913
|
-
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:
|
|
13923
|
-
bounceReason:
|
|
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:
|
|
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:
|
|
13965
|
-
format:
|
|
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:
|
|
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:
|
|
13997
|
-
toNodeId:
|
|
13998
|
-
action:
|
|
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:
|
|
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:
|
|
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:
|
|
14038
|
-
userAgent:
|
|
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:
|
|
14053
|
-
userId:
|
|
14054
|
-
event:
|
|
14879
|
+
organizationId: text2("organization_id"),
|
|
14880
|
+
userId: text2("user_id").notNull(),
|
|
14881
|
+
event: text2("event").notNull(),
|
|
14055
14882
|
properties: jsonb("properties").$type(),
|
|
14056
|
-
idempotencyKey:
|
|
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:
|
|
14078
|
-
url:
|
|
14079
|
-
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:
|
|
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:
|
|
14921
|
+
secret: text2("secret"),
|
|
14095
14922
|
// e.g. "whsec_AbCd" — safe to show on list/get. Nullable alongside `secret`.
|
|
14096
|
-
secretPrefix:
|
|
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:
|
|
14944
|
+
organizationId: text2("organization_id"),
|
|
14118
14945
|
// == Webhook-Id header; ONE per logical event, shared across endpoints +
|
|
14119
14946
|
// reused across retries.
|
|
14120
|
-
webhookId:
|
|
14121
|
-
eventType:
|
|
14947
|
+
webhookId: text2("webhook_id").notNull(),
|
|
14948
|
+
eventType: text2("event_type").notNull(),
|
|
14122
14949
|
// producer-side dedup (idempotencyKey/stateId/emailSendId/...).
|
|
14123
|
-
dedupeKey:
|
|
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:
|
|
14959
|
+
responseBodySnippet: text2("response_body_snippet"),
|
|
14133
14960
|
deliveredAt: timestamp("delivered_at", { withTimezone: true }),
|
|
14134
|
-
lastError:
|
|
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 } =
|
|
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
|
|
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
|
|
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
|
|
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 } =
|
|
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(
|
|
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 =
|
|
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:
|
|
14938
|
-
run:
|
|
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
|
|
14946
|
-
import { confirm as
|
|
14947
|
-
var
|
|
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
|
|
14984
|
-
const { values: values2 } =
|
|
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(
|
|
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
|
|
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:
|
|
15124
|
-
run:
|
|
15938
|
+
usage: usage17,
|
|
15939
|
+
run: run17
|
|
15125
15940
|
};
|
|
15126
15941
|
|
|
15127
15942
|
// src/commands/webhooks.ts
|
|
15128
|
-
import { parseArgs as
|
|
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
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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
|
|
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:
|
|
15557
|
-
run:
|
|
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,
|