@go-labs-sg/rsvp 0.3.0 → 0.6.1

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 (3) hide show
  1. package/README.md +37 -2
  2. package/dist/index.js +352 -423
  3. package/package.json +1 -2
package/README.md CHANGED
@@ -49,18 +49,53 @@ rsvp auth status
49
49
  rsvp auth whoami
50
50
  ```
51
51
 
52
- `rsvp auth login` opens RSVP in the system browser. Sign in through RSVP's normal Google-backed web flow if needed, verify that the displayed device and code match the terminal, then click **Authorize CLI**. Existing browser sessions skip another Google login but never skip explicit CLI approval.
52
+ `rsvp auth login` opens RSVP in the system browser. Sign in through RSVP's normal email one-time-password flow if needed, verify that the displayed device code matches the terminal, then click **Authorize CLI**. Existing browser sessions skip another email-code prompt but never skip explicit CLI approval.
53
53
 
54
54
  RSVP CLI access is limited to global administrators. The server rechecks the account's current global role and ban state when issuing, refreshing, and authenticating CLI credentials, so removing the admin role immediately blocks further CLI access.
55
55
 
56
- The CLI polls automatically and stores only RSVP-issued credentials through `Bun.secrets`. It does not store Google credentials and has no API-key or environment-variable fallback. Use `rsvp auth logout` to revoke the remote session and remove its local credential. For SSH or containers, pass `--no-browser` and open the printed URL on another device.
56
+ The CLI polls automatically and stores only RSVP-issued credentials through `Bun.secrets`. It does not store browser or email-OTP credentials and has no API-key or environment-variable fallback. Use `rsvp auth logout` to revoke the remote session and remove its local credential. For SSH or containers, pass `--no-browser` and open the printed URL on another device.
57
57
 
58
58
  The default API is `https://rsvp.getout.events`. Override it with `RSVP_API_URL` or `--api-url`; non-local overrides must use HTTPS.
59
59
 
60
+ Global administrators can update another account's global role from the web user manager or with `rsvp user role update --allow-state-change --target <user-id> --payload '{"userId":"<user-id>","role":"admin"}'`. Administrators cannot remove their own global admin role.
61
+
60
62
  ## Command safety
61
63
 
64
+ Argument-parsing errors use a safe command label and never echo option values,
65
+ including roster JSON passed through `--payload`. Surrounding shell history and
66
+ automation can still record arguments independently of the CLI's error output.
67
+
62
68
  Use `rsvp help` for the command catalogue. Every response is a JSON envelope on stdout; errors and diagnostics go to stderr. `rsvp manifest` emits the machine-readable command reference for tooling.
63
69
 
70
+ For a read-only registration-journey diagnosis, inspect the event, draft,
71
+ published versions, and event-owned resources together:
72
+
73
+ ```sh
74
+ rsvp event get --organization-id org_123 --event-id evt_123
75
+ rsvp event flow draft get --organization-id org_123 --event-id evt_123
76
+ rsvp event flow versions list --organization-id org_123 --event-id evt_123
77
+ rsvp event flow resources get --organization-id org_123 --event-id evt_123
78
+ ```
79
+
80
+ Create an immutable menu version with an explicit mutation acknowledgement. The
81
+ target is the event ID in the payload:
82
+
83
+ ```sh
84
+ rsvp event flow menu version create \
85
+ --allow-state-change \
86
+ --target evt_123 \
87
+ --payload '{"organizationId":"org_123","eventId":"evt_123","menu":{"mode":"NEW","key":"dinner","name":"Dinner"},"choices":[{"key":"standard","label":"Standard meal","dietaryTags":[]}]}'
88
+ ```
89
+
90
+ To version an existing menu, use `"menu":{"mode":"EXISTING","menuId":"..."}`
91
+ in the payload and keep the event ID as `--target`.
92
+
93
+ Reference the returned `version.id` from the draft's `MENU_SELECTION` blocks,
94
+ then save with `event flow draft save`, `--allow-state-change`, and the event ID
95
+ as `--target`. Include the latest draft revision as `expectedRevision`; nested
96
+ resource IDs such as a seating block's `seatMapId` are not approval targets for
97
+ this command. Saving the draft does not publish the registration flow.
98
+
64
99
  Commands returning invitee, registration, roster, report, audit, export, payment, or user data require `--ack-sensitive-data`. Mutations require their exact effect gates and a `--target` matching the affected resource in the payload. Depending on the operation, gates include:
65
100
 
66
101
  - `--allow-state-change`
package/dist/index.js CHANGED
@@ -1,38 +1,6 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env -S bun --no-env-file
2
2
  // @bun
3
- var __create = Object.create;
4
- var __getProtoOf = Object.getPrototypeOf;
5
3
  var __defProp = Object.defineProperty;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- function __accessProp(key) {
9
- return this[key];
10
- }
11
- var __toESMCache_node;
12
- var __toESMCache_esm;
13
- var __toESM = (mod, isNodeMode, target) => {
14
- var canCache = mod != null && typeof mod === "object";
15
- if (canCache) {
16
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
- var cached = cache.get(mod);
18
- if (cached)
19
- return cached;
20
- }
21
- target = mod != null ? __create(__getProtoOf(mod)) : {};
22
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
23
- if (mod && typeof mod === "object" || typeof mod === "function") {
24
- for (let key of __getOwnPropNames(mod))
25
- if (!__hasOwnProp.call(to, key))
26
- __defProp(to, key, {
27
- get: __accessProp.bind(mod, key),
28
- enumerable: true
29
- });
30
- }
31
- if (canCache)
32
- cache.set(mod, to);
33
- return to;
34
- };
35
- var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
36
4
  var __returnValue = (v) => v;
37
5
  function __exportSetter(name, newValue) {
38
6
  this[name] = __returnValue.bind(null, newValue);
@@ -46,334 +14,24 @@ var __export = (target, all) => {
46
14
  set: __exportSetter.bind(all, name)
47
15
  });
48
16
  };
