@go-labs-sg/gosh 0.11.0 → 0.12.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
@@ -2,6 +2,20 @@
2
2
 
3
3
  `@go-labs-sg/gosh` provides the `gosh` binary for permission-aware GOSH organizer operations and agent automation. It calls the same tRPC procedures as the web app using the authorizing user's global, organization, and event permissions.
4
4
 
5
+ ## First step: update before use
6
+
7
+ Before starting a task, **update to the latest published release first**, including when an AI agent runs this CLI:
8
+
9
+ ```bash
10
+ bun add --global @go-labs-sg/gosh@latest
11
+ gosh version
12
+ gosh --help
13
+ ```
14
+
15
+ Run this sequence once per task, not before every command. Re-read the updated help before choosing commands or flags. If updating fails, stop and report the failure rather than silently using an outdated CLI. If the user explicitly requested a pinned version or a development/staging build, preserve that version instead of replacing it with the public release.
16
+
17
+ New installation? Follow the runtime and installation instructions below to install Bun first, then return to this step.
18
+
5
19
  ## Runtime and installation
6
20
 
7
21
  The CLI requires [Bun](https://bun.com/docs/installation) 1.4.0 or newer. npm remains the package distribution channel, while the installed `gosh` command runs under Bun.
@@ -237,6 +251,37 @@ measures mission work only: travel between stations and staff review time are
237
251
  excluded. Existing leaderboard and results commands include the resulting
238
252
  timing information.
239
253
 
254
+ ### Artwork requirements (no login required)
255
+
256
+ Read the bundled upload policy before preparing station, mission, or branding artwork:
257
+
258
+ ```bash
259
+ gosh assets requirements --type station
260
+ gosh assets requirements --type mission --format text
261
+ gosh assets requirements
262
+ ```
263
+
264
+ The default output is the standard JSON envelope; `--format text` gives a readable brief.
265
+ Omitting `--type` lists every supported artwork type. `gosh commands`,
266
+ `gosh help assets requirements`, and the upload-command help expose this guidance too.
267
+ This command is local and read-only: it does not access credentials, contact the API,
268
+ or upload a file. API URL configuration is not used or validated for this command or
269
+ its help. Its policy comes from the installed CLI version, not a live deployment.
270
+ Update the CLI when newer policy definitions are published.
271
+
272
+ - Station and mission setup images: JPEG, PNG, WebP; at most **10 MiB (10,485,760 bytes)**.
273
+ - Branding images (logo, sponsor-logo, login-banner, map-marker, player-background):
274
+ the same formats; at most **5 MiB (5,242,880 bytes)**.
275
+ - Neither artwork upload policy enforces pixel dimensions or aspect ratio.
276
+ - **Recommendations, not requirements:** station artwork 1920 × 1080 (16:9),
277
+ mission artwork 1600 × 1000 (16:10). Participant cover containers can crop artwork;
278
+ the standard station card also becomes shorter on completion. Keep essential
279
+ text in station/mission text fields and important image content away from the edges.
280
+ - Logos, login banners, map markers, and player backgrounds have separate display
281
+ guidance; no exact canvas is prescribed for them. Preview on the relevant mobile surface.
282
+
283
+ These policies describe organizer artwork, not participant submission media or profile images.
284
+
240
285
  ### Local file uploads
241
286
 
242
287
  Upload commands add the local file metadata to the supplied API input, request a permission-scoped signed URL, and upload the bytes directly to storage without sending the GOSH CLI access token to storage:
@@ -2947,5 +2947,17 @@
2947
2947
  "slug",
2948
2948
  "name"
2949
2949
  ]
2950
+ },
2951
+ {
2952
+ "path": ["assets", "requirements"],
2953
+ "operation": "local",
2954
+ "input": "none",
2955
+ "requiresAuth": false,
2956
+ "effects": [],
2957
+ "summary": "Show upload limits and separate artwork recommendations without login or network access.",
2958
+ "options": {
2959
+ "--type": "Select station, mission, logo, sponsor-logo, login-banner, map-marker, player-background; omit for all asset types.",
2960
+ "--format": "json (default, standard CLI envelope) or text."
2961
+ }
2950
2962
  }
2951
2963
  ]
@@ -953,6 +953,7 @@ Upload an event branding asset from a local file.
953
953
  - Authentication: browser-authenticated CLI session required
954
954
  - Input: `--input '<json>'` required
955
955
  - Effects: state-change, external-write
956
+ - Artwork guidance: `gosh assets requirements` (bundled policy, no login required).
956
957
 
