@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/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,
@@ -495,6 +496,8 @@ export const restTaskSchema = z.object({
495
496
  priority: z.enum(TASK_PRIORITIES),
496
497
  /** Whose task it is: one person, whoever wrote it. */
497
498
  owner: restPersonSchema,
499
+ /** The plugin that made it, by key (ADR-0052), or `null`; left out by a console older than the plugins. */
500
+ plugin: z.optional(z.nullable(z.string())),
498
501
  /** The people at work on it (ADR-0037), in the order they came to it; `[]` while nobody is. */
499
502
  workers: z.array(restPersonSchema),
500
503
  /** The task this one is part of, or `null`. */
@@ -668,6 +671,8 @@ export const restDecisionSchema = z.object({
668
671
  decider: restPersonSchema,
669
672
  /** The run that raised it, as the agent asked, or `null` when a person did. */
670
673
  run: z.nullable(z.object({ id: uuid, agent: z.string() })),
674
+ /** The plugin that raised it, by key (ADR-0052), or `null`; left out by a console older than the plugins. */
675
+ plugin: z.optional(z.nullable(z.string())),
671
676
  /** The answer, with who gave it and when; `null` while the decision waits. */
672
677
  answer: z.nullable(
673
678
  z.object({
@@ -815,6 +820,18 @@ export const restEventDataSchema = z.object({
815
820
  excerpt: z.optional(z.string()),
816
821
  /** For `task.commented`: how many files came with it. */
817
822
  files: z.optional(z.int()),
823
+ /** 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]`. */
824
+ command: z.optional(z.string()),
825
+ outcome: z.optional(z.enum(PLUGIN_COMMAND_OUTCOMES)),
826
+ code: z.optional(z.string()),
827
+ durationMs: z.optional(z.int()),
828
+ args: z.optional(z.record(z.string(), z.unknown())),
829
+ /** For `plugin.enabled` and `plugin.configured`: the capabilities consented to. */
830
+ capabilities: z.optional(z.array(z.string())),
831
+ /** For `plugin.granted` and `plugin.revoked`: whether the role went to everyone. */
832
+ everyone: z.optional(z.boolean()),
833
+ /** For `plugin.failed`: what went wrong, in words for a person. */
834
+ problem: z.optional(z.string()),
818
835
  });
819
836
  export type RestEventData = z.infer<typeof restEventDataSchema>;
820
837
 
@@ -835,6 +852,8 @@ export const restEventSchema = z.object({
835
852
  documentId: z.nullable(uuid),
836
853
  /** The goal it is about, or `null`. */
837
854
  goalId: z.nullable(uuid),
855
+ /** The plugin that caused it, by key, or `null` (ADR-0052). */
856
+ plugin: z.nullable(z.string()),
838
857
  data: restEventDataSchema,
839
858
  createdAt: instant,
840
859
  });
@@ -1193,6 +1212,8 @@ export const restDocumentSummarySchema = z.object({
1193
1212
  /** Who wrote the latest version, and the agent they wrote it through, or `null`. */
1194
1213
  updatedBy: restPersonSchema,
1195
1214
  agent: z.nullable(z.string()),
1215
+ /** The plugin that wrote it, by key (ADR-0052), or `null`; left out by a console older than the plugins. */
1216
+ plugin: z.optional(z.nullable(z.string())),
1196
1217
  /** When the document was archived, or `null` while it is current. */
1197
1218
  archivedAt: z.nullable(instant),
1198
1219
  createdAt: instant,
@@ -1265,6 +1286,21 @@ export const restDocumentInputSchema = z.object({
1265
1286
  });
1266
1287
  export type RestDocumentInput = z.infer<typeof restDocumentInputSchema>;
1267
1288
 
1289
+ /**
1290
+ * A plugin named after the rules (docs/specs/plugins.md, "The command"): one the asker may use,
1291
+ * bound to the project where the rules are a project's; what agents are told of it, in the
1292
+ * manifest's languages, and the asker's roles in it.
1293
+ */
1294
+ export const restRulesPluginSchema = z.object({
1295
+ key: z.string(),
1296
+ name: z.record(z.string(), z.string()),
1297
+ /** The sentence agents read, by language; `null` for the description. */
1298
+ agentNote: z.nullable(z.record(z.string(), z.string())),
1299
+ description: z.record(z.string(), z.string()),
1300
+ roles: z.array(z.string()),
1301
+ });
1302
+ export type RestRulesPlugin = z.infer<typeof restRulesPluginSchema>;
1303
+
1268
1304
  /**
1269
1305
  * `GET /api/v1/rules`, and each half of a project's (ADR-0023): the rules that hold, and where
1270
1306
  * they come from. `reference`: the console's own, since the organization wrote none;
@@ -1276,6 +1312,8 @@ export const restRulesSchema = z.object({
1276
1312
  body: z.nullable(z.string()),
1277
1313
  /** The page they are written on, or `null` for the console's own and for none. */
1278
1314
  page: z.nullable(restDocumentSummarySchema),
1315
+ /** At the workspace: every plugin the asker may use, by key; left out in the halves of a project's answer. */
1316
+ plugins: z.optional(z.array(restRulesPluginSchema)),
1279
1317
  });
1280
1318
  export type RestRules = z.infer<typeof restRulesSchema>;
1281
1319
 
@@ -1283,6 +1321,8 @@ export type RestRules = z.infer<typeof restRulesSchema>;
1283
1321
  export const restProjectRulesSchema = z.object({
1284
1322
  workspace: restRulesSchema,
1285
1323
  project: restRulesSchema,
1324
+ /** The plugins bound to the project, as its board project or on it, that the asker holds a role in. */
1325
+ plugins: z.optional(z.array(restRulesPluginSchema)),
1286
1326
  });
1287
1327
  export type RestProjectRules = z.infer<typeof restProjectRulesSchema>;
1288
1328
 
@@ -1390,3 +1430,6 @@ export const restRunEndInputSchema = z.object({
1390
1430
  summary: z.optional(summary),
1391
1431
  });
1392
1432
  export type RestRunEndInput = z.infer<typeof restRunEndInputSchema>;
1433
+
1434
+ // The plugins' routes (docs/specs/plugins.md, "REST"), beside the board's.
1435
+ export * from "./plugin/rest-schemas.js";
@@ -41,6 +41,18 @@
41
41
  --sc-color-warning-subtle: #fff4cc;
42
42
  --sc-color-danger: #b0273a;
43
43
  --sc-color-danger-subtle: #fadadf;
44
+ /* For the plugins' pages (ADR-0054): a state that informs, one neither good nor bad, a palette for figures, tabular numerals. */
45
+ --sc-color-info: #1d4ed8;
46
+ --sc-color-info-subtle: #dbe6ff;
47
+ --sc-color-neutral: #4b5563;
48
+ --sc-color-neutral-subtle: #e7eaf0;
49
+ --sc-series-1: #2c5bc4;
50
+ --sc-series-2: #c2410c;
51
+ --sc-series-3: #146b44;
52
+ --sc-series-4: #8f6100;
53
+ --sc-series-5: #7c3aed;
54
+ --sc-series-6: #0e7490;
55
+ --sc-numeric: "tnum" 1, "lnum" 1;
44
56
 
45
57
  /* The ground: a pale field, one gradient from the top. The default UI provides the field's
46
58
  face (the shell); a custom console may paint its own. */
@@ -473,6 +485,108 @@ table {
473
485
  color: var(--sc-color-text);
474
486
  }
475
487
 
488
+ /* A tab that opens a list of pages: a group of several plugins (ADR-0054). */
489
+
490
+ .sc-tab-group {
491
+ position: relative;
492
+ }
493
+
494
+ .sc-tab-summary {
495
+ list-style: none;
496
+ cursor: pointer;
497
+ }
498
+
499
+ .sc-tab-summary::-webkit-details-marker {
500
+ display: none;
501
+ }
502
+
503
+ .sc-tab-group-panel {
504
+ right: auto;
505
+ left: 0;
506
+ top: 100%;
507
+ }
508
+
509
+ /* The sub-tabs of a plugin with several pages, above its frame. */
510
+
511
+ .sc-subtabs {
512
+ display: flex;
513
+ gap: var(--sc-space-4);
514
+ margin-bottom: var(--sc-space-4);
515
+ border-bottom: var(--sc-border);
516
+ overflow: auto hidden;
517
+ }
518
+
519
+ /* A plugin's frame: the width of the page, as tall as the frame says. */
520
+
521
+ .sc-plugin-frame {
522
+ display: block;
523
+ width: 100%;
524
+ min-height: 2.5rem;
525
+ border: 0;
526
+ border-radius: var(--sc-radius-lg);
527
+ background: transparent;
528
+ }
529
+
530
+ /* A plugin's frame document, which the console serves marked with data-console: it paints no
531
+ background and is as tall as its content, so that the box the console draws shows through and
532
+ the frame shrinks as well as grows; no room is kept for a scrollbar it never shows. */
533
+
534
+ html[data-console] {
535
+ scrollbar-gutter: auto;
536
+ scroll-padding-top: 0;
537
+ }
538
+
539
+ html[data-console] body {
540
+ min-height: 0;
541
+ background: transparent;
542
+ }
543
+
544
+ /* The widgets of the front page: a grid of six columns, above or below the projects. The page's
545
+ head keeps its own distance above the top ones. */
546
+
547
+ .sc-widgets {
548
+ display: grid;
549
+ grid-template-columns: repeat(6, minmax(0, 1fr));
550
+ gap: var(--sc-space-4);
551
+ }
552
+
553
+ .sc-widgets-top {
554
+ margin: 0 0 var(--sc-space-5);
555
+ }
556
+
557
+ .sc-widgets-bottom {
558
+ margin: var(--sc-space-5) 0 0;
559
+ }
560
+
561
+ .sc-widget {
562
+ grid-column: span 6;
563
+ padding: var(--sc-space-3) var(--sc-space-4);
564
+ border: var(--sc-border);
565
+ border-radius: var(--sc-radius-lg);
566
+ background: var(--sc-color-bg-raised);
567
+ }
568
+
569
+ .sc-widget-title {
570
+ margin: 0 0 var(--sc-space-2);
571
+ font-size: var(--sc-text-sm);
572
+ font-weight: var(--sc-weight-medium);
573
+ color: var(--sc-color-text-muted);
574
+ }
575
+
576
+ @media (min-width: 48rem) {
577
+ .sc-widget-third {
578
+ grid-column: span 2;
579
+ }
580
+
581
+ .sc-widget-half {
582
+ grid-column: span 3;
583
+ }
584
+
585
+ .sc-widget-full {
586
+ grid-column: span 6;
587
+ }
588
+ }
589
+
476
590
  /* What waits, beside a tab's name: a count in the point colour, so it reads before the words. */
477
591
  .sc-nav-count {
478
592
  display: inline-flex;
@@ -5699,3 +5813,216 @@ select.sc-input {
5699
5813
  padding-inline: var(--sc-space-4);
5700
5814
  font-size: var(--sc-text-sm);
5701
5815
  }
5816
+
5817
+ /* The plugins' settings (docs/specs/plugins.md, "The settings pages"): one plugin's page. */
5818
+
5819
+ /* A choice with its box or its dot, on one line with its word. */
5820
+ .sc-check {
5821
+ display: inline-flex;
5822
+ align-items: center;
5823
+ gap: var(--sc-space-2);
5824
+ padding-block: var(--sc-space-1);
5825
+ }
5826
+
5827
+ .sc-radio-row {
5828
+ display: flex;
5829
+ flex-wrap: wrap;
5830
+ gap: var(--sc-space-4);
5831
+ }
5832
+
5833
+ /* The form that enables a plugin: its parts one under the other, a hairline between. */
5834
+ .sc-plugin-enable {
5835
+ gap: 0;
5836
+ }
5837
+
5838
+ .sc-plugin-group {
5839
+ display: grid;
5840
+ gap: var(--sc-space-2);
5841
+ padding-block: var(--sc-space-4);
5842
+ border-top: var(--sc-hairline);
5843
+ }
5844
+
5845
+ .sc-plugin-group:first-child {
5846
+ padding-top: 0;
5847
+ border-top: 0;
5848
+ }
5849
+
5850
+ .sc-plugin-group:last-child {
5851
+ padding-bottom: 0;
5852
+ }
5853
+
5854
+ .sc-plugin-group-title {
5855
+ margin: 0;
5856
+ font-size: var(--sc-text-md);
5857
+ font-weight: var(--sc-weight-medium);
5858
+ }
5859
+
5860
+ .sc-plugin-group > .sc-settings-hint {
5861
+ margin-top: calc(-1 * var(--sc-space-1));
5862
+ }
5863
+
5864
+ /* The fields of the board project to make: as wide as a form is comfortable, no wider. */
5865
+ .sc-plugin-fields {
5866
+ display: grid;
5867
+ gap: var(--sc-space-3);
5868
+ max-width: 32rem;
5869
+ }
5870
+
5871
+ /* A list to tick: each row a sheet with its box, a name and a word under the name. */
5872
+ .sc-plugin-ticks {
5873
+ display: grid;
5874
+ gap: var(--sc-space-2);
5875
+ margin: 0;
5876
+ padding: 0;
5877
+ list-style: none;
5878
+ }
5879
+
5880
+ .sc-plugin-tick {
5881
+ display: grid;
5882
+ grid-template-columns: auto minmax(0, 1fr);
5883
+ gap: var(--sc-space-3);
5884
+ align-items: start;
5885
+ padding: var(--sc-space-3) var(--sc-space-4);
5886
+ border: var(--sc-border);
5887
+ border-radius: var(--sc-radius-md);
5888
+ background: var(--sc-color-sheet);
5889
+ cursor: pointer;
5890
+ }
5891
+
5892
+ .sc-plugin-tick input {
5893
+ margin-top: 0.25em;
5894
+ }
5895
+
5896
+ .sc-plugin-tick-words {
5897
+ display: grid;
5898
+ gap: 2px;
5899
+ }
5900
+
5901
+ .sc-plugin-tick-name {
5902
+ font-weight: var(--sc-weight-medium);
5903
+ }
5904
+
5905
+ .sc-plugin-tick-under {
5906
+ color: var(--sc-color-text-muted);
5907
+ font-size: var(--sc-text-sm);
5908
+ }
5909
+
5910
+ /* The capabilities an enabled plugin holds, one a line; its commands as words side by side. */
5911
+ .sc-plugin-held {
5912
+ display: grid;
5913
+ gap: var(--sc-space-1);
5914
+ margin: 0;
5915
+ padding: 0;
5916
+ list-style: none;
5917
+ }
5918
+
5919
+ .sc-plugin-codes {
5920
+ display: inline-flex;
5921
+ flex-wrap: wrap;
5922
+ gap: var(--sc-space-2);
5923
+ }
5924
+
5925
+ /* A field with its buttons on one line: a secret to set, the key to type before a purge. */
5926
+ .sc-plugin-line {
5927
+ display: flex;
5928
+ flex-wrap: wrap;
5929
+ gap: var(--sc-space-2);
5930
+ width: 100%;
5931
+ }
5932
+
5933
+ .sc-plugin-line-input {
5934
+ flex: 1 1 8rem;
5935
+ width: auto;
5936
+ }
5937
+
5938
+ /* The roles: one a row, its name and what it is for beside who holds it and the way to grant it. */
5939
+ .sc-plugin-roles {
5940
+ display: grid;
5941
+ margin: 0;
5942
+ padding: 0;
5943
+ list-style: none;
5944
+ }
5945
+
5946
+ .sc-plugin-role {
5947
+ display: grid;
5948
+ grid-template-columns: minmax(10rem, 16rem) minmax(0, 1fr);
5949
+ gap: var(--sc-space-2) var(--sc-space-5);
5950
+ padding: var(--sc-space-4) 0;
5951
+ border-top: var(--sc-hairline);
5952
+ }
5953
+
5954
+ .sc-plugin-role:first-child {
5955
+ padding-top: 0;
5956
+ border-top: 0;
5957
+ }
5958
+
5959
+ .sc-plugin-role:last-child {
5960
+ padding-bottom: 0;
5961
+ }
5962
+
5963
+ .sc-plugin-role-name {
5964
+ display: flex;
5965
+ flex-wrap: wrap;
5966
+ align-items: center;
5967
+ gap: var(--sc-space-2);
5968
+ font-weight: var(--sc-weight-medium);
5969
+ }
5970
+
5971
+ .sc-plugin-role-key {
5972
+ color: var(--sc-color-text-muted);
5973
+ font-weight: normal;
5974
+ }
5975
+
5976
+ .sc-plugin-holders {
5977
+ display: flex;
5978
+ flex-wrap: wrap;
5979
+ align-items: center;
5980
+ gap: var(--sc-space-2);
5981
+ }
5982
+
5983
+ /* Who holds a role, a chip each, with the way to revoke a grant in it. */
5984
+ .sc-plugin-holder {
5985
+ display: inline-flex;
5986
+ align-items: center;
5987
+ gap: var(--sc-space-1);
5988
+ min-height: 2rem;
5989
+ padding-inline: var(--sc-space-3) var(--sc-space-1);
5990
+ border: var(--sc-border);
5991
+ border-radius: var(--sc-radius-full);
5992
+ background: var(--sc-color-sheet);
5993
+ font-size: var(--sc-text-sm);
5994
+ }
5995
+
5996
+ .sc-plugin-holder-fixed {
5997
+ padding-inline-end: var(--sc-space-3);
5998
+ }
5999
+
6000
+ .sc-plugin-holder .sc-button {
6001
+ min-height: 1.5rem;
6002
+ padding-inline: var(--sc-space-2);
6003
+ font-size: var(--sc-text-md);
6004
+ line-height: 1;
6005
+ }
6006
+
6007
+ .sc-plugin-grant {
6008
+ display: flex;
6009
+ flex-wrap: wrap;
6010
+ gap: var(--sc-space-2);
6011
+ margin-top: var(--sc-space-3);
6012
+ }
6013
+
6014
+ .sc-plugin-grant-input {
6015
+ flex: 0 1 20rem;
6016
+ width: auto;
6017
+ }
6018
+
6019
+ .sc-plugin-problems {
6020
+ margin: 0;
6021
+ padding-left: var(--sc-space-5);
6022
+ }
6023
+
6024
+ @media (max-width: 40rem) {
6025
+ .sc-plugin-role {
6026
+ grid-template-columns: minmax(0, 1fr);
6027
+ }
6028
+ }