@skillcdn/console 0.1.24 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +6 -5
  2. package/dist/api.d.ts +1 -1
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +1 -1
  5. package/dist/api.js.map +1 -1
  6. package/dist/cli/cli.d.ts.map +1 -1
  7. package/dist/cli/cli.js +64 -65
  8. package/dist/cli/cli.js.map +1 -1
  9. package/dist/cli/format.d.ts +4 -28
  10. package/dist/cli/format.d.ts.map +1 -1
  11. package/dist/cli/format.js +7 -261
  12. package/dist/cli/format.js.map +1 -1
  13. package/dist/cli/help.d.ts.map +1 -1
  14. package/dist/cli/help.js +6 -6
  15. package/dist/cli/help.js.map +1 -1
  16. package/dist/cli/pack.d.ts +46 -0
  17. package/dist/cli/pack.d.ts.map +1 -0
  18. package/dist/cli/pack.js +250 -0
  19. package/dist/cli/pack.js.map +1 -0
  20. package/dist/compat.d.ts +1 -1
  21. package/dist/compat.js +1 -1
  22. package/dist/components/decision-list.d.ts +13 -1
  23. package/dist/components/decision-list.d.ts.map +1 -1
  24. package/dist/components/decision-list.js +74 -29
  25. package/dist/components/decision-list.js.map +1 -1
  26. package/dist/components/paged.d.ts +24 -0
  27. package/dist/components/paged.d.ts.map +1 -1
  28. package/dist/components/paged.js +40 -4
  29. package/dist/components/paged.js.map +1 -1
  30. package/dist/components/skills.d.ts.map +1 -1
  31. package/dist/components/skills.js +8 -4
  32. package/dist/components/skills.js.map +1 -1
  33. package/dist/components/ui.d.ts +2 -0
  34. package/dist/components/ui.d.ts.map +1 -1
  35. package/dist/components/ui.js +7 -2
  36. package/dist/components/ui.js.map +1 -1
  37. package/dist/console.css +112 -5
  38. package/dist/console.d.ts.map +1 -1
  39. package/dist/console.js +2 -2
  40. package/dist/console.js.map +1 -1
  41. package/dist/data.d.ts.map +1 -1
  42. package/dist/data.js +27 -2
  43. package/dist/data.js.map +1 -1
  44. package/dist/i18n/en.d.ts +13 -0
  45. package/dist/i18n/en.d.ts.map +1 -1
  46. package/dist/i18n/en.js +15 -0
  47. package/dist/i18n/en.js.map +1 -1
  48. package/dist/i18n/ko.d.ts.map +1 -1
  49. package/dist/i18n/ko.js +15 -0
  50. package/dist/i18n/ko.js.map +1 -1
  51. package/dist/index.d.ts +2 -2
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +2 -2
  54. package/dist/index.js.map +1 -1
  55. package/dist/router.d.ts +6 -1
  56. package/dist/router.d.ts.map +1 -1
  57. package/dist/router.js +13 -7
  58. package/dist/router.js.map +1 -1
  59. package/dist/schemas.d.ts +321 -159
  60. package/dist/schemas.d.ts.map +1 -1
  61. package/dist/schemas.js +61 -154
  62. package/dist/schemas.js.map +1 -1
  63. package/docs/specs/cli.md +3 -3
  64. package/docs/specs/rest.md +5 -13
  65. package/package.json +2 -1
  66. package/skills/deploying-a-console/SKILL.md +2 -2
  67. package/skills/working-the-board/SKILL.md +3 -3
  68. package/src/api.ts +2 -7
  69. package/src/cli/cli.ts +92 -76
  70. package/src/cli/format.ts +10 -328
  71. package/src/cli/help.ts +7 -6
  72. package/src/cli/pack.ts +356 -0
  73. package/src/compat.ts +1 -1
  74. package/src/components/decision-list.tsx +227 -94
  75. package/src/components/paged.tsx +72 -3
  76. package/src/components/skills.tsx +51 -33
  77. package/src/components/ui.tsx +16 -3
  78. package/src/console.tsx +3 -0
  79. package/src/data.ts +32 -2
  80. package/src/i18n/en.ts +18 -0
  81. package/src/i18n/ko.ts +17 -0
  82. package/src/index.ts +9 -1
  83. package/src/router.ts +20 -9
  84. package/src/schemas.ts +76 -180
  85. package/src/styles/console.css +112 -5
@@ -197,7 +197,7 @@ Writing one, for whoever may work in the project, with a session or a token, is
197
197
 
