@skillcdn/console 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/README.md +4 -3
  2. package/dist/api.d.ts +2 -2
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +2 -2
  5. package/dist/api.js.map +1 -1
  6. package/dist/cli/cli.d.ts.map +1 -1
  7. package/dist/cli/cli.js +132 -18
  8. package/dist/cli/cli.js.map +1 -1
  9. package/dist/cli/format.d.ts.map +1 -1
  10. package/dist/cli/format.js +6 -4
  11. package/dist/cli/format.js.map +1 -1
  12. package/dist/cli/help.d.ts.map +1 -1
  13. package/dist/cli/help.js +72 -21
  14. package/dist/cli/help.js.map +1 -1
  15. package/dist/cli/pack.d.ts +7 -2
  16. package/dist/cli/pack.d.ts.map +1 -1
  17. package/dist/cli/pack.js +16 -2
  18. package/dist/cli/pack.js.map +1 -1
  19. package/dist/client.d.ts +19 -3
  20. package/dist/client.d.ts.map +1 -1
  21. package/dist/client.js +11 -1
  22. package/dist/client.js.map +1 -1
  23. package/dist/compat.d.ts +1 -1
  24. package/dist/compat.js +2 -2
  25. package/dist/components/board.d.ts +12 -1
  26. package/dist/components/board.d.ts.map +1 -1
  27. package/dist/components/board.js +52 -4
  28. package/dist/components/board.js.map +1 -1
  29. package/dist/components/calendar.d.ts +4 -0
  30. package/dist/components/calendar.d.ts.map +1 -1
  31. package/dist/components/calendar.js +61 -22
  32. package/dist/components/calendar.js.map +1 -1
  33. package/dist/components/decision-form.d.ts +12 -4
  34. package/dist/components/decision-form.d.ts.map +1 -1
  35. package/dist/components/decision-form.js +24 -6
  36. package/dist/components/decision-form.js.map +1 -1
  37. package/dist/components/decision-list.d.ts +4 -0
  38. package/dist/components/decision-list.d.ts.map +1 -1
  39. package/dist/components/decision-list.js +9 -6
  40. package/dist/components/decision-list.js.map +1 -1
  41. package/dist/components/documents.d.ts.map +1 -1
  42. package/dist/components/documents.js +3 -3
  43. package/dist/components/documents.js.map +1 -1
  44. package/dist/components/event-feed.d.ts.map +1 -1
  45. package/dist/components/event-feed.js +63 -8
  46. package/dist/components/event-feed.js.map +1 -1
  47. package/dist/components/markdown.d.ts.map +1 -1
  48. package/dist/components/markdown.js +6 -3
  49. package/dist/components/markdown.js.map +1 -1
  50. package/dist/components/my-decisions.d.ts +20 -0
  51. package/dist/components/my-decisions.d.ts.map +1 -0
  52. package/dist/components/my-decisions.js +36 -0
  53. package/dist/components/my-decisions.js.map +1 -0
  54. package/dist/components/pack-connect.d.ts +3 -1
  55. package/dist/components/pack-connect.d.ts.map +1 -1
  56. package/dist/components/pack-connect.js +4 -4
  57. package/dist/components/pack-connect.js.map +1 -1
  58. package/dist/components/pack-token.d.ts +3 -1
  59. package/dist/components/pack-token.d.ts.map +1 -1
  60. package/dist/components/pack-token.js +1 -1
  61. package/dist/components/pack-token.js.map +1 -1
  62. package/dist/components/people.d.ts.map +1 -1
  63. package/dist/components/people.js +14 -3
  64. package/dist/components/people.js.map +1 -1
  65. package/dist/components/projects.d.ts.map +1 -1
  66. package/dist/components/projects.js +3 -3
  67. package/dist/components/projects.js.map +1 -1
  68. package/dist/components/remote-pictures.d.ts +6 -0
  69. package/dist/components/remote-pictures.d.ts.map +1 -0
  70. package/dist/components/remote-pictures.js +8 -0
  71. package/dist/components/remote-pictures.js.map +1 -0
  72. package/dist/components/roadmap.js +1 -1
  73. package/dist/components/roadmap.js.map +1 -1
  74. package/dist/components/runs.d.ts.map +1 -1
  75. package/dist/components/runs.js +1 -1
  76. package/dist/components/runs.js.map +1 -1
  77. package/dist/components/shell.d.ts.map +1 -1
  78. package/dist/components/shell.js +36 -1
  79. package/dist/components/shell.js.map +1 -1
  80. package/dist/components/sign-in.d.ts.map +1 -1
  81. package/dist/components/sign-in.js +49 -2
  82. package/dist/components/sign-in.js.map +1 -1
  83. package/dist/components/task-list.js +1 -1
  84. package/dist/components/task-list.js.map +1 -1
  85. package/dist/components/task-view.d.ts.map +1 -1
  86. package/dist/components/task-view.js +2 -2
  87. package/dist/components/task-view.js.map +1 -1
  88. package/dist/components/ui.d.ts +13 -2
  89. package/dist/components/ui.d.ts.map +1 -1
  90. package/dist/components/ui.js +21 -14
  91. package/dist/components/ui.js.map +1 -1
  92. package/dist/console.css +1686 -425
  93. package/dist/console.d.ts.map +1 -1
  94. package/dist/console.js +83 -13
  95. package/dist/console.js.map +1 -1
  96. package/dist/data.d.ts +15 -2
  97. package/dist/data.d.ts.map +1 -1
  98. package/dist/data.js +18 -19
  99. package/dist/data.js.map +1 -1
  100. package/dist/i18n/en.d.ts +48 -1
  101. package/dist/i18n/en.d.ts.map +1 -1
  102. package/dist/i18n/en.js +50 -3
  103. package/dist/i18n/en.js.map +1 -1
  104. package/dist/i18n/ko.d.ts.map +1 -1
  105. package/dist/i18n/ko.js +47 -4
  106. package/dist/i18n/ko.js.map +1 -1
  107. package/dist/index.d.ts +3 -2
  108. package/dist/index.d.ts.map +1 -1
  109. package/dist/index.js +3 -2
  110. package/dist/index.js.map +1 -1
  111. package/dist/limits.d.ts +14 -0
  112. package/dist/limits.d.ts.map +1 -1
  113. package/dist/limits.js +14 -0
  114. package/dist/limits.js.map +1 -1
  115. package/dist/router.d.ts +9 -1
  116. package/dist/router.d.ts.map +1 -1
  117. package/dist/router.js +12 -0
  118. package/dist/router.js.map +1 -1
  119. package/dist/routes.d.ts +5 -1
  120. package/dist/routes.d.ts.map +1 -1
  121. package/dist/routes.js +5 -1
  122. package/dist/routes.js.map +1 -1
  123. package/dist/rules.js +1 -1
  124. package/dist/rules.js.map +1 -1
  125. package/dist/schemas.d.ts +272 -4
  126. package/dist/schemas.d.ts.map +1 -1
  127. package/dist/schemas.js +33 -8
  128. package/dist/schemas.js.map +1 -1
  129. package/dist/web.d.ts.map +1 -1
  130. package/dist/web.js +3 -2
  131. package/dist/web.js.map +1 -1
  132. package/docs/specs/cli.md +11 -10
  133. package/docs/specs/rest.md +33 -17
  134. package/package.json +2 -2
  135. package/skills/building-a-console/SKILL.md +5 -5
  136. package/skills/building-a-console/references/the-default-console.md +4 -4
  137. package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
  138. package/skills/working-the-board/SKILL.md +6 -4
  139. package/skills/working-the-board/references/setting-up.md +1 -1
  140. package/skills/working-the-board/references/terms.md +1 -1
  141. package/src/api.ts +10 -0
  142. package/src/cli/cli.ts +162 -19
  143. package/src/cli/format.ts +11 -4
  144. package/src/cli/help.ts +79 -20
  145. package/src/cli/pack.ts +17 -1
  146. package/src/client.ts +51 -2
  147. package/src/compat.ts +2 -2
  148. package/src/components/board.tsx +114 -21
  149. package/src/components/calendar.tsx +207 -102
  150. package/src/components/decision-form.tsx +71 -23
  151. package/src/components/decision-list.tsx +30 -3
  152. package/src/components/documents.tsx +27 -11
  153. package/src/components/event-feed.tsx +116 -33
  154. package/src/components/markdown.tsx +6 -3
  155. package/src/components/my-decisions.tsx +138 -0
  156. package/src/components/pack-connect.tsx +10 -7
  157. package/src/components/pack-token.tsx +4 -1
  158. package/src/components/people.tsx +20 -8
  159. package/src/components/projects.tsx +7 -5
  160. package/src/components/remote-pictures.ts +19 -0
  161. package/src/components/roadmap.tsx +1 -1
  162. package/src/components/runs.tsx +5 -1
  163. package/src/components/shell.tsx +36 -1
  164. package/src/components/sign-in.tsx +114 -13
  165. package/src/components/task-list.tsx +1 -1
  166. package/src/components/task-view.tsx +48 -47
  167. package/src/components/ui.tsx +70 -14
  168. package/src/console.tsx +153 -32
  169. package/src/data.ts +37 -20
  170. package/src/i18n/en.ts +53 -3
  171. package/src/i18n/ko.ts +50 -4
  172. package/src/index.ts +3 -0
  173. package/src/limits.ts +14 -0
  174. package/src/router.ts +18 -1
  175. package/src/routes.ts +5 -1
  176. package/src/rules.ts +1 -1
  177. package/src/schemas.ts +37 -8
  178. package/src/styles/console.css +1686 -425
  179. package/src/web.ts +3 -2
@@ -38,6 +38,7 @@ The API is the board as its members see it: the people, the projects, and in eac
38
38
  | 400 | `decision.invalid_task` | The decision is about a task that is not one of the project's. |
39
39
  | 400 | `decision.no_such_option` | The answer names an option the decision does not have. |
40
40
  | 400 | `decision.invalid_run` | A decision raised from a run that is not the asker's, or is over. |
