@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/dist/console.js CHANGED
@@ -14,6 +14,8 @@ import { MyDecisions } from "./components/my-decisions.js";
14
14
  import { PackTokenWays } from "./components/pack-token.js";
15
15
  import { PAGE_SIZE } from "./components/paged.js";
16
16
  import { ListingForm, PeopleList } from "./components/people.js";
17
+ import { PluginFrame, PluginWidgets } from "./components/plugin-frame.js";
18
+ import { PluginSettingsList, PluginSettingsView } from "./components/plugin-settings.js";
17
19
  import { MemberList, ProjectForm, ProjectList, ProjectMark, } from "./components/projects.js";
18
20
  import { RemotePicturesContext } from "./components/remote-pictures.js";
19
21
  import { GoalTimeline } from "./components/roadmap.js";
@@ -33,7 +35,8 @@ import { useConsoleData } from "./data.js";
33
35
  import { monthOf, today } from "./dates.js";
34
36
  import { DocsPage } from "./docs-page.js";
35
37
  import { chooseLanguage, DEFAULT_LANGUAGES, LanguageContext, useWords, } from "./i18n/index.js";
36
- import { boardHref, calendarHref, customPageHref, decisionHref, decisionsHref, docHref, goalHref, matchRoute, newDecisionHref, newGoalHref, newTaskHref, PATHS, personAgentsHref, personWorkHref, projectHref, projectSettingsHref, roadmapHref, settingsHref, signInFailureOf, taskHref, withoutSignInParam, workHref, } from "./router.js";
38
+ import { pluginNavItems, pluginPagesAt, pluginWordOf } from "./plugin-nav.js";
39
+ import { boardHref, calendarHref, customPageHref, decisionHref, decisionsHref, docHref, goalHref, matchRoute, newDecisionHref, newGoalHref, newTaskHref, PATHS, personAgentsHref, personWorkHref, pluginHref, pluginSettingsHref, projectHref, projectSettingsHref, roadmapHref, settingsHref, signInFailureOf, taskHref, withoutSignInParam, workHref, } from "./router.js";
37
40
  import { projectPath } from "./routes.js";
38
41
  import { errorWords, useAction } from "./use-action.js";
