clankerbend 0.1.1 → 0.1.3

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,6 +1,6 @@
1
1
  (function installCodexVimNavigator() {
2
2
  const VIM_NAV_APP_ID = "onewill.vim-nav";
3
- const BRIDGE_VERSION = 114;
3
+ const BRIDGE_VERSION = 116;
4
4
  const RUNTIME_KEY = "__clankerbendRuntime";
5
5
  const STYLE_ID = "codex-vim-nav-style";
6
6
  const ANNOTATION_CLASS = "codex-vim-nav-annotation";
@@ -11,6 +11,7 @@
11
11
  const MODE_BADGE_ID = "codex-vim-nav-mode-badge";
12
12
  const HOST_UI_CLASS = "clankerbend-host-ui";
13
13
  const SELECTION_MENU_ID = "clankerbend-selection-menu";
14
+ const ACCOUNT_SWITCHER_ID = "clankerbend-account-switcher";
14
15
  const OVERLAY_ID = "clankerbend-anchored-overlay";
15
16
  const COMPOSER_CHIPS_ID = "clankerbend-composer-chips";
16
17
  const SELECTOR = [
@@ -67,6 +68,12 @@
67
68
  activeTextSelection: null,
68
69
  lastSelectionMenuAt: 0,
69
70
  renderedOverlaySignature: null,
71
+ accountUiStatus: "",
72
+ accountAddOpen: false,
73
+ accountAddLabel: "",
74
+ accountManageOpen: false,
75
+ accountPendingAdoptId: null,
76
+ accountPendingDeleteId: null,
70
77
  pendingComposerSubmission: null,
71
78
  submittedComposerSubmissions: []
72
79
  };
@@ -121,6 +128,7 @@
121
128
  color: #241f12 !important;
122
129
  }
123
130
  #${OVERLAY_ID} button,
131
+ #${ACCOUNT_SWITCHER_ID} button,
124
132
  #${COMPOSER_CHIPS_ID} button {
125
133
  appearance: none !important;
126
134
  border: 0 !important;
@@ -131,6 +139,202 @@
131
139
  font: 650 12px/1 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
132
140
  padding: 7px 8px !important;
133
141
  }
