diff --git a/src/commands/posts.ts b/src/commands/posts.ts index d5b2f1a..e3ec7a0 100644 --- a/src/commands/posts.ts +++ b/src/commands/posts.ts @@ -198,7 +198,7 @@ export function registerPostCommands(ctx: CommandContext): void { const res = await request(client, "POST", "/posts", { body: { - submolt: cmd.submolt, + submolt_name: cmd.submolt, title: sanitized.title, ...(sanitized.content ? { content: sanitized.content } : { url: sanitized.url }), }, diff --git a/src/commands/verify.ts b/src/commands/verify.ts new file mode 100644 index 0000000..b8ad514 --- /dev/null +++ b/src/commands/verify.ts @@ -0,0 +1,88 @@ +import type { CommandContext } from "../cli/context"; +import { request } from "../lib/http"; +import { printError, printInfo, printJson } from "../lib/output"; +import { recordRequest } from "../lib/rate_limit"; + +export function registerVerifyCommands(ctx: CommandContext): void { + const { program, globals, buildClient, enforceRateLimit, logOutbound, handleDryRun, applyRetryAfter } = + ctx; + + program + .command("verify") + .description("Submit a verification challenge answer") + .requiredOption("--code ", "Verification code from the challenge") + .requiredOption("--answer ", "Answer to the verification challenge (always sent as string)") + .action(async (cmd) => { + const opts = globals(); + const { client, profileName } = buildClient(true); + const endpoint = "/verify"; + const verificationCode = String(cmd.code); + const answer = String(cmd.answer); + + try { + await enforceRateLimit(profileName, "request", opts); + } catch { + logOutbound({ + profile: profileName, + action: "verify", + method: "POST", + endpoint, + status: "blocked", + reason: "rate_limit", + }); + process.exit(1); + } + + const res = await request(client, "POST", endpoint, { + body: { verification_code: verificationCode, answer }, + idempotent: false, + }); + + if (handleDryRun(res, opts, { verification_code: verificationCode })) { + logOutbound({ + profile: profileName, + action: "verify", + method: "POST", + endpoint, + status: "dry_run", + }); + return; + } + + if (!res.ok) { + if (res.status === 429) { + applyRetryAfter(profileName, "request", res.data); + } + logOutbound({ + profile: profileName, + action: "verify", + method: "POST", + endpoint, + status: "blocked", + reason: `http_${res.status}`, + }); + printError( + `Verification failed (${res.status}): ${res.error || "unknown error"}\n` + + "⚠️ Failed attempts count toward AI verification challenges. " + + "10 failures triggers a 24-hour account suspension.", + opts, + ); + process.exit(1); + } + + recordRequest(profileName); + logOutbound({ + profile: profileName, + action: "verify", + method: "POST", + endpoint, + status: "sent", + }); + + if (opts.json) { + printJson({ result: res.data || { verified: true }, verification_code: verificationCode }); + return; + } + printInfo("Verification submitted successfully.", opts); + }); +} diff --git a/src/mb.ts b/src/mb.ts index 1a2693e..155b880 100644 --- a/src/mb.ts +++ b/src/mb.ts @@ -14,6 +14,7 @@ import { registerSearchCommands } from "./commands/search"; import { registerSecretCommands } from "./commands/secrets"; import { registerSubmoltCommands } from "./commands/submolts"; import { registerVersionCommand } from "./commands/version"; +import { registerVerifyCommands } from "./commands/verify"; import { registerVoteCommands } from "./commands/vote"; declare const MB_NO_DMS: boolean | undefined; @@ -56,6 +57,7 @@ registerDmCommands(ctx); registerSecretCommands(ctx); registerSafetyCommands(ctx); registerAuthCommands(ctx); +registerVerifyCommands(ctx); registerVersionCommand(ctx); program.parseAsync(process.argv);