@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/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,20 +75,31 @@ 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,
86
81
  formatVersionLine,
87
82
  formatWork,
88
83
  } from "./format.js";
89
- import { COMMAND_HELP, helpFor, usage } from "./help.js";
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
@@ -339,11 +350,11 @@ async function currentRun(project: ProjectClient, values: Values): Promise<strin
339
350
  }
340
351
  if (items.length === 0) {
341
352
  throw failed(
342
- "No run of yours is open in this project. Take a task first: console take <number>",
353
+ "run.none: no run of yours is open in this project. Take a task first: console take <number>; or name a run: --run <id>",
343
354
  );
344
355
  }
345
356
  throw failed(
346
- `Several runs of yours are open; say which with --run <id>:\n${items.map((run) => ` ${formatRunLine(run)}`).join("\n")}`,
357
+ `run.ambiguous: several runs of yours are open in this project; say which with --run <id>:\n${items.map((run) => ` ${formatRunLine(run)}`).join("\n")}`,
347
358
  );
348
359
  }
349
360
 
@@ -851,14 +862,14 @@ const whoami: Command = async (args, io) => {
851
862
  ...(key === undefined
852
863
  ? []
853
864
  : [
854
- "What this organization asks of agents: console rules. How the board is worked: the skill working-the-board, shipped with this command (console skills says where it is).",
865
+ `What this organization asks of agents: console rules. How the board is worked: the skill working-the-board, shipped with this command${io.skillsDir === undefined ? " (console skills says where it is)." : `: ${ownSkillPath(io.skillsDir)}`}`,
855
866
  ]),
856
867
  `This command is ${io.version ?? "of a version it does not know"}${io.install === undefined ? "" : `, ${describeInstall(io.install)}`}; ${consoleWords}.`,
857
868
  ...(verdict === "behind"
858
869
  ? [`A newer command exists. Update it: ${updateWords(io, me.console?.version)}`]
859
870
  : verdict === "ahead"
860
871
  ? [
861
- `This command is newer than the console: what the console has not got yet is refused as not found. To match the console: ${updateWords(io, me.console?.version)}`,
872
+ `This command is newer than the console: it works, and what the console has not got yet is refused as not found; nothing to do until then. To match the console then: ${updateWords(io, me.console?.version)}`,
862
873
  ]
863
874
  : []),
864
875
  ].join("\n"),
@@ -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,54 +1101,70 @@ 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 = theOne(
1102
- positionals,
1103
- "Say which skill: console skill <path of its SKILL.md>, or console skill --file <path> for a file of the pack",
1135
+ const wanted = skillPathIn(
1136
+ theOne(
1137
+ positionals,
1138
+ "Say which skill: console skill <path of its SKILL.md>, or console skill --file <path> for a file of the pack",
1139
+ ),
1140
+ listing.address,
1104
1141
  );
1142
+ if ("error" in wanted) {
1143
+ throw misuse(wanted.error);
1144
+ }
1105
1145
  // The skill whole: its pages until complete, the rules first, as load_skill lays them out.
1106
- const pages: RestSkillPage[] = [];
1146
+ const pages: RestSkillLoad[] = [];
1107
1147
  let cursor: string | undefined;
1108
1148
  let last: RestSkillLoad | undefined;
1109
1149
  for (let n = 0; n < MAX_SKILL_PAGES; n += 1) {
1110
- last = await project.skill(path, cursor);
1111
- if (last.status !== "ready" || last.skill === null) {
1150
+ last = await project.skill(wanted.path, cursor);
1151
+ if (last.read?.status !== "ready") {
1112
1152
  break;
1113
1153
  }
1114
- pages.push(last.skill);
1115
- 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) {
1116
1157
  break;
1117
1158
  }
1118
- cursor = last.skill.nextCursor;
1119
1159
  }
1120
1160
  if (last === undefined) {
1121
1161
  throw failed("The skill could not be read.");
1122
1162
  }
1123
1163
  const loaded = last;
1124
- 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
+ );
1125
1168
  return EXIT.ok;
