@skillcdn/console 0.4.3 → 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.
- package/README.md +6 -6
- package/dist/api.d.ts +4 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +4 -4
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +255 -33
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +20 -5
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +98 -26
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +39 -15
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/plugin-check.d.ts +5 -1
- package/dist/cli/plugin-check.d.ts.map +1 -1
- package/dist/cli/plugin-check.js +23 -4
- package/dist/cli/plugin-check.js.map +1 -1
- package/dist/cli/plugin-cli.d.ts.map +1 -1
- package/dist/cli/plugin-cli.js +10 -1
- package/dist/cli/plugin-cli.js.map +1 -1
- package/dist/cli/plugin-init.d.ts +2 -0
- package/dist/cli/plugin-init.d.ts.map +1 -1
- package/dist/cli/plugin-init.js +6 -0
- package/dist/cli/plugin-init.js.map +1 -1
- package/dist/client.d.ts +32 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +14 -2
- package/dist/client.js.map +1 -1
- package/dist/compat.js +1 -1
- package/dist/components/board.d.ts +8 -0
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +49 -18
- package/dist/components/board.js.map +1 -1
- package/dist/components/decision-form.d.ts +6 -3
- package/dist/components/decision-form.d.ts.map +1 -1
- package/dist/components/decision-form.js +24 -18
- package/dist/components/decision-form.js.map +1 -1
- package/dist/components/decision-list.d.ts +5 -1
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +25 -5
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +34 -8
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/goals.d.ts.map +1 -1
- package/dist/components/goals.js +2 -2
- package/dist/components/goals.js.map +1 -1
- package/dist/components/home.d.ts +35 -0
- package/dist/components/home.d.ts.map +1 -0
- package/dist/components/home.js +326 -0
- package/dist/components/home.js.map +1 -0
- package/dist/components/my-decisions.d.ts +20 -3
- package/dist/components/my-decisions.d.ts.map +1 -1
- package/dist/components/my-decisions.js +14 -1
- package/dist/components/my-decisions.js.map +1 -1
- package/dist/components/pack-connect.d.ts.map +1 -1
- package/dist/components/pack-connect.js +5 -4
- package/dist/components/pack-connect.js.map +1 -1
- package/dist/components/people.d.ts +45 -1
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +125 -4
- package/dist/components/people.js.map +1 -1
- package/dist/components/picker.d.ts +8 -0
- package/dist/components/picker.d.ts.map +1 -1
- package/dist/components/picker.js +2 -2
- package/dist/components/picker.js.map +1 -1
- package/dist/components/plugin-frame.d.ts +3 -13
- package/dist/components/plugin-frame.d.ts.map +1 -1
- package/dist/components/plugin-frame.js +75 -21
- package/dist/components/plugin-frame.js.map +1 -1
- package/dist/components/plugin-settings.d.ts +5 -1
- package/dist/components/plugin-settings.d.ts.map +1 -1
- package/dist/components/plugin-settings.js +283 -45
- package/dist/components/plugin-settings.js.map +1 -1
- package/dist/components/projects.d.ts +0 -8
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +4 -14
- package/dist/components/projects.js.map +1 -1
- package/dist/components/searches.d.ts +11 -0
- package/dist/components/searches.d.ts.map +1 -0
- package/dist/components/searches.js +30 -0
- package/dist/components/searches.js.map +1 -0
- package/dist/components/task-form.d.ts.map +1 -1
- package/dist/components/task-form.js +6 -5
- package/dist/components/task-form.js.map +1 -1
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.js +2 -1
- package/dist/components/task-view.js.map +1 -1
- package/dist/components/ui.d.ts +16 -0
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +15 -2
- package/dist/components/ui.js.map +1 -1
- package/dist/connect-page.d.ts.map +1 -1
- package/dist/connect-page.js +5 -6
- package/dist/connect-page.js.map +1 -1
- package/dist/console.css +1123 -258
- package/dist/console.d.ts +12 -4
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +123 -27
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +18 -3
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +41 -1
- package/dist/data.js.map +1 -1
- package/dist/docs-page.d.ts.map +1 -1
- package/dist/docs-page.js +6 -5
- package/dist/docs-page.js.map +1 -1
- package/dist/home-tiles.d.ts +92 -0
- package/dist/home-tiles.d.ts.map +1 -0
- package/dist/home-tiles.js +294 -0
- package/dist/home-tiles.js.map +1 -0
- package/dist/i18n/en.d.ts +264 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +260 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/index.d.ts +4 -2
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +10 -11
- package/dist/i18n/index.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +255 -4
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +12 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +12 -0
- package/dist/limits.js.map +1 -1
- package/dist/plugin/args.d.ts +6 -1
- package/dist/plugin/args.d.ts.map +1 -1
- package/dist/plugin/args.js +16 -11
- package/dist/plugin/args.js.map +1 -1
- package/dist/plugin/bridge.d.ts +63 -3
- package/dist/plugin/bridge.d.ts.map +1 -1
- package/dist/plugin/bridge.js +54 -0
- package/dist/plugin/bridge.js.map +1 -1
- package/dist/plugin/capabilities.d.ts +7 -1
- package/dist/plugin/capabilities.d.ts.map +1 -1
- package/dist/plugin/capabilities.js +24 -1
- package/dist/plugin/capabilities.js.map +1 -1
- package/dist/plugin/context.d.ts +174 -12
- package/dist/plugin/context.d.ts.map +1 -1
- package/dist/plugin/define.d.ts +14 -1
- package/dist/plugin/define.d.ts.map +1 -1
- package/dist/plugin/define.js +18 -2
- package/dist/plugin/define.js.map +1 -1
- package/dist/plugin/limits.d.ts +9 -2
- package/dist/plugin/limits.d.ts.map +1 -1
- package/dist/plugin/limits.js +9 -2
- package/dist/plugin/limits.js.map +1 -1
- package/dist/plugin/manifest.d.ts +98 -6
- package/dist/plugin/manifest.d.ts.map +1 -1
- package/dist/plugin/manifest.js +171 -10
- package/dist/plugin/manifest.js.map +1 -1
- package/dist/plugin/rest-schemas.d.ts +120 -0
- package/dist/plugin/rest-schemas.d.ts.map +1 -1
- package/dist/plugin/rest-schemas.js +78 -5
- package/dist/plugin/rest-schemas.js.map +1 -1
- package/dist/plugin/sandbox.d.ts +4 -3
- package/dist/plugin/sandbox.d.ts.map +1 -1
- package/dist/plugin/sandbox.js +33 -6
- package/dist/plugin/sandbox.js.map +1 -1
- package/dist/plugin/semver.d.ts +26 -0
- package/dist/plugin/semver.d.ts.map +1 -0
- package/dist/plugin/semver.js +76 -0
- package/dist/plugin/semver.js.map +1 -0
- package/dist/plugin/testing.d.ts +15 -1
- package/dist/plugin/testing.d.ts.map +1 -1
- package/dist/plugin/testing.js +42 -5
- package/dist/plugin/testing.js.map +1 -1
- package/dist/plugin/web.d.ts +16 -1
- package/dist/plugin/web.d.ts.map +1 -1
- package/dist/plugin/web.js +44 -1
- package/dist/plugin/web.js.map +1 -1
- package/dist/plugin.d.ts +3 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +3 -2
- package/dist/plugin.js.map +1 -1
- package/dist/router.d.ts +19 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +18 -0
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +11 -2
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +14 -1
- package/dist/routes.js.map +1 -1
- package/dist/rules.d.ts +2 -2
- package/dist/rules.js +3 -3
- package/dist/schemas.d.ts +864 -2
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +156 -9
- package/dist/schemas.js.map +1 -1
- package/dist/use-action.d.ts +8 -2
- package/dist/use-action.d.ts.map +1 -1
- package/dist/use-action.js +13 -6
- package/dist/use-action.js.map +1 -1
- package/dist/vocabulary.d.ts +3 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +9 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +10 -6
- package/docs/specs/plugins.md +83 -62
- package/docs/specs/rest.md +34 -16
- package/package.json +1 -1
- package/skills/building-a-plugin/SKILL.md +15 -12
- package/skills/planning-the-board/references/setting-up.md +2 -1
- package/skills/working-the-board/SKILL.md +3 -3
- package/src/api.ts +23 -0
- package/src/cli/cli.ts +323 -30
- package/src/cli/format.ts +122 -26
- package/src/cli/help.ts +43 -14
- package/src/cli/plugin-check.ts +41 -2
- package/src/cli/plugin-cli.ts +10 -1
- package/src/cli/plugin-init.ts +8 -0
- package/src/client.ts +78 -0
- package/src/compat.ts +1 -1
- package/src/components/board.tsx +86 -45
- package/src/components/decision-form.tsx +73 -36
- package/src/components/decision-list.tsx +45 -13
- package/src/components/event-feed.tsx +40 -7
- package/src/components/goals.tsx +4 -1
- package/src/components/home.tsx +615 -0
- package/src/components/my-decisions.tsx +50 -5
- package/src/components/pack-connect.tsx +5 -4
- package/src/components/people.tsx +500 -61
- package/src/components/picker.tsx +11 -2
- package/src/components/plugin-frame.tsx +85 -63
- package/src/components/plugin-settings.tsx +735 -185
- package/src/components/projects.tsx +14 -69
- package/src/components/searches.ts +47 -0
- package/src/components/task-form.tsx +23 -28
- package/src/components/task-view.tsx +7 -0
- package/src/components/ui.tsx +42 -2
- package/src/connect-page.tsx +5 -6
- package/src/console.tsx +303 -64
- package/src/data.ts +62 -3
- package/src/docs-page.tsx +6 -5
- package/src/home-tiles.ts +438 -0
- package/src/i18n/en.ts +274 -0
- package/src/i18n/index.ts +17 -14
- package/src/i18n/ko.ts +267 -4
- package/src/index.ts +18 -5
- package/src/limits.ts +12 -0
- package/src/plugin/args.ts +16 -11
- package/src/plugin/bridge.ts +122 -1
- package/src/plugin/capabilities.ts +28 -1
- package/src/plugin/context.ts +186 -9
- package/src/plugin/define.ts +38 -1
- package/src/plugin/limits.ts +9 -2
- package/src/plugin/manifest.ts +228 -13
- package/src/plugin/rest-schemas.ts +89 -6
- package/src/plugin/sandbox.ts +36 -8
- package/src/plugin/semver.ts +89 -0
- package/src/plugin/testing.ts +63 -6
- package/src/plugin/web.ts +66 -0
- package/src/plugin.ts +19 -0
- package/src/router.ts +25 -0
- package/src/routes.ts +17 -2
- package/src/rules.ts +3 -3
- package/src/schemas.ts +197 -10
- package/src/styles/console.css +1123 -258
- package/src/use-action.ts +16 -7
- package/src/vocabulary.ts +10 -0
- package/template/plugin/console-plugin.json +3 -1
package/src/cli/format.ts
CHANGED
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
RestRulesPlugin,
|
|
31
31
|
RestRun,
|
|
32
32
|
RestTask,
|
|
33
|
+
RestTeam,
|
|
33
34
|
RestVersion,
|
|
34
35
|
RestVersionSummary,
|
|
35
36
|
RestWork,
|
|
@@ -119,12 +120,15 @@ export function formatProject(project: RestProject): string {
|
|
|
119
120
|
return lines.join("\n");
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
/** One person: their login, their name where the host gives one, what they are in the workspace,
|
|
123
|
-
export function formatPersonLine(person: RestPerson): string {
|
|
123
|
+
/** One person: their login, their name where the host gives one, what they are in the workspace, that they are out when they are, and whom they report to where known. */
|
|
124
|
+
export function formatPersonLine(person: RestPerson, reportsTo?: string | undefined): string {
|
|
124
125
|
const standing = person.status === "active" ? person.role : `${person.role}, deactivated`;
|
|
126
|
+
const line = reportsTo === undefined ? "" : `, reports to ${reportsTo}`;
|
|
127
|
+
// The work email where the console answers one: to an administrator, and for oneself.
|
|
128
|
+
const email = typeof person.email === "string" ? ` <${person.email}>` : "";
|
|
125
129
|
return person.name === null
|
|
126
|
-
? `${person.login} (${standing})`
|
|
127
|
-
: `${person.login} ${person.name} (${standing})`;
|
|
130
|
+
? `${person.login}${email} (${standing}${line})`
|
|
131
|
+
: `${person.login} ${person.name}${email} (${standing}${line})`;
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
/** The provider a listing names, as people call it. */
|
|
@@ -219,6 +223,7 @@ export function formatTask(
|
|
|
219
223
|
`#${task.number} ${task.title}`,
|
|
220
224
|
`state: ${task.state} priority: ${task.priority} owner: ${task.owner.login} workers: ${task.workers.length === 0 ? "nobody" : logins(task.workers)}`,
|
|
221
225
|
`id: ${task.id}${task.parentId === null ? "" : ` part of: ${task.parentId}`}`,
|
|
226
|
+
...(task.agent == null ? [] : [`written through: ${task.agent}`]),
|
|
222
227
|
...scheduleLines(task),
|
|
223
228
|
];
|
|
224
229
|
if (task.links.length > 0) {
|
|
@@ -379,7 +384,7 @@ export function formatGoalLine(goal: RestGoal): string {
|
|
|
379
384
|
export function formatGoal(goal: RestGoal, tasks?: readonly RestTask[]): string {
|
|
380
385
|
const lines = [
|
|
381
386
|
`goal ${goal.id}: ${goal.title}`,
|
|
382
|
-
`state: ${goal.state} category: ${goal.category?.key ?? "none"} starts: ${goal.startsOn ?? "none"} due: ${goal.dueOn ?? "none"} set by: ${goal.createdBy.login}`,
|
|
387
|
+
`state: ${goal.state} category: ${goal.category?.key ?? "none"} starts: ${goal.startsOn ?? "none"} due: ${goal.dueOn ?? "none"} set by: ${goal.createdBy.login}${through(goal.agent)}`,
|
|
383
388
|
`progress: ${progressOf(goal)}`,
|
|
384
389
|
];
|
|
385
390
|
if (goal.body.trim().length > 0) {
|
|
@@ -431,9 +436,27 @@ export function formatPersonWork(person: RestPerson, work: RestPersonWork): stri
|
|
|
431
436
|
].join("\n");
|
|
432
437
|
}
|
|
433
438
|
|
|
434
|
-
/**
|
|
439
|
+
/** The agent a thing came through, beside the person (ADR-0056); nothing when they did it themselves. */
|
|
440
|
+
const through = (agent: string | null | undefined): string =>
|
|
441
|
+
agent === null || agent === undefined ? "" : ` through ${agent}`;
|
|
442
|
+
|
|
443
|
+
/** Everyone a decision is addressed to (ADR-0057): `deciders` where the console answers it, else the one decider. */
|
|
444
|
+
const decidersOf = (decision: RestDecision): readonly RestPerson[] =>
|
|
445
|
+
decision.deciders !== undefined && decision.deciders.length > 0
|
|
446
|
+
? decision.deciders
|
|
447
|
+
: [decision.decider];
|
|
448
|
+
|
|
449
|
+
/** The logins of everyone a decision is addressed to, with commas between. */
|
|
450
|
+
export const deciderLogins = (decision: RestDecision): string =>
|
|
451
|
+
decidersOf(decision)
|
|
452
|
+
.map((person) => person.login)
|
|
453
|
+
.join(", ");
|
|
454
|
+
|
|
455
|
+
/** Whom a decision is for, where that is not the one who asked alone (ADR-0042, ADR-0057). */
|
|
435
456
|
const addressed = (decision: RestDecision): string =>
|
|
436
|
-
decision.
|
|
457
|
+
decidersOf(decision).every((person) => person.id === decision.raisedBy.id)
|
|
458
|
+
? ""
|
|
459
|
+
: `, for ${deciderLogins(decision)}`;
|
|
437
460
|
|
|
438
461
|
export function formatDecisionLine(decision: RestDecision): string {
|
|
439
462
|
const state = decision.answer === null ? "waiting" : `answered ${decision.answer.option}`;
|
|
@@ -454,11 +477,11 @@ export function formatOptions(decision: RestDecision): string {
|
|
|
454
477
|
export function formatAnswer(decision: RestDecision): string {
|
|
455
478
|
const { answer } = decision;
|
|
456
479
|
if (answer === null) {
|
|
457
|
-
return `waits for ${decision
|
|
480
|
+
return `waits for ${deciderLogins(decision)}.`;
|
|
458
481
|
}
|
|
459
482
|
const chosen = decision.options.find((option) => option.id === answer.option);
|
|
460
483
|
const lines = [
|
|
461
|
-
`Answered: ${answer.option}) ${chosen?.label ?? answer.option}${chosen?.own === true ? ", in their own words" : ""}, by ${answer.by.login} at ${answer.at}`,
|
|
484
|
+
`Answered: ${answer.option}) ${chosen?.label ?? answer.option}${chosen?.own === true ? ", in their own words" : ""}, by ${answer.by.login}${through(answer.agent)} at ${answer.at}`,
|
|
462
485
|
];
|
|
463
486
|
if (answer.note !== null && answer.note.length > 0) {
|
|
464
487
|
lines.push(`Note: ${answer.note}`);
|
|
@@ -488,6 +511,13 @@ export function formatDecision(decision: RestDecision): string {
|
|
|
488
511
|
lines.push("", "options:", formatOptions(decision), "", formatAnswer(decision));
|
|
489
512
|
if (decision.outcome !== null && decision.outcome.trim().length > 0) {
|
|
490
513
|
lines.push("", "what followed:", indent(decision.outcome));
|
|
514
|
+
if (decision.outcomeBy != null && decision.outcomeAt != null) {
|
|
515
|
+
lines.push(
|
|
516
|
+
indent(
|
|
517
|
+
`written by ${decision.outcomeBy.login}${through(decision.outcomeAgent)} at ${decision.outcomeAt}`,
|
|
518
|
+
),
|
|
519
|
+
);
|
|
520
|
+
}
|
|
491
521
|
}
|
|
492
522
|
return lines.join("\n");
|
|
493
523
|
}
|
|
@@ -590,6 +620,9 @@ export function localized(
|
|
|
590
620
|
export function formatPluginLine(plugin: RestPlugin, language: string | null | undefined): string {
|
|
591
621
|
const notes = [
|
|
592
622
|
plugin.roles.length === 0 ? undefined : `roles: ${plugin.roles.join(", ")}`,
|
|
623
|
+
plugin.console === null || plugin.console === undefined
|
|
624
|
+
? undefined
|
|
625
|
+
: `console: ${plugin.console}`,
|
|
593
626
|
plugin.problems.length === 0 ? undefined : `problems: ${plugin.problems.join("; ")}`,
|
|
594
627
|
].filter((note): note is string => note !== undefined);
|
|
595
628
|
return `${plugin.key} ${localized(plugin.name, language)} ${plugin.version} ${plugin.state}${notes.length === 0 ? "" : ` ${notes.join(" ")}`}`;
|
|
@@ -607,12 +640,22 @@ export function formatPlugins(plugins: RestPlugins, language: string | null | un
|
|
|
607
640
|
].join("\n");
|
|
608
641
|
}
|
|
609
642
|
|
|
610
|
-
/**
|
|
611
|
-
|
|
643
|
+
/**
|
|
644
|
+
* How a command or a query of a plugin is given, as help prints it: the positionals, then the
|
|
645
|
+
* flags. In brief, for a listing that an agent reads whole at every first use, the positionals
|
|
646
|
+
* and the required flags alone, the rest as `[options]`, since `help <command>` has them all.
|
|
647
|
+
*/
|
|
648
|
+
export function formatOperationUsage(
|
|
649
|
+
key: string,
|
|
650
|
+
operation: RestPluginOperation,
|
|
651
|
+
{ brief = false }: { readonly brief?: boolean } = {},
|
|
652
|
+
): string {
|
|
612
653
|
const entries = Object.entries(operation.args);
|
|
654
|
+
const shown = brief ? entries.filter(([, arg]) => arg.positional || !arg.optional) : entries;
|
|
613
655
|
const usage = [
|
|
614
|
-
...
|
|
615
|
-
...
|
|
656
|
+
...shown.filter(([, arg]) => arg.positional).map(([name, arg]) => argUsage(name, arg)),
|
|
657
|
+
...shown.filter(([, arg]) => !arg.positional).map(([name, arg]) => argUsage(name, arg)),
|
|
658
|
+
...(brief && shown.length < entries.length ? ["[options]"] : []),
|
|
616
659
|
];
|
|
617
660
|
return `console plugin ${key} ${operation.name}${usage.map((part) => ` ${part}`).join("")}`;
|
|
618
661
|
}
|
|
@@ -673,11 +716,11 @@ export function formatPluginHelp(detail: RestPluginDetail, name: string | undefi
|
|
|
673
716
|
}
|
|
674
717
|
return [
|
|
675
718
|
...detail.operations.flatMap((operation) => [
|
|
676
|
-
formatOperationUsage(detail.key, operation),
|
|
719
|
+
formatOperationUsage(detail.key, operation, { brief: true }),
|
|
677
720
|
` ${operation.kind === "query" ? "[query] " : ""}${operation.about}`,
|
|
678
721
|
]),
|
|
679
722
|
"",
|
|
680
|
-
`One in full: console plugin ${detail.key} help <command>.`,
|
|
723
|
+
`One in full, with every option: console plugin ${detail.key} help <command>.`,
|
|
681
724
|
].join("\n");
|
|
682
725
|
}
|
|
683
726
|
const operation = detail.operations.find((candidate) => candidate.name === name);
|
|
@@ -720,7 +763,13 @@ export function formatPluginSettingLine(
|
|
|
720
763
|
const holds =
|
|
721
764
|
setting.type === "secret"
|
|
722
765
|
? setting.set
|
|
723
|
-
? `set${setting.setAt === null ? "" : ` ${moment(setting.setAt)}`}
|
|
766
|
+
? `set${setting.setAt === null ? "" : ` ${moment(setting.setAt)}`}${
|
|
767
|
+
setting.source === null ||
|
|
768
|
+
setting.source === undefined ||
|
|
769
|
+
setting.source === "administrator"
|
|
770
|
+
? ""
|
|
771
|
+
: ` from ${setting.source}`
|
|
772
|
+
}${setting.expiresAt === null || setting.expiresAt === undefined ? "" : `, expires ${moment(setting.expiresAt)}`}`
|
|
724
773
|
: "not set"
|
|
725
774
|
: setting.set
|
|
726
775
|
? setting.value === null
|
|
@@ -735,14 +784,25 @@ export function formatPluginSettingLine(
|
|
|
735
784
|
/** A grant in a line: its id, the role, whom it is to, since when. */
|
|
736
785
|
export function formatPluginGrantLine(grant: RestPluginGrant): string {
|
|
737
786
|
const whom =
|
|
738
|
-
grant.
|
|
739
|
-
? `
|
|
740
|
-
: grant.
|
|
741
|
-
? `the
|
|
742
|
-
:
|
|
787
|
+
grant.plugin !== null && grant.plugin !== undefined
|
|
788
|
+
? `the plugin ${grant.plugin}`
|
|
789
|
+
: grant.team !== null && grant.team !== undefined
|
|
790
|
+
? `the team ${grant.team.key}`
|
|
791
|
+
: grant.person !== null
|
|
792
|
+
? `person ${grant.person.login}`
|
|
793
|
+
: grant.project !== null
|
|
794
|
+
? `the members of ${grant.project.key}`
|
|
795
|
+
: "everyone";
|
|
743
796
|
return `${grant.id} ${grant.role} to ${whom} since ${moment(grant.createdAt)}`;
|
|
744
797
|
}
|
|
745
798
|
|
|
799
|
+
/** A team in a line (ADR-0073): its key (ADR-0074), its name, how many are in it, and their logins. */
|
|
800
|
+
export function formatTeamLine(team: RestTeam, loginOf: ReadonlyMap<string, string>): string {
|
|
801
|
+
const logins = team.memberIds.map((id) => loginOf.get(id) ?? id);
|
|
802
|
+
const count = team.memberIds.length === 1 ? "1 member" : `${team.memberIds.length} members`;
|
|
803
|
+
return `${team.key} ${team.name} ${count}${logins.length === 0 ? "" : `: ${logins.join(", ")}`}`;
|
|
804
|
+
}
|
|
805
|
+
|
|
746
806
|
/** One plugin in full: what it is, how it stands, what one may run, its skills and its settings. */
|
|
747
807
|
export function formatPlugin(
|
|
748
808
|
detail: RestPluginDetail,
|
|
@@ -761,6 +821,9 @@ export function formatPlugin(
|
|
|
761
821
|
if (detail.problems.length > 0) {
|
|
762
822
|
lines.push(`problems: ${detail.problems.join("; ")}`);
|
|
763
823
|
}
|
|
824
|
+
if (detail.console !== null && detail.console !== undefined) {
|
|
825
|
+
lines.push(`Runs on consoles ${detail.console}.`);
|
|
826
|
+
}
|
|
764
827
|
lines.push(
|
|
765
828
|
detail.roles.length === 0
|
|
766
829
|
? "You hold no role in it."
|
|
@@ -778,11 +841,11 @@ export function formatPlugin(
|
|
|
778
841
|
lines.push("Commands and queries:");
|
|
779
842
|
for (const operation of detail.operations) {
|
|
780
843
|
lines.push(
|
|
781
|
-
` ${formatOperationUsage(detail.key, operation)}`,
|
|
844
|
+
` ${formatOperationUsage(detail.key, operation, { brief: true })}`,
|
|
782
845
|
` ${operation.kind === "query" ? "[query] " : ""}${operation.about}`,
|
|
783
846
|
);
|
|
784
847
|
}
|
|
785
|
-
lines.push(`One in full: console plugin ${detail.key} help <command>.`);
|
|
848
|
+
lines.push(`One in full, with every option: console plugin ${detail.key} help <command>.`);
|
|
786
849
|
}
|
|
787
850
|
if (detail.skills !== null) {
|
|
788
851
|
lines.push(
|
|
@@ -806,18 +869,51 @@ export function formatPlugin(
|
|
|
806
869
|
...detail.grants.map((grant) => ` ${formatPluginGrantLine(grant)}`),
|
|
807
870
|
);
|
|
808
871
|
}
|
|
872
|
+
lines.push(...formatPluginAsks(detail, language));
|
|
809
873
|
return lines.join("\n");
|
|
810
874
|
}
|
|
811
875
|
|
|
876
|
+
/**
|
|
877
|
+
* The roles the plugin asks of other plugins (ADR-0072), each with how it stands and, where not
|
|
878
|
+
* granted, the command that grants it; nothing where it asks none.
|
|
879
|
+
*/
|
|
880
|
+
export function formatPluginAsks(
|
|
881
|
+
detail: RestPluginDetail,
|
|
882
|
+
language: string | null | undefined,
|
|
883
|
+
): string[] {
|
|
884
|
+
const asks = detail.asks ?? [];
|
|
885
|
+
if (asks.length === 0) {
|
|
886
|
+
return [];
|
|
887
|
+
}
|
|
888
|
+
return [
|
|
889
|
+
"",
|
|
890
|
+
"Asks of other plugins:",
|
|
891
|
+
...asks.map((ask) => {
|
|
892
|
+
const standing =
|
|
893
|
+
ask.state === "granted"
|
|
894
|
+
? "granted"
|
|
895
|
+
: ask.state === "unavailable"
|
|
896
|
+
? `${ask.plugin} is not enabled`
|
|
897
|
+
: `not granted; console plugin ${ask.plugin} grant ${ask.role} --plugin ${detail.key}`;
|
|
898
|
+
return ` ${ask.role} of ${ask.plugin}: ${standing} ${localized(ask.why, language)}`;
|
|
899
|
+
}),
|
|
900
|
+
];
|
|
901
|
+
}
|
|
902
|
+
|
|
812
903
|
/** What a command or a query answered, as the command prints it. */
|
|
813
904
|
export function formatInvocation(result: RestPluginInvocation): string {
|
|
814
905
|
if (result.kind === "query") {
|
|
815
906
|
return result.data === undefined ? "null" : JSON.stringify(result.data, null, 2);
|
|
816
907
|
}
|
|
817
908
|
const text = result.text.length === 0 ? "Done." : result.text;
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
909
|
+
if (result.waiting === null) {
|
|
910
|
+
return text;
|
|
911
|
+
}
|
|
912
|
+
const where =
|
|
913
|
+
result.project === null || result.project === undefined
|
|
914
|
+
? ""
|
|
915
|
+
: ` in the project ${result.project}`;
|
|
916
|
+
return `${text}\ndecision id: ${result.waiting}\nThe decision waits for a person${where}: console decision ${result.waiting} --wait 100`;
|
|
821
917
|
}
|
|
822
918
|
|
|
823
919
|
/** A record's data on one line, cut where it runs long. */
|
package/src/cli/help.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
MAX_CATEGORY_KEY_LENGTH,
|
|
8
8
|
MAX_CATEGORY_NAME_LENGTH,
|
|
9
9
|
MAX_COMMENTS_PER_TASK,
|
|
10
|
+
MAX_DECIDERS,
|
|
10
11
|
MAX_DECISIONS_PER_PROJECT,
|
|
11
12
|
MAX_DOCUMENT_LENGTH,
|
|
12
13
|
MAX_DOCUMENT_PATH_LENGTH,
|
|
@@ -228,6 +229,30 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
228
229
|
about:
|
|
229
230
|
"The people of the workspace you see, by login, with their name where the host gives one and what they are in it: admin, who configures the workspace, or member, who works in the projects they are in; a person taken out is marked deactivated. Everyone, unless the workspace shows a member only the people they share a project with (console workspace says which; an administrator sees everyone). Then, where you see everyone, the logins an administrator let in that nobody has signed in with yet. The logins are what --worker, member and person take; a login is found exactly, so an owner lists someone the list does not show.",
|
|
230
231
|
},
|
|
232
|
+
teams: {
|
|
233
|
+
usage: "teams",
|
|
234
|
+
synopsis: "The teams of the workspace, each with who is in it.",
|
|
235
|
+
about:
|
|
236
|
+
"The teams of the workspace (a name and its members: a unit to call people by, and to grant a plugin's role to, ADR-0073), by name, each with its key, what it is called by (ADR-0074), how many are in it and their logins. console team makes and keeps them, for an administrator.",
|
|
237
|
+
},
|
|
238
|
+
team: {
|
|
239
|
+
usage:
|
|
240
|
+
'team new <key> "<name>" | team rename <key> "<new name>" | team remove <key> | team add <key> <login> | team drop <key> <login>',
|
|
241
|
+
synopsis:
|
|
242
|
+
"Makes, renames or removes a team, or puts a person in one or takes them out; for an administrator.",
|
|
243
|
+
about:
|
|
244
|
+
"For an administrator: a team is a key, a name and its members, any number per person. The key is what a grant, a plugin and this command call the team by (ADR-0074): lowercase letters, digits and hyphens, one among the teams' and the projects' keys, and it never changes; the name, one per workspace, is what people read. new makes one; rename gives it another name, the key staying; remove takes it away, with its memberships and the plugin roles granted to it; add and drop put a person in and take them out, by login. The board records each as the workspace's own event, and a plugin with people:read:all hears of them.",
|
|
245
|
+
refusals: [
|
|
246
|
+
"auth.forbidden: only an administrator keeps the teams",
|
|
247
|
+
"request.invalid: the key is not lowercase letters, digits and hyphens, or the name is empty or too long",
|
|
248
|
+
"team.not_found: no team has that key",
|
|
249
|
+
"team.key_taken: a team or a project has that key already",
|
|
250
|
+
"team.name_taken: a team of that name is there already",
|
|
251
|
+
"team.too_many: the workspace holds as many teams as one may",
|
|
252
|
+
"team.invalid_member: the person is not an active person of the workspace",
|
|
253
|
+
"team.not_a_member: the person is not in the team",
|
|
254
|
+
],
|
|
255
|
+
},
|
|
231
256
|
workspace: {
|
|
232
257
|
usage:
|
|
233
258
|
"workspace | workspace edit [--people-visibility <everyone|shared-projects>] [--week-start <auto|monday|sunday>] [--time-zone <zone|none>] | workspace pack-token --connect | workspace pack-token --stdin | workspace pack-token --remove",
|
|
@@ -247,14 +272,16 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
247
272
|
},
|
|
248
273
|
person: {
|
|
249
274
|
usage:
|
|
250
|
-
"person role <login> <admin|member> | person add <login> [--host gh|google] | person remove <login> | person deactivate <login> | person reactivate <login>",
|
|
275
|
+
"person role <login> <admin|member> | person manager <login> <manager login|none> | person email <login> <address|none> | person add <login> [--host gh|google] | person remove <login> | person deactivate <login> | person reactivate <login>",
|
|
251
276
|
synopsis:
|
|
252
|
-
"Says what a person is in the workspace, lets a login in or takes one out; for an administrator.",
|
|
277
|
+
"Says what a person is in the workspace, whom they report to and their work email, lets a login in or takes one out; for an administrator.",
|
|
253
278
|
about:
|
|
254
|
-
"For an administrator: the person themselves on the People page, or their agent here, named on it. role says what a person is in the workspace; the board keeps at least one administrator in. add lets a login in: whoever signs in with that account at that host is a person from then on; the host is read off the login (an address is Google's, anything else GitHub's) unless --host says. remove takes a login away that nobody has signed in with yet. deactivate takes a person out: refused on every request at once, their agents with them, kept with everything they did, and listed nowhere; reactivate takes them back in. A person the organization let in comes and goes with it, and is neither.",
|
|
279
|
+
"For an administrator: the person themselves on the People page, or their agent here, named on it. role says what a person is in the workspace; the board keeps at least one administrator in. manager says whom a person reports to, the reporting line of the directory, which the plugins read as a default (the approver of their leave, say) in place of a line of their own: another active person of the workspace who does not report to them, directly or through others, or none; console people shows it. email writes the person's work email, or none, by which a plugin allowed to read it matches them to an account elsewhere: a person who signs in at Google has their address from the sign-in, and an administrator writes everyone else's; it is read by an administrator, the person and such a plugin, and by nobody from the feed, which says only that it changed. add lets a login in: whoever signs in with that account at that host is a person from then on; the host is read off the login (an address is Google's, anything else GitHub's) unless --host says. remove takes a login away that nobody has signed in with yet. deactivate takes a person out: refused on every request at once, their agents with them, kept with everything they did, and listed nowhere; reactivate takes them back in. A person the organization let in comes and goes with it, and is neither.",
|
|
255
280
|
refusals: [
|
|
256
281
|
"auth.forbidden: only an administrator says who is in and what they are",
|
|
257
282
|
"person.last_admin: the board keeps at least one administrator in; make another one first",
|
|
283
|
+
"person.invalid_manager: the manager is not another active person of the workspace, or reports to this person already",
|
|
284
|
+
"person.email_taken: the address is another person's in this workspace",
|
|
258
285
|
"person.of_organization: the organization says whether that person is in",
|
|
259
286
|
"listing.exists: the login is let in already, or is a person's who is in",
|
|
260
287
|
"listing.unknown_host: nobody signs in through that provider here",
|
|
@@ -378,17 +405,18 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
378
405
|
},
|
|
379
406
|
plugin: {
|
|
380
407
|
usage:
|
|
381
|
-
"plugin <key> | plugin <key> help [<command>] | plugin <key> <command> [<args>] [--<name> <value>]... [--file <path> | -] [--run <id>] | plugin <key> records <collection> [<id>] [--where <field>=<value>]... [--search <words>] [--project <key>] [--order <field>[:desc]] [--limit <n>] [--after <cursor>] [--archived] [--versions] | plugin <key> skills [<folder>|<file>] [--search <words> [--limit <n>] [--cursor <cursor>]] | plugin <key> skill <path> | plugin <key> skill --file <path> [--offset <n>] [--limit <n>] | plugin <key> enable --consent <capability>,... [--board new|<key>] [--projects all|<key>,...] [--set <key>=<value>]... | plugin <key> disable | plugin <key> settings [--set <key>=<value>]... | plugin <key> secret <name> [--remove] | plugin <key> grant <role> --person <login>|--project <key>|--everyone | plugin <key> revoke <grant id>",
|
|
408
|
+
"plugin <key> | plugin <key> help [<command>] | plugin <key> <command> [<args>] [--<name> <value>]... [--file <path> | -] [--run <id>] | plugin <key> records <collection> [<id>] [--where <field>=<value>]... [--search <words>] [--project <key>] [--order <field>[:desc]] [--limit <n>] [--after <cursor>] [--archived] [--versions] | plugin <key> skills [<folder>|<file>] [--search <words> [--limit <n>] [--cursor <cursor>]] | plugin <key> skill <path> | plugin <key> skill --file <path> [--offset <n>] [--limit <n>] | plugin <key> enable --consent <capability>,... [--board new|<key>] [--projects all|<key>,...] [--set <key>=<value>]... | plugin <key> disable | plugin <key> capabilities [--consent <capability>,...|none] | plugin <key> settings [--set <key>=<value>]... | plugin <key> secret <name> [--remove] | plugin <key> grant <role> --person <login>|--project <key>|--plugin <key>|--team <key>|--everyone | plugin <key> revoke <grant id>",
|
|
382
409
|
overview:
|
|
383
|
-
"plugin <key> [help [<command>] | <command> [<args>] | records <collection> | skills | skill <path> | enable | disable | settings | secret | grant | revoke]",
|
|
410
|
+
"plugin <key> [help [<command>] | <command> [<args>] | records <collection> | skills | skill <path> | enable | disable | capabilities | settings | secret | grant | revoke]",
|
|
384
411
|
synopsis:
|
|
385
412
|
"One plugin: what it is for and its commands; a command run as you; its records, its skills; its settings, for an administrator.",
|
|
386
413
|
about:
|
|
387
|
-
"One plugin of this console, by its key. Alone: what it is, the sentence for agents, its state, the roles you hold in it, its board project, then one line per command and query you may run, with its usage, where its skills are, and its settings. help [<command>]: one command or query in full, as console help prints one: its arguments, their types and bounds, and the refusals; every argument is given by its place when positional, else as --<name> <value> or --<name>=<value>; a boolean as --<name> alone; a list as the flag given again, or with commas between; a day as YYYY-MM-DD; a person by login, or me; a project by key; a file by --file <path>, or - for standard input, which the command uploads first, with the plugin's files capability. <command> [<args>]: runs the command or the query as the person whose token you hold; the console parses the words against the definition and refuses the first that does not fit; prints what the command says, or a query's answer as JSON, a file a query answers saved under its name; --json prints the whole answer. A command that raised a decision prints its id and exits with 3: console decision <id> --wait 100 waits for the answer. The event the command leaves names your open run in the plugin's board project, --run <id> saying which when several are open; an argument marked secret is hidden in it. records <collection>: the records you may read, one a line, newest first; --where <field>=<value>, given again for several, narrows by a field the collection indexes (id, owner and project always; a number, true, false and null are read as such); --search <words> by words; --project <key> to those linked to a project; --order <field>, with :desc for the newest first; a page of --limit with --after <cursor> as the last page printed; --archived the ones put away; <id> reads one record whole, --versions its versions. skills and skill: the plugin's own skills, a pack read from its package, exactly as console skills and console skill read a project's pack. enable, disable, settings, secret, grant and revoke are an administrator's. enable prints what each capability the plugin asks opens and what a malicious plugin would reach through it, and refuses unless --consent names every capability again; --board new makes the board project the manifest proposes, --board <key> chooses one, left out where the plugin needs none; --projects all, or the keys of the projects the plugin is on; --set a setting's value, which settings changes afterwards too, an empty value clearing one, a number or true and false read as such. secret <name> reads the value from standard input, seals it and shows it never; --remove takes it away. grant gives a role to a person by login, to the members of a project by key, or to everyone, and revoke takes a grant back by its id, which console plugin <key> lists to an administrator.",
|
|
414
|
+
"One plugin of this console, by its key. Alone: what it is, the sentence for agents, its state, the roles you hold in it, its board project, then one line per command and query you may run, with its usage, where its skills are, and its settings. help [<command>]: one command or query in full, as console help prints one: its arguments, their types and bounds, and the refusals; every argument is given by its place when positional, else as --<name> <value> or --<name>=<value>; a boolean as --<name> alone; a list as the flag given again, or with commas between; a day as YYYY-MM-DD; a person by login, or me; a project by key; a file by --file <path>, or - for standard input, which the command uploads first, with the plugin's files capability. <command> [<args>]: runs the command or the query as the person whose token you hold; the console parses the words against the definition and refuses the first that does not fit; prints what the command says, or a query's answer as JSON, a file a query answers saved under its name; --json prints the whole answer. A command that raised a decision prints its id and exits with 3: console decision <id> --wait 100 waits for the answer. The event the command leaves names your open run in the plugin's board project, --run <id> saying which when several are open; an argument marked secret is hidden in it. records <collection>: the records you may read, one a line, newest first; --where <field>=<value>, given again for several, narrows by a field the collection indexes (id, owner and project always; a number, true, false and null are read as such); --search <words> by words; --project <key> to those linked to a project; --order <field>, with :desc for the newest first; a page of --limit with --after <cursor> as the last page printed; --archived the ones put away; <id> reads one record whole, --versions its versions. skills and skill: the plugin's own skills, a pack read from its package, exactly as console skills and console skill read a project's pack. enable, disable, settings, secret, grant and revoke are an administrator's. enable prints what each capability the plugin asks opens and what a malicious plugin would reach through it, and refuses unless --consent names every capability again; --board new makes the board project the manifest proposes, --board <key> chooses one, left out where the plugin needs none; --projects all, or the keys of the projects the plugin is on; --set a setting's value, which settings changes afterwards too, an empty value clearing one, a number or true and false read as such. secret <name> reads the value from standard input, seals it and shows it never; --remove takes it away. grant gives a role to a person by login, to the members of a project by key, to another plugin by key, to the members of a team by key, or to everyone, and revoke takes a grant back by its id, which console plugin <key> lists to an administrator.",
|
|
388
415
|
limits: [
|
|
389
416
|
"a command line: up to 200 words of up to 50,000 characters each; one file, up to the file bound (console help hand-in), with the plugin's files capability",
|
|
390
417
|
"records: a page of up to 200; where: a field the collection indexes",
|
|
391
|
-
"enable: consent names every capability the manifest
|
|
418
|
+
"enable: consent names every capability the manifest requires, and the optional ones to turn on; settings and secrets as the manifest bounds them",
|
|
419
|
+
"capabilities: --consent names the optional capabilities the plugin holds from now on, none for none of them; those it requires stay",
|
|
392
420
|
],
|
|
393
421
|
refusals: [
|
|
394
422
|
"plugin.not_found: no plugin has that key, or you may not know of it; or the plugin has no such command or query",
|
|
@@ -451,25 +479,26 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
451
479
|
},
|
|
452
480
|
ask: {
|
|
453
481
|
usage:
|
|
454
|
-
'ask "<question>" --option "<label>" --option "<label>" [...] [--body <markdown>|--file <path>] [--attach <path>]... [--to <login|me>] [--wait <seconds>] [--run <id>]',
|
|
482
|
+
'ask "<question>" --option "<label>" --option "<label>" [...] [--body <markdown>|--file <path>] [--attach <path>]... [--to <login|me>]... [--wait <seconds>] [--run <id>]',
|
|
455
483
|
overview:
|
|
456
|
-
'ask "<question>" --option "<label>" --option "<label>" [...] [--body <markdown>|--file <path>] [--attach <path>]... [--to <login|me>] [--wait <seconds>]',
|
|
484
|
+
'ask "<question>" --option "<label>" --option "<label>" [...] [--body <markdown>|--file <path>] [--attach <path>]... [--to <login|me>]... [--wait <seconds>]',
|
|
457
485
|
synopsis:
|
|
458
486
|
"Raises a decision a person must make, with options, and waits a while for the answer.",
|
|
459
|
-
about: `Raises a decision a person must make: the question, the options to choose from, and what they need to know. --attach puts a file on the decision, in the one request: a picture of what is to be chosen between, a screenshot of what was made; a picture the body names as  is shown there, the rest after the body, and the person reads each back with \`console file <id>\` (ADR-0055). It is for the person you work for unless --to names another of the project, who then finds it among their decisions (ADR-0042); anyone of the project may still answer. The run waits for the answer. The command waits 60 seconds for it unless --wait says otherwise, and exits with 3 if it is still to come: keep waiting with \`console decision <id> --wait 100\`, within what your shell gives one command. The decision's id is printed as soon as it is raised. The person chooses one of the options, or answers in their own words: the answer then names one more option, their words, marked own; act on it as given. ${ON_THE_RUN}`,
|
|
487
|
+
about: `Raises a decision a person must make: the question, the options to choose from, and what they need to know. --attach puts a file on the decision, in the one request: a picture of what is to be chosen between, a screenshot of what was made; a picture the body names as  is shown there, the rest after the body, and the person reads each back with \`console file <id>\` (ADR-0055). It is for the person you work for unless --to names another of the project, who then finds it among their decisions (ADR-0042); --to given again names several, each of whom finds it among theirs, and the first answer ends it (ADR-0057); anyone of the project may still answer. The run waits for the answer. The command waits 60 seconds for it unless --wait says otherwise, and exits with 3 if it is still to come: keep waiting with \`console decision <id> --wait 100\`, within what your shell gives one command. The decision's id is printed as soon as it is raised. The person chooses one of the options, or answers in their own words: the answer then names one more option, their words, marked own; act on it as given. ${ON_THE_RUN}`,
|
|
460
488
|
limits: [
|
|
461
489
|
`question: one line, up to ${MAX_QUESTION_LENGTH} characters`,
|
|
462
490
|
`options: ${MIN_OPTIONS} to ${MAX_OPTIONS}, each one line up to ${MAX_OPTION_LABEL_LENGTH} characters`,
|
|
463
491
|
`body: ${MARKDOWN}`,
|
|
464
492
|
`attach: a file: up to ${MAX_FILE_BYTES} bytes, not empty; called by its name, without the path; a picture is placed in the body as `,
|
|
465
493
|
`at most ${MAX_FILES_PER_DECISION} files on one decision, together up to ${MAX_FILE_BYTES} bytes`,
|
|
466
|
-
|
|
494
|
+
`to: a login of the project, or me; up to ${MAX_DECIDERS} of them`,
|
|
467
495
|
`at most ${MAX_DECISIONS_PER_PROJECT} decisions in one project`,
|
|
468
496
|
],
|
|
469
497
|
refusals: [
|
|
470
498
|
...RUN_CHOICE,
|
|
471
499
|
"decision.invalid_run: the run is not yours, or is over",
|
|
472
|
-
"decision.invalid_decider:
|
|
500
|
+
"decision.invalid_decider: a person named may not work in the project",
|
|
501
|
+
"decision.too_many_deciders: more people named than one decision takes",
|
|
473
502
|
"decision.too_many: the project holds as many decisions as one may",
|
|
474
503
|
"decision.too_many_files: more files than one decision carries",
|
|
475
504
|
"request.too_large: a file, or the files together, over the bound",
|
|
@@ -479,7 +508,7 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
479
508
|
usage: 'answer <id> --option <n>|--own "<answer>" [--note <markdown>|--file <path>]',
|
|
480
509
|
synopsis: "Writes a person's answer on a decision: the option they chose, or their own words.",
|
|
481
510
|
about:
|
|
482
|
-
"Writes on the board the answer a person gave: one of the decision's options by its number, or their own words, which become one more option of the decision marked as theirs; and a note about it. The answer is the person's, written in their name through you (ADR-0021):
|
|
511
|
+
"Writes on the board the answer a person gave: one of the decision's options by its number, or their own words, which become one more option of the decision marked as theirs; and a note about it. The answer is the person's, written in their name through you (ADR-0021), and the record says so: the decision names you beside them as the agent the answer came through, where an answer given on the pages names the person alone (ADR-0056). A choice they made in chat, in a meeting or by changing the task is written here, with where and when they made it in the note, so that the record is whole and a run that waited goes on. A decision is answered once; what followed is written with console decision <id> --outcome.",
|
|
483
512
|
limits: [
|
|
484
513
|
`own: one line, up to ${MAX_OPTION_LABEL_LENGTH} characters`,
|
|
485
514
|
`note: Markdown, up to ${MAX_NOTE_LENGTH} characters`,
|
|
@@ -495,7 +524,7 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
495
524
|
synopsis:
|
|
496
525
|
"One decision in full; waits for its answer with --wait; writes what followed with --outcome.",
|
|
497
526
|
about:
|
|
498
|
-
"One decision, with its context, its options, its answer (one of the options, or the person's own words as one more, marked own) and what followed. With --wait, holds on for the answer that long, and exits with 3 if it is still to come. With --outcome, writes down what followed the decision, so that it reads as a record.",
|
|
527
|
+
"One decision, with its context, its options, its answer (one of the options, or the person's own words as one more, marked own) and what followed. With --wait, holds on for the answer that long, and exits with 3 if it is still to come. With --outcome, writes down what followed the decision, so that it reads as a record; the record names who wrote it, you beside them, and when (ADR-0056).",
|
|
499
528
|
limits: [`outcome: ${MARKDOWN}`],
|
|
500
529
|
refusals: ["decision.not_found: no decision has that id"],
|
|
501
530
|
},
|
package/src/cli/plugin-check.ts
CHANGED
|
@@ -8,11 +8,13 @@ import { descriptionProblems, type PluginDescription } from "../plugin/define.js
|
|
|
8
8
|
import type { ServerLoad } from "../plugin/load.js";
|
|
9
9
|
import {
|
|
10
10
|
CURRENT_PLUGIN_API,
|
|
11
|
+
capabilitiesAsked,
|
|
11
12
|
PLUGIN_API_MAJORS,
|
|
12
13
|
type PluginManifest,
|
|
13
14
|
parsePluginManifest,
|
|
14
15
|
} from "../plugin/manifest.js";
|
|
15
16
|
import { forbiddenImportsIn, removedGlobalsIn } from "../plugin/sandbox.js";
|
|
17
|
+
import { formatTriple, satisfiesRange, versionOfDependency } from "../plugin/semver.js";
|
|
16
18
|
import { isLanguageMap } from "./plugin-init.js";
|
|
17
19
|
|
|
18
20
|
export interface CheckReport {
|
|
@@ -330,10 +332,16 @@ export function sameAsEnglish(value: unknown, at = ""): string[] {
|
|
|
330
332
|
);
|
|
331
333
|
}
|
|
332
334
|
|
|
335
|
+
export interface CheckOptions {
|
|
336
|
+
/** The version of the console this command belongs to, held against the manifest's `console`; left out, not compared. */
|
|
337
|
+
readonly consoleVersion?: string | undefined;
|
|
338
|
+
}
|
|
339
|
+
|
|
333
340
|
/** Reads the plugin at `root` as a console would, and says what it would refuse. */
|
|
334
341
|
export async function checkPlugin(
|
|
335
342
|
root: string,
|
|
336
343
|
loadServer: (file: string) => Promise<ServerLoad>,
|
|
344
|
+
options: CheckOptions = {},
|
|
337
345
|
): Promise<CheckReport> {
|
|
338
346
|
const errors: string[] = [];
|
|
339
347
|
const warnings: string[] = [];
|
|
@@ -365,6 +373,15 @@ export async function checkPlugin(
|
|
|
365
373
|
`console-plugin.json: ${path} is the same as en: translate it, or leave the language out`,
|
|
366
374
|
);
|
|
367
375
|
}
|
|
376
|
+
if (
|
|
377
|
+
manifest.console !== undefined &&
|
|
378
|
+
options.consoleVersion !== undefined &&
|
|
379
|
+
!satisfiesRange(options.consoleVersion, manifest.console)
|
|
380
|
+
) {
|
|
381
|
+
warnings.push(
|
|
382
|
+
`console-plugin.json: console ${manifest.console} leaves out ${options.consoleVersion}, the console this command belongs to`,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
368
385
|
|
|
369
386
|
const packageRead = await readJson(join(root, "package.json"));
|
|
370
387
|
let version = "0.0.0";
|
|
@@ -393,6 +410,25 @@ export async function checkPlugin(
|
|
|
393
410
|
if (pkg.type !== "module") {
|
|
394
411
|
errors.push('package.json: type is not "module"; the server module is ESM');
|
|
395
412
|
}
|
|
413
|
+
// The console the plugin is built against is the one its types come from: a devDependency
|
|
414
|
+
// outside the consoles it declares is a plugin built for a console it says it does not run on.
|
|
415
|
+
const devDependencies = (
|
|
416
|
+
typeof pkg.devDependencies === "object" && pkg.devDependencies !== null
|
|
417
|
+
? pkg.devDependencies
|
|
418
|
+
: {}
|
|
419
|
+
) as Record<string, unknown>;
|
|
420
|
+
const dependency = devDependencies["@skillcdn/console"];
|
|
421
|
+
const builtAgainst =
|
|
422
|
+
typeof dependency === "string" ? versionOfDependency(dependency) : undefined;
|
|
423
|
+
if (
|
|
424
|
+
manifest.console !== undefined &&
|
|
425
|
+
builtAgainst !== undefined &&
|
|
426
|
+
!satisfiesRange(formatTriple(builtAgainst), manifest.console)
|
|
427
|
+
) {
|
|
428
|
+
errors.push(
|
|
429
|
+
`package.json: devDependencies names @skillcdn/console ${String(dependency)}, outside the consoles the manifest declares (console ${manifest.console})`,
|
|
430
|
+
);
|
|
431
|
+
}
|
|
396
432
|
const scripts = (
|
|
397
433
|
typeof pkg.scripts === "object" && pkg.scripts !== null ? pkg.scripts : {}
|
|
398
434
|
) as Record<string, unknown>;
|
|
@@ -456,14 +492,17 @@ export async function checkPlugin(
|
|
|
456
492
|
errors.push(`${manifest.server}: ${problem}`);
|
|
457
493
|
}
|
|
458
494
|
if (
|
|
459
|
-
manifest.
|
|
495
|
+
capabilitiesAsked(manifest).includes("endpoints") &&
|
|
460
496
|
Object.keys(description.endpoints).length === 0
|
|
461
497
|
) {
|
|
462
498
|
warnings.push(
|
|
463
499
|
"console-plugin.json: asks the capability endpoints and the server module defines no endpoint",
|
|
464
500
|
);
|
|
465
501
|
}
|
|
466
|
-
if (
|
|
502
|
+
if (
|
|
503
|
+
capabilitiesAsked(manifest).includes("jobs") &&
|
|
504
|
+
Object.keys(description.jobs).length === 0
|
|
505
|
+
) {
|
|
467
506
|
warnings.push(
|
|
468
507
|
"console-plugin.json: asks the capability jobs and the server module defines no job",
|
|
469
508
|
);
|
package/src/cli/plugin-cli.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { resolve } from "node:path";
|
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
6
6
|
import type { ServerLoad } from "../plugin/load.js";
|
|
7
7
|
import { isPluginKey, RESERVED_PLUGIN_KEYS } from "../plugin/manifest.js";
|
|
8
|
+
import { lineRangeFrom, versionOfDependency } from "../plugin/semver.js";
|
|
8
9
|
import { checkPlugin } from "./plugin-check.js";
|
|
9
10
|
import { LANGUAGE_TAG, nameFromKey, PACKAGE_NAME, writePluginFromTemplate } from "./plugin-init.js";
|
|
10
11
|
|
|
@@ -176,12 +177,18 @@ async function init(args: readonly string[], io: PluginCliIo): Promise<number> {
|
|
|
176
177
|
return PLUGIN_EXIT.usage;
|
|
177
178
|
}
|
|
178
179
|
const consoleDependency = values.console ?? (io.version === undefined ? "*" : `^${io.version}`);
|
|
180
|
+
// The consoles the plugin runs on: the line of the version it is written against, read from
|
|
181
|
+
// the dependency where it names one (`^0.4.3`, `0.4.3`); a dependency that names none
|
|
182
|
+
// (`workspace:*`) leaves the manifest naming no console.
|
|
183
|
+
const writtenAgainst = versionOfDependency(consoleDependency);
|
|
184
|
+
const consoleRange = writtenAgainst === undefined ? undefined : lineRangeFrom(writtenAgainst);
|
|
179
185
|
const target = resolve(io.cwd, dir);
|
|
180
186
|
try {
|
|
181
187
|
const written = await writePluginFromTemplate(io.templateDir, target, {
|
|
182
188
|
key,
|
|
183
189
|
name,
|
|
184
190
|
consoleDependency,
|
|
191
|
+
consoleRange,
|
|
185
192
|
packageName,
|
|
186
193
|
repository:
|
|
187
194
|
values.repository === undefined
|
|
@@ -220,7 +227,9 @@ async function check(args: readonly string[], io: PluginCliIo): Promise<number>
|
|
|
220
227
|
return PLUGIN_EXIT.usage;
|
|
221
228
|
}
|
|
222
229
|
const root = resolve(io.cwd, dir ?? ".");
|
|
223
|
-
const report = await checkPlugin(root, (file) => io.loadServer(file)
|
|
230
|
+
const report = await checkPlugin(root, (file) => io.loadServer(file), {
|
|
231
|
+
consoleVersion: io.version,
|
|
232
|
+
});
|
|
224
233
|
io.stdout(`console-plugin check ${root}\n`);
|
|
225
234
|
io.stdout(wordsOf(report.errors, "error"));
|
|
226
235
|
io.stdout(wordsOf(report.warnings, "warning"));
|
package/src/cli/plugin-init.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface InitOptions {
|
|
|
13
13
|
readonly name: string;
|
|
14
14
|
/** What the plugin's devDependency on `@skillcdn/console` says: `^<version>` of the command, unless said. */
|
|
15
15
|
readonly consoleDependency: string;
|
|
16
|
+
/** The consoles the plugin runs on, the manifest's `console`: the line of the version it is written against; left out, the manifest names none. */
|
|
17
|
+
readonly consoleRange?: string | undefined;
|
|
16
18
|
/** The package's name: `console-plugin-<key>` unless said. A scoped name is published with `publishConfig.access` `public`. */
|
|
17
19
|
readonly packageName?: string | undefined;
|
|
18
20
|
/** The repository the plugin lives in and, in a repository of several, its directory: `repository`, `homepage`, `bugs` and the manifest's `store`. */
|
|
@@ -40,12 +42,18 @@ export function substitutions(options: InitOptions): readonly [string, string][]
|
|
|
40
42
|
["__package__", packageNameOf(options)],
|
|
41
43
|
["__key__", options.key],
|
|
42
44
|
["__name__", options.name],
|
|
45
|
+
["__consoleRange__", options.consoleRange ?? "*"],
|
|
43
46
|
["__console__", options.consoleDependency],
|
|
44
47
|
];
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
export function substitute(text: string, options: InitOptions): string {
|
|
48
51
|
let result = text;
|
|
52
|
+
if (options.consoleRange === undefined) {
|
|
53
|
+
// No console to name: the manifest's line goes, and any console that serves the plugin API
|
|
54
|
+
// runs the plugin, as before the field. Taken out as text, so that the file keeps its shape.
|
|
55
|
+
result = result.replace(/^[ \t]*"console": "__consoleRange__",\r?\n/m, "");
|
|
56
|
+
}
|
|
49
57
|
for (const [word, value] of substitutions(options)) {
|
|
50
58
|
result = result.replaceAll(word, value);
|
|
51
59
|
}
|