@skillcdn/console 0.1.17 → 0.1.19

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 (154) hide show
  1. package/README.md +4 -4
  2. package/SKILLCDN.md +1 -1
  3. package/dist/api.d.ts +3 -2
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/api.js +2 -1
  6. package/dist/api.js.map +1 -1
  7. package/dist/cli/cli.d.ts +11 -1
  8. package/dist/cli/cli.d.ts.map +1 -1
  9. package/dist/cli/cli.js +496 -51
  10. package/dist/cli/cli.js.map +1 -1
  11. package/dist/cli/credentials.d.ts +102 -3
  12. package/dist/cli/credentials.d.ts.map +1 -1
  13. package/dist/cli/credentials.js +177 -8
  14. package/dist/cli/credentials.js.map +1 -1
  15. package/dist/cli/directory.d.ts +6 -0
  16. package/dist/cli/directory.d.ts.map +1 -1
  17. package/dist/cli/directory.js +37 -6
  18. package/dist/cli/directory.js.map +1 -1
  19. package/dist/cli/format.d.ts +11 -2
  20. package/dist/cli/format.d.ts.map +1 -1
  21. package/dist/cli/format.js +114 -5
  22. package/dist/cli/format.js.map +1 -1
  23. package/dist/cli/help.d.ts.map +1 -1
  24. package/dist/cli/help.js +49 -25
  25. package/dist/cli/help.js.map +1 -1
  26. package/dist/cli/install.d.ts +21 -0
  27. package/dist/cli/install.d.ts.map +1 -0
  28. package/dist/cli/install.js +87 -0
  29. package/dist/cli/install.js.map +1 -0
  30. package/dist/cli/main.js +22 -3
  31. package/dist/cli/main.js.map +1 -1
  32. package/dist/cli/notices.d.ts +13 -0
  33. package/dist/cli/notices.d.ts.map +1 -0
  34. package/dist/cli/notices.js +31 -0
  35. package/dist/cli/notices.js.map +1 -0
  36. package/dist/client.d.ts +36 -3
  37. package/dist/client.d.ts.map +1 -1
  38. package/dist/client.js +22 -4
  39. package/dist/client.js.map +1 -1
  40. package/dist/compat.d.ts +66 -0
  41. package/dist/compat.d.ts.map +1 -0
  42. package/dist/compat.js +115 -0
  43. package/dist/compat.js.map +1 -0
  44. package/dist/components/board.d.ts +9 -1
  45. package/dist/components/board.d.ts.map +1 -1
  46. package/dist/components/board.js +82 -5
  47. package/dist/components/board.js.map +1 -1
  48. package/dist/components/calendar.d.ts +3 -1
  49. package/dist/components/calendar.d.ts.map +1 -1
  50. package/dist/components/calendar.js +21 -2
  51. package/dist/components/calendar.js.map +1 -1
  52. package/dist/components/comments.d.ts.map +1 -1
  53. package/dist/components/comments.js +12 -4
  54. package/dist/components/comments.js.map +1 -1
  55. package/dist/components/event-feed.d.ts.map +1 -1
  56. package/dist/components/event-feed.js +4 -0
  57. package/dist/components/event-feed.js.map +1 -1
  58. package/dist/components/goals.d.ts +20 -2
  59. package/dist/components/goals.d.ts.map +1 -1
  60. package/dist/components/goals.js +50 -13
  61. package/dist/components/goals.js.map +1 -1
  62. package/dist/components/markdown.d.ts +6 -0
  63. package/dist/components/markdown.d.ts.map +1 -1
  64. package/dist/components/markdown.js +7 -0
  65. package/dist/components/markdown.js.map +1 -1
  66. package/dist/components/pictures.d.ts +68 -0
  67. package/dist/components/pictures.d.ts.map +1 -0
  68. package/dist/components/pictures.js +213 -0
  69. package/dist/components/pictures.js.map +1 -0
  70. package/dist/components/projects.d.ts +12 -0
  71. package/dist/components/projects.d.ts.map +1 -1
  72. package/dist/components/projects.js +15 -0
  73. package/dist/components/projects.js.map +1 -1
  74. package/dist/components/roadmap.d.ts +37 -0
  75. package/dist/components/roadmap.d.ts.map +1 -0
  76. package/dist/components/roadmap.js +76 -0
  77. package/dist/components/roadmap.js.map +1 -0
  78. package/dist/components/task-view.d.ts +3 -0
  79. package/dist/components/task-view.d.ts.map +1 -1
  80. package/dist/components/task-view.js +6 -4
  81. package/dist/components/task-view.js.map +1 -1
  82. package/dist/components/tokens.d.ts +2 -0
  83. package/dist/components/tokens.d.ts.map +1 -1
  84. package/dist/components/tokens.js +13 -3
  85. package/dist/components/tokens.js.map +1 -1
  86. package/dist/console.css +486 -3
  87. package/dist/console.d.ts.map +1 -1
  88. package/dist/console.js +132 -17
  89. package/dist/console.js.map +1 -1
  90. package/dist/data.d.ts +16 -1
  91. package/dist/data.d.ts.map +1 -1
  92. package/dist/data.js +48 -3
  93. package/dist/data.js.map +1 -1
  94. package/dist/i18n/en.d.ts +76 -0
  95. package/dist/i18n/en.d.ts.map +1 -1
  96. package/dist/i18n/en.js +74 -2
  97. package/dist/i18n/en.js.map +1 -1
  98. package/dist/i18n/ko.d.ts.map +1 -1
  99. package/dist/i18n/ko.js +74 -2
  100. package/dist/i18n/ko.js.map +1 -1
  101. package/dist/index.d.ts +6 -4
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +6 -4
  104. package/dist/index.js.map +1 -1
  105. package/dist/router.d.ts +31 -7
  106. package/dist/router.d.ts.map +1 -1
  107. package/dist/router.js +48 -8
  108. package/dist/router.js.map +1 -1
  109. package/dist/rules.js +5 -5
  110. package/dist/rules.js.map +1 -1
  111. package/dist/schemas.d.ts +200 -0
  112. package/dist/schemas.d.ts.map +1 -1
  113. package/dist/schemas.js +96 -0
  114. package/dist/schemas.js.map +1 -1
  115. package/dist/vocabulary.d.ts +2 -2
  116. package/dist/vocabulary.d.ts.map +1 -1
  117. package/dist/vocabulary.js +3 -0
  118. package/dist/vocabulary.js.map +1 -1
  119. package/docs/specs/cli.md +26 -15
  120. package/docs/specs/rest.md +31 -9
  121. package/package.json +3 -2
  122. package/skills/working-the-board/SKILL.md +11 -4
  123. package/src/api.ts +36 -0
  124. package/src/cli/cli.ts +616 -54
  125. package/src/cli/credentials.ts +276 -14
  126. package/src/cli/directory.ts +43 -6
  127. package/src/cli/format.ts +146 -5
  128. package/src/cli/help.ts +51 -25
  129. package/src/cli/install.ts +111 -0
  130. package/src/cli/main.ts +23 -3
  131. package/src/cli/notices.ts +48 -0
  132. package/src/client.ts +91 -6
  133. package/src/compat.ts +173 -0
  134. package/src/components/board.tsx +154 -8
  135. package/src/components/calendar.tsx +41 -1
  136. package/src/components/comments.tsx +20 -10
  137. package/src/components/event-feed.tsx +4 -0
  138. package/src/components/goals.tsx +234 -88
  139. package/src/components/markdown.tsx +19 -0
  140. package/src/components/pictures.tsx +389 -0
  141. package/src/components/projects.tsx +68 -0
  142. package/src/components/roadmap.tsx +242 -0
  143. package/src/components/task-view.tsx +43 -2
  144. package/src/components/tokens.tsx +38 -1
  145. package/src/console.tsx +278 -4
  146. package/src/data.ts +73 -2
  147. package/src/i18n/en.ts +77 -2
  148. package/src/i18n/ko.ts +77 -2
  149. package/src/index.ts +35 -0
  150. package/src/router.ts +70 -11
  151. package/src/rules.ts +5 -5
  152. package/src/schemas.ts +117 -0
  153. package/src/styles/console.css +486 -3
  154. package/src/vocabulary.ts +3 -0
