@skillcdn/console 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/SKILLCDN.md +2 -0
- package/dist/agent-name.d.ts +3 -0
- package/dist/agent-name.d.ts.map +1 -0
- package/dist/agent-name.js +9 -0
- package/dist/agent-name.js.map +1 -0
- package/dist/api.d.ts +6 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +6 -4
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +225 -24
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +14 -4
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +59 -10
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +28 -12
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.js +5 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/client.d.ts +20 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +11 -2
- package/dist/client.js.map +1 -1
- package/dist/components/calendar.d.ts +35 -2
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +80 -31
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/connect.d.ts +4 -1
- package/dist/components/connect.d.ts.map +1 -1
- package/dist/components/connect.js +7 -3
- package/dist/components/connect.js.map +1 -1
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +2 -2
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/documents.d.ts +5 -0
- package/dist/components/documents.d.ts.map +1 -1
- package/dist/components/documents.js +3 -2
- package/dist/components/documents.js.map +1 -1
- package/dist/components/event-feed.d.ts +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +10 -1
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/people.d.ts +24 -1
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +35 -3
- package/dist/components/people.js.map +1 -1
- package/dist/components/projects.d.ts +10 -1
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +8 -5
- package/dist/components/projects.js.map +1 -1
- package/dist/components/rules.d.ts +40 -0
- package/dist/components/rules.d.ts.map +1 -0
- package/dist/components/rules.js +46 -0
- package/dist/components/rules.js.map +1 -0
- package/dist/components/runs.d.ts +1 -1
- package/dist/components/runs.d.ts.map +1 -1
- package/dist/components/runs.js +2 -1
- package/dist/components/runs.js.map +1 -1
- package/dist/components/skills.d.ts.map +1 -1
- package/dist/components/skills.js +5 -1
- package/dist/components/skills.js.map +1 -1
- package/dist/components/task-form.d.ts.map +1 -1
- package/dist/components/task-form.js +1 -1
- package/dist/components/task-form.js.map +1 -1
- package/dist/components/tokens.d.ts.map +1 -1
- package/dist/components/tokens.js +2 -9
- package/dist/components/tokens.js.map +1 -1
- package/dist/components/ui.d.ts +7 -2
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +13 -0
- package/dist/components/ui.js.map +1 -1
- package/dist/components/work.d.ts +5 -1
- package/dist/components/work.d.ts.map +1 -1
- package/dist/components/work.js +1 -1
- package/dist/components/work.js.map +1 -1
- package/dist/console.css +263 -17
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +197 -43
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +19 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +27 -1
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +84 -9
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +84 -13
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +84 -13
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +4 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +4 -0
- package/dist/limits.js.map +1 -1
- package/dist/router.d.ts +30 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +50 -10
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +14 -4
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +17 -2
- package/dist/routes.js.map +1 -1
- package/dist/rules.d.ts +28 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +102 -0
- package/dist/rules.js.map +1 -0
- package/dist/schemas.d.ts +652 -4
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +96 -6
- package/dist/schemas.js.map +1 -1
- package/dist/vocabulary.d.ts +21 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +21 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +13 -9
- package/docs/specs/rest.md +33 -9
- package/package.json +1 -1
- package/skills/building-a-console/SKILL.md +1 -1
- package/skills/deploying-a-console/SKILL.md +5 -5
- package/skills/deploying-a-console/assets/secrets.example.json +2 -1
- package/skills/deploying-a-console/references/access.md +10 -6
- package/skills/deploying-a-console/references/resources.md +10 -7
- package/skills/working-the-board/SKILL.md +10 -9
- package/skills/working-the-board/references/project-rules.md +36 -0
- package/src/agent-name.ts +9 -0
- package/src/api.ts +37 -0
- package/src/cli/cli.ts +264 -26
- package/src/cli/format.ts +74 -10
- package/src/cli/help.ts +31 -11
- package/src/cli/main.ts +5 -0
- package/src/client.ts +47 -1
- package/src/components/calendar.tsx +252 -51
- package/src/components/connect.tsx +12 -6
- package/src/components/decision-list.tsx +2 -1
- package/src/components/documents.tsx +7 -3
- package/src/components/event-feed.tsx +16 -3
- package/src/components/people.tsx +244 -35
- package/src/components/projects.tsx +37 -14
- package/src/components/rules.tsx +167 -0
- package/src/components/runs.tsx +4 -2
- package/src/components/skills.tsx +22 -10
- package/src/components/task-form.tsx +1 -0
- package/src/components/tokens.tsx +2 -14
- package/src/components/ui.tsx +25 -2
- package/src/components/work.tsx +34 -28
- package/src/console.tsx +524 -45
- package/src/data.ts +52 -2
- package/src/i18n/en.ts +94 -14
- package/src/i18n/ko.ts +90 -14
- package/src/index.ts +16 -1
- package/src/limits.ts +4 -0
- package/src/router.ts +67 -12
- package/src/routes.ts +23 -3
- package/src/rules.ts +109 -0
- package/src/schemas.ts +121 -4
- package/src/styles/console.css +263 -17
- package/src/vocabulary.ts +30 -0
package/docs/specs/rest.md
CHANGED
|
@@ -11,7 +11,7 @@ The API is the board as its members see it: the people, the projects, and in eac
|
|
|
11
11
|
- Every answer is `cache-control: no-store`: it is one person's, or it changes.
|
|
12
12
|
- A request that changes something (`POST`, `PATCH`, `DELETE`) on a session must carry the session cookie **and** come from the console's own pages: the browser's `origin` header must be `PUBLIC_URL`. The cookie is `SameSite=Lax`, so another site's request does not carry it in the first place; the origin is the second fence.
|
|
13
13
|
- A token ([`POST /api/v1/tokens`](#get-apiv1tokens-and-post-apiv1tokens)) is presented as `Authorization: Bearer <secret>` on any route, by an agent, a script or a console of a person's own. It is then the credential: a cookie beside it is not looked at, and no origin is needed, since nothing attaches a token but whoever holds it. A token that is nothing is refused, whatever cookie travels with it. What a token does is attributed to its person, and the event names the agent: the token's name, or what the agent called itself on its run.
|
|
14
|
-
- A token configures as its person does ([ADR-0021](../adr/0021-a-token-configures-as-its-person-does.md)): every request a session may make, a token may make, within the same roles, except the tokens themselves and the approval of a connection, which stay a person's own doing, signed in on the console's own pages.
|
|
14
|
+
- A token configures as its person does ([ADR-0021](../adr/0021-a-token-configures-as-its-person-does.md)): every request a session may make, a token may make, within the same roles, except the tokens themselves and the approval of a connection, which stay a person's own doing, signed in on the console's own pages; the one thing a token does about the tokens is give itself up (`DELETE /api/v1/me/token`).
|
|
15
15
|
- An agent connects without anyone copying a token ([ADR-0011](../adr/0011-an-agent-connects-with-a-short-code-a-person-approves-and-the-token-is-never-shown.md)): [`POST /api/v1/connect`](#connecting-an-agent) is nobody's, the approval is a person's own doing on the console's own pages, and the claim is the command's, with the secret it was given.
|
|
16
16
|
- Request bodies are JSON, at most 256 KiB, and parsed once with the package's input schema. Text is trimmed where it is a line, bounded everywhere (`MAX_TITLE_LENGTH` and the rest of `@skillcdn/console/api`), and refused when it carries control or invisible characters. Links are `https` URLs only.
|
|
17
17
|
- Errors have `{ "error": { "code": "...", "message": "..." } }`. The code is stable; the message is for a person, in English.
|
|
@@ -22,7 +22,8 @@ The API is the board as its members see it: the people, the projects, and in eac
|
|
|
22
22
|
| 400 | `project.invalid_address` | The skills address is not one, as the standard spells them. |
|
|
23
23
|
| 400 | `project.invalid_icon` | The image for the mark is not a PNG, a JPEG, a GIF or a WebP, by its first bytes. |
|
|
24
24
|
| 404 | `project.no_icon` | The project has no image for its mark. |
|
|
25
|
-
| 400 | `member.invalid_person` | The person to list is not one of the workspace. |
|
|
25
|
+
| 400 | `member.invalid_person` | The person to list is not one of the workspace, or is deactivated. |
|
|
26
|
+
| 400 | `listing.unknown_host` | The login to let in names a provider nobody signs in through here. |
|
|
26
27
|
| 400 | `task.invalid_assignee` | The assignee is not a person who may work in the project. |
|
|
27
28
|
| 400 | `task.invalid_parent` | The parent is not a task of the project, is the task itself, or would make a loop. |
|
|
28
29
|
| 400 | `task.invalid_category` | No category of the workspace has that key. |
|
|
@@ -42,6 +43,7 @@ The API is the board as its members see it: the people, the projects, and in eac
|
|
|
42
43
|
| 404 | `project.not_found` | No project has that key, or it is not the asker's to see: the two are one answer. The key is checked before anything is asked. |
|
|
43
44
|
| 404 | `task.not_found`, `decision.not_found`, `goal.not_found`, `token.not_found`, `person.not_found`, `run.not_found`, `run.task_not_found`, `member.not_found` | No such id in the project (or in the workspace, for a token, a person); for a token, none of the asker's. An id that is not a UUID is not found either, except a task's number. |
|
|
44
45
|
| 404 | `category.not_found` | No category of the workspace has that key; a key that is not one is not found either. |
|
|
46
|
+
| 404 | `listing.not_found` | No login let in and not signed in with yet has that id; one somebody signed in with is the person's, and goes with their deactivation. |
|
|
45
47
|
| 404 | `file.not_found` | No file handed in, or attached to the page, has that id in the project; a link handed in is not a file. |
|
|
46
48
|
| 404 | `document.not_found`, `document.version_not_found` | No page has that path in the project, or the page has no such version. A path that is not one is not found either. |
|
|
47
49
|
| 404 | `connect.not_found` | No agent asks to connect under this code or secret: it was never asked, it expired, the person said no, or the token was handed over already. |
|
|
@@ -56,7 +58,9 @@ The API is the board as its members see it: the people, the projects, and in eac
|
|
|
56
58
|
| 409 | `token.too_many` | The person holds as many live tokens as one may (`MAX_TOKENS_PER_PERSON`). |
|
|
57
59
|
| 409 | `connect.approved` | The connection was approved already; the command claims its token. |
|
|
58
60
|
| 429 | `connect.too_many` | As many agents ask to connect as the console holds at once; try again in a few minutes. |
|
|
59
|
-
| 409 | `person.last_admin` | The change would leave the board without an administrator. |
|
|
61
|
+
| 409 | `person.last_admin` | The change would leave the board without an administrator in. |
|
|
62
|
+
| 409 | `person.of_organization` | The organization let the person in, and says whether they are in; an administrator lists their login to keep one who left ([ADR-0022](../adr/0022-who-is-in-is-the-consoles-own-record-and-an-administrator-keeps-it.md)). |
|
|
63
|
+
| 409 | `listing.exists` | The login is let in already, or is one an active person signs in as. |
|
|
60
64
|
| 409 | `run.over`, `run.task_closed` | The run has ended already; the task is done or dropped. A run open on a task is no refusal: a new run takes it over ([ADR-0020](../adr/0020-the-console-does-not-claim-an-agent-is-at-work-a-run-says-when-it-was-last-heard-from-and-a-new-run-takes-over.md)). |
|
|
61
65
|
| 413 | `request.too_large` | The body is over the limit; for a file handed in or attached, the file is over `MAX_FILE_BYTES`. |
|
|
62
66
|
| 503 | `file.store_unavailable` | The bytes of files are kept in a bucket, and it did not answer or refused the console; `retry-after` says when to try again. Nothing was written on the run or the page. |
|
|
@@ -65,15 +69,19 @@ The API is the board as its members see it: the people, the projects, and in eac
|
|
|
65
69
|
|
|
66
70
|
### `GET /api/v1/me`
|
|
67
71
|
|
|
68
|
-
`{ "workspace": { "name", "tokenDaysAtMost" }, "person": ... | null, "agent": "..." | null, "language": "..." | null, "signIn": [{ "key", "label" }, ...] }`: what the board is called
|
|
72
|
+
`{ "workspace": { "name", "tokenDaysAtMost", "skills": { "source", "address" } }, "person": ... | null, "agent": "..." | null, "language": "..." | null, "signIn": [{ "key", "label" }, ...] }`: what the board is called, the most days a token may be good for here (or `null` where a person chooses), where the organization's skills come from (`source`, the origin of the SkillCDN deployment they are read through, and `address`, the organization's, canonical, which a project without one of its own reads, or `null`), who the session cookie or the token says is asking (`id`, `login`, `name` or `null`, `avatar` or `null`, `role`), with what (`agent`: the name of the token when a token asks, as its person called it, so that a console of a person's own knows it holds one and offers nothing a token cannot do; `null` on a session), the language the person chose for the pages (`language`, a tag, or `null` for the browser's), and the identity providers people sign in through (`gh`, `google`, each with the label of its button), none where nobody can. Nobody is an answer, not an error; the pages ask this once to decide what to show, and the command once, at `login`, to see that the console knows the token.
|
|
69
73
|
|
|
70
74
|
### `GET /api/v1/people`
|
|
71
75
|
|
|
72
|
-
`{ "items": [person, ...] }`: everyone who has signed in
|
|
76
|
+
`{ "items": [person, ...], "listed": [listing, ...] }`: everyone who has signed in, those in first and by login, and the logins an administrator let in that nobody has signed in with yet ([ADR-0022](../adr/0022-who-is-in-is-the-consoles-own-record-and-an-administrator-keeps-it.md)). A person carries their `role` in the workspace (`admin`, who configures it, or `member`, who works in the projects they are in), their `status` (`active`, or `deactivated`: kept with everything they did, refused on every request, and offered to no picker), their `source` (what let them in at their last sign-in: `organization`, the git host's organization the deployment names; `domain`, the Workspace domain it names; or `listed`, an administrator's listing) and `deactivatedAt` (or `null`). A listing carries its `id`, the `host` (`gh`, `google`), the `login` as the administrator spelled it, and `createdAt`. `organization` is the organization at the git host whose members are in, where the deployment names one (`host`, `name`, and `syncedAt`, when the worker last read it whole, or `null` until it has), or `null`. What a picker of assignees and of members is made of, the active people. A token reads it as its person does: what the command's `--assignee`, `member` and `person` take a login from.
|
|
73
77
|
|
|
74
78
|
### `PATCH /api/v1/people/<id>`
|
|
75
79
|
|
|
76
|
-
Takes `role` and answers the person. For an administrator, signed in or through their agent's token ([ADR-0021](../adr/0021-a-token-configures-as-its-person-does.md)). The board keeps at least one administrator (`person.last_admin`). A change writes
|
|
80
|
+
Takes `role`, `status`, or both, and answers the person. For an administrator, signed in or through their agent's token ([ADR-0021](../adr/0021-a-token-configures-as-its-person-does.md)). The board keeps at least one administrator in (`person.last_admin`). A change of role writes `person.role_changed` with `login` and `role`. `status: "deactivated"` takes the person out: refused on every request from the next one, their agents' tokens with them, listed nowhere and offered to no picker, kept with everything they did; the listing they came in on goes with it, so that signing in again admits nobody. `status: "active"` takes them back in, and lists them again where a listing let them in. Each writes `person.deactivated` or `person.reactivated` with `login`; what is already so writes nothing. A person the organization let in is neither (`person.of_organization`): the organization says, on the schedule the worker reads it on.
|
|
81
|
+
|
|
82
|
+
### `POST /api/v1/listings` and `DELETE /api/v1/listings/<id>`
|
|
83
|
+
|
|
84
|
+
Who may sign in, by login ([ADR-0022](../adr/0022-who-is-in-is-the-consoles-own-record-and-an-administrator-keeps-it.md)): for an administrator, signed in or through their agent's token. `POST` takes `host` (one the deployment signs people in through; `listing.unknown_host` otherwise) and `login` (a login at the git host, or an address at Google, as the host spells one; trimmed, compared without regard to case), answers `201` with the listing, and writes `person.listed` with `login`. Whoever signs in with that account at that host is a person from then on, and the listing is theirs: bound to the account, so that a rename changes nothing and no other account can take the login. A login listed already, or one an active person signs in as, is `listing.exists`; a deactivated person's login may be listed, and they are back in, as listed, when they next sign in. `DELETE` takes a listing away that nobody has signed in with yet, answers `204` and writes `person.unlisted`; one somebody signed in with is `listing.not_found`, since it goes with the person's deactivation.
|
|
77
85
|
|
|
78
86
|
### `GET /api/v1/projects`
|
|
79
87
|
|
|
@@ -85,7 +93,7 @@ Takes `key` and `name`, and optionally `description`, `visibility` (default `pri
|
|
|
85
93
|
|
|
86
94
|
### `GET /api/v1/events?after=&limit=`
|
|
87
95
|
|
|
88
|
-
The workspace's own events, the ones about no project: who joined (`person.joined`), who was made what (`person.role_changed`), and what became of the categories (`category.created`, `category.updated`, `category.removed`, each with the `key` and the `name`). The same shape and parameters as a project's feed below, without the filters.
|
|
96
|
+
The workspace's own events, the ones about no project: who joined (`person.joined`), who was made what (`person.role_changed`), who was let in, taken off the list, taken out and taken back in (`person.listed`, `person.unlisted`, `person.deactivated`, `person.reactivated`, each with the `login`), and what became of the categories (`category.created`, `category.updated`, `category.removed`, each with the `key` and the `name`). The same shape and parameters as a project's feed below, without the filters.
|
|
89
97
|
|
|
90
98
|
### `PATCH /api/v1/me`
|
|
91
99
|
|
|
@@ -99,6 +107,10 @@ Keeps the person's own choice of language for the pages ([ADR-0012](../adr/0012-
|
|
|
99
107
|
|
|
100
108
|
Kept by an administrator, signed in or through their agent's token. `POST` takes `key`, `name` and optionally `color` (default `gray`), answers `201` with the category and writes a `category.created` event in the workspace's own feed with `key` and `name`; a key taken is `category.key_taken`, and the workspace holds at most `MAX_CATEGORIES` (`category.too_many`). `PATCH` takes any of `name` and `color`, answers the category and writes `category.updated` naming the `fields`; what is already so writes nothing; the key does not change. `DELETE` answers `204` and writes `category.removed`; a category tasks are sorted into is `category.in_use` until they are moved out of it.
|
|
101
109
|
|
|
110
|
+
### `GET /api/v1/rules`, `PUT /api/v1/rules`, `POST /api/v1/rules/archive` and `GET /api/v1/rules/versions`
|
|
111
|
+
|
|
112
|
+
The rules of the board ([ADR-0023](../adr/0023-the-rules-of-a-board-are-pages-the-workspace-and-its-projects-keep-read-first-by-every-agent.md)): what the organization asks of agents, read by whoever is signed in or presents a token, kept by an administrator, signed in or through their agent's token. `GET` answers `{ "source", "body", "page" }`: `source` is `reference` while the organization has written none, and `body` is then the console's own rules as this version of the console carries them, with `page: null`; once written, `source` is `written`, `body` the page's and `page` the page without its body (as a folder lists one: `path` `rules`, `title` `Rules`, `version`, `updatedBy`, `agent`, `createdAt`, `updatedAt`). `PUT` takes `body` (Markdown, `MAX_RULES_LENGTH`) and optionally `baseVersion`, writes the next version of the page, bringing it back when it was put away, and answers what `GET` answers; the console's own rules sent as they are are never written, so that what holds keeps following the console's version: with none written they change nothing, and with the organization's written they put its page away, as `POST .../archive` does; a write from a version the rules have moved on from is `document.conflict`. The write is a `document.written` event in the workspace's own feed, with `path`, `title` and `version`. `POST .../archive` puts the written rules away, so that the console's own hold again (`document.archived`); what is put away already changes nothing. `.../versions` answers `{ "items": [version, ...] }` as a page's versions are answered, newest first, and `{ "items": [] }` while none is written. A body may say `{language}` for the language of the person an agent works for: the API answers the text as written, and the pages and the command fill it (`fillRules`), with the name of the language the person chose (`GET /api/v1/me`, `language`) or with the task's when they chose none.
|
|
113
|
+
|
|
102
114
|
### `GET /api/v1/work`
|
|
103
115
|
|
|
104
116
|
`{ "tasks": [{ "project": { "key", "name", "color", "symbol", "icon" }, "task" }, ...], "decisions": [{ "project", "decision" }, ...] }`: what is on the asker's plate across the projects they may see ([ADR-0019](../adr/0019-a-task-is-planned-in-days-the-board-keeps-its-moments-and-goals-and-categories-give-the-work-its-shape.md)): the open tasks assigned to them, the sooner due first and those with no day last, and the decisions that wait for a person, the longest waiting first; each with the project it belongs to, so that a page says and links it. At most `LIST_LIMIT` of each.
|
|
@@ -111,10 +123,18 @@ The tokens of whoever is signed in, for their agents, scripts and consoles of th
|
|
|
111
123
|
|
|
112
124
|
Takes one of the asker's tokens away, whoever holds it, and answers `204`. A token that is not the asker's is `token.not_found`.
|
|
113
125
|
|
|
126
|
+
### `DELETE /api/v1/me/token`
|
|
127
|
+
|
|
128
|
+
The token the request is made with gives itself up, so that it is good for nothing from now on, and the answer is `204`: what `console logout` does, so that a token forgotten on a machine does not outlive it. The one thing a token does about the tokens; with a session, which has none to give, it is `request.invalid`.
|
|
129
|
+
|
|
114
130
|
### `GET /api/v1/people/<id>/tokens` and `DELETE /api/v1/people/<id>/tokens/<token id>`
|
|
115
131
|
|
|
116
132
|
An administrator's sight of everyone's agents ([ADR-0011](../adr/0011-an-agent-connects-with-a-short-code-a-person-approves-and-the-token-is-never-shown.md)): the live tokens of any person, as `GET /api/v1/tokens` lists one's own, and the way to take one away, from the console's own pages; `auth.forbidden` for a member, `person.not_found` for an id that is nobody's, `token.not_found` for a token that is not that person's.
|
|
117
133
|
|
|
134
|
+
### `GET /api/v1/people/<id>/work`
|
|
135
|
+
|
|
136
|
+
`{ "tasks": [{ "project", "task" }, ...] }`: what is on another person's plate, for whoever oversees them: an administrator, or an owner of a project the person is in (everyone of the workspace is in a project open to it; a private one holds those listed), signed in or through their agent. The person's open tasks, the sooner due first and those with no day last, within the projects the asker may see and no other, each with its project; the decisions are left out, since they wait for anyone. `auth.forbidden` for anyone else; `person.not_found` for an id that is nobody's.
|
|
137
|
+
|
|
118
138
|
### Connecting an agent
|
|
119
139
|
|
|
120
140
|
`POST /api/v1/connect`, `GET /api/v1/connect/<code>`, `POST /api/v1/connect/<code>/approve`, `POST /api/v1/connect/<code>/deny` and `POST /api/v1/connect/claim`: how an agent gets its token without anyone copying one ([ADR-0011](../adr/0011-an-agent-connects-with-a-short-code-a-person-approves-and-the-token-is-never-shown.md)).
|
|
@@ -135,7 +155,7 @@ The project, as the asker sees it. A patch, for an owner, signed in or with a to
|
|
|
135
155
|
|
|
136
156
|
### `POST /api/v1/projects/<key>/icon`, `DELETE .../icon` and `GET .../icon`
|
|
137
157
|
|
|
138
|
-
The project's own image for its mark, in place of the symbol on the colour. `POST`, for an owner, takes a form with a part called `file`: a PNG, a JPEG, a GIF or a WebP of at most `MAX_PROJECT_ICON_BYTES`, known by its first bytes and never by what the form says of it (`project.invalid_icon`; `request.too_large`). The bytes are kept by their hash, the project is answered with its `icon`, and the board hears `project.updated` with `fields: ["icon"]`; the same image again writes nothing. `DELETE` takes the image away and answers the project. `GET`, for whoever may see the project, answers the bytes with their type, the hash as `ETag` and `Cache-Control: private, max-age=86400`, or `304` to a matching `If-None-Match`; a project without an image is `project.no_icon`. The pages read it at `projectIconPath(key, icon)`, which carries the hash as a version.
|
|
158
|
+
The project's own image for its mark, in place of the symbol on the colour. `POST`, for an owner, takes a form with a part called `file`: a PNG, a JPEG, a GIF or a WebP of at most `MAX_PROJECT_ICON_BYTES`, known by its first bytes and never by what the form says of it (`project.invalid_icon`; `request.too_large`). The bytes are kept by their hash, the project is answered with its `icon`, and the board hears `project.updated` with `fields: ["icon"]`; the same image again writes nothing. `DELETE` takes the image away and answers the project. `GET`, for whoever may see the project, answers the bytes with their type, the hash as `ETag` and `Cache-Control: private, max-age=86400, immutable`, since the address the pages read it at carries the hash, or `304` to a matching `If-None-Match`; a project without an image is `project.no_icon`. The pages read it at `projectIconPath(key, icon)`, which carries the hash as a version.
|
|
139
159
|
|
|
140
160
|
### `GET /api/v1/projects/<key>/members` and `POST /api/v1/projects/<key>/members`
|
|
141
161
|
|
|
@@ -209,9 +229,13 @@ Takes `status` (`finished`, `failed` or `abandoned`) and optionally a `summary`
|
|
|
209
229
|
|
|
210
230
|
The project's skills, as the SkillCDN deployment serves them at the project's address, or at the organization's (`SKILLS_ADDRESS`) when the project names none, read through `SKILLCDN_URL`: `{ "address", "source", "page", "status", "items" }`. `address` is canonical, or `null` with `status: "none"` when neither names one; `source` is the deployment's origin and `page` where a person browses the address. `status` is `ready`, or why `items` is empty: `indexing` and `failed` as the deployment says of the repository, `not_found` when it does not serve the address to the console (which asks as nobody, so a private repository is not read), `unavailable` when it could not be reached or read. An item is one skill: `name`, `description`, `directory` and `path` (of its `SKILL.md`, from the repository's root), `page` (the skill at the deployment, for a person), `uri` (what an agent loads it by through its own SkillCDN connection, `skill://gh/<owner>/<repo>/<path>`, or `null`), and `translations` (the title and the description by language tag, as the repository gives them). The console keeps nothing of the skills: one answer per address stands for a minute, then the deployment is asked again.
|
|
211
231
|
|
|
232
|
+
### `GET /api/v1/projects/<key>/rules`
|
|
233
|
+
|
|
234
|
+
`{ "workspace": rules, "project": rules }`: the rules that hold in the project ([ADR-0023](../adr/0023-the-rules-of-a-board-are-pages-the-workspace-and-its-projects-keep-read-first-by-every-agent.md)), for whoever may see it, each half shaped as `GET /api/v1/rules` answers: the workspace's, and the project's own, whose `source` is `written`, with its `body` and its `page`, or `none`, with `body` and `page` `null`, when the project adds nothing to the workspace's. A project's rules are the page `rules` of its documents, written and put away through the routes below.
|
|
235
|
+
|
|
212
236
|
### The documents
|
|
213
237
|
|
|
214
|
-
The project's pages of Markdown ([ADR-0009](../adr/0009-documents-are-pages-of-markdown-in-a-project-addressed-by-path-versioned-and-linked-both-ways.md)): each under a path of lowercase segments separated by slashes (`guides/onboarding`), which is its address and does not change; the folders are what the paths say. In every path below, the document's path is **one segment of the URL, its slashes encoded** (`/docs/guides%2Fonboarding`), which is what `projectPath(key, "docs", path)` builds. Whoever may see the project reads them; whoever may work in it writes them, with a session or a token.
|
|
238
|
+
The project's pages of Markdown ([ADR-0009](../adr/0009-documents-are-pages-of-markdown-in-a-project-addressed-by-path-versioned-and-linked-both-ways.md)): each under a path of lowercase segments separated by slashes (`guides/onboarding`), which is its address and does not change; the folders are what the paths say. In every path below, the document's path is **one segment of the URL, its slashes encoded** (`/docs/guides%2Fonboarding`), which is what `projectPath(key, "docs", path)` builds. Whoever may see the project reads them; whoever may work in it writes them, with a session or a token. The page `rules` is the project's rules ([ADR-0023](../adr/0023-the-rules-of-a-board-are-pages-the-workspace-and-its-projects-keep-read-first-by-every-agent.md)): written, put away, brought back and given files by an owner alone (`auth.forbidden` otherwise), kept to `MAX_RULES_LENGTH` (`request.invalid`), with the one title `Rules` whatever was sent, written again when it was put away, and in no folder and no search, since it is the project's configuration; it is read at its path as any page is.
|
|
215
239
|
|
|
216
240
|
#### `GET /api/v1/projects/<key>/docs?folder=&q=&archived=`
|
|
217
241
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skillcdn/console",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "What a custom SkillCDN console is built from: the API client and schemas, the components, the composition of the default console, and the console command agents work the board with.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/skillcdn/console#readme",
|
|
@@ -74,7 +74,7 @@ Produces the **pages**. For a page the default console lacks: `useConsoleData(cl
|
|
|
74
74
|
|
|
75
75
|
### Phase 4: Run it against the board
|
|
76
76
|
|
|
77
|
-
Produces the **running console**. `console serve` serves the API at `http://127.0.0.1:11197/api/` while `npx vite` serves the pages, and `console serve dist` serves the build whole at `http://127.0.0.1:11197/`. The pages hold no token; the command carries it, on the loopback only. The pages know they hold a token (`GET /api/v1/me` answers `agent`) and offer nothing a token cannot do: the tokens themselves stay on the organization's console, and everything else, configuring included, the pages do on the token as the person ([ADR-0021](/docs/adr/0021-a-token-configures-as-its-person-does.md)). The organization's console is looked at the same way, on the builder's token: it shows every page a token may see, and the pages a token may not (the agents) are the package's own and unchanged, so the look checkpoint covers everything the console changed. Where the organization's build then goes is the plan's answer: `console-ui/` of the deployment repository, whose workflow builds it into the image on every push, handed over to deploying-a-console, the skill beside this one at the same address (in the package under `node_modules/@skillcdn/console/skills/deploying-a-console/`), with the source and the repository, and with the package pinned at the version of the release the deployment pins as `CONSOLE_REF` (the tag `@skillcdn/console@<version>`), so that the pages and the server are one release and move together; the build beside the Dockerfile of [deploy/README.md](/deploy/README.md) "A custom console in the image", for an image the organization builds itself; or the mounted directory. A script: `createClient({ baseUrl, token })`, every answer parsed with the package's schemas; it configures as its person does too, with `createProject`, `updatePerson`, a project's `update`, `setIcon`, `addMember`, `updateMember` and `removeMember`,
|
|
77
|
+
Produces the **running console**. `console serve` serves the API at `http://127.0.0.1:11197/api/` while `npx vite` serves the pages, and `console serve dist` serves the build whole at `http://127.0.0.1:11197/`. The pages hold no token; the command carries it, on the loopback only. The pages know they hold a token (`GET /api/v1/me` answers `agent`) and offer nothing a token cannot do: the tokens themselves stay on the organization's console, and everything else, configuring included, the pages do on the token as the person ([ADR-0021](/docs/adr/0021-a-token-configures-as-its-person-does.md)). The organization's console is looked at the same way, on the builder's token: it shows every page a token may see, and the pages a token may not (the agents) are the package's own and unchanged, so the look checkpoint covers everything the console changed. Where the organization's build then goes is the plan's answer: `console-ui/` of the deployment repository, whose workflow builds it into the image on every push, handed over to deploying-a-console, the skill beside this one at the same address (in the package under `node_modules/@skillcdn/console/skills/deploying-a-console/`), with the source and the repository, and with the package pinned at the version of the release the deployment pins as `CONSOLE_REF` (the tag `@skillcdn/console@<version>`), so that the pages and the server are one release and move together; the build beside the Dockerfile of [deploy/README.md](/deploy/README.md) "A custom console in the image", for an image the organization builds itself; or the mounted directory. A script: `createClient({ baseUrl, token })`, every answer parsed with the package's schemas; it configures as its person does too, with `createProject`, `updatePerson`, a project's `update`, `setIcon`, `addMember`, `updateMember` and `removeMember`, `createCategory`, `updateCategory` and `removeCategory`, and the rules of the board with `writeRules` and `archiveRules` and a project's as the page `rules` ([ADR-0023](/docs/adr/0023-the-rules-of-a-board-are-pages-the-workspace-and-its-projects-keep-read-first-by-every-agent.md)), refused with `auth.forbidden` where the person may not, and never the tokens.
|
|
78
78
|
|
|
79
79
|
### Phase 5: Check and show
|
|
80
80
|
|
|
@@ -33,7 +33,7 @@ What goes in: an AWS account, a domain on Cloudflare or one to buy, and the answ
|
|
|
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
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
|
|
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
|
|
|
39
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.
|
|
@@ -45,7 +45,7 @@ Check these before the first message. Without the AWS CLI, offer to install it (
|
|
|
45
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
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
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"). |
|
|
48
|
-
| How people sign in, and who | Asked: "How should people sign in: with GitHub, with Google (Workspace), or both? Who
|
|
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>`." Default: none; a project names one later on its Settings page. |
|
|
50
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
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). |
|
|
@@ -69,7 +69,7 @@ Produces the **plan**, shown at the checkpoint. From the answers: the size and w
|
|
|
69
69
|
|
|
70
70
|
### Phase 3: Foundation
|
|
71
71
|
|
|
72
|
-
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
|
|
72
|
+
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.
|
|
73
73
|
|
|
74
74
|
### Phase 4: The deployment repository
|
|
75
75
|
|
|
@@ -93,7 +93,7 @@ Produces **`DEPLOY.md`** in the repository, from the template in resources.md: w
|
|
|
93
93
|
|
|
94
94
|
### Running it again
|
|
95
95
|
|
|
96
|
-
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
|
|
96
|
+
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.
|
|
97
97
|
|
|
98
98
|
### Verdicts
|
|
99
99
|
|
|
@@ -109,7 +109,7 @@ The same skill, from phase 2, with the plan naming only what changes: a new vers
|
|
|
109
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
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
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 (
|
|
112
|
+
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.
|
|
113
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
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
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.
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
"DATABASE_URL": "derived: postgres://console:<generated password>@<database endpoint>:5432/console",
|
|
5
5
|
"DATABASE_CA": "derived: the regional certificate bundle of the database service, as PEM",
|
|
6
6
|
"TUNNEL_TOKEN": "derived: the connector token of the tunnel",
|
|
7
|
-
"GITHUB_CLIENT_SECRET": "asked: the client secret of the OAuth app, or later",
|
|
7
|
+
"GITHUB_CLIENT_SECRET": "asked: the client secret of the GitHub App (or of an OAuth app), or later",
|
|
8
|
+
"GITHUB_APP_PRIVATE_KEY": "asked, with an organization: the private key of the GitHub App, as PEM, its lines joined with \\n; or later",
|
|
8
9
|
"GOOGLE_CLIENT_SECRET": "asked: the client secret of the OAuth client, or later",
|
|
9
10
|
"S3_ACCESS_KEY_ID": "only with the bucket on R2 (sizing.md, Options): asked, the key made in the Cloudflare dashboard",
|
|
10
11
|
"S3_SECRET_ACCESS_KEY": "only with the bucket on R2: its secret"
|
|
@@ -63,14 +63,18 @@ curl -s https://api.cloudflare.com/client/v4/accounts -H "Authorization: Bearer
|
|
|
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
|
|
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.
|
|
67
67
|
|
|
68
|
-
-
|
|
68
|
+
- GitHub App name: the board's name (34 characters at most, unique across GitHub).
|
|
69
69
|
- Homepage URL: the console's address, `https://<hostname>`.
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
- Callback URL: `https://<hostname>/auth/gh/callback`. Leave "Expire user authorization tokens" as it is, and "Request user authorization (OAuth) during installation" off: the console asks who the person is once, and keeps nothing of the credential.
|
|
71
|
+
- Webhook: untick Active. The console takes no webhooks.
|
|
72
|
+
- 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
|
+
- 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.
|
|
75
|
+
- 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
|
+
|
|
77
|
+
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.
|
|
74
78
|
|
|
75
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.
|
|
76
80
|
|
|
@@ -202,7 +202,7 @@ The generated one first, if the Conventions' example was not run as a step: `nod
|
|
|
202
202
|
|
|
203
203
|
### The secrets the person holds
|
|
204
204
|
|
|
205
|
-
The hostname being known, one message per provider chosen with the steps of access.md "The
|
|
205
|
+
The hostname being known, one message per provider chosen with the steps of access.md "The GitHub App" and "The OAuth client", the exact URLs filled in, the lines to add to the secrets file (`"GITHUB_CLIENT_SECRET": "<the secret>"`, and, with an organization, `"GITHUB_APP_PRIVATE_KEY": "<the key, as PEM>"`, the file's lines joined with `\n`), and `later` as the other answer. On an answer, store it (`store` again adds what is new) and note the client id, and the app's id and the organization, for the task definition. A provider answered `later` is left out of the task definition entirely, since the console wants both halves or neither, and so is the organization without its key, since it wants all three or none; a provider not chosen has no line in the secrets file and nothing in the record.
|
|
206
206
|
|
|
207
207
|
## Phase 4: The deployment repository
|
|
208
208
|
|
|
@@ -256,9 +256,10 @@ The task definition of the `api` role with the tunnel's connector beside it, in
|
|
|
256
256
|
{ "name": "TRUSTED_PROXIES", "value": "127.0.0.1,::1" },
|
|
257
257
|
{ "name": "CLIENT_IP_HEADER", "value": "cf-connecting-ip" },
|
|
258
258
|
{ "name": "HTTP_KEEP_ALIVE_SECONDS", "value": "100" },
|
|
259
|
-
{ "name": "
|
|
260
|
-
{ "name": "
|
|
261
|
-
{ "name": "
|
|
259
|
+
{ "name": "ADMINS", "value": "<the first administrators: logins and addresses, comma-separated>" },
|
|
260
|
+
{ "name": "GITHUB_CLIENT_ID", "value": "<the GitHub App's client id>" },
|
|
261
|
+
{ "name": "GITHUB_APP_ID", "value": "<the GitHub App's id, or drop the three lines of the organization>" },
|
|
262
|
+
{ "name": "GITHUB_ORG", "value": "<the organization's login at GitHub>" },
|
|
262
263
|
{ "name": "SKILLS_ADDRESS", "value": "<the organization's skills address, or drop the line>" },
|
|
263
264
|
{ "name": "S3_ENDPOINT", "value": "https://s3.<region>.amazonaws.com" },
|
|
264
265
|
{ "name": "S3_BUCKET", "value": "console-files-<account>" },
|
|
@@ -269,7 +270,8 @@ The task definition of the `api` role with the tunnel's connector beside it, in
|
|
|
269
270
|
{ "name": "DATABASE_URL", "valueFrom": "arn:aws:ssm:<region>:<account>:parameter/console/DATABASE_URL" },
|
|
270
271
|
{ "name": "DATABASE_CA", "valueFrom": "arn:aws:ssm:<region>:<account>:parameter/console/DATABASE_CA" },
|
|
271
272
|
{ "name": "AUTH_SECRET", "valueFrom": "arn:aws:ssm:<region>:<account>:parameter/console/AUTH_SECRET" },
|
|
272
|
-
{ "name": "GITHUB_CLIENT_SECRET", "valueFrom": "arn:aws:ssm:<region>:<account>:parameter/console/GITHUB_CLIENT_SECRET" }
|
|
273
|
+
{ "name": "GITHUB_CLIENT_SECRET", "valueFrom": "arn:aws:ssm:<region>:<account>:parameter/console/GITHUB_CLIENT_SECRET" },
|
|
274
|
+
{ "name": "GITHUB_APP_PRIVATE_KEY", "valueFrom": "arn:aws:ssm:<region>:<account>:parameter/console/GITHUB_APP_PRIVATE_KEY" }
|
|
273
275
|
],
|
|
274
276
|
"healthCheck": {
|
|
275
277
|
"command": ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:11199/readyz').then((r) => process.exit(r.ok ? 0 : 1), () => process.exit(1))\""],
|
|
@@ -507,7 +509,7 @@ Made on <date> with the skill `deploying-a-console` of the SkillCDN Console (ski
|
|
|
507
509
|
|
|
508
510
|
- **A deployment** is a push to `main` that touches `deploy/` or `console-ui/`: the workflow builds the image, runs the migrations, and rolls the services. `gh run list` shows the runs.
|
|
509
511
|
- **A new version of the console:** set `CONSOLE_REF` in `deploy/console.env` to the commit of the newest release tag: 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, with the commit in its first column; move `console-ui/` to the same version of the package when there is one; push. **A new connector:** `CLOUDFLARED_VERSION` there, push.
|
|
510
|
-
- **A person added or removed:** `
|
|
512
|
+
- **A person added or removed:** with an organization at GitHub (`GITHUB_ORG`), in the organization, which the console reads every ten minutes; otherwise on the People page, or with `console person add <login>` and `console person deactivate <login>`, by an administrator; nothing to push. With a Google Workspace, everyone of `GOOGLE_WORKSPACE_DOMAIN` is in. `ADMINS` in `deploy/api.task.json` names only the first administrators, who make others on the People page.
|
|
511
513
|
- **The next size:** the numbers in `deploy/api.task.json` (and `deploy/worker.task.json` at `large`), `API_COUNT` and `WORKER_COUNT` in `deploy/console.env`, push; to `large`, also `deploy/worker.task.json` written as the skill's resources.md gives it and `WORKER_IN_PROCESS` dropped from the api task; the database: `aws rds modify-db-instance --db-instance-identifier console --db-instance-class <class> --apply-immediately` (a few minutes of unavailability).
|
|
512
514
|
- **A secret rotated:** the new value into a secrets file of the same shape with the skill's script (`set`), then `node <the script> store <file> /console`, which overwrites the parameter in its tier (`$(npm root -g)/@skillcdn/console/skills/deploying-a-console/scripts/store-secrets.mjs`; `DATABASE_CA` is in the advanced tier, which an `aws ssm put-parameter --overwrite` by hand must name with `--tier Advanced`); then `aws ecs update-service --cluster console --service api --force-new-deployment`.
|
|
513
515
|
- **The organization's own console:** its source in `console-ui/` (building-a-console makes it); a push builds it into the image.
|
|
@@ -551,7 +553,8 @@ Only on request, each step confirmed, in this order, after a last snapshot of th
|
|
|
551
553
|
| `https://<hostname>` answers 530 or 502 from Cloudflare | No connector yet, or the hostname's ingress rule points elsewhere | Wait for the service; check the tunnel's configuration and the DNS record |
|
|
552
554
|
| `https://<hostname>` does not resolve | The zone is not active yet | The nameservers at the registrar; `cf "/zones?name=<domain>"` says when |
|
|
553
555
|
| The sign-in button is missing | The provider's id or secret is not in the task definition | Both halves or neither; check `environment` and `secrets` |
|
|
554
|
-
| The sign-in is refused after the provider |
|
|
556
|
+
| The sign-in is refused after the provider | Nobody let the login in: it is not in `ADMINS`, not of the organization (`GITHUB_ORG`) or of `GOOGLE_WORKSPACE_DOMAIN`, and no administrator listed it on the People page; or the person was deactivated | An administrator lets the login in on the People page (`console person add <login>`), or takes the person back in; the first administrator is `ADMINS`, a push |
|
|
557
|
+
| The log says `the organization could not be read` | The app's key is wrong (`unauthorized`), the app is not installed on the organization or lacks the Members permission (`invalid`, `unauthorized`), or GitHub was away (`transient`) | Check the parameter, the installation and the app's permissions; nothing changed meanwhile, and the next pass reads again |
|
|
555
558
|
| The mirror step cannot pull `cloudflare/cloudflared` | Docker Hub's anonymous pull limit on the runner | Run the workflow again (`gh run rerun <id>`); the pull happens once per connector version, and the tasks pull from the repository afterwards |
|
|
556
559
|
| `gh run watch` shows the build failing on `npm ci` in `console-ui` | The UI's lockfile is out of step | Fix in `console-ui/` (building-a-console), push |
|
|
557
560
|
| A 524 at Cloudflare on a long read | A request held longer than the proxy's 100 seconds | The console holds a decision's read for 50 at most; look for a slow database |
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: working-the-board
|
|
3
|
-
description: "Works a task from the board of a SkillCDN Console with the console command, as the person whose token the command holds: reads the task, takes it, reports at the milestones of the work, asks on the board when a person must decide and waits for the answer, hands in what was made as a link or a file, writes down what the next agent needs on a page of the project, and finishes. Use when an agent is told to take, work or finish a task on the console, or to report, ask, hand in or write a page there; any agent with a shell where the command is signed in. To build a console or a script against the console's API, use building-a-console; to deploy a console for an organization, use deploying-a-console."
|
|
3
|
+
description: "Works a task from the board of a SkillCDN Console with the console command, as the person whose token the command holds: reads the rules of the board and the task, takes it, reports at the milestones of the work, asks on the board when a person must decide and waits for the answer, hands in what was made as a link or a file, writes down what the next agent needs on a page of the project, and finishes. Use when an agent is told to take, work or finish a task on the console, or to report, ask, hand in or write a page there; any agent with a shell where the command is signed in. To build a console or a script against the console's API, use building-a-console; to deploy a console for an organization, use deploying-a-console."
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Needs a shell with the console command on the PATH (npm install -g @skillcdn/console, Node.js 24 or newer), signed in by a person. Nothing else; the command needs no key of its own and the agent never sees a token.
|
|
6
6
|
metadata:
|
|
7
7
|
author: skillcdn
|
|
8
|
-
version: "1.
|
|
8
|
+
version: "1.5"
|
|
9
9
|
skillcdn:
|
|
10
10
|
translations:
|
|
11
11
|
ko:
|
|
12
12
|
title: 콘솔 보드에서 일하기
|
|
13
|
-
description: "SkillCDN 콘솔의 보드에 있는 작업을 console 명령으로 수행합니다. 명령에 로그인한 사람의 자격으로, 작업을 읽고 맡고, 진행의 이정표마다 보고하고, 사람이 정해야 할 일은 보드에 물어 답을 기다리고, 만든 것을 링크나 파일로 제출하고, 다음 에이전트에게 필요한 것을 프로젝트 페이지에 적고, 마칩니다. 에이전트에게 콘솔의 작업을 맡거나 끝내라고, 또는 보고·질문·제출·페이지 쓰기를 하라고 할 때 쓰세요. 콘솔이나 API 클라이언트를 만들 때는 building-a-console을, 조직의 콘솔을 배포할 때는 deploying-a-console을 쓰세요."
|
|
13
|
+
description: "SkillCDN 콘솔의 보드에 있는 작업을 console 명령으로 수행합니다. 명령에 로그인한 사람의 자격으로, 보드의 규칙과 작업을 읽고 맡고, 진행의 이정표마다 보고하고, 사람이 정해야 할 일은 보드에 물어 답을 기다리고, 만든 것을 링크나 파일로 제출하고, 다음 에이전트에게 필요한 것을 프로젝트 페이지에 적고, 마칩니다. 에이전트에게 콘솔의 작업을 맡거나 끝내라고, 또는 보고·질문·제출·페이지 쓰기를 하라고 할 때 쓰세요. 콘솔이나 API 클라이언트를 만들 때는 building-a-console을, 조직의 콘솔을 배포할 때는 deploying-a-console을 쓰세요."
|
|
14
14
|
---
|
|
15
15
|
# Working a task on the board
|
|
16
16
|
|
|
@@ -22,7 +22,7 @@ A task of the board goes in, named by its number; a run comes out: the task take
|
|
|
22
22
|
- **Checkpoints:** on the board, as decisions (phase 4), which a person answers from wherever they are; the reports say what was done at each milestone, and need no answer.
|
|
23
23
|
- **Go-ahead:** the task's body is the go-ahead. A task that says what the agent may decide alone is followed within that; everything else that would change scope, cost or a trade-off is a decision.
|
|
24
24
|
- **Changes mid-run:** a person changes the task or answers a decision on the board; the run takes it from there and says so in its next report. A report is never rewritten.
|
|
25
|
-
- **Language:** reports, questions, pages and summaries
|
|
25
|
+
- **Language:** what people read, the reports, the questions, the pages and the summaries, is in the language the rules name for the person (`console take` prints them with the task), else the task's, so that the people of the board read as they wrote.
|
|
26
26
|
|
|
27
27
|
## Requirements
|
|
28
28
|
|
|
@@ -37,9 +37,9 @@ The full contract is the [specification of the command line](/docs/specs/cli.md)
|
|
|
37
37
|
|
|
38
38
|
| Input | Source |
|
|
39
39
|
|---|---|
|
|
40
|
-
| The task | Its number (`#7`) or its id, from what the person said. Told to pick one, `console work` lists what is on the person's plate across their projects, the sooner due first, and `console tasks --state ready --mine` what is ready and theirs in this one (`--due overdue`, `--due week`, `--category <key>` and `--goal <id>` narrow it further); a task is never taken without being told which, or that any ready one may be. |
|
|
41
|
-
| The work | The task's body, read with `console task <number>`: what to make, where, the milestones, what to ask; with its plan, the category, the goal it works towards (`console goal <id>` says what reaching it means) and the days it starts and is due, which the reports and the summary keep to. The pages it links to (`console doc <path>`) and the project's skills (`console skills`, each loaded through the agent's own SkillCDN connection) say the rest. |
|
|
42
|
-
| The
|
|
40
|
+
| The task | Its number (`#7`) or its id, from what the person said. Told to pick one, `console work` lists what is on the person's plate across their projects, the sooner due first (`--person <login>` another person's, for an administrator or an owner of a project they are in), and `console tasks --state ready --mine` what is ready and theirs in this one (`--due overdue`, `--due week`, `--category <key>` and `--goal <id>` narrow it further); a task is never taken without being told which, or that any ready one may be. |
|
|
41
|
+
| The work | The task's body, read with `console task <number>`: what to make, where, the milestones, what to ask; with its plan, the category, the goal it works towards (`console goal <id>` says what reaching it means) and the days it starts and is due, which the reports and the summary keep to. The pages it links to (`console doc <path>`) and the project's skills (`console skills`, each loaded through the agent's own SkillCDN connection to the address; with none that covers it, stop and tell the person, who connects the agent at the address's page) say the rest. |
|
|
42
|
+
| The rules | The workspace's, the console's own until an administrator writes the organization's, and this project's own when it has them: which skills to load first, where the work lives, the pages, what may be decided alone, how to report ([ADR-0023](/docs/adr/0023-the-rules-of-a-board-are-pages-the-workspace-and-its-projects-keep-read-first-by-every-agent.md)). `console take` prints them with the task, so they are read once, there; `console rules` reads them without taking, as when a task is still to be chosen. They hold over this skill's defaults where they differ. |
|
|
43
43
|
|
|
44
44
|
## Workflow
|
|
45
45
|
|
|
@@ -47,7 +47,7 @@ Each phase ends with something on the board. Only phase 4 waits for a person.
|
|
|
47
47
|
|
|
48
48
|
### Phase 1: Read
|
|
49
49
|
|
|
50
|
-
`console task <number>` shows
|
|
50
|
+
Told which task, go to phase 2: `console take` prints the rules of the board with the task, and they are read there, once. Choosing one, or checking one before taking it, `console rules` says which tasks to take and what to load, and `console task <number>` shows a task in full, with its plan, the decisions about it and the runs on it, without taking it. A run open on it means another agent was at it, and may still be: the console cannot tell ([ADR-0020](/docs/adr/0020-the-console-does-not-claim-an-agent-is-at-work-a-run-says-when-it-was-last-heard-from-and-a-new-run-takes-over.md)), so read its reports and when it was last heard from, and take the task only when the person said to, since taking it ends that run as taken over; otherwise say so and stop. A task that is past its due day is still the task; the first report says so, and what the day was for is kept in mind. Read what the body links to, with `console doc <path>` for the project's pages and `console skills` for the organization's skills, before anything is made.
|
|
51
51
|
|
|
52
52
|
### Phase 2: Take
|
|
53
53
|
|
|
@@ -75,7 +75,7 @@ After the last report, `console finish --summary "<what was done, and what is le
|
|
|
75
75
|
|
|
76
76
|
## Setting up, when told to
|
|
77
77
|
|
|
78
|
-
A task or a person may ask for the project itself rather than work in it: a project made or changed (`console project new <key> "<name>"`, `console project edit <key>`), its mark (`console project icon <key> <file>`), its members (`console member add <login>`, `console member role`, `console member remove`), the workspace's categories (`console category new <key> "<name>"`, `console category edit`, `console category remove`), what a person is (`console person role <login> <admin|member>`),
|
|
78
|
+
A task or a person may ask for the project itself rather than work in it: a project made or changed (`console project new <key> "<name>"`, `console project edit <key>`), its mark (`console project icon <key> <file>`), its members (`console member add <login>`, `console member role`, `console member remove`), the workspace's categories (`console category new <key> "<name>"`, `console category edit`, `console category remove`), what a person is (`console person role <login> <admin|member>`), its goals (`console goal new`), and its rules (`console rules write --file <path>`, from [the example](references/project-rules.md); the workspace's with `--workspace`, for an administrator). The agent does these as its person may, an owner of the project or an administrator of the workspace ([ADR-0021](/docs/adr/0021-a-token-configures-as-its-person-does.md)); the feed names the agent beside the person on each. What the person may not, the console refuses (`auth.forbidden`): that step goes back to the person in the report, never worked around. `console people` and `console members` say who is who, by login.
|
|
79
79
|
|
|
80
80
|
## Hard rules
|
|
81
81
|
|
|
@@ -83,6 +83,7 @@ A task or a person may ask for the project itself rather than work in it: a proj
|
|
|
83
83
|
- One run at a time: a command on a run acts on the one open run of this token in the project; working two tasks at once, say which with `--run <id>`.
|
|
84
84
|
- The board's words are the vocabulary's own: states `idea`, `ready`, `in_progress`, `in_review`, `done` and `dropped`; priorities `low`, `normal`, `high` and `urgent`; a day is `2026-10-24`. Taking a task moves it, finishing moves it; it is not moved by hand around a run. When a task is started and when it is done, the board writes down itself: never typed.
|
|
85
85
|
- The plan is the person's: a task's days, its category and its goal are changed (`console task edit`) only when the task says so or a person asks, and a goal is set or moved (`console goal new`, `console goal edit`) only when the person is an owner and said to.
|
|
86
|
+
- The rules are the people's: `console rules write` only when a person asked for exactly that, and never to loosen what they ask of agents.
|
|
86
87
|
- Exit codes: `0` done; `1` refused or unreachable, the reason on standard error as a code and words, which `console help <command>` names; `2` not understood; `3` a decision still waits.
|
|
87
88
|
- Everything sent is held to limits the console states (`console help <command>`); a refusal `request.invalid` names the field.
|
|
88
89
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# An example of a project's rules
|
|
2
|
+
|
|
3
|
+
What an owner writes on a project's Settings page, or an agent writes with `console rules write --file <path>` when a person asked for exactly that: what the project adds to the workspace's rules, for the agents at work in it ([ADR-0023](/docs/adr/0023-the-rules-of-a-board-are-pages-the-workspace-and-its-projects-keep-read-first-by-every-agent.md)). An invented organization, Acme, and its web app. Short and true; what is long goes on a page the rules link to, and nothing secret goes in, since every agent reads them.
|
|
4
|
+
|
|
5
|
+
~~~markdown
|
|
6
|
+
# Rules of this project
|
|
7
|
+
|
|
8
|
+
Adds to the workspace's rules, for the agents at work here.
|
|
9
|
+
|
|
10
|
+
## Where the work lives
|
|
11
|
+
|
|
12
|
+
- The repository acme/web at the organization's git host: the web app. Its AGENTS.md holds inside it; `pnpm check` before every hand-in; a pull request, never a push to main.
|
|
13
|
+
- The design file: read only. A change to a design is a decision on the board.
|
|
14
|
+
|
|
15
|
+
## Skills to load first
|
|
16
|
+
|
|
17
|
+
As `console skills` lists them, by the path of each. No connection that covers the address: stop and tell the person.
|
|
18
|
+
|
|
19
|
+
- `engineering/skills/working-in-web/SKILL.md`, for any task in the repository.
|
|
20
|
+
- `marketing/skills/release-notes/SKILL.md`, for a task in the category marketing.
|
|
21
|
+
|
|
22
|
+
## The pages
|
|
23
|
+
|
|
24
|
+
- `guides/`: how a thing is set up or done. `guides/local-setup` first, every time.
|
|
25
|
+
- `notes/`: what was found; one page per topic, the date in the body, never in the path.
|
|
26
|
+
- `specs/`: what is being built, written by people. Agents read them and ask; never edit.
|
|
27
|
+
|
|
28
|
+
## Decide alone, or ask
|
|
29
|
+
|
|
30
|
+
- Alone: anything inside the task and the repository's agreement; which tests to add.
|
|
31
|
+
- Ask: a new dependency; a change to the schema; anything a user would see differently than the spec says.
|
|
32
|
+
|
|
33
|
+
## Reports
|
|
34
|
+
|
|
35
|
+
- In Korean, as the tasks are; at each milestone the task names, and when a check fails.
|
|
36
|
+
~~~
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// What an agent is called: what it said of itself when it connected, which the person approving
|
|
2
|
+
// kept or changed, and what a run was told with `--agent`. The command proposes "<agent> on
|
|
3
|
+
// <machine>", so the pages say the agent and keep the machine for a hover.
|
|
4
|
+
|
|
5
|
+
/** The agent's name before where it runs: "Claude Code" of "Claude Code on a laptop"; the whole name when it names no place. */
|
|
6
|
+
export function agentLabel(name: string): string {
|
|
7
|
+
const at = name.indexOf(" on ");
|
|
8
|
+
return at > 0 && at + 4 < name.length ? name.slice(0, at) : name;
|
|
9
|
+
}
|