@skillcdn/console 0.4.4 → 0.4.5

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 (192) hide show
  1. package/README.md +3 -3
  2. package/dist/api.d.ts +3 -3
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +3 -3
  5. package/dist/api.js.map +1 -1
  6. package/dist/cli/cli.d.ts.map +1 -1
  7. package/dist/cli/cli.js +162 -15
  8. package/dist/cli/cli.js.map +1 -1
  9. package/dist/cli/format.d.ts +16 -3
  10. package/dist/cli/format.d.ts.map +1 -1
  11. package/dist/cli/format.js +68 -19
  12. package/dist/cli/format.js.map +1 -1
  13. package/dist/cli/help.d.ts.map +1 -1
  14. package/dist/cli/help.js +26 -5
  15. package/dist/cli/help.js.map +1 -1
  16. package/dist/client.d.ts +28 -1
  17. package/dist/client.d.ts.map +1 -1
  18. package/dist/client.js +12 -2
  19. package/dist/client.js.map +1 -1
  20. package/dist/compat.js +1 -1
  21. package/dist/components/board.d.ts.map +1 -1
  22. package/dist/components/board.js +46 -15
  23. package/dist/components/board.js.map +1 -1
  24. package/dist/components/decision-form.d.ts +0 -3
  25. package/dist/components/decision-form.d.ts.map +1 -1
  26. package/dist/components/decision-form.js +3 -15
  27. package/dist/components/decision-form.js.map +1 -1
  28. package/dist/components/event-feed.d.ts.map +1 -1
  29. package/dist/components/event-feed.js +30 -10
  30. package/dist/components/event-feed.js.map +1 -1
  31. package/dist/components/my-decisions.d.ts +20 -3
  32. package/dist/components/my-decisions.d.ts.map +1 -1
  33. package/dist/components/my-decisions.js +14 -1
  34. package/dist/components/my-decisions.js.map +1 -1
  35. package/dist/components/people.d.ts +43 -1
  36. package/dist/components/people.d.ts.map +1 -1
  37. package/dist/components/people.js +117 -9
  38. package/dist/components/people.js.map +1 -1
  39. package/dist/components/picker.d.ts +8 -0
  40. package/dist/components/picker.d.ts.map +1 -1
  41. package/dist/components/picker.js +2 -2
  42. package/dist/components/picker.js.map +1 -1
  43. package/dist/components/plugin-frame.d.ts +0 -1
  44. package/dist/components/plugin-frame.d.ts.map +1 -1
  45. package/dist/components/plugin-frame.js +22 -1
  46. package/dist/components/plugin-frame.js.map +1 -1
  47. package/dist/components/plugin-settings.d.ts +5 -1
  48. package/dist/components/plugin-settings.d.ts.map +1 -1
  49. package/dist/components/plugin-settings.js +143 -30
  50. package/dist/components/plugin-settings.js.map +1 -1
  51. package/dist/components/projects.d.ts.map +1 -1
  52. package/dist/components/projects.js +3 -1
  53. package/dist/components/projects.js.map +1 -1
  54. package/dist/components/searches.d.ts +11 -0
  55. package/dist/components/searches.d.ts.map +1 -0
  56. package/dist/components/searches.js +30 -0
  57. package/dist/components/searches.js.map +1 -0
  58. package/dist/components/task-form.d.ts.map +1 -1
  59. package/dist/components/task-form.js +6 -5
  60. package/dist/components/task-form.js.map +1 -1
  61. package/dist/components/ui.d.ts +16 -0
  62. package/dist/components/ui.d.ts.map +1 -1
  63. package/dist/components/ui.js +10 -0
  64. package/dist/components/ui.js.map +1 -1
  65. package/dist/console.css +362 -67
  66. package/dist/console.d.ts +2 -1
  67. package/dist/console.d.ts.map +1 -1
  68. package/dist/console.js +90 -14
  69. package/dist/console.js.map +1 -1
  70. package/dist/data.d.ts +14 -1
  71. package/dist/data.d.ts.map +1 -1
  72. package/dist/data.js +36 -1
  73. package/dist/data.js.map +1 -1
  74. package/dist/i18n/en.d.ts +89 -0
  75. package/dist/i18n/en.d.ts.map +1 -1
  76. package/dist/i18n/en.js +87 -2
  77. package/dist/i18n/en.js.map +1 -1
  78. package/dist/i18n/ko.d.ts.map +1 -1
  79. package/dist/i18n/ko.js +87 -3
  80. package/dist/i18n/ko.js.map +1 -1
  81. package/dist/index.d.ts +3 -2
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +3 -2
  84. package/dist/index.js.map +1 -1
  85. package/dist/limits.d.ts +6 -0
  86. package/dist/limits.d.ts.map +1 -1
  87. package/dist/limits.js +6 -0
  88. package/dist/limits.js.map +1 -1
  89. package/dist/plugin/args.d.ts +6 -1
  90. package/dist/plugin/args.d.ts.map +1 -1
  91. package/dist/plugin/args.js +16 -11
  92. package/dist/plugin/args.js.map +1 -1
  93. package/dist/plugin/bridge.d.ts +20 -1
  94. package/dist/plugin/bridge.d.ts.map +1 -1
  95. package/dist/plugin/bridge.js +28 -0
  96. package/dist/plugin/bridge.js.map +1 -1
  97. package/dist/plugin/capabilities.d.ts +1 -1
  98. package/dist/plugin/capabilities.d.ts.map +1 -1
  99. package/dist/plugin/capabilities.js +10 -0
  100. package/dist/plugin/capabilities.js.map +1 -1
  101. package/dist/plugin/context.d.ts +158 -13
  102. package/dist/plugin/context.d.ts.map +1 -1
  103. package/dist/plugin/define.d.ts +13 -1
  104. package/dist/plugin/define.d.ts.map +1 -1
  105. package/dist/plugin/define.js +17 -1
  106. package/dist/plugin/define.js.map +1 -1
  107. package/dist/plugin/limits.d.ts +4 -0
  108. package/dist/plugin/limits.d.ts.map +1 -1
  109. package/dist/plugin/limits.js +4 -0
  110. package/dist/plugin/limits.js.map +1 -1
  111. package/dist/plugin/manifest.d.ts +15 -0
  112. package/dist/plugin/manifest.d.ts.map +1 -1
  113. package/dist/plugin/manifest.js +61 -4
  114. package/dist/plugin/manifest.js.map +1 -1
  115. package/dist/plugin/rest-schemas.d.ts +44 -0
  116. package/dist/plugin/rest-schemas.d.ts.map +1 -1
  117. package/dist/plugin/rest-schemas.js +55 -3
  118. package/dist/plugin/rest-schemas.js.map +1 -1
  119. package/dist/plugin/testing.d.ts +15 -1
  120. package/dist/plugin/testing.d.ts.map +1 -1
  121. package/dist/plugin/testing.js +40 -4
  122. package/dist/plugin/testing.js.map +1 -1
  123. package/dist/plugin/web.d.ts +6 -0
  124. package/dist/plugin/web.d.ts.map +1 -1
  125. package/dist/plugin/web.js +10 -1
  126. package/dist/plugin/web.js.map +1 -1
  127. package/dist/plugin.d.ts +1 -1
  128. package/dist/plugin.d.ts.map +1 -1
  129. package/dist/plugin.js +1 -1
  130. package/dist/plugin.js.map +1 -1
  131. package/dist/router.d.ts +19 -0
  132. package/dist/router.d.ts.map +1 -1
  133. package/dist/router.js +18 -0
  134. package/dist/router.js.map +1 -1
  135. package/dist/routes.d.ts +11 -2
  136. package/dist/routes.d.ts.map +1 -1
  137. package/dist/routes.js +14 -1
  138. package/dist/routes.js.map +1 -1
  139. package/dist/schemas.d.ts +367 -0
  140. package/dist/schemas.d.ts.map +1 -1
  141. package/dist/schemas.js +62 -2
  142. package/dist/schemas.js.map +1 -1
  143. package/dist/vocabulary.d.ts +1 -1
  144. package/dist/vocabulary.d.ts.map +1 -1
  145. package/dist/vocabulary.js +6 -0
  146. package/dist/vocabulary.js.map +1 -1
  147. package/docs/specs/cli.md +7 -4
  148. package/docs/specs/plugins.md +47 -39
  149. package/docs/specs/rest.md +22 -5
  150. package/package.json +1 -1
  151. package/skills/building-a-plugin/SKILL.md +8 -6
  152. package/skills/working-the-board/SKILL.md +1 -1
  153. package/src/api.ts +17 -0
  154. package/src/cli/cli.ts +200 -12
  155. package/src/cli/format.ts +82 -19
  156. package/src/cli/help.ts +30 -5
  157. package/src/client.ts +70 -0
  158. package/src/compat.ts +1 -1
  159. package/src/components/board.tsx +83 -42
  160. package/src/components/decision-form.tsx +13 -32
  161. package/src/components/event-feed.tsx +36 -9
  162. package/src/components/my-decisions.tsx +50 -5
  163. package/src/components/people.tsx +394 -37
  164. package/src/components/picker.tsx +11 -2
  165. package/src/components/plugin-frame.tsx +23 -0
  166. package/src/components/plugin-settings.tsx +346 -137
  167. package/src/components/projects.tsx +14 -14
  168. package/src/components/searches.ts +47 -0
  169. package/src/components/task-form.tsx +23 -28
  170. package/src/components/ui.tsx +36 -0
  171. package/src/console.tsx +229 -40
  172. package/src/data.ts +53 -1
  173. package/src/i18n/en.ts +98 -2
  174. package/src/i18n/ko.ts +95 -3
  175. package/src/index.ts +11 -0
  176. package/src/limits.ts +6 -0
  177. package/src/plugin/args.ts +16 -11
  178. package/src/plugin/bridge.ts +45 -0
  179. package/src/plugin/capabilities.ts +11 -0
  180. package/src/plugin/context.ts +170 -10
  181. package/src/plugin/define.ts +34 -0
  182. package/src/plugin/limits.ts +4 -0
  183. package/src/plugin/manifest.ts +74 -3
  184. package/src/plugin/rest-schemas.ts +62 -5
  185. package/src/plugin/testing.ts +61 -5
  186. package/src/plugin/web.ts +17 -0
  187. package/src/plugin.ts +1 -0
  188. package/src/router.ts +25 -0
  189. package/src/routes.ts +17 -2
  190. package/src/schemas.ts +81 -2
  191. package/src/styles/console.css +362 -67
  192. package/src/vocabulary.ts +6 -0