957
958
  ## `gosh upload setup-image`
958
959
 
@@ -961,6 +962,7 @@ Upload a station or mission setup image from a local file.
961
962
  - Authentication: browser-authenticated CLI session required
962
963
  - Input: `--input '<json>'` required
963
964
  - Effects: state-change, external-write
965
+ - Artwork guidance: `gosh assets requirements` (bundled policy, no login required).
964
966
 
965
967
  ## `gosh upload profile-image`
966
968
 
@@ -1153,3 +1155,13 @@ Unban a user.
1153
1155
  - Authentication: browser-authenticated CLI session required
1154
1156
  - Input: `--input '<json>'` required
1155
1157
  - Effects: state-change
1158
+
1159
+ ## `gosh assets requirements`
1160
+
1161
+ Show upload limits and separate artwork recommendations without login or network access.
1162
+
1163
+ - Authentication: not required
1164
+ - Input: none
1165
+ - Effects: read-only
1166
+ - --type: Select station, mission, logo, sponsor-logo, login-banner, map-marker, player-background; omit for all asset types.
1167
+ - --format: json (default, standard CLI envelope) or text.
package/dist/index.js CHANGED
@@ -6716,126 +6716,64 @@ var logoutCliSession = async ({
6716
6716
  return { remoteRevoked };
6717
6717
  });
6718
6718
  };
