diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ef223d..29742b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Thi ### Added - `openmail inbox create` and `openmail init` accept `--domain ` to create an inbox on a verified custom domain you own. When omitted, the inbox uses your account default domain. +- `openmail usage` — per-inbox usage (inbound/outbound counts, attachment bytes, stored bytes) plus account totals, with `--from`, `--to`, and `--group-by inbox|account` flags. --- diff --git a/README.md b/README.md index f91ede9..b02c574 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,9 @@ openmail inbox create [--mailbox-name ] [--display-name ] [-- openmail inbox get --id inb_xxx openmail inbox delete --id inb_xxx +# Per-inbox usage (emails + storage) for metering +openmail usage [--from ] [--to ] [--group-by inbox|account] + # Send email (uses default inbox from setup/init, or pass --inbox-id) openmail send --to hello@example.com --subject "Hi" --body "Hello" diff --git a/src/commands/__tests__/usage.test.ts b/src/commands/__tests__/usage.test.ts new file mode 100644 index 0000000..ebd5aaf --- /dev/null +++ b/src/commands/__tests__/usage.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it, vi } from "vitest"; +import { parseArgs } from "../../lib/args"; +import { runUsageCommand } from "../usage"; +import type { OpenMailHttpClient } from "../../lib/http"; + +function makeClient() { + return { + get: vi.fn().mockResolvedValue({ groupBy: "inbox", inboxes: [], totals: {} }), + } as unknown as OpenMailHttpClient & { get: ReturnType }; +} + +describe("runUsageCommand", () => { + it("passes from/to/group-by as query params", async () => { + const client = makeClient(); + const parsed = parseArgs([ + "usage", + "--from", + "2026-05-01T00:00:00Z", + "--to", + "2026-06-01T00:00:00Z", + "--group-by", + "account", + ]); + + await runUsageCommand(client, parsed); + + expect(client.get).toHaveBeenCalledWith("/v1/usage", { + from: "2026-05-01T00:00:00Z", + to: "2026-06-01T00:00:00Z", + group_by: "account", + }); + }); + + it("sends no query params when no flags are given", async () => { + const client = makeClient(); + const parsed = parseArgs(["usage"]); + + await runUsageCommand(client, parsed); + + expect(client.get).toHaveBeenCalledWith("/v1/usage", {}); + }); +}); diff --git a/src/commands/usage.ts b/src/commands/usage.ts new file mode 100644 index 0000000..c399642 --- /dev/null +++ b/src/commands/usage.ts @@ -0,0 +1,15 @@ +import type { ParsedArgs } from "../lib/args"; +import { getStringFlag } from "../lib/args"; +import type { OpenMailHttpClient } from "../lib/http"; + +export async function runUsageCommand(client: OpenMailHttpClient, parsed: ParsedArgs) { + const from = getStringFlag(parsed.flags, "from"); + const to = getStringFlag(parsed.flags, "to"); + const groupBy = getStringFlag(parsed.flags, "group-by"); + + return client.get("/v1/usage", { + ...(from ? { from } : {}), + ...(to ? { to } : {}), + ...(groupBy ? { group_by: groupBy } : {}), + }); +} diff --git a/src/index.ts b/src/index.ts index 68f379d..0920899 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import { getBooleanFlag, parseArgs } from "./lib/args"; import { version } from "../package.json"; import { runInboxCommand } from "./commands/inbox"; +import { runUsageCommand } from "./commands/usage"; import { runMessagesCommand } from "./commands/messages"; import { runThreadsCommand } from "./commands/threads"; import { runSendCommand } from "./commands/send"; @@ -162,6 +163,8 @@ async function main() { }); } else if (command === "inbox") { output = await runInboxCommand(client, parsed); + } else if (command === "usage") { + output = await runUsageCommand(client, parsed); } else if (command === "send") { const inboxId = await resolveInboxIdWithFallback({ client, @@ -261,6 +264,7 @@ function printHelp(topic?: string) { " status Show current OpenMail/OpenClaw runtime status", " init Create a new inbox and set as default", " inbox Manage inboxes", + " usage Show per-inbox usage (emails + storage)", " send Send an email", " messages List messages for an inbox", " threads List/get threads", @@ -359,6 +363,22 @@ function printHelp(topic?: string) { return; } + if (usage === "usage") { + process.stdout.write( + [ + "openmail usage", + "", + "Usage:", + " usage [--from ] [--to ] [--group-by inbox|account]", + "", + "Shows per-inbox usage (inbound/outbound counts, attachment bytes, stored bytes) plus account totals. Defaults to the last 30 days; max window is 92 days. Use --json for machine-readable output.", + "", + ...globalFlags, + ].join("\n"), + ); + return; + } + if (usage === "send") { process.stdout.write( [