@skillcdn/console 0.1.14 → 0.1.16
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 +5 -4
- package/SKILLCDN.md +2 -0
- package/dist/agent-name.d.ts +3 -0
- package/dist/agent-name.d.ts.map +1 -0
- package/dist/agent-name.js +9 -0
- package/dist/agent-name.js.map +1 -0
- package/dist/api.d.ts +6 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +6 -4
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +225 -24
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +14 -4
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +59 -10
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +28 -12
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.js +5 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/client.d.ts +20 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +11 -2
- package/dist/client.js.map +1 -1
- package/dist/components/calendar.d.ts +35 -2
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +80 -31
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/connect.d.ts +4 -1
- package/dist/components/connect.d.ts.map +1 -1
- package/dist/components/connect.js +7 -3
- package/dist/components/connect.js.map +1 -1
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +2 -2
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/documents.d.ts +5 -0
- package/dist/components/documents.d.ts.map +1 -1
- package/dist/components/documents.js +3 -2
- package/dist/components/documents.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 +10 -1
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/people.d.ts +24 -1
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +35 -3
- package/dist/components/people.js.map +1 -1
- package/dist/components/projects.d.ts +10 -1
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +8 -5
- package/dist/components/projects.js.map +1 -1
- package/dist/components/rules.d.ts +40 -0
- package/dist/components/rules.d.ts.map +1 -0
- package/dist/components/rules.js +46 -0
- package/dist/components/rules.js.map +1 -0
- package/dist/components/runs.d.ts +1 -1
- package/dist/components/runs.d.ts.map +1 -1
- package/dist/components/runs.js +2 -1
- package/dist/components/runs.js.map +1 -1
- package/dist/components/skills.d.ts.map +1 -1
- package/dist/components/skills.js +5 -1
- package/dist/components/skills.js.map +1 -1
- package/dist/components/task-form.d.ts.map +1 -1
- package/dist/components/task-form.js +1 -1
- package/dist/components/task-form.js.map +1 -1
- package/dist/components/tokens.d.ts.map +1 -1
- package/dist/components/tokens.js +2 -9
- package/dist/components/tokens.js.map +1 -1
- package/dist/components/ui.d.ts +7 -2
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +13 -0
- package/dist/components/ui.js.map +1 -1
- package/dist/components/work.d.ts +5 -1
- package/dist/components/work.d.ts.map +1 -1
- package/dist/components/work.js +1 -1
- package/dist/components/work.js.map +1 -1
- package/dist/console.css +263 -17
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +197 -43
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +19 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +27 -1
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +84 -9
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +84 -13
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +84 -13
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +4 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +4 -0
- package/dist/limits.js.map +1 -1
- package/dist/router.d.ts +30 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +50 -10
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +14 -4
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +17 -2
- package/dist/routes.js.map +1 -1
- package/dist/rules.d.ts +28 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +102 -0
- package/dist/rules.js.map +1 -0
- package/dist/schemas.d.ts +652 -4
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +96 -6
- package/dist/schemas.js.map +1 -1
- package/dist/vocabulary.d.ts +21 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +21 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +13 -9
- package/docs/specs/rest.md +33 -9
- package/package.json +1 -1
- package/skills/building-a-console/SKILL.md +1 -1
- package/skills/deploying-a-console/SKILL.md +5 -5
- package/skills/deploying-a-console/assets/secrets.example.json +2 -1
- package/skills/deploying-a-console/references/access.md +10 -6
- package/skills/deploying-a-console/references/resources.md +10 -7
- package/skills/working-the-board/SKILL.md +10 -9
- package/skills/working-the-board/references/project-rules.md +36 -0
- package/src/agent-name.ts +9 -0
- package/src/api.ts +37 -0
- package/src/cli/cli.ts +264 -26
- package/src/cli/format.ts +74 -10
- package/src/cli/help.ts +31 -11
- package/src/cli/main.ts +5 -0
- package/src/client.ts +47 -1
- package/src/components/calendar.tsx +252 -51
- package/src/components/connect.tsx +12 -6
- package/src/components/decision-list.tsx +2 -1
- package/src/components/documents.tsx +7 -3
- package/src/components/event-feed.tsx +16 -3
- package/src/components/people.tsx +244 -35
- package/src/components/projects.tsx +37 -14
- package/src/components/rules.tsx +167 -0
- package/src/components/runs.tsx +4 -2
- package/src/components/skills.tsx +22 -10
- package/src/components/task-form.tsx +1 -0
- package/src/components/tokens.tsx +2 -14
- package/src/components/ui.tsx +25 -2
- package/src/components/work.tsx +34 -28
- package/src/console.tsx +524 -45
- package/src/data.ts +52 -2
- package/src/i18n/en.ts +94 -14
- package/src/i18n/ko.ts +90 -14
- package/src/index.ts +16 -1
- package/src/limits.ts +4 -0
- package/src/router.ts +67 -12
- package/src/routes.ts +23 -3
- package/src/rules.ts +109 -0
- package/src/schemas.ts +121 -4
- package/src/styles/console.css +263 -17
- package/src/vocabulary.ts +30 -0
package/src/api.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// custom UI talks to any console with types, and so that the contract has one source. No React
|
|
4
4
|
// here: the server and the command line import this entry point, and nothing of the pages.
|
|
5
5
|
|
|
6
|
+
export { agentLabel } from "./agent-name.js";
|
|
6
7
|
export {
|
|
7
8
|
ApiError,
|
|
8
9
|
type ClientOptions,
|
|
@@ -51,6 +52,7 @@ export {
|
|
|
51
52
|
MAX_LANGUAGE_TAG_LENGTH,
|
|
52
53
|
MAX_LINK_LABEL_LENGTH,
|
|
53
54
|
MAX_LINKS,
|
|
55
|
+
MAX_LOGIN_LENGTH,
|
|
54
56
|
MAX_NOTE_LENGTH,
|
|
55
57
|
MAX_OPTION_LABEL_LENGTH,
|
|
56
58
|
MAX_OPTIONS,
|
|
@@ -61,6 +63,7 @@ export {
|
|
|
61
63
|
MAX_PROJECT_SYMBOL_LENGTH,
|
|
62
64
|
MAX_QUESTION_LENGTH,
|
|
63
65
|
MAX_REPORTS_PER_RUN,
|
|
66
|
+
MAX_RULES_LENGTH,
|
|
64
67
|
MAX_SEARCH_LENGTH,
|
|
65
68
|
MAX_SECRET_LENGTH,
|
|
66
69
|
MAX_SKILLS_ADDRESS_LENGTH,
|
|
@@ -84,17 +87,31 @@ export {
|
|
|
84
87
|
PROJECT_COLLECTIONS,
|
|
85
88
|
type ProjectCollection,
|
|
86
89
|
personTokensPath,
|
|
90
|
+
personWorkPath,
|
|
87
91
|
projectIconPath,
|
|
88
92
|
projectPath,
|
|
89
93
|
REST_ROUTES,
|
|
90
94
|
RETURN_TO_PARAM,
|
|
91
95
|
restPath,
|
|
96
|
+
rulesPath,
|
|
92
97
|
SIGN_IN_FAILURES,
|
|
93
98
|
SIGN_IN_PARAM,
|
|
94
99
|
type SignInFailure,
|
|
95
100
|
signInPath,
|
|
96
101
|
} from "./routes.js";
|
|
97
102
|
export {
|
|
103
|
+
fillRules,
|
|
104
|
+
isReferenceRules,
|
|
105
|
+
languageNameOf,
|
|
106
|
+
PROJECT_RULES_TEMPLATE,
|
|
107
|
+
REFERENCE_RULES,
|
|
108
|
+
RULES_LANGUAGE_PLACEHOLDER,
|
|
109
|
+
RULES_PATH,
|
|
110
|
+
RULES_TITLE,
|
|
111
|
+
} from "./rules.js";
|
|
112
|
+
export {
|
|
113
|
+
hostOfLogin,
|
|
114
|
+
isLoginAt,
|
|
98
115
|
type RestAnswerInput,
|
|
99
116
|
type RestArtifact,
|
|
100
117
|
type RestArtifactInput,
|
|
@@ -129,23 +146,30 @@ export {
|
|
|
129
146
|
type RestGoalInput,
|
|
130
147
|
type RestGoalPatch,
|
|
131
148
|
type RestGoals,
|
|
149
|
+
type RestListing,
|
|
150
|
+
type RestListingInput,
|
|
132
151
|
type RestMe,
|
|
133
152
|
type RestMember,
|
|
134
153
|
type RestMemberInput,
|
|
135
154
|
type RestMemberPatch,
|
|
136
155
|
type RestMembers,
|
|
137
156
|
type RestMePatch,
|
|
157
|
+
type RestOrganization,
|
|
138
158
|
type RestPeople,
|
|
139
159
|
type RestPerson,
|
|
140
160
|
type RestPersonPatch,
|
|
161
|
+
type RestPersonWork,
|
|
141
162
|
type RestProject,
|
|
142
163
|
type RestProjectIcon,
|
|
143
164
|
type RestProjectInput,
|
|
144
165
|
type RestProjectPatch,
|
|
166
|
+
type RestProjectRules,
|
|
145
167
|
type RestProjects,
|
|
146
168
|
type RestProvider,
|
|
147
169
|
type RestReport,
|
|
148
170
|
type RestReportInput,
|
|
171
|
+
type RestRules,
|
|
172
|
+
type RestRulesInput,
|
|
149
173
|
type RestRun,
|
|
150
174
|
type RestRunEndInput,
|
|
151
175
|
type RestRunInput,
|
|
@@ -200,23 +224,30 @@ export {
|
|
|
200
224
|
restGoalPatchSchema,
|
|
201
225
|
restGoalSchema,
|
|
202
226
|
restGoalsSchema,
|
|
227
|
+
restListingInputSchema,
|
|
228
|
+
restListingSchema,
|
|
203
229
|
restMemberInputSchema,
|
|
204
230
|
restMemberPatchSchema,
|
|
205
231
|
restMemberSchema,
|
|
206
232
|
restMembersSchema,
|
|
207
233
|
restMePatchSchema,
|
|
208
234
|
restMeSchema,
|
|
235
|
+
restOrganizationSchema,
|
|
209
236
|
restPeopleSchema,
|
|
210
237
|
restPersonPatchSchema,
|
|
211
238
|
restPersonSchema,
|
|
239
|
+
restPersonWorkSchema,
|
|
212
240
|
restProjectIconSchema,
|
|
213
241
|
restProjectInputSchema,
|
|
214
242
|
restProjectPatchSchema,
|
|
243
|
+
restProjectRulesSchema,
|
|
215
244
|
restProjectSchema,
|
|
216
245
|
restProjectsSchema,
|
|
217
246
|
restProviderSchema,
|
|
218
247
|
restReportInputSchema,
|
|
219
248
|
restReportSchema,
|
|
249
|
+
restRulesInputSchema,
|
|
250
|
+
restRulesSchema,
|
|
220
251
|
restRunEndInputSchema,
|
|
221
252
|
restRunInputSchema,
|
|
222
253
|
restRunSchema,
|
|
@@ -253,9 +284,13 @@ export {
|
|
|
253
284
|
isProviderKey,
|
|
254
285
|
LINK_SOURCES,
|
|
255
286
|
type LinkSource,
|
|
287
|
+
MEMBERSHIP_SOURCES,
|
|
288
|
+
type MembershipSource,
|
|
256
289
|
OPEN_TASK_STATES,
|
|
257
290
|
PERSON_ROLES,
|
|
291
|
+
PERSON_STATUSES,
|
|
258
292
|
type PersonRole,
|
|
293
|
+
type PersonStatus,
|
|
259
294
|
PROJECT_ICON_TYPES,
|
|
260
295
|
PROJECT_ROLES,
|
|
261
296
|
PROJECT_VISIBILITIES,
|
|
@@ -264,8 +299,10 @@ export {
|
|
|
264
299
|
type ProjectRole,
|
|
265
300
|
type ProjectVisibility,
|
|
266
301
|
type ProviderKey,
|
|
302
|
+
RULES_SOURCES,
|
|
267
303
|
RUN_ENDINGS,
|
|
268
304
|
RUN_STATUSES,
|
|
305
|
+
type RulesSource,
|
|
269
306
|
type RunEnding,
|
|
270
307
|
type RunStatus,
|
|
271
308
|
SKILLS_STATUSES,
|
package/src/cli/cli.ts
CHANGED
|
@@ -9,7 +9,14 @@ import {
|
|
|
9
9
|
} from "../client.js";
|
|
10
10
|
import { addDays, dayOf as dayOfDate, isDay } from "../dates.js";
|
|
11
11
|
import { DEFAULT_PAGE_LIMIT, LIST_LIMIT, MAX_OPTIONS, MIN_OPTIONS } from "../limits.js";
|
|
12
|
-
import
|
|
12
|
+
import { fillRules, RULES_PATH, RULES_TITLE } from "../rules.js";
|
|
13
|
+
import {
|
|
14
|
+
hostOfLogin,
|
|
15
|
+
type RestClaim,
|
|
16
|
+
type RestDecision,
|
|
17
|
+
type RestPerson,
|
|
18
|
+
type RestTaskPatch,
|
|
19
|
+
} from "../schemas.js";
|
|
13
20
|
import {
|
|
14
21
|
COLOR_NAMES,
|
|
15
22
|
type ColorName,
|
|
@@ -19,8 +26,10 @@ import {
|
|
|
19
26
|
type PersonRole,
|
|
20
27
|
PROJECT_ROLES,
|
|
21
28
|
PROJECT_VISIBILITIES,
|
|
29
|
+
PROVIDER_KEYS,
|
|
22
30
|
type ProjectRole,
|
|
23
31
|
type ProjectVisibility,
|
|
32
|
+
type ProviderKey,
|
|
24
33
|
TASK_PRIORITIES,
|
|
25
34
|
TASK_STATES,
|
|
26
35
|
type TaskPriority,
|
|
@@ -38,12 +47,15 @@ import {
|
|
|
38
47
|
formatDocuments,
|
|
39
48
|
formatGoal,
|
|
40
49
|
formatGoalLine,
|
|
50
|
+
formatListingLine,
|
|
41
51
|
formatMemberLine,
|
|
42
52
|
formatMore,
|
|
43
53
|
formatOptions,
|
|
44
54
|
formatPersonLine,
|
|
55
|
+
formatPersonWork,
|
|
45
56
|
formatProject,
|
|
46
57
|
formatProjectLine,
|
|
58
|
+
formatRules,
|
|
47
59
|
formatRun,
|
|
48
60
|
formatRunLine,
|
|
49
61
|
formatSkills,
|
|
@@ -102,6 +114,8 @@ export interface CliIo {
|
|
|
102
114
|
readonly now: () => number;
|
|
103
115
|
/** The version of the package the command runs from, when the entry point knows it. */
|
|
104
116
|
readonly version?: string | undefined;
|
|
117
|
+
/** Where the console's own skills are on this machine, shipped with the package; nothing when they are not there. */
|
|
118
|
+
readonly skillsDir?: string | undefined;
|
|
105
119
|
/** How `serve` listens on the loopback (ADR-0014); left out, the command cannot serve. */
|
|
106
120
|
readonly listen?: Listen | undefined;
|
|
107
121
|
}
|
|
@@ -368,6 +382,8 @@ function wordOf<T extends string>(value: string, words: readonly T[], what: stri
|
|
|
368
382
|
|
|
369
383
|
const personRoleOf = (value: string): PersonRole =>
|
|
370
384
|
wordOf(value, PERSON_ROLES, "a role in the workspace");
|
|
385
|
+
const providerOf = (value: string): ProviderKey =>
|
|
386
|
+
wordOf(value, PROVIDER_KEYS, "a provider people sign in through");
|
|
371
387
|
const projectRoleOf = (value: string): ProjectRole =>
|
|
372
388
|
wordOf(value, PROJECT_ROLES, "a role in a project");
|
|
373
389
|
const visibilityOf = (value: string): ProjectVisibility =>
|
|
@@ -522,7 +538,8 @@ const login: Command = async (args, io) => {
|
|
|
522
538
|
// A connection (ADR-0011): a code a person approves on the console's own pages, and the token
|
|
523
539
|
// handed over here once, never shown.
|
|
524
540
|
const client = createClient({ baseUrl: url, fetch: io.fetch });
|
|
525
|
-
|
|
541
|
+
// "<agent> on <machine>": the pages say the agent and keep the machine for a hover.
|
|
542
|
+
const agent = text(values, "agent") ?? agentOf(io.env) ?? "An agent";
|
|
526
543
|
const begun = await client.connect({
|
|
527
544
|
agent: io.hostname === undefined ? agent : `${agent} on ${io.hostname}`,
|
|
528
545
|
});
|
|
@@ -550,11 +567,47 @@ const login: Command = async (args, io) => {
|
|
|
550
567
|
|
|
551
568
|
const logout: Command = async (args, io) => {
|
|
552
569
|
parse(args, {}, false);
|
|
570
|
+
const stored = await io.store.load();
|
|
571
|
+
const url = io.env.CONSOLE_URL ?? stored?.url;
|
|
572
|
+
const token = io.env.CONSOLE_TOKEN ?? stored?.token;
|
|
573
|
+
if (url === undefined || token === undefined) {
|
|
574
|
+
io.stdout("Not signed in here: nothing to forget.\n");
|
|
575
|
+
return EXIT.ok;
|
|
576
|
+
}
|
|
577
|
+
// The token is given up at the console first, so that it is good for nothing from now on;
|
|
578
|
+
// one the console no longer knows is gone already. A console out of reach leaves it to the
|
|
579
|
+
// Agents page, and the person is told so.
|
|
580
|
+
let gone = true;
|
|
581
|
+
try {
|
|
582
|
+
await createClient({ baseUrl: url, token, fetch: io.fetch }).giveUpToken();
|
|
583
|
+
} catch (error) {
|
|
584
|
+
if (!(error instanceof ApiError && error.code === "auth.required")) {
|
|
585
|
+
gone = false;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
553
588
|
await io.store.clear();
|
|
554
|
-
io.stdout(
|
|
589
|
+
io.stdout(
|
|
590
|
+
gone
|
|
591
|
+
? "Signed out: the token is given up at the console and forgotten here.\n"
|
|
592
|
+
: `Signed out here, but the console at ${url} could not be reached to give the token up: a person removes it on the Agents page.\n`,
|
|
593
|
+
);
|
|
594
|
+
if (io.env.CONSOLE_TOKEN !== undefined) {
|
|
595
|
+
io.stdout("CONSOLE_TOKEN in the environment still holds it: unset it.\n");
|
|
596
|
+
}
|
|
555
597
|
return EXIT.ok;
|
|
556
598
|
};
|
|
557
599
|
|
|
600
|
+
/** The agent the command runs under, where its environment says so: what a connection is named by default. */
|
|
601
|
+
function agentOf(env: CliIo["env"]): string | undefined {
|
|
602
|
+
if (env.CLAUDECODE !== undefined) {
|
|
603
|
+
return "Claude Code";
|
|
604
|
+
}
|
|
605
|
+
if (Object.keys(env).some((key) => key.startsWith("CODEX_"))) {
|
|
606
|
+
return "Codex";
|
|
607
|
+
}
|
|
608
|
+
return undefined;
|
|
609
|
+
}
|
|
610
|
+
|
|
558
611
|
const whoami: Command = async (args, io) => {
|
|
559
612
|
const { values } = parse(args, {}, false);
|
|
560
613
|
const session = await open(io, values);
|
|
@@ -597,7 +650,7 @@ const use: Command = async (args, io) => {
|
|
|
597
650
|
session,
|
|
598
651
|
project,
|
|
599
652
|
() =>
|
|
600
|
-
`Working in ${project.name} (${project.key}) from this directory on: kept in ${kept}, which is meant to be committed.`,
|
|
653
|
+
`Working in ${project.name} (${project.key}) from this directory on: kept in ${kept}, which is meant to be committed. What its people ask of agents: console rules.`,
|
|
601
654
|
);
|
|
602
655
|
return EXIT.ok;
|
|
603
656
|
};
|
|
@@ -653,10 +706,18 @@ const tasks: Command = async (args, io) => {
|
|
|
653
706
|
};
|
|
654
707
|
|
|
655
708
|
const work: Command = async (args, io) => {
|
|
656
|
-
const { values } = parse(args, {}, false);
|
|
709
|
+
const { values } = parse(args, { person: { type: "string" } }, false);
|
|
657
710
|
const session = await open(io, values);
|
|
658
|
-
const
|
|
659
|
-
|
|
711
|
+
const login = text(values, "person");
|
|
712
|
+
if (login === undefined) {
|
|
713
|
+
const found = await session.client.work();
|
|
714
|
+
answer(io, session, found, () => formatWork(found));
|
|
715
|
+
return EXIT.ok;
|
|
716
|
+
}
|
|
717
|
+
// Another person's plate: the console decides whether it is the asker's to see.
|
|
718
|
+
const whose = await personOf(session, login);
|
|
719
|
+
const found = await session.client.personWork(whose.id);
|
|
720
|
+
answer(io, session, found, () => formatPersonWork(whose, found));
|
|
660
721
|
return EXIT.ok;
|
|
661
722
|
};
|
|
662
723
|
|
|
@@ -675,10 +736,118 @@ const skills: Command = async (args, io) => {
|
|
|
675
736
|
const session = await open(io, values);
|
|
676
737
|
const project = await session.project();
|
|
677
738
|
const found = await project.skills();
|
|
678
|
-
answer(io, session, found, () => formatSkills(found));
|
|
739
|
+
answer(io, session, found, () => formatSkills(found, io.skillsDir));
|
|
679
740
|
return EXIT.ok;
|
|
680
741
|
};
|
|
681
742
|
|
|
743
|
+
/** A version number given as a flag, or nothing; anything else is not understood. */
|
|
744
|
+
function versionOf(given: string | undefined): number | undefined {
|
|
745
|
+
if (given === undefined) {
|
|
746
|
+
return undefined;
|
|
747
|
+
}
|
|
748
|
+
const version = Number(given);
|
|
749
|
+
if (!Number.isInteger(version) || version < 1) {
|
|
750
|
+
throw misuse(`Not a version number: ${given}`);
|
|
751
|
+
}
|
|
752
|
+
return version;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// The rules of the board (ADR-0023): read before a task is taken, written by an owner for the
|
|
756
|
+
// project and by an administrator for the workspace, through the same page the console keeps.
|
|
757
|
+
const rules: Command = async (args, io) => {
|
|
758
|
+
const [verb, ...rest] = args;
|
|
759
|
+
if (verb === "write") {
|
|
760
|
+
const { values, positionals } = parse(rest, {
|
|
761
|
+
body: { type: "string" },
|
|
762
|
+
file: { type: "string" },
|
|
763
|
+
workspace: { type: "boolean" },
|
|
764
|
+
base: { type: "string" },
|
|
765
|
+
});
|
|
766
|
+
if (positionals.length > 1) {
|
|
767
|
+
throw misuse("Say the rules as one argument: console rules write --body <markdown>");
|
|
768
|
+
}
|
|
769
|
+
const given = positionals[0] ?? text(values, "body");
|
|
770
|
+
const none = given === "none";
|
|
771
|
+
const body = none ? undefined : await bodyOf(io, values, given);
|
|
772
|
+
if (!none && body === undefined) {
|
|
773
|
+
throw misuse(
|
|
774
|
+
"Say what the rules are to say: --body <markdown>, --file <path>, - to read standard input, or none to put the written rules away.",
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
const baseVersion = versionOf(text(values, "base"));
|
|
778
|
+
const session = await open(io, values);
|
|
779
|
+
if (on(values, "workspace")) {
|
|
780
|
+
if (none) {
|
|
781
|
+
const put = await session.client.archiveRules();
|
|
782
|
+
answer(
|
|
783
|
+
io,
|
|
784
|
+
session,
|
|
785
|
+
put,
|
|
786
|
+
() => "The organization's rules are put away: the console's own hold.",
|
|
787
|
+
);
|
|
788
|
+
return EXIT.ok;
|
|
789
|
+
}
|
|
790
|
+
const written = await session.client.writeRules({
|
|
791
|
+
body: body ?? "",
|
|
792
|
+
...(baseVersion === undefined ? {} : { baseVersion }),
|
|
793
|
+
});
|
|
794
|
+
answer(io, session, written, () =>
|
|
795
|
+
written.page === null
|
|
796
|
+
? "Nothing written: these are the console's own rules as they stand, and they hold already."
|
|
797
|
+
: `Wrote the workspace's rules, version ${written.page.version}.`,
|
|
798
|
+
);
|
|
799
|
+
return EXIT.ok;
|
|
800
|
+
}
|
|
801
|
+
const project = await session.project();
|
|
802
|
+
if (none) {
|
|
803
|
+
try {
|
|
804
|
+
const put = await project.archiveDocument(RULES_PATH);
|
|
805
|
+
answer(io, session, put, () => "This project's rules are put away: it adds none now.");
|
|
806
|
+
} catch (error) {
|
|
807
|
+
if (!(error instanceof ApiError) || error.code !== "document.not_found") {
|
|
808
|
+
throw error;
|
|
809
|
+
}
|
|
810
|
+
answer(io, session, null, () => "This project has no rules of its own to put away.");
|
|
811
|
+
}
|
|
812
|
+
return EXIT.ok;
|
|
813
|
+
}
|
|
814
|
+
const written = await project.writeDocument(RULES_PATH, {
|
|
815
|
+
title: RULES_TITLE,
|
|
816
|
+
body: body ?? "",
|
|
817
|
+
...(baseVersion === undefined ? {} : { baseVersion }),
|
|
818
|
+
});
|
|
819
|
+
answer(io, session, written, () => `Wrote this project's rules, version ${written.version}.`);
|
|
820
|
+
return EXIT.ok;
|
|
821
|
+
}
|
|
822
|
+
const { values } = parse(args, {}, false);
|
|
823
|
+
const session = await open(io, values);
|
|
824
|
+
const key = await session.projectKey();
|
|
825
|
+
const [found, me] = await Promise.all([
|
|
826
|
+
key === undefined ? session.client.rules() : session.client.project(key).rules(),
|
|
827
|
+
session.client.me(),
|
|
828
|
+
]);
|
|
829
|
+
const read =
|
|
830
|
+
"workspace" in found
|
|
831
|
+
? {
|
|
832
|
+
workspace: forPerson(found.workspace, me.language),
|
|
833
|
+
project: forPerson(found.project, me.language),
|
|
834
|
+
}
|
|
835
|
+
: forPerson(found, me.language);
|
|
836
|
+
answer(io, session, read, () => formatRules(read));
|
|
837
|
+
return EXIT.ok;
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* The rules as they read for the person whose token the command holds: `{language}` filled
|
|
842
|
+
* with the language they chose for the pages, or with the task's when they chose none.
|
|
843
|
+
*/
|
|
844
|
+
function forPerson<T extends { readonly body: string | null }>(
|
|
845
|
+
half: T,
|
|
846
|
+
language: string | null,
|
|
847
|
+
): T {
|
|
848
|
+
return half.body === null ? half : { ...half, body: fillRules(half.body, language) };
|
|
849
|
+
}
|
|
850
|
+
|
|
682
851
|
/** The flags that set a task's fields, shared by writing one and changing one. */
|
|
683
852
|
const TASK_FIELDS: OptionSpec = {
|
|
684
853
|
state: { type: "string" },
|
|
@@ -804,11 +973,29 @@ const take: Command = async (args, io) => {
|
|
|
804
973
|
taskId: found.id,
|
|
805
974
|
...(agent === undefined ? {} : { agent }),
|
|
806
975
|
});
|
|
807
|
-
|
|
976
|
+
// The rules come with the run, as they read for the person: everything from here acts
|
|
977
|
+
// within them (ADR-0023); the task is read again, as it stands now that it is taken.
|
|
978
|
+
const [ruling, me, taken] = await Promise.all([
|
|
979
|
+
project.rules(),
|
|
980
|
+
session.client.me(),
|
|
981
|
+
project.task(ref),
|
|
982
|
+
]);
|
|
983
|
+
// Taking a task with a run open on it ends that run (ADR-0020): said when it happened.
|
|
984
|
+
const tookOver =
|
|
985
|
+
found.openRuns > 0
|
|
986
|
+
? " A run that was open on it is over now: the board cannot know whether its agent still ran, so taking it was deciding."
|
|
987
|
+
: "";
|
|
988
|
+
const rules = {
|
|
989
|
+
workspace: forPerson(ruling.workspace, me.language),
|
|
990
|
+
project: forPerson(ruling.project, me.language),
|
|
991
|
+
};
|
|
992
|
+
answer(io, session, { run, task: taken, rules }, () =>
|
|
808
993
|
[
|
|
809
|
-
`Run ${run.id} began on #${found.number} ${found.title}, as "${run.agent}" for ${run.person.login}. The task is in progress and theirs
|
|
994
|
+
`Run ${run.id} began on #${found.number} ${found.title}, as "${run.agent}" for ${run.person.login}. The task is in progress and theirs.${tookOver}`,
|
|
995
|
+
"",
|
|
996
|
+
formatTask(taken),
|
|
810
997
|
"",
|
|
811
|
-
|
|
998
|
+
formatRules(rules),
|
|
812
999
|
"",
|
|
813
1000
|
'Say how it goes: console report "<what you found or did>". Hand in what you make: console hand-in <https url | file path>.',
|
|
814
1001
|
'Ask when a person must decide: console ask "<question>" --option "..." --option "...". End with: console finish --summary "<what was done, what is left>".',
|
|
@@ -1403,25 +1590,67 @@ const people: Command = async (args, io) => {
|
|
|
1403
1590
|
const { values } = parse(args, {}, false);
|
|
1404
1591
|
const session = await open(io, values);
|
|
1405
1592
|
const found = await session.client.people();
|
|
1406
|
-
answer(io, session, found, () =>
|
|
1593
|
+
answer(io, session, found, () =>
|
|
1594
|
+
[...found.items.map(formatPersonLine), ...found.listed.map(formatListingLine)].join("\n"),
|
|
1595
|
+
);
|
|
1407
1596
|
return EXIT.ok;
|
|
1408
1597
|
};
|
|
1409
1598
|
|
|
1599
|
+
const PERSON_USAGE =
|
|
1600
|
+
"Say what to do: console person role <login> <admin|member> | person add <login> [--host gh|google] | person remove <login> | person deactivate <login> | person reactivate <login>";
|
|
1601
|
+
|
|
1410
1602
|
const person: Command = async (args, io) => {
|
|
1411
|
-
|
|
1412
|
-
|
|
1603
|
+
const what = args[0];
|
|
1604
|
+
if (what === "role") {
|
|
1605
|
+
const { values, positionals } = parse(args.slice(1), {});
|
|
1606
|
+
const [login, role] = theTwo(
|
|
1607
|
+
positionals,
|
|
1608
|
+
"Say whom and what: console person role <login> <admin|member>",
|
|
1609
|
+
);
|
|
1610
|
+
const wanted = personRoleOf(role);
|
|
1611
|
+
const session = await open(io, values);
|
|
1612
|
+
const found = await personOf(session, login);
|
|
1613
|
+
const changed = await session.client.updatePerson(found.id, { role: wanted });
|
|
1614
|
+
answer(io, session, changed, () => formatPersonLine(changed));
|
|
1615
|
+
return EXIT.ok;
|
|
1413
1616
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
positionals,
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1617
|
+
if (what === "add") {
|
|
1618
|
+
const { values, positionals } = parse(args.slice(1), { host: { type: "string" } });
|
|
1619
|
+
const login = theOne(positionals, "Say whom: console person add <login> [--host gh|google]");
|
|
1620
|
+
const chosen = text(values, "host");
|
|
1621
|
+
const host = chosen === undefined ? hostOfLogin(login) : providerOf(chosen);
|
|
1622
|
+
const session = await open(io, values);
|
|
1623
|
+
const made = await session.client.createListing({ host, login });
|
|
1624
|
+
answer(io, session, made, () => formatListingLine(made));
|
|
1625
|
+
return EXIT.ok;
|
|
1626
|
+
}
|
|
1627
|
+
if (what === "remove") {
|
|
1628
|
+
const { values, positionals } = parse(args.slice(1), {});
|
|
1629
|
+
const login = theOne(positionals, "Say whom: console person remove <login>");
|
|
1630
|
+
const session = await open(io, values);
|
|
1631
|
+
const { listed } = await session.client.people();
|
|
1632
|
+
const found = listed.find((listing) => listing.login.toLowerCase() === login.toLowerCase());
|
|
1633
|
+
if (found === undefined) {
|
|
1634
|
+
throw misuse(
|
|
1635
|
+
`Nobody called ${login} is let in and not signed in yet. console people lists them; a person who signed in is taken out with console person deactivate <login>.`,
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1638
|
+
await session.client.removeListing(found.id);
|
|
1639
|
+
answer(io, session, { removed: found }, () => `${found.login} is no longer let in.`);
|
|
1640
|
+
return EXIT.ok;
|
|
1641
|
+
}
|
|
1642
|
+
if (what === "deactivate" || what === "reactivate") {
|
|
1643
|
+
const { values, positionals } = parse(args.slice(1), {});
|
|
1644
|
+
const login = theOne(positionals, `Say whom: console person ${what} <login>`);
|
|
1645
|
+
const session = await open(io, values);
|
|
1646
|
+
const found = await personOf(session, login);
|
|
1647
|
+
const changed = await session.client.updatePerson(found.id, {
|
|
1648
|
+
status: what === "deactivate" ? "deactivated" : "active",
|
|
1649
|
+
});
|
|
1650
|
+
answer(io, session, changed, () => formatPersonLine(changed));
|
|
1651
|
+
return EXIT.ok;
|
|
1652
|
+
}
|
|
1653
|
+
throw misuse(PERSON_USAGE);
|
|
1425
1654
|
};
|
|
1426
1655
|
|
|
1427
1656
|
/** The flags that set what a project is, shared by making one and changing one. */
|
|
@@ -1665,6 +1894,7 @@ const COMMANDS: Readonly<Record<string, Command>> = {
|
|
|
1665
1894
|
goal,
|
|
1666
1895
|
categories,
|
|
1667
1896
|
skills,
|
|
1897
|
+
rules,
|
|
1668
1898
|
people,
|
|
1669
1899
|
person,
|
|
1670
1900
|
project,
|
|
@@ -1743,7 +1973,15 @@ function hintFor(error: ApiError): string {
|
|
|
1743
1973
|
case "category.in_use":
|
|
1744
1974
|
return "Tasks are sorted into the category. Move them out first: console task edit <number> --category <other|none>\n";
|
|
1745
1975
|
case "person.last_admin":
|
|
1746
|
-
return "The board keeps at least one administrator. Make another one first.\n";
|
|
1976
|
+
return "The board keeps at least one administrator in. Make another one first.\n";
|
|
1977
|
+
case "person.of_organization":
|
|
1978
|
+
return "The organization says who is in: a person it let in comes and goes with it, and is listed by login to stay.\n";
|
|
1979
|
+
case "listing.exists":
|
|
1980
|
+
return "The login is let in already, or is a person's who is in. console people lists both.\n";
|
|
1981
|
+
case "listing.not_found":
|
|
1982
|
+
return "No login let in and not signed in yet has that id. console people lists them.\n";
|
|
1983
|
+
case "listing.unknown_host":
|
|
1984
|
+
return "Nobody signs in through that provider here. console whoami --json names the ways in.\n";
|
|
1747
1985
|
case "connect.too_many":
|
|
1748
1986
|
return "The console holds as many connections as it may at once. Try again in a few minutes.\n";
|
|
1749
1987
|
case "network":
|
package/src/cli/format.ts
CHANGED
|
@@ -6,9 +6,13 @@ import type {
|
|
|
6
6
|
RestDocumentSummary,
|
|
7
7
|
RestDocuments,
|
|
8
8
|
RestGoal,
|
|
9
|
+
RestListing,
|
|
9
10
|
RestMember,
|
|
10
11
|
RestPerson,
|
|
12
|
+
RestPersonWork,
|
|
11
13
|
RestProject,
|
|
14
|
+
RestProjectRules,
|
|
15
|
+
RestRules,
|
|
12
16
|
RestRun,
|
|
13
17
|
RestSkills,
|
|
14
18
|
RestTask,
|
|
@@ -57,11 +61,20 @@ export function formatProject(project: RestProject): string {
|
|
|
57
61
|
return lines.join("\n");
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
/** One person: their login, their name where the host gives one,
|
|
64
|
+
/** One person: their login, their name where the host gives one, what they are in the workspace, and that they are out when they are. */
|
|
61
65
|
export function formatPersonLine(person: RestPerson): string {
|
|
66
|
+
const standing = person.status === "active" ? person.role : `${person.role}, deactivated`;
|
|
62
67
|
return person.name === null
|
|
63
|
-
? `${person.login} (${
|
|
64
|
-
: `${person.login} ${person.name} (${
|
|
68
|
+
? `${person.login} (${standing})`
|
|
69
|
+
: `${person.login} ${person.name} (${standing})`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The provider a listing names, as people call it. */
|
|
73
|
+
const HOST_NAMES = { gh: "GitHub", google: "Google" } as const;
|
|
74
|
+
|
|
75
|
+
/** One login let in that nobody has signed in with yet (ADR-0022). */
|
|
76
|
+
export function formatListingLine(listing: RestListing): string {
|
|
77
|
+
return `${listing.login} (let in at ${HOST_NAMES[listing.host]}, not signed in yet)`;
|
|
65
78
|
}
|
|
66
79
|
|
|
67
80
|
/** One member of a project: their login, what they are in it, and since when. */
|
|
@@ -203,17 +216,27 @@ const SKILLS_WORDS: Readonly<Record<Exclude<RestSkills["status"], "none" | "read
|
|
|
203
216
|
unavailable: "SkillCDN could not be reached; ask again later.",
|
|
204
217
|
};
|
|
205
218
|
|
|
206
|
-
/**
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
219
|
+
/**
|
|
220
|
+
* The organization's skills: where they are, each with what an agent loads it by, where a
|
|
221
|
+
* person connects an agent that has no connection to the address, and where the console's own
|
|
222
|
+
* skills are on this machine, when the command knows.
|
|
223
|
+
*/
|
|
224
|
+
export function formatSkills(skills: RestSkills, ownSkills?: string): string {
|
|
225
|
+
const own =
|
|
226
|
+
ownSkills === undefined
|
|
227
|
+
? []
|
|
228
|
+
: ["", `The console's own skills, for working the board: ${ownSkills}`];
|
|
229
|
+
if (skills.status === "none" || skills.address === null || skills.page === null) {
|
|
230
|
+
return ["No skills address: neither this project nor the console names one.", ...own].join(
|
|
231
|
+
"\n",
|
|
232
|
+
);
|
|
210
233
|
}
|
|
211
234
|
const head = `skills at ${skills.address}, served by ${skills.source}: ${skills.status}`;
|
|
212
235
|
if (skills.status !== "ready") {
|
|
213
|
-
return
|
|
236
|
+
return [head, SKILLS_WORDS[skills.status], ...own].join("\n");
|
|
214
237
|
}
|
|
215
238
|
if (skills.items.length === 0) {
|
|
216
|
-
return
|
|
239
|
+
return [head, "No skills at this address.", ...own].join("\n");
|
|
217
240
|
}
|
|
218
241
|
const lines = [head];
|
|
219
242
|
for (const skill of skills.items) {
|
|
@@ -221,11 +244,41 @@ export function formatSkills(skills: RestSkills): string {
|
|
|
221
244
|
}
|
|
222
245
|
lines.push(
|
|
223
246
|
"",
|
|
224
|
-
|
|
247
|
+
`Load a skill by its URI through your SkillCDN connection to this address; an agent without one is connected by a person at ${skills.page}. --json adds the page of each, for a person.`,
|
|
248
|
+
...own,
|
|
225
249
|
);
|
|
226
250
|
return lines.join("\n");
|
|
227
251
|
}
|
|
228
252
|
|
|
253
|
+
/** One half of the rules: where it comes from, and the text as it stands. */
|
|
254
|
+
function formatRulesHalf(where: "the workspace" | "this project", rules: RestRules): string {
|
|
255
|
+
const page = rules.page;
|
|
256
|
+
const facts =
|
|
257
|
+
page === null
|
|
258
|
+
? undefined
|
|
259
|
+
: `v${page.version}, by ${writer(page.updatedBy, page.agent)}; ${page.updatedAt}`;
|
|
260
|
+
if (rules.source === "none" || rules.body === null) {
|
|
261
|
+
return `Rules of ${where}: none of its own; the workspace's hold.`;
|
|
262
|
+
}
|
|
263
|
+
const head =
|
|
264
|
+
rules.source === "reference"
|
|
265
|
+
? `Rules of ${where}: the console's own, until an administrator writes the organization's (console rules write --workspace).`
|
|
266
|
+
: `Rules of ${where} (${facts ?? "written"}):`;
|
|
267
|
+
return `${head}\n\n${rules.body.trimEnd()}`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** The rules that hold: the workspace's, and what the project adds when the command works in one. */
|
|
271
|
+
export function formatRules(rules: RestProjectRules | RestRules): string {
|
|
272
|
+
if ("workspace" in rules) {
|
|
273
|
+
return [
|
|
274
|
+
formatRulesHalf("the workspace", rules.workspace),
|
|
275
|
+
"",
|
|
276
|
+
formatRulesHalf("this project", rules.project),
|
|
277
|
+
].join("\n");
|
|
278
|
+
}
|
|
279
|
+
return formatRulesHalf("the workspace", rules);
|
|
280
|
+
}
|
|
281
|
+
|
|
229
282
|
/** How far a goal is, from its tasks: the done, the at work, and the rest, in one phrase. */
|
|
230
283
|
function progressOf(goal: RestGoal): string {
|
|
231
284
|
const counts = goal.taskCounts;
|
|
@@ -282,6 +335,17 @@ export function formatWork(work: RestWork): string {
|
|
|
282
335
|
return lines.join("\n");
|
|
283
336
|
}
|
|
284
337
|
|
|
338
|
+
/** Another person's plate: their open tasks, each with its project; no decisions, which wait for anyone. */
|
|
339
|
+
export function formatPersonWork(person: RestPerson, work: RestPersonWork): string {
|
|
340
|
+
if (work.tasks.length === 0) {
|
|
341
|
+
return `No open task is assigned to ${person.login}.`;
|
|
342
|
+
}
|
|
343
|
+
return [
|
|
344
|
+
`tasks assigned to ${person.login}:`,
|
|
345
|
+
...work.tasks.map(({ project, task }) => ` ${project.key} ${formatTaskLine(task)}`),
|
|
346
|
+
].join("\n");
|
|
347
|
+
}
|
|
348
|
+
|
|
285
349
|
export function formatDecisionLine(decision: RestDecision): string {
|
|
286
350
|
const state = decision.answer === null ? "waiting" : `answered ${decision.answer.option}`;
|
|
287
351
|
return `${decision.id} ${state} ${decision.question} (asked by ${asker(decision)})`;
|