@robbeverhelst/do 1.301.0 → 1.302.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 +202 -61
  2. package/package.json +1 -1
package/dist/do.js CHANGED
@@ -5236,6 +5236,7 @@ var rsvpLinks = pgTable("rsvp_links", {
5236
5236
  tokenHash: text("token_hash").notNull().unique(),
5237
5237
  mode: rsvpLinkMode("mode").notNull().default("full"),
5238
5238
  tokenSealed: text("token_sealed"),
5239
+ shortCodeHash: text("short_code_hash").unique(),
5239
5240
  expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
5240
5241
  revokedAt: timestamp("revoked_at", { withTimezone: true }),
5241
5242
  lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
@@ -6052,6 +6053,12 @@ var healthLabAnalytes = pgTable("health_lab_analytes", {
6052
6053
  note: text("note"),
6053
6054
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
6054
6055
  }, (t) => [primaryKey({ columns: [t.tenantId, t.userId, t.analyte] })]);
6056
+ var healthDataKeys = pgTable("health_data_keys", {
6057
+ tenantId: text("tenant_id").notNull().references(() => tenants.id),
6058
+ userId: text("user_id").notNull().references(() => users.id),
6059
+ sealedKey: text("sealed_key").notNull(),
6060
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
6061
+ }, (t) => [primaryKey({ columns: [t.tenantId, t.userId] })]);
6055
6062
 
6056
6063
  // ../../packages/schema/src/db/journal.ts
6057
6064
  var journalDayStatus = pgEnum("journal_day_status", ["open", "summarised", "reviewed"]);
@@ -23214,6 +23221,14 @@ var updateHealthProfileInputSchema = object({
23214
23221
  primaryGpContactId: ulidSchema.nullish()
23215
23222
  }).strict();
23216
23223
  var grantHealthConsentInputSchema = object({ version: literal(HEALTH_CONSENT_VERSION) }).strict();
23224
+ var HEALTH_ERASE_PHRASE = "delete my health data";
23225
+ var eraseHealthDataInputSchema = object({ confirm: literal(HEALTH_ERASE_PHRASE) }).strict();
23226
+ var healthErasureSchema = object({
23227
+ records: number2().int().nonnegative(),
23228
+ files: number2().int().nonnegative(),
23229
+ keyShredded: boolean3(),
23230
+ consent: healthConsentStateSchema
23231
+ });
23217
23232
  var healthEpisodeListQuerySchema = object({
23218
23233
  status: healthEpisodeStatusSchema.optional()
23219
23234
  });
@@ -23478,7 +23493,8 @@ var FILE_SOURCES = [
23478
23493
  "doccle",
23479
23494
  "doccle_zip",
23480
23495
  "scan",
23481
- "game_save"
23496
+ "game_save",
23497
+ "google_photos"
23482
23498
  ];
23483
23499
  var fileSourceSchema = union([
23484
23500
  _enum2(FILE_SOURCES),
@@ -27789,6 +27805,11 @@ var guestListOverviewSchema = object({
27789
27805
  })
27790
27806
  });
27791
27807
  var RSVP_TOKEN_RE = /^rsvp_[A-Za-z0-9_-]{43}$/;
27808
+ var RSVP_CODE_ALPHABET = "23456789ABCDEFGHJKMNPQRSTWXYZ";
27809
+ var RSVP_CODE_LENGTH = 6;
27810
+ var RSVP_CODE_RE = new RegExp(`^[${RSVP_CODE_ALPHABET}]{${RSVP_CODE_LENGTH}}$`);
27811
+ var resolveRsvpCodeInputSchema = object({ code: string2().max(32) }).strict();
27812
+ var resolvedRsvpCodeSchema = object({ token: string2().regex(RSVP_TOKEN_RE) });
27792
27813
  var RSVP_LINK_MODES = ["address", "full"];
27793
27814
  var rsvpLinkModeSchema = _enum2(RSVP_LINK_MODES);
27794
27815
  var rsvpLinkSchema = object({
@@ -27806,7 +27827,8 @@ var rsvpLinkSchema = object({
27806
27827
  });
27807
27828
  var mintedRsvpLinkSchema = rsvpLinkSchema.extend({
27808
27829
  token: string2().regex(RSVP_TOKEN_RE),
27809
- path: string2()
27830
+ path: string2(),
27831
+ shortCode: string2().regex(RSVP_CODE_RE).nullable()
27810
27832
  });
27811
27833
  var createRsvpLinksInputSchema = object({
27812
27834
  guestListId: ulidSchema,
@@ -29564,6 +29586,102 @@ function toCollectionTypeSummary(config) {
29564
29586
  }
29565
29587
  var COLLECTION_TYPE_SUMMARIES = COLLECTION_TYPE_LIST.map(toCollectionTypeSummary);
29566
29588
 
29589
+ // ../../packages/schema/src/operations/define.ts
29590
+ function defineOperations(table) {
29591
+ return table;
29592
+ }
29593
+ var PUBLIC_API_PREFIX = "/api/v1";
29594
+ function pathOf(op, params = {}) {
29595
+ return op.path.replace(/\{([A-Za-z0-9_]+)\}/g, (_, name) => {
29596
+ const value = params[name];
29597
+ if (value === undefined)
29598
+ throw new Error(`pathOf: no value for {${name}} in ${op.path}`);
29599
+ return encodeURIComponent(value);
29600
+ });
29601
+ }
29602
+ function routeOf(op) {
29603
+ return `${op.method.toUpperCase()} ${PUBLIC_API_PREFIX}${op.path}`;
29604
+ }
29605
+
29606
+ // ../../packages/schema/src/operations/tap-tags.ts
29607
+ var READ = ["habits:read", "routines:read"];
29608
+ var WRITE = ["habits:write", "routines:write"];
29609
+ var pathParams = { id: "The tag's id: the ULID the sticker's link carries (`/t/{id}`)." };
29610
+ var tapTagOperations = defineOperations({
29611
+ list: {
29612
+ method: "get",
29613
+ path: "/tap-tags",
29614
+ scopes: READ,
29615
+ tag: "Habits",
29616
+ summary: "List tap tags",
29617
+ 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`.",
29618
+ status: 200,
29619
+ response: { description: "Every tag.", list: true },
29620
+ errors: { 403: true }
29621
+ },
29622
+ create: {
29623
+ method: "post",
29624
+ path: "/tap-tags",
29625
+ scopes: WRITE,
29626
+ tag: "Habits",
29627
+ summary: "Make a tap tag",
29628
+ 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.",
29629
+ body: createTapTagInputSchema,
29630
+ status: 201,
29631
+ response: { description: "The tag." },
29632
+ errors: { 400: true, 403: true }
29633
+ },
29634
+ get: {
29635
+ method: "get",
29636
+ path: "/tap-tags/{id}",
29637
+ pathParams,
29638
+ scopes: READ,
29639
+ tag: "Habits",
29640
+ summary: "Get a tap tag",
29641
+ description: "One tag.",
29642
+ status: 200,
29643
+ response: { description: "The tag." },
29644
+ errors: { 404: true }
29645
+ },
29646
+ update: {
29647
+ method: "patch",
29648
+ path: "/tap-tags/{id}",
29649
+ pathParams,
29650
+ scopes: WRITE,
29651
+ tag: "Habits",
29652
+ summary: "Rename or rebind a tap tag",
29653
+ description: "Change its `label`, or bind it to another habit or routine (`kind` and `targetId` together): the sticker stays as it is.",
29654
+ body: updateTapTagInputSchema,
29655
+ status: 200,
29656
+ response: { description: "The tag." },
29657
+ errors: { 400: true, 404: true }
29658
+ },
29659
+ remove: {
29660
+ method: "delete",
29661
+ path: "/tap-tags/{id}",
29662
+ pathParams,
29663
+ scopes: WRITE,
29664
+ tag: "Habits",
29665
+ summary: "Delete a tap tag",
29666
+ description: "The sticker's link stops doing anything.",
29667
+ status: 204,
29668
+ response: { description: "Deleted." },
29669
+ errors: { 404: true }
29670
+ },
29671
+ tap: {
29672
+ method: "post",
29673
+ path: "/tap-tags/{id}/tap",
29674
+ pathParams,
29675
+ scopes: WRITE,
29676
+ tag: "Habits",
29677
+ summary: "Tap a tap tag",
29678
+ 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.",
29679
+ status: 200,
29680
+ response: { description: "The tap's receipt." },
29681
+ errors: { 403: "A scope is missing, or the key acts for no member.", 404: true }
29682
+ }
29683
+ });
29684
+
29567
29685
  // ../../node_modules/ulidx/dist/node/index.js
29568
29686
  import crypto from "crypto";
29569
29687
 
@@ -29816,7 +29934,7 @@ function parseCapture2(input, now) {
29816
29934
 
29817
29935
  // src/meta.ts
29818
29936
  var CLI_NAME = "do";
29819
- var VERSION = "1.301.0";
29937
+ var VERSION = "1.302.0";
29820
29938
  var API_PREFIX = "/api/v1";
29821
29939
  var DEFAULT_API_URL = "http://localhost:3001";
29822
29940
 
@@ -30045,7 +30163,7 @@ function resolveConfig(io, flags) {
30045
30163
  }
30046
30164
 
30047
30165
  // src/contacts-commands.ts
30048
- var WRITE = ["contacts:write"];
30166
+ var WRITE2 = ["contacts:write"];
30049
30167
  var FIELD_FLAGS = [
30050
30168
  { name: "name", type: "string", description: "The display name (or pass it as the argument)." },
30051
30169
  { name: "kind", type: "string", description: "person (the default) or organization." },
@@ -30088,7 +30206,7 @@ var CONTACT_COMMAND_SPECS = [
30088
30206
  summary: "Add a contact: a person or an organisation, with their details.",
30089
30207
  args: [{ name: "name", required: false, description: "The display name." }],
30090
30208
  flags: FIELD_FLAGS,
30091
- scopes: WRITE,
30209
+ scopes: WRITE2,
30092
30210
  returns: "ContactView",
30093
30211
  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
30212
  },
@@ -30097,7 +30215,7 @@ var CONTACT_COMMAND_SPECS = [
30097
30215
  summary: "Change a contact's details.",
30098
30216
  args: [{ name: "id", required: true, description: "The contact's id." }],
30099
30217
  flags: FIELD_FLAGS,
30100
- scopes: WRITE,
30218
+ scopes: WRITE2,
30101
30219
  returns: "ContactView",
30102
30220
  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
30221
  }
@@ -30202,6 +30320,38 @@ var DUE_BEFORE_FLAG = {
30202
30320
  description: "only rows due strictly before this instant (ISO 8601 or YYYY-MM-DD)",
30203
30321
  query: "dueBefore"
30204
30322
  };
30323
+ function entityFromOperations(ops, spec) {
30324
+ const path = ops.list.path;
30325
+ const sameScopes = (a, b) => [...a.scopes].sort().join() === [...b.scopes].sort().join();
30326
+ const reads = [ops.list, ops.get].filter((op) => op !== undefined);
30327
+ const writes = [ops.create, ops.update, ops.remove].filter((op) => op !== undefined);
30328
+ const problems = [
30329
+ ...[ops.create].filter((op) => op && (op.method !== "post" || op.path !== path)),
30330
+ ...[ops.get, ops.update, ops.remove].filter((op) => op && op.path !== `${path}/{id}`),
30331
+ ...reads.filter((op) => !sameScopes(op, ops.list)),
30332
+ ...writes.filter((op) => writes[0] && !sameScopes(op, writes[0]))
30333
+ ];
30334
+ if (problems.length > 0) {
30335
+ throw new Error(`entityFromOperations(${spec.name}): ${problems.map((op) => op?.summary).join("; ")} don't fit the entity verbs`);
30336
+ }
30337
+ const [firstWrite] = writes;
30338
+ return {
30339
+ name: spec.name,
30340
+ path,
30341
+ readScopes: [...ops.list.scopes],
30342
+ returns: spec.returns,
30343
+ canGet: ops.get !== undefined,
30344
+ listFlags: spec.listFlags,
30345
+ ...firstWrite ? {
30346
+ write: {
30347
+ scopes: [...firstWrite.scopes],
30348
+ ...ops.create ? { createSchema: ops.create.body, createInput: spec.createInput } : {},
30349
+ ...ops.update ? { updateSchema: ops.update.body, updateInput: spec.updateInput } : {},
30350
+ canDelete: ops.remove !== undefined
30351
+ }
30352
+ } : {}
30353
+ };
30354
+ }
30205
30355
  var ENTITIES = [
30206
30356
  {
30207
30357
  name: "tasks",
@@ -30721,22 +30871,13 @@ var ENTITIES = [
30721
30871
  canDelete: true
30722
30872
  }
30723
30873
  },
30724
- {
30874
+ entityFromOperations(tapTagOperations, {
30725
30875
  name: "tap-tags",
30726
- path: "/tap-tags",
30727
- readScopes: ["habits:read", "routines:read"],
30728
30876
  returns: "TapTagView",
30729
- canGet: true,
30730
30877
  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
- },
30878
+ createInput: "CreateTapTagInput",
30879
+ updateInput: "UpdateTapTagInput"
30880
+ }),
30740
30881
  {
30741
30882
  name: "animals",
30742
30883
  path: "/animals",
@@ -30933,8 +31074,8 @@ function entityOperations(e) {
30933
31074
  }
30934
31075
 
30935
31076
  // src/health-commands.ts
30936
- var READ = ["health:read"];
30937
- var WRITE2 = ["health:write"];
31077
+ var READ2 = ["health:read"];
31078
+ var WRITE3 = ["health:write"];
30938
31079
  var DATA_FLAG = {
30939
31080
  name: "data",
30940
31081
  type: "string",
@@ -30948,7 +31089,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
30948
31089
  summary: `List your health ${noun}s.`,
30949
31090
  args: [],
30950
31091
  flags: listFlags,
30951
- scopes: READ,
31092
+ scopes: READ2,
30952
31093
  returns
30953
31094
  },
30954
31095
  {
@@ -30956,7 +31097,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
30956
31097
  summary: `Show one health ${noun}.`,
30957
31098
  args: [id],
30958
31099
  flags: [],
30959
- scopes: READ,
31100
+ scopes: READ2,
30960
31101
  returns
30961
31102
  },
30962
31103
  {
@@ -30964,7 +31105,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
30964
31105
  summary: `Add a health ${noun} (--data '${createInput}').`,
30965
31106
  args: [],
30966
31107
  flags: [DATA_FLAG, ...addFlags],
30967
- scopes: WRITE2,
31108
+ scopes: WRITE3,
30968
31109
  returns,
30969
31110
  notes: `Body: ${createInput}. Always private to you; subjectNodeId names who it is about (absent = you).`
30970
31111
  },
@@ -30973,7 +31114,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
30973
31114
  summary: `Change a health ${noun} (--data '${updateInput}').`,
30974
31115
  args: [id],
30975
31116
  flags: [DATA_FLAG],
30976
- scopes: WRITE2,
31117
+ scopes: WRITE3,
30977
31118
  returns,
30978
31119
  notes: `Body: ${updateInput}. null clears a field.`
30979
31120
  },
@@ -30982,7 +31123,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
30982
31123
  summary: `Delete a health ${noun}.`,
30983
31124
  args: [id],
30984
31125
  flags: [],
30985
- scopes: WRITE2,
31126
+ scopes: WRITE3,
30986
31127
  returns: "Deleted",
30987
31128
  notes: "Destructive: needs --allow-destructive. An Agent's delete waits on your confirmation."
30988
31129
  }
@@ -30994,7 +31135,7 @@ var HEALTH_COMMAND_SPECS = [
30994
31135
  summary: "Show whether Health is on (only you turn it on or off, in do).",
30995
31136
  args: [],
30996
31137
  flags: [],
30997
- scopes: READ,
31138
+ scopes: READ2,
30998
31139
  returns: "HealthConsentState",
30999
31140
  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
31141
  },
@@ -31003,7 +31144,7 @@ var HEALTH_COMMAND_SPECS = [
31003
31144
  summary: "Show your health profile: blood group, allergies, lifestyle, notes, emergency contact, GP.",
31004
31145
  args: [],
31005
31146
  flags: [],
31006
- scopes: READ,
31147
+ scopes: READ2,
31007
31148
  returns: "HealthProfile"
31008
31149
  },
31009
31150
  {
@@ -31011,7 +31152,7 @@ var HEALTH_COMMAND_SPECS = [
31011
31152
  summary: "Show your Medical ID: name, blood group, allergies, current medications, height, emergency contact.",
31012
31153
  args: [],
31013
31154
  flags: [],
31014
- scopes: READ,
31155
+ scopes: READ2,
31015
31156
  returns: "HealthMedicalId",
31016
31157
  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
31158
  },
@@ -31032,7 +31173,7 @@ var HEALTH_COMMAND_SPECS = [
31032
31173
  description: `Sex, in your words, for the doctor's page ("female"); "" clears it.`
31033
31174
  }
31034
31175
  ],
31035
- scopes: WRITE2,
31176
+ scopes: WRITE3,
31036
31177
  returns: "HealthProfile",
31037
31178
  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
31179
  },
@@ -31041,7 +31182,7 @@ var HEALTH_COMMAND_SPECS = [
31041
31182
  summary: "Log one health message: find or start the Episode, add a symptom and/or a dose.",
31042
31183
  args: [],
31043
31184
  flags: [DATA_FLAG],
31044
- scopes: WRITE2,
31185
+ scopes: WRITE3,
31045
31186
  returns: "HealthReceipt",
31046
31187
  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
31188
  },
@@ -31050,7 +31191,7 @@ var HEALTH_COMMAND_SPECS = [
31050
31191
  summary: `Correct a symptom entry and get a receipt (--data '{"bodyRegion":"ankle_l"}').`,
31051
31192
  args: [{ name: "entry", required: true, description: "The entry's id (its latest version)." }],
31052
31193
  flags: [DATA_FLAG],
31053
- scopes: WRITE2,
31194
+ scopes: WRITE3,
31054
31195
  returns: "HealthReceipt"
31055
31196
  },
31056
31197
  {
@@ -31061,7 +31202,7 @@ var HEALTH_COMMAND_SPECS = [
31061
31202
  { name: "trend", required: true, description: "better|same|worse, against yesterday." }
31062
31203
  ],
31063
31204
  flags: [],
31064
- scopes: WRITE2,
31205
+ scopes: WRITE3,
31065
31206
  returns: "HealthReceipt",
31066
31207
  notes: "One a day: a second check-in the same day corrects the first. The receipt carries its undo."
31067
31208
  },
@@ -31078,7 +31219,7 @@ var HEALTH_COMMAND_SPECS = [
31078
31219
  description: "Post the confirmation card in this thread (by slug); the agent's key only."
31079
31220
  }
31080
31221
  ],
31081
- scopes: WRITE2,
31222
+ scopes: WRITE3,
31082
31223
  returns: "HealthRedFlagsResult",
31083
31224
  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
31225
  },
@@ -31090,7 +31231,7 @@ var HEALTH_COMMAND_SPECS = [
31090
31231
  { name: "id", required: true, description: "The id from the receipt's undo." }
31091
31232
  ],
31092
31233
  flags: [],
31093
- scopes: WRITE2,
31234
+ scopes: WRITE3,
31094
31235
  returns: "HealthUndoResult",
31095
31236
  notes: "A POST, so an Agent's undo never waits on a confirmation."
31096
31237
  },
@@ -31099,7 +31240,7 @@ var HEALTH_COMMAND_SPECS = [
31099
31240
  summary: "Find the open Episode a complaint belongs to, or start one (--data UpsertHealthEpisodeInput).",
31100
31241
  args: [],
31101
31242
  flags: [DATA_FLAG],
31102
- scopes: WRITE2,
31243
+ scopes: WRITE3,
31103
31244
  returns: "HealthEpisodeUpsert"
31104
31245
  },
31105
31246
  {
@@ -31109,7 +31250,7 @@ var HEALTH_COMMAND_SPECS = [
31109
31250
  flags: [
31110
31251
  { name: "episode", type: "string", description: "The Episode; default the latest open one." }
31111
31252
  ],
31112
- scopes: READ,
31253
+ scopes: READ2,
31113
31254
  returns: "HealthSummary"
31114
31255
  },
31115
31256
  {
@@ -31132,7 +31273,7 @@ var HEALTH_COMMAND_SPECS = [
31132
31273
  summary: "Your For-the-doctor links: sections, expiry, opens; never the secret.",
31133
31274
  args: [],
31134
31275
  flags: [],
31135
- scopes: READ,
31276
+ scopes: READ2,
31136
31277
  returns: "HealthShare[]",
31137
31278
  notes: "Only the member makes a share, in do; a key can list and take one back."
31138
31279
  },
@@ -31141,7 +31282,7 @@ var HEALTH_COMMAND_SPECS = [
31141
31282
  summary: "Take a For-the-doctor link back: it stops opening.",
31142
31283
  args: [{ name: "id", required: true, description: "The share's id." }],
31143
31284
  flags: [],
31144
- scopes: WRITE2,
31285
+ scopes: WRITE3,
31145
31286
  returns: "HealthShare"
31146
31287
  },
31147
31288
  ...recordSpecs("conditions", "condition", "condition", "HealthConditionView", "CreateHealthConditionInput", "UpdateHealthConditionInput"),
@@ -31159,7 +31300,7 @@ var HEALTH_COMMAND_SPECS = [
31159
31300
  description: 'Their role on it, in your words ("dermatologist").'
31160
31301
  }
31161
31302
  ],
31162
- scopes: WRITE2,
31303
+ scopes: WRITE3,
31163
31304
  returns: "HealthConditionView",
31164
31305
  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
31306
  },
@@ -31171,7 +31312,7 @@ var HEALTH_COMMAND_SPECS = [
31171
31312
  { name: "contact", required: true, description: "The Contact's id." }
31172
31313
  ],
31173
31314
  flags: [],
31174
- scopes: WRITE2,
31315
+ scopes: WRITE3,
31175
31316
  returns: "HealthConditionView",
31176
31317
  notes: "An Agent's removal waits on your confirmation, like a delete."
31177
31318
  },
@@ -31208,7 +31349,7 @@ var HEALTH_COMMAND_SPECS = [
31208
31349
  { name: "at", type: "string", description: "When it happened (ISO); defaults to now." },
31209
31350
  { name: "note", type: "string", description: "A free-text note." }
31210
31351
  ],
31211
- scopes: WRITE2,
31352
+ scopes: WRITE3,
31212
31353
  returns: "HealthSymptomEntry",
31213
31354
  notes: "An Agent's entry is source chat. For qualifiers, send --data with LogHealthSymptomInput."
31214
31355
  },
@@ -31224,7 +31365,7 @@ var HEALTH_COMMAND_SPECS = [
31224
31365
  }
31225
31366
  ],
