@skillcdn/console 0.1.6 → 0.1.8

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 (97) hide show
  1. package/README.md +50 -2
  2. package/SKILLCDN.md +35 -0
  3. package/dist/cli/cli.d.ts +3 -0
  4. package/dist/cli/cli.d.ts.map +1 -1
  5. package/dist/cli/cli.js +61 -0
  6. package/dist/cli/cli.js.map +1 -1
  7. package/dist/cli/help.d.ts.map +1 -1
  8. package/dist/cli/help.js +5 -0
  9. package/dist/cli/help.js.map +1 -1
  10. package/dist/cli/main.js +2 -0
  11. package/dist/cli/main.js.map +1 -1
  12. package/dist/cli/serve.d.ts +37 -0
  13. package/dist/cli/serve.d.ts.map +1 -0
  14. package/dist/cli/serve.js +218 -0
  15. package/dist/cli/serve.js.map +1 -0
  16. package/dist/console.d.ts +6 -0
  17. package/dist/console.d.ts.map +1 -1
  18. package/dist/console.js +78 -13
  19. package/dist/console.js.map +1 -1
  20. package/dist/data.d.ts.map +1 -1
  21. package/dist/data.js +5 -1
  22. package/dist/data.js.map +1 -1
  23. package/dist/i18n/en.d.ts +4 -0
  24. package/dist/i18n/en.d.ts.map +1 -1
  25. package/dist/i18n/en.js +4 -0
  26. package/dist/i18n/en.js.map +1 -1
  27. package/dist/i18n/ko.d.ts.map +1 -1
  28. package/dist/i18n/ko.js +4 -0
  29. package/dist/i18n/ko.js.map +1 -1
  30. package/dist/schemas.d.ts +1 -0
  31. package/dist/schemas.d.ts.map +1 -1
  32. package/dist/schemas.js +5 -0
  33. package/dist/schemas.js.map +1 -1
  34. package/dist/web.d.ts +16 -0
  35. package/dist/web.d.ts.map +1 -0
  36. package/dist/web.js +149 -0
  37. package/dist/web.js.map +1 -0
  38. package/docs/specs/cli.md +92 -0
  39. package/docs/specs/rest.md +221 -0
  40. package/package.json +14 -3
  41. package/skills/building-a-console/SKILL.md +102 -0
  42. package/skills/building-a-console/references/the-default-console.md +140 -0
  43. package/skills/deploying-a-console/SKILL.md +130 -0
  44. package/skills/deploying-a-console/assets/deployer-policy.json +118 -0
  45. package/skills/deploying-a-console/assets/secrets.example.json +11 -0
  46. package/skills/deploying-a-console/references/access.md +78 -0
  47. package/skills/deploying-a-console/references/resources.md +516 -0
  48. package/skills/deploying-a-console/references/sizing.md +90 -0
  49. package/skills/deploying-a-console/scripts/import-aws-key.mjs +35 -0
  50. package/skills/deploying-a-console/scripts/store-secrets.mjs +114 -0
  51. package/skills/working-the-board/SKILL.md +86 -0
  52. package/src/api.ts +235 -0
  53. package/src/cli/cli.ts +1114 -0
  54. package/src/cli/credentials.ts +68 -0
  55. package/src/cli/directory.ts +59 -0
  56. package/src/cli/format.ts +284 -0
  57. package/src/cli/help.ts +326 -0
  58. package/src/cli/main.ts +39 -0
  59. package/src/cli/serve.ts +276 -0
  60. package/src/cli/terminal.ts +28 -0
  61. package/src/client.ts +533 -0
  62. package/src/components/board.tsx +112 -0
  63. package/src/components/connect.tsx +126 -0
  64. package/src/components/decision-form.tsx +136 -0
  65. package/src/components/decision-list.tsx +278 -0
  66. package/src/components/documents.tsx +569 -0
  67. package/src/components/event-feed.tsx +129 -0
  68. package/src/components/markdown.tsx +72 -0
  69. package/src/components/people.tsx +59 -0
  70. package/src/components/projects.tsx +364 -0
  71. package/src/components/runs.tsx +175 -0
  72. package/src/components/shell.tsx +284 -0
  73. package/src/components/sign-in.tsx +61 -0
  74. package/src/components/skills.tsx +78 -0
  75. package/src/components/task-form.tsx +229 -0
  76. package/src/components/task-view.tsx +272 -0
  77. package/src/components/tokens.tsx +223 -0
  78. package/src/components/ui.tsx +219 -0
  79. package/src/connect-page.tsx +148 -0
  80. package/src/connect.ts +27 -0
  81. package/src/console.tsx +1166 -0
  82. package/src/data.ts +493 -0
  83. package/src/docs-page.tsx +334 -0
  84. package/src/documents.ts +76 -0
  85. package/src/i18n/en.ts +537 -0
  86. package/src/i18n/index.ts +76 -0
  87. package/src/i18n/ko.ts +511 -0
  88. package/src/index.ts +142 -0
  89. package/src/limits.ts +61 -0
  90. package/src/router.ts +295 -0
  91. package/src/routes.ts +106 -0
  92. package/src/schemas.ts +776 -0
  93. package/src/styles/console.css +2432 -0
  94. package/src/text.ts +33 -0
  95. package/src/use-action.ts +37 -0
  96. package/src/vocabulary.ts +109 -0
  97. package/src/web.ts +170 -0
