@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/cli.ts
CHANGED
|
@@ -21,12 +21,19 @@ import {
|
|
|
21
21
|
formatVersion as versionText,
|
|
22
22
|
} from "../compat.js";
|
|
23
23
|
import { addDays, dayOf as dayOfDate, firstDayOfWeek, isDay, weekOf } from "../dates.js";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
DEFAULT_PAGE_LIMIT,
|
|
26
|
+
LIST_LIMIT,
|
|
27
|
+
MAX_DECIDERS,
|
|
28
|
+
MAX_OPTIONS,
|
|
29
|
+
MIN_OPTIONS,
|
|
30
|
+
} from "../limits.js";
|
|
25
31
|
import { capabilityWords } from "../plugin/capabilities.js";
|
|
26
32
|
import { PLUGIN_LIST_LIMIT } from "../plugin/limits.js";
|
|
27
33
|
import { fillRules, RULES_PATH, RULES_TITLE } from "../rules.js";
|
|
28
34
|
import {
|
|
29
35
|
hostOfLogin,
|
|
36
|
+
type RestAnswerInput,
|
|
30
37
|
type RestClaim,
|
|
31
38
|
type RestDecision,
|
|
32
39
|
type RestMember,
|
|
@@ -77,6 +84,7 @@ import {
|
|
|
77
84
|
} from "./credentials.js";
|
|
78
85
|
import { type DirectorySettings, type DirectoryStore, THE_SENTENCE } from "./directory.js";
|
|
79
86
|
import {
|
|
87
|
+
deciderLogins,
|
|
80
88
|
formatAnswer,
|
|
81
89
|
formatCategoryLine,
|
|
82
90
|
formatComment,
|
|
@@ -94,6 +102,7 @@ import {
|
|
|
94
102
|
formatPersonLine,
|
|
95
103
|
formatPersonWork,
|
|
96
104
|
formatPlugin,
|
|
105
|
+
formatPluginAsks,
|
|
97
106
|
formatPluginGrantLine,
|
|
98
107
|
formatPluginHelp,
|
|
99
108
|
formatPluginRecord,
|
|
@@ -108,6 +117,7 @@ import {
|
|
|
108
117
|
formatRunLine,
|
|
109
118
|
formatTask,
|
|
110
119
|
formatTaskLine,
|
|
120
|
+
formatTeamLine,
|
|
111
121
|
formatVersion,
|
|
112
122
|
formatVersionLine,
|
|
113
123
|
formatWork,
|
|
@@ -625,9 +635,14 @@ function theTwo(positionals: readonly string[], ask: string): readonly [string,
|
|
|
625
635
|
* The decision once answered, or as it stands when the wait is over. Each request asks the
|
|
626
636
|
* console to hold it for a while, so the answer arrives as soon as a person gives it.
|
|
627
637
|
*/
|
|
638
|
+
/** Whatever waits for a decision's answer: the project's client, or the decision wherever it is (ADR-0068). */
|
|
639
|
+
interface Waiter {
|
|
640
|
+
awaitDecision(id: string, waitSeconds: number): Promise<RestDecision>;
|
|
641
|
+
}
|
|
642
|
+
|
|
628
643
|
async function waitForAnswer(
|
|
629
644
|
io: CliIo,
|
|
630
|
-
project:
|
|
645
|
+
project: Waiter,
|
|
631
646
|
decision: RestDecision,
|
|
632
647
|
seconds: number,
|
|
633
648
|
): Promise<RestDecision> {
|
|
@@ -658,7 +673,7 @@ const stillWaiting = (id: string): string =>
|
|
|
658
673
|
async function settle(
|
|
659
674
|
io: CliIo,
|
|
660
675
|
session: Session,
|
|
661
|
-
project:
|
|
676
|
+
project: Waiter,
|
|
662
677
|
decision: RestDecision,
|
|
663
678
|
seconds: number,
|
|
664
679
|
shown = false,
|
|
@@ -1526,6 +1541,8 @@ const plugin: Command = async (args, io) => {
|
|
|
1526
1541
|
);
|
|
1527
1542
|
return EXIT.ok;
|
|
1528
1543
|
}
|
|
1544
|
+
case "capabilities":
|
|
1545
|
+
return pluginCapabilities(io, session, client, rest);
|
|
1529
1546
|
case "settings":
|
|
1530
1547
|
return pluginSettings(io, session, client, rest);
|
|
1531
1548
|
case "secret":
|
|
@@ -1787,7 +1804,60 @@ function capabilityLines(capabilities: readonly string[]): string[] {
|
|
|
1787
1804
|
});
|
|
1788
1805
|
}
|
|
1789
1806
|
|
|
1790
|
-
/**
|
|
1807
|
+
/** The capabilities `--consent` names, given again or with commas between. */
|
|
1808
|
+
function capabilitiesNamed(values: Values): string[] {
|
|
1809
|
+
return texts(values, "consent")
|
|
1810
|
+
.flatMap((value) => value.split(","))
|
|
1811
|
+
.map((capability) => capability.trim())
|
|
1812
|
+
.filter((capability) => capability.length > 0);
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
/** What a plugin requires, and which of what it can go without it holds, as `capabilities` prints it. */
|
|
1816
|
+
function capabilitiesHeld(detail: RestPluginDetail): string {
|
|
1817
|
+
const held = detail.consent ?? [];
|
|
1818
|
+
const optional = detail.optionalCapabilities ?? [];
|
|
1819
|
+
const named = (capabilities: readonly string[]) =>
|
|
1820
|
+
capabilities.length === 0 ? "none" : capabilities.join(", ");
|
|
1821
|
+
return [
|
|
1822
|
+
`Capabilities of ${detail.key}:`,
|
|
1823
|
+
` required: ${named(detail.capabilities)}`,
|
|
1824
|
+
` optional, on: ${named(optional.filter((capability) => held.includes(capability)))}`,
|
|
1825
|
+
` optional, off: ${named(optional.filter((capability) => !held.includes(capability)))}`,
|
|
1826
|
+
].join("\n");
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
/**
|
|
1830
|
+
* What a plugin holds; with `--consent`, which of the optional capabilities it holds from now on,
|
|
1831
|
+
* `none` for none of them. An administrator's.
|
|
1832
|
+
*/
|
|
1833
|
+
async function pluginCapabilities(
|
|
1834
|
+
io: CliIo,
|
|
1835
|
+
session: Session,
|
|
1836
|
+
client: PluginClient,
|
|
1837
|
+
rest: readonly string[],
|
|
1838
|
+
): Promise<number> {
|
|
1839
|
+
const { values, positionals } = parse(rest, { consent: { type: "string", multiple: true } });
|
|
1840
|
+
if (positionals.length > 0) {
|
|
1841
|
+
throw misuse(
|
|
1842
|
+
`Nothing follows capabilities but --consent: console plugin ${client.key} capabilities [--consent <capability>,...|none]`,
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
const detail = await client.get();
|
|
1846
|
+
const named = capabilitiesNamed(values);
|
|
1847
|
+
if (named.length === 0) {
|
|
1848
|
+
answer(io, session, detail, () => capabilitiesHeld(detail));
|
|
1849
|
+
return EXIT.ok;
|
|
1850
|
+
}
|
|
1851
|
+
const chosen = named.filter((capability) => capability !== "none");
|
|
1852
|
+
const changed = await client.updateConsent([
|
|
1853
|
+
...detail.capabilities,
|
|
1854
|
+
...chosen.filter((capability) => !detail.capabilities.includes(capability)),
|
|
1855
|
+
]);
|
|
1856
|
+
answer(io, session, changed, () => capabilitiesHeld(changed));
|
|
1857
|
+
return EXIT.ok;
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
/** Enables a plugin, with consent to every capability it requires, named again; an administrator's. */
|
|
1791
1861
|
async function pluginEnable(
|
|
1792
1862
|
io: CliIo,
|
|
1793
1863
|
session: Session,
|
|
@@ -1807,19 +1877,22 @@ async function pluginEnable(
|
|
|
1807
1877
|
}
|
|
1808
1878
|
const [detail, me] = await Promise.all([client.get(), session.client.me()]);
|
|
1809
1879
|
const asked = detail.capabilities;
|
|
1810
|
-
const
|
|
1811
|
-
|
|
1812
|
-
.map((capability) => capability.trim())
|
|
1813
|
-
.filter((capability) => capability.length > 0);
|
|
1880
|
+
const optional = detail.optionalCapabilities ?? [];
|
|
1881
|
+
const consent = capabilitiesNamed(values);
|
|
1814
1882
|
if (asked.length > 0 && !session.json) {
|
|
1815
1883
|
io.stdout(
|
|
1816
|
-
`${detail.key}
|
|
1884
|
+
`${detail.key} requires ${asked.length === 1 ? "one capability" : `${asked.length} capabilities`}:\n${capabilityLines(asked).join("\n")}\n`,
|
|
1885
|
+
);
|
|
1886
|
+
}
|
|
1887
|
+
if (optional.length > 0 && !session.json) {
|
|
1888
|
+
io.stdout(
|
|
1889
|
+
`It can go without these, which it holds if --consent names them too:\n${capabilityLines(optional).join("\n")}\n`,
|
|
1817
1890
|
);
|
|
1818
1891
|
}
|
|
1819
1892
|
const missing = asked.filter((capability) => !consent.includes(capability));
|
|
1820
1893
|
if (missing.length > 0) {
|
|
1821
1894
|
throw misuse(
|
|
1822
|
-
`Enabling ${detail.key} needs consent to every capability it
|
|
1895
|
+
`Enabling ${detail.key} needs consent to every capability it requires, named again: --consent ${asked.join(",")}. Not named: ${missing.join(", ")}.`,
|
|
1823
1896
|
);
|
|
1824
1897
|
}
|
|
1825
1898
|
const boardFlag = text(values, "board");
|
|
@@ -1854,12 +1927,18 @@ async function pluginEnable(
|
|
|
1854
1927
|
answer(io, session, enabled, () =>
|
|
1855
1928
|
[
|
|
1856
1929
|
`Enabled ${enabled.key}${enabled.board === null ? "" : `, with the board project ${enabled.board.key}`}; on ${enabled.projects === "all" || enabled.projects === null ? "every project" : enabled.projects.join(", ") || "no project"}.`,
|
|
1930
|
+
...(enabled.needsTimeZone === true
|
|
1931
|
+
? [
|
|
1932
|
+
"The workspace has no time zone: the plugin's times of day run on UTC until an administrator sets one, with console workspace edit --time-zone <zone>.",
|
|
1933
|
+
]
|
|
1934
|
+
: []),
|
|
1857
1935
|
...(enabled.settings.length === 0
|
|
1858
1936
|
? []
|
|
1859
1937
|
: [
|
|
1860
1938
|
"Settings:",
|
|
1861
1939
|
...enabled.settings.map((setting) => formatPluginSettingLine(setting, me.language)),
|
|
1862
1940
|
]),
|
|
1941
|
+
...formatPluginAsks(enabled, me.language),
|
|
1863
1942
|
].join("\n"),
|
|
1864
1943
|
);
|
|
1865
1944
|
return EXIT.ok;
|
|
@@ -1934,7 +2013,10 @@ async function pluginSecret(
|
|
|
1934
2013
|
return EXIT.ok;
|
|
1935
2014
|
}
|
|
1936
2015
|
|
|
1937
|
-
/**
|
|
2016
|
+
/**
|
|
2017
|
+
* Grants a role of the plugin to a person, to the members of a project, to another plugin, to the
|
|
2018
|
+
* members of a team, or to everyone; an administrator's.
|
|
2019
|
+
*/
|
|
1938
2020
|
async function pluginGrant(
|
|
1939
2021
|
io: CliIo,
|
|
1940
2022
|
session: Session,
|
|
@@ -1944,14 +2026,26 @@ async function pluginGrant(
|
|
|
1944
2026
|
): Promise<number> {
|
|
1945
2027
|
const { values, positionals } = parse(rest, {
|
|
1946
2028
|
person: { type: "string" },
|
|
2029
|
+
plugin: { type: "string" },
|
|
2030
|
+
team: { type: "string" },
|
|
1947
2031
|
everyone: { type: "boolean" },
|
|
1948
2032
|
});
|
|
1949
|
-
const usage = `console plugin ${client.key} grant <role> --person <login> | --project <key> | --everyone`;
|
|
2033
|
+
const usage = `console plugin ${client.key} grant <role> --person <login> | --project <key> | --plugin <key> | --team <key> | --everyone`;
|
|
1950
2034
|
const role = theOne(positionals, `Say which role: ${usage}`);
|
|
1951
2035
|
const login = text(values, "person");
|
|
2036
|
+
const other = text(values, "plugin");
|
|
2037
|
+
const teamKey = text(values, "team");
|
|
1952
2038
|
const everyone = on(values, "everyone");
|
|
1953
|
-
if (
|
|
1954
|
-
|
|
2039
|
+
if (
|
|
2040
|
+
[
|
|
2041
|
+
login !== undefined,
|
|
2042
|
+
projectKey !== undefined,
|
|
2043
|
+
other !== undefined,
|
|
2044
|
+
teamKey !== undefined,
|
|
2045
|
+
everyone,
|
|
2046
|
+
].filter(Boolean).length !== 1
|
|
2047
|
+
) {
|
|
2048
|
+
throw misuse(`Say whom, one of the five: ${usage}`);
|
|
1955
2049
|
}
|
|
1956
2050
|
let input: RestPluginGrantInput;
|
|
1957
2051
|
if (login !== undefined) {
|
|
@@ -1962,6 +2056,10 @@ async function pluginGrant(
|
|
|
1962
2056
|
input = { role, person: person.id };
|
|
1963
2057
|
} else if (projectKey !== undefined) {
|
|
1964
2058
|
input = { role, project: projectKey };
|
|
2059
|
+
} else if (other !== undefined) {
|
|
2060
|
+
input = { role, plugin: other };
|
|
2061
|
+
} else if (teamKey !== undefined) {
|
|
2062
|
+
input = { role, team: teamKey };
|
|
1965
2063
|
} else {
|
|
1966
2064
|
input = { role, everyone: true };
|
|
1967
2065
|
}
|
|
@@ -2070,7 +2168,7 @@ const rules: Command = async (args, io) => {
|
|
|
2070
2168
|
|
|
2071
2169
|
/**
|
|
2072
2170
|
* The rules as they read for the person whose token the command holds: `{language}` filled
|
|
2073
|
-
* with the language they
|
|
2171
|
+
* with the language they read in, or with the task's while they have none.
|
|
2074
2172
|
*/
|
|
2075
2173
|
function forPerson<T extends { readonly body: string | null }>(
|
|
2076
2174
|
half: T,
|
|
@@ -2530,16 +2628,19 @@ const ask: Command = async (args, io) => {
|
|
|
2530
2628
|
file: { type: "string" },
|
|
2531
2629
|
run: { type: "string" },
|
|
2532
2630
|
wait: { type: "string" },
|
|
2533
|
-
to: { type: "string" },
|
|
2631
|
+
to: { type: "string", multiple: true },
|
|
2534
2632
|
attach: { type: "string", multiple: true },
|
|
2535
2633
|
});
|
|
2536
2634
|
const question = theOne(
|
|
2537
2635
|
positionals,
|
|
2538
2636
|
'Say the question: console ask "<question>" --option "<label>" --option "<label>"',
|
|
2539
2637
|
);
|
|
2540
|
-
const
|
|
2541
|
-
if (
|
|
2542
|
-
throw misuse("Say whom the decision is for: --to <login|me
|
|
2638
|
+
const tos = texts(values, "to");
|
|
2639
|
+
if (tos.includes("none")) {
|
|
2640
|
+
throw misuse("Say whom the decision is for: --to <login|me>, given again for several.");
|
|
2641
|
+
}
|
|
2642
|
+
if (tos.length > MAX_DECIDERS) {
|
|
2643
|
+
throw misuse(`Name up to ${MAX_DECIDERS} people with --to.`);
|
|
2543
2644
|
}
|
|
2544
2645
|
const options = texts(values, "option");
|
|
2545
2646
|
if (options.length < MIN_OPTIONS || options.length > MAX_OPTIONS) {
|
|
@@ -2552,15 +2653,26 @@ const ask: Command = async (args, io) => {
|
|
|
2552
2653
|
const session = await open(io, values);
|
|
2553
2654
|
const project = await session.project();
|
|
2554
2655
|
const runId = await currentRun(project, values);
|
|
2555
|
-
// Whom the decision is for (ADR-0042): the person the agent works for, unless --to
|
|
2556
|
-
|
|
2656
|
+
// Whom the decision is for (ADR-0042, ADR-0057): the person the agent works for, unless --to
|
|
2657
|
+
// names others of the project, each once; several, and any of them may answer.
|
|
2658
|
+
const deciderIds: string[] = [];
|
|
2659
|
+
for (const to of tos) {
|
|
2660
|
+
const id = await personIdOf(session, to);
|
|
2661
|
+
if (typeof id === "string" && !deciderIds.includes(id)) {
|
|
2662
|
+
deciderIds.push(id);
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2557
2665
|
const raised = await project.raiseDecision(
|
|
2558
2666
|
{
|
|
2559
2667
|
question,
|
|
2560
2668
|
options: options.map((label) => ({ label })),
|
|
2561
2669
|
runId,
|
|
2562
2670
|
...(body === undefined ? {} : { body }),
|
|
2563
|
-
...(
|
|
2671
|
+
...(deciderIds.length === 0
|
|
2672
|
+
? {}
|
|
2673
|
+
: deciderIds.length === 1
|
|
2674
|
+
? { deciderId: deciderIds[0] }
|
|
2675
|
+
: { deciderId: deciderIds[0], deciderIds }),
|
|
2564
2676
|
},
|
|
2565
2677
|
files,
|
|
2566
2678
|
);
|
|
@@ -2570,7 +2682,7 @@ const ask: Command = async (args, io) => {
|
|
|
2570
2682
|
io.stdout(
|
|
2571
2683
|
session.json
|
|
2572
2684
|
? `${JSON.stringify(raised, null, 2)}\n`
|
|
2573
|
-
: `Decision ${raised.id} raised on run ${runId}${withFiles}; the run waits for ${raised
|
|
2685
|
+
: `Decision ${raised.id} raised on run ${runId}${withFiles}; the run waits for ${deciderLogins(raised)}.\ndecision id: ${raised.id}\n${formatOptions(raised)}\n`,
|
|
2574
2686
|
);
|
|
2575
2687
|
return settle(io, session, project, raised, seconds, true);
|
|
2576
2688
|
};
|
|
@@ -2600,8 +2712,7 @@ const answering: Command = async (args, io) => {
|
|
|
2600
2712
|
}
|
|
2601
2713
|
const note = await bodyOf(io, values, text(values, "note"));
|
|
2602
2714
|
const session = await open(io, values);
|
|
2603
|
-
const
|
|
2604
|
-
const answered = await project.answerDecision(id, {
|
|
2715
|
+
const answered = await answerReached(session, id, {
|
|
2605
2716
|
...(own === undefined ? { option } : { own }),
|
|
2606
2717
|
...(note === undefined ? {} : { note }),
|
|
2607
2718
|
});
|
|
@@ -2609,6 +2720,75 @@ const answering: Command = async (args, io) => {
|
|
|
2609
2720
|
return EXIT.ok;
|
|
2610
2721
|
};
|
|
2611
2722
|
|
|
2723
|
+
/** Whether a refusal says the decision is not in the project the command works in, or the project is not the person's. */
|
|
2724
|
+
const notInProject = (error: unknown): boolean =>
|
|
2725
|
+
error instanceof ApiError &&
|
|
2726
|
+
(error.code === "decision.not_found" || error.code === "project.not_found");
|
|
2727
|
+
|
|
2728
|
+
/**
|
|
2729
|
+
* One decision as the command reaches it (ADR-0068): in the project the command works in, as
|
|
2730
|
+
* before; else wherever it is, addressed to the person, raised by them, or a plugin's addressed
|
|
2731
|
+
* to someone they manage, through the console's own route for one decision. A plugin's decision
|
|
2732
|
+
* waits in the plugin's board project, which a checkout bound to another project does not name,
|
|
2733
|
+
* and which the person may not be in at all. A console from before that route answers as before.
|
|
2734
|
+
*/
|
|
2735
|
+
async function reachDecision(
|
|
2736
|
+
session: Session,
|
|
2737
|
+
id: string,
|
|
2738
|
+
): Promise<{ readonly decision: RestDecision; readonly waiter: Waiter }> {
|
|
2739
|
+
if ((await session.projectKey()) !== undefined) {
|
|
2740
|
+
const project = await session.project();
|
|
2741
|
+
try {
|
|
2742
|
+
return { decision: await project.decision(id), waiter: project };
|
|
2743
|
+
} catch (error) {
|
|
2744
|
+
if (!notInProject(error)) {
|
|
2745
|
+
throw error;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
const anywhere = session.client;
|
|
2750
|
+
try {
|
|
2751
|
+
const reached = await anywhere.decision(id);
|
|
2752
|
+
return {
|
|
2753
|
+
decision: reached.decision,
|
|
2754
|
+
waiter: {
|
|
2755
|
+
awaitDecision: async (decisionId, seconds) =>
|
|
2756
|
+
(await anywhere.awaitDecision(decisionId, seconds)).decision,
|
|
2757
|
+
},
|
|
2758
|
+
};
|
|
2759
|
+
} catch (error) {
|
|
2760
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
2761
|
+
throw new ApiError(404, "decision.not_found", "The decision was not found.");
|
|
2762
|
+
}
|
|
2763
|
+
throw error;
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
/** Writes an answer on a decision as the command reaches it (ADR-0068): in the project, else wherever it is. */
|
|
2768
|
+
async function answerReached(
|
|
2769
|
+
session: Session,
|
|
2770
|
+
id: string,
|
|
2771
|
+
input: RestAnswerInput,
|
|
2772
|
+
): Promise<RestDecision> {
|
|
2773
|
+
if ((await session.projectKey()) !== undefined) {
|
|
2774
|
+
try {
|
|
2775
|
+
return await (await session.project()).answerDecision(id, input);
|
|
2776
|
+
} catch (error) {
|
|
2777
|
+
if (!notInProject(error)) {
|
|
2778
|
+
throw error;
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
try {
|
|
2783
|
+
return await session.client.answerDecision(id, input);
|
|
2784
|
+
} catch (error) {
|
|
2785
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
2786
|
+
throw new ApiError(404, "decision.not_found", "The decision was not found.");
|
|
2787
|
+
}
|
|
2788
|
+
throw error;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2612
2792
|
const decision: Command = async (args, io) => {
|
|
2613
2793
|
const { values, positionals } = parse(args, {
|
|
2614
2794
|
wait: { type: "string" },
|
|
@@ -2622,19 +2802,19 @@ const decision: Command = async (args, io) => {
|
|
|
2622
2802
|
const seconds = secondsOf(text(values, "wait"), 0);
|
|
2623
2803
|
const outcome = await bodyOf(io, values, text(values, "outcome"));
|
|
2624
2804
|
const session = await open(io, values);
|
|
2625
|
-
const project = await session.project();
|
|
2626
2805
|
if (outcome !== undefined) {
|
|
2627
2806
|
// What followed the decision: the record grows, and is printed as it stands.
|
|
2807
|
+
const project = await session.project();
|
|
2628
2808
|
const grown = await project.updateDecision(id, { outcome });
|
|
2629
2809
|
answer(io, session, grown, () => formatDecision(grown));
|
|
2630
2810
|
return EXIT.ok;
|
|
2631
2811
|
}
|
|
2632
|
-
const found = await
|
|
2812
|
+
const { decision: found, waiter } = await reachDecision(session, id);
|
|
2633
2813
|
if (seconds === 0) {
|
|
2634
2814
|
answer(io, session, found, () => formatDecision(found));
|
|
2635
2815
|
return EXIT.ok;
|
|
2636
2816
|
}
|
|
2637
|
-
return settle(io, session,
|
|
2817
|
+
return settle(io, session, waiter, found, seconds);
|
|
2638
2818
|
};
|
|
2639
2819
|
|
|
2640
2820
|
const docs: Command = async (args, io) => {
|
|
@@ -3119,12 +3299,94 @@ const people: Command = async (args, io) => {
|
|
|
3119
3299
|
const { values } = parse(args, {}, false);
|
|
3120
3300
|
const session = await open(io, values);
|
|
3121
3301
|
const found = await session.client.people();
|
|
3302
|
+
const loginOf = new Map(found.items.map((person) => [person.id, person.login]));
|
|
3303
|
+
answer(io, session, found, () =>
|
|
3304
|
+
[
|
|
3305
|
+
...found.items.map((person) =>
|
|
3306
|
+
formatPersonLine(
|
|
3307
|
+
person,
|
|
3308
|
+
person.managerId === null || person.managerId === undefined
|
|
3309
|
+
? undefined
|
|
3310
|
+
: (loginOf.get(person.managerId) ?? person.managerId),
|
|
3311
|
+
),
|
|
3312
|
+
),
|
|
3313
|
+
...found.listed.map(formatListingLine),
|
|
3314
|
+
].join("\n"),
|
|
3315
|
+
);
|
|
3316
|
+
return EXIT.ok;
|
|
3317
|
+
};
|
|
3318
|
+
|
|
3319
|
+
/** The teams of the workspace (ADR-0073): each with who is in it, by login. */
|
|
3320
|
+
const teams: Command = async (args, io) => {
|
|
3321
|
+
const { values } = parse(args, {}, false);
|
|
3322
|
+
const session = await open(io, values);
|
|
3323
|
+
const [found, everyone] = await Promise.all([session.client.teams(), session.client.people()]);
|
|
3324
|
+
const loginOf = new Map(everyone.items.map((person) => [person.id, person.login]));
|
|
3122
3325
|
answer(io, session, found, () =>
|
|
3123
|
-
|
|
3326
|
+
found.items.length === 0
|
|
3327
|
+
? "No teams yet. console team new <key> <name> makes one."
|
|
3328
|
+
: found.items.map((team) => formatTeamLine(team, loginOf)).join("\n"),
|
|
3124
3329
|
);
|
|
3125
3330
|
return EXIT.ok;
|
|
3126
3331
|
};
|
|
3127
3332
|
|
|
3333
|
+
const TEAM_USAGE =
|
|
3334
|
+
'Say what to do: console team new <key> "<name>" | team rename <key> "<new name>" | team remove <key> | team add <key> <login> | team drop <key> <login>';
|
|
3335
|
+
|
|
3336
|
+
// A team is called by its key (ADR-0074), which never changes; its name is what people read.
|
|
3337
|
+
const team: Command = async (args, io) => {
|
|
3338
|
+
const what = args[0];
|
|
3339
|
+
if (what === "new") {
|
|
3340
|
+
const { values, positionals } = parse(args.slice(1), {});
|
|
3341
|
+
const [key, name] = theTwo(
|
|
3342
|
+
positionals,
|
|
3343
|
+
'Say the key and the name: console team new <key> "<name>"',
|
|
3344
|
+
);
|
|
3345
|
+
const session = await open(io, values);
|
|
3346
|
+
const made = await session.client.createTeam({ key, name });
|
|
3347
|
+
answer(io, session, made, () => formatTeamLine(made, new Map()));
|
|
3348
|
+
return EXIT.ok;
|
|
3349
|
+
}
|
|
3350
|
+
if (what === "rename") {
|
|
3351
|
+
const { values, positionals } = parse(args.slice(1), {});
|
|
3352
|
+
const [key, next] = theTwo(
|
|
3353
|
+
positionals,
|
|
3354
|
+
'Say which team and its new name: console team rename <key> "<new name>"',
|
|
3355
|
+
);
|
|
3356
|
+
const session = await open(io, values);
|
|
3357
|
+
const changed = await session.client.renameTeam(key, { name: next });
|
|
3358
|
+
answer(io, session, changed, () => formatTeamLine(changed, new Map()));
|
|
3359
|
+
return EXIT.ok;
|
|
3360
|
+
}
|
|
3361
|
+
if (what === "remove") {
|
|
3362
|
+
const { values, positionals } = parse(args.slice(1), {});
|
|
3363
|
+
const key = theOne(positionals, "Say which team: console team remove <key>");
|
|
3364
|
+
const session = await open(io, values);
|
|
3365
|
+
await session.client.removeTeam(key);
|
|
3366
|
+
answer(io, session, { removed: key }, () => `The team ${key} is no more.`);
|
|
3367
|
+
return EXIT.ok;
|
|
3368
|
+
}
|
|
3369
|
+
if (what === "add" || what === "drop") {
|
|
3370
|
+
const { values, positionals } = parse(args.slice(1), {});
|
|
3371
|
+
const [key, login] = theTwo(
|
|
3372
|
+
positionals,
|
|
3373
|
+
`Say which team and whom: console team ${what} <key> <login>`,
|
|
3374
|
+
);
|
|
3375
|
+
const session = await open(io, values);
|
|
3376
|
+
const person = await personOf(session, login);
|
|
3377
|
+
const changed =
|
|
3378
|
+
what === "add"
|
|
3379
|
+
? await session.client.addTeamMember(key, person.id)
|
|
3380
|
+
: await session.client.removeTeamMember(key, person.id);
|
|
3381
|
+
const everyone = await session.client.people();
|
|
3382
|
+
answer(io, session, changed, () =>
|
|
3383
|
+
formatTeamLine(changed, new Map(everyone.items.map((one) => [one.id, one.login]))),
|
|
3384
|
+
);
|
|
3385
|
+
return EXIT.ok;
|
|
3386
|
+
}
|
|
3387
|
+
throw misuse(TEAM_USAGE);
|
|
3388
|
+
};
|
|
3389
|
+
|
|
3128
3390
|
// The workspace's settings (ADR-0035): read by everyone, changed by an administrator, themselves
|
|
3129
3391
|
// on the Settings page or through their agent here (ADR-0021).
|
|
3130
3392
|
const WORKSPACE_FLAGS =
|
|
@@ -3201,7 +3463,7 @@ const workspace: Command = async (args, io) => {
|
|
|
3201
3463
|
};
|
|
3202
3464
|
|
|
3203
3465
|
const PERSON_USAGE =
|
|
3204
|
-
"Say what to do: console person role <login> <admin|member> | person add <login> [--host gh|google] | person remove <login> | person deactivate <login> | person reactivate <login>";
|
|
3466
|
+
"Say what to do: console 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>";
|
|
3205
3467
|
|
|
3206
3468
|
const person: Command = async (args, io) => {
|
|
3207
3469
|
const what = args[0];
|
|
@@ -3218,6 +3480,35 @@ const person: Command = async (args, io) => {
|
|
|
3218
3480
|
answer(io, session, changed, () => formatPersonLine(changed));
|
|
3219
3481
|
return EXIT.ok;
|
|
3220
3482
|
}
|
|
3483
|
+
if (what === "email") {
|
|
3484
|
+
const { values, positionals } = parse(args.slice(1), {});
|
|
3485
|
+
const [login, address] = theTwo(
|
|
3486
|
+
positionals,
|
|
3487
|
+
"Say whom and their address: console person email <login> <address|none>",
|
|
3488
|
+
);
|
|
3489
|
+
const session = await open(io, values);
|
|
3490
|
+
const found = await personOf(session, login);
|
|
3491
|
+
const changed = await session.client.updatePerson(found.id, {
|
|
3492
|
+
email: address === "none" ? null : address,
|
|
3493
|
+
});
|
|
3494
|
+
answer(io, session, changed, () => formatPersonLine(changed));
|
|
3495
|
+
return EXIT.ok;
|
|
3496
|
+
}
|
|
3497
|
+
if (what === "manager") {
|
|
3498
|
+
const { values, positionals } = parse(args.slice(1), {});
|
|
3499
|
+
const [login, manager] = theTwo(
|
|
3500
|
+
positionals,
|
|
3501
|
+
"Say whom and whom they report to: console person manager <login> <manager login|none>",
|
|
3502
|
+
);
|
|
3503
|
+
const session = await open(io, values);
|
|
3504
|
+
const found = await personOf(session, login);
|
|
3505
|
+
const managerId = manager === "none" ? null : (await personOf(session, manager)).id;
|
|
3506
|
+
const changed = await session.client.updatePerson(found.id, { managerId });
|
|
3507
|
+
answer(io, session, changed, () =>
|
|
3508
|
+
formatPersonLine(changed, managerId === null ? undefined : manager),
|
|
3509
|
+
);
|
|
3510
|
+
return EXIT.ok;
|
|
3511
|
+
}
|
|
3221
3512
|
if (what === "add") {
|
|
3222
3513
|
const { values, positionals } = parse(args.slice(1), { host: { type: "string" } });
|
|
3223
3514
|
const login = theOne(positionals, "Say whom: console person add <login> [--host gh|google]");
|
|
@@ -3664,6 +3955,8 @@ const COMMANDS: Readonly<Record<string, Command>> = {
|
|
|
3664
3955
|
plugin,
|
|
3665
3956
|
people,
|
|
3666
3957
|
person,
|
|
3958
|
+
teams,
|
|
3959
|
+
team,
|
|
3667
3960
|
workspace,
|
|
3668
3961
|
project,
|
|
3669
3962
|
members,
|