@skillcdn/console 0.4.0 → 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 (308) hide show
  1. package/README.md +20 -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 +579 -45
  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 +60 -7
  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 +109 -2
  34. package/dist/client.d.ts.map +1 -1
  35. package/dist/client.js +67 -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/markdown.d.ts.map +1 -1
  48. package/dist/components/markdown.js +6 -3
  49. package/dist/components/markdown.js.map +1 -1
  50. package/dist/components/pack-connect.d.ts +3 -1
  51. package/dist/components/pack-connect.d.ts.map +1 -1
  52. package/dist/components/pack-connect.js +4 -4
  53. package/dist/components/pack-connect.js.map +1 -1
  54. package/dist/components/pack-token.d.ts +3 -1
  55. package/dist/components/pack-token.d.ts.map +1 -1
  56. package/dist/components/pack-token.js +1 -1
  57. package/dist/components/pack-token.js.map +1 -1
  58. package/dist/components/people.d.ts.map +1 -1
  59. package/dist/components/people.js +14 -3
  60. package/dist/components/people.js.map +1 -1
  61. package/dist/components/plugin-frame.d.ts +52 -0
  62. package/dist/components/plugin-frame.d.ts.map +1 -0
  63. package/dist/components/plugin-frame.js +314 -0
  64. package/dist/components/plugin-frame.js.map +1 -0
  65. package/dist/components/plugin-settings.d.ts +38 -0
  66. package/dist/components/plugin-settings.d.ts.map +1 -0
  67. package/dist/components/plugin-settings.js +348 -0
  68. package/dist/components/plugin-settings.js.map +1 -0
  69. package/dist/components/remote-pictures.d.ts +6 -0
  70. package/dist/components/remote-pictures.d.ts.map +1 -0
  71. package/dist/components/remote-pictures.js +8 -0
  72. package/dist/components/remote-pictures.js.map +1 -0
  73. package/dist/components/shell.d.ts +2 -0
  74. package/dist/components/shell.d.ts.map +1 -1
  75. package/dist/components/shell.js +26 -1
  76. package/dist/components/shell.js.map +1 -1
  77. package/dist/components/sign-in.d.ts.map +1 -1
  78. package/dist/components/sign-in.js +49 -2
  79. package/dist/components/sign-in.js.map +1 -1
  80. package/dist/components/ui.d.ts +3 -2
  81. package/dist/components/ui.d.ts.map +1 -1
  82. package/dist/components/ui.js +6 -3
  83. package/dist/components/ui.js.map +1 -1
  84. package/dist/components/workspace.d.ts +7 -1
  85. package/dist/components/workspace.d.ts.map +1 -1
  86. package/dist/components/workspace.js +1 -1
  87. package/dist/components/workspace.js.map +1 -1
  88. package/dist/console.css +384 -5
  89. package/dist/console.d.ts.map +1 -1
  90. package/dist/console.js +146 -29
  91. package/dist/console.js.map +1 -1
  92. package/dist/data.d.ts +11 -2
  93. package/dist/data.d.ts.map +1 -1
  94. package/dist/data.js +35 -21
  95. package/dist/data.js.map +1 -1
  96. package/dist/i18n/en.d.ts +163 -1
  97. package/dist/i18n/en.d.ts.map +1 -1
  98. package/dist/i18n/en.js +174 -1
  99. package/dist/i18n/en.js.map +1 -1
  100. package/dist/i18n/ko.d.ts.map +1 -1
  101. package/dist/i18n/ko.js +154 -1
  102. package/dist/i18n/ko.js.map +1 -1
  103. package/dist/index.d.ts +3 -1
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +3 -1
  106. package/dist/index.js.map +1 -1
  107. package/dist/limits.d.ts +14 -0
  108. package/dist/limits.d.ts.map +1 -1
  109. package/dist/limits.js +14 -0
  110. package/dist/limits.js.map +1 -1
  111. package/dist/plugin/args.d.ts +148 -0
  112. package/dist/plugin/args.d.ts.map +1 -0
  113. package/dist/plugin/args.js +369 -0
  114. package/dist/plugin/args.js.map +1 -0
  115. package/dist/plugin/bridge.d.ts +146 -0
  116. package/dist/plugin/bridge.d.ts.map +1 -0
  117. package/dist/plugin/bridge.js +125 -0
  118. package/dist/plugin/bridge.js.map +1 -0
  119. package/dist/plugin/capabilities.d.ts +20 -0
  120. package/dist/plugin/capabilities.d.ts.map +1 -0
  121. package/dist/plugin/capabilities.js +133 -0
  122. package/dist/plugin/capabilities.js.map +1 -0
  123. package/dist/plugin/context.d.ts +374 -0
  124. package/dist/plugin/context.d.ts.map +1 -0
  125. package/dist/plugin/context.js +2 -0
  126. package/dist/plugin/context.js.map +1 -0
  127. package/dist/plugin/define.d.ts +208 -0
  128. package/dist/plugin/define.d.ts.map +1 -0
  129. package/dist/plugin/define.js +482 -0
  130. package/dist/plugin/define.js.map +1 -0
  131. package/dist/plugin/definition-error.d.ts +11 -0
  132. package/dist/plugin/definition-error.d.ts.map +1 -0
  133. package/dist/plugin/definition-error.js +17 -0
  134. package/dist/plugin/definition-error.js.map +1 -0
  135. package/dist/plugin/limits.d.ts +67 -0
  136. package/dist/plugin/limits.d.ts.map +1 -0
  137. package/dist/plugin/limits.js +70 -0
  138. package/dist/plugin/limits.js.map +1 -0
  139. package/dist/plugin/load-worker.d.ts +7 -0
  140. package/dist/plugin/load-worker.d.ts.map +1 -0
  141. package/dist/plugin/load-worker.js +41 -0
  142. package/dist/plugin/load-worker.js.map +1 -0
  143. package/dist/plugin/load.d.ts +18 -0
  144. package/dist/plugin/load.d.ts.map +1 -0
  145. package/dist/plugin/load.js +62 -0
  146. package/dist/plugin/load.js.map +1 -0
  147. package/dist/plugin/manifest.d.ts +237 -0
  148. package/dist/plugin/manifest.d.ts.map +1 -0
  149. package/dist/plugin/manifest.js +411 -0
  150. package/dist/plugin/manifest.js.map +1 -0
  151. package/dist/plugin/refusals.d.ts +49 -0
  152. package/dist/plugin/refusals.d.ts.map +1 -0
  153. package/dist/plugin/refusals.js +79 -0
  154. package/dist/plugin/refusals.js.map +1 -0
  155. package/dist/plugin/rest-schemas.d.ts +556 -0
  156. package/dist/plugin/rest-schemas.d.ts.map +1 -0
  157. package/dist/plugin/rest-schemas.js +262 -0
  158. package/dist/plugin/rest-schemas.js.map +1 -0
  159. package/dist/plugin/sandbox.d.ts +23 -0
  160. package/dist/plugin/sandbox.d.ts.map +1 -0
  161. package/dist/plugin/sandbox.js +99 -0
  162. package/dist/plugin/sandbox.js.map +1 -0
  163. package/dist/plugin/testing.d.ts +45 -0
  164. package/dist/plugin/testing.d.ts.map +1 -0
  165. package/dist/plugin/testing.js +420 -0
  166. package/dist/plugin/testing.js.map +1 -0
  167. package/dist/plugin/web-react.d.ts +11 -0
  168. package/dist/plugin/web-react.d.ts.map +1 -0
  169. package/dist/plugin/web-react.js +34 -0
  170. package/dist/plugin/web-react.js.map +1 -0
  171. package/dist/plugin/web.d.ts +95 -0
  172. package/dist/plugin/web.d.ts.map +1 -0
  173. package/dist/plugin/web.js +245 -0
  174. package/dist/plugin/web.js.map +1 -0
  175. package/dist/plugin-nav.d.ts +29 -0
  176. package/dist/plugin-nav.d.ts.map +1 -0
  177. package/dist/plugin-nav.js +92 -0
  178. package/dist/plugin-nav.js.map +1 -0
  179. package/dist/plugin-testing.d.ts +2 -0
  180. package/dist/plugin-testing.d.ts.map +1 -0
  181. package/dist/plugin-testing.js +4 -0
  182. package/dist/plugin-testing.js.map +1 -0
  183. package/dist/plugin-web-react.d.ts +3 -0
  184. package/dist/plugin-web-react.d.ts.map +1 -0
  185. package/dist/plugin-web-react.js +3 -0
  186. package/dist/plugin-web-react.js.map +1 -0
  187. package/dist/plugin-web.d.ts +4 -0
  188. package/dist/plugin-web.d.ts.map +1 -0
  189. package/dist/plugin-web.js +5 -0
  190. package/dist/plugin-web.js.map +1 -0
  191. package/dist/plugin.d.ts +11 -0
  192. package/dist/plugin.d.ts.map +1 -0
  193. package/dist/plugin.js +15 -0
  194. package/dist/plugin.js.map +1 -0
  195. package/dist/router.d.ts +28 -0
  196. package/dist/router.d.ts.map +1 -1
  197. package/dist/router.js +47 -0
  198. package/dist/router.js.map +1 -1
  199. package/dist/routes.d.ts +6 -0
  200. package/dist/routes.d.ts.map +1 -1
  201. package/dist/routes.js +8 -0
  202. package/dist/routes.js.map +1 -1
  203. package/dist/schemas.d.ts +147 -1
  204. package/dist/schemas.d.ts.map +1 -1
  205. package/dist/schemas.js +55 -5
  206. package/dist/schemas.js.map +1 -1
  207. package/dist/tokens.d.ts +2 -0
  208. package/dist/tokens.d.ts.map +1 -0
  209. package/dist/tokens.js +101 -0
  210. package/dist/tokens.js.map +1 -0
  211. package/dist/vocabulary.d.ts +11 -1
  212. package/dist/vocabulary.d.ts.map +1 -1
  213. package/dist/vocabulary.js +24 -0
  214. package/dist/vocabulary.js.map +1 -1
  215. package/dist/web.d.ts.map +1 -1
  216. package/dist/web.js +5 -2
  217. package/dist/web.js.map +1 -1
  218. package/docs/specs/cli.md +16 -9
  219. package/docs/specs/plugins.md +487 -0
  220. package/docs/specs/rest.md +25 -13
  221. package/package.json +21 -3
  222. package/skills/building-a-console/SKILL.md +25 -26
  223. package/skills/building-a-console/references/configuring.md +40 -0
  224. package/skills/building-a-console/references/the-default-console.md +1 -1
  225. package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
  226. package/skills/building-a-plugin/SKILL.md +137 -0
  227. package/skills/deploying-a-console/SKILL.md +24 -26
  228. package/skills/deploying-a-console/references/access.md +13 -9
  229. package/skills/deploying-a-console/references/resources.md +25 -10
  230. package/skills/deploying-a-console/references/running-it-again.md +15 -0
  231. package/skills/deploying-a-console/references/sizing.md +2 -0
  232. package/skills/planning-the-board/SKILL.md +96 -0
  233. package/skills/planning-the-board/references/a-task.md +36 -0
  234. package/skills/planning-the-board/references/setting-up.md +44 -0
  235. package/skills/working-the-board/SKILL.md +43 -34
  236. package/skills/working-the-board/references/the-pack.md +19 -0
  237. package/src/api.ts +16 -0
  238. package/src/cli/cli.ts +736 -45
  239. package/src/cli/format.ts +356 -9
  240. package/src/cli/help.ts +71 -6
  241. package/src/cli/plugin-check.ts +470 -0
  242. package/src/cli/plugin-cli.ts +148 -0
  243. package/src/cli/plugin-init.ts +90 -0
  244. package/src/cli/plugin-main.ts +30 -0
  245. package/src/client.ts +324 -1
  246. package/src/compat.ts +2 -2
  247. package/src/components/board.tsx +62 -14
  248. package/src/components/event-feed.tsx +42 -1
  249. package/src/components/markdown.tsx +6 -3
  250. package/src/components/pack-connect.tsx +10 -7
  251. package/src/components/pack-token.tsx +4 -1
  252. package/src/components/people.tsx +17 -7
  253. package/src/components/plugin-frame.tsx +437 -0
  254. package/src/components/plugin-settings.tsx +1111 -0
  255. package/src/components/remote-pictures.ts +19 -0
  256. package/src/components/shell.tsx +82 -14
  257. package/src/components/sign-in.tsx +114 -13
  258. package/src/components/ui.tsx +6 -3
  259. package/src/components/workspace.tsx +18 -0
  260. package/src/console.tsx +309 -23
  261. package/src/data.ts +47 -22
  262. package/src/i18n/en.ts +183 -1
  263. package/src/i18n/ko.ts +159 -1
  264. package/src/index.ts +12 -0
  265. package/src/limits.ts +14 -0
  266. package/src/plugin/args.ts +526 -0
  267. package/src/plugin/bridge.ts +314 -0
  268. package/src/plugin/capabilities.ts +150 -0
  269. package/src/plugin/context.ts +416 -0
  270. package/src/plugin/define.ts +792 -0
  271. package/src/plugin/definition-error.ts +19 -0
  272. package/src/plugin/limits.ts +70 -0
  273. package/src/plugin/load-worker.ts +50 -0
  274. package/src/plugin/load.ts +82 -0
  275. package/src/plugin/manifest.ts +561 -0
  276. package/src/plugin/refusals.ts +94 -0
  277. package/src/plugin/rest-schemas.ts +354 -0
  278. package/src/plugin/sandbox.ts +123 -0
  279. package/src/plugin/testing.ts +612 -0
  280. package/src/plugin/web-react.ts +45 -0
  281. package/src/plugin/web.ts +403 -0
  282. package/src/plugin-nav.ts +132 -0
  283. package/src/plugin-testing.ts +10 -0
  284. package/src/plugin-web-react.ts +4 -0
  285. package/src/plugin-web.ts +35 -0
  286. package/src/plugin.ts +126 -0
  287. package/src/router.ts +73 -0
  288. package/src/routes.ts +9 -0
  289. package/src/schemas.ts +60 -4
  290. package/src/styles/console.css +384 -5
  291. package/src/tokens.ts +101 -0
  292. package/src/vocabulary.ts +30 -0
  293. package/src/web.ts +5 -2
  294. package/template/plugin/.gitignore +2 -0
  295. package/template/plugin/README.md +26 -0
  296. package/template/plugin/SKILLCDN.md +13 -0
  297. package/template/plugin/build.mjs +40 -0
  298. package/template/plugin/console-plugin.json +50 -0
  299. package/template/plugin/package.json +27 -0
  300. package/template/plugin/skills/__key__/SKILL.md +23 -0
  301. package/template/plugin/src/check.test.ts +34 -0
  302. package/template/plugin/src/server/index.test.ts +41 -0
  303. package/template/plugin/src/server/index.ts +72 -0
  304. package/template/plugin/src/web/index.css +51 -0
  305. package/template/plugin/src/web/index.ts +115 -0
  306. package/template/plugin/tsconfig.json +17 -0
  307. package/skills/working-the-board/references/setting-up.md +0 -3
  308. /package/skills/{working-the-board → planning-the-board}/references/project-rules.md +0 -0
