@hogsend/cli 0.19.0 → 0.20.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 +974 -187
- 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 +492 -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 +149 -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 +597 -0
- package/src/lib/http.ts +6 -0
- package/src/lib/loopback.ts +223 -0
- package/src/lib/oauth.ts +256 -0
package/dist/bin.js
CHANGED
|
@@ -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
|
|
@@ -459,16 +463,796 @@ 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 } 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 hog_function:write";
|
|
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 text3 = await res.text().catch(() => "");
|
|
588
|
+
let detail = text3;
|
|
589
|
+
try {
|
|
590
|
+
const parsed = JSON.parse(text3);
|
|
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 = "Set POSTHOG_API_KEY (and POSTHOG_HOST for EU/self-hosted) on the instance, redeploy, then re-run. The server's PostHog config tells the CLI which region to authorize against.";
|
|
780
|
+
var hintOauthUnsupported = (privateHost) => `${privateHost} doesn't advertise an OAuth server (discovery returned 404).
|
|
781
|
+
Self-hosted PostHog builds may not ship OAuth. Use a personal API key instead:
|
|
782
|
+
|
|
783
|
+
1. In PostHog: Settings -> User -> Personal API keys -> create a key scoped
|
|
784
|
+
person:read, person:write, project:read, hog_function:write
|
|
785
|
+
2. Set POSTHOG_PERSONAL_API_KEY=<key> on your Hogsend instance (api + worker)
|
|
786
|
+
3. Redeploy \u2014 person reads and loop provisioning use the key automatically.`;
|
|
787
|
+
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.";
|
|
788
|
+
var SSH_NOTE = `The consent page must open in a browser on THIS machine \u2014 the OAuth callback
|
|
789
|
+
returns to 127.0.0.1 here. On a remote/SSH session this cannot complete: run
|
|
790
|
+
the command from your laptop instead and point --url at the instance (the CLI
|
|
791
|
+
never needs to run on the server).`;
|
|
792
|
+
function isLoopbackUrl(publicUrl) {
|
|
793
|
+
try {
|
|
794
|
+
const host = new URL(publicUrl).hostname.toLowerCase();
|
|
795
|
+
return host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host === "[::1]" || host === "::1" || host.endsWith(".localhost");
|
|
796
|
+
} catch {
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
var LOOPBACK_URL_NOTE = `Credential stored \u2014 but this instance's API_PUBLIC_URL is a loopback
|
|
801
|
+
address, so PostHog Cloud cannot deliver webhooks to it. Provisioning was
|
|
802
|
+
skipped (a destination pointing at localhost would be unreachable).
|
|
803
|
+
|
|
804
|
+
Once deployed, wire the loop against the real instance:
|
|
805
|
+
|
|
806
|
+
hogsend connect posthog --provision-only --url https://your-instance`;
|
|
807
|
+
var WEBHOOK_SECRET_NOTE = `Credential stored \u2014 but the PostHog -> Hogsend event loop needs a shared
|
|
808
|
+
webhook secret, and this instance doesn't have one yet. Finish the loop:
|
|
809
|
+
|
|
810
|
+
1. Generate a secret: openssl rand -hex 32
|
|
811
|
+
2. Set it on the instance: POSTHOG_WEBHOOK_SECRET=<secret> (api AND worker)
|
|
812
|
+
3. Redeploy, then run: hogsend connect posthog --provision-only`;
|
|
813
|
+
var errMsg = (err) => err instanceof Error ? err.message : String(err);
|
|
814
|
+
var httpErrorBody = (err) => {
|
|
815
|
+
if (!isHttpError(err)) return void 0;
|
|
816
|
+
const body = err.body;
|
|
817
|
+
if (body && typeof body === "object" && "error" in body && typeof body.error === "string") {
|
|
818
|
+
return body.error;
|
|
819
|
+
}
|
|
820
|
+
return void 0;
|
|
821
|
+
};
|
|
822
|
+
function fromLoopbackError(err) {
|
|
823
|
+
switch (err.reason) {
|
|
824
|
+
case "consent_denied":
|
|
825
|
+
return new ConnectError(
|
|
826
|
+
"consent_denied",
|
|
827
|
+
"authorization was denied in PostHog \u2014 re-run the command if that was a mistake"
|
|
828
|
+
);
|
|
829
|
+
case "state_mismatch":
|
|
830
|
+
return new ConnectError(
|
|
831
|
+
"state_mismatch",
|
|
832
|
+
"state mismatch on the OAuth callback \u2014 possible CSRF; retry the command"
|
|
833
|
+
);
|
|
834
|
+
case "timeout":
|
|
835
|
+
return new ConnectError(
|
|
836
|
+
"callback_timeout",
|
|
837
|
+
"timed out waiting for the OAuth callback (5 minutes) \u2014 re-run when you're ready to approve in the browser"
|
|
838
|
+
);
|
|
839
|
+
case "ports_busy":
|
|
840
|
+
return new ConnectError("port_unavailable", err.message, HINT_PORTS);
|
|
841
|
+
case "oauth_error":
|
|
842
|
+
return new ConnectError("exchange_failed", err.message);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
async function runProvisionOnly(deps, info, base) {
|
|
846
|
+
if (info.webhookSecretConfigured === false) {
|
|
847
|
+
deps.out.note(WEBHOOK_SECRET_NOTE, "Webhook secret missing");
|
|
848
|
+
throw new ConnectError(
|
|
849
|
+
"webhook_secret_missing",
|
|
850
|
+
"POSTHOG_WEBHOOK_SECRET is not set on the instance \u2014 nothing to provision against"
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
if (isLoopbackUrl(info.apiPublicUrl)) {
|
|
854
|
+
deps.out.note(LOOPBACK_URL_NOTE, "Instance not publicly reachable");
|
|
855
|
+
throw new ConnectError(
|
|
856
|
+
"api_public_url_unreachable",
|
|
857
|
+
`API_PUBLIC_URL is ${info.apiPublicUrl} \u2014 PostHog cannot deliver webhooks to a loopback address`
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
let result;
|
|
861
|
+
try {
|
|
862
|
+
result = await deps.out.step(
|
|
863
|
+
`POST ${base}/v1/admin/analytics/provision-loop`,
|
|
864
|
+
() => deps.http.post(
|
|
865
|
+
"/v1/admin/analytics/provision-loop",
|
|
866
|
+
{}
|
|
867
|
+
)
|
|
868
|
+
);
|
|
869
|
+
} catch (err) {
|
|
870
|
+
if (isHttpError(err) && err.status === 409 && httpErrorBody(err) === "no_posthog_credential") {
|
|
871
|
+
throw new ConnectError(
|
|
872
|
+
"no_credential",
|
|
873
|
+
"no PostHog credential is stored on this instance",
|
|
874
|
+
"run `hogsend connect posthog` first"
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
throw new ConnectError("provision_failed", errMsg(err));
|
|
878
|
+
}
|
|
879
|
+
printProvisioned(deps.out, result);
|
|
880
|
+
return {
|
|
881
|
+
verdict: "connected",
|
|
882
|
+
providerId: "posthog",
|
|
883
|
+
instance: base,
|
|
884
|
+
posthog: null,
|
|
885
|
+
credential: { stored: false },
|
|
886
|
+
provision: {
|
|
887
|
+
attempted: true,
|
|
888
|
+
ok: true,
|
|
889
|
+
created: result.created === true,
|
|
890
|
+
hogFunctionId: result.hogFunctionId ?? "",
|
|
891
|
+
webhookUrl: result.webhookUrl ?? ""
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
function printProvisioned(out, result) {
|
|
896
|
+
out.note(
|
|
897
|
+
[
|
|
898
|
+
"PostHog -> Hogsend loop provisioned",
|
|
899
|
+
` webhookUrl ${result.webhookUrl ?? "(unknown)"}`,
|
|
900
|
+
` hogFunctionId ${result.hogFunctionId ?? "(unknown)"}`,
|
|
901
|
+
` created ${result.created === true ? "yes" : "no (existing function adopted)"}`
|
|
902
|
+
].join("\n")
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
async function runConnectPosthog(deps, opts) {
|
|
906
|
+
const base = deps.http.cfg.baseUrl;
|
|
907
|
+
const info = await deps.out.step(
|
|
908
|
+
`GET ${base}/v1/admin/analytics/connect-info`,
|
|
909
|
+
() => deps.http.get("/v1/admin/analytics/connect-info")
|
|
910
|
+
);
|
|
911
|
+
const privateHost = info.privateHost;
|
|
912
|
+
if (privateHost === null) {
|
|
913
|
+
throw new ConnectError(
|
|
914
|
+
"not_configured",
|
|
915
|
+
"this instance has no PostHog configuration",
|
|
916
|
+
HINT_NOT_CONFIGURED
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
if (opts.provisionOnly) {
|
|
920
|
+
return runProvisionOnly(deps, info, base);
|
|
921
|
+
}
|
|
922
|
+
if (info.hostExplicit === false) {
|
|
923
|
+
if (deps.interactive) {
|
|
924
|
+
const proceed = await deps.confirm(
|
|
925
|
+
`No POSTHOG_HOST set on the instance \u2014 assume PostHog US Cloud (${privateHost})?`
|
|
926
|
+
);
|
|
927
|
+
if (!proceed) {
|
|
928
|
+
throw new ConnectError(
|
|
929
|
+
"not_configured",
|
|
930
|
+
"set POSTHOG_HOST on the instance to pick the right region"
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
} else {
|
|
934
|
+
deps.out.log(
|
|
935
|
+
`warning: no POSTHOG_HOST set on the instance \u2014 assuming PostHog US Cloud (${privateHost}).`
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (info.personalKeyConfigured === true) {
|
|
940
|
+
deps.out.log(
|
|
941
|
+
"note: POSTHOG_PERSONAL_API_KEY is set on the instance; the OAuth credential will take precedence once stored."
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
const metadata = await deps.out.step(
|
|
945
|
+
`OAuth discovery at ${privateHost}`,
|
|
946
|
+
async () => {
|
|
947
|
+
const result = await deps.discover({ privateHost });
|
|
948
|
+
if (result.status === "unsupported") {
|
|
949
|
+
throw new ConnectError(
|
|
950
|
+
"oauth_unsupported",
|
|
951
|
+
`${privateHost} doesn't advertise an OAuth server (discovery returned 404)`,
|
|
952
|
+
hintOauthUnsupported(privateHost)
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
if (result.status === "error") {
|
|
956
|
+
throw new ConnectError("discovery_failed", result.message);
|
|
957
|
+
}
|
|
958
|
+
return result.metadata;
|
|
959
|
+
}
|
|
960
|
+
);
|
|
961
|
+
try {
|
|
962
|
+
if (new URL(metadata.issuer).origin !== new URL(privateHost).origin) {
|
|
963
|
+
deps.out.log(
|
|
964
|
+
`warning: discovery issuer ${metadata.issuer} differs from ${privateHost} \u2014 continuing.`
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
} catch {
|
|
968
|
+
}
|
|
969
|
+
const pkce = generatePkce();
|
|
970
|
+
const state = generateState();
|
|
971
|
+
let server;
|
|
972
|
+
try {
|
|
973
|
+
server = await deps.startLoopback({ ports: LOOPBACK_PORTS, state });
|
|
974
|
+
} catch (err) {
|
|
975
|
+
if (err instanceof LoopbackError) throw fromLoopbackError(err);
|
|
976
|
+
throw err;
|
|
977
|
+
}
|
|
978
|
+
let code;
|
|
979
|
+
try {
|
|
980
|
+
const authorizeUrl = buildAuthorizeUrl({
|
|
981
|
+
authorizationEndpoint: metadata.authorization_endpoint,
|
|
982
|
+
clientId: POSTHOG_CLIENT_ID,
|
|
983
|
+
redirectUri: server.redirectUri,
|
|
984
|
+
scope: POSTHOG_SCOPES,
|
|
985
|
+
state,
|
|
986
|
+
pkce,
|
|
987
|
+
requiredAccessLevel: REQUIRED_ACCESS_LEVEL
|
|
988
|
+
});
|
|
989
|
+
deps.out.note(
|
|
990
|
+
[
|
|
991
|
+
"About to authorize Hogsend against PostHog",
|
|
992
|
+
` instance ${base}`,
|
|
993
|
+
` posthog ${privateHost}`,
|
|
994
|
+
` scopes ${POSTHOG_SCOPES}`,
|
|
995
|
+
` callback ${server.redirectUri}`
|
|
996
|
+
].join("\n")
|
|
997
|
+
);
|
|
998
|
+
const opened = opts.noBrowser ? false : deps.openBrowser(authorizeUrl);
|
|
999
|
+
deps.out.log(
|
|
1000
|
+
opened ? "Opening your browser. If nothing happens, open this URL yourself:" : "Open this URL in a browser on THIS machine:"
|
|
1001
|
+
);
|
|
1002
|
+
deps.out.log(` ${authorizeUrl}`);
|
|
1003
|
+
if (!opened) {
|
|
1004
|
+
deps.out.note(SSH_NOTE);
|
|
1005
|
+
}
|
|
1006
|
+
const callback = await deps.out.step(
|
|
1007
|
+
"Waiting for PostHog authorization (Ctrl-C aborts)",
|
|
1008
|
+
() => server.waitForCallback({ timeoutMs: opts.timeoutMs })
|
|
1009
|
+
);
|
|
1010
|
+
code = callback.code;
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
if (err instanceof LoopbackError) throw fromLoopbackError(err);
|
|
1013
|
+
throw err;
|
|
1014
|
+
} finally {
|
|
1015
|
+
await server.close();
|
|
1016
|
+
}
|
|
1017
|
+
const tokenEndpoint = metadata.token_endpoint;
|
|
1018
|
+
let tokens;
|
|
1019
|
+
try {
|
|
1020
|
+
tokens = await deps.out.step(
|
|
1021
|
+
`Exchanging code at ${tokenEndpoint}`,
|
|
1022
|
+
() => deps.exchangeCode({
|
|
1023
|
+
tokenEndpoint,
|
|
1024
|
+
clientId: POSTHOG_CLIENT_ID,
|
|
1025
|
+
code,
|
|
1026
|
+
codeVerifier: pkce.verifier,
|
|
1027
|
+
redirectUri: server.redirectUri
|
|
1028
|
+
})
|
|
1029
|
+
);
|
|
1030
|
+
} catch (err) {
|
|
1031
|
+
throw new ConnectError("exchange_failed", errMsg(err));
|
|
1032
|
+
}
|
|
1033
|
+
const expiresAt = new Date(
|
|
1034
|
+
deps.now().getTime() + tokens.expires_in * 1e3
|
|
1035
|
+
).toISOString();
|
|
1036
|
+
const scopes = (tokens.scope ?? POSTHOG_SCOPES).split(" ");
|
|
1037
|
+
const scopedTeams = tokens.scoped_teams ?? [];
|
|
1038
|
+
const scopedOrganizations = tokens.scoped_organizations ?? [];
|
|
1039
|
+
try {
|
|
1040
|
+
await deps.out.step(
|
|
1041
|
+
`PUT ${base}/v1/admin/provider-credentials/posthog`,
|
|
1042
|
+
() => deps.http.put("/v1/admin/provider-credentials/posthog", {
|
|
1043
|
+
kind: "oauth",
|
|
1044
|
+
payload: {
|
|
1045
|
+
accessToken: tokens.access_token,
|
|
1046
|
+
refreshToken: tokens.refresh_token,
|
|
1047
|
+
expiresAt,
|
|
1048
|
+
tokenEndpoint,
|
|
1049
|
+
clientId: POSTHOG_CLIENT_ID,
|
|
1050
|
+
scopes,
|
|
1051
|
+
scopedTeams,
|
|
1052
|
+
scopedOrganizations
|
|
1053
|
+
}
|
|
1054
|
+
})
|
|
1055
|
+
);
|
|
1056
|
+
} catch (err) {
|
|
1057
|
+
throw new ConnectError("store_failed", errMsg(err));
|
|
1058
|
+
}
|
|
1059
|
+
const stored = {
|
|
1060
|
+
providerId: "posthog",
|
|
1061
|
+
instance: base,
|
|
1062
|
+
posthog: {
|
|
1063
|
+
privateHost,
|
|
1064
|
+
issuer: metadata.issuer,
|
|
1065
|
+
scopes: scopes.join(" "),
|
|
1066
|
+
scopedTeams,
|
|
1067
|
+
scopedOrganizations
|
|
1068
|
+
},
|
|
1069
|
+
credential: { stored: true, expiresAt }
|
|
1070
|
+
};
|
|
1071
|
+
if (opts.noProvision) {
|
|
1072
|
+
return {
|
|
1073
|
+
verdict: "connected_no_provision",
|
|
1074
|
+
...stored,
|
|
1075
|
+
provision: { attempted: false, skipped: "no_provision_flag" }
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
if (info.webhookSecretConfigured === false) {
|
|
1079
|
+
deps.out.note(WEBHOOK_SECRET_NOTE, "Webhook secret missing");
|
|
1080
|
+
return {
|
|
1081
|
+
verdict: "connected_no_provision",
|
|
1082
|
+
...stored,
|
|
1083
|
+
provision: { attempted: false, skipped: "webhook_secret_missing" }
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
if (isLoopbackUrl(info.apiPublicUrl)) {
|
|
1087
|
+
deps.out.note(LOOPBACK_URL_NOTE, "Instance not publicly reachable");
|
|
1088
|
+
return {
|
|
1089
|
+
verdict: "connected_no_provision",
|
|
1090
|
+
...stored,
|
|
1091
|
+
provision: { attempted: false, skipped: "api_public_url_unreachable" }
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
try {
|
|
1095
|
+
const result = await deps.out.step(
|
|
1096
|
+
`POST ${base}/v1/admin/analytics/provision-loop`,
|
|
1097
|
+
() => deps.http.post(
|
|
1098
|
+
"/v1/admin/analytics/provision-loop",
|
|
1099
|
+
{}
|
|
1100
|
+
)
|
|
1101
|
+
);
|
|
1102
|
+
printProvisioned(deps.out, result);
|
|
1103
|
+
return {
|
|
1104
|
+
verdict: "connected",
|
|
1105
|
+
...stored,
|
|
1106
|
+
provision: {
|
|
1107
|
+
attempted: true,
|
|
1108
|
+
ok: true,
|
|
1109
|
+
created: result.created === true,
|
|
1110
|
+
hogFunctionId: result.hogFunctionId ?? "",
|
|
1111
|
+
webhookUrl: result.webhookUrl ?? ""
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
} catch (err) {
|
|
1115
|
+
const message = errMsg(err);
|
|
1116
|
+
deps.out.log(
|
|
1117
|
+
`The credential is stored, but provisioning the event loop failed: ${message}. Re-run with: hogsend connect posthog --provision-only`
|
|
1118
|
+
);
|
|
1119
|
+
return {
|
|
1120
|
+
verdict: "connected_no_provision",
|
|
1121
|
+
...stored,
|
|
1122
|
+
provision: { attempted: true, ok: false, error: message }
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// src/lib/prompt.ts
|
|
1128
|
+
import { cancel as cancel2, isCancel } from "@clack/prompts";
|
|
1129
|
+
function bail(value) {
|
|
1130
|
+
if (isCancel(value)) {
|
|
1131
|
+
cancel2("Cancelled.");
|
|
1132
|
+
process.exit(0);
|
|
1133
|
+
}
|
|
1134
|
+
return value;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// src/commands/connect.ts
|
|
1138
|
+
var usage2 = `hogsend connect <provider> [--provision-only] [--no-provision] [--no-browser] [--json]
|
|
1139
|
+
|
|
1140
|
+
Connect this Hogsend instance to an analytics provider via OAuth. Providers:
|
|
1141
|
+
|
|
1142
|
+
posthog Authorize Hogsend against your PostHog region (PKCE, loopback
|
|
1143
|
+
callback on 127.0.0.1), store the refresh token on the instance,
|
|
1144
|
+
then provision the PostHog -> Hogsend event loop (a PostHog
|
|
1145
|
+
destination posting to /v1/webhooks/posthog).
|
|
1146
|
+
|
|
1147
|
+
The browser consent must happen on THIS machine (the OAuth callback lands on
|
|
1148
|
+
127.0.0.1). The target instance can be anywhere \u2014 point --url at it and run
|
|
1149
|
+
this command from your laptop, not from an SSH session on the server.
|
|
1150
|
+
|
|
1151
|
+
Options:
|
|
1152
|
+
--provision-only Skip OAuth; (re-)provision the event loop using the
|
|
1153
|
+
already-stored credential.
|
|
1154
|
+
--no-provision Stop after storing the credential.
|
|
1155
|
+
--no-browser Don't spawn a browser; just print the authorize URL.
|
|
1156
|
+
--url, --admin-key, --json, -h, --help Global flags as usual.
|
|
1157
|
+
|
|
1158
|
+
Exit code: 0 when a credential is stored (even if provisioning was skipped),
|
|
1159
|
+
1 otherwise.`;
|
|
1160
|
+
async function run2(ctx) {
|
|
1161
|
+
const { values: values2, positionals } = parseArgs2({
|
|
1162
|
+
args: ctx.argv,
|
|
1163
|
+
allowPositionals: true,
|
|
1164
|
+
strict: false,
|
|
1165
|
+
options: {
|
|
1166
|
+
"provision-only": { type: "boolean", default: false },
|
|
1167
|
+
"no-provision": { type: "boolean", default: false },
|
|
1168
|
+
"no-browser": { type: "boolean", default: false },
|
|
1169
|
+
help: { type: "boolean", short: "h", default: false }
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
if (values2.help) {
|
|
1173
|
+
ctx.out.log(usage2);
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
const provider = positionals[0];
|
|
1177
|
+
if (!provider) {
|
|
1178
|
+
ctx.out.fail("missing provider \u2014 try: hogsend connect posthog");
|
|
1179
|
+
}
|
|
1180
|
+
if (provider !== "posthog") {
|
|
1181
|
+
ctx.out.fail(`unknown provider "${provider}" \u2014 supported: posthog`);
|
|
1182
|
+
}
|
|
1183
|
+
if (values2["provision-only"] && values2["no-provision"]) {
|
|
1184
|
+
ctx.out.fail("--provision-only and --no-provision are mutually exclusive");
|
|
1185
|
+
}
|
|
1186
|
+
try {
|
|
1187
|
+
const target = new URL(ctx.cfg.baseUrl);
|
|
1188
|
+
if (target.protocol === "http:" && target.hostname !== "localhost" && target.hostname !== "127.0.0.1") {
|
|
1189
|
+
ctx.out.log(
|
|
1190
|
+
color.yellow(
|
|
1191
|
+
`warning: ${ctx.cfg.baseUrl} is plain http \u2014 OAuth tokens will be sent to it unencrypted; use https for remote instances.`
|
|
1192
|
+
)
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
} catch {
|
|
1196
|
+
}
|
|
1197
|
+
ctx.out.intro(`${color.bgMagenta(color.black(" hogsend "))} connect`);
|
|
1198
|
+
const deps = {
|
|
1199
|
+
http: ctx.http,
|
|
1200
|
+
out: ctx.out,
|
|
1201
|
+
interactive: ctx.out.interactive,
|
|
1202
|
+
discover: discoverOAuthServer,
|
|
1203
|
+
startLoopback: startLoopbackServer,
|
|
1204
|
+
exchangeCode,
|
|
1205
|
+
openBrowser,
|
|
1206
|
+
confirm: async (message) => bail(await confirm({ message })),
|
|
1207
|
+
now: () => /* @__PURE__ */ new Date()
|
|
1208
|
+
};
|
|
1209
|
+
try {
|
|
1210
|
+
const result = await runConnectPosthog(deps, {
|
|
1211
|
+
provisionOnly: Boolean(values2["provision-only"]),
|
|
1212
|
+
noProvision: Boolean(values2["no-provision"]),
|
|
1213
|
+
noBrowser: Boolean(values2["no-browser"])
|
|
1214
|
+
});
|
|
1215
|
+
if (ctx.json) {
|
|
1216
|
+
ctx.out.json({ ok: true, ...result });
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
ctx.out.outro(
|
|
1220
|
+
color.green(
|
|
1221
|
+
`connect: posthog ${result.verdict === "connected" ? "connected" : "connected (loop not provisioned)"}`
|
|
1222
|
+
)
|
|
1223
|
+
);
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
if (error instanceof ConnectError) {
|
|
1226
|
+
if (ctx.json) {
|
|
1227
|
+
ctx.out.json({
|
|
1228
|
+
ok: false,
|
|
1229
|
+
verdict: error.verdict,
|
|
1230
|
+
error: error.message,
|
|
1231
|
+
hint: error.hint
|
|
1232
|
+
});
|
|
1233
|
+
process.exit(1);
|
|
1234
|
+
}
|
|
1235
|
+
ctx.out.note(
|
|
1236
|
+
error.hint ? `${error.message}
|
|
1237
|
+
|
|
1238
|
+
${error.hint}` : error.message
|
|
1239
|
+
);
|
|
1240
|
+
ctx.out.outro(color.red(`connect: ${error.verdict}`));
|
|
1241
|
+
process.exit(1);
|
|
1242
|
+
}
|
|
1243
|
+
throw error;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
var connectCommand = {
|
|
1247
|
+
name: "connect",
|
|
1248
|
+
summary: "Connect an analytics provider via OAuth (posthog)",
|
|
1249
|
+
usage: usage2,
|
|
1250
|
+
run: run2
|
|
467
1251
|
};
|
|
468
1252
|
|
|
469
1253
|
// src/commands/contacts.ts
|
|
470
|
-
import { parseArgs as
|
|
471
|
-
var
|
|
1254
|
+
import { parseArgs as parseArgs3 } from "util";
|
|
1255
|
+
var usage3 = `hogsend contacts <subcommand> [options]
|
|
472
1256
|
|
|
473
1257
|
Inspect contacts via the admin API (/v1/admin/contacts) and upsert them via the
|
|
474
1258
|
data plane (PUT /v1/contacts).
|
|
@@ -573,7 +1357,7 @@ async function fetchOrFail(ctx, label, fn) {
|
|
|
573
1357
|
}
|
|
574
1358
|
}
|
|
575
1359
|
async function runList(ctx, argv) {
|
|
576
|
-
const { values: values2 } =
|
|
1360
|
+
const { values: values2 } = parseArgs3({
|
|
577
1361
|
args: argv,
|
|
578
1362
|
allowPositionals: true,
|
|
579
1363
|
options: {
|
|
@@ -584,7 +1368,7 @@ async function runList(ctx, argv) {
|
|
|
584
1368
|
}
|
|
585
1369
|
});
|
|
586
1370
|
if (values2.help) {
|
|
587
|
-
ctx.out.log(
|
|
1371
|
+
ctx.out.log(usage3);
|
|
588
1372
|
return;
|
|
589
1373
|
}
|
|
590
1374
|
const query = {
|
|
@@ -616,7 +1400,7 @@ async function runList(ctx, argv) {
|
|
|
616
1400
|
);
|
|
617
1401
|
}
|
|
618
1402
|
async function runGet(ctx, argv) {
|
|
619
|
-
const { values: values2, positionals } =
|
|
1403
|
+
const { values: values2, positionals } = parseArgs3({
|
|
620
1404
|
args: argv,
|
|
621
1405
|
allowPositionals: true,
|
|
622
1406
|
options: {
|
|
@@ -624,7 +1408,7 @@ async function runGet(ctx, argv) {
|
|
|
624
1408
|
}
|
|
625
1409
|
});
|
|
626
1410
|
if (values2.help) {
|
|
627
|
-
ctx.out.log(
|
|
1411
|
+
ctx.out.log(usage3);
|
|
628
1412
|
return;
|
|
629
1413
|
}
|
|
630
1414
|
const id = positionals[1];
|
|
@@ -671,7 +1455,7 @@ async function runGet(ctx, argv) {
|
|
|
671
1455
|
ctx.out.outro(`Contact ${color.cyan(contact.externalId)}`);
|
|
672
1456
|
}
|
|
673
1457
|
async function runTimeline(ctx, argv) {
|
|
674
|
-
const { values: values2, positionals } =
|
|
1458
|
+
const { values: values2, positionals } = parseArgs3({
|
|
675
1459
|
args: argv,
|
|
676
1460
|
allowPositionals: true,
|
|
677
1461
|
options: {
|
|
@@ -682,7 +1466,7 @@ async function runTimeline(ctx, argv) {
|
|
|
682
1466
|
}
|
|
683
1467
|
});
|
|
684
1468
|
if (values2.help) {
|
|
685
|
-
ctx.out.log(
|
|
1469
|
+
ctx.out.log(usage3);
|
|
686
1470
|
return;
|
|
687
1471
|
}
|
|
688
1472
|
const id = positionals[1];
|
|
@@ -736,7 +1520,7 @@ function summarizeTimelineEntry(entry) {
|
|
|
736
1520
|
return `${subject} [${String(d.status ?? "")}]`;
|
|
737
1521
|
}
|
|
738
1522
|
async function runUpsert(ctx, argv) {
|
|
739
|
-
const { values: values2 } =
|
|
1523
|
+
const { values: values2 } = parseArgs3({
|
|
740
1524
|
args: argv,
|
|
741
1525
|
allowPositionals: true,
|
|
742
1526
|
options: {
|
|
@@ -750,7 +1534,7 @@ async function runUpsert(ctx, argv) {
|
|
|
750
1534
|
}
|
|
751
1535
|
});
|
|
752
1536
|
if (values2.help) {
|
|
753
|
-
ctx.out.log(
|
|
1537
|
+
ctx.out.log(usage3);
|
|
754
1538
|
return;
|
|
755
1539
|
}
|
|
756
1540
|
const email = values2.email;
|
|
@@ -790,7 +1574,7 @@ async function runUpsert(ctx, argv) {
|
|
|
790
1574
|
const verb = res.created ? "created" : "updated";
|
|
791
1575
|
ctx.out.outro(`Contact ${color.cyan(res.id)} ${verb}.`);
|
|
792
1576
|
}
|
|
793
|
-
async function
|
|
1577
|
+
async function run3(ctx) {
|
|
794
1578
|
const sub = ctx.argv[0];
|
|
795
1579
|
switch (sub) {
|
|
796
1580
|
case "list":
|
|
@@ -815,21 +1599,21 @@ async function run2(ctx) {
|
|
|
815
1599
|
var contactsCommand = {
|
|
816
1600
|
name: "contacts",
|
|
817
1601
|
summary: "List, inspect, trace, and upsert contacts",
|
|
818
|
-
usage:
|
|
819
|
-
run:
|
|
1602
|
+
usage: usage3,
|
|
1603
|
+
run: run3
|
|
820
1604
|
};
|
|
821
1605
|
|
|
822
1606
|
// src/commands/dev.ts
|
|
823
1607
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
824
1608
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
825
1609
|
import { join as join3, resolve } from "path";
|
|
826
|
-
import { parseArgs as
|
|
1610
|
+
import { parseArgs as parseArgs6 } from "util";
|
|
827
1611
|
|
|
828
1612
|
// src/lib/proc.ts
|
|
829
|
-
import { spawn } from "child_process";
|
|
1613
|
+
import { spawn as spawn2 } from "child_process";
|
|
830
1614
|
import { createInterface } from "readline";
|
|
831
1615
|
function spawnManaged(opts) {
|
|
832
|
-
const child =
|
|
1616
|
+
const child = spawn2(opts.cmd, opts.args, {
|
|
833
1617
|
cwd: opts.cwd,
|
|
834
1618
|
env: { ...process.env, FORCE_COLOR: "1", ...opts.env },
|
|
835
1619
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -930,7 +1714,7 @@ async function waitForHttp(url, timeoutMs) {
|
|
|
930
1714
|
|
|
931
1715
|
// src/lib/setup-steps.ts
|
|
932
1716
|
import { spawnSync } from "child_process";
|
|
933
|
-
import { randomBytes } from "crypto";
|
|
1717
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
934
1718
|
import { copyFileSync, existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
935
1719
|
import { connect } from "net";
|
|
936
1720
|
import { join as join2 } from "path";
|
|
@@ -938,10 +1722,10 @@ import { join as join2 } from "path";
|
|
|
938
1722
|
// src/lib/config.ts
|
|
939
1723
|
import { existsSync, readFileSync } from "fs";
|
|
940
1724
|
import { join } from "path";
|
|
941
|
-
import { parseArgs as
|
|
1725
|
+
import { parseArgs as parseArgs4 } from "util";
|
|
942
1726
|
var DEFAULT_BASE_URL = "http://localhost:3002";
|
|
943
1727
|
function parseGlobalFlags(argv) {
|
|
944
|
-
const { values: values2, tokens } =
|
|
1728
|
+
const { values: values2, tokens } = parseArgs4({
|
|
945
1729
|
args: argv,
|
|
946
1730
|
allowPositionals: true,
|
|
947
1731
|
strict: false,
|
|
@@ -1022,7 +1806,7 @@ var SECRET_KEY = "BETTER_AUTH_SECRET";
|
|
|
1022
1806
|
var PLACEHOLDER_PREFIX = "change-me";
|
|
1023
1807
|
var PLACEHOLDER_PREFIXES = [PLACEHOLDER_PREFIX, "REPLACE_ME"];
|
|
1024
1808
|
function generateSecret() {
|
|
1025
|
-
return
|
|
1809
|
+
return randomBytes2(32).toString("hex");
|
|
1026
1810
|
}
|
|
1027
1811
|
var COMPOSE_FILES = [
|
|
1028
1812
|
"docker-compose.yml",
|
|
@@ -1215,8 +1999,8 @@ async function detectRunningInfra(cwd) {
|
|
|
1215
1999
|
}
|
|
1216
2000
|
|
|
1217
2001
|
// src/commands/events.ts
|
|
1218
|
-
import { parseArgs as
|
|
1219
|
-
var
|
|
2002
|
+
import { parseArgs as parseArgs5 } from "util";
|
|
2003
|
+
var usage4 = `hogsend events <userId> [options]
|
|
1220
2004
|
hogsend events send <name> [options]
|
|
1221
2005
|
|
|
1222
2006
|
Read a single user's event history (admin API), or send an event into the data
|
|
@@ -1262,14 +2046,14 @@ Examples:
|
|
|
1262
2046
|
hogsend events user_123 --from 2026-01-01T00:00:00Z --json
|
|
1263
2047
|
hogsend events send signup --user-id user_123 --prop plan=pro
|
|
1264
2048
|
hogsend events send purchase --email a@b.com --props '{"amount":49}' --json`;
|
|
1265
|
-
async function
|
|
2049
|
+
async function run4(ctx) {
|
|
1266
2050
|
if (ctx.argv[0] === "send") {
|
|
1267
2051
|
return runSend2(ctx, ctx.argv.slice(1));
|
|
1268
2052
|
}
|
|
1269
2053
|
return runRead(ctx, ctx.argv);
|
|
1270
2054
|
}
|
|
1271
2055
|
async function runRead(ctx, argv) {
|
|
1272
|
-
const { values: values2, positionals } =
|
|
2056
|
+
const { values: values2, positionals } = parseArgs5({
|
|
1273
2057
|
args: argv,
|
|
1274
2058
|
allowPositionals: true,
|
|
1275
2059
|
options: {
|
|
@@ -1282,7 +2066,7 @@ async function runRead(ctx, argv) {
|
|
|
1282
2066
|
}
|
|
1283
2067
|
});
|
|
1284
2068
|
if (values2.help) {
|
|
1285
|
-
ctx.out.log(
|
|
2069
|
+
ctx.out.log(usage4);
|
|
1286
2070
|
return;
|
|
1287
2071
|
}
|
|
1288
2072
|
const userId = positionals[0];
|
|
@@ -1338,7 +2122,7 @@ async function runRead(ctx, argv) {
|
|
|
1338
2122
|
);
|
|
1339
2123
|
}
|
|
1340
2124
|
async function runSend2(ctx, argv) {
|
|
1341
|
-
const { values: values2, positionals } =
|
|
2125
|
+
const { values: values2, positionals } = parseArgs5({
|
|
1342
2126
|
args: argv,
|
|
1343
2127
|
allowPositionals: true,
|
|
1344
2128
|
options: {
|
|
@@ -1356,7 +2140,7 @@ async function runSend2(ctx, argv) {
|
|
|
1356
2140
|
}
|
|
1357
2141
|
});
|
|
1358
2142
|
if (values2.help) {
|
|
1359
|
-
ctx.out.log(
|
|
2143
|
+
ctx.out.log(usage4);
|
|
1360
2144
|
return;
|
|
1361
2145
|
}
|
|
1362
2146
|
const name = positionals[0];
|
|
@@ -1494,12 +2278,12 @@ function summarizeProps(props) {
|
|
|
1494
2278
|
var eventsCommand = {
|
|
1495
2279
|
name: "events",
|
|
1496
2280
|
summary: "Stream a user's event history, or send an event",
|
|
1497
|
-
usage:
|
|
1498
|
-
run:
|
|
2281
|
+
usage: usage4,
|
|
2282
|
+
run: run4
|
|
1499
2283
|
};
|
|
1500
2284
|
|
|
1501
2285
|
// src/commands/dev.ts
|
|
1502
|
-
var
|
|
2286
|
+
var usage5 = `hogsend dev [options]
|
|
1503
2287
|
hogsend dev --fire <event> [event-send options]
|
|
1504
2288
|
|
|
1505
2289
|
Run the full local stack for a Hogsend app from one command:
|
|
@@ -1582,7 +2366,7 @@ function extractFire(argv) {
|
|
|
1582
2366
|
}
|
|
1583
2367
|
async function runFire(ctx, event, rest) {
|
|
1584
2368
|
if (rest.includes("-h") || rest.includes("--help")) {
|
|
1585
|
-
ctx.out.log(
|
|
2369
|
+
ctx.out.log(usage5);
|
|
1586
2370
|
return;
|
|
1587
2371
|
}
|
|
1588
2372
|
try {
|
|
@@ -1690,7 +2474,7 @@ async function prepareInfra(ctx, cwd, pkg) {
|
|
|
1690
2474
|
ctx.out.log(color.dim(" no db:migrate script \u2014 skipping migrations"));
|
|
1691
2475
|
}
|
|
1692
2476
|
}
|
|
1693
|
-
async function
|
|
2477
|
+
async function run5(ctx) {
|
|
1694
2478
|
const fire = extractFire(ctx.argv);
|
|
1695
2479
|
if (fire && "error" in fire) {
|
|
1696
2480
|
ctx.out.fail(fire.error);
|
|
@@ -1699,7 +2483,7 @@ async function run4(ctx) {
|
|
|
1699
2483
|
await runFire(ctx, fire.event, fire.rest);
|
|
1700
2484
|
return;
|
|
1701
2485
|
}
|
|
1702
|
-
const { values: values2 } =
|
|
2486
|
+
const { values: values2 } = parseArgs6({
|
|
1703
2487
|
args: ctx.argv,
|
|
1704
2488
|
allowPositionals: true,
|
|
1705
2489
|
options: {
|
|
@@ -1710,7 +2494,7 @@ async function run4(ctx) {
|
|
|
1710
2494
|
}
|
|
1711
2495
|
});
|
|
1712
2496
|
if (values2.help) {
|
|
1713
|
-
ctx.out.log(
|
|
2497
|
+
ctx.out.log(usage5);
|
|
1714
2498
|
return;
|
|
1715
2499
|
}
|
|
1716
2500
|
const cwd = resolve(values2.cwd ?? process.cwd());
|
|
@@ -1817,12 +2601,12 @@ ${color.dim("Shutting down\u2026")}`);
|
|
|
1817
2601
|
var devCommand = {
|
|
1818
2602
|
name: "dev",
|
|
1819
2603
|
summary: "Run the full local stack: infra, API + worker, health, URLs",
|
|
1820
|
-
usage:
|
|
1821
|
-
run:
|
|
2604
|
+
usage: usage5,
|
|
2605
|
+
run: run5
|
|
1822
2606
|
};
|
|
1823
2607
|
|
|
1824
2608
|
// src/commands/doctor.ts
|
|
1825
|
-
import { parseArgs as
|
|
2609
|
+
import { parseArgs as parseArgs7 } from "util";
|
|
1826
2610
|
|
|
1827
2611
|
// src/lib/skills.ts
|
|
1828
2612
|
import {
|
|
@@ -1973,7 +2757,7 @@ function analyticsNudge(ctx) {
|
|
|
1973
2757
|
"PostHog person reads disabled"
|
|
1974
2758
|
);
|
|
1975
2759
|
}
|
|
1976
|
-
var
|
|
2760
|
+
var usage6 = `hogsend doctor [--url <baseUrl>] [--admin-key <key>] [--json]
|
|
1977
2761
|
|
|
1978
2762
|
Probe a running Hogsend instance via GET /v1/health and report its health:
|
|
1979
2763
|
component status (database, redis), two-track schema state (engine + client),
|
|
@@ -2015,8 +2799,8 @@ function trackLine(name, track) {
|
|
|
2015
2799
|
const required = track.required ?? color.dim("none");
|
|
2016
2800
|
return `${color.bold(name.padEnd(7))} applied ${applied} -> required ${required} ${sync}`;
|
|
2017
2801
|
}
|
|
2018
|
-
async function
|
|
2019
|
-
const { values: values2 } =
|
|
2802
|
+
async function run6(ctx) {
|
|
2803
|
+
const { values: values2 } = parseArgs7({
|
|
2020
2804
|
args: ctx.argv,
|
|
2021
2805
|
allowPositionals: true,
|
|
2022
2806
|
options: {
|
|
@@ -2026,7 +2810,7 @@ async function run5(ctx) {
|
|
|
2026
2810
|
strict: false
|
|
2027
2811
|
});
|
|
2028
2812
|
if (values2.help) {
|
|
2029
|
-
ctx.out.log(
|
|
2813
|
+
ctx.out.log(usage6);
|
|
2030
2814
|
return;
|
|
2031
2815
|
}
|
|
2032
2816
|
const { baseUrl } = ctx.http.cfg;
|
|
@@ -2117,13 +2901,13 @@ async function run5(ctx) {
|
|
|
2117
2901
|
var doctorCommand = {
|
|
2118
2902
|
name: "doctor",
|
|
2119
2903
|
summary: "Probe a running instance's health (GET /v1/health)",
|
|
2120
|
-
usage:
|
|
2121
|
-
run:
|
|
2904
|
+
usage: usage6,
|
|
2905
|
+
run: run6
|
|
2122
2906
|
};
|
|
2123
2907
|
|
|
2124
2908
|
// src/commands/domain.ts
|
|
2125
|
-
import { parseArgs as
|
|
2126
|
-
import { confirm } from "@clack/prompts";
|
|
2909
|
+
import { parseArgs as parseArgs8 } from "util";
|
|
2910
|
+
import { confirm as confirm2 } from "@clack/prompts";
|
|
2127
2911
|
|
|
2128
2912
|
// src/lib/dns.ts
|
|
2129
2913
|
import { resolveNs as nodeResolveNs } from "dns/promises";
|
|
@@ -2417,18 +3201,8 @@ async function applyRecords(opts) {
|
|
|
2417
3201
|
return applyVercel(opts, fetchImpl);
|
|
2418
3202
|
}
|
|
2419
3203
|
|
|
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
3204
|
// src/commands/domain.ts
|
|
2431
|
-
var
|
|
3205
|
+
var usage7 = `hogsend domain <subcommand> [options]
|
|
2432
3206
|
|
|
2433
3207
|
Manage the sending domain through the RUNNING instance's admin routes
|
|
2434
3208
|
(/v1/admin/domain) \u2014 provider API keys never touch the CLI. Requires an admin
|
|
@@ -2536,7 +3310,7 @@ function renderStatus(ctx, status) {
|
|
|
2536
3310
|
}
|
|
2537
3311
|
}
|
|
2538
3312
|
async function runAdd(ctx, argv) {
|
|
2539
|
-
const { values: values2, positionals } =
|
|
3313
|
+
const { values: values2, positionals } = parseArgs8({
|
|
2540
3314
|
args: argv,
|
|
2541
3315
|
allowPositionals: true,
|
|
2542
3316
|
options: {
|
|
@@ -2546,7 +3320,7 @@ async function runAdd(ctx, argv) {
|
|
|
2546
3320
|
}
|
|
2547
3321
|
});
|
|
2548
3322
|
if (values2.help) {
|
|
2549
|
-
ctx.out.log(
|
|
3323
|
+
ctx.out.log(usage7);
|
|
2550
3324
|
return;
|
|
2551
3325
|
}
|
|
2552
3326
|
const domain = positionals[0];
|
|
@@ -2572,7 +3346,7 @@ async function runAdd(ctx, argv) {
|
|
|
2572
3346
|
const autoAvailable = canAutoApply(host.id, process.env);
|
|
2573
3347
|
if (autoAvailable && records.length > 0) {
|
|
2574
3348
|
const doApply = values2.apply ? true : values2["no-apply"] ? false : ctx.out.interactive ? bail(
|
|
2575
|
-
await
|
|
3349
|
+
await confirm2({
|
|
2576
3350
|
message: `Apply these records via the ${host.label} API?`,
|
|
2577
3351
|
initialValue: true
|
|
2578
3352
|
})
|
|
@@ -2621,7 +3395,7 @@ async function runAdd(ctx, argv) {
|
|
|
2621
3395
|
);
|
|
2622
3396
|
}
|
|
2623
3397
|
async function runCheck(ctx, argv) {
|
|
2624
|
-
const { values: values2, positionals } =
|
|
3398
|
+
const { values: values2, positionals } = parseArgs8({
|
|
2625
3399
|
args: argv,
|
|
2626
3400
|
allowPositionals: true,
|
|
2627
3401
|
options: {
|
|
@@ -2631,7 +3405,7 @@ async function runCheck(ctx, argv) {
|
|
|
2631
3405
|
}
|
|
2632
3406
|
});
|
|
2633
3407
|
if (values2.help) {
|
|
2634
|
-
ctx.out.log(
|
|
3408
|
+
ctx.out.log(usage7);
|
|
2635
3409
|
return;
|
|
2636
3410
|
}
|
|
2637
3411
|
const timeoutSecs = Number(values2.timeout);
|
|
@@ -2703,7 +3477,7 @@ async function runCheck(ctx, argv) {
|
|
|
2703
3477
|
}
|
|
2704
3478
|
}
|
|
2705
3479
|
async function runStatus2(ctx, argv) {
|
|
2706
|
-
const { values: values2 } =
|
|
3480
|
+
const { values: values2 } = parseArgs8({
|
|
2707
3481
|
args: argv,
|
|
2708
3482
|
allowPositionals: false,
|
|
2709
3483
|
options: {
|
|
@@ -2712,7 +3486,7 @@ async function runStatus2(ctx, argv) {
|
|
|
2712
3486
|
}
|
|
2713
3487
|
});
|
|
2714
3488
|
if (values2.help) {
|
|
2715
|
-
ctx.out.log(
|
|
3489
|
+
ctx.out.log(usage7);
|
|
2716
3490
|
return;
|
|
2717
3491
|
}
|
|
2718
3492
|
const status = await getStatus(ctx, { refresh: values2.refresh });
|
|
@@ -2732,11 +3506,11 @@ async function runStatus2(ctx, argv) {
|
|
|
2732
3506
|
}
|
|
2733
3507
|
ctx.out.outro("Done.");
|
|
2734
3508
|
}
|
|
2735
|
-
async function
|
|
3509
|
+
async function run7(ctx) {
|
|
2736
3510
|
const sub = ctx.argv[0];
|
|
2737
3511
|
const rest = ctx.argv.slice(1);
|
|
2738
3512
|
if (!sub || sub === "--help" || sub === "-h" || sub === "help") {
|
|
2739
|
-
ctx.out.log(
|
|
3513
|
+
ctx.out.log(usage7);
|
|
2740
3514
|
return;
|
|
2741
3515
|
}
|
|
2742
3516
|
switch (sub) {
|
|
@@ -2755,15 +3529,15 @@ async function run6(ctx) {
|
|
|
2755
3529
|
var domainCommand = {
|
|
2756
3530
|
name: "domain",
|
|
2757
3531
|
summary: "Set up + verify the sending domain (DNS records, auto-apply)",
|
|
2758
|
-
usage:
|
|
2759
|
-
run:
|
|
3532
|
+
usage: usage7,
|
|
3533
|
+
run: run7
|
|
2760
3534
|
};
|
|
2761
3535
|
|
|
2762
3536
|
// src/commands/eject.ts
|
|
2763
3537
|
import { existsSync as existsSync6, realpathSync } from "fs";
|
|
2764
3538
|
import { createRequire as createRequire2 } from "module";
|
|
2765
3539
|
import { dirname, join as join6, sep as sep2 } from "path";
|
|
2766
|
-
import { parseArgs as
|
|
3540
|
+
import { parseArgs as parseArgs9 } from "util";
|
|
2767
3541
|
|
|
2768
3542
|
// src/eject.ts
|
|
2769
3543
|
import { existsSync as existsSync5 } from "fs";
|
|
@@ -2877,7 +3651,7 @@ async function countFiles(dir) {
|
|
|
2877
3651
|
}
|
|
2878
3652
|
|
|
2879
3653
|
// src/commands/eject.ts
|
|
2880
|
-
var
|
|
3654
|
+
var usage8 = `hogsend eject <package> [--force] [--cwd <dir>]
|
|
2881
3655
|
|
|
2882
3656
|
Copy a @hogsend/* package's source into vendor/<name> and rewrite the consumer
|
|
2883
3657
|
dependency to file:./vendor/<name>. Every other dependency keeps upgrading.
|
|
@@ -2905,8 +3679,8 @@ function resolveSourceDir(pkg, consumerRoot) {
|
|
|
2905
3679
|
}
|
|
2906
3680
|
return null;
|
|
2907
3681
|
}
|
|
2908
|
-
async function
|
|
2909
|
-
const { values: values2, positionals } =
|
|
3682
|
+
async function run8(ctx) {
|
|
3683
|
+
const { values: values2, positionals } = parseArgs9({
|
|
2910
3684
|
args: ctx.argv,
|
|
2911
3685
|
allowPositionals: true,
|
|
2912
3686
|
options: {
|
|
@@ -2916,7 +3690,7 @@ async function run7(ctx) {
|
|
|
2916
3690
|
}
|
|
2917
3691
|
});
|
|
2918
3692
|
if (values2.help) {
|
|
2919
|
-
ctx.out.log(
|
|
3693
|
+
ctx.out.log(usage8);
|
|
2920
3694
|
return;
|
|
2921
3695
|
}
|
|
2922
3696
|
const pkg = positionals[0];
|
|
@@ -2960,13 +3734,13 @@ async function run7(ctx) {
|
|
|
2960
3734
|
var ejectCommand = {
|
|
2961
3735
|
name: "eject",
|
|
2962
3736
|
summary: "Vendor a @hogsend/* package into vendor/<name>",
|
|
2963
|
-
usage:
|
|
2964
|
-
run:
|
|
3737
|
+
usage: usage8,
|
|
3738
|
+
run: run8
|
|
2965
3739
|
};
|
|
2966
3740
|
|
|
2967
3741
|
// src/commands/emails.ts
|
|
2968
|
-
import { parseArgs as
|
|
2969
|
-
var
|
|
3742
|
+
import { parseArgs as parseArgs10 } from "util";
|
|
3743
|
+
var usage9 = `hogsend emails <subcommand> [options]
|
|
2970
3744
|
|
|
2971
3745
|
Send a transactional email through the data plane (POST /v1/emails). The send
|
|
2972
3746
|
runs through the full preferences + tracking pipeline (link-click + open).
|
|
@@ -3044,7 +3818,7 @@ function statusColor2(status) {
|
|
|
3044
3818
|
}
|
|
3045
3819
|
}
|
|
3046
3820
|
async function runSend3(ctx, argv) {
|
|
3047
|
-
const { values: values2, positionals } =
|
|
3821
|
+
const { values: values2, positionals } = parseArgs10({
|
|
3048
3822
|
args: argv,
|
|
3049
3823
|
allowPositionals: true,
|
|
3050
3824
|
options: {
|
|
@@ -3062,7 +3836,7 @@ async function runSend3(ctx, argv) {
|
|
|
3062
3836
|
}
|
|
3063
3837
|
});
|
|
3064
3838
|
if (values2.help) {
|
|
3065
|
-
ctx.out.log(
|
|
3839
|
+
ctx.out.log(usage9);
|
|
3066
3840
|
return;
|
|
3067
3841
|
}
|
|
3068
3842
|
const template = positionals[0];
|
|
@@ -3118,7 +3892,7 @@ async function runSend3(ctx, argv) {
|
|
|
3118
3892
|
);
|
|
3119
3893
|
ctx.out.outro(`${template} \u2192 ${statusColor2(res.status)}.`);
|
|
3120
3894
|
}
|
|
3121
|
-
async function
|
|
3895
|
+
async function run9(ctx) {
|
|
3122
3896
|
const sub = ctx.argv[0];
|
|
3123
3897
|
switch (sub) {
|
|
3124
3898
|
case "send":
|
|
@@ -3135,12 +3909,12 @@ async function run8(ctx) {
|
|
|
3135
3909
|
var emailsCommand = {
|
|
3136
3910
|
name: "emails",
|
|
3137
3911
|
summary: "Send a transactional email through the data plane",
|
|
3138
|
-
usage:
|
|
3139
|
-
run:
|
|
3912
|
+
usage: usage9,
|
|
3913
|
+
run: run9
|
|
3140
3914
|
};
|
|
3141
3915
|
|
|
3142
3916
|
// src/commands/hatchet.ts
|
|
3143
|
-
import { parseArgs as
|
|
3917
|
+
import { parseArgs as parseArgs11 } from "util";
|
|
3144
3918
|
|
|
3145
3919
|
// src/lib/hatchet-token.ts
|
|
3146
3920
|
var SLUG_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
@@ -3311,7 +4085,7 @@ async function mintHatchetToken(opts) {
|
|
|
3311
4085
|
}
|
|
3312
4086
|
|
|
3313
4087
|
// src/commands/hatchet.ts
|
|
3314
|
-
var
|
|
4088
|
+
var usage10 = `hogsend hatchet token [options]
|
|
3315
4089
|
|
|
3316
4090
|
Mint a Hatchet API token (HATCHET_CLIENT_TOKEN) headlessly against a
|
|
3317
4091
|
hatchet-lite instance. Registers the account if the instance still allows
|
|
@@ -3349,7 +4123,7 @@ public URL can create an account. On a public deployment set
|
|
|
3349
4123
|
SERVER_ALLOW_SIGNUP=false (plus a real ADMIN_EMAIL/ADMIN_PASSWORD, which
|
|
3350
4124
|
hatchet-lite seeds at boot); this command then logs in with those credentials.`;
|
|
3351
4125
|
function parseTokenFlags(argv) {
|
|
3352
|
-
const { values: values2 } =
|
|
4126
|
+
const { values: values2 } = parseArgs11({
|
|
3353
4127
|
args: argv,
|
|
3354
4128
|
allowPositionals: true,
|
|
3355
4129
|
strict: false,
|
|
@@ -3415,15 +4189,15 @@ async function runToken(ctx, argv) {
|
|
|
3415
4189
|
throw err;
|
|
3416
4190
|
}
|
|
3417
4191
|
}
|
|
3418
|
-
async function
|
|
4192
|
+
async function run10(ctx) {
|
|
3419
4193
|
const sub = ctx.argv[0];
|
|
3420
4194
|
const rest = ctx.argv.slice(1);
|
|
3421
4195
|
if (!sub || sub === "--help" || sub === "-h" || sub === "help") {
|
|
3422
|
-
ctx.out.log(
|
|
4196
|
+
ctx.out.log(usage10);
|
|
3423
4197
|
return;
|
|
3424
4198
|
}
|
|
3425
4199
|
if (rest.includes("-h") || rest.includes("--help")) {
|
|
3426
|
-
ctx.out.log(
|
|
4200
|
+
ctx.out.log(usage10);
|
|
3427
4201
|
return;
|
|
3428
4202
|
}
|
|
3429
4203
|
switch (sub) {
|
|
@@ -3436,13 +4210,13 @@ async function run9(ctx) {
|
|
|
3436
4210
|
var hatchetCommand = {
|
|
3437
4211
|
name: "hatchet",
|
|
3438
4212
|
summary: "Hatchet helpers \u2014 mint a HATCHET_CLIENT_TOKEN headlessly",
|
|
3439
|
-
usage:
|
|
3440
|
-
run:
|
|
4213
|
+
usage: usage10,
|
|
4214
|
+
run: run10
|
|
3441
4215
|
};
|
|
3442
4216
|
|
|
3443
4217
|
// src/commands/journeys.ts
|
|
3444
|
-
import { parseArgs as
|
|
3445
|
-
var
|
|
4218
|
+
import { parseArgs as parseArgs12 } from "util";
|
|
4219
|
+
var usage11 = `hogsend journeys <subcommand> [options]
|
|
3446
4220
|
|
|
3447
4221
|
Inspect and toggle journeys via the admin API (/v1/admin/journeys).
|
|
3448
4222
|
|
|
@@ -3471,7 +4245,7 @@ function statusColor3(enabled) {
|
|
|
3471
4245
|
return enabled ? color.green("enabled") : color.yellow("disabled");
|
|
3472
4246
|
}
|
|
3473
4247
|
async function runList2(ctx) {
|
|
3474
|
-
const { values: values2 } =
|
|
4248
|
+
const { values: values2 } = parseArgs12({
|
|
3475
4249
|
args: ctx.argv,
|
|
3476
4250
|
allowPositionals: true,
|
|
3477
4251
|
options: {
|
|
@@ -3482,7 +4256,7 @@ async function runList2(ctx) {
|
|
|
3482
4256
|
}
|
|
3483
4257
|
});
|
|
3484
4258
|
if (values2.help) {
|
|
3485
|
-
ctx.out.log(
|
|
4259
|
+
ctx.out.log(usage11);
|
|
3486
4260
|
return;
|
|
3487
4261
|
}
|
|
3488
4262
|
if (values2.enabled !== void 0 && !["true", "false"].includes(values2.enabled)) {
|
|
@@ -3618,7 +4392,7 @@ async function runToggle(ctx, id, enabled) {
|
|
|
3618
4392
|
);
|
|
3619
4393
|
ctx.out.outro(`${j.id} is now ${statusColor3(j.enabled)}.`);
|
|
3620
4394
|
}
|
|
3621
|
-
async function
|
|
4395
|
+
async function run11(ctx) {
|
|
3622
4396
|
const sub = ctx.argv[0];
|
|
3623
4397
|
const rest = ctx.argv.slice(1);
|
|
3624
4398
|
const subCtx = { ...ctx, argv: rest };
|
|
@@ -3630,7 +4404,7 @@ async function run10(ctx) {
|
|
|
3630
4404
|
case "get": {
|
|
3631
4405
|
const id = rest.find((a) => !a.startsWith("-"));
|
|
3632
4406
|
if (rest.includes("--help") || rest.includes("-h")) {
|
|
3633
|
-
ctx.out.log(
|
|
4407
|
+
ctx.out.log(usage11);
|
|
3634
4408
|
return;
|
|
3635
4409
|
}
|
|
3636
4410
|
await runGet2(subCtx, id);
|
|
@@ -3638,7 +4412,7 @@ async function run10(ctx) {
|
|
|
3638
4412
|
}
|
|
3639
4413
|
case "enable": {
|
|
3640
4414
|
if (rest.includes("--help") || rest.includes("-h")) {
|
|
3641
|
-
ctx.out.log(
|
|
4415
|
+
ctx.out.log(usage11);
|
|
3642
4416
|
return;
|
|
3643
4417
|
}
|
|
3644
4418
|
await runToggle(
|
|
@@ -3650,7 +4424,7 @@ async function run10(ctx) {
|
|
|
3650
4424
|
}
|
|
3651
4425
|
case "disable": {
|
|
3652
4426
|
if (rest.includes("--help") || rest.includes("-h")) {
|
|
3653
|
-
ctx.out.log(
|
|
4427
|
+
ctx.out.log(usage11);
|
|
3654
4428
|
return;
|
|
3655
4429
|
}
|
|
3656
4430
|
await runToggle(
|
|
@@ -3684,14 +4458,14 @@ async function run10(ctx) {
|
|
|
3684
4458
|
var journeysCommand = {
|
|
3685
4459
|
name: "journeys",
|
|
3686
4460
|
summary: "List, inspect, enable, and disable journeys",
|
|
3687
|
-
usage:
|
|
3688
|
-
run:
|
|
4461
|
+
usage: usage11,
|
|
4462
|
+
run: run11
|
|
3689
4463
|
};
|
|
3690
4464
|
|
|
3691
4465
|
// src/commands/patch.ts
|
|
3692
4466
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
3693
|
-
import { parseArgs as
|
|
3694
|
-
var
|
|
4467
|
+
import { parseArgs as parseArgs13 } from "util";
|
|
4468
|
+
var usage12 = `hogsend patch <package> [--cwd <dir>]
|
|
3695
4469
|
|
|
3696
4470
|
Thin wrapper over pnpm's native patch flow. Runs \`pnpm patch <package>\`, which
|
|
3697
4471
|
extracts the package into a temp dir and prints the path to edit. After editing,
|
|
@@ -3702,8 +4476,8 @@ This does NOT replace scripts/patch-check.sh (the patch re-apply contract).
|
|
|
3702
4476
|
Options:
|
|
3703
4477
|
--cwd <dir> Project root to run pnpm in (defaults to current directory).
|
|
3704
4478
|
-h, --help Show this help.`;
|
|
3705
|
-
async function
|
|
3706
|
-
const { values: values2, positionals } =
|
|
4479
|
+
async function run12(ctx) {
|
|
4480
|
+
const { values: values2, positionals } = parseArgs13({
|
|
3707
4481
|
args: ctx.argv,
|
|
3708
4482
|
allowPositionals: true,
|
|
3709
4483
|
options: {
|
|
@@ -3712,7 +4486,7 @@ async function run11(ctx) {
|
|
|
3712
4486
|
}
|
|
3713
4487
|
});
|
|
3714
4488
|
if (values2.help) {
|
|
3715
|
-
ctx.out.log(
|
|
4489
|
+
ctx.out.log(usage12);
|
|
3716
4490
|
return;
|
|
3717
4491
|
}
|
|
3718
4492
|
const pkg = positionals[0];
|
|
@@ -3752,16 +4526,16 @@ async function run11(ctx) {
|
|
|
3752
4526
|
var patchCommand = {
|
|
3753
4527
|
name: "patch",
|
|
3754
4528
|
summary: "Patch a package via pnpm's native patch flow",
|
|
3755
|
-
usage:
|
|
3756
|
-
run:
|
|
4529
|
+
usage: usage12,
|
|
4530
|
+
run: run12
|
|
3757
4531
|
};
|
|
3758
4532
|
|
|
3759
4533
|
// src/commands/setup.ts
|
|
3760
4534
|
import { existsSync as existsSync7 } from "fs";
|
|
3761
4535
|
import { join as join7 } from "path";
|
|
3762
|
-
import { parseArgs as
|
|
3763
|
-
import { confirm as
|
|
3764
|
-
var
|
|
4536
|
+
import { parseArgs as parseArgs14 } from "util";
|
|
4537
|
+
import { confirm as confirm3 } from "@clack/prompts";
|
|
4538
|
+
var usage13 = `hogsend setup [--cwd <dir>] [--yes] [--json]
|
|
3765
4539
|
|
|
3766
4540
|
Interactive local onboarding for a scaffolded Hogsend app. Mirrors the
|
|
3767
4541
|
create-hogsend "next steps":
|
|
@@ -3778,8 +4552,8 @@ Options:
|
|
|
3778
4552
|
-h, --help Show this help.
|
|
3779
4553
|
|
|
3780
4554
|
Run ${color.cyan("hogsend doctor")} afterwards to verify the instance is healthy.`;
|
|
3781
|
-
async function
|
|
3782
|
-
const { values: values2 } =
|
|
4555
|
+
async function run13(ctx) {
|
|
4556
|
+
const { values: values2 } = parseArgs14({
|
|
3783
4557
|
args: ctx.argv,
|
|
3784
4558
|
allowPositionals: true,
|
|
3785
4559
|
options: {
|
|
@@ -3789,7 +4563,7 @@ async function run12(ctx) {
|
|
|
3789
4563
|
}
|
|
3790
4564
|
});
|
|
3791
4565
|
if (values2.help) {
|
|
3792
|
-
ctx.out.log(
|
|
4566
|
+
ctx.out.log(usage13);
|
|
3793
4567
|
return;
|
|
3794
4568
|
}
|
|
3795
4569
|
const cwd = values2.cwd ?? process.cwd();
|
|
@@ -3807,7 +4581,7 @@ async function run12(ctx) {
|
|
|
3807
4581
|
}
|
|
3808
4582
|
if (ctx.out.interactive && !skipConfirm) {
|
|
3809
4583
|
const proceed = bail(
|
|
3810
|
-
await
|
|
4584
|
+
await confirm3({
|
|
3811
4585
|
message: `Set up local infra in ${color.cyan(cwd)}? (docker compose up, .env, db:migrate)`
|
|
3812
4586
|
})
|
|
3813
4587
|
);
|
|
@@ -3904,16 +4678,16 @@ async function run12(ctx) {
|
|
|
3904
4678
|
var setupCommand = {
|
|
3905
4679
|
name: "setup",
|
|
3906
4680
|
summary: "Local onboarding: docker compose up, gen secret, db:migrate",
|
|
3907
|
-
usage:
|
|
3908
|
-
run:
|
|
4681
|
+
usage: usage13,
|
|
4682
|
+
run: run13
|
|
3909
4683
|
};
|
|
3910
4684
|
|
|
3911
4685
|
// src/commands/skills.ts
|
|
3912
4686
|
import { existsSync as existsSync8 } from "fs";
|
|
3913
4687
|
import { join as join8 } from "path";
|
|
3914
|
-
import { parseArgs as
|
|
4688
|
+
import { parseArgs as parseArgs15 } from "util";
|
|
3915
4689
|
import { multiselect } from "@clack/prompts";
|
|
3916
|
-
var
|
|
4690
|
+
var usage14 = `hogsend skills <subcommand> [options]
|
|
3917
4691
|
|
|
3918
4692
|
Manage the Claude Code skills bundled with @hogsend/cli. Bundled skills teach
|
|
3919
4693
|
agents how to drive the hogsend CLI; \`add\` copies them into your project's
|
|
@@ -3974,7 +4748,7 @@ function runList3(ctx) {
|
|
|
3974
4748
|
);
|
|
3975
4749
|
}
|
|
3976
4750
|
async function runAdd2(ctx, argv) {
|
|
3977
|
-
const { values: values2, positionals } =
|
|
4751
|
+
const { values: values2, positionals } = parseArgs15({
|
|
3978
4752
|
args: argv,
|
|
3979
4753
|
allowPositionals: true,
|
|
3980
4754
|
options: {
|
|
@@ -3984,7 +4758,7 @@ async function runAdd2(ctx, argv) {
|
|
|
3984
4758
|
}
|
|
3985
4759
|
});
|
|
3986
4760
|
if (values2.help) {
|
|
3987
|
-
ctx.out.log(
|
|
4761
|
+
ctx.out.log(usage14);
|
|
3988
4762
|
return;
|
|
3989
4763
|
}
|
|
3990
4764
|
const cwd = process.cwd();
|
|
@@ -4052,7 +4826,7 @@ async function runAdd2(ctx, argv) {
|
|
|
4052
4826
|
`Installed ${installedCount} skill${installedCount === 1 ? "" : "s"}` + (skippedCount > 0 ? `, skipped ${skippedCount}.` : ".")
|
|
4053
4827
|
);
|
|
4054
4828
|
}
|
|
4055
|
-
async function
|
|
4829
|
+
async function run14(ctx) {
|
|
4056
4830
|
const sub = ctx.argv[0];
|
|
4057
4831
|
switch (sub) {
|
|
4058
4832
|
case "list":
|
|
@@ -4064,7 +4838,7 @@ async function run13(ctx) {
|
|
|
4064
4838
|
case void 0:
|
|
4065
4839
|
case "-h":
|
|
4066
4840
|
case "--help":
|
|
4067
|
-
ctx.out.log(
|
|
4841
|
+
ctx.out.log(usage14);
|
|
4068
4842
|
return;
|
|
4069
4843
|
default:
|
|
4070
4844
|
ctx.out.fail(
|
|
@@ -4075,13 +4849,13 @@ async function run13(ctx) {
|
|
|
4075
4849
|
var skillsCommand = {
|
|
4076
4850
|
name: "skills",
|
|
4077
4851
|
summary: "List + install bundled Claude Code skills into .claude/skills",
|
|
4078
|
-
usage:
|
|
4079
|
-
run:
|
|
4852
|
+
usage: usage14,
|
|
4853
|
+
run: run14
|
|
4080
4854
|
};
|
|
4081
4855
|
|
|
4082
4856
|
// src/commands/stats.ts
|
|
4083
|
-
import { parseArgs as
|
|
4084
|
-
var
|
|
4857
|
+
import { parseArgs as parseArgs16 } from "util";
|
|
4858
|
+
var usage15 = `hogsend stats [--json]
|
|
4085
4859
|
|
|
4086
4860
|
Show system-wide overview metrics from a running Hogsend instance.
|
|
4087
4861
|
Wraps GET /v1/admin/metrics/overview.
|
|
@@ -4103,8 +4877,8 @@ Options:
|
|
|
4103
4877
|
function pct(rate) {
|
|
4104
4878
|
return `${(rate * 100).toFixed(2)}%`;
|
|
4105
4879
|
}
|
|
4106
|
-
async function
|
|
4107
|
-
const { values: values2 } =
|
|
4880
|
+
async function run15(ctx) {
|
|
4881
|
+
const { values: values2 } = parseArgs16({
|
|
4108
4882
|
args: ctx.argv,
|
|
4109
4883
|
allowPositionals: true,
|
|
4110
4884
|
options: {
|
|
@@ -4112,7 +4886,7 @@ async function run14(ctx) {
|
|
|
4112
4886
|
}
|
|
4113
4887
|
});
|
|
4114
4888
|
if (values2.help) {
|
|
4115
|
-
ctx.out.log(
|
|
4889
|
+
ctx.out.log(usage15);
|
|
4116
4890
|
return;
|
|
4117
4891
|
}
|
|
4118
4892
|
const metrics = await ctx.out.step(
|
|
@@ -4141,20 +4915,19 @@ async function run14(ctx) {
|
|
|
4141
4915
|
var statsCommand = {
|
|
4142
4916
|
name: "stats",
|
|
4143
4917
|
summary: "Show system-wide overview metrics",
|
|
4144
|
-
usage:
|
|
4145
|
-
run:
|
|
4918
|
+
usage: usage15,
|
|
4919
|
+
run: run15
|
|
4146
4920
|
};
|
|
4147
4921
|
|
|
4148
4922
|
// src/commands/studio.ts
|
|
4149
|
-
import { spawn as spawn2 } from "child_process";
|
|
4150
4923
|
import { createReadStream, existsSync as existsSync9, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
4151
|
-
import { createServer } from "http";
|
|
4924
|
+
import { createServer as createServer2 } from "http";
|
|
4152
4925
|
import { extname, join as join9, normalize, resolve as resolve2, sep as sep3 } from "path";
|
|
4153
4926
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4154
|
-
import { parseArgs as
|
|
4927
|
+
import { parseArgs as parseArgs18 } from "util";
|
|
4155
4928
|
|
|
4156
4929
|
// src/commands/studio-admin.ts
|
|
4157
|
-
import { parseArgs as
|
|
4930
|
+
import { parseArgs as parseArgs17 } from "util";
|
|
4158
4931
|
import { password as passwordPrompt, text as text2 } from "@clack/prompts";
|
|
4159
4932
|
|
|
4160
4933
|
// ../../node_modules/.pnpm/postgres@3.4.9/node_modules/postgres/src/index.js
|
|
@@ -13342,6 +14115,7 @@ __export(schema_exports, {
|
|
|
13342
14115
|
memberRelations: () => memberRelations,
|
|
13343
14116
|
organization: () => organization,
|
|
13344
14117
|
organizationRelations: () => organizationRelations,
|
|
14118
|
+
providerCredentials: () => providerCredentials,
|
|
13345
14119
|
session: () => session,
|
|
13346
14120
|
sessionRelations: () => sessionRelations,
|
|
13347
14121
|
trackedLinks: () => trackedLinks,
|
|
@@ -14044,6 +14818,30 @@ var linkClicks = pgTable(
|
|
|
14044
14818
|
]
|
|
14045
14819
|
);
|
|
14046
14820
|
|
|
14821
|
+
// ../db/src/schema/provider-credentials.ts
|
|
14822
|
+
var providerCredentials = pgTable(
|
|
14823
|
+
"provider_credentials",
|
|
14824
|
+
{
|
|
14825
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
14826
|
+
// e.g. "posthog" — matches an AnalyticsProvider meta.id by convention,
|
|
14827
|
+
// but deliberately NOT foreign-keyed: providers are code-defined.
|
|
14828
|
+
providerId: text("provider_id").notNull(),
|
|
14829
|
+
// "oauth" today; "api_key" is the anticipated future kind.
|
|
14830
|
+
kind: text("kind").notNull().default("oauth"),
|
|
14831
|
+
// Encrypted JSON: base64url(iv || ciphertext || gcmTag).
|
|
14832
|
+
payload: text("payload").notNull(),
|
|
14833
|
+
...timestamps
|
|
14834
|
+
},
|
|
14835
|
+
(table) => [
|
|
14836
|
+
// unique-per-kind: one oauth credential per provider; a future api_key
|
|
14837
|
+
// credential for the same provider coexists.
|
|
14838
|
+
uniqueIndex("provider_credentials_provider_kind_idx").on(
|
|
14839
|
+
table.providerId,
|
|
14840
|
+
table.kind
|
|
14841
|
+
)
|
|
14842
|
+
]
|
|
14843
|
+
);
|
|
14844
|
+
|
|
14047
14845
|
// ../db/src/schema/user-events.ts
|
|
14048
14846
|
var userEvents = pgTable(
|
|
14049
14847
|
"user_events",
|
|
@@ -14539,7 +15337,7 @@ Examples:
|
|
|
14539
15337
|
Security: passwords are written ONLY via better-auth (scrypt) \u2014 never raw SQL,
|
|
14540
15338
|
never plaintext at rest, never logged. Prefer the masked prompt over --password.`;
|
|
14541
15339
|
function parseAdminFlags(argv) {
|
|
14542
|
-
const { values: values2 } =
|
|
15340
|
+
const { values: values2 } = parseArgs17({
|
|
14543
15341
|
args: argv,
|
|
14544
15342
|
allowPositionals: true,
|
|
14545
15343
|
strict: false,
|
|
@@ -14740,7 +15538,7 @@ async function runStudioAdmin(ctx, argv) {
|
|
|
14740
15538
|
}
|
|
14741
15539
|
|
|
14742
15540
|
// src/commands/studio.ts
|
|
14743
|
-
var
|
|
15541
|
+
var usage16 = `hogsend studio [options]
|
|
14744
15542
|
|
|
14745
15543
|
Subcommands:
|
|
14746
15544
|
admin create | reset | list Shell-gated Studio admin recovery (DB + secret).
|
|
@@ -14818,24 +15616,12 @@ function indexHtml(distPath, baseUrl) {
|
|
|
14818
15616
|
}
|
|
14819
15617
|
return `${inject}${raw}`;
|
|
14820
15618
|
}
|
|
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) {
|
|
15619
|
+
async function run16(ctx) {
|
|
14834
15620
|
if (ctx.argv[0] === "admin") {
|
|
14835
15621
|
await runStudioAdmin(ctx, ctx.argv.slice(1));
|
|
14836
15622
|
return;
|
|
14837
15623
|
}
|
|
14838
|
-
const { values: values2, positionals } =
|
|
15624
|
+
const { values: values2, positionals } = parseArgs18({
|
|
14839
15625
|
args: ctx.argv,
|
|
14840
15626
|
allowPositionals: true,
|
|
14841
15627
|
strict: false,
|
|
@@ -14848,7 +15634,7 @@ async function run15(ctx) {
|
|
|
14848
15634
|
}
|
|
14849
15635
|
});
|
|
14850
15636
|
if (values2.help) {
|
|
14851
|
-
ctx.out.log(
|
|
15637
|
+
ctx.out.log(usage16);
|
|
14852
15638
|
return;
|
|
14853
15639
|
}
|
|
14854
15640
|
const port = Number(values2.port ?? "3333");
|
|
@@ -14866,7 +15652,7 @@ async function run15(ctx) {
|
|
|
14866
15652
|
}
|
|
14867
15653
|
const cleanBase = baseUrl ? baseUrl.replace(/\/+$/, "") : void 0;
|
|
14868
15654
|
const index2 = indexHtml(distPath, cleanBase);
|
|
14869
|
-
const server =
|
|
15655
|
+
const server = createServer2((req, res) => {
|
|
14870
15656
|
const urlPath = decodeURIComponent((req.url ?? "/").split("?")[0] ?? "/");
|
|
14871
15657
|
const rel = urlPath.replace(/^\/studio/, "");
|
|
14872
15658
|
if (rel === "" || rel === "/") {
|
|
@@ -14934,17 +15720,17 @@ async function run15(ctx) {
|
|
|
14934
15720
|
var studioCommand = {
|
|
14935
15721
|
name: "studio",
|
|
14936
15722
|
summary: "Serve the bundled Hogsend Studio admin SPA locally",
|
|
14937
|
-
usage:
|
|
14938
|
-
run:
|
|
15723
|
+
usage: usage16,
|
|
15724
|
+
run: run16
|
|
14939
15725
|
};
|
|
14940
15726
|
|
|
14941
15727
|
// src/commands/upgrade.ts
|
|
14942
15728
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
14943
15729
|
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
14944
15730
|
import { join as join10 } from "path";
|
|
14945
|
-
import { parseArgs as
|
|
14946
|
-
import { confirm as
|
|
14947
|
-
var
|
|
15731
|
+
import { parseArgs as parseArgs19 } from "util";
|
|
15732
|
+
import { confirm as confirm4 } from "@clack/prompts";
|
|
15733
|
+
var usage17 = `hogsend upgrade [--cwd <dir>] [--pm <pnpm|npm|yarn|bun>] [options]
|
|
14948
15734
|
|
|
14949
15735
|
Upgrade a scaffolded Hogsend app in one step:
|
|
14950
15736
|
1. bump every @hogsend/* dependency to latest (or --to <version>), then
|
|
@@ -14980,8 +15766,8 @@ function hogsendDeps(cwd) {
|
|
|
14980
15766
|
function addArgs(pm, specs) {
|
|
14981
15767
|
return [pm === "npm" ? "install" : "add", ...specs];
|
|
14982
15768
|
}
|
|
14983
|
-
async function
|
|
14984
|
-
const { values: values2 } =
|
|
15769
|
+
async function run17(ctx) {
|
|
15770
|
+
const { values: values2 } = parseArgs19({
|
|
14985
15771
|
args: ctx.argv,
|
|
14986
15772
|
allowPositionals: true,
|
|
14987
15773
|
options: {
|
|
@@ -14995,7 +15781,7 @@ async function run16(ctx) {
|
|
|
14995
15781
|
}
|
|
14996
15782
|
});
|
|
14997
15783
|
if (values2.help) {
|
|
14998
|
-
ctx.out.log(
|
|
15784
|
+
ctx.out.log(usage17);
|
|
14999
15785
|
return;
|
|
15000
15786
|
}
|
|
15001
15787
|
if (values2["deps-only"] && values2["skills-only"]) {
|
|
@@ -15039,7 +15825,7 @@ async function run16(ctx) {
|
|
|
15039
15825
|
doSkills ? "refresh .claude/skills" : null
|
|
15040
15826
|
].filter(Boolean).join(" + ");
|
|
15041
15827
|
const proceed = bail(
|
|
15042
|
-
await
|
|
15828
|
+
await confirm4({ message: `Upgrade ${color.cyan(cwd)}: ${plan}?` })
|
|
15043
15829
|
);
|
|
15044
15830
|
if (!proceed) {
|
|
15045
15831
|
ctx.out.outro(color.dim("Nothing changed."));
|
|
@@ -15120,12 +15906,12 @@ async function run16(ctx) {
|
|
|
15120
15906
|
var upgradeCommand = {
|
|
15121
15907
|
name: "upgrade",
|
|
15122
15908
|
summary: "Bump @hogsend/* deps to latest + refresh vendored skills",
|
|
15123
|
-
usage:
|
|
15124
|
-
run:
|
|
15909
|
+
usage: usage17,
|
|
15910
|
+
run: run17
|
|
15125
15911
|
};
|
|
15126
15912
|
|
|
15127
15913
|
// src/commands/webhooks.ts
|
|
15128
|
-
import { parseArgs as
|
|
15914
|
+
import { parseArgs as parseArgs20 } from "util";
|
|
15129
15915
|
var WEBHOOK_EVENT_TYPES = [
|
|
15130
15916
|
"contact.created",
|
|
15131
15917
|
"contact.updated",
|
|
@@ -15142,7 +15928,7 @@ var WEBHOOK_EVENT_TYPES = [
|
|
|
15142
15928
|
"bucket.entered",
|
|
15143
15929
|
"bucket.left"
|
|
15144
15930
|
];
|
|
15145
|
-
var
|
|
15931
|
+
var usage18 = `hogsend webhooks <subcommand> [options]
|
|
15146
15932
|
|
|
15147
15933
|
Manage outbound webhook endpoints \u2014 the Svix-style signed event stream Hogsend
|
|
15148
15934
|
emits to your URLs. Wraps the admin routes (/v1/admin/webhooks), so this command
|
|
@@ -15232,7 +16018,7 @@ ${color.bold(secret)}`,
|
|
|
15232
16018
|
);
|
|
15233
16019
|
}
|
|
15234
16020
|
async function runList5(ctx, argv) {
|
|
15235
|
-
const { values: values2 } =
|
|
16021
|
+
const { values: values2 } = parseArgs20({
|
|
15236
16022
|
args: argv,
|
|
15237
16023
|
allowPositionals: true,
|
|
15238
16024
|
options: {
|
|
@@ -15243,7 +16029,7 @@ async function runList5(ctx, argv) {
|
|
|
15243
16029
|
}
|
|
15244
16030
|
});
|
|
15245
16031
|
if (values2.help) {
|
|
15246
|
-
ctx.out.log(
|
|
16032
|
+
ctx.out.log(usage18);
|
|
15247
16033
|
return;
|
|
15248
16034
|
}
|
|
15249
16035
|
const query = {
|
|
@@ -15292,13 +16078,13 @@ function renderEndpoint(ctx, ep, title) {
|
|
|
15292
16078
|
);
|
|
15293
16079
|
}
|
|
15294
16080
|
async function runGet3(ctx, argv) {
|
|
15295
|
-
const { values: values2, positionals } =
|
|
16081
|
+
const { values: values2, positionals } = parseArgs20({
|
|
15296
16082
|
args: argv,
|
|
15297
16083
|
allowPositionals: true,
|
|
15298
16084
|
options: { help: { type: "boolean", short: "h", default: false } }
|
|
15299
16085
|
});
|
|
15300
16086
|
if (values2.help) {
|
|
15301
|
-
ctx.out.log(
|
|
16087
|
+
ctx.out.log(usage18);
|
|
15302
16088
|
return;
|
|
15303
16089
|
}
|
|
15304
16090
|
const id = positionals[0];
|
|
@@ -15323,7 +16109,7 @@ async function runGet3(ctx, argv) {
|
|
|
15323
16109
|
ctx.out.outro(`${res.url} \u2192 ${res.status}`);
|
|
15324
16110
|
}
|
|
15325
16111
|
async function runCreate2(ctx, argv) {
|
|
15326
|
-
const { values: values2 } =
|
|
16112
|
+
const { values: values2 } = parseArgs20({
|
|
15327
16113
|
args: argv,
|
|
15328
16114
|
allowPositionals: true,
|
|
15329
16115
|
options: {
|
|
@@ -15336,7 +16122,7 @@ async function runCreate2(ctx, argv) {
|
|
|
15336
16122
|
}
|
|
15337
16123
|
});
|
|
15338
16124
|
if (values2.help) {
|
|
15339
|
-
ctx.out.log(
|
|
16125
|
+
ctx.out.log(usage18);
|
|
15340
16126
|
return;
|
|
15341
16127
|
}
|
|
15342
16128
|
const url = values2.url;
|
|
@@ -15370,7 +16156,7 @@ async function runCreate2(ctx, argv) {
|
|
|
15370
16156
|
ctx.out.outro(`${color.green("Created")} ${res.id} \u2192 ${res.url}`);
|
|
15371
16157
|
}
|
|
15372
16158
|
async function runUpdate(ctx, argv) {
|
|
15373
|
-
const { values: values2, positionals } =
|
|
16159
|
+
const { values: values2, positionals } = parseArgs20({
|
|
15374
16160
|
args: argv,
|
|
15375
16161
|
allowPositionals: true,
|
|
15376
16162
|
options: {
|
|
@@ -15384,7 +16170,7 @@ async function runUpdate(ctx, argv) {
|
|
|
15384
16170
|
}
|
|
15385
16171
|
});
|
|
15386
16172
|
if (values2.help) {
|
|
15387
|
-
ctx.out.log(
|
|
16173
|
+
ctx.out.log(usage18);
|
|
15388
16174
|
return;
|
|
15389
16175
|
}
|
|
15390
16176
|
const id = positionals[0];
|
|
@@ -15425,13 +16211,13 @@ async function runUpdate(ctx, argv) {
|
|
|
15425
16211
|
ctx.out.outro(`${color.green("Updated")} ${res.id} \u2192 ${res.status}`);
|
|
15426
16212
|
}
|
|
15427
16213
|
async function runDelete(ctx, argv) {
|
|
15428
|
-
const { values: values2, positionals } =
|
|
16214
|
+
const { values: values2, positionals } = parseArgs20({
|
|
15429
16215
|
args: argv,
|
|
15430
16216
|
allowPositionals: true,
|
|
15431
16217
|
options: { help: { type: "boolean", short: "h", default: false } }
|
|
15432
16218
|
});
|
|
15433
16219
|
if (values2.help) {
|
|
15434
|
-
ctx.out.log(
|
|
16220
|
+
ctx.out.log(usage18);
|
|
15435
16221
|
return;
|
|
15436
16222
|
}
|
|
15437
16223
|
const id = positionals[0];
|
|
@@ -15455,13 +16241,13 @@ async function runDelete(ctx, argv) {
|
|
|
15455
16241
|
ctx.out.outro(`${color.green("Deleted")} ${id}`);
|
|
15456
16242
|
}
|
|
15457
16243
|
async function runRotate(ctx, argv) {
|
|
15458
|
-
const { values: values2, positionals } =
|
|
16244
|
+
const { values: values2, positionals } = parseArgs20({
|
|
15459
16245
|
args: argv,
|
|
15460
16246
|
allowPositionals: true,
|
|
15461
16247
|
options: { help: { type: "boolean", short: "h", default: false } }
|
|
15462
16248
|
});
|
|
15463
16249
|
if (values2.help) {
|
|
15464
|
-
ctx.out.log(
|
|
16250
|
+
ctx.out.log(usage18);
|
|
15465
16251
|
return;
|
|
15466
16252
|
}
|
|
15467
16253
|
const id = positionals[0];
|
|
@@ -15490,13 +16276,13 @@ async function runRotate(ctx, argv) {
|
|
|
15490
16276
|
);
|
|
15491
16277
|
}
|
|
15492
16278
|
async function runTest(ctx, argv) {
|
|
15493
|
-
const { values: values2, positionals } =
|
|
16279
|
+
const { values: values2, positionals } = parseArgs20({
|
|
15494
16280
|
args: argv,
|
|
15495
16281
|
allowPositionals: true,
|
|
15496
16282
|
options: { help: { type: "boolean", short: "h", default: false } }
|
|
15497
16283
|
});
|
|
15498
16284
|
if (values2.help) {
|
|
15499
|
-
ctx.out.log(
|
|
16285
|
+
ctx.out.log(usage18);
|
|
15500
16286
|
return;
|
|
15501
16287
|
}
|
|
15502
16288
|
const id = positionals[0];
|
|
@@ -15522,7 +16308,7 @@ async function runTest(ctx, argv) {
|
|
|
15522
16308
|
`${color.green("Enqueued")} a ${color.cyan(res.eventType)} delivery to ${id}.`
|
|
15523
16309
|
);
|
|
15524
16310
|
}
|
|
15525
|
-
async function
|
|
16311
|
+
async function run18(ctx) {
|
|
15526
16312
|
const sub = ctx.argv[0];
|
|
15527
16313
|
switch (sub) {
|
|
15528
16314
|
case "list":
|
|
@@ -15553,8 +16339,8 @@ async function run17(ctx) {
|
|
|
15553
16339
|
var webhooksCommand = {
|
|
15554
16340
|
name: "webhooks",
|
|
15555
16341
|
summary: "Manage outbound webhook endpoints (create, rotate, test)",
|
|
15556
|
-
usage:
|
|
15557
|
-
run:
|
|
16342
|
+
usage: usage18,
|
|
16343
|
+
run: run18
|
|
15558
16344
|
};
|
|
15559
16345
|
|
|
15560
16346
|
// src/commands/index.ts
|
|
@@ -15568,6 +16354,7 @@ var commands = [
|
|
|
15568
16354
|
campaignsCommand,
|
|
15569
16355
|
webhooksCommand,
|
|
15570
16356
|
domainCommand,
|
|
16357
|
+
connectCommand,
|
|
15571
16358
|
hatchetCommand,
|
|
15572
16359
|
studioCommand,
|
|
15573
16360
|
devCommand,
|