@skillcdn/console 0.4.0 → 0.4.2

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 (308) hide show
  1. package/README.md +20 -2
  2. package/SKILLCDN.md +7 -5
  3. package/dist/api.d.ts +5 -4
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/api.js +5 -4
  6. package/dist/api.js.map +1 -1
  7. package/dist/cli/cli.d.ts.map +1 -1
  8. package/dist/cli/cli.js +579 -45
  9. package/dist/cli/cli.js.map +1 -1
  10. package/dist/cli/format.d.ts +34 -3
  11. package/dist/cli/format.d.ts.map +1 -1
  12. package/dist/cli/format.js +277 -6
  13. package/dist/cli/format.js.map +1 -1
  14. package/dist/cli/help.d.ts.map +1 -1
  15. package/dist/cli/help.js +60 -7
  16. package/dist/cli/help.js.map +1 -1
  17. package/dist/cli/plugin-check.d.ts +21 -0
  18. package/dist/cli/plugin-check.d.ts.map +1 -0
  19. package/dist/cli/plugin-check.js +374 -0
  20. package/dist/cli/plugin-check.js.map +1 -0
  21. package/dist/cli/plugin-cli.d.ts +20 -0
  22. package/dist/cli/plugin-cli.d.ts.map +1 -0
  23. package/dist/cli/plugin-cli.js +123 -0
  24. package/dist/cli/plugin-cli.js.map +1 -0
  25. package/dist/cli/plugin-init.d.ts +20 -0
  26. package/dist/cli/plugin-init.d.ts.map +1 -0
  27. package/dist/cli/plugin-init.js +74 -0
  28. package/dist/cli/plugin-init.js.map +1 -0
  29. package/dist/cli/plugin-main.d.ts +3 -0
  30. package/dist/cli/plugin-main.d.ts.map +1 -0
  31. package/dist/cli/plugin-main.js +28 -0
  32. package/dist/cli/plugin-main.js.map +1 -0
  33. package/dist/client.d.ts +109 -2
  34. package/dist/client.d.ts.map +1 -1
  35. package/dist/client.js +67 -2
  36. package/dist/client.js.map +1 -1
  37. package/dist/compat.d.ts +1 -1
  38. package/dist/compat.js +2 -2
  39. package/dist/components/board.d.ts +8 -0
  40. package/dist/components/board.d.ts.map +1 -1
  41. package/dist/components/board.js +59 -15
  42. package/dist/components/board.js.map +1 -1
  43. package/dist/components/event-feed.d.ts +1 -1
  44. package/dist/components/event-feed.d.ts.map +1 -1
  45. package/dist/components/event-feed.js +33 -1
  46. package/dist/components/event-feed.js.map +1 -1
  47. package/dist/components/markdown.d.ts.map +1 -1
  48. package/dist/components/markdown.js +6 -3
  49. package/dist/components/markdown.js.map +1 -1
  50. package/dist/components/pack-connect.d.ts +3 -1
  51. package/dist/components/pack-connect.d.ts.map +1 -1
  52. package/dist/components/pack-connect.js +4 -4
  53. package/dist/components/pack-connect.js.map +1 -1
  54. package/dist/components/pack-token.d.ts +3 -1
  55. package/dist/components/pack-token.d.ts.map +1 -1
  56. package/dist/components/pack-token.js +1 -1
  57. package/dist/components/pack-token.js.map +1 -1
  58. package/dist/components/people.d.ts.map +1 -1
  59. package/dist/components/people.js +14 -3
  60. package/dist/components/people.js.map +1 -1
  61. package/dist/components/plugin-frame.d.ts +52 -0
  62. package/dist/components/plugin-frame.d.ts.map +1 -0
  63. package/dist/components/plugin-frame.js +314 -0
  64. package/dist/components/plugin-frame.js.map +1 -0
  65. package/dist/components/plugin-settings.d.ts +38 -0
  66. package/dist/components/plugin-settings.d.ts.map +1 -0
  67. package/dist/components/plugin-settings.js +348 -0
  68. package/dist/components/plugin-settings.js.map +1 -0
  69. package/dist/components/remote-pictures.d.ts +6 -0
  70. package/dist/components/remote-pictures.d.ts.map +1 -0
  71. package/dist/components/remote-pictures.js +8 -0
  72. package/dist/components/remote-pictures.js.map +1 -0
  73. package/dist/components/shell.d.ts +2 -0
  74. package/dist/components/shell.d.ts.map +1 -1
  75. package/dist/components/shell.js +26 -1
  76. package/dist/components/shell.js.map +1 -1
  77. package/dist/components/sign-in.d.ts.map +1 -1
  78. package/dist/components/sign-in.js +49 -2
  79. package/dist/components/sign-in.js.map +1 -1
  80. package/dist/components/ui.d.ts +3 -2
  81. package/dist/components/ui.d.ts.map +1 -1
  82. package/dist/components/ui.js +6 -3
  83. package/dist/components/ui.js.map +1 -1
  84. package/dist/components/workspace.d.ts +7 -1
  85. package/dist/components/workspace.d.ts.map +1 -1
  86. package/dist/components/workspace.js +1 -1
  87. package/dist/components/workspace.js.map +1 -1
  88. package/dist/console.css +384 -5
  89. package/dist/console.d.ts.map +1 -1
  90. package/dist/console.js +146 -29
  91. package/dist/console.js.map +1 -1
  92. package/dist/data.d.ts +11 -2
  93. package/dist/data.d.ts.map +1 -1
  94. package/dist/data.js +35 -21
  95. package/dist/data.js.map +1 -1
  96. package/dist/i18n/en.d.ts +163 -1
  97. package/dist/i18n/en.d.ts.map +1 -1
  98. package/dist/i18n/en.js +174 -1
  99. package/dist/i18n/en.js.map +1 -1
  100. package/dist/i18n/ko.d.ts.map +1 -1
  101. package/dist/i18n/ko.js +154 -1
  102. package/dist/i18n/ko.js.map +1 -1
  103. package/dist/index.d.ts +3 -1
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +3 -1
  106. package/dist/index.js.map +1 -1
  107. package/dist/limits.d.ts +14 -0
  108. package/dist/limits.d.ts.map +1 -1
  109. package/dist/limits.js +14 -0
  110. package/dist/limits.js.map +1 -1
  111. package/dist/plugin/args.d.ts +148 -0
  112. package/dist/plugin/args.d.ts.map +1 -0
  113. package/dist/plugin/args.js +369 -0
  114. package/dist/plugin/args.js.map +1 -0
  115. package/dist/plugin/bridge.d.ts +146 -0
  116. package/dist/plugin/bridge.d.ts.map +1 -0
  117. package/dist/plugin/bridge.js +125 -0
  118. package/dist/plugin/bridge.js.map +1 -0
  119. package/dist/plugin/capabilities.d.ts +20 -0
  120. package/dist/plugin/capabilities.d.ts.map +1 -0
  121. package/dist/plugin/capabilities.js +133 -0
  122. package/dist/plugin/capabilities.js.map +1 -0
  123. package/dist/plugin/context.d.ts +374 -0
  124. package/dist/plugin/context.d.ts.map +1 -0
  125. package/dist/plugin/context.js +2 -0
  126. package/dist/plugin/context.js.map +1 -0
  127. package/dist/plugin/define.d.ts +208 -0
  128. package/dist/plugin/define.d.ts.map +1 -0
  129. package/dist/plugin/define.js +482 -0
  130. package/dist/plugin/define.js.map +1 -0
  131. package/dist/plugin/definition-error.d.ts +11 -0
  132. package/dist/plugin/definition-error.d.ts.map +1 -0
  133. package/dist/plugin/definition-error.js +17 -0
  134. package/dist/plugin/definition-error.js.map +1 -0
  135. package/dist/plugin/limits.d.ts +67 -0
  136. package/dist/plugin/limits.d.ts.map +1 -0
  137. package/dist/plugin/limits.js +70 -0
  138. package/dist/plugin/limits.js.map +1 -0
  139. package/dist/plugin/load-worker.d.ts +7 -0
  140. package/dist/plugin/load-worker.d.ts.map +1 -0
  141. package/dist/plugin/load-worker.js +41 -0
  142. package/dist/plugin/load-worker.js.map +1 -0
  143. package/dist/plugin/load.d.ts +18 -0
  144. package/dist/plugin/load.d.ts.map +1 -0
  145. package/dist/plugin/load.js +62 -0
  146. package/dist/plugin/load.js.map +1 -0
  147. package/dist/plugin/manifest.d.ts +237 -0
  148. package/dist/plugin/manifest.d.ts.map +1 -0
  149. package/dist/plugin/manifest.js +411 -0
  150. package/dist/plugin/manifest.js.map +1 -0
  151. package/dist/plugin/refusals.d.ts +49 -0
  152. package/dist/plugin/refusals.d.ts.map +1 -0
  153. package/dist/plugin/refusals.js +79 -0
  154. package/dist/plugin/refusals.js.map +1 -0
  155. package/dist/plugin/rest-schemas.d.ts +556 -0
  156. package/dist/plugin/rest-schemas.d.ts.map +1 -0
  157. package/dist/plugin/rest-schemas.js +262 -0
  158. package/dist/plugin/rest-schemas.js.map +1 -0
  159. package/dist/plugin/sandbox.d.ts +23 -0
  160. package/dist/plugin/sandbox.d.ts.map +1 -0
  161. package/dist/plugin/sandbox.js +99 -0
  162. package/dist/plugin/sandbox.js.map +1 -0
  163. package/dist/plugin/testing.d.ts +45 -0
  164. package/dist/plugin/testing.d.ts.map +1 -0
  165. package/dist/plugin/testing.js +420 -0
  166. package/dist/plugin/testing.js.map +1 -0
  167. package/dist/plugin/web-react.d.ts +11 -0
  168. package/dist/plugin/web-react.d.ts.map +1 -0
  169. package/dist/plugin/web-react.js +34 -0
  170. package/dist/plugin/web-react.js.map +1 -0
  171. package/dist/plugin/web.d.ts +95 -0
  172. package/dist/plugin/web.d.ts.map +1 -0
  173. package/dist/plugin/web.js +245 -0
  174. package/dist/plugin/web.js.map +1 -0
  175. package/dist/plugin-nav.d.ts +29 -0
  176. package/dist/plugin-nav.d.ts.map +1 -0
  177. package/dist/plugin-nav.js +92 -0
  178. package/dist/plugin-nav.js.map +1 -0
  179. package/dist/plugin-testing.d.ts +2 -0
  180. package/dist/plugin-testing.d.ts.map +1 -0
  181. package/dist/plugin-testing.js +4 -0
  182. package/dist/plugin-testing.js.map +1 -0
  183. package/dist/plugin-web-react.d.ts +3 -0
  184. package/dist/plugin-web-react.d.ts.map +1 -0
  185. package/dist/plugin-web-react.js +3 -0
  186. package/dist/plugin-web-react.js.map +1 -0
  187. package/dist/plugin-web.d.ts +4 -0
  188. package/dist/plugin-web.d.ts.map +1 -0
  189. package/dist/plugin-web.js +5 -0
  190. package/dist/plugin-web.js.map +1 -0
  191. package/dist/plugin.d.ts +11 -0
  192. package/dist/plugin.d.ts.map +1 -0
  193. package/dist/plugin.js +15 -0
  194. package/dist/plugin.js.map +1 -0
  195. package/dist/router.d.ts +28 -0
  196. package/dist/router.d.ts.map +1 -1
  197. package/dist/router.js +47 -0
  198. package/dist/router.js.map +1 -1
  199. package/dist/routes.d.ts +6 -0
  200. package/dist/routes.d.ts.map +1 -1
  201. package/dist/routes.js +8 -0
  202. package/dist/routes.js.map +1 -1
  203. package/dist/schemas.d.ts +147 -1
  204. package/dist/schemas.d.ts.map +1 -1
  205. package/dist/schemas.js +55 -5
  206. package/dist/schemas.js.map +1 -1
  207. package/dist/tokens.d.ts +2 -0
  208. package/dist/tokens.d.ts.map +1 -0
  209. package/dist/tokens.js +101 -0
  210. package/dist/tokens.js.map +1 -0
  211. package/dist/vocabulary.d.ts +11 -1
  212. package/dist/vocabulary.d.ts.map +1 -1
  213. package/dist/vocabulary.js +24 -0
  214. package/dist/vocabulary.js.map +1 -1
  215. package/dist/web.d.ts.map +1 -1
  216. package/dist/web.js +5 -2
  217. package/dist/web.js.map +1 -1
  218. package/docs/specs/cli.md +16 -9
  219. package/docs/specs/plugins.md +487 -0
  220. package/docs/specs/rest.md +25 -13
  221. package/package.json +21 -3
  222. package/skills/building-a-console/SKILL.md +25 -26
  223. package/skills/building-a-console/references/configuring.md +40 -0
  224. package/skills/building-a-console/references/the-default-console.md +1 -1
  225. package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
  226. package/skills/building-a-plugin/SKILL.md +137 -0
  227. package/skills/deploying-a-console/SKILL.md +24 -26
  228. package/skills/deploying-a-console/references/access.md +13 -9
  229. package/skills/deploying-a-console/references/resources.md +25 -10
  230. package/skills/deploying-a-console/references/running-it-again.md +15 -0
  231. package/skills/deploying-a-console/references/sizing.md +2 -0
  232. package/skills/planning-the-board/SKILL.md +96 -0
  233. package/skills/planning-the-board/references/a-task.md +36 -0
  234. package/skills/planning-the-board/references/setting-up.md +44 -0
  235. package/skills/working-the-board/SKILL.md +43 -34
  236. package/skills/working-the-board/references/the-pack.md +19 -0
  237. package/src/api.ts +16 -0
  238. package/src/cli/cli.ts +736 -45
  239. package/src/cli/format.ts +356 -9
  240. package/src/cli/help.ts +71 -6
  241. package/src/cli/plugin-check.ts +470 -0
  242. package/src/cli/plugin-cli.ts +148 -0
  243. package/src/cli/plugin-init.ts +90 -0
  244. package/src/cli/plugin-main.ts +30 -0
  245. package/src/client.ts +324 -1
  246. package/src/compat.ts +2 -2
  247. package/src/components/board.tsx +62 -14
  248. package/src/components/event-feed.tsx +42 -1
  249. package/src/components/markdown.tsx +6 -3
  250. package/src/components/pack-connect.tsx +10 -7
  251. package/src/components/pack-token.tsx +4 -1
  252. package/src/components/people.tsx +17 -7
  253. package/src/components/plugin-frame.tsx +437 -0
  254. package/src/components/plugin-settings.tsx +1111 -0
  255. package/src/components/remote-pictures.ts +19 -0
  256. package/src/components/shell.tsx +82 -14
  257. package/src/components/sign-in.tsx +114 -13
  258. package/src/components/ui.tsx +6 -3
  259. package/src/components/workspace.tsx +18 -0
  260. package/src/console.tsx +309 -23
  261. package/src/data.ts +47 -22
  262. package/src/i18n/en.ts +183 -1
  263. package/src/i18n/ko.ts +159 -1
  264. package/src/index.ts +12 -0
  265. package/src/limits.ts +14 -0
  266. package/src/plugin/args.ts +526 -0
  267. package/src/plugin/bridge.ts +314 -0
  268. package/src/plugin/capabilities.ts +150 -0
  269. package/src/plugin/context.ts +416 -0
  270. package/src/plugin/define.ts +792 -0
  271. package/src/plugin/definition-error.ts +19 -0
  272. package/src/plugin/limits.ts +70 -0
  273. package/src/plugin/load-worker.ts +50 -0
  274. package/src/plugin/load.ts +82 -0
  275. package/src/plugin/manifest.ts +561 -0
  276. package/src/plugin/refusals.ts +94 -0
  277. package/src/plugin/rest-schemas.ts +354 -0
  278. package/src/plugin/sandbox.ts +123 -0
  279. package/src/plugin/testing.ts +612 -0
  280. package/src/plugin/web-react.ts +45 -0
  281. package/src/plugin/web.ts +403 -0
  282. package/src/plugin-nav.ts +132 -0
  283. package/src/plugin-testing.ts +10 -0
  284. package/src/plugin-web-react.ts +4 -0
  285. package/src/plugin-web.ts +35 -0
  286. package/src/plugin.ts +126 -0
  287. package/src/router.ts +73 -0
  288. package/src/routes.ts +9 -0
  289. package/src/schemas.ts +60 -4
  290. package/src/styles/console.css +384 -5
  291. package/src/tokens.ts +101 -0
  292. package/src/vocabulary.ts +30 -0
  293. package/src/web.ts +5 -2
  294. package/template/plugin/.gitignore +2 -0
  295. package/template/plugin/README.md +26 -0
  296. package/template/plugin/SKILLCDN.md +13 -0
  297. package/template/plugin/build.mjs +40 -0
  298. package/template/plugin/console-plugin.json +50 -0
  299. package/template/plugin/package.json +27 -0
  300. package/template/plugin/skills/__key__/SKILL.md +23 -0
  301. package/template/plugin/src/check.test.ts +34 -0
  302. package/template/plugin/src/server/index.test.ts +41 -0
  303. package/template/plugin/src/server/index.ts +72 -0
  304. package/template/plugin/src/web/index.css +51 -0
  305. package/template/plugin/src/web/index.ts +115 -0
  306. package/template/plugin/tsconfig.json +17 -0
  307. package/skills/working-the-board/references/setting-up.md +0 -3
  308. /package/skills/{working-the-board → planning-the-board}/references/project-rules.md +0 -0
