@robbeverhelst/do 1.302.0 → 1.303.0

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.
Files changed (2) hide show
  1. package/dist/do.js +229 -62
  2. package/package.json +1 -1
package/dist/do.js CHANGED
@@ -5174,6 +5174,8 @@ var guestLists = pgTable("guest_lists", {
5174
5174
  finalInfoKeyDateId: text("final_info_key_date_id").references(() => keyDates.id, {
5175
5175
  onDelete: "set null"
5176
5176
  }),
5177
+ siteUrl: text("site_url"),
5178
+ sitePassword: text("site_password"),
5177
5179
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
5178
5180
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
5179
5181
  }, (t) => [uniqueIndex("guest_lists_project_uniq").on(t.tenantId, t.projectId)]);
@@ -19912,6 +19914,12 @@ var linkGoogleInputSchema = object({
19912
19914
  state: string2().min(1)
19913
19915
  });
19914
19916
 
19917
+ // ../../packages/schema/src/contracts/pagination.ts
19918
+ var paginationQuerySchema = object({
19919
+ limit: exports_coerce.number().int().positive().optional(),
19920
+ cursor: string2().min(1).optional()
19921
+ });
19922
+
19915
19923
  // ../../packages/schema/src/contracts/changes.ts
19916
19924
  var changeOpSchema = _enum2(["created", "updated", "deleted"]);
