@jcjeon/integration-cli 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 (264) hide show
  1. package/.gitignore +23 -0
  2. package/.npmignore +21 -0
  3. package/.prettierignore +6 -0
  4. package/.prettierrc +26 -0
  5. package/AGENTS.md +10 -0
  6. package/CLAUDE.md +10 -0
  7. package/README.md +384 -0
  8. package/apps/server/README.md +294 -0
  9. package/apps/server/eslint.config.mjs +20 -0
  10. package/apps/server/nest-cli.json +8 -0
  11. package/apps/server/package.json +89 -0
  12. package/apps/server/scripts/postinstall.js +53 -0
  13. package/apps/server/src/__mocks__/glob.js +6 -0
  14. package/apps/server/src/__mocks__/uuid.js +5 -0
  15. package/apps/server/src/app.controller.spec.ts +24 -0
  16. package/apps/server/src/app.controller.ts +13 -0
  17. package/apps/server/src/app.module.ts +18 -0
  18. package/apps/server/src/app.service.ts +8 -0
  19. package/apps/server/src/common/ji-paths.ts +41 -0
  20. package/apps/server/src/database/database.module.ts +27 -0
  21. package/apps/server/src/database/entities/agent-changelog.entity.ts +39 -0
  22. package/apps/server/src/database/entities/agent-session.entity.ts +29 -0
  23. package/apps/server/src/database/entities/conversation.entity.ts +41 -0
  24. package/apps/server/src/database/entities/session.entity.ts +16 -0
  25. package/apps/server/src/database/entities/task-agent-run.entity.ts +40 -0
  26. package/apps/server/src/database/entities/task-agent.entity.ts +42 -0
  27. package/apps/server/src/database/entities/task-requirement.entity.ts +27 -0
  28. package/apps/server/src/database/entities/task-run.entity.ts +41 -0
  29. package/apps/server/src/database/entities/task.entity.ts +44 -0
  30. package/apps/server/src/main.ts +65 -0
  31. package/apps/server/src/modules/agents/agent-model-settings.spec.ts +80 -0
  32. package/apps/server/src/modules/agents/agents.module.ts +11 -0
  33. package/apps/server/src/modules/agents/claude/claude-auth.manager.ts +83 -0
  34. package/apps/server/src/modules/agents/claude/claude-pty.manager.ts +380 -0
  35. package/apps/server/src/modules/agents/claude/claude.controller.ts +85 -0
  36. package/apps/server/src/modules/agents/claude/claude.gateway.ts +158 -0
  37. package/apps/server/src/modules/agents/claude/claude.module.ts +18 -0
  38. package/apps/server/src/modules/agents/claude/claude.service.ts +67 -0
  39. package/apps/server/src/modules/agents/claude/dto/create-session.dto.ts +24 -0
  40. package/apps/server/src/modules/agents/claude/dto/resize-session.dto.ts +13 -0
  41. package/apps/server/src/modules/agents/claude/dto/send-input.dto.ts +9 -0
  42. package/apps/server/src/modules/agents/claude/interfaces/claude-session.interface.ts +26 -0
  43. package/apps/server/src/modules/agents/claude/interfaces/pty-event.interface.ts +10 -0
  44. package/apps/server/src/modules/agents/claude/interfaces/stream-event.interface.ts +61 -0
  45. package/apps/server/src/modules/agents/codex/codex-auth.manager.ts +107 -0
  46. package/apps/server/src/modules/agents/codex/codex-session.manager.ts +357 -0
  47. package/apps/server/src/modules/agents/codex/codex.controller.ts +64 -0
  48. package/apps/server/src/modules/agents/codex/codex.gateway.ts +97 -0
  49. package/apps/server/src/modules/agents/codex/codex.module.ts +17 -0
  50. package/apps/server/src/modules/agents/codex/dto/configure-auth.dto.ts +7 -0
  51. package/apps/server/src/modules/agents/gemini/dto/configure-auth.dto.ts +15 -0
  52. package/apps/server/src/modules/agents/gemini/dto/create-session.dto.ts +9 -0
  53. package/apps/server/src/modules/agents/gemini/dto/send-input.dto.ts +9 -0
  54. package/apps/server/src/modules/agents/gemini/gemini-auth.manager.ts +157 -0
  55. package/apps/server/src/modules/agents/gemini/gemini-session.manager.ts +287 -0
  56. package/apps/server/src/modules/agents/gemini/gemini.controller.ts +93 -0
  57. package/apps/server/src/modules/agents/gemini/gemini.gateway.ts +149 -0
  58. package/apps/server/src/modules/agents/gemini/gemini.module.ts +17 -0
  59. package/apps/server/src/modules/agents/gemini/interfaces/gemini-session.interface.ts +18 -0
  60. package/apps/server/src/modules/agents/gemini/interfaces/stream-event.interface.ts +14 -0
  61. package/apps/server/src/modules/agents/session-termination.spec.ts +103 -0
  62. package/apps/server/src/modules/changelog/changelog.controller.ts +20 -0
  63. package/apps/server/src/modules/changelog/changelog.module.ts +14 -0
  64. package/apps/server/src/modules/changelog/changelog.service.spec.ts +531 -0
  65. package/apps/server/src/modules/changelog/changelog.service.ts +690 -0
  66. package/apps/server/src/modules/conversations/conversation.controller.spec.ts +106 -0
  67. package/apps/server/src/modules/conversations/conversation.controller.ts +60 -0
  68. package/apps/server/src/modules/conversations/conversation.module.ts +14 -0
  69. package/apps/server/src/modules/conversations/conversation.service.spec.ts +176 -0
  70. package/apps/server/src/modules/conversations/conversation.service.ts +54 -0
  71. package/apps/server/src/modules/conversations/dto/create-conversation.dto.ts +37 -0
  72. package/apps/server/src/modules/conversations/enums/conversation.enum.ts +13 -0
  73. package/apps/server/src/modules/fs/fs.controller.ts +29 -0
  74. package/apps/server/src/modules/fs/fs.module.ts +8 -0
  75. package/apps/server/src/modules/harness/dto/save-harness.dto.ts +9 -0
  76. package/apps/server/src/modules/harness/harness.controller.spec.ts +95 -0
  77. package/apps/server/src/modules/harness/harness.controller.ts +35 -0
  78. package/apps/server/src/modules/harness/harness.module.ts +11 -0
  79. package/apps/server/src/modules/harness/harness.service.spec.ts +217 -0
  80. package/apps/server/src/modules/harness/harness.service.ts +112 -0
  81. package/apps/server/src/modules/sessions/session.controller.spec.ts +68 -0
  82. package/apps/server/src/modules/sessions/session.controller.ts +43 -0
  83. package/apps/server/src/modules/sessions/session.module.ts +14 -0
  84. package/apps/server/src/modules/sessions/session.service.spec.ts +106 -0
  85. package/apps/server/src/modules/sessions/session.service.ts +35 -0
  86. package/apps/server/src/modules/tasks/dto/create-task.dto.ts +54 -0
  87. package/apps/server/src/modules/tasks/dto/execute-task.dto.ts +22 -0
  88. package/apps/server/src/modules/tasks/dto/merge-file.dto.ts +7 -0
  89. package/apps/server/src/modules/tasks/dto/rerun-task.dto.ts +14 -0
  90. package/apps/server/src/modules/tasks/dto/update-task.dto.ts +55 -0
  91. package/apps/server/src/modules/tasks/task-execution.service.ts +978 -0
  92. package/apps/server/src/modules/tasks/task.gateway.ts +140 -0
  93. package/apps/server/src/modules/tasks/tasks.controller.spec.ts +210 -0
  94. package/apps/server/src/modules/tasks/tasks.controller.ts +139 -0
  95. package/apps/server/src/modules/tasks/tasks.module.ts +30 -0
  96. package/apps/server/src/modules/tasks/tasks.service.spec.ts +552 -0
  97. package/apps/server/src/modules/tasks/tasks.service.ts +333 -0
  98. package/apps/server/test/app.e2e-spec.ts +28 -0
  99. package/apps/server/test/jest-e2e.json +9 -0
  100. package/apps/server/tsconfig.build.json +4 -0
  101. package/apps/server/tsconfig.json +13 -0
  102. package/apps/web/AGENTS.md +7 -0
  103. package/apps/web/CLAUDE.md +1 -0
  104. package/apps/web/README.md +36 -0
  105. package/apps/web/eslint.config.mjs +21 -0
  106. package/apps/web/next-env.d.ts +6 -0
  107. package/apps/web/next.config.ts +7 -0
  108. package/apps/web/package.json +49 -0
  109. package/apps/web/postcss.config.mjs +7 -0
  110. package/apps/web/public/file.svg +1 -0
  111. package/apps/web/public/globe.svg +1 -0
  112. package/apps/web/public/next.svg +1 -0
  113. package/apps/web/public/vercel.svg +1 -0
  114. package/apps/web/public/window.svg +1 -0
  115. package/apps/web/src/app/claude/page.tsx +5 -0
  116. package/apps/web/src/app/codex/page.tsx +126 -0
  117. package/apps/web/src/app/favicon.ico +0 -0
  118. package/apps/web/src/app/gemini/page.tsx +130 -0
  119. package/apps/web/src/app/globals.css +149 -0
  120. package/apps/web/src/app/layout.tsx +40 -0
  121. package/apps/web/src/app/login/page.tsx +67 -0
  122. package/apps/web/src/app/page.tsx +497 -0
  123. package/apps/web/src/app/task/[id]/page.tsx +11 -0
  124. package/apps/web/src/app/test/page.tsx +298 -0
  125. package/apps/web/src/components/ui/Modal.tsx +78 -0
  126. package/apps/web/src/components/ui/WorkingDirPicker.tsx +195 -0
  127. package/apps/web/src/components/ui/__tests__/Modal.test.tsx +68 -0
  128. package/apps/web/src/features/auth/api/__tests__/auth.api.test.ts +83 -0
  129. package/apps/web/src/features/auth/api/auth.api.ts +81 -0
  130. package/apps/web/src/features/auth/hooks/__tests__/useClaudeAuth.test.ts +166 -0
  131. package/apps/web/src/features/auth/hooks/__tests__/useCodexAuth.test.ts +127 -0
  132. package/apps/web/src/features/auth/hooks/__tests__/useGeminiAuth.test.ts +120 -0
  133. package/apps/web/src/features/auth/hooks/useClaudeAuth.ts +88 -0
  134. package/apps/web/src/features/auth/hooks/useCodexAuth.ts +149 -0
  135. package/apps/web/src/features/auth/hooks/useGeminiAuth.ts +125 -0
  136. package/apps/web/src/features/auth/ui/CodexLoginPanel.tsx +302 -0
  137. package/apps/web/src/features/auth/ui/GeminiLoginPanel.tsx +316 -0
  138. package/apps/web/src/features/auth/ui/LoginForm.tsx +190 -0
  139. package/apps/web/src/features/auth/ui/LoginPanel.tsx +114 -0
  140. package/apps/web/src/features/auth/ui/__tests__/LoginPanel.test.tsx +105 -0
  141. package/apps/web/src/features/chat/api/__tests__/sessions.api.test.ts +187 -0
  142. package/apps/web/src/features/chat/api/sessions.api.ts +161 -0
  143. package/apps/web/src/features/chat/container/ClaudePageContainer.tsx +152 -0
  144. package/apps/web/src/features/chat/hooks/__tests__/useCodexSessions.test.ts +131 -0
  145. package/apps/web/src/features/chat/hooks/__tests__/useGeminiSessions.test.ts +130 -0
  146. package/apps/web/src/features/chat/hooks/useAgentModelSettings.ts +54 -0
  147. package/apps/web/src/features/chat/hooks/useClaudeSessions.ts +323 -0
  148. package/apps/web/src/features/chat/hooks/useCodexSessions.ts +275 -0
  149. package/apps/web/src/features/chat/hooks/useGeminiSessions.ts +255 -0
  150. package/apps/web/src/features/chat/hooks/useSessionCommand.ts +66 -0
  151. package/apps/web/src/features/chat/hooks/useSessionRename.ts +61 -0
  152. package/apps/web/src/features/chat/hooks/useSessionWorkingDirectories.ts +34 -0
  153. package/apps/web/src/features/chat/hooks/useUnifiedSessions.ts +156 -0
  154. package/apps/web/src/features/chat/lib/agentModelOptions.ts +72 -0
  155. package/apps/web/src/features/chat/ui/AgentModelPicker.tsx +134 -0
  156. package/apps/web/src/features/chat/ui/AgentSelectModal.tsx +236 -0
  157. package/apps/web/src/features/chat/ui/ChatInput.tsx +162 -0
  158. package/apps/web/src/features/chat/ui/ChatMessage.tsx +204 -0
  159. package/apps/web/src/features/chat/ui/ChatWorkspace.tsx +207 -0
  160. package/apps/web/src/features/chat/ui/CheckingSkeleton.tsx +44 -0
  161. package/apps/web/src/features/chat/ui/ClaudeLoginView.tsx +44 -0
  162. package/apps/web/src/features/chat/ui/PermissionCard.tsx +37 -0
  163. package/apps/web/src/features/chat/ui/SessionSidebar.tsx +280 -0
  164. package/apps/web/src/features/chat/ui/__tests__/AgentSelectModal.test.tsx +58 -0
  165. package/apps/web/src/features/chat/ui/__tests__/ChatInput.test.tsx +134 -0
  166. package/apps/web/src/features/chat/ui/__tests__/ChatMessage.test.tsx +106 -0
  167. package/apps/web/src/features/chat/ui/__tests__/ChatWorkspace.test.tsx +66 -0
  168. package/apps/web/src/features/diff/ui/DiffFileRow.tsx +73 -0
  169. package/apps/web/src/features/diff/ui/DiffHunk.tsx +61 -0
  170. package/apps/web/src/features/diff/ui/FileChangeBadge.tsx +23 -0
  171. package/apps/web/src/features/diff/ui/__tests__/DiffFileRow.test.tsx +40 -0
  172. package/apps/web/src/features/diff/ui/__tests__/DiffHunk.test.tsx +24 -0
  173. package/apps/web/src/features/diff/ui/__tests__/FileChangeBadge.test.tsx +16 -0
  174. package/apps/web/src/features/fs/api/fs.api.ts +14 -0
  175. package/apps/web/src/features/fs/hooks/useDirBrowser.ts +50 -0
  176. package/apps/web/src/features/harness/api/__tests__/harness.api.test.ts +73 -0
  177. package/apps/web/src/features/harness/api/harness.api.ts +46 -0
  178. package/apps/web/src/features/harness/hooks/__tests__/useHarness.test.ts +65 -0
  179. package/apps/web/src/features/harness/hooks/useHarness.ts +66 -0
  180. package/apps/web/src/features/harness/ui/HarnessModal.tsx +171 -0
  181. package/apps/web/src/features/harness/ui/__tests__/HarnessModal.test.tsx +46 -0
  182. package/apps/web/src/features/status/ui/AgentStatusModal.tsx +267 -0
  183. package/apps/web/src/features/status/ui/__tests__/AgentStatusModal.test.tsx +71 -0
  184. package/apps/web/src/features/tasks/api/__tests__/changelog.api.test.ts +89 -0
  185. package/apps/web/src/features/tasks/api/__tests__/tasks.api.test.ts +282 -0
  186. package/apps/web/src/features/tasks/api/changelog.api.ts +52 -0
  187. package/apps/web/src/features/tasks/api/tasks.api.ts +175 -0
  188. package/apps/web/src/features/tasks/container/TaskDetailPageContainer.tsx +69 -0
  189. package/apps/web/src/features/tasks/hooks/__tests__/useChangelogCodeCopy.test.ts +48 -0
  190. package/apps/web/src/features/tasks/hooks/__tests__/useTaskChangelog.test.ts +48 -0
  191. package/apps/web/src/features/tasks/hooks/__tests__/useTaskCreate.test.ts +217 -0
  192. package/apps/web/src/features/tasks/hooks/__tests__/useTaskEdit.test.ts +152 -0
  193. package/apps/web/src/features/tasks/hooks/__tests__/useTaskExecution.test.ts +143 -0
  194. package/apps/web/src/features/tasks/hooks/__tests__/useTaskList.test.ts +168 -0
  195. package/apps/web/src/features/tasks/hooks/__tests__/useTaskNotification.test.ts +125 -0
  196. package/apps/web/src/features/tasks/hooks/__tests__/useTaskRuns.test.ts +51 -0
  197. package/apps/web/src/features/tasks/hooks/useChangelogCodeCopy.ts +52 -0
  198. package/apps/web/src/features/tasks/hooks/useCopyToClipboard.ts +47 -0
  199. package/apps/web/src/features/tasks/hooks/useTaskChangelog.ts +32 -0
  200. package/apps/web/src/features/tasks/hooks/useTaskCreate.ts +137 -0
  201. package/apps/web/src/features/tasks/hooks/useTaskDetail.ts +217 -0
  202. package/apps/web/src/features/tasks/hooks/useTaskEdit.ts +130 -0
  203. package/apps/web/src/features/tasks/hooks/useTaskExecution.ts +137 -0
  204. package/apps/web/src/features/tasks/hooks/useTaskList.ts +159 -0
  205. package/apps/web/src/features/tasks/hooks/useTaskNotification.ts +80 -0
  206. package/apps/web/src/features/tasks/hooks/useTaskRuns.ts +32 -0
  207. package/apps/web/src/features/tasks/ui/AgentOutputPanel.tsx +203 -0
  208. package/apps/web/src/features/tasks/ui/AgentRoleSelect.tsx +97 -0
  209. package/apps/web/src/features/tasks/ui/ChangelogPanel.tsx +321 -0
  210. package/apps/web/src/features/tasks/ui/RunHistoryPanel.tsx +193 -0
  211. package/apps/web/src/features/tasks/ui/TaskCreateModal.tsx +205 -0
  212. package/apps/web/src/features/tasks/ui/TaskDetailView.tsx +413 -0
  213. package/apps/web/src/features/tasks/ui/TaskEditModal.tsx +165 -0
  214. package/apps/web/src/features/tasks/ui/TaskListModal.tsx +591 -0
  215. package/apps/web/src/features/tasks/ui/__tests__/AgentRoleSelect.test.tsx +91 -0
  216. package/apps/web/src/features/tasks/ui/__tests__/ChangelogPanel.test.tsx +94 -0
  217. package/apps/web/src/features/tasks/ui/__tests__/RunHistoryPanel.test.tsx +71 -0
  218. package/apps/web/src/features/tasks/ui/__tests__/TaskCreateModal.test.tsx +153 -0
  219. package/apps/web/src/features/tasks/ui/__tests__/TaskEditModal.test.tsx +75 -0
  220. package/apps/web/src/features/tasks/ui/__tests__/TaskListModal.test.tsx +243 -0
  221. package/apps/web/src/hooks/useWorkingDir.ts +28 -0
  222. package/apps/web/src/lib/__tests__/ansi.test.ts +88 -0
  223. package/apps/web/src/lib/ansi.ts +105 -0
  224. package/apps/web/src/lib/constants.ts +4 -0
  225. package/apps/web/src/lib/quota.ts +22 -0
  226. package/apps/web/src/lib/theme.tsx +78 -0
  227. package/apps/web/src/lib/toast.tsx +175 -0
  228. package/apps/web/src/store/agentStatusStore.ts +38 -0
  229. package/apps/web/tsconfig.json +18 -0
  230. package/apps/web/vitest.config.ts +25 -0
  231. package/apps/web/vitest.setup.ts +10 -0
  232. package/package.json +85 -0
  233. package/packages/cli/dist/commands/check.d.ts +1 -0
  234. package/packages/cli/dist/commands/check.js +89 -0
  235. package/packages/cli/dist/commands/init.d.ts +5 -0
  236. package/packages/cli/dist/commands/init.js +183 -0
  237. package/packages/cli/dist/commands/start.d.ts +4 -0
  238. package/packages/cli/dist/commands/start.js +188 -0
  239. package/packages/cli/dist/index.d.ts +2 -0
  240. package/packages/cli/dist/index.js +71 -0
  241. package/packages/cli/dist/utils/agent-tools.d.ts +28 -0
  242. package/packages/cli/dist/utils/agent-tools.js +193 -0
  243. package/packages/cli/dist/utils/project-init.d.ts +12 -0
  244. package/packages/cli/dist/utils/project-init.js +258 -0
  245. package/packages/cli/dist/utils/proxy.d.ts +8 -0
  246. package/packages/cli/dist/utils/proxy.js +138 -0
  247. package/packages/cli/package.json +30 -0
  248. package/packages/cli/src/commands/check.ts +77 -0
  249. package/packages/cli/src/commands/init.ts +209 -0
  250. package/packages/cli/src/commands/start.ts +183 -0
  251. package/packages/cli/src/index.ts +91 -0
  252. package/packages/cli/src/utils/agent-tools.ts +201 -0
  253. package/packages/cli/src/utils/project-init.ts +252 -0
  254. package/packages/cli/src/utils/proxy.ts +123 -0
  255. package/packages/cli/tsconfig.json +14 -0
  256. package/packages/eslint-config/base.mjs +31 -0
  257. package/packages/eslint-config/nest.mjs +55 -0
  258. package/packages/eslint-config/next.mjs +23 -0
  259. package/packages/eslint-config/package.json +20 -0
  260. package/packages/typescript-config/base.json +16 -0
  261. package/packages/typescript-config/nestjs.json +17 -0
  262. package/packages/typescript-config/nextjs.json +15 -0
  263. package/packages/typescript-config/package.json +11 -0
  264. package/turbo.json +28 -0
