@skillcdn/console 0.4.1 → 0.4.3

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 (299) hide show
  1. package/README.md +19 -2
  2. package/SKILLCDN.md +7 -5
  3. package/dist/api.d.ts +5 -4
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/api.js +5 -4
  6. package/dist/api.js.map +1 -1
  7. package/dist/cli/cli.d.ts.map +1 -1
  8. package/dist/cli/cli.js +569 -48
  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 +283 -7
  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 +44 -7
  16. package/dist/cli/help.js.map +1 -1
  17. package/dist/cli/plugin-check.d.ts +26 -0
  18. package/dist/cli/plugin-check.d.ts.map +1 -0
  19. package/dist/cli/plugin-check.js +396 -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 +189 -0
  24. package/dist/cli/plugin-cli.js.map +1 -0
  25. package/dist/cli/plugin-init.d.ts +37 -0
  26. package/dist/cli/plugin-init.d.ts.map +1 -0
  27. package/dist/cli/plugin-init.js +206 -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 +100 -3
  34. package/dist/client.d.ts.map +1 -1
  35. package/dist/client.js +79 -7
  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/comments.d.ts.map +1 -1
  44. package/dist/components/comments.js +1 -1
  45. package/dist/components/comments.js.map +1 -1
  46. package/dist/components/decision-form.d.ts +2 -1
  47. package/dist/components/decision-form.d.ts.map +1 -1
  48. package/dist/components/decision-form.js +36 -4
  49. package/dist/components/decision-form.js.map +1 -1
  50. package/dist/components/decision-list.d.ts +4 -0
  51. package/dist/components/decision-list.d.ts.map +1 -1
  52. package/dist/components/decision-list.js +29 -3
  53. package/dist/components/decision-list.js.map +1 -1
  54. package/dist/components/event-feed.d.ts +1 -1
  55. package/dist/components/event-feed.d.ts.map +1 -1
  56. package/dist/components/event-feed.js +33 -1
  57. package/dist/components/event-feed.js.map +1 -1
  58. package/dist/components/my-decisions.d.ts +2 -0
  59. package/dist/components/my-decisions.d.ts.map +1 -1
  60. package/dist/components/my-decisions.js +2 -2
  61. package/dist/components/my-decisions.js.map +1 -1
  62. package/dist/components/plugin-frame.d.ts +52 -0
  63. package/dist/components/plugin-frame.d.ts.map +1 -0
  64. package/dist/components/plugin-frame.js +314 -0
  65. package/dist/components/plugin-frame.js.map +1 -0
  66. package/dist/components/plugin-settings.d.ts +38 -0
  67. package/dist/components/plugin-settings.d.ts.map +1 -0
  68. package/dist/components/plugin-settings.js +348 -0
  69. package/dist/components/plugin-settings.js.map +1 -0
  70. package/dist/components/shell.d.ts +2 -0
  71. package/dist/components/shell.d.ts.map +1 -1
  72. package/dist/components/shell.js +26 -1
  73. package/dist/components/shell.js.map +1 -1
  74. package/dist/components/task-view.d.ts +2 -1
  75. package/dist/components/task-view.d.ts.map +1 -1
  76. package/dist/components/task-view.js.map +1 -1
  77. package/dist/components/workspace.d.ts +7 -1
  78. package/dist/components/workspace.d.ts.map +1 -1
  79. package/dist/components/workspace.js +1 -1
  80. package/dist/components/workspace.js.map +1 -1
  81. package/dist/console.css +330 -1
  82. package/dist/console.d.ts.map +1 -1
  83. package/dist/console.js +152 -41
  84. package/dist/console.js.map +1 -1
  85. package/dist/data.d.ts +9 -2
  86. package/dist/data.d.ts.map +1 -1
  87. package/dist/data.js +45 -9
  88. package/dist/data.js.map +1 -1
  89. package/dist/i18n/en.d.ts +155 -0
  90. package/dist/i18n/en.d.ts.map +1 -1
  91. package/dist/i18n/en.js +166 -0
  92. package/dist/i18n/en.js.map +1 -1
  93. package/dist/i18n/ko.d.ts.map +1 -1
  94. package/dist/i18n/ko.js +150 -0
  95. package/dist/i18n/ko.js.map +1 -1
  96. package/dist/index.d.ts +3 -1
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +3 -1
  99. package/dist/index.js.map +1 -1
  100. package/dist/limits.d.ts +2 -0
  101. package/dist/limits.d.ts.map +1 -1
  102. package/dist/limits.js +2 -0
  103. package/dist/limits.js.map +1 -1
  104. package/dist/plugin/args.d.ts +148 -0
  105. package/dist/plugin/args.d.ts.map +1 -0
  106. package/dist/plugin/args.js +369 -0
  107. package/dist/plugin/args.js.map +1 -0
  108. package/dist/plugin/bridge.d.ts +146 -0
  109. package/dist/plugin/bridge.d.ts.map +1 -0
  110. package/dist/plugin/bridge.js +125 -0
  111. package/dist/plugin/bridge.js.map +1 -0
  112. package/dist/plugin/capabilities.d.ts +20 -0
  113. package/dist/plugin/capabilities.d.ts.map +1 -0
  114. package/dist/plugin/capabilities.js +133 -0
  115. package/dist/plugin/capabilities.js.map +1 -0
  116. package/dist/plugin/context.d.ts +380 -0
  117. package/dist/plugin/context.d.ts.map +1 -0
  118. package/dist/plugin/context.js +2 -0
  119. package/dist/plugin/context.js.map +1 -0
  120. package/dist/plugin/define.d.ts +208 -0
  121. package/dist/plugin/define.d.ts.map +1 -0
  122. package/dist/plugin/define.js +482 -0
  123. package/dist/plugin/define.js.map +1 -0
  124. package/dist/plugin/definition-error.d.ts +11 -0
  125. package/dist/plugin/definition-error.d.ts.map +1 -0
  126. package/dist/plugin/definition-error.js +17 -0
  127. package/dist/plugin/definition-error.js.map +1 -0
  128. package/dist/plugin/limits.d.ts +67 -0
  129. package/dist/plugin/limits.d.ts.map +1 -0
  130. package/dist/plugin/limits.js +70 -0
  131. package/dist/plugin/limits.js.map +1 -0
  132. package/dist/plugin/load-worker.d.ts +7 -0
  133. package/dist/plugin/load-worker.d.ts.map +1 -0
  134. package/dist/plugin/load-worker.js +41 -0
  135. package/dist/plugin/load-worker.js.map +1 -0
  136. package/dist/plugin/load.d.ts +18 -0
  137. package/dist/plugin/load.d.ts.map +1 -0
  138. package/dist/plugin/load.js +62 -0
  139. package/dist/plugin/load.js.map +1 -0
  140. package/dist/plugin/manifest.d.ts +237 -0
  141. package/dist/plugin/manifest.d.ts.map +1 -0
  142. package/dist/plugin/manifest.js +411 -0
  143. package/dist/plugin/manifest.js.map +1 -0
  144. package/dist/plugin/refusals.d.ts +49 -0
  145. package/dist/plugin/refusals.d.ts.map +1 -0
  146. package/dist/plugin/refusals.js +79 -0
  147. package/dist/plugin/refusals.js.map +1 -0
  148. package/dist/plugin/rest-schemas.d.ts +556 -0
  149. package/dist/plugin/rest-schemas.d.ts.map +1 -0
  150. package/dist/plugin/rest-schemas.js +262 -0
  151. package/dist/plugin/rest-schemas.js.map +1 -0
  152. package/dist/plugin/sandbox.d.ts +23 -0
  153. package/dist/plugin/sandbox.d.ts.map +1 -0
  154. package/dist/plugin/sandbox.js +99 -0
  155. package/dist/plugin/sandbox.js.map +1 -0
  156. package/dist/plugin/testing.d.ts +45 -0
  157. package/dist/plugin/testing.d.ts.map +1 -0
  158. package/dist/plugin/testing.js +420 -0
  159. package/dist/plugin/testing.js.map +1 -0
  160. package/dist/plugin/web-react.d.ts +11 -0
  161. package/dist/plugin/web-react.d.ts.map +1 -0
  162. package/dist/plugin/web-react.js +34 -0
  163. package/dist/plugin/web-react.js.map +1 -0
  164. package/dist/plugin/web.d.ts +95 -0
  165. package/dist/plugin/web.d.ts.map +1 -0
  166. package/dist/plugin/web.js +245 -0
  167. package/dist/plugin/web.js.map +1 -0
  168. package/dist/plugin-nav.d.ts +29 -0
  169. package/dist/plugin-nav.d.ts.map +1 -0
  170. package/dist/plugin-nav.js +92 -0
  171. package/dist/plugin-nav.js.map +1 -0
  172. package/dist/plugin-testing.d.ts +2 -0
  173. package/dist/plugin-testing.d.ts.map +1 -0
  174. package/dist/plugin-testing.js +4 -0
  175. package/dist/plugin-testing.js.map +1 -0
  176. package/dist/plugin-web-react.d.ts +3 -0
  177. package/dist/plugin-web-react.d.ts.map +1 -0
  178. package/dist/plugin-web-react.js +3 -0
  179. package/dist/plugin-web-react.js.map +1 -0
  180. package/dist/plugin-web.d.ts +4 -0
  181. package/dist/plugin-web.d.ts.map +1 -0
  182. package/dist/plugin-web.js +5 -0
  183. package/dist/plugin-web.js.map +1 -0
  184. package/dist/plugin.d.ts +11 -0
  185. package/dist/plugin.d.ts.map +1 -0
  186. package/dist/plugin.js +15 -0
  187. package/dist/plugin.js.map +1 -0
  188. package/dist/router.d.ts +28 -0
  189. package/dist/router.d.ts.map +1 -1
  190. package/dist/router.js +47 -0
  191. package/dist/router.js.map +1 -1
  192. package/dist/routes.d.ts +4 -0
  193. package/dist/routes.d.ts.map +1 -1
  194. package/dist/routes.js +6 -0
  195. package/dist/routes.js.map +1 -1
  196. package/dist/schemas.d.ts +194 -9
  197. package/dist/schemas.d.ts.map +1 -1
  198. package/dist/schemas.js +98 -17
  199. package/dist/schemas.js.map +1 -1
  200. package/dist/tokens.d.ts +2 -0
  201. package/dist/tokens.d.ts.map +1 -0
  202. package/dist/tokens.js +101 -0
  203. package/dist/tokens.js.map +1 -0
  204. package/dist/vocabulary.d.ts +11 -1
  205. package/dist/vocabulary.d.ts.map +1 -1
  206. package/dist/vocabulary.js +24 -0
  207. package/dist/vocabulary.js.map +1 -1
  208. package/dist/web.d.ts.map +1 -1
  209. package/dist/web.js +2 -0
  210. package/dist/web.js.map +1 -1
  211. package/docs/specs/cli.md +12 -5
  212. package/docs/specs/plugins.md +488 -0
  213. package/docs/specs/rest.md +11 -9
  214. package/package.json +26 -8
  215. package/skills/building-a-console/SKILL.md +25 -26
  216. package/skills/building-a-console/references/configuring.md +40 -0
  217. package/skills/building-a-console/references/the-default-console.md +1 -1
  218. package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
  219. package/skills/building-a-plugin/SKILL.md +137 -0
  220. package/skills/deploying-a-console/SKILL.md +24 -26
  221. package/skills/deploying-a-console/references/access.md +13 -9
  222. package/skills/deploying-a-console/references/resources.md +25 -10
  223. package/skills/deploying-a-console/references/running-it-again.md +15 -0
  224. package/skills/deploying-a-console/references/sizing.md +2 -0
  225. package/skills/planning-the-board/SKILL.md +96 -0
  226. package/skills/planning-the-board/references/a-task.md +36 -0
  227. package/skills/planning-the-board/references/setting-up.md +44 -0
  228. package/skills/working-the-board/SKILL.md +43 -34
  229. package/skills/working-the-board/references/the-pack.md +19 -0
  230. package/src/api.ts +12 -0
  231. package/src/cli/cli.ts +737 -55
  232. package/src/cli/format.ts +368 -10
  233. package/src/cli/help.ts +50 -6
  234. package/src/cli/plugin-check.ts +497 -0
  235. package/src/cli/plugin-cli.ts +237 -0
  236. package/src/cli/plugin-init.ts +257 -0
  237. package/src/cli/plugin-main.ts +30 -0
  238. package/src/client.ts +322 -9
  239. package/src/compat.ts +2 -2
  240. package/src/components/board.tsx +62 -14
  241. package/src/components/comments.tsx +1 -0
  242. package/src/components/decision-form.tsx +92 -11
  243. package/src/components/decision-list.tsx +71 -2
  244. package/src/components/event-feed.tsx +42 -1
  245. package/src/components/my-decisions.tsx +4 -1
  246. package/src/components/plugin-frame.tsx +437 -0
  247. package/src/components/plugin-settings.tsx +1111 -0
  248. package/src/components/shell.tsx +82 -14
  249. package/src/components/task-view.tsx +2 -1
  250. package/src/components/workspace.tsx +18 -0
  251. package/src/console.tsx +304 -31
  252. package/src/data.ts +59 -11
  253. package/src/i18n/en.ts +175 -0
  254. package/src/i18n/ko.ts +155 -0
  255. package/src/index.ts +12 -0
  256. package/src/limits.ts +2 -0
  257. package/src/plugin/args.ts +526 -0
  258. package/src/plugin/bridge.ts +314 -0
  259. package/src/plugin/capabilities.ts +150 -0
  260. package/src/plugin/context.ts +421 -0
  261. package/src/plugin/define.ts +792 -0
  262. package/src/plugin/definition-error.ts +19 -0
  263. package/src/plugin/limits.ts +70 -0
  264. package/src/plugin/load-worker.ts +50 -0
  265. package/src/plugin/load.ts +82 -0
  266. package/src/plugin/manifest.ts +561 -0
  267. package/src/plugin/refusals.ts +94 -0
  268. package/src/plugin/rest-schemas.ts +354 -0
  269. package/src/plugin/sandbox.ts +123 -0
  270. package/src/plugin/testing.ts +612 -0
  271. package/src/plugin/web-react.ts +45 -0
  272. package/src/plugin/web.ts +403 -0
  273. package/src/plugin-nav.ts +132 -0
  274. package/src/plugin-testing.ts +10 -0
  275. package/src/plugin-web-react.ts +4 -0
  276. package/src/plugin-web.ts +35 -0
  277. package/src/plugin.ts +126 -0
  278. package/src/router.ts +73 -0
  279. package/src/routes.ts +7 -0
  280. package/src/schemas.ts +115 -18
  281. package/src/styles/console.css +330 -1
  282. package/src/tokens.ts +101 -0
  283. package/src/vocabulary.ts +30 -0
  284. package/src/web.ts +2 -0
  285. package/template/plugin/.gitignore +2 -0
  286. package/template/plugin/README.md +26 -0
  287. package/template/plugin/SKILLCDN.md +13 -0
  288. package/template/plugin/build.mjs +40 -0
  289. package/template/plugin/console-plugin.json +50 -0
  290. package/template/plugin/package.json +27 -0
  291. package/template/plugin/skills/__key__/SKILL.md +23 -0
  292. package/template/plugin/src/check.test.ts +34 -0
  293. package/template/plugin/src/server/index.test.ts +41 -0
  294. package/template/plugin/src/server/index.ts +72 -0
  295. package/template/plugin/src/web/index.css +51 -0
  296. package/template/plugin/src/web/index.ts +115 -0
  297. package/template/plugin/tsconfig.json +17 -0
  298. package/skills/working-the-board/references/setting-up.md +0 -3
  299. /package/skills/{working-the-board → planning-the-board}/references/project-rules.md +0 -0
