@robbeverhelst/do 1.301.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 +430 -122
  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)]);
@@ -5236,6 +5238,7 @@ var rsvpLinks = pgTable("rsvp_links", {
5236
5238
  tokenHash: text("token_hash").notNull().unique(),
5237
5239
  mode: rsvpLinkMode("mode").notNull().default("full"),
5238
5240
  tokenSealed: text("token_sealed"),
5241
+ shortCodeHash: text("short_code_hash").unique(),
5239
5242
  expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
5240
5243
  revokedAt: timestamp("revoked_at", { withTimezone: true }),
5241
5244
  lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
@@ -6052,6 +6055,12 @@ var healthLabAnalytes = pgTable("health_lab_analytes", {
6052
6055
  note: text("note"),
6053
6056
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
6054
6057
  }, (t) => [primaryKey({ columns: [t.tenantId, t.userId, t.analyte] })]);
6058
+ var healthDataKeys = pgTable("health_data_keys", {
6059
+ tenantId: text("tenant_id").notNull().references(() => tenants.id),
6060
+ userId: text("user_id").notNull().references(() => users.id),
6061
+ sealedKey: text("sealed_key").notNull(),
6062
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
6063
+ }, (t) => [primaryKey({ columns: [t.tenantId, t.userId] })]);
6055
6064
 
6056
6065
  // ../../packages/schema/src/db/journal.ts
6057
6066
  var journalDayStatus = pgEnum("journal_day_status", ["open", "summarised", "reviewed"]);
@@ -19905,6 +19914,12 @@ var linkGoogleInputSchema = object({
19905
19914
  state: string2().min(1)
19906
19915
  });
19907
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
+
19908
19923
  // ../../packages/schema/src/contracts/changes.ts
19909
19924
  var changeOpSchema = _enum2(["created", "updated", "deleted"]);
19910
19925
  var CHANGE_ENTITY_SCOPES = {
@@ -20763,7 +20778,8 @@ var metricSourceSchema = _enum2([
20763
20778
  "agent",
20764
20779
  "google_fit",
20765
20780
  "google_health",
20766
- "health_connect"
20781
+ "health_connect",
20782
+ "withings"
20767
20783
  ]);
20768
20784
  var loggedMetricSourceSchema = _enum2(["manual", "device", "agent"]);
20769
20785
  var metricPointSchema = object({
@@ -23214,6 +23230,14 @@ var updateHealthProfileInputSchema = object({
23214
23230
  primaryGpContactId: ulidSchema.nullish()
23215
23231
  }).strict();
23216
23232
  var grantHealthConsentInputSchema = object({ version: literal(HEALTH_CONSENT_VERSION) }).strict();
23233
+ var HEALTH_ERASE_PHRASE = "delete my health data";
23234
+ var eraseHealthDataInputSchema = object({ confirm: literal(HEALTH_ERASE_PHRASE) }).strict();
23235
+ var healthErasureSchema = object({
23236
+ records: number2().int().nonnegative(),
23237
+ files: number2().int().nonnegative(),
23238
+ keyShredded: boolean3(),
23239
+ consent: healthConsentStateSchema
23240
+ });
23217
23241
  var healthEpisodeListQuerySchema = object({
23218
23242
  status: healthEpisodeStatusSchema.optional()
23219
23243
  });
@@ -23478,7 +23502,8 @@ var FILE_SOURCES = [
23478
23502
  "doccle",
23479
23503
  "doccle_zip",
23480
23504
  "scan",
23481
- "game_save"
23505
+ "game_save",
23506
+ "google_photos"
23482
23507
  ];
23483
23508
  var fileSourceSchema = union([
23484
23509
  _enum2(FILE_SOURCES),
@@ -27684,6 +27709,7 @@ var guestListViewSchema = guestListSchema.extend({
27684
27709
  finalInfoFrom: keyDateDaySchema.nullable().default(null),
27685
27710
  projectDay: keyDateDaySchema.nullable().default(null)
27686
27711
  });
27712
+ var siteUrlSchema = string2().trim().max(200).regex(/^https:\/\/[^\s/?#]+(\/[^\s?#]*)?$/, "expected an https:// address without ? or #").transform((u) => u.replace(/\/+$/, ""));
27687
27713
  var createGuestListInputSchema = object({
27688
27714
  id: ulidSchema,
27689
27715
  projectId: ulidSchema,
@@ -27691,7 +27717,9 @@ var createGuestListInputSchema = object({
27691
27717
  caps: capsSchema.optional(),
27692
27718
  sides: array(sideSchema).max(4).optional(),
27693
27719
  rsvpBy: keyDateDaySchema.nullish(),
27694
- finalInfoFrom: keyDateDaySchema.nullish()
27720
+ finalInfoFrom: keyDateDaySchema.nullish(),
27721
+ siteUrl: siteUrlSchema.nullish(),
27722
+ sitePassword: string2().trim().min(1).max(64).nullish()
27695
27723
  }).strict();
27696
27724
  var updateGuestListInputSchema = createGuestListInputSchema.omit({ id: true, projectId: true }).partial().strict();
27697
27725
  var guestListsQuerySchema = object({ projectId: ulidSchema.optional() });
@@ -27789,6 +27817,11 @@ var guestListOverviewSchema = object({
27789
27817
  })
27790
27818
  });
27791
27819
  var RSVP_TOKEN_RE = /^rsvp_[A-Za-z0-9_-]{43}$/;
27820
+ var RSVP_CODE_ALPHABET = "23456789ABCDEFGHJKMNPQRSTWXYZ";
27821
+ var RSVP_CODE_LENGTH = 6;
27822
+ var RSVP_CODE_RE = new RegExp(`^[${RSVP_CODE_ALPHABET}]{${RSVP_CODE_LENGTH}}$`);
27823
+ var resolveRsvpCodeInputSchema = object({ code: string2().max(32) }).strict();
27824
+ var resolvedRsvpCodeSchema = object({ token: string2().regex(RSVP_TOKEN_RE) });
27792
27825
  var RSVP_LINK_MODES = ["address", "full"];
27793
27826
  var rsvpLinkModeSchema = _enum2(RSVP_LINK_MODES);
27794
27827
  var rsvpLinkSchema = object({
@@ -27806,7 +27839,8 @@ var rsvpLinkSchema = object({
27806
27839
  });
27807
27840
  var mintedRsvpLinkSchema = rsvpLinkSchema.extend({
27808
27841
  token: string2().regex(RSVP_TOKEN_RE),
27809
- path: string2()
27842
+ path: string2(),
27843
+ shortCode: string2().regex(RSVP_CODE_RE).nullable()
27810
27844
  });
27811
27845
  var createRsvpLinksInputSchema = object({
27812
27846
  guestListId: ulidSchema,
@@ -27837,6 +27871,23 @@ var publicRsvpEventSchema = object({
27837
27871
  dressCode: object({ nl: string2().optional(), pt: string2().optional() }).nullable(),
27838
27872
  note: object({ nl: string2().optional(), pt: string2().optional() }).nullable()
27839
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();
27840
27891
  var publicRsvpViewSchema = object({
27841
27892
  mode: rsvpLinkModeSchema,
27842
27893
  language: rsvpLanguageSchema,
@@ -27846,6 +27897,8 @@ var publicRsvpViewSchema = object({
27846
27897
  finalInfoFrom: keyDateDaySchema.nullable().default(null),
27847
27898
  tiers: array(guestTierSchema),
27848
27899
  events: array(publicRsvpEventSchema).default([]),
27900
+ questions: array(publicRsvpQuestionSchema).default([]),
27901
+ answers: array(publicRsvpAnswerSchema).default([]),
27849
27902
  guests: array(publicRsvpGuestSchema),
27850
27903
  address: householdAddressSchema.nullable(),
27851
27904
  answeredAt: exports_coerce.date().nullable()
@@ -27931,7 +27984,14 @@ var createSeatingProposalInputSchema = object({
27931
27984
  return new Set(ids).size === ids.length;
27932
27985
  }, { message: "a guest can sit at one table only", path: ["tables"] });
27933
27986
  var seatingProposalsQuerySchema = object({ guestListId: ulidSchema });
27934
- 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
+ ];
27935
27995
  var guestExportKindSchema = _enum2(GUEST_EXPORT_KINDS);
27936
27996
  var thankYouGiftSchema = object({
27937
27997
  momentId: ulidSchema,
@@ -29564,6 +29624,241 @@ function toCollectionTypeSummary(config) {
29564
29624
  }
29565
29625
  var COLLECTION_TYPE_SUMMARIES = COLLECTION_TYPE_LIST.map(toCollectionTypeSummary);
29566
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
+
29698
+ // ../../packages/schema/src/operations/define.ts
29699
+ function defineOperations(table) {
29700
+ return table;
29701
+ }
29702
+ var PUBLIC_API_PREFIX = "/api/v1";
29703
+ function pathOf(op, params = {}) {
29704
+ return op.path.replace(/\{([A-Za-z0-9_]+)\}/g, (_, name) => {
29705
+ const value = params[name];
29706
+ if (value === undefined)
29707
+ throw new Error(`pathOf: no value for {${name}} in ${op.path}`);
29708
+ return encodeURIComponent(value);
29709
+ });
29710
+ }
29711
+ function routeOf(op) {
29712
+ return `${op.method.toUpperCase()} ${PUBLIC_API_PREFIX}${op.path}`;
29713
+ }
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
+
29783
+ // ../../packages/schema/src/operations/tap-tags.ts
29784
+ var READ = ["habits:read", "routines:read"];
29785
+ var WRITE = ["habits:write", "routines:write"];
29786
+ var pathParams = { id: "The tag's id: the ULID the sticker's link carries (`/t/{id}`)." };
29787
+ var tapTagOperations = defineOperations({
29788
+ list: {
29789
+ method: "get",
29790
+ path: "/tap-tags",
29791
+ scopes: READ,
29792
+ tag: "Habits",
29793
+ summary: "List tap tags",
29794
+ description: "The Space's NFC stickers: where each is (`label`), what a tap does (`habit`: check it in; `routine`: mark today's run done), its target's title and its link's `path`.",
29795
+ status: 200,
29796
+ response: { description: "Every tag.", list: true },
29797
+ errors: { 403: true }
29798
+ },
29799
+ create: {
29800
+ method: "post",
29801
+ path: "/tap-tags",
29802
+ scopes: WRITE,
29803
+ tag: "Habits",
29804
+ summary: "Make a tap tag",
29805
+ description: "Bind a new sticker id (a client-minted ULID) to a live habit or routine of the Space, of the tag's kind. Write `https://<your do>/t/{id}` onto an NFC sticker.",
29806
+ body: createTapTagInputSchema,
29807
+ status: 201,
29808
+ response: { description: "The tag." },
29809
+ errors: { 400: true, 403: true }
29810
+ },
29811
+ get: {
29812
+ method: "get",
29813
+ path: "/tap-tags/{id}",
29814
+ pathParams,
29815
+ scopes: READ,
29816
+ tag: "Habits",
29817
+ summary: "Get a tap tag",
29818
+ description: "One tag.",
29819
+ status: 200,
29820
+ response: { description: "The tag." },
29821
+ errors: { 404: true }
29822
+ },
29823
+ update: {
29824
+ method: "patch",
29825
+ path: "/tap-tags/{id}",
29826
+ pathParams,
29827
+ scopes: WRITE,
29828
+ tag: "Habits",
29829
+ summary: "Rename or rebind a tap tag",
29830
+ description: "Change its `label`, or bind it to another habit or routine (`kind` and `targetId` together): the sticker stays as it is.",
29831
+ body: updateTapTagInputSchema,
29832
+ status: 200,
29833
+ response: { description: "The tag." },
29834
+ errors: { 400: true, 404: true }
29835
+ },
29836
+ remove: {
29837
+ method: "delete",
29838
+ path: "/tap-tags/{id}",
29839
+ pathParams,
29840
+ scopes: WRITE,
29841
+ tag: "Habits",
29842
+ summary: "Delete a tap tag",
29843
+ description: "The sticker's link stops doing anything.",
29844
+ status: 204,
29845
+ response: { description: "Deleted." },
29846
+ errors: { 404: true }
29847
+ },
29848
+ tap: {
29849
+ method: "post",
29850
+ path: "/tap-tags/{id}/tap",
29851
+ pathParams,
29852
+ scopes: WRITE,
29853
+ tag: "Habits",
29854
+ summary: "Tap a tap tag",
29855
+ description: "What the sticker does, as the key's member: check the habit in, or take today's run of the routine. Returns the receipt (`line`) and its `undo` (the check-in's `habitId` + `entryId`, or the run's `runId` + `stepRunIds` for `POST /routines/runs/{runId}/undo`). The same member's second tap within 30 s does nothing (`outcome: \"already\"`). The key must act for a member.",
29856
+ status: 200,
29857
+ response: { description: "The tap's receipt." },
29858
+ errors: { 403: "A scope is missing, or the key acts for no member.", 404: true }
29859
+ }
29860
+ });
29861
+
29567
29862
  // ../../node_modules/ulidx/dist/node/index.js
29568
29863
  import crypto from "crypto";
29569
29864
 
@@ -29816,7 +30111,7 @@ function parseCapture2(input, now) {
29816
30111
 
29817
30112
  // src/meta.ts
29818
30113
  var CLI_NAME = "do";
29819
- var VERSION = "1.301.0";
30114
+ var VERSION = "1.303.0";
29820
30115
  var API_PREFIX = "/api/v1";
29821
30116
  var DEFAULT_API_URL = "http://localhost:3001";
29822
30117
 
@@ -30045,7 +30340,7 @@ function resolveConfig(io, flags) {
30045
30340
  }
30046
30341
 
30047
30342
  // src/contacts-commands.ts
30048
- var WRITE = ["contacts:write"];
30343
+ var WRITE2 = ["contacts:write"];
30049
30344
  var FIELD_FLAGS = [
30050
30345
  { name: "name", type: "string", description: "The display name (or pass it as the argument)." },
30051
30346
  { name: "kind", type: "string", description: "person (the default) or organization." },
@@ -30088,7 +30383,7 @@ var CONTACT_COMMAND_SPECS = [
30088
30383
  summary: "Add a contact: a person or an organisation, with their details.",
30089
30384
  args: [{ name: "name", required: false, description: "The display name." }],
30090
30385
  flags: FIELD_FLAGS,
30091
- scopes: WRITE,
30386
+ scopes: WRITE2,
30092
30387
  returns: "ContactView",
30093
30388
  notes: 'Flags build a CreateContactInput; --data adds any other field (a flag wins). The id is minted when omitted. e.g. `do contacts create "Sam Peters" --email sam@example.com --birthday 1990-04-12 --circles <id>`.'
30094
30389
  },
@@ -30097,7 +30392,7 @@ var CONTACT_COMMAND_SPECS = [
30097
30392
  summary: "Change a contact's details.",
30098
30393
  args: [{ name: "id", required: true, description: "The contact's id." }],
30099
30394
  flags: FIELD_FLAGS,
30100
- scopes: WRITE,
30395
+ scopes: WRITE2,
30101
30396
  returns: "ContactView",
30102
30397
  notes: "Only the flags you pass change. `none` clears a field (--email none, --birthday none). --birthday keeps the contact's other dates; --roles, --circles and --tags replace the list (a role it already has keeps its details)."
30103
30398
  }
@@ -30202,6 +30497,38 @@ var DUE_BEFORE_FLAG = {
30202
30497
  description: "only rows due strictly before this instant (ISO 8601 or YYYY-MM-DD)",
30203
30498
  query: "dueBefore"
30204
30499
  };
30500
+ function entityFromOperations(ops, spec) {
30501
+ const path = ops.list.path;
30502
+ const sameScopes = (a, b) => [...a.scopes].sort().join() === [...b.scopes].sort().join();
30503
+ const reads = [ops.list, ops.get].filter((op) => op !== undefined);
30504
+ const writes = [ops.create, ops.update, ops.remove].filter((op) => op !== undefined);
30505
+ const problems = [
30506
+ ...[ops.create].filter((op) => op && (op.method !== "post" || op.path !== path)),
30507
+ ...[ops.get, ops.update, ops.remove].filter((op) => op && op.path !== `${path}/{id}`),
30508
+ ...reads.filter((op) => !sameScopes(op, ops.list)),
30509
+ ...writes.filter((op) => writes[0] && !sameScopes(op, writes[0]))
30510
+ ];
30511
+ if (problems.length > 0) {
30512
+ throw new Error(`entityFromOperations(${spec.name}): ${problems.map((op) => op?.summary).join("; ")} don't fit the entity verbs`);
30513
+ }
30514
+ const [firstWrite] = writes;
30515
+ return {
30516
+ name: spec.name,
30517
+ path,
30518
+ readScopes: [...ops.list.scopes],
30519
+ returns: spec.returns,
30520
+ canGet: ops.get !== undefined,
30521
+ listFlags: spec.listFlags,
30522
+ ...firstWrite ? {
30523
+ write: {
30524
+ scopes: [...firstWrite.scopes],
30525
+ ...ops.create ? { createSchema: ops.create.body, createInput: spec.createInput } : {},
30526
+ ...ops.update ? { updateSchema: ops.update.body, updateInput: spec.updateInput } : {},
30527
+ canDelete: ops.remove !== undefined
30528
+ }
30529
+ } : {}
30530
+ };
30531
+ }
30205
30532
  var ENTITIES = [
30206
30533
  {
30207
30534
  name: "tasks",
@@ -30693,12 +31020,9 @@ var ENTITIES = [
30693
31020
  canDelete: true
30694
31021
  }
30695
31022
  },
30696
- {
31023
+ entityFromOperations(periodOperations, {
30697
31024
  name: "periods",
30698
- path: "/periods",
30699
- readScopes: ["periods:read"],
30700
31025
  returns: "PeriodView",
30701
- canGet: true,
30702
31026
  listFlags: [
30703
31027
  { name: "node", description: "only the periods on this node id", query: "nodeId" },
30704
31028
  {
@@ -30712,31 +31036,16 @@ var ENTITIES = [
30712
31036
  query: "active"
30713
31037
  }
30714
31038
  ],
30715
- write: {
30716
- scopes: ["periods:write"],
30717
- createSchema: createPeriodInputSchema,
30718
- createInput: "CreatePeriodInput",
30719
- updateSchema: updatePeriodInputSchema,
30720
- updateInput: "UpdatePeriodInput",
30721
- canDelete: true
30722
- }
30723
- },
30724
- {
31039
+ createInput: "CreatePeriodInput",
31040
+ updateInput: "UpdatePeriodInput"
31041
+ }),
31042
+ entityFromOperations(tapTagOperations, {
30725
31043
  name: "tap-tags",
30726
- path: "/tap-tags",
30727
- readScopes: ["habits:read", "routines:read"],
30728
31044
  returns: "TapTagView",
30729
- canGet: true,
30730
31045
  listFlags: [],
30731
- write: {
30732
- scopes: ["habits:write", "routines:write"],
30733
- createSchema: createTapTagInputSchema,
30734
- createInput: "CreateTapTagInput",
30735
- updateSchema: updateTapTagInputSchema,
30736
- updateInput: "UpdateTapTagInput",
30737
- canDelete: true
30738
- }
30739
- },
31046
+ createInput: "CreateTapTagInput",
31047
+ updateInput: "UpdateTapTagInput"
31048
+ }),
30740
31049
  {
30741
31050
  name: "animals",
30742
31051
  path: "/animals",
@@ -30791,38 +31100,20 @@ var ENTITIES = [
30791
31100
  canDelete: true
30792
31101
  }
30793
31102
  },
30794
- {
31103
+ entityFromOperations(shoppingListOperations, {
30795
31104
  name: "shopping-lists",
30796
- path: "/shopping-lists",
30797
- readScopes: ["shopping:read"],
30798
31105
  returns: "ShoppingList",
30799
- canGet: true,
30800
31106
  listFlags: [],
30801
- write: {
30802
- scopes: ["shopping:write"],
30803
- createSchema: createShoppingListInputSchema,
30804
- createInput: "CreateShoppingListInput",
30805
- updateSchema: updateShoppingListInputSchema,
30806
- updateInput: "UpdateShoppingListInput",
30807
- canDelete: true
30808
- }
30809
- },
30810
- {
31107
+ createInput: "CreateShoppingListInput",
31108
+ updateInput: "UpdateShoppingListInput"
31109
+ }),
31110
+ entityFromOperations(inventoryItemOperations, {
30811
31111
  name: "inventory-items",
30812
- path: "/inventory-items",
30813
- readScopes: ["inventory:read"],
30814
31112
  returns: "InventoryItem",
30815
- canGet: true,
30816
31113
  listFlags: [],
30817
- write: {
30818
- scopes: ["inventory:write"],
30819
- createSchema: createInventoryItemInputSchema,
30820
- createInput: "CreateInventoryItemInput",
30821
- updateSchema: updateInventoryItemInputSchema,
30822
- updateInput: "UpdateInventoryItemInput",
30823
- canDelete: true
30824
- }
30825
- },
31114
+ createInput: "CreateInventoryItemInput",
31115
+ updateInput: "UpdateInventoryItemInput"
31116
+ }),
30826
31117
  {
30827
31118
  name: "recipes",
30828
31119
  path: "/recipes",
@@ -30839,22 +31130,13 @@ var ENTITIES = [
30839
31130
  canDelete: true
30840
31131
  }
30841
31132
  },
30842
- {
31133
+ entityFromOperations(mealPlanOperations, {
30843
31134
  name: "meal-plans",
30844
- path: "/meal-plans",
30845
- readScopes: ["meals:read"],
30846
31135
  returns: "MealPlan",
30847
- canGet: true,
30848
31136
  listFlags: [],
30849
- write: {
30850
- scopes: ["meals:write"],
30851
- createSchema: createMealPlanInputSchema,
30852
- createInput: "CreateMealPlanInput",
30853
- updateSchema: updateMealPlanInputSchema,
30854
- updateInput: "UpdateMealPlanInput",
30855
- canDelete: true
30856
- }
30857
- },
31137
+ createInput: "CreateMealPlanInput",
31138
+ updateInput: "UpdateMealPlanInput"
31139
+ }),
30858
31140
  {
30859
31141
  name: "occurrences",
30860
31142
  path: "/occurrences",
@@ -30933,8 +31215,8 @@ function entityOperations(e) {
30933
31215
  }
30934
31216
 
30935
31217
  // src/health-commands.ts
30936
- var READ = ["health:read"];
30937
- var WRITE2 = ["health:write"];
31218
+ var READ2 = ["health:read"];
31219
+ var WRITE3 = ["health:write"];
30938
31220
  var DATA_FLAG = {
30939
31221
  name: "data",
30940
31222
  type: "string",
@@ -30948,7 +31230,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
30948
31230
  summary: `List your health ${noun}s.`,
30949
31231
  args: [],
30950
31232
  flags: listFlags,
30951
- scopes: READ,
31233
+ scopes: READ2,
30952
31234
  returns
30953
31235
  },
30954
31236
  {
@@ -30956,7 +31238,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
30956
31238
  summary: `Show one health ${noun}.`,
30957
31239
  args: [id],
30958
31240
  flags: [],
30959
- scopes: READ,
31241
+ scopes: READ2,
30960
31242
  returns
30961
31243
  },
30962
31244
  {
@@ -30964,7 +31246,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
30964
31246
  summary: `Add a health ${noun} (--data '${createInput}').`,
30965
31247
  args: [],
30966
31248
  flags: [DATA_FLAG, ...addFlags],
30967
- scopes: WRITE2,
31249
+ scopes: WRITE3,
30968
31250
  returns,
30969
31251
  notes: `Body: ${createInput}. Always private to you; subjectNodeId names who it is about (absent = you).`
30970
31252
  },
@@ -30973,7 +31255,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
30973
31255
  summary: `Change a health ${noun} (--data '${updateInput}').`,
30974
31256
  args: [id],
30975
31257
  flags: [DATA_FLAG],
30976
- scopes: WRITE2,
31258
+ scopes: WRITE3,
30977
31259
  returns,
30978
31260
  notes: `Body: ${updateInput}. null clears a field.`
30979
31261
  },
@@ -30982,7 +31264,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
30982
31264
  summary: `Delete a health ${noun}.`,
30983
31265
  args: [id],
30984
31266
  flags: [],
30985
- scopes: WRITE2,
31267
+ scopes: WRITE3,
30986
31268
  returns: "Deleted",
30987
31269
  notes: "Destructive: needs --allow-destructive. An Agent's delete waits on your confirmation."
30988
31270
  }
@@ -30994,7 +31276,7 @@ var HEALTH_COMMAND_SPECS = [
30994
31276
  summary: "Show whether Health is on (only you turn it on or off, in do).",
30995
31277
  args: [],
30996
31278
  flags: [],
30997
- scopes: READ,
31279
+ scopes: READ2,
30998
31280
  returns: "HealthConsentState",
30999
31281
  notes: "`granted` is on at today's consent text (`currentVersion`); `renewalNeeded` means you agreed to an older text, so writes wait until you agree again in do. `aiProvider` names who processes what the agent reads (null when this deployment names none)."
31000
31282
  },
@@ -31003,7 +31285,7 @@ var HEALTH_COMMAND_SPECS = [
31003
31285
  summary: "Show your health profile: blood group, allergies, lifestyle, notes, emergency contact, GP.",
31004
31286
  args: [],
31005
31287
  flags: [],
31006
- scopes: READ,
31288
+ scopes: READ2,
31007
31289
  returns: "HealthProfile"
31008
31290
  },
31009
31291
  {
@@ -31011,7 +31293,7 @@ var HEALTH_COMMAND_SPECS = [
31011
31293
  summary: "Show your Medical ID: name, blood group, allergies, current medications, height, emergency contact.",
31012
31294
  args: [],
31013
31295
  flags: [],
31014
- scopes: READ,
31296
+ scopes: READ2,
31015
31297
  returns: "HealthMedicalId",
31016
31298
  notes: "The few facts someone helping in an emergency needs. The emergency contact comes with their name and phone; a field not set is null. Each read by a key is logged for the member."
31017
31299
  },
@@ -31032,7 +31314,7 @@ var HEALTH_COMMAND_SPECS = [
31032
31314
  description: `Sex, in your words, for the doctor's page ("female"); "" clears it.`
31033
31315
  }
31034
31316
  ],
31035
- scopes: WRITE2,
31317
+ scopes: WRITE3,
31036
31318
  returns: "HealthProfile",
31037
31319
  notes: "Body: UpdateHealthProfileInput. An absent key keeps a field; null clears it. Lifestyle is `alcohol`, `smoking`, `exercise`, and `notes` anything else: the member's own words. Notes go on For the doctor's background; `sex` on its patient line."
31038
31320
  },
@@ -31041,7 +31323,7 @@ var HEALTH_COMMAND_SPECS = [
31041
31323
  summary: "Log one health message: find or start the Episode, add a symptom and/or a dose.",
31042
31324
  args: [],
31043
31325
  flags: [DATA_FLAG],
31044
- scopes: WRITE2,
31326
+ scopes: WRITE3,
31045
31327
  returns: "HealthReceipt",
31046
31328
  notes: 'Body: LogHealthInput, e.g. {"episode":{"title":"Sprained ankle","bodyRegion":"ankle_l"},"symptom":{"symptom":"worse on the stairs","severity":6,"severityGuessed":true},"dose":{"medication":"Ibuprofen"}}. The receipt lists each part with a line, what was guessed, and its undo. With "thread":{"slug":"<agent>"} (only an agent key that converses) the receipt card is posted there too, and postId names it.'
31047
31329
  },
@@ -31050,7 +31332,7 @@ var HEALTH_COMMAND_SPECS = [
31050
31332
  summary: `Correct a symptom entry and get a receipt (--data '{"bodyRegion":"ankle_l"}').`,
31051
31333
  args: [{ name: "entry", required: true, description: "The entry's id (its latest version)." }],
31052
31334
  flags: [DATA_FLAG],
31053
- scopes: WRITE2,
31335
+ scopes: WRITE3,
31054
31336
  returns: "HealthReceipt"
31055
31337
  },
31056
31338
  {
@@ -31061,7 +31343,7 @@ var HEALTH_COMMAND_SPECS = [
31061
31343
  { name: "trend", required: true, description: "better|same|worse, against yesterday." }
31062
31344
  ],
31063
31345
  flags: [],
31064
- scopes: WRITE2,
31346
+ scopes: WRITE3,
31065
31347
  returns: "HealthReceipt",
31066
31348
  notes: "One a day: a second check-in the same day corrects the first. The receipt carries its undo."
31067
31349
  },
@@ -31078,7 +31360,7 @@ var HEALTH_COMMAND_SPECS = [
31078
31360
  description: "Post the confirmation card in this thread (by slug); the agent's key only."
31079
31361
  }
31080
31362
  ],
31081
- scopes: WRITE2,
31363
+ scopes: WRITE3,
31082
31364
  returns: "HealthRedFlagsResult",
31083
31365
  notes: "Flags: fever, vomiting, black_stools, blood, weight_loss, night_pain, chest_pain, short_of_breath. Answers merge into those asked before; the Episode is marked checked today, and For the doctor shows it."
31084
31366
  },
@@ -31090,7 +31372,7 @@ var HEALTH_COMMAND_SPECS = [
31090
31372
  { name: "id", required: true, description: "The id from the receipt's undo." }
31091
31373
  ],
31092
31374
  flags: [],
31093
- scopes: WRITE2,
31375
+ scopes: WRITE3,
31094
31376
  returns: "HealthUndoResult",
31095
31377
  notes: "A POST, so an Agent's undo never waits on a confirmation."
31096
31378
  },
@@ -31099,7 +31381,7 @@ var HEALTH_COMMAND_SPECS = [
31099
31381
  summary: "Find the open Episode a complaint belongs to, or start one (--data UpsertHealthEpisodeInput).",
31100
31382
  args: [],
31101
31383
  flags: [DATA_FLAG],
31102
- scopes: WRITE2,
31384
+ scopes: WRITE3,
31103
31385
  returns: "HealthEpisodeUpsert"
31104
31386
  },
31105
31387
  {
@@ -31109,7 +31391,7 @@ var HEALTH_COMMAND_SPECS = [
31109
31391
  flags: [
31110
31392
  { name: "episode", type: "string", description: "The Episode; default the latest open one." }
31111
31393
  ],
31112
- scopes: READ,
31394
+ scopes: READ2,
31113
31395
  returns: "HealthSummary"
31114
31396
  },
31115
31397
  {
@@ -31132,7 +31414,7 @@ var HEALTH_COMMAND_SPECS = [
31132
31414
  summary: "Your For-the-doctor links: sections, expiry, opens; never the secret.",
31133
31415
  args: [],
31134
31416
  flags: [],
31135
- scopes: READ,
31417
+ scopes: READ2,
31136
31418
  returns: "HealthShare[]",
31137
31419
  notes: "Only the member makes a share, in do; a key can list and take one back."
31138
31420
  },
@@ -31141,7 +31423,7 @@ var HEALTH_COMMAND_SPECS = [
31141
31423
  summary: "Take a For-the-doctor link back: it stops opening.",
31142
31424
  args: [{ name: "id", required: true, description: "The share's id." }],
31143
31425
  flags: [],
31144
- scopes: WRITE2,
31426
+ scopes: WRITE3,
31145
31427
  returns: "HealthShare"
31146
31428
  },
31147
31429
  ...recordSpecs("conditions", "condition", "condition", "HealthConditionView", "CreateHealthConditionInput", "UpdateHealthConditionInput"),
@@ -31159,7 +31441,7 @@ var HEALTH_COMMAND_SPECS = [
31159
31441
  description: 'Their role on it, in your words ("dermatologist").'
31160
31442
  }
31161
31443
  ],
31162
- scopes: WRITE2,
31444
+ scopes: WRITE3,
31163
31445
  returns: "HealthConditionView",
31164
31446
  notes: "A cared_by link (condition \u2192 contact). Adding one already on it sets the role. The condition's page, the Health home's Care team and For the doctor list them."
31165
31447
  },
@@ -31171,7 +31453,7 @@ var HEALTH_COMMAND_SPECS = [
31171
31453
  { name: "contact", required: true, description: "The Contact's id." }
31172
31454
  ],
31173
31455
  flags: [],
31174
- scopes: WRITE2,
31456
+ scopes: WRITE3,
31175
31457
  returns: "HealthConditionView",
31176
31458
  notes: "An Agent's removal waits on your confirmation, like a delete."
31177
31459
  },
@@ -31208,7 +31490,7 @@ var HEALTH_COMMAND_SPECS = [
31208
31490
  { name: "at", type: "string", description: "When it happened (ISO); defaults to now." },
31209
31491
  { name: "note", type: "string", description: "A free-text note." }
31210
31492
  ],
31211
- scopes: WRITE2,
31493
+ scopes: WRITE3,
31212
31494
  returns: "HealthSymptomEntry",
31213
31495
  notes: "An Agent's entry is source chat. For qualifiers, send --data with LogHealthSymptomInput."
31214
31496
  },
@@ -31224,7 +31506,7 @@ var HEALTH_COMMAND_SPECS = [
31224
31506
  }
31225
31507
  ],
31226
31508
  flags: [DATA_FLAG],
31227
- scopes: WRITE2,
31509
+ scopes: WRITE3,
31228
31510
  returns: "HealthSymptomEntry",
31229
31511
  notes: "Body: CorrectHealthSymptomInput; fields not named are copied. The history stays."
31230
31512
  },
@@ -31236,7 +31518,7 @@ var HEALTH_COMMAND_SPECS = [
31236
31518
  { name: "entry", required: true, description: "The entry's id." }
31237
31519
  ],
31238
31520
  flags: [],
31239
- scopes: WRITE2,
31521
+ scopes: WRITE3,
31240
31522
  returns: "Deleted"
31241
31523
  },
31242
31524
  ...recordSpecs("visits", "visit", "visit", "HealthEncounterView", "CreateHealthEncounterInput", "UpdateHealthEncounterInput"),
@@ -31246,7 +31528,7 @@ var HEALTH_COMMAND_SPECS = [
31246
31528
  summary: "List the doses of a Medication, latest first.",
31247
31529
  args: [{ name: "med", required: true, description: "The Medication's id." }],
31248
31530
  flags: [],
31249
- scopes: READ,
31531
+ scopes: READ2,
31250
31532
  returns: "HealthMedicationDose"
31251
31533
  },
31252
31534
  {
@@ -31260,7 +31542,7 @@ var HEALTH_COMMAND_SPECS = [
31260
31542
  { name: "at", type: "string", description: "When (ISO); defaults to now." },
31261
31543
  { name: "note", type: "string", description: "A free-text note." }
31262
31544
  ],
31263
- scopes: WRITE2,
31545
+ scopes: WRITE3,
31264
31546
  returns: "HealthMedicationDose"
31265
31547
  },
31266
31548
  {
@@ -31271,7 +31553,7 @@ var HEALTH_COMMAND_SPECS = [
31271
31553
  { name: "dose", required: true, description: "The dose's id." }
31272
31554
  ],
31273
31555
  flags: [DATA_FLAG],
31274
- scopes: WRITE2,
31556
+ scopes: WRITE3,
31275
31557
  returns: "HealthMedicationDose",
31276
31558
  notes: "Body: UpdateHealthDoseInput."
31277
31559
  },
@@ -31283,7 +31565,7 @@ var HEALTH_COMMAND_SPECS = [
31283
31565
  { name: "dose", required: true, description: "The dose's id." }
31284
31566
  ],
31285
31567
  flags: [],
31286
- scopes: WRITE2,
31568
+ scopes: WRITE3,
31287
31569
  returns: "Deleted"
31288
31570
  },
31289
31571
  ...recordSpecs("labs", "lab", "lab draw", "HealthLabPanelView", "CreateHealthLabPanelInput", "UpdateHealthLabPanelInput"),
@@ -31305,7 +31587,7 @@ var HEALTH_COMMAND_SPECS = [
31305
31587
  description: "The file's media type, when its name doesn't say."
31306
31588
  }
31307
31589
  ],
31308
- scopes: WRITE2,
31590
+ scopes: WRITE3,
31309
31591
  returns: "HealthLabImportResult",
31310
31592
  notes: "The draw stays a draft (status draft, source import) until the member checks it; the report is kept as its original. A report with no lab value in it is a 422."
31311
31593
  },
@@ -31314,7 +31596,7 @@ var HEALTH_COMMAND_SPECS = [
31314
31596
  summary: "The canonical analytes: key, printed name, group, unit, LOINC and aliases.",
31315
31597
  args: [],
31316
31598
  flags: [],
31317
- scopes: READ,
31599
+ scopes: READ2,
31318
31600
  returns: "LabAnalyteDef[]",
31319
31601
  notes: 'A result is written under the canonical key its analyte or name stands for ("ALAT" \u2192 alt, "25-OH vitamin D" \u2192 vitamin_d); a name that matches none keeps its slug and comes back in the draw\'s `unmatched`.'
31320
31602
  },
@@ -31329,7 +31611,7 @@ var HEALTH_COMMAND_SPECS = [
31329
31611
  description: "Required: only report, change nothing."
31330
31612
  }
31331
31613
  ],
31332
- scopes: READ,
31614
+ scopes: READ2,
31333
31615
  returns: "HealthLabNormalizeReport",
31334
31616
  notes: "Nothing is rewritten: trends already read old keys as their canonical one. Lists each result's from \u2192 to, and the keys that match no canonical analyte."
31335
31617
  },
@@ -31338,7 +31620,7 @@ var HEALTH_COMMAND_SPECS = [
31338
31620
  summary: "Every analyte your draws measured, each with its trend, grouped as a lab sheet.",
31339
31621
  args: [],
31340
31622
  flags: [],
31341
- scopes: READ,
31623
+ scopes: READ2,
31342
31624
  returns: "HealthLabOverview"
31343
31625
  },
31344
31626
  {
@@ -31346,7 +31628,7 @@ var HEALTH_COMMAND_SPECS = [
31346
31628
  summary: "Whether your Google Health is connected, the last sync, and what stops it.",
31347
31629
  args: [],
31348
31630
  flags: [],
31349
- scopes: READ,
31631
+ scopes: READ2,
31350
31632
  returns: "GoogleHealthStatus",
31351
31633
  notes: "GET /api/v1/health/google-health. Connecting needs Google's consent in a browser: Settings \u2192 Health in do. `failure` (reauth, not_linked, unavailable, other) says what stops the sync, with a title and a hint. `nutrition` says whether your food is written back to Google Health (turned on in do; each settled day once, one entry per meal type) and the last day written."
31352
31634
  },
@@ -31355,16 +31637,34 @@ var HEALTH_COMMAND_SPECS = [
31355
31637
  summary: "Sync your Google Health now (sleep, resting heart rate, HRV, steps, workouts), as the morning sync does.",
31356
31638
  args: [],
31357
31639
  flags: [],
31358
- scopes: WRITE2,
31640
+ scopes: WRITE3,
31359
31641
  returns: "GoogleHealthSyncResult",
31360
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."
31361
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
+ },
31362
31662
  {
31363
31663
  path: ["health", "analyte"],
31364
31664
  summary: "One analyte's trend across draws (do health analyte vitamin_d).",
31365
31665
  args: [{ name: "analyte", required: true, description: "The analyte key (vitamin_d)." }],
31366
31666
  flags: [],
31367
- scopes: READ,
31667
+ scopes: READ2,
31368
31668
  returns: "HealthLabAnalyteView"
31369
31669
  },
31370
31670
  {
@@ -31372,7 +31672,7 @@ var HEALTH_COMMAND_SPECS = [
31372
31672
  summary: `Keep a target band or the supplement for an analyte (--data '{"targetLow":40,"targetHigh":60}').`,
31373
31673
  args: [{ name: "analyte", required: true, description: "The analyte key (vitamin_d)." }],
31374
31674
  flags: [DATA_FLAG],
31375
- scopes: WRITE2,
31675
+ scopes: WRITE3,
31376
31676
  returns: "HealthLabAnalyteView",
31377
31677
  notes: "Body: SetHealthLabAnalyteInput. An absent key keeps a field; null clears it."
31378
31678
  }
@@ -32712,9 +33012,9 @@ var COMMAND_SPECS = [
32712
33012
  summary: "Tap a tag as if the phone read its sticker: check its habit in, or mark today's run of its routine done.",
32713
33013
  args: [{ name: "id", required: true, description: "The tag id (the ULID in /t/<id>)." }],
32714
33014
  flags: [],
32715
- scopes: ["habits:write", "routines:write"],
33015
+ scopes: [...tapTagOperations.tap.scopes],
32716
33016
  returns: "TapResult",
32717
- notes: "POST /api/v1/tap-tags/{id}/tap. Acts as the key's member; a second tap within 30 s does nothing (outcome already). The receipt's undo names the check-in (DELETE /habits/{habitId}/entries/{entryId}) or the run's steps (POST /routines/runs/{runId}/undo)."
33017
+ notes: `${routeOf(tapTagOperations.tap)}. Acts as the key's member; a second tap within 30 s does nothing (outcome already). The receipt's undo names the check-in (DELETE /habits/{habitId}/entries/{entryId}) or the run's steps (POST /routines/runs/{runId}/undo).`
32718
33018
  },
32719
33019
  {
32720
33020
  path: ["metrics", "list"],
@@ -35889,23 +36189,23 @@ var COMMAND_SPECS = [
35889
36189
  },
35890
36190
  {
35891
36191
  path: ["guest-lists", "export"],
35892
- 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.",
35893
36193
  args: [{ name: "id", required: true, description: "The guest list's id." }],
35894
36194
  flags: [
35895
36195
  {
35896
36196
  name: "kind",
35897
36197
  type: "string",
35898
- 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)."
35899
36199
  },
35900
36200
  {
35901
36201
  name: "out",
35902
36202
  type: "string",
35903
- 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)."
35904
36204
  }
35905
36205
  ],
35906
36206
  scopes: ["contacts:read"],
35907
36207
  returns: "string",
35908
- 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."
35909
36209
  },
35910
36210
  {
35911
36211
  path: ["rsvp-links", "list"],
@@ -35932,12 +36232,12 @@ var COMMAND_SPECS = [
35932
36232
  },
35933
36233
  {
35934
36234
  path: ["rsvp-links", "token"],
35935
- summary: "Show a live RSVP link again: the same token it was minted with.",
36235
+ summary: "Show a live RSVP link again: the same token it was minted with, and a whole-form link's short code.",
35936
36236
  args: [{ name: "id", required: true, description: "The link's id." }],
35937
36237
  flags: [],
35938
36238
  scopes: ["contacts:write"],
35939
36239
  returns: "MintedRsvpLink",
35940
- notes: "The mail and the paper QR carry this one link. A revoked or expired link is gone; one minted before tokens were kept can't be shown: replace it."
36240
+ notes: "The mail and the paper QR carry this one link; `shortCode` (six characters) is printed on the paper invitation and typed at the site's /code. A revoked or expired link is gone; one minted before tokens were kept can't be shown: replace it."
35941
36241
  },
35942
36242
  {
35943
36243
  path: ["rsvp-links", "replace"],
@@ -39794,7 +40094,7 @@ var HANDLERS = {
39794
40094
  "tap-tags tap": async ({ args, requireClient }) => {
39795
40095
  const id = requireArg(args, 0, "tag id");
39796
40096
  return {
39797
- data: await requireClient().post(`/tap-tags/${encodeURIComponent(id)}/tap`, {}),
40097
+ data: await requireClient().post(pathOf(tapTagOperations.tap, { id }), {}),
39798
40098
  render: renderRecord
39799
40099
  };
39800
40100
  },
@@ -41051,6 +41351,14 @@ var HANDLERS = {
41051
41351
  data: await requireClient().delete(`/health/shares/${requireArg(args, 0, "id")}`),
41052
41352
  render: renderRecord
41053
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
+ }),
41054
41362
  "health google status": async ({ requireClient }) => ({
41055
41363
  data: await requireClient().get("/health/google-health"),
41056
41364
  render: renderRecord
@@ -41542,9 +41850,9 @@ var HANDLERS = {
41542
41850
  const id = requireArg(args, 0, "guest list id");
41543
41851
  const kind = parseInput(guestExportKindSchema, flagString(flags, "kind") ?? "venue");
41544
41852
  const path = `/guest-lists/${id}/exports/${kind}`;
41545
- if (kind === "floor-plan") {
41853
+ if (kind === "floor-plan" || kind === "invitations") {
41546
41854
  const file = await requireClient().getBytes(path);
41547
- const out = flagString(flags, "out") ?? basename2(file.name ?? "floor-plan.pdf");
41855
+ const out = flagString(flags, "out") ?? basename2(file.name ?? `${kind}.pdf`);
41548
41856
  writeFileSync2(out, file.bytes);
41549
41857
  return {
41550
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.301.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",