@skillcdn/console 0.1.20 → 0.1.22

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.
Files changed (59) hide show
  1. package/README.md +1 -1
  2. package/dist/api.d.ts +1 -1
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +1 -1
  5. package/dist/api.js.map +1 -1
  6. package/dist/cli/cli.d.ts.map +1 -1
  7. package/dist/cli/cli.js +67 -6
  8. package/dist/cli/cli.js.map +1 -1
  9. package/dist/cli/format.d.ts +15 -4
  10. package/dist/cli/format.d.ts.map +1 -1
  11. package/dist/cli/format.js +133 -7
  12. package/dist/cli/format.js.map +1 -1
  13. package/dist/cli/help.d.ts +8 -4
  14. package/dist/cli/help.d.ts.map +1 -1
  15. package/dist/cli/help.js +67 -19
  16. package/dist/cli/help.js.map +1 -1
  17. package/dist/client.d.ts +13 -1
  18. package/dist/client.d.ts.map +1 -1
  19. package/dist/client.js +12 -1
  20. package/dist/client.js.map +1 -1
  21. package/dist/components/board.d.ts +4 -4
  22. package/dist/components/board.d.ts.map +1 -1
  23. package/dist/components/board.js +48 -19
  24. package/dist/components/board.js.map +1 -1
  25. package/dist/components/task-view.d.ts.map +1 -1
  26. package/dist/components/task-view.js +2 -4
  27. package/dist/components/task-view.js.map +1 -1
  28. package/dist/console.css +59 -17
  29. package/dist/console.d.ts.map +1 -1
  30. package/dist/console.js +1 -1
  31. package/dist/console.js.map +1 -1
  32. package/dist/i18n/en.js +1 -1
  33. package/dist/i18n/en.js.map +1 -1
  34. package/dist/i18n/ko.js +1 -1
  35. package/dist/i18n/ko.js.map +1 -1
  36. package/dist/rules.d.ts.map +1 -1
  37. package/dist/rules.js +3 -2
  38. package/dist/rules.js.map +1 -1
  39. package/dist/schemas.d.ts +145 -0
  40. package/dist/schemas.d.ts.map +1 -1
  41. package/dist/schemas.js +89 -0
  42. package/dist/schemas.js.map +1 -1
  43. package/docs/specs/cli.md +5 -5
  44. package/docs/specs/rest.md +8 -3
  45. package/package.json +1 -1
  46. package/skills/working-the-board/SKILL.md +4 -4
  47. package/src/api.ts +8 -0
  48. package/src/cli/cli.ts +77 -9
  49. package/src/cli/format.ts +156 -7
  50. package/src/cli/help.ts +93 -25
  51. package/src/client.ts +48 -0
  52. package/src/components/board.tsx +86 -33
  53. package/src/components/task-view.tsx +10 -12
  54. package/src/console.tsx +8 -2
  55. package/src/i18n/en.ts +1 -1
  56. package/src/i18n/ko.ts +1 -1
  57. package/src/rules.ts +3 -2
  58. package/src/schemas.ts +110 -0
  59. package/src/styles/console.css +59 -17
package/src/cli/format.ts CHANGED
@@ -15,7 +15,10 @@ import type {
15
15
  RestProjectRules,
16
16
  RestRules,
17
17
  RestRun,
18
+ RestSkillBrowse,
19
+ RestSkillBrowseEntry,
18
20
  RestSkillFile,
21
+ RestSkillFind,
19
22
  RestSkillLoad,
20
23
  RestSkillPage,
21
24
  RestSkills,
@@ -254,10 +257,13 @@ const SKILLS_WORDS: Readonly<Record<Exclude<RestSkills["status"], "none" | "read
254
257
  unavailable: "SkillCDN could not be reached; ask again later.",
255
258
  };
256
259
 
260
+ /** A count with its noun: `1 skill`, `2 skills`. */
261
+ const counted = (n: number, noun: string): string => `${n} ${noun}${n === 1 ? "" : "s"}`;
262
+
257
263
  /**
258
- * The organization's skills: where they are, each with what an agent loads it by, where a
259
- * person connects an agent that has no connection to the address, and where the console's own
260
- * skills are on this machine, when the command knows.
264
+ * The organization's skills: where they are, the pack by name and by count, each skill with
265
+ * what an agent loads it by, where a person connects an agent that has no connection to the
266
+ * address, and where the console's own skills are on this machine, when the command knows.
261
267
  */
