create-arete-workspace 0.2.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 (180) hide show
  1. package/README.md +77 -0
  2. package/bin/arete.js +156 -0
  3. package/bin/create.js +111 -0
  4. package/lib/install-openclaw.js +50 -0
  5. package/lib/scaffold.js +213 -0
  6. package/lib/setup-wizard.js +88 -0
  7. package/lib/updater.js +130 -0
  8. package/package.json +34 -0
  9. package/packages/gatsaeng-os/README.md +36 -0
  10. package/packages/gatsaeng-os/components.json +23 -0
  11. package/packages/gatsaeng-os/eslint.config.mjs +18 -0
  12. package/packages/gatsaeng-os/next.config.ts +7 -0
  13. package/packages/gatsaeng-os/package.json +59 -0
  14. package/packages/gatsaeng-os/postcss.config.mjs +7 -0
  15. package/packages/gatsaeng-os/public/file.svg +1 -0
  16. package/packages/gatsaeng-os/public/globe.svg +1 -0
  17. package/packages/gatsaeng-os/public/next.svg +1 -0
  18. package/packages/gatsaeng-os/public/vercel.svg +1 -0
  19. package/packages/gatsaeng-os/public/window.svg +1 -0
  20. package/packages/gatsaeng-os/python/api_server.py +248 -0
  21. package/packages/gatsaeng-os/python/briefing.py +145 -0
  22. package/packages/gatsaeng-os/python/config.py +55 -0
  23. package/packages/gatsaeng-os/python/goal_context_agent.py +193 -0
  24. package/packages/gatsaeng-os/python/gyeokguk.py +171 -0
  25. package/packages/gatsaeng-os/python/proactive.py +158 -0
  26. package/packages/gatsaeng-os/python/requirements.txt +11 -0
  27. package/packages/gatsaeng-os/python/run.py +28 -0
  28. package/packages/gatsaeng-os/python/scoring.py +44 -0
  29. package/packages/gatsaeng-os/python/streak.py +70 -0
  30. package/packages/gatsaeng-os/python/telegram_bot.py +331 -0
  31. package/packages/gatsaeng-os/python/timing_engine.py +117 -0
  32. package/packages/gatsaeng-os/python/vault_io.py +423 -0
  33. package/packages/gatsaeng-os/src/app/(dashboard)/areas/[id]/page.tsx +215 -0
  34. package/packages/gatsaeng-os/src/app/(dashboard)/areas/page.tsx +161 -0
  35. package/packages/gatsaeng-os/src/app/(dashboard)/books/[id]/page.tsx +215 -0
  36. package/packages/gatsaeng-os/src/app/(dashboard)/books/page.tsx +268 -0
  37. package/packages/gatsaeng-os/src/app/(dashboard)/calendar/page.tsx +379 -0
  38. package/packages/gatsaeng-os/src/app/(dashboard)/error.tsx +30 -0
  39. package/packages/gatsaeng-os/src/app/(dashboard)/focus/page.tsx +293 -0
  40. package/packages/gatsaeng-os/src/app/(dashboard)/goals/[id]/page.tsx +426 -0
  41. package/packages/gatsaeng-os/src/app/(dashboard)/goals/page.tsx +178 -0
  42. package/packages/gatsaeng-os/src/app/(dashboard)/layout.tsx +29 -0
  43. package/packages/gatsaeng-os/src/app/(dashboard)/notes/[id]/page.tsx +147 -0
  44. package/packages/gatsaeng-os/src/app/(dashboard)/notes/page.tsx +254 -0
  45. package/packages/gatsaeng-os/src/app/(dashboard)/page.tsx +26 -0
  46. package/packages/gatsaeng-os/src/app/(dashboard)/projects/[id]/page.tsx +86 -0
  47. package/packages/gatsaeng-os/src/app/(dashboard)/projects/page.tsx +215 -0
  48. package/packages/gatsaeng-os/src/app/(dashboard)/review/page.tsx +475 -0
  49. package/packages/gatsaeng-os/src/app/(dashboard)/routines/page.tsx +436 -0
  50. package/packages/gatsaeng-os/src/app/(dashboard)/tasks/[id]/page.tsx +210 -0
  51. package/packages/gatsaeng-os/src/app/(dashboard)/tasks/page.tsx +307 -0
  52. package/packages/gatsaeng-os/src/app/(dashboard)/voice/page.tsx +212 -0
  53. package/packages/gatsaeng-os/src/app/api/areas/[id]/route.ts +26 -0
  54. package/packages/gatsaeng-os/src/app/api/areas/route.ts +22 -0
  55. package/packages/gatsaeng-os/src/app/api/auth/login/route.ts +52 -0
  56. package/packages/gatsaeng-os/src/app/api/auth/logout/route.ts +8 -0
  57. package/packages/gatsaeng-os/src/app/api/books/[id]/route.ts +27 -0
  58. package/packages/gatsaeng-os/src/app/api/books/route.ts +20 -0
  59. package/packages/gatsaeng-os/src/app/api/calendar/[id]/route.ts +24 -0
  60. package/packages/gatsaeng-os/src/app/api/calendar/import/route.ts +52 -0
  61. package/packages/gatsaeng-os/src/app/api/calendar/route.ts +37 -0
  62. package/packages/gatsaeng-os/src/app/api/daily/route.ts +51 -0
  63. package/packages/gatsaeng-os/src/app/api/goals/[id]/route.ts +34 -0
  64. package/packages/gatsaeng-os/src/app/api/goals/route.ts +30 -0
  65. package/packages/gatsaeng-os/src/app/api/logs/energy/route.ts +40 -0
  66. package/packages/gatsaeng-os/src/app/api/logs/focus/route.ts +22 -0
  67. package/packages/gatsaeng-os/src/app/api/logs/routine/route.ts +54 -0
  68. package/packages/gatsaeng-os/src/app/api/milestones/[id]/route.ts +26 -0
  69. package/packages/gatsaeng-os/src/app/api/milestones/route.ts +47 -0
  70. package/packages/gatsaeng-os/src/app/api/notes/[id]/route.ts +29 -0
  71. package/packages/gatsaeng-os/src/app/api/notes/route.ts +37 -0
  72. package/packages/gatsaeng-os/src/app/api/profile/route.ts +17 -0
  73. package/packages/gatsaeng-os/src/app/api/projects/[id]/route.ts +27 -0
  74. package/packages/gatsaeng-os/src/app/api/projects/route.ts +25 -0
  75. package/packages/gatsaeng-os/src/app/api/reviews/[id]/route.ts +26 -0
  76. package/packages/gatsaeng-os/src/app/api/reviews/route.ts +29 -0
  77. package/packages/gatsaeng-os/src/app/api/routines/[id]/route.ts +26 -0
  78. package/packages/gatsaeng-os/src/app/api/routines/route.ts +28 -0
  79. package/packages/gatsaeng-os/src/app/api/tasks/[id]/route.ts +28 -0
  80. package/packages/gatsaeng-os/src/app/api/tasks/route.ts +66 -0
  81. package/packages/gatsaeng-os/src/app/api/timing/current/route.ts +63 -0
  82. package/packages/gatsaeng-os/src/app/api/voice/chat/route.ts +50 -0
  83. package/packages/gatsaeng-os/src/app/api/voice/transcribe/route.ts +25 -0
  84. package/packages/gatsaeng-os/src/app/api/voice/tts/route.ts +36 -0
  85. package/packages/gatsaeng-os/src/app/error.tsx +30 -0
  86. package/packages/gatsaeng-os/src/app/favicon.ico +0 -0
  87. package/packages/gatsaeng-os/src/app/globals.css +208 -0
  88. package/packages/gatsaeng-os/src/app/layout.tsx +33 -0
  89. package/packages/gatsaeng-os/src/app/login/page.tsx +87 -0
  90. package/packages/gatsaeng-os/src/app/providers.tsx +27 -0
  91. package/packages/gatsaeng-os/src/components/ErrorBoundary.tsx +46 -0
  92. package/packages/gatsaeng-os/src/components/dashboard/DashboardGrid.tsx +86 -0
  93. package/packages/gatsaeng-os/src/components/dashboard/DdayWidget.tsx +88 -0
  94. package/packages/gatsaeng-os/src/components/dashboard/EnergyTracker.tsx +87 -0
  95. package/packages/gatsaeng-os/src/components/dashboard/FocusTimer.tsx +139 -0
  96. package/packages/gatsaeng-os/src/components/dashboard/GatsaengScore.tsx +30 -0
  97. package/packages/gatsaeng-os/src/components/dashboard/GoalRings.tsx +107 -0
  98. package/packages/gatsaeng-os/src/components/dashboard/ProactiveBar.tsx +98 -0
  99. package/packages/gatsaeng-os/src/components/dashboard/RoutineChecklist.tsx +81 -0
  100. package/packages/gatsaeng-os/src/components/dashboard/TimingWidget.tsx +86 -0
  101. package/packages/gatsaeng-os/src/components/dashboard/WidgetCustomizer.tsx +95 -0
  102. package/packages/gatsaeng-os/src/components/dashboard/WidgetWrapper.tsx +33 -0
  103. package/packages/gatsaeng-os/src/components/dashboard/ZeigarnikPanel.tsx +43 -0
  104. package/packages/gatsaeng-os/src/components/editor/EditorToolbar.tsx +186 -0
  105. package/packages/gatsaeng-os/src/components/editor/TiptapEditor.tsx +114 -0
  106. package/packages/gatsaeng-os/src/components/layout/Header.tsx +47 -0
  107. package/packages/gatsaeng-os/src/components/layout/MobileBottomNav.tsx +122 -0
  108. package/packages/gatsaeng-os/src/components/layout/MobileSidebar.tsx +29 -0
  109. package/packages/gatsaeng-os/src/components/layout/Sidebar.tsx +142 -0
  110. package/packages/gatsaeng-os/src/components/onboarding/OnboardingFlow.tsx +229 -0
  111. package/packages/gatsaeng-os/src/components/onboarding/OnboardingGate.tsx +78 -0
  112. package/packages/gatsaeng-os/src/components/projects/CalendarView.tsx +152 -0
  113. package/packages/gatsaeng-os/src/components/projects/KanbanView.tsx +180 -0
  114. package/packages/gatsaeng-os/src/components/projects/ListView.tsx +82 -0
  115. package/packages/gatsaeng-os/src/components/projects/TableView.tsx +206 -0
  116. package/packages/gatsaeng-os/src/components/projects/TaskCard.tsx +154 -0
  117. package/packages/gatsaeng-os/src/components/projects/TaskForm.tsx +128 -0
  118. package/packages/gatsaeng-os/src/components/projects/ViewSwitcher.tsx +40 -0
  119. package/packages/gatsaeng-os/src/components/search/GlobalSearch.tsx +179 -0
  120. package/packages/gatsaeng-os/src/components/shared/InlineEdit.tsx +77 -0
  121. package/packages/gatsaeng-os/src/components/shared/PinButton.tsx +42 -0
  122. package/packages/gatsaeng-os/src/components/tasks/DDayBadge.tsx +34 -0
  123. package/packages/gatsaeng-os/src/components/ui/badge.tsx +48 -0
  124. package/packages/gatsaeng-os/src/components/ui/button.tsx +64 -0
  125. package/packages/gatsaeng-os/src/components/ui/card.tsx +92 -0
  126. package/packages/gatsaeng-os/src/components/ui/checkbox.tsx +32 -0
  127. package/packages/gatsaeng-os/src/components/ui/command.tsx +184 -0
  128. package/packages/gatsaeng-os/src/components/ui/dialog.tsx +158 -0
  129. package/packages/gatsaeng-os/src/components/ui/input.tsx +21 -0
  130. package/packages/gatsaeng-os/src/components/ui/label.tsx +24 -0
  131. package/packages/gatsaeng-os/src/components/ui/popover.tsx +89 -0
  132. package/packages/gatsaeng-os/src/components/ui/progress.tsx +31 -0
  133. package/packages/gatsaeng-os/src/components/ui/select.tsx +190 -0
  134. package/packages/gatsaeng-os/src/components/ui/sheet.tsx +143 -0
  135. package/packages/gatsaeng-os/src/components/ui/tabs.tsx +91 -0
  136. package/packages/gatsaeng-os/src/components/ui/toggle-group.tsx +83 -0
  137. package/packages/gatsaeng-os/src/components/ui/toggle.tsx +47 -0
  138. package/packages/gatsaeng-os/src/components/ui/tooltip.tsx +57 -0
  139. package/packages/gatsaeng-os/src/hooks/useAreas.ts +53 -0
  140. package/packages/gatsaeng-os/src/hooks/useBooks.ts +62 -0
  141. package/packages/gatsaeng-os/src/hooks/useCalendar.ts +59 -0
  142. package/packages/gatsaeng-os/src/hooks/useDaily.ts +15 -0
  143. package/packages/gatsaeng-os/src/hooks/useGlobalTasks.ts +45 -0
  144. package/packages/gatsaeng-os/src/hooks/useGoals.ts +53 -0
  145. package/packages/gatsaeng-os/src/hooks/useMilestones.ts +75 -0
  146. package/packages/gatsaeng-os/src/hooks/useNotes.ts +65 -0
  147. package/packages/gatsaeng-os/src/hooks/useProjects.ts +102 -0
  148. package/packages/gatsaeng-os/src/hooks/useRoutines.ts +76 -0
  149. package/packages/gatsaeng-os/src/hooks/useTiming.ts +27 -0
  150. package/packages/gatsaeng-os/src/lib/apiFetch.ts +14 -0
  151. package/packages/gatsaeng-os/src/lib/auth.ts +32 -0
  152. package/packages/gatsaeng-os/src/lib/date.ts +7 -0
  153. package/packages/gatsaeng-os/src/lib/editor/markdown.ts +35 -0
  154. package/packages/gatsaeng-os/src/lib/llm-governor.ts +167 -0
  155. package/packages/gatsaeng-os/src/lib/neuroscience/energyCycle.ts +35 -0
  156. package/packages/gatsaeng-os/src/lib/neuroscience/habitStack.ts +22 -0
  157. package/packages/gatsaeng-os/src/lib/neuroscience/scoring.ts +32 -0
  158. package/packages/gatsaeng-os/src/lib/routes.ts +15 -0
  159. package/packages/gatsaeng-os/src/lib/utils.ts +6 -0
  160. package/packages/gatsaeng-os/src/lib/vault/config.ts +29 -0
  161. package/packages/gatsaeng-os/src/lib/vault/frontmatter.ts +84 -0
  162. package/packages/gatsaeng-os/src/lib/vault/index.ts +180 -0
  163. package/packages/gatsaeng-os/src/lib/vault/schemas.ts +274 -0
  164. package/packages/gatsaeng-os/src/middleware.ts +34 -0
  165. package/packages/gatsaeng-os/src/stores/dashboardStore.ts +26 -0
  166. package/packages/gatsaeng-os/src/stores/favoritesStore.ts +47 -0
  167. package/packages/gatsaeng-os/src/stores/timerStore.ts +65 -0
  168. package/packages/gatsaeng-os/src/types/index.ts +320 -0
  169. package/packages/gatsaeng-os/tsconfig.json +34 -0
  170. package/templates/scripts/forge_qa.sh.tmpl +237 -0
  171. package/templates/scripts/forge_ship.sh.tmpl +183 -0
  172. package/templates/scripts/session_indexer.py.tmpl +420 -0
  173. package/templates/scripts/tracer.py.tmpl +266 -0
  174. package/templates/workspace/AGENTS.md.tmpl +190 -0
  175. package/templates/workspace/BOOTSTRAP.md.tmpl +27 -0
  176. package/templates/workspace/HEARTBEAT.md.tmpl +23 -0
  177. package/templates/workspace/MEMORY.md.tmpl +35 -0
  178. package/templates/workspace/SOUL.md.tmpl +258 -0
  179. package/templates/workspace/TOOLS.md.tmpl +28 -0
  180. package/templates/workspace/USER.md.tmpl +43 -0
