@skillcdn/console 0.1.23 → 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 (97) 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 +80 -54
  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 -260
  12. package/dist/cli/format.js.map +1 -1
  13. package/dist/cli/help.d.ts +6 -1
  14. package/dist/cli/help.d.ts.map +1 -1
  15. package/dist/cli/help.js +47 -24
  16. package/dist/cli/help.js.map +1 -1
  17. package/dist/cli/pack.d.ts +46 -0
  18. package/dist/cli/pack.d.ts.map +1 -0
  19. package/dist/cli/pack.js +250 -0
  20. package/dist/cli/pack.js.map +1 -0
  21. package/dist/compat.d.ts +1 -1
  22. package/dist/compat.js +1 -1
  23. package/dist/components/decision-list.d.ts +13 -1
  24. package/dist/components/decision-list.d.ts.map +1 -1
  25. package/dist/components/decision-list.js +74 -29
  26. package/dist/components/decision-list.js.map +1 -1
  27. package/dist/components/paged.d.ts +24 -0
  28. package/dist/components/paged.d.ts.map +1 -1
  29. package/dist/components/paged.js +40 -4
  30. package/dist/components/paged.js.map +1 -1
  31. package/dist/components/skills.d.ts.map +1 -1
  32. package/dist/components/skills.js +8 -4
  33. package/dist/components/skills.js.map +1 -1
  34. package/dist/components/ui.d.ts +2 -0
  35. package/dist/components/ui.d.ts.map +1 -1
  36. package/dist/components/ui.js +7 -2
  37. package/dist/components/ui.js.map +1 -1
  38. package/dist/components/work.d.ts +2 -0
  39. package/dist/components/work.d.ts.map +1 -1
  40. package/dist/components/work.js +3 -2
  41. package/dist/components/work.js.map +1 -1
  42. package/dist/console.css +112 -5
  43. package/dist/console.d.ts.map +1 -1
  44. package/dist/console.js +5 -3
  45. package/dist/console.js.map +1 -1
  46. package/dist/data.d.ts.map +1 -1
  47. package/dist/data.js +27 -2
  48. package/dist/data.js.map +1 -1
  49. package/dist/i18n/en.d.ts +13 -0
  50. package/dist/i18n/en.d.ts.map +1 -1
  51. package/dist/i18n/en.js +15 -0
  52. package/dist/i18n/en.js.map +1 -1
  53. package/dist/i18n/ko.d.ts.map +1 -1
  54. package/dist/i18n/ko.js +15 -0
  55. package/dist/i18n/ko.js.map +1 -1
  56. package/dist/index.d.ts +2 -2
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +2 -2
  59. package/dist/index.js.map +1 -1
  60. package/dist/router.d.ts +6 -1
  61. package/dist/router.d.ts.map +1 -1
  62. package/dist/router.js +13 -7
  63. package/dist/router.js.map +1 -1
  64. package/dist/rules.js +3 -3
  65. package/dist/rules.js.map +1 -1
  66. package/dist/schemas.d.ts +557 -162
  67. package/dist/schemas.d.ts.map +1 -1
  68. package/dist/schemas.js +72 -158
  69. package/dist/schemas.js.map +1 -1
  70. package/docs/specs/cli.md +12 -12
  71. package/docs/specs/rest.md +7 -15
  72. package/package.json +2 -1
  73. package/skills/deploying-a-console/SKILL.md +2 -2
  74. package/skills/working-the-board/SKILL.md +9 -14
  75. package/skills/working-the-board/references/setting-up.md +3 -0
  76. package/skills/working-the-board/references/terms.md +9 -0
  77. package/skills/working-the-board/references/the-sentence.md +5 -0
  78. package/src/api.ts +2 -7
  79. package/src/cli/cli.ts +113 -62
  80. package/src/cli/format.ts +10 -324
  81. package/src/cli/help.ts +53 -27
  82. package/src/cli/pack.ts +356 -0
  83. package/src/compat.ts +1 -1
  84. package/src/components/decision-list.tsx +227 -94
  85. package/src/components/paged.tsx +72 -3
  86. package/src/components/skills.tsx +51 -33
  87. package/src/components/ui.tsx +16 -3
  88. package/src/components/work.tsx +9 -5
  89. package/src/console.tsx +8 -2
  90. package/src/data.ts +32 -2
  91. package/src/i18n/en.ts +18 -0
  92. package/src/i18n/ko.ts +17 -0
  93. package/src/index.ts +9 -1
  94. package/src/router.ts +20 -9
  95. package/src/rules.ts +3 -3
  96. package/src/schemas.ts +87 -184
  97. package/src/styles/console.css +112 -5
