@hogsend/cli 0.12.2 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -1012,7 +1012,8 @@ function resolveConfig(flags, cwd = process.cwd()) {
1012
1012
  return {
1013
1013
  baseUrl: baseUrlRaw.replace(/\/+$/, ""),
1014
1014
  adminKey: adminKey && adminKey.length > 0 ? adminKey : void 0,
1015
- dataKey: dataKey && dataKey.length > 0 ? dataKey : void 0
1015
+ dataKey: dataKey && dataKey.length > 0 ? dataKey : void 0,
1016
+ urlExplicit: flags.url !== void 0
1016
1017
  };
1017
1018
  }
1018
1019
 
@@ -3118,9 +3119,310 @@ var emailsCommand = {
3118
3119
  run: run8
3119
3120
  };
3120
3121
 
3121
- // src/commands/journeys.ts
3122
+ // src/commands/hatchet.ts
3122
3123
  import { parseArgs as parseArgs10 } from "util";
3123
- var usage9 = `hogsend journeys <subcommand> [options]
3124
+
3125
+ // src/lib/hatchet-token.ts
3126
+ var SLUG_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
3127
+ var HatchetTokenError = class extends Error {
3128
+ status;
3129
+ constructor(message, status) {
3130
+ super(message);
3131
+ this.name = "HatchetTokenError";
3132
+ this.status = status;
3133
+ }
3134
+ };
3135
+ function extractApiError(body) {
3136
+ if (typeof body !== "object" || body === null) return void 0;
3137
+ const errors = body.errors;
3138
+ if (!Array.isArray(errors)) return void 0;
3139
+ const descriptions = errors.map(
3140
+ (e) => typeof e === "object" && e !== null ? e.description : void 0
3141
+ ).filter((d) => typeof d === "string");
3142
+ return descriptions.length > 0 ? descriptions.join("; ") : void 0;
3143
+ }
3144
+ async function readBody(res) {
3145
+ try {
3146
+ return await res.json();
3147
+ } catch {
3148
+ return void 0;
3149
+ }
3150
+ }
3151
+ function cookieHeaderFrom(res) {
3152
+ const setCookies = res.headers.getSetCookie();
3153
+ return setCookies.map((c) => c.split(";", 1)[0] ?? "").filter((c) => c.includes("=")).join("; ");
3154
+ }
3155
+ async function mintHatchetToken(opts) {
3156
+ const fetchImpl = opts.fetchImpl ?? fetch;
3157
+ const progress = opts.onProgress ?? (() => {
3158
+ });
3159
+ const base = opts.url.replace(/\/+$/, "");
3160
+ if (!/^https?:\/\//.test(base)) {
3161
+ throw new HatchetTokenError(
3162
+ `invalid --url "${opts.url}" (expected http(s)://...)`
3163
+ );
3164
+ }
3165
+ const tenantSlug = opts.tenantSlug ?? "default";
3166
+ if (!SLUG_RE.test(tenantSlug)) {
3167
+ throw new HatchetTokenError(
3168
+ `invalid tenant slug "${tenantSlug}" (lowercase letters, digits, dashes)`
3169
+ );
3170
+ }
3171
+ const tokenName = opts.tokenName ?? "hogsend";
3172
+ const postJson = (path, body, cookie2) => fetchImpl(`${base}${path}`, {
3173
+ method: "POST",
3174
+ headers: {
3175
+ "content-type": "application/json",
3176
+ ...cookie2 ? { cookie: cookie2 } : {}
3177
+ },
3178
+ body: JSON.stringify(body)
3179
+ });
3180
+ let registered = false;
3181
+ progress(`registering ${opts.email} ...`);
3182
+ const registerRes = await postJson("/api/v1/users/register", {
3183
+ name: opts.email.split("@")[0] || opts.email,
3184
+ email: opts.email,
3185
+ password: opts.password
3186
+ });
3187
+ if (registerRes.ok) {
3188
+ registered = true;
3189
+ await readBody(registerRes);
3190
+ } else if (registerRes.status >= 500) {
3191
+ const msg = extractApiError(await readBody(registerRes));
3192
+ throw new HatchetTokenError(
3193
+ `Hatchet register failed (${registerRes.status})${msg ? `: ${msg}` : ""}`,
3194
+ registerRes.status
3195
+ );
3196
+ } else {
3197
+ await readBody(registerRes);
3198
+ progress("registration unavailable or account exists \u2014 logging in ...");
3199
+ }
3200
+ const loginRes = await postJson("/api/v1/users/login", {
3201
+ email: opts.email,
3202
+ password: opts.password
3203
+ });
3204
+ if (!loginRes.ok) {
3205
+ const msg = extractApiError(await readBody(loginRes));
3206
+ throw new HatchetTokenError(
3207
+ `Hatchet login failed (${loginRes.status})${msg ? `: ${msg}` : ""} \u2014 check --email/--password (on a locked-down hatchet-lite these are its ADMIN_EMAIL/ADMIN_PASSWORD)`,
3208
+ loginRes.status
3209
+ );
3210
+ }
3211
+ await readBody(loginRes);
3212
+ const cookie = cookieHeaderFrom(loginRes);
3213
+ if (!cookie) {
3214
+ throw new HatchetTokenError(
3215
+ "Hatchet login succeeded but returned no session cookie"
3216
+ );
3217
+ }
3218
+ progress(`resolving tenant "${tenantSlug}" ...`);
3219
+ const membershipsRes = await fetchImpl(`${base}/api/v1/users/memberships`, {
3220
+ headers: { cookie }
3221
+ });
3222
+ if (!membershipsRes.ok) {
3223
+ const msg = extractApiError(await readBody(membershipsRes));
3224
+ throw new HatchetTokenError(
3225
+ `failed to list tenant memberships (${membershipsRes.status})${msg ? `: ${msg}` : ""}`,
3226
+ membershipsRes.status
3227
+ );
3228
+ }
3229
+ const memberships = await readBody(membershipsRes);
3230
+ let tenant;
3231
+ for (const row of memberships?.rows ?? []) {
3232
+ const id = row.tenant?.metadata?.id;
3233
+ if (id && row.tenant?.slug === tenantSlug) {
3234
+ tenant = { id, slug: tenantSlug };
3235
+ break;
3236
+ }
3237
+ }
3238
+ let createdTenant = false;
3239
+ if (!tenant) {
3240
+ progress(`creating tenant "${tenantSlug}" ...`);
3241
+ const createRes = await postJson(
3242
+ "/api/v1/tenants",
3243
+ { name: tenantSlug, slug: tenantSlug, engineVersion: "V1" },
3244
+ cookie
3245
+ );
3246
+ const createBody = await readBody(createRes);
3247
+ if (!createRes.ok) {
3248
+ const msg = extractApiError(createBody);
3249
+ throw new HatchetTokenError(
3250
+ `failed to create tenant "${tenantSlug}" (${createRes.status})${msg ? `: ${msg}` : ""}`,
3251
+ createRes.status
3252
+ );
3253
+ }
3254
+ const created = createBody;
3255
+ const id = created?.metadata?.id;
3256
+ if (!id) {
3257
+ throw new HatchetTokenError(
3258
+ "tenant create succeeded but the response had no id"
3259
+ );
3260
+ }
3261
+ tenant = { id, slug: tenantSlug };
3262
+ createdTenant = true;
3263
+ }
3264
+ progress(`minting API token "${tokenName}" ...`);
3265
+ const tokenRes = await postJson(
3266
+ `/api/v1/tenants/${tenant.id}/api-tokens`,
3267
+ { name: tokenName },
3268
+ cookie
3269
+ );
3270
+ const tokenBody = await readBody(tokenRes);
3271
+ if (!tokenRes.ok) {
3272
+ const msg = extractApiError(tokenBody);
3273
+ throw new HatchetTokenError(
3274
+ `failed to mint API token (${tokenRes.status})${msg ? `: ${msg}` : ""}`,
3275
+ tokenRes.status
3276
+ );
3277
+ }
3278
+ const token = tokenBody?.token;
3279
+ if (typeof token !== "string" || token.length === 0) {
3280
+ throw new HatchetTokenError(
3281
+ "token create succeeded but the response had no token"
3282
+ );
3283
+ }
3284
+ return {
3285
+ token,
3286
+ tenantId: tenant.id,
3287
+ tenantSlug: tenant.slug,
3288
+ createdTenant,
3289
+ registered
3290
+ };
3291
+ }
3292
+
3293
+ // src/commands/hatchet.ts
3294
+ var usage9 = `hogsend hatchet token [options]
3295
+
3296
+ Mint a Hatchet API token (HATCHET_CLIENT_TOKEN) headlessly against a
3297
+ hatchet-lite instance. Registers the account if the instance still allows
3298
+ signups, otherwise logs in (e.g. with the seeded ADMIN_EMAIL/ADMIN_PASSWORD on
3299
+ a locked-down deployment), ensures the tenant exists, and creates the token.
3300
+
3301
+ On success the token is the ONLY thing printed to stdout \u2014 pipe it straight
3302
+ into your platform's variable store. Progress and errors go to stderr.
3303
+
3304
+ Options:
3305
+ --url <hatchet-url> Hatchet base URL (or HATCHET_URL), e.g. the
3306
+ hatchet-lite service's public https URL. Required \u2014
3307
+ this command never falls back to HOGSEND_API_URL or
3308
+ the localhost default (those target your Hogsend
3309
+ API, not Hatchet). NOTE: for this command the global
3310
+ --url targets HATCHET, not your Hogsend API.
3311
+ --email <e> Account email (or HATCHET_ADMIN_EMAIL).
3312
+ --password <p> Account password (or HATCHET_ADMIN_PASSWORD). Prefer
3313
+ the env var \u2014 flags can leak into shell history.
3314
+ --tenant <slug> Tenant slug (default "default", the seeded tenant).
3315
+ Created (engine V1) if it doesn't exist yet.
3316
+ --token-name <n> Display name for the minted token (default "hogsend").
3317
+ --json Emit { token, tenantId, ... } as one JSON document.
3318
+ -h, --help Show this help.
3319
+
3320
+ Examples:
3321
+ hogsend hatchet token --url https://hatchet-lite-production.up.railway.app \\
3322
+ --email admin@example.com --password 'Admin123!!'
3323
+ HATCHET_ADMIN_PASSWORD=... hogsend hatchet token --url https://... --email admin@acme.com
3324
+ railway variables --service hogsend-worker \\
3325
+ --set "HATCHET_CLIENT_TOKEN=$(hogsend hatchet token --url ... --email ... --password ...)"
3326
+
3327
+ Lockdown note: hatchet-lite ships with OPEN registration \u2014 anyone who finds the
3328
+ public URL can create an account. On a public deployment set
3329
+ SERVER_ALLOW_SIGNUP=false (plus a real ADMIN_EMAIL/ADMIN_PASSWORD, which
3330
+ hatchet-lite seeds at boot); this command then logs in with those credentials.`;
3331
+ function parseTokenFlags(argv) {
3332
+ const { values: values2 } = parseArgs10({
3333
+ args: argv,
3334
+ allowPositionals: true,
3335
+ strict: false,
3336
+ options: {
3337
+ url: { type: "string" },
3338
+ email: { type: "string" },
3339
+ password: { type: "string" },
3340
+ tenant: { type: "string" },
3341
+ "token-name": { type: "string" }
3342
+ }
3343
+ });
3344
+ const str = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
3345
+ return {
3346
+ url: str(values2.url),
3347
+ email: str(values2.email),
3348
+ password: str(values2.password),
3349
+ tenant: str(values2.tenant),
3350
+ tokenName: str(values2["token-name"])
3351
+ };
3352
+ }
3353
+ function failToStderr(ctx, message) {
3354
+ if (ctx.json) {
3355
+ ctx.out.fail(message);
3356
+ }
3357
+ process.stderr.write(`${color.red("error")} ${message}
3358
+ `);
3359
+ process.exit(1);
3360
+ }
3361
+ async function runToken(ctx, argv) {
3362
+ const flags = parseTokenFlags(argv);
3363
+ const url = flags.url ?? (ctx.cfg.urlExplicit ? ctx.cfg.baseUrl : void 0) ?? process.env.HATCHET_URL;
3364
+ const email = flags.email ?? process.env.HATCHET_ADMIN_EMAIL;
3365
+ const password = flags.password ?? process.env.HATCHET_ADMIN_PASSWORD;
3366
+ const missing = [];
3367
+ if (!url) missing.push("--url (or HATCHET_URL)");
3368
+ if (!email) missing.push("--email (or HATCHET_ADMIN_EMAIL)");
3369
+ if (!password) missing.push("--password (or HATCHET_ADMIN_PASSWORD)");
3370
+ if (missing.length > 0 || !url || !email || !password) {
3371
+ failToStderr(ctx, `missing ${missing.join(", ")}`);
3372
+ }
3373
+ const onProgress = ctx.json ? void 0 : (msg) => process.stderr.write(`${color.dim(msg)}
3374
+ `);
3375
+ onProgress?.(`hatchet: ${url}`);
3376
+ try {
3377
+ const result = await mintHatchetToken({
3378
+ url,
3379
+ email,
3380
+ password,
3381
+ tenantSlug: flags.tenant,
3382
+ tokenName: flags.tokenName,
3383
+ onProgress
3384
+ });
3385
+ if (ctx.json) {
3386
+ ctx.out.json(result);
3387
+ return;
3388
+ }
3389
+ process.stdout.write(`${result.token}
3390
+ `);
3391
+ } catch (err) {
3392
+ if (err instanceof HatchetTokenError) {
3393
+ failToStderr(ctx, err.message);
3394
+ }
3395
+ throw err;
3396
+ }
3397
+ }
3398
+ async function run9(ctx) {
3399
+ const sub = ctx.argv[0];
3400
+ const rest = ctx.argv.slice(1);
3401
+ if (!sub || sub === "--help" || sub === "-h" || sub === "help") {
3402
+ ctx.out.log(usage9);
3403
+ return;
3404
+ }
3405
+ if (rest.includes("-h") || rest.includes("--help")) {
3406
+ ctx.out.log(usage9);
3407
+ return;
3408
+ }
3409
+ switch (sub) {
3410
+ case "token":
3411
+ return runToken(ctx, rest);
3412
+ default:
3413
+ failToStderr(ctx, `unknown subcommand "${sub}" \u2014 expected token`);
3414
+ }
3415
+ }
3416
+ var hatchetCommand = {
3417
+ name: "hatchet",
3418
+ summary: "Hatchet helpers \u2014 mint a HATCHET_CLIENT_TOKEN headlessly",
3419
+ usage: usage9,
3420
+ run: run9
3421
+ };
3422
+
3423
+ // src/commands/journeys.ts
3424
+ import { parseArgs as parseArgs11 } from "util";
3425
+ var usage10 = `hogsend journeys <subcommand> [options]
3124
3426
 
3125
3427
  Inspect and toggle journeys via the admin API (/v1/admin/journeys).
3126
3428
 
@@ -3149,7 +3451,7 @@ function statusColor3(enabled) {
3149
3451
  return enabled ? color.green("enabled") : color.yellow("disabled");
3150
3452
  }
3151
3453
  async function runList2(ctx) {
3152
- const { values: values2 } = parseArgs10({
3454
+ const { values: values2 } = parseArgs11({
3153
3455
  args: ctx.argv,
3154
3456
  allowPositionals: true,
3155
3457
  options: {
@@ -3160,7 +3462,7 @@ async function runList2(ctx) {
3160
3462
  }
3161
3463
  });
3162
3464
  if (values2.help) {
3163
- ctx.out.log(usage9);
3465
+ ctx.out.log(usage10);
3164
3466
  return;
3165
3467
  }
3166
3468
  if (values2.enabled !== void 0 && !["true", "false"].includes(values2.enabled)) {
@@ -3296,7 +3598,7 @@ async function runToggle(ctx, id, enabled) {
3296
3598
  );
3297
3599
  ctx.out.outro(`${j.id} is now ${statusColor3(j.enabled)}.`);
3298
3600
  }
3299
- async function run9(ctx) {
3601
+ async function run10(ctx) {
3300
3602
  const sub = ctx.argv[0];
3301
3603
  const rest = ctx.argv.slice(1);
3302
3604
  const subCtx = { ...ctx, argv: rest };
@@ -3308,7 +3610,7 @@ async function run9(ctx) {
3308
3610
  case "get": {
3309
3611
  const id = rest.find((a) => !a.startsWith("-"));
3310
3612
  if (rest.includes("--help") || rest.includes("-h")) {
3311
- ctx.out.log(usage9);
3613
+ ctx.out.log(usage10);
3312
3614
  return;
3313
3615
  }
3314
3616
  await runGet2(subCtx, id);
@@ -3316,7 +3618,7 @@ async function run9(ctx) {
3316
3618
  }
3317
3619
  case "enable": {
3318
3620
  if (rest.includes("--help") || rest.includes("-h")) {
3319
- ctx.out.log(usage9);
3621
+ ctx.out.log(usage10);
3320
3622
  return;
3321
3623
  }
3322
3624
  await runToggle(
@@ -3328,7 +3630,7 @@ async function run9(ctx) {
3328
3630
  }
3329
3631
  case "disable": {
3330
3632
  if (rest.includes("--help") || rest.includes("-h")) {
3331
- ctx.out.log(usage9);
3633
+ ctx.out.log(usage10);
3332
3634
  return;
3333
3635
  }
3334
3636
  await runToggle(
@@ -3362,14 +3664,14 @@ async function run9(ctx) {
3362
3664
  var journeysCommand = {
3363
3665
  name: "journeys",
3364
3666
  summary: "List, inspect, enable, and disable journeys",
3365
- usage: usage9,
3366
- run: run9
3667
+ usage: usage10,
3668
+ run: run10
3367
3669
  };
3368
3670
 
3369
3671
  // src/commands/patch.ts
3370
3672
  import { spawnSync as spawnSync3 } from "child_process";
3371
- import { parseArgs as parseArgs11 } from "util";
3372
- var usage10 = `hogsend patch <package> [--cwd <dir>]
3673
+ import { parseArgs as parseArgs12 } from "util";
3674
+ var usage11 = `hogsend patch <package> [--cwd <dir>]
3373
3675
 
3374
3676
  Thin wrapper over pnpm's native patch flow. Runs \`pnpm patch <package>\`, which
3375
3677
  extracts the package into a temp dir and prints the path to edit. After editing,
@@ -3380,8 +3682,8 @@ This does NOT replace scripts/patch-check.sh (the patch re-apply contract).
3380
3682
  Options:
3381
3683
  --cwd <dir> Project root to run pnpm in (defaults to current directory).
3382
3684
  -h, --help Show this help.`;
