@go-labs-sg/gosh 0.11.0 → 0.13.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 +60 -1
- package/command-manifest.json +12 -0
- package/command-reference.md +12 -0
- package/dist/index.js +300 -149
- package/package.json +2 -2
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.
|
|
@@ -83,7 +97,7 @@ Commands use `--input '<json>'` for the exact validated tRPC input object. This
|
|
|
83
97
|
|
|
84
98
|
`gosh event list-page` returns `{ items, page, pageSize, pageCount, totalCount }`. It defaults to page 1 with 25 results, accepts page sizes from 1 to 100, and supports server-side `search` and `status` filters. Archived events are excluded unless an explicit status is supplied. Request subsequent pages explicitly; the CLI does not automatically fetch every page. The existing `gosh event list` command and its array response remain unchanged.
|
|
85
99
|
|
|
86
|
-
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
|
|
100
|
+
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.
|
|
87
101
|
|
|
88
102
|
### Templates and organization scope
|
|
89
103
|
|
|
@@ -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:
|
|
@@ -313,6 +358,20 @@ Without the allow flags, an interactive terminal prints a workflow preview and r
|
|
|
313
358
|
|
|
314
359
|
One-shot commands write one JSON envelope to stdout. Watch commands write newline-delimited JSON envelopes. Failures and opt-in `--debug` diagnostics go to stderr, and known secrets, payloads, join codes, share tokens, and signed URLs are redacted. `--quiet` or `-q` suppresses diagnostics.
|
|
315
360
|
|
|
361
|
+
## Monorepo development
|
|
362
|
+
|
|
363
|
+
Follow the [development workflow](../../../docs/development-workflow.md#required-cli-coverage). App/API changes affecting this CLI must update its commands, types/input-output contracts, help/docs and verification in the same change. Use the existing authenticated product API rather than CLI-only business logic. Presentation-only changes do not require CLI edits; record why when no CLI update was needed.
|
|
364
|
+
|
|
365
|
+
From the repository root after isolated-worktree initialization:
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
bun --filter @go-labs-sg/gosh typecheck
|
|
369
|
+
bun --filter @go-labs-sg/gosh build
|
|
370
|
+
bun run packages/gosh/cli/src/index.ts help
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Building is not publication or permission to mutate live data. Use focused temporary business-behavior tests and the final pre-push gate from the shared workflow. Its existing build regenerates command artifacts; review the manifest/reference and typed operations after registry changes.
|
|
374
|
+
|
|
316
375
|
## Release verification
|
|
317
376
|
|
|
318
377
|
The `gosh-cli-v*` release workflow runs
|
package/command-manifest.json
CHANGED
|
@@ -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
|
]
|
package/command-reference.md
CHANGED
|
@@ -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
|
@@ -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.
|
|
38
|
+
// ../../../node_modules/.bun/dotenv@18.0.6/node_modules/dotenv/dist/index.cjs
|
|
39
39
|
var require_dist = __commonJS(function(exports, module) {
|
|
40
|
-
var
|
|
40
|
+
var S = (e, o) => () => {
|
|
41
41
|
try {
|
|
42
42
|
return o || e((o = { exports: {} }).exports, o), o.exports;
|
|
43
43
|
} catch (t) {
|
|
44
44
|
throw o = 0, t;
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
|
-
var
|
|
47
|
+
var j = S((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: X };
|
|
60
60
|
});
|
|
61
|
-
var N =
|
|
62
|
-
var Y = __require("fs"),
|
|
61
|
+
var N = S((ye, x) => {
|
|
62
|
+
var Y = __require("fs"), k = __require("path"), z = __require("os"), { URL: Z, fileURLToPath: ee } = __require("url"), { parseBoolean: $, optionsFromEnv: B } = j(), te = /(?:^|^)\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++)
|
|
@@ -185,7 +185,7 @@ var require_dist = __commonJS(function(exports, module) {
|
|
|
185
185
|
return o;
|
|
186
186
|
}
|
|
187
187
|
function ne(e, o) {
|
|
188
|
-
return o &&
|
|
188
|
+
return o && $(o.fast) ? oe(e) : re(e);
|
|
189
189
|
}
|
|
190
190
|
function T(e) {
|
|
191
191
|
console.log(`\u2506 ${e}`);
|
|
@@ -194,16 +194,16 @@ var require_dist = __commonJS(function(exports, module) {
|
|
|
194
194
|
console.error(`\u25C7 ${e}`);
|
|
195
195
|
}
|
|
196
196
|
function V(e) {
|
|
197
|
-
return e[0] === "~" ?
|
|
197
|
+
return e[0] === "~" ? k.join(z.homedir(), e.slice(1)) : e;
|
|
198
198
|
}
|
|
199
199
|
function ie(e = {}) {
|
|
200
200
|
return { ...B(), ...e };
|
|
201
201
|
}
|
|
202
202
|
function ce(e) {
|
|
203
203
|
e = ie(e);
|
|
204
|
-
let o =
|
|
204
|
+
let o = k.resolve(process.cwd(), ".env"), t = "utf8", n = process.env;
|
|
205
205
|
e && e.processEnv != null && (n = e.processEnv);
|
|
206
|
-
let r =
|
|
206
|
+
let r = $(e && e.debug);
|
|
207
207
|
e && e.encoding ? t = e.encoding : r && T("no encoding is specified (UTF-8 is used by default)");
|
|
208
208
|
let s = [o];
|
|
209
209
|
if (e && e.path)
|
|
@@ -222,12 +222,12 @@ var require_dist = __commonJS(function(exports, module) {
|
|
|
222
222
|
} catch (a) {
|
|
223
223
|
r && T(`failed to load ${c} ${a.message}`), i = a;
|
|
224
224
|
}
|
|
225
|
-
let p = A.populate(n, l, e), f =
|
|
225
|
+
let p = A.populate(n, l, e), f = $(Object.prototype.hasOwnProperty.call(e, "quiet") ? e.quiet : B(n).quiet);
|
|
226
226
|
if (r || !f) {
|
|
227
227
|
let c = Object.keys(p).length, a = [];
|
|
228
228
|
for (let g of s)
|
|
229
229
|
try {
|
|
230
|
-
let h =
|
|
230
|
+
let h = k.relative(process.cwd(), g instanceof Z ? ee(g) : g);
|
|
231
231
|
a.push(h);
|
|
232
232
|
} catch (h) {
|
|
233
233
|
r && T(`failed to load ${g} ${h.message}`), i = h;
|
|
@@ -240,7 +240,7 @@ var require_dist = __commonJS(function(exports, module) {
|
|
|
240
240
|
return A.configDotenv(e);
|
|
241
241
|
}
|
|
242
242
|
function le(e, o, t = {}) {
|
|
243
|
-
let n =
|
|
243
|
+
let n = $(t && t.debug), r = $(t && t.override), s = {};
|
|
244
244
|
if (e === null || typeof e != "object" || o === null || typeof o != "object") {
|
|
245
245
|
let i = new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
|
|
246
246
|
throw i.code = "OBJECT_REQUIRED", i;
|
|
@@ -256,7 +256,7 @@ var require_dist = __commonJS(function(exports, module) {
|
|
|
256
256
|
x.exports.populate = A.populate;
|
|
257
257
|
x.exports = A;
|
|
258
258
|
});
|
|
259
|
-
var M =
|
|
259
|
+
var M = S((Te, W) => {
|
|
260
260
|
var _ = __require("child_process"), fe = __require("fs"), L = __require("path");
|
|
261
261
|
function ue(e) {
|
|
262
262
|
let o = ['"'], t = 0;
|
|
@@ -309,9 +309,9 @@ var require_dist = __commonJS(function(exports, module) {
|
|
|
309
309
|
}
|
|
310
310
|
W.exports = pe;
|
|
311
311
|
});
|
|
312
|
-
var K =
|
|
313
|
-
var he = __require("fs"), ge = __require("os"), Q = __require("path"), me = __require("child_process"), ve = M(), R = N(), { optionsFromEnv: Ce } =
|
|
314
|
-
function
|
|
312
|
+
var K = S(($e, F) => {
|
|
313
|
+
var he = __require("fs"), ge = __require("os"), Q = __require("path"), me = __require("child_process"), ve = M(), R = N(), { optionsFromEnv: Ce } = j();
|
|
314
|
+
function D() {
|
|
315
315
|
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(`
|
|
316
316
|
`));
|
|
317
317
|
}
|
|
@@ -383,24 +383,24 @@ var require_dist = __commonJS(function(exports, module) {
|
|
|
383
383
|
function J(e) {
|
|
384
384
|
let o = e[0];
|
|
385
385
|
if (o === "--help" || o === "-h") {
|
|
386
|
-
|
|
386
|
+
D();
|
|
387
387
|
return;
|
|
388
388
|
}
|
|
389
389
|
if (o !== "run") {
|
|
390
|
-
|
|
390
|
+
D(), process.exitCode = 1;
|
|
391
391
|
return;
|
|
392
392
|
}
|
|
393
393
|
let t = we(e.slice(1));
|
|
394
394
|
if (t.help) {
|
|
395
|
-
|
|
395
|
+
D();
|
|
396
396
|
return;
|
|
397
397
|
}
|
|
398
398
|
if (t.error) {
|
|
399
|
-
console.error(`dotenv: ${t.error}`),
|
|
399
|
+
console.error(`dotenv: ${t.error}`), D(), process.exitCode = 1;
|
|
400
400
|
return;
|
|
401
401
|
}
|
|
402
402
|
if (t.command.length === 0) {
|
|
403
|
-
|
|
403
|
+
D(), process.exitCode = 1;
|
|
404
404
|
return;
|
|
405
405
|
}
|
|
406
406
|
let n = Ee(t);
|
|
@@ -456,13 +456,13 @@ var require_dist = __commonJS(function(exports, module) {
|
|
|
456
456
|
F.exports = J;
|
|
457
457
|
F === __require.main && J(process.argv.slice(2));
|
|
458
458
|
});
|
|
459
|
-
var
|
|
459
|
+
var I = N();
|
|
460
460
|
var Oe = K();
|
|
461
|
-
module.exports =
|
|
462
|
-
module.exports.config =
|
|
463
|
-
module.exports.configDotenv =
|
|
464
|
-
module.exports.parse =
|
|
465
|
-
module.exports.populate =
|
|
461
|
+
module.exports = I;
|
|
462
|
+
module.exports.config = I.config;
|
|
463
|
+
module.exports.configDotenv = I.configDotenv;
|
|
464
|
+
module.exports.parse = I.parse;
|
|
465
|
+
module.exports.populate = I.populate;
|
|
466
466
|
});
|
|
467
467
|
|
|
468
468
|
// src/load-env.ts
|
|
@@ -6716,126 +6716,64 @@ var logoutCliSession = async ({
|
|
|
6716
6716
|
return { remoteRevoked };
|
|
6717
6717
|
});
|
|
6718
6718
|
};
|
|
6719
|
-
// src/
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
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.
|
|
3
|
+
"version": "0.13.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.
|
|
22
|
+
"dotenv": "^18.0.6",
|
|
23
23
|
"superjson": "^2.2.6"
|
|
24
24
|
},
|
|
25
25
|
"engines": {
|