package/src/console.tsx CHANGED
@@ -98,6 +98,7 @@ import {
98
98
  calendarHref,
99
99
  customPageHref,
100
100
  decisionHref,
101
+ decisionsHref,
101
102
  docHref,
102
103
  type GoalTaskView,
103
104
  goalHref,
@@ -1112,6 +1113,8 @@ function ProjectPage(props: {
1112
1113
  taskHref={href}
1113
1114
  docHref={toDoc}
1114
1115
  decisionHref={(decision) => decisionHref(key, decision.id)}
1116
+ page={route.page}
1117
+ pageHref={(page) => decisionsHref(key, page)}
1115
1118
  onAnswer={onAnswer}
1116
1119
  onUpdate={onUpdate}
1117
1120
  busy={busy}
@@ -1748,9 +1751,10 @@ function PersonAgentsPage(props: {
1748
1751
  );
1749
1752
  }
1750
1753
 
1751
- /** What a plate's page shows: the tasks, and the decisions waiting on the person's own. */
1754
+ /** What a plate's page shows: the tasks, open and lately closed, and the decisions waiting on the person's own. */
1752
1755
  interface Plate {
1753
1756
  readonly tasks: RestWork["tasks"];
1757
+ readonly closed?: RestWork["closed"] | undefined;
1754
1758
  readonly decisions?: RestWork["decisions"] | undefined;
1755
1759
  }
1756
1760
 
@@ -1825,7 +1829,9 @@ function PlatePage(props: {
1825
1829
  <Spinner label={props.loading} />
1826
1830
  </div>
1827
1831
  )
1828
- ) : plate.tasks.length === 0 && (plate.decisions?.length ?? 0) === 0 ? (
1832
+ ) : plate.tasks.length === 0 &&
1833
+ (plate.closed?.length ?? 0) === 0 &&
1834
+ (plate.decisions?.length ?? 0) === 0 ? (
1829
1835
  <EmptyState title={props.empty.title} body={props.empty.body} />
1830
1836
  ) : (
1831
1837
  <components.WorkList
package/src/data.ts CHANGED
@@ -245,6 +245,28 @@ async function readTasks(
245
245
  return { items };
246
246
  }
247
247
 
248
+ /** How many pages of the decisions are read before the record is shown as it stands. */
249
+ const DECISIONS_MAX_PAGES = 20;
250
+
251
+ /**
252
+ * The decisions of a project, whole: page after page while the server says there is more, so
253
+ * that the record is read to its end and the pages turn it without asking again.
254
+ */
255
+ async function readDecisions(
256
+ scope: ProjectClient,
257
+ signal: AbortSignal | undefined,
258
+ ): Promise<{ readonly items: RestDecision[] }> {
259
+ const items: RestDecision[] = [];
260
+ for (let page = 0; page < DECISIONS_MAX_PAGES; page += 1) {
261
+ const result = await scope.decisions({ limit: LIST_LIMIT, offset: page * LIST_LIMIT }, signal);
262
+ items.push(...result.items);
263
+ if (!result.more) {
264
+ break;
265
+ }
266
+ }
267
+ return { items };
268
+ }
269
+
248
270
  const wordsOf = (failure: unknown, fallback: string): string =>
249
271
  failure instanceof ApiError ? failure.message : fallback;
250
272
 
@@ -310,7 +332,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
310
332
  await Promise.all([
311
333
  scope.get(signal),
312
334
  readTasks(scope, signal),
313
- scope.decisions(undefined, signal),
335
+ readDecisions(scope, signal),
314
336
  scope.goals({}, signal),
315
337
  scope.runs(undefined, signal),
316
338
  scope.members(signal),
@@ -427,7 +449,15 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
427
449
  // No answer at all is said on the page as the deployment being out of reach,
428
450
  // rather than waited for.
429
451
  if (!signal.aborted) {
430
- setSkills({ address: null, source: "", page: null, status: "unavailable", items: [] });
452
+ setSkills({
453
+ address: null,
454
+ source: "",
455
+ page: null,
456
+ status: "unavailable",
457
+ refusal: null,
458
+ items: [],
459
+ mount: null,
460
+ });
431
461
  }
432
462
  });
433
463
  if (typeof EventSource === "undefined") {
package/src/i18n/en.ts CHANGED
@@ -9,6 +9,7 @@ import type {
9
9
  ProjectVisibility,
10
10
  ProviderKey,
11
11
  RunStatus,
12
+ SkillsStatus,
12
13
  TaskPriority,
13
14
  TaskState,
14
15
  } from "../vocabulary.js";
@@ -102,6 +103,12 @@ export const en = {
102
103
  copy: "Copy",
103
104
  copied: "Copied",
104
105
  showMore: (n: number, of: number) => `Show ${n} more of ${of}`,
106
+ pager: {
107
+ label: "Pages",
108
+ previous: "Previous page",
109
+ next: "Next page",
110
+ range: (from: number, to: number, of: number) => `${from}-${to} of ${of}`,
111
+ },
105
112
  },
106
113
 
107
114
  nav: {
@@ -325,6 +332,9 @@ export const en = {
325
332
  waitingForPerson: "Waiting for a person.",
326
333
  options: "Options",
327
334
  notePlaceholder: "A word about it, if any",
335
+ own: "Something else",
336
+ ownPlaceholder: "Your answer, in a line",
337
+ ownAnswer: "in their own words",
328
338
  answer: "Answer",
329
339
  asked: "asked",
330
340
  askedThrough: (agent: string) => `asked through ${agent}`,
@@ -662,6 +672,14 @@ export const en = {
662
672
  connect:
663
673
  "An agent reads these with the console command (console skill <path>), or through its own MCP connection to this address, which a person makes once:",
664
674
  connectLink: "how, on the address's page at SkillCDN",
675
+ fromMachine: "Read from a directory on the console's machine, at",
676
+ connectLocal:
677
+ "An agent reads these with the console command (console skill <path>); the pack is on the console's machine and nowhere else.",
678
+ localStatus: {
679
+ not_found:
680
+ "No pack is served from the console's machine at this address: whoever runs the console puts it in the packs directory, as <owner>/<name>.",
681
+ unavailable: "The pack could not be read from the console's machine. Look again later.",
682
+ } as Partial<Record<SkillsStatus, string>>,
665
683
  status: {
666
684
  indexing: "SkillCDN is still indexing the repository. Look again in a moment.",
667
685
  failed: "SkillCDN could not index the repository. What it found is on the address's page.",
package/src/i18n/ko.ts CHANGED
@@ -72,6 +72,12 @@ export const ko: Messages = {
72
72
  copy: "복사",
73
73
  copied: "복사됨",
74
74
  showMore: (n, of) => `${of}개 중 ${n}개 더 보기`,
75
+ pager: {
76
+ label: "페이지",
77
+ previous: "이전",
78
+ next: "다음",
79
+ range: (from, to, of) => `${of}건 중 ${from}-${to}`,
80
+ },
75
81
  },
76
82
 
77
83
  nav: {
@@ -294,6 +300,9 @@ export const ko: Messages = {
294
300
  waitingForPerson: "사람을 기다리는 중입니다.",
295
301
  options: "선택지",
296
302
  notePlaceholder: "덧붙일 말이 있다면",
303
+ own: "직접 입력",
304
+ ownPlaceholder: "원하는 답을 한 줄로",
305
+ ownAnswer: "직접 입력",
297
306
  answer: "답변",
298
307
  asked: "질문",
299
308
  askedThrough: (agent) => `${agent}을(를) 통해 질문`,
@@ -626,6 +635,14 @@ export const ko: Messages = {
626
635
  connect:
627
636
  "에이전트는 콘솔 명령(console skill <경로>)으로, 또는 이 주소에 자기 MCP 연결로 스킬을 받습니다. MCP 연결은 사람이 한 번 해 줍니다:",
628
637
  connectLink: "SkillCDN 페이지의 연결 안내",
638
+ fromMachine: "콘솔 기계의 디렉터리에서 읽음, 주소:",
639
+ connectLocal:
640
+ "에이전트는 콘솔 명령(console skill <경로>)으로 읽습니다. 이 팩은 콘솔 기계에만 있습니다.",
641
+ localStatus: {
642
+ not_found:
643
+ "이 주소로 콘솔 기계에서 제공되는 팩이 없습니다. 콘솔 운영자가 팩 디렉터리에 <owner>/<name>으로 넣습니다.",
644
+ unavailable: "콘솔 기계에서 팩을 읽지 못했습니다. 나중에 다시 보세요.",
645
+ },
629
646
  status: {
630
647
  indexing: "SkillCDN이 아직 저장소를 색인하고 있습니다. 잠시 후 다시 보세요.",
631
648
  failed: "SkillCDN이 저장소를 색인하지 못했습니다. 찾은 내용은 주소의 페이지에 있습니다.",
package/src/index.ts CHANGED
@@ -82,7 +82,14 @@ export {
82
82
  goalMonth,
83
83
  } from "./components/goals.js";
84
84
  export { Markdown, type MarkdownProps } from "./components/markdown.js";
85
- export { PAGE_SIZE, ShowMore, usePaged } from "./components/paged.js";
85
+ export {
86
+ PAGE_SIZE,
87
+ Pager,
88
+ pageOf,
89
+ RECORD_PAGE_SIZE,
90
+ ShowMore,
91
+ usePaged,
92
+ } from "./components/paged.js";
86
93
  export {
87
94
  ListingForm,
88
95
  type ListingFormProps,
@@ -246,6 +253,7 @@ export {
246
253
  customPageHref,
247
254
  type DocsView,
248
255
  decisionHref,
256
+ decisionsHref,
249
257
  docHref,
250
258
  GOAL_TASK_VIEWS,
251
259
  type GoalPageOptions,
package/src/router.ts CHANGED
@@ -5,7 +5,7 @@ import { isDay, isMonth } from "./dates.js";
5
5
  // route, and the hrefs below are its inverse. A project's pages live under `/projects/<key>`,
6
6
  // the word the REST API uses; an address names what it shows (a task by its number, a decision
7
7
  // by its id, a document by its path, a goal by its id), every form has one, and the query says
8
- // how a page is shown (`?edit`, `?ask`, `?new`, `?version=`, `?q=`, `?month=`, `?week=`, and the
8
+ // how a page is shown (`?edit`, `?ask`, `?new`, `?version=`, `?q=`, `?month=`, `?week=`, `?page=`, and the
9
9
  // board's filters). The old addresses are taken to the new.
10
10
 
11
11
  /** How a task's page is shown: as it is, or with one of its forms open. */
@@ -49,7 +49,8 @@ export type Route =
49
49
  readonly path: string;
50
50
  readonly view: DocsView;
51
51
  }
52
- | { readonly name: "decisions"; readonly project: string }
52
+ /** The decisions of a project: the waiting ones, and the record on one of its pages, the first when the address names none. */
53
+ | { readonly name: "decisions"; readonly project: string; readonly page: number | undefined }
53
54
  | { readonly name: "new-decision"; readonly project: string }
54
55
  | { readonly name: "decision"; readonly project: string; readonly id: string }
55
56
  /** The calendar of a project: the month shown, or this one when the address names none, narrowed as the board is. */
@@ -155,13 +156,10 @@ const NUMBER = /^[1-9][0-9]{0,8}$/;
155
156
  /** A code as it may be typed into an address: checked for what it is by the page. */
156
157
  const CODE_TEXT = /^[A-Za-z0-9-]{1,16}$/;
157
158
  /** The pages of a project that take nothing after their name. */
158
- const PLAIN_PAGES: readonly Exclude<ProjectPage, "docs" | "calendar" | "roadmap" | "settings">[] = [
159
- "decisions",
160
- "archive",
161
- "feed",
162
- "skills",
163
- "members",
164
- ];
159
+ const PLAIN_PAGES: readonly Exclude<
160
+ ProjectPage,
161
+ "docs" | "decisions" | "calendar" | "roadmap" | "settings"
162
+ >[] = ["archive", "feed", "skills", "members"];
165
163
  const NOT_FOUND: Route = { name: "not-found" };
166
164
 
167
165
  /** Where an address of the 0.1 line before this one leads now, or nothing for a current one. */
@@ -386,6 +384,13 @@ export function matchRoute(pathname: string, search = ""): Route {
386
384
  : undefined,
387
385
  };
388
386
  }
387
+ if (page === "decisions") {
388
+ const turned = params.get("page");
389
+ if (turned !== null && !NUMBER.test(turned)) {
390
+ return NOT_FOUND;
391
+ }
392
+ return { name: "decisions", project, page: turned === null ? undefined : Number(turned) };
393
+ }
389
394
  const found = PLAIN_PAGES.find((candidate) => candidate === page);
390
395
  return found === undefined ? NOT_FOUND : { name: found, project };
391
396
  }
@@ -469,6 +474,12 @@ export function decisionHref(key: string, id: string): string {
469
474
  return `${PATHS.project}/${key}/decisions/${id}`;
470
475
  }
471
476
 
477
+ /** Where a project's decisions are: the waiting ones and the record, on one of its pages, or on the first when none is named. */
478
+ export function decisionsHref(key: string, page?: number): string {
479
+ const base = projectHref(key, "decisions");
480
+ return page === undefined || page <= 1 ? base : `${base}?page=${page}`;
481
+ }
482
+
472
483
  /** Where the person's own work is, with the week of a day at the top, or this week's when none is named. */
473
484
  export function workHref(week?: string): string {
474
485
  return week === undefined ? PATHS.work : `${PATHS.work}?week=${week}`;
package/src/rules.ts CHANGED
@@ -23,13 +23,13 @@ export const REFERENCE_RULES = [
23
23
  "",
24
24
  "## Start here, every time",
25
25
  "",
26
- "1. These rules come with the task: `console take` prints them under it, and `console rules` reads them any time. Read them before anything is made. The task says where the work is and what it links to; `console task <number>` shows a task in full, with its plan, its decisions, the runs on it and the discussion on it, without taking it. A task put away (archived) is out of the listings and the board, and opens by its number still. How the board is worked is the skill working-the-board, shipped with the command: read it before the first command of a run (`console skills` prints where it is); these rules say what this organization asks, not how the command works.",
26
+ "1. These rules come with the task: `console take` prints them under it, and `console rules` reads them any time. Read them before anything is made. The task says where the work is and what it links to; `console task <number>` shows a task in full, with its plan, its decisions, the runs on it and the discussion on it, without taking it. A task put away (archived) is out of the listings and the board, and opens by its number still. How the board is worked is the skill working-the-board, shipped with the command (`console whoami` prints where it is): read it, if you have not yet, before anything is made; these rules say what this organization asks, not how the command works.",
27
27
  "2. Act as the person whose token the command holds, and no further. What they may not do, the console refuses; that step goes back to them in a report, never around them.",
28
28
  '3. Work given in chat, with no task on the board yet, goes on the board before it is done: write it (`console task new "<title>" --body <markdown>`), tell the person its number, take it (`console take <number>`) and work it as any task, without asking whether to. Until the task is written, the board is only read.',
29
29
  "",
30
30
  "## The organization's skills",
31
31
  "",
32
- "`console skills` lists the skills of this project's pack: each with its name, what it does, and the path of its SKILL.md; `console skills <folder>` shows what a folder of the pack holds, and `console skills --search <words>` finds its skills and documents by words. `console skill <path>` reads one whole, its rules first, as the console reads it from the SkillCDN deployment for you, a private pack with the pack token the project holds; `console skill --file <file>` reads a file of the pack. Where your host lists skills through a connection of your own to the address, the URI `skill://gh/<owner>/<repo>/<path>` is the skill there, followed until its context is complete. Either way, never fetch a skill another way. Load the skill the task names before anything is made; with none named, the one whose description fits the work. The rules those skills share arrive with each of them, from their repository's SKILLCDN.md, and hold within that work. What you read of a private pack is the project's: it stays in your work, and is not copied into a report, a page or a comment beyond what the work needs.",
32
+ "`console skills` lists the skills of this project's pack, each with the path of its SKILL.md, and `console skill <path>` reads one whole, its rules first, through the console, a private pack with the pack token the project holds (`console help skills` says how the pack is browsed and searched). Where your host lists skills through a connection of your own to the address, the URI `skill://gh/<owner>/<repo>/<path>` is the skill there, followed until its context is complete. Either way, never fetch a skill another way. Load the skill the task names before anything is made; with none named, the one whose description fits the work. The rules those skills share arrive with each of them, from their repository's SKILLCDN.md, and hold within that work. What you read of a private pack is the project's: it stays in your work, and is not copied into a report, a page or a comment beyond what the work needs.",
33
33
  "",
34
34
  "## Where the work lives",
35
35
  "",
@@ -45,7 +45,7 @@ export const REFERENCE_RULES = [
45
45
  "",
46
46
  "## Reports",
47
47
  "",
48
- "Report at the milestones, not every step: what was found, what was done, what is next, in words for people; a problem as a problem, with what it blocks. What people read on the board, the reports, the questions and their options, the tasks and the pages, is written in {language}. A task's title says what is to be done, in plain words that stand on their own; what it refers to, a milestone, a commit, a page, goes in the body, linked where it can be. The result goes in a last report or is handed in as a link or a file, with the check that ran and its result; a check that did not run is said to be not run. A report is the run's record; a word to the task's people, an answer to what they asked in its comments, a question that is not a decision, a remark, is a comment on the task (`console comment`), and what they put in a comment is read with `console file`.",
48
+ "Report at the milestones, not every step: what was found, what was done, what is next, in words for people; a problem as a problem, with what it blocks. What people read on the board, the reports, the questions and their options, the tasks and the pages, is written in {language}. A task's title says what is to be done, in plain words that stand on their own; what it refers to, a milestone, a commit, a page, goes in the body, linked where it can be. The result goes in a last report or is handed in as a link or a file, with the check that ran and its result; a check that did not run is said to be not run. A report is the run's record; a word to the task's people, an answer to what they asked in its comments, a question that is not a decision, a remark, is a comment on the task, not a report.",
49
49
  "",
50
50
  "## What never happens",
51
51
  "",
package/src/schemas.ts CHANGED
@@ -2,6 +2,13 @@
2
2
  // most of what a page downloads. The server validates what it is sent with the input schemas
3
3
  // and builds the output shapes; the pages and a custom console parse answers with the rest.
4
4
  // Absent values are `null` on the wire, never missing keys. Changes within a version are additive.
5
+ import {
6
+ restBrowseSchema as packBrowseSchema,
7
+ restFileSchema as packFileSchema,
8
+ restFindSchema as packFindSchema,
9
+ restMountSchema as packMountSchema,
10
+ restSkillSchema as packSkillSchema,
11
+ } from "@skillcdn/core";
5
12
  import * as z from "zod/mini";
6
13
  import { isDocumentPath } from "./documents.js";
7
14
  import {
@@ -548,9 +555,15 @@ export const restGoalPatchSchema = z.object({
548
555
  export type RestGoalPatch = z.infer<typeof restGoalPatchSchema>;
549
556
 
550
557
  export const restDecisionOptionSchema = z.object({
551
- /** What an answer names: stable for the life of the decision. */
558
+ /** What an answer names: stable once given. */
552
559
  id: z.string(),
553
560
  label: z.string(),
561
+ /**
562
+ * On the one option a person's own answer became, when they answered in their own words
563
+ * instead of choosing (`POST .../answer` with `own`): added then, after the options given,
564
+ * which carry no `own`.
565
+ */
566
+ own: z.optional(z.literal(true)),
554
567
  });
555
568
  export type RestDecisionOption = z.infer<typeof restDecisionOptionSchema>;
556
569
 
@@ -603,19 +616,26 @@ const projectRef = z.object({
603
616
 
604
617
  /**
605
618
  * `GET /api/v1/work`: what is on the asker's plate across the projects they may see (ADR-0019):
606
- * the open tasks assigned to them, and the decisions that wait for a person, each with its project.
619
+ * the open tasks assigned to them, and the decisions that wait for a person, each with its
620
+ * project; and, for the week on the plate, `closed`, the tasks assigned to them that are done
621
+ * or dropped and not yet put away. Left out by a console older than it.
607
622
  */
608
623
  export const restWorkSchema = z.object({
609
624
  tasks: z.array(z.object({ project: projectRef, task: restTaskSchema })),
625
+ closed: z.optional(z.array(z.object({ project: projectRef, task: restTaskSchema }))),
610
626
  decisions: z.array(z.object({ project: projectRef, decision: restDecisionSchema })),
611
627
  });
612
628
  export type RestWork = z.infer<typeof restWorkSchema>;
613
629
 
614
630
  /**
615
- * What `GET /api/v1/people/<id>/work` answers: another person's open tasks, within the projects
616
- * the asker may see, for whoever oversees them. No decisions, which wait for anyone.
631
+ * What `GET /api/v1/people/<id>/work` answers: another person's open tasks, and their closed
632
+ * ones for the week, within the projects the asker may see, for whoever oversees them. No
633
+ * decisions, which wait for anyone.
617
634
  */
618
- export const restPersonWorkSchema = z.object({ tasks: restWorkSchema.shape.tasks });
635
+ export const restPersonWorkSchema = z.object({
636
+ tasks: restWorkSchema.shape.tasks,
637
+ closed: restWorkSchema.shape.closed,
638
+ });
619
639
  export type RestPersonWork = z.infer<typeof restPersonWorkSchema>;
620
640
 
621
641
  /** What `POST /api/v1/projects/<key>/decisions` is sent: the question, its context, and the options. */
@@ -639,11 +659,24 @@ export const restDecisionPatchSchema = z.object({
639
659
  });
640
660
  export type RestDecisionPatch = z.infer<typeof restDecisionPatchSchema>;
641
661
 
642
- /** What `POST .../decisions/<id>/answer` is sent: an option, and a word about it. */
643
- export const restAnswerInputSchema = z.object({
644
- option: z.string().check(z.minLength(1), z.maxLength(64)),
645
- note: z.optional(paragraphs(MAX_NOTE_LENGTH)),
646
- });
662
+ /**
663
+ * What `POST .../decisions/<id>/answer` is sent: one of the options by its id, or the person's
664
+ * own answer in a line, one of the two; and a word about either.
665
+ */
666
+ export const restAnswerInputSchema = z
667
+ .object({
668
+ /** The id of the chosen option. */
669
+ option: z.optional(z.string().check(z.minLength(1), z.maxLength(64))),
670
+ /** The person's own answer, when none of the options is it: one line, worded as an option is. */
671
+ own: z.optional(line(MAX_OPTION_LABEL_LENGTH)),
672
+ note: z.optional(paragraphs(MAX_NOTE_LENGTH)),
673
+ })
674
+ .check(
675
+ z.refine(
676
+ (input) => (input.option === undefined) !== (input.own === undefined),
677
+ "must name an option or give an answer of its own, one of the two",
678
+ ),
679
+ );
647
680
  export type RestAnswerInput = z.infer<typeof restAnswerInputSchema>;
648
681
 
649
682
  /**
@@ -887,15 +920,39 @@ export const restClaimSchema = z.union([
887
920
  ]);
888
921
  export type RestClaim = z.infer<typeof restClaimSchema>;
889
922
 
890
- /** One of the project's skills, as the SkillCDN deployment lists it at the project's address. */
923
+ /** Why a pack did not answer a read: the code and the words the deployment, or the console reading a directory, gave, as the main repository's API spells them. */
924
+ export const restPackRefusalSchema = z.object({
925
+ code: z.string(),
926
+ message: z.string(),
927
+ /** With a file the pack will not carry: where it is at its source, or `null`. */
928
+ sourceUrl: z.nullable(z.string()),
929
+ /** With `skill.ambiguous`: the directories to choose from. */
930
+ directories: z.array(z.string()),
931
+ });
932
+ export type RestPackRefusal = z.infer<typeof restPackRefusalSchema>;
933
+
934
+ /** What every answer about a pack says first: its address, where it is read, and how the read went. */
935
+ const packEnvelope = {
936
+ /** The address, canonical, or `null` when neither the project nor the console names one. */
937
+ address: z.nullable(z.string()),
938
+ /** The origin of the SkillCDN deployment the pack is read at; `null` for a pack read from a directory on the console's machine (ADR-0031). */
939
+ source: z.nullable(z.string()),
940
+ /** Where a person browses the address at the deployment, or `null` without one. */
941
+ page: z.nullable(z.string()),
942
+ status: z.enum(SKILLS_STATUSES),
943
+ /** Why the read did not happen, in the pack's own words, when `status` says it did not; `null` otherwise. */
944
+ refusal: z.nullable(restPackRefusalSchema),
945
+ };
946
+
947
+ /** One of the project's skills, as the pack lists it: what the repository says of it, where a person reads it, what an agent loads it by. */
891
948
  export const restSkillSchema = z.object({
892
949
  name: z.string(),
893
950
  description: z.string(),
894
951
  /** The skill's folder in the repository (`""` for the root), and the path of its `SKILL.md`. */
895
952
  directory: z.string(),
896
953
  path: z.string(),
897
- /** Where a person reads the skill: its page at the deployment. */
898
- page: z.string(),
954
+ /** Where a person reads the skill: its page at the deployment, or `null` for a pack read from a directory. */
955
+ page: z.nullable(z.string()),
899
956
  /** What an agent loads the skill by, through its own SkillCDN connection; `null` when the path is not one. */
900
957
  uri: z.nullable(z.string()),
901
958
  /** The title and the description in other languages, by language tag, as the repository gives them. */
@@ -906,15 +963,9 @@ export const restSkillSchema = z.object({
906
963
  });
907
964
  export type RestSkill = z.infer<typeof restSkillSchema>;
908
965
 
909
- /** `GET /api/v1/projects/<key>/skills`: the project's skills, read by address through SkillCDN. */
966
+ /** `GET /api/v1/projects/<key>/skills`: the project's pack, read by address (ADR-0028, ADR-0031). */
910
967
  export const restSkillsSchema = z.object({
911
- /** The address, canonical, or `null` when neither the project nor the deployment names one. */
912
- address: z.nullable(z.string()),
913
- /** The origin of the SkillCDN deployment the skills are read through. */
914
- source: z.string(),
915
- /** Where a person browses the address, or `null` without one. */
916
- page: z.nullable(z.string()),
917
- status: z.enum(SKILLS_STATUSES),
968
+ ...packEnvelope,
918
969
  /** The pack as its manifest or its README names it, when `ready`: what it is called, what it is for, the language it is written in. */
919
970
  pack: z.optional(
920
971
  z.object({
@@ -923,10 +974,12 @@ export const restSkillsSchema = z.object({
923
974
  language: z.nullable(z.string()),
924
975
  }),
925
976
  ),
926
- /** How many skills and documents the deployment indexed at the address, when `ready`. */
977
+ /** How many skills and documents the index found at the address, when `ready`. */
927
978
  counts: z.optional(z.object({ skills: count, documents: count })),
928
979
  /** The skills, when `ready`; empty otherwise. */
929
980
  items: z.array(restSkillSchema),
981
+ /** 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; `null` when nothing was answered. */
982
+ mount: z.nullable(packMountSchema),
930
983
  });
931
984
  export type RestSkills = z.infer<typeof restSkillsSchema>;
932
985
 
@@ -938,181 +991,31 @@ export const restPackTokenInputSchema = z.object({
938
991
  });
939
992
  export type RestPackTokenInput = z.infer<typeof restPackTokenInputSchema>;
940
993
 
941
- /** A rule, the body or an included file of a skill: its path from the repository's root and its text, cut where a page ends. */
942
- export const restSkillTextSchema = z.object({
943
- path: z.string(),
944
- body: z.string(),
945
- truncated: z.boolean(),
946
- });
947
- /** A skill as the deployment serves it, on one page of its context: what `load_skill` gives a model, in the console's shape. */
948
- export const restSkillPageSchema = z.object({
949
- path: z.string(),
950
- directory: z.string(),
951
- name: z.string(),
952
- description: z.string(),
953
- /** The rules that hold for the skill, from the repository's root to the nearest manifest. */
954
- rules: z.array(restSkillTextSchema),
955
- body: z.string(),
956
- /** The files the skill includes with its body. */
957
- included: z.array(restSkillTextSchema),
958
- /** The paths of the skill's own files, read with the file route. */
959
- files: z.array(z.string()),
960
- references: z.array(
961
- z.object({
962
- path: z.string(),
963
- status: z.enum(["available", "blocked", "missing", "outside_mount"]),
964
- }),
965
- ),
966
- /** Whether this page completes the skill; else the cursor of the next. */
967
- complete: z.boolean(),
968
- nextCursor: z.nullable(z.string()),
969
- /** How the skill is served: whole, or described only, with its license and where it is read at its host. */
970
- serving: z.object({
971
- full: z.boolean(),
972
- license: z.nullable(z.string()),
973
- sourceUrl: z.nullable(z.string()),
974
- }),
975
- warnings: z.array(z.string()),
976
- });
977
- export type RestSkillPage = z.infer<typeof restSkillPageSchema>;
978
-
979
- /** `GET .../skills/one?path=&cursor=`: one skill of the pack, a page of it; `skill` is `null` unless `ready`. */
994
+ /** `GET .../skills/one?path=&cursor=`: a page of a skill's context, as the pack answers `load_skill` (`RestSkill` of `@skillcdn/core`); `read` is `null` unless `ready`. */
980
995
  export const restSkillLoadSchema = z.object({
981
- address: z.nullable(z.string()),
982
- source: z.string(),
983
- page: z.nullable(z.string()),
984
- status: z.enum(SKILLS_STATUSES),
985
- skill: z.nullable(restSkillPageSchema),
996
+ ...packEnvelope,
997
+ read: z.nullable(packSkillSchema),
986
998
  });
987
999
  export type RestSkillLoad = z.infer<typeof restSkillLoadSchema>;
988
1000
 
989
- /** `GET .../skills/file?path=&offset=&limit=`: a page of a file of the pack, or what a folder holds; both `null` unless `ready`. */
1001
+ /** `GET .../skills/file?path=&offset=&limit=`: a page of a file, or what a folder holds, as the pack answers `read_repo_file` (`RestFile` of `@skillcdn/core`); `read` is `null` unless `ready`. */
990
1002
  export const restSkillFileSchema = z.object({
991
- address: z.nullable(z.string()),
992
- source: z.string(),
993
- status: z.enum(SKILLS_STATUSES),
994
- file: z.nullable(
995
- z.object({
996
- path: z.string(),
997
- content: z.string(),
998
- offset: count,
999
- nextOffset: z.nullable(count),
1000
- totalLength: count,
1001
- }),
1002
- ),
1003
- folder: z.nullable(
1004
- z.object({
1005
- path: z.string(),
1006
- entries: z.array(
1007
- z.object({
1008
- path: z.string(),
1009
- kind: z.enum(["directory", "file"]),
1010
- size: z.nullable(count),
1011
- }),
1012
- ),
1013
- truncated: z.boolean(),
1014
- }),
1015
- ),
1003
+ ...packEnvelope,
1004
+ read: z.nullable(packFileSchema),
1016
1005
  });
1017
1006
  export type RestSkillFile = z.infer<typeof restSkillFileSchema>;
1018
1007
 
1019
- /** An entry of a folder of the pack, as the deployment browses it: a skill, a folder with what it holds, or a file. */
1020
- export const restSkillBrowseEntrySchema = z.object({
1021
- kind: z.enum(["skill", "directory", "file"]),
1022
- /** The path from the repository's root: of the `SKILL.md` for a skill, of the folder, of the file. */
1023
- path: z.string(),
1024
- name: z.nullable(z.string()),
1025
- description: z.nullable(z.string()),
1026
- /** For a folder: the skills and the documents under it. */
1027
- skillCount: count,
1028
- documentCount: count,
1029
- /** For a file: its size in bytes. */
1030
- size: z.nullable(count),
1031
- /** For a folder: the README that introduces it, read with the file route. */
1032
- overviewPath: z.nullable(z.string()),
1033
- /** For a skill: its license, and whether the address only describes it. */
1034
- license: z.nullable(z.string()),
1035
- describedOnly: z.boolean(),
1036
- });
1037
- export type RestSkillBrowseEntry = z.infer<typeof restSkillBrowseEntrySchema>;
1038
-
1039
- /** `GET .../skills/browse?path=&cursor=&limit=`: what a folder of the pack holds, a page of it (`browse_repo`); `folder` is `null` unless `ready`. */
1008
+ /** `GET .../skills/browse?path=&cursor=&limit=`: what a folder holds, a page of it, as the pack answers `browse_repo` (`RestBrowse` of `@skillcdn/core`); `read` is `null` unless `ready`. */
1040
1009
  export const restSkillBrowseSchema = z.object({
1041
- address: z.nullable(z.string()),
1042
- source: z.string(),
1043
- page: z.nullable(z.string()),
1044
- status: z.enum(SKILLS_STATUSES),
1045
- folder: z.nullable(
1046
- z.object({
1047
- path: z.string(),
1048
- /** The README that introduces the folder, or `null`. */
1049
- overview: z.nullable(
1050
- z.object({
1051
- path: z.string(),
1052
- title: z.nullable(z.string()),
1053
- description: z.nullable(z.string()),
1054
- }),
1055
- ),
1056
- entries: z.array(restSkillBrowseEntrySchema),
1057
- /** Where the listing goes on, or `null` at its end. */
1058
- nextCursor: z.nullable(z.string()),
1059
- }),
1060
- ),
1010
+ ...packEnvelope,
1011
+ read: z.nullable(packBrowseSchema),
1061
1012
  });
1062
1013
  export type RestSkillBrowse = z.infer<typeof restSkillBrowseSchema>;
1063
1014
 
1064
- /** What a search of the pack found: a skill, as the listing has it, with the files of it that matched; or a document on its own. */
1065
- export const restSkillFindItemSchema = z.discriminatedUnion("kind", [
1066
- z.object({
1067
- kind: z.literal("skill"),
1068
- name: z.string(),
1069
- description: z.string(),
1070
- directory: z.string(),
1071
- path: z.string(),
1072
- page: z.string(),
1073
- uri: z.nullable(z.string()),
1074
- translations: z.record(
1075
- z.string(),
1076
- z.object({ title: z.nullable(z.string()), description: z.nullable(z.string()) }),
1077
- ),
1078
- /** The files of the skill that matched, a few, and how many more did. */
1079
- files: z.array(
1080
- z.object({
1081
- path: z.string(),
1082
- title: z.nullable(z.string()),
1083
- summary: z.nullable(z.string()),
1084
- }),
1085
- ),
1086
- moreFiles: count,
1087
- license: z.nullable(z.string()),
1088
- describedOnly: z.boolean(),
1089
- }),
1090
- z.object({
1091
- kind: z.literal("document"),
1092
- path: z.string(),
1093
- title: z.nullable(z.string()),
1094
- summary: z.nullable(z.string()),
1095
- /** The folder of the skill the document belongs to, or `null` for one on its own. */
1096
- skillDirectory: z.nullable(z.string()),
1097
- }),
1098
- ]);
1099
- export type RestSkillFindItem = z.infer<typeof restSkillFindItemSchema>;
1100
-
1101
- /** `GET .../skills/find?query=&path=&cursor=&limit=`: what words find in the pack, a page of it in relevance order (`search_repo`); `found` is `null` unless `ready`. */
1015
+ /** `GET .../skills/find?query=&path=&cursor=&limit=`: what words find, a page of it in relevance order, as the pack answers `search_repo` (`RestFind` of `@skillcdn/core`); `read` is `null` unless `ready`. */
1102
1016
  export const restSkillFindSchema = z.object({
1103
- address: z.nullable(z.string()),
1104
- source: z.string(),
1105
- page: z.nullable(z.string()),
1106
- status: z.enum(SKILLS_STATUSES),
1107
- found: z.nullable(
1108
- z.object({
1109
- query: z.string(),
1110
- /** The folder the search was kept to, or `null` for the whole pack. */
1111
- path: z.nullable(z.string()),
1112
- items: z.array(restSkillFindItemSchema),
1113
- nextCursor: z.nullable(z.string()),
1114
- }),
1115
- ),
1017
+ ...packEnvelope,
1018
+ read: z.nullable(packFindSchema),
1116
1019
  });
1117
1020
  export type RestSkillFind = z.infer<typeof restSkillFindSchema>;
1118
1021