3383
- async function run10(ctx) {
3384
- const { values: values2, positionals } = parseArgs11({
3685
+ async function run11(ctx) {
3686
+ const { values: values2, positionals } = parseArgs12({
3385
3687
  args: ctx.argv,
3386
3688
  allowPositionals: true,
3387
3689
  options: {
@@ -3390,7 +3692,7 @@ async function run10(ctx) {
3390
3692
  }
3391
3693
  });
3392
3694
  if (values2.help) {
3393
- ctx.out.log(usage10);
3695
+ ctx.out.log(usage11);
3394
3696
  return;
3395
3697
  }
3396
3698
  const pkg = positionals[0];
@@ -3430,16 +3732,16 @@ async function run10(ctx) {
3430
3732
  var patchCommand = {
3431
3733
  name: "patch",
3432
3734
  summary: "Patch a package via pnpm's native patch flow",
3433
- usage: usage10,
3434
- run: run10
3735
+ usage: usage11,
3736
+ run: run11
3435
3737
  };
3436
3738
 
3437
3739
  // src/commands/setup.ts
3438
3740
  import { existsSync as existsSync7 } from "fs";
3439
3741
  import { join as join7 } from "path";
3440
- import { parseArgs as parseArgs12 } from "util";
3742
+ import { parseArgs as parseArgs13 } from "util";
3441
3743
  import { confirm as confirm2 } from "@clack/prompts";
3442
- var usage11 = `hogsend setup [--cwd <dir>] [--yes] [--json]
3744
+ var usage12 = `hogsend setup [--cwd <dir>] [--yes] [--json]
3443
3745
 
3444
3746
  Interactive local onboarding for a scaffolded Hogsend app. Mirrors the
3445
3747
  create-hogsend "next steps":
@@ -3456,8 +3758,8 @@ Options:
3456
3758
  -h, --help Show this help.
3457
3759
 
3458
3760
  Run ${color.cyan("hogsend doctor")} afterwards to verify the instance is healthy.`;
3459
- async function run11(ctx) {
3460
- const { values: values2 } = parseArgs12({
3761
+ async function run12(ctx) {
3762
+ const { values: values2 } = parseArgs13({
3461
3763
  args: ctx.argv,
3462
3764
  allowPositionals: true,
3463
3765
  options: {
@@ -3467,7 +3769,7 @@ async function run11(ctx) {
3467
3769
  }
3468
3770
  });
3469
3771
  if (values2.help) {
3470
- ctx.out.log(usage11);
3772
+ ctx.out.log(usage12);
3471
3773
  return;
3472
3774
  }
3473
3775
  const cwd = values2.cwd ?? process.cwd();
@@ -3582,16 +3884,16 @@ async function run11(ctx) {
3582
3884
  var setupCommand = {
3583
3885
  name: "setup",
3584
3886
  summary: "Local onboarding: docker compose up, gen secret, db:migrate",
3585
- usage: usage11,
3586
- run: run11
3887
+ usage: usage12,
3888
+ run: run12
3587
3889
  };
3588
3890
 
3589
3891
  // src/commands/skills.ts
3590
3892
  import { existsSync as existsSync8 } from "fs";
3591
3893
  import { join as join8 } from "path";
3592
- import { parseArgs as parseArgs13 } from "util";
3894
+ import { parseArgs as parseArgs14 } from "util";
3593
3895
  import { multiselect } from "@clack/prompts";
3594
- var usage12 = `hogsend skills <subcommand> [options]
3896
+ var usage13 = `hogsend skills <subcommand> [options]
3595
3897
 
3596
3898
  Manage the Claude Code skills bundled with @hogsend/cli. Bundled skills teach
3597
3899
  agents how to drive the hogsend CLI; \`add\` copies them into your project's
@@ -3652,7 +3954,7 @@ function runList3(ctx) {
3652
3954
  );
3653
3955
  }
3654
3956
  async function runAdd2(ctx, argv) {
3655
- const { values: values2, positionals } = parseArgs13({
3957
+ const { values: values2, positionals } = parseArgs14({
3656
3958
  args: argv,
3657
3959
  allowPositionals: true,
3658
3960
  options: {
@@ -3662,7 +3964,7 @@ async function runAdd2(ctx, argv) {
3662
3964
  }
3663
3965
  });
3664
3966
  if (values2.help) {
3665
- ctx.out.log(usage12);
3967
+ ctx.out.log(usage13);
3666
3968
  return;
3667
3969
  }
3668
3970
  const cwd = process.cwd();
@@ -3730,7 +4032,7 @@ async function runAdd2(ctx, argv) {
3730
4032
  `Installed ${installedCount} skill${installedCount === 1 ? "" : "s"}` + (skippedCount > 0 ? `, skipped ${skippedCount}.` : ".")
3731
4033
  );
3732
4034
  }
3733
- async function run12(ctx) {
4035
+ async function run13(ctx) {
3734
4036
  const sub = ctx.argv[0];
3735
4037
  switch (sub) {
3736
4038
  case "list":
@@ -3742,7 +4044,7 @@ async function run12(ctx) {
3742
4044
  case void 0:
3743
4045
  case "-h":
3744
4046
  case "--help":
3745
- ctx.out.log(usage12);
4047
+ ctx.out.log(usage13);
3746
4048
  return;
3747
4049
  default:
3748
4050
  ctx.out.fail(
@@ -3753,13 +4055,13 @@ async function run12(ctx) {
3753
4055
  var skillsCommand = {
3754
4056
  name: "skills",
3755
4057
  summary: "List + install bundled Claude Code skills into .claude/skills",
3756
- usage: usage12,
3757
- run: run12
4058
+ usage: usage13,
4059
+ run: run13
3758
4060
  };
3759
4061
 
3760
4062
  // src/commands/stats.ts
3761
- import { parseArgs as parseArgs14 } from "util";
3762
- var usage13 = `hogsend stats [--json]
4063
+ import { parseArgs as parseArgs15 } from "util";
4064
+ var usage14 = `hogsend stats [--json]
3763
4065
 
3764
4066
  Show system-wide overview metrics from a running Hogsend instance.
3765
4067
  Wraps GET /v1/admin/metrics/overview.
@@ -3781,8 +4083,8 @@ Options:
3781
4083
  function pct(rate) {
3782
4084
  return `${(rate * 100).toFixed(2)}%`;
3783
4085
  }
3784
- async function run13(ctx) {
3785
- const { values: values2 } = parseArgs14({
4086
+ async function run14(ctx) {
4087
+ const { values: values2 } = parseArgs15({
3786
4088
  args: ctx.argv,
3787
4089
  allowPositionals: true,
3788
4090
  options: {
@@ -3790,7 +4092,7 @@ async function run13(ctx) {
3790
4092
  }
3791
4093
  });
3792
4094
  if (values2.help) {
3793
- ctx.out.log(usage13);
4095
+ ctx.out.log(usage14);
3794
4096
  return;
3795
4097
  }
3796
4098
  const metrics = await ctx.out.step(
@@ -3819,8 +4121,8 @@ async function run13(ctx) {
3819
4121
  var statsCommand = {
3820
4122
  name: "stats",
3821
4123
  summary: "Show system-wide overview metrics",
3822
- usage: usage13,
3823
- run: run13
4124
+ usage: usage14,
4125
+ run: run14
3824
4126
  };
3825
4127
 
3826
4128
  // src/commands/studio.ts
@@ -3829,10 +4131,10 @@ import { createReadStream, existsSync as existsSync9, readFileSync as readFileSy
3829
4131
  import { createServer } from "http";
3830
4132
  import { extname, join as join9, normalize, resolve as resolve2, sep as sep3 } from "path";
3831
4133
  import { fileURLToPath as fileURLToPath2 } from "url";
3832
- import { parseArgs as parseArgs16 } from "util";
4134
+ import { parseArgs as parseArgs17 } from "util";
3833
4135
 
3834
4136
  // src/commands/studio-admin.ts
3835
- import { parseArgs as parseArgs15 } from "util";
4137
+ import { parseArgs as parseArgs16 } from "util";
3836
4138
  import { password as passwordPrompt, text as text2 } from "@clack/prompts";
3837
4139
 
3838
4140
  // ../../node_modules/.pnpm/postgres@3.4.9/node_modules/postgres/src/index.js
@@ -13564,7 +13866,10 @@ var journeyStates = pgTable(
13564
13866
  index("journey_states_journey_id_status_idx").on(
13565
13867
  table.journeyId,
13566
13868
  table.status
13567
- )
13869
+ ),
13870
+ // Time-windowed activity counts (GET /v1/health) range-scan on updatedAt —
13871
+ // without this the healthcheck seq-scans the whole table on every hit.
13872
+ index("journey_states_updated_at_idx").on(table.updatedAt)
13568
13873
  ]
13569
13874
  );
13570
13875
 
@@ -14203,7 +14508,7 @@ Examples:
14203
14508
  Security: passwords are written ONLY via better-auth (scrypt) \u2014 never raw SQL,
14204
14509
  never plaintext at rest, never logged. Prefer the masked prompt over --password.`;
14205
14510
  function parseAdminFlags(argv) {
14206
- const { values: values2 } = parseArgs15({
14511
+ const { values: values2 } = parseArgs16({
14207
14512
  args: argv,
14208
14513
  allowPositionals: true,
14209
14514
  strict: false,
@@ -14404,7 +14709,7 @@ async function runStudioAdmin(ctx, argv) {
14404
14709
  }
14405
14710
 
14406
14711
  // src/commands/studio.ts
14407
- var usage14 = `hogsend studio [options]
14712
+ var usage15 = `hogsend studio [options]
14408
14713
 
14409
14714
  Subcommands:
14410
14715
  admin create | reset | list Shell-gated Studio admin recovery (DB + secret).
@@ -14494,12 +14799,12 @@ function openBrowser(url) {
14494
14799
  } catch {
14495
14800
  }
14496
14801
  }
14497
- async function run14(ctx) {
14802
+ async function run15(ctx) {
14498
14803
  if (ctx.argv[0] === "admin") {
14499
14804
  await runStudioAdmin(ctx, ctx.argv.slice(1));
14500
14805
  return;
14501
14806
  }
14502
- const { values: values2, positionals } = parseArgs16({
14807
+ const { values: values2, positionals } = parseArgs17({
14503
14808
  args: ctx.argv,
14504
14809
  allowPositionals: true,
14505
14810
  strict: false,
@@ -14512,7 +14817,7 @@ async function run14(ctx) {
14512
14817
  }
14513
14818
  });
14514
14819
  if (values2.help) {
14515
- ctx.out.log(usage14);
14820
+ ctx.out.log(usage15);
14516
14821
  return;
14517
14822
  }
14518
14823
  const port = Number(values2.port ?? "3333");
@@ -14598,17 +14903,17 @@ async function run14(ctx) {
14598
14903
  var studioCommand = {
14599
14904
  name: "studio",
14600
14905
  summary: "Serve the bundled Hogsend Studio admin SPA locally",
14601
- usage: usage14,
14602
- run: run14
14906
+ usage: usage15,
14907
+ run: run15
14603
14908
  };
14604
14909
 
14605
14910
  // src/commands/upgrade.ts
14606
14911
  import { spawnSync as spawnSync4 } from "child_process";
14607
14912
  import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
14608
14913
  import { join as join10 } from "path";
14609
- import { parseArgs as parseArgs17 } from "util";
14914
+ import { parseArgs as parseArgs18 } from "util";
14610
14915
  import { confirm as confirm3 } from "@clack/prompts";
14611
- var usage15 = `hogsend upgrade [--cwd <dir>] [--pm <pnpm|npm|yarn|bun>] [options]
14916
+ var usage16 = `hogsend upgrade [--cwd <dir>] [--pm <pnpm|npm|yarn|bun>] [options]
14612
14917
 
14613
14918
  Upgrade a scaffolded Hogsend app in one step:
14614
14919
  1. bump every @hogsend/* dependency to latest (or --to <version>), then
@@ -14644,8 +14949,8 @@ function hogsendDeps(cwd) {
14644
14949
  function addArgs(pm, specs) {
14645
14950
  return [pm === "npm" ? "install" : "add", ...specs];
14646
14951
  }
14647
- async function run15(ctx) {
14648
- const { values: values2 } = parseArgs17({
14952
+ async function run16(ctx) {
14953
+ const { values: values2 } = parseArgs18({
14649
14954
  args: ctx.argv,
14650
14955
  allowPositionals: true,
14651
14956
  options: {
@@ -14659,7 +14964,7 @@ async function run15(ctx) {
14659
14964
  }
14660
14965
  });
14661
14966
  if (values2.help) {
14662
- ctx.out.log(usage15);
14967
+ ctx.out.log(usage16);
14663
14968
  return;
14664
14969
  }
14665
14970
  if (values2["deps-only"] && values2["skills-only"]) {
@@ -14784,12 +15089,12 @@ async function run15(ctx) {
14784
15089
  var upgradeCommand = {
14785
15090
  name: "upgrade",
14786
15091
  summary: "Bump @hogsend/* deps to latest + refresh vendored skills",
14787
- usage: usage15,
14788
- run: run15
15092
+ usage: usage16,
15093
+ run: run16
14789
15094
  };
14790
15095
 
14791
15096
  // src/commands/webhooks.ts
14792
- import { parseArgs as parseArgs18 } from "util";
15097
+ import { parseArgs as parseArgs19 } from "util";
14793
15098
  var WEBHOOK_EVENT_TYPES = [
14794
15099
  "contact.created",
14795
15100
  "contact.updated",
@@ -14805,7 +15110,7 @@ var WEBHOOK_EVENT_TYPES = [
14805
15110
  "bucket.entered",
14806
15111
  "bucket.left"
14807
15112
  ];
14808
- var usage16 = `hogsend webhooks <subcommand> [options]
15113
+ var usage17 = `hogsend webhooks <subcommand> [options]
14809
15114
 
14810
15115
  Manage outbound webhook endpoints \u2014 the Svix-style signed event stream Hogsend
14811
15116
  emits to your URLs. Wraps the admin routes (/v1/admin/webhooks), so this command
@@ -14895,7 +15200,7 @@ ${color.bold(secret)}`,
14895
15200
  );
14896
15201
  }
14897
15202
  async function runList5(ctx, argv) {
14898
- const { values: values2 } = parseArgs18({
15203
+ const { values: values2 } = parseArgs19({
14899
15204
  args: argv,
14900
15205
  allowPositionals: true,
14901
15206
  options: {
@@ -14906,7 +15211,7 @@ async function runList5(ctx, argv) {
14906
15211
  }
14907
15212
  });
14908
15213
  if (values2.help) {
14909
- ctx.out.log(usage16);
15214
+ ctx.out.log(usage17);
14910
15215
  return;
14911
15216
  }
14912
15217
  const query = {
@@ -14955,13 +15260,13 @@ function renderEndpoint(ctx, ep, title) {
14955
15260
  );
14956
15261
  }
14957
15262
  async function runGet3(ctx, argv) {
14958
- const { values: values2, positionals } = parseArgs18({
15263
+ const { values: values2, positionals } = parseArgs19({
14959
15264
  args: argv,
14960
15265
  allowPositionals: true,
14961
15266
  options: { help: { type: "boolean", short: "h", default: false } }
14962
15267
  });
14963
15268
  if (values2.help) {
14964
- ctx.out.log(usage16);
15269
+ ctx.out.log(usage17);
14965
15270
  return;
14966
15271
  }
14967
15272
  const id = positionals[0];
@@ -14986,7 +15291,7 @@ async function runGet3(ctx, argv) {
14986
15291
  ctx.out.outro(`${res.url} \u2192 ${res.status}`);
14987
15292
  }
14988
15293
  async function runCreate2(ctx, argv) {
14989
- const { values: values2 } = parseArgs18({
15294
+ const { values: values2 } = parseArgs19({
14990
15295
  args: argv,
14991
15296
  allowPositionals: true,
14992
15297
  options: {
@@ -14999,7 +15304,7 @@ async function runCreate2(ctx, argv) {
14999
15304
  }
15000
15305
  });
15001
15306
  if (values2.help) {
15002
- ctx.out.log(usage16);
15307
+ ctx.out.log(usage17);
15003
15308
  return;
15004
15309
  }
15005
15310
  const url = values2.url;
@@ -15033,7 +15338,7 @@ async function runCreate2(ctx, argv) {
15033
15338
  ctx.out.outro(`${color.green("Created")} ${res.id} \u2192 ${res.url}`);
15034
15339
  }
15035
15340
  async function runUpdate(ctx, argv) {
15036
- const { values: values2, positionals } = parseArgs18({
15341
+ const { values: values2, positionals } = parseArgs19({
15037
15342
  args: argv,
15038
15343
  allowPositionals: true,
15039
15344
  options: {
@@ -15047,7 +15352,7 @@ async function runUpdate(ctx, argv) {
15047
15352
  }
15048
15353
  });
15049
15354
  if (values2.help) {
15050
- ctx.out.log(usage16);
15355
+ ctx.out.log(usage17);
15051
15356
  return;
15052
15357
  }
15053
15358
  const id = positionals[0];
@@ -15088,13 +15393,13 @@ async function runUpdate(ctx, argv) {
15088
15393
  ctx.out.outro(`${color.green("Updated")} ${res.id} \u2192 ${res.status}`);
15089
15394
  }
15090
15395
  async function runDelete(ctx, argv) {
15091
- const { values: values2, positionals } = parseArgs18({
15396
+ const { values: values2, positionals } = parseArgs19({
15092
15397
  args: argv,
15093
15398
  allowPositionals: true,
15094
15399
  options: { help: { type: "boolean", short: "h", default: false } }
15095
15400
  });
15096
15401
  if (values2.help) {
15097
- ctx.out.log(usage16);
15402
+ ctx.out.log(usage17);
15098
15403
  return;
15099
15404
  }
15100
15405
  const id = positionals[0];
@@ -15118,13 +15423,13 @@ async function runDelete(ctx, argv) {
15118
15423
  ctx.out.outro(`${color.green("Deleted")} ${id}`);
15119
15424
  }
15120
15425
  async function runRotate(ctx, argv) {
15121
- const { values: values2, positionals } = parseArgs18({
15426
+ const { values: values2, positionals } = parseArgs19({
15122
15427
  args: argv,
15123
15428
  allowPositionals: true,
15124
15429
  options: { help: { type: "boolean", short: "h", default: false } }
15125
15430
  });
15126
15431
  if (values2.help) {
15127
- ctx.out.log(usage16);
15432
+ ctx.out.log(usage17);
15128
15433
  return;
15129
15434
  }
15130
15435
  const id = positionals[0];
@@ -15153,13 +15458,13 @@ async function runRotate(ctx, argv) {
15153
15458
  );
15154
15459
  }
15155
15460
  async function runTest(ctx, argv) {
15156
- const { values: values2, positionals } = parseArgs18({
15461
+ const { values: values2, positionals } = parseArgs19({
15157
15462
  args: argv,
15158
15463
  allowPositionals: true,
15159
15464
  options: { help: { type: "boolean", short: "h", default: false } }
15160
15465
  });
15161
15466
  if (values2.help) {
15162
- ctx.out.log(usage16);
15467
+ ctx.out.log(usage17);
15163
15468
  return;
15164
15469
  }
15165
15470
  const id = positionals[0];
@@ -15185,7 +15490,7 @@ async function runTest(ctx, argv) {
15185
15490
  `${color.green("Enqueued")} a ${color.cyan(res.eventType)} delivery to ${id}.`
15186
15491
  );
15187
15492
  }
15188
- async function run16(ctx) {
15493
+ async function run17(ctx) {
15189
15494
  const sub = ctx.argv[0];
15190
15495
  switch (sub) {
15191
15496
  case "list":
@@ -15216,8 +15521,8 @@ async function run16(ctx) {
15216
15521
  var webhooksCommand = {
15217
15522
  name: "webhooks",
15218
15523
  summary: "Manage outbound webhook endpoints (create, rotate, test)",
15219
- usage: usage16,
15220
- run: run16
15524
+ usage: usage17,
15525
+ run: run17
15221
15526
  };
15222
15527
 
15223
15528
  // src/commands/index.ts
@@ -15231,6 +15536,7 @@ var commands = [
15231
15536
  campaignsCommand,
15232
15537
  webhooksCommand,
15233
15538
  domainCommand,
15539
+ hatchetCommand,
15234
15540
  studioCommand,
15235
15541
  devCommand,
15236
15542
  setupCommand,