@skillcdn/console 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. package/README.md +19 -2
  2. package/SKILLCDN.md +7 -5
  3. package/dist/api.d.ts +5 -4
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/api.js +5 -4
  6. package/dist/api.js.map +1 -1
  7. package/dist/cli/cli.d.ts.map +1 -1
  8. package/dist/cli/cli.js +569 -48
  9. package/dist/cli/cli.js.map +1 -1
  10. package/dist/cli/format.d.ts +34 -3
  11. package/dist/cli/format.d.ts.map +1 -1
  12. package/dist/cli/format.js +283 -7
  13. package/dist/cli/format.js.map +1 -1
  14. package/dist/cli/help.d.ts.map +1 -1
  15. package/dist/cli/help.js +44 -7
  16. package/dist/cli/help.js.map +1 -1
  17. package/dist/cli/plugin-check.d.ts +26 -0
  18. package/dist/cli/plugin-check.d.ts.map +1 -0
  19. package/dist/cli/plugin-check.js +396 -0
  20. package/dist/cli/plugin-check.js.map +1 -0
  21. package/dist/cli/plugin-cli.d.ts +20 -0
  22. package/dist/cli/plugin-cli.d.ts.map +1 -0
  23. package/dist/cli/plugin-cli.js +189 -0
  24. package/dist/cli/plugin-cli.js.map +1 -0
  25. package/dist/cli/plugin-init.d.ts +37 -0
  26. package/dist/cli/plugin-init.d.ts.map +1 -0
  27. package/dist/cli/plugin-init.js +206 -0
  28. package/dist/cli/plugin-init.js.map +1 -0
  29. package/dist/cli/plugin-main.d.ts +3 -0
  30. package/dist/cli/plugin-main.d.ts.map +1 -0
  31. package/dist/cli/plugin-main.js +28 -0
  32. package/dist/cli/plugin-main.js.map +1 -0
  33. package/dist/client.d.ts +100 -3
  34. package/dist/client.d.ts.map +1 -1
  35. package/dist/client.js +79 -7
  36. package/dist/client.js.map +1 -1
  37. package/dist/compat.d.ts +1 -1
  38. package/dist/compat.js +2 -2
  39. package/dist/components/board.d.ts +8 -0
  40. package/dist/components/board.d.ts.map +1 -1
  41. package/dist/components/board.js +59 -15
  42. package/dist/components/board.js.map +1 -1
  43. package/dist/components/comments.d.ts.map +1 -1
  44. package/dist/components/comments.js +1 -1
  45. package/dist/components/comments.js.map +1 -1
  46. package/dist/components/decision-form.d.ts +2 -1
  47. package/dist/components/decision-form.d.ts.map +1 -1
  48. package/dist/components/decision-form.js +36 -4
  49. package/dist/components/decision-form.js.map +1 -1
  50. package/dist/components/decision-list.d.ts +4 -0
  51. package/dist/components/decision-list.d.ts.map +1 -1
  52. package/dist/components/decision-list.js +29 -3
  53. package/dist/components/decision-list.js.map +1 -1
  54. package/dist/components/event-feed.d.ts +1 -1
  55. package/dist/components/event-feed.d.ts.map +1 -1
  56. package/dist/components/event-feed.js +33 -1
  57. package/dist/components/event-feed.js.map +1 -1
  58. package/dist/components/my-decisions.d.ts +2 -0
  59. package/dist/components/my-decisions.d.ts.map +1 -1
  60. package/dist/components/my-decisions.js +2 -2
  61. package/dist/components/my-decisions.js.map +1 -1
  62. package/dist/components/plugin-frame.d.ts +52 -0
  63. package/dist/components/plugin-frame.d.ts.map +1 -0
  64. package/dist/components/plugin-frame.js +314 -0
  65. package/dist/components/plugin-frame.js.map +1 -0
  66. package/dist/components/plugin-settings.d.ts +38 -0
  67. package/dist/components/plugin-settings.d.ts.map +1 -0
  68. package/dist/components/plugin-settings.js +348 -0
  69. package/dist/components/plugin-settings.js.map +1 -0
  70. package/dist/components/shell.d.ts +2 -0
  71. package/dist/components/shell.d.ts.map +1 -1
  72. package/dist/components/shell.js +26 -1
  73. package/dist/components/shell.js.map +1 -1
  74. package/dist/components/task-view.d.ts +2 -1
  75. package/dist/components/task-view.d.ts.map +1 -1
  76. package/dist/components/task-view.js.map +1 -1
  77. package/dist/components/workspace.d.ts +7 -1
  78. package/dist/components/workspace.d.ts.map +1 -1
  79. package/dist/components/workspace.js +1 -1
  80. package/dist/components/workspace.js.map +1 -1
  81. package/dist/console.css +330 -1
  82. package/dist/console.d.ts.map +1 -1
  83. package/dist/console.js +152 -41
  84. package/dist/console.js.map +1 -1
  85. package/dist/data.d.ts +9 -2
  86. package/dist/data.d.ts.map +1 -1
  87. package/dist/data.js +45 -9
  88. package/dist/data.js.map +1 -1
  89. package/dist/i18n/en.d.ts +155 -0
  90. package/dist/i18n/en.d.ts.map +1 -1
  91. package/dist/i18n/en.js +166 -0
  92. package/dist/i18n/en.js.map +1 -1
  93. package/dist/i18n/ko.d.ts.map +1 -1
  94. package/dist/i18n/ko.js +150 -0
  95. package/dist/i18n/ko.js.map +1 -1
  96. package/dist/index.d.ts +3 -1
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +3 -1
  99. package/dist/index.js.map +1 -1
  100. package/dist/limits.d.ts +2 -0
  101. package/dist/limits.d.ts.map +1 -1
  102. package/dist/limits.js +2 -0
  103. package/dist/limits.js.map +1 -1
  104. package/dist/plugin/args.d.ts +148 -0
  105. package/dist/plugin/args.d.ts.map +1 -0
  106. package/dist/plugin/args.js +369 -0
  107. package/dist/plugin/args.js.map +1 -0
  108. package/dist/plugin/bridge.d.ts +146 -0
  109. package/dist/plugin/bridge.d.ts.map +1 -0
  110. package/dist/plugin/bridge.js +125 -0
  111. package/dist/plugin/bridge.js.map +1 -0
  112. package/dist/plugin/capabilities.d.ts +20 -0
  113. package/dist/plugin/capabilities.d.ts.map +1 -0
  114. package/dist/plugin/capabilities.js +133 -0
  115. package/dist/plugin/capabilities.js.map +1 -0
  116. package/dist/plugin/context.d.ts +380 -0
  117. package/dist/plugin/context.d.ts.map +1 -0
  118. package/dist/plugin/context.js +2 -0
  119. package/dist/plugin/context.js.map +1 -0
  120. package/dist/plugin/define.d.ts +208 -0
  121. package/dist/plugin/define.d.ts.map +1 -0
  122. package/dist/plugin/define.js +482 -0
  123. package/dist/plugin/define.js.map +1 -0
  124. package/dist/plugin/definition-error.d.ts +11 -0
  125. package/dist/plugin/definition-error.d.ts.map +1 -0
  126. package/dist/plugin/definition-error.js +17 -0
  127. package/dist/plugin/definition-error.js.map +1 -0
  128. package/dist/plugin/limits.d.ts +67 -0
  129. package/dist/plugin/limits.d.ts.map +1 -0
  130. package/dist/plugin/limits.js +70 -0
  131. package/dist/plugin/limits.js.map +1 -0
  132. package/dist/plugin/load-worker.d.ts +7 -0
  133. package/dist/plugin/load-worker.d.ts.map +1 -0
  134. package/dist/plugin/load-worker.js +41 -0
  135. package/dist/plugin/load-worker.js.map +1 -0
  136. package/dist/plugin/load.d.ts +18 -0
  137. package/dist/plugin/load.d.ts.map +1 -0
  138. package/dist/plugin/load.js +62 -0
  139. package/dist/plugin/load.js.map +1 -0
  140. package/dist/plugin/manifest.d.ts +237 -0
  141. package/dist/plugin/manifest.d.ts.map +1 -0
  142. package/dist/plugin/manifest.js +411 -0
  143. package/dist/plugin/manifest.js.map +1 -0
  144. package/dist/plugin/refusals.d.ts +49 -0
  145. package/dist/plugin/refusals.d.ts.map +1 -0
  146. package/dist/plugin/refusals.js +79 -0
  147. package/dist/plugin/refusals.js.map +1 -0
  148. package/dist/plugin/rest-schemas.d.ts +556 -0
  149. package/dist/plugin/rest-schemas.d.ts.map +1 -0
  150. package/dist/plugin/rest-schemas.js +262 -0
  151. package/dist/plugin/rest-schemas.js.map +1 -0
  152. package/dist/plugin/sandbox.d.ts +23 -0
  153. package/dist/plugin/sandbox.d.ts.map +1 -0
  154. package/dist/plugin/sandbox.js +99 -0
  155. package/dist/plugin/sandbox.js.map +1 -0
  156. package/dist/plugin/testing.d.ts +45 -0
  157. package/dist/plugin/testing.d.ts.map +1 -0
  158. package/dist/plugin/testing.js +420 -0
  159. package/dist/plugin/testing.js.map +1 -0
  160. package/dist/plugin/web-react.d.ts +11 -0
  161. package/dist/plugin/web-react.d.ts.map +1 -0
  162. package/dist/plugin/web-react.js +34 -0
  163. package/dist/plugin/web-react.js.map +1 -0
  164. package/dist/plugin/web.d.ts +95 -0
  165. package/dist/plugin/web.d.ts.map +1 -0
  166. package/dist/plugin/web.js +245 -0
  167. package/dist/plugin/web.js.map +1 -0
  168. package/dist/plugin-nav.d.ts +29 -0
  169. package/dist/plugin-nav.d.ts.map +1 -0
  170. package/dist/plugin-nav.js +92 -0
  171. package/dist/plugin-nav.js.map +1 -0
  172. package/dist/plugin-testing.d.ts +2 -0
  173. package/dist/plugin-testing.d.ts.map +1 -0
  174. package/dist/plugin-testing.js +4 -0
  175. package/dist/plugin-testing.js.map +1 -0
  176. package/dist/plugin-web-react.d.ts +3 -0
  177. package/dist/plugin-web-react.d.ts.map +1 -0
  178. package/dist/plugin-web-react.js +3 -0
  179. package/dist/plugin-web-react.js.map +1 -0
  180. package/dist/plugin-web.d.ts +4 -0
  181. package/dist/plugin-web.d.ts.map +1 -0
  182. package/dist/plugin-web.js +5 -0
  183. package/dist/plugin-web.js.map +1 -0
  184. package/dist/plugin.d.ts +11 -0
  185. package/dist/plugin.d.ts.map +1 -0
  186. package/dist/plugin.js +15 -0
  187. package/dist/plugin.js.map +1 -0
  188. package/dist/router.d.ts +28 -0
  189. package/dist/router.d.ts.map +1 -1
  190. package/dist/router.js +47 -0
  191. package/dist/router.js.map +1 -1
  192. package/dist/routes.d.ts +4 -0
  193. package/dist/routes.d.ts.map +1 -1
  194. package/dist/routes.js +6 -0
  195. package/dist/routes.js.map +1 -1
  196. package/dist/schemas.d.ts +194 -9
  197. package/dist/schemas.d.ts.map +1 -1
  198. package/dist/schemas.js +98 -17
  199. package/dist/schemas.js.map +1 -1
  200. package/dist/tokens.d.ts +2 -0
  201. package/dist/tokens.d.ts.map +1 -0
  202. package/dist/tokens.js +101 -0
  203. package/dist/tokens.js.map +1 -0
  204. package/dist/vocabulary.d.ts +11 -1
  205. package/dist/vocabulary.d.ts.map +1 -1
  206. package/dist/vocabulary.js +24 -0
  207. package/dist/vocabulary.js.map +1 -1
  208. package/dist/web.d.ts.map +1 -1
  209. package/dist/web.js +2 -0
  210. package/dist/web.js.map +1 -1
  211. package/docs/specs/cli.md +12 -5
  212. package/docs/specs/plugins.md +488 -0
  213. package/docs/specs/rest.md +11 -9
  214. package/package.json +26 -8
  215. package/skills/building-a-console/SKILL.md +25 -26
  216. package/skills/building-a-console/references/configuring.md +40 -0
  217. package/skills/building-a-console/references/the-default-console.md +1 -1
  218. package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
  219. package/skills/building-a-plugin/SKILL.md +137 -0
  220. package/skills/deploying-a-console/SKILL.md +24 -26
  221. package/skills/deploying-a-console/references/access.md +13 -9
  222. package/skills/deploying-a-console/references/resources.md +25 -10
  223. package/skills/deploying-a-console/references/running-it-again.md +15 -0
  224. package/skills/deploying-a-console/references/sizing.md +2 -0
  225. package/skills/planning-the-board/SKILL.md +96 -0
  226. package/skills/planning-the-board/references/a-task.md +36 -0
  227. package/skills/planning-the-board/references/setting-up.md +44 -0
  228. package/skills/working-the-board/SKILL.md +43 -34
  229. package/skills/working-the-board/references/the-pack.md +19 -0
  230. package/src/api.ts +12 -0
  231. package/src/cli/cli.ts +737 -55
  232. package/src/cli/format.ts +368 -10
  233. package/src/cli/help.ts +50 -6
  234. package/src/cli/plugin-check.ts +497 -0
  235. package/src/cli/plugin-cli.ts +237 -0
  236. package/src/cli/plugin-init.ts +257 -0
  237. package/src/cli/plugin-main.ts +30 -0
  238. package/src/client.ts +322 -9
  239. package/src/compat.ts +2 -2
  240. package/src/components/board.tsx +62 -14
  241. package/src/components/comments.tsx +1 -0
  242. package/src/components/decision-form.tsx +92 -11
  243. package/src/components/decision-list.tsx +71 -2
  244. package/src/components/event-feed.tsx +42 -1
  245. package/src/components/my-decisions.tsx +4 -1
  246. package/src/components/plugin-frame.tsx +437 -0
  247. package/src/components/plugin-settings.tsx +1111 -0
  248. package/src/components/shell.tsx +82 -14
  249. package/src/components/task-view.tsx +2 -1
  250. package/src/components/workspace.tsx +18 -0
  251. package/src/console.tsx +304 -31
  252. package/src/data.ts +59 -11
  253. package/src/i18n/en.ts +175 -0
  254. package/src/i18n/ko.ts +155 -0
  255. package/src/index.ts +12 -0
  256. package/src/limits.ts +2 -0
  257. package/src/plugin/args.ts +526 -0
  258. package/src/plugin/bridge.ts +314 -0
  259. package/src/plugin/capabilities.ts +150 -0
  260. package/src/plugin/context.ts +421 -0
  261. package/src/plugin/define.ts +792 -0
  262. package/src/plugin/definition-error.ts +19 -0
  263. package/src/plugin/limits.ts +70 -0
  264. package/src/plugin/load-worker.ts +50 -0
  265. package/src/plugin/load.ts +82 -0
  266. package/src/plugin/manifest.ts +561 -0
  267. package/src/plugin/refusals.ts +94 -0
  268. package/src/plugin/rest-schemas.ts +354 -0
  269. package/src/plugin/sandbox.ts +123 -0
  270. package/src/plugin/testing.ts +612 -0
  271. package/src/plugin/web-react.ts +45 -0
  272. package/src/plugin/web.ts +403 -0
  273. package/src/plugin-nav.ts +132 -0
  274. package/src/plugin-testing.ts +10 -0
  275. package/src/plugin-web-react.ts +4 -0
  276. package/src/plugin-web.ts +35 -0
  277. package/src/plugin.ts +126 -0
  278. package/src/router.ts +73 -0
  279. package/src/routes.ts +7 -0
  280. package/src/schemas.ts +115 -18
  281. package/src/styles/console.css +330 -1
  282. package/src/tokens.ts +101 -0
  283. package/src/vocabulary.ts +30 -0
  284. package/src/web.ts +2 -0
  285. package/template/plugin/.gitignore +2 -0
  286. package/template/plugin/README.md +26 -0
  287. package/template/plugin/SKILLCDN.md +13 -0
  288. package/template/plugin/build.mjs +40 -0
  289. package/template/plugin/console-plugin.json +50 -0
  290. package/template/plugin/package.json +27 -0
  291. package/template/plugin/skills/__key__/SKILL.md +23 -0
  292. package/template/plugin/src/check.test.ts +34 -0
  293. package/template/plugin/src/server/index.test.ts +41 -0
  294. package/template/plugin/src/server/index.ts +72 -0
  295. package/template/plugin/src/web/index.css +51 -0
  296. package/template/plugin/src/web/index.ts +115 -0
  297. package/template/plugin/tsconfig.json +17 -0
  298. package/skills/working-the-board/references/setting-up.md +0 -3
  299. /package/skills/{working-the-board → planning-the-board}/references/project-rules.md +0 -0