31226
31367
  flags: [DATA_FLAG],
31227
- scopes: WRITE2,
31368
+ scopes: WRITE3,
31228
31369
  returns: "HealthSymptomEntry",
31229
31370
  notes: "Body: CorrectHealthSymptomInput; fields not named are copied. The history stays."
31230
31371
  },
@@ -31236,7 +31377,7 @@ var HEALTH_COMMAND_SPECS = [
31236
31377
  { name: "entry", required: true, description: "The entry's id." }
31237
31378
  ],
31238
31379
  flags: [],
31239
- scopes: WRITE2,
31380
+ scopes: WRITE3,
31240
31381
  returns: "Deleted"
31241
31382
  },
31242
31383
  ...recordSpecs("visits", "visit", "visit", "HealthEncounterView", "CreateHealthEncounterInput", "UpdateHealthEncounterInput"),
@@ -31246,7 +31387,7 @@ var HEALTH_COMMAND_SPECS = [
31246
31387
  summary: "List the doses of a Medication, latest first.",
31247
31388
  args: [{ name: "med", required: true, description: "The Medication's id." }],
31248
31389
  flags: [],
31249
- scopes: READ,
31390
+ scopes: READ2,
31250
31391
  returns: "HealthMedicationDose"
31251
31392
  },
31252
31393
  {
@@ -31260,7 +31401,7 @@ var HEALTH_COMMAND_SPECS = [
31260
31401
  { name: "at", type: "string", description: "When (ISO); defaults to now." },
31261
31402
  { name: "note", type: "string", description: "A free-text note." }
31262
31403
  ],
31263
- scopes: WRITE2,
31404
+ scopes: WRITE3,
31264
31405
  returns: "HealthMedicationDose"
31265
31406
  },
