@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,252 @@
1
+ import { existsSync, mkdirSync, readdirSync, statSync, copyFileSync } from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+ import { spawnSync } from 'child_process';
5
+
6
+ import chalk from 'chalk';
7
+ import ora from 'ora';
8
+
9
+ import { getNpmCommand, type CommandResult, type SupportedPlatform } from './agent-tools';
10
+
11
+ export interface InitProjectOptions {
12
+ targetDir?: string;
13
+ force?: boolean;
14
+ skipInstall?: boolean;
15
+ }
16
+
17
+ export interface ProjectReadyResult {
18
+ ok: boolean;
19
+ projectRoot: string;
20
+ }
21
+
22
+ const PROJECT_MARKER_WORKSPACES = ['apps/*', 'packages/*'];
23
+ const RUNTIME_DIRS = [
24
+ '.ji',
25
+ path.join('.ji', 'logs'),
26
+ path.join('.ji', 'worktrees'),
27
+ path.join('.ji', 'harness'),
28
+ path.join('.ji', 'agents'),
29
+ path.join('.ji', 'agents', 'gemini'),
30
+ path.join('.ji', 'agents', 'codex'),
31
+ ];
32
+
33
+ const COPY_EXCLUDES = new Set([
34
+ '.git',
35
+ '.claude',
36
+ '.turbo',
37
+ 'node_modules',
38
+ '.next',
39
+ 'out',
40
+ 'coverage',
41
+ '.env',
42
+ '.env.local',
43
+ ]);
44
+
45
+ const COPY_EXCLUDED_SUFFIXES = [
46
+ path.normalize('apps/server/dist'),
47
+ path.normalize('apps/web/.next'),
48
+ path.normalize('apps/web/out'),
49
+ path.normalize('packages/typescript-config/dist'),
50
+ ];
51
+
52
+ export function ensureProjectReady(
53
+ options: InitProjectOptions,
54
+ platform: SupportedPlatform,
55
+ ): ProjectReadyResult {
56
+ try {
57
+ const cwdProjectRoot = findProjectRoot(process.cwd());
58
+ const targetRoot = resolveTargetRoot(options.targetDir, cwdProjectRoot);
59
+
60
+ if (!cwdProjectRoot || path.resolve(cwdProjectRoot) !== path.resolve(targetRoot)) {
61
+ const scaffoldOk = scaffoldProject(targetRoot, Boolean(options.force));
62
+ if (!scaffoldOk) return { ok: false, projectRoot: targetRoot };
63
+ } else {
64
+ console.log(chalk.green(`✓ 프로젝트 감지: ${cwdProjectRoot}`));
65
+ }
66
+
67
+ ensureRuntimeDirs();
68
+
69
+ if (options.skipInstall) {
70
+ console.log(chalk.yellow('프로젝트 의존성 설치를 건너뜁니다.\n'));
71
+ return { ok: true, projectRoot: targetRoot };
72
+ }
73
+
74
+ const installOk = ensureDependencies(targetRoot, platform);
75
+ return { ok: installOk, projectRoot: targetRoot };
76
+ } catch (error) {
77
+ console.error(chalk.red('프로젝트 초기화 중 오류가 발생했습니다.'));
78
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
79
+ return { ok: false, projectRoot: process.cwd() };
80
+ }
81
+ }
82
+
83
+ function resolveTargetRoot(targetDir: string | undefined, cwdProjectRoot: string | null): string {
84
+ if (targetDir) {
85
+ return path.resolve(process.cwd(), targetDir);
86
+ }
87
+
88
+ return cwdProjectRoot ?? process.cwd();
89
+ }
90
+
91
+ export function findProjectRoot(startDir: string): string | null {
92
+ let current = path.resolve(startDir);
93
+
94
+ while (true) {
95
+ if (isProjectRoot(current)) return current;
96
+
97
+ const parent = path.dirname(current);
98
+ if (parent === current) return null;
99
+ current = parent;
100
+ }
101
+ }
102
+
103
+ function isProjectRoot(dir: string): boolean {
104
+ const packageJsonPath = path.join(dir, 'package.json');
105
+ if (!existsSync(packageJsonPath)) return false;
106
+
107
+ try {
108
+ const packageJson = require(packageJsonPath) as { workspaces?: string[] };
109
+ const workspaces = packageJson.workspaces ?? [];
110
+ return PROJECT_MARKER_WORKSPACES.every((workspace) => workspaces.includes(workspace));
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+
116
+ function scaffoldProject(targetRoot: string, force: boolean): boolean {
117
+ const sourceRoot = getTemplateRoot();
118
+
119
+ if (!isDirectoryUsableForScaffold(targetRoot, force)) {
120
+ console.error(chalk.red(`대상 폴더가 비어 있지 않습니다: ${targetRoot}`));
121
+ console.error(chalk.gray(' 빈 폴더에서 실행하거나 jccli init <folder> 를 사용해 주세요.'));
122
+ console.error(chalk.gray(' 기존 파일 위에 복사하려면 --force 옵션을 사용할 수 있습니다.'));
123
+ return false;
124
+ }
125
+
126
+ mkdirSync(targetRoot, { recursive: true });
127
+
128
+ const spinner = ora({
129
+ text: `프로젝트 파일 복사 중: ${targetRoot}`,
130
+ color: 'cyan',
131
+ }).start();
132
+
133
+ try {
134
+ copyDirectory(sourceRoot, targetRoot, sourceRoot);
135
+ spinner.succeed('프로젝트 파일 복사 완료');
136
+ return true;
137
+ } catch (error) {
138
+ spinner.fail('프로젝트 파일 복사 실패');
139
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
140
+ return false;
141
+ }
142
+ }
143
+
144
+ function getTemplateRoot(): string {
145
+ return path.resolve(__dirname, '..', '..', '..', '..');
146
+ }
147
+
148
+ function isDirectoryUsableForScaffold(targetRoot: string, force: boolean): boolean {
149
+ if (!existsSync(targetRoot)) return true;
150
+ if (!statSync(targetRoot).isDirectory()) return false;
151
+ if (force) return true;
152
+
153
+ return readdirSync(targetRoot).filter((entry) => entry !== '.DS_Store').length === 0;
154
+ }
155
+
156
+ function copyDirectory(sourceDir: string, targetDir: string, sourceRoot: string): void {
157
+ mkdirSync(targetDir, { recursive: true });
158
+
159
+ for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
160
+ const sourcePath = path.join(sourceDir, entry.name);
161
+ const targetPath = path.join(targetDir, entry.name);
162
+ const relativePath = path.relative(sourceRoot, sourcePath);
163
+
164
+ if (shouldSkipCopy(entry.name, relativePath)) continue;
165
+
166
+ if (entry.isDirectory()) {
167
+ copyDirectory(sourcePath, targetPath, sourceRoot);
168
+ continue;
169
+ }
170
+
171
+ if (entry.isFile()) {
172
+ copyFileSync(sourcePath, targetPath);
173
+ }
174
+ }
175
+ }
176
+
177
+ function shouldSkipCopy(entryName: string, relativePath: string): boolean {
178
+ if (COPY_EXCLUDES.has(entryName)) return true;
179
+ if (entryName.endsWith('.log')) return true;
180
+ if (entryName.endsWith('.tsbuildinfo')) return true;
181
+ if (/^\.env\..*\.local$/.test(entryName)) return true;
182
+
183
+ const normalized = path.normalize(relativePath);
184
+ return COPY_EXCLUDED_SUFFIXES.some((suffix) => normalized === suffix || normalized.startsWith(`${suffix}${path.sep}`));
185
+ }
186
+
187
+ function ensureRuntimeDirs(): void {
188
+ const home = os.homedir();
189
+
190
+ try {
191
+ for (const dir of RUNTIME_DIRS) {
192
+ mkdirSync(path.join(home, dir), { recursive: true });
193
+ }
194
+ console.log(chalk.green(`✓ 런타임 디렉터리 준비: ${path.join(home, '.ji')}`));
195
+ } catch (error) {
196
+ console.error(chalk.red('런타임 디렉터리를 만들 수 없습니다.'));
197
+ throw error;
198
+ }
199
+ }
200
+
201
+ function ensureDependencies(projectRoot: string, platform: SupportedPlatform): boolean {
202
+ if (existsSync(path.join(projectRoot, 'node_modules'))) {
203
+ console.log(chalk.green('✓ 프로젝트 의존성 설치 확인: node_modules 감지\n'));
204
+ return true;
205
+ }
206
+
207
+ const spinner = ora({
208
+ text: '프로젝트 의존성 설치 중...',
209
+ color: 'cyan',
210
+ }).start();
211
+ const result = runNpmInstall(projectRoot, platform);
212
+
213
+ if (result.ok) {
214
+ spinner.succeed('프로젝트 의존성 설치 완료');
215
+ console.log();
216
+ return true;
217
+ }
218
+
219
+ spinner.fail('프로젝트 의존성 설치 실패');
220
+ if (result.stderr) console.error(chalk.red(result.stderr));
221
+ if (result.error) console.error(chalk.red(result.error));
222
+ console.error(chalk.yellow('\n수동 설치 명령:'));
223
+ console.error(chalk.gray(` cd ${projectRoot}`));
224
+ console.error(chalk.gray(' npm install\n'));
225
+ return false;
226
+ }
227
+
228
+ function runNpmInstall(projectRoot: string, platform: SupportedPlatform): CommandResult {
229
+ try {
230
+ const result = spawnSync(getNpmCommand(platform), ['install'], {
231
+ cwd: projectRoot,
232
+ stdio: ['inherit', 'pipe', 'pipe'],
233
+ encoding: 'utf8',
234
+ shell: platform === 'windows',
235
+ windowsHide: true,
236
+ });
237
+
238
+ return {
239
+ ok: result.status === 0 && !result.error,
240
+ stdout: result.stdout?.trim() ?? '',
241
+ stderr: result.stderr?.trim() ?? '',
242
+ error: result.error?.message,
243
+ };
244
+ } catch (error) {
245
+ return {
246
+ ok: false,
247
+ stdout: '',
248
+ stderr: '',
249
+ error: error instanceof Error ? error.message : 'Unknown npm install error',
250
+ };
251
+ }
252
+ }
@@ -0,0 +1,123 @@
1
+ import * as http from 'http';
2
+ import * as net from 'net';
3
+
4
+ export interface ProxyServerOptions {
5
+ port: number;
6
+ serverPort: number;
7
+ webPort: number;
8
+ }
9
+
10
+ // NestJS 컨트롤러/게이트웨이가 사용하는 경로 prefix. 나머지는 모두 Next.js로 보낸다.
11
+ const BACKEND_PREFIXES = [
12
+ 'agents',
13
+ 'tasks',
14
+ 'fs',
15
+ 'harness',
16
+ 'sessions',
17
+ 'conversations',
18
+ 'docs',
19
+ 'docs-json',
20
+ 'socket.io',
21
+ ];
22
+
23
+ function isBackendPath(url: string): boolean {
24
+ const pathname = url.split('?')[0] ?? '';
25
+ return BACKEND_PREFIXES.some(
26
+ (prefix) => pathname === `/${prefix}` || pathname.startsWith(`/${prefix}/`),
27
+ );
28
+ }
29
+
30
+ export function startProxyServer(options: ProxyServerOptions): Promise<http.Server> {
31
+ const { port, serverPort, webPort } = options;
32
+
33
+ const resolveTargetPort = (url: string | undefined): number =>
34
+ isBackendPath(url ?? '/') ? serverPort : webPort;
35
+
36
+ const server = http.createServer((req, res) => {
37
+ try {
38
+ const targetPort = resolveTargetPort(req.url);
39
+ const proxyReq = http.request(
40
+ {
41
+ host: '127.0.0.1',
42
+ port: targetPort,
43
+ path: req.url,
44
+ method: req.method,
45
+ headers: req.headers,
46
+ },
47
+ (proxyRes) => {
48
+ res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
49
+ proxyRes.pipe(res);
50
+ },
51
+ );
52
+
53
+ proxyReq.on('error', () => {
54
+ try {
55
+ if (!res.headersSent) {
56
+ res.writeHead(502, { 'content-type': 'text/plain; charset=utf-8' });
57
+ }
58
+ res.end('업스트림 서버가 아직 준비되지 않았습니다. 잠시 후 새로고침해 주세요.');
59
+ } catch {
60
+ res.destroy();
61
+ }
62
+ });
63
+
64
+ req.pipe(proxyReq);
65
+ } catch {
66
+ res.destroy();
67
+ }
68
+ });
69
+
70
+ // WebSocket(socket.io, Next.js HMR) 업그레이드 요청을 원본 헤더 그대로 터널링한다.
71
+ server.on('upgrade', (req, socket, head) => {
72
+ try {
73
+ const targetPort = resolveTargetPort(req.url);
74
+ const upstream = net.connect(targetPort, '127.0.0.1', () => {
75
+ const headerLines = [`${req.method} ${req.url} HTTP/1.1`];
76
+ for (let i = 0; i < req.rawHeaders.length; i += 2) {
77
+ headerLines.push(`${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}`);
78
+ }
79
+ upstream.write(`${headerLines.join('\r\n')}\r\n\r\n`);
80
+ if (head.length > 0) upstream.write(head);
81
+ socket.pipe(upstream).pipe(socket);
82
+ });
83
+
84
+ upstream.on('error', () => socket.destroy());
85
+ socket.on('error', () => upstream.destroy());
86
+ } catch {
87
+ socket.destroy();
88
+ }
89
+ });
90
+
91
+ return new Promise((resolve, reject) => {
92
+ server.once('error', reject);
93
+ server.listen(port, () => {
94
+ server.removeListener('error', reject);
95
+ resolve(server);
96
+ });
97
+ });
98
+ }
99
+
100
+ export function waitForPort(port: number, timeoutMs: number): Promise<boolean> {
101
+ const deadline = Date.now() + timeoutMs;
102
+
103
+ return new Promise((resolve) => {
104
+ const tryConnect = (): void => {
105
+ const socket = net.connect(port, '127.0.0.1');
106
+
107
+ socket.once('connect', () => {
108
+ socket.destroy();
109
+ resolve(true);
110
+ });
111
+ socket.once('error', () => {
112
+ socket.destroy();
113
+ if (Date.now() > deadline) {
114
+ resolve(false);
115
+ return;
116
+ }
117
+ setTimeout(tryConnect, 500);
118
+ });
119
+ };
120
+
121
+ tryConnect();
122
+ });
123
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "@ji/typescript-config/base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "module": "CommonJS",
7
+ "moduleResolution": "Node",
8
+ "target": "ES2022",
9
+ "declaration": true,
10
+ "esModuleInterop": true
11
+ },
12
+ "include": ["src/**/*"],
13
+ "exclude": ["node_modules", "dist"]
14
+ }
@@ -0,0 +1,31 @@
1
+ import eslintConfigPrettier from "eslint-config-prettier";
2
+ import eslintPluginPrettier from "eslint-plugin-prettier";
3
+ import importPlugin from "eslint-plugin-import";
4
+
5
+ /** @type {import('eslint').Linter.Config[]} */
6
+ export const baseConfig = [
7
+ {
8
+ plugins: {
9
+ prettier: eslintPluginPrettier,
10
+ import: importPlugin,
11
+ },
12
+ rules: {
13
+ "prettier/prettier": "error",
14
+
15
+ "import/order": [
16
+ "error",
17
+ {
18
+ groups: ["builtin", "external", "internal", "parent", "sibling", "index"],
19
+ "newlines-between": "always",
20
+ alphabetize: { order: "asc", caseInsensitive: true },
21
+ },
22
+ ],
23
+ "import/no-duplicates": "error",
24
+
25
+ "prefer-const": "error",
26
+ "no-var": "error",
27
+ "no-console": ["warn", { allow: ["warn", "error"] }],
28
+ },
29
+ },
30
+ eslintConfigPrettier,
31
+ ];
@@ -0,0 +1,55 @@
1
+ import eslint from "@eslint/js";
2
+ import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
3
+ import globals from "globals";
4
+ import tseslint from "typescript-eslint";
5
+ import importPlugin from "eslint-plugin-import";
6
+ import eslintConfigPrettier from "eslint-config-prettier";
7
+
8
+ /** @type {import('typescript-eslint').ConfigArray} */
9
+ export default tseslint.config(
10
+ {
11
+ ignores: ["eslint.config.mjs", "dist/**", "node_modules/**"],
12
+ },
13
+ eslint.configs.recommended,
14
+ ...tseslint.configs.recommendedTypeChecked,
15
+ eslintPluginPrettierRecommended,
16
+ {
17
+ plugins: { import: importPlugin },
18
+ languageOptions: {
19
+ globals: { ...globals.node, ...globals.jest },
20
+ sourceType: "commonjs",
21
+ parserOptions: {
22
+ projectService: true,
23
+ tsconfigRootDir: import.meta.dirname,
24
+ },
25
+ },
26
+ },
27
+ {
28
+ rules: {
29
+ "prettier/prettier": ["error", { endOfLine: "auto" }],
30
+
31
+ "@typescript-eslint/no-explicit-any": "warn",
32
+ "@typescript-eslint/no-floating-promises": "error",
33
+ "@typescript-eslint/no-unsafe-argument": "warn",
34
+ "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
35
+ "@typescript-eslint/consistent-type-imports": ["error", { prefer: "type-imports" }],
36
+ "@typescript-eslint/explicit-function-return-type": "off",
37
+ "@typescript-eslint/explicit-module-boundary-types": "off",
38
+
39
+ "import/order": [
40
+ "error",
41
+ {
42
+ groups: ["builtin", "external", "internal", "parent", "sibling", "index"],
43
+ "newlines-between": "always",
44
+ alphabetize: { order: "asc", caseInsensitive: true },
45
+ },
46
+ ],
47
+ "import/no-duplicates": "error",
48
+
49
+ "no-console": ["warn", { allow: ["warn", "error", "log"] }],
50
+ "prefer-const": "error",
51
+ "no-var": "error",
52
+ },
53
+ },
54
+ eslintConfigPrettier,
55
+ );
@@ -0,0 +1,23 @@
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTs from "eslint-config-next/typescript";
4
+ import { baseConfig } from "./base.mjs";
5
+
6
+ /** @type {import('eslint').Linter.Config[]} */
7
+ export default defineConfig([
8
+ ...nextVitals,
9
+ ...nextTs,
10
+ globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts", "node_modules/**"]),
11
+ ...baseConfig,
12
+ {
13
+ rules: {
14
+ "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
15
+ "@typescript-eslint/no-explicit-any": "warn",
16
+ "@typescript-eslint/consistent-type-imports": ["error", { prefer: "type-imports" }],
17
+
18
+ "react/self-closing-comp": "error",
19
+ "react/jsx-curly-brace-presence": ["error", { props: "never", children: "never" }],
20
+ "react/display-name": "off",
21
+ },
22
+ },
23
+ ]);
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@ji/eslint-config",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "license": "UNLICENSED",
6
+ "exports": {
7
+ "./base": "./base.mjs",
8
+ "./next": "./next.mjs",
9
+ "./nest": "./nest.mjs"
10
+ },
11
+ "devDependencies": {
12
+ "@eslint/js": "^9.18.0",
13
+ "@typescript-eslint/eslint-plugin": "^8.59.1",
14
+ "@typescript-eslint/parser": "^8.59.1",
15
+ "eslint-config-prettier": "^10.1.8",
16
+ "eslint-plugin-import": "^2.32.0",
17
+ "eslint-plugin-prettier": "^5.5.5",
18
+ "typescript-eslint": "^8.20.0"
19
+ }
20
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "target": "ES2023",
5
+ "lib": ["ES2023"],
6
+ "esModuleInterop": true,
7
+ "allowSyntheticDefaultImports": true,
8
+ "skipLibCheck": true,
9
+ "strict": true,
10
+ "noImplicitAny": true,
11
+ "strictNullChecks": true,
12
+ "strictBindCallApply": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "noFallthroughCasesInSwitch": true
15
+ }
16
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "./base.json",
4
+ "compilerOptions": {
5
+ "module": "nodenext",
6
+ "moduleResolution": "nodenext",
7
+ "resolvePackageJsonExports": true,
8
+ "declaration": true,
9
+ "removeComments": true,
10
+ "emitDecoratorMetadata": true,
11
+ "experimentalDecorators": true,
12
+ "sourceMap": true,
13
+ "outDir": "./dist",
14
+ "baseUrl": "./",
15
+ "incremental": true
16
+ }
17
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "./base.json",
4
+ "compilerOptions": {
5
+ "module": "esnext",
6
+ "moduleResolution": "bundler",
7
+ "allowJs": true,
8
+ "jsx": "preserve",
9
+ "incremental": true,
10
+ "plugins": [{ "name": "next" }],
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "noEmit": true
14
+ }
15
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "@ji/typescript-config",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "license": "UNLICENSED",
6
+ "exports": {
7
+ "./base.json": "./base.json",
8
+ "./nextjs.json": "./nextjs.json",
9
+ "./nestjs.json": "./nestjs.json"
10
+ }
11
+ }
package/turbo.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "https://turbo.build/schema.json",
3
+ "ui": "tui",
4
+ "tasks": {
5
+ "build": {
6
+ "dependsOn": ["^build"],
7
+ "inputs": ["$TURBO_DEFAULT$", ".env*"],
8
+ "outputs": [".next/**", "!.next/cache/**", "dist/**"]
9
+ },
10
+ "dev": {
11
+ "cache": false,
12
+ "persistent": true
13
+ },
14
+ "lint": {
15
+ "dependsOn": ["^build"]
16
+ },
17
+ "lint:fix": {
18
+ "cache": false
19
+ },
20
+ "test": {
21
+ "dependsOn": ["^build"],
22
+ "outputs": ["coverage/**"]
23
+ },
24
+ "clean": {
25
+ "cache": false
26
+ }
27
+ }
28
+ }