@go-labs-sg/gosh 1.0.0 → 1.2.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.
package/README.md CHANGED
@@ -65,6 +65,17 @@ Before requesting approval, login verifies write/read/delete capability with a u
65
65
 
66
66
  See the [shared authentication contract](../../cli-auth/README.md) for recovery and verification limits.
67
67
 
68
+ ## Realtime watch reconciliation
69
+
70
+ `gosh player watch` and `gosh submission watch` continue to emit newline-delimited
71
+ JSON. On a v2-enabled server, process-local changes can arrive immediately;
72
+ cross-process changes are reconciled through the existing 15-second resync
73
+ cadence while a watch is active. A player `resync` message or submission
74
+ `resynced: true` message means fetch the current authorized state again, not that
75
+ a complete business-event replay was delivered. Command names, arguments and
76
+ permission checks are unchanged. Browser clients use scoped v2 subscriptions
77
+ instead of this compatibility cadence.
78
+
68
79
  ## Authentication
69
80
 
70
81
  ```bash
@@ -99,6 +110,53 @@ Commands use `--input '<json>'` for the exact validated tRPC input object. This
99
110
 
100
111
  The command registry exposes the supported GOSH tRPC procedures across organizer and global-admin operations, organization invitations and membership, player workflows, public results/login procedures, uploads, and realtime subscriptions. Update the registry alongside API changes, then run `bun --filter @go-labs-sg/gosh generate:commands` from the repository root to regenerate the manifest, reference, and typed operations.
101
112
 
113
+ ### Messages after submitting
114
+
115
+ `gosh mission create` and `gosh mission update` accept optional `revealMessage`
116
+ plain text (up to 2,000 characters, with line breaks) in `--input`. It is hidden
117
+ from players until their team/player has a submission, regardless of review
118
+ status. Skipping a mission does not reveal it. The player submit response includes
119
+ `revealMessage`, including already-completed submissions; station and map reads
120
+ include it only on submitted missions.
121
+
122
+ ```bash
123
+ gosh mission create \
124
+ --input '{"stationId":"station-id","name":"Pavilion materials","type":"FILL_IN_THE_BLANK","points":10,"correctAnswer":"grocery bags","revealMessage":"The pavilion is clad with grocery bags.\nNotice how everyday materials are reused."}' \
125
+ --target station-id \
126
+ --allow-state-change
127
+ ```
128
+
129
+ For updates, send the existing mission editor fields (including option IDs and
130
+ answer keys) together with `missionId` and `revealMessage`. Omitting
131
+ `revealMessage` preserves it; `"revealMessage":""` clears it. The message is
132
+ display-only and can change during a live event without changing grading or
133
+ saved playthroughs. Templates, station clones and event versions preserve it.
134
+
135
+ ### Multiple accepted text answers
136
+
137
+ Fill-in-the-blank missions accept the primary `correctAnswer` or any of up to 50
138
+ `acceptedAnswers` (each trimmed, non-empty and at most 200 characters). Case and
139
+ repeated whitespace are ignored; no fuzzy or partial matching is used. Duplicate
140
+ alternatives are removed after normalization. Alternatives require a primary
141
+ answer and cannot be used for other mission types.
142
+
143
+ ```bash
144
+ gosh mission create --input '{"stationId":"station-id","name":"Climbing plants","type":"FILL_IN_THE_BLANK","correctAnswer":"Mesh","acceptedAnswers":["Trellis","Scaffolding"]}'
145
+ gosh mission update --input '{"missionId":"mission-id","name":"Climbing plants","type":"FILL_IN_THE_BLANK","correctAnswer":"Mesh","acceptedAnswers":["Trellis","Frame","Scaffolding"]}'
146
+ ```
147
+
148
+ When updating, omit `acceptedAnswers` to preserve the saved list or pass `[]` to
149
+ clear it. Alternative-answer changes affect future submissions only: stored
150
+ answers and awarded points are never re-graded. Existing restrictions on changing
151
+ the primary answer, mission type and choice options after submissions still apply.
152
+ Templates, station clones and saved event versions preserve alternatives. Answer
153
+ keys are visible to authorized admins/reviewers, never in player mission reads.
154
+
155
+ Deploy prerequisite: apply the additive GOSH database schema changes for both
156
+ `Mission.acceptedAnswers` and `GoshTemplateMission.acceptedAnswers`, each defaulting
157
+ to `[]`, before deploying the updated server. Older event snapshots restore an
158
+ empty list. Client generation alone does not apply these database changes.
159
+
102
160
  ### Templates and organization scope
103
161
 
104
162
  Template commands use the selected organization as their scope. Provide its ID in
@@ -183,10 +241,14 @@ value in that template team's `order` field:
183
241
  Events default to `OPEN` progression, which keeps the existing open-play flow.
184
242
  Sequential progression starts each team or individual on an ordered station
185
243
  route. Group routing is available only for `TEAM` play mode. Create teams and
186
- assign them to routing groups before launching the event; sequential setup
187
- freezes when play starts. Grouped OPEN events lock only a started team's group
188
- assignment, a started group's route and the gameplay settings. A group shares its ordered station route, while each team keeps
189
- independent progress through that route.
244
+ assign them to routing groups before launching the event. Routes, stations
245
+ and missions stay editable after play starts: each edit re-syncs every started
246
+ team's saved progress in the same transaction. Played stations keep their
247
+ progress and order, while unplayed stations follow the new route. An edit that
248
+ would remove the station a team is in the middle of fails with a conflict and
249
+ changes nothing. A started team's group assignment, team creation, play mode
250
+ and progression settings stay locked. A group shares its ordered station route,
251
+ while each team keeps independent progress through that route.
190
252
 
191
253
  Configure gameplay, then manage the event's routing groups and their complete
192
254
  station lists:
@@ -281,7 +343,7 @@ gosh player mission skip \
281
343
  ```