49
- var __require = import.meta.require;
50
-
51
- // ../../../node_modules/.bun/dotenv@17.4.2/node_modules/dotenv/lib/main.js
52
- var require_main = __commonJS(function(exports, module) {
53
- var fs = __require("fs");
54
- var path = __require("path");
55
- var os = __require("os");
56
- var crypto = __require("crypto");
57
- var TIPS = [
58
- "\u25C8 encrypted .env [www.dotenvx.com]",
59
- "\u25C8 secrets for agents [www.dotenvx.com]",
60
- "\u2301 auth for agents [www.vestauth.com]",
61
- "\u2318 custom filepath { path: '/custom/path/.env' }",
62
- "\u2318 enable debugging { debug: true }",
63
- "\u2318 override existing { override: true }",
64
- "\u2318 suppress logs { quiet: true }",
65
- "\u2318 multiple files { path: ['.env.local', '.env'] }"
66
- ];
67
- function _getRandomTip() {
68
- return TIPS[Math.floor(Math.random() * TIPS.length)];
69
- }
70
- function parseBoolean(value) {
71
- if (typeof value === "string") {
72
- return !["false", "0", "no", "off", ""].includes(value.toLowerCase());
73
- }
74
- return Boolean(value);
75
- }
76
- function supportsAnsi() {
77
- return process.stdout.isTTY;
78
- }
79
- function dim(text) {
80
- return supportsAnsi() ? `\x1B[2m${text}\x1B[0m` : text;
81
- }
82
- var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
83
- function parse(src) {
84
- const obj = {};
85
- let lines = src.toString();
86
- lines = lines.replace(/\r\n?/mg, `
87
- `);
88
- let match;
89
- while ((match = LINE.exec(lines)) != null) {
90
- const key = match[1];
91
- let value = match[2] || "";
92
- value = value.trim();
93
- const maybeQuote = value[0];
94
- value = value.replace(/^(['"`])([\s\S]*)\1$/mg, "$2");
95
- if (maybeQuote === '"') {
96
- value = value.replace(/\\n/g, `
97
- `);
98
- value = value.replace(/\\r/g, "\r");
99
- }
100
- obj[key] = value;
101
- }
102
- return obj;
103
- }
104
- function _parseVault(options) {
105
- options = options || {};
106
- const vaultPath = _vaultPath(options);
107
- options.path = vaultPath;
108
- const result = DotenvModule.configDotenv(options);
109
- if (!result.parsed) {
110
- const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
111
- err.code = "MISSING_DATA";
112
- throw err;
113
- }
114
- const keys = _dotenvKey(options).split(",");
115
- const length = keys.length;
116
- let decrypted;
117
- for (let i = 0;i < length; i++) {
118
- try {
119
- const key = keys[i].trim();
120
- const attrs = _instructions(result, key);
121
- decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
122
- break;
123
- } catch (error) {
124
- if (i + 1 >= length) {
125
- throw error;
126
- }
127
- }
128
- }
129
- return DotenvModule.parse(decrypted);
130
- }
131
- function _warn(message) {
132
- console.error(`\u26A0 ${message}`);
133
- }
134
- function _debug(message) {
135
- console.log(`\u2506 ${message}`);
136
- }
137
- function _log(message) {
138
- console.log(`\u25C7 ${message}`);
139
- }
140
- function _dotenvKey(options) {
141
- if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {
142
- return options.DOTENV_KEY;
143
- }
144
- if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
145
- return process.env.DOTENV_KEY;
146
- }
147
- return "";
148
- }
149
- function _instructions(result, dotenvKey) {
150
- let uri;
151
- try {
152
- uri = new URL(dotenvKey);
153
- } catch (error) {
154
- if (error.code === "ERR_INVALID_URL") {
155
- const err = new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
156
- err.code = "INVALID_DOTENV_KEY";
157
- throw err;
158
- }
159
- throw error;
160
- }
161
- const key = uri.password;
162
- if (!key) {
163
- const err = new Error("INVALID_DOTENV_KEY: Missing key part");
164
- err.code = "INVALID_DOTENV_KEY";
165
- throw err;
166
- }
167
- const environment = uri.searchParams.get("environment");
168
- if (!environment) {
169
- const err = new Error("INVALID_DOTENV_KEY: Missing environment part");
170
- err.code = "INVALID_DOTENV_KEY";
171
- throw err;
172
- }
173
- const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
174
- const ciphertext = result.parsed[environmentKey];
175
- if (!ciphertext) {
176
- const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
177
- err.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
178
- throw err;
179
- }
180
- return { ciphertext, key };
181
- }
182
- function _vaultPath(options) {
183
- let possibleVaultPath = null;
184
- if (options && options.path && options.path.length > 0) {
185
- if (Array.isArray(options.path)) {
186
- for (const filepath of options.path) {
187
- if (fs.existsSync(filepath)) {
188
- possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
189
- }
190
- }
191
- } else {
192
- possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
193
- }
194
- } else {
195
- possibleVaultPath = path.resolve(process.cwd(), ".env.vault");
196
- }
197
- if (fs.existsSync(possibleVaultPath)) {
198
- return possibleVaultPath;
199
- }
200
- return null;
201
- }
202
- function _resolveHome(envPath) {
203
- return envPath[0] === "~" ? path.join(os.homedir(), envPath.slice(1)) : envPath;
204
- }
205
- function _configVault(options) {
206
- const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
207
- const quiet = parseBoolean(process.env.DOTENV_CONFIG_QUIET || options && options.quiet);
208
- if (debug || !quiet) {
209
- _log("loading env from encrypted .env.vault");
210
- }
211
- const parsed = DotenvModule._parseVault(options);
212
- let processEnv = process.env;
213
- if (options && options.processEnv != null) {
214
- processEnv = options.processEnv;
215
- }
216
- DotenvModule.populate(processEnv, parsed, options);
217
- return { parsed };
218
- }
219
- function configDotenv(options) {
220
- const dotenvPath = path.resolve(process.cwd(), ".env");
221
- let encoding = "utf8";
222
- let processEnv = process.env;
223
- if (options && options.processEnv != null) {
224
- processEnv = options.processEnv;
225
- }
226
- let debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || options && options.debug);
227
- let quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || options && options.quiet);
228
- if (options && options.encoding) {
229
- encoding = options.encoding;
230
- } else {
231
- if (debug) {
232
- _debug("no encoding is specified (UTF-8 is used by default)");
233
- }
234
- }
235
- let optionPaths = [dotenvPath];
236
- if (options && options.path) {
237
- if (!Array.isArray(options.path)) {
238
- optionPaths = [_resolveHome(options.path)];
239
- } else {
240
- optionPaths = [];
241
- for (const filepath of options.path) {
242
- optionPaths.push(_resolveHome(filepath));
243
- }
244
- }
245
- }
246
- let lastError;
247
- const parsedAll = {};
248
- for (const path2 of optionPaths) {
249
- try {
250
- const parsed = DotenvModule.parse(fs.readFileSync(path2, { encoding }));
251
- DotenvModule.populate(parsedAll, parsed, options);
252
- } catch (e) {
253
- if (debug) {
254
- _debug(`failed to load ${path2} ${e.message}`);
255
- }
256
- lastError = e;
257
- }
258
- }
259
- const populated = DotenvModule.populate(processEnv, parsedAll, options);
260
- debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || debug);
261
- quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || quiet);
262
- if (debug || !quiet) {
263
- const keysCount = Object.keys(populated).length;
264
- const shortPaths = [];
265
- for (const filePath of optionPaths) {
266
- try {
267
- const relative = path.relative(process.cwd(), filePath);
268
- shortPaths.push(relative);
269
- } catch (e) {
270
- if (debug) {
271
- _debug(`failed to load ${filePath} ${e.message}`);
272
- }
273
- lastError = e;
274
- }
275
- }
276
- _log(`injected env (${keysCount}) from ${shortPaths.join(",")} ${dim(`// tip: ${_getRandomTip()}`)}`);
277
- }
278
- if (lastError) {
279
- return { parsed: parsedAll, error: lastError };
280
- } else {
281
- return { parsed: parsedAll };
282
- }
283
- }
284
- function config(options) {
285
- if (_dotenvKey(options).length === 0) {
286
- return DotenvModule.configDotenv(options);
287
- }
288
- const vaultPath = _vaultPath(options);
289
- if (!vaultPath) {
290
- _warn(`you set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}`);
291
- return DotenvModule.configDotenv(options);
292
- }
293
- return DotenvModule._configVault(options);
294
- }
295
- function decrypt(encrypted, keyStr) {
296
- const key = Buffer.from(keyStr.slice(-64), "hex");
297
- let ciphertext = Buffer.from(encrypted, "base64");
298
- const nonce = ciphertext.subarray(0, 12);
299
- const authTag = ciphertext.subarray(-16);
300
- ciphertext = ciphertext.subarray(12, -16);
301
- try {
302
- const aesgcm = crypto.createDecipheriv("aes-256-gcm", key, nonce);
303
- aesgcm.setAuthTag(authTag);
304
- return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
305
- } catch (error) {
306
- const isRange = error instanceof RangeError;
307
- const invalidKeyLength = error.message === "Invalid key length";
308
- const decryptionFailed = error.message === "Unsupported state or unable to authenticate data";
309
- if (isRange || invalidKeyLength) {
310
- const err = new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
311
- err.code = "INVALID_DOTENV_KEY";
312
- throw err;
313
- } else if (decryptionFailed) {
314
- const err = new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
315
- err.code = "DECRYPTION_FAILED";
316
- throw err;
317
- } else {
318
- throw error;
319
- }
320
- }
321
- }
322
- function populate(processEnv, parsed, options = {}) {
323
- const debug = Boolean(options && options.debug);
324
- const override = Boolean(options && options.override);
325
- const populated = {};
326
- if (typeof parsed !== "object") {
327
- const err = new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
328
- err.code = "OBJECT_REQUIRED";
329
- throw err;
330
- }
331
- for (const key of Object.keys(parsed)) {
332
- if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
333
- if (override === true) {
334
- processEnv[key] = parsed[key];
335
- populated[key] = parsed[key];
336
- }
337
- if (debug) {
338
- if (override === true) {
339
- _debug(`"${key}" is already defined and WAS overwritten`);
340
- } else {
341
- _debug(`"${key}" is already defined and was NOT overwritten`);
342
- }
343
- }
344
- } else {
345
- processEnv[key] = parsed[key];
346
- populated[key] = parsed[key];
347
- }
348
- }
349
- return populated;
350
- }
351
- var DotenvModule = {
352
- configDotenv,
353
- _configVault,
354
- _parseVault,
355
- config,
356
- decrypt,
357
- parse,
358
- populate
359
- };
360
- module.exports.configDotenv = DotenvModule.configDotenv;
361
- module.exports._configVault = DotenvModule._configVault;
362
- module.exports._parseVault = DotenvModule._parseVault;
363
- module.exports.config = DotenvModule.config;
364
- module.exports.decrypt = DotenvModule.decrypt;
365
- module.exports.parse = DotenvModule.parse;
366
- module.exports.populate = DotenvModule.populate;
367
- module.exports = DotenvModule;
368
- });
369
-
370
- // src/load-env.ts
371
- var import_dotenv = __toESM(require_main(), 1);
372
- import_dotenv.config({ quiet: true });
373
17
 
