saterminal 0.3.2 → 0.5.0

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 (83) hide show
  1. package/README.md +168 -4
  2. package/data/question-bank.json.zst +0 -0
  3. package/package.json +16 -10
  4. package/src/cli/commands/_app.tsx +10 -0
  5. package/src/cli/commands/config/index.tsx +21 -0
  6. package/src/cli/commands/config/reset.tsx +12 -0
  7. package/src/cli/commands/config/set.tsx +34 -0
  8. package/src/cli/commands/focus.tsx +12 -0
  9. package/src/cli/commands/history.tsx +21 -0
  10. package/src/cli/commands/index.tsx +7 -0
  11. package/src/cli/commands/review.tsx +7 -0
  12. package/src/cli/commands/show.tsx +36 -0
  13. package/src/cli/commands/stats.tsx +15 -0
  14. package/src/cli/commands/weak.tsx +13 -0
  15. package/src/cli/components/command-action.tsx +23 -0
  16. package/src/cli/components/report.tsx +16 -0
  17. package/src/cli/index.ts +10 -0
  18. package/src/cli/report-options.ts +19 -0
  19. package/src/cli/reports/focus.ts +20 -0
  20. package/src/cli/reports/history.ts +21 -0
  21. package/src/cli/reports/question.ts +24 -0
  22. package/src/cli/reports/stats.ts +66 -0
  23. package/src/cli/reports/terminal-format.ts +28 -0
  24. package/src/cli/reports/weaknesses.ts +36 -0
  25. package/src/database/focus-repository.ts +26 -0
  26. package/src/database/index.ts +28 -0
  27. package/src/database/migrations.ts +100 -0
  28. package/src/database/progress-repository.ts +90 -0
  29. package/src/local-data/paths.ts +17 -0
  30. package/src/local-data/setup.ts +25 -0
  31. package/src/practice/answer-question.ts +25 -0
  32. package/src/practice/question-queue.ts +58 -0
  33. package/src/preferences/index.ts +131 -0
  34. package/src/preferences/preferences.schema.json +45 -0
  35. package/src/progress/activity.ts +55 -0
  36. package/src/progress/attempt.ts +70 -0
  37. package/src/progress/history.ts +34 -0
  38. package/src/progress/review-queue.ts +44 -0
  39. package/src/progress/statistics.ts +35 -0
  40. package/src/progress/weaknesses.ts +44 -0
  41. package/src/questions/focus.ts +77 -0
  42. package/src/questions/local-bank.ts +133 -0
  43. package/src/questions/practice-sat.ts +14 -0
  44. package/src/questions/question.ts +25 -0
  45. package/src/questions/taxonomy.ts +56 -0
  46. package/src/text/duration.ts +4 -0
  47. package/src/{text.ts → text/html.ts} +3 -119
  48. package/src/text/media.ts +16 -0
  49. package/src/text/rich-text.ts +37 -0
  50. package/src/text/wrap.ts +59 -0
  51. package/src/tui/app.tsx +243 -0
  52. package/src/tui/components/chrome.tsx +39 -0
  53. package/src/tui/components/question-content.tsx +91 -0
  54. package/src/tui/hooks/use-terminal-size.ts +16 -0
  55. package/src/tui/screens/detail.tsx +20 -0
  56. package/src/tui/screens/focus.tsx +244 -0
  57. package/src/tui/screens/history.tsx +33 -0
  58. package/src/tui/screens/practice.tsx +159 -0
  59. package/src/tui/screens/result.tsx +169 -0
  60. package/src/tui/screens/setup.tsx +17 -0
  61. package/src/tui/screens/summary.tsx +16 -0
  62. package/src/api.ts +0 -113
  63. package/src/cli.ts +0 -917
  64. package/src/focus.ts +0 -231
  65. package/src/main.ts +0 -13
  66. package/src/progress.ts +0 -48
  67. package/src/state.ts +0 -346
  68. package/src/tui/app.ts +0 -134
  69. package/src/tui/focus-grid.ts +0 -113
  70. package/src/tui/frame.ts +0 -210
  71. package/src/tui/history.ts +0 -6
  72. package/src/tui/input.ts +0 -442
  73. package/src/tui/kit.ts +0 -9
  74. package/src/tui/layout.ts +0 -25
  75. package/src/tui/pane-content.ts +0 -150
  76. package/src/tui/question.ts +0 -46
  77. package/src/tui/render.ts +0 -627
  78. package/src/tui/timer.ts +0 -43
  79. package/src/tui/types.ts +0 -51
  80. package/src/tui/viewport.ts +0 -60
  81. package/src/tui.ts +0 -1
  82. package/src/types/terminal-kit.d.ts +0 -121
  83. package/src/types.ts +0 -69