6719
- // src/api-client.ts
6720
- import {
6721
- createTRPCProxyClient,
6722
- httpBatchLink,
6723
- httpSubscriptionLink,
6724
- loggerLink,
6725
- splitLink
6726
- } from "@trpc/client";
6727
- import SuperJSON from "superjson";
6728
-
6729
- // src/features/authenticated-event-source.ts
6730
- class AuthenticatedEventSource {
6731
- url;
6732
- init;
6733
- CLOSED = 2;
6734
- CONNECTING = 0;
6735
- OPEN = 1;
6736
- readyState = this.CONNECTING;
6737
- abortController = null;
6738
- closed = false;
6739
- lastEventId = "";
6740
- listeners = new Map;
6741
- retryMilliseconds = 1000;
6742
- constructor(url, init = {}) {
6743
- this.url = url;
6744
- this.init = init;
6745
- this.connect();
6746
- }
6747
- addEventListener(type, listener) {
6748
- const listeners = this.listeners.get(type) ?? new Set;
6749
- listeners.add(listener);
6750
- this.listeners.set(type, listeners);
6751
- }
6752
- removeEventListener(type, listener) {
6753
- this.listeners.get(type)?.delete(listener);
6754
- }
6755
- close = () => {
6756
- this.closed = true;
6757
- this.readyState = this.CLOSED;
6758
- this.abortController?.abort();
6759
- };
6760
- dispatch(type, event) {
6761
- for (const listener of this.listeners.get(type) ?? [])
6762
- listener(event);
6763
- }
6764
- async connect() {
6765
- if (this.closed)
6766
- return;
6767
- this.readyState = this.CONNECTING;
6768
- this.abortController = new AbortController;
6769
- try {
6770
- const response = await fetch(this.url, {
6771
- headers: {
6772
- Accept: "text/event-stream",
6773
- ...this.init.headers,
6774
- ...this.lastEventId ? { "Last-Event-ID": this.lastEventId } : {}
6775
- },
6776
- signal: this.abortController.signal
6777
- });
6778
- if (!response.ok || !response.body) {
6779
- throw new Error(`SSE connection failed with HTTP ${response.status}.`);
6780
- }
6781
- this.readyState = this.OPEN;
6782
- this.dispatch("open", new Event("open"));
6783
- await this.consume(response.body);
6784
- } catch (error) {
6785
- if (!this.closed) {
6786
- this.dispatch("error", new ErrorEvent("error", {
6787
- error: error instanceof Error ? error : new Error(String(error))
6788
- }));
6789
- }
6790
- }
6791
- if (!this.closed) {
6792
- setTimeout(() => void this.connect(), this.retryMilliseconds);
6793
- }
6794
- }
6795
- async consume(body) {
6796
- const reader = body.getReader();
6797
- const decoder = new TextDecoder;
6798
- let pending = "";
6799
- while (!this.closed) {
6800
- const { done, value } = await reader.read();
6801
- pending += decoder.decode(value, { stream: !done });
6802
- const blocks = pending.split(/\r?\n\r?\n/);
6803
- pending = blocks.pop() ?? "";
6804
- for (const block of blocks)
6805
- this.processBlock(block);
6806
- if (done)
6807
- break;
6808
- }
6809
- }
6810
- processBlock(block) {
6811
- let data = "";
6812
- let eventType = "message";
6813
- for (const line of block.split(/\r?\n/)) {
6814
- if (!line || line.startsWith(":"))
6815
- continue;
6816
- const separator = line.indexOf(":");
6817
- const field = separator === -1 ? line : line.slice(0, separator);
6818
- const rawValue = separator === -1 ? "" : line.slice(separator + 1);
6819
- const value = rawValue.startsWith(" ") ? rawValue.slice(1) : rawValue;
6820
- if (field === "data")
6821
- data += `${value}
6822
- `;
6823
- if (field === "event")
6824
- eventType = value;
6825
- if (field === "id")
6826
- this.lastEventId = value;
6827
- if (field === "retry" && /^\d+$/.test(value)) {
6828
- this.retryMilliseconds = Number(value);
6829
- }
6830
- }
6831
- if (!data)
6832
- return;
6833
- this.dispatch(eventType, new MessageEvent(eventType, {
6834
- data: data.slice(0, -1),
6835
- lastEventId: this.lastEventId
6836
- }));
6719
+ // ../validators/src/asset-requirements.ts
6720
+ var goshImageContentTypes = ["image/jpeg", "image/png", "image/webp"];
6721
+ var goshImageAccept = goshImageContentTypes.join(",");
6722
+ var maxGoshSetupImageBytes = 10 * 1024 * 1024;
6723
+ var maxGoshBrandingImageBytes = 5 * 1024 * 1024;
6724
+ var goshAssetTypes = [
6725
+ "station",
6726
+ "mission",
6727
+ "logo",
6728
+ "sponsor-logo",
6729
+ "login-banner",
6730
+ "map-marker",
6731
+ "player-background"
6732
+ ];
6733
+ var logoGuidance = {
6734
+ recommendedCanvas: null,
6735
+ display: "Logo containers use contain sizing; available space varies by surface.",
6736
+ artworkAdvice: "Use PNG or WebP for transparency. Keep the logo legible at small sizes; there is no prescribed canvas."
6737
+ };
6738
+ var guidance2 = {
6739
+ station: {
6740
+ recommendedCanvas: { width: 1920, height: 1080, aspectRatio: "16:9" },
6741
+ display: "The standard event-home station card uses a 16:9 cover crop and a shorter crop when completed. Other station surfaces differ.",
6742
+ artworkAdvice: "Keep important text and logos away from the edges. Prefer putting essential instructions in station text rather than the image."
6743
+ },
6744
+ mission: {
6745
+ recommendedCanvas: { width: 1600, height: 1000, aspectRatio: "16:10" },
6746
+ display: "Participant mission cards and mission artwork use 16:10 cover crops.",
6747
+ artworkAdvice: "Keep important content away from the edges. Use mission text for essential instructions."
6748
+ },
6749
+ logo: logoGuidance,
6750
+ "sponsor-logo": logoGuidance,
6751
+ "login-banner": {
6752
+ recommendedCanvas: null,
6753
+ display: "The login banner uses full container width and automatic height; there is no prescribed upload ratio.",
6754
+ artworkAdvice: "Preview in the mobile login card. No exact canvas is prescribed; do not assume the station's 16:9 recommendation applies."
6755
+ },
6756
+ "map-marker": {
6757
+ recommendedCanvas: null,
6758
+ display: "Custom markers appear in small map-marker containers.",
6759
+ artworkAdvice: "Use a simple, legible icon and PNG or WebP for transparency. Preview on the participant map."
6760
+ },
6761
+ "player-background": {
6762
+ recommendedCanvas: null,
6763
+ display: "Participant backgrounds use centered cover sizing and can crop differently with the viewport.",
6764
+ artworkAdvice: "Use decorative artwork without essential text. Check both portrait and landscape previews."
6837
6765
  }
6838
- }
6766
+ };
6767
+ var getGoshAssetRequirements = (type) => ({
6768
+ type,
6769
+ requirements: {
6770
+ acceptedContentTypes: goshImageContentTypes,
6771
+ maxBytes: type === "station" || type === "mission" ? maxGoshSetupImageBytes : maxGoshBrandingImageBytes,
6772
+ dimensions: null,
6773
+ aspectRatio: null
6774
+ },
6775
+ recommendations: guidance2[type]
6776
+ });
6839
6777
 
6840
6778
  // src/runtime/index.ts
6841
6779
  import { createInterface } from "readline/promises";
@@ -7075,6 +7013,168 @@ var createProcessRuntime = () => ({
7075
7013
  }
7076
7014
  });