@@ -0,0 +1,140 @@
1
+ # The default console, as it is wired
2
+
3
+ The four files of the default UI, `apps/console/web/` in the console's repository, quoted here so that an agent that has the skill and not the repository starts from the same shape: the page with its root, the script that composes the console with the brand's marks and the Korean face, the build that links the icons from the brand's package and sends the API to a running server, and the compiler's settings. The repository's check keeps them identical to the files; the package's sources under `src/` say what each import is.
4
+
5
+ ### `apps/console/web/index.html`
6
+
7
+ ```html
8
+ <!doctype html>
9
+ <html lang="en">
10
+ <head>
11
+ <meta charset="utf-8" />
12
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
13
+ <meta name="color-scheme" content="dark" />
14
+ <title>Console</title>
15
+ </head>
16
+ <body>
17
+ <div id="root"></div>
18
+ <script type="module" src="/src/main.tsx"></script>
19
+ </body>
20
+ </html>
21
+ ```
22
+
23
+ ### `apps/console/web/src/main.tsx`
24
+
25
+ ```tsx
26
+ // The marks of the brand come from the main repository's package, never from a copy here
27
+ // (ADR-0013); the build gives back their addresses. The pages are dark, so the wordmark is white.
28
+ import symbol from "@skillcdn/brand/symbol.svg";
29
+ import wordmark from "@skillcdn/brand/wordmark-white.svg";
30
+ import { createConsole } from "@skillcdn/console";
31
+ // The styles ship with the package; the build bundles them next to the script.
32
+ import "@skillcdn/console/console.css";
33
+ // The Korean face of SkillCDN's own pages, Pretendard, in the subsets a browser fetches as it
34
+ // needs them; the package's stylesheet names it first and falls through to the system's faces.
35
+ import "pretendard/dist/web/variable/pretendardvariable-dynamic-subset.css";
36
+
37
+ // The default UI the image serves: the package's composition, with the default configuration
38
+ // (docs/architecture.md, "The package and custom consoles"). A custom console is this file,
39
+ // with a configuration of its own, in a repository of its own.
40
+
41
+ const container = document.getElementById("root");
42
+ if (container === null) {
43
+ throw new Error("the page has no #root element");
44
+ }
45
+ createConsole({ brand: { symbol, wordmark } }).mount(container);
46
+ ```
47
+
48
+ ### `apps/console/web/vite.config.ts`
49
+
50
+ ```ts
51
+ import { createRequire } from "node:module";
52
+ import path from "node:path";
53
+ import { fileURLToPath } from "node:url";
54
+ import react from "@vitejs/plugin-react";
55
+ import { defineConfig, type Plugin } from "vite";
56
+
57
+ /** Where the `api` role listens by default; what the development server sends the API to. */
58
+ const API_URL = "http://127.0.0.1:11199";
59
+
60
+ const here = fileURLToPath(new URL(".", import.meta.url));
61
+ const resolve = createRequire(import.meta.url).resolve;
62
+
63
+ /** A file of the brand package as an address relative to the page, which the build takes as an asset. */
64
+ function brandFile(name: string): string {
65
+ return path
66
+ .relative(here, resolve(`@skillcdn/brand/${name}`))
67
+ .split(path.sep)
68
+ .join("/");
69
+ }
70
+
71
+ // The page's icons come from `@skillcdn/brand`, as the symbol and the wordmark do in main.tsx
72
+ // (ADR-0013): linked here, before the build reads the page, so that it bundles them next to the
73
+ // script like any other asset and never copies them into this repository.
74
+ const icons: Plugin = {
75
+ name: "console:icons",
76
+ transformIndexHtml: {
77
+ order: "pre",
78
+ handler: () => [
79
+ {
80
+ tag: "link",
81
+ attrs: { rel: "icon", href: brandFile("favicon.ico"), sizes: "32x32" },
82
+ injectTo: "head",
83
+ },
84
+ {
85
+ tag: "link",
86
+ attrs: { rel: "icon", href: brandFile("favicon.svg"), type: "image/svg+xml" },
87
+ injectTo: "head",
88
+ },
89
+ {
90
+ tag: "link",
91
+ attrs: { rel: "apple-touch-icon", href: brandFile("apple-touch-icon.png") },
92
+ injectTo: "head",
93
+ },
94
+ ],
95
+ },
96
+ };
97
+
98
+ // `vite build` writes the default UI into web/dist, which the `api` role serves from WEB_ROOT.
99
+ // `vite` serves the UI with its own reloading and sends everything else to a running `api`.
100
+ export default defineConfig({
101
+ root: here,
102
+ plugins: [react(), icons],
103
+ publicDir: false,
104
+ server: {
105
+ // Next to the api role's 11199, so that every local port of the project is one block.
106
+ port: 11198,
107
+ proxy: {
108
+ "/api": { target: API_URL, changeOrigin: false },
109
+ "/auth": { target: API_URL, changeOrigin: false },
110
+ "/healthz": { target: API_URL, changeOrigin: false },
111
+ "/readyz": { target: API_URL, changeOrigin: false },
112
+ },
113
+ },
114
+ build: {
115
+ outDir: "dist",
116
+ emptyOutDir: true,
117
+ sourcemap: true,
118
+ },
119
+ });
120
+ ```
121
+
122
+ ### `apps/console/web/tsconfig.json`
123
+
124
+ ```json
125
+ {
126
+ "extends": "../../../tsconfig.base.json",
127
+ "compilerOptions": {
128
+ "lib": ["ES2024", "DOM", "DOM.Iterable"],
129
+ "module": "ESNext",
130
+ "moduleResolution": "Bundler",
131
+ "jsx": "react-jsx",
132
+ "noEmit": true,
133
+ "types": ["vite/client", "node"]
134
+ },
135
+ "include": ["src", "vite.config.ts"],
136
+ "references": [{ "path": "../../../packages/console/tsconfig.build.json" }]
137
+ }
138
+ ```
139
+
140
+ What a console of one's own changes. In the page: its `<title>` and `lang`, and its icons, linked directly in the page or through the plugin, for its own. In the script: the `title` it passes; the two `@skillcdn/brand` imports, for its own symbol and wordmark (a light one, since the pages are dark), or none; the face it loads, for its languages (`npm install pretendard` for Korean). In the build: the proxy's target, `http://127.0.0.1:11197` with `changeOrigin: true` for `console serve`; the default console's own proxy points at the `api` role's development port, which a console of one's own does not have, and its plugin reads the icons from the brand package, which a console of one's own replaces with files of its own. In the compiler's settings: `jsx`, `moduleResolution` and the `vite/client` types stay, which make an imported picture a module; `extends` and `references` are the repository's and go, with `"strict": true` of the console's own in their place.
@@ -0,0 +1,130 @@
1
+ ---
2
+ name: deploying-a-console
3
+ description: "Deploys a SkillCDN Console for an organization on AWS behind Cloudflare, with an agent on the person's own machine and 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), shows the monthly cost of the size that fits and of the next one before anything is made, creates the database, the bucket, the secrets, the tunnel and the containers, leaves a private repository that redeploys on every push, signs the first administrator in, makes the first project, connects an agent and proves it all with a file handed in and read back. Also grows a deployment 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."
4
+ license: MIT
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
+ metadata:
7
+ author: skillcdn
8
+ version: "1.0"
9
+ skillcdn:
10
+ translations:
11
+ ko:
12
+ title: 조직의 콘솔 배포하기
13
+ description: "조직의 SkillCDN 콘솔을 AWS와 Cloudflare에 배포합니다. 개발자가 아니어도 됩니다: 에이전트가 쉬운 질문 몇 개(몇 명이 쓰는지, 주소, 로그인 방식과 구성원, 조직 스킬의 위치)를 묻고, 맞는 크기와 그 다음 크기의 월 비용을 먼저 보여준 뒤, 데이터베이스·버킷·비밀·터널·컨테이너를 만들고, 푸시할 때마다 다시 배포되는 비공개 저장소를 남기고, 첫 관리자를 로그인시키고 첫 프로젝트를 만들고 에이전트를 연결해 파일 제출과 되읽기로 증명합니다. 규모 키우기, 콘솔 새 버전으로 올리기, 구성원 추가, 비밀 교체도 합니다. 조직의 콘솔을 배포·설치·호스팅·업그레이드·확장하라고 할 때 쓰세요. 콘솔 페이지를 만들 때는 building-a-console, 보드의 작업을 할 때는 working-the-board를 쓰세요."
14
+ ---
15
+ # Deploying a console for an organization
16
+
17
+ What goes in: an AWS account, a domain on Cloudflare or one to buy, and the answers to a few plain questions. What comes out: the console at `https://<hostname>`, signed in by its first administrator, with a first project and an agent connected; running as containers on AWS next to a managed PostgreSQL and a bucket, reached only through Cloudflare, every secret in a parameter store; a private deployment repository whose every push rebuilds and redeploys; a budget alert; and a record, `DEPLOY.md`, of what exists and how it is run. The person never needs to know the cloud: every choice is derived from their answers, shown with its cost, and changed when they ask. The shape and why it was chosen are [ADR-0017](/docs/adr/0017-the-reference-deployment-is-the-image-on-aws-behind-cloudflare-written-as-a-skill.md); what the console asks of any platform is [deploy/README.md](/deploy/README.md).
18
+
19
+ ## How the person is involved
20
+
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
+ - **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.
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.
27
+
28
+ ## Requirements
29
+
30
+ | What | Used for |
31
+ |---|---|
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
+ | 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
+ | 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 |
36
+ | The person, with a browser on any device | The OAuth app, the first sign-in, approving the agent's code, a registrar's nameservers |
37
+ | Network access to AWS, Cloudflare, GitHub and npm | |
38
+
39
+ Check these before the first message. Without the AWS CLI, offer to install it (access.md); without `gh`, the person makes the repository; without network, stop. This skill's own pages ([access.md](references/access.md), [sizing.md](references/sizing.md), [resources.md](references/resources.md)) arrive with it through SkillCDN, in the package, and in a copy of its directory. `deploy/README.md` and the decisions are read through the repository connection or in a checkout of the console's repository; without them, this skill and its pages carry the shape.
40
+
41
+ ## Inputs
42
+
43
+ | Input | Source |
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 | Asked, with the organization's name as the suggestion when the request gives one: "What should the board be called? Usually the organization's name." |
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. |
48
+ | How people sign in, and who | Asked: "How should people sign in: with GitHub, with Google (Workspace), or both? Who is in: everyone's GitHub login or Google address, and who among them configures the console (you, at least)? With a Google Workspace, everyone of the domain can be in." |
49
+ | The organization's skills | Asked: "Where are your organization's skills on SkillCDN, if anywhere yet? An address like `/gh/<owner>/<repo>`." Default: none; a project names one later on its Settings page. |
50
+ | 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). |
51
+ | 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). |
52
+ | 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. |
53
+ | 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. |
54
+ | Where alerts go | Asked: "Which email should the cost alert go to?" |
55
+ | The console's version | Derived: the current commit of `main` of the console's repository (`git ls-remote https://github.com/skillcdn/console main`), pinned in the deployment repository; an update is a new commit there. |
56
+ | 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`, `CLIENT_IP_HEADER=cf-connecting-ip`, `HTTP_KEEP_ALIVE_SECONDS=100`), 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. |
57
+
58
+ ## Workflow
59
+
60
+ Each phase produces a named artifact and ends with one line to the person. The procedure of each, its commands, its checks and what to do when a step fails, is in [resources.md](references/resources.md) under the phase's name; the sizes and the prices in [sizing.md](references/sizing.md); the credentials and the dashboard steps in [access.md](references/access.md).
61
+
62
+ ### Phase 1: Access
63
+
64
+ Produces the **access record**: the account id, the region, the default VPC and its subnets, the Cloudflare account and the zone of the domain (or none yet), the git host owner. Make the run's folder with its `scratch/` subfolder and `.gitignore`; install the `console` command and copy this skill's scripts into `scratch/`; import the key into the profile `console` and read the token from its file, as access.md says, and verify each with the harmless calls there; report a missing permission now, with the row to add, not at the step that fails.
65
+
66
+ ### Phase 2: Plan and estimate
67
+
68
+ Produces the **plan**, shown at the checkpoint. From the answers: the size and what it is made of (sizing.md "The sizes"); today's prices, read as sizing.md "Reading today's prices" says, and the estimate of this size and the next one, each line saying whether it is today's price or the snapshot; the resources to create under which names; the domain's path (owned, with the registrar's steps already sent; bought at the price shown); the secrets, which are generated, which are derived, and which the person holds and will be asked for at phase 3 with the exact steps; the budget alert; the few steps only the person takes, and when. Then stop, with the estimate message of sizing.md and: "I will create <the resources>, set <n> secrets, connect <hostname> through Cloudflare, and make a repository at <owner>/<name> that deploys on every push. You do once: <the steps>. OK?"
69
+
70
+ ### Phase 3: Foundation
71
+
72
+ Produces the **resource record**: each resource with its name and id. In this order: the zone at Cloudflare, 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 and its keys; the image repository, the log group, the roles, the cluster; the tunnel, the hostname, always-HTTPS 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 OAuth 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.
73
+
74
+ ### Phase 4: The deployment repository
75
+
76
+ 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; 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; 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.
77
+
78
+ ### Phase 5: Live
79
+
80
+ Produces the **live address**. The zone is active (else the wait of "How the person is involved"); `https://<hostname>/readyz` answers `200` through Cloudflare; the page shows the sign-in buttons of the providers set (none set: the stop of "How the person is involved"); the tunnel shows a connector; the log says `api listening`; the services are stable. Create the budget alert. Report the address.
81
+
82
+ ### Phase 6: The first people
83
+
84
+ Produces the **first administrator and the first project**. Tell the person: "Open <address> and sign in with <provider>; you are an administrator. Make the first project: Projects, New project, a key such as `general`, its name, and the skills address <address or none>. Tell me the key." Then connect this agent: `console login --url <address> --agent deploying-a-console`, which shows an address and a code; relay them, and the person approves on the console's own pages. `console use <key>` in the run's folder.
85
+
86
+ ### Phase 7: Proof
87
+
88
+ Produces the **proof**: `console task new "Deployment check"` with a body that says what this run deployed, `console take <number>` (the number and the run's id come from the two commands' `--json` answers), `console hand-in ./deployment-check.md --label "the check"` with a small file written for it, `console run <id> --json` showing the file with its hash, the object under that hash in the bucket (`aws s3api head-object`), and `console finish --summary "<what was deployed, and what is owed>"`. The task stays on the board as the first record of the deployment.
89
+
90
+ ### Phase 8: Record and delivery
91
+
92
+ 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.
93
+
94
+ ### Running it again
95
+
96
+ The same skill, from phase 2, with the plan naming only what changes: a new version (the commit in `deploy/console.env`, 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 member added or removed (`MEMBERS` and `ADMINS` in `deploy/api.task.json`, a 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.
97
+
98
+ ### Verdicts
99
+
100
+ | Result | Verdict |
101
+ |---|---|
102
+ | `/readyz` answers `200` at the hostname over HTTPS; the administrator signed in; the project made; the agent connected with a code; the file handed in is in the bucket under its hash and `console run` shows it; the workflow's run is green; every parameter is present; the record is pushed | Accepted |
103
+ | A second way of signing in is `later` (it stays off); the budget alert waits on the person confirming its email | Accepted, with the gap named in `DEPLOY.md` and the delivery |
104
+ | The zone waits for its nameservers, or the only way of signing in is `later`: phases 5 to 7 wait with it, the record says so, and the delivery says what was verified and what waits | Paused, to resume at phase 5 |
105
+ | A task never becomes healthy (the log says why: exit `78` names a variable, a database unreachable names the network), the tunnel shows no connector, the first sign-in is refused, the migration task exits other than `0` | Redone from the phase that failed, with resources.md "When a step fails"; the person is told what was tried |
106
+
107
+ ## Hard rules
108
+
109
+ 1. The access key, the token and every secret stay out of the repository, the logs, the chat and the agent's own words: a value is read from its file into the environment or the parameter store, never put as a literal on a command line, never echoed; a value the person pasted is written to its file with a file tool and the person is told to make a new one when the run is over; the secrets file is deleted, on the person's word, when its values are stored. A leak means a rotation, which the person is told at once.
110
+ 2. Nothing is bought or made before the price is in front of the person and they said the word: the second message carries the order of cost, the plan carries today's estimate of everything that bills; a domain is bought only after its price is shown.
111
+ 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.
112
+ 4. The person is never asked to do what the key and the token can do; what only they can do (an OAuth app, 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.
113
+ 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.
114
+ 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.
115
+ 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.
116
+ 8. The console's contract holds on every rollout: `migrate` runs and exits `0` before the new `api` and `worker` start, the stop timeout stays above the shutdown grace period, and no secret is a build argument.
117
+
118
+ ## Terms
119
+
120
+ | Term | Meaning |
121
+ |---|---|
122
+ | Size | `small`, `medium` or `large`: the numbers of sizing.md, chosen by how many people. |
123
+ | The deployer | The IAM user whose key the agent acts with, as the profile `console` of the AWS CLI. |
124
+ | 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. |
125
+ | The tunnel | The Cloudflare Tunnel whose connector runs beside the `api` container; the hostname's traffic comes in through it and nothing else does. |
126
+ | 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. |
127
+ | Parameter | A value in SSM Parameter Store that a task reads as an environment variable at start. |
128
+ | The deployment repository | The private repository of the task definitions, the workflow and the record; a push to it is a deployment. |
129
+ | `DEPLOY.md` | The record: what exists under which names, how it is run, what is owed, what it costs. |
130
+ | Later | A secret the person did not supply; the way of signing in it serves stays off, and the record says so. |
@@ -0,0 +1,118 @@
1
+ {
2
+ "Version": "2012-10-17",
3
+ "Statement": [
4
+ {
5
+ "Sid": "WhoAmIAndPrices",
6
+ "Effect": "Allow",
7
+ "Action": ["sts:GetCallerIdentity", "pricing:GetProducts", "ec2:DescribeRegions"],
8
+ "Resource": "*"
9
+ },
10
+ {
11
+ "Sid": "Network",
12
+ "Effect": "Allow",
13
+ "Action": [
14
+ "ec2:DescribeVpcs",
15
+ "ec2:DescribeSubnets",
16
+ "ec2:DescribeSecurityGroups",
17
+ "ec2:DescribeAvailabilityZones",
18
+ "ec2:DescribeNetworkInterfaces",
19
+ "ec2:CreateDefaultVpc",
20
+ "ec2:CreateSecurityGroup",
21
+ "ec2:DeleteSecurityGroup",
22
+ "ec2:AuthorizeSecurityGroupIngress",
23
+ "ec2:RevokeSecurityGroupIngress",
24
+ "ec2:CreateTags"
25
+ ],
26
+ "Resource": "*"
27
+ },
28
+ {
29
+ "Sid": "Containers",
30
+ "Effect": "Allow",
31
+ "Action": ["ecs:*", "ecr:*", "logs:*"],
32
+ "Resource": "*"
33
+ },
34
+ {
35
+ "Sid": "Database",
36
+ "Effect": "Allow",
37
+ "Action": ["rds:*"],
38
+ "Resource": "*"
39
+ },
40
+ {
41
+ "Sid": "Files",
42
+ "Effect": "Allow",
43
+ "Action": ["s3:*"],
44
+ "Resource": ["arn:aws:s3:::console-*", "arn:aws:s3:::console-*/*"]
45
+ },
46
+ {
47
+ "Sid": "ListBuckets",
48
+ "Effect": "Allow",
49
+ "Action": ["s3:ListAllMyBuckets"],
50
+ "Resource": "*"
51
+ },
52
+ {
53
+ "Sid": "Parameters",
54
+ "Effect": "Allow",
55
+ "Action": [
56
+ "ssm:PutParameter",
57
+ "ssm:GetParameter",
58
+ "ssm:GetParameters",
59
+ "ssm:GetParametersByPath",
60
+ "ssm:DeleteParameter",
61
+ "ssm:AddTagsToResource"
62
+ ],
63
+ "Resource": "arn:aws:ssm:*:*:parameter/console/*"
64
+ },
65
+ {
66
+ "Sid": "ListParameters",
67
+ "Effect": "Allow",
68
+ "Action": ["ssm:DescribeParameters"],
69
+ "Resource": "*"
70
+ },
71
+ {
72
+ "Sid": "RolesAndUsersOfTheConsole",
73
+ "Effect": "Allow",
74
+ "Action": [
75
+ "iam:CreateRole",
76
+ "iam:DeleteRole",
77
+ "iam:GetRole",
78
+ "iam:UpdateAssumeRolePolicy",
79
+ "iam:AttachRolePolicy",
80
+ "iam:DetachRolePolicy",
81
+ "iam:PutRolePolicy",
82
+ "iam:DeleteRolePolicy",
83
+ "iam:ListAttachedRolePolicies",
84
+ "iam:ListRolePolicies",
85
+ "iam:PassRole",
86
+ "iam:TagRole",
87
+ "iam:CreateUser",
88
+ "iam:DeleteUser",
89
+ "iam:GetUser",
90
+ "iam:PutUserPolicy",
91
+ "iam:DeleteUserPolicy",
92
+ "iam:CreateAccessKey",
93
+ "iam:DeleteAccessKey",
94
+ "iam:ListAccessKeys",
95
+ "iam:TagUser"
96
+ ],
97
+ "Resource": ["arn:aws:iam::*:role/console-*", "arn:aws:iam::*:user/console-*"]
98
+ },
99
+ {
100
+ "Sid": "ServiceLinkedRolesAndTheGitHostProvider",
101
+ "Effect": "Allow",
102
+ "Action": [
103
+ "iam:CreateServiceLinkedRole",
104
+ "iam:CreateOpenIDConnectProvider",
105
+ "iam:GetOpenIDConnectProvider",
106
+ "iam:ListOpenIDConnectProviders",
107
+ "iam:TagOpenIDConnectProvider"
108
+ ],
109
+ "Resource": "*"
110
+ },
111
+ {
112
+ "Sid": "BudgetAlert",
113
+ "Effect": "Allow",
114
+ "Action": ["budgets:ViewBudget", "budgets:ModifyBudget"],
115
+ "Resource": "*"
116
+ }
117
+ ]
118
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "_": "The shape of the secrets file the run keeps outside any repository and deletes, on the person's word, once every value is in the parameter store. A value is what the run generated or derived, what the person gave, or the word later; a provider the person did not choose has no line.",
3
+ "AUTH_SECRET": "generated: 48 random characters, never shown",
4
+ "DATABASE_URL": "derived: postgres://console:<generated password>@<database endpoint>:5432/console",
5
+ "DATABASE_CA": "derived: the regional certificate bundle of the database service, as PEM",
6
+ "S3_ACCESS_KEY_ID": "derived: the access key id of the files user",
7
+ "S3_SECRET_ACCESS_KEY": "derived: its secret",
8
+ "TUNNEL_TOKEN": "derived: the connector token of the tunnel",
9
+ "GITHUB_CLIENT_SECRET": "asked: the client secret of the OAuth app, or later",
10
+ "GOOGLE_CLIENT_SECRET": "asked: the client secret of the OAuth client, or later"
11
+ }
@@ -0,0 +1,78 @@
1
+ # Access: what the person creates once, and how it reaches the agent
2
+
3
+ What the agent needs to act on the organization's accounts, the exact steps of the day for the person to create each, and how each value reaches the agent without passing through the chat. Dashboards move; every path here carries the month it was checked, and the vendor's own page is the source when a label has changed.
4
+
5
+ ## The AWS account and the deployer
6
+
7
+ **Which account.** The skill asks whether the organization has an account for this. A dedicated AWS account for the console is the simplest to run and to read the bill of: nothing else shares it, and one budget alert covers it. An account that already runs other things works with the scoped policy below. A new account (`aws.amazon.com`, Create an AWS account; a payment method is asked; checked 2026-10) is created by the person on the **paid plan**, once the order of cost is in front of them (the skill's second message): the free plan ends after six months and closes the account with its data, and a console is meant to stay. Both plans start with credits, which the paid plan spends on the first months' bills.
8
+
9
+ **The deployer user and its policy.** Signed in to the account, in the IAM console (checked 2026-10):
10
+
11
+ 1. Users, Create user. Name `console-deployer`. Leave console access off: this user is for the agent's commands only. Next.
12
+ 2. Permissions: Attach policies directly. In a dedicated account, `AdministratorAccess`, which is the honest choice when nobody else is in the account. In a shared account, first create a policy from [deployer-policy.json](../assets/deployer-policy.json) (Policies, Create policy, JSON, paste, name it `console-deployer`), then attach that one. The scoped policy is broad where names cannot be known in advance and tight where they can (`console-` prefixed roles, users and buckets, `/console/` parameters); a step the policy does not cover fails with `AccessDenied` naming the action, and the fix is to add that action.
13
+ 3. Create user. Open it, Security credentials, Access keys, Create access key. Use case: Command Line Interface (and tick the confirmation), or Other. Next, Create access key. **Download .csv file.** The secret is shown once; the file is the one copy.
14
+
15
+ **How the key reaches the agent.** Save the downloaded file as `.aws-key.csv` in the run's folder, and tell the agent. The agent reads the two values from the file with a script, writes them as the profile `console` into the AWS CLI's own credentials file (`~/.aws/credentials`, the person's file, readable by them alone where the system can say so), sets the region on the profile, and deletes `.aws-key.csv`; neither value is ever written in a command line, a message or a log. A key pasted into the chat instead is written to the profile the same way and never repeated, and the person is told to make a new key once the run is over, since the chat kept a copy.
16
+
17
+ **The AWS CLI.** Version 2: `aws --version`. Without it, Windows: `winget install Amazon.AWSCLI`; macOS: `brew install awscli`, or the installer at `docs.aws.amazon.com/cli` (checked 2026-10). The agent installs it with the person's word.
18
+
19
+ **Verify:**
20
+
21
+ ```sh
22
+ aws sts get-caller-identity --profile console # the account id, and the user's ARN
23
+ aws ec2 describe-vpcs --profile console --filters Name=is-default,Values=true --query 'Vpcs[0].VpcId'
24
+ ```
25
+
26
+ The first answers with the account id the plan names; the second with the default VPC, or nothing, in which case `aws ec2 create-default-vpc` makes one (the deployer's policy allows it). Every command of the run carries `--profile console` (or `AWS_PROFILE=console` in the shell) and `--region <region>`.
27
+
28
+ ## The Cloudflare account and the token
29
+
30
+ **The domain.** The console's address is a hostname of a zone on Cloudflare: a domain the organization already has, moved to Cloudflare's nameservers (the zone is made first in the run, and the two nameservers it answers go to the person to set at their registrar), or one bought through Cloudflare's Registrar in the run with its price shown first. There is no free hostname for a tunnel; a domain is needed. The account is free (`dash.cloudflare.com/sign-up`).
31
+
32
+ **The nameservers, at the registrar.** The person named the registrar with the domain; give its steps where they are known (the registrar's own help page for "nameservers" or "DNS settings", read at run time), and otherwise the general ones: sign in at the registrar, open the domain, find the nameserver (NS) setting, replace what is there with the two names Cloudflare gave, save; the change takes minutes to hours to reach the world, and the run checks the zone's status until it is `active`. Everything else of the domain's DNS (mail records, other hosts) is copied into the Cloudflare zone before the switch, from what the registrar shows, so that nothing stops working; the run asks for that list when the domain already serves something.
33
+
34
+ **The token.** My Profile, API Tokens (`dash.cloudflare.com/profile/api-tokens`), Create Token, **Create Custom Token** (checked 2026-10). Name it `console deploy`. Permissions, one row each:
35
+
36
+ | Scope | Permission | Level | For |
37
+ |---|---|---|---|
38
+ | Account | Cloudflare Tunnel | Edit | The tunnel the task connects out through, and its routing |
39
+ | Account | Account Settings | Read | Listing accounts, the account id |
40
+ | Zone | DNS | Edit | The console's hostname |
41
+ | Zone | Zone | Edit | Making the zone for a domain the organization owns, finding it, its status and nameservers |
42
+ | Zone | Zone Settings | Edit | Always use HTTPS |
43
+ | Zone | Zone WAF | Edit | The rate limiting rule |
44
+ | Account | Registrar | Edit | Only when a domain is to be bought in the run |
45
+
46
+ 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.
47
+
48
+ **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.
49
+
50
+ **Verify:**
51
+
52
+ ```sh
53
+ CLOUDFLARE_API_TOKEN=$(tr -d '\r\n ' < .cf-token)
54
+ curl -s https://api.cloudflare.com/client/v4/user/tokens/verify -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
55
+ curl -s https://api.cloudflare.com/client/v4/zones -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
56
+ curl -s https://api.cloudflare.com/client/v4/accounts -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
57
+ ```
58
+
59
+ `"status": "active"`, the zones the token sees (the domain among them, or none yet), and the account with its id. The file carries a carriage return on Windows; the `tr` strips it.
60
+
61
+ ## The git host
62
+
63
+ **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.
64
+
65
+ **The OAuth app, for signing in with GitHub.** Once the console's address is known, the person, signed in to GitHub (checked 2026-10): Settings (under the avatar), Developer settings, OAuth Apps, New OAuth App (Register a new application, the first time).
66
+
67
+ - Application name: the board's name.
68
+ - Homepage URL: the console's address, `https://<hostname>`.
69
+ - Authorization callback URL: `https://<hostname>/auth/gh/callback`.
70
+ - Register application. Copy the **Client ID** into the chat (it is not secret). Generate a new client secret, and save it to the secrets file as `GITHUB_CLIENT_SECRET` (below), 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.
71
+
72
+ An organization-owned app is the same form under the organization's settings, Developer settings. No scopes are needed: the console asks only who the person is.
73
+
74
+ **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.
75
+
76
+ ## The secrets file
77
+
78
+ What the person holds (a client secret) is written to a file outside any repository, in the shape of [secrets.example.json](../assets/secrets.example.json): `console-secrets.json` in the folder above the run's folder, with the generated and derived values the run adds to it. The run reads it to put each value in the parameter store, and deletes it at the end on the person's word. A value the person says `later` to leaves its provider out until it is set; the record names it. A provider the person did not choose has no line in it.