@seriousgaming/sdk-tools 0.0.0-stage → 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (391) hide show
  1. package/LICENSE +93 -0
  2. package/README.md +36 -2
  3. package/dist/bundling/logic-exports.d.ts +34 -0
  4. package/dist/bundling/node-builtins.d.ts +21 -0
  5. package/dist/bundling/transpiler.d.ts +7 -0
  6. package/dist/cli.js +27368 -0
  7. package/dist/client/dev-server.d.ts +59 -0
  8. package/dist/client/index.d.ts +28 -0
  9. package/dist/client/stub-services.d.ts +32 -0
  10. package/dist/client/test-session.d.ts +195 -0
  11. package/dist/client.js +16884 -0
  12. package/dist/core/asset-reference-translator.d.ts +46 -0
  13. package/dist/core/clock.d.ts +57 -0
  14. package/dist/core/faults.d.ts +65 -0
  15. package/dist/core/game-settings.d.ts +65 -0
  16. package/dist/core/history.d.ts +89 -0
  17. package/dist/core/listen-with-fallback.d.ts +30 -0
  18. package/dist/core/mutation-queue.d.ts +61 -0
  19. package/dist/core/package-scenario.d.ts +89 -0
  20. package/dist/core/participant-registry.d.ts +109 -0
  21. package/dist/core/pending-changes.d.ts +14 -0
  22. package/dist/core/protocol.d.ts +83 -0
  23. package/dist/core/scenario-drafts.d.ts +32 -0
  24. package/dist/core/session-endpoints.d.ts +108 -0
  25. package/dist/core/session-gates.d.ts +75 -0
  26. package/dist/core/session-host.d.ts +94 -0
  27. package/dist/core/session-refusals.d.ts +56 -0
  28. package/dist/core/session-rewind.d.ts +32 -0
  29. package/dist/core/session-roster.d.ts +128 -0
  30. package/dist/core/session-surfaces.d.ts +32 -0
  31. package/dist/core/session-ticks.d.ts +18 -0
  32. package/dist/core/session-timers.d.ts +84 -0
  33. package/dist/core/session-writes.d.ts +40 -0
  34. package/dist/core/session.d.ts +385 -0
  35. package/dist/core/settings-locks.d.ts +22 -0
  36. package/dist/core/team-names.d.ts +52 -0
  37. package/dist/core/team-state-store.d.ts +209 -0
  38. package/dist/core/tick-timer.d.ts +75 -0
  39. package/dist/dev/blob-links.d.ts +10 -0
  40. package/dist/dev/console/assets/index-DgFglsnA.js +204 -0
  41. package/dist/dev/console/index.html +12 -0
  42. package/dist/dev/console-path.d.ts +19 -0
  43. package/dist/dev/lockfile.d.ts +124 -0
  44. package/dist/dev/package-watcher.d.ts +21 -0
  45. package/dist/dev/routes.d.ts +67 -0
  46. package/dist/dev/shell.d.ts +260 -0
  47. package/dist/dev/surfaces.d.ts +41 -0
  48. package/dist/dev/tools-identity.d.ts +23 -0
  49. package/dist/dev/view/batch.d.ts +172 -0
  50. package/dist/dev/view/commands.d.ts +75 -0
  51. package/dist/dev/view/route.d.ts +58 -0
  52. package/dist/dev/view/view.d.ts +78 -0
  53. package/dist/dev/view/viewer.d.ts +32 -0
  54. package/dist/discovery/config.d.ts +9 -0
  55. package/dist/discovery/manifest-paths.d.ts +21 -0
  56. package/dist/discovery/removed-fields.d.ts +90 -0
  57. package/dist/docs/api/deck.md +386 -0
  58. package/dist/docs/api/index.md +67 -0
  59. package/dist/docs/api/sdk-commands.md +286 -0
  60. package/dist/docs/api/sdk-e2e.md +433 -0
  61. package/dist/docs/api/sdk-projection.md +207 -0
  62. package/dist/docs/api/sdk-testing.md +214 -0
  63. package/dist/docs/api/sdk-tools-client.md +560 -0
  64. package/dist/docs/api/sdk-ui-testing.md +231 -0
  65. package/dist/docs/api/sdk-ui.md +1269 -0
  66. package/dist/docs/api/sdk.md +2140 -0
  67. package/dist/docs/changelog-0.x.md +1156 -0
  68. package/dist/docs/changelog.md +236 -0
  69. package/dist/docs/cli-and-dev-server.md +217 -0
  70. package/dist/docs/cli-error-codes.md +154 -0
  71. package/dist/docs/cli-reference.md +2488 -0
  72. package/dist/docs/cli-simulation-testing.md +222 -0
  73. package/dist/docs/concepts/index.md +46 -0
  74. package/dist/docs/concepts/terminology.md +322 -0
  75. package/dist/docs/environment-variables.md +102 -0
  76. package/dist/docs/getting-started.md +145 -0
  77. package/dist/docs/guides/ai-facilitation.md +220 -0
  78. package/dist/docs/guides/capabilities.md +460 -0
  79. package/dist/docs/guides/endpoints.md +335 -0
  80. package/dist/docs/guides/engine-testing.md +477 -0
  81. package/dist/docs/guides/engine.md +527 -0
  82. package/dist/docs/guides/file-handling.md +405 -0
  83. package/dist/docs/guides/games.md +176 -0
  84. package/dist/docs/guides/global-settings.md +94 -0
  85. package/dist/docs/guides/guide.md +123 -0
  86. package/dist/docs/guides/hooks.md +183 -0
  87. package/dist/docs/guides/index.md +51 -0
  88. package/dist/docs/guides/internationalization.md +288 -0
  89. package/dist/docs/guides/llm-integration.md +635 -0
  90. package/dist/docs/guides/multi-team-games.md +586 -0
  91. package/dist/docs/guides/patterns.md +391 -0
  92. package/dist/docs/guides/presentation.md +196 -0
  93. package/dist/docs/guides/production-constraints.md +130 -0
  94. package/dist/docs/guides/scenario-editor.md +143 -0
  95. package/dist/docs/guides/scenarios.md +515 -0
  96. package/dist/docs/guides/session.md +972 -0
  97. package/dist/docs/guides/settings.md +458 -0
  98. package/dist/docs/guides/setup.md +193 -0
  99. package/dist/docs/guides/speech-generation.md +233 -0
  100. package/dist/docs/guides/starter-game.md +164 -0
  101. package/dist/docs/guides/testing.md +575 -0
  102. package/dist/docs/guides/tick-controller.md +569 -0
  103. package/dist/docs/guides/type-safety.md +516 -0
  104. package/dist/docs/guides/ui.md +1389 -0
  105. package/dist/docs/guides/upgrading.md +636 -0
  106. package/dist/docs/guides/validators.md +217 -0
  107. package/dist/docs/guides/waiting-room.md +244 -0
  108. package/dist/docs/index.md +41 -0
  109. package/dist/docs/recipes/act-on-the-session-from-an-endpoint.md +95 -0
  110. package/dist/docs/recipes/adapt-a-game-for-walkthroughs.md +133 -0
  111. package/dist/docs/recipes/adapt-ui-for-observers.md +74 -0
  112. package/dist/docs/recipes/call-endpoint-from-ui.md +109 -0
  113. package/dist/docs/recipes/check-a-package-from-a-script.md +117 -0
  114. package/dist/docs/recipes/create-capability.md +66 -0
  115. package/dist/docs/recipes/create-endpoint.md +127 -0
  116. package/dist/docs/recipes/create-engine.md +111 -0
  117. package/dist/docs/recipes/create-game.md +70 -0
  118. package/dist/docs/recipes/create-guide.md +75 -0
  119. package/dist/docs/recipes/create-hook.md +80 -0
  120. package/dist/docs/recipes/create-presentation.md +142 -0
  121. package/dist/docs/recipes/create-scenario-editor.md +102 -0
  122. package/dist/docs/recipes/create-scenario.md +98 -0
  123. package/dist/docs/recipes/create-session-ui.md +116 -0
  124. package/dist/docs/recipes/create-settings.md +88 -0
  125. package/dist/docs/recipes/create-setup.md +115 -0
  126. package/dist/docs/recipes/create-tick-controller.md +65 -0
  127. package/dist/docs/recipes/create-user-interface.md +96 -0
  128. package/dist/docs/recipes/create-validator.md +67 -0
  129. package/dist/docs/recipes/create-waiting-room-ui.md +109 -0
  130. package/dist/docs/recipes/declare-api-uses.md +67 -0
  131. package/dist/docs/recipes/design-state-for-concurrent-writes.md +96 -0
  132. package/dist/docs/recipes/distribute-package.md +80 -0
  133. package/dist/docs/recipes/e2e-testing.md +116 -0
  134. package/dist/docs/recipes/end-game-on-condition.md +154 -0
  135. package/dist/docs/recipes/fix-duplicate-packages.md +101 -0
  136. package/dist/docs/recipes/index.md +86 -0
  137. package/dist/docs/recipes/make-an-endpoint-external.md +101 -0
  138. package/dist/docs/recipes/negotiate-across-teams.md +164 -0
  139. package/dist/docs/recipes/observe-other-teams-state.md +119 -0
  140. package/dist/docs/recipes/publish-a-package.md +139 -0
  141. package/dist/docs/recipes/remember-a-viewers-choice.md +55 -0
  142. package/dist/docs/recipes/review-a-past-day.md +73 -0
  143. package/dist/docs/recipes/share-assets-across-scenarios.md +120 -0
  144. package/dist/docs/recipes/share-logic-between-engine-and-ui.md +130 -0
  145. package/dist/docs/recipes/show-all-teams-participants.md +109 -0
  146. package/dist/docs/recipes/start-from-an-empty-package.md +169 -0
  147. package/dist/docs/recipes/take-a-file-in-an-endpoint.md +82 -0
  148. package/dist/docs/recipes/test-a-multi-role-game.md +51 -0
  149. package/dist/docs/recipes/test-user-interface.md +95 -0
  150. package/dist/docs/recipes/test-with-engine.md +115 -0
  151. package/dist/docs/recipes/upload-and-display-files.md +148 -0
  152. package/dist/docs/recipes/use-request-play.md +65 -0
  153. package/dist/docs/recipes/use-request-tick.md +75 -0
  154. package/dist/docs/troubleshooting.md +816 -0
  155. package/dist/execution/blob-inputs.d.ts +57 -0
  156. package/dist/execution/bundle-reload.d.ts +42 -0
  157. package/dist/execution/config-resolver.d.ts +127 -0
  158. package/dist/execution/configuration-builder.d.ts +66 -0
  159. package/dist/execution/context-composition.d.ts +161 -0
  160. package/dist/execution/endpoint-actions.d.ts +9 -0
  161. package/dist/execution/endpoint-payloads.d.ts +32 -0
  162. package/dist/execution/endpoint-runner.d.ts +129 -0
  163. package/dist/execution/engine-state.d.ts +40 -0
  164. package/dist/execution/executor-callbacks.d.ts +56 -0
  165. package/dist/execution/executor.d.ts +436 -0
  166. package/dist/execution/gates.d.ts +45 -0
  167. package/dist/execution/hook-chain.d.ts +25 -0
  168. package/dist/execution/logic-calls.d.ts +190 -0
  169. package/dist/execution/logic-failure.d.ts +84 -0
  170. package/dist/execution/logic-loader.d.ts +54 -0
  171. package/dist/execution/logic-result.d.ts +76 -0
  172. package/dist/execution/tick-controller-call.d.ts +32 -0
  173. package/dist/execution/types.d.ts +48 -0
  174. package/dist/generation/ts-type.d.ts +39 -0
  175. package/dist/guide/locale.d.ts +12 -0
  176. package/dist/guide/video-registry.d.ts +42 -0
  177. package/dist/help-examples.d.ts +125 -0
  178. package/dist/host/adapter.d.ts +67 -0
  179. package/dist/host/host-channel.d.ts +222 -0
  180. package/dist/host/index.d.ts +20 -0
  181. package/dist/host/protocol.d.ts +783 -0
  182. package/dist/host/surfaces.d.ts +42 -0
  183. package/dist/host/theme.d.ts +55 -0
  184. package/dist/host.js +583 -0
  185. package/dist/prompts/index.d.ts +7 -0
  186. package/dist/prompts/schema-builder.d.ts +29 -0
  187. package/dist/prompts/types.d.ts +108 -0
  188. package/dist/prompts/validators.d.ts +32 -0
  189. package/dist/runtime/capability-scope.d.ts +6 -0
  190. package/dist/runtime/context-merge.d.ts +29 -0
  191. package/dist/runtime/context-projection.d.ts +41 -0
  192. package/dist/runtime/guarded-callbacks.d.ts +15 -0
  193. package/dist/runtime/index.d.ts +27 -0
  194. package/dist/runtime/manifest-version.d.ts +55 -0
  195. package/dist/runtime/tick-validation.d.ts +25 -0
  196. package/dist/runtime.js +346 -0
  197. package/dist/schema.json +1546 -0
  198. package/dist/server/vite-plugin.d.ts +46 -0
  199. package/dist/services/tts/client.d.ts +51 -0
  200. package/dist/services/tts/elevenlabs.d.ts +24 -0
  201. package/dist/services/tts/types.d.ts +44 -0
  202. package/dist/storage/asset-resolver.d.ts +49 -0
  203. package/dist/storage/blob-store.d.ts +41 -0
  204. package/dist/templates/endpoint/logic.test.ts.liquid +61 -0
  205. package/dist/templates/endpoint/logic.ts.liquid +25 -0
  206. package/dist/templates/engine/init.json.liquid +7 -0
  207. package/dist/templates/engine/logic-aggregate.test.ts.liquid +90 -0
  208. package/dist/templates/engine/logic-aggregate.ts.liquid +46 -0
  209. package/dist/templates/engine/logic.test.ts.liquid +61 -0
  210. package/dist/templates/engine/logic.ts.liquid +32 -0
  211. package/dist/templates/engine/run.json.liquid +7 -0
  212. package/dist/templates/guide/01-at-a-glance.md.liquid +19 -0
  213. package/dist/templates/guide/02-preparation.md.liquid +18 -0
  214. package/dist/templates/guide/03-running-the-session.md.liquid +37 -0
  215. package/dist/templates/hook/logic.test.ts.liquid +46 -0
  216. package/dist/templates/hook/logic.ts.liquid +12 -0
  217. package/dist/templates/package/CLAUDE.md.template +338 -0
  218. package/dist/templates/package/DESIGN.md.liquid +144 -0
  219. package/dist/templates/package/README.md.liquid +143 -0
  220. package/dist/templates/package/claude/rules/workflow-create-capability.md +26 -0
  221. package/dist/templates/package/claude/rules/workflow-create-endpoint.md +35 -0
  222. package/dist/templates/package/claude/rules/workflow-create-engine.md +35 -0
  223. package/dist/templates/package/claude/rules/workflow-create-guide.md +40 -0
  224. package/dist/templates/package/claude/rules/workflow-create-presentation.md +37 -0
  225. package/dist/templates/package/claude/rules/workflow-create-user-interface.md +31 -0
  226. package/dist/templates/package/claude/rules/workflow-publish-package.md +54 -0
  227. package/dist/templates/package/claude/rules/workflow-test-engine-or-ui.md +30 -0
  228. package/dist/templates/package/claude/rules/workflow-update-sdk-version.md +43 -0
  229. package/dist/templates/package/claude/settings.json.template +16 -0
  230. package/dist/templates/package/gitignore.template +15 -0
  231. package/dist/templates/package/global-setup.ts.template +21 -0
  232. package/dist/templates/package/globals.d.ts.template +2 -0
  233. package/dist/templates/package/oxfmtrc.json.template +14 -0
  234. package/dist/templates/package/oxlintrc.json.template +238 -0
  235. package/dist/templates/package/package.json.liquid +65 -0
  236. package/dist/templates/package/playwright.config.ts.liquid +19 -0
  237. package/dist/templates/package/pnpm-workspace.yaml.template +27 -0
  238. package/dist/templates/package/test-setup.ts.liquid +7 -0
  239. package/dist/templates/package/tsconfig.json.liquid +15 -0
  240. package/dist/templates/package/vite.config.ts.liquid +19 -0
  241. package/dist/templates/package/vitest.config.ts.liquid +25 -0
  242. package/dist/templates/package/vitest.engine.config.ts.liquid +12 -0
  243. package/dist/templates/presentation/index.html.liquid +20 -0
  244. package/dist/templates/presentation/src/App.tsx.liquid +49 -0
  245. package/dist/templates/presentation/src/Slides.tsx.liquid +81 -0
  246. package/dist/templates/presentation/src/i18n.ts.liquid +54 -0
  247. package/dist/templates/presentation/src/main.tsx.liquid +14 -0
  248. package/dist/templates/scenario_editor/src/App.test.tsx.liquid +30 -0
  249. package/dist/templates/scenario_editor/src/App.tsx.liquid +95 -0
  250. package/dist/templates/scenario_editor/src/i18n.ts.liquid +44 -0
  251. package/dist/templates/session/src/App.test.tsx.liquid +60 -0
  252. package/dist/templates/session/src/App.tsx.liquid +128 -0
  253. package/dist/templates/session/src/i18n.ts.liquid +53 -0
  254. package/dist/templates/session/src/main.tsx.liquid +48 -0
  255. package/dist/templates/session/src/theme.ts.liquid +46 -0
  256. package/dist/templates/settings-editor/index.html.liquid +12 -0
  257. package/dist/templates/settings-editor/src/main.tsx.liquid +48 -0
  258. package/dist/templates/settings-editor/src/theme.ts.liquid +24 -0
  259. package/dist/templates/setup/src/App.test.tsx.liquid +39 -0
  260. package/dist/templates/setup/src/App.tsx.liquid +102 -0
  261. package/dist/templates/setup/src/i18n.ts.liquid +47 -0
  262. package/dist/templates/starter-game/capabilities/briefing/capability.sdk.json +18 -0
  263. package/dist/templates/starter-game/capabilities/plan/capability.sdk.json +17 -0
  264. package/dist/templates/starter-game/capabilities/results/capability.sdk.json +61 -0
  265. package/dist/templates/starter-game/capabilities/roles/capability.sdk.json +17 -0
  266. package/dist/templates/starter-game/capabilities/weather/capability.sdk.json +23 -0
  267. package/dist/templates/starter-game/engine/defaults.ts +81 -0
  268. package/dist/templates/starter-game/engine/engine.sdk.json +12 -0
  269. package/dist/templates/starter-game/engine/logic.test.ts +221 -0
  270. package/dist/templates/starter-game/engine/logic.ts +149 -0
  271. package/dist/templates/starter-game/engine/test-helpers.ts +74 -0
  272. package/dist/templates/starter-game/engine/types.ts +139 -0
  273. package/dist/templates/starter-game/guide/content/en/01-at-a-glance.md +51 -0
  274. package/dist/templates/starter-game/guide/content/en/02-preparation.md +35 -0
  275. package/dist/templates/starter-game/guide/content/en/03-running-the-day.md +58 -0
  276. package/dist/templates/starter-game/guide/guide.sdk.json +10 -0
  277. package/dist/templates/starter-game/guide/guide.test.ts +108 -0
  278. package/dist/templates/starter-game/hooks/assignproducts/defaults.ts +37 -0
  279. package/dist/templates/starter-game/hooks/assignproducts/hook.sdk.json +13 -0
  280. package/dist/templates/starter-game/hooks/assignproducts/logic/logic.test.ts +77 -0
  281. package/dist/templates/starter-game/hooks/assignproducts/logic/logic.ts +47 -0
  282. package/dist/templates/starter-game/hooks/assignproducts/types.ts +55 -0
  283. package/dist/templates/starter-game/package.json +68 -0
  284. package/dist/templates/starter-game/playwright.config.ts +19 -0
  285. package/dist/templates/starter-game/scenarios/heatwave/scenario.sdk.json +20 -0
  286. package/dist/templates/starter-game/scenarios/ordinary_week/scenario.sdk.json +12 -0
  287. package/dist/templates/starter-game/seriousgaming.sdk.json +23 -0
  288. package/dist/templates/starter-game/session/defaults.ts +80 -0
  289. package/dist/templates/starter-game/session/index.html +12 -0
  290. package/dist/templates/starter-game/session/session.sdk.json +11 -0
  291. package/dist/templates/starter-game/session/src/App.test.tsx +307 -0
  292. package/dist/templates/starter-game/session/src/App.tsx +309 -0
  293. package/dist/templates/starter-game/session/src/Section.tsx +37 -0
  294. package/dist/templates/starter-game/session/src/TeamDoor.tsx +96 -0
  295. package/dist/templates/starter-game/session/src/format.ts +8 -0
  296. package/dist/templates/starter-game/session/src/i18n.ts +51 -0
  297. package/dist/templates/starter-game/session/src/main.tsx +48 -0
  298. package/dist/templates/starter-game/session/src/theme.ts +45 -0
  299. package/dist/templates/starter-game/session/types.ts +135 -0
  300. package/dist/templates/starter-game/settings/engine/settings.sdk.json +50 -0
  301. package/dist/templates/starter-game/settings/menu/settings.sdk.json +78 -0
  302. package/dist/templates/starter-game/settings/session/settings.sdk.json +23 -0
  303. package/dist/templates/starter-game/settings/ui/settings.sdk.json +25 -0
  304. package/dist/templates/starter-game/test-setup.ts +7 -0
  305. package/dist/templates/starter-game/testing/e2e/game.spec.ts +139 -0
  306. package/dist/templates/starter-game/testing/e2e/global-setup.ts +21 -0
  307. package/dist/templates/starter-game/testing/engine/game.engine.test.ts +171 -0
  308. package/dist/templates/starter-game/tsconfig.json +19 -0
  309. package/dist/templates/starter-game/ui/defaults.ts +60 -0
  310. package/dist/templates/starter-game/ui/index.html +12 -0
  311. package/dist/templates/starter-game/ui/src/App.test.tsx +138 -0
  312. package/dist/templates/starter-game/ui/src/App.tsx +175 -0
  313. package/dist/templates/starter-game/ui/src/DayBar.test.tsx +78 -0
  314. package/dist/templates/starter-game/ui/src/DayBar.tsx +72 -0
  315. package/dist/templates/starter-game/ui/src/HistoryPanel.test.tsx +74 -0
  316. package/dist/templates/starter-game/ui/src/HistoryPanel.tsx +120 -0
  317. package/dist/templates/starter-game/ui/src/MakeRow.tsx +129 -0
  318. package/dist/templates/starter-game/ui/src/Section.tsx +37 -0
  319. package/dist/templates/starter-game/ui/src/ThemeToggle.tsx +49 -0
  320. package/dist/templates/starter-game/ui/src/TodayPanel.test.tsx +179 -0
  321. package/dist/templates/starter-game/ui/src/TodayPanel.tsx +92 -0
  322. package/dist/templates/starter-game/ui/src/YesterdayPanel.tsx +142 -0
  323. package/dist/templates/starter-game/ui/src/format.ts +12 -0
  324. package/dist/templates/starter-game/ui/src/i18n.ts +95 -0
  325. package/dist/templates/starter-game/ui/src/main.tsx +48 -0
  326. package/dist/templates/starter-game/ui/src/menu.ts +18 -0
  327. package/dist/templates/starter-game/ui/src/test-helpers.ts +58 -0
  328. package/dist/templates/starter-game/ui/src/theme.ts +45 -0
  329. package/dist/templates/starter-game/ui/src/writes.ts +43 -0
  330. package/dist/templates/starter-game/ui/types.ts +137 -0
  331. package/dist/templates/starter-game/ui/ui.sdk.json +11 -0
  332. package/dist/templates/starter-game/validators/capacity/defaults.ts +37 -0
  333. package/dist/templates/starter-game/validators/capacity/logic/logic.test.ts +96 -0
  334. package/dist/templates/starter-game/validators/capacity/logic/logic.ts +45 -0
  335. package/dist/templates/starter-game/validators/capacity/types.ts +55 -0
  336. package/dist/templates/starter-game/validators/capacity/validator.sdk.json +11 -0
  337. package/dist/templates/starter-game/vite.config.ts +19 -0
  338. package/dist/templates/starter-game/vitest.config.ts +15 -0
  339. package/dist/templates/starter-game/vitest.engine.config.ts +19 -0
  340. package/dist/templates/starter-game/waiting_room/defaults.ts +40 -0
  341. package/dist/templates/starter-game/waiting_room/index.html +12 -0
  342. package/dist/templates/starter-game/waiting_room/src/App.test.tsx +102 -0
  343. package/dist/templates/starter-game/waiting_room/src/App.tsx +194 -0
  344. package/dist/templates/starter-game/waiting_room/src/i18n.ts +33 -0
  345. package/dist/templates/starter-game/waiting_room/src/main.tsx +48 -0
  346. package/dist/templates/starter-game/waiting_room/src/theme.ts +45 -0
  347. package/dist/templates/starter-game/waiting_room/types.ts +59 -0
  348. package/dist/templates/starter-game/waiting_room/waiting_room.sdk.json +10 -0
  349. package/dist/templates/tick-controller/logic.ts.liquid +8 -0
  350. package/dist/templates/ui/index.html.liquid +12 -0
  351. package/dist/templates/ui/src/App.test.tsx.liquid +41 -0
  352. package/dist/templates/ui/src/App.tsx.liquid +103 -0
  353. package/dist/templates/ui/src/i18n.ts.liquid +47 -0
  354. package/dist/templates/ui/src/main.tsx.liquid +48 -0
  355. package/dist/templates/ui/src/theme.ts.liquid +46 -0
  356. package/dist/templates/validator/logic.test.ts.liquid +42 -0
  357. package/dist/templates/validator/logic.ts.liquid +21 -0
  358. package/dist/templates/waiting-room/index.html.liquid +12 -0
  359. package/dist/templates/waiting-room/src/App.test.tsx.liquid +57 -0
  360. package/dist/templates/waiting-room/src/App.tsx.liquid +118 -0
  361. package/dist/templates/waiting-room/src/i18n.ts.liquid +44 -0
  362. package/dist/templates/waiting-room/src/main.tsx.liquid +48 -0
  363. package/dist/templates/waiting-room/src/theme.ts.liquid +46 -0
  364. package/dist/templating/resource-metadata.d.ts +131 -0
  365. package/dist/types.d.ts +14 -0
  366. package/dist/utils/atomic-write.d.ts +40 -0
  367. package/dist/utils/cli-error.d.ts +64 -0
  368. package/dist/utils/document-parser-client.d.ts +20 -0
  369. package/dist/utils/env-loader.d.ts +40 -0
  370. package/dist/utils/error-codes.d.ts +335 -0
  371. package/dist/utils/errors.d.ts +19 -0
  372. package/dist/utils/global-settings.d.ts +65 -0
  373. package/dist/utils/llm-client.d.ts +34 -0
  374. package/dist/utils/logger.d.ts +36 -0
  375. package/dist/utils/output.d.ts +109 -0
  376. package/dist/utils/platform.d.ts +14 -0
  377. package/dist/utils/prompt.d.ts +36 -0
  378. package/dist/utils/real-path.d.ts +6 -0
  379. package/dist/utils/schema-properties.d.ts +35 -0
  380. package/dist/validation/ajv.d.ts +17 -0
  381. package/dist/validation/facilitator-guide-validator.d.ts +27 -0
  382. package/dist/validation/findings.d.ts +40 -0
  383. package/dist/validation/game-constraints.d.ts +10 -0
  384. package/dist/validation/game-validator.d.ts +34 -0
  385. package/dist/validation/package-validation.d.ts +41 -0
  386. package/dist/validation/package-validator.d.ts +19 -0
  387. package/dist/validation/resource-validator.d.ts +79 -0
  388. package/dist/validation/schema-defaults-validator.d.ts +29 -0
  389. package/dist/validation/schema-validation.d.ts +115 -0
  390. package/dist/validation/semver.d.ts +9 -0
  391. package/package.json +122 -3