7077
7015
 
7016
+ // src/features/asset-requirements.ts
7017
+ var assetRequirementsCommand = {
7018
+ path: ["assets", "requirements"],
7019
+ operation: "local",
7020
+ input: "none",
7021
+ requiresAuth: false,
7022
+ effects: [],
7023
+ summary: "Show upload limits and separate artwork recommendations without login or network access.",
7024
+ options: {
7025
+ "--type": `Select ${goshAssetTypes.join(", ")}; omit for all asset types.`,
7026
+ "--format": "json (default, standard CLI envelope) or text."
7027
+ }
7028
+ };
7029
+ var getAssetRequirementsOutput = (type) => {
7030
+ const selectedTypes = type !== undefined ? goshAssetTypes.filter((candidate) => candidate === type) : goshAssetTypes;
7031
+ if (selectedTypes.length === 0) {
7032
+ throw new CliError("USAGE", `Unknown asset type: ${type}. Choose ${goshAssetTypes.join(", ")}.`);
7033
+ }
7034
+ return {
7035
+ provenance: "Bundled CLI policy; not a live-server verification. Update the CLI to obtain newer policy definitions.",
7036
+ assets: selectedTypes.map(getGoshAssetRequirements)
7037
+ };
7038
+ };
7039
+ var formatAssetRequirementsText = (output) => [
7040
+ output.provenance,
7041
+ ...output.assets.map(({ type, requirements, recommendations }) => {
7042
+ const canvas = recommendations.recommendedCanvas;
7043
+ return [
7044
+ `
7045
+ ${type}`,
7046
+ `Required: JPEG, PNG, WebP; maximum ${requirements.maxBytes} bytes (${requirements.maxBytes / (1024 * 1024)} MiB).`,
7047
+ "Enforced pixel dimensions / aspect ratio: none.",
7048
+ `Recommended canvas (not required): ${canvas ? `${canvas.width} \xD7 ${canvas.height} px (${canvas.aspectRatio})` : "not prescribed"}.`,
7049
+ `Display: ${recommendations.display}`,
7050
+ `Artwork advice: ${recommendations.artworkAdvice}`
7051
+ ].join(`
7052
+ `);
7053
+ })
7054
+ ].join(`
7055
+ `);
7056
+
7057
+ // src/api-client.ts
7058
+ import {
7059
+ createTRPCProxyClient,
7060
+ httpBatchLink,
7061
+ httpSubscriptionLink,
7062
+ loggerLink,
7063
+ splitLink
7064
+ } from "@trpc/client";
7065
+ import SuperJSON from "superjson";
7066
+
7067
+ // src/features/authenticated-event-source.ts
7068
+ class AuthenticatedEventSource {
7069
+ url;
7070
+ init;
7071
+ CLOSED = 2;
7072
+ CONNECTING = 0;
7073
+ OPEN = 1;
7074
+ readyState = this.CONNECTING;
7075
+ abortController = null;
7076
+ closed = false;
7077
+ lastEventId = "";
7078
+ listeners = new Map;
7079
+ retryMilliseconds = 1000;
7080
+ constructor(url, init = {}) {
7081
+ this.url = url;
7082
+ this.init = init;
7083
+ this.connect();
7084
+ }
7085
+ addEventListener(type, listener) {
7086
+ const listeners = this.listeners.get(type) ?? new Set;
7087
+ listeners.add(listener);
7088
+ this.listeners.set(type, listeners);
7089
+ }
7090
+ removeEventListener(type, listener) {
7091
+ this.listeners.get(type)?.delete(listener);
7092
+ }
7093
+ close = () => {
7094
+ this.closed = true;
7095
+ this.readyState = this.CLOSED;
7096
+ this.abortController?.abort();
7097
+ };
7098
+ dispatch(type, event) {
7099
+ for (const listener of this.listeners.get(type) ?? [])
7100
+ listener(event);
7101
+ }
7102
+ async connect() {
7103
+ if (this.closed)
7104
+ return;
7105
+ this.readyState = this.CONNECTING;
7106
+ this.abortController = new AbortController;
7107
+ try {
7108
+ const response = await fetch(this.url, {
7109
+ headers: {
7110
+ Accept: "text/event-stream",
7111
+ ...this.init.headers,
7112
+ ...this.lastEventId ? { "Last-Event-ID": this.lastEventId } : {}
7113
+ },
7114
+ signal: this.abortController.signal
7115
+ });
7116
+ if (!response.ok || !response.body) {
7117
+ throw new Error(`SSE connection failed with HTTP ${response.status}.`);
7118
+ }
7119
+ this.readyState = this.OPEN;
7120
+ this.dispatch("open", new Event("open"));
7121
+ await this.consume(response.body);
7122
+ } catch (error) {
7123
+ if (!this.closed) {
7124
+ this.dispatch("error", new ErrorEvent("error", {
7125
+ error: error instanceof Error ? error : new Error(String(error))
7126
+ }));
7127
+ }
7128
+ }
7129
+ if (!this.closed) {
7130
+ setTimeout(() => void this.connect(), this.retryMilliseconds);
7131
+ }
7132
+ }
7133
+ async consume(body) {
7134
+ const reader = body.getReader();
7135
+ const decoder = new TextDecoder;
7136
+ let pending = "";
7137
+ while (!this.closed) {
7138
+ const { done, value } = await reader.read();
7139
+ pending += decoder.decode(value, { stream: !done });
7140
+ const blocks = pending.split(/\r?\n\r?\n/);
7141
+ pending = blocks.pop() ?? "";
7142
+ for (const block of blocks)
7143
+ this.processBlock(block);
7144
+ if (done)
7145
+ break;
7146
+ }
7147
+ }
7148
+ processBlock(block) {
7149
+ let data = "";
7150
+ let eventType = "message";
7151
+ for (const line of block.split(/\r?\n/)) {
7152
+ if (!line || line.startsWith(":"))
7153
+ continue;
7154
+ const separator = line.indexOf(":");
7155
+ const field = separator === -1 ? line : line.slice(0, separator);
7156
+ const rawValue = separator === -1 ? "" : line.slice(separator + 1);
7157
+ const value = rawValue.startsWith(" ") ? rawValue.slice(1) : rawValue;
7158
+ if (field === "data")
7159
+ data += `${value}
7160
+ `;
7161
+ if (field === "event")
7162
+ eventType = value;
7163
+ if (field === "id")
7164
+ this.lastEventId = value;
7165
+ if (field === "retry" && /^\d+$/.test(value)) {
7166
+ this.retryMilliseconds = Number(value);
7167
+ }
7168
+ }
7169
+ if (!data)
7170
+ return;
7171
+ this.dispatch(eventType, new MessageEvent(eventType, {
7172
+ data: data.slice(0, -1),
7173
+ lastEventId: this.lastEventId
7174
+ }));
7175
+ }
7176
+ }
7177
+
7078
7178
  // src/config.ts
