@skillcdn/console 0.1.24 → 0.3.1
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 +8 -7
- package/dist/api.d.ts +2 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +2 -2
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +248 -92
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/credentials.d.ts +5 -0
- package/dist/cli/credentials.d.ts.map +1 -1
- package/dist/cli/credentials.js +2 -0
- package/dist/cli/credentials.js.map +1 -1
- package/dist/cli/directory.d.ts +7 -0
- package/dist/cli/directory.d.ts.map +1 -1
- package/dist/cli/directory.js +7 -0
- package/dist/cli/directory.js.map +1 -1
- package/dist/cli/format.d.ts +13 -28
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +74 -274
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +36 -19
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/pack.d.ts +46 -0
- package/dist/cli/pack.d.ts.map +1 -0
- package/dist/cli/pack.js +250 -0
- package/dist/cli/pack.js.map +1 -0
- package/dist/client.d.ts +26 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +14 -1
- package/dist/client.js.map +1 -1
- package/dist/compat.d.ts +1 -1
- package/dist/compat.js +1 -1
- package/dist/components/calendar.d.ts +5 -1
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +3 -3
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/decision-list.d.ts +13 -1
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +74 -29
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +2 -0
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/pack-connect.d.ts +23 -0
- package/dist/components/pack-connect.d.ts.map +1 -0
- package/dist/components/pack-connect.js +86 -0
- package/dist/components/pack-connect.js.map +1 -0
- package/dist/components/pack-token.d.ts +27 -0
- package/dist/components/pack-token.d.ts.map +1 -0
- package/dist/components/pack-token.js +11 -0
- package/dist/components/pack-token.js.map +1 -0
- package/dist/components/paged.d.ts +24 -0
- package/dist/components/paged.d.ts.map +1 -1
- package/dist/components/paged.js +40 -4
- package/dist/components/paged.js.map +1 -1
- package/dist/components/projects.d.ts +13 -0
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +37 -5
- package/dist/components/projects.js.map +1 -1
- package/dist/components/skills.d.ts.map +1 -1
- package/dist/components/skills.js +8 -4
- package/dist/components/skills.js.map +1 -1
- package/dist/components/ui.d.ts +2 -0
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +7 -2
- package/dist/components/ui.js.map +1 -1
- package/dist/components/work.d.ts +3 -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/components/workspace.d.ts +46 -0
- package/dist/components/workspace.d.ts.map +1 -0
- package/dist/components/workspace.js +124 -0
- package/dist/components/workspace.js.map +1 -0
- package/dist/console.css +268 -5
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +62 -23
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +16 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +64 -2
- package/dist/data.js.map +1 -1
- package/dist/dates.d.ts +12 -2
- package/dist/dates.d.ts.map +1 -1
- package/dist/dates.js +20 -2
- package/dist/dates.js.map +1 -1
- package/dist/i18n/en.d.ts +66 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +80 -8
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +75 -7
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +10 -5
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +24 -9
- 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 +4 -0
- package/dist/routes.js.map +1 -1
- package/dist/schemas.d.ts +500 -158
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +169 -165
- package/dist/schemas.js.map +1 -1
- package/dist/vocabulary.d.ts +19 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +16 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +19 -14
- package/docs/specs/rest.md +34 -19
- package/package.json +2 -1
- package/skills/building-a-console/SKILL.md +5 -5
- package/skills/building-a-console/references/the-look-from-a-website.md +39 -0
- package/skills/deploying-a-console/SKILL.md +9 -8
- package/skills/working-the-board/SKILL.md +5 -5
- package/skills/working-the-board/references/setting-up.md +1 -1
- package/skills/working-the-board/references/terms.md +2 -0
- package/skills/working-the-board/references/the-sentence.md +1 -1
- package/src/api.ts +18 -7
- package/src/cli/cli.ts +345 -109
- package/src/cli/credentials.ts +7 -0
- package/src/cli/directory.ts +9 -0
- package/src/cli/format.ts +82 -341
- package/src/cli/help.ts +43 -19
- package/src/cli/pack.ts +356 -0
- package/src/client.ts +84 -0
- package/src/compat.ts +1 -1
- package/src/components/calendar.tsx +8 -2
- package/src/components/decision-list.tsx +227 -94
- package/src/components/event-feed.tsx +2 -0
- package/src/components/pack-connect.tsx +179 -0
- package/src/components/pack-token.tsx +74 -0
- package/src/components/paged.tsx +72 -3
- package/src/components/projects.tsx +108 -9
- package/src/components/skills.tsx +51 -33
- package/src/components/ui.tsx +16 -3
- package/src/components/work.tsx +4 -1
- package/src/components/workspace.tsx +298 -0
- package/src/console.tsx +176 -29
- package/src/data.ts +89 -2
- package/src/dates.ts +23 -2
- package/src/i18n/en.ts +94 -8
- package/src/i18n/ko.ts +84 -7
- package/src/index.ts +25 -1
- package/src/router.ts +37 -14
- package/src/routes.ts +4 -0
- package/src/schemas.ts +202 -189
- package/src/styles/console.css +268 -5
- package/src/vocabulary.ts +25 -0
package/dist/cli/cli.js
CHANGED
|
@@ -6,12 +6,14 @@ import { addDays, dayOf as dayOfDate, isDay } from "../dates.js";
|
|
|
6
6
|
import { DEFAULT_PAGE_LIMIT, LIST_LIMIT, MAX_OPTIONS, MIN_OPTIONS } from "../limits.js";
|
|
7
7
|
import { fillRules, RULES_PATH, RULES_TITLE } from "../rules.js";
|
|
8
8
|
import { hostOfLogin, } from "../schemas.js";
|
|
9
|
-
import { COLOR_NAMES, GOAL_STATES, PERSON_ROLES, PROJECT_ROLES, PROJECT_VISIBILITIES, PROVIDER_KEYS, TASK_PRIORITIES, TASK_STATES, } from "../vocabulary.js";
|
|
9
|
+
import { COLOR_NAMES, GOAL_STATES, PEOPLE_VISIBILITIES, PERSON_ROLES, PROJECT_ROLES, PROJECT_VISIBILITIES, PROVIDER_KEYS, TASK_PRIORITIES, TASK_STATES, WEEK_STARTS, } from "../vocabulary.js";
|
|
10
10
|
import { loadWebRoot, WebRootError } from "../web.js";
|
|
11
11
|
import { CredentialsError, EMPTY_CREDENTIALS, loginsAt, resolveLogin, withLogin, withoutLogin, } from "./credentials.js";
|
|
12
|
-
import {
|
|
12
|
+
import { THE_SENTENCE } from "./directory.js";
|
|
13
|
+
import { formatAnswer, formatCategoryLine, formatComment, formatDecision, formatDecisionLine, formatDocument, formatDocuments, formatGoal, formatGoalLine, formatListingLine, formatMemberLine, formatMore, formatOptions, formatPersonLine, formatPersonWork, formatProject, formatProjectLine, formatRules, formatRun, formatRunLine, formatTask, formatTaskLine, formatVersion, formatVersionLine, formatWork, formatWorkspace, moment, setMomentZone, } from "./format.js";
|
|
13
14
|
import { COMMAND_HELP, helpFor, ownSkillPath, usage } from "./help.js";
|
|
14
15
|
import { describeInstall, updateCommandFor } from "./install.js";
|
|
16
|
+
import { bodyOfFile, bodyOfFolder, bodyOfSkill, formatSkillBrowse, formatSkillFile, formatSkillFind, formatSkillLoad, formatSkills, joinFile, joinFolder, joinSkill, skillPathIn, summaryOf, } from "./pack.js";
|
|
15
17
|
import { createServeHandler, DEFAULT_SERVE_PORT } from "./serve.js";
|
|
16
18
|
// The `console` command (docs/specs/cli.md): the agent's side of the console. A thin client of
|
|
17
19
|
// the REST API with no logic of its own: every command is one or two requests and the words to
|
|
@@ -148,6 +150,12 @@ async function open(io, values) {
|
|
|
148
150
|
throw failed(wordsOf(resolved));
|
|
149
151
|
}
|
|
150
152
|
const { url, token } = resolved;
|
|
153
|
+
// Moments are printed in the workspace's time zone as the login kept it (ADR-0035); the
|
|
154
|
+
// environment's pair keeps nothing, and they stay as the API gives them.
|
|
155
|
+
setMomentZone(resolved.via === "login"
|
|
156
|
+
? credentials?.logins.find((kept) => kept.url === url && kept.account === resolved.account)
|
|
157
|
+
?.timeZone
|
|
158
|
+
: undefined);
|
|
151
159
|
const client = clientOf(io, url, token);
|
|
152
160
|
const projectKey = async () => text(values, "project") ?? io.env.CONSOLE_PROJECT ?? directory?.project;
|
|
153
161
|
return {
|
|
@@ -290,15 +298,20 @@ async function personIdOf(session, value) {
|
|
|
290
298
|
}
|
|
291
299
|
return (await personOf(session, value)).id;
|
|
292
300
|
}
|
|
293
|
-
/**
|
|
301
|
+
/**
|
|
302
|
+
* The person a login names, among the workspace's, found by their exact login as people sign in
|
|
303
|
+
* (ADR-0035): whom the console shows the person, and, for an owner of a project, anyone of the
|
|
304
|
+
* workspace, so that someone the People page does not show can still be listed.
|
|
305
|
+
*/
|
|
294
306
|
async function personOf(session, login) {
|
|
295
|
-
const
|
|
296
|
-
const found = items.find((person) => person.login.toLowerCase() === login.toLowerCase());
|
|
307
|
+
const found = await session.client.findPerson(login);
|
|
297
308
|
if (found === undefined) {
|
|
298
|
-
throw misuse(`Nobody of the workspace is called ${login}. console people lists
|
|
309
|
+
throw misuse(`Nobody of the workspace is called ${login}, or they are not yours to see. console people lists whom you see, by login.`);
|
|
299
310
|
}
|
|
300
311
|
return found;
|
|
301
312
|
}
|
|
313
|
+
/** Who sees whom, as the command takes it: with a hyphen or an underscore. */
|
|
314
|
+
const peopleVisibilityOf = (value) => wordOf(value.replaceAll("-", "_"), PEOPLE_VISIBILITIES, "who sees whom");
|
|
302
315
|
/** One word of a vocabulary, or the refusal to understand. */
|
|
303
316
|
function wordOf(value, words, what) {
|
|
304
317
|
const found = words.find((word) => word === value);
|
|
@@ -412,7 +425,14 @@ async function keep(io, url, token, agent, asDefault) {
|
|
|
412
425
|
.giveUpToken()
|
|
413
426
|
.catch(() => undefined);
|
|
414
427
|
}
|
|
415
|
-
const next = withLogin(credentials, {
|
|
428
|
+
const next = withLogin(credentials, {
|
|
429
|
+
url,
|
|
430
|
+
account: person.login,
|
|
431
|
+
token,
|
|
432
|
+
agent,
|
|
433
|
+
since: new Date(io.now()).toISOString(),
|
|
434
|
+
timeZone: me.workspace.timeZone ?? undefined,
|
|
435
|
+
}, { default: asDefault });
|
|
416
436
|
const kept = await io.store.save(next);
|
|
417
437
|
const standing = next.default === url ? ", the default console here" : "";
|
|
418
438
|
io.stdout(`${agent === undefined ? "Signed in" : `Connected as "${agent}"`} to ${me.workspace.name} at ${url} as ${person.login} (${person.role})${standing}. The token is kept in ${kept}.\nNext, say which project this directory works in: console use <key> (console projects lists them).\n`);
|
|
@@ -556,6 +576,21 @@ function agentOf(env) {
|
|
|
556
576
|
}
|
|
557
577
|
return undefined;
|
|
558
578
|
}
|
|
579
|
+
/**
|
|
580
|
+
* Keeps the workspace's time zone on the login the command used (ADR-0035), so that moments are
|
|
581
|
+
* printed in it from now on without asking; the environment's pair keeps nothing.
|
|
582
|
+
*/
|
|
583
|
+
async function rememberZone(io, session, zone) {
|
|
584
|
+
setMomentZone(zone);
|
|
585
|
+
if (session.via !== "login" || session.credentials === undefined) {
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const kept = session.credentials.logins.find((login) => login.url === session.url && login.account === session.account);
|
|
589
|
+
if (kept === undefined || kept.timeZone === zone) {
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
await io.store.save(withLogin(session.credentials, { ...kept, timeZone: zone }));
|
|
593
|
+
}
|
|
559
594
|
const whoami = async (args, io) => {
|
|
560
595
|
const { values } = parse(args, {}, false);
|
|
561
596
|
const session = await open(io, values);
|
|
@@ -564,6 +599,7 @@ const whoami = async (args, io) => {
|
|
|
564
599
|
if (person === null) {
|
|
565
600
|
throw failed("The console does not know this token. Sign in again: console login");
|
|
566
601
|
}
|
|
602
|
+
await rememberZone(io, session, me.workspace.timeZone ?? undefined);
|
|
567
603
|
const key = await session.projectKey();
|
|
568
604
|
const command = parseVersion(io.version);
|
|
569
605
|
const server = parseVersion(me.console?.version);
|
|
@@ -627,7 +663,14 @@ const use = async (args, io) => {
|
|
|
627
663
|
const bound = pinUrl
|
|
628
664
|
? ` at ${session.url}${pinAccount && session.account !== undefined ? ` as ${session.account}` : ""}`
|
|
629
665
|
: "";
|
|
630
|
-
answer(io, session, project, () =>
|
|
666
|
+
answer(io, session, project, () => [
|
|
667
|
+
`Working in ${project.name} (${project.key})${bound} from this directory on: kept in ${kept}, which is meant to be committed in a private repository and left out of a public one. What its people ask of agents: console rules.`,
|
|
668
|
+
// Nothing sends an agent to the console until the checkout says so; the sentence is the
|
|
669
|
+
// skill's (references/the-sentence.md), printed here as `skillcdn use` prints its own.
|
|
670
|
+
"Then put this sentence into the checkout's agreement (AGENTS.md, or CLAUDE.md where that is the file agents read), as it is, so that every agent working here is sent to the console:",
|
|
671
|
+
"",
|
|
672
|
+
THE_SENTENCE,
|
|
673
|
+
].join("\n"));
|
|
631
674
|
return EXIT.ok;
|
|
632
675
|
};
|
|
633
676
|
const tasks = async (args, io) => {
|
|
@@ -705,10 +748,16 @@ const categories = async (args, io) => {
|
|
|
705
748
|
const MAX_SKILL_PAGES = 64;
|
|
706
749
|
/** How many results a search answers at most: what the deployment allows on one page. */
|
|
707
750
|
const MAX_FIND_LIMIT = 25;
|
|
751
|
+
/** The pack of the project as the console lists it, and what its reads are rendered at; none when the pack did not answer. */
|
|
752
|
+
async function packOf(project) {
|
|
753
|
+
const listing = await project.skills();
|
|
754
|
+
return { listing, summary: summaryOf(listing) };
|
|
755
|
+
}
|
|
708
756
|
const skills = async (args, io) => {
|
|
709
757
|
const { values, positionals } = parse(args, {
|
|
710
758
|
search: { type: "string" },
|
|
711
759
|
limit: { type: "string" },
|
|
760
|
+
cursor: { type: "string" },
|
|
712
761
|
});
|
|
713
762
|
if (positionals.length > 1) {
|
|
714
763
|
throw misuse("Say one folder: console skills [<folder>] [--search <words>]");
|
|
@@ -718,8 +767,9 @@ const skills = async (args, io) => {
|
|
|
718
767
|
const folder = given === undefined || given === "/" || given === "." ? undefined : given;
|
|
719
768
|
const words = text(values, "search");
|
|
720
769
|
const limit = text(values, "limit");
|
|
721
|
-
|
|
722
|
-
|
|
770
|
+
const cursor = text(values, "cursor");
|
|
771
|
+
if (words === undefined && (limit !== undefined || cursor !== undefined)) {
|
|
772
|
+
throw misuse("--limit and --cursor go with --search: console skills --search <words> [--limit <n>] [--cursor <cursor>]");
|
|
723
773
|
}
|
|
724
774
|
const atMost = limit === undefined ? undefined : Number(limit);
|
|
725
775
|
if (atMost !== undefined &&
|
|
@@ -728,37 +778,39 @@ const skills = async (args, io) => {
|
|
|
728
778
|
}
|
|
729
779
|
const session = await open(io, values);
|
|
730
780
|
const project = await session.project();
|
|
781
|
+
const { listing, summary } = await packOf(project);
|
|
782
|
+
// The pack as it stands; or why it does not answer, which a folder or a search would meet too.
|
|
783
|
+
if (summary === undefined || (words === undefined && given === undefined)) {
|
|
784
|
+
answer(io, session, listing, () => formatSkills(listing, io.skillsDir));
|
|
785
|
+
return EXIT.ok;
|
|
786
|
+
}
|
|
731
787
|
if (words !== undefined) {
|
|
732
|
-
// One page of what the words find; more is said, and
|
|
733
|
-
const found = await project.skillFind(words, { path: folder, limit: atMost });
|
|
734
|
-
answer(io, session, found, () => formatSkillFind(found));
|
|
788
|
+
// One page of what the words find; more is said, and asked for with --cursor.
|
|
789
|
+
const found = await project.skillFind(words, { path: folder, limit: atMost, cursor });
|
|
790
|
+
answer(io, session, found, () => formatSkillFind(found, summary));
|
|
735
791
|
return EXIT.ok;
|
|
736
792
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
break;
|
|
746
|
-
}
|
|
747
|
-
folders.push(last.folder);
|
|
748
|
-
if (last.folder.nextCursor === null) {
|
|
749
|
-
break;
|
|
750
|
-
}
|
|
751
|
-
cursor = last.folder.nextCursor;
|
|
793
|
+
// The folder whole: its pages until the end, as the pack lists them.
|
|
794
|
+
const pages = [];
|
|
795
|
+
let next;
|
|
796
|
+
let last;
|
|
797
|
+
for (let n = 0; n < MAX_SKILL_PAGES; n += 1) {
|
|
798
|
+
last = await project.skillBrowse({ path: folder, cursor: next });
|
|
799
|
+
if (last.read?.status !== "ready") {
|
|
800
|
+
break;
|
|
752
801
|
}
|
|
753
|
-
|
|
754
|
-
|
|
802
|
+
pages.push(last);
|
|
803
|
+
next = last.read.nextCursor ?? undefined;
|
|
804
|
+
if (next === undefined) {
|
|
805
|
+
break;
|
|
755
806
|
}
|
|
756
|
-
const browsed = last;
|
|
757
|
-
answer(io, session, { ...browsed, folders }, () => formatSkillBrowse(browsed, folders));
|
|
758
|
-
return EXIT.ok;
|
|
759
807
|
}
|
|
760
|
-
|
|
761
|
-
|
|
808
|
+
if (last === undefined) {
|
|
809
|
+
throw failed("The folder could not be read.");
|
|
810
|
+
}
|
|
811
|
+
const browsed = last;
|
|
812
|
+
const whole = joinFolder(pages, summary);
|
|
813
|
+
answer(io, session, { ...browsed, read: whole === undefined ? null : bodyOfFolder(whole) }, () => formatSkillBrowse(browsed, whole));
|
|
762
814
|
return EXIT.ok;
|
|
763
815
|
};
|
|
764
816
|
const skill = async (args, io) => {
|
|
@@ -771,73 +823,62 @@ const skill = async (args, io) => {
|
|
|
771
823
|
}
|
|
772
824
|
const session = await open(io, values);
|
|
773
825
|
const project = await session.project();
|
|
826
|
+
const { listing, summary } = await packOf(project);
|
|
827
|
+
if (summary === undefined) {
|
|
828
|
+
answer(io, session, listing, () => formatSkills(listing, io.skillsDir));
|
|
829
|
+
return EXIT.ok;
|
|
830
|
+
}
|
|
774
831
|
if (file !== undefined) {
|
|
775
|
-
// The file whole, page after page, as the console reads it from the
|
|
776
|
-
const
|
|
832
|
+
// The file whole, page after page, as the console reads it from the pack.
|
|
833
|
+
const pages = [];
|
|
777
834
|
let offset;
|
|
778
835
|
let last;
|
|
779
|
-
for (let
|
|
836
|
+
for (let n = 0; n < MAX_SKILL_PAGES; n += 1) {
|
|
780
837
|
last = await project.skillFile(file, offset === undefined ? {} : { offset });
|
|
781
|
-
if (last.
|
|
838
|
+
if (last.read === null) {
|
|
782
839
|
break;
|
|
783
840
|
}
|
|
784
|
-
|
|
785
|
-
if (last.file.nextOffset === null) {
|
|
841
|
+
pages.push(last);
|
|
842
|
+
if (last.read.kind !== "file" || last.read.nextOffset === null) {
|
|
786
843
|
break;
|
|
787
844
|
}
|
|
788
|
-
offset = last.
|
|
845
|
+
offset = last.read.nextOffset;
|
|
789
846
|
}
|
|
790
847
|
if (last === undefined) {
|
|
791
848
|
throw failed("The file could not be read.");
|
|
792
849
|
}
|
|
793
|
-
const
|
|
794
|
-
const
|
|
795
|
-
answer(io, session, { ...
|
|
850
|
+
const fileRead = last;
|
|
851
|
+
const whole = joinFile(pages, summary);
|
|
852
|
+
answer(io, session, { ...fileRead, read: whole === undefined ? null : bodyOfFile(whole) }, () => formatSkillFile(fileRead, whole));
|
|
796
853
|
return EXIT.ok;
|
|
797
854
|
}
|
|
798
|
-
const
|
|
855
|
+
const wanted = skillPathIn(theOne(positionals, "Say which skill: console skill <path of its SKILL.md>, or console skill --file <path> for a file of the pack"), listing.address);
|
|
856
|
+
if ("error" in wanted) {
|
|
857
|
+
throw misuse(wanted.error);
|
|
858
|
+
}
|
|
799
859
|
// The skill whole: its pages until complete, the rules first, as load_skill lays them out.
|
|
800
860
|
const pages = [];
|
|
801
861
|
let cursor;
|
|
802
862
|
let last;
|
|
803
863
|
for (let n = 0; n < MAX_SKILL_PAGES; n += 1) {
|
|
804
|
-
last = await project.skill(path, cursor);
|
|
805
|
-
if (last.status !== "ready"
|
|
864
|
+
last = await project.skill(wanted.path, cursor);
|
|
865
|
+
if (last.read?.status !== "ready") {
|
|
806
866
|
break;
|
|
807
867
|
}
|
|
808
|
-
pages.push(last
|
|
809
|
-
|
|
868
|
+
pages.push(last);
|
|
869
|
+
cursor = last.read.skill.nextCursor ?? undefined;
|
|
870
|
+
if (last.read.skill.complete === true || cursor === undefined) {
|
|
810
871
|
break;
|
|
811
872
|
}
|
|
812
|
-
cursor = last.skill.nextCursor;
|
|
813
873
|
}
|
|
814
874
|
if (last === undefined) {
|
|
815
875
|
throw failed("The skill could not be read.");
|
|
816
876
|
}
|
|
817
|
-
// A URI of another pack would read this pack's skill at the same path, or nothing: said instead.
|
|
818
|
-
if (pack !== undefined && last.address !== null && last.address !== pack) {
|
|
819
|
-
throw misuse(`That URI is of another pack, ${pack}; this project's pack is at ${last.address}. Say the path of the SKILL.md, or a URI console skills lists.`);
|
|
820
|
-
}
|
|
821
877
|
const loaded = last;
|
|
822
|
-
|
|
878
|
+
const whole = joinSkill(pages, summary);
|
|
879
|
+
answer(io, session, { ...loaded, read: whole === undefined ? null : bodyOfSkill(whole) }, () => formatSkillLoad(loaded, whole));
|
|
823
880
|
return EXIT.ok;
|
|
824
881
|
};
|
|
825
|
-
/**
|
|
826
|
-
* A skill's path as given, or taken out of the URI `console skills` lists beside it
|
|
827
|
-
* (`skill://gh/<owner>/<repo>/<path>`), with the pack the URI names, for the read to check.
|
|
828
|
-
*/
|
|
829
|
-
function skillPathOf(given) {
|
|
830
|
-
const uri = /^skill:\/\/(gh\/[^/]+\/[^/]+)\/(.+)$/.exec(given);
|
|
831
|
-
const named = uri?.[1];
|
|
832
|
-
const within = uri?.[2];
|
|
833
|
-
if (named !== undefined && within !== undefined) {
|
|
834
|
-
return { path: within, pack: `/${named}` };
|
|
835
|
-
}
|
|
836
|
-
if (given.startsWith("skill://")) {
|
|
837
|
-
throw misuse(`Not a skill's URI the console knows: ${given}. Say the path of its SKILL.md from the repository's root, or the URI console skills lists.`);
|
|
838
|
-
}
|
|
839
|
-
return { path: given };
|
|
840
|
-
}
|
|
841
882
|
/** A version number given as a flag, or nothing; anything else is not understood. */
|
|
842
883
|
function versionOf(given) {
|
|
843
884
|
if (given === undefined) {
|
|
@@ -980,7 +1021,7 @@ const newTask = async (args, io) => {
|
|
|
980
1021
|
...(body === undefined ? {} : { body }),
|
|
981
1022
|
...fields,
|
|
982
1023
|
});
|
|
983
|
-
answer(io, session, made, () => `Task #${made.number} written: ${made.title}\
|
|
1024
|
+
answer(io, session, made, () => `Task #${made.number} written: ${made.title}\ntask id: ${made.id}`);
|
|
984
1025
|
return EXIT.ok;
|
|
985
1026
|
};
|
|
986
1027
|
const editTask = async (args, io) => {
|
|
@@ -1170,6 +1211,9 @@ const take = async (args, io) => {
|
|
|
1170
1211
|
};
|
|
1171
1212
|
answer(io, session, { run, task: taken, comments: comments.items, rules }, () => [
|
|
1172
1213
|
`Run ${run.id} began on #${found.number} ${found.title}, as "${run.agent}" for ${run.person.login}. The task is in progress and theirs.${tookOver}${elsewhere}`,
|
|
1214
|
+
// The ids on a line of their own, each named: a run's and a task's begin alike, and a
|
|
1215
|
+
// task has a number for people as well.
|
|
1216
|
+
`run id: ${run.id} task id: ${found.id} (task #${found.number})`,
|
|
1173
1217
|
"",
|
|
1174
1218
|
formatTask(taken, undefined, undefined, comments.items),
|
|
1175
1219
|
"",
|
|
@@ -1284,7 +1328,7 @@ const ask = async (args, io) => {
|
|
|
1284
1328
|
// The id is printed before any waiting, so that a wait cut short loses nothing.
|
|
1285
1329
|
io.stdout(session.json
|
|
1286
1330
|
? `${JSON.stringify(raised, null, 2)}\n`
|
|
1287
|
-
: `Decision ${raised.id} raised on run ${runId}; the run waits for a person.\n${formatOptions(raised)}\n`);
|
|
1331
|
+
: `Decision ${raised.id} raised on run ${runId}; the run waits for a person.\ndecision id: ${raised.id}\n${formatOptions(raised)}\n`);
|
|
1288
1332
|
return settle(io, session, project, raised, seconds, true);
|
|
1289
1333
|
};
|
|
1290
1334
|
const decision = async (args, io) => {
|
|
@@ -1489,7 +1533,7 @@ const newGoal = async (args, io) => {
|
|
|
1489
1533
|
...(startsOn === undefined ? {} : { startsOn }),
|
|
1490
1534
|
...(dueOn === undefined ? {} : { dueOn }),
|
|
1491
1535
|
});
|
|
1492
|
-
answer(io, session, made, () => `Goal set: ${made.title}\
|
|
1536
|
+
answer(io, session, made, () => `Goal set: ${made.title}\ngoal id: ${made.id}`);
|
|
1493
1537
|
return EXIT.ok;
|
|
1494
1538
|
};
|
|
1495
1539
|
const editGoal = async (args, io) => {
|
|
@@ -1684,6 +1728,66 @@ const people = async (args, io) => {
|
|
|
1684
1728
|
answer(io, session, found, () => [...found.items.map(formatPersonLine), ...found.listed.map(formatListingLine)].join("\n"));
|
|
1685
1729
|
return EXIT.ok;
|
|
1686
1730
|
};
|
|
1731
|
+
// The workspace's settings (ADR-0035): read by everyone, changed by an administrator, themselves
|
|
1732
|
+
// on the Settings page or through their agent here (ADR-0021).
|
|
1733
|
+
const WORKSPACE_FLAGS = "--people-visibility <everyone|shared-projects>, --week-start <auto|monday|sunday>, --time-zone <zone|none>";
|
|
1734
|
+
const WORKSPACE_USAGE = `Say what to do: console workspace | console workspace edit [${WORKSPACE_FLAGS.replaceAll(", ", "] [")}] | console workspace pack-token --connect|--stdin|--remove`;
|
|
1735
|
+
const workspace = async (args, io) => {
|
|
1736
|
+
const [verb, ...rest] = args;
|
|
1737
|
+
if (verb === undefined || verb.startsWith("--")) {
|
|
1738
|
+
const { values } = parse(args, {}, false);
|
|
1739
|
+
const session = await open(io, values);
|
|
1740
|
+
const me = await session.client.me();
|
|
1741
|
+
await rememberZone(io, session, me.workspace.timeZone ?? undefined);
|
|
1742
|
+
answer(io, session, me.workspace, () => formatWorkspace(me.workspace));
|
|
1743
|
+
return EXIT.ok;
|
|
1744
|
+
}
|
|
1745
|
+
if (verb === "edit") {
|
|
1746
|
+
const { values } = parse(rest, {
|
|
1747
|
+
"people-visibility": { type: "string" },
|
|
1748
|
+
"week-start": { type: "string" },
|
|
1749
|
+
"time-zone": { type: "string" },
|
|
1750
|
+
}, false);
|
|
1751
|
+
const visibility = text(values, "people-visibility");
|
|
1752
|
+
const weekStart = text(values, "week-start");
|
|
1753
|
+
const zone = text(values, "time-zone");
|
|
1754
|
+
if (visibility === undefined && weekStart === undefined && zone === undefined) {
|
|
1755
|
+
throw misuse(`Say what to change: ${WORKSPACE_FLAGS}`);
|
|
1756
|
+
}
|
|
1757
|
+
const session = await open(io, values);
|
|
1758
|
+
const changed = await session.client.updateWorkspace({
|
|
1759
|
+
...(visibility === undefined ? {} : { peopleVisibility: peopleVisibilityOf(visibility) }),
|
|
1760
|
+
...(weekStart === undefined
|
|
1761
|
+
? {}
|
|
1762
|
+
: { weekStart: wordOf(weekStart, WEEK_STARTS, "a day the week begins on") }),
|
|
1763
|
+
...(zone === undefined ? {} : { timeZone: zone === "none" ? null : zone }),
|
|
1764
|
+
});
|
|
1765
|
+
await rememberZone(io, session, changed.timeZone ?? undefined);
|
|
1766
|
+
answer(io, session, changed, () => formatWorkspace(changed));
|
|
1767
|
+
return EXIT.ok;
|
|
1768
|
+
}
|
|
1769
|
+
if (verb === "pack-token") {
|
|
1770
|
+
const { values, positionals } = parse(rest, PACK_TOKEN_FLAGS);
|
|
1771
|
+
if (positionals.length > 0) {
|
|
1772
|
+
throw misuse("The workspace's token names no project: console workspace pack-token --connect, --stdin or --remove");
|
|
1773
|
+
}
|
|
1774
|
+
const way = wayOf(values);
|
|
1775
|
+
const session = await open(io, values);
|
|
1776
|
+
const { client } = session;
|
|
1777
|
+
return packToken(io, session, way, {
|
|
1778
|
+
set: (token) => client.setWorkspacePackToken({ token }),
|
|
1779
|
+
remove: () => client.removeWorkspacePackToken(),
|
|
1780
|
+
connect: () => client.connectWorkspacePackToken(),
|
|
1781
|
+
async standing() {
|
|
1782
|
+
const state = await client.workspacePackTokenConnection();
|
|
1783
|
+
return state.status === "connected"
|
|
1784
|
+
? { status: "connected", held: state.workspace }
|
|
1785
|
+
: state;
|
|
1786
|
+
},
|
|
1787
|
+
}, formatWorkspace);
|
|
1788
|
+
}
|
|
1789
|
+
throw misuse(WORKSPACE_USAGE);
|
|
1790
|
+
};
|
|
1687
1791
|
const PERSON_USAGE = "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>";
|
|
1688
1792
|
const person = async (args, io) => {
|
|
1689
1793
|
const what = args[0];
|
|
@@ -1781,30 +1885,79 @@ const editProject = async (args, io) => {
|
|
|
1781
1885
|
answer(io, session, changed, () => formatProject(changed));
|
|
1782
1886
|
return EXIT.ok;
|
|
1783
1887
|
};
|
|
1784
|
-
/**
|
|
1785
|
-
const
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1888
|
+
/** How a pack's connection ended short of a token (ADR-0036), in words for the person. */
|
|
1889
|
+
const PACK_CONNECT_ENDED = {
|
|
1890
|
+
expired: "The code expired before anyone approved it. Run the command again for a new one.",
|
|
1891
|
+
declined: "The request was declined at SkillCDN, or the code was spent.",
|
|
1892
|
+
refused: "SkillCDN refused the request: the repository is not one the person may open, the pack is public and needs no token, or they hold as many tokens as one may.",
|
|
1893
|
+
};
|
|
1894
|
+
/** How a SkillCDN token is to change, one of the three the flags name. */
|
|
1895
|
+
const wayOf = (values) => {
|
|
1896
|
+
const ways = ["connect", "stdin", "remove"].filter((way) => on(values, way));
|
|
1897
|
+
const [way] = ways;
|
|
1898
|
+
if (way === undefined || ways.length !== 1) {
|
|
1899
|
+
throw misuse("Say one of --connect, which asks SkillCDN for a code a person approves; --stdin, which reads a token made by hand from standard input; and --remove, which takes the token away.");
|
|
1900
|
+
}
|
|
1901
|
+
return way;
|
|
1902
|
+
};
|
|
1903
|
+
/**
|
|
1904
|
+
* The SkillCDN token (ADR-0028): connected with a code a person approves at SkillCDN (ADR-0036),
|
|
1905
|
+
* or read from standard input and never from an argument, so that no shell keeps it; or taken
|
|
1906
|
+
* away. The same for a project's and for the workspace's.
|
|
1907
|
+
*/
|
|
1908
|
+
async function packToken(io, session, way, holder, format) {
|
|
1909
|
+
if (way === "remove") {
|
|
1910
|
+
const changed = await holder.remove();
|
|
1911
|
+
answer(io, session, changed, () => format(changed));
|
|
1799
1912
|
return EXIT.ok;
|
|
1800
1913
|
}
|
|
1914
|
+
if (way === "connect") {
|
|
1915
|
+
const started = await holder.connect();
|
|
1916
|
+
io.stdout(`To connect the pack, a person who may open its repository opens ${started.url} signed in at SkillCDN, checks that the page shows the code ${started.code}, and approves; the console receives the SkillCDN token and keeps it sealed, shown to nobody.\nWaiting for that, until ${moment(started.expiresAt)}.\n`);
|
|
1917
|
+
for (;;) {
|
|
1918
|
+
await io.sleep(started.intervalSeconds * 1000);
|
|
1919
|
+
const state = await holder.standing();
|
|
1920
|
+
if (state.status === "pending") {
|
|
1921
|
+
continue;
|
|
1922
|
+
}
|
|
1923
|
+
if (state.status === "connected") {
|
|
1924
|
+
answer(io, session, state.held, () => `Connected.\n${format(state.held)}`);
|
|
1925
|
+
return EXIT.ok;
|
|
1926
|
+
}
|
|
1927
|
+
if (state.status === "ended") {
|
|
1928
|
+
throw failed(PACK_CONNECT_ENDED[state.reason]);
|
|
1929
|
+
}
|
|
1930
|
+
throw failed("The console no longer waits for the connection; run the command again for a new code.");
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1801
1933
|
const token = (await io.readStdin()).trim();
|
|
1802
1934
|
if (token.length === 0) {
|
|
1803
1935
|
throw failed("No token was given on standard input.");
|
|
1804
1936
|
}
|
|
1805
|
-
const changed = await
|
|
1806
|
-
answer(io, session, changed, () =>
|
|
1937
|
+
const changed = await holder.set(token);
|
|
1938
|
+
answer(io, session, changed, () => format(changed));
|
|
1807
1939
|
return EXIT.ok;
|
|
1940
|
+
}
|
|
1941
|
+
const PACK_TOKEN_FLAGS = {
|
|
1942
|
+
connect: { type: "boolean" },
|
|
1943
|
+
stdin: { type: "boolean" },
|
|
1944
|
+
remove: { type: "boolean" },
|
|
1945
|
+
};
|
|
1946
|
+
const projectPackToken = async (args, io) => {
|
|
1947
|
+
const { values, positionals } = parse(args, PACK_TOKEN_FLAGS);
|
|
1948
|
+
const key = theOne(positionals, "Say which project: console project pack-token <key> --connect, --stdin or --remove");
|
|
1949
|
+
const way = wayOf(values);
|
|
1950
|
+
const session = await open(io, values);
|
|
1951
|
+
const project = session.client.project(key);
|
|
1952
|
+
return packToken(io, session, way, {
|
|
1953
|
+
set: (token) => project.setPackToken({ token }),
|
|
1954
|
+
remove: () => project.removePackToken(),
|
|
1955
|
+
connect: () => project.connectPackToken(),
|
|
1956
|
+
async standing() {
|
|
1957
|
+
const state = await project.packTokenConnection();
|
|
1958
|
+
return state.status === "connected" ? { status: "connected", held: state.project } : state;
|
|
1959
|
+
},
|
|
1960
|
+
}, formatProject);
|
|
1808
1961
|
};
|
|
1809
1962
|
const projectIcon = async (args, io) => {
|
|
1810
1963
|
const { values, positionals } = parse(args, { remove: { type: "boolean" } });
|
|
@@ -1991,6 +2144,7 @@ const COMMANDS = {
|
|
|
1991
2144
|
rules,
|
|
1992
2145
|
people,
|
|
1993
2146
|
person,
|
|
2147
|
+
workspace,
|
|
1994
2148
|
project,
|
|
1995
2149
|
members,
|
|
1996
2150
|
member,
|
|
@@ -2125,6 +2279,8 @@ function hintFor(error, io, seen) {
|
|
|
2125
2279
|
return "The person is listed already. console member role <login> <owner|member> changes what they are.\n";
|
|
2126
2280
|
case "member.not_found":
|
|
2127
2281
|
return "The person is not listed in the project. console members lists those who are.\n";
|
|
2282
|
+
case "member.open_project":
|
|
2283
|
+
return "The project is open to the workspace, so everyone of it is a member already. console member add <login> --role owner lists an owner; console member role <login> member takes one back to member.\n";
|
|
2128
2284
|
case "category.key_taken":
|
|
2129
2285
|
return "A category has that key already. console categories lists them.\n";
|
|
2130
2286
|
case "category.not_found":
|