374
18
  // src/cli.ts
375
19
  import { readFileSync } from "fs";
376
20
 
21
+ // ../../cli-auth/src/client/credential-store.ts
22
+ import { createHash as createHash2, randomUUID } from "crypto";
23
+ import {
24
+ chmod,
25
+ lstat,
26
+ mkdir,
27
+ open as open2,
28
+ readFile,
29
+ stat,
30
+ unlink
31
+ } from "fs/promises";
32
+ import { userInfo } from "os";
33
+ import { join } from "path";
34
+
377
35
  // ../../cli-auth/src/protocol/constants.ts
378
36
  var CLI_ACCESS_TOKEN_TTL_MS = 15 * 60 * 1000;
379
37
  var CLI_DEVICE_AUTHORIZATION_TTL_MS = 10 * 60 * 1000;
@@ -502,7 +160,7 @@ __export(exports_external, {
502
160
  codec: () => codec,
503
161
  coerce: () => exports_coerce,
504
162
  compile: () => compile,
505
- config: () => config2,
163
+ config: () => config,
506
164
  core: () => exports_core2,
507
165
  creditCard: () => creditCard2,
508
166
  cuid: () => cuid3,
@@ -905,7 +563,7 @@ __export(exports_core2, {
905
563
  clone: () => clone,
906
564
  compile: () => compile,
907
565
  compileFn: () => compileFn,
908
- config: () => config2,
566
+ config: () => config,
909
567
  createStandardJSONSchemaMethod: () => createStandardJSONSchemaMethod,
910
568
  createToJSONSchemaMethod: () => createToJSONSchemaMethod,
911
569
  decode: () => decode,
@@ -1569,7 +1227,7 @@ function attachSchema(issues, start, inst) {
1569
1227
  (_a = issues[i]).schema ?? (_a.schema = inst);
1570
1228
  }
1571
1229
  }
1572
- function finalizeIssue(iss, ctx, config2) {
1230
+ function finalizeIssue(iss, ctx, config) {
1573
1231
  var _a;
1574
1232
  const traits = iss.inst?._zod?.traits;
1575
1233
  if (traits?.has("$ZodType")) {
@@ -1579,7 +1237,7 @@ function finalizeIssue(iss, ctx, config2) {
1579
1237
  iss.schema = iss.inst;
1580
1238
  }
1581
1239
  const schemaError = iss.schema !== iss.inst ? iss.schema?._zod.def?.error : undefined;
1582
- const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(schemaError?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config2.customError?.(iss)) ?? unwrapMessage(config2.localeError?.(iss)) ?? "Invalid input";
1240
+ const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(schemaError?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config.customError?.(iss)) ?? unwrapMessage(config.localeError?.(iss)) ?? "Invalid input";
1583
1241
  const { inst: _inst, schema: _schema, continue: _continue, input: _input, ...rest } = iss;
1584
1242
  rest.path ?? (rest.path = []);
1585
1243
  rest.message = message;
@@ -1913,7 +1571,7 @@ class $ZodEncodeError extends Error {
1913
1571
  }
1914
1572
  (_a = globalThis).__zod_globalConfig ?? (_a.__zod_globalConfig = {});
1915
1573
  var globalConfig = globalThis.__zod_globalConfig;
1916
- function config2(newConfig) {
1574
+ function config(newConfig) {
1917
1575
  if (newConfig)
1918
1576
  Object.assign(globalConfig, newConfig);
1919
1577
  return globalConfig;
@@ -2127,7 +1785,7 @@ var _parse = (_Err) => {
2127
1785
  throw new $ZodAsyncError;
2128
1786
  }
2129
1787
  if (result.issues.length) {
2130
- const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config2())));
1788
+ const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
2131
1789
  captureStackTrace(e, _params?.callee ?? fn);
2132
1790
  throw e;
2133
1791
  }
@@ -2143,7 +1801,7 @@ var _parseAsync = (_Err) => {
2143
1801
  if (result instanceof Promise)
2144
1802
  result = await result;
2145
1803
  if (result.issues.length) {
2146
- const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config2())));
1804
+ const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
2147
1805
  captureStackTrace(e, params?.callee ?? fn);
2148
1806
  throw e;
2149
1807
  }
@@ -2160,7 +1818,7 @@ var _safeParse = (_Err) => (schema, value, _ctx) => {
2160
1818
  }
2161
1819
  return result.issues.length ? {
2162
1820
  success: false,
2163
- error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config2())))
1821
+ error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
2164
1822
  } : { success: true, data: result.value };