282
344
 
283
345
  Player actions require event enrollment and the configured team submission
284
- permissions. A skipped mission is irreversible and earns zero points; it resolves only that mission,
346
+ permissions. A player cannot undo a skip: it earns zero points and resolves only that mission,
285
347
  preserves points from the station's other submitted missions, and advances the
286
348
  route once all station missions are submitted or skipped. The station timer
287
349
  runs from check-in/start through the final mission submission or skip. It
@@ -289,6 +351,34 @@ measures mission work only: travel between stations and staff review time are
289
351
  excluded. Existing leaderboard and results commands include the resulting
290
352
  timing information.
291
353
 
354
+ A host with `reviewSubmissions` can reopen one submitted or skipped mission for
355
+ a team (`teamId`) or individual player (`eventPlayerId`) on a fixed-order route
356
+ while the event is live:
357
+
358
+ ```bash
359
+ gosh mission reopen \
360
+ --input '{"eventId":"event-id","missionId":"mission-1","teamId":"team-1"}' \
361
+ --target team-1 \
362
+ --allow-state-change
363
+ ```
364
+
365
+ The earlier submission's points keep counting until the team submits again;
366
+ then only the new submission counts, up or down. Station times and the team's
367
+ finish time are kept, and a finished station accepts the reopened mission
368
+ until it is resolved. Pending submissions must be reviewed first, and a
369
+ reopened mission that still holds earlier points cannot be skipped.
370
+
371
+ When reviewing, pass the submission's `updatedAt` from the review queue as
372
+ `expectedUpdatedAt`. If the team resubmitted after you read it, the review is
373
+ refused with `CONFLICT` instead of scoring the new answer blind:
374
+
375
+ ```bash
376
+ gosh submission review \
377
+ --input '{"eventId":"event-id","organizationId":"org-id","submissionId":"submission-1","status":"APPROVED","expectedUpdatedAt":"2026-10-14T07:00:00.000Z"}' \
378
+ --target submission-1 \
379
+ --allow-state-change
380
+ ```
381
+
292
382
  ### Artwork requirements (no login required)
293
383
 
294
384
  Read the bundled upload policy before preparing station, mission, or branding artwork:
@@ -791,7 +791,7 @@
791
791
  "path": ["mission", "create"],
792
792
  "procedure": "event.createMission",
793
793
  "requiresAuth": true,
794
- "summary": "Create a mission in a station.",
794
+ "summary": "Create a mission in a station. Fill-in-the-blank inputs support correctAnswer and acceptedAnswers (up to 50 alternatives). Optional revealMessage (up to 2,000 characters) is shown only after submitting.",
795
795
  "targetKeys": [
796
796
  "id",
797
797
  "memberId",
@@ -823,7 +823,7 @@
823
823
  "path": ["mission", "update"],
824
824
  "procedure": "event.updateMission",
825
825
  "requiresAuth": true,
826
- "summary": "Update a mission.",
826
+ "summary": "Update a mission. Fill-in-the-blank inputs support acceptedAnswers; omit to preserve or pass [] to clear. Existing submissions are not re-graded. Edit revealMessage during live play; omit it to preserve or use an empty string to clear.",
827
827
  "targetKeys": [
828
828
  "id",
829
829
  "memberId",
@@ -1622,6 +1622,16 @@
1622
1622
  "name"
1623
1623
  ]
1624
1624
  },