package/src/data.ts CHANGED
@@ -6,6 +6,8 @@ import {
6
6
  type DocumentFilter,
7
7
  EVENT_KINDS,
8
8
  type FileUpload,
9
+ LIST_LIMIT,
10
+ type ProjectClient,
9
11
  type RestAnswerInput,
10
12
  type RestCategory,
11
13
  type RestCategoryInput,
@@ -44,6 +46,7 @@ import {
44
46
  type RestTask,
45
47
  type RestTaskInput,
46
48
  type RestTaskPatch,
49
+ type RestTasks,
47
50
  type RestToken,
48
51
  type RestTokenCreated,
49
52
  type RestTokenInput,
@@ -74,11 +77,26 @@ export interface ConsoleActions {
74
77
  /** Sets the image for the mark of the project the page is on, or takes it away; for an owner. */
75
78
  setProjectIcon(input: FileUpload): Promise<RestProject>;
76
79
  removeProjectIcon(): Promise<RestProject>;
80
+ /** Sets the pack token of the project the page is on, or takes it away; for an owner (ADR-0028). */
81
+ setPackToken(token: string): Promise<RestProject>;
82
+ removePackToken(): Promise<RestProject>;
77
83
  addMember(input: RestMemberInput): Promise<RestMember>;
78
84
  updateMember(personId: string, patch: RestMemberPatch): Promise<RestMember>;
79
85
  removeMember(personId: string): Promise<void>;
80
86
  createTask(input: RestTaskInput): Promise<RestTask>;
81
87
  updateTask(id: string, patch: RestTaskPatch): Promise<RestTask>;
88
+ /** One task by its id or its number, asked for by itself: a task put away is not among the board's. */
89
+ fetchTask(ref: string, signal?: AbortSignal): Promise<RestTask>;
90
+ /** Puts a done or dropped task away, or brings one back (ADR-0029). */
91
+ archiveTask(id: string): Promise<RestTask>;
92
+ restoreTask(id: string): Promise<RestTask>;
93
+ /** Puts every done or dropped task of a goal away; for an owner. Answers how many went. */
94
+ archiveGoalTasks(goalId: string): Promise<number>;
95
+ /** A page of the tasks put away, newest first. */
96
+ archivedTasks(
97
+ page: { readonly limit: number; readonly offset: number },
98
+ signal?: AbortSignal,
99
+ ): Promise<RestTasks>;
82
100
  raiseDecision(input: RestDecisionInput): Promise<RestDecision>;
83
101
  answerDecision(id: string, input: RestAnswerInput): Promise<RestDecision>;
84
102
  /** Grows a decision's record: its context, or what followed. */
@@ -201,6 +219,32 @@ async function readFeed(
201
219
  return events.slice(-FEED_KEEP);
202
220
  }
203
221
 
222
+ /** How many pages of the current tasks are read before the board is shown as it stands. */
223
+ const TASKS_MAX_PAGES = 20;
224
+
225
+ /**
226
+ * The current tasks of a project, whole: page after page while the server says there is more,
227
+ * so that a board past one page loses nothing without a word; the tasks put away are not among
228
+ * them (ADR-0029).
229
+ */
230
+ async function readTasks(
231
+ scope: ProjectClient,
232
+ signal: AbortSignal | undefined,
233
+ ): Promise<{ readonly items: RestTask[] }> {
234
+ const items: RestTask[] = [];
235
+ for (let page = 0; page < TASKS_MAX_PAGES; page += 1) {
236
+ const result = await scope.tasks(
237
+ { archived: false, limit: LIST_LIMIT, offset: page * LIST_LIMIT },
238
+ signal,
239
+ );
240
+ items.push(...result.items);
241
+ if (!result.more) {
242
+ break;
243
+ }
244
+ }
245
+ return { items };
246
+ }
247
+
204
248
  const wordsOf = (failure: unknown, fallback: string): string =>
205
249
  failure instanceof ApiError ? failure.message : fallback;
206
250
 
@@ -265,9 +309,9 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
265
309
  const [nextProject, nextTasks, nextDecisions, nextGoals, nextRuns, nextMembers] =
266
310
  await Promise.all([
267
311
  scope.get(signal),
268
- scope.tasks(undefined, signal),
312
+ readTasks(scope, signal),
269
313
  scope.decisions(undefined, signal),
270
- scope.goals(signal),
314
+ scope.goals({}, signal),
271
315
  scope.runs(undefined, signal),
272
316
  scope.members(signal),
273
317
  ]);
@@ -454,6 +498,16 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
454
498
  await refreshWorkspace();
455
499
  return changed;
456
500
  },
501
+ async setPackToken(token) {
502
+ const changed = await scope().setPackToken({ token });
503
+ setProject(changed);
504
+ return changed;
505
+ },
506
+ async removePackToken() {
507
+ const changed = await scope().removePackToken();
508
+ setProject(changed);
509
+ return changed;
510
+ },
457
511
  async addMember(input) {
458
512
  const member = await scope().addMember(input);
459
513
  await refreshProject();
@@ -478,6 +532,23 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
478
532
  setTasks((current) => current.map((candidate) => (candidate.id === id ? task : candidate)));
479
533
  return task;
480
534
  },
535
+ fetchTask: (ref, signal) => scope().task(ref, signal),
536
+ async archiveTask(id) {
537
+ const task = await scope().archiveTask(id);
538
+ setTasks((current) => current.filter((candidate) => candidate.id !== id));
539
+ return task;
540
+ },
541
+ async restoreTask(id) {
542
+ const task = await scope().restoreTask(id);
543
+ setTasks((current) => [task, ...current.filter((candidate) => candidate.id !== id)]);
544
+ return task;
545
+ },
546
+ async archiveGoalTasks(goalId) {
547
+ const { archived } = await scope().archiveTasksOfGoal(goalId);
548
+ await refreshProject();
549
+ return archived;
550
+ },
551
+ archivedTasks: (page, signal) => scope().tasks({ archived: true, ...page }, signal),
481
552
  async raiseDecision(input) {
482
553
  const decision = await scope().raiseDecision(input);
483
554
  await refreshProject();
package/src/i18n/en.ts CHANGED
@@ -194,12 +194,24 @@ export const en = {
194
194
  private: "The administrators own it until an owner is listed.",
195
195
  },
196
196
  },
197
+ packToken: {
198
+ aria: "The pack token",
199
+ label: "Token",
200
+ hint: "A token a person made at SkillCDN for the pack's repository, on their account page there. The console keeps it sealed, reads the pack with it for whoever may see the project, and shows it to nobody.",
201
+ placeholder: "scdn_repo_…",
202
+ set: "Set the token",
203
+ remove: "Remove the token",
204
+ },
197
205
  settings: {
198
206
  title: "Settings",
199
207
  aria: "Project settings",
200
208
  edit: "Edit the project",
201
209
  organizationsSkills: (address: string) => `The organization's: ${address}`,
202
210
  noSkills: "None: the console names no address for the organization's.",
211
+ packToken: "Pack token",
212
+ packTokenNone: "None: a public pack needs none, and a private one is not read.",
213
+ packTokenSet: "Set",
214
+ packTokenEdit: "Change it",
203
215
  },
204
216
  },
205
217
 
@@ -210,6 +222,8 @@ export const en = {
210
222
  body: "Write the first task: what is to be done, and for whom.",
211
223
  },
212
224
  moveTo: "Move to",
225
+ grip: "Drag to another column",
226
+ archive: "Archive",
213
227
  decisions: (n: number) => count(n, "decision", "decisions"),
214
228
  decisionsTitle: "Decisions waiting for a person",
215
229
  filters: {
@@ -265,6 +279,10 @@ export const en = {
265
279
  : "",
266
280
  started: "Started",
267
281
  done: "Done",
282
+ archived: "Put away",
283
+ archivedAt: "Put away",
284
+ archive: "Put away",
285
+ restore: "Bring back",
268
286
  form: {
269
287
  title: "Title",
270
288
  titlePlaceholder: "What is to be done, in plain words that stand on their own.",
@@ -385,16 +403,39 @@ export const en = {
385
403
  inProgress: (n: number) => `${n} in progress`,
386
404
  noTasks: "No task works towards it yet.",
387
405
  tasks: "Tasks",
406
+ category: "Category",
407
+ noCategory: "No category",
408
+ starts: "Starts",
409
+ noStart: "No start day",
388
410
  target: "Target",
389
411
  noTarget: "No target day",
390
412
  setBy: "Set by",
391
413
  state: "State",
392
414
  nothingMore: "Nothing more was written about it.",
415
+ about: "What reaching it means",
416
+ archiveTasks: "Put the done tasks away",
417
+ archiveAsk: (n: number) =>
418
+ `Put the ${count(n, "task", "tasks")} done or dropped under this goal away as well?`,
419
+ view: "View",
420
+ views: { board: "Board", list: "List", calendar: "Calendar" },
421
+ timeline: {
422
+ title: "The months",
423
+ legend: "Category colours",
424
+ earlier: "A month earlier",
425
+ later: "A month later",
426
+ thisMonth: "This month",
427
+ today: "Today",
428
+ nothing: "No goal is planned in these months.",
429
+ noDays: "No day set",
430
+ },
393
431
  form: {
394
432
  title: "Title",
395
433
  body: "What reaching it means",
396
434
  bodyPlaceholder: "What is true once the goal is reached, in Markdown.",
397
435
  state: "State",
436
+ category: "Category",
437
+ noCategory: "No category",
438
+ startsOn: "Start day",
398
439
  dueOn: "Target day",
399
440
  set: "Set the goal",
400
441
  },
@@ -538,6 +579,8 @@ export const en = {
538
579
  taskUpdated: (fields: string, task: string) => `changed ${fields} of ${task}`,
539
580
  taskCommented: (task: string, excerpt: string | undefined) =>
540
581
  `commented on ${task}${excerpt === undefined || excerpt === "" ? "" : `: ${excerpt}`}`,
582
+ taskArchived: (task: string) => `put ${task} away`,
583
+ taskRestored: (task: string) => `brought ${task} back`,
541
584
  decisionRaised: (question: string) => `asked: ${question}`,
542
585
  decisionAnswered: (question: string, option: string) => `answered "${question}": ${option}`,
543
586
  decisionUpdated: (fields: string, question: string) =>
@@ -558,6 +601,19 @@ export const en = {
558
601
  },
559
602
  },
560
603
 
604
+ archive: {
605
+ title: "Archive",
606
+ lead: "Tasks done or dropped and put away: out of the board, the lists and the calendar, each still open by its number. Brought back from here.",
607
+ loading: "Loading the archive",
608
+ empty: {
609
+ title: "Nothing is put away",
610
+ body: "A task done or dropped is put away from its page, with its goal, or by the console after a while.",
611
+ },
612
+ since: "put away",
613
+ restore: "Bring back",
614
+ more: "Show more",
615
+ },
616
+
561
617
  runs: {
562
618
  for: "for",
563
619
  since: "since",
@@ -584,6 +640,16 @@ export const en = {
584
640
  picture: (name: string) => `The picture ${name}`,
585
641
  label: (name: string) => `A comment by ${name}`,
586
642
  },
643
+ pictures: {
644
+ open: (name: string) => `Open the picture ${name}`,
645
+ label: (name: string) => `The picture ${name}, large`,
646
+ close: "Close",
647
+ zoomIn: "Zoom in",
648
+ zoomOut: "Zoom out",
649
+ fit: "Fit to the screen",
650
+ file: "Open the file itself",
651
+ hint: "Zoom with the wheel, the buttons or two fingers; drag to move; Escape closes.",
652
+ },
587
653
 
588
654
  skills: {
589
655
  loading: "Loading the skills",
@@ -595,13 +661,15 @@ export const en = {
595
661
  servedBy: "Served by",
596
662
  at: "at",
597
663
  connect:
598
- "An agent reads these through its own MCP connection to this address, which a person makes once:",
664
+ "An agent reads these with the console command (console skill <path>), or through its own MCP connection to this address, which a person makes once:",
599
665
  connectLink: "how, on the address's page at SkillCDN",
600
666
  status: {
601
667
  indexing: "SkillCDN is still indexing the repository. Look again in a moment.",
602
668
  failed: "SkillCDN could not index the repository. What it found is on the address's page.",
603
669
  not_found:
604
- "SkillCDN does not serve this address, or serves it only to someone signed in there. The address's page says which.",
670
+ "SkillCDN does not serve this address to the console: there is nothing there, or the pack is private and the project holds no pack token for it (Settings).",
671
+ token_invalid:
672
+ "The pack token the project holds no longer works at SkillCDN: it expired, was removed, or its maker lost the repository. An owner sets a new one on the Settings page.",
605
673
  unavailable: "SkillCDN could not be reached. The skills are as they were; look again later.",
606
674
  },
607
675
  none: {
@@ -734,6 +802,13 @@ export const en = {
734
802
  goodUntil: "Good until",
735
803
  notActive: "Not active yet",
736
804
  lastActive: "Last active",
805
+ command: (version: string) => `console ${version}`,
806
+ commandVerdict: {
807
+ behind: "an update is available",
808
+ ahead: "newer than this console",
809
+ outdated: "must be updated",
810
+ too_new: "too new for this console",
811
+ },
737
812
  disconnect: "Disconnect",
738
813
  form: {
739
814
  name: "Name",
package/src/i18n/ko.ts CHANGED
@@ -163,12 +163,24 @@ export const ko: Messages = {
163
163
  private: "소유자가 지정될 때까지 관리자가 소유합니다.",
164
164
  },
165
165
  },
166
+ packToken: {
167
+ aria: "팩 토큰",
168
+ label: "토큰",
169
+ hint: "사람이 SkillCDN 계정 페이지에서 팩의 저장소에 대해 만든 토큰입니다. 콘솔이 봉인해 보관하고, 프로젝트를 볼 수 있는 사람을 위해 팩을 읽는 데 쓰며, 누구에게도 보여 주지 않습니다.",
170
+ placeholder: "scdn_repo_…",
171
+ set: "토큰 넣기",
172
+ remove: "토큰 빼기",
173
+ },
166
174
  settings: {
167
175
  title: "설정",
168
176
  aria: "프로젝트 설정",
169
177
  edit: "프로젝트 편집",
170
178
  organizationsSkills: (address) => `조직의 스킬: ${address}`,
171
179
  noSkills: "없음: 콘솔에 조직의 주소가 없습니다.",
180
+ packToken: "팩 토큰",
181
+ packTokenNone: "없음: 공개 팩은 필요 없고, 비공개 팩은 읽히지 않습니다.",
182
+ packTokenSet: "설정됨",
183
+ packTokenEdit: "바꾸기",
172
184
  },
173
185
  },
174
186
 
@@ -179,6 +191,8 @@ export const ko: Messages = {
179
191
  body: "첫 작업을 쓰세요. 무엇을, 누구를 위해 할지.",
180
192
  },
181
193
  moveTo: "이동",
194
+ grip: "끌어서 다른 열로",
195
+ archive: "보관함",
182
196
  decisions: (n) => `결정 ${n}건`,
183
197
  decisionsTitle: "사람을 기다리는 결정",
184
198
  filters: {
@@ -234,6 +248,10 @@ export const ko: Messages = {
234
248
  : "",
235
249
  started: "시작",
236
250
  done: "완료",
251
+ archived: "보관됨",
252
+ archivedAt: "보관",
253
+ archive: "보관하기",
254
+ restore: "되살리기",
237
255
  form: {
238
256
  title: "제목",
239
257
  titlePlaceholder: "무엇을 할지, 제목만 보고도 알 수 있게.",
@@ -354,16 +372,38 @@ export const ko: Messages = {
354
372
  inProgress: (n) => `${n}개 진행 중`,
355
373
  noTasks: "아직 이 목표를 향한 작업이 없습니다.",
356
374
  tasks: "작업",
375
+ category: "카테고리",
376
+ noCategory: "카테고리 없음",
377
+ starts: "시작일",
378
+ noStart: "시작일 없음",
357
379
  target: "목표일",
358
380
  noTarget: "목표일 없음",
359
381
  setBy: "세운 사람",
360
382
  state: "상태",
361
383
  nothingMore: "더 적힌 내용이 없습니다.",
384
+ about: "이루면 무엇이 달라지나",
385
+ archiveTasks: "완료 작업 모두 보관",
386
+ archiveAsk: (n) => `이 목표의 완료·폐기 작업 ${n}건도 함께 보관할까요?`,
387
+ view: "보기",
388
+ views: { board: "보드", list: "목록", calendar: "캘린더" },
389
+ timeline: {
390
+ title: "달별 로드맵",
391
+ legend: "카테고리 색",
392
+ earlier: "한 달 앞으로",
393
+ later: "한 달 뒤로",
394
+ thisMonth: "이번 달",
395
+ today: "오늘",
396
+ nothing: "이 기간에 잡힌 목표가 없습니다.",
397
+ noDays: "날짜 없음",
398
+ },
362
399
  form: {
363
400
  title: "제목",
364
401
  body: "달성의 의미",
365
402
  bodyPlaceholder: "목표를 이루면 무엇이 참이 되는지, Markdown으로.",
366
403
  state: "상태",
404
+ category: "카테고리",
405
+ noCategory: "카테고리 없음",
406
+ startsOn: "시작일",
367
407
  dueOn: "목표일",
368
408
  set: "목표 세우기",
369
409
  },
@@ -504,6 +544,8 @@ export const ko: Messages = {
504
544
  taskUpdated: (fields, task) => `${task}의 ${fields}을(를) 바꿨습니다`,
505
545
  taskCommented: (task, excerpt) =>
506
546
  `${task}에 댓글을 남겼습니다${excerpt === undefined || excerpt === "" ? "" : `: ${excerpt}`}`,
547
+ taskArchived: (task) => `${task}을(를) 보관했습니다`,
548
+ taskRestored: (task) => `${task}을(를) 되살렸습니다`,
507
549
  decisionRaised: (question) => `질문했습니다: ${question}`,
508
550
  decisionAnswered: (question, option) => `"${question}"에 답했습니다: ${option}`,
509
551
  decisionUpdated: (fields, question) => `결정 "${question}"의 ${fields}을(를) 썼습니다`,
@@ -523,6 +565,19 @@ export const ko: Messages = {
523
565
  },
524
566
  },
525
567
 
568
+ archive: {
569
+ title: "보관함",
570
+ lead: "완료·폐기된 뒤 보관된 작업입니다. 보드·목록·캘린더에서는 빠지고, 번호로는 여전히 열립니다. 여기서 되살립니다.",
571
+ loading: "보관함을 불러오는 중",
572
+ empty: {
573
+ title: "보관된 작업이 없습니다",
574
+ body: "완료·폐기된 작업은 작업 페이지에서, 목표를 닫을 때 함께, 또는 일정 기간 뒤 콘솔이 보관합니다.",
575
+ },
576
+ since: "보관",
577
+ restore: "되살리기",
578
+ more: "더 보기",
579
+ },
580
+
526
581
  runs: {
527
582
  for: "대신:",
528
583
  since: "시작",
@@ -549,6 +604,16 @@ export const ko: Messages = {
549
604
  picture: (name) => `그림 ${name}`,
550
605
  label: (name) => `${name}의 댓글`,
551
606
  },
607
+ pictures: {
608
+ open: (name) => `그림 ${name} 크게 보기`,
609
+ label: (name) => `그림 ${name}, 크게`,
610
+ close: "닫기",
611
+ zoomIn: "확대",
612
+ zoomOut: "축소",
613
+ fit: "화면에 맞추기",
614
+ file: "파일 자체 열기",
615
+ hint: "휠·버튼·두 손가락으로 확대와 축소, 끌어서 이동, Esc로 닫기.",
616
+ },
552
617
 
553
618
  skills: {
554
619
  loading: "스킬을 불러오는 중",
@@ -559,13 +624,16 @@ export const ko: Messages = {
559
624
  },
560
625
  servedBy: "제공:",
561
626
  at: "주소:",
562
- connect: "에이전트는 이 주소에 자기 MCP 연결로 스킬을 받습니다. 사람이 한 번 연결해 줍니다:",
627
+ connect:
628
+ "에이전트는 콘솔 명령(console skill <경로>)으로, 또는 이 주소에 자기 MCP 연결로 스킬을 받습니다. MCP 연결은 사람이 한 번 해 줍니다:",
563
629
  connectLink: "SkillCDN 페이지의 연결 안내",
564
630
  status: {
565
631
  indexing: "SkillCDN이 아직 저장소를 색인하고 있습니다. 잠시 후 다시 보세요.",
566
632
  failed: "SkillCDN이 저장소를 색인하지 못했습니다. 찾은 내용은 주소의 페이지에 있습니다.",
567
633
  not_found:
568
- "SkillCDN이 이 주소를 제공하지 않거나, 그곳에 로그인한 사람에게만 제공합니다. 주소의 페이지에서 확인하세요.",
634
+ "SkillCDN이 이 주소를 콘솔에 제공하지 않습니다. 그곳에 아무것도 없거나, 비공개 팩인데 프로젝트에 팩 토큰이 없습니다(설정).",
635
+ token_invalid:
636
+ "프로젝트가 가진 팩 토큰이 SkillCDN에서 더 이상 통하지 않습니다. 만료되었거나 지워졌거나, 만든 사람이 저장소 권한을 잃었습니다. 소유자가 설정 페이지에서 새로 넣습니다.",
569
637
  unavailable:
570
638
  "SkillCDN에 연결할 수 없습니다. 스킬은 이전 상태 그대로입니다. 나중에 다시 보세요.",
571
639
  },
@@ -698,6 +766,13 @@ export const ko: Messages = {
698
766
  goodUntil: "만료",
699
767
  notActive: "아직 활동 없음",
700
768
  lastActive: "마지막 활동",
769
+ command: (version) => `console ${version}`,
770
+ commandVerdict: {
771
+ behind: "업데이트할 수 있음",
772
+ ahead: "이 콘솔보다 새 버전",
773
+ outdated: "업데이트 필요",
774
+ too_new: "이 콘솔에는 너무 새 버전",
775
+ },
701
776
  disconnect: "연결 끊기",
702
777
  form: {
703
778
  name: "이름",
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ export {
10
10
  type BoardFiltersProps,
11
11
  type BoardProps,
12
12
  byDue,
13
+ columnStateOf,
13
14
  filterTasks,
14
15
  TaskCard,
15
16
  } from "./components/board.js";
@@ -78,6 +79,7 @@ export {
78
79
  type GoalListProps,
79
80
  GoalView,
80
81
  type GoalViewProps,
82
+ goalMonth,
81
83
  } from "./components/goals.js";
82
84
  export { Markdown, type MarkdownProps } from "./components/markdown.js";
83
85
  export { PAGE_SIZE, ShowMore, usePaged } from "./components/paged.js";
@@ -87,10 +89,32 @@ export {
87
89
  PeopleList,
88
90
  type PeopleListProps,
89
91
  } from "./components/people.js";
92
+ export {
93
+ clampScale,
94
+ FITTED,
95
+ Lightbox,
96
+ type LightboxProps,
97
+ MAX_SCALE,
98
+ MIN_SCALE,
99
+ type Picture,
100
+ PictureOverlay,
101
+ type PictureOverlayProps,
102
+ type PictureState,
103
+ PictureThumb,
104
+ type PictureThumbProps,
105
+ type PictureView,
106
+ panBy,
107
+ usePicture,
108
+ wheelFactor,
109
+ ZOOM_STEP,
110
+ zoomAround,
111
+ } from "./components/pictures.js";
90
112
  export {
91
113
  keyOf,
92
114
  MemberList,
93
115
  type MemberListProps,
116
+ PackTokenForm,
117
+ type PackTokenFormProps,
94
118
  PROJECT_ROLE_LABELS,
95
119
  ProjectForm,
96
120
  type ProjectFormProps,
@@ -100,6 +124,13 @@ export {
100
124
  ProjectRoleBadge,
101
125
  VISIBILITY_LABELS,
102
126
  } from "./components/projects.js";
127
+ export {
128
+ DEFAULT_TIMELINE_MONTHS,
129
+ GoalTimeline,
130
+ type GoalTimelineProps,
131
+ type TimelineBar,
132
+ timelineBars,
133
+ } from "./components/roadmap.js";
103
134
  export {
104
135
  RulesForm,
105
136
  type RulesFormProps,
@@ -216,6 +247,9 @@ export {
216
247
  type DocsView,
217
248
  decisionHref,
218
249
  docHref,
250
+ GOAL_TASK_VIEWS,
251
+ type GoalPageOptions,
252
+ type GoalTaskView,
219
253
  goalHref,
220
254
  matchRoute,
221
255
  movedFrom,
@@ -230,6 +264,7 @@ export {
230
264
  projectHref,
231
265
  projectSettingsHref,
232
266
  type Route,
267
+ roadmapHref,
233
268
  settingsHref,
234
269
  signInFailureOf,
235
270
  type TaskFormKind,
package/src/router.ts CHANGED
@@ -59,22 +59,28 @@ export type Route =
59
59
  readonly month: string | undefined;
60
60
  readonly filter: BoardFilter;
61
61
  }
62
- | { readonly name: "roadmap"; readonly project: string }
62
+ /** The roadmap of a project: the months shown from the one named, or from this one. */
63
+ | { readonly name: "roadmap"; readonly project: string; readonly from: string | undefined }
63
64
  | { readonly name: "new-goal"; readonly project: string }
65
+ /** One goal: its tasks as a board, a list or a calendar (on a month, or this one), or its form. */
64
66
  | {
65
67
  readonly name: "goal";
66
68
  readonly project: string;
67
69
  readonly id: string;
68
70
  readonly form: "edit" | undefined;
71
+ readonly view: GoalTaskView;
72
+ readonly month: string | undefined;
69
73
  }
70
74
  | { readonly name: "feed"; readonly project: string }
75
+ /** The archive of a project's board (ADR-0029): the tasks put away, read a page at a time. */
76
+ | { readonly name: "archive"; readonly project: string }
71
77
  | { readonly name: "skills"; readonly project: string }
72
78
  | { readonly name: "members"; readonly project: string }
73
79
  /** A project's settings, read; or one of its forms open: the project's own, or its rules (ADR-0023). */
74
80
  | {
75
81
  readonly name: "settings";
76
82
  readonly project: string;
77
- readonly form: "edit" | "rules" | undefined;
83
+ readonly form: "edit" | "rules" | "pack-token" | undefined;
78
84
  }
79
85
  /** The workspace's settings: its rules, its categories, its skills; or the form the rules are written on. */
80
86
  | { readonly name: "workspace-settings"; readonly form: "rules" | undefined }
@@ -110,6 +116,7 @@ export type Route =
110
116
  export type ProjectPage =
111
117
  | "docs"
112
118
  | "decisions"
119
+ | "archive"
113
120
  | "calendar"
114
121
  | "roadmap"
115
122
  | "feed"
@@ -150,6 +157,7 @@ const CODE_TEXT = /^[A-Za-z0-9-]{1,16}$/;
150
157
  /** The pages of a project that take nothing after their name. */
151
158
  const PLAIN_PAGES: readonly Exclude<ProjectPage, "docs" | "calendar" | "roadmap" | "settings">[] = [
152
159
  "decisions",
160
+ "archive",
153
161
  "feed",
154
162
  "skills",
155
163
  "members",
@@ -330,9 +338,23 @@ export function matchRoute(pathname: string, search = ""): Route {
330
338
  if (third === "new") {
331
339
  return { name: "new-goal", project };
332
340
  }
333
- return UUID.test(third)
334
- ? { name: "goal", project, id: third, form: params.has("edit") ? "edit" : undefined }
335
- : NOT_FOUND;
341
+ if (!UUID.test(third)) {
342
+ return NOT_FOUND;
343
+ }
344
+ const named = params.get("view");
345
+ const view = named === null ? "board" : isGoalTaskView(named) ? named : undefined;
346
+ const month = params.get("month");
347
+ if (view === undefined || (month !== null && !isMonth(month))) {
348
+ return NOT_FOUND;
349
+ }
350
+ return {
351
+ name: "goal",
352
+ project,
353
+ id: third,
354
+ form: params.has("edit") ? "edit" : undefined,
355
+ view,
356
+ month: month ?? undefined,
357
+ };
336
358
  }
337
359
  if (third !== undefined) {
338
360
  return NOT_FOUND;
@@ -345,13 +367,23 @@ export function matchRoute(pathname: string, search = ""): Route {
345
367
  return { name: "calendar", project, month: month ?? undefined, filter: boardFilterOf(params) };
346
368
  }
347
369
  if (page === "roadmap") {
348
- return { name: "roadmap", project };
370
+ const from = params.get("from");
371
+ if (from !== null && !isMonth(from)) {
372
+ return NOT_FOUND;
373
+ }
374
+ return { name: "roadmap", project, from: from ?? undefined };
349
375
  }
350
376
  if (page === "settings") {
351
377
  return {
352
378
  name: "settings",
353
379
  project,
354
- form: params.has("edit") ? "edit" : params.has("rules") ? "rules" : undefined,
380
+ form: params.has("edit")
381
+ ? "edit"
382
+ : params.has("rules")
383
+ ? "rules"
384
+ : params.has("pack-token")
385
+ ? "pack-token"
386
+ : undefined,
355
387
  };
356
388
  }
357
389
  const found = PLAIN_PAGES.find((candidate) => candidate === page);
@@ -381,7 +413,7 @@ export function projectHref(key: string, page?: ProjectPage): string {
381
413
  }
382
414
 
383
415
  /** Where a project's settings are read, or one of their forms is open: the project's own, or its rules. */
384
- export function projectSettingsHref(key: string, form?: "edit" | "rules"): string {
416
+ export function projectSettingsHref(key: string, form?: "edit" | "rules" | "pack-token"): string {
385
417
  const base = projectHref(key, "settings");
386
418
  return form === undefined ? base : `${base}?${form}`;
387
419
  }
@@ -455,14 +487,41 @@ export function customPageHref(page: string, project?: string): string {
455
487
  }
456
488
 
457
489
  /** Where a goal is set: the form. */
490
+ /** Where a project's roadmap is, from a month on, or from this one when none is named. */
491
+ export function roadmapHref(key: string, from?: string): string {
492
+ const base = projectHref(key, "roadmap");
493
+ return from === undefined ? base : `${base}?from=${from}`;
494
+ }
495
+
458
496
  export function newGoalHref(key: string): string {
459
497
  return `${PATHS.project}/${key}/roadmap/new`;
460
498
  }
461
499
 
462
- /** Where one goal is read, or changed. */
463
- export function goalHref(key: string, id: string, form?: "edit"): string {
500
+ /** The views of a goal's tasks: the board of them, the list by state, or the calendar. */
501
+ export const GOAL_TASK_VIEWS = ["board", "list", "calendar"] as const;
502
+ export type GoalTaskView = (typeof GOAL_TASK_VIEWS)[number];
503
+
504
+ function isGoalTaskView(value: string): value is GoalTaskView {
505
+ return (GOAL_TASK_VIEWS as readonly string[]).includes(value);
506
+ }
507
+
508
+ /** What a goal's address may say besides the goal: its form, the view of its tasks, the calendar's month. */
509
+ export interface GoalPageOptions {
510
+ readonly form?: "edit" | undefined;
511
+ /** The board is the view an address without one shows. */
512
+ readonly view?: GoalTaskView | undefined;
513
+ readonly month?: string | undefined;
514
+ }
515
+
516
+ /** Where one goal is read, in a view of its tasks, or changed. */
517
+ export function goalHref(key: string, id: string, options: GoalPageOptions = {}): string {
464
518
  const base = `${PATHS.project}/${key}/roadmap/${id}`;
465
- return form === undefined ? base : `${base}?${form}`;
519
+ const parts = [
520
+ ...(options.form === undefined ? [] : [options.form]),
521
+ ...(options.view === undefined || options.view === "board" ? [] : [`view=${options.view}`]),
522
+ ...(options.month === undefined ? [] : [`month=${options.month}`]),
523
+ ];
524
+ return parts.length === 0 ? base : `${base}?${parts.join("&")}`;
466
525
  }
467
526
 
468
527
  /**