@@ -0,0 +1,180 @@
1
+ import fs from 'fs/promises'
2
+ import path from 'path'
3
+ import { nanoid } from 'nanoid'
4
+ import { FOLDERS, PROFILE_PATH, type FolderKey } from './config'
5
+ import { parseMarkdown, safeParseMarkdown, stringifyMarkdown } from './frontmatter'
6
+ import type { ZodSchema } from 'zod'
7
+
8
+ async function ensureDir(dirPath: string): Promise<void> {
9
+ await fs.mkdir(dirPath, { recursive: true })
10
+ }
11
+
12
+ async function atomicWrite(filePath: string, content: string): Promise<void> {
13
+ const tmpPath = `${filePath}.tmp`
14
+ await fs.writeFile(tmpPath, content, 'utf-8')
15
+ await fs.rename(tmpPath, filePath)
16
+ }
17
+
18
+ export async function listEntities<T>(
19
+ folder: FolderKey,
20
+ schema?: ZodSchema<T>
21
+ ): Promise<Array<{ data: T; content: string; filename: string }>> {
22
+ const dirPath = FOLDERS[folder]
23
+ await ensureDir(dirPath)
24
+
25
+ const files = await fs.readdir(dirPath)
26
+ const mdFiles = files.filter(f => f.endsWith('.md'))
27
+
28
+ const all = await Promise.all(
29
+ mdFiles.map(async (filename) => {
30
+ const raw = await fs.readFile(path.join(dirPath, filename), 'utf-8')
31
+ if (schema) {
32
+ const parsed = safeParseMarkdown<T>(raw, schema)
33
+ if (!parsed) return null // skip files that don't match schema
34
+ return { ...parsed, filename }
35
+ }
36
+ const parsed = parseMarkdown<T>(raw)
37
+ return { ...parsed, filename }
38
+ })
39
+ )
40
+
41
+ return all.filter((r): r is NonNullable<typeof r> => r !== null)
42
+ }
43
+
44
+ function matchesId(filename: string, id: string): boolean {
45
+ // Match patterns: prefix-{id}.md or just {id}.md
46
+ const name = filename.replace('.md', '')
47
+ return name === id || name.endsWith(`-${id}`)
48
+ }
49
+
50
+ export async function getEntity<T>(
51
+ folder: FolderKey,
52
+ id: string,
53
+ schema?: ZodSchema<T>
54
+ ): Promise<{ data: T; content: string } | null> {
55
+ const dirPath = FOLDERS[folder]
56
+ const files = await fs.readdir(dirPath)
57
+ const target = files.find(f => f.endsWith('.md') && matchesId(f, id))
58
+
59
+ if (!target) return null
60
+
61
+ const raw = await fs.readFile(path.join(dirPath, target), 'utf-8')
62
+ return parseMarkdown<T>(raw, schema)
63
+ }
64
+
65
+ export async function getEntityByDate<T>(
66
+ folder: FolderKey,
67
+ date: string,
68
+ schema?: ZodSchema<T>
69
+ ): Promise<{ data: T; content: string } | null> {
70
+ const dirPath = FOLDERS[folder]
71
+ const filePath = path.join(dirPath, `${date}.md`)
72
+
73
+ try {
74
+ const raw = await fs.readFile(filePath, 'utf-8')
75
+ return parseMarkdown<T>(raw, schema)
76
+ } catch {
77
+ return null
78
+ }
79
+ }
80
+
81
+ export async function createEntity<T extends object>(
82
+ folder: FolderKey,
83
+ data: Omit<T, 'id'> & { id?: string },
84
+ body?: string
85
+ ): Promise<T> {
86
+ const dirPath = FOLDERS[folder]
87
+ await ensureDir(dirPath)
88
+
89
+ const id = data.id || nanoid(10)
90
+ const entityData = { ...data, id } as T
91
+ const prefix = folder.replace(/s$/, '').replace(/Logs$/, '').replace(/Sessions$/, 'session')
92
+ const filename = `${prefix}-${id}.md`
93
+ const filePath = path.join(dirPath, filename)
94
+
95
+ const content = stringifyMarkdown(entityData as object, body)
96
+ await atomicWrite(filePath, content)
97
+
98
+ return entityData
99
+ }
100
+
101
+ export async function createDateEntity<T extends object>(
102
+ folder: FolderKey,
103
+ date: string,
104
+ data: T
105
+ ): Promise<T> {
106
+ const dirPath = FOLDERS[folder]
107
+ await ensureDir(dirPath)
108
+
109
+ const filePath = path.join(dirPath, `${date}.md`)
110
+ const content = stringifyMarkdown(data as object)
111
+ await atomicWrite(filePath, content)
112
+
113
+ return data
114
+ }
115
+
116
+ export async function updateEntity<T extends object>(
117
+ folder: FolderKey,
118
+ id: string,
119
+ updates: Partial<T>,
120
+ body?: string
121
+ ): Promise<T | null> {
122
+ const dirPath = FOLDERS[folder]
123
+ const files = await fs.readdir(dirPath)
124
+ const target = files.find(f => f.endsWith('.md') && matchesId(f, id))
125
+
126
+ if (!target) return null
127
+
128
+ const filePath = path.join(dirPath, target)
129
+ const raw = await fs.readFile(filePath, 'utf-8')
130
+ const parsed = parseMarkdown<T>(raw)
131
+
132
+ const updated = { ...parsed.data, ...updates } as T
133
+ const newBody = body !== undefined ? body : parsed.content
134
+ const content = stringifyMarkdown(updated as object, newBody)
135
+ await atomicWrite(filePath, content)
136
+
137
+ return updated
138
+ }
139
+
140
+ export async function updateDateEntity<T extends object>(
141
+ folder: FolderKey,
142
+ date: string,
143
+ data: T
144
+ ): Promise<T> {
145
+ const dirPath = FOLDERS[folder]
146
+ await ensureDir(dirPath)
147
+
148
+ const filePath = path.join(dirPath, `${date}.md`)
149
+ const content = stringifyMarkdown(data as object)
150
+ await atomicWrite(filePath, content)
151
+
152
+ return data
153
+ }
154
+
155
+ export async function deleteEntity(folder: FolderKey, id: string): Promise<boolean> {
156
+ const dirPath = FOLDERS[folder]
157
+ const files = await fs.readdir(dirPath)
158
+ const target = files.find(f => f.endsWith('.md') && matchesId(f, id))
159
+
160
+ if (!target) return false
161
+
162
+ await fs.unlink(path.join(dirPath, target))
163
+ return true
164
+ }
165
+
166
+ export async function getProfile<T>(schema?: ZodSchema<T>): Promise<{ data: T; content: string }> {
167
+ const raw = await fs.readFile(PROFILE_PATH, 'utf-8')
168
+ return parseMarkdown<T>(raw, schema)
169
+ }
170
+
171
+ export async function updateProfile<T extends object>(
172
+ updates: Partial<T>
173
+ ): Promise<T> {
174
+ const raw = await fs.readFile(PROFILE_PATH, 'utf-8')
175
+ const parsed = parseMarkdown<T>(raw)
176
+ const updated = { ...parsed.data, ...updates } as T
177
+ const content = stringifyMarkdown(updated as object, parsed.content)
178
+ await fs.writeFile(PROFILE_PATH, content, 'utf-8')
179
+ return updated
180
+ }
@@ -0,0 +1,274 @@
1
+ import { z } from 'zod'
2
+
3
+ // ─── Area ───
4
+ export const areaSchema = z.object({
5
+ id: z.string(),
6
+ title: z.string(),
7
+ icon: z.string().default('📌'),
8
+ status: z.enum(['active', 'completed', 'archived']).default('active'),
9
+ linked_goals: z.array(z.string()).default([]),
10
+ created: z.string(),
11
+ })
12
+
13
+ // ─── Key Metric ───
14
+ const keyMetricSchema = z.object({
15
+ name: z.string(),
16
+ current: z.number(),
17
+ target: z.number(),
18
+ unit: z.string(),
19
+ })
20
+
21
+ // ─── Goal ───
22
+ export const goalSchema = z.object({
23
+ id: z.string(),
24
+ area_id: z.string().optional(),
25
+ title: z.string(),
26
+ description: z.string().optional(),
27
+ type: z.string().default('general'),
28
+ status: z.enum(['active', 'completed', 'archived']).default('active'),
29
+ color: z.string().default('#f5a623'),
30
+ created: z.string().optional(), // v2 files may only have created_at
31
+ ai_next_review: z.string().optional(),
32
+ ai_diagnosis: z.string().optional(),
33
+ ai_direction: z.string().optional(),
34
+ linked_milestones: z.array(z.string()).default([]),
35
+ linked_routines: z.array(z.string()).default([]),
36
+ linked_projects: z.array(z.string()).default([]),
37
+ key_metrics: z.array(keyMetricSchema).default([]),
38
+ why_statement: z.string().optional(),
39
+ identity_statement: z.string().optional(),
40
+ when_where_how: z.string().optional(),
41
+ // v2 compat
42
+ target_value: z.number().optional(),
43
+ current_value: z.number().optional(),
44
+ unit: z.string().optional(),
45
+ due_date: z.string().optional(),
46
+ core_value: z.string().optional(),
47
+ created_at: z.string().optional(),
48
+ updated_at: z.string().optional(),
49
+ })
50
+
51
+ // ─── Milestone ───
52
+ export const milestoneSchema = z.object({
53
+ id: z.string(),
54
+ goal_id: z.string(),
55
+ title: z.string(),
56
+ target_value: z.number(),
57
+ current_value: z.number().default(0),
58
+ unit: z.string(),
59
+ due_date: z.string(),
60
+ status: z.enum(['active', 'completed', 'archived']).default('active'),
61
+ created_by: z.enum(['user', 'ai']).default('user'),
62
+ created: z.string(),
63
+ })
64
+
65
+ // ─── Project ───
66
+ export const projectSchema = z.object({
67
+ id: z.string(),
68
+ goal_id: z.string().optional(),
69
+ milestone_id: z.string().optional(),
70
+ title: z.string(),
71
+ description: z.string().optional(),
72
+ status: z.enum(['active', 'completed', 'archived']).default('active'),
73
+ color: z.string().default('#7c5cbf'),
74
+ due_date: z.string().optional(),
75
+ default_view: z.enum(['kanban', 'table', 'calendar', 'list']).default('kanban'),
76
+ created_by: z.enum(['user', 'ai']).optional(),
77
+ created_at: z.string(),
78
+ updated_at: z.string(),
79
+ })
80
+
81
+ // ─── Task (project-level) ───
82
+ export const taskSchema = z.object({
83
+ id: z.string(),
84
+ project_id: z.string(),
85
+ parent_task_id: z.string().optional(),
86
+ title: z.string(),
87
+ description: z.string().optional(),
88
+ status: z.enum(['backlog', 'todo', 'doing', 'done']).default('backlog'),
89
+ priority: z.enum(['low', 'medium', 'high', 'urgent']).default('medium'),
90
+ energy_required: z.enum(['low', 'medium', 'high']).optional(),
91
+ tag: z.string().optional(),
92
+ due_date: z.string().optional(),
93
+ position: z.number().default(0),
94
+ goal_ids: z.array(z.string()).optional(),
95
+ area_id: z.string().optional(),
96
+ created_at: z.string(),
97
+ updated_at: z.string(),
98
+ })
99
+
100
+ // ─── Book ───
101
+ export const bookSchema = z.object({
102
+ id: z.string(),
103
+ title: z.string(),
104
+ author: z.string(),
105
+ status: z.enum(['reading', 'completed', 'want_to_read', 'dropped']).default('want_to_read'),
106
+ rating: z.number().min(1).max(5).optional(),
107
+ total_pages: z.number().optional(),
108
+ current_page: z.number().optional(),
109
+ goal_id: z.string().optional(),
110
+ started_at: z.string().optional(),
111
+ finished_at: z.string().optional(),
112
+ created_at: z.string().optional(),
113
+ })
114
+
115
+ // ─── Calendar Event ───
116
+ export const calendarEventSchema = z.object({
117
+ id: z.string(),
118
+ title: z.string(),
119
+ date: z.string(),
120
+ time_start: z.string().optional(),
121
+ time_end: z.string().optional(),
122
+ all_day: z.boolean().optional(),
123
+ category: z.enum(['work', 'personal', 'health', 'study', 'social', 'other']).optional(),
124
+ location: z.string().optional(),
125
+ description: z.string().optional(),
126
+ goal_id: z.string().optional(),
127
+ created_by: z.enum(['user', 'ai', 'telegram']).optional(),
128
+ created_at: z.string().optional(),
129
+ })
130
+
131
+ // ─── Note ───
132
+ export const noteSchema = z.object({
133
+ id: z.string(),
134
+ title: z.string(),
135
+ type: z.enum(['note', 'file', 'reference', 'link']).default('note'),
136
+ priority: z.union([z.literal(1), z.literal(2), z.literal(3)]).default(2),
137
+ area_id: z.string().optional(),
138
+ project_id: z.string().optional(),
139
+ url: z.string().optional(),
140
+ created_at: z.string(),
141
+ updated_at: z.string(),
142
+ })
143
+
144
+ // ─── Daily Manifest ───
145
+ export const dailyManifestSchema = z.object({
146
+ date: z.string(),
147
+ gatsaeng_score: z.number().default(0),
148
+ routines_done: z.number().default(0),
149
+ routines_total: z.number().default(0),
150
+ focus_minutes: z.number().default(0),
151
+ timing_note: z.string().optional(),
152
+ growth_actions: z.object({
153
+ core: z.array(z.string()).default([]),
154
+ filler: z.array(z.string()).default([]),
155
+ missed: z.array(z.string()).default([]),
156
+ }).optional(),
157
+ })
158
+
159
+ // ─── Routine ───
160
+ export const routineSchema = z.object({
161
+ id: z.string(),
162
+ title: z.string(),
163
+ area_id: z.string().optional(),
164
+ goal_id: z.string().optional(),
165
+ scheduled_time: z.string().optional(),
166
+ scheduled_days: z.array(z.number()).default([1, 2, 3, 4, 5, 6, 7]),
167
+ trigger_cue: z.string().optional(),
168
+ trigger_type: z.enum(['time', 'event', 'location']).default('time'),
169
+ after_routine_id: z.string().nullable().optional(),
170
+ reward_note: z.string().optional(),
171
+ energy_required: z.enum(['low', 'medium', 'high']).default('medium'),
172
+ streak: z.number().default(0),
173
+ longest_streak: z.number().default(0),
174
+ is_active: z.boolean().default(true),
175
+ position: z.number().default(0),
176
+ created_by: z.enum(['user', 'ai']).optional(),
177
+ created: z.string().optional(), // v2 files may only have created_at
178
+ // v2 compat
179
+ days_of_week: z.array(z.number()).optional(),
180
+ created_at: z.string().optional(),
181
+ })
182
+
183
+ // ─── Routine Log ───
184
+ export const routineLogSchema = z.object({
185
+ date: z.string(),
186
+ completions: z.array(z.object({
187
+ routine_id: z.string(),
188
+ completed_at: z.string(),
189
+ mood: z.number().min(1).max(5).optional(),
190
+ })),
191
+ })
192
+
193
+ // ─── Energy Log ───
194
+ export const energyLogSchema = z.object({
195
+ date: z.string(),
196
+ entries: z.array(z.object({
197
+ hour: z.number().min(0).max(23),
198
+ level: z.number().min(1).max(5),
199
+ note: z.string().optional(),
200
+ })),
201
+ })
202
+
203
+ // ─── Focus Session ───
204
+ export const focusSessionSchema = z.object({
205
+ id: z.string(),
206
+ date: z.string().optional(),
207
+ task_id: z.string().optional(),
208
+ duration_minutes: z.number(),
209
+ session_type: z.enum(['pomodoro_25', 'focus_90', 'deep_work']).default('pomodoro_25'),
210
+ energy_level: z.number().min(1).max(5).optional(),
211
+ completed: z.boolean().default(false),
212
+ started_at: z.string(),
213
+ })
214
+
215
+ // ─── Review ───
216
+ export const reviewSchema = z.object({
217
+ id: z.string().optional(),
218
+ type: z.enum(['daily', 'weekly']).default('weekly'),
219
+ date: z.string().optional(),
220
+ week: z.string().optional(),
221
+ week_start: z.string().optional(),
222
+ week_end: z.string().optional(),
223
+ routines_completion_rate: z.number().optional(),
224
+ tasks_completed: z.number().optional(),
225
+ gatsaeng_score_total: z.number().optional(),
226
+ ai_reanalysis_triggered: z.boolean().optional(),
227
+ goal_ids_to_review: z.array(z.string()).optional(),
228
+ accomplished: z.string().optional(),
229
+ struggled: z.string().optional(),
230
+ learnings: z.string().optional(),
231
+ next_week_focus: z.string().optional(),
232
+ energy_pattern: z.string().optional(),
233
+ habit_insight: z.string().optional(),
234
+ mood: z.number().min(1).max(5).optional(),
235
+ score: z.number().optional(),
236
+ created_at: z.string().optional(),
237
+ })
238
+
239
+ // ─── Profile ───
240
+ export const profileSchema = z.object({
241
+ display_name: z.string(),
242
+ level: z.number().default(1),
243
+ total_score: z.number().default(0),
244
+ longest_streak: z.number().default(0),
245
+ current_streak: z.number().default(0),
246
+ peak_hours: z.array(z.number()),
247
+ dashboard_widgets: z.array(z.enum(['routine', 'goals', 'heatmap', 'kanban', 'timer', 'zeigarnik', 'energy', 'dday', 'proactive'])),
248
+ created_at: z.string(),
249
+ updated_at: z.string(),
250
+ })
251
+
252
+ // ─── Timing Context (사주) ───
253
+ export const timingContextSchema = z.object({
254
+ year: z.number(),
255
+ month_name: z.string(),
256
+ month_hanja: z.string(),
257
+ period_start: z.string(),
258
+ period_end: z.string(),
259
+ rating: z.number().min(1).max(5),
260
+ theme: z.string(),
261
+ key_insight: z.string(),
262
+ action_guide: z.string(),
263
+ })
264
+
265
+ // ─── Growth Scorecard (사주 L1~L4) ───
266
+ export const growthScorecardSchema = z.object({
267
+ date: z.string(),
268
+ l4_timing: z.enum(['aligned', 'misaligned', 'counter']),
269
+ l3_chain: z.enum(['active', 'weak', 'depleted']),
270
+ l2_expansion: z.enum(['forward', 'stagnant', 'retreat']),
271
+ l1_correction: z.enum(['corrected', 'in_progress', 'stuck']),
272
+ health_defense: z.enum(['maintained', 'warning', 'collapsed']),
273
+ note: z.string().optional(),
274
+ })
@@ -0,0 +1,34 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { jwtVerify } from 'jose'
3
+
4
+ export const config = {
5
+ matcher: ['/((?!_next/static|_next/image|favicon.ico|login).*)'],
6
+ }
7
+
8
+ const SECRET = new TextEncoder().encode(
9
+ process.env.SESSION_SECRET || 'build-placeholder'
10
+ )
11
+
12
+ export async function middleware(req: NextRequest) {
13
+ const { pathname } = req.nextUrl
14
+
15
+ // Public routes
16
+ if (pathname.startsWith('/login') || pathname === '/api/auth/login') {
17
+ return NextResponse.next()
18
+ }
19
+
20
+ const token = req.cookies.get('gs_auth')?.value
21
+
22
+ if (!token) {
23
+ return NextResponse.redirect(new URL('/login', req.url))
24
+ }
25
+
26
+ try {
27
+ await jwtVerify(token, SECRET)
28
+ return NextResponse.next()
29
+ } catch {
30
+ const res = NextResponse.redirect(new URL('/login', req.url))
31
+ res.cookies.delete('gs_auth')
32
+ return res
33
+ }
34
+ }
@@ -0,0 +1,26 @@
1
+ 'use client'
2
+
3
+ import { create } from 'zustand'
4
+ import { persist } from 'zustand/middleware'
5
+ import type { WidgetId } from '@/types'
6
+
7
+ interface DashboardStore {
8
+ activeWidgets: WidgetId[]
9
+ toggleWidget: (id: WidgetId) => void
10
+ reorderWidgets: (newOrder: WidgetId[]) => void
11
+ }
12
+
13
+ export const useDashboardStore = create<DashboardStore>()(
14
+ persist(
15
+ (set) => ({
16
+ activeWidgets: ['proactive', 'routine', 'goals', 'dday', 'zeigarnik', 'timer', 'energy'],
17
+ toggleWidget: (id) => set((s) => ({
18
+ activeWidgets: s.activeWidgets.includes(id)
19
+ ? s.activeWidgets.filter(w => w !== id)
20
+ : [...s.activeWidgets, id],
21
+ })),
22
+ reorderWidgets: (newOrder) => set({ activeWidgets: newOrder }),
23
+ }),
24
+ { name: 'gatsaeng-dashboard-layout' }
25
+ )
26
+ )
@@ -0,0 +1,47 @@
1
+ 'use client'
2
+
3
+ import { create } from 'zustand'
4
+ import { persist } from 'zustand/middleware'
5
+
6
+ export type FavoriteType = 'note' | 'task' | 'goal' | 'project' | 'book'
7
+
8
+ export interface FavoriteItem {
9
+ type: FavoriteType
10
+ id: string
11
+ title: string
12
+ }
13
+
14
+ interface FavoritesStore {
15
+ favorites: FavoriteItem[]
16
+ add: (item: FavoriteItem) => void
17
+ remove: (type: FavoriteType, id: string) => void
18
+ toggle: (item: FavoriteItem) => void
19
+ isFavorite: (type: FavoriteType, id: string) => boolean
20
+ }
21
+
22
+ export const useFavoritesStore = create<FavoritesStore>()(
23
+ persist(
24
+ (set, get) => ({
25
+ favorites: [],
26
+ add: (item) =>
27
+ set((s) => {
28
+ if (s.favorites.some((f) => f.type === item.type && f.id === item.id)) return s
29
+ return { favorites: [...s.favorites, item] }
30
+ }),
31
+ remove: (type, id) =>
32
+ set((s) => ({
33
+ favorites: s.favorites.filter((f) => !(f.type === type && f.id === id)),
34
+ })),
35
+ toggle: (item) => {
36
+ const exists = get().isFavorite(item.type, item.id)
37
+ if (exists) {
38
+ get().remove(item.type, item.id)
39
+ } else {
40
+ get().add(item)
41
+ }
42
+ },
43
+ isFavorite: (type, id) => get().favorites.some((f) => f.type === type && f.id === id),
44
+ }),
45
+ { name: 'gatsaeng-favorites' }
46
+ )
47
+ )
@@ -0,0 +1,65 @@
1
+ 'use client'
2
+
3
+ import { create } from 'zustand'
4
+ import type { SessionType } from '@/types'
5
+
6
+ const DURATIONS: Record<SessionType, number> = {
7
+ pomodoro_25: 25 * 60,
8
+ focus_90: 90 * 60,
9
+ deep_work: 50 * 60,
10
+ }
11
+
12
+ interface TimerState {
13
+ seconds: number
14
+ isRunning: boolean
15
+ sessionType: SessionType
16
+ completedSessions: number
17
+ energyLevelAtStart: number | null
18
+ customMinutes: number
19
+ startedAt: string | null
20
+ start: () => void
21
+ pause: () => void
22
+ reset: () => void
23
+ tick: () => void
24
+ setSessionType: (type: SessionType) => void
25
+ setCustomMinutes: (minutes: number) => void
26
+ setEnergyLevel: (level: number) => void
27
+ }
28
+
29
+ export const useTimerStore = create<TimerState>((set, get) => ({
30
+ seconds: DURATIONS.pomodoro_25,
31
+ isRunning: false,
32
+ sessionType: 'pomodoro_25',
33
+ completedSessions: 0,
34
+ energyLevelAtStart: null,
35
+ customMinutes: 50,
36
+ startedAt: null,
37
+ start: () => {
38
+ const { startedAt } = get()
39
+ set({ isRunning: true, startedAt: startedAt ?? new Date().toISOString() })
40
+ },
41
+ pause: () => set({ isRunning: false }),
42
+ reset: () => {
43
+ const { sessionType, customMinutes } = get()
44
+ const secs = sessionType === 'deep_work' ? customMinutes * 60 : DURATIONS[sessionType]
45
+ set({ seconds: secs, isRunning: false, startedAt: null })
46
+ },
47
+ tick: () => {
48
+ const { seconds, completedSessions } = get()
49
+ if (seconds <= 1) {
50
+ set({ isRunning: false, seconds: 0, completedSessions: completedSessions + 1 })
51
+ } else {
52
+ set({ seconds: seconds - 1 })
53
+ }
54
+ },
55
+ setSessionType: (type) => {
56
+ const { customMinutes } = get()
57
+ const secs = type === 'deep_work' ? customMinutes * 60 : DURATIONS[type]
58
+ set({ sessionType: type, seconds: secs, isRunning: false })
59
+ },
60
+ setCustomMinutes: (minutes) => {
61
+ const clamped = Math.max(1, Math.min(180, minutes))
62
+ set({ customMinutes: clamped, seconds: clamped * 60, isRunning: false })
63
+ },
64
+ setEnergyLevel: (level) => set({ energyLevelAtStart: level }),
65
+ }))