1126
1169
  };
1127
1170
 
@@ -1487,24 +1530,32 @@ const take: Command = async (args, io) => {
1487
1530
  });
1488
1531
  // The rules come with the run, as they read for the person: everything from here acts
1489
1532
  // within them (ADR-0023); the task is read again, as it stands now that it is taken.
1490
- const [ruling, me, taken, comments] = await Promise.all([
1533
+ const [ruling, me, taken, comments, openRuns] = await Promise.all([
1491
1534
  project.rules(),
1492
1535
  session.client.me(),
1493
1536
  project.task(ref),
1494
1537
  project.comments(found.id),
1538
+ project.runs({ open: true, mine: true }),
1495
1539
  ]);
1496
1540
  // Taking a task with a run open on it ends that run (ADR-0020): said when it happened.
1497
1541
  const tookOver =
1498
1542
  found.openRuns > 0
1499
1543
  ? " A run that was open on it is over now: the board cannot know whether its agent still ran, so taking it was deciding."
1500
1544
  : "";
1545
+ // Another run of this token open in the project makes every command on a run ask which
1546
+ // (currentRun): said here, with the id to say, before the first report runs into it.
1547
+ const others = openRuns.items.filter((item) => item.id !== run.id);
1548
+ const elsewhere =
1549
+ others.length === 0
1550
+ ? ""
1551
+ : `\nOther runs of yours are open in this project (${others.map((item) => `#${item.taskNumber}`).join(", ")}): say which run on report, hand-in, ask, finish and fail, --run ${run.id} for this one.`;
1501
1552
  const rules = {
1502
1553
  workspace: forPerson(ruling.workspace, me.language),
1503
1554
  project: forPerson(ruling.project, me.language),
1504
1555
  };
1505
1556
  answer(io, session, { run, task: taken, comments: comments.items, rules }, () =>
1506
1557
  [
1507
- `Run ${run.id} began on #${found.number} ${found.title}, as "${run.agent}" for ${run.person.login}. The task is in progress and theirs.${tookOver}`,
1558
+ `Run ${run.id} began on #${found.number} ${found.title}, as "${run.agent}" for ${run.person.login}. The task is in progress and theirs.${tookOver}${elsewhere}`,
1508
1559
  "",
1509
1560
  formatTask(taken, undefined, undefined, comments.items),
1510
1561
  "",
@@ -2540,7 +2591,7 @@ const COMMANDS: Readonly<Record<string, Command>> = {
2540
2591
 
2541
2592
  function help(io: CliIo, name: string | undefined): number {
2542
2593
  if (name === undefined) {
2543
- io.stdout(usage(io.version));
2594
+ io.stdout(usage(io.version, io.skillsDir));
2544
2595
  return EXIT.ok;
2545
2596
  }
2546
2597
  const found = helpFor(name);
@@ -2614,7 +2665,7 @@ async function noticeVersions(io: CliIo, seen: ConsoleSeen): Promise<void> {
2614
2665
  io.stderr(
2615
2666
  verdict === "behind"
2616
2667
  ? `A newer command exists: the console runs ${seen.version}, this command is ${io.version}. Update it: ${updateWords(io, seen.version)}\n`
2617
- : `This command (${io.version}) is newer than the console (${seen.version}, which takes ${seen.range ?? "an older line"}): what the console has not got yet is refused as not found. The console's operator updates it; to match the console meanwhile: ${updateWords(io, seen.version)}\n`,
2668
+ : `This command (${io.version}) is newer than the console (${seen.version}, which takes ${seen.range ?? "an older line"}): it works, and what the console has not got yet is refused as not found; nothing to do until then. The console's operator updates it; to match the console meanwhile: ${updateWords(io, seen.version)}\n`,
2618
2669
  );
2619
2670
  await io.notices?.save({
2620
2671
  command: io.version,