package/dist/console.js CHANGED
@@ -14,7 +14,10 @@ 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";
20
+ import { RemotePicturesContext } from "./components/remote-pictures.js";
18
21
  import { GoalTimeline } from "./components/roadmap.js";
19
22
  import { RulesForm, RulesView } from "./components/rules.js";
20
23
  import { isPlainClick, Shell, } from "./components/shell.js";
@@ -32,7 +35,8 @@ import { useConsoleData } from "./data.js";
32
35
  import { monthOf, today } from "./dates.js";
33
36
  import { DocsPage } from "./docs-page.js";
34
37
  import { chooseLanguage, DEFAULT_LANGUAGES, LanguageContext, useWords, } from "./i18n/index.js";
35
- 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";
36
40
  import { projectPath } from "./routes.js";
37
41
  import { errorWords, useAction } from "./use-action.js";
38
42
  export const DEFAULT_COMPONENTS = {
@@ -191,13 +195,87 @@ function NotWithToken() {
191
195
  }
192
196
  function ProjectsPage(props) {
193
197
  const words = useWords().projects;
194
- const { data, components, navigation, making } = 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) }));
195
202
  const { busy, error, act } = useAction();
196
203
  const href = (project) => projectHref(project.key);
197
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: words.title }), !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 () => {
204
+ // A project is made by an administrator (ADR-0045): a member sees the list alone, the form's
205
+ // address included.
206
+ const administrator = data.me?.person?.role === "admin";
207
+ const making = props.making && administrator;
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 () => {
198
209
  const made = await data.actions.createProject(input, icon instanceof File ? uploadOf(icon) : undefined);
199
210
  navigation.replace(projectHref(made.key));
200
- }), 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: words.empty.body, action: _jsx(LinkButton, { href: PATHS.newProject, children: words.newProject }) }) })] }));
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
+ } }));
201
279
  }