@@ -0,0 +1,2488 @@
1
+ <!-- Generated by `pnpm docs:generate` from `sdk-tools/cli/program.ts`, the resource registry and `sdk-tools/docs-partials/cli-reference/`. Edit those, not this file. -->
2
+
3
+ # CLI reference
4
+
5
+ Every `seriousgaming-sdk` command and flag, in one place. For a worked session that strings these commands together, see [Drive a game from the CLI](./cli-simulation-testing.md).
6
+
7
+ ## Command structure
8
+
9
+ ```
10
+ pnpm run sdk [options] <command> [arguments]
11
+ ```
12
+
13
+ ## Global options
14
+
15
+ The program's own options, which work with every command:
16
+
17
+ | Option | Default | Description |
18
+ | --------------- | ------- | ----------------------- |
19
+ | `-v, --verbose` | | Enable verbose logging. |
20
+
21
+ Every command also takes `-j, --json` (see [JSON output](#json-output)). `-j, --json` and `-w, --working-dir` may come before the command as well as after it, so `sdk --json -w ./game view` is `sdk view --json -w ./game`.
22
+
23
+ ### Verbose mode
24
+
25
+ ```bash
26
+ pnpm run sdk --verbose <command>
27
+ ```
28
+
29
+ `-v, --verbose` works on every command. It is a flag only: no configuration file or environment variable turns it on.
30
+
31
+ By default the CLI prints as little as it can, which suits scripts, CI, and agents that parse its output:
32
+
33
+ - Errors and warnings always appear
34
+ - Validation reports problems only, not the checks that passed
35
+ - Progress from Vite and the TypeScript compiler is suppressed
36
+ - Type generation and configuration loading run silently
37
+ - The final summary (`✓ Build complete!`) always appears
38
+
39
+ With `--verbose` you also see:
40
+
41
+ - Each configuration file as it loads
42
+ - The validation result for every resource, including the ones that passed
43
+ - Type generation for each resource
44
+ - Each build step, with Vite and TypeScript output
45
+ - Asset copying and file generation
46
+
47
+ ```bash
48
+ pnpm run sdk build --verbose
49
+ pnpm run sdk generate --verbose
50
+ pnpm run sdk resolve --verbose
51
+ ```
52
+
53
+ A verbose build looks like this:
54
+
55
+ ```
56
+ Step 1: Loading configuration...
57
+ Loaded: capabilities/counter/capability.sdk.json
58
+ Loaded: settings/engine/settings.sdk.json
59
+ Loaded: engine/engine.sdk.json
60
+ Loaded: ui/ui.sdk.json
61
+ ...
62
+ Found 1 capabilities, an engine, 1 user interfaces, 0 settings editors, 0 tick controllers, 0 hooks, and 0 validators
63
+
64
+ Step 2: Validating...
65
+ ...
66
+ package "com.e2e" is valid
67
+ capability "counter" is valid
68
+ engine "engine" is valid
69
+ ui "ui" is valid
70
+ ...
71
+
72
+ Step 3: Generating types...
73
+ Generating types for module engine
74
+ Generating types for engine...
75
+ Generated /path/to/engine/types.ts
76
+ Generated /path/to/engine/defaults.ts
77
+ ...
78
+
79
+ ✓ Build complete!
80
+ ```
81
+
82
+ What each kind of output looks like, and when it appears:
83
+
84
+ | Output | Default | `--verbose` | Example |
85
+ | ------------------------------------ | ------- | ----------- | ---------------------------------------------------------------------------------- |
86
+ | Errors | Shown | Shown | `Engine "engine" reads capability "invalid" which is not found in configuration` |
87
+ | Warnings | Shown | Shown | `⚠️ Engine "engine" declares a read of capability "forecast", but no engine, ...` |
88
+ | Progress and debug detail | Hidden | Shown | `Loaded: capabilities/price/capability.sdk.json` |
89
+ | Per-resource success lines | Hidden | Shown | `game "com.e2e.game" is valid` |
90
+ | Vite, TypeScript, and process output | Hidden | Shown | `✓ 1176 modules transformed.` |
91
+
92
+ To diagnose a failing build, rerun it verbose and keep the log:
93
+
94
+ ```bash
95
+ pnpm run build --verbose 2>&1 | tee build.log
96
+ ```
97
+
98
+ ## Exit codes
99
+
100
+ Every command exits with one of six codes, and the code says what kind of failure it was. Scripts, CI and agents branch on it; the message is for people and may change.
101
+
102
+ | Code | Meaning | Examples |
103
+ | ---- | ------------------------- | --------------------------------------------------------------------------------------------------------- |
104
+ | 0 | Success | |
105
+ | 1 | Unexpected internal error | A bug in the CLI. Please report it |
106
+ | 2 | Usage error | An unknown flag, a bad argument, missing input, a prompt needed with no terminal to ask on |
107
+ | 3 | Invalid package | A schema or validation failure, a missing `seriousgaming.sdk.json`, a logic file that will not bundle |
108
+ | 4 | Refused by the game | A denied command, a validator rejection, a conflict, a failed tick, a superseded write, a failed endpoint |
109
+ | 5 | Environment | No session running, a port in use, a dev server already running, the platform unreachable or unauthorised |
110
+
111
+ Each failure also carries a stable error code in `UPPER_SNAKE` case, such as `MANIFEST_NOT_FOUND`, `REFUSED_VALIDATION` or `NO_SESSION`. The codes are the contract, and each belongs to one exit code. Every code, with its exit code and when it is reported, is listed in [CLI error codes](./cli-error-codes.md). The catalogue is also exported from `cli/utils/error-codes.ts` in `@seriousgaming/sdk-tools`.
112
+
113
+ A session refusal maps one-to-one onto the outcome kinds a UI sees when one of its writes is refused ([`MutationOutcome`](./api/sdk-ui.md#mutationoutcome)): `REFUSED_DENIED`, `REFUSED_VALIDATION`, `REFUSED_CONFLICT`, `REFUSED_SUPERSEDED` and `TICK_FAILED`. A failed endpoint call reports `INVALID_REQUEST`, `HANDLER_ERROR` or `INVALID_RESPONSE`, or the refusal it shares with any other write. All of these exit 4.
114
+
115
+ Without `--json`, a failure prints `Error: <message>` on stderr, followed by `Hint: <what to do>` when there is one.
116
+
117
+ ## JSON output
118
+
119
+ Pass `-j, --json` to any command, or set `SERIOUSGAMING_JSON=1` in the environment, and the command writes exactly one JSON document to stdout, whether it succeeds or fails. Progress, logs and notices go to stderr. Every document is indented with two spaces.
120
+
121
+ On success:
122
+
123
+ ```json
124
+ {
125
+ "ok": true,
126
+ "command": "tick",
127
+ "data": {
128
+ "gameId": "com.example.factory.game",
129
+ "completedTicks": 1,
130
+ "complete": false,
131
+ "context": {}
132
+ },
133
+ "diagnostics": [],
134
+ "warnings": []
135
+ }
136
+ ```
137
+
138
+ On failure:
139
+
140
+ ```json
141
+ {
142
+ "ok": false,
143
+ "command": "build",
144
+ "error": {
145
+ "code": "PACKAGE_INVALID",
146
+ "message": "Validation failed: 1 of 6 resources have errors.",
147
+ "hint": "Fix the errors listed and run the command again."
148
+ },
149
+ "diagnostics": [
150
+ {
151
+ "severity": "error",
152
+ "code": "SETTINGS_UNKNOWN_KEY",
153
+ "message": "Overrides \"BaseDemnd\", which settings \"engine\" does not declare",
154
+ "file": "scenarios/hard/scenario.sdk.json",
155
+ "pointer": "/definition/settings/engine/BaseDemnd",
156
+ "resource": { "type": "scenario", "identifier": "hard" },
157
+ "hint": "Did you mean 'BaseDemand'?"
158
+ }
159
+ ],
160
+ "warnings": []
161
+ }
162
+ ```
163
+
164
+ - `ok` says which of the two it is, as the UI protocol's `mutation:response` does
165
+ - `command` is the full command path: `tick`, `new engine`, `timer`, `package publish`
166
+ - `data` is the command's result: the session report for session commands, the change list for `new`, `update` and `delete`, the validation summary for `build --validate-only`. A command with nothing to report has `"data": null`
167
+ - `error.code` is the stable code; `error.hint` says what to do; `error.details` carries structured detail where a failure has it, such as the outcome of a refused write (with a validator's `rejections`) or a failed endpoint call (with the steps it `applied`)
168
+ - `diagnostics` lists findings about the package, errors and warnings both: on failure what failed it, on success the warnings a passing `build` found. See [Diagnostics](#diagnostics)
169
+ - `warnings` lists notices the command printed on the way that point at no file
170
+
171
+ Session commands (`init`, `tick`, `view`, `patch` and the rest) print their result as JSON even without the flag: the bare report, not the envelope.
172
+
173
+ ### Diagnostics
174
+
175
+ A diagnostic is one finding, located as precisely as the check that made it can say. Read `file` and `pointer` (or `line` and `column`) to go straight to the problem; branch on `code`, never on `message`.
176
+
177
+ | Field | Always | What it holds |
178
+ | ---------- | ------ | --------------------------------------------------------------------------------------------------------------- |
179
+ | `severity` | Yes | `error`, which fails the command, or `warning`, which does not |
180
+ | `code` | Yes | A stable code from the list below |
181
+ | `message` | Yes | What is wrong, for a person. Not part of the contract |
182
+ | `file` | No | The file it is about, relative to the package root, with forward slashes. A directory ends in `/` |
183
+ | `pointer` | No | Where in `file`, when `file` is JSON: an RFC 6901 pointer such as `/definition/reads/0`. `""` is the whole file |
184
+ | `line` | No | Where in `file`, when `file` is source code: 1-based |
185
+ | `column` | No | 1-based, with `line` |
186
+ | `resource` | No | The resource it is about: `{ "type": "scenario", "identifier": "hard" }` |
187
+ | `hint` | No | What to do about it, such as `Did you mean 'BaseDemand'?` or what replaced a removed field |
188
+ | `tick` | No | For `LOGIC_ERROR`: the tick the logic threw in |
189
+ | `team` | No | For `LOGIC_ERROR`: the team whose logic threw, `{ "teamId": "team-1", "index": 0 }` |
190
+
191
+ When game logic throws during a session command (`init`, `tick`, `endpoint call` and the rest), the command fails with the code for what it was doing (`TICK_FAILED` for a tick, `HANDLER_ERROR` for an endpoint, `SESSION_ERROR` otherwise, all exit 4), and the first diagnostic is `LOGIC_ERROR`, located in your TypeScript through the bundle's source map:
192
+
193
+ ```json
194
+ {
195
+ "severity": "error",
196
+ "code": "LOGIC_ERROR",
197
+ "message": "the second round has no stock",
198
+ "file": "engine/logic.ts",
199
+ "line": 13,
200
+ "column": 15,
201
+ "resource": { "type": "engine", "identifier": "engine" },
202
+ "tick": 2,
203
+ "team": { "teamId": "team-1", "index": 0 }
204
+ }
205
+ ```
206
+
207
+ A failure that is the environment's rather than the game's keeps its own code even when it surfaces through logic: a call to `generateText` with no platform API key configured fails the tick or endpoint call with `LLM_NOT_CONFIGURED` (exit 5), and its hint says how to configure one.
208
+
209
+ Without `--json`, each diagnostic that names a file is printed under the error as `<code> at <location>`: `LOGIC_ERROR at engine/logic.ts:13:15 (tick 2, team 1)`, or `SETTINGS_UNKNOWN_KEY at scenarios/hard/scenario.sdk.json#/definition/settings/engine/BaseDemnd`.
210
+
211
+ #### Diagnostic codes
212
+
213
+ | Code | When |
214
+ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
215
+ | `PACKAGE_INVALID` | The package fails validation. The diagnostics say where. As a diagnostic code, a finding no finer code describes yet. |
216
+ | `SCHEMA_INVALID` | A bundle or the manifest does not match the SDK's schema. The diagnostic's pointer names the value. |
217
+ | `JSON_INVALID` | A bundle file is not valid JSON. |
218
+ | `BUNDLE_MISSING` | A resource directory holds no bundle file. |
219
+ | `REMOVED_FIELD` | A bundle or the manifest carries a field the SDK no longer reads. The hint says what replaced it. |
220
+ | `LAYOUT_OUTDATED` | A directory or bundle file sits where an earlier version of the SDK kept it. The hint says where it goes now. |
221
+ | `FEATURE_UNSUPPORTED` | The manifest's `requires` names an optional feature this version of the SDK does not support. |
222
+ | `REFERENCE_UNKNOWN` | A `reads`, `writes`, `settings`, `configures` or `endpoints` entry names something the package does not declare. |
223
+ | `SETTINGS_UNKNOWN` | A scenario overrides a settings resource the package does not declare. |
224
+ | `SETTINGS_UNKNOWN_KEY` | A scenario overrides a key that its settings resource does not declare. |
225
+ | `SETTINGS_INVALID` | A scenario's override does not match its settings resource's schema. |
226
+ | `SETTINGS_RESERVED` | A settings resource is named `game`, which is reserved for the built-in game settings. |
227
+ | `SETTINGS_LOCKED` | A scenario overrides a setting the game locks with `readOnly`, in its settings schema or its `gameSettingsConstraints`. |
228
+ | `GAME_CONSTRAINT_INVALID` | A `gameSettingsConstraints` entry widens the SDK schema for the `game` settings rather than narrowing it. |
229
+ | `GAME_CONSTRAINT_VIOLATED` | The game's own value for a `game` setting breaks its `gameSettingsConstraints`. |
230
+ | `LOGIC_EXPORT_MISSING` | A logic file is missing, or does not export the functions its resource needs. |
231
+ | `LOGIC_NODE_BUILTIN` | Game logic imports a Node built-in, which is absent in production: logic runs on the JavaScript language alone. |
232
+ | `LOGIC_BUNDLE_FAILED` | A logic file does not bundle: a syntax error, or an import that does not resolve. |
233
+ | `READ_NEVER_WRITTEN` | A warning: a resource reads a capability nothing in the package writes, so it only ever holds its defaults. |
234
+ | `NO_TERMINATION` | A warning: the game declares neither `maxTicks` nor a tick controller, so only its engine can end it. |
235
+ | `EDITOR_ENDPOINT_STATEFUL` | The scenario editor lists an endpoint that declares `reads`, `writes` or `actions`, which need a session the editor does not have. |
236
+ | `EXTERNAL_REQUEST_NOT_OBJECT` | An external endpoint's `requestSchema` does not declare `"type": "object"` at the top, which a caller outside the game needs to pass named arguments. |
237
+ | `ENDPOINT_UNREQUESTED` | A warning: an internal endpoint no user interface lists in its `endpoints`. |
238
+ | `LOGIC_ERROR` | Game logic (an engine, hook, endpoint, validator or tick controller) threw. A diagnostic, not an error code: it sits in `diagnostics` under the command's TICK_FAILED, SESSION_ERROR or HANDLER_ERROR, with the file, line and column it threw at. |
239
+
240
+ ### Non-interactive use
241
+
242
+ The CLI prompts only when stdin and stdout are both terminals, JSON output is off and `CI` is unset. Anywhere else, a command that would need to ask fails with exit code 2 and `PROMPT_REQUIRED`, naming the question and how to pass the answer instead: as an argument, with `--file`, or on stdin. Input piped on stdin is read to the end, however long it takes.
243
+
244
+ `delete` asks for confirmation, and so does an interactive `update` before it writes. Pass `-y, --yes` to answer it.
245
+
246
+ `new package` fails with exit code 5 and `INSTALL_FAILED` when `pnpm install` fails in the new package. The files are written; run `pnpm install` in the package to finish.
247
+
248
+ ### Parsing JSON through pnpm
249
+
250
+ The catch is `pnpm run`, not the CLI. Before version 12, pnpm prints a banner to stdout ahead of your command:
251
+
252
+ ```
253
+ > com.example.game@0.0.1 sdk /path/to/package
254
+ > seriousgaming-sdk view
255
+ ```
256
+
257
+ That banner is not JSON, so `JSON.parse(stdout)` throws and `jq` stops at the first line. pnpm 12 sends it to stderr instead, but a script cannot count on which pnpm runs it, and the CLI cannot suppress the banner from inside. Add `--silent` to `pnpm run` whenever you parse the output:
258
+
259
+ ```bash
260
+ # Pipe straight into jq
261
+ pnpm run --silent sdk view | jq '.completedTicks'
262
+
263
+ # The envelope from a command that takes --json
264
+ pnpm run --silent sdk configure -f config.json --json | jq '.data.restarted'
265
+
266
+ # Branch on the code, not the message
267
+ pnpm run --silent sdk tick --json | jq -r 'if .ok then "ticked" else .error.code end'
268
+
269
+ # Redirect to a file other tools will parse
270
+ pnpm run --silent sdk package bundle --json > bundle.json
271
+ ```
272
+
273
+ The same flag belongs in any command a Node script runs:
274
+
275
+ ```typescript
276
+ const { stdout } = await execAsync('pnpm run --silent sdk view');
277
+ const state = JSON.parse(stdout);
278
+ ```
279
+
280
+ Calling the CLI binary directly, rather than through a `pnpm run` script, prints no banner, so `--silent` is unnecessary there.
281
+
282
+ ## Resource commands
283
+
284
+ ### Creating resources
285
+
286
+ #### Create a capability
287
+
288
+ ```text
289
+ pnpm run sdk new capability [definition] [options]
290
+ ```
291
+
292
+ Create a new capability.
293
+
294
+ | Option | Default | Description |
295
+ | -------------------------- | --------------------- | -------------------------------------------------------------------------------- |
296
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the capability in. |
297
+ | `-f, --file [PATH]` | | Path to a JSON file containing the capability definition. |
298
+ | `--scope <scope>` | | Cross-team visibility: 'team' (default), 'team-readable', 'shared' or 'private'. |
299
+
300
+ Example:
301
+
302
+ ```bash
303
+ pnpm run sdk new capability '{
304
+ "definition": {
305
+ "identifier": "inventory",
306
+ "variablesSchema": {
307
+ "type": "object",
308
+ "properties": {
309
+ "Stock": { "type": "number", "default": 100 }
310
+ },
311
+ "required": ["Stock"],
312
+ "additionalProperties": false
313
+ }
314
+ }
315
+ }'
316
+ ```
317
+
318
+ The optional `definition.scope` field sets the capability's cross-team visibility, and is written to the created `capability.sdk.json`:
319
+
320
+ - `'team'` (the default): private to each team
321
+ - `'team-readable'`: each team writes its own copy, but user interfaces on other teams may observe every team's value live in shared-state games
322
+ - `'shared'`: one value for all teams
323
+ - `'private'`: per team like `'team'`, but never delivered to any user interface, so a UI that reads or writes it fails the build
324
+
325
+ For invocations that do not pass JSON, the `--scope` flag sets any of the four:
326
+
327
+ ```bash
328
+ pnpm run sdk new capability --scope shared
329
+ ```
330
+
331
+ #### Create settings
332
+
333
+ ```text
334
+ pnpm run sdk new settings [definition] [options]
335
+ ```
336
+
337
+ Create a new settings resource.
338
+
339
+ | Option | Default | Description |
340
+ | -------------------------- | --------------------- | ---------------------------------------------------------------- |
341
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the settings resource in. |
342
+ | `-f, --file [PATH]` | | Path to a JSON file containing the settings resource definition. |
343
+
344
+ Example:
345
+
346
+ ```bash
347
+ pnpm run sdk new settings '{
348
+ "definition": {
349
+ "identifier": "demand",
350
+ "settingsSchema": {
351
+ "type": "object",
352
+ "properties": {
353
+ "BaseDemand": {
354
+ "type": "number",
355
+ "description": "Base demand per tick",
356
+ "default": 100,
357
+ "minimum": 0
358
+ }
359
+ },
360
+ "additionalProperties": false
361
+ }
362
+ }
363
+ }'
364
+ ```
365
+
366
+ Creates `settings/<identifier>/settings.sdk.json`: named, read-only parameters that any resource reads by listing its identifier in `settings`. `definition.identifier` names the directory and is stripped from the bundle that is written, so on disk the directory is the name. A package may hold any number, so `update` and `delete` take the identifier.
367
+
368
+ **Every property declares a `default`.** It is the value the game plays with unless a scenario or the session overrides it, so the command refuses a property without one, and a `settingsSchema` with a top-level `required`. With no JSON, the command prompts for the name and each property interactively.
369
+
370
+ The identifier `game` is reserved for the built-in game settings, whose values the manifest sets (see [The game settings](./guides/settings.md#the-game-settings)), so `new settings` refuses it with `SETTINGS_RESERVED`.
371
+
372
+ A settings resource does nothing until something reads it: add its identifier to the `settings` array of each resource that needs it, to the `configures` of the `setup` or `scenario_editor` slot to make it editable, and under a scenario's `settings` to override it. See the [settings guide](./guides/settings.md) and [Create settings](./recipes/create-settings.md).
373
+
374
+ #### Create the engine
375
+
376
+ ```text
377
+ pnpm run sdk new engine [definition] [options]
378
+ ```
379
+
380
+ Create a new engine.
381
+
382
+ | Option | Default | Description |
383
+ | -------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------- |
384
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the engine in. |
385
+ | `-f, --file [PATH]` | | Path to a JSON file containing the engine definition. |
386
+ | `--aggregate` | | Scaffold logic exporting InitAggregate and RunAggregate, for an engine that runs once across all teams. |
387
+
388
+ Example:
389
+
390
+ ```bash
391
+ pnpm run sdk new engine '{
392
+ "definition": {
393
+ "writes": ["inventory"],
394
+ "uses": ["generateText"],
395
+ "settings": ["demand"]
396
+ },
397
+ "logic": {
398
+ "directory": ".",
399
+ "entrypoint": "logic.ts"
400
+ },
401
+ "rootDirectory": "."
402
+ }'
403
+ ```
404
+
405
+ The optional `definition.uses` field lists the runtime callbacks the engine may invoke: `'generateText'`, `'extractText'`, `'generateSpeech'`, and `'writeBlob'`. It defaults to `[]`. Each one listed is present on the engine's generated `Callbacks`, so logic calls it without a `?.` check, and a call to an undeclared callback throws `UndeclaredApiUseError` at runtime. `new hook`, `new tick_controller`, and `new endpoint` accept the same field, as do the matching `update` commands.
406
+
407
+ User interfaces have no `uses` field. `new ui`, `new session`, `new waiting_room`, `new presentation`, `new setup`, and `new scenario_editor` refuse one, and so does `pnpm run sdk build`: a user interface reaches an external service through an endpoint instead. See [Declare API uses](./recipes/declare-api-uses.md).
408
+
409
+ The optional `definition.settings` field lists the settings resources the engine reads, by identifier, exactly as `reads` lists capabilities. Its logic receives exactly those as `settings`, keyed by identifier (`settings.demand.BaseDemand`), and an identifier the package does not declare fails the build. The UIs, the tick controller, hooks, validators, and endpoints accept the same field, as do the matching `update` commands. With no JSON, `new` offers the settings resources already on disk to choose from.
410
+
411
+ The optional `--aggregate` flag scaffolds an engine that runs once across all teams rather than per team: its logic exports `Init`, `InitAggregate`, and `RunAggregate` instead of `Init` and `Run`. `Init` is optional in either shape: an engine without one starts from its capabilities' defaults, but the scaffold writes one to fill in. Those exports are what make the engine aggregate, so nothing is written into `engine.sdk.json`, and an authored `definition.aggregate` is rejected. See the [multi-team games guide](./guides/multi-team-games.md).
412
+
413
+ #### Create the game UI
414
+
415
+ ```text
416
+ pnpm run sdk new ui [definition] [options]
417
+ ```
418
+
419
+ Create a new user interface.
420
+
421
+ | Option | Default | Description |
422
+ | -------------------------- | --------------------- | ------------------------------------------------------------- |
423
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the user interface in. |
424
+ | `-f, --file [PATH]` | | Path to a JSON file containing the user interface definition. |
425
+
426
+ Example:
427
+
428
+ ```bash
429
+ pnpm run sdk new ui '{
430
+ "definition": {
431
+ "reads": ["revenue"],
432
+ "writes": ["price"],
433
+ "settings": ["display"]
434
+ },
435
+ "rootDirectory": ".",
436
+ "ui": {
437
+ "entrypoint": "index.html"
438
+ }
439
+ }'
440
+ ```
441
+
442
+ `definition.reads` and `definition.writes` are arrays of capability identifiers, and a UI's `data` carries exactly the union of the two. `definition.settings` lists the settings resources it reads, which arrive as `settings` on `useUserInterfaceData()`, keyed by identifier. A value participants must not see, such as an answer key, belongs in a settings resource the UI does not list.
443
+
444
+ The optional `definition.crossTeamReads` narrows `reads` to the capabilities for which every team's value arrives, live. Each entry must also appear in `reads` and must name a capability with `scope: 'team-readable'`, in a shared-state game. Only `new ui` and `new waiting_room` accept it, and both write it straight into the created bundle. See [Observe other teams' state](./recipes/observe-other-teams-state.md).
445
+
446
+ #### Create a scenario
447
+
448
+ ```text
449
+ pnpm run sdk new scenario [definition] [options]
450
+ ```
451
+
452
+ Create a new scenario.
453
+
454
+ | Option | Default | Description |
455
+ | -------------------------- | --------------------- | ------------------------------------------------------- |
456
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the scenario in. |
457
+ | `-f, --file [PATH]` | | Path to a JSON file containing the scenario definition. |
458
+
459
+ Example:
460
+
461
+ ```bash
462
+ pnpm run sdk new scenario '{
463
+ "definition": {
464
+ "identifier": "ai_influence",
465
+ "locales": {
466
+ "en": {
467
+ "name": "AI Influence",
468
+ "description": "AI-driven market disruption"
469
+ }
470
+ },
471
+ "settings": {
472
+ "demand": { "BaseDemand": 50 }
473
+ }
474
+ },
475
+ "rootDirectory": "."
476
+ }'
477
+ ```
478
+
479
+ Creates `scenarios/<identifier>/scenario.sdk.json` and `scenarios/<identifier>/assets/shared/`. The directory is the declaration: nothing registers it. `settings` is keyed by settings identifier, and each entry is a partial override of that settings resource's defaults.
480
+
481
+ #### Create the facilitator guide
482
+
483
+ ```text
484
+ pnpm run sdk new guide [definition] [options]
485
+ ```
486
+
487
+ Create a new facilitator guide.
488
+
489
+ | Option | Default | Description |
490
+ | -------------------------- | --------------------- | ---------------------------------------------------------------- |
491
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the facilitator guide in. |
492
+ | `-f, --file [PATH]` | | Path to a JSON file containing the facilitator guide definition. |
493
+
494
+ Example:
495
+
496
+ ```bash
497
+ pnpm run sdk new guide '{
498
+ "definition": {
499
+ "locales": {
500
+ "en": { "name": "Facilitator guide" },
501
+ "de": { "name": "Moderationsleitfaden" }
502
+ }
503
+ },
504
+ "rootDirectory": "."
505
+ }'
506
+ ```
507
+
508
+ Creates `guide/guide.sdk.json`, an `assets/` directory, and a recommended chapter skeleton under `content/<locale>/` for every declared locale. The per-locale `name` is the guide's title. See [Facilitator guides](./guides/guide.md).
509
+
510
+ #### Create the session UI
511
+
512
+ ```text
513
+ pnpm run sdk new session [definition] [options]
514
+ ```
515
+
516
+ Create a new session user interface.
517
+
518
+ | Option | Default | Description |
519
+ | -------------------------- | --------------------- | --------------------------------------------------------------------- |
520
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the session user interface in. |
521
+ | `-f, --file [PATH]` | | Path to a JSON file containing the session user interface definition. |
522
+
523
+ Example:
524
+
525
+ ```bash
526
+ pnpm run sdk new session '{
527
+ "definition": {
528
+ "reads": ["price", "workers"],
529
+ "writes": ["price", "workers"]
530
+ },
531
+ "rootDirectory": ".",
532
+ "ui": {
533
+ "entrypoint": "index.html"
534
+ }
535
+ }'
536
+ ```
537
+
538
+ The session UI gives the facilitator cross-team, session-level control. See the [session UIs guide](./guides/session.md).
539
+
540
+ #### Create the waiting room UI
541
+
542
+ ```text
543
+ pnpm run sdk new waiting_room [definition] [options]
544
+ ```
545
+
546
+ Create a new waiting room user interface.
547
+
548
+ | Option | Default | Description |
549
+ | -------------------------- | --------------------- | -------------------------------------------------------------------------- |
550
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the waiting room user interface in. |
551
+ | `-f, --file [PATH]` | | Path to a JSON file containing the waiting room user interface definition. |
552
+
553
+ Example:
554
+
555
+ ```bash
556
+ pnpm run sdk new waiting_room '{
557
+ "definition": {
558
+ "reads": ["briefing"]
559
+ },
560
+ "rootDirectory": ".",
561
+ "ui": {
562
+ "entrypoint": "index.html"
563
+ }
564
+ }'
565
+ ```
566
+
567
+ The holding screen participants see instead of the game UIs while the facilitator has the session's waiting room open. It is a game UI in a different display context: the same `reads`, `writes`, `endpoints`, hooks, and callbacks are available, and it renders under `UserInterfaceWrapper`. It lives at the `waiting_room/` slot, so nothing references it.
568
+
569
+ The manifest's `waitingRoom` block carries one setting, `openOnStart`, so `"openOnStart": true` there makes every session begin with the room open. See the [waiting room UIs guide](./guides/waiting-room.md).
570
+
571
+ #### Create the presentation
572
+
573
+ ```text
574
+ pnpm run sdk new presentation [definition] [options]
575
+ ```
576
+
577
+ Create a new presentation.
578
+
579
+ | Option | Default | Description |
580
+ | -------------------------- | --------------------- | ----------------------------------------------------------- |
581
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the presentation in. |
582
+ | `-f, --file [PATH]` | | Path to a JSON file containing the presentation definition. |
583
+
584
+ Example:
585
+
586
+ ```bash
587
+ pnpm run sdk new presentation '{
588
+ "definition": {
589
+ "reads": ["results"],
590
+ "settings": ["engine"]
591
+ },
592
+ "rootDirectory": ".",
593
+ "ui": {
594
+ "entrypoint": "index.html"
595
+ }
596
+ }'
597
+ ```
598
+
599
+ The facilitator's slide deck. It reads every team's state through `reads` and its settings through `settings`, and accepts no `writes`, `uses` or `endpoints`. It renders under `PresentationWrapper`, with or without a session, and the starter deck it scaffolds is built with `@seriousgaming/deck`, which the package must list at the SDK's version. It lives at the `presentation/` slot, so nothing references it. See the [presentations guide](./guides/presentation.md).
600
+
601
+ #### Create the setup screen
602
+
603
+ ```text
604
+ pnpm run sdk new setup [definition] [options]
605
+ ```
606
+
607
+ Create a new setup user interface.
608
+
609
+ | Option | Default | Description |
610
+ | -------------------------- | --------------------- | ------------------------------------------------------------------- |
611
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the setup user interface in. |
612
+ | `-f, --file [PATH]` | | Path to a JSON file containing the setup user interface definition. |
613
+
614
+ Example:
615
+
616
+ ```bash
617
+ pnpm run sdk new setup '{
618
+ "definition": {
619
+ "configures": ["demand"]
620
+ },
621
+ "rootDirectory": ".",
622
+ "ui": {
623
+ "entrypoint": "index.html"
624
+ }
625
+ }'
626
+ ```
627
+
628
+ The short form a facilitator fills in at session creation. `configures` lists the settings resources it edits, by identifier, and what it writes lands in that session's overrides only. A game with a setup screen opens at its configuration step and waits for Play. It renders under `SettingsEditorWrapper` and may send `requestPlay`. It lives at the `setup/` slot, so nothing references it. See the [setup guide](./guides/setup.md).
629
+
630
+ #### Create the scenario editor
631
+
632
+ ```text
633
+ pnpm run sdk new scenario_editor [definition] [options]
634
+ ```
635
+
636
+ Create a new scenario editor.
637
+
638
+ | Option | Default | Description |
639
+ | -------------------------- | --------------------- | -------------------------------------------------------------- |
640
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the scenario editor in. |
641
+ | `-f, --file [PATH]` | | Path to a JSON file containing the scenario editor definition. |
642
+
643
+ Example:
644
+
645
+ ```bash
646
+ pnpm run sdk new scenario_editor '{
647
+ "definition": {
648
+ "configures": ["demand"]
649
+ },
650
+ "rootDirectory": ".",
651
+ "ui": {
652
+ "entrypoint": "index.html"
653
+ }
654
+ }'
655
+ ```
656
+
657
+ The screen for authoring lasting scenarios, at any time and with no session. `configures` lists the settings resources it edits, by identifier. It renders under `SettingsEditorWrapper`, the host offers save-as-scenario from it (never from setup), and the host ignores `requestPlay` from it. It lives at the `scenario_editor/` slot, so nothing references it. It is optional: `customScenarios.supported` is what switches custom scenarios on. See the [scenario editor guide](./guides/scenario-editor.md).
658
+
659
+ #### Create the tick controller
660
+
661
+ ```text
662
+ pnpm run sdk new tick_controller [definition] [options]
663
+ ```
664
+
665
+ Create a new tick controller.
666
+
667
+ | Option | Default | Description |
668
+ | -------------------------- | --------------------- | -------------------------------------------------------------- |
669
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the tick controller in. |
670
+ | `-f, --file [PATH]` | | Path to a JSON file containing the tick controller definition. |
671
+
672
+ Minimal example:
673
+
674
+ ```bash
675
+ pnpm run sdk new tick_controller '{
676
+ "definition": {},
677
+ "logic": {
678
+ "directory": ".",
679
+ "entrypoint": "logic.ts"
680
+ },
681
+ "rootDirectory": "."
682
+ }'
683
+ ```
684
+
685
+ With capability reads and settings:
686
+
687
+ ```bash
688
+ pnpm run sdk new tick_controller '{
689
+ "definition": {
690
+ "reads": ["progress"],
691
+ "settings": ["timing"]
692
+ },
693
+ "logic": {
694
+ "directory": ".",
695
+ "entrypoint": "logic.ts"
696
+ },
697
+ "rootDirectory": "."
698
+ }'
699
+ ```
700
+
701
+ #### Create a hook
702
+
703
+ ```text
704
+ pnpm run sdk new hook [definition] [options]
705
+ ```
706
+
707
+ Create a new hook.
708
+
709
+ | Option | Default | Description |
710
+ | -------------------------- | --------------------- | --------------------------------------------------- |
711
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the hook in. |
712
+ | `-f, --file [PATH]` | | Path to a JSON file containing the hook definition. |
713
+
714
+ A hook is a single-signal handler that fires on an out-of-tick lifecycle event. The available signals are `participantJoin` and `participantMove`. See the [hooks guide](./guides/hooks.md).
715
+
716
+ Minimal example:
717
+
718
+ ```bash
719
+ pnpm run sdk new hook '{
720
+ "definition": {
721
+ "identifier": "assignroles",
722
+ "signal": "participantJoin"
723
+ },
724
+ "logic": {
725
+ "directory": "logic",
726
+ "entrypoint": "logic.ts"
727
+ },
728
+ "rootDirectory": "."
729
+ }'
730
+ ```
731
+
732
+ With capability writes and settings:
733
+
734
+ ```bash
735
+ pnpm run sdk new hook '{
736
+ "definition": {
737
+ "identifier": "assignroles",
738
+ "signal": "participantJoin",
739
+ "writes": ["roles"],
740
+ "settings": ["roles"]
741
+ },
742
+ "logic": {
743
+ "directory": "logic",
744
+ "entrypoint": "logic.ts"
745
+ },
746
+ "rootDirectory": "."
747
+ }'
748
+ ```
749
+
750
+ #### Create a validator
751
+
752
+ ```text
753
+ pnpm run sdk new validator [definition] [options]
754
+ ```
755
+
756
+ Create a new validator.
757
+
758
+ | Option | Default | Description |
759
+ | -------------------------- | --------------------- | -------------------------------------------------------- |
760
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the validator in. |
761
+ | `-f, --file [PATH]` | | Path to a JSON file containing the validator definition. |
762
+
763
+ A validator is a standalone, read-only check that gates UI-submitted state mutations before they are applied. It can only reject, and never modifies state. See the [validators guide](./guides/validators.md).
764
+
765
+ Minimal example:
766
+
767
+ ```bash
768
+ pnpm run sdk new validator '{
769
+ "definition": {
770
+ "identifier": "decisionlimit"
771
+ },
772
+ "logic": {
773
+ "directory": "logic",
774
+ "entrypoint": "logic.ts"
775
+ },
776
+ "rootDirectory": "."
777
+ }'
778
+ ```
779
+
780
+ With capability reads and settings:
781
+
782
+ ```bash
783
+ pnpm run sdk new validator '{
784
+ "definition": {
785
+ "identifier": "decisionlimit",
786
+ "reads": ["decisions"],
787
+ "settings": ["limits"]
788
+ },
789
+ "logic": {
790
+ "directory": "logic",
791
+ "entrypoint": "logic.ts"
792
+ },
793
+ "rootDirectory": "."
794
+ }'
795
+ ```
796
+
797
+ #### Create an endpoint
798
+
799
+ ```text
800
+ pnpm run sdk new endpoint [definition] [options]
801
+ ```
802
+
803
+ Create a new endpoint.
804
+
805
+ | Option | Default | Description |
806
+ | -------------------------- | --------------------- | ------------------------------------------------------- |
807
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the endpoint in. |
808
+ | `-f, --file [PATH]` | | Path to a JSON file containing the endpoint definition. |
809
+
810
+ Scaffolds `endpoints/<identifier>/endpoint.sdk.json` and `endpoints/<identifier>/logic/logic.ts`, and regenerates types.
811
+
812
+ ```bash
813
+ pnpm run sdk new endpoint # interactive
814
+ pnpm run sdk new endpoint '<json definition>' # from JSON argument
815
+ pnpm run sdk new endpoint --file endpoint.json # from JSON file
816
+ ```
817
+
818
+ Example:
819
+
820
+ ```bash
821
+ pnpm run sdk new endpoint '{
822
+ "definition": {
823
+ "identifier": "summarise",
824
+ "requestSchema": {
825
+ "type": "object",
826
+ "properties": { "text": { "type": "string" } },
827
+ "required": ["text"],
828
+ "additionalProperties": false
829
+ },
830
+ "responseSchema": {
831
+ "type": "object",
832
+ "properties": { "summary": { "type": "string" } },
833
+ "required": ["summary"],
834
+ "additionalProperties": false
835
+ },
836
+ "uses": ["generateText"],
837
+ "settings": ["summaries"]
838
+ },
839
+ "logic": {
840
+ "directory": "logic",
841
+ "entrypoint": "logic.ts"
842
+ },
843
+ "rootDirectory": "."
844
+ }'
845
+ ```
846
+
847
+ An endpoint's `Handle` receives the settings resources it lists in `settings` as `settings`, keyed by identifier, as every other logic function does.
848
+
849
+ Declare the endpoint on every UI that may call it by adding the identifier to the UI bundle's `endpoints` array:
850
+
851
+ ```json
852
+ {
853
+ "definition": {
854
+ "endpoints": ["summarise"]
855
+ }
856
+ }
857
+ ```
858
+
859
+ ### Notes on resource creation
860
+
861
+ **The `rootDirectory` field.** Most `new` commands require `rootDirectory` in the root object, not inside `definition`, and it is usually `"."`. `new capability` and `new settings` are the exceptions: their bundles carry `definition` alone, and a `rootDirectory` is rejected.
862
+
863
+ - **Engine and tick controller**: `{ "definition": {...}, "logic": {...}, "rootDirectory": "." }`
864
+ - **User interfaces, including setup and the scenario editor**: `{ "definition": {...}, "rootDirectory": ".", "ui": { "entrypoint": "index.html" } }`
865
+
866
+ **Identifiers.** For the nine slot kinds (`engine`, `ui`, `session`, `waiting_room`, `presentation`, `setup`, `scenario_editor`, `tick_controller`, and `guide`) any authored identifier is ignored, since the slot names the resource. For the plural kinds (`capability`, `settings`, `endpoint`, `hook`, `validator`, and `scenario`) `definition.identifier` is required: it names the directory to create, and is stripped from the bundle that is written.
867
+
868
+ **Nothing to wire.** A package declares whatever sits under the conventional directories, and a game uses whatever its package declares, so the directory `new` creates is the whole of the wiring.
869
+
870
+ ### Updating resources
871
+
872
+ Every resource type has an `update` command, and so does the package:
873
+
874
+ | Command | What it does | Options |
875
+ | ------------------------------------------------------- | ------------------------------------- | --------------------------------------------- |
876
+ | `update package [definition] [options]` | Update a package. | `--working-dir`, `--file`, `--patch`, `--yes` |
877
+ | `update capability <identifier> [definition] [options]` | Update a capability. | `--working-dir`, `--file`, `--patch`, `--yes` |
878
+ | `update settings <identifier> [definition] [options]` | Update a settings resource. | `--working-dir`, `--file`, `--patch`, `--yes` |
879
+ | `update engine [definition] [options]` | Update an engine. | `--working-dir`, `--file`, `--patch`, `--yes` |
880
+ | `update ui [definition] [options]` | Update a user interface. | `--working-dir`, `--file`, `--patch`, `--yes` |
881
+ | `update setup [definition] [options]` | Update a setup user interface. | `--working-dir`, `--file`, `--patch`, `--yes` |
882
+ | `update scenario_editor [definition] [options]` | Update a scenario editor. | `--working-dir`, `--file`, `--patch`, `--yes` |
883
+ | `update tick_controller [definition] [options]` | Update a tick controller. | `--working-dir`, `--file`, `--patch`, `--yes` |
884
+ | `update session [definition] [options]` | Update a session user interface. | `--working-dir`, `--file`, `--patch`, `--yes` |
885
+ | `update waiting_room [definition] [options]` | Update a waiting room user interface. | `--working-dir`, `--file`, `--patch`, `--yes` |
886
+ | `update presentation [definition] [options]` | Update a presentation. | `--working-dir`, `--file`, `--patch`, `--yes` |
887
+ | `update scenario <identifier> [definition] [options]` | Update a scenario. | `--working-dir`, `--file`, `--patch`, `--yes` |
888
+ | `update guide [definition] [options]` | Update a facilitator guide. | `--working-dir`, `--file`, `--patch`, `--yes` |
889
+ | `update hook <identifier> [definition] [options]` | Update a hook. | `--working-dir`, `--file`, `--patch`, `--yes` |
890
+ | `update validator <identifier> [definition] [options]` | Update a validator. | `--working-dir`, `--file`, `--patch`, `--yes` |
891
+ | `update endpoint <identifier> [definition] [options]` | Update an endpoint. | `--working-dir`, `--file`, `--patch`, `--yes` |
892
+
893
+ Their options, as `update capability` takes them (the table above shows which commands take each):
894
+
895
+ | Option | Default | Description |
896
+ | -------------------------- | --------------------- | ---------------------------------------------------------- |
897
+ | `-w, --working-dir [PATH]` | the current directory | The working directory containing the package. |
898
+ | `-f, --file [PATH]` | | Path to a JSON file containing the update definition. |
899
+ | `-p, --patch [PATCH]` | | JSON Patch (RFC 6902) array for partial updates. |
900
+ | `-y, --yes` | | Answer yes to the confirmation an interactive update asks. |
901
+
902
+ #### Full replacement
903
+
904
+ Replace a resource's whole definition:
905
+
906
+ ```bash
907
+ pnpm run sdk update <type> <identifier> '<JSON>'
908
+ ```
909
+
910
+ The nine slot kinds take the slot as their identifier, so `update` and `delete` take no identifier for them:
911
+
912
+ ```bash
913
+ pnpm run sdk update engine '<JSON>'
914
+ ```
915
+
916
+ Examples:
917
+
918
+ ```bash
919
+ pnpm run sdk update capability inventory '{...}'
920
+ pnpm run sdk update settings demand '{...}'
921
+ pnpm run sdk update engine '{...}'
922
+ pnpm run sdk update session '{...}'
923
+ pnpm run sdk update waiting_room '{...}'
924
+ pnpm run sdk update presentation '{...}'
925
+ pnpm run sdk update setup '{...}'
926
+ pnpm run sdk update scenario_editor '{...}'
927
+ pnpm run sdk update tick_controller '{...}'
928
+ pnpm run sdk update hook assignroles '{...}'
929
+ pnpm run sdk update validator decisionlimit '{...}'
930
+ pnpm run sdk update scenario ai_influence '{...}'
931
+ pnpm run sdk update guide '{...}'
932
+ pnpm run sdk update endpoint report '{...}'
933
+ ```
934
+
935
+ #### Partial update with JSON Patch
936
+
937
+ Change part of a resource with an [RFC 6902 JSON Patch](https://datatracker.ietf.org/doc/html/rfc6902):
938
+
939
+ ```bash
940
+ pnpm run sdk update <type> <identifier> --patch '<JSON-PATCH-ARRAY>'
941
+ ```
942
+
943
+ The CLI loads the existing `.sdk.json` file, applies the patch, validates the result, and writes it back. The supported operations are `add`, `remove`, `replace`, `move`, `copy`, and `test`.
944
+
945
+ ```bash
946
+ # Add a write to an engine
947
+ pnpm run sdk update engine --patch '[
948
+ {"op": "add", "path": "/definition/writes/-", "value": "forecast"}
949
+ ]'
950
+
951
+ # Change the default value of a capability variable
952
+ pnpm run sdk update capability inventory --patch '[
953
+ {"op": "replace", "path": "/definition/variablesSchema/properties/Stock/default", "value": 200}
954
+ ]'
955
+
956
+ # Change the default of a setting
957
+ pnpm run sdk update settings demand --patch '[
958
+ {"op": "replace", "path": "/definition/settingsSchema/properties/BaseDemand/default", "value": 150}
959
+ ]'
960
+
961
+ # Remove a read from an engine (by index)
962
+ pnpm run sdk update engine --patch '[
963
+ {"op": "remove", "path": "/definition/reads/1"}
964
+ ]'
965
+
966
+ # Update the tick count. A package is a game, so update package takes
967
+ # no identifier, and its patch paths start at the manifest rather than a bundle
968
+ pnpm run sdk update package --patch '[
969
+ {"op": "replace", "path": "/maxTicks", "value": 20}
970
+ ]'
971
+ ```
972
+
973
+ **Renaming settings.** A full replacement of settings whose `definition.identifier` differs from the one named on the command line renames them, and re-keys every reference: each resource's `settings`, the `configures` of the setup and scenario editor slots, and each scenario's `settings`. The interactive `update settings` offers the same rename, and a new, changed, or removed property in the same pass. Every property must still declare a `default`:
974
+
975
+ ```bash
976
+ pnpm run sdk update settings demand '{
977
+ "definition": {
978
+ "identifier": "demand",
979
+ "settingsSchema": {
980
+ "type": "object",
981
+ "properties": {
982
+ "BaseDemand": {
983
+ "type": "number",
984
+ "description": "Base demand per tick",
985
+ "default": 150,
986
+ "minimum": 0
987
+ },
988
+ "Elasticity": {
989
+ "type": "number",
990
+ "description": "How strongly demand responds to price",
991
+ "default": 1.2
992
+ }
993
+ },
994
+ "additionalProperties": false
995
+ }
996
+ }
997
+ }'
998
+ ```
999
+
1000
+ **Patch target.** Paths address the whole `.sdk.json` file as it sits on disk: `/definition/writes/-` for an engine's writes array, or `/logic/entrypoint` for the logic file path. `update package` patches `seriousgaming.sdk.json` itself, so its paths start at the top level.
1001
+
1002
+ **Missing parent paths.** RFC 6902 `add` creates only the final path segment, so every parent must already exist. Resource templates may omit empty arrays (an engine created with `reads: []` may have no `reads` key on disk). To add to a field that may not exist, create it first:
1003
+
1004
+ ```bash
1005
+ # Two-op approach: create the array, then append
1006
+ pnpm run sdk update engine --patch '[
1007
+ {"op": "add", "path": "/definition/reads", "value": []},
1008
+ {"op": "add", "path": "/definition/reads/-", "value": "x"}
1009
+ ]'
1010
+
1011
+ # Or set the whole array in one operation
1012
+ pnpm run sdk update engine --patch '[
1013
+ {"op": "add", "path": "/definition/reads", "value": ["x"]}
1014
+ ]'
1015
+ ```
1016
+
1017
+ **Input modes.** When `--patch` is present, it takes priority over a positional JSON argument and stdin. `--file` supplies a full definition rather than a patch array, so a patch passed that way fails validation.
1018
+
1019
+ #### Update the package
1020
+
1021
+ ```bash
1022
+ pnpm run sdk update package '<JSON>'
1023
+ pnpm run sdk update package --patch '<JSON Patch>'
1024
+ ```
1025
+
1026
+ A package is a game, so this one command replaces or patches `seriousgaming.sdk.json`, which carries the package identifier and the game's own fields (locales, `maxTicks`, `teams`, and so on). There is no `new game` and no `delete game`. The game's own identifier is the package identifier with `.game` appended, and it uses whatever the package declares. The command never touches `package.json`, which owns the npm name, description and version.
1027
+
1028
+ A full replacement replaces the whole manifest, so any setting the JSON omits (`customScenarios` and `scenarioFormat` included) is dropped. The manifest has no `configuration` block: a game's values are its settings resources' defaults, so change those with `update settings`, and the build refuses a manifest that still carries one. Restate every setting you want to keep, or use `--patch` to change one path at a time.
1029
+
1030
+ ```bash
1031
+ pnpm run sdk update package '{
1032
+ "identifier": "com.example.mygame",
1033
+ "supportedLocales": ["en"],
1034
+ "locales": { "en": { "name": "My Game" } },
1035
+ "maxTicks": 10,
1036
+ "teams": { "mode": "shared-state", "min": 2 }
1037
+ }'
1038
+ ```
1039
+
1040
+ `maxTicks` is optional when something else ends the game: the engine returning `complete: true` from `Run`, or a tick controller returning `complete: true`. A game with neither `maxTicks` nor a tick controller still builds, with a warning, because only the engine can end it and the build cannot see whether it ever does. See [Goal-based games](./guides/games.md#goal-based-games).
1041
+
1042
+ ### Deleting resources
1043
+
1044
+ | Command | What it does | Options |
1045
+ | ------------------------------------------ | ------------------------------------- | ------------------------ |
1046
+ | `delete capability <identifier> [options]` | Delete a capability. | `--working-dir`, `--yes` |
1047
+ | `delete settings <identifier> [options]` | Delete a settings resource. | `--working-dir`, `--yes` |
1048
+ | `delete engine [options]` | Delete an engine. | `--working-dir`, `--yes` |
1049
+ | `delete ui [options]` | Delete a user interface. | `--working-dir`, `--yes` |
1050
+ | `delete setup [options]` | Delete a setup user interface. | `--working-dir`, `--yes` |
1051
+ | `delete scenario_editor [options]` | Delete a scenario editor. | `--working-dir`, `--yes` |
1052
+ | `delete tick_controller [options]` | Delete a tick controller. | `--working-dir`, `--yes` |
1053
+ | `delete session [options]` | Delete a session user interface. | `--working-dir`, `--yes` |
1054
+ | `delete waiting_room [options]` | Delete a waiting room user interface. | `--working-dir`, `--yes` |
1055
+ | `delete presentation [options]` | Delete a presentation. | `--working-dir`, `--yes` |
1056
+ | `delete scenario <identifier> [options]` | Delete a scenario. | `--working-dir`, `--yes` |
1057
+ | `delete guide [options]` | Delete a facilitator guide. | `--working-dir`, `--yes` |
1058
+ | `delete hook <identifier> [options]` | Delete a hook. | `--working-dir`, `--yes` |
1059
+ | `delete validator <identifier> [options]` | Delete a validator. | `--working-dir`, `--yes` |
1060
+ | `delete endpoint <identifier> [options]` | Delete an endpoint. | `--working-dir`, `--yes` |
1061
+
1062
+ Their options, as `delete capability` takes them:
1063
+
1064
+ | Option | Default | Description |
1065
+ | -------------------------- | --------------------- | ---------------------------------------------------------- |
1066
+ | `-w, --working-dir [PATH]` | the current directory | The working directory containing the package. |
1067
+ | `-y, --yes` | | Answer yes to the confirmation, so it runs without asking. |
1068
+
1069
+ A slot resource takes no identifier, since the slot is its identifier:
1070
+
1071
+ ```bash
1072
+ pnpm run sdk delete <type> <identifier>
1073
+ pnpm run sdk delete <slot-type>
1074
+ ```
1075
+
1076
+ Examples:
1077
+
1078
+ ```bash
1079
+ pnpm run sdk delete capability inventory
1080
+ pnpm run sdk delete settings demand
1081
+ pnpm run sdk delete engine
1082
+ pnpm run sdk delete session
1083
+ pnpm run sdk delete waiting_room
1084
+ pnpm run sdk delete presentation
1085
+ pnpm run sdk delete setup
1086
+ pnpm run sdk delete scenario_editor
1087
+ pnpm run sdk delete tick_controller
1088
+ pnpm run sdk delete hook assignroles
1089
+ pnpm run sdk delete validator decisionlimit
1090
+ pnpm run sdk delete endpoint summarise
1091
+ pnpm run sdk delete scenario ai_influence
1092
+ pnpm run sdk delete guide
1093
+ ```
1094
+
1095
+ Each `delete` asks for confirmation first. Pass `-y, --yes` to answer it, as a script, CI or an agent must: without a terminal to ask on, a `delete` without `--yes` fails with `PROMPT_REQUIRED` (exit 2) rather than waiting.
1096
+
1097
+ `delete settings` removes the settings directory and every reference to it: from each resource's `settings`, the `configures` of the setup and scenario editor slots, and each scenario's `settings`. It lists those references before asking you to confirm.
1098
+
1099
+ ### JSON output
1100
+
1101
+ With `--json`, the `data` of `new`, `update`, and `delete` lists what changed, the same shape for every resource type. Paths are relative to the package root, with forward slashes:
1102
+
1103
+ ```bash
1104
+ pnpm run --silent sdk update settings demand -- \
1105
+ --patch '[{"op":"replace","path":"/definition/settingsSchema/properties/BaseDemand/default","value":150}]' \
1106
+ --json
1107
+ ```
1108
+
1109
+ ```json
1110
+ {
1111
+ "ok": true,
1112
+ "command": "update settings",
1113
+ "data": {
1114
+ "resourceType": "settings",
1115
+ "identifier": "demand",
1116
+ "filesCreated": [],
1117
+ "filesUpdated": ["settings/demand/settings.sdk.json"],
1118
+ "filesDeleted": [],
1119
+ "referencesUpdated": [],
1120
+ "referencesRemoved": [],
1121
+ "directoriesRenamed": []
1122
+ },
1123
+ "warnings": []
1124
+ }
1125
+ ```
1126
+
1127
+ A failure is the failure envelope, with a code and exit code 2 for a resource that does not exist:
1128
+
1129
+ ```json
1130
+ {
1131
+ "ok": false,
1132
+ "command": "update capability",
1133
+ "error": { "code": "RESOURCE_NOT_FOUND", "message": "Capability \"missing\" not found" },
1134
+ "diagnostics": [],
1135
+ "warnings": []
1136
+ }
1137
+ ```
1138
+
1139
+ Prompts cannot produce JSON, so under `--json`:
1140
+
1141
+ - `new` and `update` need a JSON definition (as an argument, via `--file`, or on stdin) or a `--patch`, since without one they would fall through to interactive prompts and fail with `PROMPT_REQUIRED`
1142
+ - `delete` needs `--yes`, which answers the confirmation prompt
1143
+
1144
+ ### Supplying JSON
1145
+
1146
+ - **Inline**: wrap the JSON in single quotes and use double quotes inside, as every example on this page does
1147
+ - **From a file**: `--file definition.json`, or redirect it on stdin with `pnpm run sdk new capability < definition.json`
1148
+
1149
+ ### Identifier naming
1150
+
1151
+ A package is named globally, so it keeps a reverse-DNS identifier. A resource is named by its directory inside the package, so it is a single short name:
1152
+
1153
+ ```
1154
+ ✅ com.mycompany.retailgame package
1155
+ ✅ inventory resource
1156
+ ❌ com.mycompany.game.inventory resource, not a short name
1157
+ ❌ Inventory resource, not lowercase
1158
+ ```
1159
+
1160
+ A resource name is lowercase letters, digits, and underscores, starting with a letter. Its game's identifier is the package identifier with `.game` appended.
1161
+
1162
+ ## Session commands
1163
+
1164
+ These commands play the game. None takes a game identifier: a package is a game, so there is nothing to name.
1165
+
1166
+ ### Where the session lives
1167
+
1168
+ The dev server owns the session in memory and is the only thing that runs your game's logic. A session command reaches it rather than keeping a copy of its own: `sdk tick` advances the session a browser is watching, and a tick taken in the browser is what the next `sdk view` reports.
1169
+
1170
+ `.seriousgaming-sdk/session.json` is how a command finds it: a lock file, written once at startup and removed at shutdown, carrying the server's pid, port and instance id, the machine it runs on, the version and install path of the tools that started it, and whether a command started it in the background. Session state itself is never written to disk. Set `SERIOUSGAMING_DEV_STATE_DIR` to keep the lock outside the package, one entry per package, for example in a container whose project directory is restored from a snapshot.
1171
+
1172
+ With no server running, a session command starts one in the background, says so on stderr, and carries on. That server outlives the command, and `SERIOUSGAMING_DEV_PORT` tells it where to start looking for a port.
1173
+
1174
+ Set `SERIOUSGAMING_DEV_URL` to send every session command to a server by its address instead of the one the lock names, such as one started with [`sdk dev --isolated`](#run-a-second-server-beside-your-own). A server that does not answer there fails the command with `DEV_SERVER_UNREACHABLE` (exit 5), and nothing is started in its place.
1175
+
1176
+ **A stale lock.** A lock no longer names a running server when its process is gone, its pid is the process reading it, it was written on another machine, or its port refuses the connection or answers as another server. Such a lock is removed and the command carries on as if there were none, so `sdk dev` starts rather than reporting itself as already running.
1177
+
1178
+ **A server from other tools.** A server outlives the tools that started it, so after an SDK upgrade, a rebuild of a linked checkout or a switch of branch, it may be running code your CLI no longer is. A command compares the lock's tools version and install path with its own:
1179
+
1180
+ - A server a command started in the background is stopped and a fresh one started, with a `SESSION_RESTARTED` warning. Its session does not carry over
1181
+ - A server you started with `sdk dev` in a terminal is never stopped for you. The command fails with `SESSION_STALE` (exit 5) and a hint to run `sdk dev stop`
1182
+
1183
+ **What the server could not apply.** Whatever the dev server could not apply live is recorded, not only logged, and every session command repeats it on stderr and in the envelope's `warnings`, so it reaches you even from a background server nobody is watching:
1184
+
1185
+ | Code | Meaning |
1186
+ | ------------------- | ------------------------------------------------------------------------------------------------------- |
1187
+ | `SETTINGS_STALE` | An edit is pending until `sdk init` (see [What reloads](#what-reloads)) |
1188
+ | `RELOAD_FAILED` | The last edited bundle did not load, so the previous one is still in use. Cleared by an edit that loads |
1189
+ | `CAPABILITY_SEEDED` | A capability was added with its schema defaults and `Init` has not run for it. Cleared by `sdk init` |
1190
+
1191
+ `GET /api/session` reports the same list as `warnings`, with the pending edits as `pending`.
1192
+
1193
+ **What `sdk build` would refuse.** The dev server runs the checks `sdk build --validate-only` runs, when it starts and after every edit. A package that fails them still runs, so an edit that spans several files can be finished with the session in view, but not silently: each error is printed on the server's stderr, shown in a banner on the console, and added to the list above under its own diagnostic code (`REFERENCE_UNKNOWN`, `LOGIC_EXPORT_MISSING` and so on, see [CLI error codes](cli-error-codes.md)) with `build: true`. It is cleared by the edit that fixes it. Run `sdk build --validate-only` for the full report, warnings included.
1194
+
1195
+ A game with a `setup` slot starts at its configuration step (see [Building a setup screen](./guides/setup.md)). A session a command starts for itself has no configuration page anybody is looking at, so the command presses Play for you. One you started with `sdk dev`, or sent back with `sdk reconfigure`, waits there: a tick or a context write is refused until somebody presses Play in the console or runs [`sdk play`](#undo-reset-and-reconfigure).
1196
+
1197
+ Every session command also takes:
1198
+
1199
+ - `-w, --working-dir [PATH]`: the package directory (default: the current directory)
1200
+ - `--no-server`: run in this process instead of starting a dev server. Each such command is a fresh session starting at tick 0, with nothing left behind, so a multi-tick run needs a server, or [`createTestSession`](./guides/engine-testing.md)
1201
+
1202
+ See [The CLI and the dev server](./cli-and-dev-server.md) for how the two fit together.
1203
+
1204
+ ### Configure the game
1205
+
1206
+ ```text
1207
+ pnpm run sdk configure [settings] [options]
1208
+ ```
1209
+
1210
+ Override the game's settings for this session, keyed by settings identifier.
1211
+
1212
+ | Option | Default | Description |
1213
+ | -------------------------- | --------------------- | ------------------------------------ |
1214
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1215
+ | `-f, --file [PATH]` | | Read the settings from a file. |
1216
+
1217
+ ```bash
1218
+ pnpm run sdk configure '<JSON>'
1219
+ pnpm run sdk configure -f config.json
1220
+ ```
1221
+
1222
+ Sets settings values for CLI and dev server sessions, keyed by settings identifier: the directory name under `settings/`. For an engine whose settings resource is `settings/engine/`:
1223
+
1224
+ ```json
1225
+ {
1226
+ "engine": { "BaseDemand": 200, "CostPerUnit": 2 }
1227
+ }
1228
+ ```
1229
+
1230
+ The number of rounds and the team bounds are the built-in `game` settings, set the same way: `{ "game": { "maxTicks": 6 } }` plays six ticks. `game.waitingRoom.openOnStart` is refused, because it takes effect before setup (see [The game settings](./guides/settings.md#the-game-settings)).
1231
+
1232
+ The values are validated against each settings resource's `settingsSchema` and written to `.seriousgaming-sdk/configuration.json`, which holds the session overrides. A partial object is fine: keys you omit keep their scenario's value, or their default. An identifier that names no settings resource in the package is an error.
1233
+
1234
+ Settings are resolved when a session starts (see [Settings](./guides/settings.md#overriding-settings)), so a running session is started again on the new values, at tick 0, keeping its team count, scenario and locale. A session waiting at its configuration step is started, because saving a configuration is what that step's Play is for. With no session running, the next one starts on it.
1235
+
1236
+ With `--json`, the envelope's `data` is `{ "message": "...", "restarted": true|false }`, with the resolved `settings` when a session was restarted.
1237
+
1238
+ **Output:** `✓ Settings saved, and the running session restarted on them at tick 0.` With no session running, `✓ Settings saved. The next session starts on them.`
1239
+
1240
+ **Errors:** every validation failure is reported together, each labelled by its settings identifier and followed by the JSON Pointer to the offending field:
1241
+
1242
+ ```
1243
+ Error: Settings validation failed:
1244
+ Settings "engine": /BaseDemand must be number
1245
+ Unknown settings "pricing": expected one of engine, ui
1246
+ ```
1247
+
1248
+ `.seriousgaming-sdk/configuration.json` is authored content, so a value in it that no schema declares any more is an error rather than something to overwrite. Correct the file, or delete it to start again from the schema defaults.
1249
+
1250
+ ### Start or rebuild a session
1251
+
1252
+ ```text
1253
+ pnpm run sdk init [options]
1254
+ ```
1255
+
1256
+ Start the session, or put a running one back at tick 0.
1257
+
1258
+ | Option | Default | Description |
1259
+ | -------------------------- | --------------------- | -------------------------------------------------------------------------------------------- |
1260
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1261
+ | `--teams [NUMBER]` | | Number of teams to simulate (enables multi-team mode). |
1262
+ | `--participants [COUNT]` | | Participants to seed each team with, zero or more, or one per team as 3,0,2 (defaults to 1). |
1263
+ | `--scenario [ID]` | | Scenario identifier to activate (uses default scenario if omitted). |
1264
+ | `--locale [LOCALE]` | | Session locale to run the game in (defaults to en). |
1265
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1266
+ | `--walkthrough` | | Drive the walkthrough run rather than the session. |
1267
+
1268
+ Starts the session, or rebuilds a running one from the package. Any session command starts a session when none is running, so a fresh package needs no `init` at all. Reach for it to rebuild an existing session with a different team count, scenario, or locale, or after editing settings.
1269
+
1270
+ `init` resolves all three afresh, so omitting `--scenario` goes back to the package's default. That is the difference from `reset`, which returns the session to tick 0 with the scenario, team count, and locale it already has.
1271
+
1272
+ In more detail:
1273
+
1274
+ - `--teams`: a whole number, one or more; anything else is a usage error (exit 2). Each team has its own context. See [Multi-team games](./guides/multi-team-games.md)
1275
+ - `--participants`: zero is allowed, for testing a team before anybody has joined it. Without it, each team gets one, or the game's `perTeam.minParticipants`. The count is kept for later `init` runs until you pass a different `--teams`, which drops a per-team list
1276
+ - `--scenario`: the scenario's settings overrides sit between the settings defaults and the session overrides (see [Overriding settings](./guides/settings.md#overriding-settings)). If omitted and the game has a default scenario, the default is used. A scenario the package does not hold is `RESOURCE_NOT_FOUND` (exit 2). See [Scenarios](./guides/scenarios.md)
1277
+ - `--walkthrough`: see [Walkthrough runs](#walkthrough-runs)
1278
+
1279
+ **Output:** JSON with the initial context and tick data, and `scenario`, the scenario the session started on (`null` when it has none). `maxTicks` is omitted for goal-based games whose `game` settings set no `maxTicks`. `sharedContext` is included when the game has shared capabilities, and `teamContexts` when it has several teams.
1280
+
1281
+ ```json
1282
+ {
1283
+ "gameId": "com.example.factory.game",
1284
+ "completedTicks": 0,
1285
+ "maxTicks": 10,
1286
+ "complete": false,
1287
+ "context": {
1288
+ "workers": { "WorkerCount": 5 },
1289
+ "production": { "Production": 250 },
1290
+ "sales": { "Sales": 0 }
1291
+ },
1292
+ "scenario": "standard"
1293
+ }
1294
+ ```
1295
+
1296
+ **Examples:**
1297
+
1298
+ ```bash
1299
+ # Single-team (default)
1300
+ pnpm run sdk init
1301
+
1302
+ # Multi-team with 3 teams
1303
+ pnpm run sdk init --teams 3
1304
+
1305
+ # Three teams: two participants, nobody, and one
1306
+ pnpm run sdk init --teams 3 --participants 2,0,1
1307
+
1308
+ # With a specific scenario
1309
+ pnpm run sdk init --scenario hard_mode
1310
+ ```
1311
+
1312
+ ### Advance a tick
1313
+
1314
+ ```text
1315
+ pnpm run sdk tick [options]
1316
+ ```
1317
+
1318
+ Execute the next tick, initializing the game first if it has not been run yet.
1319
+
1320
+ | Option | Default | Description |
1321
+ | -------------------------- | --------------------- | --------------------------------------------------------------------------------------------- |
1322
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1323
+ | `-p, --patch [JSON]` | | RFC 6902 JSON Patch to apply to context before tick. |
1324
+ | `--team [NUMBER]` | | Team to apply --patch to (multi-team games only, 1-based). |
1325
+ | `--teams [NUMBER]` | | Number of teams to simulate (first run only). |
1326
+ | `--participants [COUNT]` | | Participants to seed each team with, zero or more, or one per team as 3,0,2 (first run only). |
1327
+ | `--scenario [ID]` | | Scenario identifier to activate (first run only). |
1328
+ | `--locale [LOCALE]` | | Change the session locale for this tick onwards. |
1329
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1330
+ | `--walkthrough` | | Drive the walkthrough run rather than the session. |
1331
+
1332
+ Executes one tick, optionally applying decisions first. Against a running `sdk dev` this advances that session, and the person watching the browser sees it immediately. With nothing running it starts a session and then ticks.
1333
+
1334
+ How teams advance depends on the game's `teams.mode`:
1335
+
1336
+ - **Shared-state**: every team advances together, with or without `--team`
1337
+ - **Independent** (the default when `mode` is omitted): with no `--team`, each team advances by one from its own tick, carrying the teams that can move past one that is complete or at its limit. `--team N` advances that team alone
1338
+
1339
+ See [Play modes](./guides/multi-team-games.md#play-modes).
1340
+
1341
+ In more detail:
1342
+
1343
+ - `--patch`: paths start at the capability (`/price/Price`)
1344
+ - `--team N`: the stable team id `team-N` to tick, and the team the `--patch` applies to. Required with `--patch` in a multi-team game, and refused in a one-team game
1345
+ - `--teams`, `--participants` and `--scenario` apply to the first run, when the tick starts the session. `--locale` changes the session locale from this tick onwards
1346
+ - `--walkthrough`: see [Walkthrough runs](#walkthrough-runs)
1347
+
1348
+ **Output:** JSON with the updated context for this tick. It includes `complete: true` when a tick controller has signalled that the game is finished. If the game is already complete, the command exits non-zero with the refusal.
1349
+
1350
+ **Examples:**
1351
+
1352
+ ```bash
1353
+ # Execute next tick with no changes
1354
+ pnpm run sdk tick
1355
+
1356
+ # Apply decisions before ticking
1357
+ pnpm run sdk tick \
1358
+ -- --patch '[{"op":"replace","path":"/price/Price","value":3}]'
1359
+
1360
+ # Multiple decisions
1361
+ pnpm run sdk tick \
1362
+ -- --patch '[
1363
+ {"op":"replace","path":"/price/Price","value":5},
1364
+ {"op":"replace","path":"/workers/WorkerCount","value":8}
1365
+ ]'
1366
+ ```
1367
+
1368
+ ### Update the context
1369
+
1370
+ ```text
1371
+ pnpm run sdk patch <patch> [options]
1372
+ ```
1373
+
1374
+ Apply an RFC 6902 JSON Patch to the game context without ticking forward.
1375
+
1376
+ | Option | Default | Description |
1377
+ | -------------------------- | --------------------- | ------------------------------------------------------- |
1378
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1379
+ | `--team [NUMBER]` | | Team to patch (required for multi-team games, 1-based). |
1380
+ | `--shared` | | Patch the shared store rather than a team. |
1381
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1382
+ | `--walkthrough` | | Drive the walkthrough run rather than the session. |
1383
+
1384
+ Applies an RFC 6902 JSON Patch to the game context without advancing the tick. With no session running, it starts one first, so it patches tick 0 of a fresh session.
1385
+
1386
+ | Command | Effect | Use it for |
1387
+ | -------------- | ------------------------------------------------------------ | --------------------------------------------------- |
1388
+ | `patch` | Modifies the context, stays at the current tick | Adjusting state without running the engine |
1389
+ | `tick --patch` | Modifies the context, then executes the engine for next tick | Simulating decisions that should drive engine logic |
1390
+
1391
+ In more detail:
1392
+
1393
+ - `<patch>`: required, in the same format as `tick --patch`
1394
+ - `--team N`: the stable team id `team-N` to patch. Required in a multi-team game, unless you pass `--shared`
1395
+ - `--shared`: patches the shared store (see [shared capabilities](./guides/multi-team-games.md#shared-capabilities)), as a session UI's shared write does. Paths start at a shared capability (`/market/AveragePrice`), and every team sees the change. A `remove` is refused, because a shared write merges into the store. Use this for a shared capability in a multi-team game, where a team-addressed patch to one is refused
1396
+
1397
+ **Output:** JSON with the updated context at the current tick.
1398
+
1399
+ **Errors:** a failed operation exits non-zero and leaves the session unchanged. `Path does not exist` means a path does not match the context's structure, and `Array index out of range` means an index is past the end of an array. Other failures are reported as `Patch failed: <reason>`.
1400
+
1401
+ **Examples:**
1402
+
1403
+ ```bash
1404
+ # Update a single context value
1405
+ pnpm run sdk patch '[{"op":"replace","path":"/price/Price","value":3}]'
1406
+
1407
+ # Update multiple values
1408
+ pnpm run sdk patch '[
1409
+ {"op":"replace","path":"/price/Price","value":5},
1410
+ {"op":"replace","path":"/workers/WorkerCount","value":8}
1411
+ ]'
1412
+ ```
1413
+
1414
+ ### View the state
1415
+
1416
+ ```text
1417
+ pnpm run sdk view [options]
1418
+ ```
1419
+
1420
+ View the current game state.
1421
+
1422
+ | Option | Default | Description |
1423
+ | -------------------------- | --------------------- | ----------------------------------------------------------------- |
1424
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1425
+ | `--team [NUMBER]` | | Show a specific team (multi-team games only, 1-based). |
1426
+ | `--full` | | Print the whole state projection rather than the reading summary. |
1427
+ | `--tick [NUMBER]` | | Print a past committed tick rather than the current one. |
1428
+ | `--to [NUMBER]` | | With --tick, print every tick from --tick to this one. |
1429
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1430
+ | `--walkthrough` | | Drive the walkthrough run rather than the session. |
1431
+
1432
+ Prints the current game state without changing it, or the state at a past tick.
1433
+
1434
+ In more detail:
1435
+
1436
+ - `--team N`: show only the team with the stable id `team-N`. Omit to show all teams
1437
+ - `--full`: the whole state projection is the session, every team's context and roster, the shared store, the resolved `settings`, the timer, and any armed faults. It is the same document the dev server serves at `GET /api/state` and the console renders from
1438
+ - `--tick N`: the state as it was at committed tick N, from the session's own snapshots. Returns `{ "entries": [...] }`, one per team per tick, each with `tick`, `teamId`, `index`, `name`, `context`, `recordedAt` and `complete`. The current tick is not in the past, so use `view` on its own for that
1439
+ - `--to M`: M is clamped to the last committed tick, so `--tick 0 --to 99` reads everything there is
1440
+
1441
+ **Output:** JSON with the latest tick data, the context, and each team's roster. In a multi-team game without `--team`, it returns `{ "teams": [...] }`. Every team carries `participants`, each with its `id`, `name`, `index`, and `connected`, so the plain view already answers who is in the room.
1442
+
1443
+ **Examples:**
1444
+
1445
+ ```bash
1446
+ pnpm run sdk view
1447
+
1448
+ # The whole state projection, which is what an agent reads
1449
+ pnpm run sdk view --full
1450
+
1451
+ # What rounds 1 to 3 left behind, for team 2
1452
+ pnpm run sdk view --tick 1 --to 3 --team 2
1453
+ ```
1454
+
1455
+ ### Undo, reset, and reconfigure
1456
+
1457
+ ```text
1458
+ pnpm run sdk undo [options]
1459
+ ```
1460
+
1461
+ Undo the last committed tick.
1462
+
1463
+ | Option | Default | Description |
1464
+ | -------------------------- | --------------------- | ----------------------------------------------------- |
1465
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1466
+ | `--team [NUMBER]` | | Team to undo (multi-team games only, 1-based). |
1467
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1468
+ | `--walkthrough` | | Drive the walkthrough run rather than the session. |
1469
+
1470
+ ```text
1471
+ pnpm run sdk reset [options]
1472
+ ```
1473
+
1474
+ Return the session to tick 0.
1475
+
1476
+ | Option | Default | Description |
1477
+ | -------------------------- | --------------------- | ----------------------------------------------------- |
1478
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1479
+ | `--team [NUMBER]` | | Team to reset (multi-team games only, 1-based). |
1480
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1481
+ | `--walkthrough` | | Drive the walkthrough run rather than the session. |
1482
+
1483
+ ```text
1484
+ pnpm run sdk reconfigure [options]
1485
+ ```
1486
+
1487
+ Return the session to its configuration step, and to tick 0.
1488
+
1489
+ | Option | Default | Description |
1490
+ | -------------------------- | --------------------- | ----------------------------------------------------- |
1491
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1492
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1493
+
1494
+ ```text
1495
+ pnpm run sdk play [options]
1496
+ ```
1497
+
1498
+ Leave the configuration step and start the session: the console's Play.
1499
+
1500
+ | Option | Default | Description |
1501
+ | -------------------------- | --------------------- | ----------------------------------------------------- |
1502
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1503
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1504
+
1505
+ ```bash
1506
+ pnpm run sdk undo [--team N] # undo the last committed tick
1507
+ pnpm run sdk reset [--team N] # back to tick 0
1508
+ pnpm run sdk reconfigure # back to the configuration step, and tick 0
1509
+ pnpm run sdk play # leave the configuration step, as Play does
1510
+ ```
1511
+
1512
+ `reset` keeps the scenario, team count, and locale the session already has; use `init` to resolve them afresh. `undo` and `reset` also take `--walkthrough`. `reset --walkthrough` starts the walkthrough again from the session's current scenario, locale and configuration, as the console's reset does. See [Walkthrough runs](#walkthrough-runs).
1513
+
1514
+ `reconfigure` is for a game with a `setup` slot, and is refused for one without. It resets as well, because settings are fixed when play starts: a change that did not restart the session would reach nothing.
1515
+
1516
+ `play` starts a session waiting at its configuration step on the configuration it has, and prints the tick report with the resolved `settings` and `started: true`. On a session already playing it changes nothing and reports `started: false`. To set values and start in one go, use [`configure`](#configure-the-game).
1517
+
1518
+ ### Teams
1519
+
1520
+ `<team>` is 1-based, as it reads in a URL.
1521
+
1522
+ ```text
1523
+ pnpm run sdk team add [name] [options]
1524
+ ```
1525
+
1526
+ Add a team to the running session.
1527
+
1528
+ | Option | Default | Description |
1529
+ | -------------------------- | --------------------- | ----------------------------------------------------- |
1530
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1531
+ | `--participants [COUNT]` | | Participants to seed the new team with, zero or more. |
1532
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1533
+
1534
+ ```text
1535
+ pnpm run sdk team remove <team> [options]
1536
+ ```
1537
+
1538
+ Remove an empty team (1-based, independent mode only).
1539
+
1540
+ | Option | Default | Description |
1541
+ | -------------------------- | --------------------- | ----------------------------------------------------- |
1542
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1543
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1544
+
1545
+ ```text
1546
+ pnpm run sdk team rename <team> <name> [options]
1547
+ ```
1548
+
1549
+ Rename a team (1-based).
1550
+
1551
+ | Option | Default | Description |
1552
+ | -------------------------- | --------------------- | ----------------------------------------------------- |
1553
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1554
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1555
+
1556
+ `team remove` deletes an empty team from an independent-mode session, as a session UI's `deleteTeam` does. It is refused in a shared-state session, for a team that still has participants (move them out with `participant move` first, connected or not), and for the last team. Every team starts with one placeholder participant unless you pass `--participants 0` to `team add` or `init`, so move that one too. The removed team's index is left as a gap and no other team is renumbered; the next `team add` takes the gap back as a new team with the default name. Undo never restores a removed team.
1557
+
1558
+ ### Participants
1559
+
1560
+ ```text
1561
+ pnpm run sdk participant add [name] [options]
1562
+ ```
1563
+
1564
+ Add a participant to a team.
1565
+
1566
+ | Option | Default | Description |
1567
+ | -------------------------- | --------------------- | ----------------------------------------------------- |
1568
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1569
+ | `--team [NUMBER]` | | Team to add them to (1-based, defaults to 1). |
1570
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1571
+
1572
+ ```text
1573
+ pnpm run sdk participant rename <participantId> <name> [options]
1574
+ ```
1575
+
1576
+ Rename a participant.
1577
+
1578
+ | Option | Default | Description |
1579
+ | -------------------------- | --------------------- | ----------------------------------------------------- |
1580
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1581
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1582
+
1583
+ ```text
1584
+ pnpm run sdk participant move <participantId> <team> [options]
1585
+ ```
1586
+
1587
+ Move a participant to another team (1-based).
1588
+
1589
+ | Option | Default | Description |
1590
+ | -------------------------- | --------------------- | ----------------------------------------------------- |
1591
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1592
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1593
+
1594
+ `participant add` defaults to team 1 when `--team` is omitted.
1595
+
1596
+ ### Waiting room, scenario, and locale
1597
+
1598
+ ```text
1599
+ pnpm run sdk waiting-room <action> [options]
1600
+ ```
1601
+
1602
+ Open or close the session waiting room.
1603
+
1604
+ | Option | Default | Description |
1605
+ | -------------------------- | --------------------- | ----------------------------------------------------- |
1606
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1607
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1608
+
1609
+ `<action>` is `open` or `close`.
1610
+
1611
+ ```text
1612
+ pnpm run sdk scenario [identifier] [options]
1613
+ ```
1614
+
1615
+ Activate a scenario, or `none` to run on the settings defaults.
1616
+
1617
+ | Option | Default | Description |
1618
+ | -------------------------- | --------------------- | ----------------------------------------------------- |
1619
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1620
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1621
+
1622
+ ```text
1623
+ pnpm run sdk locale <code> [options]
1624
+ ```
1625
+
1626
+ Change the session locale.
1627
+
1628
+ | Option | Default | Description |
1629
+ | -------------------------- | --------------------- | ----------------------------------------------------- |
1630
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1631
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1632
+
1633
+ ### The tick timer
1634
+
1635
+ For a game with a [tick controller](./guides/tick-controller.md). Each command prints the timer afterwards: one `timer` for the session, or one per team in an independent multi-team game, where every team has its own. Each is the timer's `TimerState` (`enabled`, `paused`, `nextTickAt`, `intervalMs`, `pausedAt`, `pausedMs`, `extendedMs`, `scheduleStartedAt`, durations in milliseconds) plus `nextTickIn`, the whole seconds left, for a person reading it.
1636
+
1637
+ ```text
1638
+ pnpm run sdk timer [action] [seconds] [options]
1639
+ ```
1640
+
1641
+ Pause, resume or extend the automatic tick timer, or report it when no action is given. `extend <seconds>` moves the scheduled tick only, and a negative value brings it forward.
1642
+
1643
+ | Option | Default | Description |
1644
+ | -------------------------- | --------------------- | ------------------------------------------------------------------------- |
1645
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1646
+ | `--team [NUMBER]` | | Team whose timer to address (independent multi-team games only, 1-based). |
1647
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1648
+
1649
+ ```bash
1650
+ pnpm run sdk timer # report it
1651
+ pnpm run sdk timer pause [-- --team N]
1652
+ pnpm run sdk timer resume [-- --team N]
1653
+ pnpm run sdk timer extend <seconds> [-- --team N]
1654
+ ```
1655
+
1656
+ **Moving time.** On a dev server started with `--fake-clock`, time stands still until you move it:
1657
+
1658
+ ```text
1659
+ pnpm run sdk clock advance <seconds> [options]
1660
+ ```
1661
+
1662
+ Move the clock forward, running every automatic tick that comes due.
1663
+
1664
+ | Option | Default | Description |
1665
+ | -------------------------- | --------------------- | ------------------------------------ |
1666
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1667
+
1668
+ It runs every automatic tick that falls due on the way, so three minutes on a one-minute timer is three ticks, and prints `advancedBy`, `ticksRun`, the timer and the tick report. It is refused on the real clock, and backwards. See [Testing without waiting](./guides/tick-controller.md#testing-without-waiting).
1669
+
1670
+ `extend` takes seconds, moves the currently scheduled tick only, and a negative number brings it forward. The interval the controller returns for later ticks is untouched. It is refused while the timer is paused, because there is no scheduled tick to move. Leaving out `--team` addresses every team, and `--team` is refused where every team shares one timer.
1671
+
1672
+ ### Fault injection
1673
+
1674
+ The dev server serialises every write against every tick, so it never produces the `conflict` outcome a production runtime produces most, which is the 'Try again' path your UI was told to offer. Arming a fault makes that path reachable, deliberately and reproducibly.
1675
+
1676
+ ```text
1677
+ pnpm run sdk fault [kind] [options]
1678
+ ```
1679
+
1680
+ Arm a failure the dev server cannot otherwise produce, so the path a UI was told to handle is reachable in development: conflict, denied, llm-failure, hook-throw, endpoint-timeout, tick-failure.
1681
+
1682
+ | Option | Default | Description |
1683
+ | -------------------------- | --------------------- | --------------------------------------------------------- |
1684
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1685
+ | `--times [NUMBER]` | | Fire this many times before disarming (defaults to once). |
1686
+ | `--reason [TEXT]` | | Message to carry on the resulting outcome. |
1687
+ | `--disarm` | | Disarm the fault instead of arming it. |
1688
+ | `--no-server` | | Run in this process instead of starting a dev server. |
1689
+
1690
+ ```bash
1691
+ pnpm run sdk fault conflict --reason 'testing the Try again path'
1692
+ pnpm run sdk fault llm-failure --times 3
1693
+ pnpm run sdk fault conflict --disarm
1694
+ ```
1695
+
1696
+ Available faults: `conflict`, `denied`, `llm-failure`, `hook-throw`, `endpoint-timeout`, and `tick-failure`. Each fires once unless `--times` says otherwise, and `--disarm` puts it away. A `--reason` becomes the message on the refusal, failed tick, or failed endpoint call the fault produces, so you can check how your UI shows it; on `hook-throw` it is added to the failure the console reports.
1697
+
1698
+ `llm-failure` fails the next call your logic makes to a platform service (`generateText`, `extractText`, or `generateSpeech`) as a platform failure would, with `--reason` in the error. Each failed call uses up one of its `--times`, so `--times 3` fails the next three calls, whichever resources make them. Logic that does not catch the error fails its tick, hook, or endpoint call, exactly as it would in production.
1699
+
1700
+ ### Walkthrough runs
1701
+
1702
+ A walkthrough is a disposable second run of the same game beside the session, for demonstrating it: one team, one participant, always from tick 0, and isolated from the session in both directions, so driving it moves nothing you were about to play. It inherits the session's configuration, scenario, and locale, respects the tick limit its session resolves, and never shows the waiting room.
1703
+
1704
+ `--walkthrough` points `init`, `tick`, `view`, `patch`, `undo`, `reset`, and `endpoint call` at it instead of the session.
1705
+
1706
+ ```bash
1707
+ pnpm run sdk view --walkthrough # where the walkthrough is
1708
+ pnpm run sdk tick --walkthrough # advance it, leaving the session alone
1709
+ pnpm run sdk reset --walkthrough # back to tick 0
1710
+ ```
1711
+
1712
+ The run is built the first time something asks for it, so there is nothing to create. In `sdk dev` it is also a surface, at `/walkthrough`, listed in the drawer's Surfaces tab.
1713
+
1714
+ Your logic reads `meta.runKind` and your surfaces read `useRunKind()` to tell the two apart. See [Engines](./guides/engine.md#walkthroughs) and [User interfaces](./guides/ui.md#walkthroughs).
1715
+
1716
+ ### Upload a blob
1717
+
1718
+ ```text
1719
+ pnpm run sdk blob upload <file> [options]
1720
+ ```
1721
+
1722
+ Upload a file as a blob and print its blob ID.
1723
+
1724
+ | Option | Default | Description |
1725
+ | -------------------------- | --------------------- | --------------------------------------------------------- |
1726
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1727
+ | `--settings` | | Store with settings retention (survives a session reset). |
1728
+
1729
+ Uploads a file and associates it with the game session. It returns the `blob:`-prefixed asset reference that you can patch into the game context for the engine and user interfaces to use (see [Asset references](./guides/file-handling.md#asset-references)).
1730
+
1731
+ `--settings` stores the blob with `'settings'` retention, as a setup or scenario editor upload is, which survives a session reset. Without it the blob has `'game'` retention, cleared on reset.
1732
+
1733
+ **Output (default):**
1734
+
1735
+ ```
1736
+ Uploaded blob: blob:a3f7c2d1-84b0-4e12-9f1a-0c7e8a3b1d5e
1737
+ File: market-research.pdf
1738
+ Type: application/pdf
1739
+ Retention: game
1740
+ ```
1741
+
1742
+ **Output (`--json`):** the [envelope](#json-output), whose `data` is:
1743
+
1744
+ ```json
1745
+ {
1746
+ "reference": "blob:a3f7c2d1-84b0-4e12-9f1a-0c7e8a3b1d5e",
1747
+ "name": "market-research.pdf",
1748
+ "mimeType": "application/pdf",
1749
+ "retention": "game"
1750
+ }
1751
+ ```
1752
+
1753
+ Upload and patch into the context:
1754
+
1755
+ ```bash
1756
+ REFERENCE=$(pnpm run --silent sdk blob upload ./market-research.pdf --json | jq -r '.data.reference')
1757
+ pnpm run sdk patch \
1758
+ "[{\"op\":\"replace\",\"path\":\"/upload/DocumentRef\",\"value\":\"$REFERENCE\"}]"
1759
+ ```
1760
+
1761
+ A blob with settings retention, which survives a session reset:
1762
+
1763
+ ```bash
1764
+ pnpm run sdk blob upload ./scenario-brief.pdf --settings
1765
+ ```
1766
+
1767
+ See [File handling](./guides/file-handling.md) for the full reference.
1768
+
1769
+ ### Call an endpoint
1770
+
1771
+ ```text
1772
+ pnpm run sdk endpoint call <identifier> [options]
1773
+ ```
1774
+
1775
+ Invoke an endpoint against the running session and print its response.
1776
+
1777
+ | Option | Default | Description |
1778
+ | ------------------------------ | --------------------- | ------------------------------------------------------------------------- |
1779
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1780
+ | `-r, --request [JSON]` | | The request payload, as JSON. |
1781
+ | `-f, --file [PATH]` | | Read the request payload from a file. |
1782
+ | `-t, --team [INDEX]` | | The team a team-scoped endpoint acts on (one-based). |
1783
+ | `--external` | | Call as a caller outside the game, which reaches only external endpoints. |
1784
+ | `--principal [KIND:ID[:NAME]]` | | With --external, who the call stands in for, e.g. agent:claude-7:Claude. |
1785
+ | `--attach [FIELD=PATH]` | | Send a local file as a file field, inline as a data: URI (repeatable). |
1786
+ | `--save-blobs [DIR]` | | Save every file the response names into DIR. |
1787
+ | `--no-server` | | Run in this process rather than against a dev server. |
1788
+ | `--walkthrough` | | Drive the walkthrough run rather than the session. |
1789
+
1790
+ Invokes one of the package's endpoints against the running session and prints its response as JSON.
1791
+
1792
+ It exercises an endpoint without its real caller. An external endpoint is reached in production by a caller outside the game, such as a facilitator's AI agent, and a user interface endpoint by a screen you may not have built yet: both are slow and unrepeatable ways to find out that a request schema is wrong.
1793
+
1794
+ In more detail:
1795
+
1796
+ - `--external`: an internal endpoint is refused, and the handler sees `caller.role` as `'external'` with no `userInterfaceIdentifier`
1797
+ - `--principal`: who the call stands in for, as a platform would say after authenticating its caller. The handler sees it as `caller.principal`, and the audit records it
1798
+ - `--attach`: a file field is one whose schema carries `blob`. A dotted field reaches into nested objects
1799
+ - `--save-blobs`: each `blob:` reference in the response is saved into `DIR` under its stored name
1800
+ - `--json`: the [envelope](#json-output) carries the response as `data`, or the failure (see below)
1801
+
1802
+ The request may also arrive on stdin, so a payload built by another command can be piped straight in.
1803
+
1804
+ A failed call exits non-zero and prints its kind, its message, and the effects that had already landed before it stopped:
1805
+
1806
+ ```
1807
+ Error: Endpoint "startround" failed (tick-failed): Endpoint "startround" stopped at action 1 ("tick"): The game is complete. Its write, action 0 ("tick") had already been applied
1808
+ Applied before the failure: its write, action 0 ("tick")
1809
+ Failed at: action 1 ("tick")
1810
+ ```
1811
+
1812
+ A failed call exits 4. With `--json`, the failure envelope's code names the kind, and its `error.details` carries the failure itself:
1813
+
1814
+ ```json
1815
+ {
1816
+ "ok": false,
1817
+ "command": "endpoint call",
1818
+ "error": {
1819
+ "code": "INVALID_REQUEST",
1820
+ "message": "Endpoint \"setprice\" failed (invalid-request): ...\nNothing was applied.",
1821
+ "details": {
1822
+ "outcome": {
1823
+ "kind": "invalid-request",
1824
+ "message": "Endpoint \"setprice\" rejected the request: /price must be <= 50"
1825
+ },
1826
+ "applied": []
1827
+ }
1828
+ },
1829
+ "diagnostics": [],
1830
+ "warnings": []
1831
+ }
1832
+ ```
1833
+
1834
+ The code is `INVALID_REQUEST`, `HANDLER_ERROR` or `INVALID_RESPONSE` for the three failures only an endpoint has, and the refusal's own code (`REFUSED_DENIED`, `TICK_FAILED` and so on) for the rest. `outcome.kind` is one of `denied`, `invalid-request`, `handler-error`, `invalid-response`, `conflict`, a failed step's own kind such as `tick-failed`, or `error`. `applied` lists the steps that committed, and `failedStep` names the one that failed. See [Endpoints](./guides/endpoints.md#when-a-call-fails).
1835
+
1836
+ Against a dev server, an `--external` call also prints each returned `blob:` reference with the URL an outside caller would fetch it from. That goes to stderr, so stdout stays the response alone for a script to parse.
1837
+
1838
+ A file sent the way a script without an upload route of its own would, and the export an endpoint returns saved beside it:
1839
+
1840
+ ```bash
1841
+ pnpm run sdk endpoint call attachresearch --external --team 1 --attach document=./research.pdf
1842
+ pnpm run sdk endpoint call export --external --save-blobs ./exports
1843
+ ```
1844
+
1845
+ To send a file the other way, upload it first with `sdk blob upload` and put the `blob:` reference it prints in the request.
1846
+
1847
+ A team-scoped endpoint that changes state:
1848
+
1849
+ ```bash
1850
+ pnpm run sdk endpoint call setprice --team 1 --request '{"price": 3.53}'
1851
+ ```
1852
+
1853
+ ```json
1854
+ {
1855
+ "previous": 5,
1856
+ "current": 3.53
1857
+ }
1858
+ ```
1859
+
1860
+ A session-scoped endpoint takes no `--team`, because there is no team for the value to mean anything against. An endpoint that declares `writes` applies its change to the board as part of the call, so `sdk view` afterwards shows it.
1861
+
1862
+ ### List the endpoints
1863
+
1864
+ ```text
1865
+ pnpm run sdk endpoint list [options]
1866
+ ```
1867
+
1868
+ List the package's endpoints and what each one may do.
1869
+
1870
+ | Option | Default | Description |
1871
+ | -------------------------- | --------------------- | ------------------------------------------------------ |
1872
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1873
+ | `--external` | | Only the endpoints a caller outside the game may call. |
1874
+
1875
+ Prints every endpoint the package declares, with its scope, whether it writes, whether a caller outside the game may call it, and its description. It reads the manifest, so it answers without starting a session.
1876
+
1877
+ With `--json`, the [envelope](#json-output)'s `data` lists the endpoints, including each one's request and response schemas.
1878
+
1879
+ An endpoint that declares `actions` is marked `acts` and lists them, and one that answers in two phases (`Plan` and `Respond`) is marked `responds after`. `--json` carries both as `actions` and `responds`.
1880
+
1881
+ **Output (default):**
1882
+
1883
+ ```
1884
+ setprice (team, writes, external)
1885
+ Set the unit price
1886
+ Sets a team's unit price, in pounds per unit. Use this when a facilitator asks
1887
+ to change what a team charges.
1888
+ writes: price
1889
+ ```
1890
+
1891
+ The description column is the reason to run it. On an external endpoint, the description is what an outside caller, a model most of all, reads to decide whether to call it. Reading yours side by side is when you notice that three of them say much the same thing, which means a wrong call every session and is the one thing here that no test catches.
1892
+
1893
+ ### Audit endpoint calls
1894
+
1895
+ ```text
1896
+ pnpm run sdk endpoint audit [options]
1897
+ ```
1898
+
1899
+ List every endpoint call that changed the running session, and who made it.
1900
+
1901
+ | Option | Default | Description |
1902
+ | -------------------------- | --------------------- | ------------------------------------------------- |
1903
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1904
+ | `--walkthrough` | | Read the walkthrough run rather than the session. |
1905
+
1906
+ Lists every endpoint call that changed the running session, oldest first: which endpoint, the team it ran for, who called it (with the principal of an external call), what it wrote, and the actions it carried out. A call that stopped partway says where. The dev server holds the log, so this needs one running; a `--no-server` session is new and has nothing to show.
1907
+
1908
+ With `--json`, the [envelope](#json-output)'s `data` is the entries.
1909
+
1910
+ **Output (default):**
1911
+
1912
+ ```
1913
+ 2026-09-30T14:02:11.000Z startround by external agent:claude-7 (Claude)
1914
+ wrote noticeboard; did setWaitingRoom, tick
1915
+ ```
1916
+
1917
+ ### Team addressing errors
1918
+
1919
+ | Error | Cause |
1920
+ | -------------------------------------------------------------- | ------------------------------------------------------------- |
1921
+ | `--team can only be used with multi-team games` | `--team` passed, but the session has one team |
1922
+ | `--team <number> is required for multi-team games` | `patch` in a multi-team game without `--team` |
1923
+ | `--team is required when using --patch with a multi-team game` | `tick --patch` in a multi-team game without `--team` |
1924
+ | `There is no team N in this session. Teams: 1, 2, 3` | The team number is out of range, or the team has been removed |
1925
+
1926
+ ## Development server
1927
+
1928
+ ### Start the dev server
1929
+
1930
+ Run the game with live reloading:
1931
+
1932
+ ```text
1933
+ pnpm run sdk dev [options]
1934
+ ```
1935
+
1936
+ Start the Serious Gaming development server with hot reload.
1937
+
1938
+ | Option | Default | Description |
1939
+ | --------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1940
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
1941
+ | `-p, --port [PORT]` | `3000` | Port to run the dev server on, or auto for any free port (can also be set via SERIOUSGAMING_DEV_PORT env var or .env file). |
1942
+ | `--vite-port [VITE_PORT]` | | Port to run the Vite dev server on (defaults to PORT + 1). |
1943
+ | `--asset-port [ASSET_PORT]` | | Port game UI bundles and their assets are served from, which is a separate origin from the console (defaults to the next free port after the Vite port). |
1944
+ | `--no-open` | | Do not automatically open browser. |
1945
+ | `--api-key [KEY]` | | Serious Gaming platform API key for LLM calls (can also be set via SERIOUSGAMING_API_KEY env var or .env file). |
1946
+ | `--host [URL]` | | Serious Gaming platform origin for LLM calls (can also be set via SERIOUSGAMING_API_HOST env var or .env file). |
1947
+ | `--lan` | | Listen on every network interface, so phones and tablets on the same network can connect (localhost only by default). |
1948
+ | `--teams [NUMBER]` | | Number of teams to simulate (each accessible at `/team/<n>);` omit to use the game's teams.min. |
1949
+ | `--participants [COUNT]` | | Participants to seed each team with, zero or more, or one per team as 3,0,2 (defaults to 1). |
1950
+ | `--scenario [ID]` | | Scenario identifier to activate (uses default scenario if omitted). |
1951
+ | `--fake-clock` | | Run on a clock that moves only when `sdk clock advance` moves it, to test automatic ticks without waiting. |
1952
+ | `--reinit-on-change` | | Restart the game at tick 0 whenever an edit applies only at initialisation (a setting, a manifest field such as teams, a capability removed or rescoped), rather than recording it as pending once play is under way. |
1953
+ | `--isolated` | | Run beside any dev server already running for this package, for tests and recordings: no lock file (CLI commands reach it through SERIOUSGAMING_DEV_URL), configuration from the package alone, and uploads in a temporary directory. |
1954
+
1955
+ In more detail:
1956
+
1957
+ - `--port`: where to start looking for a port. The default is `SERIOUSGAMING_DEV_PORT` when set, otherwise 3000. The server steps up to the first free port from there and prints the one it lands on. `auto` takes any free port the system offers
1958
+ - `--asset-port`: a separate origin from the console, as production's asset host is, so a UI cannot reach the console's API
1959
+ - `--lan`: to test on a phone or tablet (see below). Without it, only this machine can reach the server
1960
+ - `--api-key` and `--host`: default to `SERIOUSGAMING_API_KEY` and `SERIOUSGAMING_API_HOST`, from the environment or `.env`. The host defaults to `https://app.businesssimulations.com`
1961
+ - `--teams` and `--scenario`: see below
1962
+ - `--participants`: zero is allowed. Without it, each team gets one, or the game's `perTeam.minParticipants`
1963
+ - `--fake-clock`: moved with [`sdk clock advance`](#the-tick-timer)
1964
+ - `--reinit-on-change`: however far play has gone, rather than holding the edit as pending (see [What reloads](#what-reloads)). The console shows a short 'Settings changed, game restarted' notice each time. The hosted builder starts the dev server this way
1965
+ - `--isolated`: for a test run or a recording (see below)
1966
+
1967
+ A package holds one session, so a second `sdk dev` for the same package is refused whatever port it asks for, and names the one already running. Stop that one with `sdk dev stop` to move it elsewhere, or start the second with `--isolated`.
1968
+
1969
+ The server prints the console's address and the address game surfaces are served from. With `-j, --json`, the envelope it writes when it stops carries `url`, `assetUrl`, `port`, `assetPort` and `isolated`.
1970
+
1971
+ ### Run a second server beside your own
1972
+
1973
+ ```bash
1974
+ pnpm run sdk dev --isolated --port auto --no-open
1975
+ # ✓ Dev server running at http://localhost:53712
1976
+ # Game surfaces are served from http://localhost:53714
1977
+
1978
+ SERIOUSGAMING_DEV_URL=http://localhost:53712 pnpm run sdk tick
1979
+ ```
1980
+
1981
+ `--isolated` starts a server that touches nothing of the one you are working in, so an end-to-end run, a walkthrough recording or a clip capture does not need yours stopped:
1982
+
1983
+ - **No lock file.** It neither reads nor writes `.seriousgaming-sdk/session.json`, so it starts whatever is running, and a CLI command reaches it only through `SERIOUSGAMING_DEV_URL`
1984
+ - **Configuration from the package alone.** It neither reads nor writes `.seriousgaming-sdk/configuration.json`, so what you last ran `sdk configure` with does not leak into a test
1985
+ - **Temporary uploads.** Blobs go to a temporary directory, removed when the server stops
1986
+
1987
+ Pair it with `--port auto` so it never collides with yours. To start one from a test runner without spawning the CLI, use [`startDevServer`](./guides/testing.md#a-dev-server-for-browser-tests).
1988
+
1989
+ **Several teams.** Start with N teams, each at its own URL:
1990
+
1991
+ ```bash
1992
+ pnpm run sdk dev --teams 3
1993
+ # Team 1, first player: http://localhost:3000/team/1/player/1
1994
+ # Team 2, first player: http://localhost:3000/team/2/player/1
1995
+ # Team 3, first player: http://localhost:3000/team/3/player/1
1996
+ # The console, which shows the session UI: http://localhost:3000/
1997
+ # A link to hand out, which asks who you are: http://localhost:3000/team/1/join
1998
+ ```
1999
+
2000
+ The setup screen, for a game with a `setup` slot, is at `/setup`: the console opens there while the session is at its configuration step. The scenario editor is at `/scenario-editor`, opened on one scenario from the dev drawer's **Scenario** tab.
2001
+
2002
+ Without `--teams`, the dev server starts with `teams.min` teams when the game sets it above one. A `--teams` count below `teams.min` or above `teams.max` is refused rather than adjusted. Once the session is running, teams can be added up to `teams.max` and removed down to one. See [Multi-team games](./guides/multi-team-games.md).
2003
+
2004
+ Every surface has its own URL, and `?bare` drops the dev chrome from any of them. [Surfaces and their URLs](./cli-and-dev-server.md#surfaces-and-their-urls) lists them all.
2005
+
2006
+ **On a phone or tablet.** The dev server listens on `localhost` only, so nobody else on a shared network, such as an office, an event or a café, can reach it. To open a surface on another device, start it with `--lan`:
2007
+
2008
+ ```bash
2009
+ pnpm run sdk dev --lan
2010
+ # ⚠ Listening on your network (--lan). Anyone on the same network can open this
2011
+ # session, use its API and read your package files. Use it only on a network you trust.
2012
+ # On another device, open:
2013
+ # http://192.168.1.157:3000
2014
+ ```
2015
+
2016
+ Every port the server opens, the console's, the asset origin's and Vite's hot-reload socket, then listens on every interface. Add the surface's path to the printed address, for example `http://192.168.1.157:3000/team/1/player/1`. The device must be on the same network, and a firewall on this machine may still need to allow the ports.
2017
+
2018
+ **A scenario.** Start with a scenario's settings overrides and assets:
2019
+
2020
+ ```bash
2021
+ pnpm run sdk dev --scenario hard_mode
2022
+ ```
2023
+
2024
+ Without `--scenario`, the game's default scenario is used if it has one. Scenario assets are served at `/api/scenario/assets/*`. See [Scenarios](./guides/scenarios.md).
2025
+
2026
+ ### Manage the background server
2027
+
2028
+ ```text
2029
+ pnpm run sdk dev status [options]
2030
+ ```
2031
+
2032
+ Report whether a dev server is running for this package, and where.
2033
+
2034
+ | Option | Default | Description |
2035
+ | -------------------------- | --------------------- | ------------------------------------ |
2036
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
2037
+
2038
+ ```text
2039
+ pnpm run sdk dev stop [options]
2040
+ ```
2041
+
2042
+ Stop the dev server running for this package.
2043
+
2044
+ | Option | Default | Description |
2045
+ | -------------------------- | --------------------- | ------------------------------------ |
2046
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
2047
+
2048
+ `dev status` and `dev stop` report through the [envelope](#json-output) with `-j, --json`, like every command.
2049
+
2050
+ ### Dev server features
2051
+
2052
+ - **Hot reloading**: edit logic, bundles, the manifest or UIs and see the change without a restart (see [What reloads](#what-reloads))
2053
+ - **The dev drawer**: press the backtick key on any surface for the session, the scenario, the surface list, the state projection, armed faults, and errors (see [The dev drawer](./cli-and-dev-server.md#the-dev-drawer))
2054
+ - **One command set**: everything the drawer does, the CLI does, because both send the same commands
2055
+ - **Direct addressing**: every surface has a URL, and `?bare` removes the dev chrome
2056
+ - **Fault injection**: arm an outcome the dev server cannot otherwise produce, so the path your UI handles is reachable
2057
+ - **Several teams**: run N teams at once with `--teams N`, or from `teams.min`
2058
+
2059
+ ### What reloads
2060
+
2061
+ Every edit falls into one of three buckets. Knowing which saves guessing at why an edit appears to do nothing.
2062
+
2063
+ **Live: applied straight away, keeping the session where it is.**
2064
+
2065
+ | You edit | What happens |
2066
+ | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
2067
+ | A user interface (`ui/src/**`) | Vite hot-replaces the UI |
2068
+ | Resource logic (`engine/logic.ts`, `tick_controller/logic.ts`, or `logic.ts` in `hooks/`, `validators/`, `endpoints/`) | The next run uses the new code. Ticks already played are not re-run |
2069
+ | A resource bundle (`*.sdk.json`) | The session uses it from the next operation. A bundle that does not load is reported and the last good one stays in use |
2070
+ | A new surface, such as a `waiting_room/` or `presentation/` slot | Served at its URL as soon as its bundle is saved |
2071
+ | A new capability, or one a resource starts to read or write | Every team's board gets it with its schema defaults. `Init` has not run for it, and the dev server says so: run `sdk init` to start from `Init` |
2072
+ | `locales` or `supportedLocales` in `seriousgaming.sdk.json` | The session uses them from the next operation |
2073
+ | Scenario or facilitator-guide markdown, or an asset file | The new content is served on the next request |
2074
+ | A colocated test (`*.test.ts`, `*.spec.ts`) | Nothing. Tests are not part of a logic bundle |
2075
+
2076
+ **Pending until `sdk init`: applied when the session next initialises.** These reach the game through `Init` or session creation, so applying them mid-session would give a state no real session can reach, such as a setting `Init` used keeping its old effect while the next tick reads the new value.
2077
+
2078
+ - A settings resource's values, including a default or a scenario's override
2079
+ - `maxTicks`, `teams` and `waitingRoom.openOnStart` in `seriousgaming.sdk.json`, which are the game's own values for the built-in `game` settings, and `teams.mode` and `identifier`
2080
+ - Adding or removing the `setup` slot, which decides whether there is a configuration step
2081
+ - Removing a capability, or changing its `scope`
2082
+
2083
+ What happens to a pending change depends on whether anything has been played:
2084
+
2085
+ - **At tick 0**, nothing has been played, so the session re-initialises on its own and applies it
2086
+ - **Once a tick has been played**, the session keeps running on what it started with and records the change. The console shows 'Settings changed: re-initialise to apply' with a **Re-initialise** button. `sdk tick`, `sdk undo`, a context patch, a writing endpoint call and resuming the tick timer are refused with `SETTINGS_STALE` (exit 4) until you re-initialise, with a hint to run `sdk init`. `sdk view` still answers, with `stale` in its data naming what changed and a warning on stderr. Reads, roster and team changes, and the waiting room are unaffected
2087
+ - **Under `sdk dev --reinit-on-change`**, the session always re-initialises, however far play has gone
2088
+
2089
+ Re-initialising from the console's button keeps the team count, the scenario and the locale, and returns play to tick 0. So does a session-wide reset. `sdk init` rebuilds the session from the package and takes `--teams`, `--scenario` and `--locale` as usual. Editing the file back to what the session started with clears the pending change.
2090
+
2091
+ **Needs a restart.** Only an upgrade of the SDK tools themselves. A command notices one (see [A server from other tools](#where-the-session-lives)): it replaces a server it started in the background, and otherwise asks you to stop yours with `sdk dev stop` and start it again.
2092
+
2093
+ A UI that throws while it first loads cannot be hot-replaced, because there is no mounted component left to replace. The dev server reloads its frame on the next edit to any file it uses instead, so fixing the mistake is all it takes, though anything typed into the surface is lost with the reload.
2094
+
2095
+ The dev server watches the package directory and nothing outside it, so logic code in a linked workspace dependency is not watched: an edit there reaches the next run once a source file in the package changes. A UI is different, because its files are Vite's to watch, and Vite follows a linked dependency a UI imports and hot-replaces it like an edit under `ui/src/`. A logic edit changes the next run, not the ticks already played or the context `Init` built: undo and tick again to see a change applied from the same starting point, or reset to run the new `Init`.
2096
+
2097
+ ## Project commands
2098
+
2099
+ ### Create a package
2100
+
2101
+ ```text
2102
+ pnpm run sdk new package [definition] [options]
2103
+ ```
2104
+
2105
+ Create a new package.
2106
+
2107
+ | Option | Default | Description |
2108
+ | -------------------------- | --------------------- | -------------------------------------------------------- |
2109
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to create the package in. |
2110
+ | `-f, --file [PATH]` | | Path to a JSON file containing the package definition. |
2111
+ | `--empty` | | Skip the starter game: package files and CLAUDE.md only. |
2112
+ | `--skip-install` | | Skip running pnpm install after creating the package. |
2113
+
2114
+ ```bash
2115
+ pnpm dlx @seriousgaming/sdk-tools new package
2116
+ ```
2117
+
2118
+ Scaffolds the package structure and `seriousgaming.sdk.json`. It runs interactively by default, and also accepts a JSON definition as an argument, via `--file`, or on stdin. The `pnpm run sdk` script exists only once a package has been scaffolded, so this first command goes through `pnpm dlx`.
2119
+
2120
+ What it writes is a complete, playable game, not an empty shell: an engine, a game UI, a session UI, a waiting room UI, a validator, a `participantJoin` hook, a facilitator guide, five capabilities, four settings resources, two scenarios, and tests at every level. It is the game the SDK's own test suites play, copied out verbatim apart from the game's name. See [A tour of the starter game](./guides/starter-game.md) for what it is and why.
2121
+
2122
+ `--empty` writes the package files and the agent guidance (`CLAUDE.md`, `.claude/`) alone: no game resources or tests.
2123
+
2124
+ ### Resolve configuration
2125
+
2126
+ ```text
2127
+ pnpm run sdk resolve [options]
2128
+ ```
2129
+
2130
+ Resolve the package to its built manifest and print it to stdout.
2131
+
2132
+ | Option | Default | Description |
2133
+ | -------------------------- | --------------------- | ------------------------------------ |
2134
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
2135
+
2136
+ Prints the complete resolved package: every resource it declares, settings resources included.
2137
+
2138
+ ### Generate types
2139
+
2140
+ ```text
2141
+ pnpm run sdk generate [options]
2142
+ ```
2143
+
2144
+ Generate code from capabilities and the engine.
2145
+
2146
+ | Option | Default | Description |
2147
+ | -------------------------- | --------------------- | ------------------------------------ |
2148
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
2149
+
2150
+ Regenerates the typed `types.ts` beside each resource that has logic or a user interface, from the capabilities and bundles the package declares. `new`, `update`, and `build` already run it, so reach for it after editing a `*.sdk.json` file by hand.
2151
+
2152
+ ### Lint
2153
+
2154
+ ```bash
2155
+ pnpm run lint
2156
+ pnpm run lint:fix
2157
+ ```
2158
+
2159
+ `lint` checks code for style and quality issues, and `lint:fix` fixes what it can. Use the `lint:fix` script rather than `pnpm run lint -- --fix`: pnpm passes the `--` through, so `oxlint` reads `--fix` as a path and lints nothing.
2160
+
2161
+ ## Build and publish commands
2162
+
2163
+ ### Build the game
2164
+
2165
+ ```bash
2166
+ pnpm run build
2167
+ ```
2168
+
2169
+ ```text
2170
+ pnpm run sdk build [options]
2171
+ ```
2172
+
2173
+ Build your Serious Gaming package, or just its engine.
2174
+
2175
+ | Option | Default | Description |
2176
+ | -------------------------- | ------------------------ | ----------------------------------------------------------------------------------------------------- |
2177
+ | `-c, --config [PATH]` | `seriousgaming.sdk.json` | The Serious Gaming SDK config file to load. |
2178
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
2179
+ | `-b, --build-dir [PATH]` | `dist/` | The directory to write build artefacts to. |
2180
+ | `--validate-only` | | Run validation checks without building (checks logic files, capability references, game composition). |
2181
+
2182
+ Run in the package directory. Validates the package, generates types, and builds the engine and UIs for deployment. A top-level `shared-assets/` directory (if present) is copied verbatim into the build root as `dist/shared-assets/`, so `shared:<path>` references resolve against the built package. See [Share assets across scenarios](./recipes/share-assets-across-scenarios.md).
2183
+
2184
+ `--validate-only` is described below. With `--json`, the [envelope](#json-output)'s `data` under `--validate-only` is the validation summary (`success`, `resources`, `summary`); a failure is `PACKAGE_INVALID` (exit 3) with a diagnostic per finding. `--verbose`, the [global option](#global-options), shows detailed build progress.
2185
+
2186
+ ### Validate the package
2187
+
2188
+ ```bash
2189
+ # Human-readable validation output
2190
+ pnpm run sdk build --validate-only
2191
+
2192
+ # Structured JSON output for automation
2193
+ pnpm run sdk build --validate-only --json
2194
+ ```
2195
+
2196
+ Runs every validation check without building:
2197
+
2198
+ - Engine logic files exist and export `Init` and `Run`
2199
+ - UI entrypoint files exist
2200
+ - Capability references resolve correctly
2201
+ - Game composition (settings and locales)
2202
+ - Every `settings` entry names a settings resource the package declares, and so does the `configures` of the setup and scenario editor slots
2203
+ - Every settings property declares a `default`, and no `settingsSchema` declares a top-level `required`
2204
+ - No bundle carries a `configurationSchema`, and the manifest carries no `configuration` block
2205
+ - Scenario settings overrides against each settings resource's `settingsSchema` (see [Scenario settings](#scenario-settings))
2206
+ - Every schema `default` satisfies the subschema that declares it (see [Schema defaults](#schema-defaults))
2207
+
2208
+ It also warns, without failing, when a resource reads a capability that **nothing in the package writes**. Such a read can only ever see the capability's schema defaults, so it is usually a leftover from a deleted writer, or a set of constants that belongs in a [settings resource](./guides/settings.md) instead. The check is package-wide, so a capability written anywhere is never flagged.
2209
+
2210
+ #### Scenario settings
2211
+
2212
+ A scenario's `settings` block is keyed by settings identifier, and each entry is checked against that settings resource's `settingsSchema`. An entry naming a settings resource the package does not declare fails too. This is the check the dev server and the production runtime make when a scenario is activated, brought forward to build time.
2213
+
2214
+ Overrides are **partial**: a scenario that sets only the keys it cares about is fine, and omitted keys fall through to their defaults. What fails the build is a key the schema does not declare (usually a misspelling), or a declared key whose value violates the schema.
2215
+
2216
+ ```json
2217
+ "settings": {
2218
+ "engine": {
2219
+ "BaseDemand": 15000,
2220
+ "DemandPriceModifer": 300
2221
+ }
2222
+ }
2223
+ ```
2224
+
2225
+ `DemandPriceModifer` is a typo, so the build fails and names it, rather than the scenario failing at session start.
2226
+
2227
+ #### Schema defaults
2228
+
2229
+ A `default` must be a valid instance of the subschema that declares it. Defaults seed the initial context (`variablesSchema`) and the resolved settings (`settingsSchema`), so one that violates its own schema puts invalid state into the game before any logic runs. Validation covers `variablesSchema`, `settingsSchema`, and an endpoint's `requestSchema` and `responseSchema`, at any depth.
2230
+
2231
+ The common trap is an object default that carries a field the subschema does not declare:
2232
+
2233
+ ```json
2234
+ "Company": {
2235
+ "type": "object",
2236
+ "properties": { "Name": { "type": "string" } },
2237
+ "required": ["Name"],
2238
+ "additionalProperties": false,
2239
+ "default": { "Name": "Acme", "Tagline": "We deliver" }
2240
+ }
2241
+ ```
2242
+
2243
+ `Tagline` is not declared and `additionalProperties` is `false`, so the default can never validate. Either declare `Tagline` or drop it from the default.
2244
+
2245
+ Also flagged: a default of the wrong type, one outside a declared `enum`, `minimum`, `maximum`, or `pattern`, and one that omits a field its own object marks `required`. A default is used **whole** when its key is absent, not merged with anything, so a partial object default seeds an incomplete value.
2246
+
2247
+ > A value that is only legitimate before your logic first writes the capability still has to be in the schema. If a status starts as `'Unknown'` and your engine later sets it to one of four ratings, `'Unknown'` belongs in the `enum` too, otherwise the generated union misrepresents what the capability holds at tick 0.
2248
+
2249
+ ### Build the engine on its own
2250
+
2251
+ ```text
2252
+ pnpm run sdk build engine [options]
2253
+ ```
2254
+
2255
+ Build the engine and output to stdout.
2256
+
2257
+ | Option | Default | Description |
2258
+ | -------------------------- | ------------------------ | ------------------------------------------- |
2259
+ | `-c, --config [PATH]` | `seriousgaming.sdk.json` | The Serious Gaming SDK config file to load. |
2260
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
2261
+
2262
+ Transpiles the engine's TypeScript logic and writes the bundled JavaScript to stdout, without producing a `dist/`. Useful for inspecting what the engine actually runs, or for piping into another tool.
2263
+
2264
+ It takes no argument: a game holds one engine, in the `engine` slot. The command fails when the slot is empty.
2265
+
2266
+ **Example:**
2267
+
2268
+ ```bash
2269
+ CODE=$(pnpm run --silent sdk build engine)
2270
+ ```
2271
+
2272
+ ### Package a built game for distribution
2273
+
2274
+ ```text
2275
+ pnpm run sdk package bundle [options]
2276
+ ```
2277
+
2278
+ Package the built `dist/` directory into a distributable zip archive.
2279
+
2280
+ | Option | Default | Description |
2281
+ | -------------------------- | --------------------- | ----------------------------------------- |
2282
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
2283
+ | `-b, --build-dir [PATH]` | `dist/` | The directory containing build artefacts. |
2284
+ | `-o, --output-file [PATH]` | | Where to write the zip archive. |
2285
+
2286
+ Wraps the contents of a built `dist/` directory into a single zip archive for uploading to a production runtime. Run it after `pnpm run sdk build`; it does not build.
2287
+
2288
+ The archive root mirrors the build layout, with `seriousgaming.json` at the top and any `shared-assets/` directory included once at the root. See [Distribute a built package](./recipes/distribute-package.md) for a CI recipe.
2289
+
2290
+ `--output-file` defaults to `<build-dir>/<identifier>-<version>.zip`. With `--json`, the [envelope](#json-output)'s `data` is `outputFile`, `size`, `fileCount`, `identifier` and `version`.
2291
+
2292
+ **Example:**
2293
+
2294
+ ```bash
2295
+ pnpm run sdk build
2296
+ pnpm run sdk package bundle
2297
+ # → dist/coffeeshop-1.2.0.zip
2298
+
2299
+ pnpm run --silent sdk package bundle --json > bundle.json
2300
+ ```
2301
+
2302
+ ### Set the package version
2303
+
2304
+ ```text
2305
+ pnpm run sdk package version <value> [options]
2306
+ ```
2307
+
2308
+ Set the package version in package.json, which owns it.
2309
+
2310
+ | Option | Default | Description |
2311
+ | -------------------------- | --------------------- | ------------------------------------ |
2312
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
2313
+
2314
+ Sets the package's version in `package.json`, which owns it. The manifest carries no version; the build reads it from here and writes it into the built artefact. Useful in CI to bump the version before `package bundle`, since production runtimes typically reject a re-upload of the same identifier and version.
2315
+
2316
+ `<value>` is either a literal semver string (such as `1.2.3` or `1.2.3-rc.1`) or one of the bump keywords `patch`, `minor`, `major`, and `prerelease`. The new version is validated against the same pattern `schema.json` enforces on `PackageDefinition.version`. The command does not run `git tag` or `git commit`; leave tagging to CI.
2317
+
2318
+ **Bump keywords:**
2319
+
2320
+ - `patch`: `1.2.3` becomes `1.2.4`
2321
+ - `minor`: `1.2.3` becomes `1.3.0`
2322
+ - `major`: `1.2.3` becomes `2.0.0` (drops any existing pre-release)
2323
+ - `prerelease`: on a stable version, bumps patch and appends `-0` (`1.2.3` becomes `1.2.4-0`); on an existing pre-release, increments a trailing numeric identifier (`1.2.3-rc.1` becomes `1.2.3-rc.2`) or appends `.0` when the trailing identifier is alphanumeric (`1.2.3-rc` becomes `1.2.3-rc.0`)
2324
+
2325
+ With `--json`, the [envelope](#json-output)'s `data` is `previous`, `next` and `files`.
2326
+
2327
+ **Example:**
2328
+
2329
+ ```bash
2330
+ pnpm run sdk package version 1.4.2
2331
+ pnpm run sdk package version patch
2332
+ pnpm run --silent sdk package version prerelease --json > version.json
2333
+ ```
2334
+
2335
+ ### Publish a package to the Serious Gaming platform
2336
+
2337
+ ```text
2338
+ pnpm run sdk package publish [zip] [options]
2339
+ ```
2340
+
2341
+ Upload a built and bundled package to the Serious Gaming platform.
2342
+
2343
+ | Option | Default | Description |
2344
+ | -------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2345
+ | `-w, --working-dir [PATH]` | the current directory | The working directory to execute in. |
2346
+ | `-b, --build-dir [PATH]` | `dist/` | The directory containing build artefacts. |
2347
+ | `--host [URL]` | | Origin to upload to, e.g. https://app.businesssimulations.com (defaults to SERIOUSGAMING_API_HOST env var or https://app.businesssimulations.com). The CLI appends `/api/v0/admin/publishers/<slug>/packages` itself. |
2348
+ | `--api-key [KEY]` | | API key (defaults to SERIOUSGAMING_API_KEY env var or .env file). |
2349
+ | `--publisher [SLUG]` | | Publisher slug to upload under (defaults to SERIOUSGAMING_PUBLISHER env var or platform.defaultPublisher in global settings). |
2350
+ | `--no-auto-bundle` | | Fail rather than re-bundle when the zip is missing. |
2351
+
2352
+ Uploads a built and bundled package zip to the Serious Gaming platform through the `/api/v0/admin/publishers/<slug>/packages` endpoint. It reads the identifier from `seriousgaming.sdk.json` and the version from `package.json`, locates `<build-dir>/<identifier>-<version>.zip`, and POSTs it as `multipart/form-data` with field `body` and an `Authorization: Bearer <key>` header. The key must come from an account with publishing access, which Business Simulations grants.
2353
+
2354
+ When the bundle is missing, the command runs `package bundle` first. Pass `--no-auto-bundle` to fail instead, or an explicit `[zip]` argument to upload that file without re-bundling.
2355
+
2356
+ **Publisher.** The upload is scoped to a publisher, resolved from (highest priority first) the `--publisher` flag, the `SERIOUSGAMING_PUBLISHER` environment variable, then `platform.defaultPublisher` in the [global settings file](./guides/global-settings.md). If none resolves, the command fails with guidance rather than guessing. The first upload of a package binds it to the named publisher; later versions must be uploaded under the same publisher or the platform returns a 400.
2357
+
2358
+ **API key.** The CLI looks up `SERIOUSGAMING_API_KEY` in the standard precedence order: global settings file, then `.env` file, then `process.env`, then the `--api-key` flag, each overriding the one before. Keep the key in your `.env`, shell environment, or global settings so it stays out of shell history.
2359
+
2360
+ `--host` takes an origin only, not a full path. With `--json`, the [envelope](#json-output)'s `data` is the server's `PackageVersionResponse`.
2361
+
2362
+ The environment variables it reads are listed in [Environment variables](./environment-variables.md#package-publishing-pnpm-run-sdk-package-publish).
2363
+
2364
+ **Examples:**
2365
+
2366
+ ```bash
2367
+ # Default: bundle if needed, upload to production under the named publisher
2368
+ pnpm run sdk package publish --publisher acme
2369
+
2370
+ # Upload an explicit zip without re-bundling
2371
+ pnpm run sdk package publish artifacts/release.zip --publisher acme
2372
+
2373
+ # Target a staging environment
2374
+ pnpm run sdk package publish --host https://staging.example.com --publisher acme
2375
+
2376
+ # Capture the server response for CI
2377
+ pnpm run --silent sdk package publish --json > publish.json
2378
+ ```
2379
+
2380
+ **Errors:**
2381
+
2382
+ - `400` (`PLATFORM_REJECTED`, exit 3): each structured validation error (`{field, message}`) in the server's `errors[]` array is rendered as `field: message` on its own line. A version uploaded against the wrong publisher surfaces here
2383
+ - `401` and `403` (`PLATFORM_UNAUTHORISED`, exit 5): 'Invalid API key. Check $SERIOUSGAMING_API_KEY and that it has not been revoked.', or 'The account that created this API key does not have publishing access. Ask Business Simulations to grant it.'
2384
+ - `409` (`VERSION_EXISTS`, exit 3): 'Version {x.y.z} of {identifier} already exists. Bump it with `seriousgaming-sdk package version patch` and try again.'
2385
+ - Any other status (`PLATFORM_ERROR`, exit 5)
2386
+ - Network errors (`PLATFORM_UNREACHABLE`, exit 5) are surfaced with the original message
2387
+ - No API key (`API_KEY_MISSING`, exit 5), or no publisher (`PUBLISHER_MISSING`, exit 2)
2388
+
2389
+ ## Global settings
2390
+
2391
+ The `config` commands read and write the global settings file at `~/.seriousgaming-sdk/config.json`. It stores API keys and defaults once so every project picks them up, instead of re-exporting environment variables in each shell.
2392
+
2393
+ The file is the lowest-priority configuration source: a `.env` file, environment variables, and CLI flags all override it. See the [global settings guide](./guides/global-settings.md) for the full file shape, precedence rules, and security guidance.
2394
+
2395
+ The available keys are `elevenLabs.apiKey`, `platform.apiHost`, `platform.apiKey`, and `platform.defaultPublisher`.
2396
+
2397
+ ### Get a setting
2398
+
2399
+ ```text
2400
+ pnpm run sdk config get <key>
2401
+ ```
2402
+
2403
+ Print the value of a setting.
2404
+
2405
+ Prints the value of a single setting, or nothing if the key is valid but unset.
2406
+
2407
+ ```bash
2408
+ pnpm run sdk config get platform.apiKey
2409
+ ```
2410
+
2411
+ ### Set a setting
2412
+
2413
+ ```text
2414
+ pnpm run sdk config set <key> <value>
2415
+ ```
2416
+
2417
+ Set a setting in the global settings file.
2418
+
2419
+ Writes a single setting, creating the file (with owner-only permissions on macOS and Linux) if it does not exist.
2420
+
2421
+ ```bash
2422
+ pnpm run sdk config set platform.apiKey sg_...
2423
+ ```
2424
+
2425
+ ### Print the settings path
2426
+
2427
+ ```text
2428
+ pnpm run sdk config path
2429
+ ```
2430
+
2431
+ Print the resolved path to the global settings file.
2432
+
2433
+ Prints the resolved path to the global settings file. Honours the `SERIOUSGAMING_SDK_CONFIG` override.
2434
+
2435
+ ## Help and documentation
2436
+
2437
+ ### Command help
2438
+
2439
+ ```bash
2440
+ pnpm run sdk --help
2441
+ pnpm run sdk <command> --help
2442
+ ```
2443
+
2444
+ Shows the available commands and their options.
2445
+
2446
+ ### Print the SDK version
2447
+
2448
+ ```text
2449
+ pnpm run sdk version
2450
+ ```
2451
+
2452
+ Print the SDK version to stdout.
2453
+
2454
+ ```bash
2455
+ pnpm run --silent sdk version
2456
+ ```
2457
+
2458
+ Prints the installed SDK version to stdout.
2459
+
2460
+ ### Browse the documentation
2461
+
2462
+ ```text
2463
+ pnpm run sdk docs view [file]
2464
+ ```
2465
+
2466
+ View Serious Gaming SDK documentation.
2467
+
2468
+ ```text
2469
+ pnpm run sdk docs export [dir]
2470
+ ```
2471
+
2472
+ Export SDK documentation to your project.
2473
+
2474
+ The docs ship inside the package at `node_modules/@seriousgaming/sdk-tools/dist/docs`, so they match the installed version and need no network. Search them with your editor or `grep`.
2475
+
2476
+ - `docs view` with no argument prints the tree of documents; with a path (for example `guides/engine.md`) it prints that document
2477
+ - `docs export` copies every document to `dir`, or `.seriousgaming-sdk/docs` by default, so an editor or agent can search them locally
2478
+
2479
+ ## Environment variables
2480
+
2481
+ LLM access, speech, publishing, the global settings path, the port a background dev server starts from, the server session commands target (`SERIOUSGAMING_DEV_URL`), where the lock file lives (`SERIOUSGAMING_DEV_STATE_DIR`), and JSON output (`SERIOUSGAMING_JSON=1`) are all set through environment variables. [Environment variables](./environment-variables.md) lists every one. Logging is `--verbose`, a flag only.
2482
+
2483
+ ## Next steps
2484
+
2485
+ - [Drive a game from the CLI](./cli-simulation-testing.md): the session commands as one workflow
2486
+ - [Getting started](./getting-started.md): your first game
2487
+ - [Guides](./guides/index.md): each resource type and topic in depth
2488
+ - [Troubleshooting](./troubleshooting.md): symptoms, causes, and fixes