@wipcomputer/wip-ldm-os 0.4.84 → 0.4.85-alpha.10

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.
@@ -1,254 +0,0 @@
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, viewport-fit=cover">
6
- <title>Codex remote control</title>
7
- <style>
8
- *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
9
- :root {
10
- --bg: #FFFDF5;
11
- --bg-event: #F5F3ED;
12
- --bg-tool: #F0EDE6;
13
- --text: #1a1a1a;
14
- --text-muted: #8a8580;
15
- --accent: #0033FF;
16
- --danger: #b00020;
17
- --border: #E0DDD6;
18
- --user-bubble: #E8F0FE;
19
- --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;
20
- --mono: ui-monospace, "SF Mono", Menlo, monospace;
21
- }
22
- html, body { height: 100%; font-family: var(--font); background: var(--bg); color: var(--text); }
23
- body { display: flex; flex-direction: column; }
24
- header { padding: 12px 16px; padding-top: calc(12px + env(safe-area-inset-top, 0px)); border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 10px; }
25
- header .id { flex: 1; font-size: 13px; color: var(--text-muted); font-family: var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
26
- header .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-muted); }
27
- header .dot.online { background: #2ea44f; }
28
- header .dot.offline { background: var(--danger); }
29
- main { flex: 1; overflow-y: auto; padding: 16px; padding-bottom: 0; -webkit-overflow-scrolling: touch; }
30
- .event { margin-bottom: 12px; padding: 12px 14px; border-radius: 10px; background: var(--bg-event); font-size: 14px; line-height: 1.45; }
31
- .event .meta { font-size: 11px; color: var(--text-muted); font-family: var(--mono); margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.05em; }
32
- .event.user { background: var(--user-bubble); }
33
- .event.agent_message { background: var(--bg); border: 1px solid var(--border); }
34
- .event.command_execution { background: var(--bg-tool); font-family: var(--mono); white-space: pre-wrap; word-break: break-all; }
35
- .event.command_execution.failed { border-left: 3px solid var(--danger); }
36
- .event.error { background: #fff0f0; border: 1px solid #f0c0c0; color: var(--danger); }
37
- .event.system { background: transparent; color: var(--text-muted); font-size: 12px; padding: 6px 0; text-align: center; }
38
- .event pre { font-family: var(--mono); white-space: pre-wrap; word-break: break-word; font-size: 13px; }
39
- footer { padding: 12px; padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)); border-top: 1px solid var(--border); background: var(--bg); }
40
- .composer { display: flex; gap: 8px; align-items: flex-end; }
41
- textarea {
42
- flex: 1; min-height: 44px; max-height: 120px; padding: 12px;
43
- border: 1px solid var(--border); border-radius: 10px;
44
- background: var(--bg); color: var(--text); font-family: var(--font); font-size: 16px;
45
- resize: none;
46
- }
47
- textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
48
- button { padding: 12px 16px; border: none; border-radius: 10px; font-family: var(--font); font-size: 14px; font-weight: 600; cursor: pointer; -webkit-tap-highlight-color: transparent; }
49
- button:active { transform: scale(0.97); }
50
- button:disabled { opacity: 0.4; cursor: not-allowed; }
51
- .btn-send { background: var(--accent); color: white; }
52
- .btn-stop { background: var(--danger); color: white; }
53
- </style>
54
- </head>
55
- <body>
56
- <header>
57
- <div class="dot" id="presence" title="connecting"></div>
58
- <div class="id" id="threadId">...</div>
59
- <button id="stopBtn" class="btn-stop" type="button" disabled>Stop</button>
60
- </header>
61
- <main id="log"></main>
62
- <footer>
63
- <form class="composer" id="composer">
64
- <textarea id="prompt" rows="1" placeholder="Tell Codex what to do..." autocomplete="off"></textarea>
65
- <button type="submit" class="btn-send" id="sendBtn">Send</button>
66
- </form>
67
- </footer>
68
- <script>
69
- function getApiKey() { return sessionStorage.getItem("wip_api_key"); }
70
- function getHandle() { return sessionStorage.getItem("wip_handle") || ""; }
71
-
72
- function ensureSignedIn() {
73
- if (!getApiKey()) {
74
- location.href = "/app/login.html?next=" + encodeURIComponent(location.pathname);
75
- return false;
76
- }
77
- return true;
78
- }
79
-
80
- function parsePath() {
81
- // /:handle/codex-remote-control/:threadId
82
- const m = location.pathname.match(/^\/([^/]+)\/codex-remote-control\/([^/]+)\/?$/);
83
- if (!m) return null;
84
- return { handle: decodeURIComponent(m[1]), threadId: decodeURIComponent(m[2]) };
85
- }
86
-
87
- function setPresence(state) {
88
- const dot = document.getElementById("presence");
89
- dot.classList.remove("online", "offline");
90
- if (state === "online") dot.classList.add("online");
91
- if (state === "offline") dot.classList.add("offline");
92
- dot.title = state;
93
- }
94
-
95
- function appendEvent(html, kind) {
96
- const log = document.getElementById("log");
97
- const div = document.createElement("div");
98
- div.className = "event " + (kind || "");
99
- div.innerHTML = html;
100
- log.appendChild(div);
101
- log.scrollTop = log.scrollHeight;
102
- return div;
103
- }
104
-
105
- function escapeHtml(s) {
106
- return String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", "\"": "&quot;", "'": "&#39;" }[c]));
107
- }
108
-
109
- function renderItem(item) {
110
- if (item.type === "agent_message") {
111
- return appendEvent('<div class="meta">codex</div>' + escapeHtml(item.text || "").replace(/\n/g, "<br>"), "agent_message");
112
- }
113
- if (item.type === "command_execution") {
114
- const status = (item.status || "").toString();
115
- const out = item.aggregated_output ? '\n\n' + item.aggregated_output : "";
116
- return appendEvent(
117
- '<div class="meta">$ ' + escapeHtml(status) + (item.exit_code != null ? " (exit " + item.exit_code + ")" : "") + '</div>' +
118
- '<pre>' + escapeHtml(item.command || "") + escapeHtml(out) + '</pre>',
119
- "command_execution" + (status === "failed" ? " failed" : ""),
120
- );
121
- }
122
- if (item.type === "reasoning") {
123
- return appendEvent('<div class="meta">reasoning</div>' + escapeHtml(item.text || ""), "reasoning");
124
- }
125
- return appendEvent('<div class="meta">' + escapeHtml(item.type || "item") + '</div><pre>' + escapeHtml(JSON.stringify(item, null, 2)) + '</pre>', "item");
126
- }
127
-
128
- let ws = null;
129
- let pendingId = 1;
130
-
131
- function send(req) {
132
- if (!ws || ws.readyState !== WebSocket.OPEN) return;
133
- ws.send(JSON.stringify(req));
134
- }
135
-
136
- function connect(threadId) {
137
- const apiKey = getApiKey();
138
- const proto = location.protocol === "https:" ? "wss:" : "ws:";
139
- const url = proto + "//" + location.host + "/api/codex-relay/web/" + encodeURIComponent(threadId) + "?token=" + encodeURIComponent(apiKey);
140
- ws = new WebSocket(url);
141
-
142
- ws.addEventListener("open", () => {
143
- setPresence("online");
144
- appendEvent("connected. open this thread in Codex on your Mac if it's not already.", "system");
145
- });
146
-
147
- ws.addEventListener("close", (ev) => {
148
- setPresence("offline");
149
- appendEvent("disconnected (code " + ev.code + ")", "system");
150
- });
151
-
152
- ws.addEventListener("error", () => {
153
- setPresence("offline");
154
- });
155
-
156
- ws.addEventListener("message", (ev) => {
157
- let msg;
158
- try { msg = JSON.parse(ev.data); } catch { return; }
159
- if (msg.type === "session.started") {
160
- // Daemon assigned a temp id; the real thread id will arrive in thread.started.
161
- return;
162
- }
163
- if (msg.type === "session.event") {
164
- const evt = msg.event || {};
165
- if (evt.type === "thread.started") {
166
- // ok
167
- return;
168
- }
169
- if (evt.type === "turn.started") {
170
- document.getElementById("stopBtn").disabled = false;
171
- return;
172
- }
173
- if (evt.type === "item.completed" && evt.item) {
174
- renderItem(evt.item);
175
- return;
176
- }
177
- if (evt.type === "item.started") {
178
- return; // skip; we render on completed for now
179
- }
180
- if (evt.type === "turn.completed") {
181
- document.getElementById("stopBtn").disabled = true;
182
- const u = evt.usage;
183
- if (u) appendEvent("turn complete (" + (u.input_tokens || 0) + " in / " + (u.output_tokens || 0) + " out)", "system");
184
- else appendEvent("turn complete", "system");
185
- return;
186
- }
187
- if (evt.type === "turn.failed") {
188
- document.getElementById("stopBtn").disabled = true;
189
- appendEvent("turn failed: " + (evt.error && evt.error.message ? evt.error.message : "unknown"), "error");
190
- return;
191
- }
192
- return;
193
- }
194
- if (msg.type === "ack") return;
195
- if (msg.type === "error") {
196
- appendEvent("error: " + (msg.message || ""), "error");
197
- return;
198
- }
199
- });
200
- }
201
-
202
- function init() {
203
- if (!ensureSignedIn()) return;
204
- const parsed = parsePath();
205
- if (!parsed) {
206
- appendEvent("Invalid URL. Expected /<handle>/codex-remote-control/<thread-id>.", "error");
207
- return;
208
- }
209
- document.getElementById("threadId").textContent = parsed.threadId;
210
- connect(parsed.threadId);
211
-
212
- // Open or attach to the session on the daemon. session.start returns a temp
213
- // sessionId; the actual thread.id arrives via thread.started in the stream.
214
- setTimeout(() => {
215
- send({ type: "session.start", id: "open-" + (pendingId += 1) });
216
- }, 250);
217
-
218
- document.getElementById("composer").addEventListener("submit", (ev) => {
219
- ev.preventDefault();
220
- const input = document.getElementById("prompt");
221
- const text = input.value.trim();
222
- if (!text) return;
223
- input.value = "";
224
- appendEvent('<div class="meta">you</div>' + escapeHtml(text), "user");
225
- send({
226
- type: "session.send",
227
- id: "send-" + (pendingId += 1),
228
- sessionId: parsed.threadId,
229
- prompt: text,
230
- });
231
- });
232
-
233
- document.getElementById("stopBtn").addEventListener("click", () => {
234
- send({ type: "session.interrupt", id: "stop-" + (pendingId += 1), sessionId: parsed.threadId });
235
- });
236
-
237
- // Submit on Cmd+Enter / Ctrl+Enter; auto-resize.
238
- const ta = document.getElementById("prompt");
239
- ta.addEventListener("input", () => {
240
- ta.style.height = "auto";
241
- ta.style.height = Math.min(120, ta.scrollHeight) + "px";
242
- });
243
- ta.addEventListener("keydown", (ev) => {
244
- if ((ev.metaKey || ev.ctrlKey) && ev.key === "Enter") {
245
- ev.preventDefault();
246
- document.getElementById("composer").requestSubmit();
247
- }
248
- });
249
- }
250
-
251
- init();
252
- </script>
253
- </body>
254
- </html>