@@ -0,0 +1,126 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useEffect } from "react";
5
+
6
+ import { useCodexAuth } from "@/features/auth/hooks/useCodexAuth";
7
+ import { CodexLoginPanel } from "@/features/auth/ui/CodexLoginPanel";
8
+ import { ThemeToggle } from "@/lib/theme";
9
+
10
+ const OPENAI_LOGO = (
11
+ <svg viewBox="0 0 24 24" fill="currentColor" className="h-7 w-7 text-gray-900/80 dark:text-white/80" aria-hidden="true">
12
+ <path d="M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073zM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494zM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646zM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872zm16.597 3.855l-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667zm2.01-3.023l-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135l-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08L8.704 5.46a.795.795 0 0 0-.393.681zm1.097-2.365l2.602-1.5 2.607 1.5v2.999l-2.597 1.5-2.607-1.5z" />
13
+ </svg>
14
+ );
15
+
16
+ function CheckingSkeleton() {
17
+ return (
18
+ <div className="flex h-screen flex-col bg-[#faf8f5] dark:bg-[#07090e]">
19
+ <div className="flex items-center gap-2 border-b border-gray-900/[0.07] px-4 py-3 dark:border-white/[0.07]">
20
+ <div className="h-3.5 w-3.5 rounded bg-gray-900/[0.07] dark:bg-white/[0.07]" />
21
+ <div className="h-[14px] w-20 rounded bg-gray-900/[0.07] dark:bg-white/[0.07]" />
22
+ </div>
23
+ <div className="flex flex-1 flex-col items-center justify-center gap-3">
24
+ <span className="h-6 w-6 animate-spin rounded-full border-2 border-gray-900/[0.08] border-t-gray-900/60 dark:border-white/[0.08] dark:border-t-white/60" />
25
+ <span className="text-xs text-gray-900/20 dark:text-white/20">인증 확인 중…</span>
26
+ </div>
27
+ </div>
28
+ );
29
+ }
30
+
31
+ function NotInstalledView() {
32
+ return (
33
+ <div className="flex flex-1 flex-col items-center justify-center gap-6 px-6">
34
+ <div className="flex flex-col items-center gap-3 text-center">
35
+ <div className="flex h-16 w-16 items-center justify-center rounded-2xl border border-gray-900/[0.08] bg-gray-900/[0.04] dark:border-white/[0.08] dark:bg-white/[0.04]">
36
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="h-8 w-8 text-gray-900/30 dark:text-white/30">
37
+ <path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
38
+ </svg>
39
+ </div>
40
+ <h2 className="text-xl font-semibold text-gray-900/90 dark:text-white/90">Codex CLI가 설치되어 있지 않습니다</h2>
41
+ <p className="max-w-sm text-sm text-gray-900/40 dark:text-white/40">
42
+ 먼저 CLI 도구를 설치해 주세요.
43
+ </p>
44
+ <code className="mt-1 rounded-lg border border-gray-900/[0.08] bg-gray-900/[0.03] px-4 py-2 font-mono text-sm text-gray-900/55 dark:border-white/[0.08] dark:bg-white/[0.03] dark:text-white/55">
45
+ jccli init
46
+ </code>
47
+ </div>
48
+ </div>
49
+ );
50
+ }
51
+
52
+ function AuthenticatedView() {
53
+ return (
54
+ <div className="flex flex-1 flex-col items-center justify-center gap-4">
55
+ <div className="pointer-events-none absolute inset-0 overflow-hidden">
56
+ <div className="absolute left-1/2 top-1/2 h-[500px] w-[600px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-gray-900/[0.03] blur-[120px] dark:bg-white/[0.03]" />
57
+ </div>
58
+ <div className="relative flex h-14 w-14 items-center justify-center rounded-2xl border border-gray-900/[0.08] bg-gray-900/[0.04] dark:border-white/[0.08] dark:bg-white/[0.04]">
59
+ {OPENAI_LOGO}
60
+ </div>
61
+ <div className="relative text-center">
62
+ <p className="font-semibold text-gray-900/75 dark:text-white/75">Codex CLI</p>
63
+ <p className="mt-1 text-sm text-gray-900/30 dark:text-white/30">로그인되었습니다. 채팅 기능은 준비 중입니다.</p>
64
+ </div>
65
+ </div>
66
+ );
67
+ }
68
+
69
+ export default function CodexPage() {
70
+ const {
71
+ authState,
72
+ loginMethod,
73
+ setLoginMethod,
74
+ loginState,
75
+ loginOutput,
76
+ loginUrls,
77
+ deviceCode,
78
+ startDeviceLogin,
79
+ cancelDeviceLogin,
80
+ apiKeyLoginState,
81
+ configError,
82
+ saveApiKey,
83
+ checkAuth,
84
+ } = useCodexAuth();
85
+
86
+ useEffect(() => {
87
+ if (loginState === "done" || apiKeyLoginState === "done") void checkAuth();
88
+ }, [loginState, apiKeyLoginState, checkAuth]);
89
+
90
+ const pageHeader = (
91
+ <header className="flex items-center gap-2 border-b border-gray-900/[0.07] px-4 py-3 dark:border-white/[0.07]">
92
+ <Link href="/" className="text-gray-900/30 transition-colors hover:text-gray-900/60 dark:text-white/30 dark:hover:text-white/60">
93
+
94
+ </Link>
95
+ <span className="text-sm font-semibold text-gray-900/80 dark:text-white/80">Codex CLI</span>
96
+ <div className="ml-auto">
97
+ <ThemeToggle />
98
+ </div>
99
+ </header>
100
+ );
101
+
102
+ if (authState === "checking") return <CheckingSkeleton />;
103
+
104
+ return (
105
+ <div className="flex h-screen flex-col bg-[#faf8f5] text-gray-900 dark:bg-[#07090e] dark:text-white">
106
+ {pageHeader}
107
+ {authState === "not-installed" && <NotInstalledView />}
108
+ {authState === "unauthenticated" && (
109
+ <CodexLoginPanel
110
+ loginMethod={loginMethod}
111
+ onMethodChange={setLoginMethod}
112
+ loginState={loginState}
113
+ loginOutput={loginOutput}
114
+ loginUrls={loginUrls}
115
+ deviceCode={deviceCode}
116
+ onStartDeviceLogin={startDeviceLogin}
117
+ onCancelDeviceLogin={cancelDeviceLogin}
118
+ apiKeyLoginState={apiKeyLoginState}
119
+ configError={configError}
120
+ onSaveApiKey={saveApiKey}
121
+ />
122
+ )}
123
+ {authState === "authenticated" && <AuthenticatedView />}
124
+ </div>
125
+ );
126
+ }
Binary file
@@ -0,0 +1,130 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useEffect } from "react";
5
+
6
+ import { useGeminiAuth } from "@/features/auth/hooks/useGeminiAuth";
7
+ import { GeminiLoginPanel } from "@/features/auth/ui/GeminiLoginPanel";
8
+ import { ThemeToggle } from "@/lib/theme";
9
+
10
+ // ─── Checking Skeleton ────────────────────────────────────────────────────────
11
+
12
+ function CheckingSkeleton() {
13
+ return (
14
+ <div className="flex h-screen flex-col bg-[#faf8f5] dark:bg-[#07090e]">
15
+ <div className="flex items-center gap-2 border-b border-gray-900/[0.07] px-4 py-3 dark:border-white/[0.07]">
16
+ <div className="h-3.5 w-3.5 rounded bg-gray-900/[0.07] dark:bg-white/[0.07]" />
17
+ <div className="h-[14px] w-20 rounded bg-gray-900/[0.07] dark:bg-white/[0.07]" />
18
+ </div>
19
+ <div className="flex flex-1 flex-col items-center justify-center gap-3">
20
+ <span className="h-6 w-6 animate-spin rounded-full border-2 border-gray-900/[0.08] border-t-blue-500 dark:border-white/[0.08]" />
21
+ <span className="text-xs text-gray-900/20 dark:text-white/20">인증 확인 중…</span>
22
+ </div>
23
+ </div>
24
+ );
25
+ }
26
+
27
+ // ─── Not Installed ────────────────────────────────────────────────────────────
28
+
29
+ function NotInstalledView() {
30
+ return (
31
+ <div className="flex flex-1 flex-col items-center justify-center gap-6 px-6">
32
+ <div className="flex flex-col items-center gap-3 text-center">
33
+ <div className="flex h-16 w-16 items-center justify-center rounded-2xl border border-gray-900/[0.08] bg-gray-900/[0.04] dark:border-white/[0.08] dark:bg-white/[0.04]">
34
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="h-8 w-8 text-gray-900/30 dark:text-white/30">
35
+ <path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
36
+ </svg>
37
+ </div>
38
+ <h2 className="text-xl font-semibold text-gray-900/90 dark:text-white/90">Gemini CLI가 설치되어 있지 않습니다</h2>
39
+ <p className="max-w-sm text-sm text-gray-900/40 dark:text-white/40">
40
+ 먼저 CLI 도구를 설치해 주세요.
41
+ </p>
42
+ <code className="mt-1 rounded-lg border border-gray-900/[0.08] bg-gray-900/[0.03] px-4 py-2 font-mono text-sm text-gray-900/55 dark:border-white/[0.08] dark:bg-white/[0.03] dark:text-white/55">
43
+ jccli init
44
+ </code>
45
+ </div>
46
+ </div>
47
+ );
48
+ }
49
+
50
+ // ─── Authenticated View ───────────────────────────────────────────────────────
51
+
52
+ function AuthenticatedView() {
53
+ return (
54
+ <div className="flex flex-1 flex-col items-center justify-center gap-4">
55
+ <div className="pointer-events-none absolute inset-0 overflow-hidden">
56
+ <div className="absolute left-1/2 top-1/2 h-[500px] w-[600px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-blue-500/[0.04] blur-[120px]" />
57
+ </div>
58
+ <div className="relative flex h-14 w-14 items-center justify-center rounded-2xl border border-gray-900/[0.08] bg-blue-500/[0.08] dark:border-white/[0.08]">
59
+ <svg viewBox="0 0 24 24" className="h-7 w-7" aria-hidden="true">
60
+ <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4" />
61
+ <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853" />
62
+ <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05" />
63
+ <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335" />
64
+ </svg>
65
+ </div>
66
+ <div className="relative text-center">
67
+ <p className="font-semibold text-gray-900/75 dark:text-white/75">Gemini CLI</p>
68
+ <p className="mt-1 text-sm text-gray-900/30 dark:text-white/30">로그인되었습니다. 채팅 기능은 준비 중입니다.</p>
69
+ </div>
70
+ </div>
71
+ );
72
+ }
73
+
74
+ // ─── Page ─────────────────────────────────────────────────────────────────────
75
+
76
+ export default function GeminiPage() {
77
+ const {
78
+ authState,
79
+ loginState,
80
+ loginOutput,
81
+ loginUrls,
82
+ configError,
83
+ saveApiKey,
84
+ startGcaLogin,
85
+ cancelLogin,
86
+ resetLogin,
87
+ checkAuth,
88
+ } = useGeminiAuth();
89
+
90
+ useEffect(() => {
91
+ if (loginState === "done") void checkAuth();
92
+ }, [loginState, checkAuth]);
93
+
94
+ const pageHeader = (
95
+ <header className="flex items-center gap-2 border-b border-gray-900/[0.07] px-4 py-3 dark:border-white/[0.07]">
96
+ <Link href="/" className="text-gray-900/30 transition-colors hover:text-gray-900/60 dark:text-white/30 dark:hover:text-white/60">
97
+
98
+ </Link>
99
+ <span className="text-sm font-semibold text-gray-900/80 dark:text-white/80">Gemini CLI</span>
100
+ <div className="ml-auto">
101
+ <ThemeToggle />
102
+ </div>
103
+ </header>
104
+ );
105
+
106
+ if (authState === "checking") return <CheckingSkeleton />;
107
+
108
+ return (
109
+ <div className="flex h-screen flex-col bg-[#faf8f5] text-gray-900 dark:bg-[#07090e] dark:text-white">
110
+ {pageHeader}
111
+
112
+ {authState === "not-installed" && <NotInstalledView />}
113
+
114
+ {authState === "unauthenticated" && (
115
+ <GeminiLoginPanel
116
+ loginState={loginState}
117
+ loginOutput={loginOutput}
118
+ loginUrls={loginUrls}
119
+ configError={configError}
120
+ onSaveApiKey={saveApiKey}
121
+ onStartGca={startGcaLogin}
122
+ onCancel={cancelLogin}
123
+ onReset={resetLogin}
124
+ />
125
+ )}
126
+
127
+ {authState === "authenticated" && <AuthenticatedView />}
128
+ </div>
129
+ );
130
+ }
@@ -0,0 +1,149 @@
1
+ @import "tailwindcss";
2
+ @import "highlight.js/styles/github.css";
3
+
4
+ /* ── Dark mode: class strategy ──────────────────────────────── */
5
+ @variant dark (&:where(.dark, .dark *));
6
+
7
+ /* ── Design tokens ──────────────────────────────────────────── */
8
+ :root {
9
+ --background: #faf8f5;
10
+ --foreground: rgba(15, 17, 23, 0.87);
11
+
12
+ /* heading gradient */
13
+ --heading-gradient: linear-gradient(
14
+ 160deg,
15
+ rgba(15, 17, 23, 0.92) 0%,
16
+ rgba(15, 17, 23, 0.52) 100%
17
+ );
18
+
19
+ /* dot grid */
20
+ --dot-color: rgba(0, 0, 0, 0.08);
21
+ }
22
+
23
+ .dark {
24
+ --background: #07090e;
25
+ --foreground: #ededed;
26
+
27
+ --heading-gradient: linear-gradient(
28
+ 160deg,
29
+ rgba(255, 255, 255, 0.95) 0%,
30
+ rgba(255, 255, 255, 0.40) 100%
31
+ );
32
+
33
+ --dot-color: rgba(255, 255, 255, 0.07);
34
+ }
35
+
36
+ @theme inline {
37
+ --color-background: var(--background);
38
+ --color-foreground: var(--foreground);
39
+ --font-sans: var(--font-geist-sans);
40
+ --font-mono: var(--font-geist-mono);
41
+ }
42
+
43
+ body {
44
+ background: var(--background);
45
+ color: var(--foreground);
46
+ font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif;
47
+ }
48
+
49
+ /* ── Dark mode: override highlight.js ──────────────────────── */
50
+ .dark .hljs { background: #0d1117; color: #e6edf3; }
51
+ .dark .hljs-doctag,
52
+ .dark .hljs-keyword,
53
+ .dark .hljs-meta .hljs-keyword,
54
+ .dark .hljs-template-tag,
55
+ .dark .hljs-template-variable,
56
+ .dark .hljs-type,
57
+ .dark .hljs-variable.language_ { color: #ff7b72; }
58
+ .dark .hljs-title,
59
+ .dark .hljs-title.class_,
60
+ .dark .hljs-title.class_.inherited__,
61
+ .dark .hljs-title.function_ { color: #d2a8ff; }
62
+ .dark .hljs-attr,
63
+ .dark .hljs-attribute,
64
+ .dark .hljs-literal,
65
+ .dark .hljs-meta,
66
+ .dark .hljs-number,
67
+ .dark .hljs-operator,
68
+ .dark .hljs-selector-attr,
69
+ .dark .hljs-selector-class,
70
+ .dark .hljs-selector-id,
71
+ .dark .hljs-variable { color: #79c0ff; }
72
+ .dark .hljs-string,
73
+ .dark .hljs-meta .hljs-string { color: #a5d6ff; }
74
+ .dark .hljs-built_in,
75
+ .dark .hljs-symbol { color: #ffa657; }
76
+ .dark .hljs-comment,
77
+ .dark .hljs-code,
78
+ .dark .hljs-formula { color: #8b949e; }
79
+ .dark .hljs-name,
80
+ .dark .hljs-quote,
81
+ .dark .hljs-selector-pseudo,
82
+ .dark .hljs-selector-tag { color: #7ee787; }
83
+ .dark .hljs-subst { color: #c9d1d9; }
84
+ .dark .hljs-section { color: #1f6feb; font-weight: bold; }
85
+ .dark .hljs-bullet { color: #f2cc60; }
86
+ .dark .hljs-emphasis { color: #c9d1d9; font-style: italic; }
87
+ .dark .hljs-strong { color: #c9d1d9; font-weight: bold; }
88
+ .dark .hljs-addition { color: #aff5b4; background: #033a16; }
89
+ .dark .hljs-deletion { color: #ffdcd7; background: #67060c; }
90
+
91
+ /* ── Animations ─────────────────────────────────────────────── */
92
+
93
+ @keyframes fade-in-up {
94
+ from { opacity: 0; transform: translateY(10px); }
95
+ to { opacity: 1; transform: translateY(0); }
96
+ }
97
+
98
+ @keyframes toast-in {
99
+ from { opacity: 0; transform: translateX(calc(100% + 16px)); }
100
+ to { opacity: 1; transform: translateX(0); }
101
+ }
102
+
103
+ @keyframes toast-out {
104
+ from { opacity: 1; transform: translateX(0); max-height: 100px; margin-bottom: 0; }
105
+ to { opacity: 0; transform: translateX(calc(100% + 16px)); max-height: 0; margin-bottom: -8px; }
106
+ }
107
+
108
+ .animate-toast-in {
109
+ animation: toast-in 0.25s cubic-bezier(0.16, 1, 0.3, 1) both;
110
+ }
111
+
112
+ .animate-toast-out {
113
+ animation: toast-out 0.2s ease-in forwards;
114
+ }
115
+
116
+ @keyframes shimmer {
117
+ from { background-position: 200% 0; }
118
+ to { background-position: -200% 0; }
119
+ }
120
+
121
+ @keyframes pulse-dot {
122
+ 0%, 100% { opacity: 1; }
123
+ 50% { opacity: 0.35; }
124
+ }
125
+
126
+ .animate-fade-in-up {
127
+ animation: fade-in-up 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
128
+ }
129
+
130
+ .animate-shimmer-bg {
131
+ background: linear-gradient(
132
+ 90deg,
133
+ rgba(0, 0, 0, 0.02) 0%,
134
+ rgba(0, 0, 0, 0.05) 40%,
135
+ rgba(0, 0, 0, 0.02) 100%
136
+ );
137
+ background-size: 200% 100%;
138
+ animation: shimmer 2s ease-in-out infinite;
139
+ }
140
+
141
+ .dark .animate-shimmer-bg {
142
+ background: linear-gradient(
143
+ 90deg,
144
+ rgba(255, 255, 255, 0.03) 0%,
145
+ rgba(255, 255, 255, 0.08) 40%,
146
+ rgba(255, 255, 255, 0.03) 100%
147
+ );
148
+ background-size: 200% 100%;
149
+ }
@@ -0,0 +1,40 @@
1
+ import type { Metadata } from "next";
2
+ import { Geist, Geist_Mono } from "next/font/google";
3
+ import { ThemeProvider } from "@/lib/theme";
4
+ import { ToastProvider } from "@/lib/toast";
5
+ import "./globals.css";
6
+
7
+ const geistSans = Geist({
8
+ variable: "--font-geist-sans",
9
+ subsets: ["latin"],
10
+ });
11
+
12
+ const geistMono = Geist_Mono({
13
+ variable: "--font-geist-mono",
14
+ subsets: ["latin"],
15
+ });
16
+
17
+ export const metadata: Metadata = {
18
+ title: "ji-cli",
19
+ description: "하나의 플랫폼에서 모든 AI CLI를 제어합니다",
20
+ };
21
+
22
+ export default function RootLayout({
23
+ children,
24
+ }: Readonly<{
25
+ children: React.ReactNode;
26
+ }>) {
27
+ return (
28
+ <html
29
+ lang="en"
30
+ suppressHydrationWarning
31
+ className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
32
+ >
33
+ <body className="flex min-h-full flex-col">
34
+ <ThemeProvider>
35
+ <ToastProvider>{children}</ToastProvider>
36
+ </ThemeProvider>
37
+ </body>
38
+ </html>
39
+ );
40
+ }
@@ -0,0 +1,67 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { ThemeToggle } from "@/lib/theme";
5
+ import { LoginForm } from "@/features/auth/ui/LoginForm";
6
+
7
+ // ─── Container ────────────────────────────────────────────────────────────────
8
+
9
+ export default function LoginPage() {
10
+ const [email, setEmail] = useState("");
11
+ const [password, setPassword] = useState("");
12
+ const [loading] = useState(false);
13
+ const [error] = useState<string | null>(null);
14
+
15
+ const handleSubmit = (e: React.FormEvent) => {
16
+ e.preventDefault();
17
+ // TODO: 로그인 로직 연결
18
+ };
19
+
20
+ const handleGuestLogin = () => {
21
+ // TODO: 게스트 로그인 로직 연결
22
+ };
23
+
24
+ return (
25
+ <div className="relative flex min-h-screen flex-col items-center justify-center overflow-hidden bg-[#faf8f5] px-6 py-12 dark:bg-[#07090e]">
26
+ {/* Theme toggle */}
27
+ <div className="absolute right-4 top-4">
28
+ <ThemeToggle />
29
+ </div>
30
+
31
+ {/* Ambient background glows */}
32
+ <div className="pointer-events-none absolute inset-0 overflow-hidden">
33
+ <div className="absolute left-1/2 top-[40%] h-[600px] w-[800px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-orange-500/[0.04] blur-[120px] dark:bg-orange-500/[0.035]" />
34
+ <div className="absolute -left-24 bottom-1/4 h-[350px] w-[450px] rounded-full bg-blue-600/[0.03] blur-[100px] dark:bg-blue-600/[0.025]" />
35
+ <div className="absolute -right-24 top-1/4 h-[300px] w-[400px] rounded-full bg-purple-600/[0.03] blur-[100px] dark:bg-purple-600/[0.025]" />
36
+ </div>
37
+
38
+ {/* Dot-grid pattern */}
39
+ <div
40
+ className="pointer-events-none absolute inset-0 opacity-60"
41
+ style={{
42
+ backgroundImage: "radial-gradient(circle, var(--dot-color) 1px, transparent 1px)",
43
+ backgroundSize: "28px 28px",
44
+ maskImage: "radial-gradient(ellipse 75% 65% at 50% 50%, black 30%, transparent 100%)",
45
+ WebkitMaskImage: "radial-gradient(ellipse 75% 65% at 50% 50%, black 30%, transparent 100%)",
46
+ }}
47
+ />
48
+
49
+ {/* Form */}
50
+ <div className="relative w-full flex justify-center">
51
+ <LoginForm
52
+ email={email}
53
+ password={password}
54
+ loading={loading}
55
+ error={error}
56
+ onEmailChange={setEmail}
57
+ onPasswordChange={setPassword}
58
+ onSubmit={handleSubmit}
59
+ onGuestLogin={handleGuestLogin}
60
+ />
61
+ </div>
62
+
63
+ {/* Bottom rule */}
64
+ <div className="pointer-events-none absolute inset-x-0 bottom-0 h-px bg-gradient-to-r from-transparent via-gray-900/[0.05] to-transparent dark:via-white/[0.05]" />
65
+ </div>
66
+ );
67
+ }