39
42
  export const DEFAULT_COMPONENTS = {
@@ -192,17 +195,87 @@ function NotWithToken() {
192
195
  }
193
196
  function ProjectsPage(props) {
194
197
  const words = useWords().projects;
195
- const { data, components, navigation } = props;
198
+ const { data, components, navigation, client, language } = props;
199
+ const me = data.me;
200
+ /** The plugins' widgets at a place of this page (ADR-0054), for the person's roles. */
201
+ const widgets = (place) => me === undefined ? null : (_jsx(PluginWidgets, { plugins: data.plugins, place: place, me: me, language: language, client: client, navigation: navigation, nameOf: (name) => pluginWordOf(name, language) }));
196
202
  const { busy, error, act } = useAction();
197
203
  const href = (project) => projectHref(project.key);
198
204
  // A project is made by an administrator (ADR-0045): a member sees the list alone, the form's
199
205
  // address included.
200
206
  const administrator = data.me?.person?.role === "admin";
201
207
  const making = props.making && administrator;
202
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: words.title }), administrator && !making && (_jsx(LinkButton, { href: PATHS.newProject, variant: "primary", children: words.newProject }))] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), making && (_jsx("section", { className: "sc-panel sc-panel-form", "aria-label": words.form.aria, children: _jsx(ProjectForm, { busy: busy, skillsSource: data.me?.workspace.skills.source, organizationAddress: data.me?.workspace.skills.address ?? null, onSubmit: (input, icon) => void act(async () => {
208
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: words.title }), administrator && !making && (_jsx(LinkButton, { href: PATHS.newProject, variant: "primary", children: words.newProject }))] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), widgets("home:top"), making && (_jsx("section", { className: "sc-panel sc-panel-form", "aria-label": words.form.aria, children: _jsx(ProjectForm, { busy: busy, skillsSource: data.me?.workspace.skills.source, organizationAddress: data.me?.workspace.skills.address ?? null, onSubmit: (input, icon) => void act(async () => {
203
209
  const made = await data.actions.createProject(input, icon instanceof File ? uploadOf(icon) : undefined);
204
210
  navigation.replace(projectHref(made.key));
205
- }), onCancel: () => navigation.back(PATHS.projects) }) })), _jsx(components.ProjectList, { projects: data.projects, projectHref: href, onOpen: (project) => navigation.go(href(project)), empty: _jsx(EmptyState, { title: words.empty.title, body: administrator ? words.empty.body : words.empty.bodyMember, action: administrator ? (_jsx(LinkButton, { href: PATHS.newProject, children: words.newProject })) : undefined }) })] }));
211
+ }), onCancel: () => navigation.back(PATHS.projects) }) })), _jsx(components.ProjectList, { projects: data.projects, projectHref: href, onOpen: (project) => navigation.go(href(project)), empty: _jsx(EmptyState, { title: words.empty.title, body: administrator ? words.empty.body : words.empty.bodyMember, action: administrator ? (_jsx(LinkButton, { href: PATHS.newProject, children: words.newProject })) : undefined }) }), widgets("home:bottom")] }));
212
+ }
213
+ /**
214
+ * A plugin's page (ADR-0054): the sub-tabs of its pages at the level, where it has several, above
215
+ * the frame of the one shown, the first when the address names none. Nothing for a plugin the
216
+ * person has no page of, or that is not on the project.
217
+ */
218
+ function PluginPage(props) {
219
+ const words = useWords();
220
+ const { route, data, project, client, language, navigation } = props;
221
+ const plugin = data.plugins.find((candidate) => candidate.key === route.plugin && candidate.state === "enabled");
222
+ const bound = route.level === "workspace" ||
223
+ data.projectPlugins.some((candidate) => candidate.key === route.plugin);
224
+ const pages = plugin === undefined ? [] : pluginPagesAt(plugin, route.level);
225
+ const page = route.page === undefined ? pages[0] : pages.find((p) => p.key === route.page);
226
+ if (plugin === undefined || !bound || page === undefined || data.me === undefined) {
227
+ return _jsx(EmptyState, { title: words.plugins.noPage.title, body: words.plugins.noPage.body });
228
+ }
229
+ const projectKey = route.level === "project" ? route.project : undefined;
230
+ return (_jsxs(_Fragment, { children: [pages.length > 1 && (_jsx("nav", { className: "sc-subtabs", "aria-label": pluginWordOf(plugin.name, language), children: pages.map((candidate) => (_jsx("a", { href: pluginHref(plugin.key, candidate.key, projectKey), className: candidate.key === page.key ? "sc-tab sc-tab-current" : "sc-tab", "aria-current": candidate.key === page.key ? "page" : undefined, children: pluginWordOf(candidate.name, language) }, candidate.key))) })), _jsx(PluginFrame, { plugin: plugin, page: page.key, project: project, path: route.rest, me: data.me, language: language, client: client, navigation: navigation, events: project === undefined ? undefined : data.events, title: words.plugins.frame(pluginWordOf(page.name, language)) }, `${plugin.key}:${page.key}:${projectKey ?? ""}`)] }));
231
+ }
232
+ /**
233
+ * The plugins' settings (docs/specs/plugins.md, "The settings pages"): the list, or one plugin
234
+ * whole, read again after every change; for an administrator.
235
+ */
236
+ function PluginSettingsPage(props) {
237
+ const words = useWords();
238
+ const { data, client, language, navigation } = props;
239
+ const settings = words.plugins.settings;
240
+ const administrator = data.me?.person?.role === "admin";
241
+ const [detail, setDetail] = useState(undefined);
242
+ const [failure, setFailure] = useState(undefined);
243
+ const key = props.plugin;
244
+ const read = useCallback(async () => {
245
+ if (key === undefined) {
246
+ return;
247
+ }
248
+ try {
249
+ setDetail(await client.plugin(key).get());
250
+ setFailure(undefined);
251
+ }
252
+ catch (error) {
253
+ setFailure(error instanceof ApiError ? error.message : words.common.somethingWentWrong);
254
+ }
255
+ }, [client, key, words.common.somethingWentWrong]);
256
+ useEffect(() => {
257
+ setDetail(undefined);
258
+ void read();
259
+ }, [read]);
260
+ if (!administrator) {
261
+ return _jsx(NothingHere, {});
262
+ }
263
+ if (key === undefined) {
264
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: settings.title }) }), _jsx(PluginSettingsList, { plugins: data.plugins, language: language, href: (plugin) => pluginSettingsHref(plugin.key) })] }));
265
+ }
266
+ if (failure !== undefined) {
267
+ return _jsx(EmptyState, { title: words.common.somethingWentWrong, body: failure });
268
+ }
269
+ if (detail === undefined) {
270
+ return (_jsx("div", { className: "sc-loading", children: _jsx(Spinner, { label: words.common.loading }) }));
271
+ }
272
+ return (_jsx(PluginSettingsView, { detail: detail, projects: data.projects, people: data.people, language: language, client: client, onChanged: async () => {
273
+ await read();
274
+ await data.actions.refreshPlugins();
275
+ }, onPurged: () => {
276
+ void data.actions.refreshPlugins();
277
+ navigation.replace(pluginSettingsHref());
278
+ } }));
206
279
  }