262
268
  export function formatSkills(skills: RestSkills, ownSkills?: string): string {
263
269
  const own =
@@ -273,16 +279,29 @@ export function formatSkills(skills: RestSkills, ownSkills?: string): string {
273
279
  if (skills.status !== "ready") {
274
280
  return [head, SKILLS_WORDS[skills.status], ...own].join("\n");
275
281
  }
282
+ const lines = [head];
283
+ if (skills.pack !== undefined) {
284
+ const named = [skills.pack.name, skills.pack.description]
285
+ .filter((text): text is string => text !== null)
286
+ .join(" ");
287
+ if (named !== "") {
288
+ lines.push(named);
289
+ }
290
+ }
291
+ if (skills.counts !== undefined) {
292
+ lines.push(
293
+ `${counted(skills.counts.skills, "skill")}, ${counted(skills.counts.documents, "document")}: console skills <folder> shows what a folder holds (/ for the root), console skills --search <words> finds skills and documents by words.`,
294
+ );
295
+ }
276
296
  if (skills.items.length === 0) {
277
- return [head, "No skills at this address.", ...own].join("\n");
297
+ lines.push("No skills at this address.");
278
298
  }
279
- const lines = [head];
280
299
  for (const skill of skills.items) {
281
300
  lines.push(`${skill.name} ${skill.description}`, ` ${skill.uri ?? skill.path}`);
282
301
  }
283
302
  lines.push(
284
303
  "",
285
- `Read a skill whole with console skill <path>, its rules first, and a file of the pack with console skill <path> --file <file>; or load it by its URI through your own SkillCDN connection to this address, which a person makes at ${skills.page}. --json adds the page of each, for a person.`,
304
+ `Read a skill whole with console skill <path>, its rules first, and a file of the pack with console skill --file <path>; or load it by its URI through your own SkillCDN connection to this address, which a person makes at ${skills.page}. --json adds the page of each, for a person.`,
286
305
  ...own,
287
306
  );
288
307
  return lines.join("\n");
@@ -576,7 +595,7 @@ export function formatSkillLoad(loaded: RestSkillLoad, pages: readonly RestSkill
576
595
  if (last.files.length > 0) {
577
596
  lines.push(
578
597
  "",
579
- `Files of the skill, read with console skill ${first.path} --file <path>: ${last.files.join(", ")}`,
598
+ `Files of the skill, read with console skill --file <path>: ${last.files.join(", ")}`,
580
599
  );
581
600
  }
582
601
  if (last.references.length > 0) {
@@ -621,6 +640,8 @@ export function formatSkillFile(read: RestSkillFile, content: string): string {
621
640
  (entry) =>
622
641
  `${entry.kind === "directory" ? "d" : "f"} ${entry.path}${entry.size === null ? "" : ` ${entry.size}`}`,
623
642
  ),
643
+ "",
644
+ "console skills <folder> shows a folder with its skills marked, what each folder holds and the README that introduces it.",
624
645
  ].join("\n");
625
646
  }
626
647
  if (read.file === null) {
@@ -631,3 +652,131 @@ export function formatSkillFile(read: RestSkillFile, content: string): string {
631
652
  content.trimEnd(),
632
653
  ].join("\n");
633
654
  }
655
+
656
+ /** One entry of a browsed folder: what it is, its path, its words, and the facts of its kind. */
657
+ function formatBrowseEntry(entry: RestSkillBrowseEntry): string {
658
+ const facts: string[] = [];
659
+ if (entry.kind === "directory") {
660
+ if (entry.skillCount > 0) {
661
+ facts.push(counted(entry.skillCount, "skill"));
662
+ }
663
+ if (entry.documentCount > 0) {
664
+ facts.push(counted(entry.documentCount, "document"));
665
+ }
666
+ if (entry.overviewPath !== null) {
667
+ facts.push(`README ${entry.overviewPath}`);
668
+ }
669
+ }
670
+ if (entry.kind === "skill") {
671
+ if (entry.license !== null) {
672
+ facts.push(entry.license);
673
+ }
674
+ if (entry.describedOnly) {
675
+ facts.push("described only");
676
+ }
677
+ }
678
+ if (entry.kind === "file" && entry.size !== null) {
679
+ facts.push(`${entry.size} bytes`);
680
+ }
681
+ const words = [entry.name, entry.description]
682
+ .filter((text): text is string => text !== null && text !== "")
683
+ .join(" ");
684
+ const mark = entry.kind === "skill" ? "skill" : entry.kind === "directory" ? "d" : "f";
685
+ return `${mark} ${entry.path}${words === "" ? "" : ` ${words}`}${facts.length === 0 ? "" : ` (${facts.join("; ")})`}`;
686
+ }
687
+
688
+ /**
689
+ * What a folder of the pack holds, as browse_repo lays it out: the README that introduces the
690
+ * folder, then its skills, its folders with what each holds, and its files, across the pages the
691
+ * listing came in.
692
+ */
693
+ export function formatSkillBrowse(
694
+ browsed: RestSkillBrowse,
695
+ folders: readonly NonNullable<RestSkillBrowse["folder"]>[],
696
+ ): string {
697
+ if (browsed.status === "none" || browsed.address === null) {
698
+ return NO_ADDRESS;
699
+ }
700
+ const head = `folder at ${browsed.address}, served by ${browsed.source}: ${browsed.status}`;
701
+ if (browsed.status !== "ready") {
702
+ return [head, SKILLS_WORDS[browsed.status]].join("\n");
703
+ }
704
+ const [first] = folders;
705
+ if (first === undefined) {
706
+ return [head, "Nothing was read."].join("\n");
707
+ }
708
+ const last = folders[folders.length - 1] ?? first;
709
+ const lines = [`folder ${first.path === "" ? "/" : first.path} at ${browsed.address}`];
710
+ if (first.overview !== null) {
711
+ const words = [first.overview.title, first.overview.description]
712
+ .filter((text): text is string => text !== null && text !== "")
713
+ .join(" ");
714
+ lines.push(` ${words === "" ? "" : `${words} `}(README: ${first.overview.path})`);
715
+ }
716
+ const entries = folders.flatMap((folder) => folder.entries);
717
+ if (entries.length === 0) {
718
+ lines.push("Nothing in this folder.");
719
+ }
720
+ for (const entry of entries) {
721
+ lines.push(formatBrowseEntry(entry));
722
+ }
723
+ if (last.nextCursor !== null) {
724
+ lines.push("More entries: the listing stopped before the end of the folder.");
725
+ }
726
+ lines.push(
727
+ "",
728
+ "Read a skill with console skill <path>; a file, or a README named above, with console skill --file <path>; a folder with console skills <folder>.",
729
+ );
730
+ return lines.join("\n");
731
+ }
732
+
733
+ /**
734
+ * What words found in the pack, as search_repo lays it out: skills with the files of theirs that
735
+ * matched, documents on their own, in relevance order; words, not meaning.
736
+ */
737
+ export function formatSkillFind(found: RestSkillFind): string {
738
+ if (found.status === "none" || found.address === null) {
739
+ return NO_ADDRESS;
740
+ }
741
+ const head = `search at ${found.address}, served by ${found.source}: ${found.status}`;
742
+ if (found.status !== "ready") {
743
+ return [head, SKILLS_WORDS[found.status]].join("\n");
744
+ }
745
+ if (found.found === null) {
746
+ return [head, "Nothing was read."].join("\n");
747
+ }
748
+ const { query, path, items, nextCursor } = found.found;
749
+ const where = path === null || path === "" ? "" : ` within ${path}`;
750
+ const lines = [
751
+ `found for "${query}"${where} at ${found.address}: ${counted(items.length, "result")}`,
752
+ ];
753
+ for (const item of items) {
754
+ if (item.kind === "skill") {
755
+ lines.push(
756
+ `skill ${item.path} ${item.name} ${item.description}${item.describedOnly ? " (described only)" : ""}`,
757
+ );
758
+ for (const file of item.files) {
759
+ lines.push(` file ${file.path}${file.title === null ? "" : ` ${file.title}`}`);
760
+ }
761
+ if (item.moreFiles > 0) {
762
+ lines.push(` and ${counted(item.moreFiles, "more file")}`);
763
+ }
764
+ } else {
765
+ lines.push(
766
+ `document ${item.path}${item.title === null ? "" : ` ${item.title}`}${item.summary === null ? "" : ` ${item.summary}`}`,
767
+ );
768
+ }
769
+ }
770
+ if (nextCursor !== null) {
771
+ lines.push(
772
+ "More matched: narrow the words, or search within a folder (console skills <folder> --search <words>).",
773
+ );
774
+ }
775
+ if (items.length > 0) {
776
+ lines.push(
777
+ "",
778
+ "Read a skill with console skill <path>, a file or a document with console skill --file <path>.",
779
+ );
780
+ }
781
+ return lines.join("\n");
782
+ }
package/src/cli/help.ts CHANGED
@@ -31,15 +31,21 @@ import {
31
31
  import { COLOR_NAMES } from "../vocabulary.js";
32
32
 
33
33
  // What the command says about itself: for an agent that meets it for the first time, and for a
34
- // person. One table for every command, from which the usage and each command's help are made:
35
- // the overview says what each command does, and a command's own help adds the limits the
36
- // console holds it to and the refusals it may meet, so that an agent learns them before it
37
- // runs into them.
34
+ // person. One table for every command, from which two layers are made. The overview is a map:
35
+ // every command in a line, what it does in a sentence, and what holds everywhere, short enough
36
+ // to be read once at the start. A command's own help is the reference: every form and flag, what
37
+ // each does, the limits the console holds it to and the refusals it may meet, read when the
38
+ // command is about to be used. How the work goes is the skill working-the-board, and what the
39
+ // organization asks is the rules of the board: neither is repeated here.
38
40
 
39
41
  export interface CommandHelp {
40
- /** The command and its arguments, after `console`. */
42
+ /** The command and its arguments, after `console`, every form and flag. */
41
43
  readonly usage: string;
42
- /** What it does, in a sentence. */
44
+ /** The usage as the overview shows it, for a command whose forms are many; `usage` has them all. */
45
+ readonly overview?: string;
46
+ /** What it does, in one line, for the overview. */
47
+ readonly synopsis: string;
48
+ /** What it does in full, for the command's own help. */
43
49
  readonly about: string;
44
50
  /** What the console holds the arguments to. */
45
51
  readonly limits?: readonly string[];
@@ -85,6 +91,8 @@ const TASK_FIELD_REFUSALS = [
85
91
  export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
86
92
  login: {
87
93
  usage: "login --url <origin> [--agent <name>] [--token-stdin] [--default]",
94
+ synopsis:
95
+ "Connects this agent to the console with a code a person approves; keeps the token, never shows it.",
88
96
  about:
89
97
  "Connects this agent to the console: shows an address and a code, which a person opens signed in, names the agent and approves; the command then receives a token of its own and keeps it, with the console's address and the account, in your home directory, never showing it. One login per console and account: a second console adds a login beside the first, the first is the default console a directory that names none works at, and --default makes this one that. A login repeated for the same console and account replaces its token, which is given up at the console. An agent running the command relays the address and the code to its person and waits. --agent says what the agent calls itself; left out, the command names the agent it runs under where its environment says so (Claude Code, Codex), else An agent; the machine's name follows, and the pages show the name before 'on' with the whole on hover. --token-stdin reads a token made by hand from standard input instead, for a script or a job.",
90
98
  refusals: [
@@ -94,63 +102,75 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
94
102
  },
95
103
  logins: {
96
104
  usage: "logins",
105
+ synopsis: "The consoles this machine is signed in to, and which is the default.",
97
106
  about:
98
107
  "The consoles this machine is signed in to, each with the account, what the token is called there, since when, and which is the default; never a token.",
99
108
  },
100
109
  logout: {
101
110
  usage: "logout [--url <origin>] [--account <login>]",
111
+ synopsis: "Gives this agent's token up at one console and forgets it here.",
102
112
  about:
103
113
  "Signs this agent out of one console: gives the token up there, so that it is good for nothing from now on, and forgets it here. Which console: --url, else the one this directory is bound to, else the default, else the one console signed in to; --account says which account where the console has several. A console out of reach leaves the token to the Agents page.",
104
114
  },
105
115
  whoami: {
106
116
  usage: "whoami",
117
+ synopsis: "Who the token acts as, at which console, and in which project this directory works.",
107
118
  about:
108
119
  "Who the token acts as, which console the command talks to from here and as which account, and which project this directory works in.",
109
120
  },
110
121
  projects: {
111
122
  usage: "projects",
123
+ synopsis: "The projects you may work in, with what you are in each.",
112
124
  about:
113
125
  "The projects you may work in: each with its key, its name, what you are in it, and what waits in it. Every command on the board works in one project.",
114
126
  },
115
127
  use: {
116
128
  usage: "use <key> [--url <origin>] [--account <login>]",
129
+ synopsis: "Binds this directory to a project: writes .skillcdn-console.json here.",
117
130
  about:
118
131
  "Says which project this directory works in: writes .skillcdn-console.json here, which the commands read from this directory or one above it, and which is meant to be committed in a private repository and left out of a public one. The file names the project; and the console, where that is not the default login here or --url says so; and the account, where the console has several logins or --account says so. Never a token. --project <key> on any command, or CONSOLE_PROJECT in the environment, wins over the project it names.",
119
132
  refusals: ["project.not_found: no project has the key, or it is not yours to see"],
120
133
  },
121
134
  work: {
122
135
  usage: "work [--person <login>]",
136
+ synopsis:
137
+ "What is on your plate across your projects: your open tasks, and the decisions waiting.",
123
138
  about:
124
139
  "What is on your plate across the projects you may see: the open tasks assigned to you, the sooner due first, each with its project; and the decisions waiting for a person. Needs no project. --person <login> shows another person's plate, for an administrator or for an owner of a project they are in: their open tasks within the projects you may see, without the decisions, which wait for anyone.",
125
140
  },
126
141
  tasks: {
127
142
  usage:
128
143
  "tasks [--state <state>] [--open] [--mine] [--category <key>] [--goal <id>] [--due <day|today|week|overdue>] [--unassigned] [--archived] [--limit <n>] [--offset <n>]",
144
+ synopsis:
145
+ "The tasks on the board, newest first, narrowed by state, owner, category, goal or due day.",
129
146
  about: `The tasks on the board, newest first: number, state, priority, title, who owns and works on each, its category and its due day, and what waits on it. --open keeps the ones not done or dropped, --mine those assigned to you, --unassigned those assigned to nobody (not with --mine), --category and --goal those sorted into one, --due those due by a day: today, within the week, or overdue (the open ones past their day). The tasks put away (ADR-0029) are left out unless --archived asks for them alone. A page at a time: ${DEFAULT_PAGE_LIMIT} unless --limit says otherwise, from --offset on; a last line says when there is more. States: idea, ready, in_progress, in_review, done, dropped.`,
130
147
  },
131
148
  task: {
132
149
  usage: `task <number|id> | task new <title> [--body <markdown>|--file <path>] ${TASK_FIELD_FLAGS} | task edit <number|id> [--title <title>] [--body <markdown>|--file <path>] ${TASK_FIELD_FLAGS} | task archive <number|id> | task archive --goal <id> | task restore <number|id>`,
150
+ overview:
151
+ "task <number|id> | task new <title> [flags] | task edit <number|id> [flags] | task archive <number|id>|--goal <id> | task restore <number|id>",
152
+ synopsis: "One task in full; or a task written, changed, put away or brought back.",
133
153
  about:
134
- "One task in full, with its plan (category, goal, start and due days), what happened to it (when it was started and done, which the board keeps itself, and when it was put away), the decisions about it, the runs on it and the discussion on it, each comment whole with its files; or a new task written down; or a task changed, only in what the flags say, where none clears a field (priorities: low, normal, high, urgent). archive puts a done or dropped task away (ADR-0029): out of the listings, the board and the calendar, open by its number still; with --goal, every done or dropped task of the goal at once, for an owner. restore brings one back.",
154
+ "One task in full, with its plan (category, goal, start and due days), what happened to it (when it was started and done, which the board keeps itself, and when it was put away), the decisions about it, the runs on it and the discussion on it, each comment whole with its files; or a new task written down; or a task changed, only in what the flags say, where none clears a field (priorities: low, normal, high, urgent). archive puts a task away, whatever its state (ADR-0029, ADR-0030): out of the listings, the board and the calendar, open by its number still; with --goal, every done or dropped task of the goal at once, for an owner. restore brings one back.",
135
155
  limits: [
136
156
  `title: one line, up to ${MAX_TITLE_LENGTH} characters, saying what is to be done on its own; what it refers to goes in the body`,
137
157
  `body: ${MARKDOWN}`,
138
158
  ...TASK_FIELD_LIMITS,
139
159
  ],
140
- refusals: [
141
- "task.not_found: no task has that number or id",
142
- "task.open: an open task is not put away; one done or dropped is",
143
- ...TASK_FIELD_REFUSALS,
144
- ],
160
+ refusals: ["task.not_found: no task has that number or id", ...TASK_FIELD_REFUSALS],
145
161
  },
146
162
  goals: {
147
163
  usage: "goals [--category <key>] [--state <state>]",
164
+ synopsis: "The roadmap: the project's goals, with their state, their days and how far each is.",
148
165
  about:
149
166
  "The roadmap of the project: its goals, the active first, each with its id, its state (planned, active, done, dropped), its category, the days it starts and is meant to be reached by, and how far it is, counted from the tasks that work towards it. --category keeps those sorted into one, --state those in one state.",
150
167
  },
151
168
  goal: {
152
169
  usage:
153
170
  'goal <id> | goal new "<title>" [--body <markdown>|--file <path>] [--state <state>] [--category <key>] [--starts <day>] [--due <day>] | goal edit <id> [--title <title>] [--body <markdown>|--file <path>] [--state <state>] [--category <key|none>] [--starts <day|none>] [--due <day|none>]',
171
+ overview: "goal <id> | goal new <title> [flags] | goal edit <id> [flags]",
172
+ synopsis:
173
+ "One goal with the tasks that work towards it; or a goal set or changed, for an owner.",
154
174
  about:
155
175
  "One goal in full, with the tasks that work towards it; or a goal set on the roadmap, for an owner of the project; or one changed, only in what the flags say, where none clears a category or a day. A goal is planned in days as a task is, a start and a target, and sorted into a category of the workspace (console categories). A task works towards a goal through its --goal.",
156
176
  limits: [
@@ -167,17 +187,21 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
167
187
  },
168
188
  categories: {
169
189
  usage: "categories",
190
+ synopsis: "The categories tasks are sorted into, by key.",
170
191
  about:
171
192
  "The categories tasks are sorted into, the same in every project: each with its key, which --category takes, its name and its colour. An administrator keeps them: console category, or the Categories page.",
172
193
  },
173
194
  people: {
174
195
  usage: "people",
196
+ synopsis: "Everyone of the workspace, by login, with what each is in it.",
175
197
  about:
176
198
  "Everyone of the workspace, by login, with their name where the host gives one and what they are in it: admin, who configures the workspace, or member, who works in the projects they are in; a person taken out is marked deactivated. Then the logins an administrator let in that nobody has signed in with yet. The logins are what --assignee, member and person take.",
177
199
  },
178
200
  person: {
179
201
  usage:
180
202
  "person role <login> <admin|member> | person add <login> [--host gh|google] | person remove <login> | person deactivate <login> | person reactivate <login>",
203
+ synopsis:
204
+ "Says what a person is in the workspace, lets a login in or takes one out; for an administrator.",
181
205
  about:
182
206
  "For an administrator: the person themselves on the People page, or their agent here, named on it. role says what a person is in the workspace; the board keeps at least one administrator in. add lets a login in: whoever signs in with that account at that host is a person from then on; the host is read off the login (an address is Google's, anything else GitHub's) unless --host says. remove takes a login away that nobody has signed in with yet. deactivate takes a person out: refused on every request at once, their agents with them, kept with everything they did, and listed nowhere; reactivate takes them back in. A person the organization let in comes and goes with it, and is neither.",
183
207
  refusals: [
@@ -191,6 +215,9 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
191
215
  project: {
192
216
  usage:
193
217
  'project <key> | project new <key> "<name>" [--description <markdown>|--file <path>] [--visibility workspace|private] [--skills <address>|none] [--color <name>] [--symbol <text>] | project edit <key> [--name "<name>"] [the same flags; none clears] | project icon <key> <file> | project icon <key> --remove | project pack-token <key> --stdin | project pack-token <key> --remove',
218
+ overview:
219
+ "project <key> | project new <key> <name> [flags] | project edit <key> [flags] | project icon <key> <file>|--remove | project pack-token <key> --stdin|--remove",
220
+ synopsis: "One project in full; or a project made, changed, given its mark or its pack token.",
194
221
  about:
195
222
  "One project in full; or a project made, with you as its owner, private unless --visibility workspace says everyone of the workspace works in it; or one changed, only in what the flags say, for an owner; or its image for its mark, set from a file of this machine or taken away; or its pack token (ADR-0028), the token a person made at SkillCDN for a private pack, read from standard input and never an argument, which the console keeps sealed to read the pack with, or taken away. The organization's skills are the project's unless --skills names an address of its own.",
196
223
  limits: [
@@ -210,12 +237,15 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
210
237
  },
211
238
  members: {
212
239
  usage: "members",
240
+ synopsis: "Those listed in the project, with what each is in it.",
213
241
  about:
214
242
  "Those listed in the project, by login, with what each is in it (owner, who configures it; member, who works on it) and since when. A project open to the workspace has everyone of it as a member besides, and an administrator owns every project.",
215
243
  },
216
244
  member: {
217
245
  usage:
218
246
  "member add <login> [--role owner|member] [--project <key>[,<key>...]] | member role <login> <owner|member> | member remove <login>",
247
+ synopsis:
248
+ "Lists a person in the project, changes what they are in it, or takes them off; for an owner.",
219
249
  about:
220
250
  "Lists a person of the workspace in the project, as a member unless --role says owner; changes what they are in it; or takes them off the list. For an owner of the project. With --project naming several keys with commas, member add lists the person in each of them in turn, one line per project; one listed already is said so, and the rest go on.",
221
251
  refusals: [
@@ -228,6 +258,7 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
228
258
  category: {
229
259
  usage:
230
260
  'category new <key> "<name>" [--color <name>] | category edit <key> [--name "<name>"] [--color <name>] | category remove <key>',
261
+ synopsis: "Makes, changes or removes a category; for an administrator.",
231
262
  about:
232
263
  "Makes a category tasks are sorted into, the same in every project; changes its name or its colour; or removes one no task is sorted into. For an administrator.",
233
264
  limits: [
@@ -244,14 +275,23 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
244
275
  ],
245
276
  },
246
277
  skills: {
247
- usage: "skills",
278
+ usage: "skills | skills <folder> | skills --search <words> [<folder>] [--limit <n>]",
279
+ synopsis:
280
+ "The project's pack: its skills; what a folder of it holds; or what words find in it.",
248
281
  about:
249
- "The skills of the project's pack, as SkillCDN serves them at the project's address: each with its description, the path of its SKILL.md, which console skill reads it by, and the URI you load it by through your own SkillCDN connection where your host has one; and where the console's own skills are on this machine. A private pack is read with the pack token the project holds (ADR-0028). --json adds the page of each, for a person.",
282
+ "The project's pack, as SkillCDN serves it at the project's address, read by the console with the pack token the project holds where the pack is private (ADR-0028). Alone: the pack by name with how many skills and documents it has, and its skills, each with its description, the path of its SKILL.md, which console skill reads it by, and the URI you load it by through your own SkillCDN connection where your host has one; and where the console's own skills are on this machine. With a folder (/ for the root): what it holds, as browse_repo lays it out: its skills, its folders with how many skills and documents each holds and the README that introduces it, its files; whole, page after page. With --search: the skills and the documents the words find, in relevance order, a skill with the files of it that matched; words, not meaning, in the language the pack is written in; within the folder when one is named; 10 unless --limit says otherwise, up to 25, and a last line says when more matched. --json answers with the console's own JSON, the page of each skill included.",
283
+ limits: ["search: 1 to 500 characters; limit: 1 to 25"],
284
+ refusals: [
285
+ "status not_found: the pack is not served to the console; private, with no pack token on the project",
286
+ "status token_invalid: the pack token the project holds no longer works; an owner sets a new one",
287
+ ],
250
288
  },
251
289
  skill: {
252
- usage: "skill <path> [--file <path>]",
290
+ usage: "skill <path> | skill --file <path>",
291
+ synopsis:
292
+ "One skill of the pack whole, its rules first; or with --file a file of the pack, or what a folder holds.",
253
293
  about:
254
- "One skill of the project's pack, whole, as load_skill lays it out for you: the rules of the repository from its root to the nearest manifest, the skill's body, the files it includes, each under a line that names its path; then the files it has and what it refers to. Read by the console from the SkillCDN deployment, with the pack token the project holds where the pack is private, so you need no connection of your own. <path> is the path of the SKILL.md from the repository's root, as console skills lists it. --file <path> reads a file of the pack instead, whole, or what a folder holds. What you read is the pack's, under its license: what a private pack says stays in your work and is not copied into reports or pages beyond what the work needs.",
294
+ "One skill of the project's pack, whole, as load_skill lays it out for you: the rules of the repository from its root to the nearest manifest, the skill's body, the files it includes, each under a line that names its path; then the files it has and what it refers to. Read by the console from the SkillCDN deployment, with the pack token the project holds where the pack is private, so you need no connection of your own. <path> is the path of the SKILL.md from the repository's root, as console skills lists it. --file <path> reads a file of the pack instead, whole, or what a folder holds, and takes no skill's path beside it; console skills <folder> shows a folder with more.",
255
295
  refusals: [
256
296
  "status not_found: the pack is not served to the console; private, with no pack token on the project",
257
297
  "status token_invalid: the pack token the project holds no longer works; an owner sets a new one",
@@ -261,8 +301,10 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
261
301
  rules: {
262
302
  usage:
263
303
  "rules | rules write --body <markdown>|--file <path>|-|none [--workspace] [--base <version>]",
304
+ synopsis:
305
+ "What this organization and this project ask of agents; rules write writes them, for an owner or an administrator.",
264
306
  about:
265
- "What this organization asks of agents (ADR-0023): the workspace's rules, the console's own until an administrator writes the organization's, and this project's own when it has them. Read them before taking a task; take prints them again. rules write writes this project's rules, for an owner, or with --workspace the workspace's, for an administrator; none puts the written rules away, so that the console's own hold again, or the project adds none. Without a project named, rules shows the workspace's alone.",
307
+ "What this organization asks of agents (ADR-0023): the workspace's rules, the console's own until an administrator writes the organization's, and this project's own when it has them. rules write writes this project's rules, for an owner, or with --workspace the workspace's, for an administrator; none puts the written rules away, so that the console's own hold again, or the project adds none. Without a project named, rules shows the workspace's alone.",
266
308
  limits: [
267
309
  `body: Markdown, up to ${MAX_RULES_LENGTH} characters; the console's own rules, written as they are, write nothing`,
268
310
  ],
@@ -273,8 +315,9 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
273
315
  },
274
316
  take: {
275
317
  usage: "take <number|id> [--agent <name>]",
318
+ synopsis: "Takes a task: a run begins, and the task is in progress and the person's.",
276
319
  about:
277
- "Takes a task: a run begins, and the task is in progress and the person's. A run still open on the task is taken over, since the board cannot know whether its agent still runs: it ends as abandoned, the feed says who took over, and that agent is refused on its next command. Look before taking (console task <number> lists the runs, with when each was last heard from), and take a task another agent was heard from on lately only when told to. --agent is what people see at work; left out, it is what the person called the token.",
320
+ "Takes a task: a run begins, and the task is in progress and the person's; the rules of the board are printed with the task. A run still open on the task is taken over, since the board cannot know whether its agent still runs: it ends as abandoned, the feed says who took over, and that agent is refused on its next command; console task <number> shows the runs on a task, with when each was last heard from, before it is taken. --agent is what people see at work; left out, it is what the person called the token.",
278
321
  limits: [`agent: one line, up to ${MAX_AGENT_LENGTH} characters`],
279
322
  refusals: [
280
323
  "task.not_found: no task has that number or id",
@@ -283,8 +326,9 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
283
326
  },
284
327
  report: {
285
328
  usage: "report <markdown> | report --file <path> | report -",
329
+ synopsis: "Says how the work goes, in Markdown, on the open run.",
286
330
  about:
287
- "Says how the work goes, in Markdown: what was found, what was done, what is next. A milestone at a time, not every step. With - the report is read from standard input.",
331
+ "Says how the work goes, in Markdown: what was found, what was done, what is next. With - the report is read from standard input.",
288
332
  limits: [
289
333
  `the report: ${MARKDOWN}, not blank`,
290
334
  `at most ${MAX_REPORTS_PER_RUN} reports on one run`,
@@ -293,6 +337,7 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
293
337
  },
294
338
  "hand-in": {
295
339
  usage: "hand-in <https url | file path> [--label <words>]",
340
+ synopsis: "Hands in what was made: an https link, or a file from this machine.",
296
341
  about:
297
342
  "Hands in what was made: a link (a branch, a pull request, a document, a page), or a file from this machine, which the console keeps and shows on the task; a picture is shown there small and large, and a report that names it as ![words](its name) shows it where the words are.",
298
343
  limits: [
@@ -310,8 +355,10 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
310
355
  ask: {
311
356
  usage:
312
357
  'ask "<question>" --option "<label>" --option "<label>" [...] [--body <markdown>|--file <path>] [--wait <seconds>]',
358
+ synopsis:
359
+ "Raises a decision a person must make, with options, and waits a while for the answer.",
313
360
  about:
314
- "Raises a decision a person must make: the question, the options to choose from, and what they need to know. The run waits for the answer. The command waits 60 seconds for it unless --wait says otherwise, and exits with 3 if it is still to come: keep waiting with `console decision <id> --wait 100`, within what your shell gives one command, and go on meanwhile only with work that does not depend on the answer. The decision's id is printed as soon as it is raised.",
361
+ "Raises a decision a person must make: the question, the options to choose from, and what they need to know. The run waits for the answer. The command waits 60 seconds for it unless --wait says otherwise, and exits with 3 if it is still to come: keep waiting with `console decision <id> --wait 100`, within what your shell gives one command. The decision's id is printed as soon as it is raised.",
315
362
  limits: [
316
363
  `question: one line, up to ${MAX_QUESTION_LENGTH} characters`,
317
364
  `options: ${MIN_OPTIONS} to ${MAX_OPTIONS}, each one line up to ${MAX_OPTION_LABEL_LENGTH} characters`,
@@ -321,6 +368,8 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
321
368
  },
322
369
  decision: {
323
370
  usage: "decision <id> [--wait <seconds>] | decision <id> --outcome <markdown>|--file <path>",
371
+ synopsis:
372
+ "One decision in full; waits for its answer with --wait; writes what followed with --outcome.",
324
373
  about:
325
374
  "One decision, with its context, its options, its answer and what followed. With --wait, holds on for the answer that long, and exits with 3 if it is still to come. With --outcome, writes down what followed the decision, so that it reads as a record.",
326
375
  limits: [`outcome: ${MARKDOWN}`],
@@ -328,10 +377,12 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
328
377
  },
329
378
  decisions: {
330
379
  usage: "decisions [--open] [--limit <n>] [--offset <n>]",
380
+ synopsis: "The decisions on the board, the waiting first.",
331
381
  about: `The decisions on the board, the waiting first; --open keeps only those. A page at a time: ${DEFAULT_PAGE_LIMIT} unless --limit says otherwise, from --offset on.`,
332
382
  },
333
383
  finish: {
334
384
  usage: 'finish [--summary "<markdown>"|--file <path>]',
385
+ synopsis: "Ends the run: done and handed in; the task goes up for review.",
335
386
  about:
336
387
  "Ends the run: the work is done and handed in, and the task goes up for review. The summary says what was done and what is left; the result itself goes in a last report before, which may be much longer.",
337
388
  limits: [`summary: ${`Markdown, up to ${MAX_SUMMARY_LENGTH} characters`}`],
@@ -339,12 +390,14 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
339
390
  },
340
391
  fail: {
341
392
  usage: 'fail [--summary "<markdown>"|--file <path>]',
393
+ synopsis: "Ends the run: the work could not be done.",
342
394
  about: "Ends the run: the work could not be done. The summary says why, and what is left.",
343
395
  limits: [`summary: Markdown, up to ${MAX_SUMMARY_LENGTH} characters`],
344
396
  refusals: RUN_REFUSALS,
345
397
  },
346
398
  abandon: {
347
399
  usage: 'abandon [<run id>] [--summary "<markdown>"]',
400
+ synopsis: "Leaves a run; the task stays as it is.",
348
401
  about:
349
402
  "Leaves a run; the task stays as it is. A person gives up this way on a run of theirs that will not come back.",
350
403
  limits: [`summary: Markdown, up to ${MAX_SUMMARY_LENGTH} characters`],
@@ -352,16 +405,19 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
352
405
  },
353
406
  runs: {
354
407
  usage: "runs [--task <number|id>] [--open] [--mine] [--limit <n>] [--offset <n>]",
408
+ synopsis: "The runs, newest first, each with when its agent was last heard from.",
355
409
  about: `The runs, newest first, each with when its agent was last heard from: --open keeps the ones not over, --mine those begun with this token. A page at a time: ${DEFAULT_PAGE_LIMIT} unless --limit says otherwise, from --offset on.`,
356
410
  },
357
411
  run: {
358
412
  usage: "run <id>",
413
+ synopsis: "One run in full: its reports, what it handed in, how it ended.",
359
414
  about: "One run in full: its reports, what it handed in, what it waits for, how it ended.",
360
415
  refusals: ["run.not_found: no run has that id"],
361
416
  },
362
417
  comment: {
363
418
  usage:
364
419
  'comment <number|id> "<markdown>" [--attach <path>]... | comment <number|id> --file <path> | comment <number|id> -',
420
+ synopsis: "Says something in the discussion on a task, with files; needs no run.",
365
421
  about:
366
422
  "Says something in the discussion on a task, for its people: an answer to what they asked there, a question that is not a decision, a remark; with files from this machine put in it, which the console keeps and shows on the task, pictures in place. Needs no run. A report is the run's record; a comment is a word to people. Never rewritten: a mistake is corrected in the next one.",
367
423
  limits: [
@@ -379,23 +435,27 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
379
435
  },
380
436
  file: {
381
437
  usage: "file <id> [--out <path>]",
438
+ synopsis: "Reads a file of the project to this machine, by its id.",
382
439
  about:
383
440
  "Reads a file of the project from the console and writes it on this machine: one a run handed in, one put in a comment, or one attached to a page, by the id the task, the run or the page shows. Written under its own name in the working directory, at --out <path>, or into --out <directory>. Then look at it with your own tools.",
384
441
  refusals: ["file.not_found: no file of this project has that id"],
385
442
  },
386
443
  docs: {
387
444
  usage: "docs [<folder>] [--search <words>] [--archived]",
445
+ synopsis: "The pages of the project's documents: a folder, or those found by words.",
388
446
  about:
389
447
  "The pages of the project's documents: those in a folder, with the folders in it (the root when none is named), or those found by words in their title or body. Archived pages are left out unless --archived.",
390
448
  },
391
449
  doc: {
392
450
  usage: "doc <path> [--version <n>|--versions]",
451
+ synopsis: "One page in full, or one of its versions.",
393
452
  about:
394
453
  "One page in full: its title, its body in Markdown, what it links to, what refers to it (pages, tasks, decisions), and its files. --versions lists its versions, who wrote each and when; --version <n> prints one as it was.",
395
454
  refusals: [...DOCUMENT_REFUSALS, "document.version_not_found: the page has no such version"],
396
455
  },
397
456
  write: {
398
457
  usage: 'write <path> [--title "<title>"] --body <markdown>|--file <path> [--base <version>]',
458
+ synopsis: "Writes a page: a new one at a path, or a new version of the page there.",
399
459
  about:
400
460
  "Writes a page: the first version at a new path, or a new version of the page there. A link in the body whose destination is a page's path (guides/onboarding) refers to that page, and the page says what refers to it. Without --title the page keeps its title. With --base, the write is refused if the page has moved on since that version.",
401
461
  limits: [
@@ -413,6 +473,7 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
413
473
  },
414
474
  attach: {
415
475
  usage: "attach <path> <file> [--label <words>]",
476
+ synopsis: "Attaches a file from this machine to a page.",
416
477
  about:
417
478
  "Attaches a file from this machine to a page: the console keeps it, and the page lists it; a picture is shown where the body names it as ![words](its name).",
418
479
  limits: [
@@ -428,17 +489,21 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
428
489
  },
429
490
  archive: {
430
491
  usage: "archive <path>",
492
+ synopsis: "Puts a page away; nothing is deleted.",
431
493
  about:
432
494
  "Puts a page away: out of the folders and the search, still readable at its path, not written to until restored. Nothing is deleted.",
433
495
  refusals: DOCUMENT_REFUSALS,
434
496
  },
435
497
  restore: {
436
498
  usage: "restore <path>",
499
+ synopsis: "Brings an archived page back.",
437
500
  about: "Brings an archived page back.",
438
501
  refusals: DOCUMENT_REFUSALS,
439
502
  },
440
503
  serve: {
441
504
  usage: "serve [<dir>] [--port <n>]",
505
+ synopsis:
506
+ "For a person: serves a console of your own from this machine, its /api/ carried to the console with your token.",
442
507
  about:
443
508
  "For a person: serves a console of your own from this machine, for you. The build in <dir> (its index.html and files) is served at http://127.0.0.1:11197/, and everything under /api/ is carried to the console you are signed in to with your token, which the pages never see. Without a directory only the API is served there, for a development server to send its /api/ requests to. The loopback only; a request from another site's page is refused. Stop it with Ctrl+C.",
444
509
  refusals: ["not a build to serve: the directory holds no index.html"],
@@ -450,8 +515,12 @@ const preamble = (version: string | undefined): string =>
450
515
  `console${version === undefined ? "" : ` ${version}`}: the board of your organization's work with AI agents, from the command line.
451
516
  You act as the person whose token you hold; everything you send is shown to people as text.
452
517
  The rules of this organization come with the task: console take prints them; console rules reads them any time.
518
+ How the board is worked is the skill working-the-board, installed with this command: console skills prints
519
+ where it is; read it before the first command of a run.
453
520
 
454
521
  Usage: console <command> [arguments] [--json] [--url <origin>] [--project <key>]
522
+ A command in full, every form and flag ([flags] below), the limits the console holds it to and what
523
+ it may refuse: console help <command>. Read a command there when you are about to use it.
455
524
  `;
456
525
 
457
526
  const CLOSING = `Options everywhere: --json answers with the console's own JSON; --url names another console;
@@ -460,8 +529,7 @@ A listing (tasks, decisions, runs) is a page at a time: --limit <n> and --offset
460
529
  says when there is more, so that nothing is read that is not needed.
461
530
  Commands on a run (report, hand-in, ask, finish, fail) act on your one open run in the project;
462
531
  with several open, say which with --run <id>. A Markdown body given as - is read from standard input.
463
- console help <command> says what each command takes, the limits the console holds it to, and
464
- what it may refuse. console --version says which version of the command this is.
532
+ console --version says which version of the command this is.
465
533
 
466
534
  Exit codes: 0 done; 1 the console refused, or could not be reached (the reason is on stderr);
467
535
  2 the command was not understood; 3 the decision still waits for a person.
@@ -514,7 +582,7 @@ const SECTIONS: readonly { readonly title: string; readonly commands: readonly s
514
582
  },
515
583
  ];
516
584
 
517
- /** The whole usage, as `console help` prints it, naming the version when it is known. */
585
+ /** The overview, as `console help` prints it: every command in a line, naming the version when it is known. */
518
586
  export function usage(version?: string): string {
519
587
  const parts = [preamble(version)];
520
588
  for (const section of SECTIONS) {
@@ -522,7 +590,7 @@ export function usage(version?: string): string {
522
590
  for (const name of section.commands) {
523
591
  const help = COMMAND_HELP[name];
524
592
  if (help !== undefined) {
525
- parts.push(` console ${help.usage}\n ${help.about}`);
593
+ parts.push(` console ${help.overview ?? help.usage}\n ${help.synopsis}`);
526
594
  }
527
595
  }
528
596
  parts.push("");
@@ -531,7 +599,7 @@ export function usage(version?: string): string {
531
599
  return parts.join("\n");
532
600
  }
533
601
 
534
- /** What `console help <command>` prints, or nothing for a command that is not one. */
602
+ /** What `console help <command>` prints, the command in full; or nothing for a command that is not one. */
535
603
  export function helpFor(name: string): string | undefined {
536
604
  const help = COMMAND_HELP[name];
537
605
  if (help === undefined) {