@skillcdn/console 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/README.md +19 -2
  2. package/SKILLCDN.md +7 -5
  3. package/dist/api.d.ts +4 -3
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/api.js +4 -3
  6. package/dist/api.js.map +1 -1
  7. package/dist/cli/cli.d.ts.map +1 -1
  8. package/dist/cli/cli.js +543 -34
  9. package/dist/cli/cli.js.map +1 -1
  10. package/dist/cli/format.d.ts +34 -3
  11. package/dist/cli/format.d.ts.map +1 -1
  12. package/dist/cli/format.js +277 -6
  13. package/dist/cli/format.js.map +1 -1
  14. package/dist/cli/help.d.ts.map +1 -1
  15. package/dist/cli/help.js +35 -2
  16. package/dist/cli/help.js.map +1 -1
  17. package/dist/cli/plugin-check.d.ts +21 -0
  18. package/dist/cli/plugin-check.d.ts.map +1 -0
  19. package/dist/cli/plugin-check.js +374 -0
  20. package/dist/cli/plugin-check.js.map +1 -0
  21. package/dist/cli/plugin-cli.d.ts +20 -0
  22. package/dist/cli/plugin-cli.d.ts.map +1 -0
  23. package/dist/cli/plugin-cli.js +123 -0
  24. package/dist/cli/plugin-cli.js.map +1 -0
  25. package/dist/cli/plugin-init.d.ts +20 -0
  26. package/dist/cli/plugin-init.d.ts.map +1 -0
  27. package/dist/cli/plugin-init.js +74 -0
  28. package/dist/cli/plugin-init.js.map +1 -0
  29. package/dist/cli/plugin-main.d.ts +3 -0
  30. package/dist/cli/plugin-main.d.ts.map +1 -0
  31. package/dist/cli/plugin-main.js +28 -0
  32. package/dist/cli/plugin-main.js.map +1 -0
  33. package/dist/client.d.ts +97 -1
  34. package/dist/client.d.ts.map +1 -1
  35. package/dist/client.js +63 -2
  36. package/dist/client.js.map +1 -1
  37. package/dist/compat.d.ts +1 -1
  38. package/dist/compat.js +2 -2
  39. package/dist/components/board.d.ts +8 -0
  40. package/dist/components/board.d.ts.map +1 -1
  41. package/dist/components/board.js +59 -15
  42. package/dist/components/board.js.map +1 -1
  43. package/dist/components/event-feed.d.ts +1 -1
  44. package/dist/components/event-feed.d.ts.map +1 -1
  45. package/dist/components/event-feed.js +33 -1
  46. package/dist/components/event-feed.js.map +1 -1
  47. package/dist/components/plugin-frame.d.ts +52 -0
  48. package/dist/components/plugin-frame.d.ts.map +1 -0
  49. package/dist/components/plugin-frame.js +314 -0
  50. package/dist/components/plugin-frame.js.map +1 -0
  51. package/dist/components/plugin-settings.d.ts +38 -0
  52. package/dist/components/plugin-settings.d.ts.map +1 -0
  53. package/dist/components/plugin-settings.js +348 -0
  54. package/dist/components/plugin-settings.js.map +1 -0
  55. package/dist/components/shell.d.ts +2 -0
  56. package/dist/components/shell.d.ts.map +1 -1
  57. package/dist/components/shell.js +26 -1
  58. package/dist/components/shell.js.map +1 -1
  59. package/dist/components/workspace.d.ts +7 -1
  60. package/dist/components/workspace.d.ts.map +1 -1
  61. package/dist/components/workspace.js +1 -1
  62. package/dist/components/workspace.js.map +1 -1
  63. package/dist/console.css +327 -0
  64. package/dist/console.d.ts.map +1 -1
  65. package/dist/console.js +136 -26
  66. package/dist/console.js.map +1 -1
  67. package/dist/data.d.ts +7 -1
  68. package/dist/data.d.ts.map +1 -1
  69. package/dist/data.js +19 -2
  70. package/dist/data.js.map +1 -1
  71. package/dist/i18n/en.d.ts +149 -0
  72. package/dist/i18n/en.d.ts.map +1 -1
  73. package/dist/i18n/en.js +160 -0
  74. package/dist/i18n/en.js.map +1 -1
  75. package/dist/i18n/ko.d.ts.map +1 -1
  76. package/dist/i18n/ko.js +144 -0
  77. package/dist/i18n/ko.js.map +1 -1
  78. package/dist/index.d.ts +3 -1
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +3 -1
  81. package/dist/index.js.map +1 -1
  82. package/dist/plugin/args.d.ts +148 -0
  83. package/dist/plugin/args.d.ts.map +1 -0
  84. package/dist/plugin/args.js +369 -0
  85. package/dist/plugin/args.js.map +1 -0
  86. package/dist/plugin/bridge.d.ts +146 -0
  87. package/dist/plugin/bridge.d.ts.map +1 -0
  88. package/dist/plugin/bridge.js +125 -0
  89. package/dist/plugin/bridge.js.map +1 -0
  90. package/dist/plugin/capabilities.d.ts +20 -0
  91. package/dist/plugin/capabilities.d.ts.map +1 -0
  92. package/dist/plugin/capabilities.js +133 -0
  93. package/dist/plugin/capabilities.js.map +1 -0
  94. package/dist/plugin/context.d.ts +374 -0
  95. package/dist/plugin/context.d.ts.map +1 -0
  96. package/dist/plugin/context.js +2 -0
  97. package/dist/plugin/context.js.map +1 -0
  98. package/dist/plugin/define.d.ts +208 -0
  99. package/dist/plugin/define.d.ts.map +1 -0
  100. package/dist/plugin/define.js +482 -0
  101. package/dist/plugin/define.js.map +1 -0
  102. package/dist/plugin/definition-error.d.ts +11 -0
  103. package/dist/plugin/definition-error.d.ts.map +1 -0
  104. package/dist/plugin/definition-error.js +17 -0
  105. package/dist/plugin/definition-error.js.map +1 -0
  106. package/dist/plugin/limits.d.ts +67 -0
  107. package/dist/plugin/limits.d.ts.map +1 -0
  108. package/dist/plugin/limits.js +70 -0
  109. package/dist/plugin/limits.js.map +1 -0
  110. package/dist/plugin/load-worker.d.ts +7 -0
  111. package/dist/plugin/load-worker.d.ts.map +1 -0
  112. package/dist/plugin/load-worker.js +41 -0
  113. package/dist/plugin/load-worker.js.map +1 -0
  114. package/dist/plugin/load.d.ts +18 -0
  115. package/dist/plugin/load.d.ts.map +1 -0
  116. package/dist/plugin/load.js +62 -0
  117. package/dist/plugin/load.js.map +1 -0
  118. package/dist/plugin/manifest.d.ts +237 -0
  119. package/dist/plugin/manifest.d.ts.map +1 -0
  120. package/dist/plugin/manifest.js +411 -0
  121. package/dist/plugin/manifest.js.map +1 -0
  122. package/dist/plugin/refusals.d.ts +49 -0
  123. package/dist/plugin/refusals.d.ts.map +1 -0
  124. package/dist/plugin/refusals.js +79 -0
  125. package/dist/plugin/refusals.js.map +1 -0
  126. package/dist/plugin/rest-schemas.d.ts +556 -0
  127. package/dist/plugin/rest-schemas.d.ts.map +1 -0
  128. package/dist/plugin/rest-schemas.js +262 -0
  129. package/dist/plugin/rest-schemas.js.map +1 -0
  130. package/dist/plugin/sandbox.d.ts +23 -0
  131. package/dist/plugin/sandbox.d.ts.map +1 -0
  132. package/dist/plugin/sandbox.js +99 -0
  133. package/dist/plugin/sandbox.js.map +1 -0
  134. package/dist/plugin/testing.d.ts +45 -0
  135. package/dist/plugin/testing.d.ts.map +1 -0
  136. package/dist/plugin/testing.js +420 -0
  137. package/dist/plugin/testing.js.map +1 -0
  138. package/dist/plugin/web-react.d.ts +11 -0
  139. package/dist/plugin/web-react.d.ts.map +1 -0
  140. package/dist/plugin/web-react.js +34 -0
  141. package/dist/plugin/web-react.js.map +1 -0
  142. package/dist/plugin/web.d.ts +95 -0
  143. package/dist/plugin/web.d.ts.map +1 -0
  144. package/dist/plugin/web.js +245 -0
  145. package/dist/plugin/web.js.map +1 -0
  146. package/dist/plugin-nav.d.ts +29 -0
  147. package/dist/plugin-nav.d.ts.map +1 -0
  148. package/dist/plugin-nav.js +92 -0
  149. package/dist/plugin-nav.js.map +1 -0
  150. package/dist/plugin-testing.d.ts +2 -0
  151. package/dist/plugin-testing.d.ts.map +1 -0
  152. package/dist/plugin-testing.js +4 -0
  153. package/dist/plugin-testing.js.map +1 -0
  154. package/dist/plugin-web-react.d.ts +3 -0
  155. package/dist/plugin-web-react.d.ts.map +1 -0
  156. package/dist/plugin-web-react.js +3 -0
  157. package/dist/plugin-web-react.js.map +1 -0
  158. package/dist/plugin-web.d.ts +4 -0
  159. package/dist/plugin-web.d.ts.map +1 -0
  160. package/dist/plugin-web.js +5 -0
  161. package/dist/plugin-web.js.map +1 -0
  162. package/dist/plugin.d.ts +11 -0
  163. package/dist/plugin.d.ts.map +1 -0
  164. package/dist/plugin.js +15 -0
  165. package/dist/plugin.js.map +1 -0
  166. package/dist/router.d.ts +28 -0
  167. package/dist/router.d.ts.map +1 -1
  168. package/dist/router.js +47 -0
  169. package/dist/router.js.map +1 -1
  170. package/dist/routes.d.ts +4 -0
  171. package/dist/routes.d.ts.map +1 -1
  172. package/dist/routes.js +6 -0
  173. package/dist/routes.js.map +1 -1
  174. package/dist/schemas.d.ts +117 -0
  175. package/dist/schemas.d.ts.map +1 -1
  176. package/dist/schemas.js +40 -1
  177. package/dist/schemas.js.map +1 -1
  178. package/dist/tokens.d.ts +2 -0
  179. package/dist/tokens.d.ts.map +1 -0
  180. package/dist/tokens.js +101 -0
  181. package/dist/tokens.js.map +1 -0
  182. package/dist/vocabulary.d.ts +11 -1
  183. package/dist/vocabulary.d.ts.map +1 -1
  184. package/dist/vocabulary.js +24 -0
  185. package/dist/vocabulary.js.map +1 -1
  186. package/dist/web.d.ts.map +1 -1
  187. package/dist/web.js +2 -0
  188. package/dist/web.js.map +1 -1
  189. package/docs/specs/cli.md +11 -4
  190. package/docs/specs/plugins.md +487 -0
  191. package/docs/specs/rest.md +3 -2
  192. package/package.json +20 -2
  193. package/skills/building-a-console/SKILL.md +25 -26
  194. package/skills/building-a-console/references/configuring.md +40 -0
  195. package/skills/building-a-console/references/the-default-console.md +1 -1
  196. package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
  197. package/skills/building-a-plugin/SKILL.md +137 -0
  198. package/skills/deploying-a-console/SKILL.md +24 -26
  199. package/skills/deploying-a-console/references/access.md +13 -9
  200. package/skills/deploying-a-console/references/resources.md +25 -10
  201. package/skills/deploying-a-console/references/running-it-again.md +15 -0
  202. package/skills/deploying-a-console/references/sizing.md +2 -0
  203. package/skills/planning-the-board/SKILL.md +96 -0
  204. package/skills/planning-the-board/references/a-task.md +36 -0
  205. package/skills/planning-the-board/references/setting-up.md +44 -0
  206. package/skills/working-the-board/SKILL.md +43 -34
  207. package/skills/working-the-board/references/the-pack.md +19 -0
  208. package/src/api.ts +8 -0
  209. package/src/cli/cli.ts +697 -33
  210. package/src/cli/format.ts +356 -9
  211. package/src/cli/help.ts +41 -2
  212. package/src/cli/plugin-check.ts +470 -0
  213. package/src/cli/plugin-cli.ts +148 -0
  214. package/src/cli/plugin-init.ts +90 -0
  215. package/src/cli/plugin-main.ts +30 -0
  216. package/src/client.ts +294 -0
  217. package/src/compat.ts +2 -2
  218. package/src/components/board.tsx +62 -14
  219. package/src/components/event-feed.tsx +42 -1
  220. package/src/components/plugin-frame.tsx +437 -0
  221. package/src/components/plugin-settings.tsx +1111 -0
  222. package/src/components/shell.tsx +82 -14
  223. package/src/components/workspace.tsx +18 -0
  224. package/src/console.tsx +286 -19
  225. package/src/data.ts +27 -2
  226. package/src/i18n/en.ts +168 -0
  227. package/src/i18n/ko.ts +148 -0
  228. package/src/index.ts +12 -0
  229. package/src/plugin/args.ts +526 -0
  230. package/src/plugin/bridge.ts +314 -0
  231. package/src/plugin/capabilities.ts +150 -0
  232. package/src/plugin/context.ts +416 -0
  233. package/src/plugin/define.ts +792 -0
  234. package/src/plugin/definition-error.ts +19 -0
  235. package/src/plugin/limits.ts +70 -0
  236. package/src/plugin/load-worker.ts +50 -0
  237. package/src/plugin/load.ts +82 -0
  238. package/src/plugin/manifest.ts +561 -0
  239. package/src/plugin/refusals.ts +94 -0
  240. package/src/plugin/rest-schemas.ts +354 -0
  241. package/src/plugin/sandbox.ts +123 -0
  242. package/src/plugin/testing.ts +612 -0
  243. package/src/plugin/web-react.ts +45 -0
  244. package/src/plugin/web.ts +403 -0
  245. package/src/plugin-nav.ts +132 -0
  246. package/src/plugin-testing.ts +10 -0
  247. package/src/plugin-web-react.ts +4 -0
  248. package/src/plugin-web.ts +35 -0
  249. package/src/plugin.ts +126 -0
  250. package/src/router.ts +73 -0
  251. package/src/routes.ts +7 -0
  252. package/src/schemas.ts +43 -0
  253. package/src/styles/console.css +327 -0
  254. package/src/tokens.ts +101 -0
  255. package/src/vocabulary.ts +30 -0
  256. package/src/web.ts +2 -0
  257. package/template/plugin/.gitignore +2 -0
  258. package/template/plugin/README.md +26 -0
  259. package/template/plugin/SKILLCDN.md +13 -0
  260. package/template/plugin/build.mjs +40 -0
  261. package/template/plugin/console-plugin.json +50 -0
  262. package/template/plugin/package.json +27 -0
  263. package/template/plugin/skills/__key__/SKILL.md +23 -0
  264. package/template/plugin/src/check.test.ts +34 -0
  265. package/template/plugin/src/server/index.test.ts +41 -0
  266. package/template/plugin/src/server/index.ts +72 -0
  267. package/template/plugin/src/web/index.css +51 -0
  268. package/template/plugin/src/web/index.ts +115 -0
  269. package/template/plugin/tsconfig.json +17 -0
  270. package/skills/working-the-board/references/setting-up.md +0 -3
  271. /package/skills/{working-the-board → planning-the-board}/references/project-rules.md +0 -0
