@skillcdn/console 0.1.12 → 0.1.13

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/src/cli/help.ts CHANGED
@@ -3,6 +3,9 @@ import {
3
3
  MAX_AGENT_LENGTH,
4
4
  MAX_ARTIFACTS_PER_RUN,
5
5
  MAX_BODY_LENGTH,
6
+ MAX_CATEGORIES,
7
+ MAX_CATEGORY_KEY_LENGTH,
8
+ MAX_CATEGORY_NAME_LENGTH,
6
9
  MAX_DOCUMENT_LENGTH,
7
10
  MAX_DOCUMENT_PATH_LENGTH,
8
11
  MAX_FILE_BYTES,
@@ -10,6 +13,11 @@ import {
10
13
  MAX_LINK_LABEL_LENGTH,
11
14
  MAX_OPTION_LABEL_LENGTH,
12
15
  MAX_OPTIONS,
16
+ MAX_PROJECT_DESCRIPTION_LENGTH,
17
+ MAX_PROJECT_ICON_BYTES,
18
+ MAX_PROJECT_KEY_LENGTH,
19
+ MAX_PROJECT_NAME_LENGTH,
20
+ MAX_PROJECT_SYMBOL_LENGTH,
13
21
  MAX_QUESTION_LENGTH,
14
22
  MAX_REPORTS_PER_RUN,
15
23
  MAX_SUMMARY_LENGTH,
@@ -17,6 +25,7 @@ import {
17
25
  MAX_VERSIONS_PER_DOCUMENT,
18
26
  MIN_OPTIONS,
19
27
  } from "../limits.js";
28
+ import { COLOR_NAMES } from "../vocabulary.js";
20
29
 
21
30
  // What the command says about itself: for an agent that meets it for the first time, and for a
22
31
  // person. One table for every command, from which the usage and each command's help are made:
@@ -53,6 +62,8 @@ const DOCUMENT_PATH = `a path of lowercase letters, digits and hyphens, with sla
53
62
  const DOCUMENT_REFUSALS = ["document.not_found: no page has that path in this project"];
54
63
 
55
64
  const DAY = "a day as 2026-10-24, today, tomorrow, or +7 for seven days from today";
65
+ const COLORS = `one of ${COLOR_NAMES.join(", ")}`;
66
+ const KEY = "lowercase letters, digits and hyphens";
56
67
  const TASK_FIELD_FLAGS =
57
68
  "[--state <state>] [--priority <priority>] [--category <key>] [--goal <id>] [--starts <day>] [--due <day>] [--assignee <login|me>] [--parent <number|id>]";
58
69
  const TASK_FIELD_LIMITS = [
@@ -143,7 +154,76 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
143
154
  categories: {
144
155
  usage: "categories",
145
156
  about:
146
- "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 on the console's Categories page.",
157
+ "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.",
158
+ },
159
+ people: {
160
+ usage: "people",
161
+ about:
162
+ "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. The logins are what --assignee, member and person role take.",
163
+ },
164
+ person: {
165
+ usage: "person role <login> <admin|member>",
166
+ about:
167
+ "Says what a person is in the workspace, for an administrator: the person themselves on the People page, or their agent here, named on it. The board keeps at least one administrator.",
168
+ refusals: [
169
+ "auth.forbidden: only an administrator says what a person is",
170
+ "person.last_admin: the board keeps at least one administrator; make another one first",
171
+ ],
172
+ },
173
+ project: {
174
+ usage:
175
+ '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',
176
+ about:
177
+ "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. The organization's skills are the project's unless --skills names an address of its own.",
178
+ limits: [
179
+ `key: ${KEY}, up to ${MAX_PROJECT_KEY_LENGTH} characters; it does not change once made`,
180
+ `name: one line, up to ${MAX_PROJECT_NAME_LENGTH} characters; description: up to ${MAX_PROJECT_DESCRIPTION_LENGTH}`,
181
+ `color: ${COLORS}; symbol: up to ${MAX_PROJECT_SYMBOL_LENGTH} characters without spaces, shown on the colour; left out, the pages choose both from the key and the name`,
182
+ `image: a PNG, a JPEG, a GIF or a WebP of at most ${MAX_PROJECT_ICON_BYTES} bytes`,
183
+ ],
184
+ refusals: [
185
+ "project.not_found: no project has that key, or it is not yours to see",
186
+ "auth.forbidden: only an owner of the project changes it",
187
+ "project.key_taken: a project has that key already",
188
+ "project.invalid_address: the skills address is not one as SkillCDN spells it",
189
+ "project.invalid_icon: the file is not an image of those kinds, by its bytes",
190
+ "request.too_large: the image is over the limit",
191
+ ],
192
+ },
193
+ members: {
194
+ usage: "members",
195
+ about:
196
+ "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.",
197
+ },
198
+ member: {
199
+ usage:
200
+ "member add <login> [--role owner|member] | member role <login> <owner|member> | member remove <login>",
201
+ about:
202
+ "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.",
203
+ refusals: [
204
+ "auth.forbidden: only an owner of the project lists its members",
205
+ "member.invalid_person: the person is not of the workspace",
206
+ "member.exists: the person is listed already; member role changes what they are",
207
+ "member.not_found: the person is not listed in the project",
208
+ ],
209
+ },
210
+ category: {
211
+ usage:
212
+ 'category new <key> "<name>" [--color <name>] | category edit <key> [--name "<name>"] [--color <name>] | category remove <key>',
213
+ about:
214
+ "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.",
215
+ limits: [
216
+ `key: ${KEY}, up to ${MAX_CATEGORY_KEY_LENGTH} characters; it does not change once made`,
217
+ `name: one line, up to ${MAX_CATEGORY_NAME_LENGTH} characters; color: ${COLORS}, gray when left out`,
218
+ `at most ${MAX_CATEGORIES} categories in the workspace`,
219
+ ],
220
+ refusals: [
221
+ "auth.forbidden: only an administrator keeps the categories",
222
+ "category.key_taken: a category has that key already",
223
+ "category.not_found: no category has that key",
224
+ "category.in_use: tasks are sorted into it; move them out first (console task edit <number> --category <other|none>)",
225
+ "category.too_many: the workspace holds as many categories as one may",
226
+ ],
147
227
  },
148
228
  skills: {
149
229
  usage: "skills",
@@ -352,6 +432,11 @@ const SECTIONS: readonly { readonly title: string; readonly commands: readonly s
352
432
  title: "The documents: the project's pages of Markdown, in folders, by path",
353
433
  commands: ["docs", "doc", "write", "attach", "archive", "restore"],
354
434
  },
435
+ {
436
+ title:
437
+ "Configuring, as the person: what an administrator or an owner does on the pages, their agent does here, named on it",
438
+ commands: ["people", "person", "project", "members", "member", "category"],
439
+ },
355
440
  ];
356
441
 
357
442
  /** The whole usage, as `console help` prints it, naming the version when it is known. */
package/src/console.tsx CHANGED
@@ -332,13 +332,12 @@ function holdsToken(data: ConsoleData): boolean {
332
332
  }
333
333
 
334
334
  /**
335
- * The pages a console that holds a token does not offer: the tokens themselves and
336
- * configuring, which are a person's own doing on the console's own pages, signed in.
335
+ * The pages a console that holds a token does not offer: the tokens themselves, and the
336
+ * approval of an agent's connection, which makes one; a person's own doing on the console's
337
+ * own pages, signed in. Everything else a person may do, their agent may (ADR-0021).
337
338
  */
338
339
  export function onlyOnOwnPages(route: Route): boolean {
339
340
  return (
340
- route.name === "new-project" ||
341
- route.name === "settings" ||
342
341
  route.name === "agents" ||
343
342
  route.name === "new-token" ||
344
343
  route.name === "person-agents" ||
@@ -351,28 +350,6 @@ function NotWithToken() {
351
350
  return <EmptyState title={words.title} body={words.body} />;
352
351
  }
353
352
 
354
- /**
355
- * Where a console that holds a token keeps the person's choice of language: the browser's own
356
- * storage, which may be absent or refuse, and then the browser's languages decide.
357
- */
358
- const LANGUAGE_KEY = "sc-language";
359
-
360
- function storedLanguage(): string | undefined {
361
- try {
362
- return globalThis.localStorage?.getItem(LANGUAGE_KEY) ?? undefined;
363
- } catch {
364
- return undefined;
365
- }
366
- }
367
-
368
- function storeLanguage(tag: string): void {
369
- try {
370
- globalThis.localStorage?.setItem(LANGUAGE_KEY, tag);
371
- } catch {
372
- // A browser that keeps nothing: the choice holds for this page.
373
- }
374
- }
375
-
376
353
  /** A link that reads as a button: where a form is, for instance. */
377
354
  function LinkButton(props: {
378
355
  readonly href: string;
@@ -397,12 +374,11 @@ function ProjectsPage(props: {
397
374
  const { data, components, navigation, making } = props;
398
375
  const { busy, error, act } = useAction();
399
376
  const href = (project: RestProject) => projectHref(project.key);
400
- const configures = !holdsToken(data);
401
377
  return (
402
378
  <>
403
379
  <div className="sc-page-head">
404
380
  <h1 className="sc-page-title">{words.title}</h1>
405
- {!making && configures && (
381
+ {!making && (
406
382
  <LinkButton href={PATHS.newProject} variant="primary">
407
383
  {words.newProject}
408
384
  </LinkButton>
@@ -435,11 +411,7 @@ function ProjectsPage(props: {
435
411
  <EmptyState
436
412
  title={words.empty.title}
437
413
  body={words.empty.body}
438
- action={
439
- configures ? (
440
- <LinkButton href={PATHS.newProject}>{words.newProject}</LinkButton>
441
- ) : undefined
442
- }
414
+ action={<LinkButton href={PATHS.newProject}>{words.newProject}</LinkButton>}
443
415
  />
444
416
  }
445
417
  />
@@ -966,7 +938,7 @@ function ProjectPage(props: {
966
938
  }
967
939
  if (route.name === "members") {
968
940
  const me = data.me?.person ?? undefined;
969
- const configures = owner && !holdsToken(data);
941
+ const configures = owner;
970
942
  const members = words.projects.members;
971
943
  return (
972
944
  <>
@@ -1200,7 +1172,7 @@ function WorkspacePage(props: {
1200
1172
  }
1201
1173
  if (route.name === "categories") {
1202
1174
  const me = data.me?.person ?? undefined;
1203
- const administrator = me?.role === "admin" && !holdsToken(data);
1175
+ const administrator = me?.role === "admin";
1204
1176
  const categories = words.categories;
1205
1177
  return (
1206
1178
  <>
@@ -1239,7 +1211,7 @@ function WorkspacePage(props: {
1239
1211
 
1240
1212
  if (route.name === "people") {
1241
1213
  const me = data.me?.person ?? undefined;
1242
- const administrator = me?.role === "admin" && !holdsToken(data);
1214
+ const administrator = me?.role === "admin";
1243
1215
  return (
1244
1216
  <>
1245
1217
  <div className="sc-page-head">
@@ -1334,10 +1306,7 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
1334
1306
  const title = data.me?.workspace.name ?? fallbackTitle;
1335
1307
  const viaToken = holdsToken(data);
1336
1308
  // The language: the person's choice, else the browser's, else the first the console speaks.
1337
- // A console that holds a token cannot keep the choice on the person, and keeps it in the
1338
- // browser instead.
1339
- const [ownChoice, setOwnChoice] = useState<string | undefined>(storedLanguage);
1340
- const chosen = viaToken ? (ownChoice ?? data.me?.language) : data.me?.language;
1309
+ const chosen = data.me?.language;
1341
1310
  const pack = useMemo(
1342
1311
  () =>
1343
1312
  chooseLanguage(
@@ -1485,7 +1454,7 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
1485
1454
  label: words.nav.members,
1486
1455
  current: route.name === "members",
1487
1456
  },
1488
- ...(project?.role === "owner" && !viaToken
1457
+ ...(project?.role === "owner"
1489
1458
  ? [
1490
1459
  {
1491
1460
  href: projectHref(projectKey, "settings"),
@@ -1591,14 +1560,7 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
1591
1560
  label: candidate.label,
1592
1561
  current: candidate.tag === pack.tag,
1593
1562
  }))}
1594
- onLanguage={(tag) => {
1595
- if (viaToken) {
1596
- storeLanguage(tag);
1597
- setOwnChoice(tag);
1598
- } else {
1599
- void data.actions.updateMe({ language: tag }).catch(() => undefined);
1600
- }
1601
- }}
1563
+ onLanguage={(tag) => void data.actions.updateMe({ language: tag }).catch(() => undefined)}
1602
1564
  person={data.me.person}
1603
1565
  live={projectKey === undefined ? undefined : data.live}
1604
1566
  wide={
package/src/i18n/en.ts CHANGED
@@ -89,7 +89,7 @@ export const en = {
89
89
  },
90
90
  notWithToken: {
91
91
  title: "Done on the console's own pages",
92
- body: "This console holds a token, which acts as you on the board. Connecting agents, configuring projects and people, and keeping a language are done on the organization's console, signed in.",
92
+ body: "This console holds a token, which acts as you on the board, configuring included. Agents are connected, and tokens made and removed, on the organization's console, signed in.",
93
93
  },
94
94
  you: "(you)",
95
95
  nobody: "Nobody",
@@ -684,7 +684,7 @@ export const en = {
684
684
  lookingUp: "Looking the code up",
685
685
  notACode: "That is not a code: eight letters and digits, in two groups.",
686
686
  noSuchCode:
687
- "No agent asks to connect with this code. A code is good for ten minutes and for one approval; ask your agent for a new one.",
687
+ "No agent asks to connect with this code. A code is good for thirty minutes and for one approval; ask your agent for a new one.",
688
688
  couldNotAsk: "The console could not be asked.",
689
689
  asks: {
690
690
  title: "An agent asks to connect as you",
package/src/i18n/ko.ts CHANGED
@@ -62,7 +62,7 @@ export const ko: Messages = {
62
62
  },
63
63
  notWithToken: {
64
64
  title: "콘솔 자체 페이지에서 하는 일입니다",
65
- body: "이 콘솔은 토큰으로 당신을 대신해 보드를 씁니다. 에이전트 연결, 프로젝트와 사람 설정, 언어 저장은 조직 콘솔에 로그인해서 합니다.",
65
+ body: "이 콘솔은 토큰으로 당신을 대신해 보드를 쓰며, 설정도 그렇게 합니다. 에이전트 연결과 토큰 관리는 조직 콘솔에 로그인해서 합니다.",
66
66
  },
67
67
  you: "(나)",
68
68
  nobody: "없음",
@@ -652,7 +652,7 @@ export const ko: Messages = {
652
652
  lookingUp: "코드를 찾는 중",
653
653
  notACode: "코드가 아닙니다. 영문자와 숫자 여덟 자, 두 묶음입니다.",
654
654
  noSuchCode:
655
- "이 코드로 연결을 요청한 에이전트가 없습니다. 코드는 10분 동안, 한 번의 승인에만 유효합니다. 에이전트에게 새 코드를 요청하세요.",
655
+ "이 코드로 연결을 요청한 에이전트가 없습니다. 코드는 30분 동안, 한 번의 승인에만 유효합니다. 에이전트에게 새 코드를 요청하세요.",
656
656
  couldNotAsk: "콘솔에 물어볼 수 없습니다.",
657
657
  asks: {
658
658
  title: "에이전트가 나로서 연결하기를 요청합니다",