19917
19925
  var CHANGE_ENTITY_SCOPES = {
@@ -20770,7 +20778,8 @@ var metricSourceSchema = _enum2([
20770
20778
  "agent",
20771
20779
  "google_fit",
20772
20780
  "google_health",
20773
- "health_connect"
20781
+ "health_connect",
20782
+ "withings"
20774
20783
  ]);
20775
20784
  var loggedMetricSourceSchema = _enum2(["manual", "device", "agent"]);
20776
20785
  var metricPointSchema = object({
@@ -27700,6 +27709,7 @@ var guestListViewSchema = guestListSchema.extend({
27700
27709
  finalInfoFrom: keyDateDaySchema.nullable().default(null),
27701
27710
  projectDay: keyDateDaySchema.nullable().default(null)
27702
27711
  });
27712
+ var siteUrlSchema = string2().trim().max(200).regex(/^https:\/\/[^\s/?#]+(\/[^\s?#]*)?$/, "expected an https:// address without ? or #").transform((u) => u.replace(/\/+$/, ""));
27703
27713
  var createGuestListInputSchema = object({
27704
27714
  id: ulidSchema,
27705
27715
  projectId: ulidSchema,
@@ -27707,7 +27717,9 @@ var createGuestListInputSchema = object({
27707
27717
  caps: capsSchema.optional(),
27708
27718
  sides: array(sideSchema).max(4).optional(),
27709
27719
  rsvpBy: keyDateDaySchema.nullish(),
27710
- finalInfoFrom: keyDateDaySchema.nullish()
27720
+ finalInfoFrom: keyDateDaySchema.nullish(),
27721
+ siteUrl: siteUrlSchema.nullish(),
27722
+ sitePassword: string2().trim().min(1).max(64).nullish()
27711
27723
  }).strict();
27712
27724
  var updateGuestListInputSchema = createGuestListInputSchema.omit({ id: true, projectId: true }).partial().strict();
27713
27725
  var guestListsQuerySchema = object({ projectId: ulidSchema.optional() });
@@ -27859,6 +27871,23 @@ var publicRsvpEventSchema = object({
27859
27871
  dressCode: object({ nl: string2().optional(), pt: string2().optional() }).nullable(),
27860
27872
  note: object({ nl: string2().optional(), pt: string2().optional() }).nullable()
27861
27873
  }).strict();
27874
+ var publicTextSchema = object({ nl: string2().optional(), pt: string2().optional() });
27875
+ var publicRsvpQuestionSchema = object({
27876
+ id: ulidSchema,
27877
+ kind: rsvpQuestionKindSchema,
27878
+ label: publicTextSchema,
27879
+ options: array(object({ key: string2(), label: publicTextSchema }).strict()),
27880
+ scope: rsvpQuestionScopeSchema,
27881
+ eventKey: string2().nullable(),
27882
+ required: boolean3(),
27883
+ showIf: array(rsvpShowIfConditionSchema),
27884
+ preset: rsvpQuestionPresetSchema.nullable()
27885
+ }).strict();
27886
+ var publicRsvpAnswerSchema = object({
27887
+ questionId: ulidSchema,
27888
+ guestId: ulidSchema.nullable(),
27889
+ value: rsvpAnswerValueSchema
27890
+ }).strict();
27862
27891
  var publicRsvpViewSchema = object({
27863
27892
  mode: rsvpLinkModeSchema,
27864
27893
  language: rsvpLanguageSchema,
@@ -27868,6 +27897,8 @@ var publicRsvpViewSchema = object({
27868
27897
  finalInfoFrom: keyDateDaySchema.nullable().default(null),
27869
27898
  tiers: array(guestTierSchema),
27870
27899
  events: array(publicRsvpEventSchema).default([]),
27900
+ questions: array(publicRsvpQuestionSchema).default([]),
27901
+ answers: array(publicRsvpAnswerSchema).default([]),
27871
27902
  guests: array(publicRsvpGuestSchema),
27872
27903
  address: householdAddressSchema.nullable(),
27873
27904
  answeredAt: exports_coerce.date().nullable()
@@ -27953,7 +27984,14 @@ var createSeatingProposalInputSchema = object({
27953
27984
  return new Set(ids).size === ids.length;
27954
27985
  }, { message: "a guest can sit at one table only", path: ["tables"] });
27955
27986
  var seatingProposalsQuerySchema = object({ guestListId: ulidSchema });
27956
- var GUEST_EXPORT_KINDS = ["caterer", "venue", "floor-plan", "church", "answers"];
27987
+ var GUEST_EXPORT_KINDS = [
27988
+ "caterer",
27989
+ "venue",
27990
+ "floor-plan",
27991
+ "church",
27992
+ "answers",
27993
+ "invitations"
27994
+ ];
27957
27995
  var guestExportKindSchema = _enum2(GUEST_EXPORT_KINDS);
27958
27996
  var thankYouGiftSchema = object({
27959
27997
  momentId: ulidSchema,
@@ -29586,6 +29624,77 @@ function toCollectionTypeSummary(config) {
29586
29624
  }
29587
29625
  var COLLECTION_TYPE_SUMMARIES = COLLECTION_TYPE_LIST.map(toCollectionTypeSummary);
29588
29626
 
29627
+ // ../../packages/schema/src/operations/crud.ts
29628
+ function crudOperations(r) {
29629
+ const base = `/${r.segment}`;
29630
+ const one = `/${r.segment}/{id}`;
29631
+ const event = r.eventBase ?? r.noun.replace(/ /g, ".");
29632
+ return {
29633
+ list: {
29634
+ method: "get",
29635
+ path: base,
29636
+ scopes: [r.read],
29637
+ tag: r.tag,
29638
+ summary: `List ${r.noun}s`,
29639
+ description: `Every ${r.noun} in the key's Space${r.paginated ? ", newest first" : ""}.`,
29640
+ query: r.listQuery,
29641
+ querySchema: r.listQuerySchema,
29642
+ paginated: r.paginated,
29643
+ status: 200,
29644
+ response: { description: `The Space's ${r.noun}s.`, list: true, view: r.view }
29645
+ },
29646
+ get: {
29647
+ method: "get",
29648
+ path: one,
29649
+ scopes: [r.read],
29650
+ tag: r.tag,
29651
+ summary: `Get a ${r.noun}`,
29652
+ description: `Fetch one ${r.noun} by id.`,
29653
+ status: 200,
29654
+ response: { description: `The ${r.noun}.`, view: r.view },
29655
+ errors: { 404: true }
29656
+ },
29657
+ create: {
29658
+ method: "post",
29659
+ path: base,
29660
+ scopes: [r.write],
29661
+ tag: r.tag,
29662
+ summary: `Create a ${r.noun}`,
29663
+ description: `Create a ${r.noun}; fires the \`${event}.created\` webhook.`,
29664
+ body: r.create,
29665
+ status: 201,
29666
+ response: { description: `The created ${r.noun}.`, view: r.view },
29667
+ errors: { 400: true, 409: true },
29668
+ webhook: `${event}.created`
29669
+ },
29670
+ update: {
29671
+ method: "patch",
29672
+ path: one,
29673
+ scopes: [r.write],
29674
+ tag: r.tag,
29675
+ summary: `Update a ${r.noun}`,
29676
+ description: `Patch a ${r.noun}; fires the \`${event}.updated\` webhook.`,
29677
+ body: r.update,
29678
+ status: 200,
29679
+ response: { description: `The updated ${r.noun}.`, view: r.view },
29680
+ errors: { 400: true, 404: true, 409: true },
29681
+ webhook: `${event}.updated`
29682
+ },
29683
+ remove: {
29684
+ method: "delete",
29685
+ path: one,
29686
+ scopes: [r.write],
29687
+ tag: r.tag,
29688
+ summary: `Delete a ${r.noun}`,
29689
+ description: `Delete a ${r.noun}; fires the \`${event}.deleted\` webhook.`,
29690
+ status: 204,
29691
+ response: { description: "Deleted." },
29692
+ errors: { 404: true },
29693
+ webhook: `${event}.deleted`
29694
+ }
29695
+ };
29696
+ }
29697
+
29589
29698
  // ../../packages/schema/src/operations/define.ts
29590
29699
  function defineOperations(table) {
29591
29700
  return table;
@@ -29603,6 +29712,74 @@ function routeOf(op) {
29603
29712
  return `${op.method.toUpperCase()} ${PUBLIC_API_PREFIX}${op.path}`;
29604
29713
  }
29605
29714
 
29715
+ // ../../packages/schema/src/operations/inventory-items.ts
29716
+ var inventoryItemOperations = defineOperations({
29717
+ ...crudOperations({
29718
+ segment: "inventory-items",
29719
+ tag: "Inventory",
29720
+ noun: "inventory item",
29721
+ read: "inventory:read",
29722
+ write: "inventory:write",
29723
+ create: createInventoryItemInputSchema,
29724
+ update: updateInventoryItemInputSchema,
29725
+ paginated: true,
29726
+ listQuerySchema: paginationQuerySchema
29727
+ })
29728
+ });
29729
+
29730
+ // ../../packages/schema/src/operations/meal-plans.ts
29731
+ var mealPlanOperations = defineOperations({
29732
+ ...crudOperations({
29733
+ segment: "meal-plans",
29734
+ tag: "Meals",
29735
+ noun: "meal plan",
29736
+ read: "meals:read",
29737
+ write: "meals:write",
29738
+ create: createMealPlanInputSchema,
29739
+ update: updateMealPlanInputSchema
29740
+ })
29741
+ });
29742
+
29743
+ // ../../packages/schema/src/operations/periods.ts
29744
+ var periodOperations = defineOperations({
29745
+ ...crudOperations({
29746
+ segment: "periods",
29747
+ tag: "Periods",
29748
+ noun: "period",
29749
+ read: "periods:read",
29750
+ write: "periods:write",
29751
+ create: createPeriodInputSchema,
29752
+ update: updatePeriodInputSchema,
29753
+ listQuery: [
29754
+ { name: "nodeId", description: "Only the periods on this node." },
29755
+ {
29756
+ name: "kind",
29757
+ description: "Only this kind (`hibernation`, `quarantine`, `away`, `treatment_course`, `withdrawal`, \u2026)."
29758
+ },
29759
+ {
29760
+ name: "active",
29761
+ description: "`true`: only running periods; `false`: only ended or not yet started."
29762
+ }
29763
+ ],
29764
+ listQuerySchema: periodsQuerySchema
29765
+ })
29766
+ });
29767
+
29768
+ // ../../packages/schema/src/operations/shopping-lists.ts
29769
+ var shoppingListOperations = defineOperations({
29770
+ ...crudOperations({
29771
+ segment: "shopping-lists",
29772
+ tag: "Shopping",
29773
+ noun: "shopping list",
29774
+ read: "shopping:read",
29775
+ write: "shopping:write",
29776
+ create: createShoppingListInputSchema,
29777
+ update: updateShoppingListInputSchema,
29778
+ paginated: true,
29779
+ listQuerySchema: paginationQuerySchema
29780
+ })
29781
+ });
29782
+
29606
29783
  // ../../packages/schema/src/operations/tap-tags.ts
29607
29784
  var READ = ["habits:read", "routines:read"];
29608
29785
  var WRITE = ["habits:write", "routines:write"];
@@ -29934,7 +30111,7 @@ function parseCapture2(input, now) {
29934
30111
 
29935
30112
  // src/meta.ts
29936
30113
  var CLI_NAME = "do";
29937
- var VERSION = "1.302.0";
30114
+ var VERSION = "1.303.0";
29938
30115
  var API_PREFIX = "/api/v1";
29939
30116
  var DEFAULT_API_URL = "http://localhost:3001";
29940
30117
 
@@ -30843,12 +31020,9 @@ var ENTITIES = [
30843
31020
  canDelete: true
30844
31021
  }
30845
31022
  },
30846
- {
31023
+ entityFromOperations(periodOperations, {
30847
31024
  name: "periods",
30848
- path: "/periods",
30849
- readScopes: ["periods:read"],
30850
31025
  returns: "PeriodView",
30851
- canGet: true,
30852
31026
  listFlags: [
30853
31027
  { name: "node", description: "only the periods on this node id", query: "nodeId" },
30854
31028
  {
@@ -30862,15 +31036,9 @@ var ENTITIES = [
30862
31036
  query: "active"
30863
31037
  }
30864
31038
  ],
30865
- write: {
30866
- scopes: ["periods:write"],
30867
- createSchema: createPeriodInputSchema,
30868
- createInput: "CreatePeriodInput",
30869
- updateSchema: updatePeriodInputSchema,
30870
- updateInput: "UpdatePeriodInput",
30871
- canDelete: true
30872
- }
30873
- },
31039
+ createInput: "CreatePeriodInput",
31040
+ updateInput: "UpdatePeriodInput"
31041
+ }),
30874
31042
  entityFromOperations(tapTagOperations, {
30875
31043
  name: "tap-tags",
30876
31044
  returns: "TapTagView",
@@ -30932,38 +31100,20 @@ var ENTITIES = [
30932
31100
  canDelete: true
30933
31101
  }
30934
31102
  },
30935
- {
31103
+ entityFromOperations(shoppingListOperations, {
30936
31104
  name: "shopping-lists",
30937
- path: "/shopping-lists",
30938
- readScopes: ["shopping:read"],
30939
31105
  returns: "ShoppingList",
30940
- canGet: true,
30941
31106
  listFlags: [],
30942
- write: {
30943
- scopes: ["shopping:write"],
30944
- createSchema: createShoppingListInputSchema,
30945
- createInput: "CreateShoppingListInput",
30946
- updateSchema: updateShoppingListInputSchema,
30947
- updateInput: "UpdateShoppingListInput",
30948
- canDelete: true
30949
- }
30950
- },
30951
- {
31107
+ createInput: "CreateShoppingListInput",
31108
+ updateInput: "UpdateShoppingListInput"
31109
+ }),
31110
+ entityFromOperations(inventoryItemOperations, {
30952
31111
  name: "inventory-items",
30953
- path: "/inventory-items",
30954
- readScopes: ["inventory:read"],
30955
31112
  returns: "InventoryItem",
30956
- canGet: true,
30957
31113
  listFlags: [],
30958
- write: {
30959
- scopes: ["inventory:write"],
30960
- createSchema: createInventoryItemInputSchema,
30961
- createInput: "CreateInventoryItemInput",
30962
- updateSchema: updateInventoryItemInputSchema,
30963
- updateInput: "UpdateInventoryItemInput",
30964
- canDelete: true
30965
- }
30966
- },
31114
+ createInput: "CreateInventoryItemInput",
31115
+ updateInput: "UpdateInventoryItemInput"
31116
+ }),
30967
31117
  {
30968
31118
  name: "recipes",
30969
31119
  path: "/recipes",
@@ -30980,22 +31130,13 @@ var ENTITIES = [
30980
31130
  canDelete: true
30981
31131
  }
30982
31132
  },
30983
- {
31133
+ entityFromOperations(mealPlanOperations, {
30984
31134
  name: "meal-plans",
30985
- path: "/meal-plans",
30986
- readScopes: ["meals:read"],
30987
31135
  returns: "MealPlan",
30988
- canGet: true,
30989
31136
  listFlags: [],
30990
- write: {
30991
- scopes: ["meals:write"],
30992
- createSchema: createMealPlanInputSchema,
30993
- createInput: "CreateMealPlanInput",
30994
- updateSchema: updateMealPlanInputSchema,
30995
- updateInput: "UpdateMealPlanInput",
30996
- canDelete: true
30997
- }
30998
- },
31137
+ createInput: "CreateMealPlanInput",
31138
+ updateInput: "UpdateMealPlanInput"
31139
+ }),
30999
31140
  {
31000
31141
  name: "occurrences",
31001
31142
  path: "/occurrences",
@@ -31500,6 +31641,24 @@ var HEALTH_COMMAND_SPECS = [
31500
31641
  returns: "GoogleHealthSyncResult",
31501
31642
  notes: "POST /api/v1/health/google-health/sync. Written and skipped per resource, and the status after it; a sync that stops answers 200 with `status.failure`. 409 before Google Health is connected, 403 while the server has it switched off."
31502
31643
  },
31644
+ {
31645
+ path: ["health", "withings", "status"],
31646
+ summary: "Whether your Withings scale is connected, the last sync, and what stops it.",
31647
+ args: [],
31648
+ flags: [],
31649
+ scopes: READ2,
31650
+ returns: "WithingsStatus",
31651
+ notes: "GET /api/v1/health/withings. Connecting needs Withings' consent in a browser: Settings \u2192 Health in do. `failure` (reauth, unavailable, other) says what stops the sync, with a title and a hint."
31652
+ },
31653
+ {
31654
+ path: ["health", "withings", "sync"],
31655
+ summary: "Sync your Withings weigh-ins now, as the morning sync does.",
31656
+ args: [],
31657
+ flags: [],
31658
+ scopes: WRITE3,
31659
+ returns: "WithingsSyncResult",
31660
+ notes: "POST /api/v1/health/withings/sync. Written and skipped, and the status after it; a sync that stops answers 200 with `status.failure`. 409 before Withings is connected, 403 while the server has it switched off."
31661
+ },
31503
31662
  {
31504
31663
  path: ["health", "analyte"],
31505
31664
  summary: "One analyte's trend across draws (do health analyte vitamin_d).",
@@ -36030,23 +36189,23 @@ var COMMAND_SPECS = [
36030
36189
  },
36031
36190
  {
36032
36191
  path: ["guest-lists", "export"],
36033
- summary: "Export for the caterer, the venue, the church or the RSVP answers (CSV) to stdout, or save the floor plan (PDF) to a file.",
36192
+ summary: "Export for the caterer, the venue, the church or the RSVP answers (CSV) to stdout, or save the floor plan or the paper invitations (PDF) to a file.",
36034
36193
  args: [{ name: "id", required: true, description: "The guest list's id." }],
36035
36194
  flags: [
36036
36195
  {
36037
36196
  name: "kind",
36038
36197
  type: "string",
36039
- description: "caterer (meals and diets per table), venue (who sits where), church (who comes to the ceremony), answers (every RSVP answer, a row per guest) or floor-plan (PDF)."
36198
+ description: "caterer (meals and diets per table), venue (who sits where), church (who comes to the ceremony), answers (every RSVP answer, a row per guest), floor-plan (PDF) or invitations (PDF: a card per household with a live RSVP link, its QR and code)."
36040
36199
  },
36041
36200
  {
36042
36201
  name: "out",
36043
36202
  type: "string",
36044
- description: "Where to save the floor plan (default: the name the API gives it)."
36203
+ description: "Where to save a PDF (default: the name the API gives it)."
36045
36204
  }
36046
36205
  ],
36047
36206
  scopes: ["contacts:read"],
36048
36207
  returns: "string",
36049
- notes: "A CSV goes to stdout (`> tables.csv`); the floor plan is saved: `do guest-lists export <id> --kind floor-plan --out plan.pdf`."
36208
+ notes: "A CSV goes to stdout (`> tables.csv`); a PDF is saved: `do guest-lists export <id> --kind invitations --out invitations.pdf`. Mint the links first (`do rsvp-links create`); the QR opens the list's `siteUrl` + /i/<token>, and its `sitePassword` is printed only when set."
36050
36209
  },
36051
36210
  {
36052
36211
  path: ["rsvp-links", "list"],
@@ -41192,6 +41351,14 @@ var HANDLERS = {
41192
41351
  data: await requireClient().delete(`/health/shares/${requireArg(args, 0, "id")}`),
41193
41352
  render: renderRecord
41194
41353
  }),
41354
+ "health withings status": async ({ requireClient }) => ({
41355
+ data: await requireClient().get("/health/withings"),
41356
+ render: renderRecord
41357
+ }),
41358
+ "health withings sync": async ({ requireClient }) => ({
41359
+ data: await requireClient().post("/health/withings/sync", {}),
41360
+ render: renderRecord
41361
+ }),
41195
41362
  "health google status": async ({ requireClient }) => ({
41196
41363
  data: await requireClient().get("/health/google-health"),
41197
41364
  render: renderRecord
@@ -41683,9 +41850,9 @@ var HANDLERS = {
41683
41850
  const id = requireArg(args, 0, "guest list id");
41684
41851
  const kind = parseInput(guestExportKindSchema, flagString(flags, "kind") ?? "venue");
41685
41852
  const path = `/guest-lists/${id}/exports/${kind}`;
41686
- if (kind === "floor-plan") {
41853
+ if (kind === "floor-plan" || kind === "invitations") {
41687
41854
  const file = await requireClient().getBytes(path);
41688
- const out = flagString(flags, "out") ?? basename2(file.name ?? "floor-plan.pdf");
41855
+ const out = flagString(flags, "out") ?? basename2(file.name ?? `${kind}.pdf`);
41689
41856
  writeFileSync2(out, file.bytes);
41690
41857
  return {
41691
41858
  data: { id, kind, saved: out, size: file.bytes.byteLength, type: file.contentType },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robbeverhelst/do",
3
- "version": "1.302.0",
3
+ "version": "1.303.0",
4
4
  "private": false,
5
5
  "description": "do — the agent-drivable CLI over the public /api/v1 surface",
6
6
  "homepage": "https://github.com/robbeverhelst/do/tree/main/apps/cli",