@skillcdn/console 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -2
- package/SKILLCDN.md +7 -5
- package/dist/api.d.ts +5 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +5 -4
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +579 -45
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +34 -3
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +277 -6
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +60 -7
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/plugin-check.d.ts +21 -0
- package/dist/cli/plugin-check.d.ts.map +1 -0
- package/dist/cli/plugin-check.js +374 -0
- package/dist/cli/plugin-check.js.map +1 -0
- package/dist/cli/plugin-cli.d.ts +20 -0
- package/dist/cli/plugin-cli.d.ts.map +1 -0
- package/dist/cli/plugin-cli.js +123 -0
- package/dist/cli/plugin-cli.js.map +1 -0
- package/dist/cli/plugin-init.d.ts +20 -0
- package/dist/cli/plugin-init.d.ts.map +1 -0
- package/dist/cli/plugin-init.js +74 -0
- package/dist/cli/plugin-init.js.map +1 -0
- package/dist/cli/plugin-main.d.ts +3 -0
- package/dist/cli/plugin-main.d.ts.map +1 -0
- package/dist/cli/plugin-main.js +28 -0
- package/dist/cli/plugin-main.js.map +1 -0
- package/dist/client.d.ts +109 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +67 -2
- package/dist/client.js.map +1 -1
- package/dist/compat.d.ts +1 -1
- package/dist/compat.js +2 -2
- package/dist/components/board.d.ts +8 -0
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +59 -15
- package/dist/components/board.js.map +1 -1
- package/dist/components/event-feed.d.ts +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +33 -1
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +6 -3
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/pack-connect.d.ts +3 -1
- package/dist/components/pack-connect.d.ts.map +1 -1
- package/dist/components/pack-connect.js +4 -4
- package/dist/components/pack-connect.js.map +1 -1
- package/dist/components/pack-token.d.ts +3 -1
- package/dist/components/pack-token.d.ts.map +1 -1
- package/dist/components/pack-token.js +1 -1
- package/dist/components/pack-token.js.map +1 -1
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +14 -3
- package/dist/components/people.js.map +1 -1
- package/dist/components/plugin-frame.d.ts +52 -0
- package/dist/components/plugin-frame.d.ts.map +1 -0
- package/dist/components/plugin-frame.js +314 -0
- package/dist/components/plugin-frame.js.map +1 -0
- package/dist/components/plugin-settings.d.ts +38 -0
- package/dist/components/plugin-settings.d.ts.map +1 -0
- package/dist/components/plugin-settings.js +348 -0
- package/dist/components/plugin-settings.js.map +1 -0
- package/dist/components/remote-pictures.d.ts +6 -0
- package/dist/components/remote-pictures.d.ts.map +1 -0
- package/dist/components/remote-pictures.js +8 -0
- package/dist/components/remote-pictures.js.map +1 -0
- package/dist/components/shell.d.ts +2 -0
- package/dist/components/shell.d.ts.map +1 -1
- package/dist/components/shell.js +26 -1
- package/dist/components/shell.js.map +1 -1
- package/dist/components/sign-in.d.ts.map +1 -1
- package/dist/components/sign-in.js +49 -2
- package/dist/components/sign-in.js.map +1 -1
- package/dist/components/ui.d.ts +3 -2
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +6 -3
- package/dist/components/ui.js.map +1 -1
- package/dist/components/workspace.d.ts +7 -1
- package/dist/components/workspace.d.ts.map +1 -1
- package/dist/components/workspace.js +1 -1
- package/dist/components/workspace.js.map +1 -1
- package/dist/console.css +384 -5
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +146 -29
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +11 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +35 -21
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +163 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +174 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +154 -1
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +14 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +14 -0
- package/dist/limits.js.map +1 -1
- package/dist/plugin/args.d.ts +148 -0
- package/dist/plugin/args.d.ts.map +1 -0
- package/dist/plugin/args.js +369 -0
- package/dist/plugin/args.js.map +1 -0
- package/dist/plugin/bridge.d.ts +146 -0
- package/dist/plugin/bridge.d.ts.map +1 -0
- package/dist/plugin/bridge.js +125 -0
- package/dist/plugin/bridge.js.map +1 -0
- package/dist/plugin/capabilities.d.ts +20 -0
- package/dist/plugin/capabilities.d.ts.map +1 -0
- package/dist/plugin/capabilities.js +133 -0
- package/dist/plugin/capabilities.js.map +1 -0
- package/dist/plugin/context.d.ts +374 -0
- package/dist/plugin/context.d.ts.map +1 -0
- package/dist/plugin/context.js +2 -0
- package/dist/plugin/context.js.map +1 -0
- package/dist/plugin/define.d.ts +208 -0
- package/dist/plugin/define.d.ts.map +1 -0
- package/dist/plugin/define.js +482 -0
- package/dist/plugin/define.js.map +1 -0
- package/dist/plugin/definition-error.d.ts +11 -0
- package/dist/plugin/definition-error.d.ts.map +1 -0
- package/dist/plugin/definition-error.js +17 -0
- package/dist/plugin/definition-error.js.map +1 -0
- package/dist/plugin/limits.d.ts +67 -0
- package/dist/plugin/limits.d.ts.map +1 -0
- package/dist/plugin/limits.js +70 -0
- package/dist/plugin/limits.js.map +1 -0
- package/dist/plugin/load-worker.d.ts +7 -0
- package/dist/plugin/load-worker.d.ts.map +1 -0
- package/dist/plugin/load-worker.js +41 -0
- package/dist/plugin/load-worker.js.map +1 -0
- package/dist/plugin/load.d.ts +18 -0
- package/dist/plugin/load.d.ts.map +1 -0
- package/dist/plugin/load.js +62 -0
- package/dist/plugin/load.js.map +1 -0
- package/dist/plugin/manifest.d.ts +237 -0
- package/dist/plugin/manifest.d.ts.map +1 -0
- package/dist/plugin/manifest.js +411 -0
- package/dist/plugin/manifest.js.map +1 -0
- package/dist/plugin/refusals.d.ts +49 -0
- package/dist/plugin/refusals.d.ts.map +1 -0
- package/dist/plugin/refusals.js +79 -0
- package/dist/plugin/refusals.js.map +1 -0
- package/dist/plugin/rest-schemas.d.ts +556 -0
- package/dist/plugin/rest-schemas.d.ts.map +1 -0
- package/dist/plugin/rest-schemas.js +262 -0
- package/dist/plugin/rest-schemas.js.map +1 -0
- package/dist/plugin/sandbox.d.ts +23 -0
- package/dist/plugin/sandbox.d.ts.map +1 -0
- package/dist/plugin/sandbox.js +99 -0
- package/dist/plugin/sandbox.js.map +1 -0
- package/dist/plugin/testing.d.ts +45 -0
- package/dist/plugin/testing.d.ts.map +1 -0
- package/dist/plugin/testing.js +420 -0
- package/dist/plugin/testing.js.map +1 -0
- package/dist/plugin/web-react.d.ts +11 -0
- package/dist/plugin/web-react.d.ts.map +1 -0
- package/dist/plugin/web-react.js +34 -0
- package/dist/plugin/web-react.js.map +1 -0
- package/dist/plugin/web.d.ts +95 -0
- package/dist/plugin/web.d.ts.map +1 -0
- package/dist/plugin/web.js +245 -0
- package/dist/plugin/web.js.map +1 -0
- package/dist/plugin-nav.d.ts +29 -0
- package/dist/plugin-nav.d.ts.map +1 -0
- package/dist/plugin-nav.js +92 -0
- package/dist/plugin-nav.js.map +1 -0
- package/dist/plugin-testing.d.ts +2 -0
- package/dist/plugin-testing.d.ts.map +1 -0
- package/dist/plugin-testing.js +4 -0
- package/dist/plugin-testing.js.map +1 -0
- package/dist/plugin-web-react.d.ts +3 -0
- package/dist/plugin-web-react.d.ts.map +1 -0
- package/dist/plugin-web-react.js +3 -0
- package/dist/plugin-web-react.js.map +1 -0
- package/dist/plugin-web.d.ts +4 -0
- package/dist/plugin-web.d.ts.map +1 -0
- package/dist/plugin-web.js +5 -0
- package/dist/plugin-web.js.map +1 -0
- package/dist/plugin.d.ts +11 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +15 -0
- package/dist/plugin.js.map +1 -0
- package/dist/router.d.ts +28 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +47 -0
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +6 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +8 -0
- package/dist/routes.js.map +1 -1
- package/dist/schemas.d.ts +147 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +55 -5
- package/dist/schemas.js.map +1 -1
- package/dist/tokens.d.ts +2 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +101 -0
- package/dist/tokens.js.map +1 -0
- package/dist/vocabulary.d.ts +11 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +24 -0
- package/dist/vocabulary.js.map +1 -1
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +5 -2
- package/dist/web.js.map +1 -1
- package/docs/specs/cli.md +16 -9
- package/docs/specs/plugins.md +487 -0
- package/docs/specs/rest.md +25 -13
- package/package.json +21 -3
- package/skills/building-a-console/SKILL.md +25 -26
- package/skills/building-a-console/references/configuring.md +40 -0
- package/skills/building-a-console/references/the-default-console.md +1 -1
- package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
- package/skills/building-a-plugin/SKILL.md +137 -0
- package/skills/deploying-a-console/SKILL.md +24 -26
- package/skills/deploying-a-console/references/access.md +13 -9
- package/skills/deploying-a-console/references/resources.md +25 -10
- package/skills/deploying-a-console/references/running-it-again.md +15 -0
- package/skills/deploying-a-console/references/sizing.md +2 -0
- package/skills/planning-the-board/SKILL.md +96 -0
- package/skills/planning-the-board/references/a-task.md +36 -0
- package/skills/planning-the-board/references/setting-up.md +44 -0
- package/skills/working-the-board/SKILL.md +43 -34
- package/skills/working-the-board/references/the-pack.md +19 -0
- package/src/api.ts +16 -0
- package/src/cli/cli.ts +736 -45
- package/src/cli/format.ts +356 -9
- package/src/cli/help.ts +71 -6
- package/src/cli/plugin-check.ts +470 -0
- package/src/cli/plugin-cli.ts +148 -0
- package/src/cli/plugin-init.ts +90 -0
- package/src/cli/plugin-main.ts +30 -0
- package/src/client.ts +324 -1
- package/src/compat.ts +2 -2
- package/src/components/board.tsx +62 -14
- package/src/components/event-feed.tsx +42 -1
- package/src/components/markdown.tsx +6 -3
- package/src/components/pack-connect.tsx +10 -7
- package/src/components/pack-token.tsx +4 -1
- package/src/components/people.tsx +17 -7
- package/src/components/plugin-frame.tsx +437 -0
- package/src/components/plugin-settings.tsx +1111 -0
- package/src/components/remote-pictures.ts +19 -0
- package/src/components/shell.tsx +82 -14
- package/src/components/sign-in.tsx +114 -13
- package/src/components/ui.tsx +6 -3
- package/src/components/workspace.tsx +18 -0
- package/src/console.tsx +309 -23
- package/src/data.ts +47 -22
- package/src/i18n/en.ts +183 -1
- package/src/i18n/ko.ts +159 -1
- package/src/index.ts +12 -0
- package/src/limits.ts +14 -0
- package/src/plugin/args.ts +526 -0
- package/src/plugin/bridge.ts +314 -0
- package/src/plugin/capabilities.ts +150 -0
- package/src/plugin/context.ts +416 -0
- package/src/plugin/define.ts +792 -0
- package/src/plugin/definition-error.ts +19 -0
- package/src/plugin/limits.ts +70 -0
- package/src/plugin/load-worker.ts +50 -0
- package/src/plugin/load.ts +82 -0
- package/src/plugin/manifest.ts +561 -0
- package/src/plugin/refusals.ts +94 -0
- package/src/plugin/rest-schemas.ts +354 -0
- package/src/plugin/sandbox.ts +123 -0
- package/src/plugin/testing.ts +612 -0
- package/src/plugin/web-react.ts +45 -0
- package/src/plugin/web.ts +403 -0
- package/src/plugin-nav.ts +132 -0
- package/src/plugin-testing.ts +10 -0
- package/src/plugin-web-react.ts +4 -0
- package/src/plugin-web.ts +35 -0
- package/src/plugin.ts +126 -0
- package/src/router.ts +73 -0
- package/src/routes.ts +9 -0
- package/src/schemas.ts +60 -4
- package/src/styles/console.css +384 -5
- package/src/tokens.ts +101 -0
- package/src/vocabulary.ts +30 -0
- package/src/web.ts +5 -2
- package/template/plugin/.gitignore +2 -0
- package/template/plugin/README.md +26 -0
- package/template/plugin/SKILLCDN.md +13 -0
- package/template/plugin/build.mjs +40 -0
- package/template/plugin/console-plugin.json +50 -0
- package/template/plugin/package.json +27 -0
- package/template/plugin/skills/__key__/SKILL.md +23 -0
- package/template/plugin/src/check.test.ts +34 -0
- package/template/plugin/src/server/index.test.ts +41 -0
- package/template/plugin/src/server/index.ts +72 -0
- package/template/plugin/src/web/index.css +51 -0
- package/template/plugin/src/web/index.ts +115 -0
- package/template/plugin/tsconfig.json +17 -0
- package/skills/working-the-board/references/setting-up.md +0 -3
- /package/skills/{working-the-board → planning-the-board}/references/project-rules.md +0 -0
package/src/cli/cli.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type ConsoleClient,
|
|
6
6
|
createClient,
|
|
7
7
|
type FetchLike,
|
|
8
|
+
type PluginClient,
|
|
8
9
|
type ProjectClient,
|
|
9
10
|
} from "../client.js";
|
|
10
11
|
import {
|
|
@@ -20,6 +21,8 @@ import {
|
|
|
20
21
|
} from "../compat.js";
|
|
21
22
|
import { addDays, dayOf as dayOfDate, firstDayOfWeek, isDay, weekOf } from "../dates.js";
|
|
22
23
|
import { DEFAULT_PAGE_LIMIT, LIST_LIMIT, MAX_OPTIONS, MIN_OPTIONS } from "../limits.js";
|
|
24
|
+
import { capabilityWords } from "../plugin/capabilities.js";
|
|
25
|
+
import { PLUGIN_LIST_LIMIT } from "../plugin/limits.js";
|
|
23
26
|
import { fillRules, RULES_PATH, RULES_TITLE } from "../rules.js";
|
|
24
27
|
import {
|
|
25
28
|
hostOfLogin,
|
|
@@ -28,6 +31,10 @@ import {
|
|
|
28
31
|
type RestMember,
|
|
29
32
|
type RestPackConnectStarted,
|
|
30
33
|
type RestPerson,
|
|
34
|
+
type RestPluginDetail,
|
|
35
|
+
type RestPluginEnableInput,
|
|
36
|
+
type RestPluginGrantInput,
|
|
37
|
+
type RestPluginInvocation,
|
|
31
38
|
type RestSkillBrowse,
|
|
32
39
|
type RestSkillFile,
|
|
33
40
|
type RestSkillLoad,
|
|
@@ -78,12 +85,21 @@ import {
|
|
|
78
85
|
formatDocuments,
|
|
79
86
|
formatGoal,
|
|
80
87
|
formatGoalLine,
|
|
88
|
+
formatInvocation,
|
|
81
89
|
formatListingLine,
|
|
82
90
|
formatMemberLine,
|
|
83
91
|
formatMore,
|
|
84
92
|
formatOptions,
|
|
85
93
|
formatPersonLine,
|
|
86
94
|
formatPersonWork,
|
|
95
|
+
formatPlugin,
|
|
96
|
+
formatPluginGrantLine,
|
|
97
|
+
formatPluginHelp,
|
|
98
|
+
formatPluginRecord,
|
|
99
|
+
formatPluginRecords,
|
|
100
|
+
formatPluginRecordVersions,
|
|
101
|
+
formatPluginSettingLine,
|
|
102
|
+
formatPlugins,
|
|
87
103
|
formatProject,
|
|
88
104
|
formatProjectLine,
|
|
89
105
|
formatRules,
|
|
@@ -95,6 +111,7 @@ import {
|
|
|
95
111
|
formatVersionLine,
|
|
96
112
|
formatWork,
|
|
97
113
|
formatWorkspace,
|
|
114
|
+
localized,
|
|
98
115
|
moment,
|
|
99
116
|
setMomentZone,
|
|
100
117
|
} from "./format.js";
|
|
@@ -801,13 +818,19 @@ const logout: Command = async (args, io) => {
|
|
|
801
818
|
}
|
|
802
819
|
// The token is given up at the console first, so that it is good for nothing from now on;
|
|
803
820
|
// one the console no longer knows is gone already. A console out of reach leaves it to the
|
|
804
|
-
// Agents page, and the person is told so.
|
|
805
|
-
|
|
821
|
+
// Agents page, and the person is told so. A console that keeps it because the person is out
|
|
822
|
+
// (ADR-0044) is told so too: the token is theirs again when they are back in.
|
|
823
|
+
let outcome: "gone" | "kept" | "left" = "gone";
|
|
806
824
|
try {
|
|
807
825
|
await clientOf(io, url, login.token).giveUpToken();
|
|
808
826
|
} catch (error) {
|
|
809
|
-
if (
|
|
810
|
-
|
|
827
|
+
if (
|
|
828
|
+
error instanceof ApiError &&
|
|
829
|
+
(error.code === "auth.deactivated" || error.code === "auth.not_admitted")
|
|
830
|
+
) {
|
|
831
|
+
outcome = "kept";
|
|
832
|
+
} else if (!(error instanceof ApiError && error.code === "auth.required")) {
|
|
833
|
+
outcome = "left";
|
|
811
834
|
}
|
|
812
835
|
}
|
|
813
836
|
const next = withoutLogin(credentials, url, login.account);
|
|
@@ -818,9 +841,11 @@ const logout: Command = async (args, io) => {
|
|
|
818
841
|
}
|
|
819
842
|
const whom = login.account === undefined ? "" : ` as ${login.account}`;
|
|
820
843
|
io.stdout(
|
|
821
|
-
gone
|
|
844
|
+
outcome === "gone"
|
|
822
845
|
? `Signed out of ${url}${whom}: the token is given up at the console and forgotten here.\n`
|
|
823
|
-
:
|
|
846
|
+
: outcome === "kept"
|
|
847
|
+
? `Signed out of ${url}${whom} here: the console keeps the token while the person is out, for when they are back in.\n`
|
|
848
|
+
: `Signed out of ${url}${whom} here, but the console could not be reached to give the token up: a person removes it on the Agents page.\n`,
|
|
824
849
|
);
|
|
825
850
|
envNote();
|
|
826
851
|
return EXIT.ok;
|
|
@@ -1099,20 +1124,55 @@ const MAX_FIND_LIMIT = 25;
|
|
|
1099
1124
|
|
|
1100
1125
|
/** The pack of the project as the console lists it, and what its reads are rendered at; none when the pack did not answer. */
|
|
1101
1126
|
async function packOf(
|
|
1102
|
-
|
|
1127
|
+
reader: PackReader,
|
|
1103
1128
|
): Promise<{ readonly listing: RestSkills; readonly summary: PackSummary | undefined }> {
|
|
1104
|
-
const listing = await
|
|
1129
|
+
const listing = await reader.skills();
|
|
1105
1130
|
return { listing, summary: summaryOf(listing) };
|
|
1106
1131
|
}
|
|
1107
1132
|
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1133
|
+
/** What reads a pack: a project's client, or a plugin's, whose skills are a pack of their own. */
|
|
1134
|
+
type PackReader = Pick<
|
|
1135
|
+
ProjectClient,
|
|
1136
|
+
"skills" | "skill" | "skillFile" | "skillBrowse" | "skillFind"
|
|
1137
|
+
>;
|
|
1138
|
+
|
|
1139
|
+
const SKILLS_OPTIONS: OptionSpec = {
|
|
1140
|
+
search: { type: "string" },
|
|
1141
|
+
limit: { type: "string" },
|
|
1142
|
+
cursor: { type: "string" },
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
const SKILL_OPTIONS: OptionSpec = {
|
|
1146
|
+
file: { type: "string" },
|
|
1147
|
+
offset: { type: "string" },
|
|
1148
|
+
limit: { type: "string" },
|
|
1149
|
+
};
|
|
1150
|
+
|
|
1151
|
+
/** The line `console skills` ends with: where the plugins' own skills are. */
|
|
1152
|
+
const PLUGIN_SKILLS_LINE =
|
|
1153
|
+
"The plugins of this console have skills of their own: console plugins lists the plugins, console plugin <key> skills the skills of one.";
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* The pack as the words say: whole, a folder of it, a file's outline, or what words find; for
|
|
1157
|
+
* the project's pack as `console skills`, and for a plugin's as `console plugin <key> skills`.
|
|
1158
|
+
*/
|
|
1159
|
+
async function readSkills(
|
|
1160
|
+
io: CliIo,
|
|
1161
|
+
session: Session,
|
|
1162
|
+
reader: PackReader,
|
|
1163
|
+
values: Values,
|
|
1164
|
+
positionals: readonly string[],
|
|
1165
|
+
extras: {
|
|
1166
|
+
/** The command these words came after, for what is said when they are not understood. */
|
|
1167
|
+
readonly command: string;
|
|
1168
|
+
/** Where the console's own skills are on this machine, said with the project's pack. */
|
|
1169
|
+
readonly own?: string | undefined;
|
|
1170
|
+
/** A line after the listing whole. */
|
|
1171
|
+
readonly afterwards?: string | undefined;
|
|
1172
|
+
},
|
|
1173
|
+
): Promise<number> {
|
|
1114
1174
|
if (positionals.length > 1) {
|
|
1115
|
-
throw misuse(
|
|
1175
|
+
throw misuse(`Say one folder: ${extras.command} [<folder>] [--search <words>]`);
|
|
1116
1176
|
}
|
|
1117
1177
|
// The root of the pack is the mounted folder, which the deployment reads as no path at all.
|
|
1118
1178
|
const given = positionals[0];
|
|
@@ -1122,7 +1182,7 @@ const skills: Command = async (args, io) => {
|
|
|
1122
1182
|
const cursor = text(values, "cursor");
|
|
1123
1183
|
if (words === undefined && (limit !== undefined || cursor !== undefined)) {
|
|
1124
1184
|
throw misuse(
|
|
1125
|
-
|
|
1185
|
+
`--limit and --cursor go with --search: ${extras.command} --search <words> [--limit <n>] [--cursor <cursor>]`,
|
|
1126
1186
|
);
|
|
1127
1187
|
}
|
|
1128
1188
|
const atMost = limit === undefined ? undefined : Number(limit);
|
|
@@ -1132,17 +1192,19 @@ const skills: Command = async (args, io) => {
|
|
|
1132
1192
|
) {
|
|
1133
1193
|
throw misuse(`Not a number of results, 1 to ${MAX_FIND_LIMIT}: ${limit}`);
|
|
1134
1194
|
}
|
|
1135
|
-
const
|
|
1136
|
-
const project = await session.project();
|
|
1137
|
-
const { listing, summary } = await packOf(project);
|
|
1195
|
+
const { listing, summary } = await packOf(reader);
|
|
1138
1196
|
// The pack as it stands; or why it does not answer, which a folder or a search would meet too.
|
|
1139
1197
|
if (summary === undefined || (words === undefined && given === undefined)) {
|
|
1140
|
-
answer(io, session, listing, () =>
|
|
1198
|
+
answer(io, session, listing, () =>
|
|
1199
|
+
[formatSkills(listing, extras.own), extras.afterwards]
|
|
1200
|
+
.filter((part): part is string => part !== undefined)
|
|
1201
|
+
.join("\n"),
|
|
1202
|
+
);
|
|
1141
1203
|
return EXIT.ok;
|
|
1142
1204
|
}
|
|
1143
1205
|
if (words !== undefined) {
|
|
1144
1206
|
// One page of what the words find; more is said, and asked for with --cursor.
|
|
1145
|
-
const found = await
|
|
1207
|
+
const found = await reader.skillFind(words, { path: folder, limit: atMost, cursor });
|
|
1146
1208
|
answer(io, session, found, () => formatSkillFind(found, summary));
|
|
1147
1209
|
return EXIT.ok;
|
|
1148
1210
|
}
|
|
@@ -1151,7 +1213,7 @@ const skills: Command = async (args, io) => {
|
|
|
1151
1213
|
let next: string | undefined;
|
|
1152
1214
|
let last: RestSkillBrowse | undefined;
|
|
1153
1215
|
for (let n = 0; n < MAX_SKILL_PAGES; n += 1) {
|
|
1154
|
-
last = await
|
|
1216
|
+
last = await reader.skillBrowse({ path: folder, cursor: next });
|
|
1155
1217
|
if (last.read?.status !== "ready") {
|
|
1156
1218
|
break;
|
|
1157
1219
|
}
|
|
@@ -1170,6 +1232,17 @@ const skills: Command = async (args, io) => {
|
|
|
1170
1232
|
formatSkillBrowse(browsed, whole),
|
|
1171
1233
|
);
|
|
1172
1234
|
return EXIT.ok;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const skills: Command = async (args, io) => {
|
|
1238
|
+
const { values, positionals } = parse(args, SKILLS_OPTIONS);
|
|
1239
|
+
const session = await open(io, values);
|
|
1240
|
+
const project = await session.project();
|
|
1241
|
+
return readSkills(io, session, project, values, positionals, {
|
|
1242
|
+
command: "console skills",
|
|
1243
|
+
own: io.skillsDir,
|
|
1244
|
+
afterwards: PLUGIN_SKILLS_LINE,
|
|
1245
|
+
});
|
|
1173
1246
|
};
|
|
1174
1247
|
|
|
1175
1248
|
/** The most characters one read of a file asks for: what the console's API allows on one page. */
|
|
@@ -1197,18 +1270,24 @@ function countOf(
|
|
|
1197
1270
|
return count;
|
|
1198
1271
|
}
|
|
1199
1272
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1273
|
+
/**
|
|
1274
|
+
* One skill of the pack whole, a file of it, or one page of a file; for the project's pack as
|
|
1275
|
+
* `console skill`, and for a plugin's as `console plugin <key> skill`.
|
|
1276
|
+
*/
|
|
1277
|
+
async function readSkill(
|
|
1278
|
+
io: CliIo,
|
|
1279
|
+
session: Session,
|
|
1280
|
+
reader: PackReader,
|
|
1281
|
+
values: Values,
|
|
1282
|
+
positionals: readonly string[],
|
|
1283
|
+
command: string,
|
|
1284
|
+
): Promise<number> {
|
|
1206
1285
|
const file = text(values, "file");
|
|
1207
1286
|
// A file is read by its own path from the repository's root; a skill's path beside it would be
|
|
1208
1287
|
// ignored, which reads as if it mattered, so the two together are not understood.
|
|
1209
1288
|
if (file !== undefined && positionals.length > 0) {
|
|
1210
1289
|
throw misuse(
|
|
1211
|
-
|
|
1290
|
+
`Say one: ${command} <path of its SKILL.md> for a skill, or ${command} --file <path> for a file of the pack.`,
|
|
1212
1291
|
);
|
|
1213
1292
|
}
|
|
1214
1293
|
// A section is a page of a file read as asked (SkillCDN 0.4): the flags mean nothing without one.
|
|
@@ -1217,14 +1296,12 @@ const skill: Command = async (args, io) => {
|
|
|
1217
1296
|
(text(values, "offset") !== undefined || text(values, "limit") !== undefined)
|
|
1218
1297
|
) {
|
|
1219
1298
|
throw misuse(
|
|
1220
|
-
|
|
1299
|
+
`--offset and --limit go with --file: ${command} --file <path> [--offset <n>] [--limit <n>]`,
|
|
1221
1300
|
);
|
|
1222
1301
|
}
|
|
1223
1302
|
const offset = countOf(text(values, "offset"), "offset", { least: 0 });
|
|
1224
1303
|
const limit = countOf(text(values, "limit"), "limit", { least: 1, most: MAX_FILE_LIMIT });
|
|
1225
|
-
const
|
|
1226
|
-
const project = await session.project();
|
|
1227
|
-
const { listing, summary } = await packOf(project);
|
|
1304
|
+
const { listing, summary } = await packOf(reader);
|
|
1228
1305
|
if (summary === undefined) {
|
|
1229
1306
|
answer(io, session, listing, () => formatSkills(listing, io.skillsDir));
|
|
1230
1307
|
return EXIT.ok;
|
|
@@ -1232,7 +1309,7 @@ const skill: Command = async (args, io) => {
|
|
|
1232
1309
|
if (file !== undefined && (offset !== undefined || limit !== undefined)) {
|
|
1233
1310
|
// One page as asked, the section a search or an outline named; the result says where the
|
|
1234
1311
|
// file continues, and asks for nothing.
|
|
1235
|
-
const page = await
|
|
1312
|
+
const page = await reader.skillFile(file, { offset, limit });
|
|
1236
1313
|
const section = page.read === null ? undefined : joinFile([page], summary, limit);
|
|
1237
1314
|
answer(io, session, { ...page, read: section === undefined ? null : bodyOfFile(section) }, () =>
|
|
1238
1315
|
formatSkillFile(page, section),
|
|
@@ -1245,7 +1322,7 @@ const skill: Command = async (args, io) => {
|
|
|
1245
1322
|
let offset: number | undefined;
|
|
1246
1323
|
let last: RestSkillFile | undefined;
|
|
1247
1324
|
for (let n = 0; n < MAX_SKILL_PAGES; n += 1) {
|
|
1248
|
-
last = await
|
|
1325
|
+
last = await reader.skillFile(file, offset === undefined ? {} : { offset });
|
|
1249
1326
|
if (last.read === null) {
|
|
1250
1327
|
break;
|
|
1251
1328
|
}
|
|
@@ -1268,7 +1345,7 @@ const skill: Command = async (args, io) => {
|
|
|
1268
1345
|
const wanted = skillPathIn(
|
|
1269
1346
|
theOne(
|
|
1270
1347
|
positionals,
|
|
1271
|
-
|
|
1348
|
+
`Say which skill: ${command} <path of its SKILL.md>, or ${command} --file <path> for a file of the pack`,
|
|
1272
1349
|
),
|
|
1273
1350
|
listing.address,
|
|
1274
1351
|
);
|
|
@@ -1280,7 +1357,7 @@ const skill: Command = async (args, io) => {
|
|
|
1280
1357
|
let cursor: string | undefined;
|
|
1281
1358
|
let last: RestSkillLoad | undefined;
|
|
1282
1359
|
for (let n = 0; n < MAX_SKILL_PAGES; n += 1) {
|
|
1283
|
-
last = await
|
|
1360
|
+
last = await reader.skill(wanted.path, cursor);
|
|
1284
1361
|
if (last.read?.status !== "ready") {
|
|
1285
1362
|
break;
|
|
1286
1363
|
}
|
|
@@ -1299,8 +1376,599 @@ const skill: Command = async (args, io) => {
|
|
|
1299
1376
|
formatSkillLoad(loaded, whole),
|
|
1300
1377
|
);
|
|
1301
1378
|
return EXIT.ok;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
const skill: Command = async (args, io) => {
|
|
1382
|
+
const { values, positionals } = parse(args, SKILL_OPTIONS);
|
|
1383
|
+
const session = await open(io, values);
|
|
1384
|
+
const project = await session.project();
|
|
1385
|
+
return readSkill(io, session, project, values, positionals, "console skill");
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1388
|
+
// ---- The plugins (docs/specs/plugins.md, "The command") ----
|
|
1389
|
+
// `console plugins` and `console plugin <key> ...` are the two entries: the command sends the
|
|
1390
|
+
// words as typed and renders the console's generic shapes, and knows nothing of any plugin.
|
|
1391
|
+
|
|
1392
|
+
const plugins: Command = async (args, io) => {
|
|
1393
|
+
const { values } = parse(args, {}, false);
|
|
1394
|
+
const session = await open(io, values);
|
|
1395
|
+
const [listed, me] = await Promise.all([session.client.plugins(), session.client.me()]);
|
|
1396
|
+
answer(io, session, listed, () => formatPlugins(listed, me.language));
|
|
1397
|
+
return EXIT.ok;
|
|
1398
|
+
};
|
|
1399
|
+
|
|
1400
|
+
/** The flags of the command's own on `console plugin ...`, wherever they stand among the words. */
|
|
1401
|
+
const PLUGIN_GLOBAL_FLAGS: Readonly<Record<string, "string" | "boolean">> = {
|
|
1402
|
+
json: "boolean",
|
|
1403
|
+
url: "string",
|
|
1404
|
+
account: "string",
|
|
1405
|
+
project: "string",
|
|
1406
|
+
};
|
|
1407
|
+
|
|
1408
|
+
/** The flags of the command's own on a plugin's command: the file it uploads first, the run it is for. */
|
|
1409
|
+
const PLUGIN_COMMAND_FLAGS: Readonly<Record<string, "string" | "boolean">> = {
|
|
1410
|
+
file: "string",
|
|
1411
|
+
run: "string",
|
|
1412
|
+
};
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* The words with the flags named in `own` taken out, wherever they stand: the rest is the
|
|
1416
|
+
* plugin's, and goes to the console as typed. `--` ends the flags, and stays among the words.
|
|
1417
|
+
*/
|
|
1418
|
+
function splitWords(
|
|
1419
|
+
args: readonly string[],
|
|
1420
|
+
own: Readonly<Record<string, "string" | "boolean">>,
|
|
1421
|
+
): { readonly values: Record<string, string | boolean>; readonly words: string[] } {
|
|
1422
|
+
const values: Record<string, string | boolean> = {};
|
|
1423
|
+
const words: string[] = [];
|
|
1424
|
+
let ended = false;
|
|
1425
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1426
|
+
const word = args[index] ?? "";
|
|
1427
|
+
if (ended || !word.startsWith("--") || word.length === 2) {
|
|
1428
|
+
ended = ended || word === "--";
|
|
1429
|
+
words.push(word);
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
const equals = word.indexOf("=");
|
|
1433
|
+
const name = equals < 0 ? word.slice(2) : word.slice(2, equals);
|
|
1434
|
+
const type = Object.hasOwn(own, name) ? own[name] : undefined;
|
|
1435
|
+
if (type === undefined) {
|
|
1436
|
+
words.push(word);
|
|
1437
|
+
continue;
|
|
1438
|
+
}
|
|
1439
|
+
if (type === "boolean") {
|
|
1440
|
+
if (equals >= 0) {
|
|
1441
|
+
throw misuse(`--${name} takes no value`);
|
|
1442
|
+
}
|
|
1443
|
+
values[name] = true;
|
|
1444
|
+
continue;
|
|
1445
|
+
}
|
|
1446
|
+
if (equals >= 0) {
|
|
1447
|
+
values[name] = word.slice(equals + 1);
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
const next = args[index + 1];
|
|
1451
|
+
if (next === undefined) {
|
|
1452
|
+
throw misuse(`--${name} needs a value`);
|
|
1453
|
+
}
|
|
1454
|
+
values[name] = next;
|
|
1455
|
+
index += 1;
|
|
1456
|
+
}
|
|
1457
|
+
return { values, words };
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
const PLUGIN_USAGE =
|
|
1461
|
+
"console plugin <key> [help [<command>] | <command> [args] | records <collection> | skills | skill <path> | enable | disable | settings | secret <name> | grant <role> | revoke <id>]. console plugins lists the plugins.";
|
|
1462
|
+
|
|
1463
|
+
const plugin: Command = async (args, io) => {
|
|
1464
|
+
const { values, words } = splitWords(args, PLUGIN_GLOBAL_FLAGS);
|
|
1465
|
+
const [key, verb, ...rest] = words;
|
|
1466
|
+
if (key === undefined || key.startsWith("-")) {
|
|
1467
|
+
throw misuse(`Say which plugin: ${PLUGIN_USAGE}`);
|
|
1468
|
+
}
|
|
1469
|
+
const session = await open(io, values);
|
|
1470
|
+
const client = session.client.plugin(key);
|
|
1471
|
+
switch (verb) {
|
|
1472
|
+
case undefined: {
|
|
1473
|
+
const [detail, me] = await Promise.all([client.get(), session.client.me()]);
|
|
1474
|
+
answer(io, session, detail, () => formatPlugin(detail, me.language));
|
|
1475
|
+
return EXIT.ok;
|
|
1476
|
+
}
|
|
1477
|
+
case "help": {
|
|
1478
|
+
if (rest.length > 1) {
|
|
1479
|
+
throw misuse(`Say one command: console plugin ${key} help [<command>]`);
|
|
1480
|
+
}
|
|
1481
|
+
const name = rest[0];
|
|
1482
|
+
const detail = await client.get();
|
|
1483
|
+
const operation =
|
|
1484
|
+
name === undefined
|
|
1485
|
+
? undefined
|
|
1486
|
+
: detail.operations.find((candidate) => candidate.name === name);
|
|
1487
|
+
if (name !== undefined && operation === undefined) {
|
|
1488
|
+
throw misuse(
|
|
1489
|
+
`Not a command or a query of ${key} you may run: ${name}. console plugin ${key} lists them.`,
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
answer(io, session, operation ?? detail.operations, () => formatPluginHelp(detail, name));
|
|
1493
|
+
return EXIT.ok;
|
|
1494
|
+
}
|
|
1495
|
+
case "records":
|
|
1496
|
+
return pluginRecords(io, session, client, rest, text(values, "project"));
|
|
1497
|
+
case "skills": {
|
|
1498
|
+
const parsed = parse(rest, SKILLS_OPTIONS);
|
|
1499
|
+
return readSkills(io, session, client, parsed.values, parsed.positionals, {
|
|
1500
|
+
command: `console plugin ${key} skills`,
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
case "skill": {
|
|
1504
|
+
const parsed = parse(rest, SKILL_OPTIONS);
|
|
1505
|
+
return readSkill(
|
|
1506
|
+
io,
|
|
1507
|
+
session,
|
|
1508
|
+
client,
|
|
1509
|
+
parsed.values,
|
|
1510
|
+
parsed.positionals,
|
|
1511
|
+
`console plugin ${key} skill`,
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
case "enable":
|
|
1515
|
+
return pluginEnable(io, session, client, rest);
|
|
1516
|
+
case "disable": {
|
|
1517
|
+
parse(rest, {}, false);
|
|
1518
|
+
const detail = await client.disable();
|
|
1519
|
+
answer(
|
|
1520
|
+
io,
|
|
1521
|
+
session,
|
|
1522
|
+
detail,
|
|
1523
|
+
() =>
|
|
1524
|
+
`Disabled ${detail.key}: its commands are refused until it is enabled again; what the console kept of it stays.`,
|
|
1525
|
+
);
|
|
1526
|
+
return EXIT.ok;
|
|
1527
|
+
}
|
|
1528
|
+
case "settings":
|
|
1529
|
+
return pluginSettings(io, session, client, rest);
|
|
1530
|
+
case "secret":
|
|
1531
|
+
return pluginSecret(io, session, client, rest);
|
|
1532
|
+
case "grant":
|
|
1533
|
+
return pluginGrant(io, session, client, rest, text(values, "project"));
|
|
1534
|
+
case "revoke": {
|
|
1535
|
+
const { positionals } = parse(rest, {});
|
|
1536
|
+
const id = theOne(positionals, `Say which grant: console plugin ${key} revoke <grant id>`);
|
|
1537
|
+
const revoked = await client.revoke(id);
|
|
1538
|
+
answer(io, session, revoked, () => `Revoked: ${formatPluginGrantLine(revoked)}`);
|
|
1539
|
+
return EXIT.ok;
|
|
1540
|
+
}
|
|
1541
|
+
default:
|
|
1542
|
+
return pluginInvoke(io, session, client, verb, rest);
|
|
1543
|
+
}
|
|
1302
1544
|
};
|
|
1303
1545
|
|
|
1546
|
+
/** A query's answer that is a file, as the console carries one: its name, its type, its bytes as base64. */
|
|
1547
|
+
function queryFileOf(
|
|
1548
|
+
result: RestPluginInvocation,
|
|
1549
|
+
): { readonly name: string; readonly type: string; readonly bytes: Uint8Array } | undefined {
|
|
1550
|
+
if (result.kind !== "query" || typeof result.data !== "object" || result.data === null) {
|
|
1551
|
+
return undefined;
|
|
1552
|
+
}
|
|
1553
|
+
const file = (result.data as { file?: unknown }).file;
|
|
1554
|
+
if (
|
|
1555
|
+
typeof file !== "object" ||
|
|
1556
|
+
file === null ||
|
|
1557
|
+
typeof (file as { name?: unknown }).name !== "string" ||
|
|
1558
|
+
typeof (file as { type?: unknown }).type !== "string" ||
|
|
1559
|
+
typeof (file as { base64?: unknown }).base64 !== "string"
|
|
1560
|
+
) {
|
|
1561
|
+
return undefined;
|
|
1562
|
+
}
|
|
1563
|
+
const named = file as { name: string; type: string; base64: string };
|
|
1564
|
+
return {
|
|
1565
|
+
name: basename(named.name) || "answer",
|
|
1566
|
+
type: named.type,
|
|
1567
|
+
bytes: Uint8Array.from(Buffer.from(named.base64, "base64")),
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
/**
|
|
1572
|
+
* A command or a query of the plugin, by the words as typed: a file named with --file, or read
|
|
1573
|
+
* from standard input for -, is uploaded first and goes as a handle; the console parses the rest.
|
|
1574
|
+
*/
|
|
1575
|
+
async function pluginInvoke(
|
|
1576
|
+
io: CliIo,
|
|
1577
|
+
session: Session,
|
|
1578
|
+
client: PluginClient,
|
|
1579
|
+
name: string,
|
|
1580
|
+
rest: readonly string[],
|
|
1581
|
+
): Promise<number> {
|
|
1582
|
+
const { values, words } = splitWords(rest, PLUGIN_COMMAND_FLAGS);
|
|
1583
|
+
const stdin = words.includes("-");
|
|
1584
|
+
const filePath = text(values, "file");
|
|
1585
|
+
if (filePath !== undefined && stdin) {
|
|
1586
|
+
throw misuse("Say one file: --file <path>, or - for standard input.");
|
|
1587
|
+
}
|
|
1588
|
+
const files: string[] = [];
|
|
1589
|
+
if (filePath !== undefined) {
|
|
1590
|
+
let bytes: Uint8Array;
|
|
1591
|
+
try {
|
|
1592
|
+
bytes = await io.readBytes(filePath);
|
|
1593
|
+
} catch {
|
|
1594
|
+
throw misuse(`The file could not be read: ${filePath}`);
|
|
1595
|
+
}
|
|
1596
|
+
const fileName = basename(filePath);
|
|
1597
|
+
const uploaded = await client.upload({
|
|
1598
|
+
name: fileName,
|
|
1599
|
+
bytes,
|
|
1600
|
+
contentType: CONTENT_TYPES[extname(fileName).toLowerCase()] ?? "application/octet-stream",
|
|
1601
|
+
});
|
|
1602
|
+
files.push(uploaded.handle);
|
|
1603
|
+
} else if (stdin) {
|
|
1604
|
+
const uploaded = await client.upload({
|
|
1605
|
+
name: "stdin.txt",
|
|
1606
|
+
bytes: new TextEncoder().encode(await io.readStdin()),
|
|
1607
|
+
contentType: "text/plain",
|
|
1608
|
+
});
|
|
1609
|
+
files.push(uploaded.handle);
|
|
1610
|
+
}
|
|
1611
|
+
const project = await session.projectKey();
|
|
1612
|
+
const run = text(values, "run");
|
|
1613
|
+
const result = await client.invoke({
|
|
1614
|
+
argv: [name, ...words.filter((word) => word !== "-")],
|
|
1615
|
+
...(files.length === 0 ? {} : { files }),
|
|
1616
|
+
...(project === undefined ? {} : { project }),
|
|
1617
|
+
...(run === undefined ? {} : { run }),
|
|
1618
|
+
});
|
|
1619
|
+
// A query that answers a file: saved under its name in the working directory, as console file saves one.
|
|
1620
|
+
const file = queryFileOf(result);
|
|
1621
|
+
let saved: string | undefined;
|
|
1622
|
+
if (file !== undefined && !session.json) {
|
|
1623
|
+
try {
|
|
1624
|
+
saved = await io.writeBytes(file.name, file.bytes, file.name);
|
|
1625
|
+
} catch {
|
|
1626
|
+
throw failed(`The file could not be written at ${file.name}.`);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
answer(io, session, result, () =>
|
|
1630
|
+
file !== undefined && saved !== undefined
|
|
1631
|
+
? `Saved ${file.name} (${file.bytes.byteLength} bytes, ${file.type}) at ${saved}`
|
|
1632
|
+
: formatInvocation(result),
|
|
1633
|
+
);
|
|
1634
|
+
return result.waiting === null ? EXIT.ok : EXIT.waiting;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
/** A condition's value as typed: a number, true, false and null read as such; JSON where it begins as JSON; text otherwise. */
|
|
1638
|
+
function whereValueOf(word: string): unknown {
|
|
1639
|
+
if (word === "true") {
|
|
1640
|
+
return true;
|
|
1641
|
+
}
|
|
1642
|
+
if (word === "false") {
|
|
1643
|
+
return false;
|
|
1644
|
+
}
|
|
1645
|
+
if (word === "null") {
|
|
1646
|
+
return null;
|
|
1647
|
+
}
|
|
1648
|
+
if (/^-?\d+(?:\.\d+)?$/.test(word)) {
|
|
1649
|
+
return Number(word);
|
|
1650
|
+
}
|
|
1651
|
+
if (word.startsWith("{") || word.startsWith("[")) {
|
|
1652
|
+
try {
|
|
1653
|
+
return JSON.parse(word);
|
|
1654
|
+
} catch {
|
|
1655
|
+
return word;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
return word;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
/** The records of a collection one may read, a page of them; or one record, or its versions. */
|
|
1662
|
+
async function pluginRecords(
|
|
1663
|
+
io: CliIo,
|
|
1664
|
+
session: Session,
|
|
1665
|
+
client: PluginClient,
|
|
1666
|
+
rest: readonly string[],
|
|
1667
|
+
project: string | undefined,
|
|
1668
|
+
): Promise<number> {
|
|
1669
|
+
const { values, positionals } = parse(rest, {
|
|
1670
|
+
where: { type: "string", multiple: true },
|
|
1671
|
+
search: { type: "string" },
|
|
1672
|
+
order: { type: "string" },
|
|
1673
|
+
limit: { type: "string" },
|
|
1674
|
+
after: { type: "string" },
|
|
1675
|
+
archived: { type: "boolean" },
|
|
1676
|
+
versions: { type: "boolean" },
|
|
1677
|
+
});
|
|
1678
|
+
const [collection, id, ...more] = positionals;
|
|
1679
|
+
if (collection === undefined || more.length > 0) {
|
|
1680
|
+
throw misuse(
|
|
1681
|
+
`Say which collection: console plugin ${client.key} records <collection> [<id>] [--where <field>=<value>]... [--search <words>] [--order <field>[:desc]] [--limit <n>] [--after <cursor>] [--archived] [--versions]`,
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
if (id !== undefined) {
|
|
1685
|
+
if (on(values, "versions")) {
|
|
1686
|
+
const versions = await client.recordVersions(collection, id);
|
|
1687
|
+
answer(io, session, versions, () => formatPluginRecordVersions(versions));
|
|
1688
|
+
return EXIT.ok;
|
|
1689
|
+
}
|
|
1690
|
+
const record = await client.record(collection, id);
|
|
1691
|
+
answer(io, session, record, () => formatPluginRecord(record));
|
|
1692
|
+
return EXIT.ok;
|
|
1693
|
+
}
|
|
1694
|
+
if (on(values, "versions")) {
|
|
1695
|
+
throw misuse(
|
|
1696
|
+
"--versions goes with a record's id: console plugin <key> records <collection> <id> --versions",
|
|
1697
|
+
);
|
|
1698
|
+
}
|
|
1699
|
+
const where: Record<string, unknown> = {};
|
|
1700
|
+
for (const pair of texts(values, "where")) {
|
|
1701
|
+
const equals = pair.indexOf("=");
|
|
1702
|
+
if (equals <= 0) {
|
|
1703
|
+
throw misuse(`Not a condition, as <field>=<value>: ${pair}`);
|
|
1704
|
+
}
|
|
1705
|
+
where[pair.slice(0, equals)] = whereValueOf(pair.slice(equals + 1));
|
|
1706
|
+
}
|
|
1707
|
+
const order = text(values, "order");
|
|
1708
|
+
const page = await client.records(collection, {
|
|
1709
|
+
...(Object.keys(where).length === 0 ? {} : { where }),
|
|
1710
|
+
q: text(values, "search"),
|
|
1711
|
+
order:
|
|
1712
|
+
order === undefined ? undefined : order.endsWith(":desc") ? `-${order.slice(0, -5)}` : order,
|
|
1713
|
+
limit: countOf(text(values, "limit"), "limit", { least: 1, most: PLUGIN_LIST_LIMIT }),
|
|
1714
|
+
after: text(values, "after"),
|
|
1715
|
+
project,
|
|
1716
|
+
archived: on(values, "archived") ? true : undefined,
|
|
1717
|
+
});
|
|
1718
|
+
answer(io, session, page, () => formatPluginRecords(page));
|
|
1719
|
+
return EXIT.ok;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
/** The values `--set <key>=<value>` give, each read as its setting's type; an empty value clears one. */
|
|
1723
|
+
function settingValuesOf(
|
|
1724
|
+
pairs: readonly string[],
|
|
1725
|
+
detail: RestPluginDetail,
|
|
1726
|
+
): Record<string, unknown> {
|
|
1727
|
+
const values: Record<string, unknown> = {};
|
|
1728
|
+
const read = (type: string, word: string, key: string): unknown => {
|
|
1729
|
+
switch (type) {
|
|
1730
|
+
case "number": {
|
|
1731
|
+
const value = Number(word);
|
|
1732
|
+
if (!Number.isFinite(value) || word.trim().length === 0) {
|
|
1733
|
+
throw misuse(`Not a number for the setting ${key}: ${word}`);
|
|
1734
|
+
}
|
|
1735
|
+
return value;
|
|
1736
|
+
}
|
|
1737
|
+
case "boolean":
|
|
1738
|
+
if (word !== "true" && word !== "false") {
|
|
1739
|
+
throw misuse(`The setting ${key} is true or false: ${word}`);
|
|
1740
|
+
}
|
|
1741
|
+
return word === "true";
|
|
1742
|
+
default:
|
|
1743
|
+
return word;
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
for (const pair of pairs) {
|
|
1747
|
+
const equals = pair.indexOf("=");
|
|
1748
|
+
if (equals <= 0) {
|
|
1749
|
+
throw misuse(`Not a setting, as <key>=<value>: ${pair}`);
|
|
1750
|
+
}
|
|
1751
|
+
const key = pair.slice(0, equals);
|
|
1752
|
+
const word = pair.slice(equals + 1);
|
|
1753
|
+
const setting = detail.settings.find((candidate) => candidate.key === key);
|
|
1754
|
+
if (setting === undefined) {
|
|
1755
|
+
throw misuse(
|
|
1756
|
+
`Not a setting of ${detail.key}: ${key}. Its settings: ${detail.settings.map((candidate) => candidate.key).join(", ") || "none"}`,
|
|
1757
|
+
);
|
|
1758
|
+
}
|
|
1759
|
+
if (setting.type === "secret") {
|
|
1760
|
+
throw misuse(
|
|
1761
|
+
`${key} is a secret: console plugin ${detail.key} secret ${key} reads it from standard input.`,
|
|
1762
|
+
);
|
|
1763
|
+
}
|
|
1764
|
+
if (word.length === 0) {
|
|
1765
|
+
values[key] = null;
|
|
1766
|
+
} else if (setting.type === "list") {
|
|
1767
|
+
values[key] = word
|
|
1768
|
+
.split(",")
|
|
1769
|
+
.map((item) => item.trim())
|
|
1770
|
+
.filter((item) => item.length > 0)
|
|
1771
|
+
.map((item) => read(setting.of ?? "text", item, key));
|
|
1772
|
+
} else {
|
|
1773
|
+
values[key] = read(setting.type, word, key);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
return values;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
/** The capabilities a plugin asks, each with what it opens and what it risks, for the administrator to read before consenting. */
|
|
1780
|
+
function capabilityLines(capabilities: readonly string[]): string[] {
|
|
1781
|
+
return capabilities.map((capability) => {
|
|
1782
|
+
const words = capabilityWords(capability);
|
|
1783
|
+
return words === undefined
|
|
1784
|
+
? ` ${capability}: a capability this command does not know`
|
|
1785
|
+
: ` ${capability}: opens ${words.opens}; a malicious plugin reaches ${words.reach}`;
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
/** Enables a plugin, with consent to every capability it asks, named again; an administrator's. */
|
|
1790
|
+
async function pluginEnable(
|
|
1791
|
+
io: CliIo,
|
|
1792
|
+
session: Session,
|
|
1793
|
+
client: PluginClient,
|
|
1794
|
+
rest: readonly string[],
|
|
1795
|
+
): Promise<number> {
|
|
1796
|
+
const { values, positionals } = parse(rest, {
|
|
1797
|
+
consent: { type: "string", multiple: true },
|
|
1798
|
+
board: { type: "string" },
|
|
1799
|
+
projects: { type: "string", multiple: true },
|
|
1800
|
+
set: { type: "string", multiple: true },
|
|
1801
|
+
});
|
|
1802
|
+
if (positionals.length > 0) {
|
|
1803
|
+
throw misuse(
|
|
1804
|
+
`Nothing follows enable but its flags: console plugin ${client.key} enable [--consent <capability>,...] [--board new|<key>] [--projects all|<key>,...] [--set <key>=<value>]...`,
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1807
|
+
const [detail, me] = await Promise.all([client.get(), session.client.me()]);
|
|
1808
|
+
const asked = detail.capabilities;
|
|
1809
|
+
const consent = texts(values, "consent")
|
|
1810
|
+
.flatMap((value) => value.split(","))
|
|
1811
|
+
.map((capability) => capability.trim())
|
|
1812
|
+
.filter((capability) => capability.length > 0);
|
|
1813
|
+
if (asked.length > 0 && !session.json) {
|
|
1814
|
+
io.stdout(
|
|
1815
|
+
`${detail.key} asks ${asked.length === 1 ? "one capability" : `${asked.length} capabilities`}:\n${capabilityLines(asked).join("\n")}\n`,
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
const missing = asked.filter((capability) => !consent.includes(capability));
|
|
1819
|
+
if (missing.length > 0) {
|
|
1820
|
+
throw misuse(
|
|
1821
|
+
`Enabling ${detail.key} needs consent to every capability it asks, named again: --consent ${asked.join(",")}. Not named: ${missing.join(", ")}.`,
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1824
|
+
const boardFlag = text(values, "board");
|
|
1825
|
+
let board: RestPluginEnableInput["board"] = null;
|
|
1826
|
+
if (boardFlag === "new") {
|
|
1827
|
+
if (detail.boardDefaults === null) {
|
|
1828
|
+
throw misuse(
|
|
1829
|
+
`${detail.key} names no defaults for a board project: choose one with --board <key>.`,
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1832
|
+
board = {
|
|
1833
|
+
create: {
|
|
1834
|
+
key: detail.boardDefaults.key,
|
|
1835
|
+
name: localized(detail.boardDefaults.name, me.language),
|
|
1836
|
+
description: localized(detail.boardDefaults.description, me.language),
|
|
1837
|
+
},
|
|
1838
|
+
};
|
|
1839
|
+
} else if (boardFlag !== undefined) {
|
|
1840
|
+
board = { project: boardFlag };
|
|
1841
|
+
}
|
|
1842
|
+
const named = texts(values, "projects")
|
|
1843
|
+
.flatMap((value) => value.split(","))
|
|
1844
|
+
.map((key) => key.trim())
|
|
1845
|
+
.filter((key) => key.length > 0);
|
|
1846
|
+
const settings = settingValuesOf(texts(values, "set"), detail);
|
|
1847
|
+
const enabled = await client.enable({
|
|
1848
|
+
consent,
|
|
1849
|
+
board,
|
|
1850
|
+
projects: named.length === 0 || named.includes("all") ? "all" : named,
|
|
1851
|
+
...(Object.keys(settings).length === 0 ? {} : { settings }),
|
|
1852
|
+
});
|
|
1853
|
+
answer(io, session, enabled, () =>
|
|
1854
|
+
[
|
|
1855
|
+
`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"}.`,
|
|
1856
|
+
...(enabled.settings.length === 0
|
|
1857
|
+
? []
|
|
1858
|
+
: [
|
|
1859
|
+
"Settings:",
|
|
1860
|
+
...enabled.settings.map((setting) => formatPluginSettingLine(setting, me.language)),
|
|
1861
|
+
]),
|
|
1862
|
+
].join("\n"),
|
|
1863
|
+
);
|
|
1864
|
+
return EXIT.ok;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
/** The settings as they stand, or changed as `--set` says; an administrator's. */
|
|
1868
|
+
async function pluginSettings(
|
|
1869
|
+
io: CliIo,
|
|
1870
|
+
session: Session,
|
|
1871
|
+
client: PluginClient,
|
|
1872
|
+
rest: readonly string[],
|
|
1873
|
+
): Promise<number> {
|
|
1874
|
+
const { values, positionals } = parse(rest, { set: { type: "string", multiple: true } });
|
|
1875
|
+
if (positionals.length > 0) {
|
|
1876
|
+
throw misuse(
|
|
1877
|
+
`Nothing follows settings but --set: console plugin ${client.key} settings [--set <key>=<value>]...`,
|
|
1878
|
+
);
|
|
1879
|
+
}
|
|
1880
|
+
const [detail, me] = await Promise.all([client.get(), session.client.me()]);
|
|
1881
|
+
const sets = texts(values, "set");
|
|
1882
|
+
if (sets.length === 0) {
|
|
1883
|
+
answer(io, session, detail.settings, () =>
|
|
1884
|
+
detail.settings.length === 0
|
|
1885
|
+
? `${detail.key} has no settings.`
|
|
1886
|
+
: [
|
|
1887
|
+
`Settings of ${detail.key}:`,
|
|
1888
|
+
...detail.settings.map((setting) => formatPluginSettingLine(setting, me.language)),
|
|
1889
|
+
].join("\n"),
|
|
1890
|
+
);
|
|
1891
|
+
return EXIT.ok;
|
|
1892
|
+
}
|
|
1893
|
+
const changed = await client.updateSettings({ values: settingValuesOf(sets, detail) });
|
|
1894
|
+
answer(io, session, changed.settings, () =>
|
|
1895
|
+
[
|
|
1896
|
+
`Changed the settings of ${changed.key}:`,
|
|
1897
|
+
...changed.settings.map((setting) => formatPluginSettingLine(setting, me.language)),
|
|
1898
|
+
].join("\n"),
|
|
1899
|
+
);
|
|
1900
|
+
return EXIT.ok;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
/** Seals a secret of the plugin, read from standard input and shown never; or takes it away. */
|
|
1904
|
+
async function pluginSecret(
|
|
1905
|
+
io: CliIo,
|
|
1906
|
+
session: Session,
|
|
1907
|
+
client: PluginClient,
|
|
1908
|
+
rest: readonly string[],
|
|
1909
|
+
): Promise<number> {
|
|
1910
|
+
const { values, positionals } = parse(rest, { remove: { type: "boolean" } });
|
|
1911
|
+
const name = theOne(
|
|
1912
|
+
positionals,
|
|
1913
|
+
`Say which secret: console plugin ${client.key} secret <name> [--remove]; the value is read from standard input.`,
|
|
1914
|
+
);
|
|
1915
|
+
if (on(values, "remove")) {
|
|
1916
|
+
await client.removeSecret(name);
|
|
1917
|
+
answer(io, session, { name, set: false }, () => `Removed the secret ${name} of ${client.key}.`);
|
|
1918
|
+
return EXIT.ok;
|
|
1919
|
+
}
|
|
1920
|
+
const value = (await io.readStdin()).replace(/\r?\n$/, "");
|
|
1921
|
+
if (value.length === 0) {
|
|
1922
|
+
throw misuse(
|
|
1923
|
+
`Pipe the value in: console plugin ${client.key} secret ${name} < file. It is sealed by the console and shown never.`,
|
|
1924
|
+
);
|
|
1925
|
+
}
|
|
1926
|
+
await client.setSecret(name, value);
|
|
1927
|
+
answer(
|
|
1928
|
+
io,
|
|
1929
|
+
session,
|
|
1930
|
+
{ name, set: true },
|
|
1931
|
+
() => `Set the secret ${name} of ${client.key}: sealed, and shown by nothing from now on.`,
|
|
1932
|
+
);
|
|
1933
|
+
return EXIT.ok;
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
/** Grants a role of the plugin to a person, to the members of a project, or to everyone; an administrator's. */
|
|
1937
|
+
async function pluginGrant(
|
|
1938
|
+
io: CliIo,
|
|
1939
|
+
session: Session,
|
|
1940
|
+
client: PluginClient,
|
|
1941
|
+
rest: readonly string[],
|
|
1942
|
+
projectKey: string | undefined,
|
|
1943
|
+
): Promise<number> {
|
|
1944
|
+
const { values, positionals } = parse(rest, {
|
|
1945
|
+
person: { type: "string" },
|
|
1946
|
+
everyone: { type: "boolean" },
|
|
1947
|
+
});
|
|
1948
|
+
const usage = `console plugin ${client.key} grant <role> --person <login> | --project <key> | --everyone`;
|
|
1949
|
+
const role = theOne(positionals, `Say which role: ${usage}`);
|
|
1950
|
+
const login = text(values, "person");
|
|
1951
|
+
const everyone = on(values, "everyone");
|
|
1952
|
+
if ([login !== undefined, projectKey !== undefined, everyone].filter(Boolean).length !== 1) {
|
|
1953
|
+
throw misuse(`Say whom, one of the three: ${usage}`);
|
|
1954
|
+
}
|
|
1955
|
+
let input: RestPluginGrantInput;
|
|
1956
|
+
if (login !== undefined) {
|
|
1957
|
+
const person = await session.client.findPerson(login);
|
|
1958
|
+
if (person === undefined) {
|
|
1959
|
+
throw failed(`No person has the login ${login} among those you may see.`);
|
|
1960
|
+
}
|
|
1961
|
+
input = { role, person: person.id };
|
|
1962
|
+
} else if (projectKey !== undefined) {
|
|
1963
|
+
input = { role, project: projectKey };
|
|
1964
|
+
} else {
|
|
1965
|
+
input = { role, everyone: true };
|
|
1966
|
+
}
|
|
1967
|
+
const granted = await client.grant(input);
|
|
1968
|
+
answer(io, session, granted, () => `Granted: ${formatPluginGrantLine(granted)}`);
|
|
1969
|
+
return EXIT.ok;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1304
1972
|
/** A version number given as a flag, or nothing; anything else is not understood. */
|
|
1305
1973
|
function versionOf(given: string | undefined): number | undefined {
|
|
1306
1974
|
if (given === undefined) {
|
|
@@ -1392,9 +2060,10 @@ const rules: Command = async (args, io) => {
|
|
|
1392
2060
|
? {
|
|
1393
2061
|
workspace: forPerson(found.workspace, me.language),
|
|
1394
2062
|
project: forPerson(found.project, me.language),
|
|
2063
|
+
...(found.plugins === undefined ? {} : { plugins: found.plugins }),
|
|
1395
2064
|
}
|
|
1396
2065
|
: forPerson(found, me.language);
|
|
1397
|
-
answer(io, session, read, () => formatRules(read));
|
|
2066
|
+
answer(io, session, read, () => formatRules(read, me.language));
|
|
1398
2067
|
return EXIT.ok;
|
|
1399
2068
|
};
|
|
1400
2069
|
|
|
@@ -1722,6 +2391,7 @@ const take: Command = async (args, io) => {
|
|
|
1722
2391
|
const rules = {
|
|
1723
2392
|
workspace: forPerson(ruling.workspace, me.language),
|
|
1724
2393
|
project: forPerson(ruling.project, me.language),
|
|
2394
|
+
...(ruling.plugins === undefined ? {} : { plugins: ruling.plugins }),
|
|
1725
2395
|
};
|
|
1726
2396
|
answer(io, session, { run, task: taken, comments: comments.items, rules }, () =>
|
|
1727
2397
|
[
|
|
@@ -1732,7 +2402,7 @@ const take: Command = async (args, io) => {
|
|
|
1732
2402
|
"",
|
|
1733
2403
|
formatTask(taken, undefined, undefined, comments.items),
|
|
1734
2404
|
"",
|
|
1735
|
-
formatRules(rules),
|
|
2405
|
+
formatRules(rules, me.language),
|
|
1736
2406
|
"",
|
|
1737
2407
|
'Say how it goes: console report "<what you found or did>". Hand in what you make: console hand-in <https url | file path>.',
|
|
1738
2408
|
'Ask when a person must decide: console ask "<question>" --option "..." --option "...". A word for the task\'s people that is not a report: console comment <number> "<markdown>". End with: console finish --summary "<what was done, what is left>".',
|
|
@@ -2499,8 +3169,8 @@ const workspace: Command = async (args, io) => {
|
|
|
2499
3169
|
set: (token) => client.setWorkspacePackToken({ token }),
|
|
2500
3170
|
remove: () => client.removeWorkspacePackToken(),
|
|
2501
3171
|
connect: () => client.connectWorkspacePackToken(),
|
|
2502
|
-
async
|
|
2503
|
-
const state = await client.
|
|
3172
|
+
async claim() {
|
|
3173
|
+
const state = await client.claimWorkspacePackTokenConnection();
|
|
2504
3174
|
return state.status === "connected"
|
|
2505
3175
|
? { status: "connected", held: state.workspace }
|
|
2506
3176
|
: state;
|
|
@@ -2660,7 +3330,8 @@ interface TokenHolder<T> {
|
|
|
2660
3330
|
set(token: string): Promise<T>;
|
|
2661
3331
|
remove(): Promise<T>;
|
|
2662
3332
|
connect(): Promise<RestPackConnectStarted>;
|
|
2663
|
-
|
|
3333
|
+
/** Asks the console to claim the token at the deployment (ADR-0046), and answers how it stands. */
|
|
3334
|
+
claim(): Promise<
|
|
2664
3335
|
| { readonly status: "none" }
|
|
2665
3336
|
| { readonly status: "pending" }
|
|
2666
3337
|
| { readonly status: "ended"; readonly reason: keyof typeof PACK_CONNECT_ENDED }
|
|
@@ -2692,7 +3363,7 @@ async function packToken<T>(
|
|
|
2692
3363
|
);
|
|
2693
3364
|
for (;;) {
|
|
2694
3365
|
await io.sleep(started.intervalSeconds * 1000);
|
|
2695
|
-
const state = await holder.
|
|
3366
|
+
const state = await holder.claim();
|
|
2696
3367
|
if (state.status === "pending") {
|
|
2697
3368
|
continue;
|
|
2698
3369
|
}
|
|
@@ -2740,8 +3411,8 @@ const projectPackToken: Command = async (args, io) => {
|
|
|
2740
3411
|
set: (token) => project.setPackToken({ token }),
|
|
2741
3412
|
remove: () => project.removePackToken(),
|
|
2742
3413
|
connect: () => project.connectPackToken(),
|
|
2743
|
-
async
|
|
2744
|
-
const state = await project.
|
|
3414
|
+
async claim() {
|
|
3415
|
+
const state = await project.claimPackTokenConnection();
|
|
2745
3416
|
return state.status === "connected" ? { status: "connected", held: state.project } : state;
|
|
2746
3417
|
},
|
|
2747
3418
|
},
|
|
@@ -2971,6 +3642,8 @@ const COMMANDS: Readonly<Record<string, Command>> = {
|
|
|
2971
3642
|
skills,
|
|
2972
3643
|
skill,
|
|
2973
3644
|
rules,
|
|
3645
|
+
plugins,
|
|
3646
|
+
plugin,
|
|
2974
3647
|
people,
|
|
2975
3648
|
person,
|
|
2976
3649
|
workspace,
|
|
@@ -3122,6 +3795,10 @@ function hintFor(error: ApiError, io: CliIo, seen: ConsoleSeen): string {
|
|
|
3122
3795
|
return `Install the console's version: ${updateWords(io, seen.version)}; or ask the console's operator to update the console.\n`;
|
|
3123
3796
|
case "auth.required":
|
|
3124
3797
|
return "The token is not one the console knows, any more or at all. A person signs the command in again: console login\n";
|
|
3798
|
+
case "auth.deactivated":
|
|
3799
|
+
return "Tell the person: they are taken back in on the People page, by an administrator or by their organization, and this token works again then. console logout forgets it here meanwhile.\n";
|
|
3800
|
+
case "auth.not_admitted":
|
|
3801
|
+
return "Tell the person: they are back in when what let them in is named again, or when an administrator lists their login and they sign in again, and this token works again then. console logout forgets it here meanwhile.\n";
|
|
3125
3802
|
case "project.not_found":
|
|
3126
3803
|
return "No project has this key, or it is not yours to see. console projects lists the projects you may work in.\n";
|
|
3127
3804
|
case "document.not_found":
|
|
@@ -3146,6 +3823,20 @@ function hintFor(error: ApiError, io: CliIo, seen: ConsoleSeen): string {
|
|
|
3146
3823
|
return "What was asked is an administrator's, or an owner's of the project, and the person this token acts as is not one here. Hand the step to them.\n";
|
|
3147
3824
|
case "project.key_taken":
|
|
3148
3825
|
return "A project has that key already. console projects lists them.\n";
|
|
3826
|
+
case "project.too_many":
|
|
3827
|
+
return "The workspace holds as many projects as one may. Work goes on in a project there is.\n";
|
|
3828
|
+
case "project.too_many_bytes":
|
|
3829
|
+
return "The project's files hold as many bytes together as this console allows. Hand in a link to where the file is instead, and say so in the report.\n";
|
|
3830
|
+
case "task.too_many":
|
|
3831
|
+
return "The project holds as many tasks as one may, those put away counted. The work goes on in another project; ask the person which.\n";
|
|
3832
|
+
case "goal.too_many":
|
|
3833
|
+
return "The project holds as many goals as one may. console goals lists them; one done or dropped is still a goal.\n";
|
|
3834
|
+
case "decision.too_many":
|
|
3835
|
+
return "The project holds as many decisions as one may. Ask the person in the report instead.\n";
|
|
3836
|
+
case "document.too_many":
|
|
3837
|
+
return "The project holds as many pages as one may. Write on a page there is: console docs lists them.\n";
|
|
3838
|
+
case "run.too_many":
|
|
3839
|
+
return "The task has been taken as many times as one may. Write a new task for what is left, and take that.\n";
|
|
3149
3840
|
case "project.invalid_address":
|
|
3150
3841
|
return "The skills address is not one as SkillCDN spells it; none makes the organization's skills the project's.\n";
|
|
3151
3842
|
case "project.invalid_icon":
|