frontpage-host 0.1.20 → 0.1.22

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.
@@ -2,9 +2,11 @@ import { readFile } from "node:fs/promises";
2
2
  import { resolveSlug } from "../site.js";
3
3
  import { CliHttpError, EXIT_OK, EXIT_USAGE, v1Get, v1Patch, v1Post, v1Put, } from "../http/client.js";
4
4
  import { assertNoUnknownFlags, hasFlag, takeFlag } from "../argv.js";
5
- import { emitErr, emitOk, emitUsage, isJson } from "../runtime.js";
5
+ import { emitErr, emitOk, emitUsage, isJson, requireYesOrTty } from "../runtime.js";
6
+ import { openBrowser, resolveOpenUrl } from "../modules/oauth.js";
6
7
  import { SHOP_ID_RE, VARIANT_ID_RE } from "./shop-ids.js";
7
- const USAGE = "Usage: frontpage shop items|variants|categories|settings|orders|payments … [--site <slug>] [--json]";
8
+ const USAGE = "Usage: frontpage store items|variants|categories|settings|orders|payments|connect|status|disconnect|import … [--site <slug>] [--json]";
9
+ const CONNECT_FINISH = "Open the Frontpage URL while logged in, finish Stripe in the browser, then re-run status. This CLI cannot complete OAuth.";
8
10
  const CENTS_HINT = "$49.99 is 4999";
9
11
  function takeAll(work, name) {
10
12
  const out = [];
@@ -20,10 +22,10 @@ function requireShopId(work, label = "Item id", opts) {
20
22
  const positional = work.find((a) => !a.startsWith("-"));
21
23
  const raw = fromFlag ?? (positional ? work.splice(work.indexOf(positional), 1)[0] : undefined);
22
24
  if (!raw) {
23
- throw new CliHttpError(`${label} is required. Run: frontpage help shop`, EXIT_USAGE, 0, "bad_request");
25
+ throw new CliHttpError(`${label} is required. Run: frontpage help store`, EXIT_USAGE, 0, "bad_request");
24
26
  }
25
27
  if (!SHOP_ID_RE.test(raw)) {
26
- throw new CliHttpError("Item not found.", EXIT_USAGE, 404, "not_found");
28
+ throw new CliHttpError("Item id must be kebab-case, like camp-hat (2–40 letters, numbers, or hyphens).", EXIT_USAGE, 0, "bad_request");
27
29
  }
28
30
  return raw;
29
31
  }
@@ -34,7 +36,7 @@ function requireVariantId(raw, required = true) {
34
36
  throw new CliHttpError("Variant id is required. Use --variant m or --id m.", EXIT_USAGE, 0, "bad_request");
35
37
  }
36
38
  if (!VARIANT_ID_RE.test(raw)) {
37
- throw new CliHttpError("Variant not found.", EXIT_USAGE, 404, "not_found");
39
+ throw new CliHttpError("Version id must be kebab-case, like s, m, or camp-hat-m.", EXIT_USAGE, 0, "bad_request");
38
40
  }
39
41
  return raw;
40
42
  }
@@ -43,7 +45,7 @@ function requireLedgerId(work, kind) {
43
45
  const positional = work.find((a) => !a.startsWith("-"));
44
46
  const raw = fromFlag ?? (positional ? work.splice(work.indexOf(positional), 1)[0] : undefined);
45
47
  if (!raw || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(raw)) {
46
- throw new CliHttpError(`${kind} not found.`, EXIT_USAGE, 404, "not_found");
48
+ throw new CliHttpError(`${kind} id must be a UUID. Run: frontpage store ${kind.toLowerCase()}s list`, EXIT_USAGE, 0, "bad_request");
47
49
  }
48
50
  return raw;
49
51
  }
