frontpage-host 0.1.21 → 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.
@@ -5,7 +5,7 @@ import { assertNoUnknownFlags, hasFlag, takeFlag } from "../argv.js";
5
5
  import { emitErr, emitOk, emitUsage, isJson, requireYesOrTty } from "../runtime.js";
6
6
  import { openBrowser, resolveOpenUrl } from "../modules/oauth.js";
7
7
  import { SHOP_ID_RE, VARIANT_ID_RE } from "./shop-ids.js";
8
- const USAGE = "Usage: frontpage shop items|variants|categories|settings|orders|payments|connect|status|disconnect … [--site <slug>] [--json]";
8
+ const USAGE = "Usage: frontpage store items|variants|categories|settings|orders|payments|connect|status|disconnect|import … [--site <slug>] [--json]";
9
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.";
10
10
  const CENTS_HINT = "$49.99 is 4999";
11
11
  function takeAll(work, name) {
@@ -22,10 +22,10 @@ function requireShopId(work, label = "Item id", opts) {
22
22
  const positional = work.find((a) => !a.startsWith("-"));
23
23
  const raw = fromFlag ?? (positional ? work.splice(work.indexOf(positional), 1)[0] : undefined);
24
24
  if (!raw) {
25
- 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");
26
26
  }
27
27
  if (!SHOP_ID_RE.test(raw)) {
28
- 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");
29
29
  }
30
30
  return raw;
31
31
  }
@@ -36,7 +36,7 @@ function requireVariantId(raw, required = true) {
36
36
  throw new CliHttpError("Variant id is required. Use --variant m or --id m.", EXIT_USAGE, 0, "bad_request");
37
37
  }
38
38
  if (!VARIANT_ID_RE.test(raw)) {
39
- 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");
40
40
  }
41
41
  return raw;
42
42
  }
@@ -45,7 +45,7 @@ function requireLedgerId(work, kind) {
45
45
  const positional = work.find((a) => !a.startsWith("-"));
46
46
  const raw = fromFlag ?? (positional ? work.splice(work.indexOf(positional), 1)[0] : undefined);
47
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)) {
48
- 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");
49
49
  }
50
50
  return raw;
51
51
  }