package/src/plugin.ts ADDED
@@ -0,0 +1,126 @@
1
+ // The contract between a console and a plugin's server module (docs/specs/plugins.md): the
2
+ // manifest's schema and words, `definePlugin` and its definers, the arguments, the types of `ctx`,
3
+ // the capabilities and the bounds, and `z`, the schema library a plugin writes its records with.
4
+ // A plugin bundles this into its server module; nothing of it is imported at runtime by the
5
+ // console, which reads the description the module yields.
6
+
7
+ export * as z from "zod";
8
+ export {
9
+ ARG_TYPES,
10
+ type ArgDefinition,
11
+ type ArgDefinitions,
12
+ type ArgDescription,
13
+ type ArgsOf,
14
+ type ArgType,
15
+ type ArgValue,
16
+ arg,
17
+ argProblem,
18
+ argsProblem,
19
+ argUsage,
20
+ argValueFromWord,
21
+ type ParsedArgv,
22
+ type PluginFileHandle,
23
+ type PluginPersonRef,
24
+ type PluginProjectRef,
25
+ parseArgv,
26
+ } from "./plugin/args.js";
27
+ export {
28
+ CAPABILITY_WORDS,
29
+ type CapabilityWords,
30
+ capabilityWords,
31
+ isPluginCapability,
32
+ outboundAllows,
33
+ PLUGIN_CAPABILITIES,
34
+ PLUGIN_CAPABILITY_PREFIXES,
35
+ type PluginCapability,
36
+ } from "./plugin/capabilities.js";
37
+ export type * from "./plugin/context.js";
38
+ export {
39
+ type ApiDefinition,
40
+ type CollectionDescription,
41
+ type CollectionInput,
42
+ type CommandDefinition,
43
+ type CommandInput,
44
+ type CommandResult,
45
+ defineCollection,
46
+ defineCommand,
47
+ defineEndpoint,
48
+ definePlugin,
49
+ defineQuery,
50
+ describePlugin,
51
+ descriptionProblems,
52
+ durationMs,
53
+ type EndpointAuth,
54
+ type EndpointDefinition,
55
+ type EndpointDescription,
56
+ type EndpointInput,
57
+ type HookHandler,
58
+ isPluginDefinition,
59
+ isQueryFile,
60
+ type JobDefinition,
61
+ type Lifecycle,
62
+ type OperationDescription,
63
+ type PluginDefinition,
64
+ type PluginDescription,
65
+ type PluginParts,
66
+ type QueryDefinition,
67
+ type QueryFile,
68
+ type QueryInput,
69
+ type UpgradeDefinition,
70
+ } from "./plugin/define.js";
71
+ export { PluginDefinitionError } from "./plugin/definition-error.js";
72
+ export * from "./plugin/limits.js";
73
+ export {
74
+ CURRENT_PLUGIN_API,
75
+ isPluginKey,
76
+ type Localized,
77
+ localizedText,
78
+ type ManifestParse,
79
+ PLUGIN_API_MAJORS,
80
+ PLUGIN_BOARD_NEEDS,
81
+ PLUGIN_KEY_PATTERN,
82
+ PLUGIN_MENU_GROUPS,
83
+ PLUGIN_PAGE_LEVELS,
84
+ PLUGIN_SETTING_TYPES,
85
+ PLUGIN_WIDGET_PLACES,
86
+ PLUGIN_WIDGET_SIZES,
87
+ type PluginBoard,
88
+ type PluginBoardNeeds,
89
+ type PluginManifest,
90
+ type PluginMenu,
91
+ type PluginMenuGroup,
92
+ type PluginPage,
93
+ type PluginPageLevel,
94
+ type PluginRole,
95
+ type PluginSetting,
96
+ type PluginSettingType,
97
+ type PluginWidget,
98
+ type PluginWidgetPlace,
99
+ type PluginWidgetSize,
100
+ parsePluginManifest,
101
+ pluginManifestSchema,
102
+ RESERVED_COMMAND_NAMES,
103
+ RESERVED_PAGE_KEYS,
104
+ RESERVED_PLUGIN_KEYS,
105
+ SETTING_KEY_PATTERN,
106
+ } from "./plugin/manifest.js";
107
+ export {
108
+ isPluginError,
109
+ isPluginRefusal,
110
+ ownRefusalCode,
111
+ PLUGIN_OWN_REFUSAL_STATUS,
112
+ PLUGIN_REFUSALS,
113
+ PluginError,
114
+ PluginRefusal,
115
+ type PluginRefusalCode,
116
+ } from "./plugin/refusals.js";
117
+ export {
118
+ FORBIDDEN_BUILTINS,
119
+ forbiddenImportsIn,
120
+ forbiddingResolve,
121
+ type HardenedThread,
122
+ hardenThread,
123
+ isForbiddenBuiltin,
124
+ REMOVED_GLOBALS,
125
+ removedGlobalsIn,
126
+ } from "./plugin/sandbox.js";
package/src/router.ts CHANGED
@@ -121,6 +121,24 @@ export type Route =
121
121
  readonly project: string;
