gamesteel 0.5.2 → 0.5.4

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/dist/gamesteel.js CHANGED
@@ -25339,7 +25339,7 @@ interface Purchases {
25339
25339
  restore(): void
25340
25340
  }
25341
25341
 
25342
- /** The game's own server, for a game whose Online tab turned it on (the guide's online topic). Each ask goes to the
25342
+ /** The game's own server, for a game whose Server page turned it on (the guide's online topic). Each ask goes to the
25343
25343
  * server through the page; what comes back reaches every script on a later tick as onEvent('online', heard), where
25344
25344
  * heard is { kind: 'answer', id, name, result, data?, reason? } (result 'ok', 'error', 'busy', 'resting' or 'refused'),
25345
25345
  * { kind: 'message', room, data }, or { kind: 'joined' | 'left' | 'full', room }. A tick never waits on the network. */
@@ -25696,7 +25696,7 @@ interface Game {
25696
25696
  /** An event every onEvent hears on the next tick; it also counts as the tick's event. */
25697
25697
  emit(name: string, data?: Plain): void
25698
25698
  /** The game's seeded random number in [0, 1); Math.random is the same stream. Each
25699
- * play in the Game tab starts from a seed of its own; a playtest starts from seed 1,
25699
+ * play in the editor starts from a seed of its own; a playtest starts from seed 1,
25700
25700
  * so its runs repeat. */
25701
25701
  random(): number
25702
25702
  /** A whole number from lo to hi, both included. */
@@ -25751,7 +25751,7 @@ interface Game {
25751
25751
  /** Opens the game's privacy policy, as a game on the app stores must from inside itself: call it when the player
25752
25752
  * taps the game's own Privacy button. The page knows where the policy is; with none yet, nothing opens. */
25753
25753
  openPrivacy(): void
25754
- /** Kept across restarts and levels, and between plays in the Game tab (in that
25754
+ /** Kept across restarts and levels, and between plays in the editor (in that
25755
25755
  * browser). A playtest starts with nothing saved. */
25756
25756
  save(key: string, value: Plain): void
25757
25757
  load(key: string): Plain
@@ -86206,6 +86206,7 @@ var PLANS = [
86206
86206
  monthly: 999,
86207
86207
  seats: 1,
86208
86208
  extraSeat: null,
86209
+ storageBlock: 499,
86209
86210
  everythingIn: null,
86210
86211
  pick: false,
86211
86212
  storage: 10 * GB,
@@ -86213,7 +86214,7 @@ var PLANS = [
86213
86214
  includes: [
86214
86215
  room(10 * GB, 500 * MB),
86215
86216
  line("Everything Gamesteel does, with your own agent"),
86216
- line("The agent's 15 tools"),
86217
+ line("The agent's 16 tools"),
86217
86218
  line("Export to the web"),
86218
86219
  line("Phone app builds")
86219
86220
  ]
@@ -86224,6 +86225,7 @@ var PLANS = [
86224
86225
  monthly: 1999,
86225
86226
  seats: 3,
86226
86227
  extraSeat: 1e3,
86228
+ storageBlock: 399,
86227
86229
  everythingIn: "starter",
86228
86230
  pick: true,
86229
86231
  storage: 50 * GB,
@@ -86231,11 +86233,12 @@ var PLANS = [
86231
86233
  includes: [room(50 * GB, GB), line("An organization with shared games and roles")]
86232
86234
  },
86233
86235
  {
86234
- id: "enterprise",
86235
- name: "Enterprise",
86236
+ id: "business",
86237
+ name: "Business",
86236
86238
  monthly: 3999,
86237
86239
  seats: 20,
86238
- extraSeat: 1e3,
86240
+ extraSeat: 800,
86241
+ storageBlock: 299,
86239
86242
  everythingIn: "pro",
86240
86243
  pick: false,
86241
86244
  storage: 250 * GB,
@@ -86243,6 +86246,12 @@ var PLANS = [
86243
86246
  includes: [room(250 * GB, 2 * GB), line("Invoices instead of cards"), line("Priority support")]
86244
86247
  }
86245
86248
  ];
86249
+ var ENTERPRISE = {
86250
+ name: "Enterprise",
86251
+ everythingIn: "business",
86252
+ includes: [line("Seats and storage sized for your team"), line("Terms and invoicing agreed with you"), line("A direct line to the team")],
86253
+ email: "business@gamesteel.studio"
86254
+ };
86246
86255
  var TRIAL = {
86247
86256
  days: 7,
86248
86257
  games: 1,
@@ -86252,11 +86261,17 @@ var TRIAL = {
86252
86261
  gameBytes: 200 * MB,
86253
86262
  rules: ["One week, free", "One game", "No export", "A watermark on the game"]
86254
86263
  };
86255
- var STORAGE_BLOCK = { bytes: 10 * GB, monthly: 499 };
86264
+ var STORAGE_BLOCK = { bytes: 10 * GB };
86256
86265
  var STORAGE_FULL = (site, allowed) => `this workspace's ${sizeText(allowed)} are full: delete something it keeps, or its owner can add storage or choose a bigger plan at ${site}/editor#plan`;
86257
86266
  var GAME_FULL = (site, allowed) => `a game may hold at most ${sizeText(allowed)} on this plan: delete something in it, or its owner can choose a bigger plan at ${site}/editor#plan`;
86258
86267
  var NEARLY_FULL = 0.8;
86259
86268
  var UNLIMITED_GAME_BYTES = 2 * GB;
86269
+ function planById(id) {
86270
+ const plan = PLANS.find((one2) => one2.id === id);
86271
+ if (plan === void 0)
86272
+ throw new Error(`no plan called ${id}`);
86273
+ return plan;
86274
+ }
86260
86275
 
86261
86276
  // ../edit/dist/model.js
86262
86277
  var TILE_LAYERS = ["back", "front"];
@@ -91891,7 +91906,7 @@ var SCRIPTS_PRIMER = [
91891
91906
  " and sets that language and game.languages lists them. The HUD, the end screens and the menus write @hello for a",
91892
91907
  " key's text. The person picks the language in the player's menu; the edit operation game.setStrings writes the tables.",
91893
91908
  "- The person exports a game from the toolbar's Export: a zip whose page plays it from the disk or any web host and",
91894
- " installs on a phone, playing as the Game tab does, saves in the browser and all.",
91909
+ " installs on a phone, playing as the editor does, saves in the browser and all.",
91895
91910
  "- Mix freely: rules and scripts run in the same game, and a script may read what a rule changed.",
91896
91911
  "",
91897
91912
  "A kind's script:",
@@ -96164,7 +96179,7 @@ interface GameRequest {
96164
96179
  json(): Json
96165
96180
  }
96166
96181
 
96167
- /** Players' files and the game's own, in the game's storage (turned on in its Online tab, with its database): a
96182
+ /** Players' files and the game's own, in the game's storage (turned on, on its Server page, with its database): a
96168
96183
  * player's under players/<their id>/, so "Delete my online data" reaches them, the game's under game/. */
96169
96184
  interface Files {
96170
96185
  /** An address the player's page uploads exactly size bytes to, for 15 minutes or the seconds given: at most the
@@ -96180,7 +96195,7 @@ interface Files {
96180
96195
  list(prefix: string): Promise<Array<{ key: string; size: number }>>
96181
96196
  }
96182
96197
 
96183
- /** Notifications to players' phones, through the game's own Firebase project (turned on in its Online tab, with its
96198
+ /** Notifications to players' phones, through the game's own Firebase project (turned on, on its Server page, with its
96184
96199
  * database). A player is reached once their app asked them (game.online.askPush) and they said yes. Each push costs
96185
96200
  * one event. Never required to play, never an ad without the player's consent, never a reward for saying yes. */
96186
96201
  interface Push {
@@ -96190,7 +96205,7 @@ interface Push {
96190
96205
 
96191
96206
  /** What every function of the server is given. */
96192
96207
  interface Ctx {
96193
- /** The game's own database (Postgres): turned on in its Online tab. Keep each player's rows with a player_id column,
96208
+ /** The game's own database (Postgres): turned on, on its Server page. Keep each player's rows with a player_id column,
96194
96209
  * so "Delete my online data" reaches them. */
96195
96210
  readonly db: { query(sql: string, params?: readonly Json[]): Promise<{ rows: Record<string, Json>[] }> }
96196
96211
  /** Live rooms: send to everyone in a room, or to one player in it. Who is in a room is room.players, in its hooks. */
@@ -96631,7 +96646,7 @@ var SOUND_TOPIC = [
96631
96646
  "a noise layer, a brighter wave, a filter opened. It is muddy: highpass it, or lower the body. A click at its end: a",
96632
96647
  "longer release. A loop jumps at its seam: end on notes that lead back. Compare two of the game's own with listen",
96633
96648
  "{ names: ['own:hit', 'own:hit-heavy'] }. A playtest plays no audio, so the readout is how you hear it; the person",
96634
- "hears it in the Game tab."
96649
+ "hears it when they play it in the editor."
96635
96650
  ];
96636
96651
 
96637
96652
  // ../agent/dist/sight.js
@@ -97043,7 +97058,7 @@ var PLAYTEST_TOOLS = [
97043
97058
  },
97044
97059
  {
97045
97060
  name: "playtest_look",
97046
- description: "a picture of the game now, as the person sees it in the Game tab, and the data of the objects you name; or, with frames, a strip of moments side by side in one picture, each labelled with its time. Pictures cost money: look at the moments that matter, not after every step.",
97061
+ description: "a picture of the game now, as the person sees it when they play it in the editor, and the data of the objects you name; or, with frames, a strip of moments side by side in one picture, each labelled with its time. Pictures cost money: look at the moments that matter, not after every step.",
97047
97062
  inputSchema: object2({
97048
97063
  game: GAME,
97049
97064
  scene: SCENE,
@@ -97914,7 +97929,7 @@ var FILM = [
97914
97929
  " second, its sound mixed from the ticks that asked for it. Nobody watches it live as it renders, so a film too",
97915
97930
  " heavy to play smoothly live comes out as smooth as a light one.",
97916
97931
  "- Each later answer about the game says how the render goes, then how it ended. With no editor open, ask the person",
97917
- " to open the game and press Render video in the Game tab's Films.",
97932
+ " to open the game and press Render video in Upload \u203A Films.",
97918
97933
  "- The exported game plays a film's video in the film's place, in the player's language, with pause, chapters and",
97919
97934
  " skip; a film with no video in that language plays live. After any edit a video is older than the game: render it",
97920
97935
  " again before the person exports.",
@@ -97927,11 +97942,11 @@ var FILM = [
97927
97942
  var PUBLISH = [
97928
97943
  "# Publishing a game",
97929
97944
  "",
97930
- "A finished game goes out from the editor's Upload menu. Every account it goes to is the person's own: their GitHub,",
97945
+ "A finished game goes out from the editor's Upload page. Every account it goes to is the person's own: their GitHub,",
97931
97946
  "their Google Play and App Store developer accounts, their AdMob. Gamesteel makes the files and walks them through each",
97932
97947
  "step; the stores' clicks, checkboxes and agreements are theirs.",
97933
97948
  "",
97934
- "## The Upload menu",
97949
+ "## The Upload page",
97935
97950
  "",
97936
97951
  "- Export: a zip whose index.html plays the game from the disk, and installs as an app once any website serves it.",
97937
97952
  "- GitHub: one repo per game, in the person's own GitHub account, which they connect in the menu; each save can go to it.",
@@ -97964,7 +97979,7 @@ var PUBLISH = [
97964
97979
  "",
97965
97980
  "A game going to the App Store or Google Play opens its privacy policy from inside itself: add a small Privacy button on",
97966
97981
  "its title or pause menu, drawn like its other buttons, that calls this.game.openPrivacy() (the scripts topic shows",
97967
- "one). The person writes and confirms the policy in the editor's Upload menu, in the App Store's wizard or Google",
97982
+ "one). The person writes and confirms the policy in the editor's Upload page, in the App Store's wizard or Google",
97968
97983
  "Play's. A game with ads gets an ads part in the policy's draft, and the policy is confirmed again whenever the",
97969
97984
  "game's ads change. A game with its own server gets an online part, and its Privacy button offers Delete my online",
97970
97985
  "data: the online topic has it. A game that sells gets a purchases part: the purchases topic has it.",
@@ -97989,7 +98004,7 @@ var PUBLISH = [
97989
98004
  " It warns of a break while the player is in the middle of play, and of a banner beside one of the game's buttons.",
97990
98005
  "- In a store app, the Privacy button also opens Ad choices, where Google's consent asks for them: the player's ad",
97991
98006
  " choices, which the law asks they can change.",
97992
- "- A game whose audience includes children under 13 (the App tab asks) is made for children: it shows no ads, every",
98007
+ "- A game whose audience includes children under 13 (Upload \u203A App asks) is made for children: it shows no ads, every",
97993
98008
  " ad is refused, and its builds carry no ad library. Never place ads in a game for children.",
97994
98009
  "- An APK to try and the Xcode project download show Google's test ads, whatever ids the person gave; a release shows",
97995
98010
  " their real ones. Tell the person never to tap their own real ads, even to test: AdMob can close the account. They",
@@ -98014,7 +98029,7 @@ var ONLINE = () => [
98014
98029
  "",
98015
98030
  "A game's own server does what one device can't: a leaderboard, a save that follows the player, players together, an",
98016
98031
  "outside service with a secret key. It is the person's choice and money: the person turns it on, sizes it, pays for it",
98017
- "and publishes it in the editor's Online tab. You write its code; you never turn it on, resize it, spend or publish.",
98032
+ "and publishes it on the editor's Server page. You write its code; you never turn it on, resize it, spend or publish.",
98018
98033
  "",
98019
98034
  "## The code",
98020
98035
  "",
@@ -98025,7 +98040,7 @@ var ONLINE = () => [
98025
98040
  " is given the player (an anonymous id, checked before the code runs), the data the game sent, and ctx; what it returns",
98026
98041
  " is the answer's data, and what it throws the answer's error, in its words.",
98027
98042
  "- ctx.db is the game's own Postgres database, once the person turns it on. ctx.secrets holds the game's secrets by",
98028
- " name, whose values the person sets in the Online tab: never write a key in code. The rest of ctx is below.",
98043
+ " name, whose values the person sets on the Server page: never write a key in code. The rest of ctx is below.",
98029
98044
  "- Check every field a player sends, its type and its range, as the patterns do.",
98030
98045
  "",
98031
98046
  "The server API, exactly as server code is checked against it:",
@@ -98049,16 +98064,16 @@ var ONLINE = () => [
98049
98064
  "",
98050
98065
  "- Every table keeps the player's id in a player_id column. Delete my online data, behind the game's Privacy button,",
98051
98066
  " deletes every row whose player_id is the player's, then runs onForget, where the code deletes anything else it keeps",
98052
- " of them. The Online tab marks a table with no player_id.",
98067
+ " of them. The Server page marks a table with no player_id.",
98053
98068
  "- The privacy policy and the store forms follow the game's online, as they follow its ads: the person confirms them",
98054
- " again in the Upload menu's wizards when its server, database, live rooms, files, push or purchases change (the",
98069
+ " again in the Upload page's wizards when its server, database, live rooms, files, push or purchases change (the",
98055
98070
  " publish topic).",
98056
98071
  "- onPurchase, onRevoke and onReward hear what players buy and the ads they watch for a reward, each checked first",
98057
98072
  " with its store or AdMob: the purchases topic has them.",
98058
98073
  "",
98059
98074
  "## Players' files",
98060
98075
  "",
98061
- "- ctx.files keeps the game's files beside its database, once the person turns them on in the Online tab: a player's",
98076
+ "- ctx.files keeps the game's files beside its database, once the person turns them on, on the Server page: a player's",
98062
98077
  " under players/<their id>/, the game's own under game/; a key is at most 512 bytes, with no '..'.",
98063
98078
  "- this.game.online.upload('saveLevel', file, data) sends a file of plain data (at most 1,000,000 bytes as JSON);",
98064
98079
  " uploadScreen('saveShot', data) sends the frame as a PNG. The page asks that call with { size, type, data }; the call",
@@ -98079,15 +98094,15 @@ var ONLINE = () => [
98079
98094
  "- Ask after a moment that shows why (a match ended, a level shared), never at launch. Push is never needed to play,",
98080
98095
  " never an ad the player didn't agree to, never a reward for saying yes (App Store Review Guideline 4.5.4, Google",
98081
98096
  " Play's notifications policy).",
98082
- "- The person's steps, in their own Firebase project, which the Online tab says: add the game's Android and Apple apps;",
98097
+ "- The person's steps, in their own Firebase project, which the Server page says: add the game's Android and Apple apps;",
98083
98098
  " Apple's push key goes into Project settings \u203A Cloud Messaging; the service account and the two app files go into",
98084
- " the Online tab, and each app build carries its own.",
98099
+ " the Server page, and each app build carries its own.",
98085
98100
  "",
98086
98101
  "## Test and live",
98087
98102
  "",
98088
- "- The editor's Game tab plays the game against its test server, and players against live. Each save that changes the",
98103
+ "- Playing in the editor runs the game against its test server, and players against live. Each save that changes the",
98089
98104
  " server code goes to test a moment later; the person sends test's code to players with Publish to players in the",
98090
- " Online tab, and can undo it there. Test and live each have their own database and secrets: test's data is never",
98105
+ " Server page, and can undo it there. Test and live each have their own database and secrets: test's data is never",
98091
98106
  " players'.",
98092
98107
  "- Old apps keep calling: ctx.version says which, so a change can answer them the old way.",
98093
98108
  "",
@@ -98107,13 +98122,13 @@ var ONLINE = () => [
98107
98122
  " person turns on, and where.",
98108
98123
  "- app online_logs reads the server's logs, stage test or live.",
98109
98124
  "- app online_query reads the game's data with one read-only SQL statement: here, where playtests write it, unless",
98110
- " stage says test, or live once the person allows it in the Online tab.",
98125
+ " stage says test, or live once the person allows it on the Server page.",
98111
98126
  "",
98112
98127
  "## What it costs",
98113
98128
  "",
98114
98129
  `- Online is paid by use from the workspace's wallet, apart from its plan: the person adds credit on the Plan page, and`,
98115
98130
  ` it turns on with $10 or more there. A small game's month (about 10,000 calls a day, its database awake two hours a`,
98116
- ` day) costs about ${dollars(priceOf(TYPICAL_MONTH))}; the Online tab and the Plan page give every price.`,
98131
+ ` day) costs about ${dollars(priceOf(TYPICAL_MONTH))}; the Server page and the Plan page give every price.`,
98117
98132
  "- Each size holds its ceilings (calls a second, players at once, work a call, the database's compute and data); a call",
98118
98133
  " past them answers busy.",
98119
98134
  "- When the wallet runs out, the game's server pauses: the game keeps running, its calls answer resting, and nothing",
@@ -98121,8 +98136,8 @@ var ONLINE = () => [
98121
98136
  "",
98122
98137
  "## Games made for children",
98123
98138
  "",
98124
- "- A game made for children (the App tab asks) keeps its live rooms, player files and push off until the person confirms,",
98125
- " in the Online tab, that each is designed to be safe. Design them so: no free text between players, only preset",
98139
+ "- A game made for children (Upload \u203A App asks) keeps its live rooms, player files and push off until the person confirms,",
98140
+ " on the Server page, that each is designed to be safe. Design them so: no free text between players, only preset",
98126
98141
  " phrases, emotes and moves; no name a player types; nothing that says who or where a player is; files only the game",
98127
98142
  " makes, like a level; notifications that never sell and never reward.",
98128
98143
  "- Keep no more of anyone than the game needs.",
@@ -98228,10 +98243,10 @@ var PURCHASES = [
98228
98243
  "- Google's check uses the workspace's Play key file, the one that uploads: its account needs View financial data and",
98229
98244
  " Manage orders in Play Console \u203A Users and permissions. Until the workspace's owner adds it under Store access,",
98230
98245
  " Google's purchases are checked on the phone.",
98231
- "- Refunds and renewals reach the game's own addresses, which the Online tab gives. Apple's: the App Store wizard's",
98246
+ "- Refunds and renewals reach the game's own addresses, which the Server page gives. Apple's: the App Store wizard's",
98232
98247
  " Purchases step sets it. Google's: the person makes a Pub/Sub topic in their Google Cloud project, names it in Play",
98233
98248
  " Console \u203A Monetize \u203A Monetization setup, and adds a push subscription to the game's address, authenticating as the",
98234
- " service account of the workspace's Play key file; a notice sent as anyone else is refused. The Online tab says each",
98249
+ " service account of the workspace's Play key file; a notice sent as anyone else is refused. The Server page says each",
98235
98250
  " step.",
98236
98251
  "",
98237
98252
  "## Checked rewards",
@@ -98241,7 +98256,7 @@ var PURCHASES = [
98241
98256
  " reward, ctx) hears it once: { placement, amount, item, transaction }. Give the reward there; the game asks its",
98242
98257
  " server for it.",
98243
98258
  "- The person pastes that address into each rewarded ad unit in AdMob, under Advanced settings \u203A Server-side",
98244
- " verification; the Online tab gives it. Only rewards from the game's own rewarded ad units (Upload \u203A Ads) count: AdMob",
98259
+ " verification; the Server page gives it. Only rewards from the game's own rewarded ad units (Upload \u203A Ads) count: AdMob",
98245
98260
  " signs every account's alike. AdMob's call can come a few seconds after the ad ends: ask when the ad says rewarded,",
98246
98261
  " and once more a moment later.",
98247
98262
  "- The server keeps each reward under the player's id, which Delete my online data deletes too.",
@@ -98258,7 +98273,7 @@ var PURCHASES = [
98258
98273
  "",
98259
98274
  "## Testing",
98260
98275
  "",
98261
- "- In the editor's Game tab each buy is a card the person answers: Bought, Cancelled or Pending. With the test server",
98276
+ "- When the person plays in the editor, each buy is a card they answer: Bought, Cancelled or Pending. With the test server",
98262
98277
  " granting purchases, Bought reaches its onPurchase as a test purchase, and a reward watched reaches its onReward.",
98263
98278
  "- A playtest buys by itself, granted as a test purchase, and a restore finds nothing owned; with onPurchase, the buy",
98264
98279
  " goes to the server on this machine first. The answer lists each, like purchases: buy coins_100 granted at 1.2 s,",
@@ -98473,7 +98488,7 @@ var GUIDE_TOPICS = [
98473
98488
  { name: "model", about: "art model: 3D art Blender builds from a script, when asked for 3D", text: () => MODEL.join("\n") },
98474
98489
  { name: "team", about: "working beside teammates and their agents: board, parts, claims, notes", text: () => TEAM.join("\n") },
98475
98490
  { name: "film", about: "films: levels that play themselves, cutscenes, chapters, render to MP4", text: () => FILM.join("\n") },
98476
- { name: "publish", about: "the Upload menu: export, GitHub, app builds, stores, privacy, ads", text: () => PUBLISH.join("\n") },
98491
+ { name: "publish", about: "the Upload page: export, GitHub, app builds, stores, privacy, ads", text: () => PUBLISH.join("\n") },
98477
98492
  { name: "online", about: "a game's own server: code, database, live rooms, test and live, costs", text: () => ONLINE().join("\n") },
98478
98493
  { name: "purchases", about: "selling in store apps, and ad rewards the server verifies", text: () => PURCHASES.join("\n") }
98479
98494
  ];
@@ -106781,7 +106796,7 @@ function readArt2(args2, ctx) {
106781
106796
 
106782
106797
  ${painting}` : modelled !== null ? `${head}, modelled in 3D.${earlier} its model, to change with art model's edit or give whole again, or to render again with art model and its name alone:
106783
106798
 
106784
- ${modelled}` : by === "code" ? `${head}, made with code in the Art tab (the person's Make with code).${earlier} paint it with art paint for a finished look` : `${head}, imported, not painted: the person's own, never replaced`;
106799
+ ${modelled}` : by === "code" ? `${head}, made with code in the Art view (the person's Make with code).${earlier} paint it with art paint for a finished look` : `${head}, imported, not painted: the person's own, never replaced`;
106785
106800
  const shown3 = answer(body3, { name: `own:${name2}`, kind: entry.kind, painted: painting !== null, modelled: modelled !== null });
106786
106801
  return { ...shown3, content: [...shown3.content, ...picture5] };
106787
106802
  }
@@ -107239,7 +107254,7 @@ var APP_TOOLS = [
107239
107254
  },
107240
107255
  {
107241
107256
  name: "app_online",
107242
- description: "the game's own server (guide online): sizes, today's calls and errors, cost and wallet, versions in use; what the person turns on in the editor's Online tab.",
107257
+ description: "the game's own server (guide online): sizes, today's calls and errors, cost and wallet, versions in use; what the person turns on, on the editor's Server page.",
107243
107258
  inputSchema: object2({ game: GAME }, ["game"]),
107244
107259
  handle: (args2, ctx) => sent(args2, ctx, { action: "online" }, ONLINE2)
107245
107260
  },
@@ -107251,7 +107266,7 @@ var APP_TOOLS = [
107251
107266
  },
107252
107267
  {
107253
107268
  name: "app_online_query",
107254
- description: "one read-only SQL statement on its data: here (playtests'), test, or live once the person allows it in the Online tab.",
107269
+ description: "one read-only SQL statement on its data: here (playtests'), test, or live once the person allows it in the Server page.",
107255
107270
  inputSchema: object2({
107256
107271
  game: GAME,
107257
107272
  sql: { type: "string", minLength: 1, maxLength: 4e3, description: "one statement, like select player_id, score from scores" },
@@ -107276,7 +107291,8 @@ var NEEDS_PROGRAM6 = "exporting to a file runs in the Gamesteel program on your
107276
107291
  var EXPORT_TOOLS = [
107277
107292
  {
107278
107293
  name: "export_game",
107279
- description: "write the game's export, a zip that plays anywhere, on this machine, and say where it is: unzipped, its index.html plays in a browser. A plan exports games up to its cap; a game of gigabytes takes minutes, and as much of this machine's memory.",
107294
+ // Its words cut to make room under Cursor's list size for the website's own tool (2026-10-26).
107295
+ description: "write the game's export, a zip that plays anywhere, on this machine, and say where it is: unzipped, its index.html plays in a browser. A plan exports games up to its cap; a large game takes minutes, and memory.",
107280
107296
  inputSchema: object2({ game: GAME }, ["game"]),
107281
107297
  handle: async (args2, ctx) => {
107282
107298
  const slug = textArg(args2, "game");
@@ -107321,7 +107337,7 @@ var GITHUB_TOOLS = [
107321
107337
  // ../agent/dist/film-tools.js
107322
107338
  var NEEDS_PROGRAM8 = "rendering video runs in the Gamesteel program on your machine, which asks the editor with the game open: add it to your agent with npx gamesteel@latest connect <agent>";
107323
107339
  var STARTED = "Rendering started in the editor where the game is open. It renders each film with no video, or an old one, once for each of the game's languages, and keeps the videos with the game; each answer about the game says how it goes, and how it ended.";
107324
- var NONE = "No editor has this game open: ask the person to open it in the editor and press Render video in the Game tab's Films, or to keep it open and ask you again.";
107340
+ var NONE = "No editor has this game open: ask the person to open it in the editor and press Render video in Upload \u203A Films, or to keep it open and ask you again.";
107325
107341
  var FILM_TOOLS = [
107326
107342
  {
107327
107343
  name: "render_video",
@@ -111289,7 +111305,7 @@ var DEFAULT_SERVE = { schemas: "full" };
111289
111305
  var SINGLES2 = ["guide", "edit", "write_code", "team"];
111290
111306
  var fam = (name2, about, actions) => ({ name: name2, about, actions: Object.entries(actions).map(([action, tool]) => ({ action, tool })) });
111291
111307
  var FAMILIES2 = [
111292
- fam("games", "the games in the library: list them, open one, start or import one, export one, keep one on GitHub; and game text given as source, checked, run or replayed as it is", {
111308
+ fam("games", "the games in the library, and game text given as source", {
111293
111309
  list: "list_games",
111294
111310
  open: "open_game",
111295
111311
  new: "new_game",
@@ -111316,7 +111332,7 @@ var FAMILIES2 = [
111316
111332
  redo: "redo",
111317
111333
  art: "undo_art"
111318
111334
  }),
111319
- fam("playtest", "play the game yourself, headless: play a track, play until something happens, look at it, inspect it, start over, and save or return to a moment", {
111335
+ fam("playtest", "play the game yourself, headless, and look at it", {
111320
111336
  play: "playtest_play",
111321
111337
  until: "playtest_until",
111322
111338
  look: "playtest_look",
@@ -111325,13 +111341,13 @@ var FAMILIES2 = [
111325
111341
  snapshot: "playtest_snapshot",
111326
111342
  restore: "playtest_restore"
111327
111343
  }),
111328
- fam("art", "a game's pictures: paint one, model one in 3D, read one, list them", {
111344
+ fam("art", "a game's pictures, painted or modelled in 3D", {
111329
111345
  paint: "paint_art",
111330
111346
  model: "model_art",
111331
111347
  read: "read_art",
111332
111348
  list: "list_art"
111333
111349
  }),
111334
- fam("sound", "a game's sound: design an effect, compose music, listen to them, list them", {
111350
+ fam("sound", "a game's sound effects and music", {
111335
111351
  design: "design_sound",
111336
111352
  compose: "compose_music",
111337
111353
  listen: "listen",
@@ -111357,6 +111373,14 @@ var FAMILIES2 = [
111357
111373
  online: "app_online",
111358
111374
  online_logs: "app_online_logs",
111359
111375
  online_query: "app_online_query"
111376
+ }),
111377
+ // The 16th (the owner, 2026-10-26): what is the website's, not a game's.
111378
+ fam("website", "your Gamesteel account on the website", {
111379
+ account: "website_account",
111380
+ workspaces: "website_workspaces",
111381
+ members: "website_members",
111382
+ storage: "website_storage",
111383
+ sign_in: "sign_in"
111360
111384
  })
111361
111385
  ];
111362
111386
  function nowCalled(tool) {
@@ -111447,7 +111471,7 @@ function listingOf(family2, tools) {
111447
111471
  };
111448
111472
  for (const [key, { shape, uses }] of options)
111449
111473
  properties[key] = { ...shape, description: saidOf(uses, present2.length) };
111450
- const description = [`${family2.about}. Each action is one of:`, ...present2.map((a4) => `- ${a4.action}: ${a4.def.description}`)].join("\n");
111474
+ const description = [`${family2.about}. Actions:`, ...present2.map((a4) => `- ${a4.action}: ${a4.def.description}`)].join("\n");
111451
111475
  return { name: family2.name, description, inputSchema: { type: "object", additionalProperties: false, required: ["action"], properties } };
111452
111476
  }
111453
111477
  var ORDER = [
@@ -111465,7 +111489,8 @@ var ORDER = [
111465
111489
  "library",
111466
111490
  "film",
111467
111491
  "team",
111468
- "app"
111492
+ "app",
111493
+ "website"
111469
111494
  ];
111470
111495
  function familyListing(tools) {
111471
111496
  const singles = SINGLES2.flatMap((name2) => tools.filter((t2) => t2.name === name2));
@@ -111513,6 +111538,20 @@ var WAIT_TOOL = {
111513
111538
  description: `wait up to ${String(JOB_MS / 1e3)} seconds for a call of yours still going, and answer with what it said`,
111514
111539
  inputSchema: { type: "object", additionalProperties: false, properties: {} }
111515
111540
  };
111541
+ var WEBSITE_READS = { account: "website_account", workspaces: "website_workspaces", members: "website_members", storage: "website_storage" };
111542
+ var NO_OPTIONS = { type: "object", additionalProperties: false, properties: {} };
111543
+ var WEBSITE_TOOLS = [
111544
+ { name: WEBSITE_READS.account, description: "which account and workspace this works in, its plan, limits and pages", inputSchema: NO_OPTIONS },
111545
+ { name: WEBSITE_READS.workspaces, description: "the person's workspaces, their role in each, and which this works in", inputSchema: NO_OPTIONS },
111546
+ { name: WEBSITE_READS.members, description: "the workspace's people and their roles", inputSchema: NO_OPTIONS },
111547
+ { name: WEBSITE_READS.storage, description: "what the workspace keeps: by kind, the largest files, what no game uses", inputSchema: NO_OPTIONS }
111548
+ ];
111549
+ var WEBSITE_NAMES = new Set(Object.values(WEBSITE_READS));
111550
+ var SIGN_IN_TOOL = {
111551
+ name: "sign_in",
111552
+ description: "sign in again, as another account or workspace",
111553
+ inputSchema: { type: "object", additionalProperties: false, properties: {} }
111554
+ };
111516
111555
  var NOTHING_GOING = "no call of yours is going on";
111517
111556
  function guideAnswer(raw) {
111518
111557
  const name2 = typeof raw["topic"] === "string" ? raw["topic"] : "";
@@ -111549,6 +111588,10 @@ function schemaFor(tool) {
111549
111588
  return GUIDE_TOOL.inputSchema;
111550
111589
  if (tool === WAIT_TOOL.name)
111551
111590
  return WAIT_TOOL.inputSchema;
111591
+ if (tool === SIGN_IN_TOOL.name)
111592
+ return SIGN_IN_TOOL.inputSchema;
111593
+ if (WEBSITE_NAMES.has(tool))
111594
+ return NO_OPTIONS;
111552
111595
  const editing = EDITING.get(tool);
111553
111596
  if (editing !== void 0)
111554
111597
  return editing.inputSchema;
@@ -111570,6 +111613,8 @@ async function callToolAsync(tool, args2, ctx, label = nowCalled(tool)) {
111570
111613
  return { content: [{ type: "text", text: refused3 }], isError: true };
111571
111614
  return { content: [{ type: "text", text: NOTHING_GOING }] };
111572
111615
  }
111616
+ if (tool === SIGN_IN_TOOL.name || WEBSITE_NAMES.has(tool))
111617
+ return { content: [{ type: "text", text: ON_YOUR_MACHINE }], isError: true };
111573
111618
  const editing = EDITING.get(tool);
111574
111619
  if (editing !== void 0) {
111575
111620
  const refused3 = admitArgs(editing.inputSchema, raw, label);
@@ -111637,13 +111682,18 @@ function toolList() {
111637
111682
  return [
111638
111683
  GUIDE_TOOL,
111639
111684
  WAIT_TOOL,
111685
+ ...WEBSITE_TOOLS,
111686
+ SIGN_IN_TOOL,
111640
111687
  ...EDIT_TOOLS.map((tool) => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema })),
111641
111688
  ...CORE_TOOLS.map((name2) => ({ name: name2, description: CORE_LINES.get(name2) ?? name2, inputSchema: schemaFor(name2) }))
111642
111689
  ];
111643
111690
  }
111644
111691
  var READING_TOOLS = /* @__PURE__ */ new Set([
111692
+ // The account read and signed in again too, so an agent in a read-only workspace can still move to another.
111645
111693
  "guide",
111646
111694
  "wait_for_calls",
111695
+ ...Object.values(WEBSITE_READS),
111696
+ "sign_in",
111647
111697
  "open_game",
111648
111698
  "read_scene",
111649
111699
  "read_type",
@@ -111668,6 +111718,7 @@ var READING_TOOLS = /* @__PURE__ */ new Set([
111668
111718
  "app_online_logs",
111669
111719
  "app_online_query"
111670
111720
  ]);
111721
+ var ON_YOUR_MACHINE = "This runs on your own machine, not on the website. Add Gamesteel to your agent with `npx gamesteel@latest connect`, or `claude mcp add --scope user gamesteel -- npx -y gamesteel@latest`, then ask again.";
111671
111722
  function makesGame(name2, args2, exists) {
111672
111723
  if (name2 === "new_game")
111673
111724
  return true;
@@ -112034,6 +112085,110 @@ function studioBackend(run3, session, headers = {}, timings = {}) {
112034
112085
 
112035
112086
  // ../studio/dist/server/http.js
112036
112087
  import { randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
112088
+
112089
+ // ../design/dist/tokens.js
112090
+ var PALETTE2 = {
112091
+ "bg": "#050607",
112092
+ /** A panel's solid stand-in, and what the scene canvas reads. */
112093
+ "surface": "#101216",
112094
+ /** Raised, solid: banners, and floating glass where blur is missing. */
112095
+ "surface-2": "#181B20",
112096
+ /** The code editor's well. */
112097
+ "field": "#08090B",
112098
+ /** The scene canvas's grid and frame; glass draws its hairlines in white. */
112099
+ "line": "#232833",
112100
+ "line-strong": "#2E3440",
112101
+ "text": "#F3F5F8",
112102
+ "text-2": "#B0B6C0",
112103
+ "text-3": "#999FAA",
112104
+ /** The editor's mint. The renderer's palette and the chrome still agree. */
112105
+ "accent": "#5ED3A0",
112106
+ /** The top of the primary button's gradient, where light catches it. */
112107
+ "accent-hi": "#8EF2C7",
112108
+ /** Words on an accent fill. */
112109
+ "accent-ink": "#06281A",
112110
+ /** Accent chips, solid. */
112111
+ "accent-soft": "#163A2C",
112112
+ /** The AI's colour in the editor: its edits, its outlines, its entries in
112113
+ * the console. Reserved for it, so a person always knows which changes
112114
+ * were not theirs. */
112115
+ "ai": "#8EAEFF",
112116
+ /** The forge's red: the landing page's room glows in it (the owner, 2026-10-12: "put the glow on red"), and it is
112117
+ * the site's accent where the editor has its mint. */
112118
+ "forge": "#EC1B1C",
112119
+ /** The forge's red, light enough to read as words: the site's code and its accent text. */
112120
+ "forge-hi": "#FF6B5E",
112121
+ /** Words on a forge fill. */
112122
+ "forge-ink": "#2B0705",
112123
+ /** The forge's red as the editor's words: lighter than forge-hi, so it reads at 4.5:1 on the editor's lightest
112124
+ * grounds (a hovered row, the selection and the glass over a picture, in a lit room). */
112125
+ "forge-text": "#FF867B",
112126
+ /** What is well and live: the editor's Server with a version live, a teammate in the editor (2026-10-27). */
112127
+ "good": "#6EDB9A",
112128
+ "warn": "#F2B65E",
112129
+ "warn-soft": "#3A2C14",
112130
+ "bad": "#F28A92",
112131
+ "bad-soft": "#3D1D22"
112132
+ };
112133
+ var SPARK = {
112134
+ white: "#FFF3D6",
112135
+ yellow: "#FFC24A",
112136
+ orange: "#FF7A1C",
112137
+ red: "#B8320F"
112138
+ };
112139
+ var CHROME = [
112140
+ [0, "#FFFFFF"],
112141
+ [14, "#FFFFFF"],
112142
+ [30, "#E6E9EE"],
112143
+ [44, "#A9AFB9"],
112144
+ [50, "#3D4148"],
112145
+ [56, "#5D636C"],
112146
+ [66, "#C7CCD4"],
112147
+ [74, "#F0D2B6"],
112148
+ [84, "#FF9A4A"],
112149
+ [100, SPARK.orange]
112150
+ ];
112151
+ var GRAIN_ALPHA = 0.06;
112152
+ var GRAIN = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 ${String(GRAIN_ALPHA)} 0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E")`;
112153
+ var ICON = (paths) => `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E${paths}%3C/svg%3E")`;
112154
+ var ICON_ALERT = ICON("%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E");
112155
+ var ICON_CHECK = ICON("%3Cpath d='M20 6 9 17l-5-5'/%3E");
112156
+ var CHEVRON = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23${PALETTE2["text-2"].slice(1)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E")`;
112157
+
112158
+ // ../studio/dist/loading-mark.js
112159
+ var LOADING_MARK = ["svg", { class: "ed-loading-mark", viewBox: "0 0 160 100", width: "160", height: "100", "aria-hidden": "true", focusable: "false" }, [
112160
+ ["defs", {}, [
112161
+ [
112162
+ "linearGradient",
112163
+ { id: "ed-loading-chrome", gradientUnits: "userSpaceOnUse", x1: "0", y1: "28", x2: "0", y2: "72" },
112164
+ CHROME.map(([at5, hex5]) => ["stop", { offset: `${String(at5)}%`, "stop-color": hex5 }])
112165
+ ],
112166
+ ["radialGradient", { id: "ed-loading-heat" }, [
112167
+ ["stop", { offset: "0%", "stop-color": SPARK.orange, "stop-opacity": "0.75" }],
112168
+ ["stop", { offset: "55%", "stop-color": SPARK.red, "stop-opacity": "0.25" }],
112169
+ ["stop", { offset: "100%", "stop-color": SPARK.red, "stop-opacity": "0" }]
112170
+ ]],
112171
+ ["filter", { id: "ed-loading-melt", filterUnits: "userSpaceOnUse", x: "0", y: "0", width: "160", height: "100" }, [
112172
+ ["feGaussianBlur", { in: "SourceGraphic", stdDeviation: "4.5" }],
112173
+ ["feColorMatrix", { values: "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -8" }]
112174
+ ]]
112175
+ ]],
112176
+ ["ellipse", { class: "ed-loading-heat", cx: "80", cy: "78", rx: "58", ry: "12", fill: "url(#ed-loading-heat)" }],
112177
+ ["g", { filter: "url(#ed-loading-melt)", fill: "url(#ed-loading-chrome)" }, [
112178
+ ["circle", { class: "ed-loading-drop is-left", cx: "67", cy: "50", r: "20" }],
112179
+ ["circle", { class: "ed-loading-drop is-right", cx: "95", cy: "51", r: "16" }]
112180
+ ]],
112181
+ ["ellipse", { class: "ed-loading-shine is-left", cx: "60", cy: "39", rx: "7", ry: "2.6", fill: "#FFFFFF" }],
112182
+ ["ellipse", { class: "ed-loading-shine is-right", cx: "90", cy: "42", rx: "5", ry: "2", fill: "#FFFFFF" }]
112183
+ ]];
112184
+ var attribute = (text3) => text3.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
112185
+ function markupOf([tag, attributes, inside2 = []]) {
112186
+ const named3 = Object.entries(attributes).map(([name2, value]) => ` ${name2}="${attribute(value)}"`).join("");
112187
+ return inside2.length === 0 ? `<${tag}${named3}/>` : `<${tag}${named3}>${inside2.map(markupOf).join("")}</${tag}>`;
112188
+ }
112189
+ var LOADING_PAGE = `<div class="ed-loading"><span class="ed-loading-art">${markupOf(LOADING_MARK)}</span><div class="ed-loading-words"></div><div class="ed-loading-bar"></div><div class="ed-loading-slow"></div></div>`;
112190
+
112191
+ // ../studio/dist/server/http.js
112037
112192
  import { createServer as createServer2 } from "node:http";
112038
112193
  function paramsOf(pattern, path) {
112039
112194
  if (!pattern.includes(":") && !pattern.endsWith("/*"))
@@ -113266,7 +113421,7 @@ function editRoutes(manager, options = {}) {
113266
113421
  method: "GET",
113267
113422
  path: `${base}/scripts`,
113268
113423
  // The game's scripts as files (the free engine spec, §2.5), each with
113269
- // what the type checker found in it, for the Code tab and the Console.
113424
+ // what the type checker found in it, for the Code view and the Console.
113270
113425
  handle: (_req, res, _body, params) => {
113271
113426
  game(res, params["slug"], (s5) => {
113272
113427
  const version2 = s5.version;
@@ -114891,7 +115046,7 @@ ${said4}` };
114891
115046
  }
114892
115047
  /** What a frame of this game is drawn with: the library and the game's
114893
115048
  * own art, each image by its file, and the HUD in the game's style, as
114894
- * the Game tab draws it. */
115049
+ * the editor's run draws it. */
114895
115050
  artOf(slug) {
114896
115051
  const { store } = this.options.manager;
114897
115052
  this.library ??= libraryCatalogue().map((entry) => ({ ...entry, src: join18(LIBRARY_ROOT2, entry.src) }));
@@ -116090,73 +116245,6 @@ import { dirname as dirname8, join as join20, resolve as resolve3 } from "node:p
116090
116245
  import { promisify } from "node:util";
116091
116246
  import { gzip } from "node:zlib";
116092
116247
 
116093
- // ../design/dist/tokens.js
116094
- var PALETTE2 = {
116095
- "bg": "#050607",
116096
- /** A panel's solid stand-in, and what the scene canvas reads. */
116097
- "surface": "#101216",
116098
- /** Raised, solid: banners, and floating glass where blur is missing. */
116099
- "surface-2": "#181B20",
116100
- /** The code editor's well. */
116101
- "field": "#08090B",
116102
- /** The scene canvas's grid and frame; glass draws its hairlines in white. */
116103
- "line": "#232833",
116104
- "line-strong": "#2E3440",
116105
- "text": "#F3F5F8",
116106
- "text-2": "#B0B6C0",
116107
- "text-3": "#999FAA",
116108
- /** The editor's mint. The renderer's palette and the chrome still agree. */
116109
- "accent": "#5ED3A0",
116110
- /** The top of the primary button's gradient, where light catches it. */
116111
- "accent-hi": "#8EF2C7",
116112
- /** Words on an accent fill. */
116113
- "accent-ink": "#06281A",
116114
- /** Accent chips, solid. */
116115
- "accent-soft": "#163A2C",
116116
- /** The AI's colour in the editor: its edits, its outlines, its entries in
116117
- * the console. Reserved for it, so a person always knows which changes
116118
- * were not theirs. */
116119
- "ai": "#8EAEFF",
116120
- /** The forge's red: the landing page's room glows in it (the owner, 2026-10-12: "put the glow on red"), and it is
116121
- * the site's accent where the editor has its mint. */
116122
- "forge": "#EC1B1C",
116123
- /** The forge's red, light enough to read as words: the site's code and its accent text. */
116124
- "forge-hi": "#FF6B5E",
116125
- /** Words on a forge fill. */
116126
- "forge-ink": "#2B0705",
116127
- /** The forge's red as the editor's words: lighter than forge-hi, so it reads at 4.5:1 on the editor's lightest
116128
- * grounds (a hovered row, the selection and the glass over a picture, in a lit room). */
116129
- "forge-text": "#FF867B",
116130
- "warn": "#F2B65E",
116131
- "warn-soft": "#3A2C14",
116132
- "bad": "#F28A92",
116133
- "bad-soft": "#3D1D22"
116134
- };
116135
- var SPARK = {
116136
- white: "#FFF3D6",
116137
- yellow: "#FFC24A",
116138
- orange: "#FF7A1C",
116139
- red: "#B8320F"
116140
- };
116141
- var CHROME = [
116142
- [0, "#FFFFFF"],
116143
- [14, "#FFFFFF"],
116144
- [30, "#E6E9EE"],
116145
- [44, "#A9AFB9"],
116146
- [50, "#3D4148"],
116147
- [56, "#5D636C"],
116148
- [66, "#C7CCD4"],
116149
- [74, "#F0D2B6"],
116150
- [84, "#FF9A4A"],
116151
- [100, SPARK.orange]
116152
- ];
116153
- var GRAIN_ALPHA = 0.06;
116154
- var GRAIN = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 ${String(GRAIN_ALPHA)} 0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E")`;
116155
- var ICON = (paths) => `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E${paths}%3C/svg%3E")`;
116156
- var ICON_ALERT = ICON("%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E");
116157
- var ICON_CHECK = ICON("%3Cpath d='M20 6 9 17l-5-5'/%3E");
116158
- var CHEVRON = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23${PALETTE2["text-2"].slice(1)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E")`;
116159
-
116160
116248
  // ../design/dist/fonts.js
116161
116249
  var resolve2 = __gamesteelResolve;
116162
116250
  var FONT_FILES = {
@@ -116168,6 +116256,18 @@ var FONT_FILES = {
116168
116256
  ui: resolve2("@fontsource-variable/geist/files/geist-latin-wght-normal.woff2")
116169
116257
  };
116170
116258
 
116259
+ // ../design/dist/roles.js
116260
+ var PERMISSIONS = ["play", "edit", "publish", "servers", "members", "billing", "workspace"];
116261
+ var GRANTS = {
116262
+ owner: new Set(PERMISSIONS),
116263
+ admin: /* @__PURE__ */ new Set(["play", "edit", "publish", "servers", "members"]),
116264
+ member: /* @__PURE__ */ new Set(["play", "edit"]),
116265
+ viewer: /* @__PURE__ */ new Set(["play"])
116266
+ };
116267
+ function can(role, permission) {
116268
+ return role != null && GRANTS[role].has(permission);
116269
+ }
116270
+
116171
116271
  // ../i18n/dist/index.js
116172
116272
  function chooseLanguage(header2) {
116173
116273
  const best = { en: { q: 0, at: Infinity }, es: { q: 0, at: Infinity } };
@@ -116214,6 +116314,8 @@ var SITE = resolve3(dirname8(__gamesteelResolve("@gamesteel/site/package.json"))
116214
116314
  var WORDS = resolve3(SITE, "..", "i18n");
116215
116315
  var KEEP_LANGUAGE = 365 * 24 * 60 * 60;
116216
116316
  var KEEP_FONT = 7 * 24 * 60 * 60;
116317
+ var PUBLIC_PAGES = ["/", "/engine", "/updates", "/demo", "/connect", "/pricing", "/docs", "/terms", "/privacy"];
116318
+ var PUBLIC = new Set(PUBLIC_PAGES);
116217
116319
  var gzipped = promisify(gzip);
116218
116320
 
116219
116321
  // ../studio/dist/server/export.js
@@ -119584,7 +119686,7 @@ function pushFileProblem(platform, id, text3) {
119584
119686
  const file = PUSH_FILES[platform];
119585
119687
  const app = platform === "android" ? "the Android app" : "the Apple app";
119586
119688
  if (text3 === null)
119587
- return `${file} is missing: download it from the game's Firebase project (Project settings, Your apps, ${app} ${id}) and add it in the Online tab`;
119689
+ return `${file} is missing: download it from the game's Firebase project (Project settings, Your apps, ${app} ${id}) and add it on the Server page`;
119588
119690
  const ids = firebaseIds(platform, text3);
119589
119691
  if (ids === null)
119590
119692
  return `${file} is not the file Firebase gives: download it again`;
@@ -121839,7 +121941,7 @@ function settingsWords(state, builder, releases, icon) {
121839
121941
  `- icon: ${s5.icon === "title" ? "the title icon (the first letter on the HUD plate)" : s5.icon}${icon === null ? "" : `, but ${icon}`}`,
121840
121942
  `- splash: ${s5.splash}`,
121841
121943
  `- audience: ${s5.children === true ? "includes children under 13, so it shows no ads" : "does not include children under 13"}`,
121842
- state.keyHidden === true ? "Upload key: read by the owner's or an admin's program for a release build." : state.key === null ? "Upload key: none yet; a release build makes one, and Gamesteel keeps it sealed." : "Upload key: kept by Gamesteel, sealed; the workspace's owner and admins download a copy from the App tab.",
121944
+ state.keyHidden === true ? "Upload key: read by the owner's or an admin's program for a release build." : state.key === null ? "Upload key: none yet; a release build makes one, and Gamesteel keeps it sealed." : "Upload key: kept by Gamesteel, sealed; the workspace's owner and admins download a copy from Upload \u203A App.",
121843
121945
  builder,
121844
121946
  ...release,
121845
121947
  `Build targets: ${TARGETS2}.`
@@ -121879,7 +121981,7 @@ function hostedAppRoutes(options) {
121879
121981
  const mayExport = options.mayExport ?? ((req2) => requestPlaces.get(req2)?.export !== false);
121880
121982
  const mayKeepKeys = options.mayKeepKeys ?? ((req2) => {
121881
121983
  const role = requestPlaces.get(req2)?.role;
121882
- return !agentRequests.has(req2) && (role === "owner" || role === "admin");
121984
+ return !agentRequests.has(req2) && can(role, "publish");
121883
121985
  });
121884
121986
  const gameOf = (params) => {
121885
121987
  const slug = params["slug"] ?? "";
@@ -122144,7 +122246,7 @@ function hostedAppRoutes(options) {
122144
122246
  store.saveStoreListing(slug, listing);
122145
122247
  const problems = listingProblems(listing);
122146
122248
  const kept = `${slug}'s listing in ${language} is kept with the game.`;
122147
- const pictures = problems.some((one2) => PICTURE_PROBLEMS.has(one2)) ? "; the person makes the pictures in the editor's App and Game tabs" : "";
122249
+ const pictures = problems.some((one2) => PICTURE_PROBLEMS.has(one2)) ? "; the person makes the pictures in the editor's Upload \u203A App, and with Store screenshot while the game plays" : "";
122148
122250
  const play2 = problems.length === 0 ? " It is finished." : ` It still needs ${listingNeeds(problems)}${pictures}.`;
122149
122251
  const appleSet = APPLE_TEXT2.some((key) => set2[key] !== void 0);
122150
122252
  const update = appleSet && options.stores !== void 0 ? await options.stores.appleUpdate(slug).catch(() => false) : false;
@@ -127834,6 +127936,7 @@ var HOUR = 36e5;
127834
127936
  var DAY = 24 * HOUR;
127835
127937
  var SESSION_LIFE = 30 * DAY;
127836
127938
  var FREE_WEEK = 7 * DAY;
127939
+ var PICTURE_MOST = 512 * 1024;
127837
127940
 
127838
127941
  // ../accounts/dist/limits.js
127839
127942
  var MINUTE = 6e4;
@@ -127847,6 +127950,8 @@ var LIMITS2 = {
127847
127950
  loginAddress: { tries: 30, per: 15 * MINUTE },
127848
127951
  /** Sign-ups from one address. */
127849
127952
  signup: { tries: 5, per: HOUR2 },
127953
+ /** Google log-ins started from one address, each keeping a state until it comes back (the Google log-in spec). */
127954
+ googleStart: { tries: 30, per: 15 * MINUTE },
127850
127955
  /** Requests that can email an address (sign up, send again, forgot), counted whether or not one goes out. */
127851
127956
  emails: { tries: 3, per: HOUR2 },
127852
127957
  /** Apps registering to sign agents in, from one address (the agents by address spec, part 2). */
@@ -127858,6 +127963,9 @@ var LIMITS2 = {
127858
127963
  oauthTokenAll: { tries: 600, per: MINUTE }
127859
127964
  };
127860
127965
 
127966
+ // ../accounts/dist/mail.js
127967
+ var INK = { band: PALETTE2.bg, red: PALETTE2.forge, link: "#C81516", page: "#F2F2F3", card: "#FFFFFF", heading: "#0B0B0C", text: "#3F3F46", small: "#52525B", faint: "#A1A1AA", foot: "#FAFAFA", rule: "#ECECEE" };
127968
+
127861
127969
  // ../accounts/dist/workspaces.js
127862
127970
  var DAY2 = 864e5;
127863
127971
  var INVITE_LIFE = 7 * DAY2;
@@ -128132,7 +128240,7 @@ Team since your last call: ${news.map((event) => event.words).join("; ")}.`;
128132
128240
  own3.delete(listener);
128133
128241
  };
128134
128242
  }
128135
- /** The editor's Team tab: the board as the person sees it, and their actions on it. */
128243
+ /** The editor's Team page: the board as the person sees it, and their actions on it. */
128136
128244
  routes() {
128137
128245
  const personOf = (req2) => {
128138
128246
  const id = requestPlaces.get(req2)?.person;
@@ -128183,7 +128291,7 @@ Team since your last call: ${news.map((event) => event.words).join("; ")}.`;
128183
128291
  }
128184
128292
  ];
128185
128293
  }
128186
- /** The board as the Team tab shows it to `viewer`. */
128294
+ /** The board as the Team page shows it to `viewer`. */
128187
128295
  async view(slug, viewer) {
128188
128296
  const board = await this.load(slug);
128189
128297
  const doc = this.docOf(slug);
@@ -134850,6 +134958,11 @@ var SiteRefusal = class extends Error {
134850
134958
  status;
134851
134959
  body;
134852
134960
  };
134961
+ var SignInMoved = class extends SiteRefusal {
134962
+ constructor() {
134963
+ super(409, "this program's sign-in moved to another account or workspace: ask again", { error: "workspace" });
134964
+ }
134965
+ };
134853
134966
  var pathOf3 = (path) => path.split("/").map(encodeURIComponent).join("/");
134854
134967
  var sha2562 = (bytes2) => createHash5("sha256").update(bytes2).digest("hex");
134855
134968
  var DeviceSite = class {
@@ -134862,11 +134975,19 @@ var DeviceSite = class {
134862
134975
  get site() {
134863
134976
  return this.options.site;
134864
134977
  }
134978
+ /** The workspace the program started in, named in each request once it is known, so a sign-in that moved under the
134979
+ * program to another workspace is refused there, and nothing of this one is ever sent into it. */
134980
+ pinned = null;
134981
+ pin(workspaceId) {
134982
+ this.pinned = workspaceId;
134983
+ }
134865
134984
  async request(method, path, init2 = {}) {
134985
+ const pinned = init2.unpinned === true ? null : this.pinned;
134866
134986
  const headers = {
134867
134987
  authorization: `Bearer ${await this.options.token()}`,
134868
134988
  "x-gamesteel-engine": ENGINE_VERSION,
134869
134989
  "x-gamesteel-session": init2.session ?? this.options.session(),
134990
+ ...pinned === null ? {} : { "x-gamesteel-workspace": pinned },
134870
134991
  ...init2.headers
134871
134992
  };
134872
134993
  if (init2.json !== void 0) headers["content-type"] = "application/json";
@@ -134883,6 +135004,13 @@ var DeviceSite = class {
134883
135004
  await response.body?.cancel();
134884
135005
  throw new SiteRefusal(401, `this program is signed out of ${this.options.site}: sign in again`, null);
134885
135006
  }
135007
+ if (response.status === 409 && pinned !== null) {
135008
+ const said4 = await response.clone().json().catch(() => null);
135009
+ if (said4?.error === "workspace") {
135010
+ await response.body?.cancel();
135011
+ throw new SignInMoved();
135012
+ }
135013
+ }
134886
135014
  return response;
134887
135015
  }
134888
135016
  /** The answer's body when its status is one the route answers with, else thrown in its words. */
@@ -134930,8 +135058,21 @@ var DeviceSite = class {
134930
135058
  async githubConfirmed(confirmation) {
134931
135059
  return (await this.answer(await this.request("POST", "github/confirmed", { json: confirmation }), [200])).confirmed;
134932
135060
  }
135061
+ /** Who the sign-in is now, whatever workspace the program started in. */
134933
135062
  async standing() {
134934
- return this.answer(await this.request("GET", "standing"), [200]);
135063
+ return this.answer(await this.request("GET", "standing", { unpinned: true }), [200]);
135064
+ }
135065
+ /** The person's workspaces, their role in each, and the one this works in (the website tool). */
135066
+ async workspaces() {
135067
+ return (await this.answer(await this.request("GET", "workspaces"), [200])).workspaces;
135068
+ }
135069
+ /** The workspace's people and their roles. */
135070
+ async members() {
135071
+ return (await this.answer(await this.request("GET", "members"), [200])).members;
135072
+ }
135073
+ /** What the workspace keeps, game by game: each item, its size, and whether its game uses it. */
135074
+ async storage() {
135075
+ return this.answer(await this.request("GET", "storage"), [200]);
134935
135076
  }
134936
135077
  /** A person of the workspace by name, or their agent. */
134937
135078
  async name(user, grant) {
@@ -135173,7 +135314,7 @@ var DeviceSite = class {
135173
135314
  const game = answer3.endpoint?.game;
135174
135315
  return answer3.open && answer3.server != null && typeof host === "string" && typeof game === "string" ? { host, game, push: answer3.push === true, purchases: answer3.purchases === true, rewards: answer3.rewards === true } : null;
135175
135316
  }
135176
- /** The game's online settings whole, as the Online tab reads them (the online services spec), for its agent's words. */
135317
+ /** The game's online settings whole, as the Server page reads them (the online services spec), for its agent's words. */
135177
135318
  async onlineState(slug) {
135178
135319
  return this.answer(await this.request("GET", `games/${slug}/online`), [200]);
135179
135320
  }
@@ -135242,7 +135383,7 @@ var DeviceSite = class {
135242
135383
  };
135243
135384
 
135244
135385
  // src/program/online-agent.ts
135245
- var TAB = "the editor's Online tab";
135386
+ var TAB = "the editor's Server page";
135246
135387
  var MOST_CHARS = 7e3;
135247
135388
  var REGIONS = {
135248
135389
  "aws-us-east-2": "Ohio",
@@ -135301,20 +135442,20 @@ var AgentOnline = class {
135301
135442
  const days = online.wallet?.daysLeft ?? null;
135302
135443
  return [
135303
135444
  `- server: ${server}: ${String(ceilings.eventsPerSecond)} calls a second, ${String(ceilings.connected)} players at once, ${String(ceilings.cpuMs)} ms of work a call`,
135304
- database === null ? "- database: off: the person turns it on in the Online tab" : `- database: ${database}: up to ${String(DATABASE_SIZES[database].maxCu)} compute units and ${String(DATABASE_SIZES[database].dataBytes / 1e9)} GB of data${online.region == null ? "" : `, in ${REGIONS[online.region] ?? online.region}`}` + (online.databaseUse == null ? "" : `; data kept: ${String(Math.round(online.databaseUse.bytes / 1e6))} MB, compute this month: ${String(Math.round(online.databaseUse.cuHours * 10) / 10)} compute-unit hours`),
135305
- online.rooms === true ? "- live rooms: on" : "- live rooms: off: a game made for children turns them on once the person confirms, in the Online tab, they are designed to be safe",
135445
+ database === null ? "- database: off: the person turns it on, on the Server page" : `- database: ${database}: up to ${String(DATABASE_SIZES[database].maxCu)} compute units and ${String(DATABASE_SIZES[database].dataBytes / 1e9)} GB of data${online.region == null ? "" : `, in ${REGIONS[online.region] ?? online.region}`}` + (online.databaseUse == null ? "" : `; data kept: ${String(Math.round(online.databaseUse.bytes / 1e6))} MB, compute this month: ${String(Math.round(online.databaseUse.cuHours * 10) / 10)} compute-unit hours`),
135446
+ online.rooms === true ? "- live rooms: on" : "- live rooms: off: a game made for children turns them on once the person confirms, on the Server page, they are designed to be safe",
135306
135447
  `- test: ${versions?.test == null ? "no code deployed yet: each save of server/main.ts goes to test a moment later" : `version ${String(versions.test)}`}`,
135307
- `- live: ${versions?.live == null ? "nothing published yet: the person sends test's code to players with Publish to players, in the Online tab" : `version ${String(versions.live)}; the person publishes test's code to players with Publish to players`}`,
135448
+ `- live: ${versions?.live == null ? "nothing published yet: the person sends test's code to players with Publish to players, on the Server page" : `version ${String(versions.live)}; the person publishes test's code to players with Publish to players`}`,
135308
135449
  `- players this week: ${players.length === 0 ? "no live calls" : players.map((one2) => `${one2.version} (${count2(one2.calls, "call")})`).join(", ")}`,
135309
135450
  `- today: ${count2(online.use?.events ?? 0, "event")}, ${count2(online.use?.calls ?? 0, "call")}; errors: ${count2(online.errors?.calls ?? 0, "call")}, ${count2(online.errors?.runs ?? 0, "run")}`,
135310
- ...(online.errors?.busy ?? 0) > 0 ? [`- ${count2(online.errors?.busy ?? 0, "call")} answered busy today: the game reaches its size's ceilings; a bigger size, which the person chooses in the Online tab, lets it do more`] : [],
135451
+ ...(online.errors?.busy ?? 0) > 0 ? [`- ${count2(online.errors?.busy ?? 0, "call")} answered busy today: the game reaches its size's ceilings; a bigger size, which the person chooses on the Server page, lets it do more`] : [],
135311
135452
  ...online.errors?.deploy == null ? [] : [`- not deployed: ${online.errors.deploy}`],
135312
135453
  `- this month: ${dollars(online.cost ?? 0)}${wallet === null ? "" : `; ${wallet}${days === null ? "" : `, about ${count2(days, "day")} at this rate`}`}`,
135313
135454
  ...online.notice == null ? [] : [`- paused since ${online.notice.pausedAt.slice(0, 10)}: the wallet ran out, its calls answer resting${online.notice.deleteOn === null ? "" : `, and its data goes on ${online.notice.deleteOn.slice(0, 10)} unless what is owed is paid`}; the person adds credit on the Plan page`],
135314
135455
  // Part 2: players' files and push, each on the database.
135315
- online.files === true ? `- player files: on: a file is at most ${grouped(FILE_CAPS[server])} bytes on this server size; kept: ${String(Math.round((online.filesUse?.bytes ?? 0) / 1e5) / 10)} MB, ` + (online.filesUse?.measured == null ? "measured each day" : `measured ${online.filesUse.measured.slice(0, 10)}`) : "- player files: off: the person turns them on in the Online tab, with the database",
135316
- online.push === true ? "- push: on, through the game's own Firebase project: each push costs an event; never needed to play, never a reward for saying yes" : "- push: off: the person turns it on in the Online tab, with the game's own Firebase project and the database",
135317
- `- live players' data: ${online.liveForAgent === true ? "the person lets the agent read it" : "read only once the person lets the agent, in the Online tab"}`
135456
+ online.files === true ? `- player files: on: a file is at most ${grouped(FILE_CAPS[server])} bytes on this server size; kept: ${String(Math.round((online.filesUse?.bytes ?? 0) / 1e5) / 10)} MB, ` + (online.filesUse?.measured == null ? "measured each day" : `measured ${online.filesUse.measured.slice(0, 10)}`) : "- player files: off: the person turns them on, on the Server page, with the database",
135457
+ online.push === true ? "- push: on, through the game's own Firebase project: each push costs an event; never needed to play, never a reward for saying yes" : "- push: off: the person turns it on, on the Server page, with the game's own Firebase project and the database",
135458
+ `- live players' data: ${online.liveForAgent === true ? "the person lets the agent read it" : "read only once the person lets the agent, on the Server page"}`
135318
135459
  ];
135319
135460
  }
135320
135461
  async logs(slug, stage) {
@@ -136178,7 +136319,7 @@ var KEEP = 5;
136178
136319
  var LOG_MOST = 200;
136179
136320
  var TARGETS3 = ["android-debug", "android-release", "ios-project", "ios-release"];
136180
136321
  var CLOSED = "the program closed while this was being built";
136181
- var KEY_NOTICE = "Gamesteel keeps this app's upload key on the website, sealed. The workspace's owner and admins download a copy from the App tab: keep it safe, as Google Play takes updates to the app only when they are signed with it.";
136322
+ var KEY_NOTICE = "Gamesteel keeps this app's upload key on the website, sealed. The workspace's owner and admins download a copy from Upload \u203A App: keep it safe, as Google Play takes updates to the app only when they are signed with it.";
136182
136323
  var MEMBERS_RELEASE = "release builds run from the workspace owner's or an admin's program, which signs them with the key the website keeps: ask one of them to build android-release";
136183
136324
  var timeWords = (ms) => ms < 6e4 ? `${String(ms / 1e3)} seconds` : `${String(Math.round(ms / 6e4))} minutes`;
136184
136325
  var ranPast = (ms) => `the build ran longer than ${timeWords(ms)} and was stopped: try again`;
@@ -136298,7 +136439,7 @@ var DeviceApps = class {
136298
136439
  const path = join34(this.folder, slug, name2);
136299
136440
  return existsSync22(path) ? new Uint8Array(readFileSync30(path)) : null;
136300
136441
  }
136301
- /** The key never leaves the website through the program: its owner and admins download it from the App tab. */
136442
+ /** The key never leaves the website through the program: its owner and admins download it from Upload › App. */
136302
136443
  async keyZip() {
136303
136444
  return null;
136304
136445
  }
@@ -136438,7 +136579,7 @@ var DeviceApps = class {
136438
136579
  const made3 = await this.options.site.storeState(slug, store).then((state) => state.products?.fingerprint ?? null, () => null);
136439
136580
  if (made3 !== purchasesFingerprint(sold)) {
136440
136581
  const count3 = sold.products.length;
136441
- noSale = `this release sells ${String(count3)} product${count3 === 1 ? "" : "s"} not yet made in ${appStore ? "the App Store" : "Google Play"}: make them in the editor's Upload \u203A ${appStore ? "App Store" : "Play Store"}, in its Purchases step`;
136582
+ noSale = `this release sells ${String(count3)} product${count3 === 1 ? "" : "s"} not yet made in ${appStore ? "the App Store" : "Google Play"}: make them in the editor's Upload \u203A ${appStore ? "App Store" : "Google Play"}, in its Purchases step`;
136442
136583
  }
136443
136584
  }
136444
136585
  const checkedAds = server?.rewards === true && ads.exported !== void 0 ? { ...ads, exported: { ...ads.exported, rewardsChecked: true } } : ads;
@@ -136966,6 +137107,7 @@ var GitHubClient = class {
136966
137107
  }
136967
137108
  const response = await this.fetch(`${this.api}${path}`, {
136968
137109
  method,
137110
+ cache: "no-cache",
136969
137111
  headers: {
136970
137112
  authorization: `Bearer ${await this.options.token()}`,
136971
137113
  accept: accept2,
@@ -137581,6 +137723,26 @@ var SignIn = class {
137581
137723
  signedIn() {
137582
137724
  return this.options.key !== null || this.read() !== null;
137583
137725
  }
137726
+ /** The sign-in the program holds now, by its client (each sign-in registers its own), or the key; null for none.
137727
+ * Another sign-in on this machine changes it under every program running. */
137728
+ current() {
137729
+ return this.options.key !== null ? "key" : this.read()?.clientId ?? null;
137730
+ }
137731
+ /** This machine's sign-in ended for every program on it, and revoked on the website, so it leaves the account's
137732
+ * connected apps; the next call signs in afresh, as another account or into another workspace (a person with two
137733
+ * accounts, 2026-10-26). A website that cannot be reached now keeps the grant, which is forgotten here all the same. */
137734
+ async forget() {
137735
+ this.close();
137736
+ const held = await this.locked(async () => {
137737
+ const was = this.read();
137738
+ if (was !== null) this.write(null);
137739
+ return was;
137740
+ });
137741
+ if (held === null) return;
137742
+ await this.form("/oauth/revoke", { token: held.refresh }).then(async (answer3) => {
137743
+ await answer3.body?.cancel();
137744
+ }, () => void 0);
137745
+ }
137584
137746
  /** A live token: the key, or the access token, refreshed when it ends within a minute. Throws SignInNeeded. */
137585
137747
  async token() {
137586
137748
  if (this.options.key !== null) return this.options.key;
@@ -137954,7 +138116,7 @@ function appleNextStep(wizard, facts) {
137954
138116
  const { state } = facts;
137955
138117
  if (!facts.connected) return `the person gives their App Store Connect key (an Admin team key) in ${wizard}`;
137956
138118
  if (state.app === null) return `the person creates the app in App Store Connect (New App, iOS, the bundle id ${facts.bundleId}); app store_check then finds it`;
137957
- if (!facts.listed) return "finish the listing: app store_listing sets its text; the person makes its App Store screenshots in the editor's Game tab and gives App Review's contact in the wizard";
138119
+ if (!facts.listed) return "finish the listing: app store_listing sets its text; the person makes its App Store screenshots with Store screenshot while the game plays in the editor and gives App Review's contact in the wizard";
137958
138120
  if (!facts.confirmed) return `the person reads, edits and confirms the privacy policy as theirs in ${wizard}`;
137959
138121
  if (!facts.opens) return "add a small Privacy button that calls this.game.openPrivacy() (the playbook's privacy button)";
137960
138122
  if (!answersComplete(state.answers)) return `the person answers Apple's age rating questions in ${wizard}`;
@@ -138061,7 +138223,7 @@ var AppleWork = class {
138061
138223
  }
138062
138224
  async settingsOf(slug) {
138063
138225
  const settings = await this.kit.settings(slug);
138064
- if (settings === null) throw new Refused3(`${slug} has no app settings yet: set the app up in the editor's App tab first`);
138226
+ if (settings === null) throw new Refused3(`${slug} has no app settings yet: set the app up in the editor's Upload \u203A App first`);
138065
138227
  return settings;
138066
138228
  }
138067
138229
  /** The person's app on App Store Connect, recorded once found; null before they made it. */
@@ -138307,7 +138469,7 @@ var ADS_TXT_KEPT_MS = 3e4;
138307
138469
  function nextStep(wizard, facts, now) {
138308
138470
  const { state } = facts;
138309
138471
  if (!facts.connected) return `the person signs in with Google, or the workspace's owner gives a key file, in ${wizard}`;
138310
- if (!facts.listed) return "finish the listing: app store_listing sets its text; the person makes its pictures in the editor's App and Game tabs";
138472
+ if (!facts.listed) return "finish the listing: app store_listing sets its text; the person makes its pictures in the editor's Upload \u203A App, and with Store screenshot while the game plays";
138311
138473
  if (!facts.confirmed) return `the person reads, edits and confirms the privacy policy as theirs in ${wizard}`;
138312
138474
  if (!facts.opens) return "add a small Privacy button that calls this.game.openPrivacy() (the playbook's privacy button)";
138313
138475
  if (state.upload === null) return "upload it to testing with app store_upload";
@@ -138368,7 +138530,7 @@ var DeviceStores = class _DeviceStores {
138368
138530
  return this.options.clock?.() ?? /* @__PURE__ */ new Date();
138369
138531
  }
138370
138532
  wizard(slug) {
138371
- return `the editor's Upload \u203A Play Store at ${this.siteUrl}/editor?game=${slug}`;
138533
+ return `the editor's Upload \u203A Google Play at ${this.siteUrl}/editor?game=${slug}`;
138372
138534
  }
138373
138535
  /** What the store ask does, in words; `from` the agent or the person's click in the editor. */
138374
138536
  async act(slug, ask, from, report) {
@@ -138532,7 +138694,7 @@ ${await this.apple.state(slug)}`;
138532
138694
  /** What every upload and submission needs first: the listing, the privacy policy, and the game opening it. */
138533
138695
  async ready(slug) {
138534
138696
  const settings = await this.options.apps.settingsOf(this.options.workspaceId(), slug, this.game(slug));
138535
- if (settings === null) throw new Refused4(`${slug} has no app settings yet: set the app up in the editor's App tab first`);
138697
+ if (settings === null) throw new Refused4(`${slug} has no app settings yet: set the app up in the editor's Upload \u203A App first`);
138536
138698
  this.appIds.set(slug, settings.id);
138537
138699
  const listing = this.listingOf(slug);
138538
138700
  const problems = listingProblems(listing);
@@ -138635,7 +138797,7 @@ Warning: ${checked3.words}`;
138635
138797
  async products(slug) {
138636
138798
  const { site, apps } = this.options;
138637
138799
  const settings = await apps.settingsOf(this.options.workspaceId(), slug, this.game(slug));
138638
- if (settings === null) throw new Refused4(`${slug} has no app settings yet: set the app up in the editor's App tab first`);
138800
+ if (settings === null) throw new Refused4(`${slug} has no app settings yet: set the app up in the editor's Upload \u203A App first`);
138639
138801
  this.appIds.set(slug, settings.id);
138640
138802
  const kept = await sellable(slug, this.options.sessions().store.loadPurchases(slug), settings, site);
138641
138803
  if (typeof kept === "string") throw new Refused4(kept);
@@ -138733,7 +138895,7 @@ Warning: ${checked3.words}`;
138733
138895
  async check(slug) {
138734
138896
  const { site } = this.options;
138735
138897
  const settings = await this.options.apps.settingsOf(this.options.workspaceId(), slug, this.game(slug));
138736
- if (settings === null) throw new Refused4(`${slug} has no app settings yet: set the app up in the editor's App tab first`);
138898
+ if (settings === null) throw new Refused4(`${slug} has no app settings yet: set the app up in the editor's Upload \u203A App first`);
138737
138899
  this.appIds.set(slug, settings.id);
138738
138900
  const { client } = await this.access(slug);
138739
138901
  if (!await client.exists(settings.id)) {
@@ -139163,6 +139325,90 @@ function processLink() {
139163
139325
  };
139164
139326
  }
139165
139327
 
139328
+ // src/program/website.ts
139329
+ var ANOTHER = "For another account or workspace: website { action: 'sign_in' }.";
139330
+ var whoOf = (standing2) => `${standing2.email == null ? "" : ` as ${standing2.email}`}${standing2.workspaceName === void 0 ? "" : ` in the workspace ${standing2.workspaceName}`}`;
139331
+ function planSaid(standing2) {
139332
+ const plan = standing2.plan === null ? null : planById(standing2.plan).name;
139333
+ switch (standing2.state) {
139334
+ case "unlimited":
139335
+ return "has every feature on";
139336
+ case "free-week":
139337
+ return "is in its free week";
139338
+ case "active":
139339
+ return `is on the ${plan ?? ""} plan`;
139340
+ case "past-due":
139341
+ return `is on the ${plan ?? ""} plan, its payment past due`;
139342
+ case "ended":
139343
+ return "is read-only: its free week or plan has ended";
139344
+ default:
139345
+ return `is ${standing2.state}`;
139346
+ }
139347
+ }
139348
+ function accountSaid(standing2, site) {
139349
+ const { used, allowed } = standing2.storage ?? { used: 0, allowed: null };
139350
+ const keeps = allowed === null ? `${sizeText(used)}, with no limit` : `${sizeText(used)} of ${sizeText(allowed)}`;
139351
+ const { games, gameBytes } = standing2.limits;
139352
+ const makes = [
139353
+ games === null ? "any number of games" : `${String(games)} game${games === 1 ? "" : "s"}`,
139354
+ gameBytes === void 0 ? null : `each up to ${sizeText(gameBytes)}`
139355
+ ].filter((part2) => part2 !== null).join(", ");
139356
+ return [
139357
+ `Signed in${whoOf(standing2)}, as its ${standing2.role}.`,
139358
+ `The workspace ${planSaid(standing2)}, and keeps ${keeps}. Its plan makes ${makes}${standing2.limits.export ? "" : ", and does not export"}.`,
139359
+ `The person's pages: their games ${site}/editor, what the workspace keeps ${site}/editor#storage, the plan ${site}/editor#plan.`,
139360
+ ANOTHER
139361
+ ].join(" ");
139362
+ }
139363
+ function workspacesSaid(workspaces) {
139364
+ const lines = workspaces.map((one2) => `- ${one2.name}: ${one2.role}${one2.here ? " (this works in it)" : ""}`);
139365
+ return [`The person's workspaces, ${String(workspaces.length)}:`, ...lines, `To work in another: website { action: 'sign_in' }, and choose it on the page.`].join("\n");
139366
+ }
139367
+ function membersSaid(members, site) {
139368
+ const lines = members.map((one2) => `- ${one2.name === one2.email ? one2.email : `${one2.name} (${one2.email})`}: ${one2.role}${one2.you ? " (the person)" : ""}`);
139369
+ return [`The workspace's people, ${String(members.length)}:`, ...lines, `Its owner and admins invite and remove people at ${site}/editor#organization.`].join("\n");
139370
+ }
139371
+ var KINDS3 = {
139372
+ picture: "picture",
139373
+ sound: "sound",
139374
+ music: "music",
139375
+ font: "font",
139376
+ film: "film",
139377
+ replay: "replay",
139378
+ store: "store picture",
139379
+ game: "game text and settings",
139380
+ other: "file"
139381
+ };
139382
+ var PLURAL = {
139383
+ picture: "pictures",
139384
+ sound: "sounds",
139385
+ music: "music",
139386
+ font: "fonts",
139387
+ film: "films",
139388
+ replay: "replays",
139389
+ store: "store pictures",
139390
+ game: "game text and settings",
139391
+ other: "other files"
139392
+ };
139393
+ var LARGEST = 10;
139394
+ function storageSaid(storage, standing2, site) {
139395
+ const items = storage.games.flatMap((game) => game.items.map((item) => ({ ...item, game: game.title ?? game.slug })));
139396
+ const used = standing2.storage?.used ?? items.reduce((sum, item) => sum + item.bytes, 0) + storage.styles.bytes;
139397
+ const allowed = standing2.storage?.allowed ?? null;
139398
+ const kinds = /* @__PURE__ */ new Map();
139399
+ for (const item of items) kinds.set(item.kind, (kinds.get(item.kind) ?? 0) + item.bytes);
139400
+ const largest = [...items].sort((a4, b5) => b5.bytes - a4.bytes).slice(0, LARGEST);
139401
+ const unused = items.filter((item) => item.used === false && !item.kept);
139402
+ const useOf = (item) => item.kept ? "part of the game" : item.used === null ? "not checked" : item.used ? "in use" : "not in use";
139403
+ return [
139404
+ `The workspace keeps ${sizeText(used)}${allowed === null ? ", with no limit" : ` of ${sizeText(allowed)}`}, in ${String(storage.games.length)} game${storage.games.length === 1 ? "" : "s"}.`,
139405
+ kinds.size === 0 ? null : `By kind: ${[...kinds].sort((a4, b5) => b5[1] - a4[1]).map(([kind, bytes2]) => `${PLURAL[kind] ?? kind} ${sizeText(bytes2)}`).join(", ")}.`,
139406
+ largest.length === 0 ? null : "Largest:",
139407
+ ...largest.map((item) => `- ${item.kind === "game" ? "the game itself" : item.name}, ${KINDS3[item.kind] ?? item.kind} of ${item.game}: ${sizeText(item.bytes)}, ${useOf(item)}`),
139408
+ unused.length === 0 ? "No game is known not to use anything it keeps." : `Not in use by their games: ${String(unused.length)}, ${sizeText(unused.reduce((sum, item) => sum + item.bytes, 0))}. The person deletes them at ${site}/editor#storage, where each says its game and whether it is used.`
139409
+ ].filter((line2) => line2 !== null).join("\n");
139410
+ }
139411
+
139166
139412
  // src/program/program.ts
139167
139413
  var STANDING_MS = 3e4;
139168
139414
  var OLD_EXPORT_BYTES = 100 * MB;
@@ -139172,6 +139418,7 @@ var signInFirst = (needed) => ({
139172
139418
  ${needed.message}` }]
139173
139419
  });
139174
139420
  var clientName = (name2) => (name2 ?? "agent").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "agent";
139421
+ var movedFrom = (was, now) => was.workspaceId !== now.workspaceId || was.userId !== now.userId || was.grantId !== now.grantId;
139175
139422
  async function startProgram(options) {
139176
139423
  const { settings, signIn } = options;
139177
139424
  const log = options.log ?? ((line2) => {
@@ -139200,9 +139447,11 @@ async function startProgram(options) {
139200
139447
  let standingAt = 0;
139201
139448
  let standing2 = null;
139202
139449
  const start2 = async () => {
139450
+ const signedWith = signIn.current();
139203
139451
  const known2 = await site.standing();
139204
139452
  standing2 = known2;
139205
139453
  standingAt = Date.now();
139454
+ site.pin(known2.workspaceId);
139206
139455
  const grantId = known2.grantId ?? "";
139207
139456
  session = `${settings.programId}:${clientName(server.getClientVersion()?.name)}`;
139208
139457
  const author = `ai:${grantId}:${session}`;
@@ -139259,7 +139508,7 @@ async function startProgram(options) {
139259
139508
  ...known2.team === null ? {} : { team: { board: remoteBoard(site), names: remoteNames(site) } },
139260
139509
  orderer: (slug) => siteOrderer({ site, slug, grantId }),
139261
139510
  models: machineModels(options.models),
139262
- // The plan's rule for an app build is the website's, read here; the key is downloaded only from the App tab.
139511
+ // The plan's rule for an app build is the website's, read here; the key is downloaded only from Upload › App.
139263
139512
  apps,
139264
139513
  appOptions: {
139265
139514
  mayExport: async () => (await plan()).limits.export,
@@ -139369,6 +139618,7 @@ async function startProgram(options) {
139369
139618
  madeStores = publishing;
139370
139619
  return {
139371
139620
  standing: known2,
139621
+ signedWith,
139372
139622
  copy,
139373
139623
  uploader: sending,
139374
139624
  follower,
@@ -139396,9 +139646,24 @@ async function startProgram(options) {
139396
139646
  const listen = () => {
139397
139647
  if (signIn.signedIn()) desk.start();
139398
139648
  };
139649
+ let ending = Promise.resolve();
139650
+ const restart = (from = running) => {
139651
+ if (from === null || running !== from) return ending;
139652
+ const was = running;
139653
+ running = null;
139654
+ standing2 = null;
139655
+ current = null;
139656
+ told = false;
139657
+ storageTold = false;
139658
+ nearTold.clear();
139659
+ if (was !== null) ending = ending.then(async () => {
139660
+ await (await was.catch(() => null))?.close();
139661
+ });
139662
+ return ending;
139663
+ };
139399
139664
  const prepared = () => {
139400
139665
  if (running === null) {
139401
- const starting = start2();
139666
+ const starting = ending.then(start2);
139402
139667
  running = starting;
139403
139668
  starting.catch(() => {
139404
139669
  if (running === starting) running = null;
@@ -139426,20 +139691,67 @@ async function startProgram(options) {
139426
139691
  return `${slug} holds ${sizeText(bytes2)} of the ${sizeText(cap)} a game may hold on this plan. Past that it takes no new files, though it still saves; its owner can choose a bigger plan at ${settings.site}/editor#plan.`;
139427
139692
  };
139428
139693
  const gamesHere = (root2) => new Set(readdirSync17(root2).filter((slug) => existsSync26(join40(root2, slug, "game.json"))));
139694
+ const signInAgain = async () => {
139695
+ if (settings.key !== null) {
139696
+ return refusal3("this program signs in with a personal key (GAMESTEEL_KEY), which is of one account and workspace: to work as another, give it a key made there, on the editor's Agents page");
139697
+ }
139698
+ const was = (await running?.catch(() => null))?.standing ?? standing2;
139699
+ await restart();
139700
+ await signIn.forget();
139701
+ const needed = new SignInNeeded(await signIn.address());
139702
+ const from = was === null ? "" : whoOf(was);
139703
+ return { content: [{ type: "text", text: `Gamesteel signed out${from}, so nothing else was done. On the page, the person can use another account and chooses the workspace.
139704
+ ${needed.message}` }] };
139705
+ };
139706
+ const websiteSaid = async (name2, now) => {
139707
+ switch (name2) {
139708
+ case WEBSITE_READS.account:
139709
+ return accountSaid(now, settings.site);
139710
+ case WEBSITE_READS.workspaces:
139711
+ return workspacesSaid(await site.workspaces());
139712
+ case WEBSITE_READS.members:
139713
+ return membersSaid(await site.members(), settings.site);
139714
+ case WEBSITE_READS.storage:
139715
+ return storageSaid(await site.storage(), now, settings.site);
139716
+ default:
139717
+ return null;
139718
+ }
139719
+ };
139429
139720
  const around2 = async (name2, args2, call) => {
139721
+ if (name2 === SIGN_IN_TOOL.name) return signInAgain();
139430
139722
  let program;
139431
139723
  let now;
139724
+ let switched = null;
139725
+ let on2 = null;
139432
139726
  try {
139433
139727
  if (!signIn.signedIn()) throw new SignInNeeded(await signIn.address());
139434
- program = await prepared();
139728
+ on2 = prepared();
139729
+ program = await on2;
139730
+ if (signIn.current() !== program.signedWith || name2 === WEBSITE_READS.account || name2 === WEBSITE_READS.storage) standing2 = null;
139435
139731
  now = await plan();
139732
+ if (movedFrom(program.standing, now)) {
139733
+ await restart(on2);
139734
+ on2 = prepared();
139735
+ program = await on2;
139736
+ now = await plan();
139737
+ switched = `Gamesteel's sign-in on this computer changed: it works${whoOf(now)} now.`;
139738
+ }
139436
139739
  listen();
139437
139740
  } catch (error2) {
139438
139741
  if (error2 instanceof SignInNeeded) return signInFirst(error2);
139439
139742
  if (error2 instanceof SiteRefusal && error2.status === 401) return signInFirst(new SignInNeeded(await signIn.address()));
139743
+ if (error2 instanceof SignInMoved) {
139744
+ await restart(on2);
139745
+ return refusal3(error2.message);
139746
+ }
139440
139747
  return refusal3(`the website could not be reached: ${error2.message}. Ask again in a moment`);
139441
139748
  }
139442
139749
  current = program.ctx;
139750
+ const website = await websiteSaid(name2, now).catch((error2) => ({ refused: error2.message }));
139751
+ if (typeof website === "object" && website !== null) return refusal3(website.refused);
139752
+ if (website !== null) return { content: [{ type: "text", text: switched === null ? website : `${switched}
139753
+
139754
+ ${website}` }] };
139443
139755
  const { copy, follower, team: team2, author, uploader, moves: moves2 } = program;
139444
139756
  const reading = READING_TOOLS.has(name2) || name2 === "team" && args2["action"] === "read";
139445
139757
  if (!reading && !now.limits.writes) {
@@ -139486,6 +139798,7 @@ async function startProgram(options) {
139486
139798
  }
139487
139799
  if (unsaved.length > 0) pending.push(...(await uploader.send(unsaved)).pending);
139488
139800
  } catch (error2) {
139801
+ if (error2 instanceof SignInMoved) await restart(on2);
139489
139802
  return refusal3(error2.message);
139490
139803
  }
139491
139804
  const cap = now.limits.exportBytes;
@@ -139532,6 +139845,11 @@ ${line2}` }] };
139532
139845
  ${line2}` }] };
139533
139846
  }
139534
139847
  }
139848
+ if (name2 === "list_games" && answer3.isError !== true && now.email != null) {
139849
+ answer3 = { ...answer3, content: [...answer3.content, { type: "text", text: `
139850
+
139851
+ Signed in${whoOf(now)}. For another account or workspace: website { action: 'sign_in' }.` }] };
139852
+ }
139535
139853
  if (name2 === "list_games" && unread.length > 0) {
139536
139854
  answer3 = { ...answer3, content: [...answer3.content, { type: "text", text: `
139537
139855
 
@@ -139550,6 +139868,9 @@ Not on the website yet: ${pending.join("; ")}.` }] };
139550
139868
  const team3 = program.standing.team;
139551
139869
  answer3 = withFirstWords(answer3, team3 === null ? null : teamInstructions({ agent: team3.agent, workspace: team3.workspace, people: [...team3.people] }));
139552
139870
  }
139871
+ if (switched !== null) answer3 = { ...answer3, content: [{ type: "text", text: `${switched}
139872
+
139873
+ ` }, ...answer3.content] };
139553
139874
  return answer3;
139554
139875
  };
139555
139876
  serveTools(server, {
@@ -139572,6 +139893,7 @@ Not on the website yet: ${pending.join("; ")}.` }] };
139572
139893
  await desk.close();
139573
139894
  signIn.close();
139574
139895
  if (running !== null) await (await running.catch(() => null))?.close();
139896
+ await ending;
139575
139897
  })();
139576
139898
  return closing;
139577
139899
  }