142
+ #${ACCOUNT_SWITCHER_ID} {
143
+ display: grid !important;
144
+ gap: 7px !important;
145
+ min-width: 220px !important;
146
+ max-width: 320px !important;
147
+ border-top: 1px solid rgba(148, 163, 184, .24) !important;
148
+ border-bottom: 1px solid rgba(148, 163, 184, .18) !important;
149
+ margin: 5px 0 !important;
150
+ padding: 8px 4px !important;
151
+ color: inherit !important;
152
+ }
153
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-head {
154
+ display: flex !important;
155
+ align-items: center !important;
156
+ justify-content: space-between !important;
157
+ gap: 8px !important;
158
+ padding: 0 6px !important;
159
+ }
160
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-title {
161
+ color: currentColor !important;
162
+ font: 700 11px/1.2 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
163
+ letter-spacing: 0 !important;
164
+ }
165
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-status {
166
+ color: color-mix(in srgb, currentColor 62%, transparent) !important;
167
+ font: 500 11px/1.2 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
168
+ overflow: hidden !important;
169
+ text-overflow: ellipsis !important;
170
+ white-space: nowrap !important;
171
+ }
172
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-list {
173
+ display: grid !important;
174
+ gap: 2px !important;
175
+ }
176
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-row {
177
+ display: grid !important;
178
+ grid-template-columns: minmax(0, 1fr) auto !important;
179
+ align-items: center !important;
180
+ gap: 5px !important;
181
+ border-radius: 6px !important;
182
+ padding: 2px !important;
183
+ }
184
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-row.is-active {
185
+ background: rgba(143, 199, 212, .14) !important;
186
+ }
187
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-main {
188
+ display: grid !important;
189
+ min-width: 0 !important;
190
+ border-radius: 5px !important;
191
+ padding: 6px !important;
192
+ text-align: left !important;
193
+ color: inherit !important;
194
+ }
195
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-main:hover,
196
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-icon:hover {
197
+ background: rgba(148, 163, 184, .16) !important;
198
+ }
199
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-label,
200
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-meta {
201
+ overflow: hidden !important;
202
+ text-overflow: ellipsis !important;
203
+ white-space: nowrap !important;
204
+ }
205
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-label {
206
+ font: 650 12px/1.2 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
207
+ }
208
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-meta {
209
+ color: color-mix(in srgb, currentColor 58%, transparent) !important;
210
+ font: 500 11px/1.2 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
211
+ }
212
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-icon {
213
+ width: 28px !important;
214
+ height: 28px !important;
215
+ display: inline-grid !important;
216
+ place-items: center !important;
217
+ border-radius: 5px !important;
218
+ color: color-mix(in srgb, currentColor 76%, transparent) !important;
219
+ padding: 0 !important;
220
+ }
221
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-actions {
222
+ display: grid !important;
223
+ grid-template-columns: 1fr 1fr !important;
224
+ gap: 4px !important;
225
+ }
226
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-actions button {
227
+ border-radius: 5px !important;
228
+ background: rgba(148, 163, 184, .12) !important;
229
+ color: inherit !important;
230
+ min-width: 0 !important;
231
+ }
232
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-add-form {
233
+ display: grid !important;
234
+ gap: 6px !important;
235
+ border-radius: 6px !important;
236
+ background: rgba(148, 163, 184, .10) !important;
237
+ padding: 6px !important;
238
+ }
239
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-add-form input {
240
+ width: 100% !important;
241
+ min-width: 0 !important;
242
+ border: 1px solid rgba(148, 163, 184, .32) !important;
243
+ border-radius: 5px !important;
244
+ background: rgba(0, 0, 0, .18) !important;
245
+ color: inherit !important;
246
+ font: 500 12px/1.2 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
247
+ outline: none !important;
248
+ padding: 7px 8px !important;
249
+ }
250
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-add-form input:focus {
251
+ border-color: rgba(143, 199, 212, .76) !important;
252
+ box-shadow: 0 0 0 2px rgba(143, 199, 212, .18) !important;
253
+ }
254
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-add-actions {
255
+ display: grid !important;
256
+ grid-template-columns: 1fr 1fr !important;
257
+ gap: 4px !important;
258
+ }
259
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-add-actions button {
260
+ background: rgba(148, 163, 184, .14) !important;
261
+ }
262
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-manage {
263
+ display: grid !important;
264
+ gap: 7px !important;
265
+ border-top: 1px solid rgba(148, 163, 184, .18) !important;
266
+ padding-top: 7px !important;
267
+ }
268
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-manage-head {
269
+ display: flex !important;
270
+ align-items: baseline !important;
271
+ justify-content: space-between !important;
272
+ gap: 8px !important;
273
+ padding: 0 6px !important;
274
+ }
275
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-manage-head strong {
276
+ font: 700 11px/1.2 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
277
+ }
278
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-manage-head span {
279
+ color: color-mix(in srgb, currentColor 58%, transparent) !important;
280
+ font: 500 11px/1.2 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
281
+ }
282
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-manage-list {
283
+ display: grid !important;
284
+ gap: 6px !important;
285
+ max-height: 204px !important;
286
+ overflow-y: auto !important;
287
+ overscroll-behavior: contain !important;
288
+ padding-right: 2px !important;
289
+ }
290
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-manage-list::-webkit-scrollbar {
291
+ width: 8px !important;
292
+ }
293
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-manage-list::-webkit-scrollbar-thumb {
294
+ border-radius: 999px !important;
295
+ background: rgba(148, 163, 184, .34) !important;
296
+ }
297
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-manage-row {
298
+ display: grid !important;
299
+ grid-template-columns: minmax(0, 1fr) auto !important;
300
+ align-items: center !important;
301
+ gap: 7px !important;
302
+ border-radius: 6px !important;
303
+ background: rgba(148, 163, 184, .10) !important;
304
+ padding: 6px !important;
305
+ }
306
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-manage-title {
307
+ display: grid !important;
308
+ min-width: 0 !important;
309
+ }
310
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-manage-title strong,
311
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-manage-title span {
312
+ overflow: hidden !important;
313
+ text-overflow: ellipsis !important;
314
+ white-space: nowrap !important;
315
+ }
316
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-manage-title strong {
317
+ font: 650 12px/1.2 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
318
+ }
319
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-manage-title span {
320
+ color: color-mix(in srgb, currentColor 58%, transparent) !important;
321
+ font: 500 11px/1.2 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
322
+ }
323
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-manage-actions {
324
+ display: flex !important;
325
+ align-items: center !important;
326
+ justify-content: flex-end !important;
327
+ gap: 4px !important;
328
+ }
329
+ #${ACCOUNT_SWITCHER_ID} .clankerbend-account-manage-actions button {
330
+ background: rgba(148, 163, 184, .14) !important;
331
+ white-space: nowrap !important;
332
+ padding: 6px 7px !important;
333
+ }
334
+ #${ACCOUNT_SWITCHER_ID} button:disabled {
335
+ cursor: default !important;
336
+ opacity: .55 !important;
337
+ }
134
338
  #${OVERLAY_ID} {