1625
+ {
1626
+ "effects": ["state-change"],
1627
+ "input": "json",
1628
+ "operation": "mutation",
1629
+ "path": ["mission", "reopen"],
1630
+ "procedure": "event.reopenMission",
1631
+ "requiresAuth": true,
1632
+ "summary": "Reopen a submitted or skipped mission for one team (teamId) or player (eventPlayerId) on a fixed-order route. Earlier points count until they resubmit; then only the new submission counts. Finish times are kept.",
1633
+ "targetKeys": ["teamId", "eventPlayerId", "missionId"]
1634
+ },
1625
1635
  {
1626
1636
  "effects": ["state-change"],
1627
1637
  "input": "json",
@@ -2305,7 +2315,7 @@
2305
2315
  "path": ["player", "mission", "submit"],
2306
2316
  "procedure": "event.submitMission",
2307
2317
  "requiresAuth": true,
2308
- "summary": "Submit a mission response.",
2318
+ "summary": "Submit a mission response and return revealMessage, including already-completed submissions.",
2309
2319
  "targetKeys": [
2310
2320
  "id",
2311
2321
  "memberId",
@@ -332,7 +332,7 @@ Reorder shared stations, or a selected team's station route when scopeTeamId is
332
332
 
333
333
  ## `gosh mission create`
334
334
 
335
- Create a mission in a station.
335
+ Create a mission in a station. Fill-in-the-blank inputs support correctAnswer and acceptedAnswers (up to 50 alternatives). Optional revealMessage (up to 2,000 characters) is shown only after submitting.
336
336
 
337
337
  - Authentication: browser-authenticated CLI session required
338
338
  - Input: `--input '<json>'` required
@@ -340,7 +340,7 @@ Create a mission in a station.
340
340
 
341
341
  ## `gosh mission update`
342
342
 
343
- Update a mission.
343
+ Update a mission. Fill-in-the-blank inputs support acceptedAnswers; omit to preserve or pass [] to clear. Existing submissions are not re-graded. Edit revealMessage during live play; omit it to preserve or use an empty string to clear.
344
344
 
345
345
  - Authentication: browser-authenticated CLI session required
346
346
  - Input: `--input '<json>'` required
@@ -618,6 +618,14 @@ Review a mission submission.
618
618
  - Input: `--input '<json>'` required
619
619
  - Effects: state-change
620
620
 
621
+ ## `gosh mission reopen`
622
+
623
+ Reopen a submitted or skipped mission for one team (teamId) or player (eventPlayerId) on a fixed-order route. Earlier points count until they resubmit; then only the new submission counts. Finish times are kept.
624
+
625
+ - Authentication: browser-authenticated CLI session required
626
+ - Input: `--input '<json>'` required
627
+ - Effects: state-change
628
+
621
629
  ## `gosh results-share regenerate`
622
630
 
623
631
  Create a new results portal share token.
@@ -932,7 +940,7 @@ Permanently skip one unresolved mission for zero points when enabled; retain oth
932
940
 
933
941
  ## `gosh player mission submit`
934
942
 
935
- Submit a mission response.
943
+ Submit a mission response and return revealMessage, including already-completed submissions.
936
944
 
937
945
  - Authentication: browser-authenticated CLI session required
938
946
  - Input: `--input '<json>'` required
package/dist/index.js CHANGED
@@ -35,16 +35,16 @@ var __toESM = (mod, isNodeMode, target) => {
35
35
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
36
36
  var __require = import.meta.require;
37
37
 
38
- // ../../../node_modules/.bun/dotenv@18.0.7/node_modules/dotenv/dist/index.cjs
38
+ // ../../../node_modules/.bun/dotenv@18.0.8/node_modules/dotenv/dist/index.cjs
39
39
  var require_dist = __commonJS(function(exports, module) {
40
- var k = (e, n) => () => {
40
+ var j = (e, n) => () => {
41
41
  try {
42
42
  return n || e((n = { exports: {} }).exports, n), n.exports;
43
43
  } catch (t) {
44
44
  throw n = 0, t;
45
45
  }
46
46
  };
47
- var I = k((xe, U) => {
47
+ var k = j((xe, U) => {
48
48
  function G(e) {
49
49
  return typeof e == "string" ? !["false", "0", "no", "off", ""].includes(e.toLowerCase()) : !!e;
50
50
  }
@@ -58,8 +58,8 @@ var require_dist = __commonJS(function(exports, module) {
58
58
  }
59
59
  U.exports = { parseBoolean: G, optionsFromEnv: Y };
60
60
  });
61
- var N = k((ye, x) => {
62
- var z = __require("fs"), S = __require("path"), Z = __require("os"), { URL: ee, fileURLToPath: te } = __require("url"), { parseBoolean: $, optionsFromEnv: B } = I(), re = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg, b = new Uint8Array(256);
61
+ var N = j((ye, x) => {
62
+ var z = __require("fs"), I = __require("path"), Z = __require("os"), { URL: ee, fileURLToPath: te } = __require("url"), { parseBoolean: $, optionsFromEnv: B } = k(), re = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg, b = new Uint8Array(256);
63
63
  for (let e = 48;e <= 57; e++)
64
64
  b[e] = 1;
65
65
  for (let e = 65;e <= 90; e++)
@@ -124,7 +124,7 @@ var require_dist = __commonJS(function(exports, module) {
124
124
  r++;
125
125
  continue;
126
126
  }
127
- let p = t.slice(l, r), f = r;
127
+ let h = t.slice(l, r), f = r;
128
128
  if (r >= o && (d = 0), w(d))
129
129
  do
130
130
  r++, d = r < o ? t.charCodeAt(r) : 0;
@@ -141,8 +141,8 @@ var require_dist = __commonJS(function(exports, module) {
141
141
  let c = r, a = r;
142
142
  for (;a < o && w(t.charCodeAt(a)); )
143
143
  a++;
144
- let h = t.charCodeAt(a), g, y = false;
145
- if (h === 39 || h === 34 || h === 96) {
144
+ let g = t.charCodeAt(a), p, y = false;
145
+ if (g === 39 || g === 34 || g === 96) {
146
146
  let v = t[a], u = t.indexOf(v, a + 1), m = -1, C = -1;
147
147
  for (;u !== -1; ) {
148
148
  let O = t.charCodeAt(u - 1) === 92, E = u + 1;
@@ -153,7 +153,7 @@ var require_dist = __commonJS(function(exports, module) {
153
153
  u = t.indexOf(v, u + 1);
154
154
  }
155
155
  if (m !== -1) {
156
- if (g = t.slice(a + 1, m), r = C, t.charCodeAt(r) === 35)
156
+ if (p = t.slice(a + 1, m), r = C, t.charCodeAt(r) === 35)
157
157
  for (;r < o && !q(t.charCodeAt(r)); )
158
158
  r++;
159
159
  y = true;
@@ -175,12 +175,12 @@ var require_dist = __commonJS(function(exports, module) {
175
175
  for (;C > m && w(t.charCodeAt(C - 1)); )
176
176
  C--;
177
177
  let O = t.charCodeAt(m);
178
- if (C - m >= 2 && (O === 39 || O === 34 || O === 96) && t.charCodeAt(C - 1) === O ? g = t.slice(m + 1, C - 1) : g = t.slice(m, C), r = u, u < v)
178
+ if (p = t.slice(m, C), p.includes("\u2028") || p.includes("\u2029") ? p = p.replace(/^(['"`])([\s\S]*)\1$/mg, "$2") : C - m >= 2 && (O === 39 || O === 34 || O === 96) && t.charCodeAt(C - 1) === O && (p = t.slice(m + 1, C - 1)), r = u, u < v)
179
179
  for (;r < o && !q(t.charCodeAt(r)); )
180
180
  r++;
181
181
  }
182
- h === 34 && (y || a < r) && g.indexOf("\\") !== -1 && (g = g.replace(/\\n/g, `
183
- `).replace(/\\r/g, "\r")), n[p] = g;
182
+ g === 34 && (y || a < r) && p.indexOf("\\") !== -1 && (p = p.replace(/\\n/g, `
183
+ `).replace(/\\r/g, "\r")), n[h] = p;
184
184
  }
185
185
  return n;
186
186
  }
@@ -194,7 +194,7 @@ var require_dist = __commonJS(function(exports, module) {
194
194
  console.error(`\u25C7 ${e}`);
195
195
  }
196
196
  function V(e) {
197
- return e[0] === "~" ? S.join(Z.homedir(), e.slice(1)) : e;
197
+ return e[0] === "~" ? I.join(Z.homedir(), e.slice(1)) : e;
198
198
  }
199
199
  function ce(e = {}) {
200
200
  let n = { ...e };
@@ -204,7 +204,7 @@ var require_dist = __commonJS(function(exports, module) {
204
204
  }
205
205
  function ae(e) {
206
206
  e = ce(e);
207
- let n = S.resolve(process.cwd(), ".env"), t = "utf8", o = process.env;
207
+ let n = I.resolve(process.cwd(), ".env"), t = "utf8", o = process.env;
208
208
  e && e.processEnv != null && (o = e.processEnv);
209
209
  let r = $(e && e.debug);
210
210
  e && e.encoding ? t = e.encoding : r && T("no encoding is specified (UTF-8 is used by default)");
@@ -225,15 +225,15 @@ var require_dist = __commonJS(function(exports, module) {
225
225
  } catch (a) {
226
226
  r && T(`failed to load ${c} ${a.message}`), i = a;
227
227
  }
228
- let p = A.populate(o, l, e), f = $(Object.prototype.hasOwnProperty.call(e, "quiet") ? e.quiet : B(o).quiet);
228
+ let h = A.populate(o, l, e), f = $(Object.prototype.hasOwnProperty.call(e, "quiet") ? e.quiet : B(o).quiet);
229
229
  if (r || !f) {
230
- let c = Object.keys(p).length, a = [];
231
- for (let h of s)
230
+ let c = Object.keys(h).length, a = [];
231
+ for (let g of s)
232
232
  try {
233
- let g = S.relative(process.cwd(), h instanceof ee ? te(h) : h);
234
- a.push(g);
235
- } catch (g) {
236
- r && T(`failed to load ${h} ${g.message}`), i = g;
233
+ let p = I.relative(process.cwd(), g instanceof ee ? te(g) : g);
234
+ a.push(p);
235
+ } catch (p) {
236
+ r && T(`failed to load ${g} ${p.message}`), i = p;
237
237
  }
238
238
  ie(`injected env (${c}) from ${a.join(",")}`);
239
239
  }
@@ -259,7 +259,7 @@ var require_dist = __commonJS(function(exports, module) {
259
259
  x.exports.populate = A.populate;
260
260
  x.exports = A;
261
261
  });
262
- var M = k((Te, W) => {
262
+ var M = j((Te, W) => {
263
263
  var _ = __require("child_process"), ue = __require("fs"), L = __require("path");
264
264
  function de(e) {
265
265
  let n = ['"'], t = 0;
@@ -291,10 +291,10 @@ var require_dist = __commonJS(function(exports, module) {
291
291
  let o = (P(n, "PATHEXT") || ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean), s = o.some((l) => e.toLowerCase().endsWith(l.toLowerCase())) ? ["", ...o] : [...o, ""], i = /[\\/]/.test(e) ? [t] : [t, ...(P(n, "PATH") || "").split(";")];
292
292
  for (let l of i)
293
293
  for (let d of s) {
294
- let p = L.resolve(t, l.replace(/^"|"$/g, ""), e + d);
294
+ let h = L.resolve(t, l.replace(/^"|"$/g, ""), e + d);
295
295
  try {
296
- if (ue.statSync(p).isFile())
297
- return p;
296
+ if (ue.statSync(h).isFile())
297
+ return h;
298
298
  } catch {}
299
299
  }
300
300
  }
@@ -312,18 +312,18 @@ var require_dist = __commonJS(function(exports, module) {
312
312
  }
313
313
  W.exports = he;
314
314
  });
315
- var X = k(($e, F) => {
316
- var ge = __require("fs"), me = __require("os"), Q = __require("path"), ve = __require("child_process"), Ce = M(), R = N(), { optionsFromEnv: J } = I();
315
+ var X = j(($e, F) => {
316
+ var ge = __require("fs"), me = __require("os"), Q = __require("path"), ve = __require("child_process"), Ce = M(), R = N(), { optionsFromEnv: J } = k();
317
317
  function D() {
318
318
  console.log(["Usage: dotenv run [--help] [-q|--quiet] [--debug] [--override] [--fast] [-f|--file <paths>] [--] <command> [args...]", "", "Run a command with environment variables from a .env file.", "Place dotenv options before the command; all following arguments go to the command.", "", "Options:", " -f, --file <paths> .env paths, comma-separated or repeated (default: .env)", " -q, --quiet suppress the injected env message", " --debug enable debug logging", " --override override existing environment variables", " --fast use the faster character-scanner parser", "", "Environment variables (DOTENV_CONFIG_* names remain as fallbacks):", " DOTENV_PATH, DOTENV_ENCODING, DOTENV_QUIET,", " DOTENV_DEBUG, DOTENV_OVERRIDE,", " DOTENV_FAST"].join(`
319
319
  `));
320
320
  }
321
321
  function we(e) {
322
322
  let n = [], t = false, o, r, s, i, l = -1;
323
- for (let p = 0;p < e.length; p++) {
324
- let f = e[p];
323
+ for (let h = 0;h < e.length; h++) {
324
+ let f = e[h];
325
325
  if (f === "--") {
326
- l = p + 1;
326
+ l = h + 1;
327
327
  break;
328
328
  }
329
329
  if (f === "--help" || f === "-h")
@@ -345,18 +345,18 @@ var require_dist = __commonJS(function(exports, module) {
345
345
  continue;
346
346
  }
347
347
  if (f === "-f" || f === "--file" || f.startsWith("-f=") || f.startsWith("--file=")) {
348
- let c = f.indexOf("="), a = c === -1 ? f : f.slice(0, c), h = c === -1 ? e[++p] : f.slice(c + 1);
349
- if (!h || h === "--")
348
+ let c = f.indexOf("="), a = c === -1 ? f : f.slice(0, c), g = c === -1 ? e[++h] : f.slice(c + 1);
349
+ if (!g || g === "--")
350
350
  return { error: `${a} requires a path` };
351
- let g = h.split(",").map((y) => y.trim()).filter(Boolean);
352
- if (g.length === 0)
351
+ let p = g.split(",").map((y) => y.trim()).filter(Boolean);
352
+ if (p.length === 0)
353
353
  return { error: `${a} requires a path` };
354
- n.push(...g), t = true;
354
+ n.push(...p), t = true;
355
355
  continue;
356
356
  }
357
357
  if (f.startsWith("-"))
358
358
  return { error: `unknown option: ${f}` };
359
- l = p;
359
+ l = h;
360
360
  break;
361
361
  }
362
362
  let d = l === -1 ? [] : e.slice(l);
@@ -410,15 +410,15 @@ var require_dist = __commonJS(function(exports, module) {
410
410
  try {
411
411
  let c = be(o);
412
412
  if (!(o.quiet == null ? J().quiet : o.quiet)) {
413
- let h = `\u25C7 injected env (${Object.keys(c.injected).length})`;
414
- c.loadedPaths.length > 0 && (h += ` from ${c.loadedPaths.join(", ")}`), console.error(h);
413
+ let g = `\u25C7 injected env (${Object.keys(c.injected).length})`;
414
+ c.loadedPaths.length > 0 && (g += ` from ${c.loadedPaths.join(", ")}`), console.error(g);
415
415
  }
416
416
  } catch (c) {
417
417
  console.error(`dotenv: ${c.message}`), process.exitCode = 1;
418
418
  return;
419
419
  }
420
420
  let r = !!process.stdin.isTTY, s = process.platform !== "win32" && !r, i = Ce(t.command[0], t.command.slice(1), { stdio: "inherit", detached: s }), l = new Map, d = 0;
421
- function p(c) {
421
+ function h(c) {
422
422
  if (!(!i.pid || i.exitCode !== null || i.signalCode !== null)) {
423
423
  if (process.platform === "win32") {
424
424
  ve.spawnSync("taskkill", ["/pid", String(i.pid), "/T", "/F"], { stdio: "ignore" });
@@ -442,11 +442,11 @@ var require_dist = __commonJS(function(exports, module) {
442
442
  if (d++, r && process.platform !== "win32" && d === 1)
443
443
  return;
444
444
  if (d > 1) {
445
- p(d === 2 ? "SIGTERM" : "SIGKILL");
445
+ h(d === 2 ? "SIGTERM" : "SIGKILL");
446
446
  return;
447
447
  }
448
448
  }
449
- p(c);
449
+ h(c);
450
450
  };
451
451
  l.set(c, a), process.on(c, a);
452
452
  }
@@ -459,13 +459,13 @@ var require_dist = __commonJS(function(exports, module) {
459
459
  F.exports = K;
460
460
  F === __require.main && K(process.argv.slice(2));
461
461
  });
462
- var j = N();
462
+ var S = N();
463
463
  var qe = X();
464
- module.exports = j;
465
- module.exports.config = j.config;
466
- module.exports.configDotenv = j.configDotenv;
467
- module.exports.parse = j.parse;
468
- module.exports.populate = j.populate;
464
+ module.exports = S;
465
+ module.exports.config = S.config;
466
+ module.exports.configDotenv = S.configDotenv;
467
+ module.exports.parse = S.parse;
468
+ module.exports.populate = S.populate;
469
469
  });
470
470
 
471
471
  // src/load-env.ts
@@ -515,7 +515,7 @@ var createPkcePair = () => {
515
515
  verifier
516
516
  };
517
517
  };
518
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/core/util.js
518
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/util.js
519
519
  function getEnumValues(entries) {
520
520
  const numericValues = Object.values(entries).filter((v) => typeof v === "number");
521
521
  const values = Object.entries(entries).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_, v]) => v);
@@ -861,6 +861,18 @@ function prefixIssues(path, issues) {
861
861
  function unwrapMessage(message) {
862
862
  return typeof message === "string" ? message : message?.message;
863
863
  }
864
+ function sizeCaps(defChecks) {
865
+ const caps = [];
866
+ for (const ch of defChecks ?? []) {
867
+ const d = ch._zod.def;
868
+ if (!/_(length|size)$|_equals$/.test(d.check))
869
+ break;
870
+ const max = d.maximum ?? d.length ?? d.size;
871
+ if (max >= 0)
872
+ caps.push([max, ch]);
873
+ }
874
+ return caps;
875
+ }
864
876
  function attachSchema(issues, start, inst) {
865
877
  var _a;
866
878
  for (let i = start;i < issues.length; i++) {
@@ -1058,7 +1070,7 @@ function constantCatch(value) {
1058
1070
  return fn;
1059
1071
  }
1060
1072
 
1061
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/core/core.js
1073
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/core.js
1062
1074
  var _a;
1063
1075
  var _zodDesc = { value: undefined, enumerable: false };
1064
1076
  var _E = "captureStackTrace" in Error ? Error : null;
@@ -1176,7 +1188,7 @@ function config2(newConfig) {
1176
1188
  Object.assign(globalConfig, newConfig);
1177
1189
  return globalConfig;
1178
1190
  }
1179
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/core/errors.js
1191
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/errors.js
1180
1192
  function _getMessage() {
1181
1193
  const internals = this._zod;
1182
1194
  internals.message ?? (internals.message = JSON.stringify(internals.def, jsonStringifyReplacer, 2));
@@ -1291,7 +1303,7 @@ function formatError(error, mapper = (issue) => issue.message) {
1291
1303
  return fieldErrors;
1292
1304
  }
1293
1305
 
1294
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/core/parse.js
1306
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/parse.js
1295
1307
  function finalizeParams(callee, params) {
1296
1308
  return { callee: params?.callee ?? callee, Err: params?.Err };
1297
1309
  }
@@ -1438,7 +1450,7 @@ var _safeEncodeAsync = (_Err) => async (schema, value, _ctx) => {
1438
1450
  var _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
1439
1451
  return _safeParseAsync(_Err)(schema, value, _ctx);
1440
1452
  };
1441
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/core/regexes.js
1453
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/regexes.js
1442
1454
  var cuid = /^[cC][0-9a-z]{6,}$/;
1443
1455
  var cuid2 = /^[0-9a-z]+$/;
1444
1456
  var ulid = /^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$/;
@@ -1496,7 +1508,7 @@ var _null = /^null$/i;
1496
1508
  var lowercase = /^[^A-Z]*$/;
1497
1509
  var uppercase = /^[^a-z]*$/;
1498
1510
 
1499
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/core/checks.js
1511
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/checks.js
1500
1512
  var $ZodCheck = /* @__PURE__ */ $constructor("$ZodCheck", (inst, def) => {
1501
1513
  var _a;
1502
1514
  inst._zod ?? (inst._zod = {});
@@ -1812,7 +1824,7 @@ var $ZodCheckOverwrite = /* @__PURE__ */ $constructor("$ZodCheckOverwrite", (ins
1812
1824
  };
1813
1825
  });
1814
1826
 
1815
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/core/doc.js
1827
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/doc.js
1816
1828
  class Doc {
1817
1829
  constructor(args = [], closed = {}) {
1818
1830
  this.content = [];
@@ -1854,14 +1866,14 @@ ${content.join(`
1854
1866
  }
1855
1867
  }
1856
1868
 
1857
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/core/versions.js
1869
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/versions.js
1858
1870
  var version = {
1859
1871
  major: 4,
1860
1872
  minor: 6,
1861
- patch: 5
1873
+ patch: 7
1862
1874
  };
1863
1875
 
1864
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/core/schemas.js
1876
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/schemas.js
1865
1877
  var $ZodType = /* @__PURE__ */ $constructor("$ZodType", (inst, def) => {
1866
1878
  var _a;
1867
1879
  inst ?? (inst = {});
@@ -2427,6 +2439,29 @@ var $ZodNever = /* @__PURE__ */ $constructor("$ZodNever", (inst, def) => {
2427
2439
  return payload;
2428
2440
  };
2429
2441
  });
2442
+ function capSize(inst, size) {
2443
+ const caps = sizeCaps(inst._zod.def.checks);
2444
+ if (!caps.length)
2445
+ return;
2446
+ const cap = Math.min(...caps.map((c) => c[0]));
2447
+ const parse = inst._zod.parse;
2448
+ inst._zod.parse = (payload, ctx) => {
2449
+ const n = size(payload.value);
2450
+ if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
2451
+ for (const [max, ch] of sizeCaps(inst._zod.def.checks)) {
2452
+ if (n > max && ch._zod.def.when(payload)) {
2453
+ const start = payload.issues.length;
2454
+ ch._zod.check(payload);
2455
+ payload.issues[start].continue = false;
2456
+ attachSchema(payload.issues, start, inst);
2457
+ return payload;
2458
+ }
2459
+ }
2460
+ }
2461
+ return parse(payload, ctx);
2462
+ };
2463
+ }
2464
+ var arraySize = (input) => Array.isArray(input) ? input.length : -1;
2430
2465
  function handleArrayResult(result, final, index) {
2431
2466
  if (result.issues.length) {
2432
2467
  final.issues.push(...prefixIssues(index, result.issues));
@@ -2470,6 +2505,7 @@ var $ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
2470
2505
  }
2471
2506
  return payload;
2472
2507
  };
2508
+ capSize(inst, arraySize);
2473
2509
  });
2474
2510
  function handlePropertyResult(result, final, key, input, optin, optout) {
2475
2511
  const isPresent = key in input;
@@ -3252,7 +3288,7 @@ function handleRefineResult(result, payload, input, inst) {
3252
3288
  payload.issues.push(issue(_iss));
3253
3289
  }
3254
3290
  }
3255
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/core/memoizer.js
3291
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/memoizer.js
3256
3292
  class $ZodCyclicError extends Error {
3257
3293
  constructor() {
3258
3294
  super(`Cannot parse a reference cycle that closes through a transform`);
@@ -3521,7 +3557,7 @@ function isBackEdge(ctx, value) {
3521
3557
  const backEdges = ctx[STATE]?.backEdges;
3522
3558
  return backEdges !== undefined && isRef(value) && backEdges.has(value);
3523
3559
  }
3524
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/locales/en.js
3560
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/locales/en.js
3525
3561
  var error = () => {
3526
3562
  const Sizable = {
3527
3563
  string: { unit: "characters", verb: "to have" },
@@ -3643,7 +3679,7 @@ function en_default() {
3643
3679
  localeError: error()
3644
3680
  };
3645
3681
  }
3646
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/core/registries.js
3682
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/registries.js
3647
3683
  var _a2;
3648
3684
  class $ZodRegistry {
3649
3685
  constructor() {
@@ -3690,7 +3726,7 @@ function registry() {
3690
3726
  }
3691
3727
  (_a2 = globalThis).__zod_globalRegistry ?? (_a2.__zod_globalRegistry = registry());
3692
3728
  var globalRegistry = globalThis.__zod_globalRegistry;
3693
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/core/api.js
3729
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/api.js
3694
3730
  function snapshotChecks(def) {
3695
3731
  if (def.checks)
3696
3732
  def.checks = [...def.checks];
@@ -4144,7 +4180,7 @@ function _check(fn, params) {
4144
4180
  ch._zod.check = fn;
4145
4181
  return ch;
4146
4182
  }
4147
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/core/to-json-schema.js
4183
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/to-json-schema.js
4148
4184
  function assignProps(target, ...sources) {
4149
4185
  for (const source of sources) {
4150
4186
  for (const key of Reflect.ownKeys(source)) {
@@ -4667,7 +4703,7 @@ var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) =
4667
4703
  extractDefs(ctx, schema);
4668
4704
  return finalize(ctx, schema);
4669
4705
  };
4670
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/core/json-schema-processors.js
4706
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/json-schema-processors.js
4671
4707
  var narrowMin = (agg, key, value) => {
4672
4708
  if (agg[key] === undefined || value > agg[key])
4673
4709
  agg[key] = value;
@@ -5096,7 +5132,7 @@ var optionalProcessor = (schema, ctx, _json, params) => {
5096
5132
  const seen = ctx.seen.get(schema);
5097
5133
  seen.ref = def.innerType;
5098
5134
  };
5099
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/classic/errors.js
5135
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/classic/errors.js
5100
5136
  var _installedErrorProtos = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);
5101
5137
  function _lazyMethod(proto, key, make) {
5102
5138
  Object.defineProperty(proto, key, {
@@ -5141,7 +5177,7 @@ var ZodRealError = /* @__PURE__ */ $constructor("ZodError", initializer2, undefi
5141
5177
  Parent: Error
5142
5178
  });
5143
5179
 
5144
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/classic/parse.js
5180
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/classic/parse.js
5145
5181
  var parse2 = /* @__PURE__ */ _parse(ZodRealError);
5146
5182
  var parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);
5147
5183
  var safeParse = /* @__PURE__ */ _safeParse(ZodRealError);
@@ -5155,7 +5191,7 @@ var safeDecode = /* @__PURE__ */ _safeDecode(ZodRealError);
5155
5191
  var safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);
5156
5192
  var safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);
5157
5193
 
5158
- // ../../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/classic/schemas.js
5194
+ // ../../../node_modules/.bun/zod@4.6.7/node_modules/zod/v4/classic/schemas.js
5159
5195
  function _ensureDefaultLocale() {
5160
5196
  if (!globalConfig.localeError)
5161
5197
  config2(en_default());
@@ -7540,6 +7576,10 @@ var typedOperations = {
7540
7576
  kind: "mutation",
7541
7577
  invoke: (client, input) => client.event.reviewSubmission.mutate(input)
7542
7578
  },
7579
+ "event.reopenMission": {
7580
+ kind: "mutation",
7581
+ invoke: (client, input) => client.event.reopenMission.mutate(input)
7582
+ },
7543
7583
  "event.regenerateResultsPortalShare": {
7544
7584
  kind: "mutation",
7545
7585
  invoke: (client, input) => client.event.regenerateResultsPortalShare.mutate(input)
@@ -7885,6 +7925,7 @@ var mutationTargetKeys = {
7885
7925
  "team.assignTeamRoutingGroup": ["teamId"],
7886
7926
  "event.startStation": ["stationId"],
7887
7927
  "event.skipMission": ["missionId"],
7928
+ "event.reopenMission": ["teamId", "eventPlayerId", "missionId"],
7888
7929
  "event.create": ["organizationId", "name"],
7889
7930
  "event.createFromTemplate": ["organizationId", "templateId"],
7890
7931
  "event.requestLoginEmailOtp": ["eventId", "email"],
@@ -7976,10 +8017,8 @@ var commands = [
7976
8017
  mutation(["station", "clone"], "event.cloneStation", "Clone a station and its missions.", ["state-change"]),
7977
8018
  mutation(["station", "archive"], "event.archiveStation", "Archive a station.", ["state-change", "delete"]),
7978
8019
  mutation(["station", "reorder"], "event.reorderStations", "Reorder shared stations, or a selected team's station route when scopeTeamId is supplied.", ["state-change"]),
7979
- mutation(["mission", "create"], "event.createMission", "Create a mission in a station.", ["state-change"]),
7980
- mutation(["mission", "update"], "event.updateMission", "Update a mission.", [
7981
- "state-change"
7982
- ]),
8020
+ mutation(["mission", "create"], "event.createMission", "Create a mission in a station. Fill-in-the-blank inputs support correctAnswer and acceptedAnswers (up to 50 alternatives). Optional revealMessage (up to 2,000 characters) is shown only after submitting.", ["state-change"]),
8021
+ mutation(["mission", "update"], "event.updateMission", "Update a mission. Fill-in-the-blank inputs support acceptedAnswers; omit to preserve or pass [] to clear. Existing submissions are not re-graded. Edit revealMessage during live play; omit it to preserve or use an empty string to clear.", ["state-change"]),
7983
8022
  mutation(["mission", "archive"], "event.archiveMission", "Archive a mission.", ["state-change", "delete"]),
7984
8023
  mutation(["mission", "reorder"], "event.reorderMissions", "Reorder missions within a station.", ["state-change"]),
7985
8024
  query(["team", "list"], "team.list", "List teams and their current members."),
@@ -8021,6 +8060,7 @@ var commands = [
8021
8060
  mutation(["reward", "update"], "event.updateReward", "Update an event reward.", ["state-change"]),
8022
8061
  mutation(["score", "adjust"], "event.createScoreAdjustment", "Create a manual score adjustment.", ["state-change"]),
8023
8062
  mutation(["submission", "review"], "event.reviewSubmission", "Review a mission submission.", ["state-change"]),
8063
+ mutation(["mission", "reopen"], "event.reopenMission", "Reopen a submitted or skipped mission for one team (teamId) or player (eventPlayerId) on a fixed-order route. Earlier points count until they resubmit; then only the new submission counts. Finish times are kept.", ["state-change"]),
8024
8064
  mutation(["results-share", "regenerate"], "event.regenerateResultsPortalShare", "Create a new results portal share token.", ["state-change"]),
8025
8065
  mutation(["results-share", "revoke"], "event.revokeResultsPortalShare", "Revoke the current results portal share token.", ["state-change", "delete"]),
8026
8066
  query(["event", "analytics-shell"], "event.getAdminAnalyticsShell", "Get the organizer analytics shell."),
@@ -8062,7 +8102,7 @@ var commands = [
8062
8102
  mutation(["player", "station-check-in"], "event.createStationCheckInProof", "Create an expiring station check-in proof.", ["state-change"]),
8063
8103
  mutation(["player", "station", "start"], "event.startStation", "Start the current sequential station's shared mission timer and reveal its missions; required check-in still applies.", ["state-change"]),
8064
8104
  mutation(["player", "mission", "skip"], "event.skipMission", "Permanently skip one unresolved mission for zero points when enabled; retain other points and elapsed station time.", ["state-change"]),
8065
- mutation(["player", "mission", "submit"], "event.submitMission", "Submit a mission response.", ["state-change"]),
8105
+ mutation(["player", "mission", "submit"], "event.submitMission", "Submit a mission response and return revealMessage, including already-completed submissions.", ["state-change"]),
8066
8106
  query(["public", "event-login"], "event.getPublicLoginDetails", "Get public event login details.", { requiresAuth: false }),
8067
8107
  query(["public", "results"], "event.getResultsPortal", "Get a shared public results portal.", { requiresAuth: false }),
8068
8108
  mutation(["public", "request-login-code"], "event.requestLoginEmailOtp", "Send an event login email code.", ["email", "external-write"], { requiresAuth: false }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@go-labs-sg/gosh",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "GOSH CLI for organizers and AI agents — manage organizations, events, teams, stations, missions, templates, and live operations through the GOSH API.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "dependencies": {
21
21
  "@trpc/client": "^11.19.0",
22
- "dotenv": "^18.0.7",
22
+ "dotenv": "^18.0.8",
23
23
  "superjson": "^2.2.6"
24
24
  },
25
25
  "engines": {