122
122
  readonly page: string;
123
123
  }
124
+ /** A plugin's page (docs/specs/plugins.md, "Tabs, sub-tabs and addresses"): the first when the address names none; `rest` is what follows the page, handed to the frame. */
125
+ | {
126
+ readonly name: "plugin";
127
+ readonly level: "workspace";
128
+ readonly plugin: string;
129
+ readonly page: string | undefined;
130
+ readonly rest: string;
131
+ }
132
+ | {
133
+ readonly name: "plugin";
134
+ readonly level: "project";
135
+ readonly project: string;
136
+ readonly plugin: string;
137
+ readonly page: string | undefined;
138
+ readonly rest: string;
139
+ }
140
+ /** The plugins' settings, for an administrator: the list, or one plugin by its key. */
141
+ | { readonly name: "plugins-settings"; readonly plugin: string | undefined }
124
142
  | { readonly name: "not-found" };
125
143
 
126
144
  /** The pages of a project, after its key. */
@@ -158,12 +176,34 @@ export const PATHS = {
158
176
  settings: "/settings",
159
177
  /** Where the pages a console adds of its own are: `/pages/<key>`, and `/projects/<key>/pages/<page>` on a project. */
160
178
  pages: "/pages",
179
+ /** Where the plugins' pages are: `/plugins/<key>[/<page>[/<rest>]]`, and `/projects/<key>/plugins/<key>...` on a project (ADR-0054). */
180
+ plugins: "/plugins",
181
+ /** The plugins' settings, for an administrator: the list, and `/settings/plugins/<key>` for one. */
182
+ pluginSettings: "/settings/plugins",
161
183
  /** Where an agent's connection is approved (ADR-0011): `/connect/<code>`, or the code typed. */
162
184
  connect: "/connect",
163
185
  } as const;