41
+ | 400 | `decision.invalid_decider` | The person named to decide may not work in the project ([ADR-0042](../adr/0042-a-decision-is-addressed-to-a-person-its-raiser-unless-another-is-named.md)). |
41
42
  | 400 | `token.expiry_at_most` | A token that would not expire, or would outlast `TOKEN_DAYS_AT_MOST`, where the organization requires an expiry. |
42
43
  | 400 | `document.invalid_path` | The path a page is written at is not one: segments of lowercase letters, digits and hyphens, separated by slashes. |
43
44
  | 400 | `run.too_many_reports`, `run.too_many_artifacts` | The run carries as many as one may (`MAX_REPORTS_PER_RUN`, `MAX_ARTIFACTS_PER_RUN`). |
@@ -46,6 +47,8 @@ The API is the board as its members see it: the people, the projects, and in eac
46
47
  | 403 | `auth.forbidden_origin` | A request that changes something on a session did not come from the console's own pages. |
47
48
  | 403 | `auth.forbidden` | A member asked for what only an administrator, or only an owner of the project, may do. |
48
49
  | 403 | `auth.session_required` | A token asked for what only a person signed in may do: the tokens themselves, and the approval of an agent's connection, which makes one. |
50
+ | 403 | `auth.deactivated` | A token the console knows, for a person deactivated: the token is kept, refused on every route while they are out, `GET /api/v1/me` included, and theirs again when they are taken back in ([ADR-0044](../adr/0044-a-token-of-a-person-who-is-out-is-kept-and-refused-with-the-reason-until-they-are-back-in.md)). |
51
+ | 403 | `auth.not_admitted` | A token the console knows, for a person no longer let in: what let them in, the domain or the organization, is no longer named. Kept and refused the same way, and theirs again when they are back in. |
49
52
  | 403 | `run.not_yours` | The run is another person's. |
50
53
  | 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. |
51
54
  | 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. |
@@ -59,6 +62,11 @@ The API is the board as its members see it: the people, the projects, and in eac
59
62
  | 409 | `member.exists` | The person is listed in the project already. |
60
63
  | 409 | `member.open_project` | The project is open to the workspace, so everyone of it is a member already; only an owner is listed there ([ADR-0035](../adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md)). |
61
64
  | 409 | `category.key_taken`, `category.in_use`, `category.too_many` | A category of the workspace has that key already; tasks or goals are sorted into the category, so it stays until they are moved; the workspace has as many categories as one may (`MAX_CATEGORIES`). |