202
280
  /**
203
281
  * The archive of the board (ADR-0029): the tasks put away, newest first, a page at a time from
@@ -508,6 +586,9 @@ function ProjectPage(props) {
508
586
  if (route.name === "settings") {
509
587
  return (_jsx(ProjectSettingsPage, { route: route, project: project, data: data, navigation: navigation }));
510
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
+ }
511
592
  return _jsx(NothingHere, {});
512
593
  }
513
594
  /**
@@ -573,7 +654,7 @@ function ProjectSettingsPage(props) {
573
654
  if (!owner) {
574
655
  return _jsx(NothingHere, {});
575
656
  }
576
- return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(PackTokenWays, { title: settings.packToken, intro: words.projects.packToken.intro, set: project.packToken !== null, setAt: project.packToken?.setAt, busy: busy, onStart: data.actions.connectPackToken, poll: data.actions.packTokenConnection, onCancelConnect: data.actions.cancelPackTokenConnection, onConnected: () => navigation.replace(here), onSubmit: (token) => void act(async () => {
657
+ return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(PackTokenWays, { title: settings.packToken, intro: words.projects.packToken.intro, set: project.packToken !== null, setAt: project.packToken?.setAt, busy: busy, onStart: data.actions.connectPackToken, poll: data.actions.packTokenConnection, claim: data.actions.claimPackTokenConnection, onCancelConnect: data.actions.cancelPackTokenConnection, onConnected: () => navigation.replace(here), onSubmit: (token) => void act(async () => {
577
658
  await data.actions.setPackToken(token);
578
659
  navigation.replace(here);
579
660
  }), onRemove: project.packToken === null
@@ -597,7 +678,7 @@ function ProjectSettingsPage(props) {
597
678
  */