package/src/cli/format.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { argUsage } from "../plugin/args.js";
1
2
  import type {
2
3
  RestArtifact,
3
4
  RestCategory,
@@ -11,9 +12,22 @@ import type {
11
12
  RestMember,
12
13
  RestPerson,
13
14
  RestPersonWork,
15
+ RestPlugin,
16
+ RestPluginArg,
17
+ RestPluginDetail,
18
+ RestPluginGrant,
19
+ RestPluginInvocation,
20
+ RestPluginOperation,
21
+ RestPluginRecord,
22
+ RestPluginRecordPage,
23
+ RestPluginRecordRead,
24
+ RestPluginRecordVersions,
25
+ RestPluginSetting,
26
+ RestPlugins,
14
27
  RestProject,
15
28
  RestProjectRules,
16
29
  RestRules,
30
+ RestRulesPlugin,
17
31
  RestRun,
18
32
  RestTask,
19
33
  RestVersion,
@@ -320,16 +334,26 @@ function formatRulesHalf(where: "the workspace" | "this project", rules: RestRul
320
334
  return `${head}\n\n${rules.body.trimEnd()}`;
321
335
  }
322
336
 
323
- /** The rules that hold: the workspace's, and what the project adds when the command works in one. */
324
- export function formatRules(rules: RestProjectRules | RestRules): string {
325
- if ("workspace" in rules) {
326
- return [
327
- formatRulesHalf("the workspace", rules.workspace),
328
- "",
329
- formatRulesHalf("this project", rules.project),
330
- ].join("\n");
337
+ /**
338
+ * The rules that hold: the workspace's, and what the project adds when the command works in one;
339
+ * then the plugins the console names after them, a line each, where there are any.
340
+ */
341
+ export function formatRules(
342
+ rules: RestProjectRules | RestRules,
343
+ language?: string | null | undefined,
344
+ ): string {
345
+ const parts =
346
+ "workspace" in rules
347
+ ? [
348
+ formatRulesHalf("the workspace", rules.workspace),
349
+ "",
350
+ formatRulesHalf("this project", rules.project),
351
+ ]
352
+ : [formatRulesHalf("the workspace", rules)];
353
+ if (rules.plugins !== undefined && rules.plugins.length > 0) {
354
+ parts.push("", formatRulesPlugins(rules.plugins, language));
331
355
  }
332
- return formatRulesHalf("the workspace", rules);
356
+ return parts.join("\n");
333
357
  }
334
358
 
335
359
  /** How far a goal is, from its tasks: the done, the at work, and the rest, in one phrase. */
@@ -536,3 +560,326 @@ export function formatVersion(path: string, version: RestVersion): string {
536
560
  }
537
561
 
538
562
  /** The text of one path across the pages a skill came in: the fragments joined in order. */
563
+
564
+ // ---- The plugins (docs/specs/plugins.md, "The command") ----
565
+
566
+ /** A text in the person's language, else in English, else the first the manifest has; nothing for none. */
567
+ export function localized(
568
+ texts: Readonly<Record<string, string>> | null | undefined,
569
+ language: string | null | undefined,
570
+ ): string {
571
+ if (texts === null || texts === undefined) {
572
+ return "";
573
+ }
574
+ const tag = language ?? "en";
575
+ return texts[tag] ?? texts[tag.split("-")[0] ?? tag] ?? texts.en ?? Object.values(texts)[0] ?? "";
576
+ }
577
+
578
+ /** One plugin in a line: its key, its name and version, its state, the roles held in it, its problems. */
579
+ export function formatPluginLine(plugin: RestPlugin, language: string | null | undefined): string {
580
+ const notes = [
581
+ plugin.roles.length === 0 ? undefined : `roles: ${plugin.roles.join(", ")}`,
582
+ plugin.problems.length === 0 ? undefined : `problems: ${plugin.problems.join("; ")}`,
583
+ ].filter((note): note is string => note !== undefined);
584
+ return `${plugin.key} ${localized(plugin.name, language)} ${plugin.version} ${plugin.state}${notes.length === 0 ? "" : ` ${notes.join(" ")}`}`;
585
+ }
586
+
587
+ /** The plugins one may use, a line each, and how to go on. */
588
+ export function formatPlugins(plugins: RestPlugins, language: string | null | undefined): string {
589
+ if (plugins.items.length === 0) {
590
+ return "This console has no plugins you may use.";
591
+ }
592
+ return [
593
+ ...plugins.items.map((plugin) => formatPluginLine(plugin, language)),
594
+ "",
595
+ "One plugin, with its commands: console plugin <key>. One command in full: console plugin <key> help <command>.",
596
+ ].join("\n");
597
+ }
598
+
599
+ /** How a command or a query of a plugin is given, as help prints it: the positionals, then the flags. */
600
+ export function formatOperationUsage(key: string, operation: RestPluginOperation): string {
601
+ const entries = Object.entries(operation.args);
602
+ const usage = [
603
+ ...entries.filter(([, arg]) => arg.positional).map(([name, arg]) => argUsage(name, arg)),
604
+ ...entries.filter(([, arg]) => !arg.positional).map(([name, arg]) => argUsage(name, arg)),
605
+ ];
606
+ return `console plugin ${key} ${operation.name}${usage.map((part) => ` ${part}`).join("")}`;
607
+ }
608
+
609
+ /** What an argument takes, in a line: its form, its type with its bounds, and what it is for. */
610
+ function formatArgLine(name: string, arg: RestPluginArg): string {
611
+ const kind = (description: RestPluginArg): string => {
612
+ switch (description.type) {
613
+ case "text":
614
+ case "markdown":
615
+ return `${description.type}${description.max === undefined ? "" : `, up to ${description.max} characters`}${description.secret === true ? ", hidden in the event" : ""}`;
616
+ case "number": {
617
+ const bounds = [
618
+ description.integer === true ? "whole" : undefined,
619
+ description.min === undefined ? undefined : `from ${description.min}`,
620
+ description.max === undefined ? undefined : `to ${description.max}`,
621
+ ].filter((bound): bound is string => bound !== undefined);
622
+ return `number${bounds.length === 0 ? "" : ` (${bounds.join(", ")})`}`;
623
+ }
624
+ case "boolean":
625
+ return "true or false; the flag alone is true";
626
+ case "enum":
627
+ return `one of ${(description.options ?? []).join(", ")}`;
628
+ case "day":
629
+ return "a day as YYYY-MM-DD";
630
+ case "person":
631
+ return "a login, or me";
632
+ case "project":
633
+ return "a project's key";
634
+ case "record":
635
+ return `the id of a record of ${description.collection ?? "a collection"}`;
636
+ case "file":
637
+ return "a file: --file <path>, or - for standard input";
638
+ case "list":
639
+ return `a list of ${description.of === undefined ? "items" : kind(description.of)}${description.max === undefined ? "" : `, up to ${description.max}`}; the flag given again, or commas between`;
640
+ default:
641
+ return description.type;
642
+ }
643
+ };
644
+ const about = arg.about === undefined ? "" : `: ${arg.about}`;
645
+ return ` ${argUsage(name, arg)} ${kind(arg)}${arg.optional ? ", optional" : ""}${about}`;
646
+ }
647
+
648
+ /** The refusals a plugin's command or query may meet, as `console help` words them. */
649
+ const PLUGIN_COMMAND_REFUSALS = [
650
+ "plugin.invalid_args: a word does not fit the definition; the message names the first at fault",
651
+ "plugin.forbidden: the command needs a role you do not hold in the plugin",
652
+ "plugin.disabled, plugin.unavailable: the plugin is not enabled, or cannot run right now",
653
+ "plugin.timeout: the plugin ran past its time",
654
+ "plugin.<key>.<code>: the plugin's own refusal, in its own words",
655
+ ];
656
+
657
+ /** One command or query of a plugin in full, as `console help <command>` prints one; or every one in a line. */
658
+ export function formatPluginHelp(detail: RestPluginDetail, name: string | undefined): string {
659
+ if (name === undefined) {
660
+ if (detail.operations.length === 0) {
661
+ return `${detail.key} has no command or query you may run.`;
662
+ }
663
+ return [
664
+ ...detail.operations.flatMap((operation) => [
665
+ formatOperationUsage(detail.key, operation),
666
+ ` ${operation.kind === "query" ? "[query] " : ""}${operation.about}`,
667
+ ]),
668
+ "",
669
+ `One in full: console plugin ${detail.key} help <command>.`,
670
+ ].join("\n");
671
+ }
672
+ const operation = detail.operations.find((candidate) => candidate.name === name);
673
+ if (operation === undefined) {
674
+ return `${detail.key} has no command or query ${name} you may run.`;
675
+ }
676
+ const args = Object.entries(operation.args);
677
+ const parts = [
678
+ formatOperationUsage(detail.key, operation),
679
+ "",
680
+ `${operation.kind === "query" ? "A query: reads only, and answers JSON. " : "A command. "}${operation.about}`,
681
+ ];
682
+ if (args.length > 0) {
683
+ parts.push(
684
+ "",
685
+ "Arguments (a positional one by its place, the others as --<name> <value> or --<name>=<value>):",
686
+ ...args.map(([argName, arg]) => formatArgLine(argName, arg)),
687
+ );
688
+ }
689
+ if (operation.roles !== null) {
690
+ parts.push("", `Roles: ${operation.roles.join(", ")}`);
691
+ }
692
+ parts.push(
693
+ "",
694
+ operation.kind === "query"
695
+ ? "Prints the answer as JSON; a query that answers a file saves it under its name."
696
+ : "Prints what the command says; --json prints its data. A command that raised a decision prints its id and exits with 3: console decision <id> --wait 100 waits for the answer.",
697
+ "",
698
+ "Refusals:",
699
+ ...PLUGIN_COMMAND_REFUSALS.map((refusal) => ` ${refusal}`),
700
+ );
701
+ return parts.join("\n");
702
+ }
703
+
704
+ /** A setting in a line: its key and type, what it holds or whether it is set, and its name. */
705
+ export function formatPluginSettingLine(
706
+ setting: RestPluginSetting,
707
+ language: string | null | undefined,
708
+ ): string {
709
+ const holds =
710
+ setting.type === "secret"
711
+ ? setting.set
712
+ ? `set${setting.setAt === null ? "" : ` ${moment(setting.setAt)}`}`
713
+ : "not set"
714
+ : setting.set
715
+ ? setting.value === null
716
+ ? "set"
717
+ : JSON.stringify(setting.value)
718
+ : setting.default === null
719
+ ? "not set"
720
+ : `default ${JSON.stringify(setting.default)}`;
721
+ return ` ${setting.key} (${setting.type}${setting.required ? ", required" : ""}): ${holds} ${localized(setting.name, language)}`;
722
+ }
723
+
724
+ /** A grant in a line: its id, the role, whom it is to, since when. */
725
+ export function formatPluginGrantLine(grant: RestPluginGrant): string {
726
+ const whom =
727
+ grant.person !== null
728
+ ? `person ${grant.person.login}`
729
+ : grant.project !== null
730
+ ? `the members of ${grant.project.key}`
731
+ : "everyone";
732
+ return `${grant.id} ${grant.role} to ${whom} since ${moment(grant.createdAt)}`;
733
+ }
734
+
735
+ /** One plugin in full: what it is, how it stands, what one may run, its skills and its settings. */
736
+ export function formatPlugin(
737
+ detail: RestPluginDetail,
738
+ language: string | null | undefined,
739
+ ): string {
740
+ const name = localized(detail.name, language);
741
+ const lines = [`${detail.key}: ${name} ${detail.version}, ${detail.state}`];
742
+ const description = localized(detail.description, language);
743
+ if (description.length > 0) {
744
+ lines.push(description);
745
+ }
746
+ const note = localized(detail.agentNote, language);
747
+ if (note.length > 0) {
748
+ lines.push(`For agents: ${note}`);
749
+ }
750
+ if (detail.problems.length > 0) {
751
+ lines.push(`problems: ${detail.problems.join("; ")}`);
752
+ }
753
+ lines.push(
754
+ detail.roles.length === 0
755
+ ? "You hold no role in it."
756
+ : `Your roles in it: ${detail.roles.join(", ")}.`,
757
+ );
758
+ if (detail.board !== null) {
759
+ lines.push(`Board project: ${detail.board.key} (${detail.board.name}).`);
760
+ } else if (detail.boardNeeds !== "none") {
761
+ lines.push("Board project: none yet.");
762
+ }
763
+ lines.push("");
764
+ if (detail.operations.length === 0) {
765
+ lines.push("No command or query you may run.");
766
+ } else {
767
+ lines.push("Commands and queries:");
768
+ for (const operation of detail.operations) {
769
+ lines.push(
770
+ ` ${formatOperationUsage(detail.key, operation)}`,
771
+ ` ${operation.kind === "query" ? "[query] " : ""}${operation.about}`,
772
+ );
773
+ }
774
+ lines.push(`One in full: console plugin ${detail.key} help <command>.`);
775
+ }
776
+ if (detail.skills !== null) {
777
+ lines.push(
778
+ "",
779
+ `Skills: console plugin ${detail.key} skills lists them; console plugin ${detail.key} skill <path> reads one. Read the one that fits before the first command.`,
780
+ );
781
+ }
782
+ if (detail.settings.length > 0) {
783
+ lines.push(
784
+ "",
785
+ "Settings:",
786
+ ...detail.settings.map((setting) => formatPluginSettingLine(setting, language)),
787
+ );
788
+ }
789
+ if (detail.grants !== null) {
790
+ lines.push(
791
+ "",
792
+ detail.grants.length === 0
793
+ ? "Grants: none; the default role, where the manifest names one, is everyone's."
794
+ : "Grants:",
795
+ ...detail.grants.map((grant) => ` ${formatPluginGrantLine(grant)}`),
796
+ );
797
+ }
798
+ return lines.join("\n");
799
+ }
800
+
801
+ /** What a command or a query answered, as the command prints it. */
802
+ export function formatInvocation(result: RestPluginInvocation): string {
803
+ if (result.kind === "query") {
804
+ return result.data === undefined ? "null" : JSON.stringify(result.data, null, 2);
805
+ }
806
+ const text = result.text.length === 0 ? "Done." : result.text;
807
+ return result.waiting === null
808
+ ? text
809
+ : `${text}\ndecision id: ${result.waiting}\nThe decision waits for a person: console decision ${result.waiting} --wait 100`;
810
+ }
811
+
812
+ /** A record's data on one line, cut where it runs long. */
813
+ function compactData(data: Readonly<Record<string, unknown>>): string {
814
+ const text = JSON.stringify(data);
815
+ return text.length > 200 ? `${text.slice(0, 197)}...` : text;
816
+ }
817
+
818
+ /** A record in a line: its id, its version, when it last changed, whether it is put away, its data. */
819
+ export function formatPluginRecordLine(record: RestPluginRecord): string {
820
+ return `${record.id} v${record.version} ${moment(record.updatedAt)}${record.archivedAt === null ? "" : " archived"} ${compactData(record.data)}`;
821
+ }
822
+
823
+ /** A page of records, and how the next is asked for. */
824
+ export function formatPluginRecords(page: RestPluginRecordPage): string {
825
+ if (page.items.length === 0) {
826
+ return "No records.";
827
+ }
828
+ const lines = page.items.map(formatPluginRecordLine);
829
+ if (page.after !== null) {
830
+ lines.push(`More: --after ${page.after}`);
831
+ }
832
+ return lines.join("\n");
833
+ }
834
+
835
+ /** One record in full, or one version of it as it was. */
836
+ export function formatPluginRecord(record: RestPluginRecordRead): string {
837
+ if (!("id" in record)) {
838
+ return [
839
+ `version ${record.version}, ${moment(record.at)}, by ${record.by ?? "the console"}`,
840
+ JSON.stringify(record.data, null, 2),
841
+ ].join("\n");
842
+ }
843
+ const facts = [
844
+ `id: ${record.id} collection: ${record.collection} version: ${record.version}`,
845
+ `created: ${moment(record.createdAt)} by ${record.createdBy ?? "the console"} updated: ${moment(record.updatedAt)} by ${record.updatedBy ?? "the console"}`,
846
+ ];
847
+ if (record.project !== null) {
848
+ facts.push(`project: ${record.project}`);
849
+ }
850
+ if (record.owner !== null) {
851
+ facts.push(`owner: ${record.owner}`);
852
+ }
853
+ if (record.archivedAt !== null) {
854
+ facts.push(`archived: ${moment(record.archivedAt)}`);
855
+ }
856
+ return [...facts, JSON.stringify(record.data, null, 2)].join("\n");
857
+ }
858
+
859
+ /** The versions of a record, newest first, a line each. */
860
+ export function formatPluginRecordVersions(versions: RestPluginRecordVersions): string {
861
+ if (versions.items.length === 0) {
862
+ return "No versions.";
863
+ }
864
+ return versions.items
865
+ .map(
866
+ (version) =>
867
+ `v${version.version} ${moment(version.at)} by ${version.by ?? "the console"} ${compactData(version.data)}`,
868
+ )
869
+ .join("\n");
870
+ }
871
+
872
+ /** The plugins named after the rules: a line each, with the sentence for agents and where to start. */
873
+ export function formatRulesPlugins(
874
+ plugins: readonly RestRulesPlugin[],
875
+ language: string | null | undefined,
876
+ ): string {
877
+ return [
878
+ "## Plugins",
879
+ "",
880
+ ...plugins.map((plugin) => {
881
+ const note = localized(plugin.agentNote, language) || localized(plugin.description, language);
882
+ return `- ${plugin.key} (${localized(plugin.name, language)}): ${note} Start with console plugin ${plugin.key}.`;
883
+ }),
884
+ ].join("\n");
885
+ }
package/src/cli/help.ts CHANGED
@@ -360,7 +360,7 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
360
360
  synopsis:
361
361
  "What this organization and this project ask of agents; rules write writes them, for an owner or an administrator.",
362
362
  about:
363
- "What this organization asks of agents (ADR-0023): the workspace's rules, the console's own until an administrator writes the organization's, and this project's own when it has them. rules write writes this project's rules, for an owner, or with --workspace the workspace's, for an administrator; none puts the written rules away, so that the console's own hold again, or the project adds none. Without a project named, rules shows the workspace's alone.",
363
+ "What this organization asks of agents (ADR-0023): the workspace's rules, the console's own until an administrator writes the organization's, and this project's own when it has them. rules write writes this project's rules, for an owner, or with --workspace the workspace's, for an administrator; none puts the written rules away, so that the console's own hold again, or the project adds none. Without a project named, rules shows the workspace's alone. After the rules, a section Plugins names the plugins bound to the project that you hold a role in, or at the workspace every plugin you may use, a line each with the sentence its manifest gives agents and where to start: console plugin <key>.",
364
364
  limits: [
365
365
  `body: Markdown, up to ${MAX_RULES_LENGTH} characters; the console's own rules, written as they are, write nothing`,
366
366
  ],
@@ -369,11 +369,45 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
369
369
  "document.conflict: the rules have moved on since --base; read them again",
370
370
  ],
371
371
  },
372
+ plugins: {
373
+ usage: "plugins",
374
+ synopsis: "The plugins of this console you may use: key, name, version, state, your roles.",
375
+ about:
376
+ "The plugins of this console (docs/specs/plugins.md), one a line: the enabled ones a member may use, every one with its state for an administrator; the key, the name, the version, the state, the roles you hold in it. A plugin is worked through console plugin <key>, and the rules of a board name the plugins to use there under Plugins, a sentence each. Needs no project.",
377
+ },
378
+ plugin: {
379
+ usage:
380
+ "plugin <key> | plugin <key> help [<command>] | plugin <key> <command> [<args>] [--<name> <value>]... [--file <path> | -] [--run <id>] | plugin <key> records <collection> [<id>] [--where <field>=<value>]... [--search <words>] [--project <key>] [--order <field>[:desc]] [--limit <n>] [--after <cursor>] [--archived] [--versions] | plugin <key> skills [<folder>|<file>] [--search <words> [--limit <n>] [--cursor <cursor>]] | plugin <key> skill <path> | plugin <key> skill --file <path> [--offset <n>] [--limit <n>] | plugin <key> enable --consent <capability>,... [--board new|<key>] [--projects all|<key>,...] [--set <key>=<value>]... | plugin <key> disable | plugin <key> settings [--set <key>=<value>]... | plugin <key> secret <name> [--remove] | plugin <key> grant <role> --person <login>|--project <key>|--everyone | plugin <key> revoke <grant id>",
381
+ overview:
382
+ "plugin <key> [help [<command>] | <command> [<args>] | records <collection> | skills | skill <path> | enable | disable | settings | secret | grant | revoke]",
383
+ synopsis:
384
+ "One plugin: what it is for and its commands; a command run as you; its records, its skills; its settings, for an administrator.",
385
+ about:
386
+ "One plugin of this console, by its key. Alone: what it is, the sentence for agents, its state, the roles you hold in it, its board project, then one line per command and query you may run, with its usage, where its skills are, and its settings. help [<command>]: one command or query in full, as console help prints one: its arguments, their types and bounds, and the refusals; every argument is given by its place when positional, else as --<name> <value> or --<name>=<value>; a boolean as --<name> alone; a list as the flag given again, or with commas between; a day as YYYY-MM-DD; a person by login, or me; a project by key; a file by --file <path>, or - for standard input, which the command uploads first, with the plugin's files capability. <command> [<args>]: runs the command or the query as the person whose token you hold; the console parses the words against the definition and refuses the first that does not fit; prints what the command says, or a query's answer as JSON, a file a query answers saved under its name; --json prints the whole answer. A command that raised a decision prints its id and exits with 3: console decision <id> --wait 100 waits for the answer. The event the command leaves names your open run in the plugin's board project, --run <id> saying which when several are open; an argument marked secret is hidden in it. records <collection>: the records you may read, one a line, newest first; --where <field>=<value>, given again for several, narrows by a field the collection indexes (id, owner and project always; a number, true, false and null are read as such); --search <words> by words; --project <key> to those linked to a project; --order <field>, with :desc for the newest first; a page of --limit with --after <cursor> as the last page printed; --archived the ones put away; <id> reads one record whole, --versions its versions. skills and skill: the plugin's own skills, a pack read from its package, exactly as console skills and console skill read a project's pack. enable, disable, settings, secret, grant and revoke are an administrator's. enable prints what each capability the plugin asks opens and what a malicious plugin would reach through it, and refuses unless --consent names every capability again; --board new makes the board project the manifest proposes, --board <key> chooses one, left out where the plugin needs none; --projects all, or the keys of the projects the plugin is on; --set a setting's value, which settings changes afterwards too, an empty value clearing one, a number or true and false read as such. secret <name> reads the value from standard input, seals it and shows it never; --remove takes it away. grant gives a role to a person by login, to the members of a project by key, or to everyone, and revoke takes a grant back by its id, which console plugin <key> lists to an administrator.",
387
+ limits: [
388
+ "a command line: up to 200 words of up to 50,000 characters each; one file, up to the file bound (console help hand-in), with the plugin's files capability",
389
+ "records: a page of up to 200; where: a field the collection indexes",
390
+ "enable: consent names every capability the manifest asks; settings and secrets as the manifest bounds them",
391
+ ],
392
+ refusals: [
393
+ "plugin.not_found: no plugin has that key, or you may not know of it; or the plugin has no such command or query",
394
+ "plugin.disabled: the plugin is installed and not enabled",
395
+ "plugin.unavailable: the plugin failed to load, its host is away, or this deployment lacks what it needs",
396
+ "plugin.forbidden: the command, the record or the file needs a role you do not hold in the plugin, or the plugin lacks the capability",
397
+ "plugin.invalid_args: a word does not fit the definition; the message names the first at fault",
398
+ "plugin.timeout: the plugin's code ran past its time",
399
+ "plugin.too_many: a quota of the plugin is reached",
400
+ "plugin.needs_consent, plugin.requires, plugin.required_by: enabling without consent to every capability; a plugin this one requires is not enabled or not in range; another enabled plugin requires this one",
401
+ "plugin.failed: the plugin's code failed; the console's log says what it said, the answer does not",
402
+ "plugin.<key>.<code>: the plugin's own refusal, in its own words",
403
+ "auth.forbidden: enable, disable, settings, secret, grant and revoke are an administrator's",
404
+ ],
405
+ },
372
406
  take: {
373
407
  usage: "take <number|id> [--agent <name>]",
374
408
  synopsis: "Takes a task: a run begins, and the task is in progress and the person's.",
375
409
  about:
376
- "Takes a task: a run begins, and the task is in progress and the person's; the run's id and the task's id are printed on a line of their own, then the task, then the rules of the board. A run still open on the task is taken over, since the board cannot know whether its agent still runs: it ends as abandoned, the feed says who took over, and that agent is refused on its next command; console task <number> shows the runs on a task, with when each was last heard from, before it is taken. --agent is what people see at work; left out, it is what the person called the token.",
410
+ "Takes a task: a run begins, and the task is in progress and the person's; the run's id and the task's id are printed on a line of their own, then the task, then the rules of the board, with a section Plugins after them where plugins are bound to the project: a line per plugin you may use there, which console plugin <key> then says how to use. A run still open on the task is taken over, since the board cannot know whether its agent still runs: it ends as abandoned, the feed says who took over, and that agent is refused on its next command; console task <number> shows the runs on a task, with when each was last heard from, before it is taken. --agent is what people see at work; left out, it is what the person called the token.",
377
411
  limits: [
378
412
  `agent: one line, up to ${MAX_AGENT_LENGTH} characters`,
379
413
  `at most ${MAX_RUNS_PER_TASK} runs on one task`,
@@ -668,6 +702,11 @@ const SECTIONS: readonly { readonly title: string; readonly commands: readonly s
668
702
  "rules",
669
703
  ],
670
704
  },
705
+ {
706
+ title:
707
+ "The plugins of this console, if it has any: a feature added to the board, worked through one command",
708
+ commands: ["plugins", "plugin"],
709
+ },
671
710
  {
672
711
  title: "Working",
673
712
  commands: [