135
339
  position: fixed !important;
136
340
  z-index: 2147483004 !important;
@@ -261,6 +465,7 @@
261
465
  font: 600 12px/1.25 ui-monospace, SFMono-Regular, Menlo, monospace !important;
262
466
  letter-spacing: 0 !important;
263
467
  user-select: none !important;
468
+ pointer-events: none !important;
264
469
  }
265
470
  #${MODE_BADGE_ID}.is-visible {
266
471
  display: flex !important;
@@ -290,6 +495,7 @@
290
495
  padding: 0 3px !important;
291
496
  font: 700 12px/1 ui-monospace, SFMono-Regular, Menlo, monospace !important;
292
497
  cursor: pointer !important;
498
+ pointer-events: auto !important;
293
499
  }
294
500
  #${MODE_BADGE_ID} .codex-vim-nav-dismiss:hover {
295
501
  color: #e8f6fa !important;
@@ -306,6 +512,7 @@
306
512
  padding: 0 5px !important;
307
513
  font: 700 11px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace !important;
308
514
  cursor: pointer !important;
515
+ pointer-events: auto !important;
309
516
  }
310
517
  #${MODE_BADGE_ID} .codex-vim-nav-help-toggle:hover {
311
518
  border-color: rgba(143, 199, 212, .75) !important;
@@ -2131,6 +2338,8 @@
2131
2338
 
2132
2339
  function applyHostState(hostState) {
2133
2340
  state.hostState = hostState || null;
2341
+ settleAccountUiStatus();
2342
+ renderCodexAccountSwitcher();
2134
2343
  renderSelectionMenu();
2135
2344
  renderOverlay();
2136
2345
  renderComposerContextChips();
@@ -2139,6 +2348,430 @@
2139
2348
  return { ok: true };
2140
2349
  }
2141
2350
 