@@ -0,0 +1,314 @@
1
+ // The messages between the console's page and a plugin's frame (docs/specs/plugins.md, "The
2
+ // bridge"): every message `{ v: 1, t, ... }`, the page accepting the frame's window alone, the
3
+ // frame the console's origin alone. The shapes and their guards, with no DOM in them, so that the
4
+ // page's side and the frame's side read the same words and the tests need no browser.
5
+
6
+ export const PLUGIN_BRIDGE_VERSION = 1;
7
+
8
+ /** What the frame may ask the page to perform, each under `/api/v1/plugins/<key>/` and nothing else. */
9
+ export const BRIDGE_METHODS = [
10
+ "command",
11
+ "query",
12
+ "records.list",
13
+ "records.get",
14
+ "records.versions",
15
+ "files.get",
16
+ "files.put",
17
+ ] as const;
18
+
19
+ export type BridgeMethod = (typeof BRIDGE_METHODS)[number];
20
+
21
+ /** The kinds of `event` a frame may subscribe to: its plugin's records changed, a command ran, the plugin emitted. */
22
+ export const BRIDGE_EVENT_KINDS = ["records", "command", "plugin"] as const;
23
+
24
+ export type BridgeEventKind = (typeof BRIDGE_EVENT_KINDS)[number];
25
+
26
+ export interface BridgePerson {
27
+ readonly id: string;
28
+ readonly login: string;
29
+ readonly name: string | null;
30
+ readonly language: string;
31
+ readonly admin: boolean;
32
+ }
33
+
34
+ /** What the page tells the frame once it is ready: who looks, at what, in which words and colours. */
35
+ export interface BridgeInit {
36
+ readonly v: 1;
37
+ readonly t: "init";
38
+ readonly plugin: { readonly key: string; readonly version: string };
39
+ readonly page?: string;
40
+ readonly widget?: string;
41
+ readonly project: { readonly key: string; readonly name: string } | null;
42
+ readonly person: BridgePerson;
43
+ readonly roles: readonly string[];
44
+ /** What follows the page's key in the address. */
45
+ readonly path: string;
46
+ readonly language: string;
47
+ /** The console's computed design tokens, `--sc-*`. */
48
+ readonly theme: Readonly<Record<string, string>>;
49
+ }
50
+
51
+ export interface BridgeError {
52
+ readonly code: string;
53
+ readonly message: string;
54
+ readonly data?: unknown;
55
+ }
56
+
57
+ export interface BridgeResult {
58
+ readonly v: 1;
59
+ readonly t: "result";
60
+ readonly id: string;
61
+ readonly ok: boolean;
62
+ readonly value?: unknown;
63
+ readonly error?: BridgeError;
64
+ }
65
+
66
+ export interface BridgeEvent {
67
+ readonly v: 1;
68
+ readonly t: "event";
69
+ readonly kind: BridgeEventKind;
70
+ readonly data: unknown;
71
+ }
72
+
73
+ export interface BridgeTheme {
74
+ readonly v: 1;
75
+ readonly t: "theme";
76
+ readonly theme: Readonly<Record<string, string>>;
77
+ }
78
+
79
+ export interface BridgePath {
80
+ readonly v: 1;
81
+ readonly t: "path";
82
+ readonly path: string;
83
+ }
84
+
85
+ export type BridgeToFrame = BridgeInit | BridgeResult | BridgeEvent | BridgeTheme | BridgePath;
86
+
87
+ export interface BridgeReady {
88
+ readonly v: 1;
89
+ readonly t: "ready";
90
+ }
91
+
92
+ export interface BridgeCall {
93
+ readonly v: 1;
94
+ readonly t: "call";
95
+ readonly id: string;
96
+ readonly method: BridgeMethod;
97
+ readonly params: unknown;
98
+ }
99
+
100
+ export interface BridgeNavigate {
101
+ readonly v: 1;
102
+ readonly t: "navigate";
103
+ readonly path: string;
104
+ }
105
+
106
+ export interface BridgeResize {
107
+ readonly v: 1;
108
+ readonly t: "resize";
109
+ readonly height: number;
110
+ }
111
+
112
+ export interface BridgeTitle {
113
+ readonly v: 1;
114
+ readonly t: "title";
115
+ readonly text: string;
116
+ }
117
+
118
+ export interface BridgeDownload {
119
+ readonly v: 1;
120
+ readonly t: "download";
121
+ readonly id: string;
122
+ }
123
+
124
+ export interface BridgeSubscribe {
125
+ readonly v: 1;
126
+ readonly t: "subscribe";
127
+ readonly kinds: readonly BridgeEventKind[];
128
+ }
129
+
130
+ export type BridgeToPage =
131
+ | BridgeReady
132
+ | BridgeCall
133
+ | BridgeNavigate
134
+ | BridgeResize
135
+ | BridgeTitle
136
+ | BridgeDownload
137
+ | BridgeSubscribe;
138
+
139
+ /** A path of the console's, as `navigate` and `path` carry one: absolute, no scheme, no host, one line. */
140
+ export const MAX_BRIDGE_PATH_LENGTH = 2048;
141
+ export const MAX_BRIDGE_TITLE_LENGTH = 200;
142
+ const MAX_ID_LENGTH = 128;
143
+ const MAX_THEME_TOKENS = 500;
144
+
145
+ function isRecord(value: unknown): value is Record<string, unknown> {
146
+ return typeof value === "object" && value !== null && !Array.isArray(value);
147
+ }
148
+
149
+ function isLine(value: unknown, max: number): value is string {
150
+ return (
151
+ typeof value === "string" && value.length > 0 && value.length <= max && !/[\r\n]/.test(value)
152
+ );
153
+ }
154
+
155
+ /** True for a path the page may follow: within the console, never a URL. */
156
+ export function isBridgePath(value: unknown): value is string {
157
+ return (
158
+ typeof value === "string" &&
159
+ value.length <= MAX_BRIDGE_PATH_LENGTH &&
160
+ value.startsWith("/") &&
161
+ !value.startsWith("//") &&
162
+ !/[\s\\]/.test(value)
163
+ );
164
+ }
165
+
166
+ function isTheme(value: unknown): value is Record<string, string> {
167
+ if (!isRecord(value)) {
168
+ return false;
169
+ }
170
+ const entries = Object.entries(value);
171
+ return (
172
+ entries.length <= MAX_THEME_TOKENS &&
173
+ entries.every(
174
+ ([token, text]) =>
175
+ /^--sc-[a-z0-9-]+$/.test(token) && typeof text === "string" && text.length <= 200,
176
+ )
177
+ );
178
+ }
179
+
180
+ function isEnvelope(value: unknown): value is Record<string, unknown> & { t: string } {
181
+ return isRecord(value) && value.v === PLUGIN_BRIDGE_VERSION && typeof value.t === "string";
182
+ }
183
+
184
+ /** True for a message the frame may act on, as the console's page sends them. */
185
+ export function isBridgeToFrame(value: unknown): value is BridgeToFrame {
186
+ if (!isEnvelope(value)) {
187
+ return false;
188
+ }
189
+ switch (value.t) {
190
+ case "init":
191
+ return (
192
+ isRecord(value.plugin) &&
193
+ isLine(value.plugin.key, 32) &&
194
+ isLine(value.plugin.version, 64) &&
195
+ (value.page === undefined || isLine(value.page, 32)) &&
196
+ (value.widget === undefined || isLine(value.widget, 32)) &&
197
+ (value.project === null ||
198
+ (isRecord(value.project) &&
199
+ isLine(value.project.key, 64) &&
200
+ typeof value.project.name === "string")) &&
201
+ isRecord(value.person) &&
202
+ typeof value.person.id === "string" &&
203
+ typeof value.person.login === "string" &&
204
+ (value.person.name === null || typeof value.person.name === "string") &&
205
+ typeof value.person.language === "string" &&
206
+ typeof value.person.admin === "boolean" &&
207
+ Array.isArray(value.roles) &&
208
+ value.roles.every((role) => typeof role === "string") &&
209
+ typeof value.path === "string" &&
210
+ value.path.length <= MAX_BRIDGE_PATH_LENGTH &&
211
+ typeof value.language === "string" &&
212
+ isTheme(value.theme)
213
+ );
214
+ case "result":
215
+ return (
216
+ isLine(value.id, MAX_ID_LENGTH) &&
217
+ typeof value.ok === "boolean" &&
218
+ (value.error === undefined ||
219
+ (isRecord(value.error) &&
220
+ typeof value.error.code === "string" &&
221
+ typeof value.error.message === "string"))
222
+ );
223
+ case "event":
224
+ return (
225
+ typeof value.kind === "string" &&
226
+ (BRIDGE_EVENT_KINDS as readonly string[]).includes(value.kind)
227
+ );
228
+ case "theme":
229
+ return isTheme(value.theme);
230
+ case "path":
231
+ return typeof value.path === "string" && value.path.length <= MAX_BRIDGE_PATH_LENGTH;
232
+ default:
233
+ return false;
234
+ }
235
+ }
236
+
237
+ /** True for a message the page may act on, as a frame sends them; anything else is dropped unread. */
238
+ export function isBridgeToPage(value: unknown): value is BridgeToPage {
239
+ if (!isEnvelope(value)) {
240
+ return false;
241
+ }
242
+ switch (value.t) {
243
+ case "ready":
244
+ return true;
245
+ case "call":
246
+ return (
247
+ isLine(value.id, MAX_ID_LENGTH) &&
248
+ typeof value.method === "string" &&
249
+ (BRIDGE_METHODS as readonly string[]).includes(value.method) &&
250
+ isRecord(value.params)
251
+ );
252
+ case "navigate":
253
+ return isBridgePath(value.path);
254
+ case "resize":
255
+ return typeof value.height === "number" && Number.isFinite(value.height) && value.height >= 0;
256
+ case "title":
257
+ return (
258
+ typeof value.text === "string" &&
259
+ value.text.length <= MAX_BRIDGE_TITLE_LENGTH &&
260
+ !/[\r\n]/.test(value.text)
261
+ );
262
+ case "download":
263
+ return isLine(value.id, MAX_ID_LENGTH);
264
+ case "subscribe":
265
+ return (
266
+ Array.isArray(value.kinds) &&
267
+ value.kinds.length <= BRIDGE_EVENT_KINDS.length &&
268
+ value.kinds.every(
269
+ (kind) =>
270
+ typeof kind === "string" && (BRIDGE_EVENT_KINDS as readonly string[]).includes(kind),
271
+ )
272
+ );
273
+ default:
274
+ return false;
275
+ }
276
+ }
277
+
278
+ /** The result of a command as the frame receives it, the same shape the command line prints from. */
279
+ export interface BridgeCommandResult {
280
+ readonly kind: "command" | "query";
281
+ readonly text: string;
282
+ readonly data: unknown;
283
+ readonly waiting: string | null;
284
+ }
285
+
286
+ /** What `records.list` and `records.get` carry in `params`, as the page sends them on. */
287
+ export interface BridgeRecordsListParams {
288
+ readonly collection: string;
289
+ readonly where?: Readonly<Record<string, unknown>>;
290
+ readonly order?: string;
291
+ readonly limit?: number;
292
+ readonly after?: string;
293
+ readonly q?: string;
294
+ readonly project?: string;
295
+ }
296
+
297
+ export interface BridgeRecordsGetParams {
298
+ readonly collection: string;
299
+ readonly id: string;
300
+ readonly version?: number;
301
+ }
302
+
303
+ export interface BridgeFilesPutParams {
304
+ readonly bytes: ArrayBuffer;
305
+ readonly name: string;
306
+ readonly type: string;
307
+ }
308
+
309
+ /** What `files.get` answers: the bytes, transferred, with the file's name and type. */
310
+ export interface BridgeFile {
311
+ readonly name: string;
312
+ readonly type: string;
313
+ readonly bytes: ArrayBuffer;
314
+ }
@@ -0,0 +1,150 @@
1
+ // The catalogue of what a plugin may ask of a console (docs/specs/plugins.md, "Capabilities"):
2
+ // declared in the manifest, consented to by an administrator when the plugin is enabled, enforced
3
+ // by the parent on every call. Each carries the sentence an administrator reads before consenting,
4
+ // and the reach of a plugin that turns out malicious and holds it.
5
+
6
+ /** The capabilities that take no parameter. */
7
+ export const PLUGIN_CAPABILITIES = [
8
+ "files",
9
+ "people:read",
10
+ "people:read:all",
11
+ "projects:read",
12
+ "projects:read:all",
13
+ "board:read",
14
+ "board:tasks",
15
+ "board:decisions",
16
+ "board:documents",
17
+ "events:console",
18
+ "events:plugins",
19
+ "jobs",
20
+ "endpoints",
21
+ "storage:sql",
22
+ ] as const;
23
+
24
+ export type PluginCapability = (typeof PLUGIN_CAPABILITIES)[number];
25
+
26
+ /** The capabilities that carry a parameter: a host to reach, a plugin to call. */
27
+ export const PLUGIN_CAPABILITY_PREFIXES = ["outbound:", "plugins:"] as const;
28
+
29
+ /** A host as `outbound:<host>` names one: a name, or `*.` before a name for its subdomains. Never a port, never a path. */
30
+ const OUTBOUND_HOST = /^(?:\*\.)?(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/;
31
+
32
+ const PLUGIN_KEY = /^[a-z][a-z0-9-]{1,31}$/;
33
+
34
+ export interface CapabilityWords {
35
+ /** What it opens, as the administrator reads it before consenting. */
36
+ readonly opens: string;
37
+ /** The reach of a plugin that turns out malicious and holds it. */
38
+ readonly reach: string;
39
+ }
40
+
41
+ /** What each capability opens and what it risks, in the words of the specification. */
42
+ export const CAPABILITY_WORDS: Readonly<Record<PluginCapability, CapabilityWords>> = {
43
+ files: {
44
+ opens: "files on its records, within the bytes the deployment allows a plugin",
45
+ reach: "storage filled to the bound",
46
+ },
47
+ "people:read": {
48
+ opens: "the people the caller may see, as the person",
49
+ reach: "the names and logins its role holders may see anyway",
50
+ },
51
+ "people:read:all": {
52
+ opens: "everyone in the workspace, for its jobs and hooks too",
53
+ reach: "the directory",
54
+ },
55
+ "projects:read": {
56
+ opens: "the projects and members the caller may see",
57
+ reach: "what the person sees",
58
+ },
59
+ "projects:read:all": {
60
+ opens: "every project and its members",
61
+ reach: "the projects and who is in them",
62
+ },
63
+ "board:read": {
64
+ opens:
65
+ "the tasks, decisions, runs, documents and events of its board projects, and of the project a call is on",
66
+ reach: "the content of those boards, to its role holders",
67
+ },
68
+ "board:tasks": {
69
+ opens: "tasks created in its projects; its own changed, closed and commented",
70
+ reach: "tasks that mislead; each marked as the plugin's, put away by anyone",
71
+ },
72
+ "board:decisions": {
73
+ opens: "decisions raised in its projects; the answers to its own read",
74
+ reach: "questions that mislead; never an answer",
75
+ },
76
+ "board:documents": {
77
+ opens: "pages written under plugins/<key>/ in its projects",
78
+ reach: "pages that mislead, in its folder",
79
+ },
80
+ "events:console": {
81
+ opens: "hooks on the console's events of its board projects",
82
+ reach: "when things happen on those boards",
83
+ },
84
+ "events:plugins": {
85
+ opens: "hooks on the events other plugins emit",
86
+ reach: "what they publish",
87
+ },
88
+ jobs: {
89
+ opens: "runs on a schedule",
90
+ reach: "time, within the bound",
91
+ },
92
+ endpoints: {
93
+ opens: "inbound requests under its path, a person's or a webhook's",
94
+ reach: "input it accepts without verifying; the rate limit holds",
95
+ },
96
+ "storage:sql": {
97
+ opens: "a schema and a login role of its own in the console's database",
98
+ reach: "statements up to the timeout, within its schema",
99
+ },
100
+ };
101
+
102
+ /** True for a capability the catalogue knows, parameter included. */
103
+ export function isPluginCapability(value: string): boolean {
104
+ if ((PLUGIN_CAPABILITIES as readonly string[]).includes(value)) {
105
+ return true;
106
+ }
107
+ if (value.startsWith("outbound:")) {
108
+ return OUTBOUND_HOST.test(value.slice("outbound:".length));
109
+ }
110
+ if (value.startsWith("plugins:")) {
111
+ return PLUGIN_KEY.test(value.slice("plugins:".length));
112
+ }
113
+ return false;
114
+ }
115
+
116
+ /** The sentences for a capability, parameter included; nothing for one the catalogue does not know. */
117
+ export function capabilityWords(capability: string): CapabilityWords | undefined {
118
+ if ((PLUGIN_CAPABILITIES as readonly string[]).includes(capability)) {
119
+ return CAPABILITY_WORDS[capability as PluginCapability];
120
+ }
121
+ if (capability.startsWith("outbound:") && isPluginCapability(capability)) {
122
+ const host = capability.slice("outbound:".length);
123
+ return {
124
+ opens: `requests to ${host}, a public address, over https`,
125
+ reach: `what it may read, sent to ${host}; a cost there`,
126
+ };
127
+ }
128
+ if (capability.startsWith("plugins:") && isPluginCapability(capability)) {
129
+ const key = capability.slice("plugins:".length);
130
+ return {
131
+ opens: `the API of the plugin ${key}, as the caller`,
132
+ reach: "what that API gives the caller",
133
+ };
134
+ }
135
+ return undefined;
136
+ }
137
+
138
+ /** True when the request's host is within what `outbound:<host>` names: the host itself, or a subdomain where the capability starts with `*.`. */
139
+ export function outboundAllows(capability: string, host: string): boolean {
140
+ if (!capability.startsWith("outbound:") || !isPluginCapability(capability)) {
141
+ return false;
142
+ }
143
+ const named = capability.slice("outbound:".length);
144
+ const lower = host.toLowerCase();
145
+ if (named.startsWith("*.")) {
146
+ const domain = named.slice(2);
147
+ return lower.endsWith(`.${domain}`) && lower.length > domain.length + 1;
148
+ }
149
+ return lower === named;
150
+ }