164
186
 
165
187
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
166
188
  const PROJECT_KEY = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
189
+ /** A plugin's key, and a page's: as the manifest spells them. */
190
+ const PLUGIN_KEY = /^[a-z][a-z0-9-]{1,31}$/;
191
+
192
+ /** A plugin's address after its key: the page, if any, and what follows it for the frame. */
193
+ function pluginRouteOf(
194
+ segments: readonly string[],
195
+ ):
196
+ | { readonly plugin: string; readonly page: string | undefined; readonly rest: string }
197
+ | undefined {
198
+ const [plugin, page, ...rest] = segments;
199
+ if (plugin === undefined || !PLUGIN_KEY.test(plugin)) {
200
+ return undefined;
201
+ }
202
+ if (page !== undefined && !PLUGIN_KEY.test(page)) {
203
+ return undefined;
204
+ }
205
+ return { plugin, page, rest: rest.length === 0 ? "" : `/${rest.join("/")}` };
206
+ }
167
207
  const NUMBER = /^[1-9][0-9]{0,8}$/;
168
208
  /** A code as it may be typed into an address: checked for what it is by the page. */
169
209
  const CODE_TEXT = /^[A-Za-z0-9-]{1,16}$/;
@@ -268,6 +308,15 @@ export function matchRoute(pathname: string, search = ""): Route {
268
308
  if (path === PATHS.categories) {
269
309
  return { name: "categories" };
270
310
  }
311
+ if (path === PATHS.pluginSettings) {
312
+ return { name: "plugins-settings", plugin: undefined };
313
+ }
314
+ if (path.startsWith(`${PATHS.pluginSettings}/`)) {
315
+ const [plugin, ...more] = path.slice(PATHS.pluginSettings.length + 1).split("/");
316
+ return plugin !== undefined && PLUGIN_KEY.test(plugin) && more.length === 0
317
+ ? { name: "plugins-settings", plugin }
318
+ : NOT_FOUND;
319
+ }
271
320
  if (path === PATHS.settings) {
272
321
  return {
273
322
  name: "workspace-settings",
@@ -307,6 +356,10 @@ export function matchRoute(pathname: string, search = ""): Route {
307
356
  ? { name: "custom", level: "workspace", page }
308
357
  : NOT_FOUND;
309
358
  }
359
+ if (path.startsWith(`${PATHS.plugins}/`)) {
360
+ const found = pluginRouteOf(path.slice(PATHS.plugins.length + 1).split("/"));
361
+ return found === undefined ? NOT_FOUND : { name: "plugin", level: "workspace", ...found };
362
+ }
310
363
  if (!path.startsWith(`${PATHS.project}/`)) {
311
364
  return NOT_FOUND;
312
365
  }
@@ -331,6 +384,12 @@ export function matchRoute(pathname: string, search = ""): Route {
331
384
  ? { name: "custom", level: "project", project, page: third }
332
385
  : NOT_FOUND;
333
386
  }
387
+ if (page === "plugins") {
388
+ const found = pluginRouteOf([third, ...rest].filter((segment) => segment !== undefined));
389
+ return found === undefined
390
+ ? NOT_FOUND
391
+ : { name: "plugin", level: "project", project, ...found };
392
+ }
334
393
  if (rest.length > 0) {
335
394
  return NOT_FOUND;
336
395
  }
@@ -526,6 +585,20 @@ export function customPageHref(page: string, project?: string): string {
526
585
  return project === undefined ? `${PATHS.pages}/${page}` : `${projectHref(project)}/pages/${page}`;
527
586
  }
528
587
 
588
+ /** Where a plugin's page is (ADR-0054): its first page, or the one named, with what follows it; on a project when its key is given. */
589
+ export function pluginHref(plugin: string, page?: string, project?: string, rest = ""): string {
590
+ const base =
591
+ project === undefined
592
+ ? `${PATHS.plugins}/${plugin}`
593
+ : `${projectHref(project)}/plugins/${plugin}`;
594
+ return page === undefined ? base : `${base}/${page}${rest}`;
595
+ }
596
+
597
+ /** Where the plugins' settings are, or one plugin's. */
598
+ export function pluginSettingsHref(plugin?: string): string {
599
+ return plugin === undefined ? PATHS.pluginSettings : `${PATHS.pluginSettings}/${plugin}`;
600
+ }
601
+
529
602
  /** Where a goal is set: the form. */
530
603
  /** Where a project's roadmap is, from a month on, or from this one when none is named. */
531
604
  export function roadmapHref(key: string, from?: string): string {
package/src/routes.ts CHANGED
@@ -35,8 +35,15 @@ export const REST_ROUTES = {
35
35
  decisions: "/api/v1/decisions",
36
36
  /** The rules of the board (ADR-0023): the console's own until an administrator writes the organization's. */
37
37
  rules: "/api/v1/rules",
38
+ /** The plugins (ADR-0049): the ones a member may know of, and under each key the plugin's own routes. */
39
+ plugins: "/api/v1/plugins",
38
40
  } as const;
39
41
 
42
+ /** The REST path of one plugin, by its key, and of what is under it. */
43
+ export function pluginPath(key: string, ...parts: readonly string[]): string {
44
+ return [REST_ROUTES.plugins, encodeURIComponent(key), ...parts.map(encodeURIComponent)].join("/");
45
+ }
46
+
40
47
  /** The REST path of the workspace's rules put away, or of their versions. */
41
48
  export function rulesPath(part?: "archive" | "versions"): string {
42
49
  return part === undefined ? REST_ROUTES.rules : `${REST_ROUTES.rules}/${part}`;
package/src/schemas.ts CHANGED
@@ -55,6 +55,7 @@ import {
55
55
  PEOPLE_VISIBILITIES,
56
56
  PERSON_ROLES,
57
57
  PERSON_STATUSES,
58
+ PLUGIN_COMMAND_OUTCOMES,
58
59
  PROJECT_ICON_TYPES,
59
60
  PROJECT_ROLES,
60
61
  PROJECT_VISIBILITIES,
@@ -283,6 +284,13 @@ export const restMeSchema = z.object({
283
284
  * built on this package still reads an older console's answer, and says it is older.
284
285
  */
285
286
  console: z.optional(z.object({ version: z.string(), clientRange: z.string() })),
287
+ /**
288
+ * How many decisions wait for the person's answer across the projects they may see
289
+ * (ADR-0042), for the pages to say beside My decisions; `null` for nobody. Answered from
290
+ * 0.4.3 and read as optional, so that a console built on this package still reads an older
291
+ * console's answer.
292
+ */
293
+ decisions: z.optional(z.nullable(z.object({ waiting: count }))),
286
294
  });
287
295
  export type RestMe = z.infer<typeof restMeSchema>;
288
296
 
@@ -495,6 +503,8 @@ export const restTaskSchema = z.object({
495
503
  priority: z.enum(TASK_PRIORITIES),
496
504
  /** Whose task it is: one person, whoever wrote it. */
497
505
  owner: restPersonSchema,
506
+ /** The plugin that made it, by key (ADR-0052), or `null`; left out by a console older than the plugins. */
507
+ plugin: z.optional(z.nullable(z.string())),
498
508
  /** The people at work on it (ADR-0037), in the order they came to it; `[]` while nobody is. */
499
509
  workers: z.array(restPersonSchema),
500
510
  /** The task this one is part of, or `null`. */
@@ -640,10 +650,50 @@ export const restGoalPatchSchema = z.object({
640
650
  });
641
651
  export type RestGoalPatch = z.infer<typeof restGoalPatchSchema>;
642
652
 
653
+ /** A file a run handed in, as the console keeps it; its bytes are at `projectPath(key, "files", <artifact id>)`. */
654
+ export const restFileSchema = z.object({
655
+ /** What the agent called it: one line, without a path. */
656
+ name: z.string(),
657
+ /** In bytes. */
658
+ size: count,
659
+ /** The media type the agent said, or `application/octet-stream`. */
660
+ contentType: z.string(),
661
+ /** The SHA-256 of the bytes, in hex: what the console keeps them under. */
662
+ sha256: z.string(),
663
+ });
664
+ export type RestFile = z.infer<typeof restFileSchema>;
665
+
666
+ /** A file put on a decision when it was raised (ADR-0055), kept as a run's file is; its bytes are at `projectPath(key, "files", <id>)`. */
667
+ export const restDecisionFileSchema = z.object({
668
+ id: uuid,
669
+ file: restFileSchema,
670
+ });
671
+ export type RestDecisionFile = z.infer<typeof restDecisionFileSchema>;
672
+
673
+ /**
674
+ * The key a raiser may give an option, for a program to read the answer by instead of
675
+ * comparing labels: spelled as a plugin's key is.
676
+ */
677
+ export const OPTION_KEY_PATTERN = /^[a-z][a-z0-9-]{1,31}$/;
678
+ const optionKey = z
679
+ .string()
680
+ .check(
681
+ z.regex(
682
+ OPTION_KEY_PATTERN,
683
+ "must be a key: a lowercase letter, then lowercase letters, digits and hyphens, 2 to 32 long",
684
+ ),
685
+ );
686
+
643
687
  export const restDecisionOptionSchema = z.object({
644
688
  /** What an answer names: stable once given. */
645
689
  id: z.string(),
646
690
  label: z.string(),
691
+ /**
692
+ * The key the raiser gave the option, for a program to read the answer by (`optionKey` on the
693
+ * `decision.answered` event) without comparing labels; none on an option given without one,
694
+ * and none on the person's own answer.
695
+ */
696
+ key: z.optional(z.string()),
647
697
  /**
648
698
  * On the one option a person's own answer became, when they answered in their own words
649
699
  * instead of choosing (`POST .../answer` with `own`): added then, after the options given,
@@ -668,6 +718,8 @@ export const restDecisionSchema = z.object({
668
718
  decider: restPersonSchema,
669
719
  /** The run that raised it, as the agent asked, or `null` when a person did. */
670
720
  run: z.nullable(z.object({ id: uuid, agent: z.string() })),
721
+ /** The plugin that raised it, by key (ADR-0052), or `null`; left out by a console older than the plugins. */
722
+ plugin: z.optional(z.nullable(z.string())),
671
723
  /** The answer, with who gave it and when; `null` while the decision waits. */
672
724
  answer: z.nullable(
673
725
  z.object({
@@ -681,6 +733,13 @@ export const restDecisionSchema = z.object({
681
733
  ),
682
734
  /** What followed the decision, in Markdown, written afterwards; `null` until someone does. */
683
735
  outcome: z.nullable(z.string()),
736
+ /**
737
+ * The files put on it when it was raised (ADR-0055), in the order they came; a picture among
738
+ * them is shown where the context names it as `![words](its name)`, else after it. Answered
739
+ * from 0.4.3 and read as optional, so that a console built on this package still reads an
740
+ * older console's decisions.
741
+ */
742
+ files: z.optional(z.array(restDecisionFileSchema)),
684
743
  createdAt: instant,
685
744
  updatedAt: instant,
686
745
  });
@@ -737,13 +796,25 @@ export const restPersonWorkSchema = z.object({
737
796
  });
738
797
  export type RestPersonWork = z.infer<typeof restPersonWorkSchema>;
739
798
 
740
- /** What `POST /api/v1/projects/<key>/decisions` is sent: the question, its context, and the options. */
799
+ /**
800
+ * What `POST /api/v1/projects/<key>/decisions` is sent: the question, its context, and the
801
+ * options. As JSON; or, with files put on the decision (ADR-0055), as the part `decision` of a
802
+ * form, with a part `file` for each file.
803
+ */
741
804
  export const restDecisionInputSchema = z.object({
742
805
  question: line(MAX_QUESTION_LENGTH),
743
806
  body: z.optional(body),
807
+ /** Each a label, and optionally a key a program reads the answer by, each key once. */
744
808
  options: z
745
- .array(z.object({ label: line(MAX_OPTION_LABEL_LENGTH) }))
746
- .check(z.minLength(MIN_OPTIONS), z.maxLength(MAX_OPTIONS)),
809
+ .array(z.object({ label: line(MAX_OPTION_LABEL_LENGTH), key: z.optional(optionKey) }))
810
+ .check(
811
+ z.minLength(MIN_OPTIONS),
812
+ z.maxLength(MAX_OPTIONS),
813
+ z.refine((options) => {
814
+ const keys = options.flatMap((option) => (option.key === undefined ? [] : [option.key]));
815
+ return new Set(keys).size === keys.length;
816
+ }, "each key once"),
817
+ ),
747
818
  taskId: z.optional(z.nullable(uuid)),
748
819
  /** The run that asks, when an agent does: the decision is about its task, and it waits for the answer. */
749
820
  runId: z.optional(z.nullable(uuid)),
@@ -796,8 +867,9 @@ export const restEventDataSchema = z.object({
796
867
  from: z.optional(z.enum(TASK_STATES)),
797
868
  to: z.optional(z.enum(TASK_STATES)),
798
869
  question: z.optional(z.string()),
799
- /** For `decision.answered`: the label of the chosen option. */
870
+ /** For `decision.answered`: the label of the chosen option, and its key where the raiser gave one. */
800
871
  option: z.optional(z.string()),
872
+ optionKey: z.optional(z.string()),
801
873
  /** For `person.role_changed` and the events about a project's members: whose role, and what it became. */
802
874
  login: z.optional(z.string()),
803
875
  role: z.optional(z.string()),
@@ -813,8 +885,20 @@ export const restEventDataSchema = z.object({
813
885
  label: z.optional(z.string()),
814
886
  /** For `run.reported` and `task.commented`: the first words of what was written. */
815
887
  excerpt: z.optional(z.string()),
816
- /** For `task.commented`: how many files came with it. */
888
+ /** For `task.commented`, and for `decision.raised` when any came: how many files came with it. */
817
889
  files: z.optional(z.int()),
890
+ /** For `plugin.command`: the command, how it ended, the refusal's code where it was refused, how long it ran, and the arguments, a secret one as `[hidden]`. */
891
+ command: z.optional(z.string()),
892
+ outcome: z.optional(z.enum(PLUGIN_COMMAND_OUTCOMES)),
893
+ code: z.optional(z.string()),
894
+ durationMs: z.optional(z.int()),
895
+ args: z.optional(z.record(z.string(), z.unknown())),
896
+ /** For `plugin.enabled` and `plugin.configured`: the capabilities consented to. */
897
+ capabilities: z.optional(z.array(z.string())),
898
+ /** For `plugin.granted` and `plugin.revoked`: whether the role went to everyone. */
899
+ everyone: z.optional(z.boolean()),
900
+ /** For `plugin.failed`: what went wrong, in words for a person. */
901
+ problem: z.optional(z.string()),
818
902
  });
819
903
  export type RestEventData = z.infer<typeof restEventDataSchema>;
820
904
 
@@ -835,6 +919,8 @@ export const restEventSchema = z.object({
835
919
  documentId: z.nullable(uuid),
836
920
  /** The goal it is about, or `null`. */
837
921
  goalId: z.nullable(uuid),
922
+ /** The plugin that caused it, by key, or `null` (ADR-0052). */
923
+ plugin: z.nullable(z.string()),
838
924
  data: restEventDataSchema,
839
925
  createdAt: instant,
840
926
  });
@@ -848,19 +934,6 @@ export const restReportSchema = z.object({
848
934
  });
849
935
  export type RestReport = z.infer<typeof restReportSchema>;
850
936
 
851
- /** A file a run handed in, as the console keeps it; its bytes are at `projectPath(key, "files", <artifact id>)`. */
852
- export const restFileSchema = z.object({
853
- /** What the agent called it: one line, without a path. */
854
- name: z.string(),
855
- /** In bytes. */
856
- size: count,
857
- /** The media type the agent said, or `application/octet-stream`. */
858
- contentType: z.string(),
859
- /** The SHA-256 of the bytes, in hex: what the console keeps them under. */
860
- sha256: z.string(),
861
- });
862
- export type RestFile = z.infer<typeof restFileSchema>;
863
-
864
937
  /** What a run handed in: a link to a branch, a pull request, a page; or a file the console keeps. */
865
938
  export const restArtifactSchema = z.object({
866
939
  id: uuid,
@@ -1193,6 +1266,8 @@ export const restDocumentSummarySchema = z.object({
1193
1266
  /** Who wrote the latest version, and the agent they wrote it through, or `null`. */
1194
1267
  updatedBy: restPersonSchema,
1195
1268
  agent: z.nullable(z.string()),
1269
+ /** The plugin that wrote it, by key (ADR-0052), or `null`; left out by a console older than the plugins. */
1270
+ plugin: z.optional(z.nullable(z.string())),
1196
1271
  /** When the document was archived, or `null` while it is current. */
1197
1272
  archivedAt: z.nullable(instant),
1198
1273
  createdAt: instant,
@@ -1265,6 +1340,21 @@ export const restDocumentInputSchema = z.object({
1265
1340
  });
1266
1341
  export type RestDocumentInput = z.infer<typeof restDocumentInputSchema>;
1267
1342
 
1343
+ /**
1344
+ * A plugin named after the rules (docs/specs/plugins.md, "The command"): one the asker may use,
1345
+ * bound to the project where the rules are a project's; what agents are told of it, in the
1346
+ * manifest's languages, and the asker's roles in it.
1347
+ */
1348
+ export const restRulesPluginSchema = z.object({
1349
+ key: z.string(),
1350
+ name: z.record(z.string(), z.string()),
1351
+ /** The sentence agents read, by language; `null` for the description. */
1352
+ agentNote: z.nullable(z.record(z.string(), z.string())),
1353
+ description: z.record(z.string(), z.string()),
1354
+ roles: z.array(z.string()),
1355
+ });
1356
+ export type RestRulesPlugin = z.infer<typeof restRulesPluginSchema>;
1357
+
1268
1358
  /**
1269
1359
  * `GET /api/v1/rules`, and each half of a project's (ADR-0023): the rules that hold, and where
1270
1360
  * they come from. `reference`: the console's own, since the organization wrote none;
@@ -1276,6 +1366,8 @@ export const restRulesSchema = z.object({
1276
1366
  body: z.nullable(z.string()),
1277
1367
  /** The page they are written on, or `null` for the console's own and for none. */
1278
1368
  page: z.nullable(restDocumentSummarySchema),
1369
+ /** At the workspace: every plugin the asker may use, by key; left out in the halves of a project's answer. */
1370
+ plugins: z.optional(z.array(restRulesPluginSchema)),
1279
1371
  });
1280
1372
  export type RestRules = z.infer<typeof restRulesSchema>;
1281
1373
 
@@ -1283,6 +1375,8 @@ export type RestRules = z.infer<typeof restRulesSchema>;
1283
1375
  export const restProjectRulesSchema = z.object({
1284
1376
  workspace: restRulesSchema,
1285
1377
  project: restRulesSchema,
1378
+ /** The plugins bound to the project, as its board project or on it, that the asker holds a role in. */
1379
+ plugins: z.optional(z.array(restRulesPluginSchema)),
1286
1380
  });
1287
1381
  export type RestProjectRules = z.infer<typeof restProjectRulesSchema>;
1288
1382
 
@@ -1390,3 +1484,6 @@ export const restRunEndInputSchema = z.object({
1390
1484
  summary: z.optional(summary),
1391
1485
  });
1392
1486
  export type RestRunEndInput = z.infer<typeof restRunEndInputSchema>;
1487
+
1488
+ // The plugins' routes (docs/specs/plugins.md, "REST"), beside the board's.
1489
+ export * from "./plugin/rest-schemas.js";