2351
+ function settleAccountUiStatus() {
2352
+ if (state.accountUiStatus !== "Saving default...") return;
2353
+ if (state.hostState?.codexAccounts?.switching) return;
2354
+ state.accountUiStatus = "Default saved";
2355
+ setTimeout(() => {
2356
+ if (state.accountUiStatus !== "Default saved") return;
2357
+ state.accountUiStatus = "";
2358
+ renderCodexAccountSwitcher();
2359
+ }, 1400);
2360
+ }
2361
+
2362
+ function codexAccountsState() {
2363
+ return state.hostState?.codexAccounts || null;
2364
+ }
2365
+
2366
+ function visibleElement(el) {
2367
+ if (!(el instanceof HTMLElement)) return false;
2368
+ if (!el.isConnected) return false;
2369
+ if (isHostUiElement(el)) return false;
2370
+ const rect = el.getBoundingClientRect();
2371
+ if (rect.width <= 0 || rect.height <= 0) return false;
2372
+ const style = getComputedStyle(el);
2373
+ return style.visibility !== "hidden" && style.display !== "none";
2374
+ }
2375
+
2376
+ function normalizedText(el) {
2377
+ return String(el?.innerText || el?.textContent || "").replace(/\s+/g, " ").trim();
2378
+ }
2379
+
2380
+ function findLogoutMenuItem() {
2381
+ const candidates = Array.from(document.querySelectorAll("button,a,[role='menuitem'],[role='button']"))
2382
+ .filter(visibleElement)
2383
+ .map((el) => {
2384
+ const text = normalizedText(el);
2385
+ const label = [text, el.getAttribute("aria-label"), el.getAttribute("title")].filter(Boolean).join(" ");
2386
+ return { el, label };
2387
+ })
2388
+ .filter((candidate) => /\b(log\s*out|logout|sign\s*out)\b/i.test(candidate.label));
2389
+ return candidates
2390
+ .map((candidate) => candidate.el.closest?.("button,a,[role='menuitem']") || candidate.el)
2391
+ .find((el) => el instanceof HTMLElement && visibleElement(el)) || null;
2392
+ }
2393
+
2394
+ function accountMenuInsertionParent(logoutItem) {
2395
+ if (!(logoutItem instanceof HTMLElement)) return null;
2396
+ for (let node = logoutItem.parentElement; node && node !== document.body && node !== document.documentElement; node = node.parentElement) {
2397
+ if (isHostUiElement(node)) return null;
2398
+ const rect = node.getBoundingClientRect();
2399
+ if (rect.width < 150 || rect.width > 520 || rect.height < 40 || rect.height > window.innerHeight * 0.9) continue;
2400
+ if (node.contains(logoutItem)) return node;
2401
+ }
2402
+ return logoutItem.parentElement;
2403
+ }
2404
+
2405
+ function accountMenuInsertionReference(parent, logoutItem) {
2406
+ if (!(parent instanceof HTMLElement) || !(logoutItem instanceof HTMLElement)) return null;
2407
+ let reference = logoutItem;
2408
+ while (reference?.parentElement && reference.parentElement !== parent) {
2409
+ reference = reference.parentElement;
2410
+ }
2411
+ return reference?.parentElement === parent ? reference : null;
2412
+ }
2413
+
2414
+ function accountUiSignature(accountsState) {
2415
+ const accounts = accountsState?.accounts || [];
2416
+ return JSON.stringify({
2417
+ active: accountsState?.activeAccountId || null,
2418
+ def: accountsState?.clankerbendDefaultAccountId || null,
2419
+ switching: Boolean(accountsState?.switching),
2420
+ status: state.accountUiStatus,
2421
+ addOpen: state.accountAddOpen,
2422
+ manageOpen: state.accountManageOpen,
2423
+ pendingAdopt: state.accountPendingAdoptId,
2424
+ pendingDelete: state.accountPendingDeleteId,
2425
+ accounts: accounts.map((account) => [
2426
+ account.id,
2427
+ account.kind,
2428
+ account.label,
2429
+ account.codexHome,
2430
+ account.auth?.authJson ? 1 : 0,
2431
+ account.backup ? 1 : 0
2432
+ ])
2433
+ });
2434
+ }
2435
+
2436
+ function renderCodexAccountSwitcher() {
2437
+ const accountsState = codexAccountsState();
2438
+ const logoutItem = findLogoutMenuItem();
2439
+ if (!accountsState?.available || !logoutItem) {
2440
+ document.getElementById(ACCOUNT_SWITCHER_ID)?.remove();
2441
+ return;
2442
+ }
2443
+
2444
+ const parent = accountMenuInsertionParent(logoutItem);
2445
+ if (!(parent instanceof HTMLElement)) return;
2446
+ const reference = accountMenuInsertionReference(parent, logoutItem);
2447
+ if (!(reference instanceof HTMLElement)) return;
2448
+ let ui = document.getElementById(ACCOUNT_SWITCHER_ID);
2449
+ if (ui && ui.parentElement !== parent) {
2450
+ ui.remove();
2451
+ ui = null;
2452
+ }
2453
+ if (!ui) {
2454
+ ui = document.createElement("div");
2455
+ ui.id = ACCOUNT_SWITCHER_ID;
2456
+ ui.className = HOST_UI_CLASS;
2457
+ stopNativeMenuPropagation(ui);
2458
+ parent.insertBefore(ui, reference);
2459
+ } else if (ui.nextElementSibling !== reference) {
2460
+ parent.insertBefore(ui, reference);
2461
+ }
2462
+
2463
+ const signature = accountUiSignature(accountsState);
2464
+ if (ui.dataset.signature === signature) return;
2465
+ ui.dataset.signature = signature;
2466
+ ui.replaceChildren();
2467
+
2468
+ const head = document.createElement("div");
2469
+ head.className = "clankerbend-account-head";
2470
+ const title = document.createElement("div");
2471
+ title.className = "clankerbend-account-title";
2472
+ title.textContent = "ClankerID";
2473
+ const status = document.createElement("div");
2474
+ status.className = "clankerbend-account-status";
2475
+ status.textContent = state.accountUiStatus || (accountsState.switching ? "Switching..." : `${accountsState.accounts?.length || 0}/${accountsState.maxAccounts || 20}`);
2476
+ head.append(title, status);
2477
+ ui.appendChild(head);
2478
+
2479
+ if (state.accountManageOpen) {
2480
+ ui.appendChild(renderCodexAccountManage(accountsState));
2481
+ } else {
2482
+ const list = document.createElement("div");
2483
+ list.className = "clankerbend-account-list";
2484
+ for (const account of accountsState.accounts || []) {
2485
+ list.appendChild(renderCodexAccountRow(account, accountsState));
2486
+ }
2487
+ ui.appendChild(list);
2488
+ }
2489
+
2490
+ const actions = document.createElement("div");
2491
+ actions.className = "clankerbend-account-actions";
2492
+ const add = document.createElement("button");
2493
+ add.type = "button";
2494
+ add.textContent = "Add account";
2495
+ add.disabled = Boolean(accountsState.switching) || (accountsState.accounts?.length || 0) >= (accountsState.maxAccounts || 20);
2496
+ bindInjectedActivation(add, () => {
2497
+ state.accountAddOpen = true;
2498
+ state.accountUiStatus = "";
2499
+ renderCodexAccountSwitcher();
2500
+ setTimeout(() => {
2501
+ document.querySelector(`#${ACCOUNT_SWITCHER_ID} input[data-clankerbend-account-label]`)?.focus?.({ preventScroll: true });
2502
+ }, 0);
2503
+ });
2504
+ const manage = document.createElement("button");
2505
+ manage.type = "button";
2506
+ manage.textContent = state.accountManageOpen ? "Done" : "Manage";
2507
+ bindInjectedActivation(manage, () => {
2508
+ state.accountManageOpen = !state.accountManageOpen;
2509
+ state.accountPendingAdoptId = null;
2510
+ state.accountPendingDeleteId = null;
2511
+ state.accountUiStatus = "";
2512
+ renderCodexAccountSwitcher();
2513
+ });
2514
+ actions.append(add, manage);
2515
+ ui.appendChild(actions);
2516
+ if (state.accountAddOpen) ui.appendChild(renderCodexAccountAddForm(accountsState));
2517
+ }
2518
+
2519
+ function renderCodexAccountAddForm(accountsState) {
2520
+ const form = document.createElement("div");
2521
+ form.className = "clankerbend-account-add-form";
2522
+
2523
+ const input = document.createElement("input");
2524
+ input.type = "text";
2525
+ input.dataset.clankerbendAccountLabel = "true";
2526
+ input.placeholder = "Account label";
2527
+ input.value = state.accountAddLabel || "";
2528
+ input.autocomplete = "off";
2529
+ input.spellcheck = false;
2530
+ const stopInputEvent = (event) => {
2531
+ event.stopPropagation();
2532
+ event.stopImmediatePropagation?.();
2533
+ };
2534
+ input.addEventListener("pointerdown", stopInputEvent, true);
2535
+ input.addEventListener("mousedown", stopInputEvent, true);
2536
+ input.addEventListener("click", stopInputEvent, true);
2537
+ input.addEventListener("input", () => {
2538
+ state.accountAddLabel = input.value;
2539
+ });
2540
+ input.addEventListener("keydown", (event) => {
2541
+ event.stopPropagation();
2542
+ event.stopImmediatePropagation?.();
2543
+ if (event.key === "Enter") {
2544
+ event.preventDefault();
2545
+ submitCodexAccountAdd(input.value);
2546
+ } else if (event.key === "Escape") {
2547
+ event.preventDefault();
2548
+ cancelCodexAccountAdd();
2549
+ }
2550
+ }, true);
2551
+
2552
+ const buttons = document.createElement("div");
2553
+ buttons.className = "clankerbend-account-add-actions";
2554
+ const cancel = document.createElement("button");
2555
+ cancel.type = "button";
2556
+ cancel.textContent = "Cancel";
2557
+ bindInjectedActivation(cancel, cancelCodexAccountAdd);
2558
+ const create = document.createElement("button");
2559
+ create.type = "button";
2560
+ create.textContent = "Create";
2561
+ create.disabled = Boolean(accountsState.switching);
2562
+ bindInjectedActivation(create, () => submitCodexAccountAdd(input.value));
2563
+ buttons.append(cancel, create);
2564
+ form.append(input, buttons);
2565
+ return form;
2566
+ }
2567
+
2568
+ function cancelCodexAccountAdd() {
2569
+ state.accountAddOpen = false;
2570
+ state.accountAddLabel = "";
2571
+ state.accountUiStatus = "";
2572
+ renderCodexAccountSwitcher();
2573
+ }
2574
+
2575
+ function submitCodexAccountAdd(rawLabel) {
2576
+ const label = String(rawLabel || "").trim();
2577
+ if (!label) {
2578
+ state.accountUiStatus = "Label required";
2579
+ renderCodexAccountSwitcher();
2580
+ setTimeout(() => {
2581
+ if (state.accountUiStatus !== "Label required") return;
2582
+ state.accountUiStatus = "";
2583
+ renderCodexAccountSwitcher();
2584
+ }, 1400);
2585
+ return;
2586
+ }
2587
+ state.accountAddOpen = false;
2588
+ state.accountAddLabel = "";
2589
+ state.accountUiStatus = "Creating...";
2590
+ renderCodexAccountSwitcher();
2591
+ enqueueHostEvent({ kind: "codexAccountCreateAndSwitch", label });
2592
+ }
2593
+
2594
+ function renderCodexAccountManage(accountsState) {
2595
+ const wrap = document.createElement("div");
2596
+ wrap.className = "clankerbend-account-manage";
2597
+ const head = document.createElement("div");
2598
+ head.className = "clankerbend-account-manage-head";
2599
+ const title = document.createElement("strong");
2600
+ title.textContent = "Manage profiles";
2601
+ const count = document.createElement("span");
2602
+ const managed = (accountsState.accounts || []).filter((account) => account.kind === "managed");
2603
+ count.textContent = `${managed.length} managed`;
2604
+ head.append(title, count);
2605
+ wrap.appendChild(head);
2606
+ const list = document.createElement("div");
2607
+ list.className = "clankerbend-account-manage-list";
2608
+ if (!managed.length) {
2609
+ const row = document.createElement("div");
2610
+ row.className = "clankerbend-account-manage-row";
2611
+ const title = document.createElement("div");
2612
+ title.className = "clankerbend-account-manage-title";
2613
+ const strong = document.createElement("strong");
2614
+ strong.textContent = "No managed accounts";
2615
+ const hint = document.createElement("span");
2616
+ hint.textContent = "Create one with Add account.";
2617
+ title.append(strong, hint);
2618
+ row.appendChild(title);
2619
+ list.appendChild(row);
2620
+ wrap.appendChild(list);
2621
+ return wrap;
2622
+ }
2623
+ for (const account of managed) list.appendChild(renderCodexAccountManageRow(account, accountsState));
2624
+ wrap.appendChild(list);
2625
+ return wrap;
2626
+ }
2627
+
2628
+ function renderCodexAccountManageRow(account, accountsState) {
2629
+ const row = document.createElement("div");
2630
+ row.className = "clankerbend-account-manage-row";
2631
+ row.title = accountCodexHomeTitle(account);
2632
+ const title = document.createElement("div");
2633
+ title.className = "clankerbend-account-manage-title";
2634
+ const strong = document.createElement("strong");
2635
+ strong.textContent = account.label || account.id;
2636
+ const hint = document.createElement("span");
2637
+ const pendingAdopt = state.accountPendingAdoptId === account.id;
2638
+ const pendingDelete = state.accountPendingDeleteId === account.id;
2639
+ hint.textContent = pendingAdopt
2640
+ ? "Replace ~/.codex with this profile; current primary is backed up."
2641
+ : pendingDelete
2642
+ ? "Hide this profile and move its files to archive storage."
2643
+ : [account.auth?.authJson ? "signed in" : "not signed in", account.backup ? "backup" : ""].filter(Boolean).join(" · ");
2644
+ title.append(strong, hint);
2645
+
2646
+ const actions = document.createElement("div");
2647
+ actions.className = "clankerbend-account-manage-actions";
2648
+ const adopt = document.createElement("button");
2649
+ adopt.type = "button";
2650
+ adopt.textContent = pendingAdopt ? "Replace ~/.codex" : "Make primary";
2651
+ adopt.title = "Make this the primary Codex home";
2652
+ adopt.disabled = Boolean(accountsState.switching);
2653
+ bindInjectedActivation(adopt, () => {
2654
+ if (!pendingAdopt) {
2655
+ state.accountPendingAdoptId = account.id;
2656
+ state.accountPendingDeleteId = null;
2657
+ state.accountUiStatus = "";
2658
+ renderCodexAccountSwitcher();
2659
+ return;
2660
+ }
2661
+ state.accountUiStatus = "Making primary...";
2662
+ state.accountPendingAdoptId = null;
2663
+ renderCodexAccountSwitcher();
2664
+ enqueueHostEvent({ kind: "codexAccountAdoptAsPrimary", accountId: account.id });
2665
+ });
2666
+
2667
+ const remove = document.createElement("button");
2668
+ remove.type = "button";
2669
+ remove.textContent = pendingDelete ? "Archive profile" : "Archive";
2670
+ remove.title = "Archive this managed profile";
2671
+ remove.disabled = Boolean(accountsState.switching) || account.id === accountsState.activeAccountId;
2672
+ bindInjectedActivation(remove, () => {
2673
+ if (!pendingDelete) {
2674
+ state.accountPendingDeleteId = account.id;
2675
+ state.accountPendingAdoptId = null;
2676
+ state.accountUiStatus = "";
2677
+ renderCodexAccountSwitcher();
2678
+ return;
2679
+ }
2680
+ state.accountUiStatus = "Archiving...";
2681
+ state.accountPendingDeleteId = null;
2682
+ renderCodexAccountSwitcher();
2683
+ enqueueHostEvent({ kind: "codexAccountDelete", accountId: account.id });
2684
+ });
2685
+
2686
+ actions.append(adopt, remove);
2687
+ row.append(title, actions);
2688
+ return row;
2689
+ }
2690
+
2691
+ function stopNativeMenuPropagation(el) {
2692
+ const stop = (event) => {
2693
+ event.stopPropagation();
2694
+ event.stopImmediatePropagation?.();
2695
+ };
2696
+ el.addEventListener("pointerdown", stop);
2697
+ el.addEventListener("mousedown", stop);
2698
+ el.addEventListener("touchstart", stop, { passive: false });
2699
+ el.addEventListener("click", stop);
2700
+ }
2701
+
2702
+ function bindInjectedActivation(button, handler) {
2703
+ let lastRunAt = 0;
2704
+ const run = (event) => {
2705
+ event.preventDefault();
2706
+ event.stopPropagation();
2707
+ event.stopImmediatePropagation?.();
2708
+ if (button.disabled) return;
2709
+ const now = Date.now();
2710
+ if (now - lastRunAt < 450) return;
2711
+ lastRunAt = now;
2712
+ handler(event);
2713
+ };
2714
+ button.addEventListener("pointerdown", run, true);
2715
+ button.addEventListener("mousedown", run, true);
2716
+ button.addEventListener("touchstart", run, { capture: true, passive: false });
2717
+ button.addEventListener("click", run, true);
2718
+ }
2719
+
2720
+ function renderCodexAccountRow(account, accountsState) {
2721
+ const row = document.createElement("div");
2722
+ row.className = `clankerbend-account-row${account.id === accountsState.activeAccountId ? " is-active" : ""}`;
2723
+
2724
+ const main = document.createElement("button");
2725
+ main.type = "button";
2726
+ main.className = "clankerbend-account-main";
2727
+ main.disabled = Boolean(accountsState.switching) || account.id === accountsState.activeAccountId;
2728
+ main.title = [
2729
+ account.id === accountsState.activeAccountId ? "Current Codex account profile" : `Switch to ${account.label || account.id}`,
2730
+ accountCodexHomeTitle(account)
2731
+ ].filter(Boolean).join("\n");
2732
+ bindInjectedActivation(main, () => {
2733
+ if (account.id === accountsState.activeAccountId) return;
2734
+ state.accountUiStatus = "Switching...";
2735
+ renderCodexAccountSwitcher();
2736
+ enqueueHostEvent({ kind: "codexAccountSwitch", accountId: account.id });
2737
+ });
2738
+
2739
+ const label = document.createElement("span");
2740
+ label.className = "clankerbend-account-label";
2741
+ label.textContent = account.label || account.id;
2742
+ const meta = document.createElement("span");
2743
+ meta.className = "clankerbend-account-meta";
2744
+ meta.textContent = [
2745
+ account.kind,
2746
+ account.id === accountsState.clankerbendDefaultAccountId ? "default" : "",
2747
+ account.auth?.authJson ? "signed in" : "not signed in"
2748
+ ].filter(Boolean).join(" · ");
2749
+ main.append(label, meta);
2750
+
2751
+ const def = document.createElement("button");
2752
+ def.type = "button";
2753
+ def.className = "clankerbend-account-icon";
2754
+ def.textContent = account.id === accountsState.clankerbendDefaultAccountId ? "✓" : "☆";
2755
+ def.setAttribute("aria-label", account.id === accountsState.clankerbendDefaultAccountId ? "Launches by default" : "Launch by default");
2756
+ def.title = [
2757
+ account.id === accountsState.clankerbendDefaultAccountId ? "Launches by default" : "Launch by default",
2758
+ accountCodexHomeTitle(account)
2759
+ ].filter(Boolean).join("\n");
2760
+ def.disabled = Boolean(accountsState.switching) || account.id === accountsState.clankerbendDefaultAccountId;
2761
+ bindInjectedActivation(def, () => {
2762
+ state.accountUiStatus = "Saving default...";
2763
+ renderCodexAccountSwitcher();
2764
+ enqueueHostEvent({ kind: "codexAccountSetDefault", accountId: account.id });
2765
+ });
2766
+
2767
+ row.append(main, def);
2768
+ return row;
2769
+ }
2770
+
2771
+ function accountCodexHomeTitle(account) {
2772
+ return account?.codexHome ? `CODEX_HOME: ${account.codexHome}` : "";
2773
+ }
2774
+
2142
2775
  function hostSelectionActions() {
2143
2776
  return (state.hostState?.selectionActions || [])
2144
2777
  .filter((action) => action?.enabled !== false && action.appliesTo === "text-selection");
@@ -3415,6 +4048,7 @@
3415
4048
  const mutationObserver = new MutationObserver(() => {
3416
4049
  state.version += 1;
3417
4050
  scheduleAnnotationRefresh();
4051
+ renderCodexAccountSwitcher();
3418
4052
  maskSubmittedComposerContexts();
3419
4053
  });
3420
4054
  window.addEventListener("resize", resizeHandler);
@@ -3428,6 +4062,7 @@
3428
4062
  document.removeEventListener("mouseup", handlePossibleTextSelection, true);
3429
4063
  document.removeEventListener("click", window.__clankerbendComposerSubmitClickHandler, true);
3430
4064
  document.removeEventListener("keydown", window.__clankerbendComposerSubmitKeyHandler, true);
4065
+ document.getElementById(ACCOUNT_SWITCHER_ID)?.remove();
3431
4066
  window.removeEventListener("resize", resizeHandler);
3432
4067
  window.removeEventListener("blur", blurHandler);
3433
4068
  mutationObserver.disconnect();