clankerbend 0.1.0 → 0.1.2

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 (38) hide show
  1. package/DEVELOPERS.md +6 -6
  2. package/README.md +31 -14
  3. package/apps/README.md +3 -3
  4. package/apps/sticky-notes/README.md +2 -2
  5. package/apps/sticky-notes/{onewhack.manifest.json → clankerbend.manifest.json} +1 -1
  6. package/apps/sticky-notes/package.json +3 -3
  7. package/apps/sticky-notes/public/app.js +42 -9
  8. package/apps/sticky-notes/src/sticky-notes-app.js +17 -21
  9. package/apps/vim-nav/README.md +22 -22
  10. package/apps/vim-nav/{onewhack.manifest.json → clankerbend.manifest.json} +4 -4
  11. package/apps/vim-nav/package.json +3 -3
  12. package/apps/vim-nav/public/app.js +44 -11
  13. package/apps/vim-nav/public/index.html +2 -2
  14. package/apps/vim-nav/server.mjs +2 -2
  15. package/apps/vim-nav/src/vim-nav-app.js +5 -5
  16. package/assets/clankerbend-banner.webp +0 -0
  17. package/assets/clankerbend-demo-thumbnail.webp +0 -0
  18. package/assets/clankerbend.svg +26 -0
  19. package/cli.mjs +22 -11
  20. package/docs/app-lifecycle.md +9 -9
  21. package/docs/app-manifest.md +4 -4
  22. package/docs/author-guide.md +5 -5
  23. package/docs/launcher-profiles.md +45 -10
  24. package/docs/protocol.md +248 -241
  25. package/host/README.md +4 -4
  26. package/host/src/app-registry.js +8 -6
  27. package/host/src/codex-desktop-cdp-adapter.js +85 -65
  28. package/host/src/codex-desktop-renderer-bridge.js +839 -270
  29. package/host/src/index.js +186 -34
  30. package/launch/accounts.mjs +360 -0
  31. package/launch/codex-mux-adapter.mjs +175 -0
  32. package/launch/profiles.mjs +40 -16
  33. package/launch/runtime-paths.mjs +9 -6
  34. package/launch/test-accounts.mjs +71 -0
  35. package/package.json +12 -10
  36. package/scripts/release-npm.mjs +1 -2
  37. package/server.mjs +21 -14
  38. package/assets/onewhack.jpg +0 -0
package/host/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # OneWhack Host
1
+ # ClankerBend Host
2
2
 
3
- OneWhack is an independent OneWill project compatible with OpenAI Codex
3
+ ClankerBend is an independent OneWill project compatible with OpenAI Codex
4
4
  Desktop. It is not affiliated with or endorsed by OpenAI.
5
5
 
6
- This package contains reusable host primitives for OneWhack `0.1`:
6
+ This package contains reusable host primitives for ClankerBend `0.1`:
7
7
 
8
8
  - loopback HTTP server
9
9
  - JSON response envelopes and error envelopes
@@ -14,5 +14,5 @@ This package contains reusable host primitives for OneWhack `0.1`:
14
14
  - monotonic selection handling
15
15
  - mock transcript adapter for tests and local development
16
16
 
17
- The host does not contain Vim Navigator-specific logic. Apps register a
17
+ The host does not contain VimNav-specific logic. Apps register a
18
18
  manifest, static panel directory, state function, and action handler.
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
 
5
- export const ONEWHACK_APP_MANIFEST_VERSION = "0.1";
5
+ export const CLANKERBEND_APP_MANIFEST_VERSION = "0.1";
6
6
 
7
7
  const VALID_DISTRIBUTION_KINDS = new Set(["local", "npm", "tarball", "binary"]);
8
8
  const VALID_PLATFORM_OS = new Set(["darwin", "linux", "win32", "any"]);