198
198
  ### `GET /api/v1/projects/<key>/decisions?open=&task=&limit=&offset=`
199
199
 
200
- `{ "items": [decision, ...], "more": boolean }`: the project's, the ones that wait first, newest first within each, a page at a time as the tasks are (`limit`, `offset`, `more`); `open=true` keeps only those that wait, `task=<id>` only those about one task. A decision carries its `question`, `body` (Markdown: the context it rests on), `options` (each an `id` and a `label`), `taskId` and `taskNumber` (or `null`), `raisedBy`, `run` (the `id` and `agent` of the run that raised it, when an agent asked, or `null`), `answer`: `null` while it waits, else the chosen `option`, a `note` or `null` (the rationale given with it), `by` and `at`; and `outcome`, what followed, in Markdown, or `null` until someone writes it. Context, rationale and outcome make the decision a record ([ADR-0009](../adr/0009-documents-are-pages-of-markdown-in-a-project-addressed-by-path-versioned-and-linked-both-ways.md)); a link in any of them to a document's path is kept both ways, so that the document says the decision refers to it.
200
+ `{ "items": [decision, ...], "more": boolean }`: the project's, the ones that wait first, newest first within each, a page at a time as the tasks are (`limit`, `offset`, `more`); `open=true` keeps only those that wait, `task=<id>` only those about one task. A decision carries its `question`, `body` (Markdown: the context it rests on), `options` (each an `id` and a `label`; the last carries `own: true` when it is the person's own answer, written instead of choosing, and the options given carry no `own`), `taskId` and `taskNumber` (or `null`), `raisedBy`, `run` (the `id` and `agent` of the run that raised it, when an agent asked, or `null`), `answer`: `null` while it waits, else the chosen `option`, a `note` or `null` (the rationale given with it), `by` and `at`; and `outcome`, what followed, in Markdown, or `null` until someone writes it. Context, rationale and outcome make the decision a record ([ADR-0009](../adr/0009-documents-are-pages-of-markdown-in-a-project-addressed-by-path-versioned-and-linked-both-ways.md)); a link in any of them to a document's path is kept both ways, so that the document says the decision refers to it.
201
201
 
202
202
  ### `POST /api/v1/projects/<key>/decisions`
203
203
 
@@ -207,7 +207,7 @@ Takes `question`, `options` (two to `MAX_OPTIONS` labels), and optionally `body`
207
207
 
208
208
  One decision. With `wait=<seconds>` (`0` to `600`) the request is held while the decision waits, up to the server's own bound (50 seconds), and answers the decision as it stands then: how an agent learns the answer at once, woken through the database's own channel, without asking again and again. A decision answered already, or a wait of `0`, answers at once.
209
209
 
