@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 +382 -76
- package/dist/bin.js.map +1 -1
- package/package.json +4 -4
- package/skills/hogsend-database/references/migrations.md +1 -1
- package/skills/hogsend-deploy/SKILL.md +11 -0
- package/src/__tests__/hatchet-token.test.ts +205 -0
- package/src/commands/hatchet.ts +181 -0
- package/src/commands/index.ts +2 -0
- package/src/lib/config.ts +8 -0
- package/src/lib/hatchet-token.ts +268 -0
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/
|
|
3122
|
+
// src/commands/hatchet.ts
|
|
3122
3123
|
import { parseArgs as parseArgs10 } from "util";
|
|
3123
|
-
|
|
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 } =
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
3366
|
-
run:
|
|
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
|
|
3372
|
-
var
|
|
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
|
|
3384
|
-
const { values: values2, positionals } =
|
|
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(
|
|
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:
|
|
3434
|
-
run:
|
|
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
|
|
3742
|
+
import { parseArgs as parseArgs13 } from "util";
|
|
3441
3743
|
import { confirm as confirm2 } from "@clack/prompts";
|
|
3442
|
-
var
|
|
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
|
|
3460
|
-
const { values: values2 } =
|
|
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(
|
|
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:
|
|
3586
|
-
run:
|
|
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
|
|
3894
|
+
import { parseArgs as parseArgs14 } from "util";
|
|
3593
3895
|
import { multiselect } from "@clack/prompts";
|
|
3594
|
-
var
|
|
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 } =
|
|
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(
|
|
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
|
|
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(
|
|
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:
|
|
3757
|
-
run:
|
|
4058
|
+
usage: usage13,
|
|
4059
|
+
run: run13
|
|
3758
4060
|
};
|
|
3759
4061
|
|
|
3760
4062
|
// src/commands/stats.ts
|
|
3761
|
-
import { parseArgs as
|
|
3762
|
-
var
|
|
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
|
|
3785
|
-
const { values: values2 } =
|
|
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(
|
|
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:
|
|
3823
|
-
run:
|
|
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
|
|
4134
|
+
import { parseArgs as parseArgs17 } from "util";
|
|
3833
4135
|
|
|
3834
4136
|
// src/commands/studio-admin.ts
|
|
3835
|
-
import { parseArgs as
|
|
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 } =
|
|
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
|
|
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
|
|
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 } =
|
|
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(
|
|
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:
|
|
14602
|
-
run:
|
|
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
|
|
14914
|
+
import { parseArgs as parseArgs18 } from "util";
|
|
14610
14915
|
import { confirm as confirm3 } from "@clack/prompts";
|
|
14611
|
-
var
|
|
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
|
|
14648
|
-
const { values: values2 } =
|
|
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(
|
|
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:
|
|
14788
|
-
run:
|
|
15092
|
+
usage: usage16,
|
|
15093
|
+
run: run16
|
|
14789
15094
|
};
|
|
14790
15095
|
|
|
14791
15096
|
// src/commands/webhooks.ts
|
|
14792
|
-
import { parseArgs as
|
|
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
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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 } =
|
|
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(
|
|
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
|
|
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:
|
|
15220
|
-
run:
|
|
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,
|