remote-coder 0.4.1__py3-none-any.whl

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 (78) hide show
  1. app/__init__.py +3 -0
  2. app/admin/__init__.py +0 -0
  3. app/admin/advanced_settings.py +88 -0
  4. app/admin/database_browser.py +301 -0
  5. app/admin/router.py +528 -0
  6. app/admin/static/i18n.js +401 -0
  7. app/admin/static/icons/advanced.svg +8 -0
  8. app/admin/static/icons/database.svg +5 -0
  9. app/admin/static/icons/download.svg +3 -0
  10. app/admin/static/icons/home.svg +4 -0
  11. app/admin/static/icons/logs.svg +3 -0
  12. app/admin/static/icons/projects.svg +5 -0
  13. app/admin/static/summary.js +73 -0
  14. app/admin/templates/admin.html +511 -0
  15. app/admin/templates/advanced.html +635 -0
  16. app/admin/templates/database.html +880 -0
  17. app/admin/templates/logs.html +686 -0
  18. app/admin/templates/projects.html +878 -0
  19. app/ai/__init__.py +0 -0
  20. app/ai/base.py +129 -0
  21. app/ai/claude.py +20 -0
  22. app/ai/codex.py +34 -0
  23. app/ai/factory.py +27 -0
  24. app/ai/gemini.py +20 -0
  25. app/ai/model_catalog.py +47 -0
  26. app/ai/usage.py +134 -0
  27. app/cli.py +238 -0
  28. app/config.py +130 -0
  29. app/git/__init__.py +0 -0
  30. app/git/ai_commit.py +88 -0
  31. app/git/branch_naming.py +21 -0
  32. app/git/commit_message.py +279 -0
  33. app/git/service.py +669 -0
  34. app/jobs/__init__.py +0 -0
  35. app/jobs/manager.py +770 -0
  36. app/jobs/schemas.py +116 -0
  37. app/jobs/store.py +334 -0
  38. app/main.py +265 -0
  39. app/models.py +20 -0
  40. app/monitoring/__init__.py +10 -0
  41. app/monitoring/code.py +161 -0
  42. app/monitoring/events.py +33 -0
  43. app/monitoring/git.py +103 -0
  44. app/monitoring/log_buffer.py +245 -0
  45. app/monitoring/memory.py +19 -0
  46. app/monitoring/model.py +598 -0
  47. app/projects/__init__.py +19 -0
  48. app/projects/registry.py +384 -0
  49. app/security/__init__.py +0 -0
  50. app/security/auth.py +19 -0
  51. app/system_startup.py +34 -0
  52. app/telegram/__init__.py +0 -0
  53. app/telegram/bot_instances.py +67 -0
  54. app/telegram/commands/__init__.py +64 -0
  55. app/telegram/commands/base.py +222 -0
  56. app/telegram/commands/branch.py +366 -0
  57. app/telegram/commands/clear_stop.py +221 -0
  58. app/telegram/commands/fix.py +219 -0
  59. app/telegram/commands/model.py +93 -0
  60. app/telegram/commands/monitor.py +185 -0
  61. app/telegram/commands/registry.py +110 -0
  62. app/telegram/commands/status.py +243 -0
  63. app/telegram/commands/system.py +201 -0
  64. app/telegram/confirmations.py +36 -0
  65. app/telegram/conversation.py +789 -0
  66. app/telegram/i18n.py +742 -0
  67. app/telegram/model_preferences.py +53 -0
  68. app/telegram/notifier.py +387 -0
  69. app/telegram/parser.py +267 -0
  70. app/telegram/webhook.py +988 -0
  71. app/telegram/webhook_registration.py +172 -0
  72. app/tunnel.py +104 -0
  73. remote_coder-0.4.1.dist-info/METADATA +520 -0
  74. remote_coder-0.4.1.dist-info/RECORD +78 -0
  75. remote_coder-0.4.1.dist-info/WHEEL +5 -0
  76. remote_coder-0.4.1.dist-info/entry_points.txt +2 -0
  77. remote_coder-0.4.1.dist-info/licenses/LICENSE +201 -0
  78. remote_coder-0.4.1.dist-info/top_level.txt +1 -0