package/src/data.ts CHANGED
@@ -44,6 +44,7 @@ import {
44
44
  type RestProjectInput,
45
45
  type RestProjectPatch,
46
46
  type RestProjectRules,
47
+ type RestReachedDecision,
47
48
  type RestRules,
48
49
  type RestRulesInput,
49
50
  type RestRulesPlugin,
@@ -53,6 +54,9 @@ import {
53
54
  type RestTaskInput,
54
55
  type RestTaskPatch,
55
56
  type RestTasks,
57
+ type RestTeam,
58
+ type RestTeamInput,
59
+ type RestTeamPatch,
56
60
  type RestToken,
57
61
  type RestTokenCreated,
58
62
  type RestTokenInput,
@@ -148,6 +152,10 @@ export interface ConsoleActions {
148
152
  },
149
153
  signal?: AbortSignal,
150
154
  ): Promise<RestMyDecisions>;
155
+ /** One decision wherever it is (ADR-0068), with its project and whether the person may answer it, read when the page asks. */
156
+ reachDecision(id: string, signal?: AbortSignal): Promise<RestReachedDecision>;
157
+ /** Answers a decision wherever it is, as whoever it reaches (ADR-0068). */
158
+ answerReachedDecision(id: string, input: RestAnswerInput): Promise<RestDecision>;
151
159
  /** The categories tasks are sorted into: kept by an administrator. */
152
160
  createCategory(input: RestCategoryInput): Promise<RestCategory>;
153
161
  updateCategory(key: string, patch: RestCategoryPatch): Promise<RestCategory>;
@@ -173,6 +181,13 @@ export interface ConsoleActions {
173
181
  createListing(input: RestListingInput): Promise<RestListing>;
174
182
  /** Takes a listing away that nobody signed in with; for an administrator. */
175
183
  removeListing(id: string): Promise<void>;
184
+ /** The teams (ADR-0073): made, renamed and removed, a person put in or taken out; for an administrator. */
185
+ createTeam(input: RestTeamInput): Promise<RestTeam>;
186
+ /** A team is called by its key (ADR-0074), which never changes. */
187
+ renameTeam(key: string, patch: RestTeamPatch): Promise<RestTeam>;
188
+ removeTeam(key: string): Promise<void>;
189
+ addTeamMember(key: string, personId: string): Promise<RestTeam>;
190
+ removeTeamMember(key: string, personId: string): Promise<RestTeam>;
176
191
  /** Keeps the person's language (ADR-0065) or their front page. */
177
192
  updateMe(patch: RestMePatch): Promise<RestMe>;
178
193
  /** Changes the workspace's settings (ADR-0035); for an administrator. `me` says them from then on. */
@@ -226,6 +241,8 @@ export interface ConsoleData {
226
241
  readonly people: readonly RestPerson[];
227
242
  /** The logins let in that nobody has signed in with yet. */
228
243
  readonly listed: readonly RestListing[];
244
+ /** The teams of the workspace (ADR-0073), by name, each with the members the person may see. */
245
+ readonly teams: readonly RestTeam[];
229
246
  /** The organization at the git host that says who is in, where the deployment names one. */
230
247
  readonly organization: RestOrganization | null;
231
248
  /** The categories tasks are sorted into, by name. */
@@ -364,6 +381,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
364
381
  const [projects, setProjects] = useState<readonly RestProject[]>([]);
365
382
  const [people, setPeople] = useState<readonly RestPerson[]>([]);
366
383
  const [listed, setListed] = useState<readonly RestListing[]>([]);
384
+ const [teams, setTeams] = useState<readonly RestTeam[]>([]);
367
385
  const [organization, setOrganization] = useState<RestOrganization | null>(null);
368
386
  const [categories, setCategories] = useState<readonly RestCategory[]>([]);
369
387
  const [plugins, setPlugins] = useState<readonly RestPlugin[]>([]);
@@ -403,12 +421,14 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
403
421
  /** Loads the workspace's lists again. What fails stays as it was. */
404
422
  const refreshWorkspace = useCallback(
405
423
  async (signal?: AbortSignal) => {
406
- const [nextProjects, nextPeople, nextCategories, nextPlugins] = await Promise.all([
424
+ const [nextProjects, nextPeople, nextCategories, nextPlugins, nextTeams] = await Promise.all([
407
425
  client.projects(signal),
408
426
  client.people(signal),
409
427
  client.categories(signal),
410
428
  // A console without plugins answers none; one older than them answers nothing, which is none too.
411
429
  client.plugins(signal).catch(() => ({ items: [], sql: false })),
430
+ // A console older than teams (ADR-0073) answers nothing, which is no team.
431
+ client.teams(signal).catch(() => ({ items: [] })),
412
432
  ]);
413
433
  if (signal?.aborted === true) {
414
434
  return;
@@ -416,6 +436,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
416
436
  setProjects(nextProjects.items);
417
437
  setPeople(nextPeople.items);
418
438
  setListed(nextPeople.listed);
439
+ setTeams(nextTeams.items);
419
440
  setOrganization(nextPeople.organization);
420
441
  setCategories(nextCategories.items);
421
442
  setPlugins(nextPlugins.items);
@@ -744,6 +765,12 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
744
765
  },
745
766
  work: (signal) => client.work(signal),
746
767
  myDecisions: (filter, signal) => client.myDecisions(filter, signal),
768
+ reachDecision: (id, signal) => client.decision(id, signal),
769
+ async answerReachedDecision(id, input) {
770
+ const decision = await client.answerDecision(id, input);
771
+ await refreshMe().catch(() => undefined);
772
+ return decision;
773
+ },
747
774
  async createCategory(input) {
748
775
  const category = await client.createCategory(input);
749
776
  setCategories((await client.categories()).items);
@@ -781,6 +808,30 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
781
808
  await client.removeListing(id);
782
809
  await refreshWorkspace();
783
810
  },
811
+ async createTeam(input) {
812
+ const team = await client.createTeam(input);
813
+ await refreshWorkspace();
814
+ return team;
815
+ },
816
+ async renameTeam(key, patch) {
817
+ const team = await client.renameTeam(key, patch);
818
+ await refreshWorkspace();
819
+ return team;
820
+ },
821
+ async removeTeam(key) {
822
+ await client.removeTeam(key);
823
+ await refreshWorkspace();
824
+ },
825
+ async addTeamMember(key, personId) {
826
+ const team = await client.addTeamMember(key, personId);
827
+ await refreshWorkspace();
828
+ return team;
829
+ },
830
+ async removeTeamMember(key, personId) {
831
+ const team = await client.removeTeamMember(key, personId);
832
+ await refreshWorkspace();
833
+ return team;
834
+ },
784
835
  async updateMe(patch) {
785
836
  const answer = await client.updateMe(patch);
786
837
  setMe(answer);
@@ -873,6 +924,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
873
924
  projects,
874
925
  people,
875
926
  listed,
927
+ teams,
876
928
  organization,
877
929
  categories,
878
930
  plugins,
package/src/i18n/en.ts CHANGED
@@ -207,6 +207,14 @@ export const en = {
207
207
  "member.open_project": "The project is open to the workspace, so everyone is already a member.",
208
208
  "mount.ref_not_found": "That version of the pack was not found.",
209
209
  "mount.repo_not_found": "The pack was not found.",
210
+ "person.email_taken": "That address is another person's in this workspace.",
211
+ "team.not_found": "The team was not found.",
212
+ "team.name_taken": "A team of that name is there already.",
213
+ "team.key_taken": "A team or a project of that key is there already.",
214
+ "team.invalid_name": "A team's name is one line of words.",
215
+ "team.too_many": "The workspace holds as many teams as one may.",
216
+ "team.invalid_member": "Only an active person of the workspace is put in a team.",
217
+ "team.not_a_member": "The person is not in the team.",
210
218
  "person.invalid_manager":
211
219
  "A manager must be another active person, and not one of this person's own team.",
212
220
  "person.last_admin": "The workspace needs at least one administrator.",
@@ -229,7 +237,7 @@ export const en = {
229
237
  "plugin.unavailable": "The plugin can't be reached right now.",
230
238
  "project.invalid_address": "That is not the address of a pack.",
231
239
  "project.invalid_icon": "That file can't be used as the project's image.",
232
- "project.key_taken": "A project with this key already exists.",
240
+ "project.key_taken": "A project or a team with this key already exists.",
233
241
  "project.no_icon": "The project has no image.",
234
242
  "project.not_found": "The project was not found.",
235
243
  "project.pack_connect_refused": "SkillCDN refused the connection.",
@@ -324,6 +332,10 @@ export const en = {
324
332
  projectsSome: "Some projects",
325
333
  settingsForm: "Settings",
326
334
  secretSet: (when: string) => `Set ${when}`,
335
+ secretSealedFrom: (host: string, when: string) => `Received from ${host} ${when}`,
336
+ secretExpires: (when: string) => `expires ${when}`,
337
+ secretExpired: (when: string) => `expired ${when}`,
338
+ secretHosts: (hosts: string) => `Sent to ${hosts} only.`,
327
339
  secretUnset: "Not set",
328
340
  secretNew: "New value",
329
341
  enable: "Enable",
@@ -332,11 +344,24 @@ export const en = {
332
344
  rolesNone: "No roles: every member may use it.",
333
345
  roleDefault: "Default",
334
346
  everyMember: "Every member",
347
+ roleManagers: "Reporting line",
348
+ whoeverManages: "Whoever someone reports to",
335
349
  grantEveryone: "Everyone",
336
350
  grantMembersOf: (project: string) => `Members of ${project}`,
337
351
  grantPeople: "People",
338
352
  grantProjects: "Members of a project",
339
353
  grantWhom: "Grant to…",
354
+ grantPlugins: "Plugins",
355
+ grantTeams: "Teams",
356
+ grantTeam: (team: string) => `The team ${team}`,
357
+ grantPlugin: (plugin: string) => `The plugin ${plugin}`,
358
+ asks: "Roles asked of other plugins",
359
+ asksHint:
360
+ "Roles of other plugins this plugin asks to hold, to act as itself in them; each is an administrator's to grant, here or when enabling.",
361
+ askGranted: "Granted",
362
+ askUngranted: "Not granted",
363
+ askUnavailable: (plugin: string) => `${plugin} is not enabled; grant it here once it is.`,
364
+ askGrantNow: "Grant it now",
340
365
  grant: "Grant",
341
366
  revoke: "Revoke",
342
367
  events: "What happened",
@@ -356,9 +381,16 @@ export const en = {
356
381
  console: "Console versions",
357
382
  consoleRange: (range: string) => `console ${range}`,
358
383
  timeZoneHint: "In the workspace's time zone.",
384
+ keeps: "Kept for",
385
+ keepsFor: (collection: string, days: number) =>
386
+ `${collection}: ${days} ${days === 1 ? "day" : "days"}, then removed`,
387
+ needsTimeZone:
388
+ "The workspace has no time zone, so this plugin's times of day run on UTC until one is set.",
389
+ setTimeZone: "Set the time zone",
359
390
  addItem: "Add",
360
391
  itemPlaceholder: "Type, then add",
361
392
  choose: "Choose…",
393
+ addOne: "Add…",
362
394
  },
363
395
  /** What stands in for a frame the console has nothing to show in. */
364
396
  frameUnavailable: "This can't be shown right now.",
@@ -373,6 +405,16 @@ export const en = {
373
405
  name: "Everyone",
374
406
  opens: "Reads everyone in the workspace, from its jobs and hooks too.",
375
407
  },
408
+ "people:read:email": {
409
+ name: "Work email",
410
+ opens:
411
+ "Reads the work email of the people it may read, to match them to accounts elsewhere.",
412
+ },
413
+ "people:act": {
414
+ name: "Acting for a person",
415
+ opens:
416
+ "Runs a command and answers a decision for a person who did it elsewhere, within what they may do, recorded as theirs through the plugin.",
417
+ },
376
418
  "projects:read": {
377
419
  name: "Projects",
378
420
  opens: "Reads the projects and members the person may see.",
@@ -941,8 +983,13 @@ export const en = {
941
983
  console: "The console",
942
984
  as: (agent: string) => `as ${agent}`,
943
985
  asTitle: "The agent the person acted through",
986
+ via: (plugin: string) => `through ${plugin}`,
987
+ viaTitle: "The plugin that acted for the person",
988
+ thePlugin: (plugin: string) => `the plugin ${plugin}`,
944
989
  aTask: "a task",
945
990
  aPlugin: "a plugin",
991
+ aTeam: "a team",
992
+ theTeam: (team: string) => `the team ${team}`,
946
993
  /** After the sentence of a board's event a plugin caused (ADR-0052). */
947
994
  throughPlugin: (plugin: string) => `through the plugin ${plugin}`,
948
995
  everyone: "everyone",
@@ -1000,6 +1047,13 @@ export const en = {
1000
1047
  personReactivated: (login: string) => `reactivated ${login}`,
1001
1048
  managerChanged: (login: string, manager: string) => `set ${login}'s manager to ${manager}`,
1002
1049
  managerCleared: (login: string) => `cleared ${login}'s manager`,
1050
+ emailChanged: (login: string) => `changed ${login}'s work email`,
1051
+ emailCleared: (login: string) => `cleared ${login}'s work email`,
1052
+ teamCreated: (team: string) => `made the team ${team}`,
1053
+ teamRenamed: (from: string, team: string) => `renamed the team ${from} to ${team}`,
1054
+ teamRemoved: (team: string) => `deleted the team ${team}`,
1055
+ teamMemberAdded: (login: string, team: string) => `put ${login} in the team ${team}`,
1056
+ teamMemberRemoved: (login: string, team: string) => `took ${login} out of the team ${team}`,
1003
1057
  projectCreated: (project: string) => `made the project ${project}`,
1004
1058
  projectUpdated: (fields: string, project: string) =>
1005
1059
  `changed ${fields} of the project ${project}`,
@@ -1045,6 +1099,8 @@ export const en = {
1045
1099
  pluginConsented: (plugin: string) => `changed the capabilities of the plugin ${plugin}`,
1046
1100
  pluginConfigured: (fields: string, plugin: string) =>
1047
1101
  `changed ${fields} of the plugin ${plugin}`,
1102
+ pluginSealed: (fields: string, plugin: string, host: string) =>
1103
+ `stored ${fields} of the plugin ${plugin}, received from ${host}`,
1048
1104
  pluginGranted: (role: string, who: string, plugin: string) =>
1049
1105
  `gave ${who} the role ${role} in the plugin ${plugin}`,
1050
1106
  pluginRevoked: (role: string, who: string, plugin: string) =>
@@ -1238,7 +1294,17 @@ export const en = {
1238
1294
  role: "Role",
1239
1295
  manager: "Manager",
1240
1296
  managerOf: (name: string) => `Manager of ${name}`,
1241
- team: "Team",
1297
+ email: "Work email",
1298
+ emailOf: (name: string) => `Work email of ${name}`,
1299
+ emailHint:
1300
+ "Google's at sign-in; written here otherwise. Read by an administrator, the person, and a plugin allowed to.",
1301
+ team: "Direct reports",
1302
+ teams: "Teams",
1303
+ joinTeam: "Add to a team…",
1304
+ joinTeamOf: (name: string) => `Add ${name} to a team`,
1305
+ leaveTeam: (name: string, team: string) => `Take ${name} out of ${team}`,
1306
+ howMany: (count: number) => (count === 1 ? "1 person" : `${count} people`),
1307
+ views: { aria: "People and teams", people: "People", teams: "Teams" },
1242
1308
  nobody: "None",
1243
1309
  letIn: "Add a person",
1244
1310
  letInAria: "Add a person",
@@ -1289,6 +1355,36 @@ export const en = {
1289
1355
  },
1290
1356
  },
1291
1357
 
1358
+ /** The teams of the workspace (ADR-0073), a view of the people page of their own. */
1359
+ teams: {
1360
+ list: "Teams",
1361
+ none: {
1362
+ title: "No teams yet",
1363
+ body: "A team puts people under one name: a plugin finds them by it, and a role of a plugin granted to the team is held by everyone in it.",
1364
+ },
1365
+ members: (count: number) => (count === 1 ? "1 member" : `${count} members`),
1366
+ nobody: "Nobody in it yet.",
1367
+ nameOf: (team: string) => `Name of the team ${team}`,
1368
+ keyTitle:
1369
+ "The team's key: what a grant, a plugin and the command call it by. It never changes.",
1370
+ addMember: "Add a person…",
1371
+ addMemberTo: (team: string) => `Add a person to ${team}`,
1372
+ removeMember: (name: string, team: string) => `Take ${name} out of ${team}`,
1373
+ remove: "Delete team",
1374
+ removeAsk: (team: string) =>
1375
+ `Delete the team ${team}? The roles of plugins granted to it go with it; its people stay.`,
1376
+ newTeam: "New team",
1377
+ newAria: "New team",
1378
+ form: {
1379
+ name: "Team name",
1380
+ key: "Key",
1381
+ keyHint:
1382
+ "Lowercase letters, digits and hyphens: what a grant, a plugin and the command call the team by. No other team and no project has it, and it never changes.",
1383
+ hint: "The name is what people read, and can change; the key stays. People are added on the team's card once it is made.",
1384
+ submit: "Make the team",
1385
+ },
1386
+ },
1387
+
1292
1388
  agents: {
1293
1389
  title: "Agents",
1294
1390
  makeByHand: "Make a token by hand",
package/src/i18n/ko.ts CHANGED
@@ -163,6 +163,14 @@ export const ko: Messages = {
163
163
  "member.open_project": "공개 프로젝트라서 워크스페이스의 모든 사람이 이미 멤버입니다.",
164
164
  "mount.ref_not_found": "팩의 그 버전을 찾을 수 없습니다.",
165
165
  "mount.repo_not_found": "팩을 찾을 수 없습니다.",
166
+ "person.email_taken": "이 워크스페이스의 다른 사람이 쓰는 주소입니다.",
167
+ "team.not_found": "팀을 찾을 수 없습니다.",
168
+ "team.name_taken": "같은 이름의 팀이 이미 있습니다.",
169
+ "team.key_taken": "같은 키의 팀이나 프로젝트가 이미 있습니다.",
170
+ "team.invalid_name": "팀 이름은 한 줄의 글자여야 합니다.",
171
+ "team.too_many": "워크스페이스가 가질 수 있는 만큼의 팀이 이미 있습니다.",
172
+ "team.invalid_member": "활성 상태인 워크스페이스의 사람만 팀에 넣을 수 있습니다.",
173
+ "team.not_a_member": "그 사람은 이 팀에 없습니다.",
166
174
  "person.invalid_manager": "본인, 본인의 팀원, 비활성화된 사람은 매니저로 지정할 수 없습니다.",
167
175
  "person.last_admin": "관리자가 한 명은 있어야 합니다.",
168
176
  "person.not_found": "사람을 찾을 수 없습니다.",
@@ -185,7 +193,7 @@ export const ko: Messages = {
185
193
  "plugin.unavailable": "지금은 플러그인을 쓸 수 없습니다.",
186
194
  "project.invalid_address": "팩 주소가 아닙니다.",
187
195
  "project.invalid_icon": "프로젝트 이미지로 쓸 수 없는 파일입니다.",
188
- "project.key_taken": "같은 키의 프로젝트가 이미 있습니다.",
196
+ "project.key_taken": "같은 키의 프로젝트나 팀이 이미 있습니다.",
189
197
  "project.no_icon": "프로젝트에 이미지가 없습니다.",
190
198
  "project.not_found": "프로젝트를 찾을 수 없습니다.",
191
199
  "project.pack_connect_refused": "SkillCDN이 연결을 거부했습니다.",
@@ -279,6 +287,10 @@ export const ko: Messages = {
279
287
  projectsSome: "일부 프로젝트",
280
288
  settingsForm: "설정",
281
289
  secretSet: (when) => `${when} 설정됨`,
290
+ secretSealedFrom: (host, when) => `${when} ${host}에서 받아 저장됨`,
291
+ secretExpires: (when) => `${when} 만료 예정`,
292
+ secretExpired: (when) => `${when} 만료됨`,
293
+ secretHosts: (hosts) => `${hosts}에만 보냅니다.`,
282
294
  secretUnset: "설정 안 됨",
283
295
  secretNew: "새 값",
284
296
  enable: "활성화",
@@ -287,11 +299,24 @@ export const ko: Messages = {
287
299
  rolesNone: "역할이 없어 모든 멤버가 쓸 수 있습니다.",
288
300
  roleDefault: "기본",
289
301
  everyMember: "모든 멤버",
302
+ roleManagers: "보고 라인",
303
+ whoeverManages: "누군가의 매니저인 사람",
290
304
  grantEveryone: "모두",
291
305
  grantMembersOf: (project) => `${project}의 멤버`,
292
306
  grantPeople: "사람",
293
307
  grantProjects: "프로젝트의 멤버",
294
308
  grantWhom: "부여할 대상…",
309
+ grantPlugins: "플러그인",
310
+ grantTeams: "팀",
311
+ grantTeam: (team) => `팀 ${team}`,
312
+ grantPlugin: (plugin) => `플러그인 ${plugin}`,
313
+ asks: "다른 플러그인에 요청한 역할",
314
+ asksHint:
315
+ "이 플러그인이 다른 플러그인에서 자기 이름으로 쓰려고 요청한 역할입니다. 관리자가 여기서, 또는 켤 때 부여합니다.",
316
+ askGranted: "부여됨",
317
+ askUngranted: "부여되지 않음",
318
+ askUnavailable: (plugin) => `${plugin}이(가) 꺼져 있습니다. 켜진 뒤 여기서 부여하세요.`,
319
+ askGrantNow: "지금 부여",
295
320
  grant: "부여",
296
321
  revoke: "회수",
297
322
  events: "일어난 일",
@@ -310,9 +335,15 @@ export const ko: Messages = {
310
335
  console: "콘솔 버전",
311
336
  consoleRange: (range) => `콘솔 ${range}`,
312
337
  timeZoneHint: "워크스페이스 시간대 기준입니다.",
338
+ keeps: "보관 기간",
339
+ keepsFor: (collection, days) => `${collection}: ${days}일이 지나면 삭제`,
340
+ needsTimeZone:
341
+ "워크스페이스에 시간대가 없어서, 시간대를 정하기 전까지 이 플러그인의 시각은 UTC 기준으로 실행됩니다.",
342
+ setTimeZone: "시간대 정하기",
313
343
  addItem: "추가",
314
344
  itemPlaceholder: "입력한 뒤 추가",
315
345
  choose: "선택…",
346
+ addOne: "추가…",
316
347
  },
317
348
  frameUnavailable: "지금은 표시할 수 없습니다.",
318
349
  capability: {
@@ -322,6 +353,15 @@ export const ko: Messages = {
322
353
  name: "모든 사람 읽기",
323
354
  opens: "워크스페이스의 모든 사람을 읽습니다. 예약 실행과 훅에서도.",
324
355
  },
356
+ "people:read:email": {
357
+ name: "업무 이메일",
358
+ opens: "읽을 수 있는 사람의 업무 이메일을 읽습니다. 다른 서비스의 계정과 맞추는 데 씁니다.",
359
+ },
360
+ "people:act": {
361
+ name: "사람 대신 실행",
362
+ opens:
363
+ "다른 곳에서 한 일을 그 사람 대신 실행하고 결정에 답합니다. 그 사람이 할 수 있는 범위 안에서, 플러그인을 거친 것으로 기록됩니다.",
364
+ },
325
365
  "projects:read": {
326
366
  name: "프로젝트 읽기",
327
367
  opens: "보는 사람이 볼 수 있는 프로젝트와 멤버를 읽습니다.",
@@ -516,7 +556,7 @@ export const ko: Messages = {
516
556
  filters: {
517
557
  aria: "보드 좁혀 보기",
518
558
  words: "제목, 번호, 사람",
519
- everyone: "모두",
559
+ everyone: "모든 사람",
520
560
  mine: "내 작업",
521
561
  anyCategory: "모든 카테고리",
522
562
  noCategory: "카테고리 없음",
@@ -874,8 +914,13 @@ export const ko: Messages = {
874
914
  console: "콘솔",
875
915
  as: (agent) => `${agent}(으)로`,
876
916
  asTitle: "그 사람이 사용한 에이전트",
917
+ via: (plugin) => `${plugin} 플러그인을 통해`,
918
+ viaTitle: "그 사람 대신 실행한 플러그인",
919
+ thePlugin: (plugin) => `플러그인 ${plugin}`,
877
920
  aTask: "작업",
878
921
  aPlugin: "플러그인",
922
+ aTeam: "팀",
923
+ theTeam: (team) => `팀 ${team}`,
879
924
  throughPlugin: (plugin) => `플러그인 ${plugin}을(를) 통해`,
880
925
  everyone: "모든 구성원",
881
926
  membersOf: (project) => `${project}의 구성원`,
@@ -932,6 +977,13 @@ export const ko: Messages = {
932
977
  personReactivated: (login) => `${login}을(를) 다시 활성화했습니다`,
933
978
  managerChanged: (login, manager) => `${login}의 매니저를 ${manager}(으)로 지정했습니다`,
934
979
  managerCleared: (login) => `${login}의 매니저 지정을 해제했습니다`,
980
+ emailChanged: (login) => `${login}의 업무 이메일을 바꿨습니다`,
981
+ emailCleared: (login) => `${login}의 업무 이메일을 지웠습니다`,
982
+ teamCreated: (team) => `팀 ${team}을(를) 만들었습니다`,
983
+ teamRenamed: (from, team) => `팀 ${from}의 이름을 ${team}(으)로 바꿨습니다`,
984
+ teamRemoved: (team) => `팀 ${team}을(를) 삭제했습니다`,
985
+ teamMemberAdded: (login, team) => `${login}을(를) 팀 ${team}에 추가했습니다`,
986
+ teamMemberRemoved: (login, team) => `${login}을(를) 팀 ${team}에서 뺐습니다`,
935
987
  projectCreated: (project) => `프로젝트 ${project}을(를) 만들었습니다`,
936
988
  projectUpdated: (fields, project) => `프로젝트 ${project}의 ${fields}을(를) 바꿨습니다`,
937
989
  memberAdded: (login, project, role) =>
@@ -973,6 +1025,8 @@ export const ko: Messages = {
973
1025
  pluginDisabled: (plugin) => `플러그인 ${plugin}을(를) 비활성화했습니다`,
974
1026
  pluginConsented: (plugin) => `플러그인 ${plugin}의 권한을 바꿨습니다`,
975
1027
  pluginConfigured: (fields, plugin) => `플러그인 ${plugin}의 ${fields}을(를) 바꿨습니다`,
1028
+ pluginSealed: (fields, plugin, host) =>
1029
+ `플러그인 ${plugin}의 ${fields}을(를) ${host}에서 받아 저장했습니다`,
976
1030
  pluginGranted: (role, who, plugin) =>
977
1031
  `${who}에게 플러그인 ${plugin}의 ${role} 역할을 부여했습니다`,
978
1032
  pluginRevoked: (role, who, plugin) =>
@@ -1164,7 +1218,17 @@ export const ko: Messages = {
1164
1218
  role: "역할",
1165
1219
  manager: "매니저",
1166
1220
  managerOf: (name) => `${name}의 매니저`,
1167
- team: "팀원",
1221
+ email: "업무 이메일",
1222
+ emailOf: (name) => `${name}의 업무 이메일`,
1223
+ emailHint:
1224
+ "Google 로그인은 로그인 주소가 들어오고, 그 밖은 여기서 적습니다. 관리자, 본인, 권한 있는 플러그인만 읽습니다.",
1225
+ team: "직속 구성원",
1226
+ teams: "팀",
1227
+ joinTeam: "팀 추가…",
1228
+ joinTeamOf: (name) => `${name}을(를) 추가할 팀`,
1229
+ leaveTeam: (name, team) => `${team}에서 ${name} 빼기`,
1230
+ howMany: (count) => `${count}명`,
1231
+ views: { aria: "사람과 팀", people: "사람", teams: "팀" },
1168
1232
  nobody: "없음",
1169
1233
  letIn: "사람 추가",
1170
1234
  letInAria: "사람 추가",
@@ -1213,6 +1277,34 @@ export const ko: Messages = {
1213
1277
  },
1214
1278
  },
1215
1279
 
1280
+ teams: {
1281
+ list: "팀",
1282
+ none: {
1283
+ title: "아직 팀이 없습니다",
1284
+ body: "팀은 사람들을 한 이름으로 묶습니다. 플러그인은 팀 이름으로 사람을 찾고, 팀에 준 플러그인 역할은 그 팀의 모든 사람이 갖습니다.",
1285
+ },
1286
+ members: (count) => `${count}명`,
1287
+ nobody: "아직 아무도 없습니다.",
1288
+ nameOf: (team) => `팀 ${team}의 이름`,
1289
+ keyTitle: "팀의 키: 부여, 플러그인, 명령이 팀을 부르는 이름이며 바뀌지 않습니다.",
1290
+ addMember: "사람 추가…",
1291
+ addMemberTo: (team) => `${team}에 추가할 사람`,
1292
+ removeMember: (name, team) => `${team}에서 ${name} 빼기`,
1293
+ remove: "팀 삭제",
1294
+ removeAsk: (team) =>
1295
+ `팀 ${team}을(를) 삭제할까요? 이 팀에 준 플러그인 역할도 함께 사라지고, 사람들은 그대로 남습니다.`,
1296
+ newTeam: "새 팀",
1297
+ newAria: "새 팀",
1298
+ form: {
1299
+ name: "팀 이름",
1300
+ key: "키",
1301
+ keyHint:
1302
+ "소문자, 숫자, 하이픈. 부여, 플러그인, 명령이 팀을 부르는 이름입니다. 다른 팀이나 프로젝트와 겹칠 수 없고 바뀌지 않습니다.",
1303
+ hint: "이름은 사람이 읽는 것이라 바꿀 수 있고, 키는 바뀌지 않습니다. 사람은 팀을 만든 뒤 팀 카드에서 추가합니다.",
1304
+ submit: "팀 만들기",
1305
+ },
1306
+ },
1307
+
1216
1308
  agents: {
1217
1309
  title: "에이전트",
1218
1310
  makeByHand: "토큰 직접 만들기",
package/src/index.ts CHANGED
@@ -109,6 +109,10 @@ export {
109
109
  type ListingFormProps,
110
110
  PeopleList,
111
111
  type PeopleListProps,
112
+ TeamForm,
113
+ type TeamFormProps,
114
+ TeamList,
115
+ type TeamListProps,
112
116
  } from "./components/people.js";
113
117
  export {
114
118
  PICKER_PAGE_LIMIT,
@@ -178,6 +182,12 @@ export {
178
182
  type RunListProps,
179
183
  RunStatusBadge,
180
184
  } from "./components/runs.js";
185
+ export {
186
+ goalsSearch,
187
+ listSearch,
188
+ peopleSearch,
189
+ projectsSearch,
190
+ } from "./components/searches.js";
181
191
  export {
182
192
  type Brand,
183
193
  isPlainClick,
@@ -212,6 +222,7 @@ export {
212
222
  Button,
213
223
  Callout,
214
224
  CategoryBadge,
225
+ Chip,
215
226
  CopyButton,
216
227
  cx,
217
228
  Day,
package/src/limits.ts CHANGED
@@ -48,6 +48,12 @@ export const MAX_FILE_NAME_LENGTH = 200;
48
48
  export const MAX_TOKEN_NAME_LENGTH = 80;
49
49
  /** A login at a git host, or an address at a Workspace, as an administrator lists one. */
50
50
  export const MAX_LOGIN_LENGTH = 254;
51
+ /** A team's name (ADR-0073). */
52
+ export const MAX_TEAM_NAME_LENGTH = 60;
53
+ /** A team's key (ADR-0074), as long as a project's may be: the two are one set of names. */
54
+ export const MAX_TEAM_KEY_LENGTH = 40;
55
+ /** How many teams one workspace may hold. */
56
+ export const MAX_TEAMS = 200;
51
57
  /** How long a token may be good for, in days, and how long one is when nothing is said. */
52
58
  export const MAX_TOKEN_DAYS = 365;
53
59
  export const DEFAULT_TOKEN_DAYS = 90;
@@ -4,7 +4,7 @@
4
4
  // crosses from the plugin's thread to the console is exactly that object.
5
5
  import { PluginDefinitionError } from "./definition-error.js";
6
6
  import { MAX_PLUGIN_ABOUT_LENGTH, MAX_PLUGIN_OPTIONS } from "./limits.js";
7
- import { PLUGIN_KEY_PATTERN } from "./manifest.js";
7
+ import { isCalendarDay, PLUGIN_KEY_PATTERN } from "./manifest.js";
8
8
 
9
9
  export const ARG_TYPES = [
10
10
  "text",
@@ -269,7 +269,6 @@ export const arg = {
269
269
  },
270
270
  };
271
271
 
272
- const DAY = /^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$/;
273
272
  const DEFAULT_TEXT_MAX = 2000;
274
273
  const DEFAULT_MARKDOWN_MAX = 20_000;
275
274
  const DEFAULT_LIST_MAX = 100;
@@ -304,9 +303,8 @@ export function argProblem(
304
303
  return undefined;
305
304
  }
306
305
  case "day":
307
- return typeof value === "string" && DAY.test(value)
308
- ? undefined
309
- : `${name} is not a day as YYYY-MM-DD`;
306
+ // The shape, and the calendar: February 30 fits the shape and is no day.
307
+ return isCalendarDay(value) ? undefined : `${name} is not a day as YYYY-MM-DD`;
310
308
  case "number": {
311
309
  if (typeof value !== "number" || !Number.isFinite(value)) {
312
310
  return `${name} is not a number`;
@@ -512,15 +510,22 @@ export function parseArgv(
512
510
  return { values };
513
511
  }
514
512
 
515
- /** The usage of an argument, as help prints it: `<from>` for a positional, `--to <day>` for a flag, in brackets where optional. */
513
+ /**
514
+ * The usage of an argument, as help prints it: `<from>` for a positional, `--to <day>` for a
515
+ * flag, in brackets where optional; a file as `--file <path>`, the flag the command itself takes,
516
+ * since the command uploads the file first and hands the plugin a handle, never a flag of the
517
+ * argument's name.
518
+ */
516
519
  export function argUsage(name: string, description: ArgDescription): string {
517
520
  const placeholder =
518
521
  description.type === "list" ? `${description.of?.type ?? "item"},...` : description.type;
519
522
  const form =
520
- description.type === "boolean" && !description.positional
521
- ? `--${name}`
522
- : description.positional
523
- ? `<${name}>`
524
- : `--${name} <${placeholder}>`;
523
+ description.type === "file"
524
+ ? "--file <path>"
525
+ : description.type === "boolean" && !description.positional
526
+ ? `--${name}`
527
+ : description.positional
528
+ ? `<${name}>`
529
+ : `--${name} <${placeholder}>`;
525
530
  return description.optional ? `[${form}]` : form;
526
531
  }