207
280
  /**
208
281
  * The archive of the board (ADR-0029): the tasks put away, newest first, a page at a time from
@@ -513,6 +586,9 @@ function ProjectPage(props) {
513
586
  if (route.name === "settings") {
514
587
  return (_jsx(ProjectSettingsPage, { route: route, project: project, data: data, navigation: navigation }));
515
588
  }
589
+ if (route.name === "plugin") {
590
+ return (_jsx(PluginPage, { route: route, data: data, project: project, client: props.client, language: props.language, navigation: navigation }));
591
+ }
516
592
  return _jsx(NothingHere, {});
517
593
  }
518
594
  /**
@@ -602,7 +678,7 @@ function ProjectSettingsPage(props) {
602
678
  */
603
679
  function WorkspaceSettingsPage(props) {
604
680
  const words = useWords();
605
- const { form, data, navigation } = props;
681
+ const { form, data, navigation, language } = props;
606
682
  const { busy, error, act } = useAction();
607
683
  const administrator = data.me?.person?.role === "admin";
608
684
  const workspace = data.me?.workspace;
@@ -662,11 +738,11 @@ function WorkspaceSettingsPage(props) {
662
738
  navigation.replace(PATHS.settings);
663
739
  }), onCancel: () => navigation.back(PATHS.settings) })] })] }));
664
740
  }