app/telegram/i18n.py ADDED
@@ -0,0 +1,742 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+
6
+ from app.models import UiLanguage
7
+
8
+
9
+ class UiMessage(str):
10
+ def __new__(
11
+ cls,
12
+ key: str,
13
+ default_template: str,
14
+ params: dict[str, object] | None = None,
15
+ ) -> "UiMessage":
16
+ params = dict(params or {})
17
+ default = _format_template(default_template, params, UiLanguage.ENGLISH)
18
+ obj = str.__new__(cls, default)
19
+ obj.key = key
20
+ obj.default_template = default_template
21
+ obj.params = params
22
+ return obj
23
+
24
+ key: str
25
+ default_template: str
26
+ params: dict[str, object]
27
+
28
+ def render(self, language: UiLanguage) -> str:
29
+ if language is UiLanguage.ENGLISH:
30
+ return str(self)
31
+ template = _MESSAGE_CATALOG_KO.get(self.key, self.default_template)
32
+ return _format_template(template, self.params, language)
33
+
34
+
35
+ class _SafeFormatDict(dict):
36
+ def __missing__(self, key: str) -> str:
37
+ return "{" + key + "}"
38
+
39
+
40
+ def _format_template(template: str, params: dict[str, object], language: UiLanguage) -> str:
41
+ escaped_params = {
42
+ key: value.render(language) if isinstance(value, UiMessage) else str(value)
43
+ for key, value in params.items()
44
+ }
45
+ return template.format_map(_SafeFormatDict(escaped_params))
46
+
47
+
48
+ def ui_message(key: str, default_template: str, **params: object) -> UiMessage:
49
+ return UiMessage(key, default_template, params)
50
+
51
+
52
+ def language_from_settings_store(settings_store) -> UiLanguage:
53
+ if settings_store is None:
54
+ return UiLanguage.ENGLISH
55
+ return settings_store.get().ui_language
56
+
57
+
58
+ @dataclass(frozen=True, slots=True)
59
+ class InstructionFrameLabels:
60
+ prev_context_open: str
61
+ prev_context_close: str
62
+ current_request_open: str
63
+ current_request_close: str
64
+ reply_job_open: str
65
+ reply_job_close: str
66
+ reply_chain_open: str
67
+ reply_chain_close: str
68
+ reply_message_open: str
69
+ reply_message_close: str
70
+ none_absent: str
71
+
72
+
73
+ def instruction_frame_labels(language: UiLanguage) -> InstructionFrameLabels:
74
+ if language is UiLanguage.KOREAN:
75
+ return InstructionFrameLabels(
76
+ prev_context_open="[이전 대화/작업 맥락]",
77
+ prev_context_close="[/이전 대화]",
78
+ current_request_open="[현재 요청]",
79
+ current_request_close="[/현재 요청]",
80
+ reply_job_open="[Reply Job 맥락]",
81
+ reply_job_close="[/Reply Job 맥락]",
82
+ reply_chain_open="[Reply 체인 맥락]",
83
+ reply_chain_close="[/Reply 체인 맥락]",
84
+ reply_message_open="[Reply 메시지 맥락]",
85
+ reply_message_close="[/Reply 메시지 맥락]",
86
+ none_absent="(없음)",
87
+ )
88
+ return InstructionFrameLabels(
89
+ prev_context_open="[Previous conversation/job context]",
90
+ prev_context_close="[/previous context block]",
91
+ current_request_open="[Current request]",
92
+ current_request_close="[/current request]",
93
+ reply_job_open="[Reply job context]",
94
+ reply_job_close="[/Reply job context]",
95
+ reply_chain_open="[Reply chain context]",
96
+ reply_chain_close="[/Reply chain context]",
97
+ reply_message_open="[Reply message context]",
98
+ reply_message_close="[/Reply message context]",
99
+ none_absent="(none)",
100
+ )
101
+
102
+
103
+ _GIT_BRANCH_VALIDATION_EN_TO_KO = {
104
+ "Branch name is empty or too long.": "브랜치 이름이 비었거나 너무 깁니다.",
105
+ "Branch name is not allowed.": "허용되지 않는 브랜치 이름입니다.",
106
+ "Branch names may only use letters, numbers, /, ., _, and -.": (
107
+ "브랜치 이름은 영문, 숫자, /, ., _, - 만 사용할 수 있습니다."
108
+ ),
109
+ }
110
+
111
+
112
+ def localize_git_branch_validation_message(message: str, language: UiLanguage) -> str:
113
+ if language is UiLanguage.ENGLISH:
114
+ return message
115
+ return _GIT_BRANCH_VALIDATION_EN_TO_KO.get(message, message)
116
+
117
+
118
+ def command_parse_error_empty_instruction_plan_ask(language: UiLanguage) -> str:
119
+ if language is UiLanguage.KOREAN:
120
+ return (
121
+ "작업 지시문이 비어 있습니다.\n\n"
122
+ "예: plan: 로그인 수정 계획 세워줘\n"
123
+ "예: /ask JobManager 흐름 설명해줘"
124
+ )
125
+ return (
126
+ "The work instruction is empty.\n\n"
127
+ "Example: plan: outline the login refactor\n"
128
+ "Example: /ask explain JobManager routing"
129
+ )
130
+
131
+
132
+ def command_parse_error_empty_instruction(language: UiLanguage) -> str:
133
+ return (
134
+ "작업 지시문이 비어 있습니다."
135
+ if language is UiLanguage.KOREAN
136
+ else "The work instruction is empty."
137
+ )
138
+
139
+
140
+ def command_parse_error_unknown_project(project_name: str, language: UiLanguage) -> str:
141
+ if language is UiLanguage.KOREAN:
142
+ return f"알 수 없는 프로젝트: {project_name}"
143
+ return f"Unknown project: {project_name}"
144
+
145
+
146
+ def command_parse_error_disabled_project(project_name: str, language: UiLanguage) -> str:
147
+ if language is UiLanguage.KOREAN:
148
+ return f"비활성화된 프로젝트: {project_name}"
149
+ return f"Disabled project: {project_name}"
150
+
151
+
152
+ def command_parse_error_no_previous_job_context(language: UiLanguage) -> str:
153
+ return (
154
+ "이전 작업 맥락이 없습니다. 구체적인 작업 지시를 보내주세요."
155
+ if language is UiLanguage.KOREAN
156
+ else "There is no previous job context. Send a specific work instruction."
157
+ )
158
+
159
+
160
+ HELP_MAIN_EN = "\n".join(
161
+ [
162
+ "Help",
163
+ "",
164
+ "Send work requests as regular messages.",
165
+ "",
166
+ "Options",
167
+ "- model:",
168
+ "- branch:",
169
+ "- no commit",
170
+ "- plan: <natural language> or /plan <natural language> - plan mode (plan only; no code changes)",
171
+ "- ask: <natural language> or /ask <natural language> - ask mode (analysis and answers; no code edits)",
172
+ "- Korean aliases 계획: and 질문: instead of plan:/ask: (colons `:` or full-width `:` allowed)",
173
+ "",
174
+ "Commands:",
175
+ "- /model <claude|codex|gemini>: Change the default model",
176
+ "- /status <job_id>: Check job status",
177
+ "- /branch [name]: Show or switch branches",
178
+ "- /pull: Pull all remote branch updates",
179
+ "- /rebase [branch]: Rebase a branch",
180
+ "- /pr [branch]: Open a GitHub PR for a branch",
181
+ "- /monitor <model|memory|branch|worktrees|code|project>: Monitoring",
182
+ "- /clear <branch|worktrees|memory>: Cleanup (confirmation required)",
183
+ "- /reports [count]: Conversation memory report",
184
+ "- /init: Reset this chat's settings",
185
+ "- /stop <job_id>: Stop a running job",
186
+ "- /fix <commit|source> [job_id]: Re-do a job's commit/source (amend + force-with-lease push)",
187
+ "- /start: Inline menu",
188
+ ]
189
+ )
190
+
191
+ HELP_AGENT_TOPIC_EN = "\n".join(
192
+ [
193
+ "AGENTS mode (agent)",
194
+ "",
195
+ "Natural-language coding tasks. The agent can modify code in the current project; when there are "
196
+ "changes it can create or update a branch, commit, and push.",
197
+ "",
198
+ "Examples",
199
+ "- fix the login validation bug",
200
+ "- model: codex branch: remote-auth strengthen tests",
201
+ "- no commit just verify the doc wording",
202
+ "",
203
+ "A job is accepted after project/branch/model checks via `y`/`Y` or inline buttons.",
204
+ ]
205
+ )
206
+
207
+ HELP_PLAN_TOPIC_EN = "\n".join(
208
+ [
209
+ "Plan mode (plan)",
210
+ "",
211
+ "Receive change plans only; no code edits. Like agent mode, a job is accepted after confirmation "
212
+ "(`y`/`Y` or inline buttons).",
213
+ "",
214
+ "Examples",
215
+ "- plan: summarize the login validation flow",
216
+ "- /plan model: codex list only API boundary risks",
217
+ "- 계획:refactor steps (full-width colon)",
218
+ "",
219
+ "See /help for more options.",
220
+ ]
221
+ )
222
+
223
+ HELP_ASK_TOPIC_EN = "\n".join(
224
+ [
225
+ "Ask mode (ask)",
226
+ "",
227
+ "Answer questions using the repository; no code edits, commits, or pushes. Jobs are accepted like "
228
+ "agent mode after confirmation (`y`/`Y` or inline buttons).",
229
+ "",
230
+ "Examples",
231
+ "- ask: how do I run pytest in this project?",
232
+ "- /ask explain JobManager.run stages",
233
+ "- 질문:what this error line means",
234
+ "",
235
+ "See /help for more options.",
236
+ ]
237
+ )
238
+
239
+
240
+ _TEXT_REPLACEMENTS_KO_TO_EN_RAW: tuple[tuple[str, str], ...] = (
241
+ ("도움말", "Help"),
242
+ ("작업 지시는 일반 메시지로 보내세요.", "Send work requests as regular messages."),
243
+ ("옵션", "Options"),
244
+ ("계획 모드", "plan mode"),
245
+ ("질문 모드", "ask mode"),
246
+ ("명령어 목록:", "Commands:"),
247
+ ("메뉴와 프로젝트 상태를 확인합니다", "Show the menu and project status"),
248
+ ("사용 가능한 명령어를 확인합니다", "Show available commands"),
249
+ ("채팅의 기본 AI 모델을 확인하거나 변경합니다", "Show or change this chat's default AI model"),
250
+ ("최근 Job 목록과 작업 상태를 조회합니다", "Show recent jobs and job status"),
251
+ ("모델 설정과 확인 대기 상태를 초기화합니다", "Reset model settings and pending confirmations"),
252
+ ("현재 채팅의 대화 기억 요약을 조회합니다", "Show this chat's conversation memory summary"),
253
+ ("현재 브랜치를 확인하거나 로컬 브랜치로 전환합니다", "Show the current branch or switch to a local branch"),
254
+ ("브랜치를 main 기준으로 rebase하고 push합니다", "Rebase a branch onto main and push it"),
255
+ ("원격 브랜치 정보를 가져오고 현재 브랜치를 pull합니다", "Fetch remote branches and pull the current branch"),
256
+ ("선택한 브랜치로 GitHub Pull Request를 만듭니다", "Create a GitHub Pull Request for a selected branch"),
257
+ ("모델, 메모리, 브랜치, worktree 상태를 점검합니다", "Check model, memory, branch, and worktree status"),
258
+ ("브랜치, worktree, 대화 기억을 확인 후 정리합니다", "Clean branches, worktrees, or conversation memory after confirmation"),
259
+ ("진행 중인 Job을 선택해 중단합니다", "Choose and stop a running job"),
260
+ ("이전 Job의 커밋 또는 소스를 다시 수정합니다", "Re-do the commit or source of a previous job"),
261
+ ("수정 대상을 선택하세요.", "Choose what to fix."),
262
+ ("수정할 항목을 선택하세요.", "Choose what to fix."),
263
+ ("수정 대상 Job을 선택하세요.", "Choose a job to fix."),
264
+ ("수정 가능한 Job이 없습니다.", "No job is available to fix."),
265
+ ("수정 기능을 사용할 수 없습니다.", "Fix feature is not available."),
266
+ ("커밋 메시지 재생성 미리보기", "Commit message preview"),
267
+ ("적용하려면 y/Y, 취소하려면 n/N (또는 버튼).", "Send y/Y to apply, or n/N to cancel (or use buttons)."),
268
+ ("커밋 메시지 수정을 취소했습니다.", "Cancelled the commit message fix."),
269
+ ("커밋 메시지를 수정했습니다.", "Commit message updated."),
270
+ ("커밋 메시지 수정 실패:", "Commit message fix failed:"),
271
+ ("수정 대상으로 사용할 수 없는 Job입니다", "Job cannot be used as a fix target"),
272
+ ("수정 대상 Job을 더 이상 사용할 수 없습니다.", "Fix target job is no longer available."),
273
+ ("수정 작업을 백그라운드로 시작했습니다.", "Started the fix job in the background."),
274
+ ("수정 작업을 취소했습니다.", "Cancelled the fix job."),
275
+ ("수정 작업을 확인하세요.", "Confirm the fix job."),
276
+ ("대상 Job", "Target Job"),
277
+ ("원본 커밋", "Original commit"),
278
+ ("기존 커밋을 amend 후 --force-with-lease push", "amends the existing commit and pushes with --force-with-lease"),
279
+ ("커밋 수정 (commit)", "Fix commit"),
280
+ ("소스 수정 (source)", "Fix source"),
281
+ ("브랜치:", "Branch:"),
282
+ (
283
+ "에 대한 수정 지시를 보내주세요. 다음 메시지를 그대로 지시로 사용합니다.",
284
+ ": send the fix instruction. The next message will be used as the instruction.",
285
+ ),
286
+ ("계획 모드 메시지", "plan mode message"),
287
+ ("질문 모드 메시지", "ask mode message"),
288
+ ("로그인 흐름 검토", "review login flow"),
289
+ ("역할 설명", "explain the role"),
290
+ ("기본 모델 변경", "Change the default model"),
291
+ ("작업 상태 확인", "Check job status"),
292
+ ("브랜치 조회 또는 전환", "Show or switch branches"),
293
+ ("원격 저장소의 모든 브랜치 pull", "Pull all remote branch updates"),
294
+ ("브랜치 리베이스", "Rebase a branch"),
295
+ ("브랜치를 GitHub PR로 올리기", "Open a GitHub PR for a branch"),
296
+ ("모니터링", "Monitoring"),
297
+ ("정리 (확인 필요)", "Cleanup (confirmation required)"),
298
+ ("대화 기억 리포트", "Conversation memory report"),
299
+ ("이 채팅 설정 초기화", "Reset this chat's settings"),
300
+ ("진행 중인 작업 중단", "Stop a running job"),
301
+ ("인라인 메뉴", "Inline menu"),
302
+ ("모델을 선택하세요.", "Choose a model."),
303
+ ("모델 Provider가 선택되었습니다.", "Model provider selected."),
304
+ ("세부 Model을 선택하세요.", "Choose a specific model."),
305
+ ("알 수 없는 세부 Model입니다", "Unknown specific model"),
306
+ ("모델 설정이 변경되었습니다.", "Model setting updated."),
307
+ ("모델 설정", "Model settings"),
308
+ ("현재 기본 모델", "Current default model"),
309
+ ("기본 모델을", "Default model changed to"),
310
+ ("로 변경했습니다.", "."),
311
+ ("사용법", "Usage"),
312
+ ("조회할 Job을 선택하세요.", "Choose a job to inspect."),
313
+ ("조회할 수 있는 Job이 없습니다.", "No jobs are available."),
314
+ ("해당 Job ID를 찾을 수 없습니다.", "Job ID not found."),
315
+ ("상태", "Status"),
316
+ ("프로젝트", "Project"),
317
+ ("요청 모델", "Requested model"),
318
+ ("사용 모델", "Model used"),
319
+ ("- 모드", "- Mode"),
320
+ ("토큰 사용량", "Token usage"),
321
+ ("확인 불가", "unavailable"),
322
+ ("작업 접수 완료", "Job accepted"),
323
+ ("작업 완료", "Job completed"),
324
+ ("작업 실패", "Job failed"),
325
+ ("작업 중단됨", "Job cancelled"),
326
+ ("응답 완료", "Completed"),
327
+ ("- 시작:", "- Started:"),
328
+ ("→ 완료:", "→ Finished:"),
329
+ ("(소요:", "(duration:"),
330
+ ("(경과:", "(elapsed:"),
331
+ ("- 생성:", "- Created:"),
332
+ ("커밋", "Commit"),
333
+ ("변경 파일", "Changed files"),
334
+ ("없음 (no-op)", "none (no-op)"),
335
+ ("오류 단계", "Error stage"),
336
+ ("실패 단계", "Failure stage"),
337
+ ("로그 경로", "Log path"),
338
+ ("실패 출력 요약", "Failure output summary"),
339
+ ("오류", "Error"),
340
+ ("현재 출력", "Current output"),
341
+ ("AI 출력 요약", "AI output summary"),
342
+ ("AI 응답", "AI response"),
343
+ ("Remote AI Coder가 준비되었습니다.", "Remote AI Coder is ready."),
344
+ ("Remote AI Coder 서버가 시작되었습니다.", "Remote AI Coder server started."),
345
+ ("Remote AI Coder 서버 연결이 종료되었습니다.", "Remote AI Coder server connection closed."),
346
+ ("모델", "Model"),
347
+ ("Remote AI Coder에 오신 것을 환영합니다.", "Welcome to Remote AI Coder."),
348
+ ("등록 정보 없음", "not registered"),
349
+ ("확인 실패", "check failed"),
350
+ ("실행할 명령을 선택하세요.", "Choose a command."),
351
+ ("확인할 모드 안내를 선택하세요.", "Choose a mode guide."),
352
+ ("브랜치 확인", "Branch"),
353
+ ("리베이스", "Rebase"),
354
+ ("PR 올리기", "Open PR"),
355
+ ("중단", "Stop"),
356
+ ("초기화", "Reset"),
357
+ ("뒤로", "Back"),
358
+ ("모드별 안내", "Modes"),
359
+ ("관리", "Manage"),
360
+ ("리포트", "Reports"),
361
+ ("이 채팅의 기본 모델·확인 대기 상태를 초기화했습니다.", "This chat's default model and pending confirmation were reset."),
362
+ ("프로젝트 컨텍스트가 설정되지 않았습니다.", "No project context is configured."),
363
+ ("관리 화면에서 프로젝트 설정을 확인하세요.", "Check the project settings in the admin UI."),
364
+ ("관리 화면에서 활성화 상태를 확인하세요.", "Check the enabled state in the admin UI."),
365
+ ("적용 프로젝트", "Project"),
366
+ ("기본 모델", "Default model"),
367
+ ("기억 리포트", "Memory report"),
368
+ ("총 기록", "Total entries"),
369
+ ("사용자 요청", "User requests"),
370
+ ("Job 접수", "Jobs accepted"),
371
+ ("Job 결과", "Job results"),
372
+ ("최근 사용자 요청", "Latest user request"),
373
+ ("최근 Job 결과", "Latest job result"),
374
+ ("최근 기억", "Recent memory"),
375
+ ("기억된 대화 기록이 없습니다.", "No conversation memory is stored."),
376
+ ("등록된 프로젝트가 없습니다.", "No project is registered."),
377
+ ("프로젝트를 찾을 수 없거나 비활성화되어 있습니다", "Project not found or disabled"),
378
+ ("현재 브랜치", "Current branch"),
379
+ ("브랜치가 없습니다", "Branch not found"),
380
+ ("로컬에만 전환 가능합니다", "only local branches can be selected"),
381
+ ("로 전환했습니다 (git switch).", " selected (git switch)."),
382
+ (
383
+ "리베이스할 브랜치가 없습니다. /rebase <branch> 로 직접 지정할 수 있습니다.",
384
+ "No branch is available to rebase. Specify one with /rebase <branch>.",
385
+ ),
386
+ ("리베이스할 브랜치가 없습니다.", "No branch is available to rebase."),
387
+ ("리베이스할 브랜치를 선택하세요.", "Choose a branch to rebase."),
388
+ ("등록된 프로젝트가 없습니다. /projects 로 등록하세요.", "No project is registered. Add one in /projects."),
389
+ ("브라우저에서 http://127.0.0.1:8000/projects 로 프로젝트를 등록하세요.", "Register a project at http://127.0.0.1:8000/projects."),
390
+ ("원격 브랜치를", "remote branch on"),
391
+ ("에서 찾을 수 없습니다.", "was not found."),
392
+ ("이미 rebase/병합 후 삭제되었거나 아직 push되지 않은 브랜치일 수 있습니다.", "It may have already been rebased/merged and deleted, or not pushed yet."),
393
+ ("브랜치를 로컬과", "Deleted the branch locally and from"),
394
+ ("에서 삭제했습니다.", "."),
395
+ ("PR을 올릴 브랜치가 없습니다.", "No branch is available for PR creation."),
396
+ ("PR을 올릴 브랜치를 선택하세요.", "Choose a branch for the PR."),
397
+ ("PR이 생성되었습니다:", "PR created:"),
398
+ ("작업 브랜치", "Work branch"),
399
+ ("작업 요청", "Work request"),
400
+ ("작업 브랜치 확인 실패:", "Could not resolve work branch:"),
401
+ ("AI 결과", "AI result"),
402
+ ("확인할 모니터링 항목을 선택하세요.", "Choose a monitoring view."),
403
+ ("이 봇의 프로젝트 컨텍스트를 찾을 수 없습니다.", "No project context is bound to this bot."),
404
+ ("알 수 없는 프로젝트:", "Unknown project:"),
405
+ ("알 수 없는 프로젝트", "Unknown project"),
406
+ ("비활성화된 프로젝트:", "Disabled project:"),
407
+ ("비활성화된 프로젝트", "Disabled project"),
408
+ ("이 봇 프로젝트", "This bot project"),
409
+ ("대화 기억 저장소가 설정되지 않았습니다.", "Conversation memory storage is not configured."),
410
+ ("정리할 항목을 선택하세요. 실행 전 y/Y 확인이 필요합니다.", "Choose what to clean up. Confirmation with y/Y is required."),
411
+ ("기억 저장소가 설정되지 않았습니다.", "Memory storage is not configured."),
412
+ ("현재 할 작업을 확인하세요.", "Confirm the work to run."),
413
+ ("실행 여부를 선택하세요.", "Choose whether to run it."),
414
+ (
415
+ "실행하려면 `y` 또는 `Y`를 입력하세요. "
416
+ "새 자연어 요청으로 이 확인을 바꿀 수 있습니다. "
417
+ "파싱되지 않는 입력은 대기 작업이 취소됩니다.",
418
+ "Send `y` or `Y` to run. Another natural-language request replaces this confirmation. "
419
+ "Unparseable input cancels the pending request.",
420
+ ),
421
+ ("실행하려면 `y` 또는 `Y`를 입력하세요. 그 외 응답은 취소됩니다.", "Send `y` or `Y` to run it. Any other response cancels it."),
422
+ ("작업 요청을 취소했습니다.", "Cancelled the work request."),
423
+ ("알 수 없는 clear 작업입니다.", "Unknown clear action."),
424
+ ("봇에 연결된 프로젝트가 없거나 레지스트리에서 찾을 수 없습니다.", "No project is bound to this bot or found in the registry."),
425
+ ("프로젝트가 비활성화되어 있습니다", "Project is disabled"),
426
+ ("개 삭제", " deleted"),
427
+ ("봇에 연결된 프로젝트가 없습니다.", "No project is bound to this bot."),
428
+ ("이 채팅방의 대화 기억을 삭제했습니다.", "Deleted conversation memory for this chat."),
429
+ ("진행 중 Job이 없습니다.", "No running job is available."),
430
+ ("중단할 수 있는 진행 중 Job이 없습니다.", "No running job can be stopped."),
431
+ ("중단할 Job을 선택하세요.", "Choose a job to stop."),
432
+ ("작업 중단 기능을 사용할 수 없습니다.", "Job cancellation is not available."),
433
+ ("작업 중단 요청 완료", "Stop requested"),
434
+ ("Job을 찾을 수 없습니다", "Job not found"),
435
+ ("작업을 중단할 수 없습니다", "Cannot stop job"),
436
+ ("현재 상태", "current status"),
437
+ ("확인 대기 작업을 처리할 수 없습니다.", "Could not process the pending confirmation."),
438
+ ("확인 대기 작업이 없습니다.", "There is no pending confirmation."),
439
+ ("알 수 없는 명령어입니다. /help 를 확인하세요.", "Unknown command. See /help."),
440
+ ("변경 없음", "No changes"),
441
+ ("(스테이징된 변경 없음 — push 생략)", "(nothing staged — push skipped)"),
442
+ ("(no commit 옵션 — 커밋·push 생략)", "(no commit — commit/push skipped)"),
443
+ ("(없음 — 변경 없어 브랜치 미생성)", "(none — no branch; no changes)"),
444
+ ("미생성", "not created"),
445
+ ("통합 브랜치(main 또는 master)를 찾을 수 없습니다. 저장소에 main 또는 master가 있어야 합니다.", "No integration branch (main or master). The repository needs main or master."),
446
+ ("(detached HEAD — 브랜치 이름 없음)", "(detached HEAD — no branch name)"),
447
+ ("(로컬 브랜치 없음)", "(no local branches)"),
448
+ ("로컬 브랜치가 없습니다:", "No local branch:"),
449
+ ("plan 모드로 실행할 작업 지시문을 보내주세요.", "Send the instruction to run in plan mode."),
450
+ ("ask 모드로 실행할 질문을 보내주세요.", "Send the question to run in ask mode."),
451
+ ("읽기 전용 · 커밋·push 없음", "read-only — no commit/push"),
452
+ ("코드 수정·커밋·push 가능", "allows edit, commit, and push"),
453
+ ("요청 브랜치", "Requested branch"),
454
+ ("브랜치·커밋·push", "branch/commit/push"),
455
+ ("메모리(SQLite)", "Memory (SQLite)"),
456
+ ("프로젝트:", "Project:"),
457
+ ("DB 경로:", "DB path:"),
458
+ ("이 채팅 저장 행 수:", "Rows for this chat:"),
459
+ ("역할별 행 수:", "Rows by role:"),
460
+ ("브랜치 모니터", "Branch monitor"),
461
+ ("원격 이름:", "Remote name:"),
462
+ ("현재 checkout:", "Current checkout:"),
463
+ ("로컬 브랜치 수:", "Local branch count:"),
464
+ ("원격 추적 브랜치 수:", "Tracked remote branches:"),
465
+ ("[로컬]", "[Local]"),
466
+ ("메시지 길이 제한으로 생략)", "truncated for message length)"),
467
+ ("/monitor branch 실패:", "/monitor branch failed:"),
468
+ ("/monitor worktrees 실패:", "/monitor worktrees failed:"),
469
+ ("워크트리 모니터", "Worktree monitor"),
470
+ ("관리 기준 디렉터리(worktree_base):", "Managed base (worktree_base):"),
471
+ ("총 worktree 수:", "Total worktrees:"),
472
+ ("managed 후보 수(remote-*·base·_rebase_ops):", "managed candidates (remote-*, base, _rebase_ops):"),
473
+ ("[항목]", "[Entries]"),
474
+ ("개 생략)", " omitted)"),
475
+ ("코드 규모(추정)", "Code size (estimated)"),
476
+ ("스캔한 코드 파일 수:", "Code files scanned:"),
477
+ ("합계 줄 수(대략):", "Approx. total lines:"),
478
+ ("건너뜀(바이너리/읽기 오류):", "Skipped (binary/read error):"),
479
+ ("참고: 확장자 기준 텍스트 파일만 포함합니다. 대용량 저장소에서는 상한에 도달하면 일부만 집계됩니다.", "Note: only text-like extensions included; large repos may hit scan caps."),
480
+ ("최근 Job 사용량", "Recent job usage"),
481
+ ("이 채팅/프로젝트/모델로 완료되거나 실행된 Job 기록이 아직 없습니다.", "No completed/running jobs for this chat/project/model."),
482
+ ("실제 세부 모델명과 토큰은 CLI 출력·로컬 로그에 남은 경우에만 표시됩니다.", "Model details/tokens appear only when present in CLI output or logs."),
483
+ ("최근 Job:", "Latest job:"),
484
+ ("확인한 Job 수:", "Jobs inspected:"),
485
+ ("관측된 세부 모델:", "Observed model:"),
486
+ ("CLI 기본값/설정에서 자동 선택됨 (로그에서 확인 불가)", "CLI default/auto-selected (not visible in logs)"),
487
+ ("관측된 토큰 합계:", "Observed tokens (total):"),
488
+ ("토큰 사용량 패턴을 로그에서 찾지 못했습니다.", "Could not find token usage patterns in logs."),
489
+ ("실제 로컬 사용량/잔여량", "Local usage/quota snapshot"),
490
+ ("로컬 CLI 사용량 로그를 찾지 못했습니다.", "Could not find local CLI usage logs."),
491
+ ("출처:", "Source:"),
492
+ ("관측 시각:", "Observed at:"),
493
+ ("플랜/계정 유형:", "Plan/account type:"),
494
+ ("관측된 토큰:", "Observed tokens:"),
495
+ ("오늘 로컬 로그 기준 요청 수:", "Requests today (from local logs):"),
496
+ ("잔여량:", "Remaining:"),
497
+ ("5시간 한도", "5-hour limit"),
498
+ ("주간 한도", "Weekly limit"),
499
+ ("시간 한도", "-hour limit"),
500
+ ("일 한도", "-day limit"),
501
+ ("분 한도", "-minute limit"),
502
+ ("명령을 찾을 수 없습니다.", "command not found."),
503
+ ("설치 및 PATH를 확인하세요.", "Install it and verify PATH."),
504
+ ("시간 초과.", " timed out."),
505
+ ("auth status (--text):", "auth status (--text):"),
506
+ ("auth status 실패", "auth status failed"),
507
+ ("JSON이 아닌 출력입니다", "Output was not JSON"),
508
+ ("처음 400자", "first 400 chars"),
509
+ ("auth status (JSON 요약, 민감값 제외)", "auth status (JSON summary, sensitive values omitted)"),
510
+ ("예상과 다른 JSON 형식입니다.", "Unexpected JSON shape."),
511
+ ("CLI 버전:", "CLI version:"),
512
+ ("버전 확인 실패", "version check failed"),
513
+ ("설치: npm install -g @google/gemini-cli", "Install: npm install -g @google/gemini-cli"),
514
+ ("...(생략)", "...(truncated)"),
515
+ ("관리 UI는 로컬호스트에서만 사용할 수 있습니다.", "Admin UI is only available on localhost."),
516
+ )
517
+
518
+ _MESSAGE_CATALOG_KO: dict[str, str] = {
519
+ "model.menu": "모델을 선택하세요.",
520
+ "model.settings": "모델 설정\n\n- 현재 기본 모델: {selection}",
521
+ "model.provider_selected": (
522
+ "모델 제공자가 선택되었습니다.\n\n"
523
+ "- 기본 모델: {provider}\n"
524
+ "- 세부 모델을 선택하세요."
525
+ ),
526
+ "model.updated": "모델 설정이 변경되었습니다.\n\n- 기본 모델: {selection}",
527
+ "model.unknown_specific": "알 수 없는 세부 모델입니다: {model_id}\n\n{usage}",
528
+ "job.accepted": (
529
+ "✅ 작업 접수 완료\n\n"
530
+ "- Job ID: {job_id}\n"
531
+ "- 프로젝트: {project}\n"
532
+ "- 모델: {model}{mode_line}"
533
+ ),
534
+ "job.mode_line": "\n- 모드: {mode}",
535
+ "job.stop_button": "작업 중단",
536
+ "job.cancelled": "{mode_prefix}⛔ 작업 중단됨\n\n- Job ID: {job_id}\n- 프로젝트: {project}",
537
+ "job.readonly_completed": (
538
+ "[{mode}] 응답 완료\n\n"
539
+ "- Job ID: {job_id}\n"
540
+ "- 프로젝트: {project}\n"
541
+ "- 사용 모델: {model}\n"
542
+ "- 토큰 사용량: {token_usage}{response_block}"
543
+ ),
544
+ "job.response_block": "\n\nAI 응답:\n{summary}",
545
+ "job.no_changes": "변경 없음",
546
+ "job.branch_none_no_changes": "(없음 - 변경 없어 브랜치 미생성)",
547
+ "job.no_commit_skipped": "(no commit - 커밋/push 생략)",
548
+ "job.nothing_staged_skipped": "(스테이징된 변경 없음 - push 생략)",
549
+ "common.unavailable": "확인 불가",
550
+ "job.completed": (
551
+ "✅ 작업 완료\n\n"
552
+ "- Job ID: {job_id}\n"
553
+ "- 프로젝트: {project}\n"
554
+ "- 브랜치: {branch}\n"
555
+ "- 커밋: {commit}\n"
556
+ "- 변경 파일: {changed}\n"
557
+ "- 사용 모델: {model}\n"
558
+ "- 토큰 사용량: {token_usage}{response_block}"
559
+ ),
560
+ "job.failed": (
561
+ "{mode_prefix}❌ 작업 실패\n\n"
562
+ "- Job ID: {job_id}\n"
563
+ "- 프로젝트: {project}\n"
564
+ "- 오류: {error}{details}{failure_block}"
565
+ ),
566
+ "job.failure_detail_stage": "\n- 실패 단계: {stage}",
567
+ "job.failure_detail_log_path": "\n- 로그 경로: {log_path}",
568
+ "job.failure_block": "\n\n실패 출력 요약:\n{summary}",
569
+ "server.started": "✅ Remote AI Coder 서버가 시작되었습니다.\n{body}",
570
+ "server.shutdown": "🔴 Remote AI Coder 서버 연결이 종료되었습니다.",
571
+ }
572
+
573
+
574
+ _TEXT_REPLACEMENTS_RAW: tuple[tuple[str, str], ...] = tuple(
575
+ (english, korean) for korean, english in _TEXT_REPLACEMENTS_KO_TO_EN_RAW
576
+ if english not in {".", "- Mode", "Model provider selected."}
577
+ ) + (
578
+ ("- Mode:", "- 모드:"),
579
+ ("Model provider selected.", "모델 제공자가 선택되었습니다."),
580
+ )
581
+
582
+ _TEXT_REPLACEMENTS = tuple(sorted(set(_TEXT_REPLACEMENTS_RAW), key=lambda p: len(p[0]), reverse=True))
583
+ _EXACT_TEXT_REPLACEMENTS = dict(_TEXT_REPLACEMENTS)
584
+
585
+
586
+ _REGEX_TRANSLATIONS = (
587
+ re.compile(r"Fetched updates from remote ([^.]+)\."),
588
+ re.compile(r" Updated current branch \(([^)]+)\)\."),
589
+ re.compile(r" Fast-forward updated (\d+) additional local branch\(es\)\."),
590
+ re.compile(
591
+ r"Rebase complete: rebased `([^`]+)` onto `([^`]+)/([^`]+)`, "
592
+ r"fast-forward merged into `([^`]+)`, pushed to `([^`]+)`\."
593
+ ),
594
+ re.compile(r"git fetch (\S+) failed:"),
595
+ re.compile(r"git pull (\S+) (\S+) failed \(possible conflict\):"),
596
+ re.compile(r"gh pr create failed:"),
597
+ re.compile(r"\(no remote branches on ([^\n()]+)\)"),
598
+ re.compile(r"\[([^\]\n]+) remote\]"),
599
+ re.compile(r"(\d+)m (\d+)s"),
600
+ re.compile(r"(\d+)s"),
601
+ re.compile(r"Changed files \((\d+) files\)"),
602
+ re.compile(r"- \.\.\. and (\d+) more"),
603
+ re.compile(r"\n\.\.\.\(omitted (\d+) entries\)"),
604
+ )
605
+
606
+
607
+ def _regex_patch_korean(text: str) -> str:
608
+ replacements = (
609
+ lambda m: f"원격({m.group(1)})에서 모든 정보를 가져왔습니다.",
610
+ lambda m: f" 현재 브랜치({m.group(1)})를 업데이트했습니다.",
611
+ lambda m: f" 추가로 {m.group(1)}개의 로컬 브랜치를 fast-forward 업데이트했습니다.",
612
+ lambda m: (
613
+ f"rebase 완료: 브랜치 `{m.group(1)}`를 `{m.group(2)}/{m.group(3)}` 기준으로 rebase 후 "
614
+ f"`{m.group(4)}`에 fast-forward 병합하고 `{m.group(5)}`에 push했습니다."
615
+ ),
616
+ lambda m: f"git fetch {m.group(1)} 실패:",
617
+ lambda m: f"git pull {m.group(1)} {m.group(2)} 실패 (충돌 가능성):",
618
+ lambda m: "gh pr create 실패:",
619
+ lambda m: f"({m.group(1)} 원격 브랜치 없음)",
620
+ lambda m: f"[{m.group(1)} remote]",
621
+ lambda m: f"{m.group(1)}분 {m.group(2)}초",
622
+ lambda m: f"{m.group(1)}초",
623
+ lambda m: f"변경 파일 ({m.group(1)}개)",
624
+ lambda m: f"- ... 외 {m.group(1)}개",
625
+ lambda m: f"\n...(외 {m.group(1)}개 생략)",
626
+ )
627
+ result = text
628
+ for rx, repl in zip(_REGEX_TRANSLATIONS, replacements, strict=True):
629
+ result = rx.sub(repl, result)
630
+ return result
631
+
632
+
633
+ _BUTTON_LABELS = {
634
+ "Help": "도움말",
635
+ "Modes": "모드별 안내",
636
+ "Monitor": "모니터링",
637
+ "Clean": "정리",
638
+ "Manage": "관리",
639
+ "Reports": "리포트",
640
+ "Branch": "브랜치 확인",
641
+ "Rebase": "리베이스",
642
+ "Open PR": "PR 올리기",
643
+ "Stop": "중단",
644
+ "Status": "상태",
645
+ "Model": "모델",
646
+ "Reset": "초기화",
647
+ "Back": "뒤로",
648
+ "← Back": "← 뒤로",
649
+ "Yes": "네",
650
+ "No": "아니오",
651
+ "Stop job": "작업 중단",
652
+ "AGENTS mode": "AGENTS 모드",
653
+ "PLAN mode": "PLAN 모드",
654
+ "ASK mode": "ASK 모드",
655
+ }
656
+
657
+
658
+ _LINE_TRANSLATIONS = (
659
+ (re.compile(r"^- Choose a specific model\.$"), lambda m: "- 세부 모델을 선택하세요."),
660
+ (
661
+ re.compile(r"^- Mode: agent \(allows edit, commit, and push\)$"),
662
+ lambda m: "- 모드: agent (코드 수정·커밋·push 가능)",
663
+ ),
664
+ (
665
+ re.compile(r"^- Mode: ([^(].*)$"),
666
+ lambda m: f"- 모드: {m.group(1)}",
667
+ ),
668
+ (
669
+ re.compile(r"^- 5-hour limit: remaining (.*)$"),
670
+ lambda m: f"- 5시간 한도: 잔여 {m.group(1)}",
671
+ ),
672
+ (
673
+ re.compile(r"^- Weekly limit: remaining (.*)$"),
674
+ lambda m: f"- 주간 한도: 잔여 {m.group(1)}",
675
+ ),
676
+ (re.compile(r"^- Project: (.*)$"), lambda m: f"- 프로젝트: {m.group(1)}"),
677
+ (re.compile(r"^- Model: (.*)$"), lambda m: f"- 모델: {m.group(1)}"),
678
+ (re.compile(r"^- Model used: (.*)$"), lambda m: f"- 사용 모델: {m.group(1)}"),
679
+ (re.compile(r"^- Requested model: (.*)$"), lambda m: f"- 요청 모델: {m.group(1)}"),
680
+ (re.compile(r"^- Default model: (.*)$"), lambda m: f"- 기본 모델: {m.group(1)}"),
681
+ (re.compile(r"^- Current default model: (.*)$"), lambda m: f"- 현재 기본 모델: {m.group(1)}"),
682
+ (re.compile(r"^- Token usage: (.*)$"), lambda m: f"- 토큰 사용량: {m.group(1)}"),
683
+ (re.compile(r"^- Branch: (.*)$"), lambda m: f"- 브랜치: {m.group(1)}"),
684
+ (re.compile(r"^- Commit: (.*)$"), lambda m: f"- 커밋: {m.group(1)}"),
685
+ (re.compile(r"^- Changed files: (.*)$"), lambda m: f"- 변경 파일: {m.group(1)}"),
686
+ (re.compile(r"^- Status: (.*)$"), lambda m: f"- 상태: {m.group(1)}"),
687
+ (re.compile(r"^- Instruction: (.*)$"), lambda m: f"- 지시: {m.group(1)}"),
688
+ (re.compile(r"^- Error: (.*)$"), lambda m: f"- 오류: {m.group(1)}"),
689
+ (re.compile(r"^- Error stage: (.*)$"), lambda m: f"- 오류 단계: {m.group(1)}"),
690
+ (re.compile(r"^- Failure stage: (.*)$"), lambda m: f"- 실패 단계: {m.group(1)}"),
691
+ (re.compile(r"^- Log path: (.*)$"), lambda m: f"- 로그 경로: {m.group(1)}"),
692
+ (re.compile(r"^- Started: (.*)$"), lambda m: f"- 시작: {m.group(1)}"),
693
+ (re.compile(r"^- Created: (.*)$"), lambda m: f"- 생성: {m.group(1)}"),
694
+ (re.compile(r"^Project: (.*)$"), lambda m: f"프로젝트: {m.group(1)}"),
695
+ (re.compile(r"^Current branch: (.*)$"), lambda m: f"현재 브랜치: {m.group(1)}"),
696
+ (re.compile(r"^Default model: (.*)$"), lambda m: f"기본 모델: {m.group(1)}"),
697
+ (re.compile(r"^Total entries: (.*)$"), lambda m: f"총 기록: {m.group(1)}"),
698
+ (re.compile(r"^User requests: (.*)$"), lambda m: f"사용자 요청: {m.group(1)}"),
699
+ (re.compile(r"^Jobs accepted: (.*)$"), lambda m: f"Job 접수: {m.group(1)}"),
700
+ (re.compile(r"^Job results: (.*)$"), lambda m: f"Job 결과: {m.group(1)}"),
701
+ (re.compile(r"^Latest user request: (.*)$"), lambda m: f"최근 사용자 요청: {m.group(1)}"),
702
+ (re.compile(r"^Latest job result: (.*)$"), lambda m: f"최근 Job 결과: {m.group(1)}"),
703
+ )
704
+
705
+
706
+ def _translate_known_lines(text: str) -> str:
707
+ rendered: list[str] = []
708
+ for raw_line in text.splitlines(keepends=True):
709
+ newline = ""
710
+ line = raw_line
711
+ if raw_line.endswith("\r\n"):
712
+ line = raw_line[:-2]
713
+ newline = "\r\n"
714
+ elif raw_line.endswith("\n"):
715
+ line = raw_line[:-1]
716
+ newline = "\n"
717
+ translated = _EXACT_TEXT_REPLACEMENTS.get(line)
718
+ if translated is None:
719
+ for rx, repl in _LINE_TRANSLATIONS:
720
+ match = rx.match(line)
721
+ if match is not None:
722
+ translated = repl(match)
723
+ break
724
+ rendered.append((translated if translated is not None else line) + newline)
725
+ return "".join(rendered)
726
+
727
+
728
+ def translate_text(text: str, language: UiLanguage) -> str:
729
+ if language is UiLanguage.ENGLISH:
730
+ return str(text)
731
+ if isinstance(text, UiMessage):
732
+ return text.render(language)
733
+ translated = _regex_patch_korean(text)
734
+ return _translate_known_lines(translated)
735
+
736
+
737
+ def translate_button_label(label: str, language: UiLanguage) -> str:
738
+ if language is UiLanguage.ENGLISH:
739
+ return str(label)
740
+ if isinstance(label, UiMessage):
741
+ return label.render(language)
742
+ return _BUTTON_LABELS.get(label, label.replace(" mode", " 모드"))