31266
31407
  {
@@ -31271,7 +31412,7 @@ var HEALTH_COMMAND_SPECS = [
31271
31412
  { name: "dose", required: true, description: "The dose's id." }
31272
31413
  ],
31273
31414
  flags: [DATA_FLAG],
31274
- scopes: WRITE2,
31415
+ scopes: WRITE3,
31275
31416
  returns: "HealthMedicationDose",
31276
31417
  notes: "Body: UpdateHealthDoseInput."
31277
31418
  },
@@ -31283,7 +31424,7 @@ var HEALTH_COMMAND_SPECS = [
31283
31424
  { name: "dose", required: true, description: "The dose's id." }
31284
31425
  ],
31285
31426
  flags: [],
31286
- scopes: WRITE2,
31427
+ scopes: WRITE3,
31287
31428
  returns: "Deleted"
31288
31429
  },
31289
31430
  ...recordSpecs("labs", "lab", "lab draw", "HealthLabPanelView", "CreateHealthLabPanelInput", "UpdateHealthLabPanelInput"),
@@ -31305,7 +31446,7 @@ var HEALTH_COMMAND_SPECS = [
31305
31446
  description: "The file's media type, when its name doesn't say."
31306
31447
  }
31307
31448
  ],
31308
- scopes: WRITE2,
31449
+ scopes: WRITE3,
31309
31450
  returns: "HealthLabImportResult",
