@skillcdn/console 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/api.d.ts +3 -3
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +3 -3
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +120 -33
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +4 -2
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +38 -10
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +22 -15
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/plugin-check.d.ts +10 -1
- package/dist/cli/plugin-check.d.ts.map +1 -1
- package/dist/cli/plugin-check.js +45 -4
- package/dist/cli/plugin-check.js.map +1 -1
- package/dist/cli/plugin-cli.d.ts +1 -1
- package/dist/cli/plugin-cli.d.ts.map +1 -1
- package/dist/cli/plugin-cli.js +80 -5
- package/dist/cli/plugin-cli.js.map +1 -1
- package/dist/cli/plugin-init.d.ts +19 -0
- package/dist/cli/plugin-init.d.ts.map +1 -1
- package/dist/cli/plugin-init.js +141 -3
- package/dist/cli/plugin-init.js.map +1 -1
- package/dist/client.d.ts +7 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +18 -5
- package/dist/client.js.map +1 -1
- package/dist/compat.js +1 -1
- package/dist/components/board.d.ts +8 -0
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +3 -3
- package/dist/components/board.js.map +1 -1
- package/dist/components/comments.d.ts.map +1 -1
- package/dist/components/comments.js +1 -1
- package/dist/components/comments.js.map +1 -1
- package/dist/components/decision-form.d.ts +8 -1
- package/dist/components/decision-form.d.ts.map +1 -1
- package/dist/components/decision-form.js +57 -7
- package/dist/components/decision-form.js.map +1 -1
- package/dist/components/decision-list.d.ts +9 -1
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +53 -7
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +7 -1
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/goals.d.ts.map +1 -1
- package/dist/components/goals.js +2 -2
- package/dist/components/goals.js.map +1 -1
- package/dist/components/home.d.ts +35 -0
- package/dist/components/home.d.ts.map +1 -0
- package/dist/components/home.js +326 -0
- package/dist/components/home.js.map +1 -0
- package/dist/components/my-decisions.d.ts +2 -0
- package/dist/components/my-decisions.d.ts.map +1 -1
- package/dist/components/my-decisions.js +2 -2
- package/dist/components/my-decisions.js.map +1 -1
- package/dist/components/pack-connect.d.ts.map +1 -1
- package/dist/components/pack-connect.js +5 -4
- package/dist/components/pack-connect.js.map +1 -1
- package/dist/components/people.d.ts +2 -0
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +15 -2
- package/dist/components/people.js.map +1 -1
- package/dist/components/plugin-frame.d.ts +3 -12
- package/dist/components/plugin-frame.d.ts.map +1 -1
- package/dist/components/plugin-frame.js +53 -20
- package/dist/components/plugin-frame.js.map +1 -1
- package/dist/components/plugin-settings.d.ts.map +1 -1
- package/dist/components/plugin-settings.js +151 -26
- package/dist/components/plugin-settings.js.map +1 -1
- package/dist/components/projects.d.ts +0 -8
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +1 -13
- package/dist/components/projects.js.map +1 -1
- package/dist/components/task-view.d.ts +2 -1
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.js +2 -1
- package/dist/components/task-view.js.map +1 -1
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +5 -2
- package/dist/components/ui.js.map +1 -1
- package/dist/connect-page.d.ts.map +1 -1
- package/dist/connect-page.js +5 -6
- package/dist/connect-page.js.map +1 -1
- package/dist/console.css +764 -192
- package/dist/console.d.ts +10 -3
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +49 -28
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +6 -3
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +31 -7
- package/dist/data.js.map +1 -1
- package/dist/docs-page.d.ts.map +1 -1
- package/dist/docs-page.js +6 -5
- package/dist/docs-page.js.map +1 -1
- package/dist/home-tiles.d.ts +92 -0
- package/dist/home-tiles.d.ts.map +1 -0
- package/dist/home-tiles.js +294 -0
- package/dist/home-tiles.js.map +1 -0
- package/dist/i18n/en.d.ts +180 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +181 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/index.d.ts +4 -2
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +10 -11
- package/dist/i18n/index.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +176 -3
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +8 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +8 -0
- package/dist/limits.js.map +1 -1
- package/dist/plugin/bridge.d.ts +44 -3
- package/dist/plugin/bridge.d.ts.map +1 -1
- package/dist/plugin/bridge.js +26 -0
- package/dist/plugin/bridge.js.map +1 -1
- package/dist/plugin/capabilities.d.ts +7 -1
- package/dist/plugin/capabilities.d.ts.map +1 -1
- package/dist/plugin/capabilities.js +14 -1
- package/dist/plugin/capabilities.js.map +1 -1
- package/dist/plugin/context.d.ts +24 -1
- package/dist/plugin/context.d.ts.map +1 -1
- package/dist/plugin/define.d.ts +1 -0
- package/dist/plugin/define.d.ts.map +1 -1
- package/dist/plugin/define.js +1 -1
- package/dist/plugin/define.js.map +1 -1
- package/dist/plugin/limits.d.ts +5 -2
- package/dist/plugin/limits.d.ts.map +1 -1
- package/dist/plugin/limits.js +5 -2
- package/dist/plugin/limits.js.map +1 -1
- package/dist/plugin/manifest.d.ts +83 -6
- package/dist/plugin/manifest.d.ts.map +1 -1
- package/dist/plugin/manifest.js +111 -7
- package/dist/plugin/manifest.js.map +1 -1
- package/dist/plugin/rest-schemas.d.ts +76 -0
- package/dist/plugin/rest-schemas.d.ts.map +1 -1
- package/dist/plugin/rest-schemas.js +23 -2
- package/dist/plugin/rest-schemas.js.map +1 -1
- package/dist/plugin/sandbox.d.ts +4 -3
- package/dist/plugin/sandbox.d.ts.map +1 -1
- package/dist/plugin/sandbox.js +33 -6
- package/dist/plugin/sandbox.js.map +1 -1
- package/dist/plugin/semver.d.ts +26 -0
- package/dist/plugin/semver.d.ts.map +1 -0
- package/dist/plugin/semver.js +76 -0
- package/dist/plugin/semver.js.map +1 -0
- package/dist/plugin/testing.d.ts.map +1 -1
- package/dist/plugin/testing.js +2 -1
- package/dist/plugin/testing.js.map +1 -1
- package/dist/plugin/web.d.ts +10 -1
- package/dist/plugin/web.d.ts.map +1 -1
- package/dist/plugin/web.js +34 -0
- package/dist/plugin/web.js.map +1 -1
- package/dist/plugin.d.ts +3 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +3 -2
- package/dist/plugin.js.map +1 -1
- package/dist/rules.d.ts +2 -2
- package/dist/rules.js +3 -3
- package/dist/schemas.d.ts +574 -11
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +154 -25
- package/dist/schemas.js.map +1 -1
- package/dist/use-action.d.ts +8 -2
- package/dist/use-action.d.ts.map +1 -1
- package/dist/use-action.js +13 -6
- package/dist/use-action.js.map +1 -1
- package/dist/vocabulary.d.ts +3 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +3 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +4 -3
- package/docs/specs/plugins.md +49 -35
- package/docs/specs/rest.md +20 -18
- package/package.json +7 -7
- package/skills/building-a-plugin/SKILL.md +10 -9
- package/skills/planning-the-board/references/setting-up.md +2 -1
- package/skills/working-the-board/SKILL.md +3 -3
- package/src/api.ts +10 -0
- package/src/cli/cli.ts +162 -39
- package/src/cli/format.ts +54 -10
- package/src/cli/help.ts +23 -14
- package/src/cli/plugin-check.ts +68 -2
- package/src/cli/plugin-cli.ts +104 -6
- package/src/cli/plugin-init.ts +178 -3
- package/src/client.ts +36 -9
- package/src/compat.ts +1 -1
- package/src/components/board.tsx +3 -3
- package/src/components/comments.tsx +1 -0
- package/src/components/decision-form.tsx +166 -29
- package/src/components/decision-list.tsx +116 -15
- package/src/components/event-feed.tsx +7 -1
- package/src/components/goals.tsx +4 -1
- package/src/components/home.tsx +615 -0
- package/src/components/my-decisions.tsx +4 -1
- package/src/components/pack-connect.tsx +5 -4
- package/src/components/people.tsx +136 -54
- package/src/components/plugin-frame.tsx +62 -63
- package/src/components/plugin-settings.tsx +428 -87
- package/src/components/projects.tsx +0 -55
- package/src/components/task-view.tsx +9 -1
- package/src/components/ui.tsx +6 -2
- package/src/connect-page.tsx +5 -6
- package/src/console.tsx +98 -42
- package/src/data.ts +41 -11
- package/src/docs-page.tsx +6 -5
- package/src/home-tiles.ts +438 -0
- package/src/i18n/en.ts +185 -0
- package/src/i18n/index.ts +17 -14
- package/src/i18n/ko.ts +181 -3
- package/src/index.ts +7 -5
- package/src/limits.ts +8 -0
- package/src/plugin/bridge.ts +77 -1
- package/src/plugin/capabilities.ts +17 -1
- package/src/plugin/context.ts +24 -2
- package/src/plugin/define.ts +4 -1
- package/src/plugin/limits.ts +5 -2
- package/src/plugin/manifest.ts +154 -10
- package/src/plugin/rest-schemas.ts +27 -1
- package/src/plugin/sandbox.ts +36 -8
- package/src/plugin/semver.ts +89 -0
- package/src/plugin/testing.ts +2 -1
- package/src/plugin/web.ts +49 -0
- package/src/plugin.ts +18 -0
- package/src/rules.ts +3 -3
- package/src/schemas.ts +190 -28
- package/src/styles/console.css +764 -192
- package/src/use-action.ts +16 -7
- package/src/vocabulary.ts +4 -0
- package/template/plugin/console-plugin.json +3 -1
- package/template/plugin/package.json +3 -3
package/docs/specs/rest.md
CHANGED
|
@@ -15,7 +15,7 @@ The API is the board as its members see it: the people, the projects, and in eac
|
|
|
15
15
|
- An agent connects without anyone copying a token ([ADR-0011](../adr/0011-an-agent-connects-with-a-short-code-a-person-approves-and-the-token-is-never-shown.md)): [`POST /api/v1/connect`](#connecting-an-agent) is nobody's, the approval is a person's own doing on the console's own pages, and the claim is the command's, with the secret it was given.
|
|
16
16
|
- The command names its version on every request, as the product token of its `user-agent` (`skillcdn-console/0.1.18 (cli; node 24.11.0; linux)`), and every answer under `/api` says the console's version (`console-version`) and the range of command versions it takes (`console-client-range`, as npm spells one: `>=0.1.18 <0.2.0`) ([ADR-0025](../adr/0025-the-command-names-its-version-on-every-request-and-the-console-holds-it-to-the-range-it-takes.md)). Before 1.0 the range is the console's line, every patch of its major.minor, from the floor the console names where a release raised one: the oldest command whose requests and answers still fit it, raised only by a release that changed what passes between them ([ADR-0038](../adr/0038-the-command-and-the-console-go-together-within-a-line-and-a-release-that-changes-what-passes-between-them-raises-the-floors.md)); from 1.0, the console's major and a minor within one, either way. A command outside the range is refused with `426` before anything else is asked of its request, `client.outdated` or `client.too_new`, the message naming both versions and the range. The command holds the console to a floor of its own the same way, and refuses a console below it itself, as `console.outdated`, in this same form. A request that names no command, a browser's, a script's, is held to nothing.
|
|
17
17
|
- Request bodies are JSON, at most 256 KiB, and parsed once with the package's input schema. Text is trimmed where it is a line, bounded everywhere (`MAX_TITLE_LENGTH` and the rest of `@skillcdn/console/api`), and refused when it carries control or invisible characters. Links are `https` URLs only.
|
|
18
|
-
- Errors have `{ "error": { "code": "...", "message": "..." } }`. The code is stable; the message is for a
|
|
18
|
+
- Errors have `{ "error": { "code": "...", "message": "..." } }`. The code is stable; the message is in English, for whoever reads the API by hand or through the command. The pages never show it: they read the code in the language pack, `errors`, and a test holds every code the server can answer to having words in every pack, so that a person meets a failure in their own language ([ADR-0012](../adr/0012-the-pages-speak-the-persons-language-from-packs-that-ship-with-the-package.md)).
|
|
19
19
|
- The plugins' routes, under `/api/v1/plugins`, are in [plugins.md](plugins.md) with their refusals; every event carries `plugin`, the key of the plugin that caused it, or `null` for the console's own doing.
|
|
20
20
|
|
|
21
21
|
| Status | `code` | When |
|
|
@@ -39,7 +39,9 @@ The API is the board as its members see it: the people, the projects, and in eac
|
|
|
39
39
|
| 400 | `decision.invalid_task` | The decision is about a task that is not one of the project's. |
|
|
40
40
|
| 400 | `decision.no_such_option` | The answer names an option the decision does not have. |
|
|
41
41
|
| 400 | `decision.invalid_run` | A decision raised from a run that is not the asker's, or is over. |
|
|
42
|
-
| 400 | `decision.
|
|
42
|
+
| 400 | `decision.too_many_files` | The decision carries more files than one may (`MAX_FILES_PER_DECISION`, [ADR-0055](../adr/0055-a-decision-carries-the-files-put-on-it-when-it-is-raised.md)). |
|
|
43
|
+
| 400 | `decision.too_many_deciders` | More people were named to decide than one decision takes, `MAX_DECIDERS` ([ADR-0057](../adr/0057-a-decision-may-be-addressed-to-several-people-and-the-first-answer-ends-it.md)) |
|
|
44
|
+
| 400 | `decision.invalid_decider` | A 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)). |
|
|
43
45
|
| 400 | `token.expiry_at_most` | A token that would not expire, or would outlast `TOKEN_DAYS_AT_MOST`, where the organization requires an expiry. |
|
|
44
46
|
| 400 | `document.invalid_path` | The path a page is written at is not one: segments of lowercase letters, digits and hyphens, separated by slashes. |
|
|
45
47
|
| 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`). |
|
|
@@ -64,7 +66,7 @@ The API is the board as its members see it: the people, the projects, and in eac
|
|
|
64
66
|
| 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)). |
|
|
65
67
|
| 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`). |
|
|
66
68
|
| 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. |
|
|
67
|
-
| 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. |
|
|
69
|
+
| 409 | `project.too_many_bytes` | The files of the project, handed in by runs, put in comments and on decisions 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. |
|
|
68
70
|
| 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. |
|
|
69
71
|
| 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)). |
|
|
70
72
|
| 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. |
|
|
@@ -93,15 +95,15 @@ The API is the board as its members see it: the people, the projects, and in eac
|
|
|
93
95
|
|
|
94
96
|
### `GET /api/v1/me`
|
|
95
97
|
|
|
96
|
-
`{ "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
|
|
98
|
+
`{ "workspace": { "name", "tokenDaysAtMost", "skills": { "source", "address", "packToken" }, "peopleVisibility" }, "person": ... | null, "agent": "..." | null, "language": "..." | null, "signIn": [{ "key", "label", "admits": { "organization", "domains" } }, ...], "console": { "version", "clientRange" }, "decisions": { "waiting" } | null }`: 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 reads in (`language`, a tag: the one they chose, else the one the pages first saw them in, [ADR-0065](../adr/0065-a-plugin-writes-one-text-and-a-persons-language-is-kept-from-the-first-time-the-pages-see-them.md); `null` until then), their own arrangement of the front page's widgets (`home`, [ADR-0059](../adr/0059-a-person-arranges-the-front-pages-widgets-from-the-sizes-each-comes-in.md): `[{ plugin, widget, size, place? }]` in order, or `null` for the manifests'; answered from 0.4.4 and read as optional), 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; and how many decisions wait for the person's answer across the projects they may see (`decisions.waiting`, [ADR-0042](../adr/0042-a-decision-is-addressed-to-a-person-its-raiser-unless-another-is-named.md); `null` for nobody; answered from 0.4.3 and read as optional), which the pages say beside My decisions and read again when a decision is raised or answered. 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)).
|
|
97
99
|
|
|
98
100
|
### `GET /api/v1/people?login=`
|
|
99
101
|
|
|
100
|
-
`{ "items": [person, ...], "listed": [listing, ...] }`: the people the asker may see, those in first and by login, and the logins an administrator let in that nobody has signed in with yet ([ADR-0022](../adr/0022-who-is-in-is-the-consoles-own-record-and-an-administrator-keeps-it.md)). Who sees whom is the workspace's setting, decided by the one policy of the server on every request ([ADR-0035](../adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md)): with `everyone`, everyone who has signed in; with `shared_projects`, a member sees the active people who are in a project they are in, themselves, and the administrators, while an administrator sees everyone whatever the setting; a project open to the workspace has everyone in it, so a member of one sees everyone. The logins let in (`listed`) and the organization's reading (`organization`) go only to whom the policy shows everyone, `[]` and `null` otherwise. With `login`, one person by their exact login, compared without regard to case, or none: whom the asker sees, and, for an owner of a project, anyone active of the workspace, so that an owner lists someone the list does not show, learning that the login is one of the workspace and nothing more; `listed` is `[]` and `organization` `null` then; an empty or overlong login is `request.invalid`. A person carries their `role` in the workspace (`admin`, who configures it, or `member`, who works in the projects they are in), their `status` (`active`, or `deactivated`: kept with everything they did, refused on every request, and offered to no picker), their `source` (what let them in at their last sign-in: `organization`, the git host's organization the deployment names; `domain`, the Workspace domain it names; or `listed`, an administrator's listing) and `
|
|
102
|
+
`{ "items": [person, ...], "listed": [listing, ...] }`: the people the asker may see, those in first and by login, and the logins an administrator let in that nobody has signed in with yet ([ADR-0022](../adr/0022-who-is-in-is-the-consoles-own-record-and-an-administrator-keeps-it.md)). Who sees whom is the workspace's setting, decided by the one policy of the server on every request ([ADR-0035](../adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md)): with `everyone`, everyone who has signed in; with `shared_projects`, a member sees the active people who are in a project they are in, themselves, and the administrators, while an administrator sees everyone whatever the setting; a project open to the workspace has everyone in it, so a member of one sees everyone. The logins let in (`listed`) and the organization's reading (`organization`) go only to whom the policy shows everyone, `[]` and `null` otherwise. With `login`, one person by their exact login, compared without regard to case, or none: whom the asker sees, and, for an owner of a project, anyone active of the workspace, so that an owner lists someone the list does not show, learning that the login is one of the workspace and nothing more; `listed` is `[]` and `organization` `null` then; an empty or overlong login is `request.invalid`. A person carries their `role` in the workspace (`admin`, who configures it, or `member`, who works in the projects they are in), their `status` (`active`, or `deactivated`: kept with everything they did, refused on every request, and offered to no picker), their `source` (what let them in at their last sign-in: `organization`, the git host's organization the deployment names; `domain`, the Workspace domain it names; or `listed`, an administrator's listing), `deactivatedAt` (or `null`) and `managerId`, the id of the person they report to, the reporting line of the directory an administrator keeps, or `null` for nobody (answered from 0.4.4 and read as optional): what a plugin that reads people takes as the default where it needs someone's manager, the approver of their leave, say, in place of a line of its own. A person also carries `language`, the language they read in: the one they chose, else the one the pages first saw them in, or `null` until then (answered from 0.4.4 and read as optional): what a plugin or an agent writes for them in ([ADR-0065](../adr/0065-a-plugin-writes-one-text-and-a-persons-language-is-kept-from-the-first-time-the-pages-see-them.md)). A listing carries its `id`, the `host` (`gh`, `google`), the `login` as the administrator spelled it, and `createdAt`. `organization` is the organization at the git host whose members are in, where the deployment names one (`host`, `name`, and `syncedAt`, when the worker last read it whole, or `null` until it has), or `null`. What a picker of workers and of members is made of, the active people. A token reads it as its person does: what the command's `--worker`, `member` and `person` take a login from, through `login`.
|
|
101
103
|
|
|
102
104
|
### `PATCH /api/v1/people/<id>`
|
|
103
105
|
|
|
104
|
-
Takes `role`, `status`, or
|
|
106
|
+
Takes `role`, `status`, `managerId`, one or more, and answers the person. For an administrator, signed in or through their agent's token ([ADR-0021](../adr/0021-a-token-configures-as-its-person-does.md)). The board keeps at least one administrator in (`person.last_admin`). A change of role writes `person.role_changed` with `login` and `role`. `managerId` names whom the person reports to: another active person of the workspace who does not report to them, directly or through others, so that the line never loops, or `null` for nobody (`person.invalid_manager` otherwise); it writes `person.manager_changed` with `login` and `manager`, the manager's login or `null`, which reaches the hooks of a plugin that reads the directory as every people event does ([specs/plugins.md](plugins.md#capabilities)). `status: "deactivated"` takes the person out: refused on every request from the next one, their agents' tokens with them, listed nowhere and offered to no picker, kept with everything they did; the listing they came in on goes with it, so that signing in again admits nobody. `status: "active"` takes them back in, and lists them again where a listing let them in. Each writes `person.deactivated` or `person.reactivated` with `login`; what is already so writes nothing. A person the organization let in is neither (`person.of_organization`): the organization says, on the schedule the worker reads it on.
|
|
105
107
|
|
|
106
108
|
### `POST /api/v1/listings` and `DELETE /api/v1/listings/<id>`
|
|
107
109
|
|
|
@@ -117,11 +119,11 @@ Takes `key` and `name`, and optionally `description`, `visibility` (default `pri
|
|
|
117
119
|
|
|
118
120
|
### `GET /api/v1/events?after=&limit=`
|
|
119
121
|
|
|
120
|
-
The workspace's own events, the ones about no project: who joined (`person.joined`), who was made what (`person.role_changed`), who was let in, taken off the list, taken out and taken back in (`person.listed`, `person.unlisted`, `person.deactivated`, `person.reactivated`, each with the `login`), what became of the categories (`category.created`, `category.updated`, `category.removed`, each with the `key` and the `name`), and which of the workspace's settings an administrator changed (`workspace.updated`, with `fields`). The same shape and parameters as a project's feed below, without the filters. They say who is in, so they go to whom the policy shows everyone ([ADR-0035](../adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md)): `auth.forbidden` for a member while the people are narrowed to those they share a project with.
|
|
122
|
+
The workspace's own events, the ones about no project: who joined (`person.joined`), who was made what (`person.role_changed`), who was let in, taken off the list, taken out and taken back in (`person.listed`, `person.unlisted`, `person.deactivated`, `person.reactivated`, each with the `login`), whom someone reports to now (`person.manager_changed`, with the `login` and the `manager`'s login, or `null`), what became of the categories (`category.created`, `category.updated`, `category.removed`, each with the `key` and the `name`), and which of the workspace's settings an administrator changed (`workspace.updated`, with `fields`). The same shape and parameters as a project's feed below, without the filters. They say who is in, so they go to whom the policy shows everyone ([ADR-0035](../adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md)): `auth.forbidden` for a member while the people are narrowed to those they share a project with.
|
|
121
123
|
|
|
122
124
|
### `PATCH /api/v1/me`
|
|
123
125
|
|
|
124
|
-
Keeps the
|
|
126
|
+
Keeps the language the person reads in ([ADR-0012](../adr/0012-the-pages-speak-the-persons-language-from-packs-that-ship-with-the-package.md), [ADR-0065](../adr/0065-a-plugin-writes-one-text-and-a-persons-language-is-kept-from-the-first-time-the-pages-see-them.md)): `{ "language": "ko" }`, a language tag as a browser sends one, sent by the menu of the pages and by the pages themselves the first time they see a person who has none, where the browser asks for a language they speak; `null` leaves none, which the pages fill again; and their front page ([ADR-0061](../adr/0061-the-front-page-is-one-grid-of-tiles-a-person-arranges-by-hand.md)): `{ "home": { "hidden": [<tile>, ...], "layouts": { "8": [{ "tile", "x", "y", "size" }, ...], ... } } }`, the tiles taken off and what they arranged at each width of 12, 8, 6 or 4 columns, up to `MAX_HOME_TILES` in each, a tile being `project:<id>` or `widget:<plugin>:<widget>`, `x` from 0 to 11, `size` one of `icon`, `small`, `medium`, `large`, `wide`, `full`, or `null` for nothing kept; either or both, `request.invalid` with neither. Answers what `GET` answers. From the console's own pages, or with a token: a console of a person's own keeps the choice on the person too.
|
|
125
127
|
|
|
126
128
|
### `PATCH /api/v1/workspace`
|
|
127
129
|
|
|
@@ -145,7 +147,7 @@ Kept by an administrator, signed in or through their agent's token. `POST` takes
|
|
|
145
147
|
|
|
146
148
|
### `GET /api/v1/rules`, `PUT /api/v1/rules`, `POST /api/v1/rules/archive` and `GET /api/v1/rules/versions`
|
|
147
149
|
|
|
148
|
-
The rules of the board ([ADR-0023](../adr/0023-the-rules-of-a-board-are-pages-the-workspace-and-its-projects-keep-read-first-by-every-agent.md)): what the organization asks of agents, read by whoever is signed in or presents a token, kept by an administrator, signed in or through their agent's token. `GET` answers `{ "source", "body", "page" }`: `source` is `reference` while the organization has written none, and `body` is then the console's own rules as this version of the console carries them, with `page: null`; once written, `source` is `written`, `body` the page's and `page` the page without its body (as a folder lists one: `path` `rules`, `title` `Rules`, `version`, `updatedBy`, `agent`, `createdAt`, `updatedAt`). `PUT` takes `body` (Markdown, `MAX_RULES_LENGTH`) and optionally `baseVersion`, writes the next version of the page, bringing it back when it was put away, and answers what `GET` answers; the console's own rules sent as they are are never written, so that what holds keeps following the console's version: with none written they change nothing, and with the organization's written they put its page away, as `POST .../archive` does; a write from a version the rules have moved on from is `document.conflict`. The write is a `document.written` event in the workspace's own feed, with `path`, `title` and `version`. `POST .../archive` puts the written rules away, so that the console's own hold again (`document.archived`); what is put away already changes nothing. `.../versions` answers `{ "items": [version, ...] }` as a page's versions are answered, newest first, and `{ "items": [] }` while none is written. A body may say `{language}` for the language of the person an agent works for: the API answers the text as written, and the pages and the command fill it (`fillRules`), with the name of the language the person
|
|
150
|
+
The rules of the board ([ADR-0023](../adr/0023-the-rules-of-a-board-are-pages-the-workspace-and-its-projects-keep-read-first-by-every-agent.md)): what the organization asks of agents, read by whoever is signed in or presents a token, kept by an administrator, signed in or through their agent's token. `GET` answers `{ "source", "body", "page" }`: `source` is `reference` while the organization has written none, and `body` is then the console's own rules as this version of the console carries them, with `page: null`; once written, `source` is `written`, `body` the page's and `page` the page without its body (as a folder lists one: `path` `rules`, `title` `Rules`, `version`, `updatedBy`, `agent`, `createdAt`, `updatedAt`). `PUT` takes `body` (Markdown, `MAX_RULES_LENGTH`) and optionally `baseVersion`, writes the next version of the page, bringing it back when it was put away, and answers what `GET` answers; the console's own rules sent as they are are never written, so that what holds keeps following the console's version: with none written they change nothing, and with the organization's written they put its page away, as `POST .../archive` does; a write from a version the rules have moved on from is `document.conflict`. The write is a `document.written` event in the workspace's own feed, with `path`, `title` and `version`. `POST .../archive` puts the written rules away, so that the console's own hold again (`document.archived`); what is put away already changes nothing. `.../versions` answers `{ "items": [version, ...] }` as a page's versions are answered, newest first, and `{ "items": [] }` while none is written. A body may say `{language}` for the language of the person an agent works for: the API answers the text as written, and the pages and the command fill it (`fillRules`), with the name of the language the person reads in (`GET /api/v1/me`, `language`) or with the task's while they have none. The answer also carries `plugins` ([plugins.md](plugins.md), "The command"): every enabled plugin the asker may use, by key, each with its `name`, `agentNote` and `description` by language and the asker's `roles` in it; empty where the console has none.
|
|
149
151
|
|
|
150
152
|
### `GET /api/v1/work`
|
|
151
153
|
|
|
@@ -153,7 +155,7 @@ The rules of the board ([ADR-0023](../adr/0023-the-rules-of-a-board-are-pages-th
|
|
|
153
155
|
|
|
154
156
|
### `GET /api/v1/decisions?open=&raised=&limit=&offset=`
|
|
155
157
|
|
|
156
|
-
`{ "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`.
|
|
158
|
+
`{ "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)), alone or among others ([ADR-0057](../adr/0057-a-decision-may-be-addressed-to-several-people-and-the-first-answer-ends-it.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`.
|
|
157
159
|
|
|
158
160
|
### `GET /api/v1/tokens` and `POST /api/v1/tokens`
|
|
159
161
|
|
|
@@ -211,7 +213,7 @@ For an owner, signed in or with a token. The patch takes `role` and writes `proj
|
|
|
211
213
|
|
|
212
214
|
### `GET /api/v1/projects/<key>/tasks?state=&open=&archived=&category=&goal=&worker=&unassigned=&dueBy=&q=&limit=&offset=`
|
|
213
215
|
|
|
214
|
-
`{ "items": [task, ...], "more": boolean }`: the project's board, newest first, a page at a time: `limit` (1 to `LIST_LIMIT`, the default) and `offset` (from 0) say which page, and `more` whether there is one after it; a `limit` or an `offset` that is not a number in range is `request.invalid`. `state` keeps one state of it; `open=true` the tasks not done or dropped, `open=false` the rest; `category=<key>` those sorted into a category; `goal=<id>` those that work towards a goal; `worker=<person id>` those one person is at work on, `unassigned=true` those nobody is at work on (with `worker`, nothing); `dueBy=<day>` (`2026-10-24`) those due on that day or before it; `q=<words>` (1 to `MAX_SEARCH_LENGTH` characters) those whose title holds every word, whatever the case and in any order, and the task of that number for a number (`12`, `#12`), which is how a picker or a command finds a task by its title ([ADR-0037](../adr/0037-a-task-has-one-owner-and-any-number-of-workers-and-a-parent-or-a-goal-is-found-by-search.md)). The tasks put away ([ADR-0029](../adr/0029-a-task-done-or-dropped-is-put-away-out-of-the-board-and-open-by-its-number.md)) are left out; `archived=true` answers them alone, newest first: the archive and the board are two listings, never one. A task carries its `number` (the one people say out loud, numbered per project), `title`, `body` (Markdown, to be shown as text or rendered to elements, never as HTML), `state`, `priority`, `owner` (one person, whoever wrote it), `workers` (the people at work on it, in the order they came to it, `[]` while nobody is; [ADR-0037](../adr/0037-a-task-has-one-owner-and-any-number-of-workers-and-a-parent-or-a-goal-is-found-by-search.md)), `parentId`, `category` (its `key`, `name` and `color` as the workspace keeps it, or `null`), `goalId` (or `null`), its plan ([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)): `startsOn` and `dueOn`, each a day (`2026-10-24`) or `null`; its moments, which the board keeps itself: `startedAt`, when it was first moved past `ready`, and `doneAt`, when it was moved to `done`, each an instant or `null`; `archivedAt`, when it was put away, an instant or `null` while it is current; `links` (`url` and `label`), `openDecisions` (how many decisions about it wait for a person), `openRuns` (how many runs on it are open: begun and not ended, which says nothing of whether an agent is still at it, [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)), `createdAt` and `updatedAt`.
|
|
216
|
+
`{ "items": [task, ...], "more": boolean }`: the project's board, newest first, a page at a time: `limit` (1 to `LIST_LIMIT`, the default) and `offset` (from 0) say which page, and `more` whether there is one after it; a `limit` or an `offset` that is not a number in range is `request.invalid`. `state` keeps one state of it; `open=true` the tasks not done or dropped, `open=false` the rest; `category=<key>` those sorted into a category; `goal=<id>` those that work towards a goal; `worker=<person id>` those one person is at work on, `unassigned=true` those nobody is at work on (with `worker`, nothing); `dueBy=<day>` (`2026-10-24`) those due on that day or before it; `q=<words>` (1 to `MAX_SEARCH_LENGTH` characters) those whose title holds every word, whatever the case and in any order, and the task of that number for a number (`12`, `#12`), which is how a picker or a command finds a task by its title ([ADR-0037](../adr/0037-a-task-has-one-owner-and-any-number-of-workers-and-a-parent-or-a-goal-is-found-by-search.md)). The tasks put away ([ADR-0029](../adr/0029-a-task-done-or-dropped-is-put-away-out-of-the-board-and-open-by-its-number.md)) are left out; `archived=true` answers them alone, newest first: the archive and the board are two listings, never one. A task carries its `number` (the one people say out loud, numbered per project), `title`, `body` (Markdown, to be shown as text or rendered to elements, never as HTML), `state`, `priority`, `owner` (one person, whoever wrote it), `agent` (the agent the task was written through, or `null` when a person wrote it themselves, the owner may have changed since; [ADR-0056](../adr/0056-a-record-that-names-the-person-who-did-a-thing-names-the-agent-they-did-it-through.md); from 0.4.4, read as optional), `workers` (the people at work on it, in the order they came to it, `[]` while nobody is; [ADR-0037](../adr/0037-a-task-has-one-owner-and-any-number-of-workers-and-a-parent-or-a-goal-is-found-by-search.md)), `parentId`, `category` (its `key`, `name` and `color` as the workspace keeps it, or `null`), `goalId` (or `null`), its plan ([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)): `startsOn` and `dueOn`, each a day (`2026-10-24`) or `null`; its moments, which the board keeps itself: `startedAt`, when it was first moved past `ready`, and `doneAt`, when it was moved to `done`, each an instant or `null`; `archivedAt`, when it was put away, an instant or `null` while it is current; `links` (`url` and `label`), `openDecisions` (how many decisions about it wait for a person), `openRuns` (how many runs on it are open: begun and not ended, which says nothing of whether an agent is still at it, [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)), `createdAt` and `updatedAt`.
|
|
215
217
|
|
|
216
218
|
### `POST /api/v1/projects/<key>/tasks`
|
|
217
219
|
|
|
@@ -233,25 +235,25 @@ Writing one, for whoever may work in the project, with a session or a token, is
|
|
|
233
235
|
|
|
234
236
|
### `GET /api/v1/projects/<key>/decisions?open=&task=&limit=&offset=`
|
|
235
237
|
|
|
236
|
-
`{ "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
|
|
238
|
+
`{ "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`, and the `key` the raiser gave it, if any, for a program to read the answer by; 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; the first of them where several were) and `deciders` (everyone it is addressed to, in the order named, [ADR-0057](../adr/0057-a-decision-may-be-addressed-to-several-people-and-the-first-answer-ends-it.md): each finds it among their decisions, any of them may answer, and the first answer ends it; `[decider]` where one was named; answered from 0.4.4 and read as optional), `run` (the `id` and `agent` of the run that raised it, when an agent asked, or `null`), `files` (the files put on it when it was raised, [ADR-0055](../adr/0055-a-decision-carries-the-files-put-on-it-when-it-is-raised.md): each an `id` and the `file` as a comment's is, `name`, `size`, `contentType`, `sha256`; answered from 0.4.3 and read as optional), `answer`: `null` while it waits, else the chosen `option`, a `note` or `null` (the rationale given with it), `by`, `at` and `agent` (the agent the answer came through, when the person's agent wrote it in their name, or `null` when they answered themselves; [ADR-0056](../adr/0056-a-record-that-names-the-person-who-did-a-thing-names-the-agent-they-did-it-through.md); answered from 0.4.4 and read as optional); and `outcome`, what followed, in Markdown, or `null` until someone writes it, with `outcomeBy`, `outcomeAgent` and `outcomeAt`, who wrote it, through which agent, and when (each `null` until then; from 0.4.4, read as optional). 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.
|
|
237
239
|
|
|
238
240
|
### `POST /api/v1/projects/<key>/decisions`
|
|
239
241
|
|
|
240
|
-
Takes `question`, `options` (two to `MAX_OPTIONS` labels), and optionally `body`, and either `taskId` or `runId
|
|
242
|
+
Takes `question`, `options` (two to `MAX_OPTIONS`, each a `label` and optionally a `key`, a lowercase word spelled as a plugin's key is and given once, which the `decision.answered` event carries back as `optionKey`, so that a program reads the answer without comparing labels), and optionally `body`, and either `taskId` or `runId`; as JSON, or, with files put on the decision ([ADR-0055](../adr/0055-a-decision-carries-the-files-put-on-it-when-it-is-raised.md)), as a form (`multipart/form-data`) whose part `decision` says what the JSON would, with a part `file` for each file, each checked as a file handed in is, at most `MAX_FILES_PER_DECISION` of them and together at most `MAX_FILE_BYTES` (`request.too_large`); the whole form is read and found good before any byte is kept, and a picture among the files is shown where the body names it as ``, the rest after the body. `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. `deciderIds` names several in its place ([ADR-0057](../adr/0057-a-decision-may-be-addressed-to-several-people-and-the-first-answer-ends-it.md)), each once in the order given and each able to work in the project, up to `MAX_DECIDERS` (`decision.too_many_deciders` beyond it): every one of them finds the decision among their own, any of them may answer, and the first answer ends it. 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 and, when files came, how many as `files`.
|
|
241
243
|
|
|
242
244
|
### `GET .../decisions/<id>?wait=` and `POST .../decisions/<id>/answer`
|
|
243
245
|
|
|
244
246
|
One decision. With `wait=<seconds>` (`0` to `600`) the request is held while the decision waits, up to the server's own bound (50 seconds), and answers the decision as it stands then: how an agent learns the answer at once, woken through the database's own channel, without asking again and again. A decision answered already, or a wait of `0`, answers at once.
|
|
245
247
|
|
|
246
|
-
The answer takes `option` (the id of one of its options) or `own` (the person's own answer, where none of the options is it: one line, up to `MAX_OPTION_LABEL_LENGTH`, as an option is worded; `request.invalid` with both or with neither), and optionally a `note`. An answer of the person's own becomes one more option of the decision, numbered after the ones given and marked `own: true`, and the one chosen, so that the answer names an option either way and every reader finds its label. A decision is answered once; the answer records who gave it and when, writes a `decision.answered` event with the label of the option, and lets a run that waited go on (`status: running`), unless another decision of its still waits.
|
|
248
|
+
The answer takes `option` (the id of one of its options) or `own` (the person's own answer, where none of the options is it: one line, up to `MAX_OPTION_LABEL_LENGTH`, as an option is worded; `request.invalid` with both or with neither), and optionally a `note`. An answer of the person's own becomes one more option of the decision, numbered after the ones given and marked `own: true`, and the one chosen, so that the answer names an option either way and every reader finds its label. A decision is answered once; the answer records who gave it, the agent it came through (`agent`: the token's name when an agent wrote it in the person's name, `null` when they answered themselves; [ADR-0056](../adr/0056-a-record-that-names-the-person-who-did-a-thing-names-the-agent-they-did-it-through.md)) and when, writes a `decision.answered` event with the label of the option (`option`) and, where the raiser gave one, its key (`optionKey`), and lets a run that waited go on (`status: running`), unless another decision of its still waits.
|
|
247
249
|
|
|
248
250
|
### `PATCH /api/v1/projects/<key>/decisions/<id>`
|
|
249
251
|
|
|
250
|
-
Grows the record: takes any of `body` (the context) and `outcome` (what followed; empty clears it), for anyone who may work in the project, with a session from the console's own pages or a token, answered or not. Writes a `decision.updated` event naming the `fields`; a patch that changes nothing writes nothing.
|
|
252
|
+
Grows the record: takes any of `body` (the context) and `outcome` (what followed; empty clears it), for anyone who may work in the project, with a session from the console's own pages or a token, answered or not. What followed records who wrote it, through which agent, and when (`outcomeBy`, `outcomeAgent`, `outcomeAt`; [ADR-0056](../adr/0056-a-record-that-names-the-person-who-did-a-thing-names-the-agent-they-did-it-through.md)), and clears with it. Writes a `decision.updated` event naming the `fields`; a patch that changes nothing writes nothing.
|
|
251
253
|
|
|
252
254
|
### `GET /api/v1/projects/<key>/goals?state=&open=&category=&q=&limit=&offset=` and `POST /api/v1/projects/<key>/goals`
|
|
253
255
|
|
|
254
|
-
The roadmap ([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), [ADR-0026](../adr/0026-a-goal-is-planned-in-days-and-sorted-into-a-category-and-the-roadmap-is-read-as-months.md)). `GET`, for whoever may see the project, answers `{ "items": [goal, ...], "more": boolean }`: a page of the roadmap, the active goals first, then the planned, the reached and the dropped, the sooner due first within each and the older first among those with no day; `limit` (1 to `LIST_LIMIT`, the default) and `offset` say which page and `more` whether there is one after it; `?category=<key>` keeps those sorted into a category, `?state=<state>` those in one state, `?open=true` the planned and the active and `?open=false` the reached and the dropped, and `?q=<words>` those whose title holds every word, whatever the case, which is how a picker or a command finds a goal ([ADR-0037](../adr/0037-a-task-has-one-owner-and-any-number-of-workers-and-a-parent-or-a-goal-is-found-by-search.md)). A goal carries its `title`, `body` (Markdown: what reaching it means), `state` (`planned`, `active`, `done`, `dropped`), `category` (its `key`, `name` and `color` as the workspace keeps it, or `null`), its plan, `startsOn` (the day the work towards it is meant to begin, or `null`) and `dueOn` (the day it is meant to be reached by, or `null`), `createdBy
|
|
256
|
+
The roadmap ([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), [ADR-0026](../adr/0026-a-goal-is-planned-in-days-and-sorted-into-a-category-and-the-roadmap-is-read-as-months.md)). `GET`, for whoever may see the project, answers `{ "items": [goal, ...], "more": boolean }`: a page of the roadmap, the active goals first, then the planned, the reached and the dropped, the sooner due first within each and the older first among those with no day; `limit` (1 to `LIST_LIMIT`, the default) and `offset` say which page and `more` whether there is one after it; `?category=<key>` keeps those sorted into a category, `?state=<state>` those in one state, `?open=true` the planned and the active and `?open=false` the reached and the dropped, and `?q=<words>` those whose title holds every word, whatever the case, which is how a picker or a command finds a goal ([ADR-0037](../adr/0037-a-task-has-one-owner-and-any-number-of-workers-and-a-parent-or-a-goal-is-found-by-search.md)). A goal carries its `title`, `body` (Markdown: what reaching it means), `state` (`planned`, `active`, `done`, `dropped`), `category` (its `key`, `name` and `color` as the workspace keeps it, or `null`), its plan, `startsOn` (the day the work towards it is meant to begin, or `null`) and `dueOn` (the day it is meant to be reached by, or `null`), `createdBy` and `agent` (the agent the goal was set through, or `null` when a person set it themselves; [ADR-0056](../adr/0056-a-record-that-names-the-person-who-did-a-thing-names-the-agent-they-did-it-through.md); from 0.4.4, read as optional), `taskCounts` (the tasks that work towards it, counted by state: `idea`, `ready`, `in_progress`, `in_review`, `done`, `dropped`; read from the board as it stands, never written down), `createdAt` and `updatedAt`. `POST`, for an owner of the project, from the console's own pages or with a token, takes `title` and optionally `body`, `state` (default `planned`), `category` (the key of a category of the workspace, or `null`; `goal.invalid_category`), `startsOn` and `dueOn` (days; the target not before the start, `goal.invalid_dates`), answers `201` with the goal and writes a `goal.created` event with its `title`; a member is `auth.forbidden`.
|
|
255
257
|
|
|
256
258
|
### `GET .../goals/<id>` and `PATCH .../goals/<id>`
|
|
257
259
|
|
|
@@ -271,9 +273,9 @@ How the work goes, and what was made. A report takes `body` (Markdown, `MAX_BODY
|
|
|
271
273
|
|
|
272
274
|
### `POST .../runs/<id>/files` and `GET /api/v1/projects/<key>/files/<id>`
|
|
273
275
|
|
|
274
|
-
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 `` shows it where the words are.
|
|
276
|
+
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 on a decision, or attached to a page, is `project.too_many_bytes` (`409`) and nothing of it is kept; a comment's files, a decision'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 `` shows it where the words are.
|
|
275
277
|
|
|
276
|
-
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`.
|
|
278
|
+
The bytes of any file of the project, handed in by a run, put in a comment or on a decision, 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`.
|
|
277
279
|
|
|
278
280
|
### `POST .../runs/<id>/end`
|
|
279
281
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skillcdn/console",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
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",
|
|
@@ -77,19 +77,19 @@
|
|
|
77
77
|
"access": "public"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
-
"@types/node": "^24.19.
|
|
80
|
+
"@types/node": "^24.19.2",
|
|
81
81
|
"@types/react": "^19.3.0",
|
|
82
82
|
"@types/react-dom": "^19.3.0",
|
|
83
|
-
"react": "^19.3.
|
|
84
|
-
"react-dom": "^19.3.
|
|
83
|
+
"react": "^19.3.1",
|
|
84
|
+
"react-dom": "^19.3.1",
|
|
85
85
|
"typescript": "^7.0.2",
|
|
86
|
-
"vitest": "^5.0
|
|
86
|
+
"vitest": "^5.1.0"
|
|
87
87
|
},
|
|
88
88
|
"dependencies": {
|
|
89
|
-
"@skillcdn/core": "^0.4.
|
|
89
|
+
"@skillcdn/core": "^0.4.2",
|
|
90
90
|
"react-markdown": "^10.1.0",
|
|
91
91
|
"remark-gfm": "^4.0.1",
|
|
92
|
-
"zod": "^4.6.
|
|
92
|
+
"zod": "^4.6.7"
|
|
93
93
|
},
|
|
94
94
|
"peerDependencies": {
|
|
95
95
|
"react": "^19.0.0",
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: A shell with Node.js 24 or newer, npm and git, and an agent that reads and writes files. For running the plugin on a local console, Docker for the database and a checkout of the console's repository, where signing in needs a GitHub OAuth app of the person's own, as the console's README says. The console command is optional, for running the plugin's commands as an agent would. No key of the agent's own is needed, and it never sees a token.
|
|
6
6
|
metadata:
|
|
7
7
|
author: skillcdn
|
|
8
|
-
version: "1.
|
|
8
|
+
version: "1.7"
|
|
9
9
|
skillcdn:
|
|
10
10
|
translations:
|
|
11
11
|
ko:
|
|
@@ -55,7 +55,7 @@ The contract is the [specification of plugins](/docs/specs/plugins.md): every fi
|
|
|
55
55
|
| Where it reaches | Asked when the feature does not say: "Does it belong to one project, to some, or to every project of the workspace?" Pages at the workspace level, at the project level, or both; the projects the plugin is on named by the administrator at enable. |
|
|
56
56
|
| What it keeps | Derived: the collections, each with its fields, what is filtered and ordered by (`indexes`), searched (`search`), who owns a record (`owner`), whether a record belongs to a project, whether history is kept, who may read; or a schema of its own with `storage:sql`, by the rule of phase 1. |
|
|
57
57
|
| The key and the name | The key from the feature's one word, lowercase, as the specification's rule and reserved words allow (`leave`, `crm`, `finance`); the name and the description per language; the agent note, one sentence an agent reads with the rules: what the plugin is for and when to use it. Shown at the design checkpoint and changed on request. |
|
|
58
|
-
| Where the code lives, and how it ships | Asked: "A repository of its own on GitHub, under which account? Published to npm, or installed from the repository's tag?" Default: a repository `console-plugin-<key>`, and publishing when the person has an npm account, a tag otherwise. |
|
|
58
|
+
| Where the code lives, and how it ships | Asked: "A repository of its own on GitHub, under which account? Published to npm, or installed from the repository's tag?" Default: a repository `console-plugin-<key>`, and publishing when the person has an npm account, a tag otherwise. The other shape: a repository of several plugins, one workspace each, published to the registry as `@<scope>/console-plugin-<key>`, since a tag installs a repository whole and carries no build; [`skillcdn/console-plugins`](https://github.com/skillcdn/console-plugins), the reference plugin repository, is of that shape. |
|
|
59
59
|
| The deployment | Asked: "Which console is this for, and who runs its deployment?" The install is a task for them (phase 5). |
|
|
60
60
|
| Everything else | Derived and shown in the design: the capabilities from the split, the settings (a number the organization chooses, a person who approves, a secret for an outside service), the board project's defaults (its key, name and description), the menu group, the pages and the widgets with their roles, `dataVersion: 1`. |
|
|
61
61
|
|
|
@@ -71,14 +71,14 @@ Produces the **design**: one table, a row per thing the feature does, saying who
|
|
|
71
71
|
|---|---|
|
|
72
72
|
| An agent does it for a person, and something changes | A **command**: `defineCommand({ about, args, roles, run })`. Its `text` answer is for people, its `data` for agents (`--json`); it does one thing and does it again safely where it can. |
|
|
73
73
|
| An agent, or a page, looks something up | A **query**: `defineQuery(...)`, which writes nothing; a query may answer a file. |
|
|
74
|
-
| A person must approve, choose or confirm | A **decision** the command raises with `ctx.console.decisions.ask`, with options, addressed to the person a setting or the record names (`deciderId`), `about` the record it is for; the command answers `waiting`, the agent waits as the console's own `ask` is waited for, and the plugin hears the answer in a hook on `console:decision.answered` and goes on. The plugin never answers one, and never computes what a person must choose. |
|
|
75
|
-
| Someone must do something later | A **task** on the board project with `ctx.console.tasks.create`, marked as the plugin's, worked by whoever takes it. |
|
|
74
|
+
| A person must approve, choose or confirm | A **decision** the command raises with `ctx.console.decisions.ask`, with options, addressed to the person a setting or the record names (`deciderId`), or to several of them (`deciderIds`, any of whom may answer), `about` the record it is for, its question, body and labels written once, in the language of whoever is to answer: `ctx.console.people.get(id)` says it as `language`, and where that is `null`, a person who has not opened the console yet, the plugin writes in the first of `ctx.console.workspace().languages` ([ADR-0065](/docs/adr/0065-a-plugin-writes-one-text-and-a-persons-language-is-kept-from-the-first-time-the-pages-see-them.md)); the command answers `waiting`, the agent waits as the console's own `ask` is waited for, and the plugin hears the answer in a hook on `console:decision.answered` and goes on. The plugin never answers one, and never computes what a person must choose. |
|
|
75
|
+
| Someone must do something later | A **task** on the board project with `ctx.console.tasks.create`, marked as the plugin's, worked by whoever takes it; its title and body, and a comment on it, one text each, written as a decision's words are, in the language of whoever it is for. |
|
|
76
76
|
| The console must tell the plugin something happened | A **hook**, `on["console:<kind>"]` (`events:console`): the plugin's board project, the projects it is on and the items it made are what it hears of. Delivery is at least once: a handler reads the record first and does nothing twice. |
|
|
77
77
|
| Another plugin must tell it | A **hook** on `plugin:<key>:<kind>` (`events:plugins`), or a call on the other's `api` (`plugins:<key>`). |
|
|
78
78
|
| Time passes | A **job** (`jobs`), `every` or `at` in the workspace's time zone, never two at once; what it did goes into records and, where people should know, a task or a decision. |
|
|
79
79
|
| An outside service calls in | An **endpoint** (`endpoints`): a `person` endpoint for whoever holds a role, a `webhook` for nobody, verified with `ctx.secrets.verify` against a secret setting. |
|
|
80
80
|
| The plugin must call out | `ctx.fetch` to a host the manifest names (`outbound:<host>`), the secret put in by the console from a `secret` setting, never read by the plugin. |
|
|
81
|
-
| People look at it, or fill a form | A **page** (level `workspace` or `project`) or a **widget** on the front page: it shows records and calls commands and queries through the bridge; the logic stays in the server module, so that the command line and the pages do the same thing. |
|
|
81
|
+
| People look at it, or fill a form (a calendar starts its week on `frame.context.workspace.firstDay`, the day the workspace's setting says, and reads `timeZone` beside it; faces come from `frame.people.picture(id)`, since a frame loads no image from elsewhere) | A **page** (level `workspace` or `project`) or a **widget** on the front page, which declares the `sizes` it comes in, at least one of them no wider than four cells, which is all a phone has (`small` 2×2, `medium` 4×2, `large` 4×4, `wide` 8×2, `full` 8×4, square cells of the page's grid) and lays itself out for the one it has (`frame.context.size`, `frame.onSize`), since the person arranges the page ([ADR-0061](/docs/adr/0061-the-front-page-is-one-grid-of-tiles-a-person-arranges-by-hand.md)): it shows records and calls commands and queries through the bridge; the logic stays in the server module, so that the command line and the pages do the same thing. |
|
|
82
82
|
| An agent must know how | A **skill** in the package, one per kind of work, naming the commands as `console plugin <key> <command>`, with the rules of that work; `SKILLCDN.md` at the root. The agent note of the manifest is the one line that sends an agent to them. |
|
|
83
83
|
|
|
84
84
|
Then the data. Records, unless the feature needs what records do not give: joins across many collections, more rows than a plugin's records may hold, or figures beyond counting, summing, the least and the most; then `storage:sql`, a schema and a role of the plugin's own with migrations under `migrations/`, which needs the deployment's database user to hold `CREATEROLE` and is said in the hand-over. Records are the default: the console keeps them with versions, files, links, search and aggregates, exports and purges them, and shows them to people by role; a schema is the plugin's to keep.
|
|
@@ -87,7 +87,7 @@ Then the capabilities: the list the split needs, each read as the sentence the a
|
|
|
87
87
|
|
|
88
88
|
### Phase 2: Start from the template, then fill it
|
|
89
89
|
|
|
90
|
-
Produces the **plugin as source**. `console-plugin init <dir> --key <key> --name "<Name>"` writes the smallest complete plugin: the manifest, a server module with one collection, one command and one query, a web bundle with one page and one widget, one skill with its `SKILLCDN.md`, the tests, the build and a README that says where things are; `--console` names the version of `@skillcdn/console` it depends on, the command's own unless said, and for a plugin written for a deployed console, the version that console runs (`console whoami` says it). Then, in `<dir>`: `npm install`, `npm run build`, `npm test`, `npm run check`, all green before a line is changed, and `git init`.
|
|
90
|
+
Produces the **plugin as source**. `console-plugin init <dir> --key <key> --name "<Name>"` writes the smallest complete plugin: the manifest, a server module with one collection, one command and one query, a web bundle with one page and one widget, one skill with its `SKILLCDN.md`, the tests, the build and a README that says where things are; `--console` names the version of `@skillcdn/console` it depends on, the command's own unless said, and for a plugin written for a deployed console, the version that console runs (`console whoami` says it), and the manifest's `console` is written from it, the line of that version (`>=0.4.3 <0.5.0`): the consoles the plugin runs on, which the console checks when it reads the plugin and `console-plugin check` holds the dependency to; narrow it to the patch that brought what the plugin depends on, and widen it to the next line once the plugin is built against it; `--package <name>` the package's name where it is not `console-plugin-<key>`, a scoped one (`@<scope>/console-plugin-<key>`) for a repository of several plugins, published as public; `--repository <url>` with `--directory <path>` where the plugin lives in a repository, which fill `repository`, `homepage` and `bugs` and the manifest's `store`; and `--languages en,ko` the languages of its words, `en` first, which gives every language map and both manifests' translations the others as English words to translate, of which `console-plugin check` warns while they stay English. Then, in `<dir>`: `npm install`, `npm run build`, `npm test`, `npm run check`, all green before a line is changed, and `git init`.
|
|
91
91
|
|
|
92
92
|
Then the design goes in, file by file: the manifest `console-plugin.json` from the design, its `key`, names and descriptions per language, `agentNote`, `capabilities`, `roles` and `defaultRole`, `board`, `settings`, `menu`, `pages`, `widgets`, `dataVersion`; the server module `src/server/index.ts`, one `defineCollection` per collection, one `defineCommand` or `defineQuery` per row, `endpoints`, `on`, `jobs`, `api`, `onEnable` and `onDisable` where the design has them, exported by name for the tests and gathered in `definePlugin(...)` as the default export; the web bundle `src/web/index.ts` and `index.css`, where `connect()` gives the page the context (who looks, at what, in which language), `command`, `query`, `records`, `files`, `navigate`, `download`, `setTitle` and the events, every class prefixed `pl-<key>-`, the console's `sc-` classes and `--sc-*` tokens for the look, and every word a person wrote put in as text; the skills under `skills/<name>/SKILL.md`, one per kind of work, in the SkillCDN Format, and the README of the package. The server module imports only what the host allows and bundles everything it imports; it keeps no state between calls but in records, since its thread is started afresh after a fault. Where the design has `storage:sql`, `migrations/0001-<words>.sql` makes the tables, and `manifest.migrations` names the directory.
|
|
93
93
|
|
|
@@ -97,13 +97,13 @@ Produces the **tests**, green. `src/server/index.test.ts` runs each command, que
|
|
|
97
97
|
|
|
98
98
|
### Phase 4: Run it on a local console
|
|
99
99
|
|
|
100
|
-
Produces the **plugin running**, enabled, with its commands tried and its pages shown. The console from its checkout, as the console's README's "Install" says, with one difference: before the api starts, `.env` sets `PLUGINS_DIR` to a directory made for the purpose, whose `package.json` is `{ "private": true, "dependencies": {} }` and in which `npm install --ignore-scripts <path to the plugin>` writes the plugin as a `file:` dependency and links `node_modules/<its name>` to the checkout, built; the console reads the link where it points, so the checkout is the plugin, and `npm run build` in it followed by a restart of the api is a new version. The person signs in on the local console as its administrator (`ADMINS` in `.env`, a GitHub OAuth app registered for the local address, as `apps/console/README.md` of the console says), opens the plugins' settings page, where the plugin is listed as installed with any reason it failed to load, and enables it: the consent to each capability, the board project made from the manifest's defaults or chosen, the projects it is on, the settings; or their agent does the same with `console plugin <key> enable` ([setting-up.md](/skills/planning-the-board/references/setting-up.md), "A plugin's board project"), signed in there with `console login`. The compose database's user owns the database, so `storage:sql` is available locally. Then the tabs appear where the manifest put them, and the plugin is worked as an agent works it ([working-the-board](/skills/working-the-board/SKILL.md)): `console plugin <key>` lists the commands, `console plugin <key> <command> ...` runs one, a decision raised is answered on the pages, a hook runs, a job runs at its time (the worker inside the api, locally), and the pages are looked at with a browser the agent can drive, else by the person. What is seen is shown at the checkpoint; what could not run is reported as not run.
|
|
100
|
+
Produces the **plugin running**, enabled, with its commands tried and its pages shown. The console from its checkout, as the console's README's "Install" says, with one difference: before the api starts, `.env` sets `PLUGINS_DIR` to a directory made for the purpose, whose `package.json` is `{ "private": true, "dependencies": {} }` and in which `npm install --ignore-scripts <path to the plugin>` writes the plugin as a `file:` dependency and links `node_modules/<its name>` to the checkout, built; the console reads the link where it points, so the checkout is the plugin, and `npm run build` in it followed by a restart of the api is a new version. The person signs in on the local console as its administrator (`ADMINS` in `.env`, a GitHub OAuth app registered for the local address, as `apps/console/README.md` of the console says), opens the plugins' settings page, where the plugin is listed as installed with any reason it failed to load, and enables it: the consent to each capability, the board project made from the manifest's defaults or chosen, the projects it is on, the settings; or their agent does the same with `console plugin <key> enable` ([setting-up.md](/skills/planning-the-board/references/setting-up.md), "A plugin's board project"), signed in there with `console login`. The compose database's user owns the database, so `storage:sql` is available locally. Then the tabs appear where the manifest put them, and the plugin is worked as an agent works it ([working-the-board](/skills/working-the-board/SKILL.md)): `console plugin <key>` lists the commands, `console plugin <key> <command> ...` runs one, a decision raised is answered on the pages, a hook runs, a job runs at its time (the worker inside the api, locally), and the pages are looked at with a browser the agent can drive, else by the person. What is seen is shown at the checkpoint; what could not run is reported as not run. Without a provider to register: in the console's checkout, `pnpm dev:plugins --plugin <path to the built plugin>` runs the console on this machine with the fixture's people signing in from the browser at the addresses it prints, the plugin installed beside the fixtures and not enabled, and the plugin's hooks and jobs running in that process, so that a decision answered there reaches the hook within seconds and a job runs at its time; the administrator among them enables it on the settings page, and the pages and `console plugin <key>` are looked at as above. The directory is read as the image lays a plugin out, so a checkout is built first (`npm run build`), and a directory that is not a built plugin stops the console with the reason.
|
|
101
101
|
|
|
102
102
|
### Phase 5: Ship, and hand the install to the deployment
|
|
103
103
|
|
|
104
|
-
Produces the **release** and the **hand-over**. The version in `package.json` moves as semver says, `dataVersion` moves with an entry in `upgrades` whenever the shape of a record changed; `npm run build`, `npm test` and `npm run check` pass; a commit, and a push of the repository. Then one of two, confirmed with its consequence: `npm publish` (`--access public` for a scoped name), which puts the package on the registry under that name for good, and is done by the person or with their word; or a tag `v<version>` whose tree carries the built package (the template's `.gitignore` leaves `dist/` out of git, so a release commit adds it with `git add -f dist` before the tag), since the deployment installs with no scripts and runs no build. The deployment then names the plugin as `"<name>": "<version>"` or `"github:<owner>/<repo>#v<version>"`.
|
|
104
|
+
Produces the **release** and the **hand-over**. The version in `package.json` moves as semver says, `dataVersion` moves with an entry in `upgrades` whenever the shape of a record changed; `npm run build`, `npm test` and `npm run check` pass; a commit, and a push of the repository. Then one of two, confirmed with its consequence: `npm publish` (`--access public` for a scoped name), which puts the package on the registry under that name for good, and is done by the person or with their word; or a tag `v<version>` whose tree carries the built package (the template's `.gitignore` leaves `dist/` out of git, so a release commit adds it with `git add -f dist` before the tag), since the deployment installs with no scripts and runs no build. The deployment then names the plugin as `"<name>": "<version>"` or, for a repository of one plugin alone, `"github:<owner>/<repo>#v<version>"`.
|
|
105
105
|
|
|
106
|
-
The hand-over is a task on the deployment's board, written as [planning-the-board](/skills/planning-the-board/SKILL.md) writes one, or the same words in chat where there is no board: the package and its version, and how it is installed ([deploy/README.md, "Plugins in the image"](/deploy/README.md#plugins-in-the-image), a row of deploying-a-console's [running-it-again.md](/skills/deploying-a-console/references/running-it-again.md)); what the deployment must provide, where the plugin asks it (`CREATEROLE` for `storage:sql`, the hosts of `outbound`, a bound on the files); what the administrator does when they enable it: the capabilities they consent to, the board project, the projects, the settings to fill and the secrets to set, the roles and who gets them; and where the plugin's skills are once it runs (`console plugin <key> skills`). The deployment's agent installs it as a release of the deployment; the administrator enables it; the plugin is then proven on that console as phase 4 proved it locally, and that is the delivery.
|
|
106
|
+
The hand-over is a task on the deployment's board, written as [planning-the-board](/skills/planning-the-board/SKILL.md) writes one, or the same words in chat where there is no board: the package and its version, the consoles it runs on (the manifest's `console`, which must admit the deployment's console: `console whoami` says its version, and a console outside the range lists the plugin as failed with the reason), and how it is installed ([deploy/README.md, "Plugins in the image"](/deploy/README.md#plugins-in-the-image), a row of deploying-a-console's [running-it-again.md](/skills/deploying-a-console/references/running-it-again.md)); what the deployment must provide, where the plugin asks it (`CREATEROLE` for `storage:sql`, the hosts of `outbound`, a bound on the files); what the administrator does when they enable it: the capabilities they consent to, the board project, the projects, the settings to fill and the secrets to set, the roles and who gets them; and where the plugin's skills are once it runs (`console plugin <key> skills`). The deployment's agent installs it as a release of the deployment; the administrator enables it; the plugin is then proven on that console as phase 4 proved it locally, and that is the delivery.
|
|
107
107
|
|
|
108
108
|
## Verdicts
|
|
109
109
|
|
|
@@ -121,6 +121,7 @@ The hand-over is a task on the deployment's board, written as [planning-the-boar
|
|
|
121
121
|
- A secret is a `secret` setting the administrator sets, put into a request by the console or verified by it; the plugin never reads its value, logs it, keeps it in a record, or puts it in the repository, the tests or the chat.
|
|
122
122
|
- Everything a person wrote is shown as text, in the pages and in a command's answer; the frame renders no HTML from a record, and its classes are prefixed `pl-<key>-`.
|
|
123
123
|
- The server module imports nothing the host refuses, reaches no network but `ctx.fetch`, no file but its own, and keeps no state between calls but in records; a hook does nothing twice.
|
|
124
|
+
- A page reaches the console through the bridge alone: a frame has no network and no WebRTC, and the console serves each script of the bundle with a line before it, so that a source map is a line out. A page that needs WebRTC asks for `outbound:webrtc`, optional where the plugin can go without it, whose reach an administrator reads before consenting: any address, from each person's browser, and no camera or microphone with it ([ADR-0066](/docs/adr/0066-webrtc-is-a-way-out-a-plugin-asks-for-and-how-a-frame-is-held-stays-in-one-place.md)).
|
|
124
125
|
- Nothing is published, tagged for an install or installed without the person's word; the install is the deployment's release, planned and started by its people, never by this skill.
|
|
125
126
|
- The specification wins: a field, a bound or a refusal is as [docs/specs/plugins.md](/docs/specs/plugins.md) says; the plugin is rebuilt against the console it runs on, since before 1.0 the plugin API moves with the console's line.
|
|
126
127
|
|
|
@@ -10,7 +10,7 @@ What an administrator or an owner does on the pages, their agent does here with
|
|
|
10
10
|
|
|
11
11
|
## A plugin's board project, for an administrator
|
|
12
12
|
|
|
13
|
-
A plugin the deployment installed ([building-a-plugin](/skills/building-a-plugin/SKILL.md) writes one; the deployment installs it) does nothing until an administrator enables it, and one that puts decisions, tasks and pages on a board names its project then: `console plugin <key> enable --consent <capability>,... --board new|<key> [--projects all|<key>,...] [--set <setting>=<value>]...`, `new` making a private project from the plugin's own defaults (its key, name and description, which `console plugin <key>` shows beforehand, with what each capability opens), `<key>` binding a project that exists; several plugins may share one. `--consent` must name every capability the manifest asks, each read as the sentence it is: what the plugin may do and reach. The project's members are listed as any project's; the plugin's own roles are granted apart, `console plugin <key> grant <role> --person <login>|--project <key>|--everyone`, and taken back with `console plugin <key> revoke <grant>`; `console plugin <key> settings --set <setting>=<value>` fills its settings, and a secret is read from standard input by `console plugin <key> secret <name>`, never an argument. From then on the rules an agent reads in that project end with `## Plugins`, naming it ([working-the-board](/skills/working-the-board/SKILL.md)), and what the plugin writes there carries its name beside the person's. `console plugin <key> disable` stops it and keeps its data. An owner who is not an administrator enables no plugin: the step goes back to the administrator, with the command.
|
|
13
|
+
A plugin the deployment installed ([building-a-plugin](/skills/building-a-plugin/SKILL.md) writes one; the deployment installs it) does nothing until an administrator enables it, consenting to what it requires and turning on what they choose of what it can go without (`console plugin <key> capabilities` says which, and turns them on or off later), and one that puts decisions, tasks and pages on a board names its project then: `console plugin <key> enable --consent <capability>,... --board new|<key> [--projects all|<key>,...] [--set <setting>=<value>]...`, `new` making a private project from the plugin's own defaults (its key, name and description, which `console plugin <key>` shows beforehand, with what each capability opens), `<key>` binding a project that exists; several plugins may share one. `--consent` must name every capability the manifest asks, each read as the sentence it is: what the plugin may do and reach. The project's members are listed as any project's; the plugin's own roles are granted apart, `console plugin <key> grant <role> --person <login>|--project <key>|--everyone`, and taken back with `console plugin <key> revoke <grant>`; `console plugin <key> settings --set <setting>=<value>` fills its settings, and a secret is read from standard input by `console plugin <key> secret <name>`, never an argument. From then on the rules an agent reads in that project end with `## Plugins`, naming it ([working-the-board](/skills/working-the-board/SKILL.md)), and what the plugin writes there carries its name beside the person's. `console plugin <key> disable` stops it and keeps its data. An owner who is not an administrator enables no plugin: the step goes back to the administrator, with the command.
|
|
14
14
|
|
|
15
15
|
## The members, for an owner
|
|
16
16
|
|
|
@@ -23,6 +23,7 @@ A plugin the deployment installed ([building-a-plugin](/skills/building-a-plugin
|
|
|
23
23
|
## The people, for an administrator
|
|
24
24
|
|
|
25
25
|
- `console person role <login> <admin|member>`: what a person is in the workspace; the board keeps at least one administrator in (`person.last_admin`).
|
|
26
|
+
- `console person manager <login> <manager login|none>`: whom a person reports to, the reporting line of the directory, which `console people` shows beside each person and the plugins read as a default (the approver of their leave, say) in place of a line of their own; another active person who does not report to them, directly or through others (`person.invalid_manager`), or `none`.
|
|
26
27
|
- `console person add <login> [--host gh|google]` lets a login in: whoever signs in with that account at that host is a person from then on; the host is read off the login (an address is Google's, anything else GitHub's) unless `--host` says. `console person remove <login>` takes a login away that nobody has signed in with yet.
|
|
27
28
|
- `console person deactivate <login>` takes a person out: refused on every request at once, their agents with them, kept with everything they did, and listed nowhere ([ADR-0044](/docs/adr/0044-a-token-of-a-person-who-is-out-is-kept-and-refused-with-the-reason-until-they-are-back-in.md)); `console person reactivate <login>` takes them back in. A person the organization let in, through its domain or its organization at the git host, comes and goes with it (`person.of_organization`).
|
|
28
29
|
- `console people` lists the people the person sees, by login, with what each is, and the logins let in that nobody has signed in with yet.
|
|
@@ -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.
|
|
8
|
+
version: "1.28"
|
|
9
9
|
skillcdn:
|
|
10
10
|
translations:
|
|
11
11
|
ko:
|
|
@@ -22,7 +22,7 @@ A task of the board goes in, by its number; a run comes out: the task taken, the
|
|
|
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. What it says the agent may decide alone is decided within that; anything else that changes 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 a person's to make, not the place it has to be made. An answer given 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, so that the board shows as waiting only what is still open. What was decided is not asked again.
|
|
25
|
+
- **A choice made elsewhere:** a decision is the record of a choice that is a person's to make, not the place it has to be made. An answer given 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, so that the board shows as waiting only what is still open; the record names you beside them as the agent the answer came through ([ADR-0056](/docs/adr/0056-a-record-that-names-the-person-who-did-a-thing-names-the-agent-they-did-it-through.md)). What was decided is not asked again.
|
|
26
26
|
- **A briefing:** asked what is on their plate or for a briefing, start from `console work`: the decisions waiting for the person's answer first, each with what waits on it and since when, since a waiting decision holds a run; then the tasks by their days (`--person <login>` is another person's plate, for an administrator or an owner of a project they are in). `console decisions --mine` and `--raised` read the rest of their decisions.
|
|
27
27
|
- **The discussion:** what people say about the task is in its comments, printed with the task by `console task <number>` and `console take`; a picture they put in is saved with `console file <id>` and looked at with the agent's own tools. An answer to them, a question that is not a decision, a remark on what was handed in, is a comment, `console comment <number> "<markdown>" [--attach <file>]`, which needs no run. A report is the run's record; a comment is a word to people.
|
|
28
28
|
- **Work given in chat:** in a directory that works in a project, work given in chat is written on the board first, `console task new "<title>" --body <markdown> --state ready [--category <key>] [--due <day>]`, with the plan's flags as [planning-the-board](/skills/planning-the-board/SKILL.md) lists them, and the person is told the number the command printed, never one assumed, since other agents write on the same board; then it is taken by that number and worked as below, so that the board holds the work and its record. Several tasks, a week's plan, a goal, or the project's own set-up is planning-the-board, the skill beside this one; a task whose body asks for the project itself (a member, a category, the rules, a goal) is worked as a run all the same, with the commands and the standing that skill gives.
|
|
@@ -66,7 +66,7 @@ The work happens where it happens: in the repository, the document, the tool. At
|
|
|
66
66
|
|
|
67
67
|
### Phase 4: Ask when a person must decide
|
|
68
68
|
|
|
69
|
-
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>"
|
|
69
|
+
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>"`, with `--attach <file>` for a picture of what is to be chosen between, a screenshot of what was made; a picture the body names as `` is shown there ([ADR-0055](/docs/adr/0055-a-decision-carries-the-files-put-on-it-when-it-is-raised.md)). 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](/docs/adr/0042-a-decision-is-addressed-to-a-person-its-raiser-unless-another-is-named.md)), and `--to` given again to several of them, any of whom may answer, the first answer ending it ([ADR-0057](/docs/adr/0057-a-decision-may-be-addressed-to-several-people-and-the-first-answer-ends-it.md)). 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` (seconds), 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 is written on the decision in the person's name first (`console answer`, above) 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.
|
|
70
70
|
|
|
71
71
|
### Phase 5: Hand in
|
|
72
72
|
|
package/src/api.ts
CHANGED
|
@@ -72,6 +72,7 @@ export {
|
|
|
72
72
|
MAX_CATEGORY_KEY_LENGTH,
|
|
73
73
|
MAX_CATEGORY_NAME_LENGTH,
|
|
74
74
|
MAX_COMMENTS_PER_TASK,
|
|
75
|
+
MAX_DECIDERS,
|
|
75
76
|
MAX_DECISIONS_PER_PROJECT,
|
|
76
77
|
MAX_DOCUMENT_LENGTH,
|
|
77
78
|
MAX_DOCUMENT_PATH_LENGTH,
|
|
@@ -80,6 +81,7 @@ export {
|
|
|
80
81
|
MAX_FILE_BYTES,
|
|
81
82
|
MAX_FILE_NAME_LENGTH,
|
|
82
83
|
MAX_FILES_PER_COMMENT,
|
|
84
|
+
MAX_FILES_PER_DECISION,
|
|
83
85
|
MAX_FILES_PER_DOCUMENT,
|
|
84
86
|
MAX_GOALS_PER_PROJECT,
|
|
85
87
|
MAX_LANGUAGE_TAG_LENGTH,
|
|
@@ -153,6 +155,7 @@ export {
|
|
|
153
155
|
export {
|
|
154
156
|
hostOfLogin,
|
|
155
157
|
isLoginAt,
|
|
158
|
+
OPTION_KEY_PATTERN,
|
|
156
159
|
type RestAnswerInput,
|
|
157
160
|
type RestArchivedTasks,
|
|
158
161
|
type RestArchiveTasksInput,
|
|
@@ -173,6 +176,7 @@ export {
|
|
|
173
176
|
type RestConnection,
|
|
174
177
|
type RestConnectRequest,
|
|
175
178
|
type RestDecision,
|
|
179
|
+
type RestDecisionFile,
|
|
176
180
|
type RestDecisionInput,
|
|
177
181
|
type RestDecisionOption,
|
|
178
182
|
type RestDecisionPatch,
|
|
@@ -193,6 +197,8 @@ export {
|
|
|
193
197
|
type RestGoalInput,
|
|
194
198
|
type RestGoalPatch,
|
|
195
199
|
type RestGoals,
|
|
200
|
+
type RestHome,
|
|
201
|
+
type RestHomeTile,
|
|
196
202
|
type RestListing,
|
|
197
203
|
type RestListingInput,
|
|
198
204
|
type RestMe,
|
|
@@ -269,6 +275,7 @@ export {
|
|
|
269
275
|
restConnectInputSchema,
|
|
270
276
|
restConnectionSchema,
|
|
271
277
|
restConnectRequestSchema,
|
|
278
|
+
restDecisionFileSchema,
|
|
272
279
|
restDecisionInputSchema,
|
|
273
280
|
restDecisionOptionSchema,
|
|
274
281
|
restDecisionPatchSchema,
|
|
@@ -290,6 +297,8 @@ export {
|
|
|
290
297
|
restGoalPatchSchema,
|
|
291
298
|
restGoalSchema,
|
|
292
299
|
restGoalsSchema,
|
|
300
|
+
restHomeSchema,
|
|
301
|
+
restHomeTileSchema,
|
|
293
302
|
restListingInputSchema,
|
|
294
303
|
restListingSchema,
|
|
295
304
|
restMemberInputSchema,
|
|
@@ -355,6 +364,7 @@ export {
|
|
|
355
364
|
CATEGORY_COLORS,
|
|
356
365
|
type CategoryColor,
|
|
357
366
|
COLOR_NAMES,
|
|
367
|
+
CONSOLE_LANGUAGES,
|
|
358
368
|
type ColorName,
|
|
359
369
|
EVENT_KINDS,
|
|
360
370
|
type EventKind,
|