665
- return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), workspace !== undefined && (_jsxs("section", { className: "sc-panel", "aria-label": words.settings.workspace.aria, children: [_jsxs("div", { className: "sc-section-head", children: [_jsx("h2", { className: "sc-section-title", children: words.settings.workspace.title }), administrator && (_jsx("a", { className: "sc-button sc-button-secondary sc-button-sm", href: settingsHref("workspace"), children: words.common.edit }))] }), _jsx(WorkspaceSettingsView, { workspace: workspace })] })), _jsxs("section", { className: "sc-panel", "aria-label": words.rules.title, children: [_jsx("h2", { className: "sc-section-title", children: words.rules.title }), rules === undefined ? (_jsx(Spinner, { label: words.rules.loading })) : (_jsx(RulesView, { rules: rules, scope: "workspace", editHref: administrator ? settingsHref("rules") : undefined, onArchive: administrator
741
+ return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), workspace !== undefined && (_jsxs("section", { className: "sc-panel", "aria-label": words.settings.workspace.aria, children: [_jsxs("div", { className: "sc-section-head", children: [_jsx("h2", { className: "sc-section-title", children: words.settings.workspace.title }), administrator && (_jsx("a", { className: "sc-button sc-button-secondary sc-button-sm", href: settingsHref("workspace"), children: words.common.edit }))] }), _jsx(WorkspaceSettingsView, { workspace: workspace, console: data.me?.console })] })), _jsxs("section", { className: "sc-panel", "aria-label": words.rules.title, children: [_jsx("h2", { className: "sc-section-title", children: words.rules.title }), rules === undefined ? (_jsx(Spinner, { label: words.rules.loading })) : (_jsx(RulesView, { rules: rules, scope: "workspace", editHref: administrator ? settingsHref("rules") : undefined, onArchive: administrator
666
742
  ? () => void act(async () => {
667
743
  setRules(await data.actions.archiveRules());
668
744
  })
669
- : undefined, busy: busy }))] }), _jsxs("section", { className: "sc-panel", "aria-label": words.settings.categories, children: [_jsx("h2", { className: "sc-section-title", children: words.settings.categories }), data.categories.length > 0 && (_jsx("p", { className: "sc-category-chips", children: data.categories.map((category) => (_jsx(CategoryBadge, { category: category }, category.key))) })), _jsx(LinkButton, { href: PATHS.categories, children: words.settings.manageCategories })] }), skills !== undefined && (_jsxs("section", { className: "sc-panel", "aria-label": words.settings.skills, children: [_jsx("h2", { className: "sc-section-title", children: words.settings.skills }), _jsxs("dl", { className: "sc-facts sc-facts-plain", children: [_jsxs("div", { children: [_jsx("dt", { children: words.settings.skillsSource }), _jsx("dd", { children: _jsx("a", { href: skills.source, target: "_blank", rel: "noopener noreferrer", children: skills.source }) })] }), _jsxs("div", { children: [_jsx("dt", { children: words.settings.skillsAddress }), _jsx("dd", { children: skills.address === null ? (words.settings.noSkillsAddress) : (_jsx("a", { href: `${skills.source}${skills.address}`, target: "_blank", rel: "noopener noreferrer", children: _jsx("code", { children: skills.address }) })) })] }), skills.address !== null && (_jsxs("div", { children: [_jsx("dt", { children: words.settings.skillsToken }), _jsxs("dd", { children: [skills.packToken == null ? (_jsx("span", { className: "sc-muted", children: words.projects.settings.packTokenNone })) : (_jsxs(_Fragment, { children: [words.projects.settings.packTokenSet, " ", _jsx(Time, { iso: skills.packToken.setAt })] })), administrator && (_jsxs(_Fragment, { children: [" ", _jsx("a", { href: settingsHref("pack-token"), children: skills.packToken == null
745
+ : undefined, busy: busy }))] }), administrator && (_jsxs("section", { className: "sc-panel", "aria-label": words.plugins.settings.title, children: [_jsx("h2", { className: "sc-section-title", children: words.plugins.settings.title }), data.plugins.length === 0 ? (_jsx("p", { className: "sc-settings-hint", children: words.plugins.settings.empty.body })) : (_jsx(PluginSettingsList, { plugins: data.plugins, language: language, href: (plugin) => pluginSettingsHref(plugin.key) }))] })), _jsxs("section", { className: "sc-panel", "aria-label": words.settings.categories, children: [_jsx("h2", { className: "sc-section-title", children: words.settings.categories }), data.categories.length > 0 && (_jsx("p", { className: "sc-category-chips", children: data.categories.map((category) => (_jsx(CategoryBadge, { category: category }, category.key))) })), _jsx(LinkButton, { href: PATHS.categories, children: words.settings.manageCategories })] }), skills !== undefined && (_jsxs("section", { className: "sc-panel", "aria-label": words.settings.skills, children: [_jsx("h2", { className: "sc-section-title", children: words.settings.skills }), _jsxs("dl", { className: "sc-facts sc-facts-plain", children: [_jsxs("div", { children: [_jsx("dt", { children: words.settings.skillsSource }), _jsx("dd", { children: _jsx("a", { href: skills.source, target: "_blank", rel: "noopener noreferrer", children: skills.source }) })] }), _jsxs("div", { children: [_jsx("dt", { children: words.settings.skillsAddress }), _jsx("dd", { children: skills.address === null ? (words.settings.noSkillsAddress) : (_jsx("a", { href: `${skills.source}${skills.address}`, target: "_blank", rel: "noopener noreferrer", children: _jsx("code", { children: skills.address }) })) })] }), skills.address !== null && (_jsxs("div", { children: [_jsx("dt", { children: words.settings.skillsToken }), _jsxs("dd", { children: [skills.packToken == null ? (_jsx("span", { className: "sc-muted", children: words.projects.settings.packTokenNone })) : (_jsxs(_Fragment, { children: [words.projects.settings.packTokenSet, " ", _jsx(Time, { iso: skills.packToken.setAt })] })), administrator && (_jsxs(_Fragment, { children: [" ", _jsx("a", { href: settingsHref("pack-token"), children: skills.packToken == null
670
746
  ? words.projects.settings.packTokenAdd
671
747
  : words.projects.settings.packTokenEdit })] }))] })] }))] }), _jsx("p", { className: "sc-muted", children: words.settings.skillsFromConfiguration })] }))] }));
672
748
  }
@@ -832,9 +908,15 @@ function PersonWorkPage(props) {
832
908
  }
833
909
  function WorkspacePage(props) {
834
910
  const words = useWords();
835
- const { route, data, components, navigation } = props;
911
+ const { route, data, components, navigation, client, language } = props;
836
912
  const [fresh, setFresh] = useState(undefined);
837
913
  const { busy, error, act } = useAction();
914
+ if (route.name === "plugin" && route.level === "workspace") {
915
+ return (_jsx(PluginPage, { route: route, data: data, project: undefined, client: client, language: language, navigation: navigation }));
916
+ }
917
+ if (route.name === "plugins-settings") {
918
+ return (_jsx(PluginSettingsPage, { plugin: route.plugin, data: data, client: client, language: language, navigation: navigation }));
919
+ }
838
920
  if (route.name === "work") {
839
921
  return _jsx(WorkPage, { data: data, components: components, week: route.week });
840
922
  }
@@ -845,7 +927,7 @@ function WorkspacePage(props) {
845
927
  return _jsx(PersonWorkPage, { id: route.id, data: data, components: components, week: route.week });
846
928
  }
847
929
  if (route.name === "workspace-settings") {
848
- return _jsx(WorkspaceSettingsPage, { form: route.form, data: data, navigation: navigation });
930
+ return (_jsx(WorkspaceSettingsPage, { form: route.form, data: data, navigation: navigation, language: language }));
849
931
  }
850
932
  if (route.name === "categories") {
851
933
  const me = data.me?.person ?? undefined;
@@ -966,13 +1048,6 @@ export function createConsole(config = {}) {
966
1048
  current: route.name === "agents" || route.name === "new-token",
967
1049
  },
968
1050
  ]),
969
- // The settings are read by everyone: the rules every agent follows, the categories and
970
- // what the colours mean, where the skills come from; kept by an administrator.
971
- {
972
- href: PATHS.settings,
973
- label: words.nav.settings,
974
- current: route.name === "workspace-settings" || route.name === "categories",
975
- },
976
1051
  ...pages
977
1052
  .filter((candidate) => candidate.level === "workspace")
978
1053
  .map((candidate) => ({
@@ -982,6 +1057,26 @@ export function createConsole(config = {}) {
982
1057
  route.level === "workspace" &&
983
1058
  route.page === candidate.key,
984
1059
  })),
1060
+ // The plugins' tabs (ADR-0054): the groups that hold a plugin the person has a page of.
1061
+ ...pluginNavItems({
1062
+ plugins: data.plugins,
1063
+ level: "workspace",
1064
+ current: {
1065
+ plugin: route.name === "plugin" && route.level === "workspace" ? route.plugin : undefined,
1066
+ },
1067
+ words,
1068
+ language: pack.tag,
1069
+ }),
1070
+ // The settings are read by everyone: the rules every agent follows, the categories and
1071
+ // what the colours mean, where the skills come from, the plugins; kept by an
1072
+ // administrator. Last, after the organization's own tabs and the plugins'.
1073
+ {
1074
+ href: PATHS.settings,
1075
+ label: words.nav.settings,
1076
+ current: route.name === "workspace-settings" ||
1077
+ route.name === "categories" ||
1078
+ route.name === "plugins-settings",
1079
+ },
985
1080
  ];
986
1081
  const nav = projectKey === undefined
987
1082
  ? workspaceNav
@@ -1029,12 +1124,6 @@ export function createConsole(config = {}) {
1029
1124
  label: words.nav.members,
1030
1125
  current: route.name === "members",
1031
1126
  },
1032
- // Read by every member: what the project is, and what its agents are asked (ADR-0023).
1033
- {
1034
- href: projectHref(projectKey, "settings"),
1035
- label: words.nav.settings,
1036
- current: route.name === "settings",
1037
- },
1038
1127
  ...pages
1039
1128
  .filter((candidate) => candidate.level === "project")
1040
1129
  .map((candidate) => ({
@@ -1044,6 +1133,23 @@ export function createConsole(config = {}) {
1044
1133
  route.level === "project" &&
1045
1134
  route.page === candidate.key,
1046
1135
  })),
1136
+ ...pluginNavItems({
1137
+ plugins: data.plugins,
1138
+ level: "project",
1139
+ project: { key: projectKey, plugins: data.projectPlugins },
1140
+ current: {
1141
+ plugin: route.name === "plugin" && route.level === "project" ? route.plugin : undefined,
1142
+ },
1143
+ words,
1144
+ language: pack.tag,
1145
+ }),
1146
+ // Read by every member: what the project is, and what its agents are asked
1147
+ // (ADR-0023). Last, after the organization's own tabs and the plugins'.
1148
+ {
1149
+ href: projectHref(projectKey, "settings"),
1150
+ label: words.nav.settings,
1151
+ current: route.name === "settings",
1152
+ },
1047
1153
  ];
1048
1154
  /** A page of the console's own at the address, or nothing there. */
1049
1155
  const customPageOf = (key, level) => {
@@ -1058,13 +1164,13 @@ export function createConsole(config = {}) {
1058
1164
  page = _jsx(NotWithToken, {});
1059
1165
  }
1060
1166
  else if (route.name === "projects" || route.name === "new-project") {
1061
- page = (_jsx(ProjectsPage, { data: data, components: components, navigation: navigation, making: route.name === "new-project" }));
1167
+ page = (_jsx(ProjectsPage, { data: data, components: components, navigation: navigation, client: client, language: pack.tag, making: route.name === "new-project" }));
1062
1168
  }
1063
1169
  else if (route.name === "custom" && route.level === "workspace") {
1064
1170
  page = customPageOf(route.page, "workspace");
1065
1171
  }
1066
1172
  else if (projectKey === undefined) {
1067
- page = (_jsx(WorkspacePage, { route: route, data: data, components: components, navigation: navigation }));
1173
+ page = (_jsx(WorkspacePage, { route: route, data: data, components: components, navigation: navigation, client: client, language: pack.tag }));
1068
1174
  }
1069
1175
  else if (data.projectError !== undefined) {
1070
1176
  page = (_jsx(EmptyState, { title: words.common.noSuchProject.title, body: words.common.noSuchProject.body, action: _jsx(LinkButton, { href: PATHS.projects, children: words.nav.projects }) }));
@@ -1076,7 +1182,7 @@ export function createConsole(config = {}) {
1076
1182
  page = customPageOf(route.page, "project");
1077
1183
  }
1078
1184
  else {
1079
- page = (_jsx(ProjectPage, { route: route, project: project, data: data, components: components, navigation: navigation }));
1185
+ page = (_jsx(ProjectPage, { route: route, project: project, data: data, components: components, navigation: navigation, client: client, language: pack.tag }));
1080
1186
  }
1081
1187
  content = (_jsx(components.Shell, { title: title, brand: config.brand, project: projectKey === undefined
1082
1188
  ? undefined
@@ -1092,7 +1198,11 @@ export function createConsole(config = {}) {
1092
1198
  route.name === "calendar" ||
1093
1199
  (route.name === "goal" && route.form === undefined) ||
1094
1200
  (route.name === "custom" &&
1095
- pages.some((candidate) => candidate.key === route.page && candidate.wide === true)), onNavigate: (href) => navigation.go(href), onSignOut: viaToken ? undefined : () => void data.actions.signOut(), children: page }));
1201
+ pages.some((candidate) => candidate.key === route.page && candidate.wide === true)) ||
1202
+ (route.name === "plugin" &&
1203
+ data.plugins.some((candidate) => candidate.key === route.plugin &&
1204
+ pluginPagesAt(candidate, route.level).some((candidate, index) => candidate.wide &&
1205
+ (route.page === undefined ? index === 0 : candidate.key === route.page)))), onNavigate: (href) => navigation.go(href), onSignOut: viaToken ? undefined : () => void data.actions.signOut(), children: page }));
1096
1206
  }
1097
1207
  return (_jsx(LanguageContext.Provider, { value: pack, children: _jsx(RemotePicturesContext.Provider, { value: pictureSrc, children: content }) }));
1098
1208
  }