598
679
  function WorkspaceSettingsPage(props) {
599
680
  const words = useWords();
600
- const { form, data, navigation } = props;
681
+ const { form, data, navigation, language } = props;
601
682
  const { busy, error, act } = useAction();
602
683
  const administrator = data.me?.person?.role === "admin";
603
684
  const workspace = data.me?.workspace;
@@ -638,7 +719,7 @@ function WorkspaceSettingsPage(props) {
638
719
  return _jsx(NothingHere, {});
639
720
  }
640
721
  const held = workspace.skills.packToken ?? null;
641
- return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(PackTokenWays, { title: words.settings.skillsToken, intro: words.settings.skillsTokenIntro, set: held !== null, setAt: held?.setAt, busy: busy, onStart: data.actions.connectWorkspacePackToken, poll: data.actions.workspacePackTokenConnection, onCancelConnect: data.actions.cancelWorkspacePackTokenConnection, onConnected: () => navigation.replace(PATHS.settings), onSubmit: (token) => void act(async () => {
722
+ return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(PackTokenWays, { title: words.settings.skillsToken, intro: words.settings.skillsTokenIntro, set: held !== null, setAt: held?.setAt, busy: busy, onStart: data.actions.connectWorkspacePackToken, poll: data.actions.workspacePackTokenConnection, claim: data.actions.claimWorkspacePackTokenConnection, onCancelConnect: data.actions.cancelWorkspacePackTokenConnection, onConnected: () => navigation.replace(PATHS.settings), onSubmit: (token) => void act(async () => {
642
723
  await data.actions.setWorkspacePackToken(token);
643
724
  navigation.replace(PATHS.settings);
644
725
  }), onRemove: held === null
@@ -657,11 +738,11 @@ function WorkspaceSettingsPage(props) {
657
738
  navigation.replace(PATHS.settings);
658
739
  }), onCancel: () => navigation.back(PATHS.settings) })] })] }));