210
- The answer takes `option` (the id of one of its options) and optionally a `note`. A decision is answered once; the answer records who gave it and when, writes a `decision.answered` event with the label of the option, and lets a run that waited go on (`status: running`), unless another decision of its still waits.
210
+ The answer takes `option` (the id of one of its options) or `own` (the person's own answer, where none of the options is it: one line, up to `MAX_OPTION_LABEL_LENGTH`, as an option is worded; `request.invalid` with both or with neither), and optionally a `note`. An answer of the person's own becomes one more option of the decision, numbered after the ones given and marked `own: true`, and the one chosen, so that the answer names an option either way and every reader finds its label. A decision is answered once; the answer records who gave it and when, writes a `decision.answered` event with the label of the option, and lets a run that waited go on (`status: running`), unless another decision of its still waits.
211
211
 
212
212
  ### `PATCH /api/v1/projects/<key>/decisions/<id>`
213
213
 
@@ -245,19 +245,11 @@ Takes `status` (`finished`, `failed` or `abandoned`) and optionally a `summary`
245
245
 
246
246
  ### `GET /api/v1/projects/<key>/skills`
247
247
 
248
- The project's pack, as the SkillCDN deployment serves it at the project's address, or at the organization's (`SKILLS_ADDRESS`) when the project names none, read through `SKILLCDN_URL` ([ADR-0028](../adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md)): `{ "address", "source", "page", "status", "items" }`. `address` is canonical, or `null` with `status: "none"` when neither names one; `source` is the deployment's origin and `page` where a person browses the address. When `ready`, `pack` names the pack as its manifest does, else its README, else the host (`name`, `description` and `language`, each or `null`), and `counts` says how many `skills` and `documents` the deployment indexed at the address; neither is there otherwise. `status` is `ready`, or why `items` is empty: `indexing` and `failed` as the deployment says of the repository, `not_found` when it does not serve the address to the console (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), `token_invalid` when the deployment refuses the pack token the project holds (expired, removed, or its maker lost the repository), `unavailable` when it could not be reached or read. An item is one skill: `name`, `description`, `directory` and `path` (of its `SKILL.md`, from the repository's root, which the routes below take), `page` (the skill at the deployment, for a person), `uri` (what an agent loads it by through its own SkillCDN connection, `skill://gh/<owner>/<repo>/<path>`, or `null`), and `translations` (the title and the description by language tag, as the repository gives them). The console keeps nothing of the skills: a public answer per address stands for a minute, then the deployment is asked again; what is read with a pack token is asked every time, and held for nobody.
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 pack token the project holds where the pack is private, 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
249
 
250
- ### `GET /api/v1/projects/<key>/skills/one?path=&cursor=` and `GET .../skills/file?path=&offset=&limit=`
250
+ ### `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
251
 
252
- One skill of the pack, and a file of it, read by the console from the deployment for whoever may see the project, with the pack token where the project holds one ([ADR-0028](../adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md)); each read is logged with who read what, never with what was read. `path` is the path from the repository's root, up to 500 characters; a query that is not one is `request.invalid`.
253
-
254
- `skills/one` is the deployment's `load_skill`, in the console's shape: `{ "address", "source", "page", "status", "skill" }`, `skill` being `null` unless `status` is `ready`, else `path`, `directory`, `name`, `description`, `rules` (the manifests that hold for the skill, from the repository's root to the nearest, each `path`, `body` and `truncated`), `body`, `included` (the files the skill includes, the same way), `files` (the paths of the skill's own files), `references` (each `path` and `status`: `available`, `blocked`, `missing` or `outside_mount`), `complete` and `nextCursor` (a page may end inside a rule or a file; the next page continues it, and the skill is whole only when `complete`), `serving` (`full`, whether the skill is served whole or described only under its license; `license`; `sourceUrl`, where it is read at its host) and `warnings`. `skills/file` is `read_repo_file`: `{ "address", "source", "status", "file", "folder" }`, `file` being the page (`path`, `content`, `offset`, `nextOffset` or `null` at the end, `totalLength`, in characters; `limit` 1 to 100,000) or `folder` what a folder holds (`path`, `entries` of `path`, `kind` and `size`, `truncated`), both `null` unless `ready`; `failed` here is a file the deployment will not carry, too large or not text.
255
-
256
- ### `GET /api/v1/projects/<key>/skills/browse?path=&cursor=&limit=` and `GET .../skills/find?query=&path=&cursor=&limit=`
257
-
258
- What a folder of the pack holds, and what words find in it, read the same way: the deployment's `browse_repo` and `search_repo` in the console's shape, for whoever may see the project, with the pack token where the project holds one ([ADR-0028](../adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md)); each read is logged with who read which folder, never with the words searched for. `path` is a folder's path from the repository's root, up to 500 characters, the mounted folder when left out; `cursor` continues one listing; a query out of bounds is `request.invalid`.
259
-
260
- `skills/browse` answers `{ "address", "source", "page", "status", "folder" }`, `folder` being `null` unless `ready`, else the folder's `path`, its `overview` (the README that introduces it: `path`, `title` and `description`, or `null`), its `entries`, each with `kind` (`skill`, `directory` or `file`), `path` (of the `SKILL.md` for a skill), `name` and `description` (or `null`), `skillCount` and `documentCount` (what a folder holds), `size` (a file's, in bytes, or `null`), `overviewPath` (a folder's README, or `null`), `license` and `describedOnly` (a skill's: whether the address only describes it under its license), and `nextCursor` (or `null` at the end; `limit` 1 to 200, 50 unless given). `skills/find` answers `{ "address", "source", "page", "status", "found" }`, `found` being `null` unless `ready`, else the `query` (1 to 500 characters, required), the `path` the search was kept to (or `null`), `items` in relevance order, a `skill` as the listing's item with the `files` of it that matched (each `path`, `title` and `summary`), `moreFiles`, `license` and `describedOnly`, or a `document` with its `path`, `title`, `summary` and `skillDirectory` (or `null`), and `nextCursor` (`limit` 1 to 25, 10 unless given). Words are matched, not meaning, in the language the pack is written in; the deployment's listing of a mount is bounded, and these two are how the rest of a large pack is reached.
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 pack token where the project holds one ([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.
261
253
 
262
254
  ### `PUT /api/v1/projects/<key>/pack-token` and `DELETE .../pack-token`
263
255
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillcdn/console",
3
- "version": "0.1.24",
3
+ "version": "0.3.0",
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,6 +68,7 @@
68
68
  "vitest": "^5.0.3"
69
69
  },
70
70
  "dependencies": {
71
+ "@skillcdn/core": "^0.3.0",
71
72
  "react-markdown": "^10.1.0",
72
73
  "remark-gfm": "^4.0.1",
73
74
  "zod": "^4.6.5"
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: Needs a shell with Node.js 24 or newer, git, curl, jq and the AWS CLI v2, the gh command signed in or an empty repository the person made, network access to AWS, Cloudflare, GitHub and npm, an AWS account and a domain on Cloudflare the person can create a key and a token for, and a browser on any device for the few steps only a signed-in person can take. Works in any agent that can run a shell and write files; on Windows, in Git Bash with path conversion off.
6
6
  metadata:
7
7
  author: skillcdn
8
- version: "1.1"
8
+ version: "1.2"
9
9
  skillcdn:
10
10
  translations:
11
11
  ko:
@@ -14,7 +14,7 @@ skillcdn:
14
14
  ---
15
15
  # Deploying a console for an organization
16
16
 
17
- What goes in: an AWS account, a domain on Cloudflare or one to buy, and the answers to a few plain questions. What comes out: the console at `https://<hostname>`, signed in by its first administrator, with a first project and an agent connected; running as containers on AWS next to a managed PostgreSQL and a bucket, reached only through Cloudflare, every secret in a parameter store; a private deployment repository whose every push rebuilds and redeploys; a budget alert; and a record, `DEPLOY.md`, of what exists and how it is run. The person never needs to know the cloud: every choice is derived from their answers, shown with its cost, and changed when they ask. The shape and why it was chosen are [ADR-0017](/docs/adr/0017-the-reference-deployment-is-the-image-on-aws-behind-cloudflare-written-as-a-skill.md); what the console asks of any platform is [deploy/README.md](/deploy/README.md).
17
+ What goes in: an AWS account, a domain on Cloudflare or one to buy, and the answers to a few plain questions. What comes out: the console at `https://<hostname>`, signed in by its first administrator, with a first project and an agent connected; running as containers on AWS next to a managed PostgreSQL and a bucket, reached only through Cloudflare, every secret in a parameter store; a private deployment repository whose every push rebuilds and redeploys; a budget alert; and a record, `DEPLOY.md`, of what exists and how it is run. The person never needs to know the cloud: every choice is derived from their answers, shown with its cost, and changed when they ask. The shape and why it was chosen are [ADR-0017](/docs/adr/0017-the-reference-deployment-is-the-image-on-aws-behind-cloudflare-written-as-a-skill.md); what the console asks of any platform is [deploy/README.md](/deploy/README.md). For one machine, a trial, or a closed network that reaches no cloud, the smallest install of [deploy/README.md](/deploy/README.md#the-smallest-install-and-a-closed-network) is the shape, not this skill: the image next to PostgreSQL with the packs in a directory, carried in where nothing is reachable.
18
18
 
19
19
  ## How the person is involved
20
20
 
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: Needs a shell with the console command on the PATH (npm install -g @skillcdn/console, Node.js 24 or newer), signed in by a person. Nothing else; the command needs no key of its own and the agent never sees a token.
6
6
  metadata:
7
7
  author: skillcdn
8
- version: "1.17"
8
+ version: "1.19"
9
9
  skillcdn:
10
10
  translations:
11
11
  ko:
@@ -41,7 +41,7 @@ The full contract is the [specification of the command line](/docs/specs/cli.md)
41
41
  | Input | Source |
42
42
  |---|---|
43
43
  | The task | Its number (`#7`) or its id, from what the person said. Told to pick one, `console work` lists what is on the person's plate across their projects, the sooner due first (`--person <login>` another person's, for an administrator or an owner of a project they are in), and `console tasks --state ready --mine` what is ready and theirs in this one (`--due overdue`, `--due week`, `--category <key>` and `--goal <id>` narrow it further); a task is never taken without being told which, or that any ready one may be. |
44
- | The work | The task's body, read with `console task <number>`: what to make, where, the milestones, what to ask; the comments on it, which are the discussion so far, printed with the task, their pictures saved with `console file <id>`; with its plan, the category, the goal it works towards (`console goal <id>` says what reaching it means, its category and the days it starts and is meant to be reached by) and the days it starts and is due, which the reports and the summary keep to. The pages it links to (`console doc <path>`) and the project's pack (`console skills` lists its skills and says how many skills and documents it has; `console skills <folder>` shows what a folder of it holds, `console skills --search <words>` what the words find in it; `console skill <path>` reads one whole through the console, its rules first, a private pack through the pack token the project holds, [ADR-0028](/docs/adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md); `console skill --file <file>` a file of it; a host that lists skills through a connection of its own loads them by their URI) say the rest. |
44
+ | The work | The task's body, read with `console task <number>`: what to make, where, the milestones, what to ask; the comments on it, which are the discussion so far, printed with the task, their pictures saved with `console file <id>`; with its plan, the category, the goal it works towards (`console goal <id>` says what reaching it means, its category and the days it starts and is meant to be reached by) and the days it starts and is due, which the reports and the summary keep to. The pages it links to (`console doc <path>`) and the project's pack (`console skills` says where the pack is, what a client is told of it on connect, and every skill and document; `console skills <folder>` shows what a folder of it holds, `console skills --search <words>` what the words find in it; `console skill <path>` reads one whole through the console, its rules first, as an MCP client's model is given it, a private pack through the pack token the project holds, [ADR-0028](/docs/adr/0028-the-console-reads-a-projects-pack-through-the-deployments-rest-api-with-a-pack-token-for-a-private-one.md), and a pack on the console's own machine from its directory, [ADR-0031](/docs/adr/0031-the-console-reads-a-pack-as-the-skillcdn-command-does-and-a-local-one-from-a-directory.md); `console skill --file <file>` a file of it; a host that lists skills through a connection of its own loads them by their URI, which means nothing elsewhere for a pack on the console's machine) say the rest. |
45
45
  | The rules | The workspace's, the console's own until an administrator writes the organization's, and this project's own when it has them: which skills to load first, where the work lives, the pages, what may be decided alone, how to report ([ADR-0023](/docs/adr/0023-the-rules-of-a-board-are-pages-the-workspace-and-its-projects-keep-read-first-by-every-agent.md)). `console take` prints them with the task, so they are read once, there; `console rules` reads them without taking, as when a task is still to be chosen. They hold over this skill's defaults where they differ. |
46
46
 
47
47
  ## Workflow
@@ -62,7 +62,7 @@ The work happens where it happens: in the repository, the document, the tool. At
62
62
 
63
63
  ### Phase 4: Ask when a person must decide
64
64
 
65
- A choice the body does not settle, a trade-off, a cost, a change of scope: `console ask "<question>" --option "<one>" --option "<another>" --body "<what they need to know>"`. The run waits for the answer; the command waits a minute and exits with `3` while it is still to come. Keep waiting with `console decision <id> --wait 100`, as often as needed, and meanwhile do only what does not depend on the answer. Act on the answer as given; what was answered is not asked again. Once acted on, `console decision <id> --outcome "<what followed>"` makes the decision read as a record.
65
+ A choice the body does not settle, a trade-off, a cost, a change of scope: `console ask "<question>" --option "<one>" --option "<another>" --body "<what they need to know>"`. The run waits for the answer; the command waits a minute and exits with `3` while it is still to come. Keep waiting with `console decision <id> --wait 100`, as often as needed, and meanwhile do only what does not depend on the answer. Act on the answer as given, which may be the person's own words rather than one of the options, marked as theirs; what was answered is not asked again. Once acted on, `console decision <id> --outcome "<what followed>"` makes the decision read as a record.
66
66
 
67
67
  ### Phase 5: Hand in
68
68
 
package/src/api.ts CHANGED
@@ -188,6 +188,7 @@ export {
188
188
  type RestMembers,
189
189
  type RestMePatch,
190
190
  type RestOrganization,
191
+ type RestPackRefusal,
191
192
  type RestPackTokenInput,
192
193
  type RestPeople,
193
194
  type RestPerson,
@@ -210,12 +211,9 @@ export {
210
211
  type RestRuns,
211
212
  type RestSkill,
212
213
  type RestSkillBrowse,
213
- type RestSkillBrowseEntry,
214
214
  type RestSkillFile,
215
215
  type RestSkillFind,
216
- type RestSkillFindItem,
217
216
  type RestSkillLoad,
218
- type RestSkillPage,
219
217
  type RestSkills,
220
218
  type RestTask,
221
219
  type RestTaskCounts,
@@ -280,6 +278,7 @@ export {
280
278
  restMePatchSchema,
281
279
  restMeSchema,
282
280
  restOrganizationSchema,
281
+ restPackRefusalSchema,
283
282
  restPackTokenInputSchema,
284
283
  restPeopleSchema,
285
284
  restPersonPatchSchema,
@@ -300,16 +299,12 @@ export {
300
299
  restRunInputSchema,
301
300
  restRunSchema,
302
301
  restRunsSchema,
303
- restSkillBrowseEntrySchema,
304
302
  restSkillBrowseSchema,
305
303
  restSkillFileSchema,
306
- restSkillFindItemSchema,
307
304
  restSkillFindSchema,
308
305
  restSkillLoadSchema,
309
- restSkillPageSchema,
310
306
  restSkillSchema,
311
307
  restSkillsSchema,
312
- restSkillTextSchema,
313
308
  restTaskCountsSchema,
314
309
  restTaskInputSchema,
315
310
  restTaskLinkSchema,
package/src/cli/cli.ts CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  type RestSkillBrowse,
21
21
  type RestSkillFile,
22
22
  type RestSkillLoad,
23
- type RestSkillPage,
23
+ type RestSkills,
24
24
  type RestTaskPatch,
25
25
  } from "../schemas.js";
26
26
  import {
@@ -75,11 +75,6 @@ import {
75
75
  formatRules,
76
76
  formatRun,
77
77
  formatRunLine,
78
- formatSkillBrowse,
79
- formatSkillFile,
80
- formatSkillFind,
81
- formatSkillLoad,
82
- formatSkills,
83
78
  formatTask,
84
79
  formatTaskLine,
85
80
  formatVersion,
@@ -89,6 +84,22 @@ import {
89
84
  import { COMMAND_HELP, helpFor, ownSkillPath, usage } from "./help.js";
90
85
  import { describeInstall, type Install, updateCommandFor } from "./install.js";
91
86
  import type { NoticeStore } from "./notices.js";
87
+ import {
88
+ bodyOfFile,
89
+ bodyOfFolder,
90
+ bodyOfSkill,
91
+ formatSkillBrowse,
92
+ formatSkillFile,
93
+ formatSkillFind,
94
+ formatSkillLoad,
95
+ formatSkills,
96
+ joinFile,
97
+ joinFolder,
98
+ joinSkill,
99
+ type PackSummary,
100
+ skillPathIn,
101
+ summaryOf,
102
+ } from "./pack.js";
92
103
  import { createServeHandler, DEFAULT_SERVE_PORT, type Listen } from "./serve.js";
93
104
 
94
105
  // The `console` command (docs/specs/cli.md): the agent's side of the console. A thin client of
@@ -1003,10 +1014,19 @@ const MAX_SKILL_PAGES = 64;
1003
1014
  /** How many results a search answers at most: what the deployment allows on one page. */
1004
1015
  const MAX_FIND_LIMIT = 25;
1005
1016
 
1017
+ /** The pack of the project as the console lists it, and what its reads are rendered at; none when the pack did not answer. */
1018
+ async function packOf(
1019
+ project: ProjectClient,
1020
+ ): Promise<{ readonly listing: RestSkills; readonly summary: PackSummary | undefined }> {
1021
+ const listing = await project.skills();
1022
+ return { listing, summary: summaryOf(listing) };
1023
+ }
1024
+
1006
1025
  const skills: Command = async (args, io) => {
1007
1026
  const { values, positionals } = parse(args, {
1008
1027
  search: { type: "string" },
1009
1028
  limit: { type: "string" },
1029
+ cursor: { type: "string" },
1010
1030
  });
1011
1031
  if (positionals.length > 1) {
1012
1032
  throw misuse("Say one folder: console skills [<folder>] [--search <words>]");
@@ -1016,8 +1036,11 @@ const skills: Command = async (args, io) => {
1016
1036
  const folder = given === undefined || given === "/" || given === "." ? undefined : given;
1017
1037
  const words = text(values, "search");
1018
1038
  const limit = text(values, "limit");
1019
- if (words === undefined && limit !== undefined) {
1020
- throw misuse("--limit goes with --search: console skills --search <words> [--limit <n>]");
1039
+ const cursor = text(values, "cursor");
1040
+ if (words === undefined && (limit !== undefined || cursor !== undefined)) {
1041
+ throw misuse(
1042
+ "--limit and --cursor go with --search: console skills --search <words> [--limit <n>] [--cursor <cursor>]",
1043
+ );
1021
1044
  }
1022
1045
  const atMost = limit === undefined ? undefined : Number(limit);
1023
1046
  if (
@@ -1028,37 +1051,41 @@ const skills: Command = async (args, io) => {
1028
1051
  }
1029
1052
  const session = await open(io, values);
1030
1053
  const project = await session.project();
1054
+ const { listing, summary } = await packOf(project);
1055
+ // The pack as it stands; or why it does not answer, which a folder or a search would meet too.
1056
+ if (summary === undefined || (words === undefined && given === undefined)) {
1057
+ answer(io, session, listing, () => formatSkills(listing, io.skillsDir));
1058
+ return EXIT.ok;
1059
+ }
1031
1060
  if (words !== undefined) {
1032
- // One page of what the words find; more is said, and narrowed by the agent, not fetched.
1033
- const found = await project.skillFind(words, { path: folder, limit: atMost });
1034
- answer(io, session, found, () => formatSkillFind(found));
1061
+ // One page of what the words find; more is said, and asked for with --cursor.
1062
+ const found = await project.skillFind(words, { path: folder, limit: atMost, cursor });
1063
+ answer(io, session, found, () => formatSkillFind(found, summary));
1035
1064
  return EXIT.ok;
1036
1065
  }
1037
- if (given !== undefined) {
1038
- // The folder whole: its pages until the end, as the deployment lists them.
1039
- const folders: NonNullable<RestSkillBrowse["folder"]>[] = [];
1040
- let cursor: string | undefined;
1041
- let last: RestSkillBrowse | undefined;
1042
- for (let n = 0; n < MAX_SKILL_PAGES; n += 1) {
1043
- last = await project.skillBrowse({ path: folder, cursor });
1044
- if (last.status !== "ready" || last.folder === null) {
1045
- break;
1046
- }
1047
- folders.push(last.folder);
1048
- if (last.folder.nextCursor === null) {
1049
- break;
1050
- }
1051
- cursor = last.folder.nextCursor;
1066
+ // The folder whole: its pages until the end, as the pack lists them.
1067
+ const pages: RestSkillBrowse[] = [];
1068
+ let next: string | undefined;
1069
+ let last: RestSkillBrowse | undefined;
1070
+ for (let n = 0; n < MAX_SKILL_PAGES; n += 1) {
1071
+ last = await project.skillBrowse({ path: folder, cursor: next });
1072
+ if (last.read?.status !== "ready") {
1073
+ break;
1052
1074
  }
1053
- if (last === undefined) {
1054
- throw failed("The folder could not be read.");
1075
+ pages.push(last);
1076
+ next = last.read.nextCursor ?? undefined;
1077
+ if (next === undefined) {
1078
+ break;
1055
1079
  }
1056
- const browsed = last;
1057
- answer(io, session, { ...browsed, folders }, () => formatSkillBrowse(browsed, folders));
1058
- return EXIT.ok;
1059
1080
  }
1060
- const found = await project.skills();
1061
- answer(io, session, found, () => formatSkills(found, io.skillsDir));
1081
+ if (last === undefined) {
1082
+ throw failed("The folder could not be read.");
1083
+ }
1084
+ const browsed = last;
1085
+ const whole = joinFolder(pages, summary);
1086
+ answer(io, session, { ...browsed, read: whole === undefined ? null : bodyOfFolder(whole) }, () =>
1087
+ formatSkillBrowse(browsed, whole),
1088
+ );
1062
1089
  return EXIT.ok;
1063
1090
  };
1064
1091
 
@@ -1074,84 +1101,73 @@ const skill: Command = async (args, io) => {
1074
1101
  }
1075
1102
  const session = await open(io, values);
1076
1103
  const project = await session.project();
1104
+ const { listing, summary } = await packOf(project);
1105
+ if (summary === undefined) {
1106
+ answer(io, session, listing, () => formatSkills(listing, io.skillsDir));
1107
+ return EXIT.ok;
1108
+ }
1077
1109
  if (file !== undefined) {
1078
- // The file whole, page after page, as the console reads it from the deployment.
1079
- const parts: string[] = [];
1110
+ // The file whole, page after page, as the console reads it from the pack.
1111
+ const pages: RestSkillFile[] = [];
1080
1112
  let offset: number | undefined;
1081
1113
  let last: RestSkillFile | undefined;
1082
- for (let pages = 0; pages < MAX_SKILL_PAGES; pages += 1) {
1114
+ for (let n = 0; n < MAX_SKILL_PAGES; n += 1) {
1083
1115
  last = await project.skillFile(file, offset === undefined ? {} : { offset });
1084
- if (last.status !== "ready" || last.file === null) {
1116
+ if (last.read === null) {
1085
1117
  break;
1086
1118
  }
1087
- parts.push(last.file.content);
1088
- if (last.file.nextOffset === null) {
1119
+ pages.push(last);
1120
+ if (last.read.kind !== "file" || last.read.nextOffset === null) {
1089
1121
  break;
1090
1122
  }
1091
- offset = last.file.nextOffset;
1123
+ offset = last.read.nextOffset;
1092
1124
  }
1093
1125
  if (last === undefined) {
1094
1126
  throw failed("The file could not be read.");
1095
1127
  }
1096
- const read = last;
1097
- const content = parts.join("");
1098
- answer(io, session, { ...read, content }, () => formatSkillFile(read, content));
1128
+ const fileRead = last;
1129
+ const whole = joinFile(pages, summary);
1130
+ answer(io, session, { ...fileRead, read: whole === undefined ? null : bodyOfFile(whole) }, () =>
1131
+ formatSkillFile(fileRead, whole),
1132
+ );
1099
1133
  return EXIT.ok;
1100
1134
  }
1101
- const { path, pack } = skillPathOf(
1135
+ const wanted = skillPathIn(
1102
1136
  theOne(
1103
1137
  positionals,
1104
1138
  "Say which skill: console skill <path of its SKILL.md>, or console skill --file <path> for a file of the pack",
1105
1139
  ),
1140
+ listing.address,
1106
1141
  );
1142
+ if ("error" in wanted) {
1143
+ throw misuse(wanted.error);
1144
+ }
1107
1145
  // The skill whole: its pages until complete, the rules first, as load_skill lays them out.
1108
- const pages: RestSkillPage[] = [];
1146
+ const pages: RestSkillLoad[] = [];
1109
1147
  let cursor: string | undefined;
1110
1148
  let last: RestSkillLoad | undefined;
1111
1149
  for (let n = 0; n < MAX_SKILL_PAGES; n += 1) {
1112
- last = await project.skill(path, cursor);
1113
- if (last.status !== "ready" || last.skill === null) {
1150
+ last = await project.skill(wanted.path, cursor);
1151
+ if (last.read?.status !== "ready") {
1114
1152
  break;
1115
1153
  }
1116
- pages.push(last.skill);
1117
- if (last.skill.complete || last.skill.nextCursor === null) {
1154
+ pages.push(last);
1155
+ cursor = last.read.skill.nextCursor ?? undefined;
1156
+ if (last.read.skill.complete === true || cursor === undefined) {
1118
1157
  break;
1119
1158
  }
1120
- cursor = last.skill.nextCursor;
1121
1159
  }
1122
1160
  if (last === undefined) {
1123
1161
  throw failed("The skill could not be read.");
1124
1162
  }
1125
- // A URI of another pack would read this pack's skill at the same path, or nothing: said instead.
1126
- if (pack !== undefined && last.address !== null && last.address !== pack) {
1127
- throw misuse(
1128
- `That URI is of another pack, ${pack}; this project's pack is at ${last.address}. Say the path of the SKILL.md, or a URI console skills lists.`,
1129
- );
1130
- }
1131
1163
  const loaded = last;
1132
- answer(io, session, { ...loaded, pages }, () => formatSkillLoad(loaded, pages));
1164
+ const whole = joinSkill(pages, summary);
1165
+ answer(io, session, { ...loaded, read: whole === undefined ? null : bodyOfSkill(whole) }, () =>
1166
+ formatSkillLoad(loaded, whole),
1167
+ );
1133
1168
  return EXIT.ok;
1134
1169
  };
1135
1170
 
1136
- /**
1137
- * A skill's path as given, or taken out of the URI `console skills` lists beside it
1138
- * (`skill://gh/<owner>/<repo>/<path>`), with the pack the URI names, for the read to check.
1139
- */
1140
- function skillPathOf(given: string): { readonly path: string; readonly pack?: string } {
1141
- const uri = /^skill:\/\/(gh\/[^/]+\/[^/]+)\/(.+)$/.exec(given);
1142
- const named = uri?.[1];
1143
- const within = uri?.[2];
1144
- if (named !== undefined && within !== undefined) {
1145
- return { path: within, pack: `/${named}` };
1146
- }
1147
- if (given.startsWith("skill://")) {
1148
- throw misuse(
1149
- `Not a skill's URI the console knows: ${given}. Say the path of its SKILL.md from the repository's root, or the URI console skills lists.`,
1150
- );
1151
- }
1152
- return { path: given };
1153
- }
1154
-
1155
1171
  /** A version number given as a flag, or nothing; anything else is not understood. */
1156
1172
  function versionOf(given: string | undefined): number | undefined {
1157
1173
  if (given === undefined) {