2165
1823
  };
2166
1824
  var safeParse = /* @__PURE__ */ _safeParse($ZodRealError);
@@ -2171,7 +1829,7 @@ var _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
2171
1829
  result = await result;
2172
1830
  return result.issues.length ? {
2173
1831
  success: false,
2174
- error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config2())))
1832
+ error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
2175
1833
  } : { success: true, data: result.value };
2176
1834
  };
2177
1835
  var safeParseAsync = /* @__PURE__ */ _safeParseAsync($ZodRealError);
@@ -4047,7 +3705,7 @@ function handleUnionResults(results, final, inst, ctx) {
4047
3705
  code: "invalid_union",
4048
3706
  input: final.value,
4049
3707
  inst,
4050
- errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config2())))
3708
+ errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
4051
3709
  });
4052
3710
  return final;
4053
3711
  }
@@ -4111,7 +3769,7 @@ function handleExclusiveUnionResults(results, final, inst, ctx) {
4111
3769
  code: "invalid_union",
4112
3770
  input: final.value,
4113
3771
  inst,
4114
- errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config2())))
3772
+ errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
4115
3773
  });
4116
3774
  } else {
4117
3775
  final.issues.push({
@@ -4503,7 +4161,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
4503
4161
  payload.issues.push({
4504
4162
  code: "invalid_key",
4505
4163
  origin: "record",
4506
- issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config2())),
4164
+ issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
4507
4165
  input: key,
4508
4166
  path: [key],
4509
4167
  inst
@@ -4583,7 +4241,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
4583
4241
  payload.issues.push({
4584
4242
  code: "invalid_key",
4585
4243
  origin: "record",
4586
- issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config2())),
4244
+ issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
4587
4245
  input: key,
4588
4246
  path: [key],
4589
4247
  inst
@@ -4668,7 +4326,7 @@ function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
4668
4326
  origin: "map",
4669
4327
  input,
4670
4328
  inst,
4671
- issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config2()))
4329
+ issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config()))
4672
4330
  });
4673
4331
  }
4674
4332
  }
@@ -4682,7 +4340,7 @@ function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
4682
4340
  input,
4683
4341
  inst,
4684
4342
  key,
4685
- issues: valueResult.issues.map((iss) => finalizeIssue(iss, ctx, config2()))
4343
+ issues: valueResult.issues.map((iss) => finalizeIssue(iss, ctx, config()))
4686
4344
  });
4687
4345
  }
4688
4346
  }
@@ -4946,7 +4604,7 @@ function handleCatchResult(payload, result, def, ctx) {
4946
4604
  ...result,
4947
4605
  value: payload.value,
4948
4606
  error: {
4949
- issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config2()))
4607
+ issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config()))
4950
4608
  },
4951
4609
  input: payload.value
4952
4610
  });
@@ -16797,11 +16455,11 @@ var safeDecodeAsync2 = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);
16797
16455
  // ../../../node_modules/.bun/zod@4.5.4/node_modules/zod/v4/classic/schemas.js
16798
16456
  function _ensureDefaultLocale() {
16799
16457
  if (!globalConfig.localeError)
16800
- config2(en_default());
16458
+ config(en_default());
16801
16459
  }
16802
16460
  function _ensureDefaultMemoizer() {
16803
16461
  if (!globalConfig.memoizer)
16804
- config2({ memoizer: memoizer() });
16462
+ config({ memoizer: memoizer() });
16805
16463
  }
16806
16464
  var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
16807
16465
  _ensureDefaultLocale();
@@ -18213,12 +17871,12 @@ var ZodIssueCode = {
18213
17871
  custom: "custom"
18214
17872
  };
18215
17873
  function setErrorMap(map2) {
18216
- config2({
17874
+ config({
18217
17875
  customError: map2
18218
17876
  });
18219
17877
  }
18220
17878
  function getErrorMap() {
18221
- return config2().customError;
17879
+ return config().customError;
18222
17880
  }
18223
17881
  var ZodFirstPartyTypeKind;
18224
17882
  (function(ZodFirstPartyTypeKind2) {})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
@@ -19027,13 +18685,15 @@ var deviceTokenErrorSchema = exports_external.object({
19027
18685
  "authorization_pending",
19028
18686
  "slow_down",
19029
18687
  "access_denied",
19030
- "expired_token"
18688
+ "expired_token",
18689
+ "invalid_grant"
19031
18690
  ])
19032
18691
  }).strict();
19033
18692
  var refreshRequestSchema = exports_external.object({ refreshToken: exports_external.string().min(40) }).strict();
