codex-autorunner 0.1.0__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.
- codex_autorunner/__init__.py +3 -0
- codex_autorunner/bootstrap.py +151 -0
- codex_autorunner/cli.py +886 -0
- codex_autorunner/codex_cli.py +79 -0
- codex_autorunner/codex_runner.py +17 -0
- codex_autorunner/core/__init__.py +1 -0
- codex_autorunner/core/about_car.py +125 -0
- codex_autorunner/core/codex_runner.py +100 -0
- codex_autorunner/core/config.py +1465 -0
- codex_autorunner/core/doc_chat.py +547 -0
- codex_autorunner/core/docs.py +37 -0
- codex_autorunner/core/engine.py +720 -0
- codex_autorunner/core/git_utils.py +206 -0
- codex_autorunner/core/hub.py +756 -0
- codex_autorunner/core/injected_context.py +9 -0
- codex_autorunner/core/locks.py +57 -0
- codex_autorunner/core/logging_utils.py +158 -0
- codex_autorunner/core/notifications.py +465 -0
- codex_autorunner/core/optional_dependencies.py +41 -0
- codex_autorunner/core/prompt.py +107 -0
- codex_autorunner/core/prompts.py +275 -0
- codex_autorunner/core/request_context.py +21 -0
- codex_autorunner/core/runner_controller.py +116 -0
- codex_autorunner/core/runner_process.py +29 -0
- codex_autorunner/core/snapshot.py +576 -0
- codex_autorunner/core/state.py +156 -0
- codex_autorunner/core/update.py +567 -0
- codex_autorunner/core/update_runner.py +44 -0
- codex_autorunner/core/usage.py +1221 -0
- codex_autorunner/core/utils.py +108 -0
- codex_autorunner/discovery.py +102 -0
- codex_autorunner/housekeeping.py +423 -0
- codex_autorunner/integrations/__init__.py +1 -0
- codex_autorunner/integrations/app_server/__init__.py +6 -0
- codex_autorunner/integrations/app_server/client.py +1386 -0
- codex_autorunner/integrations/app_server/supervisor.py +206 -0
- codex_autorunner/integrations/github/__init__.py +10 -0
- codex_autorunner/integrations/github/service.py +889 -0
- codex_autorunner/integrations/telegram/__init__.py +1 -0
- codex_autorunner/integrations/telegram/adapter.py +1401 -0
- codex_autorunner/integrations/telegram/commands_registry.py +104 -0
- codex_autorunner/integrations/telegram/config.py +450 -0
- codex_autorunner/integrations/telegram/constants.py +154 -0
- codex_autorunner/integrations/telegram/dispatch.py +162 -0
- codex_autorunner/integrations/telegram/handlers/__init__.py +0 -0
- codex_autorunner/integrations/telegram/handlers/approvals.py +241 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +72 -0
- codex_autorunner/integrations/telegram/handlers/commands.py +160 -0
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +5262 -0
- codex_autorunner/integrations/telegram/handlers/messages.py +477 -0
- codex_autorunner/integrations/telegram/handlers/selections.py +545 -0
- codex_autorunner/integrations/telegram/helpers.py +2084 -0
- codex_autorunner/integrations/telegram/notifications.py +164 -0
- codex_autorunner/integrations/telegram/outbox.py +174 -0
- codex_autorunner/integrations/telegram/rendering.py +102 -0
- codex_autorunner/integrations/telegram/retry.py +37 -0
- codex_autorunner/integrations/telegram/runtime.py +270 -0
- codex_autorunner/integrations/telegram/service.py +921 -0
- codex_autorunner/integrations/telegram/state.py +1223 -0
- codex_autorunner/integrations/telegram/transport.py +318 -0
- codex_autorunner/integrations/telegram/types.py +57 -0
- codex_autorunner/integrations/telegram/voice.py +413 -0
- codex_autorunner/manifest.py +150 -0
- codex_autorunner/routes/__init__.py +53 -0
- codex_autorunner/routes/base.py +470 -0
- codex_autorunner/routes/docs.py +275 -0
- codex_autorunner/routes/github.py +197 -0
- codex_autorunner/routes/repos.py +121 -0
- codex_autorunner/routes/sessions.py +137 -0
- codex_autorunner/routes/shared.py +137 -0
- codex_autorunner/routes/system.py +175 -0
- codex_autorunner/routes/terminal_images.py +107 -0
- codex_autorunner/routes/voice.py +128 -0
- codex_autorunner/server.py +23 -0
- codex_autorunner/spec_ingest.py +113 -0
- codex_autorunner/static/app.js +95 -0
- codex_autorunner/static/autoRefresh.js +209 -0
- codex_autorunner/static/bootstrap.js +105 -0
- codex_autorunner/static/bus.js +23 -0
- codex_autorunner/static/cache.js +52 -0
- codex_autorunner/static/constants.js +48 -0
- codex_autorunner/static/dashboard.js +795 -0
- codex_autorunner/static/docs.js +1514 -0
- codex_autorunner/static/env.js +99 -0
- codex_autorunner/static/github.js +168 -0
- codex_autorunner/static/hub.js +1511 -0
- codex_autorunner/static/index.html +622 -0
- codex_autorunner/static/loader.js +28 -0
- codex_autorunner/static/logs.js +690 -0
- codex_autorunner/static/mobileCompact.js +300 -0
- codex_autorunner/static/snapshot.js +116 -0
- codex_autorunner/static/state.js +87 -0
- codex_autorunner/static/styles.css +4966 -0
- codex_autorunner/static/tabs.js +50 -0
- codex_autorunner/static/terminal.js +21 -0
- codex_autorunner/static/terminalManager.js +3535 -0
- codex_autorunner/static/todoPreview.js +25 -0
- codex_autorunner/static/types.d.ts +8 -0
- codex_autorunner/static/utils.js +597 -0
- codex_autorunner/static/vendor/LICENSE.xterm +24 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-cyrillic-ext.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-cyrillic.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-greek.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-latin-ext.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-latin.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-vietnamese.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-cyrillic-ext.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-cyrillic.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-greek.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-latin-ext.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-latin.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-vietnamese.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-cyrillic-ext.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-cyrillic.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-greek.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-latin-ext.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-latin.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-vietnamese.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/OFL.txt +93 -0
- codex_autorunner/static/vendor/xterm-addon-fit.js +2 -0
- codex_autorunner/static/vendor/xterm.css +209 -0
- codex_autorunner/static/vendor/xterm.js +2 -0
- codex_autorunner/static/voice.js +591 -0
- codex_autorunner/voice/__init__.py +39 -0
- codex_autorunner/voice/capture.py +349 -0
- codex_autorunner/voice/config.py +167 -0
- codex_autorunner/voice/provider.py +66 -0
- codex_autorunner/voice/providers/__init__.py +7 -0
- codex_autorunner/voice/providers/openai_whisper.py +345 -0
- codex_autorunner/voice/resolver.py +36 -0
- codex_autorunner/voice/service.py +210 -0
- codex_autorunner/web/__init__.py +1 -0
- codex_autorunner/web/app.py +1037 -0
- codex_autorunner/web/hub_jobs.py +181 -0
- codex_autorunner/web/middleware.py +552 -0
- codex_autorunner/web/pty_session.py +357 -0
- codex_autorunner/web/runner_manager.py +25 -0
- codex_autorunner/web/schemas.py +253 -0
- codex_autorunner/web/static_assets.py +430 -0
- codex_autorunner/web/terminal_sessions.py +78 -0
- codex_autorunner/workspace.py +16 -0
- codex_autorunner-0.1.0.dist-info/METADATA +240 -0
- codex_autorunner-0.1.0.dist-info/RECORD +147 -0
- codex_autorunner-0.1.0.dist-info/WHEEL +5 -0
- codex_autorunner-0.1.0.dist-info/entry_points.txt +3 -0
- codex_autorunner-0.1.0.dist-info/licenses/LICENSE +21 -0
- codex_autorunner-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function renderTodoPreview(text) {
|
|
2
|
+
const list = document.getElementById("todo-preview-list");
|
|
3
|
+
if (!list) return;
|
|
4
|
+
list.innerHTML = "";
|
|
5
|
+
const lines = (text || "").split("\n").map((l) => l.trim());
|
|
6
|
+
const todos = lines.filter((l) => l.startsWith("- ["));
|
|
7
|
+
if (todos.length === 0) {
|
|
8
|
+
const li = document.createElement("li");
|
|
9
|
+
li.textContent = "No TODO items found.";
|
|
10
|
+
list.appendChild(li);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
todos.forEach((line) => {
|
|
14
|
+
const li = document.createElement("li");
|
|
15
|
+
const box = document.createElement("div");
|
|
16
|
+
box.className = "box";
|
|
17
|
+
const done = line.toLowerCase().startsWith("- [x]");
|
|
18
|
+
if (done) box.classList.add("done");
|
|
19
|
+
const textSpan = document.createElement("span");
|
|
20
|
+
textSpan.textContent = line.substring(5).trim();
|
|
21
|
+
li.appendChild(box);
|
|
22
|
+
li.appendChild(textSpan);
|
|
23
|
+
list.appendChild(li);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
import { CONSTANTS } from "./constants.js";
|
|
2
|
+
import { BASE_PATH } from "./env.js";
|
|
3
|
+
|
|
4
|
+
const toast = document.getElementById("toast");
|
|
5
|
+
const decoder = new TextDecoder();
|
|
6
|
+
const AUTH_TOKEN_KEY = "car_auth_token";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} ApiOptions
|
|
10
|
+
* @property {string} [method]
|
|
11
|
+
* @property {any} [body]
|
|
12
|
+
* @property {Record<string, string>} [headers]
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} StreamOptions
|
|
17
|
+
* @property {string} [method]
|
|
18
|
+
* @property {any} [body]
|
|
19
|
+
* @property {(data: string, event: string) => void} [onMessage]
|
|
20
|
+
* @property {(err: Error) => void} [onError]
|
|
21
|
+
* @property {() => void} [onFinish]
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export function getAuthToken() {
|
|
25
|
+
let token = null;
|
|
26
|
+
try {
|
|
27
|
+
token = sessionStorage.getItem(AUTH_TOKEN_KEY);
|
|
28
|
+
} catch (_err) {
|
|
29
|
+
token = null;
|
|
30
|
+
}
|
|
31
|
+
if (token) {
|
|
32
|
+
return token;
|
|
33
|
+
}
|
|
34
|
+
if (window?.__CAR_AUTH_TOKEN) {
|
|
35
|
+
return window.__CAR_AUTH_TOKEN;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function resolvePath(path) {
|
|
41
|
+
if (!path) return path;
|
|
42
|
+
const absolutePrefixes = ["http://", "https://", "ws://", "wss://"];
|
|
43
|
+
if (absolutePrefixes.some((prefix) => path.startsWith(prefix))) {
|
|
44
|
+
return path;
|
|
45
|
+
}
|
|
46
|
+
if (!BASE_PATH) {
|
|
47
|
+
return path;
|
|
48
|
+
}
|
|
49
|
+
if (path.startsWith(BASE_PATH)) {
|
|
50
|
+
return path;
|
|
51
|
+
}
|
|
52
|
+
if (path.startsWith("/")) {
|
|
53
|
+
return `${BASE_PATH}${path}`;
|
|
54
|
+
}
|
|
55
|
+
return `${BASE_PATH}/${path}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getUrlParams() {
|
|
59
|
+
try {
|
|
60
|
+
return new URLSearchParams(window.location.search || "");
|
|
61
|
+
} catch (_err) {
|
|
62
|
+
return new URLSearchParams();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function updateUrlParams(updates = {}) {
|
|
67
|
+
if (!window?.location?.href) return;
|
|
68
|
+
if (typeof history === "undefined" || !history.replaceState) return;
|
|
69
|
+
const url = new URL(window.location.href);
|
|
70
|
+
const params = url.searchParams;
|
|
71
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
72
|
+
if (value === undefined || value === null || value === "") {
|
|
73
|
+
params.delete(key);
|
|
74
|
+
} else {
|
|
75
|
+
params.set(key, String(value));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
url.search = params.toString();
|
|
79
|
+
history.replaceState(null, "", url.toString());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function escapeHtml(value) {
|
|
83
|
+
if (value === null || value === undefined) return "";
|
|
84
|
+
return String(value)
|
|
85
|
+
.replace(/&/g, "&")
|
|
86
|
+
.replace(/</g, "<")
|
|
87
|
+
.replace(/>/g, ">")
|
|
88
|
+
.replace(/"/g, """)
|
|
89
|
+
.replace(/'/g, "'");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function buildWsUrl(path, query = "") {
|
|
93
|
+
const resolved = resolvePath(path);
|
|
94
|
+
const normalized = resolved.startsWith("/") ? resolved : `/${resolved}`;
|
|
95
|
+
const proto = window.location.protocol === "https:" ? "wss" : "ws";
|
|
96
|
+
const params = new URLSearchParams(query.startsWith("?") ? query.slice(1) : query);
|
|
97
|
+
const suffix = params.toString();
|
|
98
|
+
return `${proto}://${window.location.host}${normalized}${suffix ? `?${suffix}` : ""}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function flash(message, type = "info") {
|
|
102
|
+
toast.textContent = message;
|
|
103
|
+
toast.classList.remove("error");
|
|
104
|
+
if (type === "error") {
|
|
105
|
+
toast.classList.add("error");
|
|
106
|
+
}
|
|
107
|
+
toast.classList.add("show");
|
|
108
|
+
setTimeout(() => {
|
|
109
|
+
toast.classList.remove("show", "error");
|
|
110
|
+
}, CONSTANTS.UI.TOAST_DURATION);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function statusPill(el, status) {
|
|
114
|
+
const normalized = status || "idle";
|
|
115
|
+
el.textContent = normalized;
|
|
116
|
+
el.classList.remove("pill-idle", "pill-running", "pill-error", "pill-warn");
|
|
117
|
+
const errorStates = ["error", "init_error"];
|
|
118
|
+
const warnStates = ["locked", "missing", "uninitialized", "initializing"];
|
|
119
|
+
if (normalized === "running") {
|
|
120
|
+
el.classList.add("pill-running");
|
|
121
|
+
} else if (errorStates.includes(normalized)) {
|
|
122
|
+
el.classList.add("pill-error");
|
|
123
|
+
} else if (warnStates.includes(normalized)) {
|
|
124
|
+
el.classList.add("pill-warn");
|
|
125
|
+
} else {
|
|
126
|
+
el.classList.add("pill-idle");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function extractErrorDetail(payload) {
|
|
131
|
+
if (!payload || typeof payload !== "object") return "";
|
|
132
|
+
const detail = payload.detail ?? payload.message ?? payload.error;
|
|
133
|
+
if (!detail) return "";
|
|
134
|
+
if (typeof detail === "string") return detail;
|
|
135
|
+
if (Array.isArray(detail)) {
|
|
136
|
+
const parts = detail
|
|
137
|
+
.map((item) => {
|
|
138
|
+
if (!item) return "";
|
|
139
|
+
if (typeof item === "string") return item;
|
|
140
|
+
if (typeof item === "object") {
|
|
141
|
+
const msg = item.msg || item.message || "";
|
|
142
|
+
const loc = Array.isArray(item.loc) ? item.loc.join(".") : item.loc;
|
|
143
|
+
if (msg && loc) return `${loc}: ${msg}`;
|
|
144
|
+
if (msg) return msg;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
return JSON.stringify(item);
|
|
148
|
+
} catch (_err) {
|
|
149
|
+
return String(item);
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
.filter(Boolean);
|
|
153
|
+
return parts.join(" | ");
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
return JSON.stringify(detail);
|
|
157
|
+
} catch (_err) {
|
|
158
|
+
return String(detail);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function buildErrorMessage(res) {
|
|
163
|
+
if (res.status === 401) {
|
|
164
|
+
return "Unauthorized. Provide a valid token to access this server.";
|
|
165
|
+
}
|
|
166
|
+
let text = "";
|
|
167
|
+
try {
|
|
168
|
+
text = await res.text();
|
|
169
|
+
} catch (_err) {
|
|
170
|
+
text = "";
|
|
171
|
+
}
|
|
172
|
+
let payload = null;
|
|
173
|
+
const contentType = res.headers.get("content-type") || "";
|
|
174
|
+
const trimmed = text.trim();
|
|
175
|
+
if (
|
|
176
|
+
contentType.includes("application/json") ||
|
|
177
|
+
trimmed.startsWith("{") ||
|
|
178
|
+
trimmed.startsWith("[")
|
|
179
|
+
) {
|
|
180
|
+
try {
|
|
181
|
+
payload = JSON.parse(text);
|
|
182
|
+
} catch (_err) {
|
|
183
|
+
payload = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const detail = extractErrorDetail(payload);
|
|
187
|
+
if (detail) return detail;
|
|
188
|
+
if (text) return text;
|
|
189
|
+
return `Request failed (${res.status})`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @param {string} path
|
|
194
|
+
* @param {ApiOptions} [options]
|
|
195
|
+
*/
|
|
196
|
+
export async function api(path, options = {}) {
|
|
197
|
+
/** @type {Record<string, string>} */
|
|
198
|
+
const headers = options.headers ? { ...options.headers } : {};
|
|
199
|
+
const opts = { ...options, headers };
|
|
200
|
+
const target = resolvePath(path);
|
|
201
|
+
const token = getAuthToken();
|
|
202
|
+
if (token && !headers.Authorization) {
|
|
203
|
+
headers.Authorization = `Bearer ${token}`;
|
|
204
|
+
}
|
|
205
|
+
if (opts.body && typeof opts.body === "object" && !(opts.body instanceof FormData)) {
|
|
206
|
+
headers["Content-Type"] = "application/json";
|
|
207
|
+
opts.body = JSON.stringify(opts.body);
|
|
208
|
+
}
|
|
209
|
+
const res = await fetch(target, opts);
|
|
210
|
+
if (!res.ok) {
|
|
211
|
+
const message = await buildErrorMessage(res);
|
|
212
|
+
throw new Error(message);
|
|
213
|
+
}
|
|
214
|
+
const contentType = res.headers.get("content-type") || "";
|
|
215
|
+
if (contentType.includes("application/json")) {
|
|
216
|
+
return res.json();
|
|
217
|
+
}
|
|
218
|
+
return res.text();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @param {string} path
|
|
223
|
+
* @param {StreamOptions} [options]
|
|
224
|
+
*/
|
|
225
|
+
export function streamEvents(path, options = {}) {
|
|
226
|
+
const { method = "GET", body = null, onMessage, onError, onFinish } = options;
|
|
227
|
+
const controller = new AbortController();
|
|
228
|
+
let fetchBody = body;
|
|
229
|
+
const target = resolvePath(path);
|
|
230
|
+
/** @type {Record<string, string>} */
|
|
231
|
+
const headers = {};
|
|
232
|
+
const token = getAuthToken();
|
|
233
|
+
if (token) {
|
|
234
|
+
headers.Authorization = `Bearer ${token}`;
|
|
235
|
+
}
|
|
236
|
+
if (fetchBody && typeof fetchBody === "object" && !(fetchBody instanceof FormData)) {
|
|
237
|
+
headers["Content-Type"] = "application/json";
|
|
238
|
+
fetchBody = JSON.stringify(fetchBody);
|
|
239
|
+
}
|
|
240
|
+
fetch(target, { method, body: fetchBody, headers, signal: controller.signal })
|
|
241
|
+
.then(async (res) => {
|
|
242
|
+
if (!res.ok) {
|
|
243
|
+
const message = await buildErrorMessage(res);
|
|
244
|
+
throw new Error(message);
|
|
245
|
+
}
|
|
246
|
+
if (!res.body) {
|
|
247
|
+
throw new Error("Streaming not supported in this browser");
|
|
248
|
+
}
|
|
249
|
+
const reader = res.body.getReader();
|
|
250
|
+
let buffer = "";
|
|
251
|
+
for (;;) {
|
|
252
|
+
const { value, done } = await reader.read();
|
|
253
|
+
if (done) break;
|
|
254
|
+
buffer += decoder.decode(value, { stream: true });
|
|
255
|
+
const chunks = buffer.split("\n\n");
|
|
256
|
+
buffer = chunks.pop();
|
|
257
|
+
for (const chunk of chunks) {
|
|
258
|
+
if (!chunk.trim()) continue;
|
|
259
|
+
const lines = chunk.split("\n");
|
|
260
|
+
let event = "message";
|
|
261
|
+
const dataLines = [];
|
|
262
|
+
for (const line of lines) {
|
|
263
|
+
if (line.startsWith("event:")) {
|
|
264
|
+
event = line.slice(6).trim();
|
|
265
|
+
} else if (line.startsWith("data:")) {
|
|
266
|
+
dataLines.push(line.slice(5).trimStart());
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (!dataLines.length) continue;
|
|
270
|
+
const data = dataLines.join("\n");
|
|
271
|
+
if (onMessage) onMessage(data, event || "message");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (!controller.signal.aborted && onFinish) {
|
|
275
|
+
onFinish();
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
.catch((err) => {
|
|
279
|
+
if (controller.signal.aborted) {
|
|
280
|
+
if (onFinish) onFinish();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (onError) onError(err);
|
|
284
|
+
if (onFinish) onFinish();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
return () => controller.abort();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function createPoller(fn, intervalMs, { immediate = true } = {}) {
|
|
291
|
+
let timer = null;
|
|
292
|
+
const tick = async () => {
|
|
293
|
+
try {
|
|
294
|
+
await fn();
|
|
295
|
+
} finally {
|
|
296
|
+
timer = setTimeout(tick, intervalMs);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
if (immediate) {
|
|
300
|
+
tick();
|
|
301
|
+
} else {
|
|
302
|
+
timer = setTimeout(tick, intervalMs);
|
|
303
|
+
}
|
|
304
|
+
return () => {
|
|
305
|
+
if (timer) clearTimeout(timer);
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function isMobileViewport() {
|
|
310
|
+
try {
|
|
311
|
+
return Boolean(window.matchMedia && window.matchMedia("(max-width: 640px)").matches);
|
|
312
|
+
} catch (_err) {
|
|
313
|
+
return window.innerWidth <= 640;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function setMobileChromeHidden(hidden) {
|
|
318
|
+
document.documentElement.classList.toggle("mobile-chrome-hidden", Boolean(hidden));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function setMobileComposeFixed(enabled) {
|
|
322
|
+
document.documentElement.classList.toggle("mobile-compose-fixed", Boolean(enabled));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const MODAL_BACKGROUND_IDS = ["hub-shell", "repo-shell"];
|
|
326
|
+
const FOCUSABLE_SELECTOR = [
|
|
327
|
+
"a[href]",
|
|
328
|
+
"button:not([disabled])",
|
|
329
|
+
"input:not([disabled]):not([type=\"hidden\"])",
|
|
330
|
+
"select:not([disabled])",
|
|
331
|
+
"textarea:not([disabled])",
|
|
332
|
+
"[tabindex]:not([tabindex=\"-1\"])",
|
|
333
|
+
].join(",");
|
|
334
|
+
let modalOpenCount = 0;
|
|
335
|
+
|
|
336
|
+
function getFocusableElements(container) {
|
|
337
|
+
if (!container || !container.querySelectorAll) return [];
|
|
338
|
+
return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
|
|
339
|
+
(el) => el && el.tabIndex !== -1 && !el.hidden && !el.disabled
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function setModalBackgroundHidden(hidden) {
|
|
344
|
+
if (hidden) {
|
|
345
|
+
modalOpenCount += 1;
|
|
346
|
+
} else {
|
|
347
|
+
modalOpenCount = Math.max(0, modalOpenCount - 1);
|
|
348
|
+
}
|
|
349
|
+
const shouldHide = modalOpenCount > 0;
|
|
350
|
+
MODAL_BACKGROUND_IDS.forEach((id) => {
|
|
351
|
+
const el = document.getElementById(id);
|
|
352
|
+
if (!el) return;
|
|
353
|
+
if (shouldHide) {
|
|
354
|
+
el.setAttribute("aria-hidden", "true");
|
|
355
|
+
try {
|
|
356
|
+
el.inert = true;
|
|
357
|
+
} catch (_err) {
|
|
358
|
+
el.setAttribute("inert", "");
|
|
359
|
+
}
|
|
360
|
+
} else {
|
|
361
|
+
el.removeAttribute("aria-hidden");
|
|
362
|
+
try {
|
|
363
|
+
el.inert = false;
|
|
364
|
+
} catch (_err) {
|
|
365
|
+
el.removeAttribute("inert");
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function handleTabKey(event, container) {
|
|
372
|
+
const focusable = getFocusableElements(container);
|
|
373
|
+
if (!focusable.length) {
|
|
374
|
+
event.preventDefault();
|
|
375
|
+
container?.focus?.();
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const currentIndex = focusable.indexOf(document.activeElement);
|
|
379
|
+
const lastIndex = focusable.length - 1;
|
|
380
|
+
if (event.shiftKey) {
|
|
381
|
+
if (currentIndex <= 0) {
|
|
382
|
+
event.preventDefault();
|
|
383
|
+
focusable[lastIndex].focus();
|
|
384
|
+
}
|
|
385
|
+
} else if (currentIndex === -1 || currentIndex === lastIndex) {
|
|
386
|
+
event.preventDefault();
|
|
387
|
+
focusable[0].focus();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function openModal(overlay, options = {}) {
|
|
392
|
+
if (!overlay) return () => {};
|
|
393
|
+
const {
|
|
394
|
+
closeOnEscape = true,
|
|
395
|
+
closeOnOverlay = true,
|
|
396
|
+
initialFocus,
|
|
397
|
+
returnFocusTo,
|
|
398
|
+
onKeydown,
|
|
399
|
+
onRequestClose,
|
|
400
|
+
} = options;
|
|
401
|
+
const dialog = overlay.querySelector(".modal-dialog") || overlay;
|
|
402
|
+
const previousActive = returnFocusTo || document.activeElement;
|
|
403
|
+
let isClosed = false;
|
|
404
|
+
|
|
405
|
+
const close = () => {
|
|
406
|
+
if (isClosed) return;
|
|
407
|
+
isClosed = true;
|
|
408
|
+
overlay.hidden = true;
|
|
409
|
+
overlay.removeEventListener("click", handleOverlayClick);
|
|
410
|
+
document.removeEventListener("keydown", handleKeydown);
|
|
411
|
+
setModalBackgroundHidden(false);
|
|
412
|
+
if (previousActive && previousActive.focus) {
|
|
413
|
+
previousActive.focus();
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const requestClose = onRequestClose || close;
|
|
418
|
+
|
|
419
|
+
const handleOverlayClick = (event) => {
|
|
420
|
+
if (closeOnOverlay && event.target === overlay) {
|
|
421
|
+
requestClose("overlay");
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const handleKeydown = (event) => {
|
|
426
|
+
if (event.key === "Escape" && closeOnEscape) {
|
|
427
|
+
event.preventDefault();
|
|
428
|
+
requestClose("escape");
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (event.key === "Tab") {
|
|
432
|
+
handleTabKey(event, dialog);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (onKeydown) {
|
|
436
|
+
onKeydown(event);
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
overlay.hidden = false;
|
|
441
|
+
setModalBackgroundHidden(true);
|
|
442
|
+
|
|
443
|
+
overlay.addEventListener("click", handleOverlayClick);
|
|
444
|
+
document.addEventListener("keydown", handleKeydown);
|
|
445
|
+
|
|
446
|
+
const focusTarget = initialFocus || getFocusableElements(dialog)[0] || dialog;
|
|
447
|
+
if (focusTarget && focusTarget.focus) {
|
|
448
|
+
focusTarget.focus();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return close;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Show a custom confirmation modal dialog.
|
|
456
|
+
* Works consistently across desktop and mobile.
|
|
457
|
+
* @param {string} message - The confirmation message to display
|
|
458
|
+
* @param {Object} [options] - Optional configuration
|
|
459
|
+
* @param {string} [options.confirmText="Confirm"] - Text for the confirm button
|
|
460
|
+
* @param {string} [options.cancelText="Cancel"] - Text for the cancel button
|
|
461
|
+
* @param {boolean} [options.danger=true] - Whether to style confirm as danger
|
|
462
|
+
* @returns {Promise<boolean>} - Resolves to true if confirmed, false if cancelled
|
|
463
|
+
*/
|
|
464
|
+
export function confirmModal(message, options = {}) {
|
|
465
|
+
const { confirmText = "Confirm", cancelText = "Cancel", danger = true } = options;
|
|
466
|
+
return new Promise((resolve) => {
|
|
467
|
+
const overlay = document.getElementById("confirm-modal");
|
|
468
|
+
const messageEl = document.getElementById("confirm-modal-message");
|
|
469
|
+
const okBtn = document.getElementById("confirm-modal-ok");
|
|
470
|
+
const cancelBtn = document.getElementById("confirm-modal-cancel");
|
|
471
|
+
|
|
472
|
+
const triggerEl = document.activeElement;
|
|
473
|
+
messageEl.textContent = message;
|
|
474
|
+
okBtn.textContent = confirmText;
|
|
475
|
+
cancelBtn.textContent = cancelText;
|
|
476
|
+
okBtn.className = danger ? "danger" : "primary";
|
|
477
|
+
let closeModal = null;
|
|
478
|
+
let settled = false;
|
|
479
|
+
|
|
480
|
+
const finalize = (result) => {
|
|
481
|
+
if (settled) return;
|
|
482
|
+
settled = true;
|
|
483
|
+
okBtn.removeEventListener("click", onOk);
|
|
484
|
+
cancelBtn.removeEventListener("click", onCancel);
|
|
485
|
+
if (closeModal) {
|
|
486
|
+
const close = closeModal;
|
|
487
|
+
closeModal = null;
|
|
488
|
+
close();
|
|
489
|
+
}
|
|
490
|
+
resolve(result);
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
const onOk = () => {
|
|
494
|
+
finalize(true);
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const onCancel = () => {
|
|
498
|
+
finalize(false);
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
closeModal = openModal(overlay, {
|
|
502
|
+
initialFocus: cancelBtn,
|
|
503
|
+
returnFocusTo: triggerEl,
|
|
504
|
+
onRequestClose: () => finalize(false),
|
|
505
|
+
onKeydown: (event) => {
|
|
506
|
+
if (event.key === "Enter" && document.activeElement === okBtn) {
|
|
507
|
+
event.preventDefault();
|
|
508
|
+
finalize(true);
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
okBtn.addEventListener("click", onOk);
|
|
514
|
+
cancelBtn.addEventListener("click", onCancel);
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Show a custom input modal dialog.
|
|
520
|
+
* Works consistently across desktop and mobile.
|
|
521
|
+
* @param {string} message - The prompt message to display
|
|
522
|
+
* @param {Object} [options] - Optional configuration
|
|
523
|
+
* @param {string} [options.placeholder=""] - Placeholder text for input
|
|
524
|
+
* @param {string} [options.defaultValue=""] - Default value for input
|
|
525
|
+
* @param {string} [options.confirmText="OK"] - Text for the confirm button
|
|
526
|
+
* @param {string} [options.cancelText="Cancel"] - Text for the cancel button
|
|
527
|
+
* @returns {Promise<string|null>} - Resolves to the input value, or null if cancelled
|
|
528
|
+
*/
|
|
529
|
+
export function inputModal(message, options = {}) {
|
|
530
|
+
const { placeholder = "", defaultValue = "", confirmText = "OK", cancelText = "Cancel" } = options;
|
|
531
|
+
return new Promise((resolve) => {
|
|
532
|
+
const overlay = document.getElementById("input-modal");
|
|
533
|
+
const messageEl = document.getElementById("input-modal-message");
|
|
534
|
+
const inputEl = /** @type {HTMLInputElement} */ (
|
|
535
|
+
document.getElementById("input-modal-input")
|
|
536
|
+
);
|
|
537
|
+
const okBtn = /** @type {HTMLButtonElement} */ (
|
|
538
|
+
document.getElementById("input-modal-ok")
|
|
539
|
+
);
|
|
540
|
+
const cancelBtn = /** @type {HTMLButtonElement} */ (
|
|
541
|
+
document.getElementById("input-modal-cancel")
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
const triggerEl = document.activeElement;
|
|
545
|
+
messageEl.textContent = message;
|
|
546
|
+
inputEl.placeholder = placeholder;
|
|
547
|
+
inputEl.value = defaultValue;
|
|
548
|
+
okBtn.textContent = confirmText;
|
|
549
|
+
cancelBtn.textContent = cancelText;
|
|
550
|
+
let closeModal = null;
|
|
551
|
+
let settled = false;
|
|
552
|
+
|
|
553
|
+
const finalize = (result) => {
|
|
554
|
+
if (settled) return;
|
|
555
|
+
settled = true;
|
|
556
|
+
okBtn.removeEventListener("click", onOk);
|
|
557
|
+
cancelBtn.removeEventListener("click", onCancel);
|
|
558
|
+
if (closeModal) {
|
|
559
|
+
const close = closeModal;
|
|
560
|
+
closeModal = null;
|
|
561
|
+
close();
|
|
562
|
+
}
|
|
563
|
+
resolve(result);
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
const onOk = () => {
|
|
567
|
+
const value = inputEl.value.trim();
|
|
568
|
+
finalize(value || null);
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
const onCancel = () => {
|
|
572
|
+
finalize(null);
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
closeModal = openModal(overlay, {
|
|
576
|
+
initialFocus: inputEl,
|
|
577
|
+
returnFocusTo: triggerEl,
|
|
578
|
+
onRequestClose: () => finalize(null),
|
|
579
|
+
onKeydown: (event) => {
|
|
580
|
+
if (event.key === "Enter") {
|
|
581
|
+
const active = document.activeElement;
|
|
582
|
+
if (active === inputEl || active === okBtn) {
|
|
583
|
+
event.preventDefault();
|
|
584
|
+
onOk();
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
okBtn.addEventListener("click", onOk);
|
|
591
|
+
cancelBtn.addEventListener("click", onCancel);
|
|
592
|
+
|
|
593
|
+
// Focus the input field
|
|
594
|
+
inputEl.focus();
|
|
595
|
+
inputEl.select();
|
|
596
|
+
});
|
|
597
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Applies to: xterm.js, xterm-addon-fit.js, xterm.css.
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2014 The xterm.js authors
|
|
6
|
+
Copyright (c) 2012-2013, Christopher Jeffrey (term.js)
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|