package/src/api.ts DELETED
@@ -1,113 +0,0 @@
1
- import { defaultFocus, domainsForSkills } from "./focus.ts";
2
- import type { Focus, PracticeQuestion, QuestionDetail, QuestionMeta } from "./types.ts";
3
-
4
- const baseUrl = "https://mysatprep.fun/api";
5
-
6
- type ApiEnvelope<T> = {
7
- success: boolean;
8
- data: T;
9
- error?: string;
10
- message?: string;
11
- };
12
-
13
- export async function fetchQuestionBank(excludeIds: Iterable<string> = [], focus: Focus = defaultFocus): Promise<QuestionMeta[]> {
14
- const params = focusParams(focus);
15
- const excluded = [...excludeIds].filter(Boolean);
16
- if (excluded.length > 0) {
17
- params.set("excludeIds", excluded.join(","));
18
- }
19
-
20
- const response = await fetchJson<ApiEnvelope<QuestionMeta[]>>(`${baseUrl}/get-questions?${params}`);
21
- if (!response.success || !Array.isArray(response.data)) {
22
- throw new Error(response.error || response.message || "Question bank fetch failed.");
23
- }
24
-
25
- return response.data.filter((item) => item.questionId && item.external_id);
26
- }
27
-
28
- export async function fetchQuestion(id: string): Promise<QuestionDetail> {
29
- const response = await fetchJson<ApiEnvelope<QuestionDetail>>(`${baseUrl}/question/${id}`);
30
- if (!response.success || !response.data) {
31
- throw new Error(response.error || response.message || `Question ${id} fetch failed.`);
32
- }
33
-
34
- return response.data;
35
- }
36
-
37
- export async function fetchPracticeQuestion(attemptedIds: Iterable<string>, focus: Focus = defaultFocus): Promise<PracticeQuestion> {
38
- const bank = await fetchQuestionBank(attemptedIds, focus);
39
- if (bank.length === 0) {
40
- throw new Error("No unanswered questions matched the current filters.");
41
- }
42
-
43
- const meta = bank[Math.floor(Math.random() * bank.length)];
44
- const detail = await fetchQuestion(meta.external_id);
45
- return { meta, detail };
46
- }
47
-
48
- export async function findQuestionByShortId(questionId: string): Promise<PracticeQuestion | undefined> {
49
- const bank = await fetchQuestionBank();
50
- const meta = bank.find((item) => item.questionId === questionId);
51
- if (!meta) {
52
- return undefined;
53
- }
54
-
55
- return { meta, detail: await fetchQuestion(meta.external_id) };
56
- }
57
-
58
- function focusParams(focus: Focus): URLSearchParams {
59
- return new URLSearchParams({
60
- assessment: "SAT",
61
- domains: domainsForSkills(focus.skills).join(","),
62
- difficulties: focus.difficulties.join(","),
63
- skills: focus.skills.join(","),
64
- });
65
- }
66
-
67
- async function fetchJson<T>(url: string): Promise<T> {
68
- const response = await fetch(url, {
69
- headers: {
70
- accept: "application/json",
71
- },
72
- });
73
- const body = await response.text();
74
- const contentType = response.headers.get("content-type") ?? "unknown content type";
75
-
76
- if (!response.ok) {
77
- throw new Error(`${response.status} ${response.statusText}: ${responseError(body)}`);
78
- }
79
-
80
- if (!contentType.toLowerCase().includes("application/json")) {
81
- throw new Error(`Expected JSON from ${url}, got ${contentType}: ${snippet(body)}`);
82
- }
83
-
84
- try {
85
- return JSON.parse(body) as T;
86
- } catch (error) {
87
- const message = error instanceof Error ? error.message : String(error);
88
- throw new Error(`Invalid JSON from ${url}: ${message}. Response: ${snippet(body)}`);
89
- }
90
- }
91
-
92
- function responseError(body: string): string {
93
- try {
94
- const parsed = JSON.parse(body) as { error?: unknown; message?: unknown; details?: unknown };
95
- const message = parsed.error || parsed.message || parsed.details;
96
- if (typeof message === "string" && message.trim()) {
97
- return message;
98
- }
99
- } catch {
100
- // Fall back to the raw body below.
101
- }
102
-
103
- return snippet(body);
104
- }
105
-
106
- function snippet(value: string): string {
107
- const normalized = value.replace(/\s+/g, " ").trim();
108
- if (!normalized) {
109
- return "empty response";
110
- }
111
-
112
- return normalized.length > 160 ? `${normalized.slice(0, 157)}...` : normalized;
113
- }