@@ -51,9 +51,9 @@ export function validateAppManifest(manifest, options = {}) {
51
51
  if (!isPlainObject(manifest)) errors.push("manifest must be a JSON object");
52
52
  if (errors.length) throwManifestError(errors, options);
53
53
 
54
- requireString(manifest, "oneWhackVersion", errors);
55
- if (manifest.oneWhackVersion !== ONEWHACK_APP_MANIFEST_VERSION) {
56
- errors.push(`oneWhackVersion must be ${ONEWHACK_APP_MANIFEST_VERSION}`);
54
+ requireString(manifest, "clankerbendVersion", errors);
55
+ if (manifest.clankerbendVersion !== CLANKERBEND_APP_MANIFEST_VERSION) {
56
+ errors.push(`clankerbendVersion must be ${CLANKERBEND_APP_MANIFEST_VERSION}`);
57
57
  }
58
58
  requireString(manifest, "appId", errors);
59
59
  requireString(manifest, "version", errors);
@@ -144,6 +144,8 @@ export async function loadProfileFromManifests(options) {
144
144
  hostId: options.hostId,
145
145
  hostName: options.hostName,
146
146
  runDir: options.runDir,
147
+ runtimePaths: options.runtimePaths,
148
+ accountRegistry: options.accountRegistry,
147
149
  defaultPanelAppId,
148
150
  apps,
149
151
  manifests: loadedApps.map((loaded) => loaded.manifest),
@@ -202,7 +204,7 @@ export function normalizeAppFromManifest(app, manifest) {
202
204
 
203
205
  export function defaultRegistryConfig() {
204
206
  return {
205
- oneWhackVersion: ONEWHACK_APP_MANIFEST_VERSION,
207
+ clankerbendVersion: CLANKERBEND_APP_MANIFEST_VERSION,
206
208
  installedApps: {},
207
209
  profiles: {
208
210
  default: {
@@ -307,7 +309,7 @@ function requireString(object, key, errors, label = key) {
307
309
 
308
310
  function throwManifestError(errors, options = {}) {
309
311
  const prefix = options.manifestPath ? `${options.manifestPath}: ` : "";
310
- throw new Error(`${prefix}invalid OneWhack app manifest:\n${errors.map((error) => `- ${error}`).join("\n")}`);
312
+ throw new Error(`${prefix}invalid ClankerBend app manifest:\n${errors.map((error) => `- ${error}`).join("\n")}`);
311
313
  }
312
314
 
313
315
  function isPlainObject(value) {
@@ -52,7 +52,7 @@ function normalizeProviders(options = {}, rendererBridges = []) {
52
52
 
53
53
  function userPanelError(error) {
54
54
  if (/native Browser panel controls not found|panel app did not load|panel unavailable/i.test(String(error || ""))) {
55
- return "Waiting for Codex Desktop to expose the Browser side panel. Open a Codex thread, then OneWhack will load the selected app automatically.";
55
+ return "Waiting for Codex Desktop to expose the Browser side panel. Open a Codex thread, then ClankerBend will load the selected app automatically.";
56
56
  }
57
57
  return error || "Waiting for Codex Desktop to expose the Browser side panel.";
58
58
  }
@@ -67,6 +67,8 @@ class CodexDesktopCdpAdapter {
67
67
  this.codexCli = options.codexCli || DEFAULT_CODEX_CLI;
68
68
  this.runDir = options.runDir ? resolve(options.runDir) : null;
69
69
  this.profileDir = options.profileDir || (this.runDir ? join(this.runDir, "codex-profile") : null);
70
+ this.codexHome = options.codexHome ? resolve(options.codexHome) : null;
71
+ this.resetProfileDir = options.resetProfileDir === true;
70
72
  this.rendererBridges = normalizeRendererBridges(options);
71
73
  this.providers = normalizeProviders(options, this.rendererBridges);
72
74
  this.snapshotToTranscript = options.snapshotToTranscript || defaultSnapshotToTranscript;
@@ -93,9 +95,10 @@ class CodexDesktopCdpAdapter {
93
95
  }
94
96
  if (this.runDir) mkdirSync(this.runDir, { recursive: true });
95
97
  if (this.profileDir) {
96
- rmSync(this.profileDir, { recursive: true, force: true });
98
+ if (this.resetProfileDir) rmSync(this.profileDir, { recursive: true, force: true });
97
99
  mkdirSync(this.profileDir, { recursive: true });
98
100
  }
101
+ if (this.codexHome) mkdirSync(this.codexHome, { recursive: true });
99
102
 
100
103
  this.cdpPort = await freePort();
101
104
  this.child = spawn(this.codexApp, [
@@ -105,6 +108,7 @@ class CodexDesktopCdpAdapter {
105
108
  ], {
106
109
  env: {
107
110
  ...process.env,
111
+ ...(this.codexHome ? { CODEX_HOME: this.codexHome } : {}),
108
112
  ...(this.profileDir ? { CODEX_ELECTRON_USER_DATA_PATH: this.profileDir } : {})
109
113
  },
110
114
  stdio: ["ignore", "pipe", "pipe"]
@@ -219,7 +223,7 @@ class CodexDesktopCdpAdapter {
219
223
  const bridge = this.bridgeForProvider("transcriptHighlight");
220
224
  const appId = this.bridgeAppId(bridge);
221
225
  return this.evaluate(`(() => {
222
- const runtime = window.__oneWhackRuntime;
226
+ const runtime = window.__clankerbendRuntime;
223
227
  const bridge = runtime?.getBridge?.(${JSON.stringify(appId)});
224
228
  if (bridge?.highlightRange) return bridge.highlightRange(${JSON.stringify(range)}, ${JSON.stringify(options)});
225
229
  if (runtime?.highlightRange) return runtime.highlightRange(${JSON.stringify(range)}, ${JSON.stringify(options)});
@@ -230,7 +234,7 @@ class CodexDesktopCdpAdapter {
230
234
 
231
235
  async setComposerDraft(draft, options = {}) {
232
236
  return this.evaluate(`(() => {
233
- const runtime = window.__oneWhackRuntime;
237
+ const runtime = window.__clankerbendRuntime;
234
238
  if (!runtime?.setComposerDraft) return { ok: false, error: "composer draft unavailable" };
235
239
  return runtime.setComposerDraft(${JSON.stringify(draft)}, ${JSON.stringify(options)});
236
240
  })()`);
@@ -238,7 +242,7 @@ class CodexDesktopCdpAdapter {
238
242
 
239
243
  async submitComposer(draft, options = {}) {
240
244
  return this.evaluate(`(() => {
241
- const runtime = window.__oneWhackRuntime;
245
+ const runtime = window.__clankerbendRuntime;
242
246
  if (!runtime?.submitComposer) return { ok: false, error: "composer submit unavailable" };
243
247
  return runtime.submitComposer(${JSON.stringify(draft)}, ${JSON.stringify(options)});
244
248
  })()`);
@@ -347,7 +351,7 @@ class CodexDesktopCdpAdapter {
347
351
  return { ok: false, error: "app-server active thread is unknown", mode: "app-server-inject-items", diagnostic: diagnostics };
348
352
  }
349
353
  const items = buildAttachmentResponseItems(files, {
350
- appName: options.appName || "OneWhack",
354
+ appName: options.appName || "ClankerBend",
351
355
  createdAt: new Date().toISOString()
352
356
  });
353
357
  const response = await client.threadInjectItems(thread.threadId, items);
@@ -491,20 +495,20 @@ class CodexDesktopCdpAdapter {
491
495
  const protocolVersion = ${JSON.stringify(this.host.protocolVersion)};
492
496
  const hostUrl = ${JSON.stringify(this.host.state.host.url)};
493
497
  const seededApps = ${JSON.stringify(apps)};
494
- const previous = window.__oneWhackRuntime;
498
+ const previous = window.__clankerbendRuntime;
495
499
  const slots = previous && typeof previous.apps === "object" ? previous.apps : {};
496
500
  const cssEscape = (value) => window.CSS?.escape ? window.CSS.escape(String(value)) : String(value).replace(/["\\\\]/g, "\\\\$&");
497
501
  const ensureAnnotationLayoutStyle = () => {
498
- const styleId = "onewhack-annotation-layout-style";
502
+ const styleId = "clankerbend-annotation-layout-style";
499
503
  if (document.getElementById(styleId)) return;
500
504
  const style = document.createElement("style");
501
505
  style.id = styleId;
502
506
  style.textContent = \`
503
- .onewhack-transcript-anchor {
507
+ .clankerbend-transcript-anchor {
504
508
  position: relative !important;
505
509
  overflow: visible !important;
506
510
  }
507
- .onewhack-transcript-annotations {
511
+ .clankerbend-transcript-annotations {
508
512
  position: absolute !important;
509
513
  left: -62px !important;
510
514
  top: 4px !important;
@@ -515,37 +519,35 @@ class CodexDesktopCdpAdapter {
515
519
  gap: 6px !important;
516
520
  pointer-events: none !important;
517
521
  }
518
- .onewhack-transcript-annotation-slot {
522
+ .clankerbend-transcript-annotation-slot {
519
523
  display: flex !important;
520
524
  align-items: center !important;
521
525
  justify-content: center !important;
522
526
  pointer-events: auto !important;
523
527
  }
524
528
  @media (max-width: 900px) {
525
- .onewhack-transcript-annotations {
526
- position: static !important;
527
- flex-direction: row !important;
528
- justify-content: flex-start !important;
529
- align-items: center !important;
530
- margin: 0 0 6px 0 !important;
529
+ .clankerbend-transcript-annotations {
530
+ position: absolute !important;
531
+ left: -44px !important;
532
+ top: 4px !important;
531
533
  }
532
534
  }
533
535
  \`;
534
536
  document.head.appendChild(style);
535
537
  };
536
538
  const annotationHost = (anchor, anchorId) => {
537
- if (!(anchor instanceof HTMLElement)) throw new Error("OneWhack annotation anchor must be an HTMLElement");
539
+ if (!(anchor instanceof HTMLElement)) throw new Error("ClankerBend annotation anchor must be an HTMLElement");
538
540
  ensureAnnotationLayoutStyle();
539
- anchor.classList.add("onewhack-transcript-anchor");
541
+ anchor.classList.add("clankerbend-transcript-anchor");
540
542
  let host = Array.from(anchor.children).find((child) =>
541
543
  child instanceof HTMLElement &&
542
- child.classList.contains("onewhack-transcript-annotations") &&
543
- child.dataset.onewhackAnchorId === anchorId
544
+ child.classList.contains("clankerbend-transcript-annotations") &&
545
+ child.dataset.clankerbendAnchorId === anchorId
544
546
  );
545
547
  if (!host) {
546
548
  host = document.createElement("div");
547
- host.className = "onewhack-transcript-annotations";
548
- host.dataset.onewhackAnchorId = anchorId;
549
+ host.className = "clankerbend-transcript-annotations";
550
+ host.dataset.clankerbendAnchorId = anchorId;
549
551
  anchor.insertAdjacentElement("afterbegin", host);
550
552
  }
551
553
  return host;
@@ -565,15 +567,15 @@ class CodexDesktopCdpAdapter {
565
567
  }
566
568
  return left;
567
569
  })();
568
- const visibleLeft = clippingLeft - rect.left;
569
- host.style.setProperty("left", Math.max(visibleLeft, desiredLeft) + "px", "important");
570
+ host.style.setProperty("left", desiredLeft + "px", "important");
571
+ host.style.setProperty("visibility", rect.left + desiredLeft < clippingLeft ? "hidden" : "visible", "important");
570
572
  };
571
573
  const sortAnnotationSlots = (host) => {
572
574
  [...host.children]
573
575
  .sort((a, b) =>
574
- Number(a.dataset.onewhackPriority || 100) - Number(b.dataset.onewhackPriority || 100) ||
575
- String(a.dataset.onewhackAppId || "").localeCompare(String(b.dataset.onewhackAppId || "")) ||
576
- String(a.dataset.onewhackMarkerId || "").localeCompare(String(b.dataset.onewhackMarkerId || ""))
576
+ Number(a.dataset.clankerbendPriority || 100) - Number(b.dataset.clankerbendPriority || 100) ||
577
+ String(a.dataset.clankerbendAppId || "").localeCompare(String(b.dataset.clankerbendAppId || "")) ||
578
+ String(a.dataset.clankerbendMarkerId || "").localeCompare(String(b.dataset.clankerbendMarkerId || ""))
577
579
  )
578
580
  .forEach((child) => host.appendChild(child));
579
581
  };
@@ -589,8 +591,8 @@ class CodexDesktopCdpAdapter {
589
591
  const findAnchorElement = (anchorId) => document.querySelector(transcriptAnchorSelector(anchorId));
590
592
  const isHostUiElement = (el) => {
591
593
  for (let node = el; node; node = node.parentElement) {
592
- if (node.classList?.contains?.("onewhack-host-ui")) return true;
593
- if (/^onewhack-/.test(String(node.id || ""))) return true;
594
+ if (node.classList?.contains?.("clankerbend-host-ui")) return true;
595
+ if (/^clankerbend-/.test(String(node.id || ""))) return true;
594
596
  }
595
597
  return false;
596
598
  };
@@ -633,16 +635,16 @@ class CodexDesktopCdpAdapter {
633
635
  );
634
636
  return candidates[0]?.el || null;
635
637
  };
636
- const ONEWHACK_CONTEXT_START = "--- OneWhack context ---";
637
- const ONEWHACK_CONTEXT_END = "--- End OneWhack context ---";
638
- const stripOneWhackContext = (text) => {
638
+ const CLANKERBEND_CONTEXT_START = "--- ClankerBend context ---";
639
+ const CLANKERBEND_CONTEXT_END = "--- End ClankerBend context ---";
640
+ const stripContextBlock = (text) => {
639
641
  let output = String(text || "");
640
642
  while (true) {
641
- const start = output.indexOf(ONEWHACK_CONTEXT_START);
643
+ const start = output.indexOf(CLANKERBEND_CONTEXT_START);
642
644
  if (start < 0) return output.trimStart();
643
- const end = output.indexOf(ONEWHACK_CONTEXT_END, start + ONEWHACK_CONTEXT_START.length);
645
+ const end = output.indexOf(CLANKERBEND_CONTEXT_END, start + CLANKERBEND_CONTEXT_START.length);
644
646
  if (end < 0) return output.slice(0, start).trimEnd();
645
- output = output.slice(0, start) + output.slice(end + ONEWHACK_CONTEXT_END.length);
647
+ output = output.slice(0, start) + output.slice(end + CLANKERBEND_CONTEXT_END.length);
646
648
  }
647
649
  };
648
650
  const setComposerValue = (el, value) => {
@@ -678,7 +680,7 @@ class CodexDesktopCdpAdapter {
678
680
  hostUrl,
679
681
  apps: slots,
680
682
  registerApp(app) {
681
- if (!app || !app.appId) throw new Error("OneWhack appId is required");
683
+ if (!app || !app.appId) throw new Error("ClankerBend appId is required");
682
684
  const seeded = seededApps[app.appId] || {};
683
685
  const current = this.apps[app.appId] || {};
684
686
  const currentVersion = Number(current.bridge?.version);
@@ -690,7 +692,7 @@ class CodexDesktopCdpAdapter {
690
692
  Number.isFinite(nextVersion) &&
691
693
  nextVersion < currentVersion
692
694
  ) {
693
- throw new Error("OneWhack app bridge version regressed for " + app.appId);
695
+ throw new Error("ClankerBend app bridge version regressed for " + app.appId);
694
696
  }
695
697
  const slot = {
696
698
  ...current,
@@ -713,22 +715,22 @@ class CodexDesktopCdpAdapter {
713
715
  return this.getApp(appId)?.entryUrl || null;
714
716
  },
715
717
  placeAnnotation(anchor, annotation) {
716
- if (!annotation?.appId) throw new Error("OneWhack annotation appId is required");
717
- if (!annotation?.anchorId) throw new Error("OneWhack annotation anchorId is required");
718
- if (!(annotation.element instanceof HTMLElement)) throw new Error("OneWhack annotation element is required");
718
+ if (!annotation?.appId) throw new Error("ClankerBend annotation appId is required");
719
+ if (!annotation?.anchorId) throw new Error("ClankerBend annotation anchorId is required");
720
+ if (!(annotation.element instanceof HTMLElement)) throw new Error("ClankerBend annotation element is required");
719
721
  const markerId = annotation.markerId || annotation.anchorId;
720
722
  const host = annotationHost(anchor, annotation.anchorId);
721
- const selector = '.onewhack-transcript-annotation-slot[data-onewhack-app-id="' + cssEscape(annotation.appId) + '"][data-onewhack-marker-id="' + cssEscape(markerId) + '"]';
723
+ const selector = '.clankerbend-transcript-annotation-slot[data-clankerbend-app-id="' + cssEscape(annotation.appId) + '"][data-clankerbend-marker-id="' + cssEscape(markerId) + '"]';
722
724
  let slot = host.querySelector(selector);
723
725
  if (!slot) {
724
726
  slot = document.createElement("div");
725
- slot.className = "onewhack-transcript-annotation-slot";
726
- slot.dataset.onewhackAppId = annotation.appId;
727
- slot.dataset.onewhackMarkerId = markerId;
727
+ slot.className = "clankerbend-transcript-annotation-slot";
728
+ slot.dataset.clankerbendAppId = annotation.appId;
729
+ slot.dataset.clankerbendMarkerId = markerId;
728
730
  host.appendChild(slot);
729
731
  }
730
- slot.dataset.onewhackAnchorId = annotation.anchorId;
731
- slot.dataset.onewhackPriority = String(annotation.priority ?? 100);
732
+ slot.dataset.clankerbendAnchorId = annotation.anchorId;
733
+ slot.dataset.clankerbendPriority = String(annotation.priority ?? 100);
732
734
  if (slot.firstElementChild !== annotation.element) slot.replaceChildren(annotation.element);
733
735
  sortAnnotationSlots(host);
734
736
  positionAnnotationHost(host, anchor);
@@ -736,10 +738,10 @@ class CodexDesktopCdpAdapter {
736
738
  },
737
739
  removeAnnotations(appId, liveAnchorIds = null) {
738
740
  const live = liveAnchorIds ? new Set([...liveAnchorIds].map(String)) : null;
739
- document.querySelectorAll('.onewhack-transcript-annotation-slot[data-onewhack-app-id="' + cssEscape(appId) + '"]').forEach((slot) => {
740
- if (!live || !live.has(slot.dataset.onewhackAnchorId || "")) slot.remove();
741
+ document.querySelectorAll('.clankerbend-transcript-annotation-slot[data-clankerbend-app-id="' + cssEscape(appId) + '"]').forEach((slot) => {
742
+ if (!live || !live.has(slot.dataset.clankerbendAnchorId || "")) slot.remove();
741
743
  });
742
- document.querySelectorAll(".onewhack-transcript-annotations").forEach((host) => {
744
+ document.querySelectorAll(".clankerbend-transcript-annotations").forEach((host) => {
743
745
  if (!host.children.length) host.remove();
744
746
  });
745
747
  },
@@ -747,14 +749,14 @@ class CodexDesktopCdpAdapter {
747
749
  if (!range?.anchorId) return { ok: false, error: "range.anchorId is required" };
748
750
  const anchor = findAnchorElement(range.anchorId);
749
751
  if (!anchor) return { ok: false, error: "anchor not found", anchorId: range.anchorId };
750
- anchor.classList.add("onewhack-range-highlight");
752
+ anchor.classList.add("clankerbend-range-highlight");
751
753
  const previousOutline = anchor.style.outline;
752
754
  const previousOffset = anchor.style.outlineOffset;
753
755
  anchor.style.outline = "3px solid #f6c945";
754
756
  anchor.style.outlineOffset = "3px";
755
757
  anchor.scrollIntoView({ block: options.block || "center", behavior: options.behavior || "smooth" });
756
758
  setTimeout(() => {
757
- anchor.classList.remove("onewhack-range-highlight");
759
+ anchor.classList.remove("clankerbend-range-highlight");
758
760
  anchor.style.outline = previousOutline;
759
761
  anchor.style.outlineOffset = previousOffset;
760
762
  }, Number(options.durationMs || 1200));
@@ -764,8 +766,8 @@ class CodexDesktopCdpAdapter {
764
766
  const el = findComposer();
765
767
  if (!el) return { ok: false, error: "composer not found" };
766
768
  const currentText = composerText(el);
767
- const text = draft.oneWhackContext
768
- ? (draft.text ? mergeDraftText(stripOneWhackContext(currentText), String(draft.text || ""), "prepend") : stripOneWhackContext(currentText))
769
+ const text = draft.clankerbendContext
770
+ ? (draft.text ? mergeDraftText(stripContextBlock(currentText), String(draft.text || ""), "prepend") : stripContextBlock(currentText))
769
771
  : mergeDraftText(currentText, String(draft.text || ""), draft.mode || "replace");
770
772
  if (!setComposerValue(el, text)) return { ok: false, error: "composer cannot be updated" };
771
773
  return { ok: true, draft: { ...draft, text } };
@@ -790,7 +792,7 @@ class CodexDesktopCdpAdapter {
790
792
  injectedAt: runtime.apps[appId]?.injectedAt || null
791
793
  };
792
794
  }
793
- window.__oneWhackRuntime = runtime;
795
+ window.__clankerbendRuntime = runtime;
794
796
  })()`);
795
797
  for (const bridge of this.rendererBridges) {
796
798
  await this.evaluate(bridge.injectedSource);
@@ -887,6 +889,24 @@ class CodexDesktopCdpAdapter {
887
889
  await this.host.highlightRange(event.range, { durationMs: 1200 });
888
890
  } else if (event.kind === "highlightAnchor" && event.anchorId) {
889
891
  await this.host.highlightAnchor(event.anchorId, { durationMs: 1200 });
892
+ } else if (event.kind === "codexAccountSwitch" && event.accountId) {
893
+ if (typeof this.host.transcriptAdapter.switchTo !== "function") throw new Error("Codex account switching is unavailable");
894
+ await this.host.transcriptAdapter.switchTo(String(event.accountId));
895
+ } else if (event.kind === "codexAccountCreateAndSwitch") {
896
+ if (typeof this.host.transcriptAdapter.createAccount !== "function" || typeof this.host.transcriptAdapter.switchTo !== "function") {
897
+ throw new Error("Codex account creation is unavailable");
898
+ }
899
+ const account = await this.host.transcriptAdapter.createAccount({ label: String(event.label || "Account") });
900
+ await this.host.transcriptAdapter.switchTo(account.id);
901
+ } else if (event.kind === "codexAccountSetDefault" && event.accountId) {
902
+ if (typeof this.host.transcriptAdapter.setDefault !== "function") throw new Error("Codex account defaults are unavailable");
903
+ await this.host.transcriptAdapter.setDefault(String(event.accountId));
904
+ } else if (event.kind === "codexAccountAdoptAsPrimary" && event.accountId) {
905
+ if (typeof this.host.transcriptAdapter.adoptAsPrimary !== "function") throw new Error("Codex primary adoption is unavailable");
906
+ await this.host.transcriptAdapter.adoptAsPrimary(String(event.accountId));
907
+ } else if (event.kind === "codexAccountDelete" && event.accountId) {
908
+ if (typeof this.host.transcriptAdapter.deleteAccount !== "function") throw new Error("Codex account removal is unavailable");
909
+ await this.host.transcriptAdapter.deleteAccount(String(event.accountId));
890
910
  }
891
911
  }
892
912
  }
@@ -1042,9 +1062,9 @@ class CodexDesktopCdpAdapter {
1042
1062
  async evalBridge(expression, bridge = this.primaryBridge()) {
1043
1063
  const appId = this.bridgeAppId(bridge);
1044
1064
  return this.evaluate(`(() => {
1045
- const runtime = window.__oneWhackRuntime;
1065
+ const runtime = window.__clankerbendRuntime;
1046
1066
  const bridge = runtime?.getBridge?.(${JSON.stringify(appId)});
1047
- if (!bridge) return { ok: false, error: "OneWhack bridge is not installed for " + ${JSON.stringify(appId)} };
1067
+ if (!bridge) return { ok: false, error: "ClankerBend bridge is not installed for " + ${JSON.stringify(appId)} };
1048
1068
  return bridge.${expression};
1049
1069
  })()`);
1050
1070
  }
@@ -1240,8 +1260,8 @@ class AppServerClient {
1240
1260
  async initialize() {
1241
1261
  const response = await this.call("initialize", {
1242
1262
  clientInfo: {
1243
- name: "onewhack-host",
1244
- title: "OneWhack Host",
1263
+ name: "clankerbend-host",
1264
+ title: "ClankerBend Host",
1245
1265
  version: "0.1"
1246
1266
  },
1247
1267
  capabilities: {
@@ -1448,7 +1468,7 @@ function buildAttachmentResponseItems(files = [], options = {}) {
1448
1468
  const createdAt = options.createdAt || new Date().toISOString();
1449
1469
  return files.map((file, index) => {
1450
1470
  const text = [
1451
- "OneWhack attached runtime file context.",
1471
+ "ClankerBend attached runtime file context.",
1452
1472
  "",
1453
1473
  `Attached at: ${createdAt}`,
1454
1474
  `File ${index + 1}: ${file.name || basename(file.path)}`,
@@ -1456,7 +1476,7 @@ function buildAttachmentResponseItems(files = [], options = {}) {
1456
1476
  file.relativePath ? `Runtime path: ${file.relativePath}` : null,
1457
1477
  file.mimeType ? `MIME type: ${file.mimeType}` : null,
1458
1478
  "",
1459
- "The following file was created by a OneWhack app and should be treated as attached context for the user's next prompt.",
1479
+ "The following file was created by a ClankerBend app and should be treated as attached context for the user's next prompt.",
1460
1480
  "",
1461
1481
  "```markdown",
1462
1482
  readFileSync(file.path, "utf8"),
@@ -1731,14 +1751,14 @@ function nativeAttachmentDiagnosticExpression(paths) {
1731
1751
  (() => {
1732
1752
  const paths = ${JSON.stringify(paths)};
1733
1753
  const basenames = paths.map((path) => String(path).split(/[\\\\/]/).pop()).filter(Boolean);
1734
- const oneWhackChipCount = document.querySelectorAll("#onewhack-composer-chips .onewhack-context-chip").length;
1754
+ const clankerbendChipCount = document.querySelectorAll("#clankerbend-composer-chips .clankerbend-context-chip").length;
1735
1755
  const composerInputs = [...document.querySelectorAll("textarea,[contenteditable='true'],[role='textbox']")]
1736
1756
  .map((el) => ({ el, rect: el.getBoundingClientRect() }))
1737
1757
  .filter((item) => item.rect.width > 180 && item.rect.height > 18 && item.rect.bottom > window.innerHeight * 0.45)
1738
1758
  .sort((a, b) => b.rect.bottom - a.rect.bottom);
1739
1759
  const composerRect = composerInputs[0]?.rect || null;
1740
1760
  const candidates = [...document.querySelectorAll("button,[role='button'],span,div")]
1741
- .filter((el) => !el.closest("#onewhack-composer-chips"))
1761
+ .filter((el) => !el.closest("#clankerbend-composer-chips"))
1742
1762
  .map((el) => {
1743
1763
  const text = (el.innerText || el.textContent || "").replace(/\\s+/g, " ").trim();
1744
1764
  const rect = el.getBoundingClientRect();
@@ -1760,10 +1780,10 @@ function nativeAttachmentDiagnosticExpression(paths) {
1760
1780
  );
1761
1781
  const matched = basenames.filter((name) => candidates.some((item) => item.text.includes(name)));
1762
1782
  return {
1763
- ok: matched.length === basenames.length && oneWhackChipCount === 0,
1783
+ ok: matched.length === basenames.length && clankerbendChipCount === 0,
1764
1784
  matched,
1765
1785
  basenames,
1766
- oneWhackChipCount,
1786
+ clankerbendChipCount,
1767
1787
  candidates: candidates.slice(0, 12)
1768
1788
  };
1769
1789
  })()