@skillcdn/console 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/api.d.ts +4 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +4 -4
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts +5 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +290 -46
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/credentials.d.ts +5 -0
- package/dist/cli/credentials.d.ts.map +1 -1
- package/dist/cli/credentials.js +2 -0
- package/dist/cli/credentials.js.map +1 -1
- package/dist/cli/directory.d.ts +7 -0
- package/dist/cli/directory.d.ts.map +1 -1
- package/dist/cli/directory.js +7 -0
- package/dist/cli/directory.js.map +1 -1
- package/dist/cli/format.d.ts +10 -1
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +76 -20
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +42 -23
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.js +1 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/client.d.ts +39 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +24 -3
- package/dist/client.js.map +1 -1
- package/dist/compat.d.ts +28 -6
- package/dist/compat.d.ts.map +1 -1
- package/dist/compat.js +55 -14
- package/dist/compat.js.map +1 -1
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +6 -4
- package/dist/components/board.js.map +1 -1
- package/dist/components/calendar.d.ts +18 -8
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +39 -27
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +2 -0
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/pack-connect.d.ts +23 -0
- package/dist/components/pack-connect.d.ts.map +1 -0
- package/dist/components/pack-connect.js +86 -0
- package/dist/components/pack-connect.js.map +1 -0
- package/dist/components/pack-token.d.ts +27 -0
- package/dist/components/pack-token.d.ts.map +1 -0
- package/dist/components/pack-token.js +11 -0
- package/dist/components/pack-token.js.map +1 -0
- package/dist/components/picker.d.ts +39 -0
- package/dist/components/picker.d.ts.map +1 -0
- package/dist/components/picker.js +155 -0
- package/dist/components/picker.js.map +1 -0
- package/dist/components/projects.d.ts +13 -0
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +37 -5
- package/dist/components/projects.js.map +1 -1
- package/dist/components/task-form.d.ts +18 -4
- package/dist/components/task-form.d.ts.map +1 -1
- package/dist/components/task-form.js +56 -13
- package/dist/components/task-form.js.map +1 -1
- package/dist/components/task-list.js +2 -2
- package/dist/components/task-list.js.map +1 -1
- package/dist/components/task-view.d.ts +7 -1
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.js +6 -5
- package/dist/components/task-view.js.map +1 -1
- package/dist/components/ui.d.ts +7 -0
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +11 -0
- package/dist/components/ui.js.map +1 -1
- package/dist/components/work.d.ts +3 -1
- package/dist/components/work.d.ts.map +1 -1
- package/dist/components/work.js +6 -4
- package/dist/components/work.js.map +1 -1
- package/dist/components/workspace.d.ts +46 -0
- package/dist/components/workspace.d.ts.map +1 -0
- package/dist/components/workspace.js +124 -0
- package/dist/components/workspace.js.map +1 -0
- package/dist/console.css +319 -11
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +83 -24
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +26 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +59 -1
- package/dist/data.js.map +1 -1
- package/dist/dates.d.ts +12 -2
- package/dist/dates.d.ts.map +1 -1
- package/dist/dates.js +20 -2
- package/dist/dates.js.map +1 -1
- package/dist/i18n/en.d.ts +64 -3
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +81 -16
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +77 -16
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +2 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +2 -0
- package/dist/limits.js.map +1 -1
- package/dist/router.d.ts +4 -4
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +11 -2
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +4 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +4 -0
- package/dist/routes.js.map +1 -1
- package/dist/schemas.d.ts +194 -13
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +121 -21
- package/dist/schemas.js.map +1 -1
- package/dist/vocabulary.d.ts +21 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +18 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +25 -20
- package/docs/specs/rest.md +42 -19
- package/package.json +2 -2
- package/skills/building-a-console/SKILL.md +6 -6
- package/skills/building-a-console/references/the-look-from-a-website.md +39 -0
- package/skills/deploying-a-console/SKILL.md +9 -8
- package/skills/deploying-a-console/references/resources.md +1 -1
- package/skills/working-the-board/SKILL.md +5 -5
- package/skills/working-the-board/references/setting-up.md +1 -1
- package/skills/working-the-board/references/terms.md +3 -1
- package/skills/working-the-board/references/the-sentence.md +1 -1
- package/src/api.ts +21 -0
- package/src/cli/cli.ts +384 -51
- package/src/cli/credentials.ts +7 -0
- package/src/cli/directory.ts +9 -0
- package/src/cli/format.ts +83 -20
- package/src/cli/help.ts +50 -23
- package/src/cli/main.ts +1 -0
- package/src/client.ts +107 -10
- package/src/compat.ts +63 -14
- package/src/components/board.tsx +10 -4
- package/src/components/calendar.tsx +76 -34
- package/src/components/event-feed.tsx +2 -0
- package/src/components/pack-connect.tsx +179 -0
- package/src/components/pack-token.tsx +74 -0
- package/src/components/picker.tsx +297 -0
- package/src/components/projects.tsx +108 -9
- package/src/components/task-form.tsx +149 -63
- package/src/components/task-list.tsx +2 -2
- package/src/components/task-view.tsx +23 -9
- package/src/components/ui.tsx +25 -0
- package/src/components/work.tsx +10 -5
- package/src/components/workspace.tsx +298 -0
- package/src/console.tsx +202 -32
- package/src/data.ts +101 -1
- package/src/dates.ts +23 -2
- package/src/i18n/en.ts +92 -16
- package/src/i18n/ko.ts +84 -16
- package/src/index.ts +32 -1
- package/src/limits.ts +2 -0
- package/src/router.ts +17 -5
- package/src/routes.ts +4 -0
- package/src/schemas.ts +140 -19
- package/src/styles/console.css +319 -11
- package/src/vocabulary.ts +28 -0
package/docs/specs/cli.md
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
## Signing in
|
|
7
7
|
|
|
8
|
-
A person installs the package where the agent runs, or has the agent install it, and the command connects once ([ADR-0011](../adr/0011-an-agent-connects-with-a-short-code-a-person-approves-and-the-token-is-never-shown.md)): `console login --url <origin>` asks the console to connect, prints the address of the console's own page and a code of eight characters, and waits, up to the thirty minutes the code is good for; the person opens the address signed in, checks that the page shows the same code, names the agent and approves; and the command receives a token of its own, which it never prints. An agent that can run commands does this itself and relays the address and the code to its person; `--agent <name>` says what it calls itself; left out, the command names the agent it runs under where its environment says so (Claude Code, Codex), else `An agent`; the machine's name follows (`Claude Code on <machine>`), and the pages show the name before `on` with the whole on hover. A token made by hand on the Agents page is read from standard input with `--token-stdin` instead, for a script or a job, and checked against the console (`GET /api/v1/me` must know the person). Either way the command keeps the token as a login, with the console's address and the
|
|
8
|
+
A person installs the package where the agent runs, or has the agent install it, and the command connects once ([ADR-0011](../adr/0011-an-agent-connects-with-a-short-code-a-person-approves-and-the-token-is-never-shown.md)): `console login --url <origin>` asks the console to connect, prints the address of the console's own page and a code of eight characters, and waits, up to the thirty minutes the code is good for; the person opens the address signed in, checks that the page shows the same code, names the agent and approves; and the command receives a token of its own, which it never prints. An agent that can run commands does this itself and relays the address and the code to its person; `--agent <name>` says what it calls itself; left out, the command names the agent it runs under where its environment says so (Claude Code, Codex), else `An agent`; the machine's name follows (`Claude Code on <machine>`), and the pages show the name before `on` with the whole on hover. A token made by hand on the Agents page is read from standard input with `--token-stdin` instead, for a script or a job, and checked against the console (`GET /api/v1/me` must know the person). Either way the command keeps the token as a login, with the console's address, the person's login there and the workspace's time zone as the console last said it (at `login`, `whoami` or `workspace`; the moments the command prints are in it, as ISO 8601 with its offset, and as the API gives them, in UTC, while none is known or with `--json`; [ADR-0035](../adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md)), in `$XDG_CONFIG_HOME/skillcdn-console/credentials.json` (`~/.config/skillcdn-console/credentials.json` when `XDG_CONFIG_HOME` is unset), readable by the person alone where the system can say so and written whole in one step ([ADR-0027](../adr/0027-the-command-keeps-a-login-per-console-and-a-token-goes-only-to-the-console-that-issued-it.md)). One machine holds a login per console and account: `login` at a second console adds one beside the first, the first is the default console, `--default` makes another the default, and `login` repeated for the same console and account replaces the token, giving the old one up at the console. `console logins` lists them, never a token. `logout [--url <origin>] [--account <login>]` gives one login's token up at its console (`DELETE /api/v1/me/token`) and forgets it; a console out of reach leaves the token to the Agents page, where its person or an administrator removes it. A token is never taken on the command line, never typed, and never printed, and a file the command cannot read as its own is never written over: the command says so and stops.
|
|
9
9
|
|
|
10
10
|
Which console a command talks to is decided first, and the token is then the one for that console alone: the address is `--url <origin>`, else `CONSOLE_URL` in the environment, else the console `.skillcdn-console.json` binds the directory to, else the default login; the token is `CONSOLE_TOKEN` where `CONSOLE_URL` names that address (the two are a pair, for a hook or a job, and one without the other is refused), else the login kept for the address, for the account `--account <login>` or the directory names, or the one account there. A console nobody is signed in to here gets no request, whatever is kept for another; several accounts at one console with nothing to say which stop the command. An address is `https`, or `http` on this machine alone (`localhost`), and a redirect is never followed, so that a token travels to the console it was issued by and nowhere else. `whoami` and every refusal name the console and the account.
|
|
11
11
|
|
|
12
12
|
From then on the agent is that person on the board ([ADR-0004](../adr/0004-people-and-agents-reach-the-board-only-through-the-api-with-a-credential-of-their-own.md)): what it does is attributed to the person and shown as done by the agent, called what the person called the token unless `take --agent` says otherwise.
|
|
13
13
|
|
|
14
|
-
The command names its version on every request, as the product token of its `user-agent` (`skillcdn-console/<version> (cli; node <v>; <platform>)`), and the console holds it to the versions it takes ([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,
|
|
14
|
+
The command names its version on every request, as the product token of its `user-agent` (`skillcdn-console/<version> (cli; node <v>; <platform>)`), and the console holds it to the versions it takes ([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, every command of the console's line, from the floor the console names where a release 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` (`client.outdated`, `client.too_new`), `login` included, and exits with `4` after printing the command that updates it where it is installed: `npm install -g @skillcdn/console@<version>` for one installed for the machine (`pnpm add -g`, `yarn global add`, `bun add -g` where those put it there), `pnpm add @skillcdn/console@<version>` in the project's root for one installed in the project the working directory is in (the manager its lockfile names), or `npx`, `pnpm dlx` and their kind at the version for one fetched for the run; for a checkout, pull and build. Within the range, a command older than the console is told once a day, on stderr, that a newer one exists, and one newer that what the console has not got yet is refused as not found; what it last said is kept in `notices.json` beside the credentials, and `--json` output stays the console's. `whoami` says the versions, where the command is installed and what the console takes, every time. The command holds the console to a floor of its own the same way, the oldest console it works with (`CONSOLE_AT_LEAST` in the package's `compat` module): a console below it is refused by the command itself as `console.outdated`, in the console's own form, with the command to install for that console, and exit code `4`.
|
|
15
15
|
|
|
16
16
|
## The project
|
|
17
17
|
|
|
@@ -24,44 +24,49 @@ The board is a project's ([ADR-0008](../adr/0008-a-workspace-holds-projects-and-
|
|
|
24
24
|
| `login --url <origin> [--agent <name>] [--token-stdin] [--default]` | `POST /api/v1/connect`, then `POST /api/v1/connect/claim` until a person approved, then `GET /api/v1/me` with the token; `DELETE /api/v1/me/token` with the token a repeated login replaces | the address and the code for a person; then who the token is, and whether this is the default console; keeps the login |
|
|
25
25
|
| `logins` | nothing | one line per login: the console, the account, what the token is called, since when, and which is the default; never a token |
|
|
26
26
|
| `logout [--url <origin>] [--account <login>]` | `DELETE /api/v1/me/token` | that the login's token is given up and forgotten, or that the console was out of reach and the token is left to the Agents page |
|
|
27
|
-
| `whoami` | `GET /api/v1/me` | the login, the role, the workspace, the address, and the project this directory works in; with a project, where the organization's rules are read and where the skill working-the-board is on this machine; then the command's version and where it is installed, the console's version and the range it takes, and the update to make when they differ |
|
|
27
|
+
| `whoami` | `GET /api/v1/me` | the login, the role, the workspace, the address, and the project this directory works in; with a project, where the organization's rules are read and where the skill working-the-board is on this machine; then the command's version and where it is installed, the console's version and the range it takes, and the update to make when they differ. Keeps the workspace's time zone on the login it used, so that the moments the command prints are in it from then on ([ADR-0035](../adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md)) |
|
|
28
28
|
| `projects` | `GET /api/v1/projects` | one line per project: key, name, what the person is in it, what waits in it |
|
|
29
|
-
| `use <key> [--url <origin>] [--account <login>]` | `GET /api/v1/projects/<key>` | that the directory works in the project from now on, at which console and as which account where it binds them, and that `console rules` says what its people ask; writes `.skillcdn-console.json` |
|
|
30
|
-
| `work [--person <login>]` | `GET /api/v1/work`, `GET /api/v1/people/<id>/work` | what is on the person's plate across their projects: the open tasks
|
|
31
|
-
| `tasks [--state <state>] [--open] [--mine] [--category <key>] [--goal <id>] [--due <day\|today\|week\|overdue>] [--unassigned] [--archived] [--limit <n>] [--offset <n>]` | `GET .../tasks` with the filters and the page (`--mine` and `--unassigned` not together) | one line per task: number, state, priority, title, owner,
|
|
29
|
+
| `use <key> [--url <origin>] [--account <login>]` | `GET /api/v1/projects/<key>` | that the directory works in the project from now on, at which console and as which account where it binds them, and that `console rules` says what its people ask; then the sentence for the checkout's agreement (AGENTS.md, or CLAUDE.md), to be copied as it is; writes `.skillcdn-console.json` |
|
|
30
|
+
| `work [--person <login>]` | `GET /api/v1/work`, `GET /api/v1/people/<id>/work` | what is on the person's plate across their projects: the open tasks they work on, the sooner due first, each with its project; and the decisions waiting. `--person` shows another person's plate, for an administrator or an owner of a project they are in: the open tasks they work on within the projects the asker may see, without the decisions. The tasks done or dropped lately, which the answer carries as `closed` for the week on the plate's page, are in `--json` only. Needs no project |
|
|
31
|
+
| `tasks [--state <state>] [--open] [--mine] [--category <key>] [--goal <id>] [--due <day\|today\|week\|overdue>] [--unassigned] [--search <words>] [--archived] [--limit <n>] [--offset <n>]` | `GET .../tasks` with the filters and the page (`--mine` and `--unassigned` not together; `--search` is `q=`, the words of a title or a number, how a task is found as a parent, [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)) | one line per task: number, state, priority, title, owner, workers, category, due day, what waits on it, and whether it is put away; the tasks put away are left out unless `--archived` asks for them alone; a page at a time |
|
|
32
32
|
| `task <number\|id>` | `GET .../tasks/<ref>`, the decisions about it, the runs on it, `GET .../tasks/<id>/comments` | the task in full, with its plan and its moments, and the discussion on it: each comment whole, with its files and the command that reads each |
|
|
33
|
-
| `task new <title> [--body <markdown>\|--file <path>] [--state <state>] [--priority <priority>] [--category <key>] [--goal <id>] [--starts <day>] [--due <day>] [--
|
|
33
|
+
| `task new <title> [--body <markdown>\|--file <path>] [--state <state>] [--priority <priority>] [--category <key>] [--goal <id>] [--starts <day>] [--due <day>] [--worker <login\|me\|none>]... [--parent <number\|id>]` | `POST .../tasks` | the number and the id; `--worker` given again, or with commas between, names several, in place of the workers there were, and `none` alone nobody |
|
|
34
34
|
| `task edit <number\|id> [--title <title>] [--body ...] [the same flags; `none` clears a field]` | `GET .../tasks/<ref>`, then `PATCH .../tasks/<id>` | the task in full, as it is now |
|
|
35
35
|
| `task archive <number\|id>` | `GET .../tasks/<ref>`, then `POST .../tasks/<id>/archive` | the task's line, put away ([ADR-0029](../adr/0029-a-task-done-or-dropped-is-put-away-out-of-the-board-and-open-by-its-number.md)), whatever its state ([ADR-0030](../adr/0030-a-task-is-put-away-from-any-state.md)): out of the listings, the board and the calendar, open by its number still |
|
|
36
36
|
| `task archive --goal <id>` | `POST .../tasks/archive` | how many done or dropped tasks of the goal were put away; for an owner |
|
|
37
37
|
| `task restore <number\|id>` | `GET .../tasks/<ref>`, then `POST .../tasks/<id>/restore` | the task's line, back on the board |
|
|
38
|
-
| `goals [--category <key>] [--state <state>]` | `GET .../goals?category=&state=` | one line per goal of the roadmap: id, state, title, category, the days it starts and is meant to be reached by, how far it is ([ADR-0026](../adr/0026-a-goal-is-planned-in-days-and-sorted-into-a-category-and-the-roadmap-is-read-as-months.md)) |
|
|
38
|
+
| `goals [--category <key>] [--state <state>] [--search <words>] [--limit <n>] [--offset <n>]` | `GET .../goals?category=&state=&q=&limit=&offset=` | one line per goal of the roadmap: id, state, title, category, the days it starts and is meant to be reached by, how far it is ([ADR-0026](../adr/0026-a-goal-is-planned-in-days-and-sorted-into-a-category-and-the-roadmap-is-read-as-months.md)); `--search` keeps those whose title has the words; a page at a time |
|
|
39
39
|
| `goal <id>` | `GET .../goals/<id>`, `GET .../tasks?goal=` | the goal in full, with its plan and the tasks that work towards it |
|
|
40
40
|
| `goal new <title> [--body <markdown>\|--file <path>] [--state <state>] [--category <key>] [--starts <day>] [--due <day>]` | `POST .../goals` | the title and the id; for an owner of the project |
|
|
41
41
|
| `goal edit <id> [--title <title>] [--body ...] [--state <state>] [--category <key\|none>] [--starts <day\|none>] [--due <day\|none>]` | `PATCH .../goals/<id>` | the goal in full, as it is now |
|
|
42
42
|
| `categories` | `GET /api/v1/categories` | one line per category: key, name, colour |
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
43
|
+
| `workspace` | `GET /api/v1/me` | the workspace's settings ([ADR-0035](../adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md)): its name, who sees whom on the People page (`everyone`, or `shared_projects`), the day the calendars begin a week on, its time zone, where the organization's skills come from, and the most days a token may last. Keeps the time zone on the login, as `whoami` does. Needs no project |
|
|
44
|
+
| `workspace edit [--people-visibility <everyone\|shared-projects>] [--week-start <auto\|monday\|sunday>] [--time-zone <zone\|none>]` | `PATCH /api/v1/workspace` | the settings as they are now; for an administrator. The time zone is an IANA name the console knows (`request.invalid` otherwise), and `none` takes it away; the command keeps the zone on its login and prints moments in it from then on |
|
|
45
|
+
| `workspace pack-token --connect` | `POST /api/v1/workspace/pack-token/connect`, then `GET .../pack-token/connect` at the interval the deployment named, until the token is kept or the request ended | the page at SkillCDN and the code for a person who may open the repository, then that the pack is connected and the workspace's settings as they are now, never the token; `1` when the code expired, the request was declined or SkillCDN refused; for an administrator ([ADR-0036](../adr/0036-a-private-pack-is-connected-with-a-code-a-person-approves-from-the-projects-settings.md)) |
|
|
46
|
+
| `workspace pack-token --stdin`, `workspace pack-token --remove` | `PUT /api/v1/workspace/pack-token` with the token read from standard input, or `DELETE /api/v1/workspace/pack-token` | the workspace's settings as they are now, saying when its SkillCDN token was set and never the token: what every project with no address and no token of its own reads the organization's pack with; for an administrator |
|
|
47
|
+
| `people` | `GET /api/v1/people` | one line per person the person sees (everyone, or, while the workspace narrows the people a member sees, those they share a project with and the administrators): login, name, role in the workspace, and `deactivated` for one taken out; then, where they see everyone, one line per login let in that nobody has signed in with yet. Needs no project |
|
|
48
|
+
| `person role <login> <admin\|member>` | `GET /api/v1/people?login=` for the id, then `PATCH /api/v1/people/<id>` | the person as they are now; for an administrator |
|
|
45
49
|
| `person add <login> [--host gh\|google]`, `person remove <login>` | `POST /api/v1/listings`; `GET /api/v1/people` for the id, then `DELETE /api/v1/listings/<id>` | the login let in (the host read off the login unless `--host` says: an address is Google's, anything else the git host's), or that it is no longer; for an administrator ([ADR-0022](../adr/0022-who-is-in-is-the-consoles-own-record-and-an-administrator-keeps-it.md)) |
|
|
46
|
-
| `person deactivate <login>`, `person reactivate <login>` | `GET /api/v1/people
|
|
50
|
+
| `person deactivate <login>`, `person reactivate <login>` | `GET /api/v1/people?login=` for the id, then `PATCH /api/v1/people/<id>` with `status` | the person as they are now: out, refused on every request with their agents, or back in; for an administrator; never a person the organization let in |
|
|
47
51
|
| `project <key>` | `GET /api/v1/projects/<key>` | the project in full: what it is, its mark, its skills address, what the person is in it |
|
|
48
52
|
| `project new <key> <name> [--description <markdown>\|--file <path>] [--visibility workspace\|private] [--skills <address>\|none] [--color <name>] [--symbol <text>]` | `POST /api/v1/projects` | the key and the name; the person is its owner |
|
|
49
53
|
| `project edit <key> [--name <name>] [the same flags; `none` clears]` | `PATCH /api/v1/projects/<key>` | the project in full, as it is now; for an owner |
|
|
50
54
|
| `project icon <key> <file>`, `project icon <key> --remove` | `POST .../icon` as a form, or `DELETE .../icon` | the same |
|
|
51
|
-
| `project pack-token <key> --
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
55
|
+
| `project pack-token <key> --connect` | `POST .../pack-token/connect`, then `GET .../pack-token/connect` at the interval the deployment named, until the token is kept or the request ended | the page at SkillCDN and the code for a person who may open the repository, then that the pack is connected and the project as it is now, never the token; `1` when the code expired, the request was declined or the deployment refused; for an owner ([ADR-0036](../adr/0036-a-private-pack-is-connected-with-a-code-a-person-approves-from-the-projects-settings.md)) |
|
|
56
|
+
| `project pack-token <key> --stdin`, `project pack-token <key> --remove` | `PUT .../pack-token` with the token read from standard input, or `DELETE .../pack-token` | the project as it is now, saying when the SkillCDN token was set and never the token; for an owner ([ADR-0028](../adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md)) |
|
|
57
|
+
| `members` | `GET .../members` | one line per member: login, role, since when; in a project open to the workspace, everyone of it ([ADR-0035](../adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md)) |
|
|
58
|
+
| `member add <login> [--role owner\|member] [--project <key>[,<key>...]]`, `member role <login> <owner\|member>`, `member remove <login>` | `GET /api/v1/people?login=` for the id, then `POST .../members` (once per project when `--project` names several with commas), `PATCH .../members/<id>`, `DELETE .../members/<id>` | the member as they are now, or that they are no longer listed; with several projects, one line per project, a person listed already said so; for an owner. In a project open to the workspace everyone is a member already: `add` lists an owner with `--role owner` and refuses a member (`member.open_project`), `role <login> owner` lists one and `role <login> member` takes the listing away, `remove` takes a listing away and the person stays a member |
|
|
54
59
|
| `category new <key> <name> [--color <name>]`, `category edit <key> [--name <name>] [--color <name>]`, `category remove <key>` | `POST /api/v1/categories`, `PATCH .../categories/<key>`, `DELETE .../categories/<key>` | the category as it is now, or that it is gone; for an administrator |
|
|
55
60
|
| `skills`, `skills <folder>`, `skills --search <words> [<folder>] [--limit <n>] [--cursor <cursor>]` | `GET .../skills`; `GET .../skills/browse?path=&cursor=`, page after page to the end; `GET .../skills/find?query=&path=&cursor=&limit=`, one page | the project's pack, or the organization's ([ADR-0031](../adr/0031-the-console-reads-a-pack-as-the-skillcdn-command-does-and-a-local-one-from-a-directory.md)): where it is, at a deployment or on the console's machine, and how the read went; what a client is told of it on connect, as the deployment tells one, with the reads named as the console's commands; every skill with the path of its `SKILL.md` and every document, as a search without words lists them; where a person connects an agent of its own, and where the console's own skills are on this machine. With a folder (`.` for the root; `/` is taken too, but a Windows shell rewrites it into a path before the command sees it), what it holds, whole, as `browse_repo` lays it out for a model. With `--search`, one page of what the words find, as `search_repo` does: skills first with the files of theirs that matched, then documents, in relevance order, within the folder when one is named, ten unless `--limit` says otherwise (up to 25), and a `nextCursor` line when more matched, which `--cursor` continues. `--json` is the console's answer: what the pack answered, in the shapes of the main repository's REST API, the pages of a folder joined |
|
|
56
61
|
| `skill <path>`, `skill --file <path>` | `GET .../skills`, then `GET .../skills/one?path=&cursor=`, page after page until `complete`; with `--file`, `GET .../skills/file?path=&offset=` until the end | the skill whole, as `load_skill` lays it out for a model: the rules of the repository from its root to the nearest manifest, the instructions, the included files, then its supporting files and what it refers to, rendered by the main repository's own renderers with the reads named as the console's commands; or the file whole, or what a folder holds; a private pack through the pack token the project holds ([ADR-0028](../adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md)). `<path>` is the path of the SKILL.md from the repository's root, or the URI the pack names it by (`skill://<host>/<owner>/<name>/<path>`); a URI of another pack is not understood. `--file` takes no skill's path beside it: the two given together are not understood. A read the pack refused prints its status and the pack's own words, and exits with `0` |
|
|
57
62
|
| `rules` | `GET .../rules`, or `GET /api/v1/rules` without a project named, and `GET /api/v1/me` | the rules that hold, as they read for the person (`{language}` is the language they chose, or the task's) ([ADR-0023](../adr/0023-the-rules-of-a-board-are-pages-the-workspace-and-its-projects-keep-read-first-by-every-agent.md)): the workspace's, the console's own until an administrator writes the organization's, with its version and who wrote it when written; then this project's own, or that it adds none |
|
|
58
63
|
| `rules write --body <markdown>\|--file <path>\|-\|none [--workspace] [--base <version>]` | `PUT .../docs/rules` or `POST .../docs/rules/archive`; with `--workspace`, `PUT /api/v1/rules` or `POST /api/v1/rules/archive` | the version written, that the console's own rules sent as they are wrote nothing, or that the rules are put away; for an owner of the project, or an administrator for the workspace's |
|
|
59
|
-
| `take <number\|id> [--agent <name>]` | `GET .../tasks/<ref>`, then `POST .../runs`, then `GET .../rules`, `GET /api/v1/me`, `GET .../tasks/<ref>` again, `GET .../tasks/<id>/comments` and `GET .../runs?open=true&mine=true` | the run that began, the task in full as it stands taken with the discussion on it, the rules that hold as they read for the person, and what to do next; a run open on the task is taken over, and that is said when it happened ([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)); another run of the token open in the project is named, with the `--run <id>` to say on every command on a run from here |
|
|
64
|
+
| `take <number\|id> [--agent <name>]` | `GET .../tasks/<ref>`, then `POST .../runs`, then `GET .../rules`, `GET /api/v1/me`, `GET .../tasks/<ref>` again, `GET .../tasks/<id>/comments` and `GET .../runs?open=true&mine=true` | the run that began, with the run's id and the task's id on a line of their own, the task in full as it stands taken with the discussion on it, the rules that hold as they read for the person, and what to do next; a run open on the task is taken over, and that is said when it happened ([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)); another run of the token open in the project is named, with the `--run <id>` to say on every command on a run from here |
|
|
60
65
|
| `report <markdown> [--run <id>]`, `report --file <path> [--run <id>]`, `report - [--run <id>]` | `POST .../runs/<id>/reports` | how many reports the run carries |
|
|
61
66
|
| `hand-in <https url \| file path> [--label <words>] [--run <id>]` | `POST .../runs/<id>/artifacts` for a link; `POST .../runs/<id>/files` for a file, sent as a form with its name and its media type by extension | how many artifacts; a picture handed in is shown on the task, and where a report names it as `` |
|
|
62
67
|
| `comment <number\|id> "<markdown>" [--attach <path>]...`, `comment <number\|id> --file <path>`, `comment <number\|id> -` | `GET .../tasks/<ref>`, then `POST .../tasks/<id>/comments`: as JSON, or as one form with the part `body` and a part `file` per file attached, its media type by extension | the comment as written, with its files and the command that reads each; a word to the task's people, which needs no run ([ADR-0024](../adr/0024-a-task-carries-a-discussion-of-comments-with-their-files-read-as-one-thread-with-the-runs-and-the-decisions.md)) |
|
|
63
68
|
| `file <id> [--out <path>]` | `GET .../files/<id>` | that the file is saved, where, with its size and type: under its own name in the working directory, at the path, or into the directory named; a file a run handed in, one put in a comment, or one attached to a page, so that an agent looks at a picture a person put on the board |
|
|
64
|
-
| `ask "<question>" --option "<label>" ... [--body <markdown>\|--file <path>] [--wait <seconds>] [--run <id>]` | `POST .../decisions` with `runId`, then `GET .../decisions/<id>?wait=` | the decision raised, its options; then the answer, or that it still waits |
|
|
69
|
+
| `ask "<question>" --option "<label>" ... [--body <markdown>\|--file <path>] [--wait <seconds>] [--run <id>]` | `POST .../decisions` with `runId`, then `GET .../decisions/<id>?wait=` | the decision raised, its id on a line of its own, its options; then the answer, or that it still waits |
|
|
65
70
|
| `decision <id> [--wait <seconds>]` | `GET .../decisions/<id>`, with `?wait=` while waiting | the decision; with a wait, the answer or that it still waits |
|
|
66
71
|
| `decisions [--open] [--limit <n>] [--offset <n>]` | `GET .../decisions` | one line per decision; a page at a time |
|
|
67
72
|
| `finish [--summary <markdown>\|--file <path>] [--run <id>]`, `fail [...]` | `POST .../runs/<id>/end` | how the run ended |
|
|
@@ -78,7 +83,7 @@ The board is a project's ([ADR-0008](../adr/0008-a-workspace-holds-projects-and-
|
|
|
78
83
|
| `help [<command>]` | nothing | the usage, written for an agent that meets the command for the first time |
|
|
79
84
|
| `--version` (also `version`, `-v`) | nothing | which version of the command this is, and where it is installed |
|
|
80
85
|
|
|
81
|
-
`...` is `/api/v1/projects/<key>`, the project the command works in. Everywhere: `--json` prints the console's own answer as JSON, in the shapes of `@skillcdn/console/api`; `--url <origin>` names the console this one command talks to, which must be one signed in to here; `--account <login>` the account there, where several are; `--project <key>` the project. A Markdown body given as `-` is read from standard input. A day is given as `2026-10-24`, `today`, `tomorrow` or `+7` (days from today by this machine's clock); `--due overdue` keeps the open tasks past their day, `--due week` those due
|
|
86
|
+
`...` is `/api/v1/projects/<key>`, the project the command works in. Everywhere: `--json` prints the console's own answer as JSON, in the shapes of `@skillcdn/console/api`; `--url <origin>` names the console this one command talks to, which must be one signed in to here; `--account <login>` the account there, where several are; `--project <key>` the project. A Markdown body given as `-` is read from standard input. A day is given as `2026-10-24`, `today`, `tomorrow` or `+7` (days from today by this machine's clock); `--due overdue` keeps the open tasks past their day, `--due week` those due by the last day of this week, the week beginning as the workspace says (`console workspace`) or, left to the language, as the person's language has it, else this machine's ([ADR-0039](../adr/0039-this-week-is-the-plans-unit-and-the-week-begins-as-the-workspace-says.md)). Every argument is bounded and checked by the console, as the REST API's schemas say; the command sends it as it was given.
|
|
82
87
|
|
|
83
88
|
## The documents
|
|
84
89
|
|
|
@@ -86,13 +91,13 @@ A project's pages of Markdown ([ADR-0009](../adr/0009-documents-are-pages-of-mar
|
|
|
86
91
|
|
|
87
92
|
## Configuring, as the person
|
|
88
93
|
|
|
89
|
-
What an administrator or an owner does on the pages, their agent does with `people`, `person`, `project`, `members`, `member`, `category` and `rules write` ([ADR-0021](../adr/0021-a-token-configures-as-its-person-does.md)): the console decides on every request with the same roles, so a member's agent is refused what a member is (`auth.forbidden`), and the event names the agent beside the person. `console help` lists them under "Configuring, as the person", each with its limits and its refusals by code: `project.key_taken`, `project.invalid_address`, `project.invalid_icon`, `member.invalid_person`, `member.exists`, `member.not_found`, `category.key_taken`, `category.in_use`, `category.too_many`, `person.last_admin`, `person.of_organization`, `listing.exists`, `listing.not_found`, `listing.unknown_host`. A person is named by login, as `--
|
|
94
|
+
What an administrator or an owner does on the pages, their agent does with `people`, `person`, `project`, `members`, `member`, `category` and `rules write` ([ADR-0021](../adr/0021-a-token-configures-as-its-person-does.md)): the console decides on every request with the same roles, so a member's agent is refused what a member is (`auth.forbidden`), and the event names the agent beside the person. `console help` lists them under "Configuring, as the person", each with its limits and its refusals by code: `project.key_taken`, `project.invalid_address`, `project.invalid_icon`, `member.invalid_person`, `member.exists`, `member.not_found`, `category.key_taken`, `category.in_use`, `category.too_many`, `person.last_admin`, `person.of_organization`, `listing.exists`, `listing.not_found`, `listing.unknown_host`. A person is named by login, as `--worker` names one; `console people` lists the logins. The tokens themselves have no command but `logout`, which gives the command's own up: they are made and removed on the Agents page, signed in, so that a token which leaks cannot outlive its removal.
|
|
90
95
|
|
|
91
96
|
## The run a command means
|
|
92
97
|
|
|
93
98
|
`report`, `hand-in`, `ask`, `finish` and `fail` act on a run. Named with `--run <id>`, that one; else the one open run begun with this token in the project (`GET .../runs?open=true&mine=true`). None open, or several: the command exits with `1` and says so as `run.none` or `run.ambiguous`, listing the open ones, so that an agent working two tasks at once says which; `take` says the same when the run it begins is beside another of the token's.
|
|
94
99
|
|
|
95
|
-
`tasks`, `decisions` and `runs` show a page at a time: `DEFAULT_PAGE_LIMIT` lines unless `--limit <n>` says otherwise (up to `LIST_LIMIT`), from `--offset <n>` on, and a last line, `... more: --offset <n>`, says how to ask for the next page when there is one; with `--json`, the console's answer as it is, `more` included. An agent reads the first page and asks for the next only when what it looks for is not on it.
|
|
100
|
+
`tasks`, `goals`, `decisions` and `runs` show a page at a time: `DEFAULT_PAGE_LIMIT` lines unless `--limit <n>` says otherwise (up to `LIST_LIMIT`), from `--offset <n>` on, and a last line, `... more: --offset <n>`, says how to ask for the next page when there is one; with `--json`, the console's answer as it is, `more` included. An agent reads the first page and asks for the next only when what it looks for is not on it.
|
|
96
101
|
|
|
97
102
|
## Waiting for a decision
|
|
98
103
|
|
|
@@ -112,7 +117,7 @@ Plain text on standard output: one line per thing in a list, a few lines for one
|
|
|
112
117
|
| `1` | The console refused, or could not be reached; the reason is on standard error. |
|
|
113
118
|
| `2` | The command was not understood. |
|
|
114
119
|
| `3` | The decision still waits for a person. |
|
|
115
|
-
| `4` | The command and the console do not go together ([ADR-0025](../adr/0025-the-command-names-its-version-on-every-request-and-the-console-holds-it-to-the-range-it-takes.md)); standard error says which to update, and how. |
|
|
120
|
+
| `4` | The command and the console do not go together ([ADR-0025](../adr/0025-the-command-names-its-version-on-every-request-and-the-console-holds-it-to-the-range-it-takes.md), [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)); standard error says which to update, and how. |
|
|
116
121
|
|
|
117
122
|
## What an agent is told
|
|
118
123
|
|
package/docs/specs/rest.md
CHANGED
|
@@ -13,7 +13,7 @@ The API is the board as its members see it: the people, the projects, and in eac
|
|
|
13
13
|
- A token ([`POST /api/v1/tokens`](#get-apiv1tokens-and-post-apiv1tokens)) is presented as `Authorization: Bearer <secret>` on any route, by an agent, a script or a console of a person's own. It is then the credential: a cookie beside it is not looked at, and no origin is needed, since nothing attaches a token but whoever holds it. A token that is nothing is refused, whatever cookie travels with it. What a token does is attributed to its person, and the event names the agent: the token's name, or what the agent called itself on its run.
|
|
14
14
|
- A token configures as its person does ([ADR-0021](../adr/0021-a-token-configures-as-its-person-does.md)): every request a session may make, a token may make, within the same roles, except the tokens themselves and the approval of a connection, which stay a person's own doing, signed in on the console's own pages; the one thing a token does about the tokens is give itself up (`DELETE /api/v1/me/token`).
|
|
15
15
|
- An agent connects without anyone copying a token ([ADR-0011](../adr/0011-an-agent-connects-with-a-short-code-a-person-approves-and-the-token-is-never-shown.md)): [`POST /api/v1/connect`](#connecting-an-agent) is nobody's, the approval is a person's own doing on the console's own pages, and the claim is the command's, with the secret it was given.
|
|
16
|
-
- 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
|
|
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
18
|
- Errors have `{ "error": { "code": "...", "message": "..." } }`. The code is stable; the message is for a person, in English.
|
|
19
19
|
|
|
@@ -23,10 +23,12 @@ The API is the board as its members see it: the people, the projects, and in eac
|
|
|
23
23
|
| 400 | `project.invalid_address` | The skills address is not one, as the standard spells them. |
|
|
24
24
|
| 400 | `project.pack_token_refused` | The deployment does not serve the project's pack to the pack token given, or the project names no pack ([ADR-0028](../adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md)). |
|
|
25
25
|
| 400 | `project.invalid_icon` | The image for the mark is not a PNG, a JPEG, a GIF or a WebP, by its first bytes. |
|
|
26
|
+
| 400 | `project.pack_connect_refused` | The project names no pack to connect, the pack is on the console's machine, or the deployment refused to begin connecting it, in its own words ([ADR-0036](../adr/0036-a-private-pack-is-connected-with-a-code-a-person-approves-from-the-projects-settings.md)). |
|
|
27
|
+
| 409 | `project.pack_connect_unsupported` | The console reaches no deployment it could connect a pack at, or keeps no secret to seal a token with. |
|
|
26
28
|
| 404 | `project.no_icon` | The project has no image for its mark. |
|
|
27
29
|
| 400 | `member.invalid_person` | The person to list is not one of the workspace, or is deactivated. |
|
|
28
30
|
| 400 | `listing.unknown_host` | The login to let in names a provider nobody signs in through here. |
|
|
29
|
-
| 400 | `task.
|
|
31
|
+
| 400 | `task.invalid_worker` | A person named as a worker is not one who may work in the project. |
|
|
30
32
|
| 400 | `task.invalid_parent` | The parent is not a task of the project, is the task itself, or would make a loop. |
|
|
31
33
|
| 400 | `task.invalid_category` | No category of the workspace has that key. |
|
|
32
34
|
| 400 | `task.invalid_goal` | The goal is not one of the project's. |
|
|
@@ -55,6 +57,7 @@ The API is the board as its members see it: the people, the projects, and in eac
|
|
|
55
57
|
| 404 | `not_found` | Nothing at this path. |
|
|
56
58
|
| 409 | `project.key_taken` | A project of the workspace has that key already. |
|
|
57
59
|
| 409 | `member.exists` | The person is listed in the project already. |
|
|
60
|
+
| 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)). |
|
|
58
61
|
| 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`). |
|
|
59
62
|
| 409 | `decision.answered` | The decision has an answer already. |
|
|
60
63
|
| 409 | `document.conflict` | The page has moved on since the version the write started from (`baseVersion`), or was written at that path by someone else first. |
|
|
@@ -62,26 +65,30 @@ The API is the board as its members see it: the people, the projects, and in eac
|
|
|
62
65
|
| 409 | `document.too_many_versions`, `document.too_many_files` | The page carries as many versions, or files, as one may (`MAX_VERSIONS_PER_DOCUMENT`, `MAX_FILES_PER_DOCUMENT`). |
|
|
63
66
|
| 409 | `token.too_many` | The person holds as many live tokens as one may (`MAX_TOKENS_PER_PERSON`). |
|
|
64
67
|
| 409 | `connect.approved` | The connection was approved already; the command claims its token. |
|
|
68
|
+
| 400 | `workspace.pack_token_refused` | The deployment does not serve the organization's pack to the SkillCDN token given, or the console names no address for the organization's skills. |
|
|
69
|
+
| 400 | `workspace.pack_connect_refused` | The console names no address for the organization's skills, the pack is on the console's machine, or the deployment refused to begin connecting, in its words. |
|
|
65
70
|
| 409 | `project.pack_token_unsupported` | The console keeps no secret to seal a pack token with: nobody signs in here (`AUTH_SECRET`). |
|
|
66
71
|
| 429 | `connect.too_many` | As many agents ask to connect as the console holds at once; try again in a few minutes. |
|
|
72
|
+
| 409 | `workspace.pack_token_unsupported` | The console keeps no secret to seal a SkillCDN token with: nobody signs in here (`AUTH_SECRET`). |
|
|
73
|
+
| 409 | `workspace.pack_connect_unsupported` | The console reaches no deployment it could connect the organization's pack at, or keeps no secret to seal with. |
|
|
67
74
|
| 503 | `skills.unavailable` | The SkillCDN deployment could not be reached to try a pack token against; try again later. |
|
|
68
75
|
| 409 | `person.last_admin` | The change would leave the board without an administrator in. |
|
|
69
76
|
| 409 | `person.of_organization` | The organization let the person in, and says whether they are in; an administrator lists their login to keep one who left ([ADR-0022](../adr/0022-who-is-in-is-the-consoles-own-record-and-an-administrator-keeps-it.md)). |
|
|
70
77
|
| 409 | `listing.exists` | The login is let in already, or is one an active person signs in as. |
|
|
71
78
|
| 409 | `run.over`, `run.task_closed` | The run has ended already; the task is done or dropped. A run open on a task is no refusal: a new run takes it over ([ADR-0020](../adr/0020-the-console-does-not-claim-an-agent-is-at-work-a-run-says-when-it-was-last-heard-from-and-a-new-run-takes-over.md)). |
|
|
72
79
|
| 413 | `request.too_large` | The body is over the limit; for a file handed in or attached, the file is over `MAX_FILE_BYTES`. |
|
|
73
|
-
| 426 | `client.outdated`, `client.too_new` | The command named a version outside the range the console takes ([ADR-0025](../adr/0025-the-command-names-its-version-on-every-request-and-the-console-holds-it-to-the-range-it-takes.md)):
|
|
80
|
+
| 426 | `client.outdated`, `client.too_new` | The command named a version outside the range the console takes ([ADR-0025](../adr/0025-the-command-names-its-version-on-every-request-and-the-console-holds-it-to-the-range-it-takes.md), [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)): below the floor the console names, of an older line, or of a line it does not take yet. The message names both versions and the range; `console-version` and `console-client-range` say them too. `console.outdated` is the command's own refusal of a console older than it works with, in this form. |
|
|
74
81
|
| 503 | `file.store_unavailable` | The bytes of files are kept in a bucket, and it did not answer or refused the console; `retry-after` says when to try again. Nothing was written on the run or the page. |
|
|
75
82
|
|
|
76
83
|
## The workspace
|
|
77
84
|
|
|
78
85
|
### `GET /api/v1/me`
|
|
79
86
|
|
|
80
|
-
`{ "workspace": { "name", "tokenDaysAtMost", "skills": { "source", "address" } }, "person": ... | null, "agent": "..." | null, "language": "..." | null, "signIn": [{ "key", "label" }, ...], "console": { "version", "clientRange" } }`: what the board is called, the most days a token may be good for here (or `null` where a person chooses), where the organization's skills come from (`source`, the origin of the SkillCDN deployment they are read through, and `address`, the organization's, canonical, which a project without one of its own reads, or `null`), who the session cookie or the token says is asking (`id`, `login`, `name` or `null`, `avatar` or `null`, `role`), with what (`agent`: the name of the token when a token asks, as its person called it, so that a console of a person's own knows it holds one and offers nothing a token cannot do; `null` on a session), the language the person chose for the pages (`language`, a tag, or `null` for the browser's), and the identity providers people sign in through (`gh`, `google`, each with the label of its button), none where nobody can; and the console's own version with the range of command versions it takes (`console`, [ADR-0025](../adr/0025-the-command-names-its-version-on-every-request-and-the-console-holds-it-to-the-range-it-takes.md)), for a console built on the package to compare itself with. Nobody is an answer, not an error; the pages ask this once to decide what to show, and the command once, at `login`, to see that the console knows the token.
|
|
87
|
+
`{ "workspace": { "name", "tokenDaysAtMost", "skills": { "source", "address", "packToken" }, "peopleVisibility" }, "person": ... | null, "agent": "..." | null, "language": "..." | null, "signIn": [{ "key", "label" }, ...], "console": { "version", "clientRange" } }`: what the board is called, the most days a token may be good for here (or `null` where a person chooses), where the organization's skills come from (`source`, the origin of the SkillCDN deployment they are read through, and `address`, the organization's, canonical, which a project without one of its own reads, or `null`; and `packToken`, when the SkillCDN token the organization's pack is read with was set, `{ setAt }`, or `null`), who sees whom on the People page (`peopleVisibility`: `everyone`, or `shared_projects`, where a member sees only the people they share a project with and an administrator everyone; [ADR-0035](../adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md); read as optional, for a console from before the setting), the day the calendars begin a week on and this week is counted by (`weekStart`: `auto`, as the language of the pages has it, or `monday`, `sunday`; [ADR-0039](../adr/0039-this-week-is-the-plans-unit-and-the-week-begins-as-the-workspace-says.md)) and the workspace's time zone (`timeZone`: an IANA name, in which the command prints moments, or `null` for none; both optional the same way), who the session cookie or the token says is asking (`id`, `login`, `name` or `null`, `avatar` or `null`, `role`), with what (`agent`: the name of the token when a token asks, as its person called it, so that a console of a person's own knows it holds one and offers nothing a token cannot do; `null` on a session), the language the person chose for the pages (`language`, a tag, or `null` for the browser's), and the identity providers people sign in through (`gh`, `google`, each with the label of its button), none where nobody can; and the console's own version with the range of command versions it takes (`console`, [ADR-0025](../adr/0025-the-command-names-its-version-on-every-request-and-the-console-holds-it-to-the-range-it-takes.md)), for a console built on the package to compare itself with. Nobody is an answer, not an error; the pages ask this once to decide what to show, and the command once, at `login`, to see that the console knows the token.
|
|
81
88
|
|
|
82
|
-
### `GET /api/v1/people
|
|
89
|
+
### `GET /api/v1/people?login=`
|
|
83
90
|
|
|
84
|
-
`{ "items": [person, ...], "listed": [listing, ...] }`:
|
|
91
|
+
`{ "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 `deactivatedAt` (or `null`). A listing carries its `id`, the `host` (`gh`, `google`), the `login` as the administrator spelled it, and `createdAt`. `organization` is the organization at the git host whose members are in, where the deployment names one (`host`, `name`, and `syncedAt`, when the worker last read it whole, or `null` until it has), or `null`. What a picker of 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`.
|
|
85
92
|
|
|
86
93
|
### `PATCH /api/v1/people/<id>`
|
|
87
94
|
|
|
@@ -101,12 +108,24 @@ Takes `key` and `name`, and optionally `description`, `visibility` (default `pri
|
|
|
101
108
|
|
|
102
109
|
### `GET /api/v1/events?after=&limit=`
|
|
103
110
|
|
|
104
|
-
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`),
|
|
111
|
+
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.
|
|
105
112
|
|
|
106
113
|
### `PATCH /api/v1/me`
|
|
107
114
|
|
|
108
115
|
Keeps the person's own choice of language for the pages ([ADR-0012](../adr/0012-the-pages-speak-the-persons-language-from-packs-that-ship-with-the-package.md)): `{ "language": "ko" }`, a language tag as a browser sends one, or `null` to choose none and let the browser's languages decide; 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.
|
|
109
116
|
|
|
117
|
+
### `PATCH /api/v1/workspace`
|
|
118
|
+
|
|
119
|
+
The workspace's settings, which an administrator keeps on the Settings page ([ADR-0035](../adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md)), from the console's own pages or through their agent's token ([ADR-0021](../adr/0021-a-token-configures-as-its-person-does.md)): `{ "peopleVisibility": "everyone" | "shared_projects", "weekStart": "auto" | "monday" | "sunday", "timeZone": "Asia/Seoul" | null }`, which of the settings to change, at least one, a time zone being a name the console knows (`request.invalid` otherwise, as for a patch that names nothing); answers the `workspace` as `GET /api/v1/me` describes it, and writes `workspace.updated` with the `fields` that changed; what is already so writes nothing. `auth.forbidden` for a member. The setting holds from the next request on, for everyone: nothing of it is kept in memory.
|
|
120
|
+
|
|
121
|
+
### `PUT /api/v1/workspace/pack-token` and `DELETE .../pack-token`
|
|
122
|
+
|
|
123
|
+
The SkillCDN token the organization's pack is read with ([ADR-0036](../adr/0036-a-private-pack-is-connected-with-a-code-a-person-approves-from-the-projects-settings.md)), for an administrator, signed in from the console's own pages or through their agent's token: every project that names no address of its own and holds no token of its own reads the organization's pack (`SKILLS_ADDRESS`) with it. `PUT` takes `{ "token" }` as the project's route does, tries it against the organization's pack once (`workspace.pack_token_refused` when the pack is not served to it or the console names no address for the organization's skills; `skills.unavailable` when the deployment could not be reached), seals it with the console's secret (`workspace.pack_token_unsupported` where the console has none) and keeps it on the workspace, answering the workspace as `me` carries it, with `skills.packToken: { setAt }`, and writing a `workspace.updated` event naming the field `packToken`. The token is answered nowhere and logged nowhere. `DELETE` takes it away the same way; taking away what is not there writes nothing.
|
|
124
|
+
|
|
125
|
+
### `POST /api/v1/workspace/pack-token/connect`, `GET .../pack-token/connect` and `DELETE .../pack-token/connect`
|
|
126
|
+
|
|
127
|
+
The workspace's token connected with a code a person approves ([ADR-0036](../adr/0036-a-private-pack-is-connected-with-a-code-a-person-approves-from-the-projects-settings.md)), for an administrator, as a project's is: `POST` asks the deployment to connect the organization's address and answers `201` with `{ "code", "url", "expiresAt", "intervalSeconds" }`, with `workspace.pack_connect_unsupported`, `workspace.pack_connect_refused` and `skills.unavailable` where a project's route has its own, the refusal naming a console with no address for the organization's skills; `GET` answers `{ "status": "none" }`, `pending` with what the person is shown, `connected` with `workspace` as `me` carries it once the token is kept, or `ended` with `reason`; `DELETE` gives the request up. One request for the workspace at a time.
|
|
128
|
+
|
|
110
129
|
### `GET /api/v1/categories`
|
|
111
130
|
|
|
112
131
|
`{ "items": [category, ...] }`: the kinds of work tasks are sorted into ([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)), by name, for whoever is signed in or presents a token. A category carries its `key` (what the command and the filters say; lowercase letters, digits and hyphens; immutable), `name`, `color` (one of `CATEGORY_COLORS`, a name the pages paint and never a value typed), `createdAt` and `updatedAt`. Every workspace starts with `engineering`, `product`, `design`, `marketing` and `operations`.
|
|
@@ -121,7 +140,7 @@ The rules of the board ([ADR-0023](../adr/0023-the-rules-of-a-board-are-pages-th
|
|
|
121
140
|
|
|
122
141
|
### `GET /api/v1/work`
|
|
123
142
|
|
|
124
|
-
`{ "tasks": [{ "project": { "key", "name", "color", "symbol", "icon" }, "task" }, ...], "closed": [{ "project", "task" }, ...], "decisions": [{ "project", "decision" }, ...] }`: what is on the asker's plate across the projects they may see ([ADR-0019](../adr/0019-a-task-is-planned-in-days-the-board-keeps-its-moments-and-goals-and-categories-give-the-work-its-shape.md)): the open tasks
|
|
143
|
+
`{ "tasks": [{ "project": { "key", "name", "color", "symbol", "icon" }, "task" }, ...], "closed": [{ "project", "task" }, ...], "decisions": [{ "project", "decision" }, ...] }`: what is on the asker's plate across the projects they may see ([ADR-0019](../adr/0019-a-task-is-planned-in-days-the-board-keeps-its-moments-and-goals-and-categories-give-the-work-its-shape.md)): the open tasks they are at work on, the sooner due first and those with no day last; `closed`, the tasks they are at work on that are done or dropped and not yet put away, the last closed first, so that the week on the plate's page shows them on their days as a project's calendar does; and the decisions that wait for a person, the longest waiting first; each with the project it belongs to, so that a page says and links it. A task put away is in neither list ([ADR-0030](../adr/0030-a-task-is-put-away-from-any-state.md)). At most `LIST_LIMIT` of each.
|
|
125
144
|
|
|
126
145
|
### `GET /api/v1/tokens` and `POST /api/v1/tokens`
|
|
127
146
|
|
|
@@ -141,7 +160,7 @@ An administrator's sight of everyone's agents ([ADR-0011](../adr/0011-an-agent-c
|
|
|
141
160
|
|
|
142
161
|
### `GET /api/v1/people/<id>/work`
|
|
143
162
|
|
|
144
|
-
`{ "tasks": [{ "project", "task" }, ...], "closed": [{ "project", "task" }, ...] }`: what is on another person's plate, for whoever oversees them: an administrator, or an owner of a project the person is in (everyone of the workspace is in a project open to it; a private one holds those listed), signed in or through their agent. The person
|
|
163
|
+
`{ "tasks": [{ "project", "task" }, ...], "closed": [{ "project", "task" }, ...] }`: what is on another person's plate, for whoever oversees them: an administrator, or an owner of a project the person is in (everyone of the workspace is in a project open to it; a private one holds those listed), signed in or through their agent. The open tasks the person is at work on, the sooner due first and those with no day last, and as `closed` those of them done or dropped and not yet put away, both within the projects the asker may see and no other, each with its project; the decisions are left out, since they wait for anyone. `auth.forbidden` for anyone else; `person.not_found` for an id that is nobody's.
|
|
145
164
|
|
|
146
165
|
### Connecting an agent
|
|
147
166
|
|
|
@@ -167,23 +186,23 @@ The project's own image for its mark, in place of the symbol on the colour. `POS
|
|
|
167
186
|
|
|
168
187
|
### `GET /api/v1/projects/<key>/members` and `POST /api/v1/projects/<key>/members`
|
|
169
188
|
|
|
170
|
-
`{ "items": [member, ...] }`:
|
|
189
|
+
`{ "items": [member, ...] }`: the members of the project, by login, each a `person`, their `role` in it (`owner`, `member`), `addedAt` and `listed` ([ADR-0035](../adr/0035-who-may-see-whom-is-decided-by-one-policy-and-the-workspace-keeps-its-settings.md)). In a private project, those listed. In a project open to the workspace everyone active of it is a member, so the list is everyone: an owner where a listing says so or the person is an administrator, a member otherwise; `listed` says whether a listing holds them, and `addedAt` is the listing's day or, for a member without one, the later of the project's making and their joining. An administrator is an owner of every project, listed or not. `POST`, for an owner, takes `personId` (a person of the workspace; `member.invalid_person`) and optionally `role` (default `member`), answers `201` with the member and writes `project.member_added` with `login` and `role`. A person listed already is `member.exists`; in a project open to the workspace everyone is a member already, so a `role` of `member` is `member.open_project` and only an owner is listed.
|
|
171
190
|
|
|
172
191
|
### `PATCH /api/v1/projects/<key>/members/<person id>` and `DELETE .../members/<person id>`
|
|
173
192
|
|
|
174
|
-
For an owner, signed in or with a token. The patch takes `role` and writes `project.member_changed`; the removal answers `204` and writes `project.member_removed
|
|
193
|
+
For an owner, signed in or with a token. The patch takes `role` and writes `project.member_changed`; in a private project the person must be listed (`member.not_found`). In a project open to the workspace anyone active of it may be made an `owner`, which lists them, and an owner made a `member` again loses the listing and stays the member everyone is, answered with `listed: false`; an administrator owns every project whatever is asked, and nothing is written for them. The removal takes a listing away, answers `204` and writes `project.member_removed`; a person not listed is `member.not_found`, and in a project open to the workspace the person stays a member. A project may be left without an owner listed; the administrators own it then.
|
|
175
194
|
|
|
176
|
-
### `GET /api/v1/projects/<key>/tasks?state=&open=&archived=&category=&goal=&
|
|
195
|
+
### `GET /api/v1/projects/<key>/tasks?state=&open=&archived=&category=&goal=&worker=&unassigned=&dueBy=&q=&limit=&offset=`
|
|
177
196
|
|
|
178
|
-
`{ "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; `
|
|
197
|
+
`{ "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`.
|
|
179
198
|
|
|
180
199
|
### `POST /api/v1/projects/<key>/tasks`
|
|
181
200
|
|
|
182
|
-
Takes `title`, and optionally `body`, `state` (default `idea`), `priority` (default `normal`), `
|
|
201
|
+
Takes `title`, and optionally `body`, `state` (default `idea`), `priority` (default `normal`), `workerIds` (the people to be at work on it, each one who may work in the project: everyone of the workspace for a project open to it, those listed for a private one, an administrator anywhere; each once however often named, up to `MAX_WORKERS`; `task.invalid_worker`), `parentId` (a task of the project), `category` (the key of a category of the workspace, or `null`; `task.invalid_category`), `goalId` (a goal of the project, or `null`; `task.invalid_goal`), `startsOn` and `dueOn` (days; the due day not before the start, `task.invalid_dates`) and `links`. The owner is whoever writes it. Answers `201` with the task, numbered after the project's last, and writes a `task.created` event. A task written past `ready` has its `startedAt`; one written `done` has its `doneAt` too.
|
|
183
202
|
|
|
184
203
|
### `GET /api/v1/projects/<key>/tasks/<id or number>` and `PATCH .../tasks/<id>`
|
|
185
204
|
|
|
186
|
-
One task, by its id or by its number in the project (`/tasks/7`): the pages hold ids, people and their agents say numbers. A patch takes the id and names only what changes, with the same fields as a creation; a field set to `null` clears it. A change of `state` is a `task.moved` event with `from` and `to`; any other change is one `task.updated` event naming the `fields` (`category`, `goalId`, `startsOn` and `dueOn` among them, as the input names them). A patch that changes nothing writes nothing and answers the task as it is. The moments follow the moves: the first move past `ready` sets `startedAt`, a move to `done` sets `doneAt`, and a move back out of `done` clears it.
|
|
205
|
+
One task, by its id or by its number in the project (`/tasks/7`): the pages hold ids, people and their agents say numbers. A patch takes the id and names only what changes, with the same fields as a creation; a field set to `null` clears it, and `workerIds` names the workers whole, in place of those there were, only the newcomers being checked, `[]` leaving nobody on it. A change of `state` is a `task.moved` event with `from` and `to`; any other change is one `task.updated` event naming the `fields` (`workers`, `category`, `goalId`, `startsOn` and `dueOn` among them, as the input names them). A patch that changes nothing writes nothing and answers the task as it is. The moments follow the moves: the first move past `ready` sets `startedAt`, a move to `done` sets `doneAt`, and a move back out of `done` clears it.
|
|
187
206
|
|
|
188
207
|
### `POST .../tasks/<id>/archive`, `POST .../tasks/<id>/restore` and `POST /api/v1/projects/<key>/tasks/archive`
|
|
189
208
|
|
|
@@ -213,9 +232,9 @@ The answer takes `option` (the id of one of its options) or `own` (the person's
|
|
|
213
232
|
|
|
214
233
|
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.
|
|
215
234
|
|
|
216
|
-
### `GET /api/v1/projects/<key>/goals
|
|
235
|
+
### `GET /api/v1/projects/<key>/goals?state=&open=&category=&q=&limit=&offset=` and `POST /api/v1/projects/<key>/goals`
|
|
217
236
|
|
|
218
|
-
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, ...] }`: 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;
|
|
237
|
+
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`, `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`.
|
|
219
238
|
|
|
220
239
|
### `GET .../goals/<id>` and `PATCH .../goals/<id>`
|
|
221
240
|
|
|
@@ -245,16 +264,20 @@ Takes `status` (`finished`, `failed` or `abandoned`) and optionally a `summary`
|
|
|
245
264
|
|
|
246
265
|
### `GET /api/v1/projects/<key>/skills`
|
|
247
266
|
|
|
248
|
-
The project's pack, read by address ([ADR-0028](../adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md), [ADR-0031](../adr/0031-the-console-reads-a-pack-as-the-skillcdn-command-does-and-a-local-one-from-a-directory.md)): at the project's address, or at the organization's (`SKILLS_ADDRESS`) when the project names none; at the SkillCDN deployment of `SKILLCDN_URL` for the address of a git host, with the
|
|
267
|
+
The project's pack, read by address ([ADR-0028](../adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md), [ADR-0031](../adr/0031-the-console-reads-a-pack-as-the-skillcdn-command-does-and-a-local-one-from-a-directory.md)): at the project's address, or at the organization's (`SKILLS_ADDRESS`) when the project names none; at the SkillCDN deployment of `SKILLCDN_URL` for the address of a git host, with the SkillCDN token the pack is read with where it is private, the project's own or, for a project that names no address and holds none, the workspace's ([below](#put-apiv1workspacepack-token-and-delete-pack-token)), and from the directory of `SKILLS_DIR` for a `local` address (`/local/<owner>/<name>`), read on the console's machine with the reading rules a deployment applies. `{ "address", "source", "page", "status", "refusal", "pack", "counts", "items", "mount" }`. `address` is canonical, or `null` with `status: "none"` when neither names one; `source` is the deployment's origin and `page` where a person browses the address there, both `null` for a pack read from a directory. When `ready`, `pack` names the pack as its manifest does, else its README, else the host (`name`, `description` and `language`, each or `null`), `counts` says how many `skills` and `documents` the index found, and `items` are the skills: `name`, `description`, `directory` and `path` (of its `SKILL.md`, from the repository's root, which the routes below take), `page` (the skill at the deployment, for a person, or `null`), `uri` (what an agent loads it by through its own SkillCDN connection, `skill://<host>/<owner>/<name>/<path>`, or `null`; one of a `local` pack means something on the console's machine and nowhere else), and `translations` (the title and the description by language tag, as the repository gives them). `mount` is what the address serves as the main repository's REST API answers it (`RestMount` of `@skillcdn/core`), for a program that renders what a model is given, or `null` when nothing was answered. `status` is `ready`, or why `items` is empty: `indexing` and `failed` as the pack says of itself, `not_found` when nothing is served to the console at the address (a private pack is read with the project's pack token; without one the console asks as nobody, so a private repository is not read; a `local` pack is not in the directory), `token_invalid` when the deployment refuses the pack token the project holds (expired, removed, or its maker lost the repository), `unavailable` when the pack could not be reached or read; `refusal` carries the pack's own `code` and `message` then, with `sourceUrl` and `directories` where it gave them (`null` and empty otherwise), and is `null` when the read went well. The console keeps nothing of the skills: a public answer per address stands for a minute, then the pack is asked again; what is read with a pack token is asked every time, and held for nobody.
|
|
249
268
|
|
|
250
269
|
### `GET /api/v1/projects/<key>/skills/one?path=&cursor=`, `.../skills/file?path=&offset=&limit=`, `.../skills/browse?path=&cursor=&limit=` and `.../skills/find?query=&path=&cursor=&limit=`
|
|
251
270
|
|
|
252
|
-
One skill of the pack, a file of it, what a folder holds and what words find, read by the console from the pack for whoever may see the project, with the
|
|
271
|
+
One skill of the pack, a file of it, what a folder holds and what words find, read by the console from the pack for whoever may see the project, with the SkillCDN token the pack is read with, the project's own or the workspace's ([ADR-0028](../adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md)), and answered as the pack answered them ([ADR-0031](../adr/0031-the-console-reads-a-pack-as-the-skillcdn-command-does-and-a-local-one-from-a-directory.md)): `{ "address", "source", "page", "status", "refusal", "read" }`, the envelope as above, and `read` the body of the matching route of the main repository's REST API, as `@skillcdn/core` spells it, or `null` unless `ready`. `skills/one` is `load_skill`: `read` is a `RestSkill`, a page of the skill's context, the rules from the repository's root to the nearest manifest, the body and the included files, which `complete` and `nextCursor` say to continue, and `commit`, the commit it was read from. `skills/file` is `read_repo_file`: `read` is a `RestFile`, a page of the file in characters (`offset`, `nextOffset`, `totalLength`; `limit` 1 to 100,000) or, when the path names a folder, its entries. `skills/browse` is `browse_repo`: `read` is a `RestBrowse`, a page of what the folder holds (`limit` 1 to 200, 50 unless given; `cursor` continues). `skills/find` is `search_repo`: `read` is a `RestFind`, a page of what the words find in relevance order (`query` 1 to 500 characters, required; `limit` 1 to 25, 10 unless given). `path` is a path from the repository's root, up to 500 characters, the mounted folder when `browse` and `find` leave it out; a query out of bounds is `request.invalid`. Each read is logged with who read what of which pack, never with what was read or the words searched for. What the pack refuses is the status with the pack's own `refusal`: a skill or a file that is not there is `not_found`, a file the pack will not carry, too large or not text, is `failed` with where it is at its source. `@skillcdn/core` reads every `read` back as the result it was made from (`skillResultOf`, `fileResultOf`, `directoryResultOf`, `browseResultOf`, `findResultOf`, at the `mountSummaryOf` of the listing's `mount`) and renders what a model is given; the command does exactly that.
|
|
253
272
|
|
|
254
273
|
### `PUT /api/v1/projects/<key>/pack-token` and `DELETE .../pack-token`
|
|
255
274
|
|
|
256
275
|
The pack token ([ADR-0028](../adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md)), for an owner, signed in from the console's own pages or through their agent's token. `PUT` takes `{ "token" }`, a token a person made at the deployment for the pack's repository (8 to 1024 characters, no whitespace), tries it against the deployment once (`project.pack_token_refused` when the pack is not served to it or the project names no pack; `skills.unavailable` when the deployment could not be reached), seals it with the console's secret (`project.pack_token_unsupported` where the console has none) and keeps it, answering the project with `packToken: { setAt }` and writing a `project.updated` event naming the field `packToken`. The token is answered nowhere and logged nowhere. `DELETE` takes it away the same way; taking away what is not there writes nothing.
|
|
257
276
|
|
|
277
|
+
### `POST /api/v1/projects/<key>/pack-token/connect`, `GET .../pack-token/connect` and `DELETE .../pack-token/connect`
|
|
278
|
+
|
|
279
|
+
A private pack connected with a code a person approves ([ADR-0036](../adr/0036-a-private-pack-is-connected-with-a-code-a-person-approves-from-the-projects-settings.md)), for an owner. `POST` asks the deployment to connect the project's address (the project's own, or the organization's), as nobody, the console calling itself by the workspace's name with Console after it, once (`Acme Console`, whether the name ends with it or not), and answers `201` with what the person is shown: `{ "code", "url", "expiresAt", "intervalSeconds" }`, the code, the page at the deployment to approve it on, until when, and how often to ask again; the secret the console claims with is kept sealed on a row of the project's and answered never. One request per project at a time: a new one replaces the one under way. `project.pack_connect_unsupported` (`409`) where the console reaches no deployment or keeps no secret to seal with; `project.pack_connect_refused` (`400`) when the project names no pack, the pack is on the console's machine, or the deployment refused to begin, in its words (an address with a ref or a path, a public pack); `skills.unavailable` (`503`) when the deployment could not be reached. `GET` asks how it stands and answers one of `{ "status": "none" }`, nothing asked; `{ "status": "pending", "code", "url", "expiresAt", "intervalSeconds" }`, while the person has not approved; `{ "status": "connected", "project" }`, once, when the token was claimed, sealed and kept as `PUT .../pack-token` keeps one, writing `project.updated` with `packToken`, the project answered as it stands; or `{ "status": "ended", "reason": "expired" | "declined" | "refused" }`, when the code's time ran out, the person declined or the code was spent, or the deployment refused (a repository the person may not open, as many tokens as one may hold), the request gone each time. A deployment that could not be reached is `skills.unavailable`, and the request stands. `DELETE` gives the request up and answers `204`.
|
|
280
|
+
|
|
258
281
|
### `GET /api/v1/projects/<key>/rules`
|
|
259
282
|
|
|
260
283
|
`{ "workspace": rules, "project": rules }`: the rules that hold in the project ([ADR-0023](../adr/0023-the-rules-of-a-board-are-pages-the-workspace-and-its-projects-keep-read-first-by-every-agent.md)), for whoever may see it, each half shaped as `GET /api/v1/rules` answers: the workspace's, and the project's own, whose `source` is `written`, with its `body` and its `page`, or `none`, with `body` and `page` `null`, when the project adds nothing to the workspace's. A project's rules are the page `rules` of its documents, written and put away through the routes below.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skillcdn/console",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "What a custom SkillCDN console is built from: the API client and schemas, the components, the composition of the default console, and the console command agents work the board with.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/skillcdn/console#readme",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"vitest": "^5.0.3"
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@skillcdn/core": "^0.3.
|
|
71
|
+
"@skillcdn/core": "^0.3.1",
|
|
72
72
|
"react-markdown": "^10.1.0",
|
|
73
73
|
"remark-gfm": "^4.0.1",
|
|
74
74
|
"zod": "^4.6.5"
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: building-a-console
|
|
3
|
-
description: "Builds a console from the @skillcdn/console package: a console of a person's own, served from their machine with their token by console serve; an organization's console, which takes the default UI's place in the image; or a script against the console's REST API. Says where the package's layers are (the API client and schemas, the components, the composition), how the default console is put together, what to configure and what to replace (a component, the language packs, the brand, the styles' tokens), how a build is run against the board and checked, and the rules that keep untrusted content harmless. Use when an agent is asked to make, change or extend a SkillCDN Console UI or a client of its API. To work tasks on the board, use working-the-board; to deploy a console for an organization, use deploying-a-console."
|
|
3
|
+
description: "Builds a console from the @skillcdn/console package: a console of a person's own, served from their machine with their token by console serve; an organization's console, which takes the default UI's place in the image and, given the organization's website, takes its look, the marks, the colours and the type, by default; or a script against the console's REST API. Says where the package's layers are (the API client and schemas, the components, the composition), how the default console is put together, what to configure and what to replace (a component, the language packs, the brand, the styles' tokens), how a look is read from a website and mapped onto the pages, how a build is run against the board and checked, and the rules that keep untrusted content harmless. Use when an agent is asked to make, change, re-skin or extend a SkillCDN Console UI or a client of its API. To work tasks on the board, use working-the-board; to deploy a console for an organization, use deploying-a-console."
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: A shell with Node.js 24 or newer and npm or pnpm, and an agent that reads and writes files. For looking at the pages against a board, the console command signed in by a person. A browser the agent can drive is optional. No key of the agent's own is needed, and it never sees a token.
|
|
6
6
|
metadata:
|
|
7
7
|
author: skillcdn
|
|
8
|
-
version: "1.
|
|
8
|
+
version: "1.5"
|
|
9
9
|
skillcdn:
|
|
10
10
|
translations:
|
|
11
11
|
ko:
|
|
12
12
|
title: 패키지로 콘솔 만들기
|
|
13
|
-
description: "@skillcdn/console 패키지로 콘솔을 만듭니다. console serve로 내 PC에서 내 토큰으로 돌리는 개인 콘솔, 이미지에서 기본 UI 자리를
|
|
13
|
+
description: "@skillcdn/console 패키지로 콘솔을 만듭니다. console serve로 내 PC에서 내 토큰으로 돌리는 개인 콘솔, 이미지에서 기본 UI 자리를 대신하며 조직의 웹사이트가 있으면 그 디자인(마크, 색, 글꼴)을 기본으로 삼는 조직 콘솔, 또는 콘솔 REST API를 쓰는 스크립트. 패키지의 세 계층(API 클라이언트와 스키마, 컴포넌트, 구성)이 어디 있는지, 기본 콘솔이 어떻게 조립되는지, 무엇을 설정하고 무엇을 바꾸는지(컴포넌트, 언어 팩, 브랜드, 스타일 토큰), 웹사이트에서 디자인을 읽어 페이지에 입히는 법, 빌드를 보드에 붙여 돌리고 확인하는 법, 신뢰할 수 없는 내용을 안전하게 다루는 규칙을 말합니다. SkillCDN 콘솔 UI나 API 클라이언트를 만들거나 바꾸거나 디자인을 바꾸라고 할 때 쓰세요. 보드의 작업을 할 때는 working-the-board를, 조직의 콘솔을 배포할 때는 deploying-a-console을 쓰세요."
|
|
14
14
|
---
|
|
15
15
|
# Building a console from the package
|
|
16
16
|
|
|
@@ -50,10 +50,10 @@ Nothing here restates them: they are the contract, current at the version instal
|
|
|
50
50
|
| Which kind | Asked when the request does not say: "Is this console for you alone, served from your machine with your token; for the whole organization, in place of the default pages in the image; or a script against the API?" A member usually wants their own. |
|
|
51
51
|
| Where the organization's build goes | Asked for the organization's: "How is the console run: with the deployment repository that deploying-a-console made, with an image you build yourselves, or from a directory mounted into it?" The answer places the result: the source in `console-ui/` of that repository, which its workflow builds on every push; a repository of its own whose build goes beside the Dockerfile of [deploy/README.md](/deploy/README.md) "A custom console in the image"; or the build in the directory. |
|
|
52
52
|
| What it must show or do | From the request: a page, a component, a language, a brand, a column, a filter, a report. Asked for its missing half: "What should it show or do that the default console does not?" Everything else is the default console's and stays. |
|
|
53
|
-
| The marks |
|
|
54
|
-
| The colour |
|
|
53
|
+
| The marks | From the organization's website when the request, or deploying-a-console, names one: its symbol and wordmark read from the site as [the-look-from-a-website.md](references/the-look-from-a-website.md) says, the light version or, with the person's word, the one-colour mark recoloured, shown at the plan. Else asked for a console that shows marks of its own, for the half the request leaves open: "Which picture should the pages show beside the board's name: a small square mark, the name as lettering, or both? Where is the file? The pages are dark, so it should be a light one." `brand` takes either or both. The symbol's slot is a square of one line's height (`.sc-brand-symbol`, 1rem): a wide mark, a lettermark of three or four times its height, is a wordmark and goes in as one, alone or beside a square symbol. A mark drawn in a dark colour vanishes on the dark pages: ask for a light version, or, with the person's word, recolour it when it is one colour. Default without a site: the board's name alone and no marks, which is the honest console. |
|
|
54
|
+
| The colour | From the website's own colours when one is named, as the reference says, its primary colour read from the theme colour, the stylesheet's properties or the primary button. Else asked when the request names none and marks are wanted: "Which colour, or which mood?" A colour comes as words or a value; the value is the mark's own fill when it has one, else chosen and shown. A light colour becomes the accent (`--sc-color-accent` and its family); a dark one tints the field and its light (`--sc-color-field`, `--sc-color-light-*`), with a lighter shade of it as the accent, since an accent must read on the dark field. The mapping is shown at the plan, with the contrast checked. |
|
|
55
55
|
| The languages | Asked when the organization's language is not among the packs: "Which languages should the pages speak?" Default: the packs the package ships, chosen by the browser; a Korean organization gets Pretendard loaded, as the default console does. |
|
|
56
|
-
| The name | Not asked: the server names the board (`WORKSPACE_NAME`), and `title` is only what the page says until the server answers. `index.html` gets the organization's `<title>` and `lang`. |
|
|
56
|
+
| The name | Not asked: the server names the board (`WORKSPACE_NAME`, conventionally the organization or the person it is for, then Console: `Acme Console`), and `title` is only what the page says until the server answers. `index.html` gets the organization's `<title>` and `lang`. |
|
|
57
57
|
| The console's address | For running one's own, what `console whoami` says. A script takes `CONSOLE_URL` and `CONSOLE_TOKEN` from the environment, never from a file in the repository. |
|
|
58
58
|
|
|
59
59
|
## Workflow
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# The look from a website
|
|
2
|
+
|
|
3
|
+
An organization with a website has a look already: its marks, its colours, its type. A console built for it takes that look by default, so that the board reads as the organization's and not as a second product; deploying-a-console asks for the website and hands it here, and a request for a console that names a site does the same. This page says how the look is read from the site, how it maps onto the pages, and what is left alone.
|
|
4
|
+
|
|
5
|
+
## Reading the site
|
|
6
|
+
|
|
7
|
+
Fetch the homepage as HTML, with the agent's own tools, and read, in this order:
|
|
8
|
+
|
|
9
|
+
| What | Where it is | What to take |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| The symbol | `<link rel="icon">` and `apple-touch-icon` in the head; an inline `<svg>` in the header; an `<img>` whose name or `alt` says logo, mark or brand | The square mark, as SVG where there is one, else the largest PNG. It goes in `brand.symbol`, in a slot one line high (`.sc-brand-symbol`). |
|
|
12
|
+
| The wordmark | The same header: the name as lettering, wide, often an SVG beside or instead of the symbol | `brand.wordmark`, alone or beside the symbol. A mark three or four times wider than high is a wordmark, not a symbol. |
|
|
13
|
+
| The colours | `<meta name="theme-color">`; custom properties on `:root` in the site's stylesheet (`--color-primary`, `--brand`, `--accent` and their kin); the fill of the primary button and the colour of links | One primary colour, as a value; a second where the site plainly has two. |
|
|
14
|
+
| The type | `font-family` of the body and of the headings, in the stylesheet | The names, to tell the person; a face is loaded only as the rules below allow. |
|
|
15
|
+
| The mood | The site itself: dark or light, flat or glassy, round or square corners | Whether the field stays dark (the pages are dark, and stay so), and whether the radii follow the site (`--sc-radius-*`). |
|
|
16
|
+
|
|
17
|
+
A site that cannot be fetched (a login wall, a block, no site after all) ends the reading: say so in one line and ask the person for the files, or keep the honest default.
|
|
18
|
+
|
|
19
|
+
## Mapping onto the pages
|
|
20
|
+
|
|
21
|
+
The pages are dark: a field with a light in it, and panes of glass over it. The mapping is shown at the plan as a table, value by value, before a file is written.
|
|
22
|
+
|
|
23
|
+
- **A mark drawn in a dark colour vanishes** on the dark pages. Look for the site's light or white variant first (a footer, a dark-mode asset, a `prefers-color-scheme` rule). Found none, and the mark is one colour: recolour it to the pages' text colour, with the person's word, since the mark is theirs. A mark of several colours on a dark fill: keep its fill as a rounded square behind it.
|
|
24
|
+
- **A light primary colour** becomes the accent: `--sc-color-accent` and its family (`-hover`, `-subtle`, the point), as the package's README lists them.
|
|
25
|
+
- **A dark primary colour** tints the field and its light: `--sc-color-field` and `--sc-color-light-*`, with a lighter shade of it as the accent, since an accent must read on the dark field.
|
|
26
|
+
- **Contrast is checked**, not assumed: the accent on the field, and text on the panes, at the 4.5:1 of WCAG AA for text; a shade is moved until it passes, and the plan says which.
|
|
27
|
+
- **The type follows the rules of the skill**: scripts, styles and fonts are bundled, never loaded from a CDN or written inline. A face the organization owns, or one under a license that allows bundling, is put in the repository and named in `--sc-font-sans`; otherwise the system faces stay, and the plan says that the site's face was not taken and why. Pretendard stays for Korean, as the default console has it.
|
|
28
|
+
- **The name** stays the server's (`WORKSPACE_NAME`): the look is the marks, the colours and the type, never the words.
|
|
29
|
+
|
|
30
|
+
## What is left alone
|
|
31
|
+
|
|
32
|
+
- Photographs, illustrations and copy from the site: the pages have their own content, the board.
|
|
33
|
+
- Marks of third parties shown on the site (partners, stores, badges).
|
|
34
|
+
- The structure of the site: the console's pages keep their layout; the look is skinned through the tokens of `console.css` and `brand`, and a component is replaced only when the tokens cannot do it.
|
|
35
|
+
- The marks of SkillCDN: a console with the organization's look shows none of them.
|
|
36
|
+
|
|
37
|
+
## Where it goes
|
|
38
|
+
|
|
39
|
+
For an organization's console deployed with deploying-a-console, the result is the source in `console-ui/` of the deployment repository, depending on `@skillcdn/console` at the console's pinned version, which the workflow builds into the image on every push; the first deploy already carries the look. For a console of one's own, the same configuration in its own repository. Either way the look checkpoint shows the pages, a screenshot where a browser can be driven, before more is built on it.
|