31310
31451
  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
31452
  },
@@ -31314,7 +31455,7 @@ var HEALTH_COMMAND_SPECS = [
31314
31455
  summary: "The canonical analytes: key, printed name, group, unit, LOINC and aliases.",
31315
31456
  args: [],
31316
31457
  flags: [],
31317
- scopes: READ,
31458
+ scopes: READ2,
31318
31459
  returns: "LabAnalyteDef[]",
31319
31460
  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
31461
  },
@@ -31329,7 +31470,7 @@ var HEALTH_COMMAND_SPECS = [
31329
31470
  description: "Required: only report, change nothing."
31330
31471
  }
31331
31472
  ],
31332
- scopes: READ,
31473
+ scopes: READ2,
31333
31474
  returns: "HealthLabNormalizeReport",
31334
31475
  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
31476
  },
@@ -31338,7 +31479,7 @@ var HEALTH_COMMAND_SPECS = [
31338
31479
  summary: "Every analyte your draws measured, each with its trend, grouped as a lab sheet.",
31339
31480
  args: [],
31340
31481
  flags: [],
31341
- scopes: READ,
31482
+ scopes: READ2,
31342
31483
  returns: "HealthLabOverview"
31343
31484
  },
31344
31485
  {
@@ -31346,7 +31487,7 @@ var HEALTH_COMMAND_SPECS = [
31346
31487
  summary: "Whether your Google Health is connected, the last sync, and what stops it.",
31347
31488
  args: [],
31348
31489
  flags: [],
31349
- scopes: READ,
31490
+ scopes: READ2,
31350
31491
  returns: "GoogleHealthStatus",
31351
31492
  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
31493
  },
@@ -31355,7 +31496,7 @@ var HEALTH_COMMAND_SPECS = [
31355
31496
  summary: "Sync your Google Health now (sleep, resting heart rate, HRV, steps, workouts), as the morning sync does.",
31356
31497
  args: [],
31357
31498
  flags: [],
31358
- scopes: WRITE2,
31499
+ scopes: WRITE3,
31359
31500
  returns: "GoogleHealthSyncResult",
31360
31501
  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
31502
  },
@@ -31364,7 +31505,7 @@ var HEALTH_COMMAND_SPECS = [
31364
31505
  summary: "One analyte's trend across draws (do health analyte vitamin_d).",
31365
31506
  args: [{ name: "analyte", required: true, description: "The analyte key (vitamin_d)." }],
31366
31507
  flags: [],
31367
- scopes: READ,
31508
+ scopes: READ2,
31368
31509
  returns: "HealthLabAnalyteView"
31369
31510
  },
31370
31511
  {
@@ -31372,7 +31513,7 @@ var HEALTH_COMMAND_SPECS = [
31372
31513
  summary: `Keep a target band or the supplement for an analyte (--data '{"targetLow":40,"targetHigh":60}').`,
31373
31514
  args: [{ name: "analyte", required: true, description: "The analyte key (vitamin_d)." }],
31374
31515
  flags: [DATA_FLAG],
31375
- scopes: WRITE2,
31516
+ scopes: WRITE3,
31376
31517
  returns: "HealthLabAnalyteView",
31377
31518
  notes: "Body: SetHealthLabAnalyteInput. An absent key keeps a field; null clears it."
31378
31519
  }
@@ -32712,9 +32853,9 @@ var COMMAND_SPECS = [
32712
32853
  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
32854
  args: [{ name: "id", required: true, description: "The tag id (the ULID in /t/<id>)." }],
32714
32855
  flags: [],
32715
- scopes: ["habits:write", "routines:write"],
32856
+ scopes: [...tapTagOperations.tap.scopes],
32716
32857
  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)."
32858
+ 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
32859
  },
32719
32860
  {
32720
32861
  path: ["metrics", "list"],
@@ -35932,12 +36073,12 @@ var COMMAND_SPECS = [
35932
36073
  },
35933
36074
  {
35934
36075
  path: ["rsvp-links", "token"],
35935
- summary: "Show a live RSVP link again: the same token it was minted with.",
36076
+ summary: "Show a live RSVP link again: the same token it was minted with, and a whole-form link's short code.",
35936
36077
  args: [{ name: "id", required: true, description: "The link's id." }],
35937
36078
  flags: [],
35938
36079
  scopes: ["contacts:write"],
35939
36080
  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."
36081
+ 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
36082
  },
35942
36083
  {
35943
36084
  path: ["rsvp-links", "replace"],
@@ -39794,7 +39935,7 @@ var HANDLERS = {
39794
39935
  "tap-tags tap": async ({ args, requireClient }) => {
39795
39936
  const id = requireArg(args, 0, "tag id");
39796
39937
  return {
39797
- data: await requireClient().post(`/tap-tags/${encodeURIComponent(id)}/tap`, {}),
39938
+ data: await requireClient().post(pathOf(tapTagOperations.tap, { id }), {}),
39798
39939
  render: renderRecord
39799
39940
  };
39800
39941
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robbeverhelst/do",
3
- "version": "1.301.0",
3
+ "version": "1.302.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",