@singlefax/cli 1.0.0 → 1.0.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SingleFax CLI
2
2
 
3
- Official CLI for [SingleFax](https://singlefax.com): send a fax with an API key and prepaid credits.
3
+ Official CLI for [SingleFax](https://singlefax.com): send a fax with an API key and prepaid credits, or start Checkout for other SKUs.
4
4
 
5
5
  Chat apps should use MCP (`https://singlefax.com/mcp`). Autonomous Stripe MPP payments use Stripe Link CLI, not this tool.
6
6
 
@@ -12,7 +12,7 @@ npm i -g @singlefax/cli
12
12
  npx @singlefax/cli --help
13
13
  ```
14
14
 
15
- Create a scoped key at [https://singlefax.com/dashboard/api-keys](https://singlefax.com/dashboard/api-keys) (`fax:send`, `fax:read`, `credits:read`). Prepay credits in the dashboard, or run `singlefax credits buy`.
15
+ Create a scoped key at [https://singlefax.com/dashboard/api-keys](https://singlefax.com/dashboard/api-keys) (`fax:send`, `fax:read`, `credits:read`, `credits:checkout`). Prepay credits in the dashboard, or run `singlefax credits buy` / `singlefax buy credit_pack`.
16
16
 
17
17
  ```bash
18
18
  export SINGLEFAX_API_KEY=sf_live_…
@@ -27,19 +27,24 @@ npx @singlefax/cli send ./document.pdf --to +14155552671
27
27
  npx @singlefax/cli send ./document.pdf --to +14155552671 --wait --json
28
28
  ```
29
29
 
30
- `--help` and `send --help` work without a key.
30
+ `--help`, `send --help`, and `skus` work without a key.
31
31
 
32
32
  ## Commands
33
33
 
34
34
  | Command | API |
35
35
  | --- | --- |
36
36
  | `send FILE --to +E164 [--cover] [--wait]` | `POST /uploads` → `PUT` → `POST /faxes` |
37
+ | `quote [--to +E164] [--pages 10] [FILE]` | `POST /api/v1/faxes/quote` |
37
38
  | `status <id>` | `GET /api/v1/faxes/:id` |
38
39
  | `list` | `GET /api/v1/faxes` |
40
+ | `skus` | `GET /api/v1/skus` |
41
+ | `buy <sku> [--dollars 20] [--country US] [--state CA]` | `POST /api/v1/checkout` |
39
42
  | `credits` | `GET /api/v1/credits` |
40
- | `credits buy [--dollars 20]` | `POST /api/v1/credits/checkout` (prints Checkout URL) |
43
+ | `credits buy [--dollars 20]` | `POST /api/v1/credits/checkout` |
41
44
  | `config set api-key sf_live_…` | writes `~/.config/singlefax/config.json` |
42
45
 
46
+ `buy` SKUs: `credit_pack`, `inbound_30day`, `monthly-basic`, `annual-basic`, `monthly-business`, `annual-business`, `lifetime-basic`, `lifetime-pro`. HIPAA / EIN / IRS print a website URL. Send a PDF with `send`, not `buy`.
47
+
43
48
  Flags: `--json`, `--api-key`, `--api-url` (default `https://api.singlefax.com`).
44
49
 
45
50
  Do not send PHI through this CLI. Covered Entities use the website HIPAA Secure path with an electronic BAA.
@@ -58,5 +63,9 @@ On [npmjs.com](https://www.npmjs.com) → `@singlefax/cli` → Settings → Trus
58
63
  | Environment name | leave empty |
59
64
  | Allowed actions | `npm publish` |
60
65
 
61
- Configure this on the package **before** the first Actions publish (npm does not validate the form until publish). Then tag `cli-v1.0.0` or run the CLI workflow with **Publish**. Provenance is generated automatically. The `repository.url` in `package.json` must stay `https://github.com/SingleFax/singlefax-app.git`.
66
+ Configure this on the package **before** the first Actions publish (npm does not validate the form until publish). Provenance is generated automatically. The `repository.url` in `package.json` must stay `https://github.com/SingleFax/singlefax-app.git`.
67
+
68
+ A push to `main` that touches this package (or this workflow) bumps the **patch** version from whatever is already on npm, commits `chore(cli): x.y.z [skip ci]`, and publishes. Same bump happens if you run **Actions → CLI → Publish** from `main`. Do not bump `package.json` by hand for patches.
69
+
70
+ Major / minor: set `"version"` in `package.json` above the published line, or tag `cli-v2.0.0`. Autopublish never increments major/minor on its own.
62
71
 
package/dist/cli.mjs CHANGED
@@ -4,6 +4,7 @@
4
4
  import { runMain } from "citty";
5
5
 
6
6
  // src/program.ts
7
+ import { readFile as readFile3, stat as stat2 } from "fs/promises";
7
8
  import { defineCommand, renderUsage } from "citty";
8
9
 
9
10
  // src/client.ts
@@ -38,12 +39,13 @@ function createClient(opts) {
38
39
  const doFetch = opts.fetch ?? globalThis.fetch;
39
40
  const doSleep = opts.sleep ?? sleep;
40
41
  const apiUrl = opts.apiUrl.replace(/\/$/, "");
42
+ const apiKey = opts.apiKey?.trim() || "";
41
43
  async function request(input) {
42
44
  const url = input.path.startsWith("http") ? input.path : `${apiUrl}${input.path}`;
43
45
  const headers = {
44
- authorization: `Bearer ${opts.apiKey}`,
45
46
  accept: "application/json"
46
47
  };
48
+ if (apiKey) headers.authorization = `Bearer ${apiKey}`;
47
49
  let body;
48
50
  if (input.raw) {
49
51
  headers["content-type"] = input.contentType || "application/octet-stream";
@@ -186,6 +188,52 @@ function printCreditsHuman(row) {
186
188
  `available ${cents(row.availableCents)} reserved ${cents(row.reservedCents)}`
187
189
  );
188
190
  }
191
+ function printSkusHuman(payload) {
192
+ const rows = payload.skus ?? [];
193
+ if (!rows.length) {
194
+ printLine("No SKUs.");
195
+ return;
196
+ }
197
+ for (const row of rows) {
198
+ printLine(
199
+ [
200
+ row.id || "",
201
+ row.availability || "",
202
+ row.priceLabel || "",
203
+ row.title || "",
204
+ row.availability === "website" ? row.websiteUrl || "" : ""
205
+ ].filter((part) => part !== "").join(" ")
206
+ );
207
+ }
208
+ }
209
+ function printQuoteHuman(row) {
210
+ if (row.subtotalCents == null && row.priceLabel) {
211
+ printLine(row.priceLabel);
212
+ return;
213
+ }
214
+ printLine(
215
+ [
216
+ row.to || "",
217
+ row.billablePages != null ? `${row.billablePages} pages` : "",
218
+ cents(row.subtotalCents),
219
+ row.destinationCountry || "",
220
+ row.assumedPages ? "(assumed pages)" : ""
221
+ ].filter((part) => part !== "").join(" ")
222
+ );
223
+ }
224
+ function printBuyHuman(row) {
225
+ const url = row.checkoutUrl || row.websiteUrl || "";
226
+ printLine(
227
+ [
228
+ row.status || "",
229
+ row.sku || "",
230
+ row.phoneNumber || "",
231
+ row.creditCents != null ? cents(row.creditCents) : ""
232
+ ].filter((part) => part !== "").join(" ")
233
+ );
234
+ if (url) printLine(url);
235
+ if (row.notes && row.status === "website_only") printLine(row.notes);
236
+ }
189
237
 
190
238
  // src/send.ts
191
239
  import { randomUUID } from "crypto";
@@ -450,6 +498,154 @@ var creditsCommand = defineCommand({
450
498
  }
451
499
  }
452
500
  });
501
+ var skusCommand = defineCommand({
502
+ meta: {
503
+ name: "skus",
504
+ description: "List product SKUs and prices (no API key required)"
505
+ },
506
+ args: sharedArgs,
507
+ async run({ args }) {
508
+ const config = await resolveConfig(args);
509
+ const client = createClient({
510
+ apiUrl: config.apiUrl,
511
+ apiKey: config.apiKey
512
+ });
513
+ try {
514
+ const row = await client.request({
515
+ method: "GET",
516
+ path: "/api/v1/skus"
517
+ });
518
+ if (config.json) printJson(row);
519
+ else printSkusHuman(row);
520
+ } catch (error) {
521
+ fail(error);
522
+ }
523
+ }
524
+ });
525
+ var quoteCommand = defineCommand({
526
+ meta: {
527
+ name: "quote",
528
+ description: "Quote a retail send without charging"
529
+ },
530
+ args: {
531
+ file: {
532
+ type: "positional",
533
+ description: "Optional PDF/office file for a live page count",
534
+ required: false
535
+ },
536
+ to: {
537
+ type: "string",
538
+ description: "Destination fax number in E.164"
539
+ },
540
+ pages: {
541
+ type: "string",
542
+ description: "Assumed billable pages when no file is given (default 10)"
543
+ },
544
+ ...sharedArgs
545
+ },
546
+ async run({ args }) {
547
+ const config = await resolveConfig(args);
548
+ const client = createClient({
549
+ apiUrl: config.apiUrl,
550
+ apiKey: requireApiKey(config)
551
+ });
552
+ const file = args.file ? String(args.file) : "";
553
+ let base64;
554
+ if (file) {
555
+ const info = await stat2(file);
556
+ if (!info.isFile()) throw new Error("FILE must be a regular file");
557
+ base64 = (await readFile3(file)).toString("base64");
558
+ }
559
+ const pages = args.pages != null ? Number(args.pages) : void 0;
560
+ try {
561
+ const row = await client.request({
562
+ method: "POST",
563
+ path: "/api/v1/faxes/quote",
564
+ json: {
565
+ to: args.to ? String(args.to) : void 0,
566
+ pages: Number.isFinite(pages) && pages && pages > 0 ? pages : void 0,
567
+ base64
568
+ }
569
+ });
570
+ if (config.json) printJson(row);
571
+ else printQuoteHuman(row);
572
+ } catch (error) {
573
+ fail(error);
574
+ }
575
+ }
576
+ });
577
+ var buyCommand = defineCommand({
578
+ meta: {
579
+ name: "buy",
580
+ description: "Start Stripe Checkout for a SKU (credit_pack, inbound_30day, monthly-basic, annual-basic, monthly-business, annual-business, lifetime-basic, lifetime-pro)"
581
+ },
582
+ args: {
583
+ sku: {
584
+ type: "positional",
585
+ description: "Catalog SKU id (see `singlefax skus`)",
586
+ required: true
587
+ },
588
+ dollars: {
589
+ type: "string",
590
+ description: "credit_pack amount in dollars (default 20)"
591
+ },
592
+ country: {
593
+ type: "string",
594
+ description: "US or CA for number SKUs (default US)"
595
+ },
596
+ state: {
597
+ type: "string",
598
+ description: "Optional US state or CA province for number SKUs"
599
+ },
600
+ ...sharedArgs
601
+ },
602
+ async run({ args }) {
603
+ const config = await resolveConfig(args);
604
+ const client = createClient({
605
+ apiUrl: config.apiUrl,
606
+ apiKey: requireApiKey(config)
607
+ });
608
+ const sku = String(args.sku);
609
+ const dollars = args.dollars != null ? Number(args.dollars) : void 0;
610
+ const amountCents = dollars != null && Number.isFinite(dollars) && dollars > 0 ? Math.round(dollars * 100) : void 0;
611
+ const json = {
612
+ sku,
613
+ amountCents,
614
+ country: args.country ? String(args.country).toUpperCase() : void 0,
615
+ state: args.state ? String(args.state) : void 0
616
+ };
617
+ try {
618
+ let row;
619
+ try {
620
+ row = await client.request({
621
+ method: "POST",
622
+ path: "/api/v1/checkout",
623
+ json
624
+ });
625
+ } catch (error) {
626
+ if (sku === "credit_pack" && error instanceof ApiError && error.status === 404) {
627
+ const fallback = await client.request({
628
+ method: "POST",
629
+ path: "/api/v1/credits/checkout",
630
+ json: { amountCents: amountCents ?? 2e3 }
631
+ });
632
+ row = {
633
+ status: "awaiting_payment",
634
+ sku: "credit_pack",
635
+ checkoutUrl: fallback.checkoutUrl,
636
+ creditCents: fallback.creditCents
637
+ };
638
+ } else {
639
+ throw error;
640
+ }
641
+ }
642
+ if (config.json) printJson(row);
643
+ else printBuyHuman(row);
644
+ } catch (error) {
645
+ fail(error);
646
+ }
647
+ }
648
+ });
453
649
  var configSetCommand = defineCommand({
454
650
  meta: {
455
651
  name: "set",
@@ -491,12 +687,15 @@ var main = defineCommand({
491
687
  meta: {
492
688
  name: "singlefax",
493
689
  version: "1.0.0",
494
- description: "SingleFax CLI \u2014 send faxes via API key and prepaid credits"
690
+ description: "SingleFax CLI \u2014 send faxes, quote, and buy SKUs via API key"
495
691
  },
496
692
  subCommands: {
497
693
  send: sendCommand,
498
694
  status: statusCommand,
499
695
  list: listCommand,
696
+ quote: quoteCommand,
697
+ skus: skusCommand,
698
+ buy: buyCommand,
500
699
  credits: creditsCommand,
501
700
  config: configCommand
502
701
  }
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/program.ts","../src/client.ts","../src/config.ts","../src/output.ts","../src/send.ts"],"sourcesContent":["import { runMain } from 'citty'\nimport { main } from './program.ts'\n\nawait runMain(main)\n","import { defineCommand, renderUsage } from 'citty'\nimport { ApiError, createClient } from './client.ts'\nimport { readConfigFile, requireApiKey, resolveConfig, writeConfigFile } from './config.ts'\nimport {\n printCreditsHuman,\n printJson,\n printLine,\n printListHuman,\n printSendHuman,\n printStatusHuman,\n} from './output.ts'\nimport { sendFax } from './send.ts'\n\nconst sharedArgs = {\n json: {\n type: 'boolean' as const,\n description: 'Print the API JSON body',\n },\n 'api-key': {\n type: 'string' as const,\n description: 'API key (sf_live_…). Overrides SINGLEFAX_API_KEY',\n },\n 'api-url': {\n type: 'string' as const,\n description: 'API base URL (default https://api.singlefax.com)',\n },\n}\n\nfunction fail(error: unknown): never {\n if (error instanceof ApiError) {\n throw error\n }\n throw error instanceof Error ? error : new Error(String(error))\n}\n\nexport const sendCommand = defineCommand({\n meta: {\n name: 'send',\n description: 'Upload a document and send it as a fax',\n },\n args: {\n file: {\n type: 'positional',\n description: 'Path to the PDF or office document',\n required: true,\n },\n to: {\n type: 'string',\n description: 'Destination fax number in E.164',\n required: true,\n },\n cover: {\n type: 'boolean',\n description: 'Prepend a cover page',\n },\n wait: {\n type: 'boolean',\n description: 'Poll until delivered or failed',\n },\n ...sharedArgs,\n },\n async run({ args }) {\n const config = await resolveConfig(args)\n const client = createClient({\n apiUrl: config.apiUrl,\n apiKey: requireApiKey(config),\n })\n try {\n const row = await sendFax(client, {\n file: String(args.file),\n to: String(args.to),\n cover: Boolean(args.cover),\n wait: Boolean(args.wait),\n })\n if (config.json) printJson(row)\n else printSendHuman(row)\n } catch (error) {\n fail(error)\n }\n },\n})\n\nconst statusCommand = defineCommand({\n meta: {\n name: 'status',\n description: 'Poll delivery status for an order you own',\n },\n args: {\n id: {\n type: 'positional',\n description: 'Fax order id',\n required: true,\n },\n ...sharedArgs,\n },\n async run({ args }) {\n const config = await resolveConfig(args)\n const client = createClient({\n apiUrl: config.apiUrl,\n apiKey: requireApiKey(config),\n })\n try {\n const row = await client.request({\n method: 'GET',\n path: `/api/v1/faxes/${encodeURIComponent(String(args.id))}`,\n })\n if (config.json) printJson(row)\n else printStatusHuman(row as Parameters<typeof printStatusHuman>[0])\n } catch (error) {\n fail(error)\n }\n },\n})\n\nconst listCommand = defineCommand({\n meta: {\n name: 'list',\n description: 'List recent fax orders',\n },\n args: {\n limit: {\n type: 'string',\n description: 'Page size (max 100)',\n default: '20',\n },\n offset: {\n type: 'string',\n description: 'Offset',\n default: '0',\n },\n ...sharedArgs,\n },\n async run({ args }) {\n const config = await resolveConfig(args)\n const client = createClient({\n apiUrl: config.apiUrl,\n apiKey: requireApiKey(config),\n })\n const limit = Math.min(Number(args.limit) || 20, 100)\n const offset = Number(args.offset) || 0\n try {\n const row = await client.request({\n method: 'GET',\n path: `/api/v1/faxes?limit=${limit}&offset=${offset}`,\n })\n if (config.json) printJson(row)\n else printListHuman(row as Parameters<typeof printListHuman>[0])\n } catch (error) {\n fail(error)\n }\n },\n})\n\nconst creditsBuyCommand = defineCommand({\n meta: {\n name: 'buy',\n description: 'Create a Stripe Checkout session for prepaid credits',\n },\n args: {\n dollars: {\n type: 'string',\n description: 'Credit pack in dollars (default 20)',\n default: '20',\n },\n ...sharedArgs,\n },\n async run({ args }) {\n const config = await resolveConfig(args)\n const client = createClient({\n apiUrl: config.apiUrl,\n apiKey: requireApiKey(config),\n })\n const dollars = Number(args.dollars)\n const amountCents = Number.isFinite(dollars) && dollars > 0 ? Math.round(dollars * 100) : 2000\n try {\n const row = await client.request<{ checkoutUrl?: string }>({\n method: 'POST',\n path: '/api/v1/credits/checkout',\n json: { amountCents },\n })\n if (config.json) printJson(row)\n else printLine(row.checkoutUrl || 'Checkout URL missing')\n } catch (error) {\n fail(error)\n }\n },\n})\n\nconst creditsCommand = defineCommand({\n meta: {\n name: 'credits',\n description: 'Show prepaid credit balance, or buy more',\n },\n args: sharedArgs,\n subCommands: {\n buy: creditsBuyCommand,\n },\n async run({ args }) {\n const config = await resolveConfig(args)\n const client = createClient({\n apiUrl: config.apiUrl,\n apiKey: requireApiKey(config),\n })\n try {\n const row = await client.request({\n method: 'GET',\n path: '/api/v1/credits',\n })\n if (config.json) printJson(row)\n else printCreditsHuman(row as Parameters<typeof printCreditsHuman>[0])\n } catch (error) {\n fail(error)\n }\n },\n})\n\nconst configSetCommand = defineCommand({\n meta: {\n name: 'set',\n description: 'Write a local config value (api-key)',\n },\n args: {\n name: {\n type: 'positional',\n description: 'Name (only api-key is supported)',\n required: true,\n },\n value: {\n type: 'positional',\n description: 'Value',\n required: true,\n },\n },\n async run({ args }) {\n const name = String(args.name)\n const value = String(args.value)\n if (name !== 'api-key' && name !== 'apiKey') {\n throw new Error('Supported keys: api-key')\n }\n const current = await readConfigFile()\n const path = await writeConfigFile({ ...current, apiKey: value })\n printLine(`Saved API key to ${path}`)\n },\n})\n\nconst configCommand = defineCommand({\n meta: {\n name: 'config',\n description: 'Store API credentials locally',\n },\n subCommands: {\n set: configSetCommand,\n },\n})\n\nexport const main = defineCommand({\n meta: {\n name: 'singlefax',\n version: '1.0.0',\n description: 'SingleFax CLI — send faxes via API key and prepaid credits',\n },\n subCommands: {\n send: sendCommand,\n status: statusCommand,\n list: listCommand,\n credits: creditsCommand,\n config: configCommand,\n },\n})\n\nexport async function usageText(): Promise<string> {\n return renderUsage(main)\n}\n","export class ApiError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly body: unknown,\n ) {\n super(message)\n this.name = 'ApiError'\n }\n}\n\nexport type ApiClient = {\n apiUrl: string\n request: <T>(opts: {\n method: string\n path: string\n json?: unknown\n raw?: Buffer | Uint8Array\n contentType?: string\n idempotencyKey?: string\n }) => Promise<T>\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nfunction formatError(status: number, body: unknown): string {\n if (body && typeof body === 'object') {\n const row = body as {\n statusMessage?: string\n message?: string\n data?: { code?: string; resolution?: string }\n }\n const headline = row.statusMessage || row.message || `HTTP ${status}`\n const resolution = row.data?.resolution\n return resolution ? `${headline}\\n${resolution}` : headline\n }\n return `HTTP ${status}`\n}\n\nfunction retryAfterMs(res: Response): number {\n const header = res.headers.get('retry-after')\n const seconds = header ? Number(header) : 1\n if (!Number.isFinite(seconds) || seconds < 1) return 1000\n return Math.min(seconds, 60) * 1000\n}\n\nexport function createClient(opts: {\n apiUrl: string\n apiKey: string\n fetch?: typeof fetch\n sleep?: (ms: number) => Promise<void>\n}): ApiClient {\n const doFetch = opts.fetch ?? globalThis.fetch\n const doSleep = opts.sleep ?? sleep\n const apiUrl = opts.apiUrl.replace(/\\/$/, '')\n\n async function request<T>(input: {\n method: string\n path: string\n json?: unknown\n raw?: Buffer | Uint8Array\n contentType?: string\n idempotencyKey?: string\n }): Promise<T> {\n const url = input.path.startsWith('http') ? input.path : `${apiUrl}${input.path}`\n const headers: Record<string, string> = {\n authorization: `Bearer ${opts.apiKey}`,\n accept: 'application/json',\n }\n let body: BodyInit | undefined\n if (input.raw) {\n headers['content-type'] = input.contentType || 'application/octet-stream'\n body = input.raw as BodyInit\n } else if (input.json !== undefined) {\n headers['content-type'] = 'application/json'\n body = JSON.stringify(input.json)\n }\n if (input.idempotencyKey) headers['idempotency-key'] = input.idempotencyKey\n\n const send = () =>\n doFetch(url, {\n method: input.method,\n headers,\n body,\n })\n\n let res = await send()\n if (res.status === 429) {\n await doSleep(retryAfterMs(res))\n res = await send()\n }\n\n const text = await res.text()\n let parsed: unknown = text\n if (text) {\n try {\n parsed = JSON.parse(text)\n } catch {\n parsed = { message: text }\n }\n } else {\n parsed = {}\n }\n\n if (!res.ok) {\n throw new ApiError(formatError(res.status, parsed), res.status, parsed)\n }\n return parsed as T\n }\n\n return { apiUrl, request }\n}\n","import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\n\nexport const DEFAULT_API_URL = 'https://api.singlefax.com'\n\nexport type CliConfigFile = {\n apiKey?: string\n apiUrl?: string\n}\n\nexport function configFilePath(): string {\n if (process.env.SINGLEFAX_CONFIG_PATH) return process.env.SINGLEFAX_CONFIG_PATH\n const xdg = process.env.XDG_CONFIG_HOME || join(homedir(), '.config')\n return join(xdg, 'singlefax', 'config.json')\n}\n\nexport async function readConfigFile(): Promise<CliConfigFile> {\n try {\n const raw = await readFile(configFilePath(), 'utf8')\n const parsed = JSON.parse(raw) as CliConfigFile\n return parsed && typeof parsed === 'object' ? parsed : {}\n } catch {\n return {}\n }\n}\n\nexport async function writeConfigFile(next: CliConfigFile): Promise<string> {\n const path = configFilePath()\n await mkdir(dirname(path), { recursive: true, mode: 0o700 })\n await writeFile(path, `${JSON.stringify(next, null, 2)}\\n`, { mode: 0o600 })\n await chmod(path, 0o600)\n return path\n}\n\nexport type ResolvedConfig = {\n apiUrl: string\n apiKey: string\n json: boolean\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value.trim() : undefined\n}\n\nexport async function resolveConfig(args: Record<string, unknown>): Promise<ResolvedConfig> {\n const file = await readConfigFile()\n const apiUrl =\n asString(args['api-url']) ||\n asString(args.apiUrl) ||\n asString(process.env.SINGLEFAX_API_URL) ||\n file.apiUrl ||\n DEFAULT_API_URL\n const apiKey =\n asString(args['api-key']) ||\n asString(args.apiKey) ||\n asString(process.env.SINGLEFAX_API_KEY) ||\n file.apiKey ||\n ''\n return {\n apiUrl: apiUrl.replace(/\\/$/, ''),\n apiKey,\n json: Boolean(args.json),\n }\n}\n\nexport function requireApiKey(config: ResolvedConfig): string {\n if (config.apiKey) return config.apiKey\n throw new Error(\n 'Missing API key. Set SINGLEFAX_API_KEY, pass --api-key, or run: singlefax config set api-key sf_live_…',\n )\n}\n","export function printJson(value: unknown): void {\n process.stdout.write(`${JSON.stringify(value, null, 2)}\\n`)\n}\n\nexport function printLine(line: string): void {\n process.stdout.write(`${line}\\n`)\n}\n\nfunction cents(value: unknown): string {\n const n = Number(value)\n if (!Number.isFinite(n)) return '—'\n return `$${(n / 100).toFixed(2)}`\n}\n\nexport function printSendHuman(row: {\n id?: string\n fulfillmentStatus?: string\n billablePages?: number\n totalCents?: number\n subtotalCents?: number\n}): void {\n printLine(\n [\n row.fulfillmentStatus || 'queued',\n row.id || '',\n row.billablePages != null ? `${row.billablePages} pages` : '',\n cents(row.totalCents ?? row.subtotalCents),\n ]\n .filter(Boolean)\n .join(' '),\n )\n}\n\nexport function printStatusHuman(row: {\n id?: string\n fulfillmentStatus?: string\n billablePages?: number\n totalCents?: number\n subtotalCents?: number\n failureClass?: string | null\n}): void {\n const parts = [\n row.fulfillmentStatus || 'unknown',\n row.id || '',\n row.billablePages != null ? `${row.billablePages} pages` : '',\n cents(row.totalCents ?? row.subtotalCents),\n ]\n if (row.failureClass) parts.push(row.failureClass)\n printLine(parts.filter(Boolean).join(' '))\n}\n\nexport function printListHuman(payload: {\n data?: Array<{\n id?: string\n fulfillmentStatus?: string\n billablePages?: number\n totalCents?: number\n destinationCountry?: string | null\n }>\n}): void {\n const rows = payload.data ?? []\n if (!rows.length) {\n printLine('No faxes.')\n return\n }\n for (const row of rows) {\n printLine(\n [\n row.id || '',\n row.fulfillmentStatus || '',\n row.billablePages != null ? `${row.billablePages}p` : '',\n cents(row.totalCents),\n row.destinationCountry || '',\n ]\n .filter((part) => part !== '')\n .join(' '),\n )\n }\n}\n\nexport function printCreditsHuman(row: {\n availableCents?: number\n reservedCents?: number\n}): void {\n printLine(\n `available ${cents(row.availableCents)} reserved ${cents(row.reservedCents)}`,\n )\n}\n","import { randomUUID } from 'node:crypto'\nimport { readFile, stat } from 'node:fs/promises'\nimport { extname } from 'node:path'\nimport type { ApiClient } from './client.ts'\n\nconst TERMINAL = new Set(['delivered', 'failed_final'])\n\nexport type UploadCreated = {\n uploadToken: string\n uploadUrl: string\n contentType?: string\n maxBytes?: number\n}\n\nexport type FaxCreated = {\n id: string\n fulfillmentStatus: string\n billablePages?: number\n totalCents?: number\n subtotalCents?: number\n destinationCountry?: string\n}\n\nfunction contentTypeFor(file: string): string {\n switch (extname(file).toLowerCase()) {\n case '.pdf':\n return 'application/pdf'\n case '.png':\n return 'image/png'\n case '.jpg':\n case '.jpeg':\n return 'image/jpeg'\n case '.tif':\n case '.tiff':\n return 'image/tiff'\n case '.doc':\n return 'application/msword'\n case '.docx':\n return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'\n default:\n return 'application/pdf'\n }\n}\n\nexport async function sendFax(\n client: ApiClient,\n opts: {\n file: string\n to: string\n cover?: boolean\n wait?: boolean\n sleep?: (ms: number) => Promise<void>\n now?: () => number\n },\n): Promise<FaxCreated> {\n const filePath = opts.file\n const info = await stat(filePath)\n if (!info.isFile()) {\n throw new Error('FILE must be a regular file')\n }\n const contentType = contentTypeFor(filePath)\n const bytes = await readFile(filePath)\n\n const upload = await client.request<UploadCreated>({\n method: 'POST',\n path: '/api/v1/uploads',\n json: { contentType, maxBytes: bytes.byteLength },\n })\n\n await client.request({\n method: 'PUT',\n path: upload.uploadUrl,\n raw: bytes,\n contentType,\n })\n\n const fax = await client.request<FaxCreated>({\n method: 'POST',\n path: '/api/v1/faxes',\n json: { to: opts.to, uploadToken: upload.uploadToken, cover: Boolean(opts.cover) },\n idempotencyKey: randomUUID(),\n })\n\n if (!opts.wait) return fax\n\n const doSleep = opts.sleep ?? ((ms: number) => new Promise((r) => setTimeout(r, ms)))\n const started = (opts.now ?? Date.now)()\n const deadline = started + 180_000\n let latest: FaxCreated = fax\n while ((opts.now ?? Date.now)() < deadline) {\n if (TERMINAL.has(latest.fulfillmentStatus)) return latest\n await doSleep(2000)\n latest = await client.request<FaxCreated>({\n method: 'GET',\n path: `/api/v1/faxes/${encodeURIComponent(fax.id)}`,\n })\n }\n return latest\n}\n"],"mappings":";;;AAAA,SAAS,eAAe;;;ACAxB,SAAS,eAAe,mBAAmB;;;ACApC,IAAM,WAAN,cAAuB,MAAM;AAAA,EAClC,YACE,SACS,QACA,MACT;AACA,UAAM,OAAO;AAHJ;AACA;AAGT,SAAK,OAAO;AAAA,EACd;AACF;AAcA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAEA,SAAS,YAAY,QAAgB,MAAuB;AAC1D,MAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,UAAM,MAAM;AAKZ,UAAM,WAAW,IAAI,iBAAiB,IAAI,WAAW,QAAQ,MAAM;AACnE,UAAM,aAAa,IAAI,MAAM;AAC7B,WAAO,aAAa,GAAG,QAAQ;AAAA,EAAK,UAAU,KAAK;AAAA,EACrD;AACA,SAAO,QAAQ,MAAM;AACvB;AAEA,SAAS,aAAa,KAAuB;AAC3C,QAAM,SAAS,IAAI,QAAQ,IAAI,aAAa;AAC5C,QAAM,UAAU,SAAS,OAAO,MAAM,IAAI;AAC1C,MAAI,CAAC,OAAO,SAAS,OAAO,KAAK,UAAU,EAAG,QAAO;AACrD,SAAO,KAAK,IAAI,SAAS,EAAE,IAAI;AACjC;AAEO,SAAS,aAAa,MAKf;AACZ,QAAM,UAAU,KAAK,SAAS,WAAW;AACzC,QAAM,UAAU,KAAK,SAAS;AAC9B,QAAM,SAAS,KAAK,OAAO,QAAQ,OAAO,EAAE;AAE5C,iBAAe,QAAW,OAOX;AACb,UAAM,MAAM,MAAM,KAAK,WAAW,MAAM,IAAI,MAAM,OAAO,GAAG,MAAM,GAAG,MAAM,IAAI;AAC/E,UAAM,UAAkC;AAAA,MACtC,eAAe,UAAU,KAAK,MAAM;AAAA,MACpC,QAAQ;AAAA,IACV;AACA,QAAI;AACJ,QAAI,MAAM,KAAK;AACb,cAAQ,cAAc,IAAI,MAAM,eAAe;AAC/C,aAAO,MAAM;AAAA,IACf,WAAW,MAAM,SAAS,QAAW;AACnC,cAAQ,cAAc,IAAI;AAC1B,aAAO,KAAK,UAAU,MAAM,IAAI;AAAA,IAClC;AACA,QAAI,MAAM,eAAgB,SAAQ,iBAAiB,IAAI,MAAM;AAE7D,UAAM,OAAO,MACX,QAAQ,KAAK;AAAA,MACX,QAAQ,MAAM;AAAA,MACd;AAAA,MACA;AAAA,IACF,CAAC;AAEH,QAAI,MAAM,MAAM,KAAK;AACrB,QAAI,IAAI,WAAW,KAAK;AACtB,YAAM,QAAQ,aAAa,GAAG,CAAC;AAC/B,YAAM,MAAM,KAAK;AAAA,IACnB;AAEA,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI,SAAkB;AACtB,QAAI,MAAM;AACR,UAAI;AACF,iBAAS,KAAK,MAAM,IAAI;AAAA,MAC1B,QAAQ;AACN,iBAAS,EAAE,SAAS,KAAK;AAAA,MAC3B;AAAA,IACF,OAAO;AACL,eAAS,CAAC;AAAA,IACZ;AAEA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,SAAS,YAAY,IAAI,QAAQ,MAAM,GAAG,IAAI,QAAQ,MAAM;AAAA,IACxE;AACA,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,QAAQ,QAAQ;AAC3B;;;ACjHA,SAAS,OAAO,OAAO,UAAU,iBAAiB;AAClD,SAAS,eAAe;AACxB,SAAS,SAAS,YAAY;AAEvB,IAAM,kBAAkB;AAOxB,SAAS,iBAAyB;AACvC,MAAI,QAAQ,IAAI,sBAAuB,QAAO,QAAQ,IAAI;AAC1D,QAAM,MAAM,QAAQ,IAAI,mBAAmB,KAAK,QAAQ,GAAG,SAAS;AACpE,SAAO,KAAK,KAAK,aAAa,aAAa;AAC7C;AAEA,eAAsB,iBAAyC;AAC7D,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,eAAe,GAAG,MAAM;AACnD,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO,UAAU,OAAO,WAAW,WAAW,SAAS,CAAC;AAAA,EAC1D,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,eAAsB,gBAAgB,MAAsC;AAC1E,QAAM,OAAO,eAAe;AAC5B,QAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAC3D,QAAM,UAAU,MAAM,GAAG,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,GAAM,EAAE,MAAM,IAAM,CAAC;AAC3E,QAAM,MAAM,MAAM,GAAK;AACvB,SAAO;AACT;AAQA,SAAS,SAAS,OAAoC;AACpD,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,IAAI,MAAM,KAAK,IAAI;AACpE;AAEA,eAAsB,cAAc,MAAwD;AAC1F,QAAM,OAAO,MAAM,eAAe;AAClC,QAAM,SACJ,SAAS,KAAK,SAAS,CAAC,KACxB,SAAS,KAAK,MAAM,KACpB,SAAS,QAAQ,IAAI,iBAAiB,KACtC,KAAK,UACL;AACF,QAAM,SACJ,SAAS,KAAK,SAAS,CAAC,KACxB,SAAS,KAAK,MAAM,KACpB,SAAS,QAAQ,IAAI,iBAAiB,KACtC,KAAK,UACL;AACF,SAAO;AAAA,IACL,QAAQ,OAAO,QAAQ,OAAO,EAAE;AAAA,IAChC;AAAA,IACA,MAAM,QAAQ,KAAK,IAAI;AAAA,EACzB;AACF;AAEO,SAAS,cAAc,QAAgC;AAC5D,MAAI,OAAO,OAAQ,QAAO,OAAO;AACjC,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;;;ACvEO,SAAS,UAAU,OAAsB;AAC9C,UAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAAA,CAAI;AAC5D;AAEO,SAAS,UAAU,MAAoB;AAC5C,UAAQ,OAAO,MAAM,GAAG,IAAI;AAAA,CAAI;AAClC;AAEA,SAAS,MAAM,OAAwB;AACrC,QAAM,IAAI,OAAO,KAAK;AACtB,MAAI,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO;AAChC,SAAO,KAAK,IAAI,KAAK,QAAQ,CAAC,CAAC;AACjC;AAEO,SAAS,eAAe,KAMtB;AACP;AAAA,IACE;AAAA,MACE,IAAI,qBAAqB;AAAA,MACzB,IAAI,MAAM;AAAA,MACV,IAAI,iBAAiB,OAAO,GAAG,IAAI,aAAa,WAAW;AAAA,MAC3D,MAAM,IAAI,cAAc,IAAI,aAAa;AAAA,IAC3C,EACG,OAAO,OAAO,EACd,KAAK,IAAI;AAAA,EACd;AACF;AAEO,SAAS,iBAAiB,KAOxB;AACP,QAAM,QAAQ;AAAA,IACZ,IAAI,qBAAqB;AAAA,IACzB,IAAI,MAAM;AAAA,IACV,IAAI,iBAAiB,OAAO,GAAG,IAAI,aAAa,WAAW;AAAA,IAC3D,MAAM,IAAI,cAAc,IAAI,aAAa;AAAA,EAC3C;AACA,MAAI,IAAI,aAAc,OAAM,KAAK,IAAI,YAAY;AACjD,YAAU,MAAM,OAAO,OAAO,EAAE,KAAK,IAAI,CAAC;AAC5C;AAEO,SAAS,eAAe,SAQtB;AACP,QAAM,OAAO,QAAQ,QAAQ,CAAC;AAC9B,MAAI,CAAC,KAAK,QAAQ;AAChB,cAAU,WAAW;AACrB;AAAA,EACF;AACA,aAAW,OAAO,MAAM;AACtB;AAAA,MACE;AAAA,QACE,IAAI,MAAM;AAAA,QACV,IAAI,qBAAqB;AAAA,QACzB,IAAI,iBAAiB,OAAO,GAAG,IAAI,aAAa,MAAM;AAAA,QACtD,MAAM,IAAI,UAAU;AAAA,QACpB,IAAI,sBAAsB;AAAA,MAC5B,EACG,OAAO,CAAC,SAAS,SAAS,EAAE,EAC5B,KAAK,IAAI;AAAA,IACd;AAAA,EACF;AACF;AAEO,SAAS,kBAAkB,KAGzB;AACP;AAAA,IACE,aAAa,MAAM,IAAI,cAAc,CAAC,cAAc,MAAM,IAAI,aAAa,CAAC;AAAA,EAC9E;AACF;;;ACvFA,SAAS,kBAAkB;AAC3B,SAAS,YAAAA,WAAU,YAAY;AAC/B,SAAS,eAAe;AAGxB,IAAM,WAAW,oBAAI,IAAI,CAAC,aAAa,cAAc,CAAC;AAkBtD,SAAS,eAAe,MAAsB;AAC5C,UAAQ,QAAQ,IAAI,EAAE,YAAY,GAAG;AAAA,IACnC,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEA,eAAsB,QACpB,QACA,MAQqB;AACrB,QAAM,WAAW,KAAK;AACtB,QAAM,OAAO,MAAM,KAAK,QAAQ;AAChC,MAAI,CAAC,KAAK,OAAO,GAAG;AAClB,UAAM,IAAI,MAAM,6BAA6B;AAAA,EAC/C;AACA,QAAM,cAAc,eAAe,QAAQ;AAC3C,QAAM,QAAQ,MAAMA,UAAS,QAAQ;AAErC,QAAM,SAAS,MAAM,OAAO,QAAuB;AAAA,IACjD,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,MAAM,EAAE,aAAa,UAAU,MAAM,WAAW;AAAA,EAClD,CAAC;AAED,QAAM,OAAO,QAAQ;AAAA,IACnB,QAAQ;AAAA,IACR,MAAM,OAAO;AAAA,IACb,KAAK;AAAA,IACL;AAAA,EACF,CAAC;AAED,QAAM,MAAM,MAAM,OAAO,QAAoB;AAAA,IAC3C,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,MAAM,EAAE,IAAI,KAAK,IAAI,aAAa,OAAO,aAAa,OAAO,QAAQ,KAAK,KAAK,EAAE;AAAA,IACjF,gBAAgB,WAAW;AAAA,EAC7B,CAAC;AAED,MAAI,CAAC,KAAK,KAAM,QAAO;AAEvB,QAAM,UAAU,KAAK,UAAU,CAAC,OAAe,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AACnF,QAAM,WAAW,KAAK,OAAO,KAAK,KAAK;AACvC,QAAM,WAAW,UAAU;AAC3B,MAAI,SAAqB;AACzB,UAAQ,KAAK,OAAO,KAAK,KAAK,IAAI,UAAU;AAC1C,QAAI,SAAS,IAAI,OAAO,iBAAiB,EAAG,QAAO;AACnD,UAAM,QAAQ,GAAI;AAClB,aAAS,MAAM,OAAO,QAAoB;AAAA,MACxC,QAAQ;AAAA,MACR,MAAM,iBAAiB,mBAAmB,IAAI,EAAE,CAAC;AAAA,IACnD,CAAC;AAAA,EACH;AACA,SAAO;AACT;;;AJrFA,IAAM,aAAa;AAAA,EACjB,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,WAAW;AAAA,IACT,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,WAAW;AAAA,IACT,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AACF;AAEA,SAAS,KAAK,OAAuB;AACnC,MAAI,iBAAiB,UAAU;AAC7B,UAAM;AAAA,EACR;AACA,QAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAChE;AAEO,IAAM,cAAc,cAAc;AAAA,EACvC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,IAAI;AAAA,MACF,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,GAAG;AAAA,EACL;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,UAAM,SAAS,MAAM,cAAc,IAAI;AACvC,UAAM,SAAS,aAAa;AAAA,MAC1B,QAAQ,OAAO;AAAA,MACf,QAAQ,cAAc,MAAM;AAAA,IAC9B,CAAC;AACD,QAAI;AACF,YAAM,MAAM,MAAM,QAAQ,QAAQ;AAAA,QAChC,MAAM,OAAO,KAAK,IAAI;AAAA,QACtB,IAAI,OAAO,KAAK,EAAE;AAAA,QAClB,OAAO,QAAQ,KAAK,KAAK;AAAA,QACzB,MAAM,QAAQ,KAAK,IAAI;AAAA,MACzB,CAAC;AACD,UAAI,OAAO,KAAM,WAAU,GAAG;AAAA,UACzB,gBAAe,GAAG;AAAA,IACzB,SAAS,OAAO;AACd,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AACF,CAAC;AAED,IAAM,gBAAgB,cAAc;AAAA,EAClC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,IAAI;AAAA,MACF,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,GAAG;AAAA,EACL;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,UAAM,SAAS,MAAM,cAAc,IAAI;AACvC,UAAM,SAAS,aAAa;AAAA,MAC1B,QAAQ,OAAO;AAAA,MACf,QAAQ,cAAc,MAAM;AAAA,IAC9B,CAAC;AACD,QAAI;AACF,YAAM,MAAM,MAAM,OAAO,QAAQ;AAAA,QAC/B,QAAQ;AAAA,QACR,MAAM,iBAAiB,mBAAmB,OAAO,KAAK,EAAE,CAAC,CAAC;AAAA,MAC5D,CAAC;AACD,UAAI,OAAO,KAAM,WAAU,GAAG;AAAA,UACzB,kBAAiB,GAA6C;AAAA,IACrE,SAAS,OAAO;AACd,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AACF,CAAC;AAED,IAAM,cAAc,cAAc;AAAA,EAChC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,GAAG;AAAA,EACL;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,UAAM,SAAS,MAAM,cAAc,IAAI;AACvC,UAAM,SAAS,aAAa;AAAA,MAC1B,QAAQ,OAAO;AAAA,MACf,QAAQ,cAAc,MAAM;AAAA,IAC9B,CAAC;AACD,UAAM,QAAQ,KAAK,IAAI,OAAO,KAAK,KAAK,KAAK,IAAI,GAAG;AACpD,UAAM,SAAS,OAAO,KAAK,MAAM,KAAK;AACtC,QAAI;AACF,YAAM,MAAM,MAAM,OAAO,QAAQ;AAAA,QAC/B,QAAQ;AAAA,QACR,MAAM,uBAAuB,KAAK,WAAW,MAAM;AAAA,MACrD,CAAC;AACD,UAAI,OAAO,KAAM,WAAU,GAAG;AAAA,UACzB,gBAAe,GAA2C;AAAA,IACjE,SAAS,OAAO;AACd,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AACF,CAAC;AAED,IAAM,oBAAoB,cAAc;AAAA,EACtC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,SAAS;AAAA,MACP,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,GAAG;AAAA,EACL;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,UAAM,SAAS,MAAM,cAAc,IAAI;AACvC,UAAM,SAAS,aAAa;AAAA,MAC1B,QAAQ,OAAO;AAAA,MACf,QAAQ,cAAc,MAAM;AAAA,IAC9B,CAAC;AACD,UAAM,UAAU,OAAO,KAAK,OAAO;AACnC,UAAM,cAAc,OAAO,SAAS,OAAO,KAAK,UAAU,IAAI,KAAK,MAAM,UAAU,GAAG,IAAI;AAC1F,QAAI;AACF,YAAM,MAAM,MAAM,OAAO,QAAkC;AAAA,QACzD,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,MAAM,EAAE,YAAY;AAAA,MACtB,CAAC;AACD,UAAI,OAAO,KAAM,WAAU,GAAG;AAAA,UACzB,WAAU,IAAI,eAAe,sBAAsB;AAAA,IAC1D,SAAS,OAAO;AACd,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AACF,CAAC;AAED,IAAM,iBAAiB,cAAc;AAAA,EACnC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,EACN,aAAa;AAAA,IACX,KAAK;AAAA,EACP;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,UAAM,SAAS,MAAM,cAAc,IAAI;AACvC,UAAM,SAAS,aAAa;AAAA,MAC1B,QAAQ,OAAO;AAAA,MACf,QAAQ,cAAc,MAAM;AAAA,IAC9B,CAAC;AACD,QAAI;AACF,YAAM,MAAM,MAAM,OAAO,QAAQ;AAAA,QAC/B,QAAQ;AAAA,QACR,MAAM;AAAA,MACR,CAAC;AACD,UAAI,OAAO,KAAM,WAAU,GAAG;AAAA,UACzB,mBAAkB,GAA8C;AAAA,IACvE,SAAS,OAAO;AACd,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AACF,CAAC;AAED,IAAM,mBAAmB,cAAc;AAAA,EACrC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,UAAM,OAAO,OAAO,KAAK,IAAI;AAC7B,UAAM,QAAQ,OAAO,KAAK,KAAK;AAC/B,QAAI,SAAS,aAAa,SAAS,UAAU;AAC3C,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AACA,UAAM,UAAU,MAAM,eAAe;AACrC,UAAM,OAAO,MAAM,gBAAgB,EAAE,GAAG,SAAS,QAAQ,MAAM,CAAC;AAChE,cAAU,oBAAoB,IAAI,EAAE;AAAA,EACtC;AACF,CAAC;AAED,IAAM,gBAAgB,cAAc;AAAA,EAClC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,aAAa;AAAA,IACX,KAAK;AAAA,EACP;AACF,CAAC;AAEM,IAAM,OAAO,cAAc;AAAA,EAChC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA,aAAa;AAAA,IACX,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AACF,CAAC;;;ADzQD,MAAM,QAAQ,IAAI;","names":["readFile"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/program.ts","../src/client.ts","../src/config.ts","../src/output.ts","../src/send.ts"],"sourcesContent":["import { runMain } from 'citty'\nimport { main } from './program.ts'\n\nawait runMain(main)\n","import { readFile, stat } from 'node:fs/promises'\nimport { defineCommand, renderUsage } from 'citty'\nimport { ApiError, createClient } from './client.ts'\nimport { readConfigFile, requireApiKey, resolveConfig, writeConfigFile } from './config.ts'\nimport {\n printBuyHuman,\n printCreditsHuman,\n printJson,\n printLine,\n printListHuman,\n printQuoteHuman,\n printSendHuman,\n printSkusHuman,\n printStatusHuman,\n} from './output.ts'\nimport { sendFax } from './send.ts'\n\nconst sharedArgs = {\n json: {\n type: 'boolean' as const,\n description: 'Print the API JSON body',\n },\n 'api-key': {\n type: 'string' as const,\n description: 'API key (sf_live_…). Overrides SINGLEFAX_API_KEY',\n },\n 'api-url': {\n type: 'string' as const,\n description: 'API base URL (default https://api.singlefax.com)',\n },\n}\n\nfunction fail(error: unknown): never {\n if (error instanceof ApiError) {\n throw error\n }\n throw error instanceof Error ? error : new Error(String(error))\n}\n\nexport const sendCommand = defineCommand({\n meta: {\n name: 'send',\n description: 'Upload a document and send it as a fax',\n },\n args: {\n file: {\n type: 'positional',\n description: 'Path to the PDF or office document',\n required: true,\n },\n to: {\n type: 'string',\n description: 'Destination fax number in E.164',\n required: true,\n },\n cover: {\n type: 'boolean',\n description: 'Prepend a cover page',\n },\n wait: {\n type: 'boolean',\n description: 'Poll until delivered or failed',\n },\n ...sharedArgs,\n },\n async run({ args }) {\n const config = await resolveConfig(args)\n const client = createClient({\n apiUrl: config.apiUrl,\n apiKey: requireApiKey(config),\n })\n try {\n const row = await sendFax(client, {\n file: String(args.file),\n to: String(args.to),\n cover: Boolean(args.cover),\n wait: Boolean(args.wait),\n })\n if (config.json) printJson(row)\n else printSendHuman(row)\n } catch (error) {\n fail(error)\n }\n },\n})\n\nconst statusCommand = defineCommand({\n meta: {\n name: 'status',\n description: 'Poll delivery status for an order you own',\n },\n args: {\n id: {\n type: 'positional',\n description: 'Fax order id',\n required: true,\n },\n ...sharedArgs,\n },\n async run({ args }) {\n const config = await resolveConfig(args)\n const client = createClient({\n apiUrl: config.apiUrl,\n apiKey: requireApiKey(config),\n })\n try {\n const row = await client.request({\n method: 'GET',\n path: `/api/v1/faxes/${encodeURIComponent(String(args.id))}`,\n })\n if (config.json) printJson(row)\n else printStatusHuman(row as Parameters<typeof printStatusHuman>[0])\n } catch (error) {\n fail(error)\n }\n },\n})\n\nconst listCommand = defineCommand({\n meta: {\n name: 'list',\n description: 'List recent fax orders',\n },\n args: {\n limit: {\n type: 'string',\n description: 'Page size (max 100)',\n default: '20',\n },\n offset: {\n type: 'string',\n description: 'Offset',\n default: '0',\n },\n ...sharedArgs,\n },\n async run({ args }) {\n const config = await resolveConfig(args)\n const client = createClient({\n apiUrl: config.apiUrl,\n apiKey: requireApiKey(config),\n })\n const limit = Math.min(Number(args.limit) || 20, 100)\n const offset = Number(args.offset) || 0\n try {\n const row = await client.request({\n method: 'GET',\n path: `/api/v1/faxes?limit=${limit}&offset=${offset}`,\n })\n if (config.json) printJson(row)\n else printListHuman(row as Parameters<typeof printListHuman>[0])\n } catch (error) {\n fail(error)\n }\n },\n})\n\nconst creditsBuyCommand = defineCommand({\n meta: {\n name: 'buy',\n description: 'Create a Stripe Checkout session for prepaid credits',\n },\n args: {\n dollars: {\n type: 'string',\n description: 'Credit pack in dollars (default 20)',\n default: '20',\n },\n ...sharedArgs,\n },\n async run({ args }) {\n const config = await resolveConfig(args)\n const client = createClient({\n apiUrl: config.apiUrl,\n apiKey: requireApiKey(config),\n })\n const dollars = Number(args.dollars)\n const amountCents = Number.isFinite(dollars) && dollars > 0 ? Math.round(dollars * 100) : 2000\n try {\n const row = await client.request<{ checkoutUrl?: string }>({\n method: 'POST',\n path: '/api/v1/credits/checkout',\n json: { amountCents },\n })\n if (config.json) printJson(row)\n else printLine(row.checkoutUrl || 'Checkout URL missing')\n } catch (error) {\n fail(error)\n }\n },\n})\n\nconst creditsCommand = defineCommand({\n meta: {\n name: 'credits',\n description: 'Show prepaid credit balance, or buy more',\n },\n args: sharedArgs,\n subCommands: {\n buy: creditsBuyCommand,\n },\n async run({ args }) {\n const config = await resolveConfig(args)\n const client = createClient({\n apiUrl: config.apiUrl,\n apiKey: requireApiKey(config),\n })\n try {\n const row = await client.request({\n method: 'GET',\n path: '/api/v1/credits',\n })\n if (config.json) printJson(row)\n else printCreditsHuman(row as Parameters<typeof printCreditsHuman>[0])\n } catch (error) {\n fail(error)\n }\n },\n})\n\nexport const skusCommand = defineCommand({\n meta: {\n name: 'skus',\n description: 'List product SKUs and prices (no API key required)',\n },\n args: sharedArgs,\n async run({ args }) {\n const config = await resolveConfig(args)\n const client = createClient({\n apiUrl: config.apiUrl,\n apiKey: config.apiKey,\n })\n try {\n const row = await client.request({\n method: 'GET',\n path: '/api/v1/skus',\n })\n if (config.json) printJson(row)\n else printSkusHuman(row as Parameters<typeof printSkusHuman>[0])\n } catch (error) {\n fail(error)\n }\n },\n})\n\nexport const quoteCommand = defineCommand({\n meta: {\n name: 'quote',\n description: 'Quote a retail send without charging',\n },\n args: {\n file: {\n type: 'positional',\n description: 'Optional PDF/office file for a live page count',\n required: false,\n },\n to: {\n type: 'string',\n description: 'Destination fax number in E.164',\n },\n pages: {\n type: 'string',\n description: 'Assumed billable pages when no file is given (default 10)',\n },\n ...sharedArgs,\n },\n async run({ args }) {\n const config = await resolveConfig(args)\n const client = createClient({\n apiUrl: config.apiUrl,\n apiKey: requireApiKey(config),\n })\n const file = args.file ? String(args.file) : ''\n let base64: string | undefined\n if (file) {\n const info = await stat(file)\n if (!info.isFile()) throw new Error('FILE must be a regular file')\n base64 = (await readFile(file)).toString('base64')\n }\n const pages = args.pages != null ? Number(args.pages) : undefined\n try {\n const row = await client.request({\n method: 'POST',\n path: '/api/v1/faxes/quote',\n json: {\n to: args.to ? String(args.to) : undefined,\n pages: Number.isFinite(pages) && pages && pages > 0 ? pages : undefined,\n base64,\n },\n })\n if (config.json) printJson(row)\n else printQuoteHuman(row as Parameters<typeof printQuoteHuman>[0])\n } catch (error) {\n fail(error)\n }\n },\n})\n\nexport const buyCommand = defineCommand({\n meta: {\n name: 'buy',\n description:\n 'Start Stripe Checkout for a SKU (credit_pack, inbound_30day, monthly-basic, annual-basic, monthly-business, annual-business, lifetime-basic, lifetime-pro)',\n },\n args: {\n sku: {\n type: 'positional',\n description: 'Catalog SKU id (see `singlefax skus`)',\n required: true,\n },\n dollars: {\n type: 'string',\n description: 'credit_pack amount in dollars (default 20)',\n },\n country: {\n type: 'string',\n description: 'US or CA for number SKUs (default US)',\n },\n state: {\n type: 'string',\n description: 'Optional US state or CA province for number SKUs',\n },\n ...sharedArgs,\n },\n async run({ args }) {\n const config = await resolveConfig(args)\n const client = createClient({\n apiUrl: config.apiUrl,\n apiKey: requireApiKey(config),\n })\n const sku = String(args.sku)\n const dollars = args.dollars != null ? Number(args.dollars) : undefined\n const amountCents =\n dollars != null && Number.isFinite(dollars) && dollars > 0\n ? Math.round(dollars * 100)\n : undefined\n const json = {\n sku,\n amountCents,\n country: args.country ? String(args.country).toUpperCase() : undefined,\n state: args.state ? String(args.state) : undefined,\n }\n try {\n let row: {\n status?: string\n sku?: string\n checkoutUrl?: string\n websiteUrl?: string\n phoneNumber?: string\n creditCents?: number\n notes?: string\n }\n try {\n row = await client.request({\n method: 'POST',\n path: '/api/v1/checkout',\n json,\n })\n } catch (error) {\n if (\n sku === 'credit_pack' &&\n error instanceof ApiError &&\n error.status === 404\n ) {\n const fallback = await client.request<{\n checkoutUrl?: string\n creditCents?: number\n }>({\n method: 'POST',\n path: '/api/v1/credits/checkout',\n json: { amountCents: amountCents ?? 2000 },\n })\n row = {\n status: 'awaiting_payment',\n sku: 'credit_pack',\n checkoutUrl: fallback.checkoutUrl,\n creditCents: fallback.creditCents,\n }\n } else {\n throw error\n }\n }\n if (config.json) printJson(row)\n else printBuyHuman(row)\n } catch (error) {\n fail(error)\n }\n },\n})\n\nconst configSetCommand = defineCommand({\n meta: {\n name: 'set',\n description: 'Write a local config value (api-key)',\n },\n args: {\n name: {\n type: 'positional',\n description: 'Name (only api-key is supported)',\n required: true,\n },\n value: {\n type: 'positional',\n description: 'Value',\n required: true,\n },\n },\n async run({ args }) {\n const name = String(args.name)\n const value = String(args.value)\n if (name !== 'api-key' && name !== 'apiKey') {\n throw new Error('Supported keys: api-key')\n }\n const current = await readConfigFile()\n const path = await writeConfigFile({ ...current, apiKey: value })\n printLine(`Saved API key to ${path}`)\n },\n})\n\nconst configCommand = defineCommand({\n meta: {\n name: 'config',\n description: 'Store API credentials locally',\n },\n subCommands: {\n set: configSetCommand,\n },\n})\n\nexport const main = defineCommand({\n meta: {\n name: 'singlefax',\n version: '1.0.0',\n description: 'SingleFax CLI — send faxes, quote, and buy SKUs via API key',\n },\n subCommands: {\n send: sendCommand,\n status: statusCommand,\n list: listCommand,\n quote: quoteCommand,\n skus: skusCommand,\n buy: buyCommand,\n credits: creditsCommand,\n config: configCommand,\n },\n})\n\nexport async function usageText(): Promise<string> {\n return renderUsage(main)\n}\n","export class ApiError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly body: unknown,\n ) {\n super(message)\n this.name = 'ApiError'\n }\n}\n\nexport type ApiClient = {\n apiUrl: string\n request: <T>(opts: {\n method: string\n path: string\n json?: unknown\n raw?: Buffer | Uint8Array\n contentType?: string\n idempotencyKey?: string\n }) => Promise<T>\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nfunction formatError(status: number, body: unknown): string {\n if (body && typeof body === 'object') {\n const row = body as {\n statusMessage?: string\n message?: string\n data?: { code?: string; resolution?: string }\n }\n const headline = row.statusMessage || row.message || `HTTP ${status}`\n const resolution = row.data?.resolution\n return resolution ? `${headline}\\n${resolution}` : headline\n }\n return `HTTP ${status}`\n}\n\nfunction retryAfterMs(res: Response): number {\n const header = res.headers.get('retry-after')\n const seconds = header ? Number(header) : 1\n if (!Number.isFinite(seconds) || seconds < 1) return 1000\n return Math.min(seconds, 60) * 1000\n}\n\nexport function createClient(opts: {\n apiUrl: string\n apiKey?: string\n fetch?: typeof fetch\n sleep?: (ms: number) => Promise<void>\n}): ApiClient {\n const doFetch = opts.fetch ?? globalThis.fetch\n const doSleep = opts.sleep ?? sleep\n const apiUrl = opts.apiUrl.replace(/\\/$/, '')\n const apiKey = opts.apiKey?.trim() || ''\n\n async function request<T>(input: {\n method: string\n path: string\n json?: unknown\n raw?: Buffer | Uint8Array\n contentType?: string\n idempotencyKey?: string\n }): Promise<T> {\n const url = input.path.startsWith('http') ? input.path : `${apiUrl}${input.path}`\n const headers: Record<string, string> = {\n accept: 'application/json',\n }\n if (apiKey) headers.authorization = `Bearer ${apiKey}`\n let body: BodyInit | undefined\n if (input.raw) {\n headers['content-type'] = input.contentType || 'application/octet-stream'\n body = input.raw as BodyInit\n } else if (input.json !== undefined) {\n headers['content-type'] = 'application/json'\n body = JSON.stringify(input.json)\n }\n if (input.idempotencyKey) headers['idempotency-key'] = input.idempotencyKey\n\n const send = () =>\n doFetch(url, {\n method: input.method,\n headers,\n body,\n })\n\n let res = await send()\n if (res.status === 429) {\n await doSleep(retryAfterMs(res))\n res = await send()\n }\n\n const text = await res.text()\n let parsed: unknown = text\n if (text) {\n try {\n parsed = JSON.parse(text)\n } catch {\n parsed = { message: text }\n }\n } else {\n parsed = {}\n }\n\n if (!res.ok) {\n throw new ApiError(formatError(res.status, parsed), res.status, parsed)\n }\n return parsed as T\n }\n\n return { apiUrl, request }\n}\n","import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\n\nexport const DEFAULT_API_URL = 'https://api.singlefax.com'\n\nexport type CliConfigFile = {\n apiKey?: string\n apiUrl?: string\n}\n\nexport function configFilePath(): string {\n if (process.env.SINGLEFAX_CONFIG_PATH) return process.env.SINGLEFAX_CONFIG_PATH\n const xdg = process.env.XDG_CONFIG_HOME || join(homedir(), '.config')\n return join(xdg, 'singlefax', 'config.json')\n}\n\nexport async function readConfigFile(): Promise<CliConfigFile> {\n try {\n const raw = await readFile(configFilePath(), 'utf8')\n const parsed = JSON.parse(raw) as CliConfigFile\n return parsed && typeof parsed === 'object' ? parsed : {}\n } catch {\n return {}\n }\n}\n\nexport async function writeConfigFile(next: CliConfigFile): Promise<string> {\n const path = configFilePath()\n await mkdir(dirname(path), { recursive: true, mode: 0o700 })\n await writeFile(path, `${JSON.stringify(next, null, 2)}\\n`, { mode: 0o600 })\n await chmod(path, 0o600)\n return path\n}\n\nexport type ResolvedConfig = {\n apiUrl: string\n apiKey: string\n json: boolean\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value.trim() : undefined\n}\n\nexport async function resolveConfig(args: Record<string, unknown>): Promise<ResolvedConfig> {\n const file = await readConfigFile()\n const apiUrl =\n asString(args['api-url']) ||\n asString(args.apiUrl) ||\n asString(process.env.SINGLEFAX_API_URL) ||\n file.apiUrl ||\n DEFAULT_API_URL\n const apiKey =\n asString(args['api-key']) ||\n asString(args.apiKey) ||\n asString(process.env.SINGLEFAX_API_KEY) ||\n file.apiKey ||\n ''\n return {\n apiUrl: apiUrl.replace(/\\/$/, ''),\n apiKey,\n json: Boolean(args.json),\n }\n}\n\nexport function requireApiKey(config: ResolvedConfig): string {\n if (config.apiKey) return config.apiKey\n throw new Error(\n 'Missing API key. Set SINGLEFAX_API_KEY, pass --api-key, or run: singlefax config set api-key sf_live_…',\n )\n}\n","export function printJson(value: unknown): void {\n process.stdout.write(`${JSON.stringify(value, null, 2)}\\n`)\n}\n\nexport function printLine(line: string): void {\n process.stdout.write(`${line}\\n`)\n}\n\nfunction cents(value: unknown): string {\n const n = Number(value)\n if (!Number.isFinite(n)) return '—'\n return `$${(n / 100).toFixed(2)}`\n}\n\nexport function printSendHuman(row: {\n id?: string\n fulfillmentStatus?: string\n billablePages?: number\n totalCents?: number\n subtotalCents?: number\n}): void {\n printLine(\n [\n row.fulfillmentStatus || 'queued',\n row.id || '',\n row.billablePages != null ? `${row.billablePages} pages` : '',\n cents(row.totalCents ?? row.subtotalCents),\n ]\n .filter(Boolean)\n .join(' '),\n )\n}\n\nexport function printStatusHuman(row: {\n id?: string\n fulfillmentStatus?: string\n billablePages?: number\n totalCents?: number\n subtotalCents?: number\n failureClass?: string | null\n}): void {\n const parts = [\n row.fulfillmentStatus || 'unknown',\n row.id || '',\n row.billablePages != null ? `${row.billablePages} pages` : '',\n cents(row.totalCents ?? row.subtotalCents),\n ]\n if (row.failureClass) parts.push(row.failureClass)\n printLine(parts.filter(Boolean).join(' '))\n}\n\nexport function printListHuman(payload: {\n data?: Array<{\n id?: string\n fulfillmentStatus?: string\n billablePages?: number\n totalCents?: number\n destinationCountry?: string | null\n }>\n}): void {\n const rows = payload.data ?? []\n if (!rows.length) {\n printLine('No faxes.')\n return\n }\n for (const row of rows) {\n printLine(\n [\n row.id || '',\n row.fulfillmentStatus || '',\n row.billablePages != null ? `${row.billablePages}p` : '',\n cents(row.totalCents),\n row.destinationCountry || '',\n ]\n .filter((part) => part !== '')\n .join(' '),\n )\n }\n}\n\nexport function printCreditsHuman(row: {\n availableCents?: number\n reservedCents?: number\n}): void {\n printLine(\n `available ${cents(row.availableCents)} reserved ${cents(row.reservedCents)}`,\n )\n}\n\nexport function printSkusHuman(payload: {\n skus?: Array<{\n id?: string\n title?: string\n priceLabel?: string\n availability?: string\n websiteUrl?: string\n }>\n}): void {\n const rows = payload.skus ?? []\n if (!rows.length) {\n printLine('No SKUs.')\n return\n }\n for (const row of rows) {\n printLine(\n [\n row.id || '',\n row.availability || '',\n row.priceLabel || '',\n row.title || '',\n row.availability === 'website' ? row.websiteUrl || '' : '',\n ]\n .filter((part) => part !== '')\n .join(' '),\n )\n }\n}\n\nexport function printQuoteHuman(row: {\n sku?: string\n to?: string\n billablePages?: number\n subtotalCents?: number\n destinationCountry?: string\n priceLabel?: string\n assumedPages?: boolean\n}): void {\n if (row.subtotalCents == null && row.priceLabel) {\n printLine(row.priceLabel)\n return\n }\n printLine(\n [\n row.to || '',\n row.billablePages != null ? `${row.billablePages} pages` : '',\n cents(row.subtotalCents),\n row.destinationCountry || '',\n row.assumedPages ? '(assumed pages)' : '',\n ]\n .filter((part) => part !== '')\n .join(' '),\n )\n}\n\nexport function printBuyHuman(row: {\n status?: string\n sku?: string\n checkoutUrl?: string\n websiteUrl?: string\n phoneNumber?: string\n creditCents?: number\n notes?: string\n}): void {\n const url = row.checkoutUrl || row.websiteUrl || ''\n printLine(\n [\n row.status || '',\n row.sku || '',\n row.phoneNumber || '',\n row.creditCents != null ? cents(row.creditCents) : '',\n ]\n .filter((part) => part !== '')\n .join(' '),\n )\n if (url) printLine(url)\n if (row.notes && row.status === 'website_only') printLine(row.notes)\n}\n","import { randomUUID } from 'node:crypto'\nimport { readFile, stat } from 'node:fs/promises'\nimport { extname } from 'node:path'\nimport type { ApiClient } from './client.ts'\n\nconst TERMINAL = new Set(['delivered', 'failed_final'])\n\nexport type UploadCreated = {\n uploadToken: string\n uploadUrl: string\n contentType?: string\n maxBytes?: number\n}\n\nexport type FaxCreated = {\n id: string\n fulfillmentStatus: string\n billablePages?: number\n totalCents?: number\n subtotalCents?: number\n destinationCountry?: string\n}\n\nfunction contentTypeFor(file: string): string {\n switch (extname(file).toLowerCase()) {\n case '.pdf':\n return 'application/pdf'\n case '.png':\n return 'image/png'\n case '.jpg':\n case '.jpeg':\n return 'image/jpeg'\n case '.tif':\n case '.tiff':\n return 'image/tiff'\n case '.doc':\n return 'application/msword'\n case '.docx':\n return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'\n default:\n return 'application/pdf'\n }\n}\n\nexport async function sendFax(\n client: ApiClient,\n opts: {\n file: string\n to: string\n cover?: boolean\n wait?: boolean\n sleep?: (ms: number) => Promise<void>\n now?: () => number\n },\n): Promise<FaxCreated> {\n const filePath = opts.file\n const info = await stat(filePath)\n if (!info.isFile()) {\n throw new Error('FILE must be a regular file')\n }\n const contentType = contentTypeFor(filePath)\n const bytes = await readFile(filePath)\n\n const upload = await client.request<UploadCreated>({\n method: 'POST',\n path: '/api/v1/uploads',\n json: { contentType, maxBytes: bytes.byteLength },\n })\n\n await client.request({\n method: 'PUT',\n path: upload.uploadUrl,\n raw: bytes,\n contentType,\n })\n\n const fax = await client.request<FaxCreated>({\n method: 'POST',\n path: '/api/v1/faxes',\n json: { to: opts.to, uploadToken: upload.uploadToken, cover: Boolean(opts.cover) },\n idempotencyKey: randomUUID(),\n })\n\n if (!opts.wait) return fax\n\n const doSleep = opts.sleep ?? ((ms: number) => new Promise((r) => setTimeout(r, ms)))\n const started = (opts.now ?? Date.now)()\n const deadline = started + 180_000\n let latest: FaxCreated = fax\n while ((opts.now ?? Date.now)() < deadline) {\n if (TERMINAL.has(latest.fulfillmentStatus)) return latest\n await doSleep(2000)\n latest = await client.request<FaxCreated>({\n method: 'GET',\n path: `/api/v1/faxes/${encodeURIComponent(fax.id)}`,\n })\n }\n return latest\n}\n"],"mappings":"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","names":["readFile","stat","readFile","stat","readFile"]}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@singlefax/cli",
3
- "version": "1.0.0",
4
- "description": "SingleFax CLI — send faxes with an API key and prepaid credits",
3
+ "version": "1.0.1",
4
+ "description": "SingleFax CLI — send faxes, quote, and buy SKUs with an API key",
5
5
  "type": "module",
6
6
  "bin": {
7
- "singlefax": "./dist/cli.mjs"
7
+ "singlefax": "dist/cli.mjs"
8
8
  },
9
9
  "files": [
10
10
  "dist",
@@ -30,10 +30,10 @@
30
30
  "api"
31
31
  ],
32
32
  "license": "MIT",
33
- "homepage": "https://singlefax.com/agents",
33
+ "homepage": "https://singlefax.com",
34
34
  "repository": {
35
35
  "type": "git",
36
- "url": "https://github.com/SingleFax/singlefax-app.git",
36
+ "url": "git+https://github.com/SingleFax/singlefax-app.git",
37
37
  "directory": "packages/cli"
38
38
  },
39
39
  "bugs": {