@@ -0,0 +1,137 @@
1
+ ---
2
+ name: building-a-plugin
3
+ description: "Writes a plugin of the SkillCDN Console: one npm package that adds a feature of an organization's work (leave, customers, money) to a console, under its rule that people decide and agents work. The feature is split into commands and queries an agent runs, decisions and tasks people answer on a board project, hooks and jobs the console runs itself, and pages people see; written from console-plugin init, kept in records or a schema of its own, run on a local console, tested and checked as a console would, published or tagged, and handed to the deployment. Use when a person says 'write a plugin for leave', 'add a feature to the console for our customers', 'the console should track X'. For a console's own pages, use building-a-console; to install a plugin, deploying-a-console; to work one, working-the-board. docs/specs/plugins.md is the reference for every field, bound and refusal; this skill says what to ask, how to split the work and how it is checked. Nothing is spent or published without the person's word."
4
+ license: MIT
5
+ compatibility: A shell with Node.js 24 or newer, npm and git, and an agent that reads and writes files. For running the plugin on a local console, Docker for the database and a checkout of the console's repository, where signing in needs a GitHub OAuth app of the person's own, as the console's README says. The console command is optional, for running the plugin's commands as an agent would. No key of the agent's own is needed, and it never sees a token.
6
+ metadata:
7
+ author: skillcdn
8
+ version: "1.0"
9
+ skillcdn:
10
+ translations:
11
+ ko:
12
+ title: 콘솔 플러그인 만들기
13
+ description: "SkillCDN 콘솔의 플러그인을 만듭니다. 조직 업무의 기능 하나(연차, 고객, 돈)를 npm 패키지 하나로 콘솔에 더하되, 사람은 정하고 에이전트는 일한다는 콘솔의 규칙 아래 둡니다. 기능을 에이전트가 돌리는 명령과 조회, 보드 프로젝트에서 사람이 답하는 결정과 작업, 콘솔이 스스로 돌리는 훅과 잡, 사람이 보는 페이지와 위젯으로 나누고, console-plugin init으로 시작해, 콘솔이 보관하는 레코드나 자기 스키마에 데이터를 두고, 패키지를 PLUGINS_DIR에 링크한 로컬 콘솔에서 돌리고, 패키지의 테스트 컨텍스트와 console-plugin check로 검사하고, 발행하거나 태그를 붙이고, 배포에 설치 작업으로 넘깁니다. '연차 플러그인 만들어줘', '고객 관리 기능을 콘솔에 넣어줘', '콘솔이 X를 추적하면 좋겠어'라고 할 때 쓰세요. 콘솔의 페이지 자체는 building-a-console, 플러그인 설치는 deploying-a-console, 보드에서 플러그인으로 일하기는 working-the-board를 쓰세요. 필드·한도·거절의 기준은 docs/specs/plugins.md이고, 이 스킬은 무엇을 묻고, 일을 어떻게 나누고, 어떻게 검사하는지를 말합니다. 비용은 들지 않고, 사람의 말 없이는 발행하지 않습니다."
14
+ ---
15
+ # Building a plugin of the console
16
+
17
+ What goes in: a feature of the organization's work that the console should carry, in the person's words, and who uses it. What comes out: a repository holding one npm package, the plugin: its manifest, its server module, its web bundle where people look at it, its skills, its tests and its build, checked as a console would check it; and the install, handed to the deployment as a task, since a plugin reaches a console by being built into its image and turned on by an administrator ([ADR-0049](/docs/adr/0049-a-plugin-is-an-npm-package-installed-into-the-image-and-enabled-by-an-administrator.md)). The console holds the people, the projects, the board, the records and the frames; the plugin holds the feature's logic and nothing of the console's. It runs in a host the console keeps apart from everything else and reaches the console only through what its manifest declared and an administrator consented to ([ADR-0050](/docs/adr/0050-plugin-code-runs-in-a-host-process-of-its-own-and-reaches-the-console-only-through-the-capabilities-it-declared.md)); so the first work is deciding what the feature asks for, and asking for the least.
18
+
19
+ The contract is the [specification of plugins](/docs/specs/plugins.md): every field of the manifest, every member of `ctx`, every capability with what it opens, every bound and every refusal. This skill repeats none of them; where it and the specification differ, the specification is right.
20
+
21
+ ## How the person is involved
22
+
23
+ - **Questions:** one message, before anything is written, with what the request leaves open under "Inputs": what the feature does and for whom, what a person must decide and what an agent may do alone, where it reaches (a board, which projects, people, an outside service), what it keeps, its name. Plain words; the person need not be a developer.
24
+ - **Checkpoints:** the design (phase 1), as the table of the split and the manifest's capabilities read aloud as the sentences an administrator will consent to; the pages, once they run on a local console (phase 4), where a browser can show them; publishing or tagging (phase 5), named with where it goes. One short message each, with the recommendation, and "OK" continues.
25
+ - **Go-ahead:** when the person says to go ahead alone, the design is still shown before code, and publishing is still confirmed; the rest needs no answer.
26
+ - **Language:** the conversation's in chat; code, identifiers, commit messages and the plugin's skills in English; what people read of the plugin (its name, description, agent note, roles, settings, pages) in every language the organization uses, through the manifest's language maps, `en` first.
27
+
28
+ ## Requirements
29
+
30
+ | Need | How | When missing |
31
+ |---|---|---|
32
+ | Node.js 24 or newer, npm, git | `node --version`, `npm --version`, `git --version` | Stop and tell the person: the console's runtime is Node.js 24, and the check runs the plugin as the console would. |
33
+ | The authoring command | `console-plugin`, a second command of `@skillcdn/console`: with the package installed globally (`npm install -g @skillcdn/console`, which also gives the console command), or `npx --package @skillcdn/console console-plugin ...` | `npm install -g @skillcdn/console`; `console-plugin help` says its two commands. |
34
+ | The specification and the types | In the plugin's own `node_modules/@skillcdn/console/` once installed (phase 2): `docs/specs/plugins.md`, the package's `README.md`, the types under `src/plugin/`, and this skill under `skills/building-a-plugin/`; or through the connection that served this skill | Read them there; this skill is a map of them. |
35
+ | A local console to run the plugin on (phase 4) | A checkout of the console's repository with Docker for its database, run as its README's "Install" says, with `PLUGINS_DIR` set; signing in there needs a GitHub OAuth app of the person's own, registered for the local address as `apps/console/README.md` of the console says | Phases 1 to 3 and 5 need none; phase 4 is then reported as not run, and the plugin is proven on the deployment's console after the install. |
36
+ | The console command, signed in on that console | `console login --url http://127.0.0.1:11199`, approved on the local console's pages; `console whoami` | Optional: the plugin's commands are tried on the pages, or through the frame alone. |
37
+
38
+ ## Where to read, in this order
39
+
40
+ 1. [docs/specs/plugins.md](/docs/specs/plugins.md): "Words", "The package", "The manifest", "The server module", "The context", "Capabilities", "Roles and grants", "Settings", "Records" or "SQL", "The board", "Events, hooks and jobs", then "Pages" when the plugin has any, and "Security model", "Limits and refusals" and "Authoring" before the check. Every name this skill uses is defined there.
41
+ 2. The plugin `console-plugin init` writes (phase 2): a manifest, a server module with one collection, one command and one query, a web bundle with one page and one widget, one skill, the tests and the build, each file saying what it is for. It is the smallest complete plugin, and `examples/plugin-hello` of the console's repository is the same plugin built.
42
+ 3. The package's README, "A plugin": the shape of `definePlugin` and its definers, `connect()` of the web bundle, `createTestContext` of the tests and the two commands, in a page.
43
+ 4. [deploy/README.md, "Plugins in the image"](/deploy/README.md#plugins-in-the-image): how a deployment installs the plugin, and what `storage:sql`, an `outbound` host or files ask of it, which the hand-over of phase 5 names.
44
+ 5. The decisions, for why: [ADR-0051](/docs/adr/0051-a-plugin-keeps-its-data-in-records-the-console-holds-or-in-a-schema-of-its-own-by-consent.md) (records or a schema), [ADR-0052](/docs/adr/0052-a-plugins-board-is-a-project-made-or-chosen-when-it-is-enabled-and-what-it-puts-there-carries-its-name.md) (the board project), [ADR-0053](/docs/adr/0053-agents-reach-a-plugin-through-one-command-whose-definitions-the-console-holds.md) (one command for agents), [ADR-0054](/docs/adr/0054-a-plugins-pages-run-in-a-sandboxed-frame-and-speak-only-to-their-plugin.md) (the frames).
45
+
46
+ ## Inputs
47
+
48
+ | Input | Source |
49
+ |---|---|
50
+ | The feature | What the person said, in chat or in a document they point to: what the organization does today by hand or in a spreadsheet, and what the console should carry of it. One feature is one plugin; two features that share nothing are two. |
51
+ | Who uses it | Asked: "Who uses this: everyone, or some people? What may each kind of person do?" Each kind is a role of the manifest; a role everyone holds is `defaultRole`; the rest are granted by the administrator. Up to the bound the specification names. |
52
+ | What people decide | Asked: "What must a person approve or choose here, and who?" Each is a decision the plugin raises on its board project, addressed to the person the setting or the record names; the plugin never decides it. A feature where nobody decides anything has `board.needs: "none"`. |
53
+ | What agents do, and read | Derived from the feature: each thing an agent does for a person is a command (it writes), each thing it looks up a query (it reads); named as verbs and nouns an agent would type after `console plugin <key>`, with arguments in the console's own forms (a text, a number, a day, a person by login, a project by key, a file, a record). |
54
+ | What happens on its own | Derived: what the console must tell the plugin (a decision answered, a task done, a member added) is a hook on the console's events; what another plugin tells it is a hook on that plugin's events; what time brings (a balance renewed, a reminder) is a job on a schedule; what an outside service sends is a webhook endpoint; what the plugin must fetch is an outbound request to a host the manifest names, with a secret the administrator sets. |
55
+ | Where it reaches | Asked when the feature does not say: "Does it belong to one project, to some, or to every project of the workspace?" Pages at the workspace level, at the project level, or both; the projects the plugin is on named by the administrator at enable. |
56
+ | What it keeps | Derived: the collections, each with its fields, what is filtered and ordered by (`indexes`), searched (`search`), who owns a record (`owner`), whether a record belongs to a project, whether history is kept, who may read; or a schema of its own with `storage:sql`, by the rule of phase 1. |
57
+ | The key and the name | The key from the feature's one word, lowercase, as the specification's rule and reserved words allow (`leave`, `crm`, `finance`); the name and the description per language; the agent note, one sentence an agent reads with the rules: what the plugin is for and when to use it. Shown at the design checkpoint and changed on request. |
58
+ | Where the code lives, and how it ships | Asked: "A repository of its own on GitHub, under which account? Published to npm, or installed from the repository's tag?" Default: a repository `console-plugin-<key>`, and publishing when the person has an npm account, a tag otherwise. |
59
+ | The deployment | Asked: "Which console is this for, and who runs its deployment?" The install is a task for them (phase 5). |
60
+ | Everything else | Derived and shown in the design: the capabilities from the split, the settings (a number the organization chooses, a person who approves, a secret for an outside service), the board project's defaults (its key, name and description), the menu group, the pages and the widgets with their roles, `dataVersion: 1`. |
61
+
62
+ ## Workflow
63
+
64
+ Each phase produces something the person can look at and ends with one line to them; phases 1, 4 and 5 stop for a checkpoint.
65
+
66
+ ### Phase 1: Split the feature so that agents work and people decide
67
+
68
+ Produces the **design**: one table, a row per thing the feature does, saying who starts it and what it becomes in the plugin. The questions to ask of each row, in this order:
69
+
70
+ | The row | What it becomes |
71
+ |---|---|
72
+ | An agent does it for a person, and something changes | A **command**: `defineCommand({ about, args, roles, run })`. Its `text` answer is for people, its `data` for agents (`--json`); it does one thing and does it again safely where it can. |
73
+ | An agent, or a page, looks something up | A **query**: `defineQuery(...)`, which writes nothing; a query may answer a file. |
74
+ | A person must approve, choose or confirm | A **decision** the command raises with `ctx.console.decisions.ask`, with options, addressed to the person a setting or the record names (`deciderId`), `about` the record it is for; the command answers `waiting`, the agent waits as the console's own `ask` is waited for, and the plugin hears the answer in a hook on `console:decision.answered` and goes on. The plugin never answers one, and never computes what a person must choose. |
75
+ | Someone must do something later | A **task** on the board project with `ctx.console.tasks.create`, marked as the plugin's, worked by whoever takes it. |
76
+ | The console must tell the plugin something happened | A **hook**, `on["console:<kind>"]` (`events:console`): the plugin's board project, the projects it is on and the items it made are what it hears of. Delivery is at least once: a handler reads the record first and does nothing twice. |
77
+ | Another plugin must tell it | A **hook** on `plugin:<key>:<kind>` (`events:plugins`), or a call on the other's `api` (`plugins:<key>`). |
78
+ | Time passes | A **job** (`jobs`), `every` or `at` in the workspace's time zone, never two at once; what it did goes into records and, where people should know, a task or a decision. |
79
+ | An outside service calls in | An **endpoint** (`endpoints`): a `person` endpoint for whoever holds a role, a `webhook` for nobody, verified with `ctx.secrets.verify` against a secret setting. |
80
+ | The plugin must call out | `ctx.fetch` to a host the manifest names (`outbound:<host>`), the secret put in by the console from a `secret` setting, never read by the plugin. |
81
+ | People look at it, or fill a form | A **page** (level `workspace` or `project`) or a **widget** on the front page: it shows records and calls commands and queries through the bridge; the logic stays in the server module, so that the command line and the pages do the same thing. |
82
+ | An agent must know how | A **skill** in the package, one per kind of work, naming the commands as `console plugin <key> <command>`, with the rules of that work; `SKILLCDN.md` at the root. The agent note of the manifest is the one line that sends an agent to them. |
83
+
84
+ Then the data. Records, unless the feature needs what records do not give: joins across many collections, more rows than a plugin's records may hold, or figures beyond counting, summing, the least and the most; then `storage:sql`, a schema and a role of the plugin's own with migrations under `migrations/`, which needs the deployment's database user to hold `CREATEROLE` and is said in the hand-over. Records are the default: the console keeps them with versions, files, links, search and aggregates, exports and purges them, and shows them to people by role; a schema is the plugin's to keep.
85
+
86
+ Then the capabilities: the list the split needs, each read as the sentence the administrator will consent to (the specification's catalogue says what each opens and what a malicious plugin could do with it). A capability no row needs is dropped; `people:read:all`, `projects:read:all`, `storage:sql` and every `outbound:<host>` are asked for only where the design shows the row that needs them. Write the design: the table, the collections with their fields, the roles, the settings, the board (`needs`, the defaults), the menu group, the pages and the widgets, and the capabilities with their sentences; stop for the checkpoint.
87
+
88
+ ### Phase 2: Start from the template, then fill it
89
+
90
+ Produces the **plugin as source**. `console-plugin init <dir> --key <key> --name "<Name>"` writes the smallest complete plugin: the manifest, a server module with one collection, one command and one query, a web bundle with one page and one widget, one skill with its `SKILLCDN.md`, the tests, the build and a README that says where things are; `--console` names the version of `@skillcdn/console` it depends on, the command's own unless said, and for a plugin written for a deployed console, the version that console runs (`console whoami` says it). Then, in `<dir>`: `npm install`, `npm run build`, `npm test`, `npm run check`, all green before a line is changed, and `git init`.
91
+
92
+ Then the design goes in, file by file: the manifest `console-plugin.json` from the design, its `key`, names and descriptions per language, `agentNote`, `capabilities`, `roles` and `defaultRole`, `board`, `settings`, `menu`, `pages`, `widgets`, `dataVersion`; the server module `src/server/index.ts`, one `defineCollection` per collection, one `defineCommand` or `defineQuery` per row, `endpoints`, `on`, `jobs`, `api`, `onEnable` and `onDisable` where the design has them, exported by name for the tests and gathered in `definePlugin(...)` as the default export; the web bundle `src/web/index.ts` and `index.css`, where `connect()` gives the page the context (who looks, at what, in which language), `command`, `query`, `records`, `files`, `navigate`, `download`, `setTitle` and the events, every class prefixed `pl-<key>-`, the console's `sc-` classes and `--sc-*` tokens for the look, and every word a person wrote put in as text; the skills under `skills/<name>/SKILL.md`, one per kind of work, in the SkillCDN Format, and the README of the package. The server module imports only what the host allows and bundles everything it imports; it keeps no state between calls but in records, since its thread is started afresh after a fault. Where the design has `storage:sql`, `migrations/0001-<words>.sql` makes the tables, and `manifest.migrations` names the directory.
93
+
94
+ ### Phase 3: Test it as the console runs it
95
+
96
+ Produces the **tests**, green. `src/server/index.test.ts` runs each command, query, hook and job against `createTestContext({ collections, plugin, caller, console, secrets, ... })` from `@skillcdn/console/plugin/testing`: the records in memory, checked against the schema and the indexes as the console checks them; the console answering what the test provided (a person, a project, a task) and refusing the rest as `plugin.forbidden`; `emitted` and `logged` to assert on; `as(caller)` to run the same store as another person. One test per row of the design: what it does, what it refuses (a schema it does not fit, a role it lacks, a record another owns), and what it tells people. The template's second test builds the package and runs `console-plugin check` on it, as a console or a store would, and fails on anything the console would refuse the plugin for: an invalid manifest, a server module that does not load under the host's hardening or names what the manifest does not declare, a built-in the host refuses, a web bundle that is not self-contained, a license that is not permissive, `files` that leave out what runs. `npm run typecheck`, `npm test` and `npm run check` are the three that pass before phase 4, and before every commit.
97
+
98
+ ### Phase 4: Run it on a local console
99
+
100
+ Produces the **plugin running**, enabled, with its commands tried and its pages shown. The console from its checkout, as the console's README's "Install" says, with one difference: before the api starts, `.env` sets `PLUGINS_DIR` to a directory made for the purpose, whose `package.json` is `{ "private": true, "dependencies": {} }` and in which `npm install --ignore-scripts <path to the plugin>` writes the plugin as a `file:` dependency and links `node_modules/<its name>` to the checkout, built; the console reads the link where it points, so the checkout is the plugin, and `npm run build` in it followed by a restart of the api is a new version. The person signs in on the local console as its administrator (`ADMINS` in `.env`, a GitHub OAuth app registered for the local address, as `apps/console/README.md` of the console says), opens the plugins' settings page, where the plugin is listed as installed with any reason it failed to load, and enables it: the consent to each capability, the board project made from the manifest's defaults or chosen, the projects it is on, the settings; or their agent does the same with `console plugin <key> enable` ([setting-up.md](/skills/planning-the-board/references/setting-up.md), "A plugin's board project"), signed in there with `console login`. The compose database's user owns the database, so `storage:sql` is available locally. Then the tabs appear where the manifest put them, and the plugin is worked as an agent works it ([working-the-board](/skills/working-the-board/SKILL.md)): `console plugin <key>` lists the commands, `console plugin <key> <command> ...` runs one, a decision raised is answered on the pages, a hook runs, a job runs at its time (the worker inside the api, locally), and the pages are looked at with a browser the agent can drive, else by the person. What is seen is shown at the checkpoint; what could not run is reported as not run.
101
+
102
+ ### Phase 5: Ship, and hand the install to the deployment
103
+
104
+ Produces the **release** and the **hand-over**. The version in `package.json` moves as semver says, `dataVersion` moves with an entry in `upgrades` whenever the shape of a record changed; `npm run build`, `npm test` and `npm run check` pass; a commit, and a push of the repository. Then one of two, confirmed with its consequence: `npm publish` (`--access public` for a scoped name), which puts the package on the registry under that name for good, and is done by the person or with their word; or a tag `v<version>` whose tree carries the built package (the template's `.gitignore` leaves `dist/` out of git, so a release commit adds it with `git add -f dist` before the tag), since the deployment installs with no scripts and runs no build. The deployment then names the plugin as `"<name>": "<version>"` or `"github:<owner>/<repo>#v<version>"`.
105
+
106
+ The hand-over is a task on the deployment's board, written as [planning-the-board](/skills/planning-the-board/SKILL.md) writes one, or the same words in chat where there is no board: the package and its version, and how it is installed ([deploy/README.md, "Plugins in the image"](/deploy/README.md#plugins-in-the-image), a row of deploying-a-console's [running-it-again.md](/skills/deploying-a-console/references/running-it-again.md)); what the deployment must provide, where the plugin asks it (`CREATEROLE` for `storage:sql`, the hosts of `outbound`, a bound on the files); what the administrator does when they enable it: the capabilities they consent to, the board project, the projects, the settings to fill and the secrets to set, the roles and who gets them; and where the plugin's skills are once it runs (`console plugin <key> skills`). The deployment's agent installs it as a release of the deployment; the administrator enables it; the plugin is then proven on that console as phase 4 proved it locally, and that is the delivery.
107
+
108
+ ## Verdicts
109
+
110
+ | Result | Verdict |
111
+ |---|---|
112
+ | `npm run typecheck`, `npm test` and `npm run check` pass; the design's every row is a command, a query, a decision, a task, a hook, a job, an endpoint, a page or a widget of the plugin, with a test; the plugin was enabled on a local console, its commands ran and its pages were shown; the package is published or tagged and the install is a task of the deployment | Accepted |
113
+ | A local console could not run (no Docker, no OAuth app), so phase 4 is reported as not run and the proof waits for the deployment's console | Accepted, with the gap named in the delivery |
114
+ | `console-plugin check` warns of a capability no code uses, a class without the prefix or a private package, and the person wants it so | Accepted, with the warning named |
115
+ | The plugin decides what a person should (an approval computed, a decision answered), reaches what it did not declare, reads a secret's value, renders what people wrote as HTML, or keeps state outside records; `check` fails; a capability is asked for without a row that needs it | Redone |
116
+
117
+ ## Hard rules
118
+
119
+ - People decide: a plugin raises decisions and waits; it never answers one, never acts as a person it is not, and never computes an approval. What it makes on a board carries its name and is reversible by people.
120
+ - The fewest capabilities: each one in the manifest has a row of the design that needs it, and the sentence an administrator reads for it is true of the code. `check` warns of one no definition uses; it is dropped.
121
+ - A secret is a `secret` setting the administrator sets, put into a request by the console or verified by it; the plugin never reads its value, logs it, keeps it in a record, or puts it in the repository, the tests or the chat.
122
+ - Everything a person wrote is shown as text, in the pages and in a command's answer; the frame renders no HTML from a record, and its classes are prefixed `pl-<key>-`.
123
+ - The server module imports nothing the host refuses, reaches no network but `ctx.fetch`, no file but its own, and keeps no state between calls but in records; a hook does nothing twice.
124
+ - Nothing is published, tagged for an install or installed without the person's word; the install is the deployment's release, planned and started by its people, never by this skill.
125
+ - The specification wins: a field, a bound or a refusal is as [docs/specs/plugins.md](/docs/specs/plugins.md) says; the plugin is rebuilt against the console it runs on, since before 1.0 the plugin API moves with the console's line.
126
+
127
+ ## Terms
128
+
129
+ The specification's "Words" table defines the plugin's own: plugin, manifest, host, capability, role, board project, record and collection, command and query, endpoint, hook and job, frame and bridge, widget. Beside them here: **the design**, the table of phase 1, which the manifest and the tests are read against; **the split**, how a feature becomes rows of that table so that agents work and people decide; **the hand-over**, the task that carries the install to the deployment; **a local console**, the console from its checkout with the plugin linked into `PLUGINS_DIR`.
130
+
131
+ ## Beside this skill
132
+
133
+ - The contract, every field, bound and refusal: [docs/specs/plugins.md](/docs/specs/plugins.md).
134
+ - How a deployment installs a plugin: [deploy/README.md, "Plugins in the image"](/deploy/README.md#plugins-in-the-image); as a change to a deployed console, [deploying-a-console](/skills/deploying-a-console/SKILL.md), "Running it again".
135
+ - Enabling a plugin and its board project, for an administrator's agent: [planning-the-board](/skills/planning-the-board/SKILL.md), setting-up.md "A plugin's board project".
136
+ - Working a plugin on the board, as the agents who use it will: [working-the-board](/skills/working-the-board/SKILL.md).
137
+ - A console's own pages, which show the board and hold nothing of their own: [building-a-console](/skills/building-a-console/SKILL.md).
@@ -1,16 +1,16 @@
1
1
  ---
2
2
  name: deploying-a-console
3
- description: "Deploys a SkillCDN Console for an organization on AWS behind Cloudflare, by an agent on the person's machine, no developer needed: asks a few plain questions (how many people, the web address, how people sign in and who is in, where the organization's skills are, and its website, whose look the console takes by default), shows the monthly cost of the fitting size and the next before anything is made, creates the database, bucket, secrets, tunnel and containers, builds the organization's own look into the image, leaves a private repository that redeploys on every push, signs the first administrator in, makes the first project, connects an agent and proves it with a file handed in and read back. Also grows it to the next size, updates it to a new version of the console, adds a member or rotates a secret. Use when an agent is asked to deploy, set up, host, upgrade or resize a console for an organization. To build a console's pages, use building-a-console; to work tasks on a board, use working-the-board."
3
+ description: "Deploys a SkillCDN Console for an organization on AWS behind Cloudflare, from a person's machine, no developer needed: a few plain questions (how many people, the address, how people sign in and who is in, the organization's skills, its website, whose look it takes by default), the monthly cost before anything is made, then the database, bucket, secrets, tunnel and containers, a private repository that redeploys on every push, the first administrator signed in, the first project, an agent connected and proven with a file handed in. Also grows it, updates it to a new version, installs a plugin, adds a member or rotates a secret. Use when a person says 'set up a console for our company', 'deploy the console', 'host the console on AWS', 'upgrade our console', 'we need a bigger size'. To build the pages alone, use building-a-console; to write a plugin, building-a-plugin; to work tasks, working-the-board. Every purchase and push is confirmed with its price; nothing is spent without the person's word."
4
4
  license: MIT
5
5
  compatibility: Needs a shell with Node.js 24 or newer, git, curl, jq and the AWS CLI v2, the gh command signed in or an empty repository the person made, network access to AWS, Cloudflare, GitHub and npm, an AWS account and a domain on Cloudflare the person can create a key and a token for, and a browser on any device for the few steps only a signed-in person can take. Works in any agent that can run a shell and write files; on Windows, in Git Bash with path conversion off.
6
6
  metadata:
7
7
  author: skillcdn
8
- version: "1.4"
8
+ version: "1.7"
9
9
  skillcdn:
10
10
  translations:
11
11
  ko:
12
12
  title: 조직의 콘솔 배포하기
13
- description: "조직의 SkillCDN 콘솔을 AWS와 Cloudflare에 배포합니다. 개발자가 아니어도 됩니다: 에이전트가 쉬운 질문 몇 개(몇 명이 쓰는지, 주소, 로그인 방식과 구성원, 조직 스킬의 위치, 그리고 조직의 웹사이트. 웹사이트가 있으면 그 디자인이 콘솔의 기본이 됩니다)를 묻고, 맞는 크기와 그 다음 크기의 월 비용을 먼저 보여준 뒤, 데이터베이스·버킷·비밀·터널·컨테이너를 만들고, 조직의 디자인을 이미지에 담고, 푸시할 때마다 다시 배포되는 비공개 저장소를 남기고, 첫 관리자를 로그인시키고 첫 프로젝트를 만들고 에이전트를 연결해 파일 제출과 되읽기로 증명합니다. 규모 키우기, 콘솔 새 버전으로 올리기, 구성원 추가, 비밀 교체도 합니다. 조직의 콘솔을 배포·설치·호스팅·업그레이드·확장하라고 할 때 쓰세요. 콘솔 페이지를 만들 때는 building-a-console, 보드의 작업을 할 때는 working-the-board를 쓰세요."
13
+ description: "조직의 SkillCDN 콘솔을 AWS와 Cloudflare에 배포합니다. 개발자가 아니어도 됩니다: 쉬운 질문 몇 개(몇 명이 쓰는지, 주소, 로그인 방식과 구성원, 조직의 스킬, 디자인의 기본이 되는 웹사이트)를 묻고, 무엇을 만들기 전에 맞는 크기와 그 다음 크기의 월 비용을 보여준 뒤, 데이터베이스·버킷·비밀·터널·컨테이너를 만들고, 조직의 디자인을 이미지에 담고, 푸시마다 다시 배포되는 비공개 저장소를 남기고, 첫 관리자를 로그인시키고, 첫 프로젝트를 만들고, 에이전트를 연결해 파일 제출과 되읽기로 증명합니다. 규모 키우기, 새 버전으로 올리기, 이미지에 플러그인 설치, 구성원 추가, 비밀 교체도 합니다. '우리 회사 콘솔 세팅해줘', '콘솔 배포해줘', 'AWS에 콘솔 올려줘', '콘솔 업그레이드해줘', '더 큰 크기가 필요해', '연차 플러그인 설치해줘'라고 할 때 쓰세요. 페이지만 만들 때는 building-a-console, 플러그인을 만들 때는 building-a-plugin, 작업을 할 때는 working-the-board를 쓰세요. 구매와 푸시는 하나하나 가격과 함께 확인받고, 사람의 말 없이는 아무것도 쓰지 않습니다."
14
14
  ---
15
15
  # Deploying a console for an organization
16
16
 
@@ -20,10 +20,10 @@ What goes in: an AWS account, a domain on Cloudflare or one to buy, and the answ
20
20
 
21
21
  - **Questions:** one message at the start, the ones under "Inputs" that the request leaves open, in plain words. Then a second message, so that the questions stay short: the order of cost for the size ([sizing.md](references/sizing.md)'s snapshot, named as such) and the two credentials to create ([access.md](references/access.md)), so that the person knows what it costs before an account or a key exists, and the credentials are ready when the plan is.
22
22
  - **Checkpoints:** the plan with today's cost, before anything is made; each purchase with its price (a domain); a secret only the person holds, asked with the exact steps once the address is known; the first push, which starts the first build and makes the address public once it serves; the record's push; the deletion of the secrets file and the token file. One short message each, with the recommendation, and "OK", in the person's language, continues.
23
- - **Go-ahead:** when the person says to go ahead alone, the reports between phases need no answer. The plan, a purchase, the first push and a deletion are confirmed in every mode.
24
- - **Waiting:** what only the person or the world can finish, the run waits for, in the open. A domain whose nameservers the person must change answers at Cloudflare minutes to hours later: the zone is made first, the steps go out in the second message, and from phase 5 the run checks the zone every ten minutes for up to an hour, saying so; still not there, it stops with the record saying what waits and resumes at phase 5 when the person says the nameservers are set. The only way of signing in answered `later` means nobody can sign in: the run stops at phase 5 the same way and resumes when the secret is in the secrets file.
23
+ - **Go-ahead:** when the person says to go ahead alone, the reports between phases need no answer. The plan, a purchase, every push and a deletion are confirmed in every mode.
24
+ - **Waiting:** what only the person or the world can finish, the run waits for, in the open. A domain whose nameservers the person must change answers at Cloudflare minutes to hours later: the zone, which costs nothing and is undone as easily, is made in phase 1, the one thing made before the plan, so that the steps go out in the second message, and from phase 5 the run checks the zone every ten minutes for up to an hour, saying so; still not there, it stops with the record saying what waits and resumes at phase 5 when the person says the nameservers are set. The only way of signing in answered `later` means nobody can sign in: the run stops at phase 5 the same way and resumes when the secret is in the secrets file.
25
25
  - **Changes mid-run:** applied from that point. What was created is kept and recorded, never silently deleted.
26
- - **Language:** messages to the person in the language they wrote in; the repository's files in English, its `README.md` and `DEPLOY.md` in the organization's language.
26
+ - **Language:** messages to the person in the language they wrote in; the repository's files in English, its `README.md` and `DEPLOY.md` in the language the person wrote in, unless they name another.
27
27
 
28
28
  ## Requirements
29
29
 
@@ -32,7 +32,7 @@ What goes in: an AWS account, a domain on Cloudflare or one to buy, and the answ
32
32
  | A shell with Node.js 24 or newer, git, curl, jq and the AWS CLI v2 (`aws --version`); on Windows, Git Bash with `export MSYS_NO_PATHCONV=1`, since Git Bash otherwise rewrites an argument that starts with `/` (a log group, a parameter) into a Windows path before a program sees it | Every command of the run; the deployment repository |
33
33
  | The deployer's access key and the Cloudflare token, as [access.md](references/access.md) says they are made and reach the agent | Everything on AWS and at Cloudflare |
34
34
  | The `gh` command signed in (`gh auth status`), or an empty private repository the person made | The deployment repository, its first push, watching its runs |
35
- | The `console` command, `npm install -g @skillcdn/console` in phase 1 | The proof at the end: an agent connected, a file handed in; and this skill's two scripts, which the package carries at `$(npm root -g)/@skillcdn/console/skills/deploying-a-console/scripts/`, copied into the run's `scratch/` folder, or read through the connection and written there |
35
+ | The `console` command, `npm install -g @skillcdn/console` in phase 1 | The proof at the end: an agent connected, a file handed in; and this skill's two scripts, which the package carries at `$(npm root -g)/@skillcdn/console/skills/deploying-a-console/scripts/`, copied into the run's `scratch/` folder, or read through the connection and written there; and the skill building-a-console, beside this one at `$(npm root -g)/@skillcdn/console/skills/building-a-console/SKILL.md` or through the connection, for the look and `console-ui/` |
36
36
  | The person, with a browser on any device | The GitHub App or the Google client, the first sign-in, approving the agent's code, a registrar's nameservers |
37
37
  | Network access to AWS, Cloudflare, GitHub and npm | |
38
38
 
@@ -42,19 +42,20 @@ Check these before the first message. Without the AWS CLI, offer to install it (
42
42
 
43
43
  | Input | Source |
44
44
  |---|---|
45
- | How many people | Asked: "How many people will use the console within a year: just you, up to 10, up to 30, or up to 100?" The answer picks the size ([sizing.md](references/sizing.md)). |
46
- | The board's name | Suggested from the convention and confirmed, never invented: the organization or the person the console is for, then Console, as `SkillCDN Console` is named. With the organization's name in the request or on its website: "The board will be called `Acme Console`, as consoles are named: the organization's name, then Console. Keep it, or say another?" Without one: "Whose console is this, your organization's or yours? Its name, then Console, is what the board is called." It goes to `WORKSPACE_NAME`, one line of up to 100 characters: what the pages say after the SkillCDN mark, and what the console calls itself at SkillCDN when it connects a pack. |
47
- | The web address | Asked: "Which web address should the console have? A domain you already own, and where it is registered (I set it up on Cloudflare; you change its nameservers there once, and I give you the steps), or should I find one to buy (I show the price before buying)? I suggest `console.<domain>`." A domain is needed: the tunnel has no free hostname. A domain that already serves the organization's site is fine: only the console's hostname is added to its zone, and nothing of the site is touched (resources.md "A zone that serves other hosts"). |
45
+ | How many people | Asked: "How many people will use the console within a year: just you, up to 10, up to 30, or up to 100?" The answer picks the size ([sizing.md](references/sizing.md)); more than 100 is `large` with its numbers raised at the plan, and said so. |
46
+ | The board's name | Suggested from the convention and confirmed, never invented: the organization or the person the console is for, then Console, as `SkillCDN Console` is named. With the organization's name in the request or on its website: "The board will be called `Acme Console`, as consoles are named: the organization's name, then Console. Keep it, or say another?" With an organization and no name: "What is your organization's name? Its name, then Console, is what the board is called." Without either: "Whose console is this, your organization's or yours? Its name, then Console, is what the board is called." It goes to `WORKSPACE_NAME`, one line of up to 100 characters: what the pages say after the mark, and what the console calls itself at SkillCDN when it connects a pack. |
47
+ | The web address | Asked: "Which web address should the console have? A domain you already own, and where it is registered (I set it up on Cloudflare; you change its nameservers at your registrar once, and I give you the steps; your website and your mail keep working, since their records come over as they are), or should I find one to buy (I show the price before buying)? I suggest `console.<domain>`." A domain is needed: the tunnel has no free hostname. A domain that already serves the organization's site is fine: only the console's hostname is added to its zone, and nothing of the site is touched (resources.md "A zone that serves other hosts"). |
48
48
  | How people sign in, and who | Asked: "How should people sign in: with GitHub, with Google (Workspace), or both? Who configures the console (you, at least): their GitHub login or Google address. With GitHub, everyone of your organization can be in, as the organization has them: say which organization. With a Google Workspace, everyone of the domain is in. Anyone else, an administrator lets in on the People page once the console runs." |
49
49
  | The organization's skills | Asked: "Where are your organization's skills on SkillCDN, if anywhere yet? An address like `/gh/<owner>/<repo>`. Is that repository private?" Default: none; a project names one later on its Settings page. A private one is connected in phase 6, with a code the person approves at SkillCDN ([ADR-0036](/docs/adr/0036-a-private-pack-is-connected-with-a-code-a-person-approves-from-the-projects-settings.md)); nothing of it is needed before. |
50
50
  | The organization's website, and the look | Asked: "Does your organization have a website? Its address." With one, the console takes the organization's look by default: the marks, the colours and the type read from the site as building-a-console's [the-look-from-a-website.md](/skills/building-a-console/references/the-look-from-a-website.md) says, mapped onto the pages and shown at the plan, and built into the image as the organization's own console, `console-ui/` of the deployment repository, so that the first deploy already carries it. Without a site, or when the person says the default pages are fine, the image's default pages serve: the board's name alone, and no marks. A look that is wanted later is the same skill, building-a-console, in `console-ui/`, and a push. |
51
+ | The plugins | Asked only when the request names one, or the organization's console is to carry a feature of its work (leave, customers, money): "Should the console carry a plugin? Which package, at which version or tag?" Default: none. A plugin is installed by building it into the image from `plugins/package.json` and its lockfile in the deployment repository, with no install scripts ([deploy/README.md "Plugins in the image"](/deploy/README.md#plugins-in-the-image)), and enabled by the administrator on the console's settings page once it runs, consenting to what it asks; one with `storage:sql` needs the database user to hold `CREATEROLE`, which the master user of phase 3's database does. One wanted later is a row of [running-it-again.md](references/running-it-again.md); one to be written is [building-a-plugin](/skills/building-a-plugin/SKILL.md). |
51
52
  | The AWS account | Asked: "Do you have an AWS account for this, or should the console get an account of its own (the simplest to run and to read the bill of)?" A new account is made by the person, on the paid plan, with the order of cost already in front of them (the second message). |
52
- | The region | Asked when the organization's country does not say: "Which AWS region: the one nearest your people?" Default: the nearest to where the organization is (`ap-northeast-2`, Seoul, for Korea; `ap-northeast-1` for Japan; `eu-central-1` for central Europe; `us-east-1` for the eastern United States). |
53
+ | The region | Asked when the organization's country does not say: "Where are most of your people: a country, or a city?" The region is the one nearest them: `ap-northeast-2` (Seoul), `ap-northeast-1` (Tokyo), `ap-southeast-1` (Singapore), `eu-central-1` (Frankfurt), `eu-west-1` (Ireland), `eu-west-2` (London), `us-east-1` (N. Virginia), `us-east-2` (Ohio), `us-west-2` (Oregon); `aws ec2 describe-regions` lists them all, and sizing.md says how a region's prices are read. |
53
54
  | The run's folder | Asked with a suggestion: "Which folder should the deployment live in on this machine? I suggest `console-deploy` under your home folder." It is made at once and becomes the deployment repository. |
54
55
  | The deployment repository | Asked: "Where should the deployment's files live on GitHub: a private repository under your account, or under an organization you name?" Its name is the folder's. |
55
56
  | Where alerts go | Asked: "Which email should the cost alert go to?" |
56
57
  | The console's version | Derived: the latest release of the console, the tag `@skillcdn/console@<version>` of its repository: the last line of `git ls-remote --tags https://github.com/skillcdn/console 'refs/tags/@skillcdn/console@*' \| sort -t@ -k3 -V` is the highest version, and its first column the commit (an annotated tag lists its commit on a `^{}` line, which sorts last the same way), pinned in the deployment repository as `CONSOLE_REF`. An organization's own console (building-a-console) depends on the package at that same version, so that the pages and the server are one release. An update is the next tag, a new commit there. |
57
- | Everything else | Derived and shown in the plan: the size's numbers, the names (`console` for the cluster and the database, `api` and `worker` for the services, `console-files-<account id>` for the bucket), the generated secrets (`AUTH_SECRET`, the database password), the variables of [deploy/README.md](/deploy/README.md) with the tunnel's settings (`TRUSTED_PROXIES=127.0.0.1,::1`, `CLIENT_IP_HEADER=cf-connecting-ip`, `HTTP_KEEP_ALIVE_SECONDS=100`) and the bucket read and written as the task's own role (`S3_CREDENTIALS=platform`), so that no key exists for it; the connector's image, mirrored into the organization's image repository; the estimate of this size and the next, the budget alert at one and a half times the estimate, the rate limiting rule on the sign-in paths, and what the person does once. |
58
+ | Everything else | Derived and shown in the plan: the size's numbers and the names of everything made (resources.md "Phase 2"); the generated secrets (`AUTH_SECRET`, the database's password); the variables of [deploy/README.md](/deploy/README.md) with the tunnel's settings (`TRUSTED_PROXIES=127.0.0.1,::1`, `CLIENT_IP_HEADER=cf-connecting-ip`, `HTTP_KEEP_ALIVE_SECONDS=100`) and the bucket read and written as the task's own role (`S3_CREDENTIALS=platform`), so that no key exists for it; the connector's image, mirrored into the organization's image repository; the estimate of this size and the next, the budget alert at one and a half times the estimate, the rate limiting rule on the sign-in paths, and the few steps the person takes once. |
58
59
 
59
60
  ## Workflow
60
61
 
@@ -70,11 +71,11 @@ Produces the **plan**, shown at the checkpoint. From the answers: the size and w
70
71
 
71
72
  ### Phase 3: Foundation
72
73
 
73
- Produces the **resource record**: each resource with its name and id. In this order: the zone at Cloudflare, with the records the domain serves already found by Cloudflare's scan and checked by the person against their registrar's DNS page, so that nameservers propagate meanwhile; the network and its two security groups; the database, started, and waited for while the rest is made; the bucket; the image repository, the log group, the roles, the task's with the bucket's policy, the cluster; the tunnel, the hostname, always-HTTPS for the hostname and the rate limiting rule; the parameters, generated and derived, from the secrets file. Then, the hostname being known, the message for each secret the person holds (access.md "The GitHub App", "The OAuth client"): "Save it in the secrets file as the line I show, or say `later` and that way of signing in stays off until it is set." A value the person pastes into the chat instead is written into the file by the agent's file tool, never repeated, and the person is told to generate a new one once the run is over, since the chat kept a copy; the new one goes in as "a secret rotated" of the record.
74
+ Produces the **resource record**: each resource with its name and id. In this order: the zone at Cloudflare, with the records the domain serves already found by Cloudflare's scan and checked by the person against their registrar's DNS page, so that nameservers propagate meanwhile; the network and its two security groups; the database, started, and waited for while the rest is made; the bucket; the image repository, the log group, the roles, the task's with the bucket's policy, the cluster; the tunnel, the hostname, always-HTTPS for the hostname and the rate limiting rule; the parameters, generated and derived, from the secrets file. The message for each secret the person holds (access.md "The GitHub App", "The OAuth client") goes out right after the plan's word, the hostname being fixed there, so that they do it while the foundation is made: "Save it in the secrets file as the line I show, or say `later` and that way of signing in stays off until it is set." A value the person pastes into the chat instead is written into the file by the agent's file tool, never repeated, and the person is told to generate a new one once the run is over, since the chat kept a copy; the new one goes in as "a secret rotated" of the record.
74
75
 
75
76
  ### Phase 4: The deployment repository
76
77
 
77
- Produces the **repository**: `deploy/console.env`, the task definitions `deploy/migrate.task.json`, `deploy/api.task.json` and, at the large size, `deploy/worker.task.json`, the workflow `.github/workflows/deploy.yml`, `README.md` and a draft `DEPLOY.md`, as resources.md gives them, every value filled from the record; with the organization's look, `console-ui/`, the organization's own console made here with building-a-console at the console's pinned version, from the look approved at the plan, which the workflow builds into the image on every push (resources.md "The organization's own console"); and on AWS the role the workflow assumes, trusted for this repository's `main` alone. Show the files, each with the lines that matter pointed out for a person who is not a developer, and the look's pages where a browser can be driven; commit; create the empty private repository. Checkpoint: "Pushing starts the first build (about ten minutes), runs the migrations, and starts the console at <hostname>. OK?" On the word, push, and watch the run to its end.
78
+ Produces the **repository**: `deploy/console.env`, the task definitions `deploy/migrate.task.json`, `deploy/api.task.json` and, at the large size, `deploy/worker.task.json`, the workflow `.github/workflows/deploy.yml`, `README.md` and a draft `DEPLOY.md`, as resources.md gives them, every value filled from the record; with the organization's look, `console-ui/`, the organization's own console made here with building-a-console at the console's pinned version, from the look approved at the plan, which the workflow builds into the image on every push (resources.md "The organization's own console"); with a plugin named at the plan, `plugins/package.json` and its lockfile, which the workflow installs into the image (resources.md "The plugins"); and on AWS the role the workflow assumes, trusted for this repository's `main` alone. Show the files, each with the lines that matter pointed out for a person who is not a developer, and the look's pages where a browser can be driven; commit; create the empty private repository. Checkpoint: "Pushing starts the first build (about ten minutes), runs the migrations, and starts the console at <hostname>. OK?" On the word, push, and watch the run to its end.
78
79
 
79
80
  ### Phase 5: Live
80
81
 
@@ -90,11 +91,11 @@ Produces the **proof**: `console task new "Deployment check"` with a body that s
90
91
 
91
92
  ### Phase 8: Record and delivery
92
93
 
93
- Produces **`DEPLOY.md`** in the repository, from the template in resources.md: what exists under which names, the size and its estimate, how a deploy happens, how to add a member, update, grow, rotate a secret, pause and remove, and what is owed. Checkpoint: "Committing and pushing the record. OK?" (a change to the record alone does not redeploy). Then, on the person's word, delete the secrets file and the token file. Deliver in one message: the address, what was made in the person's words, the estimate and the alert, what the person still owes (a `later` secret, nameservers), what was verified and how, and how the console is operated from here: the record, and a push.
94
+ Produces **`DEPLOY.md`** in the repository, from the template in resources.md: what exists under which names, the size and its estimate, how a deploy happens, how to add a member, update, grow, rotate a secret, pause and remove, and what is owed. Checkpoint: "Committing and pushing the record. OK?" (a change to the record alone does not redeploy). Then, on the person's word, delete the secrets file and the token file; the profile `console` stays in the AWS CLI's credentials, since the record's commands run as it, until the deployment is removed. Deliver in one message: the address, what was made in the person's words, the estimate and the alert, what the person still owes (a `later` secret, nameservers), what was verified and how, and how the console is operated from here: the record, and a push.
94
95
 
95
96
  ### Running it again
96
97
 
97
- The same skill, from phase 2, with the plan naming only what changes: a new version (the next release tag's commit as `CONSOLE_REF` in `deploy/console.env`, with `console-ui/` moved to the same version of the package when there is one, a push), a new connector (`CLOUDFLARED_VERSION` there, a push), the next size (the numbers in the task definitions and a push; the database's class with one command, as sizing.md "Growing" says), a person let in or taken out (on the People page, or `console person add <login>` and `console person deactivate <login>`, by an administrator; no push), a secret rotated (the parameter overwritten, the service rolled), the console paused or removed (resources.md "Removing"). Each is confirmed at the plan, and the record is updated with it.
98
+ The same skill, from phase 2, with the plan naming only what changes and its price: a new version of the console or of the connector, a plugin installed, updated or removed, the next size, a person let in or taken out, a secret rotated, a console shared with outside teams or customers, the console paused or removed. What each takes is in [running-it-again.md](references/running-it-again.md); each is confirmed at the plan, and the record is updated with it.
98
99
 
99
100
  ### Verdicts
100
101
 
@@ -112,7 +113,7 @@ The same skill, from phase 2, with the plan naming only what changes: a new vers
112
113
  3. The first push is confirmed, and every push after it: once the repository is connected, a push is a deployment, which the person chose at the plan.
113
114
  4. The person is never asked to do what the key and the token can do; what only they can do (a GitHub App or a Google client, a sign-in, approving a code, nameservers at their registrar) is given as the exact steps of the day in one message, for the registrar they named where its steps are known and in general words otherwise, so that they can do it while the run goes on.
114
115
  5. Nothing of the deployment listens on the internet: the tasks' security group has no inbound rule, the database is not publicly accessible, the bucket blocks public access, and the hostname reaches the console only through the tunnel.
115
- 6. Names and ids go into the repository and the record; a value a secret scanner would flag never does, and `scratch/`, the key file and the token file never enter git. Every file the repository gets is shown before it is pushed.
116
+ 6. Names and ids go into the repository and the record; a value a secret scanner would flag never does, and `scratch/`, the key file and the token file never enter git. Every file written by hand is shown before it is pushed; a generated file (a lockfile, a build) is named.
116
117
  7. What was created is kept and recorded, never silently deleted; removing the deployment is a run of its own, asked for, and confirmed resource by resource.
117
118
  8. The console's contract holds on every rollout: `migrate` runs and exits `0` before the new `api` and `worker` start, as the one-off task of the workflow, or, without a pipeline, as a container of every task of both roles that its own container waits on (resources.md "Without a pipeline"); the stop timeout stays above the shutdown grace period, and no secret is a build argument.
118
119
 
@@ -120,13 +121,10 @@ The same skill, from phase 2, with the plan naming only what changes: a new vers
120
121
 
121
122
  | Term | Meaning |
122
123
  |---|---|
123
- | Size | `small`, `medium` or `large`: the numbers of sizing.md, chosen by how many people. |
124
- | The deployer | The IAM user whose key the agent acts with, as the profile `console` of the AWS CLI. |
125
- | The task's role | The role the containers run as, `console-task`, whose credentials the platform hands them and renews: it may read and write the bucket, and nothing else; no key exists for the files. |
126
- | The run's folder | The folder the person chose, which becomes the deployment repository; `scratch/`, the key file and the token file in it never enter git; the secrets file is beside it, in the folder above. |
124
+ | The deployer | The IAM user whose key the agent acts with, the profile `console` of the AWS CLI. |
125
+ | The task's role | `console-task`, the role the containers run as, with credentials the platform hands them and renews: it may read and write the bucket, and nothing else; no key exists for the files. |
126
+ | The run's folder | The folder the person chose, which becomes the deployment repository; `scratch/`, the key file and the token file in it never enter git. |
127
127
  | The tunnel | The Cloudflare Tunnel whose connector runs beside the `api` container; the hostname's traffic comes in through it and nothing else does. |
128
- | Task, service | On ECS, a task is the containers of one role running together; a service keeps the wanted number of tasks running and rolls them to a new definition. |
129
- | Parameter | A value in SSM Parameter Store that a task reads as an environment variable at start. |
130
- | The deployment repository | The private repository of the task definitions, the workflow and the record; a push to it is a deployment. |
131
- | `DEPLOY.md` | The record: what exists under which names, how it is run, what is owed, what it costs. |
132
- | Later | A secret the person did not supply; the way of signing in it serves stays off, and the record says so. |
128
+ | Task, service, parameter | On ECS, a task is the containers of one role running together, and a service keeps the wanted number of tasks running and rolls them to a new definition; a parameter is a value in SSM Parameter Store that a task reads as an environment variable at start. |
129
+ | The deployment repository, the record | The private repository of the task definitions, the workflow and `DEPLOY.md`, the record of what exists under which names, how it is run, what is owed and what it costs; a push to it is a deployment. |
130
+ | The secrets file; the key file and the token file | `console-secrets.json` in the folder above the run's folder, the values the person holds and the run generates, each stored as a parameter (access.md); `.aws-key.csv` and `.cf-token` in the run's folder, saved there by the person and read by the agent into the profile and the header file. None enters git, and each is deleted when the run is over, the secrets file on the person's word. |
@@ -44,26 +44,28 @@ The first answers with the account id the plan names; the second with the defaul
44
44
  | Zone | Zone WAF | Edit | The rate limiting rule |
45
45
  | Account | Registrar | Edit | Only when a domain is to be bought in the run |
46
46
 
47
- Zone Resources: Include, All zones, so that a zone made in the run is covered. Account Resources: the one account. Client IP filtering: none. TTL: none, or a year. Continue to summary, Create Token, copy the secret once. Read where Edit is needed is the mistake to look for: a token with Read lists tunnels and rules and fails only at the first creation, with `403` and code `10000` (resources.md "When a step fails").
47
+ Ask for every row at once, Registrar excepted, which is asked only when a domain is to be bought: whether the zone serves other hosts, which decides between Zone Settings and Dynamic URL Redirects, is learned only once the token exists. Zone Resources: Include, All zones, so that a zone made in the run is covered. Account Resources: the one account. Client IP filtering: none. TTL: none, or a year. Continue to summary, Create Token, copy the secret once. Read where Edit is needed is the mistake to look for: a token with Read lists tunnels and rules and fails only at the first creation, with `403` and code `10000` (resources.md "When a step fails").
48
48
 
49
49
  **How it reaches the agent.** Save it in a file named `.cf-token` in the run's folder (a file without an extension: in Notepad, "All files" as the type, so that it is not `.cf-token.txt`), and tell the agent; the agent reads it into the environment for each call and never prints it. The file is listed in the deployment repository's `.gitignore` from the first commit, and deleted at the end of the run on the person's word. A token pasted into the chat is written into the file with the agent's file tool and never repeated, and the person is told to make a new token once the run is over, since the chat kept a copy.
50
50
 
51
51
  **Verify:**
52
52
 
53
53
  ```sh
54
- CLOUDFLARE_API_TOKEN=$(tr -d '\r\n ' < .cf-token)
55
- curl -s https://api.cloudflare.com/client/v4/user/tokens/verify -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
56
- curl -s https://api.cloudflare.com/client/v4/zones -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
57
- curl -s https://api.cloudflare.com/client/v4/accounts -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
54
+ printf 'Authorization: Bearer %s\n' "$(tr -d '\r\n ' < .cf-token)" > scratch/auth.txt
55
+ curl -s https://api.cloudflare.com/client/v4/user/tokens/verify -H @scratch/auth.txt
56
+ curl -s https://api.cloudflare.com/client/v4/zones -H @scratch/auth.txt
57
+ curl -s https://api.cloudflare.com/client/v4/accounts -H @scratch/auth.txt
58
58
  ```
59
59
 
60
- `"status": "active"`, the zones the token sees (the domain among them, or none yet), and the account with its id; the last call is refused without Account Settings Read, which is fine when the zone exists and carries the id. The file carries a carriage return on Windows; the `tr` strips it. These calls prove the token is live, not what it may change: Read lists exactly what Edit lists.
60
+ `"status": "active"`, the zones the token sees (the domain among them, or none yet), and the account with its id; the last call is refused without Account Settings Read, which is fine when the zone exists and carries the id. The file carries a carriage return on Windows; the `tr` strips it, and the header goes to `curl` from a file in `scratch/`, never on a command line. These calls prove the token is live, not what it may change: Read lists exactly what Edit lists.
61
61
 
62
62
  ## The git host
63
63
 
64
64
  **The deployment repository.** The organization's deployment lives in a private repository on GitHub: the task definitions, the workflow that builds and deploys, the record. The agent makes it with the `gh` command signed in (`gh auth status`; `gh auth login` otherwise, which opens the browser for the person) under the owner the person names: their account, or an organization. Without `gh`, the person creates an empty private repository in the browser (New repository, private, no README) and gives its address, and the agent pushes to it.
65
65
 
66
- **The GitHub App, for signing in with GitHub, and for the organization to say who is in.** One app does both ([ADR-0022](/docs/adr/0022-who-is-in-is-the-consoles-own-record-and-an-administrator-keeps-it.md)): its client signs people in, and, installed on the organization, it lets the console read who is a member, at sign-in and every ten minutes after. Once the console's address is known, the person, signed in to GitHub as an owner of the organization (checked 2026-10): Your organizations, Settings next to the organization, Developer settings, GitHub Apps, New GitHub App.
66
+ ### The GitHub App, for signing in with GitHub, and for the organization to say who is in
67
+
68
+ One app does both ([ADR-0022](/docs/adr/0022-who-is-in-is-the-consoles-own-record-and-an-administrator-keeps-it.md)): its client signs people in, and, installed on the organization, it lets the console read who is a member, at sign-in and every ten minutes after. Once the console's address is known, the person, signed in to GitHub as an owner of the organization (checked 2026-10): Your organizations, Settings next to the organization, Developer settings, GitHub Apps, New GitHub App.
67
69
 
68
70
  - GitHub App name: the board's name (34 characters at most, unique across GitHub).
69
71
  - Homepage URL: the console's address, `https://<hostname>`.
@@ -71,12 +73,14 @@ curl -s https://api.cloudflare.com/client/v4/accounts -H "Authorization: Bearer
71
73
  - Webhook: untick Active. The console takes no webhooks.
72
74
  - Permissions: under Organization permissions, Members: Read-only. Nothing else, and no repository permission: the console reads who is in, and nothing of the code.
73
75
  - Where can this GitHub App be installed?: Only on this account.
74
- - Create GitHub App. The app's page shows the **App ID** and the **Client ID**: copy both into the chat (neither is secret). Under Client secrets, Generate a new client secret, and save it to the secrets file as `GITHUB_CLIENT_SECRET` (below), as the line the agent shows. Under Private keys, Generate a private key: a `.pem` file downloads; save its content to the secrets file as `GITHUB_APP_PRIVATE_KEY`, the file's lines joined with `\n`, as the line the agent shows (the agent writes it from the file when told where it is, and deletes the file on the person's word). A secret pasted into the chat instead is written into the file by the agent's file tool and never repeated, and the person generates a new one once the run is over, since the chat kept a copy; the new one goes in as "a secret rotated" of the record.
76
+ - Create GitHub App. The app's page shows the **App ID** and the **Client ID**: copy both into the chat (neither is secret). Under Client secrets, Generate a new client secret, and save it to the secrets file as `GITHUB_CLIENT_SECRET` (below), as the line the agent shows. Under Private keys, Generate a private key: a `.pem` file downloads; tell the agent where the file is, and it writes the key into the secrets file as `GITHUB_APP_PRIVATE_KEY` whole and deletes the file on the person's word; by hand instead, the file's lines joined with `\n`, as the line the agent shows. A secret pasted into the chat instead is written into the file by the agent's file tool and never repeated, and the person generates a new one once the run is over, since the chat kept a copy; the new one goes in as "a secret rotated" of the record.
75
77
  - Install App, in the app's left sidebar: Install next to the organization, and confirm. No repository access is asked for, since the app has no repository permission. Everyone of the organization is in from then on, as the organization has them; the plan's `GITHUB_ORG` is the organization's login, as it reads in `github.com/<login>`.
76
78
 
77
79
  Without an organization, an OAuth app signs people in as well (Developer settings, OAuth Apps, New OAuth App: the same name, homepage and callback; Register application; the Client ID and a client secret), and an administrator lets each person in on the People page. No scopes are needed either way: the console asks only who the person is.
78
80
 
79
- **The OAuth client, for signing in with Google.** In the Google Cloud console, a project of the organization's, Google Auth Platform (checked 2026-10): if the project has not registered with Google Auth yet, the Overview asks for the app's name, a support address and the audience (Internal, for a Workspace organization), once. Then Clients, Create client, Application type **Web application**, name it, under Authorized redirect URIs add `https://<hostname>/auth/google/callback`, Create. Copy the **Client ID** into the chat; the client secret is shown once: save it to the secrets file as `GOOGLE_CLIENT_SECRET`, as the line the agent shows (pasted instead: written to the file by the agent, never repeated, and replaced after the run, as with GitHub's). The Workspace domain, when everyone of it is a member, is the plan's `GOOGLE_WORKSPACE_DOMAIN`. Changes take from minutes to hours to be effective at Google.
81
+ ### The OAuth client, for signing in with Google
82
+
83
+ In the Google Cloud console, a project of the organization's, Google Auth Platform (checked 2026-10): if the project has not registered with Google Auth yet, the Overview asks for the app's name, a support address and the audience (Internal, for a Workspace organization), once. Then Clients, Create client, Application type **Web application**, name it, under Authorized redirect URIs add `https://<hostname>/auth/google/callback`, Create. Copy the **Client ID** into the chat; the client secret is shown once: save it to the secrets file as `GOOGLE_CLIENT_SECRET`, as the line the agent shows (pasted instead: written to the file by the agent, never repeated, and replaced after the run, as with GitHub's). The Workspace domain, when everyone of it is a member, is the plan's `GOOGLE_WORKSPACE_DOMAIN`. Changes take from minutes to hours to be effective at Google.
80
84
 
81
85
  ## The secrets file
82
86
 
@@ -9,13 +9,13 @@ The run's folder is the deployment repository to be; `scratch/` in it holds the
9
9
  ```bash
10
10
  export MSYS_NO_PATHCONV=1 # Git Bash on Windows: keep /console and the like as written
11
11
  export AWS_PROFILE=console AWS_REGION=<region>
12
- export CLOUDFLARE_API_TOKEN=$(tr -d '\r\n ' < .cf-token)
13
- cf() { curl -sS "https://api.cloudflare.com/client/v4$1" -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json" "${@:2}"; }
12
+ printf 'Authorization: Bearer %s\n' "$(tr -d '\r\n ' < .cf-token)" > scratch/auth.txt # the header from a file, never on a command line
13
+ cf() { curl -sS "https://api.cloudflare.com/client/v4$1" -H @scratch/auth.txt -H "Content-Type: application/json" "${@:2}"; }
14
14
  ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
15
15
  SECRETS=../console-secrets.json # outside the repository; made by the first `set` below, never a copy of assets/secrets.example.json
16
16
  ```
17
17
 
18
- Every id the run makes or finds (`CF_ACCOUNT`, `ZONE`, `VPC`, `SUBNETS`, `TASKS_SG`, `DB_SG`, `ENDPOINT`, `BUCKET`, `TUNNEL`, the rules' ids) goes into `scratch/notes.md` as it is made, as a line `NAME=value`, and a new shell sets them again from there (`set -a; . scratch/notes.md; set +a` when the file holds nothing but such lines): phases 5 to 7 come after a ten-minute watch, or after a pause of hours, almost always in a new shell.
18
+ Every id the run makes or finds (`CF_ACCOUNT`, `ZONE`, `VPC`, `SUBNETS`, `TASKS_SG`, `DB_SG`, `ENDPOINT`, `BUCKET`, `TUNNEL`, the rules' ids) goes into `scratch/notes.md` as it is made, as a line `NAME=value`, and a new shell sets them again from there (`set -a; . scratch/notes.md; set +a` when the file holds nothing but such lines): phases 5 to 7 come after a ten-minute watch, or after a pause of hours, almost always in a new shell. Where every command runs in a new shell, as some agents' tools do, the lines above go in `scratch/env.sh`, sourced at the start of every command; a wait longer than one command may take (the database, the workflow's run, a sign-in) runs in the background or is polled.
19
19
 
20
20
  A value that must not be seen goes through a file or a pipe, never a command line: `aws ... --cli-input-json file://scratch/<file>` for a request that carries one, and the two scripts of this skill ([import-aws-key.mjs](../scripts/import-aws-key.mjs), [store-secrets.mjs](../scripts/store-secrets.mjs)), copied to `scratch/`, for the key and the parameters. A response that carries one is piped straight into the secrets file:
21
21
 
@@ -30,8 +30,8 @@ node -e "process.stdout.write(require('node:crypto').randomBytes(36).toString('b
30
30
  2. The command and the scripts: `npm install -g @skillcdn/console`, then copy `$(npm root -g)/@skillcdn/console/skills/deploying-a-console/scripts/*.mjs` into `scratch/` (or write them there from the connection's copy of the skill).
31
31
  3. The key: the person saved `.aws-key.csv` in the folder ([access.md](access.md)). `node scratch/import-aws-key.mjs .aws-key.csv console` writes the profile and deletes the file. Then `aws configure set region <region> --profile console`.
32
32
  4. `aws sts get-caller-identity`: the account id and `user/console-deployer`. A refusal means the key is wrong or not yet active (a minute after creation).
33
- 5. The network: `aws ec2 describe-vpcs --filters Name=is-default,Values=true --query 'Vpcs[0].VpcId' --output text`. `None`: `aws ec2 create-default-vpc`. Then the subnets: `aws ec2 describe-subnets --filters Name=vpc-id,Values=<vpc> Name=default-for-az,Values=true --query 'Subnets[].SubnetId' --output text`.
34
- 6. The token: `cf /user/tokens/verify` answers `"status": "active"`; `cf "/zones?name=<domain>" > scratch/zone.json` the zone, with its id, its `status` and its account (`ZONE=$(jq -r '.result[0].id' scratch/zone.json)`, `CF_ACCOUNT=$(jq -r '.result[0].account.id' scratch/zone.json)`), or an empty list when the domain is not on Cloudflare yet, in which case `CF_ACCOUNT=$(cf /accounts | jq -r '.result[0].id')` and `ZONE` waits for phase 3. A zone that exists: `cf "/zones/$ZONE/dns_records" | jq '.result | length'`; records of its own mean the zone serves the organization's site already, which the access record says and phase 3 "A zone that serves other hosts" acts on.
33
+ 5. The network: `aws ec2 describe-vpcs --filters Name=is-default,Values=true --query 'Vpcs[0].VpcId' --output text`. `None`: phase 3 "The network" makes one. Then the subnets, once there is a VPC: `aws ec2 describe-subnets --filters Name=vpc-id,Values=<vpc> Name=default-for-az,Values=true --query 'Subnets[].SubnetId' --output text`.
34
+ 6. The token: `cf /user/tokens/verify` answers `"status": "active"`; `cf "/zones?name=<domain>" > scratch/zone.json` the zone, with its id, its `status` and its account (`ZONE=$(jq -r '.result[0].id' scratch/zone.json)`, `CF_ACCOUNT=$(jq -r '.result[0].account.id' scratch/zone.json)`), or an empty list when the domain is not on Cloudflare yet, in which case `CF_ACCOUNT=$(cf /accounts | jq -r '.result[0].id')` and the zone is made now, as "The zone, first" says: the one thing made before the plan, since it costs nothing, is undone as easily, and its nameservers must reach the person in the second message. A zone that exists: `cf "/zones/$ZONE/dns_records" | jq '.result | length'`; records of its own mean the zone serves the organization's site already, which the access record says and phase 3 "A zone that serves other hosts" acts on.
35
35
  7. The git host: `gh auth status`; the owner the person named is theirs or an organization they are in (`gh api user`, `gh api user/orgs`).
36
36
  8. The tools: `node --version` (24 or newer), `git --version`, `jq --version`, `curl --version`.
37
37
 
@@ -39,7 +39,7 @@ Report what is missing in one message, with the row of access.md to add, before
39
39
 
40
40
  ## Phase 2: Plan and estimate
41
41
 
42
- Nothing is created. The size from the answer and its numbers ([sizing.md](sizing.md) "The sizes"); the prices read and the estimate computed ("Reading today's prices"); the names:
42
+ Nothing is created, but for the zone of phase 1, which costs nothing. The size from the answer and its numbers ([sizing.md](sizing.md) "The sizes"); the prices read and the estimate computed ("Reading today's prices"); the names:
43
43
 
44
44
  | Resource | Name |
45
45
  |---|---|
@@ -59,10 +59,12 @@ The plan message is the estimate message of sizing.md followed by the line in th
59
59
 
60
60
  ### The zone, first
61
61
 
62
- Not on Cloudflare yet: `cf /zones -X POST --data '{"name":"<domain>","account":{"id":"'$CF_ACCOUNT'"},"type":"full"}'`, then `ZONE=$(cf "/zones?name=<domain>" | jq -r '.result[0].id')`. Before the nameservers change, the zone must carry what the domain serves already (its site, its mail), or that stops working at the switch: `cf "/zones/$ZONE/dns_records/scan" -X POST` has Cloudflare find the common records and add them (checked 2026-10), `cf "/zones/$ZONE/dns_records" | jq -r '.result[] | "\(.type) \(.name) \(.content)"'` lists what the zone now has, and the list goes to the person with the nameserver steps: "Compare this with the DNS page at your registrar and send me anything that is missing", each missing one added with `cf "/zones/$ZONE/dns_records" -X POST --data '{"type":"<MX | A | CNAME | TXT>","name":"<name>","content":"<value>","priority":<MX only>,"ttl":1}'`. The answer's `name_servers` go to the person with the steps for their registrar (access.md "The domain"), the run goes on, and the hostname answers once `cf "/zones?name=<domain>"` says `"status": "active"`. A domain to buy: `cf "/accounts/$CF_ACCOUNT/registrar/domain-search?q=<words>"` to find one, `cf "/accounts/$CF_ACCOUNT/registrar/domain-check" -X POST --data '{"domains":["<domain>"]}'` for the live price, the checkpoint with the price and the renewal, then `cf "/accounts/$CF_ACCOUNT/registrar/registrations" -X POST --data '{"domain_name":"<domain>"}'`; a `202` is polled at `.../registrations/<domain>/registration-status`. Buying through the API was in beta when checked (2026-10) and is not open to every account: `extension_not_supported_via_api`, a `404` or a route error means the person buys in the dashboard (Domain Registration, Register Domains), with the price in front of them there. A bought domain's zone is active at once, and `ZONE` is read as above.
62
+ Not on Cloudflare yet, and not made in phase 1: `cf /zones -X POST --data '{"name":"<domain>","account":{"id":"'$CF_ACCOUNT'"},"type":"full"}' > scratch/zone.json`, then `ZONE=$(jq -r .result.id scratch/zone.json)`, and the nameservers are `jq -r '.result.name_servers[]' scratch/zone.json`. Before the nameservers change, the zone must carry what the domain serves already (its site, its mail), or that stops working at the switch: `cf "/zones/$ZONE/dns_records/scan" -X POST` has Cloudflare find the common records and add them (checked 2026-10), `cf "/zones/$ZONE/dns_records" | jq -r '.result[] | "\(.type) \(.name) \(.content)"'` lists what the zone now has, and the list goes to the person with the nameserver steps: "Compare this with the DNS page at your registrar and send me anything that is missing", each missing one added with `cf "/zones/$ZONE/dns_records" -X POST --data '{"type":"<MX | A | CNAME | TXT>","name":"<name>","content":"<value>","priority":<MX only>,"ttl":1}'`. Each record the scan added is checked for `proxied` (`cf "/zones/$ZONE/dns_records" | jq -r '.result[] | "\(.id) \(.name) \(.proxied)"'`): a record of the organization's site or mail is set `"proxied":false` (`cf "/zones/$ZONE/dns_records/<id>" -X PATCH --data '{"proxied":false}'`), so that it comes over as it is and nothing of the site passes through Cloudflare until the person asks for that; and the zone's SSL mode (`cf "/zones/$ZONE/settings/ssl"`) is read and said at the plan, since a site proxied later needs `full` or `strict` to keep serving its own certificate. The answer's `name_servers` go to the person with the steps for their registrar (access.md "The domain"), the run goes on, and the hostname answers once `cf "/zones?name=<domain>"` says `"status": "active"`. A domain to buy: `cf "/accounts/$CF_ACCOUNT/registrar/domain-search?q=<words>"` to find one, `cf "/accounts/$CF_ACCOUNT/registrar/domain-check" -X POST --data '{"domains":["<domain>"]}'` for the live price, the checkpoint with the price and the renewal, then `cf "/accounts/$CF_ACCOUNT/registrar/registrations" -X POST --data '{"domain_name":"<domain>"}'`; a `202` is polled at `.../registrations/<domain>/registration-status`. Buying through the API was in beta when checked (2026-10) and is not open to every account: `extension_not_supported_via_api`, a `404` or a route error means the person buys in the dashboard (Domain Registration, Register Domains), with the price in front of them there. A bought domain's zone is active at once, and `ZONE` is read as above.
63
63
 
64
64
  ### The network
65
65
 
66
+ No default VPC (phase 1 found `None`): `aws ec2 create-default-vpc` first, free of charge, then its subnets as phase 1 reads them.
67
+
66
68
  ```sh
67
69
  VPC=<vpc>; SUBNETS=<subnet ids, comma-separated>
68
70
  TASKS_SG=$(aws ec2 create-security-group --group-name console-tasks --description "console tasks: no inbound" --vpc-id $VPC --query GroupId --output text)
@@ -213,7 +215,7 @@ In the run's folder, write:
213
215
  ```sh
214
216
  CONSOLE_REPOSITORY=https://github.com/skillcdn/console.git
215
217
  CONSOLE_REF=<the commit of the release tag pinned at the plan>
216
- CLOUDFLARED_VERSION=<the connector's release, such as 2026.10.0, from github.com/cloudflare/cloudflared/releases>
218
+ CLOUDFLARED_VERSION=<the connector's newest release, which gh api repos/cloudflare/cloudflared/releases/latest --jq .tag_name prints>
217
219
  AWS_ACCOUNT_ID=<account id>
218
220
  AWS_REGION=<region>
219
221
  ECR_REPOSITORY=console
@@ -225,7 +227,7 @@ WORKER_COUNT=<0 | 0 | 1>
225
227
  PLATFORM=linux/amd64
226
228
  ```
227
229
 
228
- `PLATFORM=linux/arm64` with `cpuArchitecture` `ARM64` in the task definitions when the organization's builds run on ARM runners (sizing.md "Options").
230
+ `PLATFORM=linux/arm64` with `cpuArchitecture` `ARM64` in the task definitions when the organization's builds run on ARM runners (sizing.md "Options the plan offers").
229
231
 
230
232
  ### `deploy/api.task.json`
231
233
 
@@ -316,7 +318,7 @@ name: Deploy the console
316
318
  on:
317
319
  push:
318
320
  branches: [main]
319
- paths: ["deploy/**", "console-ui/**", ".github/workflows/deploy.yml"]
321
+ paths: ["deploy/**", "console-ui/**", "plugins/**", ".github/workflows/deploy.yml"]
320
322
  workflow_dispatch:
321
323
  permissions:
322
324
  id-token: write
@@ -356,6 +358,9 @@ jobs:
356
358
  (cd console-ui && npm ci && npm run build)
357
359
  printf 'FROM %s\nCOPY --chown=node:node console-ui/dist/ /app/web/\n' "$IMAGE" | docker build --platform "$PLATFORM" -t "$IMAGE" -f - .
358
360
  fi
361
+ if [ -f plugins/package.json ]; then
362
+ printf 'FROM node:24-bookworm-slim AS plugins\nWORKDIR /plugins\nCOPY plugins/package.json plugins/package-lock.json ./\nRUN npm ci --omit=dev --ignore-scripts\nFROM %s\nCOPY --from=plugins --chown=node:node /plugins/ /app/plugins/\nENV PLUGINS_DIR=/app/plugins\n' "$IMAGE" | docker build --platform "$PLATFORM" -t "$IMAGE" -f - .
363
+ fi
359
364
  docker push "$IMAGE"
360
365
  echo "IMAGE=$IMAGE" >> "$GITHUB_ENV"
361
366
  - name: Register the task definitions
@@ -431,11 +436,20 @@ aws iam put-role-policy --role-name console-github-deploy --policy-name console-
431
436
 
432
437
  `scratch/deploy-policy.json`: `ecr:GetAuthorizationToken` on `*`; `ecr:BatchCheckLayerAvailability`, `ecr:InitiateLayerUpload`, `ecr:UploadLayerPart`, `ecr:CompleteLayerUpload`, `ecr:PutImage`, `ecr:BatchGetImage`, `ecr:DescribeImages` on `arn:aws:ecr:<region>:<account>:repository/console`; `ecs:RegisterTaskDefinition`, `ecs:DescribeTaskDefinition`, `ecs:RunTask`, `ecs:DescribeTasks`, `ecs:DescribeServices`, `ecs:CreateService`, `ecs:UpdateService`, `ecs:ListTasks` on `*`; `iam:PassRole` on the two task roles; `logs:DescribeLogStreams`, `logs:GetLogEvents`, `logs:FilterLogEvents` on `arn:aws:logs:<region>:<account>:log-group:/console:*`.
433
438
 
439
+ ### The organization's own console
440
+
441
+ `console-ui/`, when the organization has a look of its own: a console made with building-a-console at the plan's look, depending on `@skillcdn/console` at the version of the release `CONSOLE_REF` pins, with a `package-lock.json` (the workflow runs `npm ci`), a `build` script that runs `vite build` into `dist/`, and a `vite.config.ts` that sends `/api` to `console serve` while it is built; the workflow builds it on every push and lays `console-ui/dist/` over the image's web root. Absent, the image's default pages serve.
442
+
443
+ ### The plugins
444
+
445
+ `plugins/package.json` and `plugins/package-lock.json`, when the plan names a plugin: `dependencies` only, each at an exact version from the registry or a tag of a git host (`github:<owner>/<repo>#<tag>`), written with `npm install --save-exact --package-lock-only --ignore-scripts <package>@<version>` in `plugins/`, which installs nothing on the machine; no scripts, no other field that does anything. The workflow installs them into the image from the lockfile with no install scripts and sets `PLUGINS_DIR`, as [deploy/README.md "Plugins in the image"](/deploy/README.md#plugins-in-the-image) lays it out; the console reads them at boot, and the administrator enables each on the settings page once the console runs. Absent, the console has no plugins. A plugin that asks `storage:sql` needs the database user to hold `CREATEROLE`, which the master user of phase 3's database does; one that keeps files is bounded with `PLUGIN_FILE_BYTES_AT_MOST` in `deploy/console.env` as `PROJECT_FILE_BYTES_AT_MOST` is, and each plugin's thread may hold `PLUGIN_MEMORY_MB_AT_MOST` (256 unless set), which the task's memory leaves room for.
446
+
434
447
  ### The rest of the repository
435
448
 
436
449
  - `README.md`, in the organization's language: what this repository is (the deployment of the organization's console), that a push to `main` deploys, where the record is, and where the skill that made it lives (`skillcdn.ai/gh/skillcdn/console`).
437
450
  - `DEPLOY.md`: the draft from the template (phase 8), filled as far as the run has gone.
438
451
  - `console-ui/`: only when the organization has its own console (building-a-console makes it); otherwise absent and the image's default UI serves.
452
+ - `plugins/`: only when the plan names a plugin; otherwise absent, and the console has none.
439
453
 
440
454
  ### Commit, create, push, watch
441
455
 
@@ -542,6 +556,7 @@ Only on request, each step confirmed, in this order, after a last snapshot of th
542
556
  | `aws rds create-db-instance` refuses the engine version | The region has no PostgreSQL 18 yet | `describe-db-engine-versions --engine postgres` and take the newest major it lists |
543
557
  | `create-bucket` says the name is taken | Bucket names are global | Add a short suffix and update the task definition and the files policy |
544
558
  | `POST /zones` is refused with a permission error | The token lacks Zone Edit | Add the row (access.md) and run the step again |
559
+ | `POST /zones` answers that the domain is on Cloudflare already, in another account | The organization's zone lives under another Cloudflare account | The person signs in there and moves the domain to this account, or makes the token under that account; the run then reads the zone as phase 1 does |
545
560
  | The migration task exits `78` | A variable refused; the log names it, never its value | Fix the parameter or the environment entry, push again |
546
561
  | The migration task exits `1` with a connection error | The tasks' group is not allowed into the database's, or the URL's host is wrong | Check the `authorize-security-group-ingress` and the endpoint in `DATABASE_URL` |
547
562
  | `ResourceInitializationError` on a task: unable to pull secrets | The execution role cannot read `/console/*` | Check `console-parameters` on `console-task-execution` and the parameter ARNs |
@@ -0,0 +1,15 @@
1
+ # Running it again
2
+
3
+ A deployment is changed with the same skill, from phase 2: the plan names only what changes, with today's price where the change bills, the person says the word, the change is made as its row says, and the record, `DEPLOY.md`, is updated with it, in the same push or, where the change made none, in a push of its own, confirmed as phase 8 confirms it. The skill is read whole first, as for a first run; its rules hold here as there, and a push is a deployment (hard rule 3). The record's "How it is run" (resources.md "Phase 8: the record") says the same for the person who runs the console without an agent; the two are kept saying the same.
4
+
5
+ | What changes | What it takes |
6
+ |---|---|
7
+ | A new version of the console | The next release tag's commit as `CONSOLE_REF` in `deploy/console.env`, found as the Inputs say under "The console's version"; `console-ui/`, where there is one, moved to the same version of the package, so that the pages and the server stay one release; a push, which builds the image and rolls it, `migrate` first. |
8
+ | A plugin installed, updated or removed | The package named in `plugins/package.json` at an exact version or a tag of a git host, and the lockfile written beside it: `cd plugins && npm install --save-exact --package-lock-only --ignore-scripts <package>@<version>` (`npm uninstall --package-lock-only <package>` to remove), as [deploy/README.md "Plugins in the image"](/deploy/README.md#plugins-in-the-image) says; a push, which builds the image with the plugin installed and rolls it, `migrate` first. Then the administrator enables it on the console's settings page, consenting to what it asks, makes or chooses its board project and grants its roles ([planning-the-board](/skills/planning-the-board/references/setting-up.md)); a plugin with `storage:sql` needs the database user to hold `CREATEROLE`, which the master user does. The record names the plugin and its version. What a removed plugin stored stays until an administrator purges it. |
9
+ | A new connector | `CLOUDFLARED_VERSION` in `deploy/console.env`, the connector's newest release, which `gh api repos/cloudflare/cloudflared/releases/latest --jq .tag_name` prints; a push. |
10
+ | The next size | The numbers of the task definitions set to the next size's (sizing.md "The sizes"), `API_COUNT` and `WORKER_COUNT` in `deploy/console.env`; to `large`, `deploy/worker.task.json` written as resources.md gives it and `WORKER_IN_PROCESS` dropped from the api task; the database's class changed with one command, when nobody works (sizing.md "Growing"); the estimate of the new size at the plan; a push. |
11
+ | A person let in or taken out | With an organization at GitHub, in the organization, which the console reads on its own; otherwise by an administrator, on the People page or with `console person add <login>` and `console person deactivate <login>`; no push, since nothing of the deployment changes. |
12
+ | A secret rotated | The new value read from a secrets file of the same shape, as in phase 3, never from the chat; the parameter overwritten with the skill's script and the service rolled, so that the tasks start with it (resources.md "Phase 8: the record", "A secret rotated"); the file deleted again on the person's word; the record's line "a secret rotated", with the date. |
13
+ | A console shared with outside teams or customers, each in projects of their own | The settings of [deploy/README.md "A shared console"](/deploy/README.md#a-shared-console): who sees whom, the bytes a project's files may hold together, tokens that end; as lines of the task definitions, with a push, or on the Settings page, with none. |
14
+ | The console paused | Both services at zero tasks and the database stopped, which pauses the deployment at the cost of storage alone, for seven days at most, after which the database starts itself (sizing.md "Growing", "Fewer people"); the reverse resumes; the record says since when. |
15
+ | The console removed | A run of its own, asked for and confirmed resource by resource (hard rule 7), as resources.md "Removing" says: a last snapshot of the database first, and the deployment repository kept, with the record of what was removed. |