@@ -144,7 +146,7 @@ function printItem(row) {
144
146
  const bits = [
145
147
  row.checkout ?? "",
146
148
  row.amount ?? "",
147
- prices.length ? prices.join(",") : "",
149
+ prices.length ? `${prices.join(",")} cents` : "",
148
150
  stocks.length ? `stock ${stocks.join(",")}` : "",
149
151
  row.active === false ? "inactive" : "active",
150
152
  row.categories?.length ? row.categories.join(",") : "",
@@ -170,7 +172,7 @@ export async function runShop(args) {
170
172
  const listWork = sub === "list" ? work : rest.slice(1);
171
173
  const archived = hasFlag(listWork, "--archived");
172
174
  const category = takeFlag(listWork, "--category");
173
- assertNoUnknownFlags(listWork, "shop");
175
+ assertNoUnknownFlags(listWork, "store");
174
176
  const qs = new URLSearchParams();
175
177
  if (archived)
176
178
  qs.set("archived", "1");
@@ -184,7 +186,7 @@ export async function runShop(args) {
184
186
  }
185
187
  const rows = data.items ?? [];
186
188
  if (rows.length === 0) {
187
- console.log(archived ? "No items." : "No items.");
189
+ console.log(category ? "No products in that category." : "No products in this store.");
188
190
  return EXIT_OK;
189
191
  }
190
192
  for (const row of rows)
@@ -193,7 +195,7 @@ export async function runShop(args) {
193
195
  }
194
196
  if (sub === "get") {
195
197
  const itemId = requireShopId(work);
196
- assertNoUnknownFlags(work, "shop");
198
+ assertNoUnknownFlags(work, "store");
197
199
  const data = await v1Get(`${base}/items/${encodeURIComponent(itemId)}`);
198
200
  if (isJson()) {
199
201
  emitOk("shop", data);
@@ -205,7 +207,7 @@ export async function runShop(args) {
205
207
  const stock = v.stock == null ? "unlimited" : String(v.stock);
206
208
  const off = v.active === false ? " inactive" : "";
207
209
  const archived = v.archivedAt ? " archived" : "";
208
- console.log(` ${v.variantId} ${v.priceCents}${sale} stock ${stock}${off}${archived}`);
210
+ console.log(` ${v.variantId} ${v.priceCents} cents${sale} stock ${stock}${off}${archived}`);
209
211
  }
210
212
  return EXIT_OK;
211
213
  }
@@ -224,12 +226,11 @@ export async function runShop(args) {
224
226
  const categories = parseCsv(takeFlag(work, "--categories"));
225
227
  const priceCents = parseCentsFlag(takeFlag(work, "--price-cents"), "priceCents");
226
228
  const discountCents = parseCentsFlag(takeFlag(work, "--discount-cents"), "discountPriceCents");
227
- const shippingCents = parseCentsFlag(takeFlag(work, "--shipping-cents"), "shippingCents");
228
229
  const stockRaw = takeFlag(work, "--stock");
229
230
  const variantId = takeFlag(work, "--variant-id") ?? takeFlag(work, "--variant");
230
231
  const options = parseOptions(takeAll(work, "--option"));
231
232
  const images = parseUrls(work);
232
- assertNoUnknownFlags(work, "shop");
233
+ assertNoUnknownFlags(work, "store");
233
234
  const body = file ? await readJsonFile(file) : {};
234
235
  if (itemId)
235
236
  body.itemId = itemId;
@@ -251,8 +252,6 @@ export async function runShop(args) {
251
252
  body.categories = categories;
252
253
  if (images.length)
253
254
  body.images = images;
254
- if (body.checkout == null)
255
- body.checkout = "cart";
256
255
  if (body.amount == null)
257
256
  body.amount = "fixed";
258
257
  if (!Array.isArray(body.variants) && priceCents !== undefined) {
@@ -263,13 +262,12 @@ export async function runShop(args) {
263
262
  ...(options ? { options } : {}),
264
263
  priceCents,
265
264
  ...(discountCents !== undefined ? { discountPriceCents: discountCents } : {}),
266
- ...(shippingCents !== undefined ? { shippingCents } : {}),
267
265
  ...(stock !== undefined ? { stock } : {}),
268
266
  },
269
267
  ];
270
268
  }
271
269
  if (typeof body.itemId !== "string" || !SHOP_ID_RE.test(body.itemId)) {
272
- throw new CliHttpError("Usage: frontpage shop items create <itemId> --title … --price-cents 4999", EXIT_USAGE, 0, "bad_request");
270
+ throw new CliHttpError("Item id must be kebab-case, like camp-hat (2–40 letters, numbers, or hyphens). Usage: frontpage store items create camp-hat --title \"Camp hat\" --price-cents 4999", EXIT_USAGE, 0, "bad_request");
273
271
  }
274
272
  const data = await v1Post(`${base}/items`, body);
275
273
  if (isJson()) {
@@ -290,7 +288,7 @@ export async function runShop(args) {
290
288
  const active = parseBoolFlag(takeFlag(work, "--active"), "active");
291
289
  const categories = parseCsv(takeFlag(work, "--categories"));
292
290
  const images = parseUrls(work);
293
- assertNoUnknownFlags(work, "shop");
291
+ assertNoUnknownFlags(work, "store");
294
292
  const body = {};
295
293
  if (title !== undefined)
296
294
  body.title = title;
@@ -323,7 +321,7 @@ export async function runShop(args) {
323
321
  }
324
322
  if (sub === "archive") {
325
323
  const itemId = requireShopId(work);
326
- assertNoUnknownFlags(work, "shop");
324
+ assertNoUnknownFlags(work, "store");
327
325
  const data = await v1Post(`${base}/items/${encodeURIComponent(itemId)}/archive`, {});
328
326
  if (isJson()) {
329
327
  emitOk("shop", data);
@@ -336,7 +334,7 @@ export async function runShop(args) {
336
334
  const itemId = requireShopId(work);
337
335
  const off = hasFlag(work, "--off");
338
336
  const active = off ? false : parseBoolFlag(takeFlag(work, "--active"), "active");
339
- assertNoUnknownFlags(work, "shop");
337
+ assertNoUnknownFlags(work, "store");
340
338
  const data = await v1Post(`${base}/items/${encodeURIComponent(itemId)}/activate`, { active: active ?? true });
341
339
  if (isJson()) {
342
340
  emitOk("shop", data);
@@ -348,7 +346,7 @@ export async function runShop(args) {
348
346
  if (sub === "images") {
349
347
  const itemId = requireShopId(work);
350
348
  const urls = parseUrls(work);
351
- assertNoUnknownFlags(work, "shop");
349
+ assertNoUnknownFlags(work, "store");
352
350
  const data = await v1Put(`${base}/items/${encodeURIComponent(itemId)}/images`, { images: urls });
353
351
  if (isJson()) {
354
352
  emitOk("shop", data);
@@ -363,7 +361,7 @@ export async function runShop(args) {
363
361
  const addRaw = takeFlag(work, "--add");
364
362
  const removeRaw = takeFlag(work, "--remove");
365
363
  const setRaw = takeFlag(work, "--set");
366
- assertNoUnknownFlags(work, "shop");
364
+ assertNoUnknownFlags(work, "store");
367
365
  const present = [addRaw, removeRaw, setRaw].filter((v) => v !== undefined).length;
368
366
  if (present !== 1) {
369
367
  throw new CliHttpError("Pass either --add 5, --remove 5, or --set 20. --add / --remove become signed delta.", EXIT_USAGE, 0, "bad_request");
@@ -414,13 +412,12 @@ export async function runShop(args) {
414
412
  const variantId = requireVariantId(takeFlag(work, "--id") ?? takeFlag(work, "--variant"));
415
413
  const priceCents = parseCentsFlag(takeFlag(work, "--price-cents"), "priceCents");
416
414
  const discountCents = parseCentsFlag(takeFlag(work, "--discount-cents"), "discountPriceCents");
417
- const shippingCents = parseCentsFlag(takeFlag(work, "--shipping-cents"), "shippingCents");
418
415
  const stockRaw = takeFlag(work, "--stock");
419
416
  const options = parseOptions(takeAll(work, "--option"));
420
417
  const active = parseBoolFlag(takeFlag(work, "--active"), "active");
421
- assertNoUnknownFlags(work, "shop");
418
+ assertNoUnknownFlags(work, "store");
422
419
  if (priceCents === undefined) {
423
- throw new CliHttpError("Usage: frontpage shop variants add <itemId> --id m --price-cents 4999", EXIT_USAGE, 0, "bad_request");
420
+ throw new CliHttpError("Usage: frontpage store variants add <itemId> --id m --price-cents 4999", EXIT_USAGE, 0, "bad_request");
424
421
  }
425
422
  const stock = stockRaw === undefined ? undefined : stockRaw === "unlimited" ? null : parseCountFlag(stockRaw, "stock");
426
423
  const data = await v1Post(`${base}/items/${encodeURIComponent(itemId)}/variants`, {
@@ -428,7 +425,6 @@ export async function runShop(args) {
428
425
  options,
429
426
  priceCents,
430
427
  discountPriceCents: discountCents,
431
- shippingCents,
432
428
  stock,
433
429
  active,
434
430
  });
@@ -444,18 +440,15 @@ export async function runShop(args) {
444
440
  const variantId = requireVariantId(takeFlag(work, "--id") ?? takeFlag(work, "--variant"));
445
441
  const priceCents = parseCentsFlag(takeFlag(work, "--price-cents"), "priceCents");
446
442
  const discountCents = parseCentsFlag(takeFlag(work, "--discount-cents"), "discountPriceCents");
447
- const shippingCents = parseCentsFlag(takeFlag(work, "--shipping-cents"), "shippingCents");
448
443
  const stockRaw = takeFlag(work, "--stock");
449
444
  const options = parseOptions(takeAll(work, "--option"));
450
445
  const active = parseBoolFlag(takeFlag(work, "--active"), "active");
451
- assertNoUnknownFlags(work, "shop");
446
+ assertNoUnknownFlags(work, "store");
452
447
  const body = {};
453
448
  if (priceCents !== undefined)
454
449
  body.priceCents = priceCents;
455
450
  if (discountCents !== undefined)
456
451
  body.discountPriceCents = discountCents;
457
- if (shippingCents !== undefined)
458
- body.shippingCents = shippingCents;
459
452
  if (stockRaw !== undefined)
460
453
  body.stock = stockRaw === "unlimited" ? null : parseCountFlag(stockRaw, "stock");
461
454
  if (options)
@@ -476,7 +469,7 @@ export async function runShop(args) {
476
469
  if (sub === "archive") {
477
470
  const itemId = requireShopId(work, "Item id", { allowIdFlag: false });
478
471
  const variantId = requireVariantId(takeFlag(work, "--id") ?? takeFlag(work, "--variant"));
479
- assertNoUnknownFlags(work, "shop");
472
+ assertNoUnknownFlags(work, "store");
480
473
  const data = await v1Post(`${base}/items/${encodeURIComponent(itemId)}/variants/${encodeURIComponent(variantId)}/archive`, {});
481
474
  if (isJson()) {
482
475
  emitOk("shop", data);
@@ -493,7 +486,7 @@ export async function runShop(args) {
493
486
  if (!sub || sub === "list" || sub.startsWith("-")) {
494
487
  const listWork = sub === "list" ? work : rest.slice(1);
495
488
  const archived = hasFlag(listWork, "--archived");
496
- assertNoUnknownFlags(listWork, "shop");
489
+ assertNoUnknownFlags(listWork, "store");
497
490
  const qs = archived ? "?archived=1" : "";
498
491
  const data = await v1Get(`${base}/categories${qs}`);
499
492
  if (isJson()) {
@@ -515,9 +508,9 @@ export async function runShop(args) {
515
508
  if (slug && work.includes(slug))
516
509
  work.splice(work.indexOf(slug), 1);
517
510
  const label = takeFlag(work, "--label") ?? takeFlag(work, "--title");
518
- assertNoUnknownFlags(work, "shop");
511
+ assertNoUnknownFlags(work, "store");
519
512
  if (!slug || !SHOP_ID_RE.test(slug) || !label) {
520
- throw new CliHttpError("Usage: frontpage shop categories create hats --label Hats", EXIT_USAGE, 0, "bad_request");
513
+ throw new CliHttpError("Usage: frontpage store categories create hats --label Hats", EXIT_USAGE, 0, "bad_request");
521
514
  }
522
515
  const data = await v1Post(`${base}/categories`, {
523
516
  slug,
@@ -533,9 +526,9 @@ export async function runShop(args) {
533
526
  if (sub === "rename") {
534
527
  const slug = requireShopId(work, "Category slug");
535
528
  const label = takeFlag(work, "--label") ?? takeFlag(work, "--title");
536
- assertNoUnknownFlags(work, "shop");
529
+ assertNoUnknownFlags(work, "store");
537
530
  if (!label) {
538
- throw new CliHttpError("Usage: frontpage shop categories rename hats --label Hats", EXIT_USAGE, 0, "bad_request");
531
+ throw new CliHttpError("Usage: frontpage store categories rename hats --label Hats", EXIT_USAGE, 0, "bad_request");
539
532
  }
540
533
  const data = await v1Patch(`${base}/categories/${encodeURIComponent(slug)}`, { label });
541
534
  if (isJson()) {
@@ -547,7 +540,7 @@ export async function runShop(args) {
547
540
  }
548
541
  if (sub === "archive") {
549
542
  const slug = requireShopId(work, "Category slug");
550
- assertNoUnknownFlags(work, "shop");
543
+ assertNoUnknownFlags(work, "store");
551
544
  const data = await v1Post(`${base}/categories/${encodeURIComponent(slug)}/archive`, {});
552
545
  if (isJson()) {
553
546
  emitOk("shop", data);
@@ -562,7 +555,7 @@ export async function runShop(args) {
562
555
  const sub = rest[1] ?? "get";
563
556
  const work = rest.slice(2);
564
557
  if (sub === "get") {
565
- assertNoUnknownFlags(work, "shop");
558
+ assertNoUnknownFlags(work, "store");
566
559
  const data = await v1Get(`${base}/settings`);
567
560
  if (isJson()) {
568
561
  emitOk("shop", data);
@@ -571,19 +564,31 @@ export async function runShop(args) {
571
564
  const s = data.settings ?? {};
572
565
  const fee = (s.checkoutFee ?? {});
573
566
  const fulfill = (s.fulfillment ?? {});
574
- const feeText = fee.enabled ? `checkout-fee ${fee.cents ?? 0}` : "checkout-fee off";
575
- console.log(`${s.currency ?? "(no currency)"} ${fulfill.shipping ?? "(no fulfillment)"} ${feeText}`);
567
+ const methods = [
568
+ fulfill.pickupEnabled ? "pickup" : null,
569
+ fulfill.shippingEnabled ? "delivery" : null,
570
+ ].filter(Boolean);
571
+ const fulfillText = methods.length > 0 ? methods.join("+") : fulfill.shipping ?? "(no fulfillment)";
572
+ const checkoutText = s.storeCheckout === "instant" ? "pay-now" : "cart";
573
+ const feeText = fee.enabled ? `service-fee ${fee.cents ?? 0}` : "service-fee off";
574
+ console.log(`${s.currency ?? "(no currency)"} ${fulfillText} ${checkoutText} ${feeText}`);
576
575
  return EXIT_OK;
577
576
  }
578
577
  if (sub === "set-fulfillment") {
579
578
  const mode = takeFlag(work, "--mode") ?? takeFlag(work, "--shipping");
579
+ const pickupOn = takeBoolish(work, "--pickup", "pickupEnabled");
580
+ const deliveryOn = takeBoolish(work, "--delivery", "shippingEnabled");
581
+ const pickupLocationName = takeFlag(work, "--pickup-location");
582
+ const pickupAddress = takeFlag(work, "--pickup-address");
583
+ const pickupHours = takeFlag(work, "--pickup-hours");
584
+ const pickupInstructions = takeFlag(work, "--pickup-instructions");
580
585
  const flatCents = parseCentsFlag(takeFlag(work, "--flat-cents"), "flatRateCents");
581
586
  const ratesRaw = takeFlag(work, "--country-rates");
582
587
  const restOn = takeBoolish(work, "--rest-of-world", "restOfWorldEnabled");
583
588
  const restCents = parseCentsFlag(takeFlag(work, "--rest-cents"), "restOfWorldCents");
584
589
  const freeOn = takeBoolish(work, "--free-over", "freeShippingOverEnabled");
585
590
  const freeCents = parseCentsFlag(takeFlag(work, "--free-over-cents"), "freeShippingOverCents");
586
- assertNoUnknownFlags(work, "shop");
591
+ assertNoUnknownFlags(work, "store");
587
592
  let countryRates;
588
593
  if (ratesRaw) {
589
594
  try {
@@ -595,6 +600,12 @@ export async function runShop(args) {
595
600
  }
596
601
  const data = await v1Post(`${base}/settings/fulfillment`, {
597
602
  shipping: mode,
603
+ ...(pickupOn !== undefined ? { pickupEnabled: pickupOn } : {}),
604
+ ...(deliveryOn !== undefined ? { shippingEnabled: deliveryOn } : {}),
605
+ ...(pickupLocationName !== undefined ? { pickupLocationName } : {}),
606
+ ...(pickupAddress !== undefined ? { pickupAddress } : {}),
607
+ ...(pickupHours !== undefined ? { pickupHours } : {}),
608
+ ...(pickupInstructions !== undefined ? { pickupInstructions } : {}),
598
609
  flatRateCents: flatCents,
599
610
  countryRates,
600
611
  restOfWorldEnabled: restOn,
@@ -610,9 +621,9 @@ export async function runShop(args) {
610
621
  }
611
622
  if (sub === "set-currency") {
612
623
  const currency = takeFlag(work, "--currency");
613
- assertNoUnknownFlags(work, "shop");
624
+ assertNoUnknownFlags(work, "store");
614
625
  if (!currency) {
615
- throw new CliHttpError("Usage: frontpage shop settings set-currency --currency usd", EXIT_USAGE, 0, "bad_request");
626
+ throw new CliHttpError("Usage: frontpage store settings set-currency --currency usd (any Stripe presentment code such as usd or jpy)", EXIT_USAGE, 0, "bad_request");
616
627
  }
617
628
  const data = await v1Post(`${base}/settings/currency`, { currency });
618
629
  if (isJson())
@@ -621,10 +632,23 @@ export async function runShop(args) {
621
632
  console.log("Currency saved.");
622
633
  return EXIT_OK;
623
634
  }
635
+ if (sub === "set-checkout") {
636
+ const checkout = takeFlag(work, "--mode") ?? takeFlag(work, "--checkout");
637
+ assertNoUnknownFlags(work, "store");
638
+ if (checkout !== "cart" && checkout !== "instant") {
639
+ throw new CliHttpError("Usage: frontpage store settings set-checkout --mode cart|instant", EXIT_USAGE, 0, "bad_request");
640
+ }
641
+ const data = await v1Post(`${base}/settings/checkout`, { checkout });
642
+ if (isJson())
643
+ emitOk("shop", data);
644
+ else
645
+ console.log(checkout === "instant" ? "Checkout is Pay now." : "Checkout is add to cart.");
646
+ return EXIT_OK;
647
+ }
624
648
  if (sub === "set-checkout-fee") {
625
649
  const off = hasFlag(work, "--off");
626
650
  const cents = parseCentsFlag(takeFlag(work, "--cents"), "checkoutFeeCents");
627
- assertNoUnknownFlags(work, "shop");
651
+ assertNoUnknownFlags(work, "store");
628
652
  const data = await v1Post(`${base}/settings/checkout-fee`, {
629
653
  enabled: off ? false : true,
630
654
  cents,
@@ -632,17 +656,96 @@ export async function runShop(args) {
632
656
  if (isJson())
633
657
  emitOk("shop", data);
634
658
  else
635
- console.log(off ? "Checkout fee off." : "Checkout fee saved.");
659
+ console.log(off ? "Service fee off." : "Service fee saved.");
636
660
  return EXIT_OK;
637
661
  }
638
662
  return emitUsage("shop", USAGE);
639
663
  }
664
+ if (group === "connect") {
665
+ const work = rest.slice(1);
666
+ const noOpen = hasFlag(work, "--no-open");
667
+ assertNoUnknownFlags(work, "store");
668
+ const data = await v1Post(`${base}/connect`, { action: "connect" });
669
+ const already = data.alreadyConnected === true;
670
+ if (isJson()) {
671
+ emitOk("shop", data);
672
+ }
673
+ else {
674
+ const url = resolveOpenUrl(data.connectUrl);
675
+ if (url)
676
+ console.log(url);
677
+ console.log(typeof data.message === "string" ? data.message : already ? "Already connected." : CONNECT_FINISH);
678
+ if (typeof data.warning === "string")
679
+ console.log(data.warning);
680
+ }
681
+ const url = resolveOpenUrl(data.connectUrl);
682
+ if (url && !noOpen && !already)
683
+ openBrowser(url);
684
+ return EXIT_OK;
685
+ }
686
+ if (group === "status") {
687
+ const work = rest.slice(1);
688
+ assertNoUnknownFlags(work, "store");
689
+ const data = await v1Get(`${base}/connect`);
690
+ if (isJson()) {
691
+ emitOk("shop", data);
692
+ return EXIT_OK;
693
+ }
694
+ const connection = (data.connection ?? {});
695
+ const display = connection.display ?? {};
696
+ console.log(`connected ${connection.ready ? "yes" : "no"}`);
697
+ console.log(`charges ${display.chargesEnabled == null ? "—" : display.chargesEnabled ? "yes" : "no"}`);
698
+ console.log(`account ${display.accountPresent ? "present" : "none"}`);
699
+ if (typeof data.sharedStripe === "string")
700
+ console.log(`note ${data.sharedStripe}`);
701
+ if (typeof data.connectUrl === "string")
702
+ console.log(`connect ${data.connectUrl}`);
703
+ return EXIT_OK;
704
+ }
705
+ if (group === "disconnect") {
706
+ const work = rest.slice(1);
707
+ assertNoUnknownFlags(work, "store");
708
+ const refuse = requireYesOrTty("disconnect the store (shared Stripe account with payments)");
709
+ if (refuse)
710
+ return emitUsage("shop", refuse);
711
+ const data = await v1Post(`${base}/connect`, { action: "disconnect" });
712
+ if (isJson()) {
713
+ emitOk("shop", data);
714
+ return EXIT_OK;
715
+ }
716
+ console.log(typeof data.message === "string" ? data.message : "Disconnected.");
717
+ return EXIT_OK;
718
+ }
719
+ if (group === "import") {
720
+ const work = rest.slice(1);
721
+ assertNoUnknownFlags(work, "store");
722
+ const data = await v1Post(`${base}/import`, {});
723
+ if (isJson()) {
724
+ emitOk("shop", data);
725
+ return EXIT_OK;
726
+ }
727
+ const imported = data.imported ?? [];
728
+ const skipped = data.skipped ?? [];
729
+ const failed = data.failed ?? [];
730
+ if (imported.length === 0 && skipped.length === 0 && failed.length === 0) {
731
+ console.log("No src/content/products files to import.");
732
+ return EXIT_OK;
733
+ }
734
+ for (const row of imported)
735
+ console.log(`imported ${row.itemId} ${row.title}`);
736
+ for (const row of skipped)
737
+ console.log(`skipped ${row.itemId} already in Store`);
738
+ for (const row of failed)
739
+ console.log(`failed ${row.path} ${row.reason}`);
740
+ console.log(`${imported.length} imported ${skipped.length} skipped ${failed.length} failed`);
741
+ return EXIT_OK;
742
+ }
640
743
  if (group === "orders" || group === "payments") {
641
744
  const noun = group;
642
745
  const sub = rest[1] ?? "list";
643
746
  const work = rest.slice(2);
644
747
  if (sub === "list") {
645
- assertNoUnknownFlags(work, "shop");
748
+ assertNoUnknownFlags(work, "store");
646
749
  const data = await v1Get(`${base}/${noun}`);
647
750
  if (isJson()) {
648
751
  emitOk("shop", data);
@@ -660,7 +763,7 @@ export async function runShop(args) {
660
763
  }
661
764
  if (sub === "get") {
662
765
  const id = requireLedgerId(work, noun === "orders" ? "Order" : "Payment");
663
- assertNoUnknownFlags(work, "shop");
766
+ assertNoUnknownFlags(work, "store");
664
767
  const data = await v1Get(`${base}/${noun}/${encodeURIComponent(id)}`);
665
768
  if (isJson()) {
666
769
  emitOk("shop", data);
@@ -26,6 +26,8 @@ _frontpage_completions() {
26
26
  articles) COMPREPLY=( $(compgen -W "list content create-section create-article set-draft" -- "\${cur}") ) ;;
27
27
  analytics) COMPREPLY=( $(compgen -W "dashboard summary query funnel timeseries compare sql" -- "\${cur}") ) ;;
28
28
  people) COMPREPLY=( $(compgen -W "list get add set note rm-note follow-up hide unhide" -- "\${cur}") ) ;;
29
+ routines) COMPREPLY=( $(compgen -W "list get add set pause resume rm run runs" -- "\${cur}") ) ;;
30
+ store) COMPREPLY=( $(compgen -W "items variants categories settings orders payments connect status disconnect import" -- "\${cur}") ) ;;
29
31
  modules)
30
32
  if [[ \${COMP_CWORD} -ge 3 && \${COMP_WORDS[2]} == google_reviews ]]; then
31
33
  COMPREPLY=( $(compgen -W "docs request-setup enable disable archive config connect resolve status list-reviews hide show set-stars set-sort set-weight reset-weights write-review-url refresh disconnect" -- "\${cur}") )
@@ -33,12 +35,10 @@ _frontpage_completions() {
33
35
  COMPREPLY=( $(compgen -W "docs request-setup enable disable archive config get set destinations connect-destination remove-destination disconnect-destination attach-destination detach-destination request-destination-setup" -- "\${cur}") )
34
36
  elif [[ \${COMP_CWORD} -ge 3 && (\${COMP_WORDS[2]} == callbar || \${COMP_WORDS[2]} == booking || \${COMP_WORDS[2]} == signups) ]]; then
35
37
  COMPREPLY=( $(compgen -W "docs request-setup enable disable archive config get set" -- "\${cur}") )
36
- elif [[ \${COMP_CWORD} -ge 3 && (\${COMP_WORDS[2]} == payments || \${COMP_WORDS[2]} == shop) ]]; then
38
+ elif [[ \${COMP_CWORD} -ge 3 && \${COMP_WORDS[2]} == payments ]]; then
37
39
  COMPREPLY=( $(compgen -W "docs request-setup enable disable archive config status connect disconnect" -- "\${cur}") )
38
- elif [[ \${COMP_CWORD} -ge 3 && \${COMP_WORDS[2]} == ordering ]]; then
39
- COMPREPLY=( $(compgen -W "docs request-setup enable disable archive config status connect disconnect sync" -- "\${cur}") )
40
40
  else
41
- COMPREPLY=( $(compgen -W "list docs request-setup enable disable archive config callbar forms booking signups payments shop ordering articles google_reviews" -- "\${cur}") )
41
+ COMPREPLY=( $(compgen -W "list docs request-setup enable disable archive config callbar forms booking signups payments articles google_reviews" -- "\${cur}") )
42
42
  fi
43
43
  ;;
44
44
  extensions) COMPREPLY=( $(compgen -W "list docs enable disable config" -- "\${cur}") ) ;;
@@ -71,6 +71,8 @@ _frontpage() {
71
71
  seo|settings) _values 'subcommand' get set ;;
72
72
  analytics) _values 'subcommand' dashboard summary query funnel timeseries compare sql ;;
73
73
  people) _values 'subcommand' list get add set note rm-note follow-up hide unhide ;;
74
+ routines) _values 'subcommand' list get add set pause resume rm run runs ;;
75
+ store) _values 'subcommand' items variants categories settings orders payments connect status disconnect import ;;
74
76
  modules)
75
77
  if [[ $words[2] == google_reviews ]]; then
76
78
  _values 'subcommand' docs request-setup enable disable archive config connect resolve status list-reviews hide show set-stars set-sort set-weight reset-weights write-review-url refresh disconnect
@@ -78,12 +80,10 @@ _frontpage() {
78
80
  _values 'subcommand' docs request-setup enable disable archive config get set destinations connect-destination remove-destination disconnect-destination attach-destination detach-destination request-destination-setup
79
81
  elif [[ $words[2] == callbar || $words[2] == booking || $words[2] == signups ]]; then
80
82
  _values 'subcommand' docs request-setup enable disable archive config get set
81
- elif [[ $words[2] == payments || $words[2] == shop ]]; then
83
+ elif [[ $words[2] == payments ]]; then
82
84
  _values 'subcommand' docs request-setup enable disable archive config status connect disconnect
83
- elif [[ $words[2] == ordering ]]; then
84
- _values 'subcommand' docs request-setup enable disable archive config status connect disconnect sync
85
85
  else
86
- _values 'subcommand' list docs request-setup enable disable archive config callbar forms booking signups payments shop ordering articles google_reviews
86
+ _values 'subcommand' list docs request-setup enable disable archive config callbar forms booking signups payments articles google_reviews
87
87
  fi
88
88
  ;;
89
89
  extensions) _values 'subcommand' list docs enable disable config ;;
@@ -109,12 +109,13 @@ export function generateFishCompletion() {
109
109
  `complete -c frontpage -n "__fish_seen_subcommand_from seo settings" -a "get set"`,
110
110
  `complete -c frontpage -n "__fish_seen_subcommand_from analytics" -a "dashboard summary query funnel timeseries compare sql"`,
111
111
  `complete -c frontpage -n "__fish_seen_subcommand_from people" -a "list get add set note rm-note follow-up hide unhide"`,
112
- `complete -c frontpage -n "__fish_seen_subcommand_from modules" -a "list docs request-setup enable disable archive config callbar forms booking signups payments shop ordering articles google_reviews"`,
112
+ `complete -c frontpage -n "__fish_seen_subcommand_from routines" -a "list get add set pause resume rm run runs"`,
113
+ `complete -c frontpage -n "__fish_seen_subcommand_from store" -a "items variants categories settings orders payments connect status disconnect import"`,
114
+ `complete -c frontpage -n "__fish_seen_subcommand_from modules" -a "list docs request-setup enable disable archive config callbar forms booking signups payments articles google_reviews"`,
113
115
  `complete -c frontpage -n "__fish_seen_subcommand_from modules; and __fish_seen_subcommand_from google_reviews" -a "connect resolve status list-reviews hide show set-stars set-sort set-weight reset-weights write-review-url refresh disconnect"`,
114
116
  `complete -c frontpage -n "__fish_seen_subcommand_from modules; and __fish_seen_subcommand_from forms" -a "get set destinations connect-destination remove-destination disconnect-destination attach-destination detach-destination request-destination-setup"`,
115
117
  `complete -c frontpage -n "__fish_seen_subcommand_from modules; and __fish_seen_subcommand_from callbar booking signups" -a "get set"`,
116
- `complete -c frontpage -n "__fish_seen_subcommand_from modules; and __fish_seen_subcommand_from payments shop" -a "status connect disconnect"`,
117
- `complete -c frontpage -n "__fish_seen_subcommand_from modules; and __fish_seen_subcommand_from ordering" -a "status connect disconnect sync"`,
118
+ `complete -c frontpage -n "__fish_seen_subcommand_from modules; and __fish_seen_subcommand_from payments" -a "status connect disconnect"`,
118
119
  `complete -c frontpage -n "__fish_seen_subcommand_from extensions" -a "list docs enable disable config"`,
119
120
  `complete -c frontpage -l site -d "Site subdomain slug"`,
120
121
  ];
@@ -51,7 +51,7 @@ export function httpErrorFromV1(status, parsed, fallbackMessage) {
51
51
  if (lower === "not found." || lower === "not found") {
52
52
  return new CliHttpError("API not available (is PUBLIC_CLI_ENABLED on for this environment?).", EXIT_UNEXPECTED, status, code, requestId);
53
53
  }
54
- return new CliHttpError(message, EXIT_AUTH, status, code, requestId);
54
+ return new CliHttpError(message, EXIT_USAGE, status, code, requestId);
55
55
  }
56
56
  return new CliHttpError(message, EXIT_UNEXPECTED, status, code, requestId);
57
57
  }
@@ -2,7 +2,7 @@
2
2
  * §CLI-MOD-FW — per-key CLI registry (usage printer + help --json).
3
3
  * Instagram is not documented here. Google reviews extras ship in Phase 1.
4
4
  */
5
- export declare const DOCUMENTED_MODULE_KEYS: readonly ["callbar", "forms", "booking", "signups", "payments", "shop", "ordering", "articles", "google_reviews"];
5
+ export declare const DOCUMENTED_MODULE_KEYS: readonly ["callbar", "forms", "booking", "signups", "articles", "google_reviews"];
6
6
  export type DocumentedModuleKey = (typeof DOCUMENTED_MODULE_KEYS)[number];
7
7
  export declare const GENERIC_MODULE_VERBS: readonly ["list", "ls", "docs", "doc", "request-setup", "request_setup", "enable", "disable", "archive", "config"];
8
8
  export type ModuleCommandDef = {
@@ -19,10 +19,11 @@ export declare const MODULE_COMMANDS: readonly ModuleCommandDef[];
19
19
  export declare const GOOGLE_REVIEWS_CLI_ACTIONS: readonly ["connect", "resolve", "status", "list-reviews", "hide", "show", "set-stars", "set-sort", "set-weight", "reset-weights", "write-review-url", "refresh", "disconnect"];
20
20
  export declare const TYPED_CONFIG_CLI_ACTIONS: readonly ["get", "set"];
21
21
  export declare const STRIPE_OAUTH_CLI_ACTIONS: readonly ["status", "connect", "disconnect"];
22
- export declare const ORDERING_OAUTH_CLI_ACTIONS: readonly ["status", "connect", "disconnect", "sync"];
23
22
  export declare function isCliModuleAction(key: DocumentedModuleKey, action: string): boolean;
24
23
  export declare function isDocumentedModuleKey(raw: string): raw is DocumentedModuleKey;
25
24
  export declare function isGenericModuleVerb(raw: string): boolean;
26
25
  export declare function moduleCommand(key: DocumentedModuleKey): ModuleCommandDef;
26
+ export declare function formatShopIsNotAModule(): string;
27
+ export declare function formatPaymentsIsNotAModule(): string;
27
28
  export declare function formatModuleUsage(key: DocumentedModuleKey): string;
28
29
  export declare function formatModulesIndex(): string;