19034
18693
  var storedCliCredentialsSchema = exports_external.object({
19035
18694
  accessToken: exports_external.string().min(1),
19036
18695
  accessTokenExpiresAt: dateTimeString,
18696
+ issuer: exports_external.string().url().optional(),
19037
18697
  refreshToken: exports_external.string().min(1),
19038
18698
  refreshTokenExpiresAt: dateTimeString,
19039
18699
  session: cliSessionIdentitySchema
@@ -19085,6 +18745,77 @@ var defaultSecrets = () => {
19085
18745
  assertBunRuntime();
19086
18746
  return Bun.secrets;
19087
18747
  };
18748
+ var refreshLockTimeoutMs = 5 * 60 * 1000;
18749
+ var refreshLockRetryMs = 50;
18750
+ var getCredentialLockDirectoryPath = () => {
18751
+ return join(userInfo().homedir, ".cache", "go-labs-cli", "locks");
18752
+ };
18753
+ var ensurePrivateCredentialLockDirectory = async () => {
18754
+ const directory = getCredentialLockDirectoryPath();
18755
+ await mkdir(directory, { mode: 448, recursive: true });
18756
+ const inspectDirectory = async () => {
18757
+ const directoryStat2 = await lstat(directory);
18758
+ if (directoryStat2.isSymbolicLink() || !directoryStat2.isDirectory()) {
18759
+ throw new Error("The CLI credential lock path is not a private directory.");
18760
+ }
18761
+ const uid = process.getuid?.();
18762
+ if (uid !== undefined && directoryStat2.uid !== uid) {
18763
+ throw new Error("The CLI credential lock directory belongs to another user.");
18764
+ }
18765
+ return directoryStat2;
18766
+ };
18767
+ const directoryStat = await inspectDirectory();
18768
+ if ((directoryStat.mode & 63) !== 0) {
18769
+ await chmod(directory, 448);
18770
+ const securedDirectoryStat = await inspectDirectory();
18771
+ if ((securedDirectoryStat.mode & 63) !== 0) {
18772
+ throw new Error("Unable to secure the CLI credential lock directory.");
18773
+ }
18774
+ }
18775
+ return directory;
18776
+ };
18777
+ var getCredentialLockPath = async (product, account) => {
18778
+ const key = createHash2("sha256").update(`${product}:${account}`).digest("hex");
18779
+ return join(await ensurePrivateCredentialLockDirectory(), `${key}.lock`);
18780
+ };
18781
+ var acquireRefreshLock = async (path) => {
18782
+ const deadline = Date.now() + refreshLockTimeoutMs;
18783
+ while (Date.now() < deadline) {
18784
+ try {
18785
+ const handle = await open2(path, "wx");
18786
+ const owner = randomUUID();
18787
+ try {
18788
+ await handle.writeFile(owner);
18789
+ } catch (cause) {
18790
+ await handle.close().catch(() => {
18791
+ return;
18792
+ });
18793
+ await unlink(path).catch(() => {
18794
+ return;
18795
+ });
18796
+ throw cause;
18797
+ }
18798
+ return { handle, owner };
18799
+ } catch (cause) {
18800
+ if (!(cause instanceof Error) || !("code" in cause) || cause.code !== "EEXIST") {
18801
+ throw cause;
18802
+ }
18803
+ try {
18804
+ const lock = await stat(path);
18805
+ if (Date.now() - lock.mtimeMs > refreshLockTimeoutMs) {
18806
+ throw new Error(`The CLI credential lock appears stale at ${path}. Ensure no CLI authentication command is running, remove that lock file, and retry.`);
18807
+ }
18808
+ } catch (statCause) {
18809
+ if (statCause instanceof Error && "code" in statCause && statCause.code === "ENOENT") {
18810
+ continue;
18811
+ }
18812
+ throw statCause;
18813
+ }
18814
+ await Bun.sleep(refreshLockRetryMs);
18815
+ }
18816
+ }
18817
+ throw new Error("Timed out waiting for another CLI process to refresh credentials. Retry the command.");
18818
+ };
19088
18819
  var createBunCredentialStore = ({
19089
18820
  account = "default",
19090
18821
  product,
@@ -19112,16 +18843,46 @@ var createBunCredentialStore = ({
19112
18843
  service,
19113
18844
  value: JSON.stringify(parsed)
19114
18845
  }));
18846
+ },
18847
+ withCredentialLock: async (operation) => {
18848
+ const refreshLockPath = await getCredentialLockPath(product, account);
18849
+ const lock = await acquireRefreshLock(refreshLockPath);
18850
+ try {
18851
+ return await operation();
18852
+ } finally {
18853
+ await lock.handle.close();
18854
+ const owner = await readFile(refreshLockPath, "utf8").catch(() => null);
18855
+ if (owner === lock.owner) {
18856
+ await unlink(refreshLockPath).catch(() => {
18857
+ return;
18858
+ });
18859
+ }
18860
+ }
19115
18861
  }
19116
18862
  };
19117
18863
  };
19118
18864
  // ../../cli-auth/src/client/login.ts
19119
18865
  import { hostname as hostname3 } from "os";