7079
7179
  var DEFAULT_GOSH_API_BASE_URL = "https://app.gosh.sg";
7080
7180
  var normalizeApiBaseUrl = (value) => {
@@ -8228,10 +8328,14 @@ var allowedFlags = new Set([
8228
8328
  "input",
8229
8329
  "no-browser",
8230
8330
  "quiet",
8231
- "target"
8331
+ "target",
8332
+ "type",
8333
+ "format"
8232
8334
  ]);
8233
8335
  var commandName = (command) => command.path.join(" ");
8336
+ var updateBeforeUse = "First step before each task: update to the latest published release with 'bun add --global @go-labs-sg/gosh@latest', then run 'gosh version' and 'gosh --help' before using commands. Update once per task. If updating fails, stop and report it. Preserve an explicitly requested pinned or development/staging version.";
8234
8337
  var commandHelp = (command) => ({
8338
+ beforeUse: updateBeforeUse,
8235
8339
  effects: command.effects,
8236
8340
  input: command.input === "json" ? "Pass the exact tRPC input object with --input '<json>'." : "This command does not accept --input.",
8237
8341
  path: command.path,
@@ -8241,9 +8345,14 @@ var commandHelp = (command) => ({
8241
8345
  target: "Pass --target with the exact resource being changed."
8242
8346
  },
8243
8347
  summary: command.summary,
8348
+ ...command.upload === "setup-image" || command.upload === "branding" ? {
8349
+ assetRequirements: goshAssetTypes.filter((type) => command.upload === "setup-image" ? type === "station" || type === "mission" : type !== "station" && type !== "mission").map(getGoshAssetRequirements),
8350
+ assetRequirementsCommand: "gosh assets requirements"
8351
+ } : {},
8244
8352
  usage: `gosh ${commandName(command)}${command.input === "json" ? " --input '<json>'" : ""}`
8245
8353
  });
8246
8354
  var rootHelp = {
8355
+ beforeUse: updateBeforeUse,
8247
8356
  authentication: {
8248
8357
  instructions: "Run 'gosh auth login'. Approve the device in your browser; credentials are stored through Bun.secrets."
8249
8358
  },
@@ -8251,6 +8360,7 @@ var rootHelp = {
8251
8360
  "gosh help [command path]",
8252
8361
  "gosh commands",
8253
8362
  "gosh version",
8363
+ "gosh assets requirements [--type station] [--format text]",
8254
8364
  "gosh <command path> [--input '<json>']"
8255
8365
  ],
8256
8366
  globalOptions: {
@@ -8286,6 +8396,16 @@ var emitHelp = (runtime, apiBaseUrl, positional) => {
8286
8396
  command: "help"
8287
8397
  }, rootHelp);
8288
8398
  }
8399
+ if (positional.join(" ") === "assets requirements") {
8400
+ return emitSuccess(runtime, {
8401
+ apiBaseUrl,
8402
+ cliVersion: GOSH_CLI_VERSION,
8403
+ command: "help assets requirements"
8404
+ }, {
8405
+ ...assetRequirementsCommand,
8406
+ usage: "gosh assets requirements [--type station] [--format json|text]"
8407
+ });
8408
+ }
8289
8409
  const resolved = findCommand(positional);
8290
8410
  if (!resolved || resolved.consumed !== positional.length) {
8291
8411
  throw new CliError("USAGE", `Unknown GOSH command: ${positional.join(" ")}`);
@@ -8302,8 +8422,14 @@ var runGoshCli = async (argv, dependencies) => {
8302
8422
  let apiBaseUrl = DEFAULT_GOSH_API_BASE_URL;
8303
8423
  try {
8304
8424
  validateFlags(flags);
8305
- apiBaseUrl = resolveApiBaseUrl(dependencies.runtime, flags);
8306
8425
  const [first, ...rest] = positional;
8426
+ const isAssetRequirements = (first === "help" ? rest : positional).join(" ") === "assets requirements";
8427
+ if (!isAssetRequirements) {
8428
+ apiBaseUrl = resolveApiBaseUrl(dependencies.runtime, flags);
8429
+ }
8430
+ if (!isAssetRequirements && (flags.type !== undefined || flags.format !== undefined)) {
8431
+ throw new CliError("USAGE", "--type and --format are only supported by assets requirements.");
8432
+ }
8307
8433
  if (first === undefined || first === "help" || flags.help === true) {
8308
8434
  return emitHelp(dependencies.runtime, apiBaseUrl, first === "help" ? rest : positional);
8309
8435
  }
@@ -8325,7 +8451,32 @@ var runGoshCli = async (argv, dependencies) => {
8325
8451
  apiBaseUrl,
8326
8452
  cliVersion: GOSH_CLI_VERSION,
8327
8453
  command: "commands"
8328
- }, commandManifest);
8454
+ }, [...commandManifest, assetRequirementsCommand]);
8455
+ }
8456
+ if (isAssetRequirements && first !== "help") {
8457
+ for (const flag of Object.keys(flags)) {
8458
+ if (!["type", "format", "api-url", "debug", "quiet", "help"].includes(flag)) {
8459
+ throw new CliError("USAGE", `assets requirements does not accept --${flag}.`);
8460
+ }
8461
+ }
8462
+ if (flags.type === true || flags.format === true) {
8463
+ throw new CliError("USAGE", "--type and --format require values.");
8464
+ }
8465
+ const format = getStringFlag(flags, "format") ?? "json";
8466
+ if (format !== "json" && format !== "text") {
8467
+ throw new CliError("USAGE", "--format must be json or text.");
8468
+ }
8469
+ const output = getAssetRequirementsOutput(getStringFlag(flags, "type"));
8470
+ if (format === "text") {
8471
+ dependencies.runtime.stdout.write(`${formatAssetRequirementsText(output)}
8472
+ `);
8473
+ return 0;
8474
+ }
8475
+ return emitSuccess(dependencies.runtime, {
8476
+ apiBaseUrl,
8477
+ cliVersion: GOSH_CLI_VERSION,
8478
+ command: "assets requirements"
8479
+ }, output);
8329
8480
  }
8330
8481
  const credentialStore = dependencies.credentialStore ?? createBunCredentialStore({
8331
8482
  account: resolveCliCredentialAccount(dependencies.runtime.env),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@go-labs-sg/gosh",
3
- "version": "0.11.0",
3
+ "version": "0.12.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",