659
740
  }
660
- 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
661
742
  ? () => void act(async () => {
662
743
  setRules(await data.actions.archiveRules());
663
744
  })
664
- : 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
665
746
  ? words.projects.settings.packTokenAdd
666
747
  : words.projects.settings.packTokenEdit })] }))] })] }))] }), _jsx("p", { className: "sc-muted", children: words.settings.skillsFromConfiguration })] }))] }));
667
748
  }
@@ -827,9 +908,15 @@ function PersonWorkPage(props) {
827
908
  }
828
909
  function WorkspacePage(props) {
829
910
  const words = useWords();
830
- const { route, data, components, navigation } = props;
911
+ const { route, data, components, navigation, client, language } = props;
831
912
  const [fresh, setFresh] = useState(undefined);
832
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
+ }
833
920
  if (route.name === "work") {
834
921
  return _jsx(WorkPage, { data: data, components: components, week: route.week });
835
922
  }
@@ -840,7 +927,7 @@ function WorkspacePage(props) {
840
927
  return _jsx(PersonWorkPage, { id: route.id, data: data, components: components, week: route.week });
841
928
  }
842
929
  if (route.name === "workspace-settings") {
843
- return _jsx(WorkspaceSettingsPage, { form: route.form, data: data, navigation: navigation });
930
+ return (_jsx(WorkspaceSettingsPage, { form: route.form, data: data, navigation: navigation, language: language }));
844
931
  }