65
+ | 409 | `project.too_many`, `task.too_many`, `goal.too_many`, `decision.too_many`, `document.too_many`, `run.too_many` | The workspace holds as many projects as one may (`MAX_PROJECTS_PER_WORKSPACE`); the project as many tasks, goals, decisions or pages (`MAX_TASKS_PER_PROJECT`, `MAX_GOALS_PER_PROJECT`, `MAX_DECISIONS_PER_PROJECT`, `MAX_DOCUMENTS_PER_PROJECT`), those put away counted; the task has been taken as many times as one may (`MAX_RUNS_PER_TASK`). Bounds a runaway script meets and real work does not. |
66
+ | 409 | `project.too_many_bytes` | The files of the project, handed in by runs, put in comments and attached to pages, hold as many bytes together as the deployment allows one project (`PROJECT_FILE_BYTES_AT_MOST`, [deploy/README.md](../../deploy/README.md#a-shared-console)); no bound unless set. Asked before any byte is kept. |
67
+ | 429 | `feed.too_many` | The person holds as many streams of the feed as one may at once (`MAX_STREAMS_PER_PERSON`), or the process does for everyone (`MAX_STREAMS`); `retry-after` says when to try again. |
68
+ | 403 | `picture.refused` | The console fetches no picture from that address: one of its own network, the loopback, a private or a link-local range, or a redirect to one ([ADR-0048](../adr/0048-a-picture-from-elsewhere-is-read-through-the-console-from-a-public-address-and-kept-a-while.md)). |
69
+ | 502 | `picture.unavailable` | The picture could not be fetched from where it is named: the origin answered an error or something that is not a picture, sent more than `MAX_FILE_BYTES`, or could not be reached in time. |
62
70
  | 409 | `decision.answered` | The decision has an answer already. |
63
71
  | 409 | `document.conflict` | The page has moved on since the version the write started from (`baseVersion`), or was written at that path by someone else first. |
64
72
  | 409 | `document.archived` | The page is archived: restore it before writing to it or attaching to it. |
@@ -84,7 +92,7 @@ The API is the board as its members see it: the people, the projects, and in eac
84
92
 
85
93
  ### `GET /api/v1/me`
86
94
 
87
- `{ "workspace": { "name", "tokenDaysAtMost", "skills": { "source", "address", "packToken" }, "peopleVisibility" }, "person": ... | null, "agent": "..." | null, "language": "..." | null, "signIn": [{ "key", "label" }, ...], "console": { "version", "clientRange" } }`: 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`; and `packToken`, when the SkillCDN token the organization's pack is read with was set, `{ setAt }`, or `null`), who sees whom on the People page (`peopleVisibility`: `everyone`, or `shared_projects`, where a member sees only the people they share a project with and an administrator everyone; [ADR-0035](../adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md); read as optional, for a console from before the setting), the day the calendars begin a week on and this week is counted by (`weekStart`: `auto`, as the language of the pages has it, or `monday`, `sunday`; [ADR-0039](../adr/0039-this-week-is-the-plans-unit-and-the-week-begins-as-the-workspace-says.md)) and the workspace's time zone (`timeZone`: an IANA name, in which the command prints moments, or `null` for none; both optional the same way), 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; and the console's own version with the range of command versions it takes (`console`, [ADR-0025](../adr/0025-the-command-names-its-version-on-every-request-and-the-console-holds-it-to-the-range-it-takes.md)), for a console built on the package to compare itself with. 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.
95
+ `{ "workspace": { "name", "tokenDaysAtMost", "skills": { "source", "address", "packToken" }, "peopleVisibility" }, "person": ... | null, "agent": "..." | null, "language": "..." | null, "signIn": [{ "key", "label", "admits": { "organization", "domains" } }, ...], "console": { "version", "clientRange" } }`: 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`; and `packToken`, when the SkillCDN token the organization's pack is read with was set, `{ setAt }`, or `null`), who sees whom on the People page (`peopleVisibility`: `everyone`, or `shared_projects`, where a member sees only the people they share a project with and an administrator everyone; [ADR-0035](../adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md); read as optional, for a console from before the setting), the day the calendars begin a week on and this week is counted by (`weekStart`: `auto`, as the language of the pages has it, or `monday`, `sunday`; [ADR-0039](../adr/0039-this-week-is-the-plans-unit-and-the-week-begins-as-the-workspace-says.md)) and the workspace's time zone (`timeZone`: an IANA name, in which the command prints moments, or `null` for none; both optional the same way), 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 and, in `admits`, who gets in through it: the organization at the git host whose members are in, or `null`, and the Workspace domains whose accounts are, which a sign-in page says under the button; answered from 0.4.1 and read as optional), none where nobody can; and the console's own version with the range of command versions it takes (`console`, [ADR-0025](../adr/0025-the-command-names-its-version-on-every-request-and-the-console-holds-it-to-the-range-it-takes.md)), for a console built on the package to compare itself with. 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. A token the console knows whose person is out is refused here as on every route, `auth.deactivated` or `auth.not_admitted`, so that the agent learns why ([ADR-0044](../adr/0044-a-token-of-a-person-who-is-out-is-kept-and-refused-with-the-reason-until-they-are-back-in.md)).
88
96
 
89
97
  ### `GET /api/v1/people?login=`
90
98
 
@@ -104,7 +112,7 @@ Who may sign in, by login ([ADR-0022](../adr/0022-who-is-in-is-the-consoles-own-
104
112
 
105
113
  ### `POST /api/v1/projects`
106
114
 
107
- Takes `key` and `name`, and optionally `description`, `visibility` (default `private`), `skillsAddress` (checked as the standard spells one, kept canonical; `project.invalid_address`), `color` and `symbol`. For whoever is signed in, or their agent with their token; they become the project's owner. Answers `201` with the project and writes a `project.created` event in it, with `key` and `name`. A key taken is `project.key_taken`.
115
+ Takes `key` and `name`, and optionally `description`, `visibility` (default `private`), `skillsAddress` (checked as the standard spells one, kept canonical; `project.invalid_address`), `color` and `symbol`. For an administrator, signed in or through their agent with their token ([ADR-0045](../adr/0045-a-project-is-made-by-an-administrator.md)); they become the project's owner, and list another as owner from there. A member is refused with `auth.forbidden`. Answers `201` with the project and writes a `project.created` event in it, with `key` and `name`. A key taken is `project.key_taken`.
108
116
 
109
117
  ### `GET /api/v1/events?after=&limit=`
110
118
 
@@ -122,9 +130,9 @@ The workspace's settings, which an administrator keeps on the Settings page ([AD
122
130
 
123
131
  The SkillCDN token the organization's pack is read with ([ADR-0036](../adr/0036-a-private-pack-is-connected-with-a-code-a-person-approves-from-the-projects-settings.md)), for an administrator, signed in from the console's own pages or through their agent's token: every project that names no address of its own and holds no token of its own reads the organization's pack (`SKILLS_ADDRESS`) with it. `PUT` takes `{ "token" }` as the project's route does, tries it against the organization's pack once (`workspace.pack_token_refused` when the pack is not served to it or the console names no address for the organization's skills; `skills.unavailable` when the deployment could not be reached), seals it with the console's secret (`workspace.pack_token_unsupported` where the console has none) and keeps it on the workspace, answering the workspace as `me` carries it, with `skills.packToken: { setAt }`, and writing a `workspace.updated` event naming the field `packToken`. The token is answered nowhere and logged nowhere. `DELETE` takes it away the same way; taking away what is not there writes nothing.
124
132
 
125
- ### `POST /api/v1/workspace/pack-token/connect`, `GET .../pack-token/connect` and `DELETE .../pack-token/connect`
133
+ ### `POST /api/v1/workspace/pack-token/connect`, `GET .../pack-token/connect`, `POST .../pack-token/connect/claim` and `DELETE .../pack-token/connect`
126
134
 
127
- The workspace's token connected with a code a person approves ([ADR-0036](../adr/0036-a-private-pack-is-connected-with-a-code-a-person-approves-from-the-projects-settings.md)), for an administrator, as a project's is: `POST` asks the deployment to connect the organization's address and answers `201` with `{ "code", "url", "expiresAt", "intervalSeconds" }`, with `workspace.pack_connect_unsupported`, `workspace.pack_connect_refused` and `skills.unavailable` where a project's route has its own, the refusal naming a console with no address for the organization's skills; `GET` answers `{ "status": "none" }`, `pending` with what the person is shown, `connected` with `workspace` as `me` carries it once the token is kept, or `ended` with `reason`; `DELETE` gives the request up. One request for the workspace at a time.
135
+ The workspace's token connected with a code a person approves ([ADR-0036](../adr/0036-a-private-pack-is-connected-with-a-code-a-person-approves-from-the-projects-settings.md)), for an administrator, as a project's is: `POST .../connect` asks the deployment to connect the organization's address and answers `201` with `{ "code", "url", "expiresAt", "intervalSeconds" }`, with `workspace.pack_connect_unsupported`, `workspace.pack_connect_refused` and `skills.unavailable` where a project's route has its own, the refusal naming a console with no address for the organization's skills; `GET` reads how it stands and changes nothing: `{ "status": "none" }`, `pending` with what the person is shown, or `ended` with `reason` `expired`; `POST .../connect/claim` asks the deployment whether the person approved ([ADR-0046](../adr/0046-the-claim-of-a-pack-token-is-a-request-of-its-own-and-reading-how-the-connection-stands-changes-nothing.md)) and answers `pending` still, `connected` with `workspace` as `me` carries it once the token is kept, or `ended` with `reason`; `DELETE` gives the request up. One request for the workspace at a time.
128
136
 
129
137
  ### `GET /api/v1/categories`
130
138
 
@@ -140,7 +148,11 @@ The rules of the board ([ADR-0023](../adr/0023-the-rules-of-a-board-are-pages-th
140
148
 
141
149
  ### `GET /api/v1/work`
142
150
 
143
- `{ "tasks": [{ "project": { "key", "name", "color", "symbol", "icon" }, "task" }, ...], "closed": [{ "project", "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 they are at work on, the sooner due first and those with no day last; `closed`, the tasks they are at work on that are done or dropped and not yet put away, the last closed first, so that the week on the plate's page shows them on their days as a project's calendar does; 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. A task put away is in neither list ([ADR-0030](../adr/0030-a-task-is-put-away-from-any-state.md)). At most `LIST_LIMIT` of each.
151
+ `{ "tasks": [{ "project": { "key", "name", "color", "symbol", "icon" }, "task" }, ...], "closed": [{ "project", "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 they are at work on, the sooner due first and those with no day last; `closed`, the tasks they are at work on that are done or dropped and not yet put away, the last closed first, so that the week on the plate's page shows them on their days as a project's calendar does; and the decisions that wait for the asker's answer ([ADR-0042](../adr/0042-a-decision-is-addressed-to-a-person-its-raiser-unless-another-is-named.md)), the longest waiting first; each with the project it belongs to, so that a page says and links it. A task put away is in neither list ([ADR-0030](../adr/0030-a-task-is-put-away-from-any-state.md)). At most `LIST_LIMIT` of each.
152
+
153
+ ### `GET /api/v1/decisions?open=&raised=&limit=&offset=`
154
+
155
+ `{ "items": [{ "project": { "key", "name", "color", "symbol", "icon" }, "decision" }, ...], "more": boolean }`: the decisions addressed to the asker ([ADR-0042](../adr/0042-a-decision-is-addressed-to-a-person-its-raiser-unless-another-is-named.md)) across the projects they may see, a page at a time as the tasks are (`limit`, `offset`, `more`): the ones that wait first, the longest waiting first among them, then the answered, newest first, as the record; `open=true` keeps only those that wait, and `raised=true` answers instead the decisions the asker raised, through their agent or themselves, for someone else. Each decision comes with the project it belongs to, as the plate's things do, so that a page says and links it. What a person's own page of decisions reads, in both kinds, and `console decisions --mine` and `--raised`.
144
156
 
145
157
  ### `GET /api/v1/tokens` and `POST /api/v1/tokens`
146
158
 
@@ -152,7 +164,7 @@ Takes one of the asker's tokens away, whoever holds it, and answers `204`. A tok
152
164
 
153
165
  ### `DELETE /api/v1/me/token`
154
166
 
155
- 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`.
167
+ 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`. A token whose person is out is refused here as everywhere ([ADR-0044](../adr/0044-a-token-of-a-person-who-is-out-is-kept-and-refused-with-the-reason-until-they-are-back-in.md)): the console keeps it for when they are back in, and the command forgets it on the machine.
156
168
 
157
169
  ### `GET /api/v1/people/<id>/tokens` and `DELETE /api/v1/people/<id>/tokens/<token id>`
158
170
 
@@ -162,11 +174,15 @@ An administrator's sight of everyone's agents ([ADR-0011](../adr/0011-an-agent-c
162
174
 
163
175
  `{ "tasks": [{ "project", "task" }, ...], "closed": [{ "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 open tasks the person is at work on, the sooner due first and those with no day last, and as `closed` those of them done or dropped and not yet put away, both 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.
164
176
 
177
+ ### `GET /api/v1/pictures?src=`
178
+
179
+ A picture from elsewhere, for a page that shows one ([ADR-0048](../adr/0048-a-picture-from-elsewhere-is-read-through-the-console-from-a-public-address-and-kept-a-while.md)): the console fetches it and hands it over, so that the person's browser reaches nobody's server. `src` is the https URL that names it, up to `MAX_URL_LENGTH`, on its default port and with no credentials in it; anything else is `request.invalid`. For whoever is signed in, with a session or a token. Fetched from a public address only, the host resolved first: an address of the console's own network, the loopback, a private or a link-local range, is `picture.refused` (`403`), and so is a redirect to one. What comes back must say it is a picture (`image/*`) and be at most `MAX_FILE_BYTES`; the origin's error, its silence or anything else is `picture.unavailable` (`502`). Answered with the picture's bytes and type, an `etag` of the bytes, `cache-control: private, max-age=86400`, so that a browser keeps it a day and asks with `if-none-match` after (`304`), and the headers a file is served with, under a policy that runs nothing. The console keeps what it fetched in memory, within a bounded room, for an hour before it asks the origin again with what the origin said the picture was; keeps serving it for a day when the origin cannot be reached; and keeps a refusal five minutes, so that a dead link costs one try in a while. The pages read every picture from elsewhere through here, a person's picture from the git host included, and load nothing from elsewhere themselves.
180
+
165
181
  ### Connecting an agent
166
182
 
167
183
  `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)).
168
184
 
169
- `POST /api/v1/connect` is nobody's: the command sends `{ "agent" }` (what the agent calls itself and where it runs, a line up to `MAX_TOKEN_NAME_LENGTH`) and is answered `201` with `{ "code", "url", "secret", "expiresAt", "interval" }`: a code of eight characters in two groups (`ABCD-EFGH`, from an alphabet without look-alikes) for a person to approve, the console's own page for it, a secret (`cns_c_...`) the command claims with and shows nowhere, when the request expires (thirty minutes after it began), and how many seconds the command waits between claims. A deployment holds a bounded number of requests at once (`connect.too_many`).
185
+ `POST /api/v1/connect` is nobody's: the command sends `{ "agent" }` (what the agent calls itself and where it runs, a line up to `MAX_TOKEN_NAME_LENGTH`) and is answered `201` with `{ "code", "url", "secret", "expiresAt", "interval" }`: a code of eight characters in two groups (`ABCD-EFGH`, from an alphabet without look-alikes) for a person to approve, the console's own page for it, a secret (`cns_c_...`) the command claims with and shows nowhere, when the request expires (thirty minutes after it began), and how many seconds the command waits between claims. A deployment holds a bounded number of requests at once (`connect.too_many`), and a bounded number from one address, as the trusted proxies resolve it: at that bound the oldest request of the address not yet approved makes way for the new one, and its claim is `connect.not_found`; an address whose requests are all approved is `connect.too_many` ([ADR-0047](../adr/0047-a-connection-request-is-bounded-per-address-and-the-oldest-not-yet-approved-makes-way.md)).
170
186
 
171
187
  `GET /api/v1/connect/<code>`, for a person signed in, answers what asks: `{ "code", "agent", "createdAt", "expiresAt", "approved" }`. A code is read as a person types it: any case, with or without the hyphen. `POST .../approve`, from the console's own pages, takes what `POST /api/v1/tokens` takes (`name`, `expiresInDays`) and answers the request, approved; `POST .../deny` takes the request away and answers `204`.
172
188
 
@@ -216,11 +232,11 @@ Writing one, for whoever may work in the project, with a session or a token, is
216
232
 
217
233
  ### `GET /api/v1/projects/<key>/decisions?open=&task=&limit=&offset=`
218
234
 
219
- `{ "items": [decision, ...], "more": boolean }`: the project's, the ones that wait first, newest first within each, a page at a time as the tasks are (`limit`, `offset`, `more`); `open=true` keeps only those that wait, `task=<id>` only those about one task. A decision carries its `question`, `body` (Markdown: the context it rests on), `options` (each an `id` and a `label`; the last carries `own: true` when it is the person's own answer, written instead of choosing, and the options given carry no `own`), `taskId` and `taskNumber` (or `null`), `raisedBy`, `run` (the `id` and `agent` of the run that raised it, when an agent asked, or `null`), `answer`: `null` while it waits, else the chosen `option`, a `note` or `null` (the rationale given with it), `by` and `at`; and `outcome`, what followed, in Markdown, or `null` until someone writes it. Context, rationale and outcome make the decision a record ([ADR-0009](../adr/0009-documents-are-pages-of-markdown-in-a-project-addressed-by-path-versioned-and-linked-both-ways.md)); a link in any of them to a document's path is kept both ways, so that the document says the decision refers to it.
235
+ `{ "items": [decision, ...], "more": boolean }`: the project's, the ones that wait first, newest first within each, a page at a time as the tasks are (`limit`, `offset`, `more`); `open=true` keeps only those that wait, `task=<id>` only those about one task. A decision carries its `question`, `body` (Markdown: the context it rests on), `options` (each an `id` and a `label`; the last carries `own: true` when it is the person's own answer, written instead of choosing, and the options given carry no `own`), `taskId` and `taskNumber` (or `null`), `raisedBy`, `decider` (whom it is for, [ADR-0042](../adr/0042-a-decision-is-addressed-to-a-person-its-raiser-unless-another-is-named.md): the raiser, unless another person of the project was named), `run` (the `id` and `agent` of the run that raised it, when an agent asked, or `null`), `answer`: `null` while it waits, else the chosen `option`, a `note` or `null` (the rationale given with it), `by` and `at`; and `outcome`, what followed, in Markdown, or `null` until someone writes it. Context, rationale and outcome make the decision a record ([ADR-0009](../adr/0009-documents-are-pages-of-markdown-in-a-project-addressed-by-path-versioned-and-linked-both-ways.md)); a link in any of them to a document's path is kept both ways, so that the document says the decision refers to it.
220
236
 
221
237
  ### `POST /api/v1/projects/<key>/decisions`
222
238
 
223
- Takes `question`, `options` (two to `MAX_OPTIONS` labels), and optionally `body`, and either `taskId` or `runId`. Options are numbered from `1`. From a run, which must be the asker's and open (`decision.invalid_run`), the decision is about the run's task, `taskId` is not looked at, and the run waits (`status: waiting`) until the answer. Answers `201` with the decision and writes a `decision.raised` event, with the `agent` when a run asked.
239
+ Takes `question`, `options` (two to `MAX_OPTIONS` labels), and optionally `body`, and either `taskId` or `runId`. `deciderId` names whom the decision is for ([ADR-0042](../adr/0042-a-decision-is-addressed-to-a-person-its-raiser-unless-another-is-named.md)): a person who may work in the project, as a worker of a task must (`decision.invalid_decider` otherwise); left out, the asker, which from a run is the person the agent works for. Anyone of the project may still answer. Options are numbered from `1`. From a run, which must be the asker's and open (`decision.invalid_run`), the decision is about the run's task, `taskId` is not looked at, and the run waits (`status: waiting`) until the answer. Answers `201` with the decision and writes a `decision.raised` event, with the `agent` when a run asked.
224
240
 
225
241
  ### `GET .../decisions/<id>?wait=` and `POST .../decisions/<id>/answer`
226
242
 
@@ -242,7 +258,7 @@ One goal, for whoever may see the project. The patch, for an owner, takes any of
242
258
 
243
259
  ### `GET /api/v1/projects/<key>/runs?task=&open=&mine=&limit=&offset=` and `GET .../runs/<id>`
244
260
 
245
- `{ "items": [run, ...], "more": boolean }`: the project's runs, newest first, a page at a time as the tasks are (`limit`, `offset`, `more`); `task=<id>` keeps those on one task, `open=true` those not over (`running` or `waiting`) and `open=false` those over, `mine=true` the asker's own: with a token, the runs begun with that token, which is one agent's; on a session, the runs for the person. A run is one agent's work on one task for one person: `taskId` and `taskNumber` (the one people say), `person` (whom the agent acts for), `agent` (what it calls itself), `status` (`running`, that is open, `waiting` for a decision, `finished`, `failed`, `abandoned`), `startedAt`, `endedAt` or `null`, `lastHeardAt` (when the agent was last heard from through the run: its start, then its latest report, hand-in or question; the console knows nothing of an agent but what it sends, and claims nothing more, [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)), `summary` (Markdown, what the agent said at the end, or `null`), `reports` (each `id`, `body` in Markdown, `createdAt`; oldest first), `artifacts` (each `id`, `kind`, `url` for a link or `null`, `label` or `null`, `file` for a file or `null`, and `createdAt`; a file is its `name`, `size` in bytes, `contentType` and `sha256`), and `waitingFor`, the id of the decision the run waits for, or `null`.
261
+ `{ "items": [run, ...], "more": boolean }`: the project's runs, newest first, a page at a time as the tasks are (`limit`, `offset`, `more`); `task=<id>` keeps those on one task, `open=true` those not over (`running` or `waiting`) and `open=false` those over, `mine=true` the asker's own: with a token, the runs begun with that token, which is one agent's; on a session, the runs for the person. A run is one agent's work on one task for one person: `taskId` and `taskNumber` (the one people say), `person` (whom the agent acts for), `agent` (what it calls itself), `status` (`running`, that is open, `waiting` for a decision, `finished`, `failed`, `abandoned`), `startedAt`, `endedAt` or `null`, `lastHeardAt` (when the agent was last heard from through the run: its start, then its latest report, hand-in or question; the console knows nothing of an agent but what it sends, and claims nothing more, [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); a run `running` and not heard from for twelve hours, or the hours the deployment sets, is ended by the console as `abandoned`, its `summary` saying so and `run.ended` naming no actor and `reason: "unheard"`, while a run `waiting` for an answer waits on, [ADR-0041](../adr/0041-a-run-not-heard-from-for-twelve-hours-is-ended-by-the-console.md)), `summary` (Markdown, what the agent said at the end, or `null`), `reports` (each `id`, `body` in Markdown, `createdAt`; oldest first), `artifacts` (each `id`, `kind`, `url` for a link or `null`, `label` or `null`, `file` for a file or `null`, and `createdAt`; a file is its `name`, `size` in bytes, `contentType` and `sha256`), and `waitingFor`, the id of the decision the run waits for, or `null`.
246
262
 
247
263
  ### `POST /api/v1/projects/<key>/runs`
248
264
 
@@ -254,13 +270,13 @@ How the work goes, and what was made. A report takes `body` (Markdown, `MAX_BODY
254
270
 
255
271
  ### `POST .../runs/<id>/files` and `GET /api/v1/projects/<key>/files/<id>`
256
272
 
257
- A file handed in, as the parts of a form (`multipart/form-data`): the part `file` carries the bytes, with the file's name and its media type; the part `label` is optional. The name is one line without a path (`MAX_FILE_NAME_LENGTH`), the file is not empty and at most `MAX_FILE_BYTES` (`request.too_large`), and a media type that is not one, or none, is `application/octet-stream`. The bytes are kept under their SHA-256, so the same file handed in twice is kept once; the artifact on the run is `kind: "file"` with `file` filled in and `url` null. Answers `201` with the run and tells the board (`run.handed_in`, with the label or the file's name). The same rules as for a link: the run's person only, while the run is open, and only so many. A picture among the files is shown on the task's page small, and large when pressed, and a report that names it as `![words](its name)` shows it where the words are.
273
+ A file handed in, as the parts of a form (`multipart/form-data`): the part `file` carries the bytes, with the file's name and its media type; the part `label` is optional. The name is one line without a path (`MAX_FILE_NAME_LENGTH`), the file is not empty and at most `MAX_FILE_BYTES` (`request.too_large`), and a media type that is not one, or none, is `application/octet-stream`. Where the deployment bounds what one project's files hold together (`PROJECT_FILE_BYTES_AT_MOST`), a file that would take the project over it, counted with every file handed in, put in a comment or attached to a page, is `project.too_many_bytes` (`409`) and nothing of it is kept; a comment's files and a page's file are held to the same bound. The bytes are kept under their SHA-256, so the same file handed in twice is kept once; the artifact on the run is `kind: "file"` with `file` filled in and `url` null. Answers `201` with the run and tells the board (`run.handed_in`, with the label or the file's name). The same rules as for a link: the run's person only, while the run is open, and only so many. A picture among the files is shown on the task's page small, and large when pressed, and a report that names it as `![words](its name)` shows it where the words are.
258
274
 
259
275
  The bytes of any file of the project, handed in by a run, put in a comment or attached to a page, are read back at `/api/v1/projects/<key>/files/<file id>`, the project's one address for files, by whoever may see the project, with a session or a token: `content-length` and `content-disposition` name the file, the few kinds a browser may show in place (pictures, plain text, Markdown, CSV, JSON) are answered with their type and `inline`, and every other kind as `application/octet-stream` and `attachment`, never sniffed (`x-content-type-options`) and under a policy that runs nothing (`sandbox`). An id that is a link's, or nothing, or another project's, is `file.not_found`.
260
276
 
261
277
  ### `POST .../runs/<id>/end`
262
278
 
263
- Takes `status` (`finished`, `failed` or `abandoned`) and optionally a `summary` (Markdown, `MAX_SUMMARY_LENGTH`: what was done and what is left), ends the run (`run.ended`), and answers it. A finished run puts a task that was `in_progress` up for review (`in_review`); the rest leave the task as it is. The run's person ends it, as `finished` or `failed` from their agent; `abandoned` is a person giving up on a run that will not come back: their own, or anyone's as an owner of the project, signed in or through their agent (`run.not_yours` otherwise). A run that is over is `run.over`.
279
+ Takes `status` (`finished`, `failed` or `abandoned`) and optionally a `summary` (Markdown, `MAX_SUMMARY_LENGTH`: what was done and what is left), ends the run (`run.ended`), and answers it. A finished run puts a task that was `in_progress` up for review (`in_review`); the rest leave the task as it is. The run's person ends it, as `finished` or `failed` from their agent; `abandoned` is a person giving up on a run that will not come back: their own, or anyone's as an owner of the project, signed in or through their agent (`run.not_yours` otherwise); the console gives up on a run itself when it has not heard from it for twelve hours ([ADR-0041](../adr/0041-a-run-not-heard-from-for-twelve-hours-is-ended-by-the-console.md)). A run that is over is `run.over`.
264
280
 
265
281
  ### `GET /api/v1/projects/<key>/skills`
266
282
 
@@ -268,15 +284,15 @@ The project's pack, read by address ([ADR-0028](../adr/0028-the-console-reads-a-
268
284
 
269
285
  ### `GET /api/v1/projects/<key>/skills/one?path=&cursor=`, `.../skills/file?path=&offset=&limit=`, `.../skills/browse?path=&cursor=&limit=` and `.../skills/find?query=&path=&cursor=&limit=`
270
286
 
271
- One skill of the pack, a file of it, what a folder holds and what words find, read by the console from the pack for whoever may see the project, with the SkillCDN token the pack is read with, the project's own or the workspace's ([ADR-0028](../adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md)), and answered as the pack answered them ([ADR-0031](../adr/0031-the-console-reads-a-pack-as-the-skillcdn-command-does-and-a-local-one-from-a-directory.md)): `{ "address", "source", "page", "status", "refusal", "read" }`, the envelope as above, and `read` the body of the matching route of the main repository's REST API, as `@skillcdn/core` spells it, or `null` unless `ready`. `skills/one` is `load_skill`: `read` is a `RestSkill`, a page of the skill's context, the rules from the repository's root to the nearest manifest, the body and the included files, which `complete` and `nextCursor` say to continue, and `commit`, the commit it was read from. `skills/file` is `read_repo_file`: `read` is a `RestFile`, a page of the file in characters (`offset`, `nextOffset`, `totalLength`; `limit` 1 to 100,000) or, when the path names a folder, its entries. `skills/browse` is `browse_repo`: `read` is a `RestBrowse`, a page of what the folder holds (`limit` 1 to 200, 50 unless given; `cursor` continues). `skills/find` is `search_repo`: `read` is a `RestFind`, a page of what the words find in relevance order (`query` 1 to 500 characters, required; `limit` 1 to 25, 10 unless given). `path` is a path from the repository's root, up to 500 characters, the mounted folder when `browse` and `find` leave it out; a query out of bounds is `request.invalid`. Each read is logged with who read what of which pack, never with what was read or the words searched for. What the pack refuses is the status with the pack's own `refusal`: a skill or a file that is not there is `not_found`, a file the pack will not carry, too large or not text, is `failed` with where it is at its source. `@skillcdn/core` reads every `read` back as the result it was made from (`skillResultOf`, `fileResultOf`, `directoryResultOf`, `browseResultOf`, `findResultOf`, at the `mountSummaryOf` of the listing's `mount`) and renders what a model is given; the command does exactly that.
287
+ One skill of the pack, a file of it, what a folder holds and what words find, read by the console from the pack for whoever may see the project, with the SkillCDN token the pack is read with, the project's own or the workspace's ([ADR-0028](../adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md)), and answered as the pack answered them ([ADR-0031](../adr/0031-the-console-reads-a-pack-as-the-skillcdn-command-does-and-a-local-one-from-a-directory.md)): `{ "address", "source", "page", "status", "refusal", "read" }`, the envelope as above, and `read` the body of the matching route of the main repository's REST API, as `@skillcdn/core` spells it, or `null` unless `ready`. `skills/one` is `load_skill`: `read` is a `RestSkill`, a page of the skill's context, the rules from the repository's root to the nearest manifest, the body and the included files, which `complete` and `nextCursor` say to continue, and `commit`, the commit it was read from. `skills/file` is `read_repo_file`: `read` is a `RestFile`, a page of the file in characters (`offset`, `nextOffset`, `totalLength`; `limit` 1 to 100,000) or, when the path names a folder, its entries. `skills/browse` is `browse_repo`: `read` is a `RestBrowse`, a page of what the folder holds (`limit` 1 to 200, 50 unless given; `cursor` continues); given a file's path, the file's `outline`, every heading with its level and the `offset` and `length` that read its section through `skills/file`, paged the same way, with `entries` empty (SkillCDN 0.4). `skills/find` is `search_repo`: `read` is a `RestFind`, a page of what the words find in relevance order (`query` 1 to 500 characters, required; `limit` 1 to 25, 10 unless given); a document among the results names the `sections` of it that matched, each with its heading path, the `offset` and `limit` that read it and an excerpt, and `ranking` says how the page was ranked where the deployment searches by meaning as well as by words. `path` is a path from the repository's root, up to 500 characters, the mounted folder when `browse` and `find` leave it out; a query out of bounds is `request.invalid`. Each read is logged with who read what of which pack, never with what was read or the words searched for. What the pack refuses is the status with the pack's own `refusal`: a skill or a file that is not there is `not_found`, a file the pack will not carry, too large or not text, is `failed` with where it is at its source. `@skillcdn/core` reads every `read` back as the result it was made from (`skillResultOf`, `fileResultOf`, `directoryResultOf`, `browseResultOf`, `findResultOf`, at the `mountSummaryOf` of the listing's `mount`) and renders what a model is given; the command does exactly that.
272
288
 
273
289
  ### `PUT /api/v1/projects/<key>/pack-token` and `DELETE .../pack-token`
274
290
 
275
291
  The pack token ([ADR-0028](../adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md)), for an owner, signed in from the console's own pages or through their agent's token. `PUT` takes `{ "token" }`, a token a person made at the deployment for the pack's repository (8 to 1024 characters, no whitespace), tries it against the deployment once (`project.pack_token_refused` when the pack is not served to it or the project names no pack; `skills.unavailable` when the deployment could not be reached), seals it with the console's secret (`project.pack_token_unsupported` where the console has none) and keeps it, answering the project with `packToken: { setAt }` and writing a `project.updated` event naming the field `packToken`. The token is answered nowhere and logged nowhere. `DELETE` takes it away the same way; taking away what is not there writes nothing.
276
292
 
277
- ### `POST /api/v1/projects/<key>/pack-token/connect`, `GET .../pack-token/connect` and `DELETE .../pack-token/connect`
293
+ ### `POST /api/v1/projects/<key>/pack-token/connect`, `GET .../pack-token/connect`, `POST .../pack-token/connect/claim` and `DELETE .../pack-token/connect`
278
294
 
279
- A private pack connected with a code a person approves ([ADR-0036](../adr/0036-a-private-pack-is-connected-with-a-code-a-person-approves-from-the-projects-settings.md)), for an owner. `POST` asks the deployment to connect the project's address (the project's own, or the organization's), as nobody, the console calling itself by the workspace's name with Console after it, once (`Acme Console`, whether the name ends with it or not), and answers `201` with what the person is shown: `{ "code", "url", "expiresAt", "intervalSeconds" }`, the code, the page at the deployment to approve it on, until when, and how often to ask again; the secret the console claims with is kept sealed on a row of the project's and answered never. One request per project at a time: a new one replaces the one under way. `project.pack_connect_unsupported` (`409`) where the console reaches no deployment or keeps no secret to seal with; `project.pack_connect_refused` (`400`) when the project names no pack, the pack is on the console's machine, or the deployment refused to begin, in its words (an address with a ref or a path, a public pack); `skills.unavailable` (`503`) when the deployment could not be reached. `GET` asks how it stands and answers one of `{ "status": "none" }`, nothing asked; `{ "status": "pending", "code", "url", "expiresAt", "intervalSeconds" }`, while the person has not approved; `{ "status": "connected", "project" }`, once, when the token was claimed, sealed and kept as `PUT .../pack-token` keeps one, writing `project.updated` with `packToken`, the project answered as it stands; or `{ "status": "ended", "reason": "expired" | "declined" | "refused" }`, when the code's time ran out, the person declined or the code was spent, or the deployment refused (a repository the person may not open, as many tokens as one may hold), the request gone each time. A deployment that could not be reached is `skills.unavailable`, and the request stands. `DELETE` gives the request up and answers `204`.
295
+ A private pack connected with a code a person approves ([ADR-0036](../adr/0036-a-private-pack-is-connected-with-a-code-a-person-approves-from-the-projects-settings.md)), for an owner. `POST` asks the deployment to connect the project's address (the project's own, or the organization's), as nobody, the console calling itself by the workspace's name with Console after it, once (`Acme Console`, whether the name ends with it or not), and answers `201` with what the person is shown: `{ "code", "url", "expiresAt", "intervalSeconds" }`, the code, the page at the deployment to approve it on, until when, and how often to ask again; the secret the console claims with is kept sealed on a row of the project's and answered never. One request per project at a time: a new one replaces the one under way. `project.pack_connect_unsupported` (`409`) where the console reaches no deployment or keeps no secret to seal with; `project.pack_connect_refused` (`400`) when the project names no pack, the pack is on the console's machine, or the deployment refused to begin, in its words (an address with a ref or a path, a public pack); `skills.unavailable` (`503`) when the deployment could not be reached. `GET` reads how it stands and changes nothing ([ADR-0046](../adr/0046-the-claim-of-a-pack-token-is-a-request-of-its-own-and-reading-how-the-connection-stands-changes-nothing.md)): `{ "status": "none" }`, nothing asked; `{ "status": "pending", "code", "url", "expiresAt", "intervalSeconds" }`, while the request stands; or `{ "status": "ended", "reason": "expired" }`, once its time ran out. `POST .../pack-token/connect/claim`, from the console's own pages or with a token, asks the deployment whether the person approved, at the interval it named, and answers `pending` still, with the same; `{ "status": "connected", "project" }`, once, when the token was claimed, sealed and kept as `PUT .../pack-token` keeps one, writing `project.updated` with `packToken`, the project answered as it stands; or `{ "status": "ended", "reason": "expired" | "declined" | "refused" }`, when the code's time ran out, the person declined or the code was spent, or the deployment refused (a repository the person may not open, as many tokens as one may hold), the request gone each time. A deployment that could not be reached is `skills.unavailable`, and the request stands. `DELETE` gives the request up and answers `204`.
280
296
 
281
297
  ### `GET /api/v1/projects/<key>/rules`
282
298
 
@@ -312,11 +328,11 @@ A file attached to the page, sent and read back exactly as a run hands one in ([
312
328
 
313
329
  ### `GET /api/v1/projects/<key>/events?after=&limit=&task=&run=&decision=&document=&goal=`
314
330
 
315
- `{ "items": [event, ...], "more": boolean }`: what happened in the project after event number `after` (`0` for the beginning), oldest first, at most `limit` (default and maximum `EVENTS_PAGE_LIMIT`); `more` says whether there is more after the last item. `task=<id>`, `run=<id>`, `decision=<id>`, `document=<id>` or `goal=<id>` keeps the events about one of them: how a page shows everything that happened to a task, a run, a decision, a document or a goal. An event carries its `id` (the number), `kind` (one of `EVENT_KINDS`), the `actor` (a person, or, for what an agent did, the person it acts for), `agent` (what the person acted through: the run's agent for what a run did, else the name of the token presented; `null` when a person acted themselves), `projectId`, `taskId`, `decisionId`, `runId`, `documentId` and `goalId` (or `null`), `data` (what a feed shows without asking for the subject: `number`, `title`, `fields`, `from`, `to`, `question`, `option`, `login`, `role`, `key`, `name`, `agent`, `status`, `label`, `excerpt`, `path`, `version`, each only when the kind has it), and `createdAt`.
331
+ `{ "items": [event, ...], "more": boolean }`: what happened in the project after event number `after` (`0` for the beginning), oldest first, at most `limit` (default and maximum `EVENTS_PAGE_LIMIT`); `more` says whether there is more after the last item. With `latest=true`, the newest `limit` events instead, oldest first, and `more` says whether there are earlier ones: how a page shows the feed as it stands without reading it from the beginning; `after` is not looked at then. The workspace's own feed takes `latest` the same way. `task=<id>`, `run=<id>`, `decision=<id>`, `document=<id>` or `goal=<id>` keeps the events about one of them: how a page shows everything that happened to a task, a run, a decision, a document or a goal. An event carries its `id` (the number), `kind` (one of `EVENT_KINDS`), the `actor` (a person, or, for what an agent did, the person it acts for), `agent` (what the person acted through: the run's agent for what a run did, else the name of the token presented; `null` when a person acted themselves), `projectId`, `taskId`, `decisionId`, `runId`, `documentId` and `goalId` (or `null`), `data` (what a feed shows without asking for the subject: `number`, `title`, `fields`, `from`, `to`, `question`, `option`, `login`, `role`, `key`, `name`, `agent`, `status`, `label`, `excerpt`, `path`, `version`, each only when the kind has it), and `createdAt`.
316
332
 
317
333
  ### `GET /api/v1/projects/<key>/events/stream?after=`
318
334
 
319
- The same events as they happen, as server-sent events: one message per event, with `id` the event's number, `event` its kind and `data` the event as JSON; a comment (`: ping`) every 25 seconds of silence. A browser that reconnects sends `Last-Event-ID`, which wins over `after`, so that nothing is missed. The stream ends when the server shuts down; an `EventSource` reconnects on its own.
335
+ The same events as they happen, as server-sent events: one message per event, with `id` the event's number, `event` its kind and `data` the event as JSON; a comment (`: ping`) every 25 seconds of silence. A browser that reconnects sends `Last-Event-ID`, which wins over `after`, so that nothing is missed. The stream ends when the server shuts down, and when the person may no longer read the project, which is asked again before every delivery and every heartbeat: a member taken off a private project, a person deactivated or a token revoked stops reading within a heartbeat. An `EventSource` reconnects on its own, and is answered as any request is. A person holds at most `MAX_STREAMS_PER_PERSON` streams at once, across their tabs, and a process of the console `MAX_STREAMS` for everyone: one more is `feed.too_many` (`429`, `retry-after`), which a browser's `EventSource` does not retry on its own; the page shows the board as it stands and loads it again on the next visit.
320
336
 
321
337
  The feed is written in the same transaction as the change it records, so what a stream carries happened, and what happened is carried; every process of the deployment learns of a change through the database's own channel, so a board with several `api` replicas is one board.
322
338
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillcdn/console",
3
- "version": "0.3.2",
3
+ "version": "0.4.1",
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",
@@ -68,7 +68,7 @@
68
68
  "vitest": "^5.0.3"
69
69
  },
70
70
  "dependencies": {
71
- "@skillcdn/core": "^0.3.1",
71
+ "@skillcdn/core": "^0.4.1",
72
72
  "react-markdown": "^10.1.0",
73
73
  "remark-gfm": "^4.0.1",
74
74
  "zod": "^4.6.5"
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: A shell with Node.js 24 or newer and npm or pnpm, and an agent that reads and writes files. For looking at the pages against a board, the console command signed in by a person. A browser the agent can drive is optional. No key of the agent's own is needed, and it never sees a token.
6
6
  metadata:
7
7
  author: skillcdn
8
- version: "1.5"
8
+ version: "1.7"
9
9
  skillcdn:
10
10
  translations:
11
11
  ko:
@@ -50,8 +50,8 @@ Nothing here restates them: they are the contract, current at the version instal
50
50
  | Which kind | Asked when the request does not say: "Is this console for you alone, served from your machine with your token; for the whole organization, in place of the default pages in the image; or a script against the API?" A member usually wants their own. |
51
51
  | Where the organization's build goes | Asked for the organization's: "How is the console run: with the deployment repository that deploying-a-console made, with an image you build yourselves, or from a directory mounted into it?" The answer places the result: the source in `console-ui/` of that repository, which its workflow builds on every push; a repository of its own whose build goes beside the Dockerfile of [deploy/README.md](/deploy/README.md) "A custom console in the image"; or the build in the directory. |
52
52
  | What it must show or do | From the request: a page, a component, a language, a brand, a column, a filter, a report. Asked for its missing half: "What should it show or do that the default console does not?" Everything else is the default console's and stays. |
53
- | The marks | From the organization's website when the request, or deploying-a-console, names one: its symbol and wordmark read from the site as [the-look-from-a-website.md](references/the-look-from-a-website.md) says, the light version or, with the person's word, the one-colour mark recoloured, shown at the plan. Else asked for a console that shows marks of its own, for the half the request leaves open: "Which picture should the pages show beside the board's name: a small square mark, the name as lettering, or both? Where is the file? The pages are dark, so it should be a light one." `brand` takes either or both. The symbol's slot is a square of one line's height (`.sc-brand-symbol`, 1rem): a wide mark, a lettermark of three or four times its height, is a wordmark and goes in as one, alone or beside a square symbol. A mark drawn in a dark colour vanishes on the dark pages: ask for a light version, or, with the person's word, recolour it when it is one colour. Default without a site: the board's name alone and no marks, which is the honest console. |
54
- | The colour | From the website's own colours when one is named, as the reference says, its primary colour read from the theme colour, the stylesheet's properties or the primary button. Else asked when the request names none and marks are wanted: "Which colour, or which mood?" A colour comes as words or a value; the value is the mark's own fill when it has one, else chosen and shown. A light colour becomes the accent (`--sc-color-accent` and its family); a dark one tints the field and its light (`--sc-color-field`, `--sc-color-light-*`), with a lighter shade of it as the accent, since an accent must read on the dark field. The mapping is shown at the plan, with the contrast checked. |
53
+ | The marks | From the organization's website when the request, or deploying-a-console, names one: its symbol and wordmark read from the site as [the-look-from-a-website.md](references/the-look-from-a-website.md) says, the dark version or, with the person's word, the one-colour mark recoloured, shown at the plan. Else asked for a console that shows marks of its own, for the half the request leaves open: "Which picture should the pages show beside the board's name: a small square mark, the name as lettering, or both? Where is the file? The pages are light, so it should be a dark one." `brand` takes either or both. The symbol's slot is a square of one line's height (`.sc-brand-symbol`, 1rem): a wide mark, a lettermark of three or four times its height, is a wordmark and goes in as one, alone or beside a square symbol. A mark drawn in a light colour vanishes on the light pages: ask for a dark version, or, with the person's word, recolour it when it is one colour. Default without a site: the board's name alone and no marks, which is the honest console. |
54
+ | The colour | From the website's own colours when one is named, as the reference says, its primary colour read from the theme colour, the stylesheet's properties or the primary button. Else asked when the request names none and marks are wanted: "Which colour, or which mood?" A colour comes as words or a value; the value is the mark's own fill when it has one, else chosen and shown. A dark or saturated colour becomes the accent (`--sc-color-accent` and its family); a pale one tints the field (`--sc-field-gradient`), with a darker shade of it as the accent, since an accent must read on the light field. The mapping is shown at the plan, with the contrast checked. |
55
55
  | The languages | Asked when the organization's language is not among the packs: "Which languages should the pages speak?" Default: the packs the package ships, chosen by the browser; a Korean organization gets Pretendard loaded, as the default console does. |
56
56
  | The name | Not asked: the server names the board (`WORKSPACE_NAME`, conventionally the organization or the person it is for, then Console: `Acme Console`), and `title` is only what the page says until the server answers. `index.html` gets the organization's `<title>` and `lang`. |
57
57
  | The console's address | For running one's own, what `console whoami` says. A script takes `CONSOLE_URL` and `CONSOLE_TOKEN` from the environment, never from a file in the repository. |
@@ -90,9 +90,9 @@ Produces the **checked build** and the delivery. `npx tsc --noEmit` and `npx vit
90
90
 
91
91
  ## Hard rules
92
92
 
93
- - What people and agents wrote is untrusted: shown with the package's `Markdown` (elements, never HTML), never with `dangerouslySetInnerHTML`, never as a script or a style; pictures over https only.
93
+ - What people and agents wrote is untrusted: shown with the package's `Markdown` (elements, never HTML), never with `dangerouslySetInnerHTML`, never as a script or a style; a picture from elsewhere is read through the console (`RemotePicturesContext`, which the composition provides from the client; [ADR-0048](/docs/adr/0048-a-picture-from-elsewhere-is-read-through-the-console-from-a-public-address-and-kept-a-while.md)).
94
94
  - A token never goes into a page, a bundle, a configuration file or a repository: `console serve` carries it, and a script reads it from the environment.
95
- - The policy stays: a build is served under a content security policy that runs nothing inline and loads nothing from elsewhere but pictures over https. Scripts, styles and fonts are bundled, never loaded from a CDN or written inline.
95
+ - The policy stays: a build is served under a content security policy that runs nothing inline and loads nothing from elsewhere, pictures included, which come through the console. Scripts, styles and fonts are bundled, never loaded from a CDN or written inline, and a picture of the console's own pages is in the build or reached through `GET /api/v1/pictures`.
96
96
  - The package is pinned by version: the API is `/api/v1`, the schemas in the package are the contract of the version it was built against, and a console is rebuilt to take a new one.
97
97
  - The marks of SkillCDN are trademarks, not part of the MIT license: a console of one's own shows its own marks through `brand`, or none.
98
98
  - Nothing of the server is rebuilt: a console shows the organization's board and holds nothing of its own. A second database is not a console.
@@ -10,7 +10,7 @@ The four files of the default UI, `apps/console/web/` in the console's repositor
10
10
  <head>
11
11
  <meta charset="utf-8" />
12
12
  <meta name="viewport" content="width=device-width, initial-scale=1" />
13
- <meta name="color-scheme" content="dark" />
13
+ <meta name="color-scheme" content="light" />
14
14
  <title>Console</title>
15
15
  </head>
16
16
  <body>
@@ -24,9 +24,9 @@ The four files of the default UI, `apps/console/web/` in the console's repositor
24
24
 
25
25
  ```tsx
26
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.
27
+ // (ADR-0013); the build gives back their addresses. The pages are light, so the wordmark is black.
28
28
  import symbol from "@skillcdn/brand/symbol.svg";
29
- import wordmark from "@skillcdn/brand/wordmark-white.svg";
29
+ import wordmark from "@skillcdn/brand/wordmark-black.svg";
30
30
  import { createConsole } from "@skillcdn/console";
31
31
  // The styles ship with the package; the build bundles them next to the script.
32
32
  import "@skillcdn/console/console.css";
@@ -137,4 +137,4 @@ export default defineConfig({
137
137
  }
138
138
  ```
139
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. One thing to add in the build: `build: { assetsInlineLimit: 0 }`, so that every picture and font stays a file; Vite inlines an asset under 4 KB as a `data:` address, which the policy allows for a picture and refuses for a font (`font-src 'self'`), and the default console gets by only because none of its assets is that small.
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 dark one, since the pages are light), 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. One thing to add in the build: `build: { assetsInlineLimit: 0 }`, so that every picture and font stays a file; Vite inlines an asset under 4 KB as a `data:` address, which the policy allows for a picture and refuses for a font (`font-src 'self'`), and the default console gets by only because none of its assets is that small.
@@ -12,17 +12,17 @@ Fetch the homepage as HTML, with the agent's own tools, and read, in this order:
12
12
  | The wordmark | The same header: the name as lettering, wide, often an SVG beside or instead of the symbol | `brand.wordmark`, alone or beside the symbol. A mark three or four times wider than high is a wordmark, not a symbol. |
13
13
  | The colours | `<meta name="theme-color">`; custom properties on `:root` in the site's stylesheet (`--color-primary`, `--brand`, `--accent` and their kin); the fill of the primary button and the colour of links | One primary colour, as a value; a second where the site plainly has two. |
14
14
  | The type | `font-family` of the body and of the headings, in the stylesheet | The names, to tell the person; a face is loaded only as the rules below allow. |
15
- | The mood | The site itself: dark or light, flat or glassy, round or square corners | Whether the field stays dark (the pages are dark, and stay so), and whether the radii follow the site (`--sc-radius-*`). |
15
+ | The mood | The site itself: dark or light, flat or glassy, round or square corners | Whether the field stays light (the pages are light by default, ADR-0040; a dark site is said at the plan, and darkening the pages is the person's word, not the default), and whether the radii follow the site (`--sc-radius-*`). |
16
16
 
17
17
  A site that cannot be fetched (a login wall, a block, no site after all) ends the reading: say so in one line and ask the person for the files, or keep the honest default.
18
18
 
19
19
  ## Mapping onto the pages
20
20
 
21
- The pages are dark: a field with a light in it, and panes of glass over it. The mapping is shown at the plan as a table, value by value, before a file is written.
21
+ The pages are light: a pale field, and panes of frosted glass over it. The mapping is shown at the plan as a table, value by value, before a file is written.
22
22
 
23
- - **A mark drawn in a dark colour vanishes** on the dark pages. Look for the site's light or white variant first (a footer, a dark-mode asset, a `prefers-color-scheme` rule). Found none, and the mark is one colour: recolour it to the pages' text colour, with the person's word, since the mark is theirs. A mark of several colours on a dark fill: keep its fill as a rounded square behind it.
24
- - **A light primary colour** becomes the accent: `--sc-color-accent` and its family (`-hover`, `-subtle`, the point), as the package's README lists them.
25
- - **A dark primary colour** tints the field and its light: `--sc-color-field` and `--sc-color-light-*`, with a lighter shade of it as the accent, since an accent must read on the dark field.
23
+ - **A mark drawn in a light colour vanishes** on the light pages. Take the site's dark or black variant first (its header on a light page, a light-mode asset, a `prefers-color-scheme` rule). Found none, and the mark is one colour: recolour it to the pages' text colour, with the person's word, since the mark is theirs. A mark of several colours on a light fill: keep its fill as a rounded square behind it.
24
+ - **A dark or saturated primary colour** becomes the accent: `--sc-color-accent` and its family (`-solid`, `-contrast`, `-subtle`, the point), as the package's README lists them.
25
+ - **A pale primary colour** tints the field: `--sc-field-gradient`, with a darker shade of it as the accent, since an accent must read on the light field.
26
26
  - **Contrast is checked**, not assumed: the accent on the field, and text on the panes, at the 4.5:1 of WCAG AA for text; a shade is moved until it passes, and the plan says which.
27
27
  - **The type follows the rules of the skill**: scripts, styles and fonts are bundled, never loaded from a CDN or written inline. A face the organization owns, or one under a license that allows bundling, is put in the repository and named in `--sc-font-sans`; otherwise the system faces stay, and the plan says that the site's face was not taken and why. Pretendard stays for Korean, as the default console has it.
28
28
  - **The name** stays the server's (`WORKSPACE_NAME`): the look is the marks, the colours and the type, never the words.
@@ -5,7 +5,7 @@ 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.21"
8
+ version: "1.25"
9
9
  skillcdn:
10
10
  translations:
11
11
  ko:
@@ -22,6 +22,8 @@ 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
+ - **A choice made elsewhere:** a decision is the record of a choice that is the person's to make, not the place it has to be made. An answer they give in chat, in a meeting or by changing the task is written on the decision in their name, `console answer <id> --option <n>|--own "<their words>" --note "<where and when>"`, before the work goes on: the run waits on the record, and the board shows as waiting only what is still open. What was decided is not asked again.
26
+ - **A briefing:** asked what is on their plate, what is next, or for a briefing, the agent starts from `console work`: the decisions waiting for the person's answer come first, each with what waits on it and since when, since a waiting decision holds a run; then the tasks by their days. `console decisions --mine` and `--raised` read the rest of their decisions, the answered ones as the record.
25
27
  - **The discussion:** what people say about the task is in its comments, which `console task <number>` and `console take` print with the task; a picture they put in is saved with `console file <id>` and looked at with the agent's own tools. An answer to what they asked there, a question that is not a decision, a remark on what was handed in, is a comment of the agent's own: `console comment <number> "<markdown>" [--attach <file>]`, which needs no run. A report is the run's record; a comment is a word to people.
26
28
  - **Work given in chat:** when a person gives work in chat, in a directory that works in a project, the agent writes it on the board first (`console task new`), tells the person the number the command printed, never one assumed, since other agents write on the same board and the next number is nobody's to guess, takes it by that number and works it as below, so that the board holds the work and its record, and the person decides on the board.
27
29
  - **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.
@@ -41,7 +43,7 @@ The full contract is the [specification of the command line](/docs/specs/cli.md)
41
43
  | Input | Source |
42
44
  |---|---|
43
45
  | 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; `--search <words>` finds a task by the words of its title, or by its number); a task is never taken without being told which, or that any ready one may be. |
44
- | The work | The task's body, read with `console task <number>`: what to make, where, the milestones, what to ask; the comments on it, which are the discussion so far, printed with the task, their pictures saved with `console file <id>`; with its plan, the category, the goal it works towards (`console goal <id>` says what reaching it means, its category and the days it starts and is meant to be reached by) 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 pack (`console skills` says where the pack is, what a client is told of it on connect, and every skill and document; `console skills <folder>` shows what a folder of it holds, `console skills --search <words>` what the words find in it; `console skill <path>` reads one whole through the console, its rules first, as an MCP client's model is given it, a private pack through the pack token the project holds, [ADR-0028](/docs/adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md), and a pack on the console's own machine from its directory, [ADR-0031](/docs/adr/0031-the-console-reads-a-pack-as-the-skillcdn-command-does-and-a-local-one-from-a-directory.md); `console skill --file <file>` a file of it; a host that lists skills through a connection of its own loads them by their URI, which means nothing elsewhere for a pack on the console's machine) say the rest. |
46
+ | The work | The task's body, read with `console task <number>`: what to make, where, the milestones, what to ask; the comments on it, which are the discussion so far, printed with the task, their pictures saved with `console file <id>`; with its plan, the category, the goal it works towards (`console goal <id>` says what reaching it means, its category and the days it starts and is meant to be reached by) 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 pack (`console skills` says where the pack is, what a client is told of it on connect, and every skill and document; `console skills <folder>` shows what a folder of it holds, `console skills --search <words>` what the words find in it; `console skill <path>` reads one whole through the console, its rules first, as an MCP client's model is given it, a private pack through the pack token the project holds, [ADR-0028](/docs/adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md), and a pack on the console's own machine from its directory, [ADR-0031](/docs/adr/0031-the-console-reads-a-pack-as-the-skillcdn-command-does-and-a-local-one-from-a-directory.md); `console skill --file <file>` a file of it, `console skills <file>` the outline of a document and `console skill --file <file> --offset <n> --limit <n>` one section of it, as a search or the outline names them; a host that lists skills through a connection of its own loads them by their URI, which means nothing elsewhere for a pack on the console's machine) say the rest. |
45
47
  | 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. |
46
48
 
47
49
  ## Workflow
@@ -54,7 +56,7 @@ Told which task, go to phase 2: `console take` prints the rules of the board wit
54
56
 
55
57
  ### Phase 2: Take
56
58
 
57
- `console take <number>`. A run begins, and the command says its id and the task's on a line of their own (`run id:`, `task id:`; a task has a number for people and an id for the API, a run, a decision and a goal have ids alone, and every command that makes one prints it so, `--json` the whole answer; an id is read from there, never assumed): the task is in progress and the person's, and every report, hand-in, question and ending from here acts on this run. With another run of the person's open in the project, which `console take` says, each of those commands names the run: `--run <id>`. Taken by mistake, `console abandon` leaves it and the task stays as it was.
59
+ `console take <number>`. A run begins, and the command says its id and the task's on a line of their own (`run id:`, `task id:`; a task has a number for people and an id for the API, a run, a decision and a goal have ids alone, and every command that makes one prints it so, `--json` the whole answer; an id is read from there, never assumed): the task is in progress and the person's, and every report, hand-in, question and ending from here acts on this run. With another run of the person's open in the project, which `console take` says, each of those commands names the run: `--run <id>`. Taken by mistake, `console abandon` leaves it and the task stays as it was. A run the board has not heard from for twelve hours (what the deployment sets), and that is not waiting for an answer, is ended by the board itself as abandoned (ADR-0041): report at the milestones, wait for a decision with `--wait` or come back for it, and, refused with `run.over` on a run ended that way, take the task again.
58
60
 
59
61
  ### Phase 3: Work, and report at the milestones
60
62
 
@@ -62,7 +64,7 @@ The work happens where it happens: in the repository, the document, the tool. At
62
64
 
63
65
  ### Phase 4: Ask when a person must decide
64
66
 
65
- A choice the body does not settle, a trade-off, a cost, a change of scope: `console ask "<question>" --option "<one>" --option "<another>" --body "<what they need to know>"`. The run waits for the answer; the command waits a minute and exits with `3` while it is still to come. Keep waiting with `console decision <id> --wait 100`, as often as needed, and meanwhile do only what does not depend on the answer. Act on the answer as given, which may be the person's own words rather than one of the options, marked as theirs; what was answered is not asked again. Once acted on, `console decision <id> --outcome "<what followed>"` makes the decision read as a record.
67
+ A choice the body does not settle, a trade-off, a cost, a change of scope: `console ask "<question>" --option "<one>" --option "<another>" --body "<what they need to know>"`. The decision is for the person you work for, who finds it among their decisions; `--to <login>` addresses it to another person of the project when the task or the rules name who decides such things (ADR-0042). The run waits for the answer; the command waits a minute and exits with `3` while it is still to come. Keep waiting with `console decision <id> --wait 100`, as often as needed, and meanwhile do only what does not depend on the answer. Act on the answer as given, which may be the person's own words rather than one of the options, marked as theirs; what was answered is not asked again. An answer that reaches you anywhere but the board, in chat, in a comment, in the task changed, is written on the decision in the person's name first, `console answer <id> --option <n>|--own "<their words>" --note "<where and when they said it>"`, and acted on from there; a question the work overtook is answered the same way, with the person's word on it, however short. Once acted on, `console decision <id> --outcome "<what followed>"` makes the decision read as a record.
66
68
 
67
69
  ### Phase 5: Hand in
68
70
 
@@ -1,3 +1,3 @@
1
1
  # Setting up, when told to
2
2
 
3
- 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](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; `console workspace` says the workspace's settings, and `console workspace edit` changes them for an administrator: `--people-visibility <everyone|shared-projects>`, who sees whom on the People page; `--week-start <auto|monday|sunday>`, the day the calendars begin a week on; `--time-zone <zone|none>`, the workspace's time zone, an IANA name such as `Asia/Seoul`, in which the command prints moments ([ADR-0035](/docs/adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md)). In a project open to the workspace everyone is a member already: `console member add <login> --role owner` lists an owner, and `member role <login> member` takes one back; a plain `member add` is refused there (`member.open_project`). A login is found exactly, so an owner lists someone `console people` does not show them. A private pack is connected by an owner with `console project pack-token <key> --connect`, or, for the organization's pack, which projects read when they name no address of their own, by an administrator with `console workspace pack-token --connect` ([ADR-0036](/docs/adr/0036-a-private-pack-is-connected-with-a-code-a-person-approves-from-the-projects-settings.md)): the command prints a page at SkillCDN and a code, which the agent relays; the person approves there, signed in as someone who may open the repository, and the console keeps the SkillCDN token, shown to nobody. Until then `console skills` says the pack is not served to the console.
3
+ 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](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)), and a project is made by an administrator alone ([ADR-0045](/docs/adr/0045-a-project-is-made-by-an-administrator.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; `console workspace` says the workspace's settings, and `console workspace edit` changes them for an administrator: `--people-visibility <everyone|shared-projects>`, who sees whom on the People page; `--week-start <auto|monday|sunday>`, the day the calendars begin a week on; `--time-zone <zone|none>`, the workspace's time zone, an IANA name such as `Asia/Seoul`, in which the command prints moments ([ADR-0035](/docs/adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md)). In a project open to the workspace everyone is a member already: `console member add <login> --role owner` lists an owner, and `member role <login> member` takes one back; a plain `member add` is refused there (`member.open_project`). A login is found exactly, so an owner lists someone `console people` does not show them. A private pack is connected by an owner with `console project pack-token <key> --connect`, or, for the organization's pack, which projects read when they name no address of their own, by an administrator with `console workspace pack-token --connect` ([ADR-0036](/docs/adr/0036-a-private-pack-is-connected-with-a-code-a-person-approves-from-the-projects-settings.md)): the command prints a page at SkillCDN and a code, which the agent relays; the person approves there, signed in as someone who may open the repository, and the console keeps the SkillCDN token, shown to nobody. Until then `console skills` says the pack is not served to the console.
@@ -4,7 +4,7 @@
4
4
  - **Goal**: what the project works towards, on its roadmap, with the day it is meant to be reached by; its progress is its tasks.
5
5
  - **Category**: a kind of work, the workspace's word for it, by key.
6
6
  - **Run**: one agent at work on one task for one person, with its reports, what it handed in, and what it waits for.
7
- - **Decision**: a question with options, which a person answers on the board.
7
+ - **Decision**: a choice that is a person's to make, kept as a record on the board: the question, the options, whom it is for ([ADR-0042](/docs/adr/0042-a-decision-is-addressed-to-a-person-its-raiser-unless-another-is-named.md)), the answer with who gave it and when, and what followed. It is answered on the board whoever decided it where: a person who answers in chat, in a meeting or by changing the task has decided, and the agent writes that answer down for them (`console answer`), so that the board shows as waiting only what is still open.
8
8
  - **Page**: a document of the project, Markdown at a path that does not change.
9
9
  - **Project**: a board of its own, with its members and its skills address.
10
10
  - **SkillCDN token**: what the console reads a private pack with, `pack-token` in the API and the command: a project's own, or the workspace's for the organization's pack, which a project reads when it names no address of its own; connected with a code a person approves at SkillCDN, or made there by hand, and shown to nobody.
package/src/api.ts CHANGED
@@ -72,13 +72,16 @@ export {
72
72
  MAX_CATEGORY_KEY_LENGTH,
73
73
  MAX_CATEGORY_NAME_LENGTH,
74
74
  MAX_COMMENTS_PER_TASK,
75
+ MAX_DECISIONS_PER_PROJECT,
75
76
  MAX_DOCUMENT_LENGTH,
76
77
  MAX_DOCUMENT_PATH_LENGTH,
77
78
  MAX_DOCUMENT_SEGMENT_LENGTH,
79
+ MAX_DOCUMENTS_PER_PROJECT,
78
80
  MAX_FILE_BYTES,
79
81
  MAX_FILE_NAME_LENGTH,
80
82
  MAX_FILES_PER_COMMENT,
81
83
  MAX_FILES_PER_DOCUMENT,
84
+ MAX_GOALS_PER_PROJECT,
82
85
  MAX_LANGUAGE_TAG_LENGTH,
83
86
  MAX_LINK_LABEL_LENGTH,
84
87
  MAX_LINKS,
@@ -91,13 +94,18 @@ export {
91
94
  MAX_PROJECT_KEY_LENGTH,
92
95
  MAX_PROJECT_NAME_LENGTH,
93
96
  MAX_PROJECT_SYMBOL_LENGTH,
97
+ MAX_PROJECTS_PER_WORKSPACE,
94
98
  MAX_QUESTION_LENGTH,
95
99
  MAX_REPORTS_PER_RUN,
96
100
  MAX_RULES_LENGTH,
101
+ MAX_RUNS_PER_TASK,
97
102
  MAX_SEARCH_LENGTH,
98
103
  MAX_SECRET_LENGTH,
99
104
  MAX_SKILLS_ADDRESS_LENGTH,
105
+ MAX_STREAMS,
106
+ MAX_STREAMS_PER_PERSON,
100
107
  MAX_SUMMARY_LENGTH,
108
+ MAX_TASKS_PER_PROJECT,
101
109
  MAX_TITLE_LENGTH,
102
110
  MAX_TOKEN_DAYS,
103
111
  MAX_TOKEN_NAME_LENGTH,
@@ -191,6 +199,7 @@ export {
191
199
  type RestMemberPatch,
192
200
  type RestMembers,
193
201
  type RestMePatch,
202
+ type RestMyDecisions,
194
203
  type RestOrganization,
195
204
  type RestPackConnect,
196
205
  type RestPackConnectStarted,
@@ -286,6 +295,7 @@ export {
286
295
  restMembersSchema,
287
296
  restMePatchSchema,
288
297
  restMeSchema,
298
+ restMyDecisionsSchema,
289
299
  restOrganizationSchema,
290
300
  restPackConnectSchema,
291
301
  restPackConnectStartedSchema,