@@ -146,7 +146,7 @@ function printItem(row) {
146
146
  const bits = [
147
147
  row.checkout ?? "",
148
148
  row.amount ?? "",
149
- prices.length ? prices.join(",") : "",
149
+ prices.length ? `${prices.join(",")} cents` : "",
150
150
  stocks.length ? `stock ${stocks.join(",")}` : "",
151
151
  row.active === false ? "inactive" : "active",
152
152
  row.categories?.length ? row.categories.join(",") : "",
@@ -172,7 +172,7 @@ export async function runShop(args) {
172
172
  const listWork = sub === "list" ? work : rest.slice(1);
173
173
  const archived = hasFlag(listWork, "--archived");
174
174
  const category = takeFlag(listWork, "--category");
175
- assertNoUnknownFlags(listWork, "shop");
175
+ assertNoUnknownFlags(listWork, "store");
176
176
  const qs = new URLSearchParams();
177
177
  if (archived)
178
178
  qs.set("archived", "1");
@@ -186,7 +186,7 @@ export async function runShop(args) {
186
186
  }
187
187
  const rows = data.items ?? [];
188
188
  if (rows.length === 0) {
189
- console.log(archived ? "No items." : "No items.");
189
+ console.log(category ? "No products in that category." : "No products in this store.");
190
190
  return EXIT_OK;
191
191
  }
192
192
  for (const row of rows)
@@ -195,7 +195,7 @@ export async function runShop(args) {
195
195
  }
196
196
  if (sub === "get") {
197
197
  const itemId = requireShopId(work);
198
- assertNoUnknownFlags(work, "shop");
198
+ assertNoUnknownFlags(work, "store");
199
199
  const data = await v1Get(`${base}/items/${encodeURIComponent(itemId)}`);
200
200
  if (isJson()) {
201
201
  emitOk("shop", data);
@@ -207,7 +207,7 @@ export async function runShop(args) {
207
207
  const stock = v.stock == null ? "unlimited" : String(v.stock);
208
208
  const off = v.active === false ? " inactive" : "";
209
209
  const archived = v.archivedAt ? " archived" : "";
210
- console.log(` ${v.variantId} ${v.priceCents}${sale} stock ${stock}${off}${archived}`);
210
+ console.log(` ${v.variantId} ${v.priceCents} cents${sale} stock ${stock}${off}${archived}`);
211
211
  }
212
212
  return EXIT_OK;
213
213
  }
@@ -226,12 +226,11 @@ export async function runShop(args) {
226
226
  const categories = parseCsv(takeFlag(work, "--categories"));
227
227
  const priceCents = parseCentsFlag(takeFlag(work, "--price-cents"), "priceCents");
228
228
  const discountCents = parseCentsFlag(takeFlag(work, "--discount-cents"), "discountPriceCents");
229
- const shippingCents = parseCentsFlag(takeFlag(work, "--shipping-cents"), "shippingCents");
230
229
  const stockRaw = takeFlag(work, "--stock");
231
230
  const variantId = takeFlag(work, "--variant-id") ?? takeFlag(work, "--variant");
232
231
  const options = parseOptions(takeAll(work, "--option"));
233
232
  const images = parseUrls(work);
234
- assertNoUnknownFlags(work, "shop");
233
+ assertNoUnknownFlags(work, "store");
235
234
  const body = file ? await readJsonFile(file) : {};
236
235
  if (itemId)
237
236
  body.itemId = itemId;
@@ -253,8 +252,6 @@ export async function runShop(args) {
253
252
  body.categories = categories;
254
253
  if (images.length)
255
254
  body.images = images;
256
- if (body.checkout == null)
257
- body.checkout = "cart";
258
255
  if (body.amount == null)
259
256
  body.amount = "fixed";
260
257
  if (!Array.isArray(body.variants) && priceCents !== undefined) {
@@ -265,13 +262,12 @@ export async function runShop(args) {
265
262
  ...(options ? { options } : {}),
266
263
  priceCents,
267
264
  ...(discountCents !== undefined ? { discountPriceCents: discountCents } : {}),
268
- ...(shippingCents !== undefined ? { shippingCents } : {}),
269
265
  ...(stock !== undefined ? { stock } : {}),
270
266
  },
271
267
  ];
272
268
  }
273
269
  if (typeof body.itemId !== "string" || !SHOP_ID_RE.test(body.itemId)) {
274
- 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");
275
271
  }
276
272
  const data = await v1Post(`${base}/items`, body);
277
273
  if (isJson()) {
@@ -292,7 +288,7 @@ export async function runShop(args) {
292
288
  const active = parseBoolFlag(takeFlag(work, "--active"), "active");
293
289
  const categories = parseCsv(takeFlag(work, "--categories"));
294
290
  const images = parseUrls(work);
295
- assertNoUnknownFlags(work, "shop");
291
+ assertNoUnknownFlags(work, "store");
296
292
  const body = {};
297
293
  if (title !== undefined)
298
294
  body.title = title;
@@ -325,7 +321,7 @@ export async function runShop(args) {
325
321
  }
326
322
  if (sub === "archive") {
327
323
  const itemId = requireShopId(work);
328
- assertNoUnknownFlags(work, "shop");
324
+ assertNoUnknownFlags(work, "store");
329
325
  const data = await v1Post(`${base}/items/${encodeURIComponent(itemId)}/archive`, {});
330
326
  if (isJson()) {
331
327
  emitOk("shop", data);
@@ -338,7 +334,7 @@ export async function runShop(args) {
338
334
  const itemId = requireShopId(work);
339
335
  const off = hasFlag(work, "--off");
340
336
  const active = off ? false : parseBoolFlag(takeFlag(work, "--active"), "active");
341
- assertNoUnknownFlags(work, "shop");
337
+ assertNoUnknownFlags(work, "store");
342
338
  const data = await v1Post(`${base}/items/${encodeURIComponent(itemId)}/activate`, { active: active ?? true });
343
339
  if (isJson()) {
344
340
  emitOk("shop", data);
@@ -350,7 +346,7 @@ export async function runShop(args) {
350
346
  if (sub === "images") {
351
347
  const itemId = requireShopId(work);
352
348
  const urls = parseUrls(work);
353
- assertNoUnknownFlags(work, "shop");
349
+ assertNoUnknownFlags(work, "store");
354
350
  const data = await v1Put(`${base}/items/${encodeURIComponent(itemId)}/images`, { images: urls });
355
351
  if (isJson()) {
356
352
  emitOk("shop", data);
@@ -365,7 +361,7 @@ export async function runShop(args) {
365
361
  const addRaw = takeFlag(work, "--add");
366
362
  const removeRaw = takeFlag(work, "--remove");
367
363
  const setRaw = takeFlag(work, "--set");
368
- assertNoUnknownFlags(work, "shop");
364
+ assertNoUnknownFlags(work, "store");
369
365
  const present = [addRaw, removeRaw, setRaw].filter((v) => v !== undefined).length;
370
366
  if (present !== 1) {
371
367
  throw new CliHttpError("Pass either --add 5, --remove 5, or --set 20. --add / --remove become signed delta.", EXIT_USAGE, 0, "bad_request");
@@ -416,13 +412,12 @@ export async function runShop(args) {
416
412
  const variantId = requireVariantId(takeFlag(work, "--id") ?? takeFlag(work, "--variant"));
417
413
  const priceCents = parseCentsFlag(takeFlag(work, "--price-cents"), "priceCents");
418
414
  const discountCents = parseCentsFlag(takeFlag(work, "--discount-cents"), "discountPriceCents");
419
- const shippingCents = parseCentsFlag(takeFlag(work, "--shipping-cents"), "shippingCents");
420
415
  const stockRaw = takeFlag(work, "--stock");
421
416
  const options = parseOptions(takeAll(work, "--option"));
422
417
  const active = parseBoolFlag(takeFlag(work, "--active"), "active");
423
- assertNoUnknownFlags(work, "shop");
418
+ assertNoUnknownFlags(work, "store");
424
419
  if (priceCents === undefined) {
425
- 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");
426
421
  }
427
422
  const stock = stockRaw === undefined ? undefined : stockRaw === "unlimited" ? null : parseCountFlag(stockRaw, "stock");
428
423
  const data = await v1Post(`${base}/items/${encodeURIComponent(itemId)}/variants`, {
@@ -430,7 +425,6 @@ export async function runShop(args) {
430
425
  options,
431
426
  priceCents,
432
427
  discountPriceCents: discountCents,
433
- shippingCents,
434
428
  stock,
435
429
  active,
436
430
  });
@@ -446,18 +440,15 @@ export async function runShop(args) {
446
440
  const variantId = requireVariantId(takeFlag(work, "--id") ?? takeFlag(work, "--variant"));
447
441
  const priceCents = parseCentsFlag(takeFlag(work, "--price-cents"), "priceCents");
448
442
  const discountCents = parseCentsFlag(takeFlag(work, "--discount-cents"), "discountPriceCents");
449
- const shippingCents = parseCentsFlag(takeFlag(work, "--shipping-cents"), "shippingCents");
450
443
  const stockRaw = takeFlag(work, "--stock");
451
444
  const options = parseOptions(takeAll(work, "--option"));
452
445
  const active = parseBoolFlag(takeFlag(work, "--active"), "active");
453
- assertNoUnknownFlags(work, "shop");
446
+ assertNoUnknownFlags(work, "store");
454
447
  const body = {};
455
448
  if (priceCents !== undefined)
456
449
  body.priceCents = priceCents;
457
450
  if (discountCents !== undefined)
458
451
  body.discountPriceCents = discountCents;
459
- if (shippingCents !== undefined)
460
- body.shippingCents = shippingCents;
461
452
  if (stockRaw !== undefined)
462
453
  body.stock = stockRaw === "unlimited" ? null : parseCountFlag(stockRaw, "stock");
463
454
  if (options)
@@ -478,7 +469,7 @@ export async function runShop(args) {
478
469
  if (sub === "archive") {
479
470
  const itemId = requireShopId(work, "Item id", { allowIdFlag: false });
480
471
  const variantId = requireVariantId(takeFlag(work, "--id") ?? takeFlag(work, "--variant"));
481
- assertNoUnknownFlags(work, "shop");
472
+ assertNoUnknownFlags(work, "store");
482
473
  const data = await v1Post(`${base}/items/${encodeURIComponent(itemId)}/variants/${encodeURIComponent(variantId)}/archive`, {});
483
474
  if (isJson()) {
484
475
  emitOk("shop", data);
@@ -495,7 +486,7 @@ export async function runShop(args) {
495
486
  if (!sub || sub === "list" || sub.startsWith("-")) {
496
487
  const listWork = sub === "list" ? work : rest.slice(1);
497
488
  const archived = hasFlag(listWork, "--archived");
498
- assertNoUnknownFlags(listWork, "shop");
489
+ assertNoUnknownFlags(listWork, "store");
499
490
  const qs = archived ? "?archived=1" : "";
500
491
  const data = await v1Get(`${base}/categories${qs}`);
501
492
  if (isJson()) {
@@ -517,9 +508,9 @@ export async function runShop(args) {
517
508
  if (slug && work.includes(slug))
518
509
  work.splice(work.indexOf(slug), 1);
519
510
  const label = takeFlag(work, "--label") ?? takeFlag(work, "--title");
520
- assertNoUnknownFlags(work, "shop");
511
+ assertNoUnknownFlags(work, "store");
521
512
  if (!slug || !SHOP_ID_RE.test(slug) || !label) {
522
- 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");
523
514
  }
524
515
  const data = await v1Post(`${base}/categories`, {
525
516
  slug,
@@ -535,9 +526,9 @@ export async function runShop(args) {
535
526
  if (sub === "rename") {
536
527
  const slug = requireShopId(work, "Category slug");
537
528
  const label = takeFlag(work, "--label") ?? takeFlag(work, "--title");
538
- assertNoUnknownFlags(work, "shop");
529
+ assertNoUnknownFlags(work, "store");
539
530
  if (!label) {
540
- 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");
541
532
  }
542
533
  const data = await v1Patch(`${base}/categories/${encodeURIComponent(slug)}`, { label });
543
534
  if (isJson()) {
@@ -549,7 +540,7 @@ export async function runShop(args) {
549
540
  }
550
541
  if (sub === "archive") {
551
542
  const slug = requireShopId(work, "Category slug");
552
- assertNoUnknownFlags(work, "shop");
543
+ assertNoUnknownFlags(work, "store");
553
544
  const data = await v1Post(`${base}/categories/${encodeURIComponent(slug)}/archive`, {});
554
545
  if (isJson()) {
555
546
  emitOk("shop", data);
@@ -564,7 +555,7 @@ export async function runShop(args) {
564
555
  const sub = rest[1] ?? "get";
565
556
  const work = rest.slice(2);
566
557
  if (sub === "get") {
567
- assertNoUnknownFlags(work, "shop");
558
+ assertNoUnknownFlags(work, "store");
568
559
  const data = await v1Get(`${base}/settings`);
569
560
  if (isJson()) {
570
561
  emitOk("shop", data);
@@ -573,19 +564,31 @@ export async function runShop(args) {
573
564
  const s = data.settings ?? {};
574
565
  const fee = (s.checkoutFee ?? {});
575
566
  const fulfill = (s.fulfillment ?? {});
576
- const feeText = fee.enabled ? `checkout-fee ${fee.cents ?? 0}` : "checkout-fee off";
577
- 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}`);
578
575
  return EXIT_OK;
579
576
  }
580
577
  if (sub === "set-fulfillment") {
581
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");
582
585
  const flatCents = parseCentsFlag(takeFlag(work, "--flat-cents"), "flatRateCents");
583
586
  const ratesRaw = takeFlag(work, "--country-rates");
584
587
  const restOn = takeBoolish(work, "--rest-of-world", "restOfWorldEnabled");
585
588
  const restCents = parseCentsFlag(takeFlag(work, "--rest-cents"), "restOfWorldCents");
586
589
  const freeOn = takeBoolish(work, "--free-over", "freeShippingOverEnabled");
587
590
  const freeCents = parseCentsFlag(takeFlag(work, "--free-over-cents"), "freeShippingOverCents");
588
- assertNoUnknownFlags(work, "shop");
591
+ assertNoUnknownFlags(work, "store");
589
592
  let countryRates;
590
593
  if (ratesRaw) {
591
594
  try {
@@ -597,6 +600,12 @@ export async function runShop(args) {
597
600
  }
598
601
  const data = await v1Post(`${base}/settings/fulfillment`, {
599
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 } : {}),
600
609
  flatRateCents: flatCents,
601
610
  countryRates,
602
611
  restOfWorldEnabled: restOn,
@@ -612,9 +621,9 @@ export async function runShop(args) {
612
621
  }
613
622
  if (sub === "set-currency") {
614
623
  const currency = takeFlag(work, "--currency");
615
- assertNoUnknownFlags(work, "shop");
624
+ assertNoUnknownFlags(work, "store");
616
625
  if (!currency) {
617
- 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");
618
627
  }
619
628
  const data = await v1Post(`${base}/settings/currency`, { currency });
620
629
  if (isJson())
@@ -623,10 +632,23 @@ export async function runShop(args) {
623
632
  console.log("Currency saved.");
624
633
  return EXIT_OK;
625
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
+ }
626
648
  if (sub === "set-checkout-fee") {
627
649
  const off = hasFlag(work, "--off");
628
650
  const cents = parseCentsFlag(takeFlag(work, "--cents"), "checkoutFeeCents");
629
- assertNoUnknownFlags(work, "shop");
651
+ assertNoUnknownFlags(work, "store");
630
652
  const data = await v1Post(`${base}/settings/checkout-fee`, {
631
653
  enabled: off ? false : true,
632
654
  cents,
@@ -634,7 +656,7 @@ export async function runShop(args) {
634
656
  if (isJson())
635
657
  emitOk("shop", data);
636
658
  else
637
- console.log(off ? "Checkout fee off." : "Checkout fee saved.");
659
+ console.log(off ? "Service fee off." : "Service fee saved.");
638
660
  return EXIT_OK;
639
661
  }
640
662
  return emitUsage("shop", USAGE);
@@ -642,7 +664,7 @@ export async function runShop(args) {
642
664
  if (group === "connect") {
643
665
  const work = rest.slice(1);
644
666
  const noOpen = hasFlag(work, "--no-open");
645
- assertNoUnknownFlags(work, "shop");
667
+ assertNoUnknownFlags(work, "store");
646
668
  const data = await v1Post(`${base}/connect`, { action: "connect" });
647
669
  const already = data.alreadyConnected === true;
648
670
  if (isJson()) {
@@ -663,7 +685,7 @@ export async function runShop(args) {
663
685
  }
664
686
  if (group === "status") {
665
687
  const work = rest.slice(1);
666
- assertNoUnknownFlags(work, "shop");
688
+ assertNoUnknownFlags(work, "store");
667
689
  const data = await v1Get(`${base}/connect`);
668
690
  if (isJson()) {
669
691
  emitOk("shop", data);
@@ -682,7 +704,7 @@ export async function runShop(args) {
682
704
  }
683
705
  if (group === "disconnect") {
684
706
  const work = rest.slice(1);
685
- assertNoUnknownFlags(work, "shop");
707
+ assertNoUnknownFlags(work, "store");
686
708
  const refuse = requireYesOrTty("disconnect the store (shared Stripe account with payments)");
687
709
  if (refuse)
688
710
  return emitUsage("shop", refuse);
@@ -694,12 +716,36 @@ export async function runShop(args) {
694
716
  console.log(typeof data.message === "string" ? data.message : "Disconnected.");
695
717
  return EXIT_OK;
696
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
+ }
697
743
  if (group === "orders" || group === "payments") {
698
744
  const noun = group;
699
745
  const sub = rest[1] ?? "list";
700
746
  const work = rest.slice(2);
701
747
  if (sub === "list") {
702
- assertNoUnknownFlags(work, "shop");
748
+ assertNoUnknownFlags(work, "store");
703
749
  const data = await v1Get(`${base}/${noun}`);
704
750
  if (isJson()) {
705
751
  emitOk("shop", data);
@@ -717,7 +763,7 @@ export async function runShop(args) {
717
763
  }
718
764
  if (sub === "get") {
719
765
  const id = requireLedgerId(work, noun === "orders" ? "Order" : "Payment");
720
- assertNoUnknownFlags(work, "shop");
766
+ assertNoUnknownFlags(work, "store");
721
767
  const data = await v1Get(`${base}/${noun}/${encodeURIComponent(id)}`);
722
768
  if (isJson()) {
723
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;