@skillcdn/console 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -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 +569 -48
- 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 +283 -7
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +44 -7
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/plugin-check.d.ts +26 -0
- package/dist/cli/plugin-check.d.ts.map +1 -0
- package/dist/cli/plugin-check.js +396 -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 +189 -0
- package/dist/cli/plugin-cli.js.map +1 -0
- package/dist/cli/plugin-init.d.ts +37 -0
- package/dist/cli/plugin-init.d.ts.map +1 -0
- package/dist/cli/plugin-init.js +206 -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 +100 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +79 -7
- 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/comments.d.ts.map +1 -1
- package/dist/components/comments.js +1 -1
- package/dist/components/comments.js.map +1 -1
- package/dist/components/decision-form.d.ts +2 -1
- package/dist/components/decision-form.d.ts.map +1 -1
- package/dist/components/decision-form.js +36 -4
- package/dist/components/decision-form.js.map +1 -1
- package/dist/components/decision-list.d.ts +4 -0
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +29 -3
- package/dist/components/decision-list.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/my-decisions.d.ts +2 -0
- package/dist/components/my-decisions.d.ts.map +1 -1
- package/dist/components/my-decisions.js +2 -2
- package/dist/components/my-decisions.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/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/task-view.d.ts +2 -1
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.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 +330 -1
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +152 -41
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +9 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +45 -9
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +155 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +166 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +150 -0
- 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 +2 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +2 -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 +380 -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 +4 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +6 -0
- package/dist/routes.js.map +1 -1
- package/dist/schemas.d.ts +194 -9
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +98 -17
- 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 +2 -0
- package/dist/web.js.map +1 -1
- package/docs/specs/cli.md +12 -5
- package/docs/specs/plugins.md +488 -0
- package/docs/specs/rest.md +11 -9
- package/package.json +26 -8
- 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 +12 -0
- package/src/cli/cli.ts +737 -55
- package/src/cli/format.ts +368 -10
- package/src/cli/help.ts +50 -6
- package/src/cli/plugin-check.ts +497 -0
- package/src/cli/plugin-cli.ts +237 -0
- package/src/cli/plugin-init.ts +257 -0
- package/src/cli/plugin-main.ts +30 -0
- package/src/client.ts +322 -9
- package/src/compat.ts +2 -2
- package/src/components/board.tsx +62 -14
- package/src/components/comments.tsx +1 -0
- package/src/components/decision-form.tsx +92 -11
- package/src/components/decision-list.tsx +71 -2
- package/src/components/event-feed.tsx +42 -1
- package/src/components/my-decisions.tsx +4 -1
- package/src/components/plugin-frame.tsx +437 -0
- package/src/components/plugin-settings.tsx +1111 -0
- package/src/components/shell.tsx +82 -14
- package/src/components/task-view.tsx +2 -1
- package/src/components/workspace.tsx +18 -0
- package/src/console.tsx +304 -31
- package/src/data.ts +59 -11
- package/src/i18n/en.ts +175 -0
- package/src/i18n/ko.ts +155 -0
- package/src/index.ts +12 -0
- package/src/limits.ts +2 -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 +421 -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 +7 -0
- package/src/schemas.ts +115 -18
- package/src/styles/console.css +330 -1
- package/src/tokens.ts +101 -0
- package/src/vocabulary.ts +30 -0
- package/src/web.ts +2 -0
- 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,8 @@ import {
|
|
|
5
5
|
type ConsoleClient,
|
|
6
6
|
createClient,
|
|
7
7
|
type FetchLike,
|
|
8
|
+
type FileUpload,
|
|
9
|
+
type PluginClient,
|
|
8
10
|
type ProjectClient,
|
|
9
11
|
} from "../client.js";
|
|
10
12
|
import {
|
|
@@ -20,6 +22,8 @@ import {
|
|
|
20
22
|
} from "../compat.js";
|
|
21
23
|
import { addDays, dayOf as dayOfDate, firstDayOfWeek, isDay, weekOf } from "../dates.js";
|
|
22
24
|
import { DEFAULT_PAGE_LIMIT, LIST_LIMIT, MAX_OPTIONS, MIN_OPTIONS } from "../limits.js";
|
|
25
|
+
import { capabilityWords } from "../plugin/capabilities.js";
|
|
26
|
+
import { PLUGIN_LIST_LIMIT } from "../plugin/limits.js";
|
|
23
27
|
import { fillRules, RULES_PATH, RULES_TITLE } from "../rules.js";
|
|
24
28
|
import {
|
|
25
29
|
hostOfLogin,
|
|
@@ -28,6 +32,10 @@ import {
|
|
|
28
32
|
type RestMember,
|
|
29
33
|
type RestPackConnectStarted,
|
|
30
34
|
type RestPerson,
|
|
35
|
+
type RestPluginDetail,
|
|
36
|
+
type RestPluginEnableInput,
|
|
37
|
+
type RestPluginGrantInput,
|
|
38
|
+
type RestPluginInvocation,
|
|
31
39
|
type RestSkillBrowse,
|
|
32
40
|
type RestSkillFile,
|
|
33
41
|
type RestSkillLoad,
|
|
@@ -78,12 +86,21 @@ import {
|
|
|
78
86
|
formatDocuments,
|
|
79
87
|
formatGoal,
|
|
80
88
|
formatGoalLine,
|
|
89
|
+
formatInvocation,
|
|
81
90
|
formatListingLine,
|
|
82
91
|
formatMemberLine,
|
|
83
92
|
formatMore,
|
|
84
93
|
formatOptions,
|
|
85
94
|
formatPersonLine,
|
|
86
95
|
formatPersonWork,
|
|
96
|
+
formatPlugin,
|
|
97
|
+
formatPluginGrantLine,
|
|
98
|
+
formatPluginHelp,
|
|
99
|
+
formatPluginRecord,
|
|
100
|
+
formatPluginRecords,
|
|
101
|
+
formatPluginRecordVersions,
|
|
102
|
+
formatPluginSettingLine,
|
|
103
|
+
formatPlugins,
|
|
87
104
|
formatProject,
|
|
88
105
|
formatProjectLine,
|
|
89
106
|
formatRules,
|
|
@@ -95,6 +112,7 @@ import {
|
|
|
95
112
|
formatVersionLine,
|
|
96
113
|
formatWork,
|
|
97
114
|
formatWorkspace,
|
|
115
|
+
localized,
|
|
98
116
|
moment,
|
|
99
117
|
setMomentZone,
|
|
100
118
|
} from "./format.js";
|
|
@@ -1107,20 +1125,55 @@ const MAX_FIND_LIMIT = 25;
|
|
|
1107
1125
|
|
|
1108
1126
|
/** The pack of the project as the console lists it, and what its reads are rendered at; none when the pack did not answer. */
|
|
1109
1127
|
async function packOf(
|
|
1110
|
-
|
|
1128
|
+
reader: PackReader,
|
|
1111
1129
|
): Promise<{ readonly listing: RestSkills; readonly summary: PackSummary | undefined }> {
|
|
1112
|
-
const listing = await
|
|
1130
|
+
const listing = await reader.skills();
|
|
1113
1131
|
return { listing, summary: summaryOf(listing) };
|
|
1114
1132
|
}
|
|
1115
1133
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1134
|
+
/** What reads a pack: a project's client, or a plugin's, whose skills are a pack of their own. */
|
|
1135
|
+
type PackReader = Pick<
|
|
1136
|
+
ProjectClient,
|
|
1137
|
+
"skills" | "skill" | "skillFile" | "skillBrowse" | "skillFind"
|
|
1138
|
+
>;
|
|
1139
|
+
|
|
1140
|
+
const SKILLS_OPTIONS: OptionSpec = {
|
|
1141
|
+
search: { type: "string" },
|
|
1142
|
+
limit: { type: "string" },
|
|
1143
|
+
cursor: { type: "string" },
|
|
1144
|
+
};
|
|
1145
|
+
|
|
1146
|
+
const SKILL_OPTIONS: OptionSpec = {
|
|
1147
|
+
file: { type: "string" },
|
|
1148
|
+
offset: { type: "string" },
|
|
1149
|
+
limit: { type: "string" },
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
/** The line `console skills` ends with: where the plugins' own skills are. */
|
|
1153
|
+
const PLUGIN_SKILLS_LINE =
|
|
1154
|
+
"The plugins of this console have skills of their own: console plugins lists the plugins, console plugin <key> skills the skills of one.";
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* The pack as the words say: whole, a folder of it, a file's outline, or what words find; for
|
|
1158
|
+
* the project's pack as `console skills`, and for a plugin's as `console plugin <key> skills`.
|
|
1159
|
+
*/
|
|
1160
|
+
async function readSkills(
|
|
1161
|
+
io: CliIo,
|
|
1162
|
+
session: Session,
|
|
1163
|
+
reader: PackReader,
|
|
1164
|
+
values: Values,
|
|
1165
|
+
positionals: readonly string[],
|
|
1166
|
+
extras: {
|
|
1167
|
+
/** The command these words came after, for what is said when they are not understood. */
|
|
1168
|
+
readonly command: string;
|
|
1169
|
+
/** Where the console's own skills are on this machine, said with the project's pack. */
|
|
1170
|
+
readonly own?: string | undefined;
|
|
1171
|
+
/** A line after the listing whole. */
|
|
1172
|
+
readonly afterwards?: string | undefined;
|
|
1173
|
+
},
|
|
1174
|
+
): Promise<number> {
|
|
1122
1175
|
if (positionals.length > 1) {
|
|
1123
|
-
throw misuse(
|
|
1176
|
+
throw misuse(`Say one folder: ${extras.command} [<folder>] [--search <words>]`);
|
|
1124
1177
|
}
|
|
1125
1178
|
// The root of the pack is the mounted folder, which the deployment reads as no path at all.
|
|
1126
1179
|
const given = positionals[0];
|
|
@@ -1130,7 +1183,7 @@ const skills: Command = async (args, io) => {
|
|
|
1130
1183
|
const cursor = text(values, "cursor");
|
|
1131
1184
|
if (words === undefined && (limit !== undefined || cursor !== undefined)) {
|
|
1132
1185
|
throw misuse(
|
|
1133
|
-
|
|
1186
|
+
`--limit and --cursor go with --search: ${extras.command} --search <words> [--limit <n>] [--cursor <cursor>]`,
|
|
1134
1187
|
);
|
|
1135
1188
|
}
|
|
1136
1189
|
const atMost = limit === undefined ? undefined : Number(limit);
|
|
@@ -1140,17 +1193,19 @@ const skills: Command = async (args, io) => {
|
|
|
1140
1193
|
) {
|
|
1141
1194
|
throw misuse(`Not a number of results, 1 to ${MAX_FIND_LIMIT}: ${limit}`);
|
|
1142
1195
|
}
|
|
1143
|
-
const
|
|
1144
|
-
const project = await session.project();
|
|
1145
|
-
const { listing, summary } = await packOf(project);
|
|
1196
|
+
const { listing, summary } = await packOf(reader);
|
|
1146
1197
|
// The pack as it stands; or why it does not answer, which a folder or a search would meet too.
|
|
1147
1198
|
if (summary === undefined || (words === undefined && given === undefined)) {
|
|
1148
|
-
answer(io, session, listing, () =>
|
|
1199
|
+
answer(io, session, listing, () =>
|
|
1200
|
+
[formatSkills(listing, extras.own), extras.afterwards]
|
|
1201
|
+
.filter((part): part is string => part !== undefined)
|
|
1202
|
+
.join("\n"),
|
|
1203
|
+
);
|
|
1149
1204
|
return EXIT.ok;
|
|
1150
1205
|
}
|
|
1151
1206
|
if (words !== undefined) {
|
|
1152
1207
|
// One page of what the words find; more is said, and asked for with --cursor.
|
|
1153
|
-
const found = await
|
|
1208
|
+
const found = await reader.skillFind(words, { path: folder, limit: atMost, cursor });
|
|
1154
1209
|
answer(io, session, found, () => formatSkillFind(found, summary));
|
|
1155
1210
|
return EXIT.ok;
|
|
1156
1211
|
}
|
|
@@ -1159,7 +1214,7 @@ const skills: Command = async (args, io) => {
|
|
|
1159
1214
|
let next: string | undefined;
|
|
1160
1215
|
let last: RestSkillBrowse | undefined;
|
|
1161
1216
|
for (let n = 0; n < MAX_SKILL_PAGES; n += 1) {
|
|
1162
|
-
last = await
|
|
1217
|
+
last = await reader.skillBrowse({ path: folder, cursor: next });
|
|
1163
1218
|
if (last.read?.status !== "ready") {
|
|
1164
1219
|
break;
|
|
1165
1220
|
}
|
|
@@ -1178,6 +1233,17 @@ const skills: Command = async (args, io) => {
|
|
|
1178
1233
|
formatSkillBrowse(browsed, whole),
|
|
1179
1234
|
);
|
|
1180
1235
|
return EXIT.ok;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
const skills: Command = async (args, io) => {
|
|
1239
|
+
const { values, positionals } = parse(args, SKILLS_OPTIONS);
|
|
1240
|
+
const session = await open(io, values);
|
|
1241
|
+
const project = await session.project();
|
|
1242
|
+
return readSkills(io, session, project, values, positionals, {
|
|
1243
|
+
command: "console skills",
|
|
1244
|
+
own: io.skillsDir,
|
|
1245
|
+
afterwards: PLUGIN_SKILLS_LINE,
|
|
1246
|
+
});
|
|
1181
1247
|
};
|
|
1182
1248
|
|
|
1183
1249
|
/** The most characters one read of a file asks for: what the console's API allows on one page. */
|
|
@@ -1205,18 +1271,24 @@ function countOf(
|
|
|
1205
1271
|
return count;
|
|
1206
1272
|
}
|
|
1207
1273
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1274
|
+
/**
|
|
1275
|
+
* One skill of the pack whole, a file of it, or one page of a file; for the project's pack as
|
|
1276
|
+
* `console skill`, and for a plugin's as `console plugin <key> skill`.
|
|
1277
|
+
*/
|
|
1278
|
+
async function readSkill(
|
|
1279
|
+
io: CliIo,
|
|
1280
|
+
session: Session,
|
|
1281
|
+
reader: PackReader,
|
|
1282
|
+
values: Values,
|
|
1283
|
+
positionals: readonly string[],
|
|
1284
|
+
command: string,
|
|
1285
|
+
): Promise<number> {
|
|
1214
1286
|
const file = text(values, "file");
|
|
1215
1287
|
// A file is read by its own path from the repository's root; a skill's path beside it would be
|
|
1216
1288
|
// ignored, which reads as if it mattered, so the two together are not understood.
|
|
1217
1289
|
if (file !== undefined && positionals.length > 0) {
|
|
1218
1290
|
throw misuse(
|
|
1219
|
-
|
|
1291
|
+
`Say one: ${command} <path of its SKILL.md> for a skill, or ${command} --file <path> for a file of the pack.`,
|
|
1220
1292
|
);
|
|
1221
1293
|
}
|
|
1222
1294
|
// A section is a page of a file read as asked (SkillCDN 0.4): the flags mean nothing without one.
|
|
@@ -1225,14 +1297,12 @@ const skill: Command = async (args, io) => {
|
|
|
1225
1297
|
(text(values, "offset") !== undefined || text(values, "limit") !== undefined)
|
|
1226
1298
|
) {
|
|
1227
1299
|
throw misuse(
|
|
1228
|
-
|
|
1300
|
+
`--offset and --limit go with --file: ${command} --file <path> [--offset <n>] [--limit <n>]`,
|
|
1229
1301
|
);
|
|
1230
1302
|
}
|
|
1231
1303
|
const offset = countOf(text(values, "offset"), "offset", { least: 0 });
|
|
1232
1304
|
const limit = countOf(text(values, "limit"), "limit", { least: 1, most: MAX_FILE_LIMIT });
|
|
1233
|
-
const
|
|
1234
|
-
const project = await session.project();
|
|
1235
|
-
const { listing, summary } = await packOf(project);
|
|
1305
|
+
const { listing, summary } = await packOf(reader);
|
|
1236
1306
|
if (summary === undefined) {
|
|
1237
1307
|
answer(io, session, listing, () => formatSkills(listing, io.skillsDir));
|
|
1238
1308
|
return EXIT.ok;
|
|
@@ -1240,7 +1310,7 @@ const skill: Command = async (args, io) => {
|
|
|
1240
1310
|
if (file !== undefined && (offset !== undefined || limit !== undefined)) {
|
|
1241
1311
|
// One page as asked, the section a search or an outline named; the result says where the
|
|
1242
1312
|
// file continues, and asks for nothing.
|
|
1243
|
-
const page = await
|
|
1313
|
+
const page = await reader.skillFile(file, { offset, limit });
|
|
1244
1314
|
const section = page.read === null ? undefined : joinFile([page], summary, limit);
|
|
1245
1315
|
answer(io, session, { ...page, read: section === undefined ? null : bodyOfFile(section) }, () =>
|
|
1246
1316
|
formatSkillFile(page, section),
|
|
@@ -1253,7 +1323,7 @@ const skill: Command = async (args, io) => {
|
|
|
1253
1323
|
let offset: number | undefined;
|
|
1254
1324
|
let last: RestSkillFile | undefined;
|
|
1255
1325
|
for (let n = 0; n < MAX_SKILL_PAGES; n += 1) {
|
|
1256
|
-
last = await
|
|
1326
|
+
last = await reader.skillFile(file, offset === undefined ? {} : { offset });
|
|
1257
1327
|
if (last.read === null) {
|
|
1258
1328
|
break;
|
|
1259
1329
|
}
|
|
@@ -1276,7 +1346,7 @@ const skill: Command = async (args, io) => {
|
|
|
1276
1346
|
const wanted = skillPathIn(
|
|
1277
1347
|
theOne(
|
|
1278
1348
|
positionals,
|
|
1279
|
-
|
|
1349
|
+
`Say which skill: ${command} <path of its SKILL.md>, or ${command} --file <path> for a file of the pack`,
|
|
1280
1350
|
),
|
|
1281
1351
|
listing.address,
|
|
1282
1352
|
);
|
|
@@ -1288,7 +1358,7 @@ const skill: Command = async (args, io) => {
|
|
|
1288
1358
|
let cursor: string | undefined;
|
|
1289
1359
|
let last: RestSkillLoad | undefined;
|
|
1290
1360
|
for (let n = 0; n < MAX_SKILL_PAGES; n += 1) {
|
|
1291
|
-
last = await
|
|
1361
|
+
last = await reader.skill(wanted.path, cursor);
|
|
1292
1362
|
if (last.read?.status !== "ready") {
|
|
1293
1363
|
break;
|
|
1294
1364
|
}
|
|
@@ -1307,8 +1377,599 @@ const skill: Command = async (args, io) => {
|
|
|
1307
1377
|
formatSkillLoad(loaded, whole),
|
|
1308
1378
|
);
|
|
1309
1379
|
return EXIT.ok;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
const skill: Command = async (args, io) => {
|
|
1383
|
+
const { values, positionals } = parse(args, SKILL_OPTIONS);
|
|
1384
|
+
const session = await open(io, values);
|
|
1385
|
+
const project = await session.project();
|
|
1386
|
+
return readSkill(io, session, project, values, positionals, "console skill");
|
|
1387
|
+
};
|
|
1388
|
+
|
|
1389
|
+
// ---- The plugins (docs/specs/plugins.md, "The command") ----
|
|
1390
|
+
// `console plugins` and `console plugin <key> ...` are the two entries: the command sends the
|
|
1391
|
+
// words as typed and renders the console's generic shapes, and knows nothing of any plugin.
|
|
1392
|
+
|
|
1393
|
+
const plugins: Command = async (args, io) => {
|
|
1394
|
+
const { values } = parse(args, {}, false);
|
|
1395
|
+
const session = await open(io, values);
|
|
1396
|
+
const [listed, me] = await Promise.all([session.client.plugins(), session.client.me()]);
|
|
1397
|
+
answer(io, session, listed, () => formatPlugins(listed, me.language));
|
|
1398
|
+
return EXIT.ok;
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
/** The flags of the command's own on `console plugin ...`, wherever they stand among the words. */
|
|
1402
|
+
const PLUGIN_GLOBAL_FLAGS: Readonly<Record<string, "string" | "boolean">> = {
|
|
1403
|
+
json: "boolean",
|
|
1404
|
+
url: "string",
|
|
1405
|
+
account: "string",
|
|
1406
|
+
project: "string",
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1409
|
+
/** The flags of the command's own on a plugin's command: the file it uploads first, the run it is for. */
|
|
1410
|
+
const PLUGIN_COMMAND_FLAGS: Readonly<Record<string, "string" | "boolean">> = {
|
|
1411
|
+
file: "string",
|
|
1412
|
+
run: "string",
|
|
1413
|
+
};
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* The words with the flags named in `own` taken out, wherever they stand: the rest is the
|
|
1417
|
+
* plugin's, and goes to the console as typed. `--` ends the flags, and stays among the words.
|
|
1418
|
+
*/
|
|
1419
|
+
function splitWords(
|
|
1420
|
+
args: readonly string[],
|
|
1421
|
+
own: Readonly<Record<string, "string" | "boolean">>,
|
|
1422
|
+
): { readonly values: Record<string, string | boolean>; readonly words: string[] } {
|
|
1423
|
+
const values: Record<string, string | boolean> = {};
|
|
1424
|
+
const words: string[] = [];
|
|
1425
|
+
let ended = false;
|
|
1426
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1427
|
+
const word = args[index] ?? "";
|
|
1428
|
+
if (ended || !word.startsWith("--") || word.length === 2) {
|
|
1429
|
+
ended = ended || word === "--";
|
|
1430
|
+
words.push(word);
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
const equals = word.indexOf("=");
|
|
1434
|
+
const name = equals < 0 ? word.slice(2) : word.slice(2, equals);
|
|
1435
|
+
const type = Object.hasOwn(own, name) ? own[name] : undefined;
|
|
1436
|
+
if (type === undefined) {
|
|
1437
|
+
words.push(word);
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1440
|
+
if (type === "boolean") {
|
|
1441
|
+
if (equals >= 0) {
|
|
1442
|
+
throw misuse(`--${name} takes no value`);
|
|
1443
|
+
}
|
|
1444
|
+
values[name] = true;
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
if (equals >= 0) {
|
|
1448
|
+
values[name] = word.slice(equals + 1);
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
const next = args[index + 1];
|
|
1452
|
+
if (next === undefined) {
|
|
1453
|
+
throw misuse(`--${name} needs a value`);
|
|
1454
|
+
}
|
|
1455
|
+
values[name] = next;
|
|
1456
|
+
index += 1;
|
|
1457
|
+
}
|
|
1458
|
+
return { values, words };
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
const PLUGIN_USAGE =
|
|
1462
|
+
"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.";
|
|
1463
|
+
|
|
1464
|
+
const plugin: Command = async (args, io) => {
|
|
1465
|
+
const { values, words } = splitWords(args, PLUGIN_GLOBAL_FLAGS);
|
|
1466
|
+
const [key, verb, ...rest] = words;
|
|
1467
|
+
if (key === undefined || key.startsWith("-")) {
|
|
1468
|
+
throw misuse(`Say which plugin: ${PLUGIN_USAGE}`);
|
|
1469
|
+
}
|
|
1470
|
+
const session = await open(io, values);
|
|
1471
|
+
const client = session.client.plugin(key);
|
|
1472
|
+
switch (verb) {
|
|
1473
|
+
case undefined: {
|
|
1474
|
+
const [detail, me] = await Promise.all([client.get(), session.client.me()]);
|
|
1475
|
+
answer(io, session, detail, () => formatPlugin(detail, me.language));
|
|
1476
|
+
return EXIT.ok;
|
|
1477
|
+
}
|
|
1478
|
+
case "help": {
|
|
1479
|
+
if (rest.length > 1) {
|
|
1480
|
+
throw misuse(`Say one command: console plugin ${key} help [<command>]`);
|
|
1481
|
+
}
|
|
1482
|
+
const name = rest[0];
|
|
1483
|
+
const detail = await client.get();
|
|
1484
|
+
const operation =
|
|
1485
|
+
name === undefined
|
|
1486
|
+
? undefined
|
|
1487
|
+
: detail.operations.find((candidate) => candidate.name === name);
|
|
1488
|
+
if (name !== undefined && operation === undefined) {
|
|
1489
|
+
throw misuse(
|
|
1490
|
+
`Not a command or a query of ${key} you may run: ${name}. console plugin ${key} lists them.`,
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
answer(io, session, operation ?? detail.operations, () => formatPluginHelp(detail, name));
|
|
1494
|
+
return EXIT.ok;
|
|
1495
|
+
}
|
|
1496
|
+
case "records":
|
|
1497
|
+
return pluginRecords(io, session, client, rest, text(values, "project"));
|
|
1498
|
+
case "skills": {
|
|
1499
|
+
const parsed = parse(rest, SKILLS_OPTIONS);
|
|
1500
|
+
return readSkills(io, session, client, parsed.values, parsed.positionals, {
|
|
1501
|
+
command: `console plugin ${key} skills`,
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
case "skill": {
|
|
1505
|
+
const parsed = parse(rest, SKILL_OPTIONS);
|
|
1506
|
+
return readSkill(
|
|
1507
|
+
io,
|
|
1508
|
+
session,
|
|
1509
|
+
client,
|
|
1510
|
+
parsed.values,
|
|
1511
|
+
parsed.positionals,
|
|
1512
|
+
`console plugin ${key} skill`,
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
case "enable":
|
|
1516
|
+
return pluginEnable(io, session, client, rest);
|
|
1517
|
+
case "disable": {
|
|
1518
|
+
parse(rest, {}, false);
|
|
1519
|
+
const detail = await client.disable();
|
|
1520
|
+
answer(
|
|
1521
|
+
io,
|
|
1522
|
+
session,
|
|
1523
|
+
detail,
|
|
1524
|
+
() =>
|
|
1525
|
+
`Disabled ${detail.key}: its commands are refused until it is enabled again; what the console kept of it stays.`,
|
|
1526
|
+
);
|
|
1527
|
+
return EXIT.ok;
|
|
1528
|
+
}
|
|
1529
|
+
case "settings":
|
|
1530
|
+
return pluginSettings(io, session, client, rest);
|
|
1531
|
+
case "secret":
|
|
1532
|
+
return pluginSecret(io, session, client, rest);
|
|
1533
|
+
case "grant":
|
|
1534
|
+
return pluginGrant(io, session, client, rest, text(values, "project"));
|
|
1535
|
+
case "revoke": {
|
|
1536
|
+
const { positionals } = parse(rest, {});
|
|
1537
|
+
const id = theOne(positionals, `Say which grant: console plugin ${key} revoke <grant id>`);
|
|
1538
|
+
const revoked = await client.revoke(id);
|
|
1539
|
+
answer(io, session, revoked, () => `Revoked: ${formatPluginGrantLine(revoked)}`);
|
|
1540
|
+
return EXIT.ok;
|
|
1541
|
+
}
|
|
1542
|
+
default:
|
|
1543
|
+
return pluginInvoke(io, session, client, verb, rest);
|
|
1544
|
+
}
|
|
1310
1545
|
};
|
|
1311
1546
|
|
|
1547
|
+
/** A query's answer that is a file, as the console carries one: its name, its type, its bytes as base64. */
|
|
1548
|
+
function queryFileOf(
|
|
1549
|
+
result: RestPluginInvocation,
|
|
1550
|
+
): { readonly name: string; readonly type: string; readonly bytes: Uint8Array } | undefined {
|
|
1551
|
+
if (result.kind !== "query" || typeof result.data !== "object" || result.data === null) {
|
|
1552
|
+
return undefined;
|
|
1553
|
+
}
|
|
1554
|
+
const file = (result.data as { file?: unknown }).file;
|
|
1555
|
+
if (
|
|
1556
|
+
typeof file !== "object" ||
|
|
1557
|
+
file === null ||
|
|
1558
|
+
typeof (file as { name?: unknown }).name !== "string" ||
|
|
1559
|
+
typeof (file as { type?: unknown }).type !== "string" ||
|
|
1560
|
+
typeof (file as { base64?: unknown }).base64 !== "string"
|
|
1561
|
+
) {
|
|
1562
|
+
return undefined;
|
|
1563
|
+
}
|
|
1564
|
+
const named = file as { name: string; type: string; base64: string };
|
|
1565
|
+
return {
|
|
1566
|
+
name: basename(named.name) || "answer",
|
|
1567
|
+
type: named.type,
|
|
1568
|
+
bytes: Uint8Array.from(Buffer.from(named.base64, "base64")),
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
/**
|
|
1573
|
+
* A command or a query of the plugin, by the words as typed: a file named with --file, or read
|
|
1574
|
+
* from standard input for -, is uploaded first and goes as a handle; the console parses the rest.
|
|
1575
|
+
*/
|
|
1576
|
+
async function pluginInvoke(
|
|
1577
|
+
io: CliIo,
|
|
1578
|
+
session: Session,
|
|
1579
|
+
client: PluginClient,
|
|
1580
|
+
name: string,
|
|
1581
|
+
rest: readonly string[],
|
|
1582
|
+
): Promise<number> {
|
|
1583
|
+
const { values, words } = splitWords(rest, PLUGIN_COMMAND_FLAGS);
|
|
1584
|
+
const stdin = words.includes("-");
|
|
1585
|
+
const filePath = text(values, "file");
|
|
1586
|
+
if (filePath !== undefined && stdin) {
|
|
1587
|
+
throw misuse("Say one file: --file <path>, or - for standard input.");
|
|
1588
|
+
}
|
|
1589
|
+
const files: string[] = [];
|
|
1590
|
+
if (filePath !== undefined) {
|
|
1591
|
+
let bytes: Uint8Array;
|
|
1592
|
+
try {
|
|
1593
|
+
bytes = await io.readBytes(filePath);
|
|
1594
|
+
} catch {
|
|
1595
|
+
throw misuse(`The file could not be read: ${filePath}`);
|
|
1596
|
+
}
|
|
1597
|
+
const fileName = basename(filePath);
|
|
1598
|
+
const uploaded = await client.upload({
|
|
1599
|
+
name: fileName,
|
|
1600
|
+
bytes,
|
|
1601
|
+
contentType: CONTENT_TYPES[extname(fileName).toLowerCase()] ?? "application/octet-stream",
|
|
1602
|
+
});
|
|
1603
|
+
files.push(uploaded.handle);
|
|
1604
|
+
} else if (stdin) {
|
|
1605
|
+
const uploaded = await client.upload({
|
|
1606
|
+
name: "stdin.txt",
|
|
1607
|
+
bytes: new TextEncoder().encode(await io.readStdin()),
|
|
1608
|
+
contentType: "text/plain",
|
|
1609
|
+
});
|
|
1610
|
+
files.push(uploaded.handle);
|
|
1611
|
+
}
|
|
1612
|
+
const project = await session.projectKey();
|
|
1613
|
+
const run = text(values, "run");
|
|
1614
|
+
const result = await client.invoke({
|
|
1615
|
+
argv: [name, ...words.filter((word) => word !== "-")],
|
|
1616
|
+
...(files.length === 0 ? {} : { files }),
|
|
1617
|
+
...(project === undefined ? {} : { project }),
|
|
1618
|
+
...(run === undefined ? {} : { run }),
|
|
1619
|
+
});
|
|
1620
|
+
// A query that answers a file: saved under its name in the working directory, as console file saves one.
|
|
1621
|
+
const file = queryFileOf(result);
|
|
1622
|
+
let saved: string | undefined;
|
|
1623
|
+
if (file !== undefined && !session.json) {
|
|
1624
|
+
try {
|
|
1625
|
+
saved = await io.writeBytes(file.name, file.bytes, file.name);
|
|
1626
|
+
} catch {
|
|
1627
|
+
throw failed(`The file could not be written at ${file.name}.`);
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
answer(io, session, result, () =>
|
|
1631
|
+
file !== undefined && saved !== undefined
|
|
1632
|
+
? `Saved ${file.name} (${file.bytes.byteLength} bytes, ${file.type}) at ${saved}`
|
|
1633
|
+
: formatInvocation(result),
|
|
1634
|
+
);
|
|
1635
|
+
return result.waiting === null ? EXIT.ok : EXIT.waiting;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
/** A condition's value as typed: a number, true, false and null read as such; JSON where it begins as JSON; text otherwise. */
|
|
1639
|
+
function whereValueOf(word: string): unknown {
|
|
1640
|
+
if (word === "true") {
|
|
1641
|
+
return true;
|
|
1642
|
+
}
|
|
1643
|
+
if (word === "false") {
|
|
1644
|
+
return false;
|
|
1645
|
+
}
|
|
1646
|
+
if (word === "null") {
|
|
1647
|
+
return null;
|
|
1648
|
+
}
|
|
1649
|
+
if (/^-?\d+(?:\.\d+)?$/.test(word)) {
|
|
1650
|
+
return Number(word);
|
|
1651
|
+
}
|
|
1652
|
+
if (word.startsWith("{") || word.startsWith("[")) {
|
|
1653
|
+
try {
|
|
1654
|
+
return JSON.parse(word);
|
|
1655
|
+
} catch {
|
|
1656
|
+
return word;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
return word;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
/** The records of a collection one may read, a page of them; or one record, or its versions. */
|
|
1663
|
+
async function pluginRecords(
|
|
1664
|
+
io: CliIo,
|
|
1665
|
+
session: Session,
|
|
1666
|
+
client: PluginClient,
|
|
1667
|
+
rest: readonly string[],
|
|
1668
|
+
project: string | undefined,
|
|
1669
|
+
): Promise<number> {
|
|
1670
|
+
const { values, positionals } = parse(rest, {
|
|
1671
|
+
where: { type: "string", multiple: true },
|
|
1672
|
+
search: { type: "string" },
|
|
1673
|
+
order: { type: "string" },
|
|
1674
|
+
limit: { type: "string" },
|
|
1675
|
+
after: { type: "string" },
|
|
1676
|
+
archived: { type: "boolean" },
|
|
1677
|
+
versions: { type: "boolean" },
|
|
1678
|
+
});
|
|
1679
|
+
const [collection, id, ...more] = positionals;
|
|
1680
|
+
if (collection === undefined || more.length > 0) {
|
|
1681
|
+
throw misuse(
|
|
1682
|
+
`Say which collection: console plugin ${client.key} records <collection> [<id>] [--where <field>=<value>]... [--search <words>] [--order <field>[:desc]] [--limit <n>] [--after <cursor>] [--archived] [--versions]`,
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
if (id !== undefined) {
|
|
1686
|
+
if (on(values, "versions")) {
|
|
1687
|
+
const versions = await client.recordVersions(collection, id);
|
|
1688
|
+
answer(io, session, versions, () => formatPluginRecordVersions(versions));
|
|
1689
|
+
return EXIT.ok;
|
|
1690
|
+
}
|
|
1691
|
+
const record = await client.record(collection, id);
|
|
1692
|
+
answer(io, session, record, () => formatPluginRecord(record));
|
|
1693
|
+
return EXIT.ok;
|
|
1694
|
+
}
|
|
1695
|
+
if (on(values, "versions")) {
|
|
1696
|
+
throw misuse(
|
|
1697
|
+
"--versions goes with a record's id: console plugin <key> records <collection> <id> --versions",
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
const where: Record<string, unknown> = {};
|
|
1701
|
+
for (const pair of texts(values, "where")) {
|
|
1702
|
+
const equals = pair.indexOf("=");
|
|
1703
|
+
if (equals <= 0) {
|
|
1704
|
+
throw misuse(`Not a condition, as <field>=<value>: ${pair}`);
|
|
1705
|
+
}
|
|
1706
|
+
where[pair.slice(0, equals)] = whereValueOf(pair.slice(equals + 1));
|
|
1707
|
+
}
|
|
1708
|
+
const order = text(values, "order");
|
|
1709
|
+
const page = await client.records(collection, {
|
|
1710
|
+
...(Object.keys(where).length === 0 ? {} : { where }),
|
|
1711
|
+
q: text(values, "search"),
|
|
1712
|
+
order:
|
|
1713
|
+
order === undefined ? undefined : order.endsWith(":desc") ? `-${order.slice(0, -5)}` : order,
|
|
1714
|
+
limit: countOf(text(values, "limit"), "limit", { least: 1, most: PLUGIN_LIST_LIMIT }),
|
|
1715
|
+
after: text(values, "after"),
|
|
1716
|
+
project,
|
|
1717
|
+
archived: on(values, "archived") ? true : undefined,
|
|
1718
|
+
});
|
|
1719
|
+
answer(io, session, page, () => formatPluginRecords(page));
|
|
1720
|
+
return EXIT.ok;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
/** The values `--set <key>=<value>` give, each read as its setting's type; an empty value clears one. */
|
|
1724
|
+
function settingValuesOf(
|
|
1725
|
+
pairs: readonly string[],
|
|
1726
|
+
detail: RestPluginDetail,
|
|
1727
|
+
): Record<string, unknown> {
|
|
1728
|
+
const values: Record<string, unknown> = {};
|
|
1729
|
+
const read = (type: string, word: string, key: string): unknown => {
|
|
1730
|
+
switch (type) {
|
|
1731
|
+
case "number": {
|
|
1732
|
+
const value = Number(word);
|
|
1733
|
+
if (!Number.isFinite(value) || word.trim().length === 0) {
|
|
1734
|
+
throw misuse(`Not a number for the setting ${key}: ${word}`);
|
|
1735
|
+
}
|
|
1736
|
+
return value;
|
|
1737
|
+
}
|
|
1738
|
+
case "boolean":
|
|
1739
|
+
if (word !== "true" && word !== "false") {
|
|
1740
|
+
throw misuse(`The setting ${key} is true or false: ${word}`);
|
|
1741
|
+
}
|
|
1742
|
+
return word === "true";
|
|
1743
|
+
default:
|
|
1744
|
+
return word;
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
for (const pair of pairs) {
|
|
1748
|
+
const equals = pair.indexOf("=");
|
|
1749
|
+
if (equals <= 0) {
|
|
1750
|
+
throw misuse(`Not a setting, as <key>=<value>: ${pair}`);
|
|
1751
|
+
}
|
|
1752
|
+
const key = pair.slice(0, equals);
|
|
1753
|
+
const word = pair.slice(equals + 1);
|
|
1754
|
+
const setting = detail.settings.find((candidate) => candidate.key === key);
|
|
1755
|
+
if (setting === undefined) {
|
|
1756
|
+
throw misuse(
|
|
1757
|
+
`Not a setting of ${detail.key}: ${key}. Its settings: ${detail.settings.map((candidate) => candidate.key).join(", ") || "none"}`,
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1760
|
+
if (setting.type === "secret") {
|
|
1761
|
+
throw misuse(
|
|
1762
|
+
`${key} is a secret: console plugin ${detail.key} secret ${key} reads it from standard input.`,
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1765
|
+
if (word.length === 0) {
|
|
1766
|
+
values[key] = null;
|
|
1767
|
+
} else if (setting.type === "list") {
|
|
1768
|
+
values[key] = word
|
|
1769
|
+
.split(",")
|
|
1770
|
+
.map((item) => item.trim())
|
|
1771
|
+
.filter((item) => item.length > 0)
|
|
1772
|
+
.map((item) => read(setting.of ?? "text", item, key));
|
|
1773
|
+
} else {
|
|
1774
|
+
values[key] = read(setting.type, word, key);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
return values;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
/** The capabilities a plugin asks, each with what it opens and what it risks, for the administrator to read before consenting. */
|
|
1781
|
+
function capabilityLines(capabilities: readonly string[]): string[] {
|
|
1782
|
+
return capabilities.map((capability) => {
|
|
1783
|
+
const words = capabilityWords(capability);
|
|
1784
|
+
return words === undefined
|
|
1785
|
+
? ` ${capability}: a capability this command does not know`
|
|
1786
|
+
: ` ${capability}: opens ${words.opens}; a malicious plugin reaches ${words.reach}`;
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
/** Enables a plugin, with consent to every capability it asks, named again; an administrator's. */
|
|
1791
|
+
async function pluginEnable(
|
|
1792
|
+
io: CliIo,
|
|
1793
|
+
session: Session,
|
|
1794
|
+
client: PluginClient,
|
|
1795
|
+
rest: readonly string[],
|
|
1796
|
+
): Promise<number> {
|
|
1797
|
+
const { values, positionals } = parse(rest, {
|
|
1798
|
+
consent: { type: "string", multiple: true },
|
|
1799
|
+
board: { type: "string" },
|
|
1800
|
+
projects: { type: "string", multiple: true },
|
|
1801
|
+
set: { type: "string", multiple: true },
|
|
1802
|
+
});
|
|
1803
|
+
if (positionals.length > 0) {
|
|
1804
|
+
throw misuse(
|
|
1805
|
+
`Nothing follows enable but its flags: console plugin ${client.key} enable [--consent <capability>,...] [--board new|<key>] [--projects all|<key>,...] [--set <key>=<value>]...`,
|
|
1806
|
+
);
|
|
1807
|
+
}
|
|
1808
|
+
const [detail, me] = await Promise.all([client.get(), session.client.me()]);
|
|
1809
|
+
const asked = detail.capabilities;
|
|
1810
|
+
const consent = texts(values, "consent")
|
|
1811
|
+
.flatMap((value) => value.split(","))
|
|
1812
|
+
.map((capability) => capability.trim())
|
|
1813
|
+
.filter((capability) => capability.length > 0);
|
|
1814
|
+
if (asked.length > 0 && !session.json) {
|
|
1815
|
+
io.stdout(
|
|
1816
|
+
`${detail.key} asks ${asked.length === 1 ? "one capability" : `${asked.length} capabilities`}:\n${capabilityLines(asked).join("\n")}\n`,
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
const missing = asked.filter((capability) => !consent.includes(capability));
|
|
1820
|
+
if (missing.length > 0) {
|
|
1821
|
+
throw misuse(
|
|
1822
|
+
`Enabling ${detail.key} needs consent to every capability it asks, named again: --consent ${asked.join(",")}. Not named: ${missing.join(", ")}.`,
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
const boardFlag = text(values, "board");
|
|
1826
|
+
let board: RestPluginEnableInput["board"] = null;
|
|
1827
|
+
if (boardFlag === "new") {
|
|
1828
|
+
if (detail.boardDefaults === null) {
|
|
1829
|
+
throw misuse(
|
|
1830
|
+
`${detail.key} names no defaults for a board project: choose one with --board <key>.`,
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
board = {
|
|
1834
|
+
create: {
|
|
1835
|
+
key: detail.boardDefaults.key,
|
|
1836
|
+
name: localized(detail.boardDefaults.name, me.language),
|
|
1837
|
+
description: localized(detail.boardDefaults.description, me.language),
|
|
1838
|
+
},
|
|
1839
|
+
};
|
|
1840
|
+
} else if (boardFlag !== undefined) {
|
|
1841
|
+
board = { project: boardFlag };
|
|
1842
|
+
}
|
|
1843
|
+
const named = texts(values, "projects")
|
|
1844
|
+
.flatMap((value) => value.split(","))
|
|
1845
|
+
.map((key) => key.trim())
|
|
1846
|
+
.filter((key) => key.length > 0);
|
|
1847
|
+
const settings = settingValuesOf(texts(values, "set"), detail);
|
|
1848
|
+
const enabled = await client.enable({
|
|
1849
|
+
consent,
|
|
1850
|
+
board,
|
|
1851
|
+
projects: named.length === 0 || named.includes("all") ? "all" : named,
|
|
1852
|
+
...(Object.keys(settings).length === 0 ? {} : { settings }),
|
|
1853
|
+
});
|
|
1854
|
+
answer(io, session, enabled, () =>
|
|
1855
|
+
[
|
|
1856
|
+
`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"}.`,
|
|
1857
|
+
...(enabled.settings.length === 0
|
|
1858
|
+
? []
|
|
1859
|
+
: [
|
|
1860
|
+
"Settings:",
|
|
1861
|
+
...enabled.settings.map((setting) => formatPluginSettingLine(setting, me.language)),
|
|
1862
|
+
]),
|
|
1863
|
+
].join("\n"),
|
|
1864
|
+
);
|
|
1865
|
+
return EXIT.ok;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
/** The settings as they stand, or changed as `--set` says; an administrator's. */
|
|
1869
|
+
async function pluginSettings(
|
|
1870
|
+
io: CliIo,
|
|
1871
|
+
session: Session,
|
|
1872
|
+
client: PluginClient,
|
|
1873
|
+
rest: readonly string[],
|
|
1874
|
+
): Promise<number> {
|
|
1875
|
+
const { values, positionals } = parse(rest, { set: { type: "string", multiple: true } });
|
|
1876
|
+
if (positionals.length > 0) {
|
|
1877
|
+
throw misuse(
|
|
1878
|
+
`Nothing follows settings but --set: console plugin ${client.key} settings [--set <key>=<value>]...`,
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1881
|
+
const [detail, me] = await Promise.all([client.get(), session.client.me()]);
|
|
1882
|
+
const sets = texts(values, "set");
|
|
1883
|
+
if (sets.length === 0) {
|
|
1884
|
+
answer(io, session, detail.settings, () =>
|
|
1885
|
+
detail.settings.length === 0
|
|
1886
|
+
? `${detail.key} has no settings.`
|
|
1887
|
+
: [
|
|
1888
|
+
`Settings of ${detail.key}:`,
|
|
1889
|
+
...detail.settings.map((setting) => formatPluginSettingLine(setting, me.language)),
|
|
1890
|
+
].join("\n"),
|
|
1891
|
+
);
|
|
1892
|
+
return EXIT.ok;
|
|
1893
|
+
}
|
|
1894
|
+
const changed = await client.updateSettings({ values: settingValuesOf(sets, detail) });
|
|
1895
|
+
answer(io, session, changed.settings, () =>
|
|
1896
|
+
[
|
|
1897
|
+
`Changed the settings of ${changed.key}:`,
|
|
1898
|
+
...changed.settings.map((setting) => formatPluginSettingLine(setting, me.language)),
|
|
1899
|
+
].join("\n"),
|
|
1900
|
+
);
|
|
1901
|
+
return EXIT.ok;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
/** Seals a secret of the plugin, read from standard input and shown never; or takes it away. */
|
|
1905
|
+
async function pluginSecret(
|
|
1906
|
+
io: CliIo,
|
|
1907
|
+
session: Session,
|
|
1908
|
+
client: PluginClient,
|
|
1909
|
+
rest: readonly string[],
|
|
1910
|
+
): Promise<number> {
|
|
1911
|
+
const { values, positionals } = parse(rest, { remove: { type: "boolean" } });
|
|
1912
|
+
const name = theOne(
|
|
1913
|
+
positionals,
|
|
1914
|
+
`Say which secret: console plugin ${client.key} secret <name> [--remove]; the value is read from standard input.`,
|
|
1915
|
+
);
|
|
1916
|
+
if (on(values, "remove")) {
|
|
1917
|
+
await client.removeSecret(name);
|
|
1918
|
+
answer(io, session, { name, set: false }, () => `Removed the secret ${name} of ${client.key}.`);
|
|
1919
|
+
return EXIT.ok;
|
|
1920
|
+
}
|
|
1921
|
+
const value = (await io.readStdin()).replace(/\r?\n$/, "");
|
|
1922
|
+
if (value.length === 0) {
|
|
1923
|
+
throw misuse(
|
|
1924
|
+
`Pipe the value in: console plugin ${client.key} secret ${name} < file. It is sealed by the console and shown never.`,
|
|
1925
|
+
);
|
|
1926
|
+
}
|
|
1927
|
+
await client.setSecret(name, value);
|
|
1928
|
+
answer(
|
|
1929
|
+
io,
|
|
1930
|
+
session,
|
|
1931
|
+
{ name, set: true },
|
|
1932
|
+
() => `Set the secret ${name} of ${client.key}: sealed, and shown by nothing from now on.`,
|
|
1933
|
+
);
|
|
1934
|
+
return EXIT.ok;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
/** Grants a role of the plugin to a person, to the members of a project, or to everyone; an administrator's. */
|
|
1938
|
+
async function pluginGrant(
|
|
1939
|
+
io: CliIo,
|
|
1940
|
+
session: Session,
|
|
1941
|
+
client: PluginClient,
|
|
1942
|
+
rest: readonly string[],
|
|
1943
|
+
projectKey: string | undefined,
|
|
1944
|
+
): Promise<number> {
|
|
1945
|
+
const { values, positionals } = parse(rest, {
|
|
1946
|
+
person: { type: "string" },
|
|
1947
|
+
everyone: { type: "boolean" },
|
|
1948
|
+
});
|
|
1949
|
+
const usage = `console plugin ${client.key} grant <role> --person <login> | --project <key> | --everyone`;
|
|
1950
|
+
const role = theOne(positionals, `Say which role: ${usage}`);
|
|
1951
|
+
const login = text(values, "person");
|
|
1952
|
+
const everyone = on(values, "everyone");
|
|
1953
|
+
if ([login !== undefined, projectKey !== undefined, everyone].filter(Boolean).length !== 1) {
|
|
1954
|
+
throw misuse(`Say whom, one of the three: ${usage}`);
|
|
1955
|
+
}
|
|
1956
|
+
let input: RestPluginGrantInput;
|
|
1957
|
+
if (login !== undefined) {
|
|
1958
|
+
const person = await session.client.findPerson(login);
|
|
1959
|
+
if (person === undefined) {
|
|
1960
|
+
throw failed(`No person has the login ${login} among those you may see.`);
|
|
1961
|
+
}
|
|
1962
|
+
input = { role, person: person.id };
|
|
1963
|
+
} else if (projectKey !== undefined) {
|
|
1964
|
+
input = { role, project: projectKey };
|
|
1965
|
+
} else {
|
|
1966
|
+
input = { role, everyone: true };
|
|
1967
|
+
}
|
|
1968
|
+
const granted = await client.grant(input);
|
|
1969
|
+
answer(io, session, granted, () => `Granted: ${formatPluginGrantLine(granted)}`);
|
|
1970
|
+
return EXIT.ok;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1312
1973
|
/** A version number given as a flag, or nothing; anything else is not understood. */
|
|
1313
1974
|
function versionOf(given: string | undefined): number | undefined {
|
|
1314
1975
|
if (given === undefined) {
|
|
@@ -1400,9 +2061,10 @@ const rules: Command = async (args, io) => {
|
|
|
1400
2061
|
? {
|
|
1401
2062
|
workspace: forPerson(found.workspace, me.language),
|
|
1402
2063
|
project: forPerson(found.project, me.language),
|
|
2064
|
+
...(found.plugins === undefined ? {} : { plugins: found.plugins }),
|
|
1403
2065
|
}
|
|
1404
2066
|
: forPerson(found, me.language);
|
|
1405
|
-
answer(io, session, read, () => formatRules(read));
|
|
2067
|
+
answer(io, session, read, () => formatRules(read, me.language));
|
|
1406
2068
|
return EXIT.ok;
|
|
1407
2069
|
};
|
|
1408
2070
|
|
|
@@ -1637,18 +2299,7 @@ const comment: Command = async (args, io) => {
|
|
|
1637
2299
|
if (body.trim().length === 0 && paths.length === 0) {
|
|
1638
2300
|
throw misuse(`Say something, or attach a file. ${ask}`);
|
|
1639
2301
|
}
|
|
1640
|
-
const files =
|
|
1641
|
-
for (const path of paths) {
|
|
1642
|
-
let bytes: Uint8Array;
|
|
1643
|
-
try {
|
|
1644
|
-
bytes = await io.readBytes(path);
|
|
1645
|
-
} catch {
|
|
1646
|
-
throw failed(`No file could be read at ${path}. Name the path of a file to attach.`);
|
|
1647
|
-
}
|
|
1648
|
-
const name = basename(path);
|
|
1649
|
-
const contentType = CONTENT_TYPES[extname(name).toLowerCase()];
|
|
1650
|
-
files.push({ name, bytes, ...(contentType === undefined ? {} : { contentType }) });
|
|
1651
|
-
}
|
|
2302
|
+
const files = await attachmentsOf(io, paths);
|
|
1652
2303
|
const session = await open(io, values);
|
|
1653
2304
|
const project = await session.project();
|
|
1654
2305
|
const task = await project.task(ref);
|
|
@@ -1662,10 +2313,30 @@ const comment: Command = async (args, io) => {
|
|
|
1662
2313
|
return EXIT.ok;
|
|
1663
2314
|
};
|
|
1664
2315
|
|
|
2316
|
+
/**
|
|
2317
|
+
* The files named by --attach, read from this machine: each called by its name, without the
|
|
2318
|
+
* path, and typed by its extension where the extension says.
|
|
2319
|
+
*/
|
|
2320
|
+
async function attachmentsOf(io: CliIo, paths: readonly string[]): Promise<FileUpload[]> {
|
|
2321
|
+
const files: FileUpload[] = [];
|
|
2322
|
+
for (const path of paths) {
|
|
2323
|
+
let bytes: Uint8Array;
|
|
2324
|
+
try {
|
|
2325
|
+
bytes = await io.readBytes(path);
|
|
2326
|
+
} catch {
|
|
2327
|
+
throw failed(`No file could be read at ${path}. Name the path of a file to attach.`);
|
|
2328
|
+
}
|
|
2329
|
+
const name = basename(path);
|
|
2330
|
+
const contentType = CONTENT_TYPES[extname(name).toLowerCase()];
|
|
2331
|
+
files.push({ name, bytes, ...(contentType === undefined ? {} : { contentType }) });
|
|
2332
|
+
}
|
|
2333
|
+
return files;
|
|
2334
|
+
}
|
|
2335
|
+
|
|
1665
2336
|
/**
|
|
1666
2337
|
* A file of the project, read from the console and written on this machine: one a run handed
|
|
1667
|
-
* in, one put in a comment, or one attached to a page, by its id, so that an
|
|
1668
|
-
* a picture a person put on the board.
|
|
2338
|
+
* in, one put in a comment or on a decision, or one attached to a page, by its id, so that an
|
|
2339
|
+
* agent can look at a picture a person put on the board.
|
|
1669
2340
|
*/
|
|
1670
2341
|
const file: Command = async (args, io) => {
|
|
1671
2342
|
const { values, positionals } = parse(args, { out: { type: "string" } });
|
|
@@ -1730,6 +2401,7 @@ const take: Command = async (args, io) => {
|
|
|
1730
2401
|
const rules = {
|
|
1731
2402
|
workspace: forPerson(ruling.workspace, me.language),
|
|
1732
2403
|
project: forPerson(ruling.project, me.language),
|
|
2404
|
+
...(ruling.plugins === undefined ? {} : { plugins: ruling.plugins }),
|
|
1733
2405
|
};
|
|
1734
2406
|
answer(io, session, { run, task: taken, comments: comments.items, rules }, () =>
|
|
1735
2407
|
[
|
|
@@ -1740,7 +2412,7 @@ const take: Command = async (args, io) => {
|
|
|
1740
2412
|
"",
|
|
1741
2413
|
formatTask(taken, undefined, undefined, comments.items),
|
|
1742
2414
|
"",
|
|
1743
|
-
formatRules(rules),
|
|
2415
|
+
formatRules(rules, me.language),
|
|
1744
2416
|
"",
|
|
1745
2417
|
'Say how it goes: console report "<what you found or did>". Hand in what you make: console hand-in <https url | file path>.',
|
|
1746
2418
|
'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>".',
|
|
@@ -1859,6 +2531,7 @@ const ask: Command = async (args, io) => {
|
|
|
1859
2531
|
run: { type: "string" },
|
|
1860
2532
|
wait: { type: "string" },
|
|
1861
2533
|
to: { type: "string" },
|
|
2534
|
+
attach: { type: "string", multiple: true },
|
|
1862
2535
|
});
|
|
1863
2536
|
const question = theOne(
|
|
1864
2537
|
positionals,
|
|
@@ -1874,23 +2547,30 @@ const ask: Command = async (args, io) => {
|
|
|
1874
2547
|
}
|
|
1875
2548
|
const seconds = secondsOf(text(values, "wait"), DEFAULT_ASK_WAIT_SECONDS);
|
|
1876
2549
|
const body = await bodyOf(io, values, text(values, "body"));
|
|
2550
|
+
// The files put on the decision (ADR-0055): a picture of what is to be chosen between, for one.
|
|
2551
|
+
const files = await attachmentsOf(io, texts(values, "attach"));
|
|
1877
2552
|
const session = await open(io, values);
|
|
1878
2553
|
const project = await session.project();
|
|
1879
2554
|
const runId = await currentRun(project, values);
|
|
1880
2555
|
// Whom the decision is for (ADR-0042): the person the agent works for, unless --to names another.
|
|
1881
2556
|
const deciderId = await personIdOf(session, to);
|
|
1882
|
-
const raised = await project.raiseDecision(
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2557
|
+
const raised = await project.raiseDecision(
|
|
2558
|
+
{
|
|
2559
|
+
question,
|
|
2560
|
+
options: options.map((label) => ({ label })),
|
|
2561
|
+
runId,
|
|
2562
|
+
...(body === undefined ? {} : { body }),
|
|
2563
|
+
...(typeof deciderId === "string" ? { deciderId } : {}),
|
|
2564
|
+
},
|
|
2565
|
+
files,
|
|
2566
|
+
);
|
|
2567
|
+
const withFiles =
|
|
2568
|
+
files.length === 0 ? "" : `, with ${files.length} file${files.length === 1 ? "" : "s"}`;
|
|
1889
2569
|
// The id is printed before any waiting, so that a wait cut short loses nothing.
|
|
1890
2570
|
io.stdout(
|
|
1891
2571
|
session.json
|
|
1892
2572
|
? `${JSON.stringify(raised, null, 2)}\n`
|
|
1893
|
-
: `Decision ${raised.id} raised on run ${runId}; the run waits for ${raised.decider.login}.\ndecision id: ${raised.id}\n${formatOptions(raised)}\n`,
|
|
2573
|
+
: `Decision ${raised.id} raised on run ${runId}${withFiles}; the run waits for ${raised.decider.login}.\ndecision id: ${raised.id}\n${formatOptions(raised)}\n`,
|
|
1894
2574
|
);
|
|
1895
2575
|
return settle(io, session, project, raised, seconds, true);
|
|
1896
2576
|
};
|
|
@@ -2980,6 +3660,8 @@ const COMMANDS: Readonly<Record<string, Command>> = {
|
|
|
2980
3660
|
skills,
|
|
2981
3661
|
skill,
|
|
2982
3662
|
rules,
|
|
3663
|
+
plugins,
|
|
3664
|
+
plugin,
|
|
2983
3665
|
people,
|
|
2984
3666
|
person,
|
|
2985
3667
|
workspace,
|