845
932
  if (route.name === "categories") {
846
933
  const me = data.me?.person ?? undefined;
@@ -893,6 +980,8 @@ function WorkspacePage(props) {
893
980
  export function createConsole(config = {}) {
894
981
  const components = { ...DEFAULT_COMPONENTS, ...config.components };
895
982
  const client = config.client ?? createClient({ baseUrl: config.baseUrl ?? "" });
983
+ /** Where the pages read a picture from elsewhere (ADR-0048): through this client's console. */
984
+ const pictureSrc = (url) => client.pictureUrl(url);
896
985
  const fallbackTitle = config.title ?? "Console";
897
986
  const languages = config.languages ?? DEFAULT_LANGUAGES;
898
987
  const pages = config.pages ?? [];
@@ -959,13 +1048,6 @@ export function createConsole(config = {}) {
959
1048
  current: route.name === "agents" || route.name === "new-token",
960
1049
  },
961
1050
  ]),
962
- // The settings are read by everyone: the rules every agent follows, the categories and
963
- // what the colours mean, where the skills come from; kept by an administrator.
964
- {
965
- href: PATHS.settings,
966
- label: words.nav.settings,
967
- current: route.name === "workspace-settings" || route.name === "categories",
968
- },
969
1051
  ...pages
970
1052
  .filter((candidate) => candidate.level === "workspace")
971
1053
  .map((candidate) => ({
@@ -975,6 +1057,26 @@ export function createConsole(config = {}) {
975
1057
  route.level === "workspace" &&
976
1058
  route.page === candidate.key,
977
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
+ },
978
1080
  ];
979
1081
  const nav = projectKey === undefined
980
1082
  ? workspaceNav
@@ -1022,12 +1124,6 @@ export function createConsole(config = {}) {
1022
1124
  label: words.nav.members,
1023
1125
  current: route.name === "members",
1024
1126
  },
1025
- // Read by every member: what the project is, and what its agents are asked (ADR-0023).
1026
- {
1027
- href: projectHref(projectKey, "settings"),
1028
- label: words.nav.settings,
1029
- current: route.name === "settings",
1030
- },
1031
1127
  ...pages
1032
1128
  .filter((candidate) => candidate.level === "project")
1033
1129
  .map((candidate) => ({
@@ -1037,6 +1133,23 @@ export function createConsole(config = {}) {
1037
1133
  route.level === "project" &&
1038
1134
  route.page === candidate.key,
1039
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
+ },
1040
1153
  ];
1041
1154
  /** A page of the console's own at the address, or nothing there. */
1042
1155
  const customPageOf = (key, level) => {
@@ -1051,13 +1164,13 @@ export function createConsole(config = {}) {
1051
1164
  page = _jsx(NotWithToken, {});
1052
1165
  }
1053
1166
  else if (route.name === "projects" || route.name === "new-project") {
1054
- 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" }));
1055
1168
  }
1056
1169
  else if (route.name === "custom" && route.level === "workspace") {
1057
1170
  page = customPageOf(route.page, "workspace");
1058
1171
  }
1059
1172
  else if (projectKey === undefined) {
1060
- 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 }));
1061
1174
  }
1062
1175
  else if (data.projectError !== undefined) {
1063
1176
  page = (_jsx(EmptyState, { title: words.common.noSuchProject.title, body: words.common.noSuchProject.body, action: _jsx(LinkButton, { href: PATHS.projects, children: words.nav.projects }) }));
@@ -1069,7 +1182,7 @@ export function createConsole(config = {}) {
1069
1182
  page = customPageOf(route.page, "project");
1070
1183
  }
1071
1184
  else {
1072
- 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 }));
1073
1186
  }
1074
1187
  content = (_jsx(components.Shell, { title: title, brand: config.brand, project: projectKey === undefined
1075
1188
  ? undefined
@@ -1085,9 +1198,13 @@ export function createConsole(config = {}) {
1085
1198
  route.name === "calendar" ||
1086
1199
  (route.name === "goal" && route.form === undefined) ||
1087
1200
  (route.name === "custom" &&
1088
- 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 }));
1089
1206
  }
1090
- return _jsx(LanguageContext.Provider, { value: pack, children: content });
1207
+ return (_jsx(LanguageContext.Provider, { value: pack, children: _jsx(RemotePicturesContext.Provider, { value: pictureSrc, children: content }) }));
1091
1208
  }
1092
1209
  return {
1093
1210
  App,