19120
- var normalizeBaseUrl = (value) => value.replace(/\/+$/, "");
19121
- var postJson = async (fetchImplementation, url2, body) => fetchImplementation(url2, {
18866
+ var normalizeCliAuthIssuer = (value) => {
18867
+ const url2 = new URL(value);
18868
+ url2.hash = "";
18869
+ url2.search = "";
18870
+ url2.pathname = url2.pathname.replace(/\/+$/, "");
18871
+ return url2.toString().replace(/\/+$/, "");
18872
+ };
18873
+ var isCliCredentialForIssuer = (credentials, apiBaseUrl) => credentials.issuer === normalizeCliAuthIssuer(apiBaseUrl);
18874
+ var credentialIssuerError = (issuer) => issuer ? new Error("These CLI credentials belong to a different API URL. Run auth login for this API URL before retrying.") : new Error("These CLI credentials were created by an older CLI and are not bound to an API URL. Run auth login again.");
18875
+ var assertCredentialIssuer = (credentials, apiBaseUrl) => {
18876
+ const expectedIssuer = normalizeCliAuthIssuer(apiBaseUrl);
18877
+ if (!credentials.issuer || credentials.issuer !== expectedIssuer) {
18878
+ throw credentialIssuerError(credentials.issuer);
18879
+ }
18880
+ };
18881
+ var postJson = async (fetchImplementation, url2, body, options = {}) => fetchImplementation(url2, {
19122
18882
  body: JSON.stringify(body),
19123
18883
  headers: { "content-type": "application/json" },
19124
- method: "POST"
18884
+ method: "POST",
18885
+ ...options
19125
18886
  });
19126
18887
  var readJson = async (response) => {
19127
18888
  try {
@@ -19130,9 +18891,29 @@ var readJson = async (response) => {
19130
18891
  throw new Error(`CLI authentication returned HTTP ${response.status} without valid JSON.`);
19131
18892
  }
19132
18893
  };
19133
- var toStoredCredentials = (response, now) => ({
18894
+ var localCredentialLocks = new WeakMap;
18895
+ var withCredentialLock = (store, operation) => {
18896
+ if (store.withCredentialLock) {
18897
+ return store.withCredentialLock(operation);
18898
+ }
18899
+ const previous = localCredentialLocks.get(store) ?? Promise.resolve();
18900
+ const pending = previous.then(operation, operation);
18901
+ const tail = pending.then(() => {
18902
+ return;
18903
+ }, () => {
18904
+ return;
18905
+ });
18906
+ localCredentialLocks.set(store, tail);
18907
+ return pending.finally(() => {
18908
+ if (localCredentialLocks.get(store) === tail) {
18909
+ localCredentialLocks.delete(store);
18910
+ }
18911
+ });
18912
+ };
18913
+ var toStoredCredentials = (response, issuer, now) => ({
19134
18914
  accessToken: response.accessToken,
19135
18915
  accessTokenExpiresAt: new Date(now.getTime() + response.expiresIn * 1000).toISOString(),
18916
+ issuer,
19136
18917
  refreshToken: response.refreshToken,
19137
18918
  refreshTokenExpiresAt: new Date(now.getTime() + response.refreshExpiresIn * 1000).toISOString(),
19138
18919
  session: response.session
@@ -19166,7 +18947,7 @@ var loginWithBrowser = async ({
19166
18947
  sleep = (milliseconds) => Bun.sleep(milliseconds),
19167
18948
  store
19168
18949
  }) => {
19169
- const baseUrl = normalizeBaseUrl(apiBaseUrl);
18950
+ const baseUrl = normalizeCliAuthIssuer(apiBaseUrl);
19170
18951
  const pkce = createPkcePair();
19171
18952
  const beginResponse = await postJson(fetchImplementation, `${baseUrl}/api/cli-auth/device`, {
19172
18953
  clientName,
@@ -19193,8 +18974,8 @@ var loginWithBrowser = async ({
19193
18974
  const payload = await readJson(response);
19194
18975
  if (response.ok) {
19195
18976
  const issued = tokenResponseSchema.parse(payload);
19196
- const credentials = toStoredCredentials(issued, now());
19197
- await store.set(credentials);
18977
+ const credentials = toStoredCredentials(issued, baseUrl, now());
18978
+ await withCredentialLock(store, () => store.set(credentials));
19198
18979
  return credentials;
19199
18980
  }
19200
18981
  const error61 = deviceTokenErrorSchema.safeParse(payload);
@@ -19215,6 +18996,26 @@ var loginWithBrowser = async ({
19215
18996
  throw new Error("CLI authentication expired. Run auth login again.");
19216
18997
  };
19217
18998
  var refreshes = new WeakMap;
18999
+ var refreshRequestTimeoutMs = 30000;
19000
+ var isInvalidGrantResponse = (payload) => {
19001
+ const parsed = deviceTokenErrorSchema.safeParse(payload);
19002
+ return parsed.success && parsed.data.error === "invalid_grant";
19003
+ };
19004
+ var readJsonOrNull = async (response) => {
19005
+ try {
19006
+ return await response.json();
19007
+ } catch {
19008
+ return null;
19009
+ }
19010
+ };
19011
+ var deleteInvalidCredentials = async (store, credentials) => {
19012
+ const current = await store.get();
19013
+ if (!current)
19014
+ return;
19015
+ if (current.issuer === credentials.issuer && current.refreshToken === credentials.refreshToken) {
19016
+ await store.delete();
19017
+ }
19018
+ };
19218
19019
  var refreshAccessToken = async ({
19219
19020
  apiBaseUrl,
19220
19021
  credentials,
@@ -19222,16 +19023,56 @@ var refreshAccessToken = async ({
19222
19023
  now,
19223
19024
  store
19224
19025
  }) => {
19225
- const response = await postJson(fetchImplementation, `${normalizeBaseUrl(apiBaseUrl)}/api/cli-auth/refresh`, { refreshToken: credentials.refreshToken });
19026
+ assertCredentialIssuer(credentials, apiBaseUrl);
19027
+ let response;
19028
+ try {
19029
+ response = await postJson(fetchImplementation, `${normalizeCliAuthIssuer(apiBaseUrl)}/api/cli-auth/refresh`, { refreshToken: credentials.refreshToken }, { signal: AbortSignal.timeout(refreshRequestTimeoutMs) });
19030
+ } catch {
19031
+ throw new Error("Unable to refresh the CLI session because the API could not be reached. Credentials were kept; retry the command.");
19032
+ }
19226
19033
  if (!response.ok) {
19227
- await store.delete();
19228
- throw new Error("The CLI session expired or was revoked. Run auth login again.");
19034
+ const payload2 = await readJsonOrNull(response);
19035
+ if (isInvalidGrantResponse(payload2)) {
19036
+ await deleteInvalidCredentials(store, credentials);
19037
+ throw new Error("The CLI session expired or was revoked. Run auth login again.");
19038
+ }
19039
+ throw new Error(`Unable to refresh the CLI session (HTTP ${response.status}). Credentials were kept; retry the command.`);
19040
+ }
19041
+ const payload = await readJsonOrNull(response);
19042
+ if (payload === null) {
19043
+ throw new Error("Unable to refresh the CLI session because the API returned malformed JSON. Credentials were kept; retry the command.");
19229
19044
  }
19230
- const issued = tokenResponseSchema.parse(await readJson(response));
19231
- const stored = toStoredCredentials(issued, now());
19045
+ let issued;
19046
+ try {
19047
+ issued = tokenResponseSchema.parse(payload);
19048
+ } catch {
19049
+ throw new Error("Unable to refresh the CLI session because the API returned invalid credentials. Credentials were kept; retry the command.");
19050
+ }
19051
+ const stored = toStoredCredentials(issued, normalizeCliAuthIssuer(apiBaseUrl), now());
19232
19052
  await store.set(stored);
19233
19053
  return stored.accessToken;
19234
19054
  };
19055
+ var refreshWithCurrentCredentials = async ({
19056
+ apiBaseUrl,
19057
+ fetch: fetchImplementation,
19058
+ now,
19059
+ store
19060
+ }) => {
19061
+ const credentials = await store.get();
19062
+ if (!credentials)
19063
+ throw new Error("Not authenticated. Run auth login first.");
19064
+ assertCredentialIssuer(credentials, apiBaseUrl);
19065
+ if (Date.parse(credentials.accessTokenExpiresAt) - now().getTime() > 60000) {
19066
+ return credentials.accessToken;
19067
+ }
19068
+ return refreshAccessToken({
19069
+ apiBaseUrl,
19070
+ credentials,
19071
+ fetch: fetchImplementation,
19072
+ now,
19073
+ store
19074
+ });
19075
+ };
19235
19076
  var getAuthenticatedAccessToken = async ({
19236
19077
  apiBaseUrl,
19237
19078
  fetch: fetchImplementation = globalThis.fetch,
@@ -19241,19 +19082,20 @@ var getAuthenticatedAccessToken = async ({
19241
19082
  const credentials = await store.get();
19242
19083
  if (!credentials)
19243
19084
  throw new Error("Not authenticated. Run auth login first.");
19085
+ assertCredentialIssuer(credentials, apiBaseUrl);
19244
19086
  if (Date.parse(credentials.accessTokenExpiresAt) - now().getTime() > 60000) {
19245
19087
  return credentials.accessToken;
19246
19088
  }
19247
19089
  const existing = refreshes.get(store);
19248
19090
  if (existing)
19249
19091
  return existing;
19250
- const refresh = refreshAccessToken({
19092
+ const refreshOperation = () => withCredentialLock(store, () => refreshWithCurrentCredentials({
19251
19093
  apiBaseUrl,
19252
- credentials,
19253
19094
  fetch: fetchImplementation,
19254
19095
  now,
19255
19096
  store
19256
- }).finally(() => refreshes.delete(store));
19097
+ }));
19098
+ const refresh = refreshOperation().finally(() => refreshes.delete(store));
19257
19099
  refreshes.set(store, refresh);
19258
19100
  return refresh;
19259
19101
  };
@@ -19262,18 +19104,22 @@ var logoutCliSession = async ({
19262
19104
  fetch: fetchImplementation = globalThis.fetch,
19263
19105
  store
19264
19106
  }) => {
19265
- const credentials = await store.get();
19266
- let remoteRevoked = credentials === null;
19267
- if (credentials) {
19268
- try {
19269
- const response = await postJson(fetchImplementation, `${normalizeBaseUrl(apiBaseUrl)}/api/cli-auth/revoke`, { refreshToken: credentials.refreshToken });
19270
- remoteRevoked = response.ok;
19271
- } catch {
19272
- remoteRevoked = false;
19107
+ const issuer = normalizeCliAuthIssuer(apiBaseUrl);
19108
+ return withCredentialLock(store, async () => {
19109
+ const credentials = await store.get();
19110
+ let remoteRevoked = credentials === null;
19111
+ if (credentials) {
19112
+ try {
19113
+ assertCredentialIssuer(credentials, apiBaseUrl);
19114
+ const response = await postJson(fetchImplementation, `${issuer}/api/cli-auth/revoke`, { refreshToken: credentials.refreshToken }, { signal: AbortSignal.timeout(refreshRequestTimeoutMs) });
19115
+ remoteRevoked = response.ok;
19116
+ } catch {
19117
+ remoteRevoked = false;
19118
+ }
19273
19119
  }
19274
- }
19275
- await store.delete();
19276
- return { remoteRevoked };
19120
+ await store.delete();
19121
+ return { remoteRevoked };
19122
+ });
19277
19123
  };
19278
19124
  // src/api-client.ts
19279
19125
  import {
@@ -19364,10 +19210,24 @@ var commands = {
19364
19210
  scope: "event",
19365
19211
  summary: "Get the editable registration flow draft."
19366
19212
  },
19213
+ "event flow resources get": {
19214
+ effects: [],
19215
+ operation: (api2, input2) => api2.eventFlow.getResourceCatalog.query(input2),
19216
+ scope: "event",
19217
+ summary: "List the menus, consent documents, images, and seat maps available to the registration flow."
19218
+ },
19219
+ "event flow menu version create": {
19220
+ effects: ["state-change"],
19221
+ operation: (api2, input2) => api2.eventFlow.createMenuVersion.mutate(input2),
19222
+ payloadOnly: true,
19223
+ targetKey: "eventId",
19224
+ summary: "Create an immutable event menu version (--payload JSON)."
19225
+ },
19367
19226
  "event flow draft save": {
19368
19227
  effects: ["state-change"],
19369
19228
  operation: (api2, input2) => api2.eventFlow.saveDraft.mutate(input2),
19370
19229
  payloadOnly: true,
19230
+ targetKey: "eventId",
19371
19231
  summary: "Save a revision-checked registration flow draft (--payload JSON)."
19372
19232
  },
19373
19233
  "event flow published get": {
@@ -19587,6 +19447,12 @@ var commands = {
19587
19447
  operation: (api2, input2) => api2.user.unban.mutate(input2),
19588
19448
  payloadOnly: true,
19589
19449
  summary: "Unban a user (--payload JSON)."
19450
+ },
19451
+ "user role update": {
19452
+ effects: ["state-change"],
19453
+ operation: (api2, input2) => api2.user.updateGlobalRole.mutate(input2),
19454
+ payloadOnly: true,
19455
+ summary: "Update a user's global role (--payload JSON)."
19590
19456
  }
19591
19457
  };
19592
19458
  var payloadCommand = (operation, summary, effects = []) => ({ effects, operation, payloadOnly: true, summary });
@@ -19901,15 +19767,27 @@ var collectValuesForKey = (value, targetKey) => {
19901
19767
  return collectValuesForKey(nested, targetKey);
19902
19768
  });
19903
19769
  };
19904
- var collectTargets = (input2) => {
19905
- for (const targetKey of targetKeys) {
19906
- const values = collectValuesForKey(input2, targetKey);
19770
+ var collectDirectTargetValues = (input2, targetKey) => {
19771
+ if (!input2)
19772
+ return [];
19773
+ const value = input2[targetKey];
19774
+ if (Array.isArray(value))
19775
+ return value.map(String);
19776
+ if (["string", "number"].includes(typeof value))
19777
+ return [String(value)];
19778
+ return [];
19779
+ };
19780
+ var collectTargets = (input2, targetKey) => {
19781
+ if (targetKey)
19782
+ return collectDirectTargetValues(input2, targetKey);
19783
+ for (const targetKey2 of targetKeys) {
19784
+ const values = collectValuesForKey(input2, targetKey2);
19907
19785
  if (values.length > 0)
19908
19786
  return values;
19909
19787
  }
19910
19788
  return [];
19911
19789
  };
19912
- var requireEffects = async (effects, flags, confirm, input2) => {
19790
+ var requireEffects = async (effects, flags, confirm, input2, targetKey) => {
19913
19791
  if (effects.length === 0)
19914
19792
  return;
19915
19793
  const missing = effects.filter((effect) => {
@@ -19918,7 +19796,7 @@ var requireEffects = async (effects, flags, confirm, input2) => {
19918
19796
  });
19919
19797
  const targetRequired = effects.some((effect) => effect !== "sensitive-data");
19920
19798
  const target = getStringFlag(flags, "--target");
19921
- const candidates = targetRequired ? collectTargets(input2) : [];
19799
+ const candidates = targetRequired ? collectTargets(input2, targetKey) : [];
19922
19800
  const targetMatches = !targetRequired || target !== undefined && candidates.includes(target);
19923
19801
  if (missing.length === 0 && targetMatches)
19924
19802
  return;
@@ -19933,13 +19811,26 @@ var requireEffects = async (effects, flags, confirm, input2) => {
19933
19811
  ];
19934
19812
  throw new CliUsageError(`Command requires ${requirements.join(" and ")} in non-interactive use.${candidates.length > 0 ? ` Expected target: ${candidates.join(" or ")}.` : ""}`);
19935
19813
  };
19814
+ var knownCommandNames = new Set([
19815
+ "help",
19816
+ "manifest",
19817
+ "version",
19818
+ "health",
19819
+ "auth login",
19820
+ "auth status",
19821
+ "auth logout",
19822
+ "auth whoami",
19823
+ ...Object.keys(commands)
19824
+ ]);
19936
19825
  var runRsvpCli = async (argv, dependencies) => {
19937
- let command = argv.filter((argument) => !argument.startsWith("--")).join(" ");
19938
- if (!command)
19939
- command = "help";
19826
+ let command = "unknown";
19940
19827
  try {
19941
19828
  const parsed = parseArgs(argv);
19942
- command = parsed.positionals.join(" ") || "help";
19829
+ const requestedCommand = parsed.positionals.join(" ") || "help";
19830
+ if (!knownCommandNames.has(requestedCommand)) {
19831
+ throw new CliUsageError("Unknown command. Run 'rsvp help' to list commands.");
19832
+ }
19833
+ command = requestedCommand;
19943
19834
  if (command === "help") {
19944
19835
  output2(dependencies.io, command, getCommandManifest());
19945
19836
  return 0;
@@ -19986,15 +19877,53 @@ Waiting for approval\u2026
19986
19877
  return 0;
19987
19878
  }
19988
19879
  if (command === "auth status") {
19989
- const credentials = await getCredentialStore().get();
19990
- output2(dependencies.io, command, credentials ? {
19991
- authenticated: true,
19992
- accessTokenExpiresAt: credentials.accessTokenExpiresAt,
19993
- refreshTokenExpiresAt: credentials.refreshTokenExpiresAt,
19994
- session: credentials.session,
19995
- storage: "Bun.secrets"
19996
- } : { authenticated: false, storage: "Bun.secrets" });
19997
- return 0;
19880
+ const store = getCredentialStore();
19881
+ const credentials = await store.get();
19882
+ if (!credentials) {
19883
+ output2(dependencies.io, command, {
19884
+ authenticated: false,
19885
+ storage: "Bun.secrets"
19886
+ });
19887
+ return 0;
19888
+ }
19889
+ if (!credentials.issuer) {
19890
+ output2(dependencies.io, command, {
19891
+ authenticated: false,
19892
+ reason: "These CLI credentials were created by an older CLI and are not bound to an API URL. Run auth login again.",
19893
+ storage: "Bun.secrets"
19894
+ });
19895
+ return 0;
19896
+ }
19897
+ try {
19898
+ const accessToken2 = await getAuthenticatedAccessToken({
19899
+ apiBaseUrl,
19900
+ store
19901
+ });
19902
+ const api3 = dependencies.apiClient ?? createClient(apiBaseUrl, accessToken2);
19903
+ await api3.user.getCurrent.query();
19904
+ const currentCredentials = await store.get();
19905
+ if (!currentCredentials || !isCliCredentialForIssuer(currentCredentials, apiBaseUrl) || currentCredentials.accessToken !== accessToken2) {
19906
+ throw new Error("CLI credentials changed while validating the session. Retry the command.");
19907
+ }
19908
+ output2(dependencies.io, command, {
19909
+ authenticated: true,
19910
+ accessTokenExpiresAt: currentCredentials.accessTokenExpiresAt,
19911
+ refreshTokenExpiresAt: currentCredentials.refreshTokenExpiresAt,
19912
+ session: currentCredentials.session,
19913
+ storage: "Bun.secrets",
19914
+ validated: true
19915
+ });
19916
+ return 0;
19917
+ } catch (cause) {
19918
+ output2(dependencies.io, command, {
19919
+ authenticated: false,
19920
+ accessTokenExpiresAt: credentials.accessTokenExpiresAt,
19921
+ refreshTokenExpiresAt: credentials.refreshTokenExpiresAt,
19922
+ reason: cause instanceof Error ? cause.message : "Unable to validate the CLI session. Retry the command.",
19923
+ storage: "Bun.secrets"
19924
+ });
19925
+ return 0;
19926
+ }
19998
19927
  }
19999
19928
  if (command === "auth logout") {
20000
19929
  output2(dependencies.io, command, {
@@ -20019,7 +19948,7 @@ Waiting for approval\u2026
20019
19948
  if (!definition)
20020
19949
  throw new CliUsageError("Unknown command. Run 'rsvp help' to list commands.");
20021
19950
  const input2 = getInput(parsed.flags, definition.payloadOnly, definition.scope);
20022
- await requireEffects(definition.effects, parsed.flags, dependencies.confirm, input2);
19951
+ await requireEffects(definition.effects, parsed.flags, dependencies.confirm, input2, definition.targetKey);
20023
19952
  const accessToken = !dependencies.apiClient && !definition.public ? await getAuthenticatedAccessToken({
20024
19953
  apiBaseUrl,
20025
19954
  store: getCredentialStore()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@go-labs-sg/rsvp",
3
- "version": "0.3.0",
3
+ "version": "0.6.1",
4
4
  "description": "RSVP CLI for authorized event operations.",
5
5
  "type": "module",
6
6
  "exports": {},
@@ -14,7 +14,6 @@
14
14
  ],
15
15
  "dependencies": {
16
16
  "@trpc/client": "^11.18.0",
17
- "dotenv": "^17.4.2",
18
17
  "superjson": "^2.2.6"
19
18
  },
20
19
  "engines": {