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
@@ -0,0 +1,511 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title data-i18n="title.admin">Remote AI Coder - Admin</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0f1419;
10
+ --bg-elevated: #1a2332;
11
+ --surface: #243044;
12
+ --border: rgba(255, 255, 255, 0.08);
13
+ --text: #e8edf4;
14
+ --text-muted: #8b9cb3;
15
+ --accent: #3d9cf5;
16
+ --accent-dim: rgba(61, 156, 245, 0.15);
17
+ --success: #34c759;
18
+ --success-bg: rgba(52, 199, 89, 0.12);
19
+ --danger: #ff453a;
20
+ --danger-bg: rgba(255, 69, 58, 0.12);
21
+ --warning: #ffcc00;
22
+ --radius: 12px;
23
+ --radius-sm: 8px;
24
+ --shadow: 0 4px 24px rgba(0, 0, 0, 0.35);
25
+ --font: "Segoe UI", system-ui, -apple-system, sans-serif;
26
+ --focus: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent);
27
+ }
28
+
29
+ *, *::before, *::after { box-sizing: border-box; }
30
+
31
+ body {
32
+ margin: 0;
33
+ min-height: 100vh;
34
+ font-family: var(--font);
35
+ font-size: 15px;
36
+ line-height: 1.5;
37
+ color: var(--text);
38
+ background: radial-gradient(ellipse 120% 80% at 50% -20%, #1e3a5f 0%, var(--bg) 55%);
39
+ padding: 1.25rem 1rem 2.5rem;
40
+ }
41
+
42
+ .wrap { max-width: 72rem; margin: 0 auto; }
43
+
44
+ .app-header {
45
+ margin-bottom: 1.5rem;
46
+ padding: 1.25rem 1.35rem;
47
+ background: linear-gradient(135deg, var(--bg-elevated) 0%, var(--surface) 100%);
48
+ border: 1px solid var(--border);
49
+ border-radius: var(--radius);
50
+ box-shadow: var(--shadow);
51
+ }
52
+
53
+ .app-header h1 {
54
+ margin: 0 0 0.35rem;
55
+ font-size: 1.5rem;
56
+ font-weight: 700;
57
+ letter-spacing: -0.02em;
58
+ }
59
+
60
+ .app-header .tagline {
61
+ margin: 0;
62
+ color: var(--text-muted);
63
+ font-size: 0.9rem;
64
+ }
65
+
66
+ .localhost-pill {
67
+ display: inline-flex;
68
+ align-items: center;
69
+ gap: 0.35rem;
70
+ margin-top: 0.75rem;
71
+ padding: 0.35rem 0.65rem;
72
+ font-size: 0.8rem;
73
+ font-weight: 600;
74
+ color: var(--warning);
75
+ background: rgba(255, 204, 0, 0.1);
76
+ border: 1px solid rgba(255, 204, 0, 0.25);
77
+ border-radius: 999px;
78
+ }
79
+
80
+ #global-msg {
81
+ margin-bottom: 1rem;
82
+ padding: 0.65rem 1rem;
83
+ border-radius: var(--radius-sm);
84
+ font-size: 0.9rem;
85
+ display: none;
86
+ }
87
+
88
+ #global-msg.is-visible { display: block; }
89
+
90
+ #global-msg.msg-err {
91
+ color: #ffb4b0;
92
+ background: var(--danger-bg);
93
+ border: 1px solid rgba(255, 69, 58, 0.35);
94
+ }
95
+
96
+ #global-msg.msg-ok {
97
+ color: #9ee6b0;
98
+ background: var(--success-bg);
99
+ border: 1px solid rgba(52, 199, 89, 0.35);
100
+ }
101
+
102
+ #global-msg.msg-info {
103
+ color: var(--text-muted);
104
+ background: var(--accent-dim);
105
+ border: 1px solid rgba(61, 156, 245, 0.25);
106
+ }
107
+
108
+ .summary-grid {
109
+ display: grid;
110
+ grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr));
111
+ gap: 0.75rem;
112
+ margin-bottom: 1.25rem;
113
+ }
114
+
115
+ .stat-card {
116
+ padding: 1rem 1.1rem;
117
+ background: var(--bg-elevated);
118
+ border: 1px solid var(--border);
119
+ border-radius: var(--radius-sm);
120
+ }
121
+
122
+ .stat-card .label {
123
+ margin: 0;
124
+ font-size: 0.72rem;
125
+ font-weight: 600;
126
+ text-transform: uppercase;
127
+ letter-spacing: 0.06em;
128
+ color: var(--text-muted);
129
+ }
130
+
131
+ .stat-card .value {
132
+ margin: 0.25rem 0 0;
133
+ font-size: 1.35rem;
134
+ font-weight: 700;
135
+ color: var(--text);
136
+ }
137
+
138
+ .stat-card .sub {
139
+ margin: 0.2rem 0 0;
140
+ font-size: 0.78rem;
141
+ color: var(--text-muted);
142
+ word-break: break-all;
143
+ }
144
+
145
+ section.card {
146
+ margin-bottom: 1.25rem;
147
+ padding: 1.25rem 1.35rem;
148
+ background: var(--bg-elevated);
149
+ border: 1px solid var(--border);
150
+ border-radius: var(--radius);
151
+ box-shadow: var(--shadow);
152
+ }
153
+
154
+ section.card h2 {
155
+ margin: 0 0 0.5rem;
156
+ font-size: 1.05rem;
157
+ font-weight: 700;
158
+ }
159
+
160
+ section.card > .lead {
161
+ margin: 0 0 1rem;
162
+ color: var(--text-muted);
163
+ font-size: 0.88rem;
164
+ }
165
+
166
+ section.card > .lead code {
167
+ padding: 0.12rem 0.35rem;
168
+ font-size: 0.82em;
169
+ background: var(--surface);
170
+ border-radius: 4px;
171
+ border: 1px solid var(--border);
172
+ }
173
+
174
+ .settings-meta {
175
+ display: grid;
176
+ gap: 0.65rem;
177
+ font-size: 0.88rem;
178
+ color: var(--text-muted);
179
+ }
180
+
181
+ .settings-meta dt {
182
+ margin: 0;
183
+ font-weight: 600;
184
+ color: var(--text);
185
+ font-size: 0.75rem;
186
+ text-transform: uppercase;
187
+ letter-spacing: 0.04em;
188
+ }
189
+
190
+ .settings-meta dd {
191
+ margin: 0.15rem 0 0;
192
+ word-break: break-word;
193
+ }
194
+
195
+ .settings-meta .mono {
196
+ font-family: ui-monospace, monospace;
197
+ font-size: 0.82rem;
198
+ color: var(--accent);
199
+ }
200
+
201
+ .section-head {
202
+ display: flex;
203
+ flex-wrap: wrap;
204
+ align-items: baseline;
205
+ justify-content: space-between;
206
+ gap: 0.75rem;
207
+ margin-bottom: 0.5rem;
208
+ }
209
+
210
+ .section-head h2 {
211
+ margin: 0;
212
+ }
213
+
214
+ .link-manage {
215
+ font-size: 0.88rem;
216
+ font-weight: 600;
217
+ color: var(--accent);
218
+ text-decoration: none;
219
+ }
220
+
221
+ .link-manage:hover {
222
+ text-decoration: underline;
223
+ }
224
+
225
+ .active-proj-empty {
226
+ margin: 0;
227
+ color: var(--text-muted);
228
+ font-size: 0.9rem;
229
+ }
230
+
231
+ .active-proj-list {
232
+ display: flex;
233
+ flex-direction: column;
234
+ gap: 0.85rem;
235
+ margin: 0;
236
+ padding: 0;
237
+ list-style: none;
238
+ }
239
+
240
+ .active-proj-item {
241
+ padding: 0.85rem 1rem;
242
+ background: var(--surface);
243
+ border: 1px solid var(--border);
244
+ border-radius: var(--radius-sm);
245
+ }
246
+
247
+ .active-proj-item h3 {
248
+ margin: 0 0 0.45rem;
249
+ font-size: 0.98rem;
250
+ font-weight: 700;
251
+ }
252
+
253
+ .active-proj-item h3 code {
254
+ font-family: ui-monospace, monospace;
255
+ font-size: 0.95em;
256
+ }
257
+
258
+ .active-proj-dl {
259
+ display: grid;
260
+ gap: 0.45rem;
261
+ margin: 0;
262
+ font-size: 0.82rem;
263
+ color: var(--text-muted);
264
+ }
265
+
266
+ .active-proj-dl > div {
267
+ margin: 0;
268
+ }
269
+
270
+ .active-proj-dl dt {
271
+ margin: 0;
272
+ font-weight: 600;
273
+ color: var(--text);
274
+ font-size: 0.72rem;
275
+ text-transform: uppercase;
276
+ letter-spacing: 0.04em;
277
+ }
278
+
279
+ .active-proj-dl dd {
280
+ margin: 0.15rem 0 0;
281
+ word-break: break-word;
282
+ }
283
+
284
+ .active-proj-dl .mono {
285
+ font-family: ui-monospace, monospace;
286
+ font-size: 0.78rem;
287
+ color: var(--accent);
288
+ }
289
+
290
+ .badge {
291
+ display: inline-block;
292
+ padding: 0.15rem 0.45rem;
293
+ font-size: 0.68rem;
294
+ font-weight: 700;
295
+ text-transform: uppercase;
296
+ letter-spacing: 0.04em;
297
+ border-radius: 4px;
298
+ margin-left: 0.35rem;
299
+ vertical-align: middle;
300
+ }
301
+
302
+ .badge-default {
303
+ color: #7ec8ff;
304
+ background: var(--accent-dim);
305
+ border: 1px solid rgba(61, 156, 245, 0.35);
306
+ }
307
+
308
+ .header-row {
309
+ display: flex;
310
+ flex-wrap: wrap;
311
+ align-items: flex-start;
312
+ justify-content: space-between;
313
+ gap: 1rem;
314
+ }
315
+
316
+ .header-icon-nav {
317
+ display: flex;
318
+ flex-shrink: 0;
319
+ gap: 0.35rem;
320
+ align-items: center;
321
+ }
322
+
323
+ .header-icon-link {
324
+ display: flex;
325
+ align-items: center;
326
+ justify-content: center;
327
+ width: 2.5rem;
328
+ height: 2.5rem;
329
+ border-radius: var(--radius-sm);
330
+ border: 1px solid var(--border);
331
+ background: rgba(0, 0, 0, 0.15);
332
+ transition: background 0.15s, border-color 0.15s;
333
+ }
334
+
335
+ .header-icon-link:hover {
336
+ background: #2d3d54;
337
+ border-color: rgba(255, 255, 255, 0.12);
338
+ }
339
+
340
+ .header-icon-link:focus-visible {
341
+ outline: none;
342
+ box-shadow: var(--focus);
343
+ }
344
+
345
+ .header-icon-link.is-current {
346
+ border-color: rgba(61, 156, 245, 0.45);
347
+ background: var(--accent-dim);
348
+ }
349
+
350
+ .header-icon-link img {
351
+ display: block;
352
+ width: 1.35rem;
353
+ height: 1.35rem;
354
+ }
355
+
356
+ @media (max-width: 640px) {
357
+ .app-header h1 { font-size: 1.25rem; }
358
+ }
359
+ </style>
360
+ </head>
361
+ <body>
362
+ <div class="wrap">
363
+ <header class="app-header">
364
+ <div class="header-row">
365
+ <div>
366
+ <h1>Remote AI Coder</h1>
367
+ <p class="tagline" data-i18n="admin.tagline">Local admin UI</p>
368
+ </div>
369
+ <nav class="header-icon-nav" aria-label="Page navigation" data-i18n-aria-label="nav.aria">
370
+ <a href="/" class="header-icon-link is-current" title="Admin home" aria-label="Admin home" data-i18n-title="nav.home" data-i18n-aria-label="nav.home">
371
+ <img src="/admin-static/icons/home.svg" width="22" height="22" alt="" />
372
+ </a>
373
+ <a href="/projects" class="header-icon-link" title="Projects" aria-label="Projects" data-i18n-title="nav.projects" data-i18n-aria-label="nav.projects">
374
+ <img src="/admin-static/icons/projects.svg" width="22" height="22" alt="" />
375
+ </a>
376
+ <a href="/advanced" class="header-icon-link" title="Advanced settings" aria-label="Advanced settings" data-i18n-title="nav.advanced" data-i18n-aria-label="nav.advanced">
377
+ <img src="/admin-static/icons/advanced.svg" width="22" height="22" alt="" />
378
+ </a>
379
+ <a href="/logs" class="header-icon-link" title="Server logs" aria-label="Server logs" data-i18n-title="nav.logs" data-i18n-aria-label="nav.logs">
380
+ <img src="/admin-static/icons/logs.svg" width="22" height="22" alt="" />
381
+ </a>
382
+ <a href="/database" class="header-icon-link" title="Data browser" aria-label="Data browser" data-i18n-title="nav.database" data-i18n-aria-label="nav.database">
383
+ <img src="/admin-static/icons/database.svg" width="22" height="22" alt="" />
384
+ </a>
385
+ </nav>
386
+ </div>
387
+ <span class="localhost-pill" aria-hidden="true" data-i18n="common.localOnly">Local only</span>
388
+ <p class="tagline" style="margin-top:0.65rem" data-i18n-html="common.localhostNote">This page is only available from <strong>127.0.0.1</strong>.</p>
389
+ </header>
390
+
391
+ <div id="global-msg" role="status" aria-live="polite"></div>
392
+
393
+ <div class="summary-grid" id="summary-grid" aria-label="Summary" data-i18n-aria-label="common.summaryAria"></div>
394
+
395
+ <section class="card" aria-labelledby="active-projects-heading">
396
+ <div class="section-head">
397
+ <h2 id="active-projects-heading" data-i18n="admin.activeProjects">Active projects</h2>
398
+ <a href="/projects" class="link-manage" data-i18n="admin.manageLink">Manage</a>
399
+ </div>
400
+ <p class="lead" data-i18n="admin.activeLead">Only enabled projects are shown. Add, disable, or delete them on the Projects page.</p>
401
+ <div id="active-projects-view" aria-live="polite"></div>
402
+ </section>
403
+
404
+ <section class="card" aria-labelledby="settings-heading">
405
+ <h2 id="settings-heading" data-i18n="admin.envSummary">Environment summary</h2>
406
+ <p class="lead" data-i18n="admin.envLead">Registry file path and Telegram-related settings (masked). Webhooks are registered via script.</p>
407
+ <dl class="settings-meta" id="settings-meta"></dl>
408
+ </section>
409
+ </div>
410
+
411
+ <script src="/admin-static/i18n.js"></script>
412
+ <script src="/admin-static/summary.js"></script>
413
+ <script>
414
+ const $ = (s) => document.querySelector(s);
415
+ const globalMsg = $("#global-msg");
416
+
417
+ function escapeHtml(s) {
418
+ return String(s).replace(/[&<>"']/g, (c) => ({
419
+ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
420
+ }[c]));
421
+ }
422
+
423
+ function setGlobalMsg(kind, text) {
424
+ if (!text) {
425
+ globalMsg.textContent = "";
426
+ globalMsg.className = "";
427
+ return;
428
+ }
429
+ globalMsg.textContent = text;
430
+ globalMsg.className =
431
+ "is-visible " + (kind === "err" ? "msg-err" : kind === "ok" ? "msg-ok" : "msg-info");
432
+ }
433
+
434
+ async function parseApiError(r, rawText) {
435
+ const ct = (r.headers.get("content-type") || "").toLowerCase();
436
+ if (ct.includes("application/json")) {
437
+ try {
438
+ const j = JSON.parse(rawText);
439
+ if (j && typeof j.detail === "string") return j.detail;
440
+ if (Array.isArray(j.detail)) {
441
+ return j.detail.map((d) => (typeof d.msg === "string" ? d.msg : JSON.stringify(d))).join("\n");
442
+ }
443
+ } catch (_) { /* ignore */ }
444
+ }
445
+ return rawText || "Request failed (" + r.status + ")";
446
+ }
447
+
448
+ function renderActiveProjects(projectsPayload) {
449
+ const container = $("#active-projects-view");
450
+ const defaultName = projectsPayload.default_project || "";
451
+ const active = (projectsPayload.projects || [])
452
+ .filter((p) => p.enabled)
453
+ .sort((a, b) => String(a.name).localeCompare(String(b.name)));
454
+
455
+ if (!active.length) {
456
+ container.innerHTML =
457
+ '<p class="active-proj-empty">' + i18n.t("admin.noActiveHtml") + "</p>";
458
+ return;
459
+ }
460
+
461
+ const items = active
462
+ .map((p) => {
463
+ const isDef = p.name === defaultName;
464
+ const badge = isDef
465
+ ? '<span class="badge badge-default">' + escapeHtml(i18n.t("common.fallbackDefault")) + "</span>"
466
+ : "";
467
+ return `<li class="active-proj-item">
468
+ <h3><code>${escapeHtml(p.name)}</code>${badge}</h3>
469
+ <dl class="active-proj-dl">
470
+ <div><dt>${escapeHtml(i18n.t("common.root"))}</dt><dd class="mono">${escapeHtml(String(p.root_path))}</dd></div>
471
+ <div><dt>${escapeHtml(i18n.t("common.worktree"))}</dt><dd class="mono">${escapeHtml(String(p.worktree_base_dir))}</dd></div>
472
+ <div><dt>${escapeHtml(i18n.t("common.defaultModel"))}</dt><dd>${escapeHtml(String(p.default_model))}</dd></div>
473
+ </dl>
474
+ </li>`;
475
+ })
476
+ .join("");
477
+
478
+ container.innerHTML = `<ul class="active-proj-list">${items}</ul>`;
479
+ }
480
+
481
+ function renderSettings(settings) {
482
+ const dl = $("#settings-meta");
483
+ const none = i18n.t("common.none");
484
+ const chats = (settings.telegram_allowed_chat_ids || []).join(", ") || none;
485
+ const users = (settings.telegram_allowed_user_ids || []).join(", ") || none;
486
+ dl.innerHTML = `
487
+ <div><dt>${escapeHtml(i18n.t("admin.projectsConfigFile"))}</dt><dd class="mono">${escapeHtml(settings.projects_config_path || "")}</dd></div>
488
+ <div><dt>${escapeHtml(i18n.t("admin.botTokenMasked"))}</dt><dd class="mono">${escapeHtml(i18n.tv(settings.telegram_bot_token_masked || ""))}</dd></div>
489
+ <div><dt>${escapeHtml(i18n.t("common.allowedChatIds"))}</dt><dd>${escapeHtml(chats)}</dd></div>
490
+ <div><dt>${escapeHtml(i18n.t("common.allowedUserIds"))}</dt><dd>${escapeHtml(users)}</dd></div>
491
+ <div><dt>${escapeHtml(i18n.t("admin.webhookSecret"))}</dt><dd>${settings.telegram_webhook_secret_set ? escapeHtml(i18n.t("common.set")) : escapeHtml(i18n.t("common.unset"))}</dd></div>
492
+ <div><dt>${escapeHtml(i18n.t("admin.webhookHintLabel"))}</dt><dd>${escapeHtml(i18n.tv(settings.webhook_hint || ""))}</dd></div>
493
+ `;
494
+ }
495
+
496
+ async function loadHub() {
497
+ setGlobalMsg("", "");
498
+ try {
499
+ const result = await adminSummary.loadSummaryGrid((msg) => setGlobalMsg("err", msg));
500
+ if (!result.ok) return;
501
+ renderSettings(result.settings);
502
+ renderActiveProjects(result.projectsPayload);
503
+ } catch (e) {
504
+ setGlobalMsg("err", String(e));
505
+ }
506
+ }
507
+
508
+ loadHub();
509
+ </script>
510
+ </body>
511
+ </html>