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/DEVELOPERS.md CHANGED
@@ -1,11 +1,11 @@
1
- # OneWhack Developers
1
+ # ClankerBend Developers
2
2
 
3
3
  ## Local Commands
4
4
 
5
5
  For protocol-only local testing without Codex Desktop:
6
6
 
7
7
  ```sh
8
- npx clankerbend codex --mock
8
+ npx clankerbend --mock
9
9
  ```
10
10
 
11
11
  For local development:
@@ -30,18 +30,18 @@ context visibility.
30
30
 
31
31
  ## Useful Docs
32
32
 
33
- - `docs/protocol.md`: OneWhack protocol
33
+ - `docs/protocol.md`: ClankerBend protocol
34
34
  - `docs/app-manifest.md`: app manifest format
35
- - `docs/author-guide.md`: writing OneWhack apps
35
+ - `docs/author-guide.md`: writing ClankerBend apps
36
36
  - `docs/launcher-profiles.md`: launcher profiles
37
37
  - `docs/app-lifecycle.md`: app installation and lifecycle model
38
38
 
39
39
  ## Release
40
40
 
41
- OneWhack publishes the same package payload under two npm names:
41
+ ClankerBend publishes the same package payload under two npm names:
42
42
 
43
43
  - `@onewillai/clankerbend`: scoped canonical package
44
- - `clankerbend`: short launcher name for `npx clankerbend codex`
44
+ - `clankerbend`: short launcher name for `npx clankerbend`
45
45
 
46
46
  ```sh
47
47
  npm test
package/README.md CHANGED
@@ -1,17 +1,21 @@
1
- # OneWhack
1
+ # ClankerBend
2
2
 
3
3
  <p align="center">
4
- <img src="./assets/onewhack.jpg" alt="OneWhack: mod your clanker with OneWill.ai" width="720">
4
+ <img src="./assets/clankerbend-banner.webp" alt="ClankerBend: mod your clankers with OneWill.ai" width="720">
5
5
  </p>
6
6
 
7
- OneWhack lets you build Codex Desktop plugins without rebuilding or re-signing
7
+ ClankerBend lets you build Codex Desktop plugins without rebuilding or re-signing
8
8
  Codex Desktop itself. It uses [CDP](https://x.com/OpenAIDevs/status/2065226355495895521)
9
9
  to provide a more stable API for tasks such as transcript navigation, chat annotation,
10
10
  attaching a file to the prompt window, opening the side panel, and so on.
11
11
 
12
- DEMO VIDEO TODO
12
+ <p align="center">
13
+ <a href="https://www.youtube.com/watch?v=FcQUxiL6enc">
14
+ <img src="./assets/clankerbend-demo-thumbnail.webp" alt="Watch the ClankerBend demo video" width="720">
15
+ </a>
16
+ </p>
13
17
 
14
- OneWhack is an independent [OneWill](https://onewill.ai) project compatible
18
+ ClankerBend is an independent [OneWill](https://onewill.ai) project compatible
15
19
  with OpenAI Codex Desktop on macOS. It is not affiliated with or endorsed by
16
20
  OpenAI (unless... 🥺).
17
21
 
@@ -21,11 +25,12 @@ You'll need macOS with [Codex Desktop](https://chatgpt.com/codex/)
21
25
  and [nodejs](https://nodejs.org/en/download). Then just:
22
26
 
23
27
  ```sh
24
- npx clankerbend codex
28
+ npx clankerbend
25
29
  ```
26
30
 
27
- That starts Codex Desktop with the default OneWhack profile and bundled apps:
31
+ That starts Codex Desktop with the default ClankerBend profile and bundled apps:
28
32
 
33
+ - **ClankerID**: switch between Codex accounts.
29
34
  - **VimNav**: Vim-style transcript navigation, number rails, role jumps, search,
30
35
  and a side panel.
31
36
  - **Sticky Notes**: select transcript text, click **Add note**, and the
@@ -35,15 +40,27 @@ That starts Codex Desktop with the default OneWhack profile and bundled apps:
35
40
 
36
41
  ## Runtime State
37
42
 
38
- OneWhack writes runtime state outside the package:
43
+ ClankerBend writes runtime state outside the package:
44
+
45
+ - macOS: `~/Library/Application Support/OneWill/ClankerBend`
39
46
 
40
- - macOS: `~/Library/Application Support/OneWill/OneWhack`
47
+ Override with `ONEWILL_CLANKERBEND_STATE_DIR`.
41
48
 
42
- Override with `ONEWILL_ONEWHACK_STATE_DIR`.
49
+ Managed Codex account profiles live under the same state directory. The primary
50
+ profile remains your normal `CODEX_HOME` or `~/.codex`; managed profiles get
51
+ their own `codex-home` and Electron user-data directories. You can start a
52
+ saved profile directly with:
53
+
54
+ ```sh
55
+ npx clankerbend --account work
56
+ ```
43
57
 
44
58
  ## Security
45
59
 
46
- OneWhack binds to `127.0.0.1` on an OS-assigned ephemeral port. The launcher
47
- prints the exact host URL, for example `Host: http://127.0.0.1:49152`.
48
- `/onewhack/*` endpoints require a bearer token by default. For local protocol
49
- debugging, you can set `ONEWILL_ONEWHACK_DISABLE_AUTH=1`.
60
+ ClankerBend binds to `127.0.0.1` on an OS-assigned ephemeral port. The launcher
61
+ prints the exact host URL and bearer token, for example
62
+ `Host: http://127.0.0.1:49152` and `Token: ...`.
63
+ `/clankerbend/*` endpoints require a bearer token by default. For local protocol
64
+ debugging, you can set `ONEWILL_CLANKERBEND_DISABLE_AUTH=1`. Hosted app pages
65
+ receive the same bearer token from their app URL fragment and from a
66
+ host-injected HTML bootstrap so URL rewriting does not break app auth.
package/apps/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # OneWhack Apps
1
+ # ClankerBend Apps
2
2
 
3
- This directory contains public OneWhack reference apps.
3
+ This directory contains public ClankerBend reference apps.
4
4
 
5
5
  - `vim-nav/`: Vim-like transcript navigation for Codex Desktop transcript
6
6
  anchors.
7
7
  - `sticky-notes/`: selected transcript notes and composer context chips through
8
- the shared OneWhack host API.
8
+ the shared ClankerBend host API.
9
9
 
10
10
  The public `apps/` tree should contain standalone, auditable apps without
11
11
  project-specific state or action vocabulary.
@@ -1,10 +1,10 @@
1
1
  # Sticky Notes
2
2
 
3
- Sticky Notes is a public OneWhack reference app for selected transcript text,
3
+ Sticky Notes is a public ClankerBend reference app for selected transcript text,
4
4
  anchored note overlays, composer context chips, and follow-up draft insertion.
5
5
 
6
6
  The app does not inspect or mutate Codex Desktop DOM. It contributes selection
7
- actions and handles note business logic through the OneWhack host API.
7
+ actions and handles note business logic through the ClankerBend host API.
8
8
 
9
9
  ```sh
10
10
  npm --prefix apps/sticky-notes test
@@ -1,5 +1,5 @@
1
1
  {
2
- "oneWhackVersion": "0.1",
2
+ "clankerbendVersion": "0.1",
3
3
  "appId": "onewill.sticky-notes",
4
4
  "version": "0.1.0",
5
5
  "name": "OneWill Sticky Notes",
@@ -1,11 +1,11 @@
1
1
  {
2
- "name": "onewhack-sticky-notes",
2
+ "name": "clankerbend-sticky-notes",
3
3
  "version": "0.1.0",
4
4
  "type": "module",
5
- "description": "OneWhack public reference app for transcript range notes and composer context.",
5
+ "description": "ClankerBend public reference app for transcript range notes and composer context.",
6
6
  "license": "MIT",
7
7
  "scripts": {
8
- "test": "node test-onewhack.mjs",
8
+ "test": "node test-clankerbend.mjs",
9
9
  "test:desktop-real": "node test-desktop-real.mjs"
10
10
  },
11
11
  "engines": {
@@ -18,7 +18,7 @@ els.insertContext.addEventListener("click", () => runAction("sticky.compose.inse
18
18
  bootstrap();
19
19
 
20
20
  async function bootstrap() {
21
- const state = await getJson("/onewhack/state");
21
+ const state = await getJson("/clankerbend/state");
22
22
  render(state);
23
23
  connectEvents();
24
24
  }
@@ -49,19 +49,52 @@ async function runAction(type, payload = {}) {
49
49
  payload,
50
50
  requestedAt: new Date().toISOString()
51
51
  };
52
- const result = await postJson(`/onewhack/apps/${encodeURIComponent(APP_ID)}/actions`, { action });
53
- const state = await getJson("/onewhack/state");
52
+ const result = await postJson(`/clankerbend/apps/${encodeURIComponent(APP_ID)}/actions`, { action });
53
+ const state = await getJson("/clankerbend/state");
54
54
  render(state);
55
55
  return result;
56
56
  }
57
57
 
58
58
  function readToken() {
59
59
  const params = new URLSearchParams(location.hash.startsWith("#") ? location.hash.slice(1) : location.hash);
60
- const value = params.get("onewhack_token") || "";
61
- if (value) history.replaceState(null, "", location.pathname + location.search);
60
+ const fragmentToken = params.get("clankerbend_token") || "";
61
+ const bootstrapToken = window.__CLANKERBEND_TOKEN || "";
62
+ const metaToken = document.querySelector("meta[name='clankerbend-token']")?.content || "";
63
+ const inlineToken = readInlineBootstrapToken();
64
+ const cachedToken = readCachedToken();
65
+ const value = fragmentToken || bootstrapToken || metaToken || inlineToken || cachedToken;
66
+ if (fragmentToken) history.replaceState(null, "", location.pathname + location.search);
67
+ if (value) writeCachedToken(value);
62
68
  return value;
63
69
  }
64
70
 
71
+ function readInlineBootstrapToken() {
72
+ for (const script of document.scripts) {
73
+ const match = script.textContent?.match(/window\.__CLANKERBEND_TOKEN=("[^"]*");?/);
74
+ if (!match) continue;
75
+ try {
76
+ return JSON.parse(match[1]);
77
+ } catch {
78
+ return "";
79
+ }
80
+ }
81
+ return "";
82
+ }
83
+
84
+ function readCachedToken() {
85
+ try {
86
+ return sessionStorage.getItem("clankerbend_token") || "";
87
+ } catch {
88
+ return "";
89
+ }
90
+ }
91
+
92
+ function writeCachedToken(value) {
93
+ try {
94
+ sessionStorage.setItem("clankerbend_token", value);
95
+ } catch {}
96
+ }
97
+
65
98
  function headers(extra = {}) {
66
99
  return {
67
100
  ...extra,
@@ -71,12 +104,12 @@ function headers(extra = {}) {
71
104
 
72
105
  function connectEvents() {
73
106
  if (!token) {
74
- const events = new EventSource("/onewhack/events");
107
+ const events = new EventSource("/clankerbend/events");
75
108
  events.addEventListener("state", (event) => render(JSON.parse(event.data)));
76
- events.addEventListener("action", () => getJson("/onewhack/state").then(render));
109
+ events.addEventListener("action", () => getJson("/clankerbend/state").then(render));
77
110
  return;
78
111
  }
79
- fetch("/onewhack/events", { headers: headers() }).then(async (res) => {
112
+ fetch("/clankerbend/events", { headers: headers() }).then(async (res) => {
80
113
  const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
81
114
  let buffer = "";
82
115
  while (true) {
@@ -90,7 +123,7 @@ function connectEvents() {
90
123
  const event = chunk.split(/\n/).find((line) => line.startsWith("event: "))?.slice(7);
91
124
  const data = chunk.split(/\n/).filter((line) => line.startsWith("data: ")).map((line) => line.slice(6)).join("\n");
92
125
  if (event === "state" && data) render(JSON.parse(data));
93
- if (event === "action") getJson("/onewhack/state").then(render);
126
+ if (event === "action") getJson("/clankerbend/state").then(render);
94
127
  }
95
128
  }
96
129
  });
@@ -35,7 +35,7 @@ export function createStickyNotesApp(options = {}) {
35
35
 
36
36
  getManifest(context) {
37
37
  return {
38
- oneWhackVersion: context.protocolVersion,
38
+ clankerbendVersion: context.protocolVersion,
39
39
  appId: STICKY_NOTES_APP_ID,
40
40
  name: "Sticky Notes",
41
41
  entry: context.entry,
@@ -170,11 +170,17 @@ function openNoteOverlay(action, context, selection) {
170
170
  label: "Add note",
171
171
  value: action.payload?.body || ""
172
172
  }],
173
- actions: [{
174
- label: "Save",
175
- type: "sticky.note.create",
176
- payload: { selection }
177
- }]
173
+ actions: [
174
+ {
175
+ label: "Cancel",
176
+ type: "overlay.close"
177
+ },
178
+ {
179
+ label: "Save",
180
+ type: "sticky.note.create",
181
+ payload: { selection }
182
+ }
183
+ ]
178
184
  }, { broadcast: false }).overlay;
179
185
  return applied(action, { overlay });
180
186
  }
@@ -232,27 +238,17 @@ function attachNoteFile(notes, noteId, context) {
232
238
 
233
239
  function noteMarkdown(note) {
234
240
  return [
235
- "# Sticky Note",
236
- "",
237
- `Note ID: ${note.noteId}`,
238
- `Status: ${note.status}`,
239
- `Anchor: ${note.anchorId}`,
240
- `Created: ${note.createdAt}`,
241
- `Updated: ${note.updatedAt}`,
241
+ "# Note",
242
242
  "",
243
- "## Note",
243
+ "The user made a note on the transcript above.",
244
244
  "",
245
- note.body,
246
- "",
247
- "## Highlighted Text",
245
+ "## Highlighted text",
248
246
  "",
249
247
  blockquote(note.quote),
250
248
  "",
251
- "## Range",
249
+ "## User notes",
252
250
  "",
253
- "```json",
254
- JSON.stringify(note.range || null, null, 2),
255
- "```",
251
+ note.body,
256
252
  ""
257
253
  ].join("\n");
258
254
  }
@@ -1,22 +1,22 @@
1
- # OneWhack Vim Navigator
1
+ # ClankerBend VimNav
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
- Vim Navigator is the public reference app for OneWhack `0.1`. It demonstrates
7
- how an app registers with a central OneWhack host and uses transcript anchors,
6
+ VimNav is the public reference app for ClankerBend `0.1`. It demonstrates
7
+ how an app registers with a central ClankerBend host and uses transcript anchors,
8
8
  annotations, selection, and app-scoped actions without owning CDP directly.
9
9
 
10
10
  ## Run
11
11
 
12
12
  ```sh
13
- cd onewhack
13
+ cd clankerbend
14
14
  npm start
15
15
  ```
16
16
 
17
- The command starts a loopback OneWhack host on an ephemeral `127.0.0.1` port,
17
+ The command starts a loopback ClankerBend host on an ephemeral `127.0.0.1` port,
18
18
  launches Codex Desktop with CDP, injects transcript markers, and serves the Vim
19
- Navigator panel at `/apps/onewill.vim-nav/`.
19
+ VimNav panel at `/apps/onewill.vim-nav/`.
20
20
 
21
21
  For protocol-only mock mode:
22
22
 
@@ -41,7 +41,7 @@ errors, HTTP status/envelope alignment, and bearer-token behavior.
41
41
  Codex Desktop routes normal typing into the prompt composer, so enter transcript
42
42
  navigation mode first:
43
43
 
44
- - `Ctrl+Alt`: toggle Vim transcript mode
44
+ - `Cmd+Option`: toggle Vim transcript mode
45
45
  - `Escape` or `i`: exit Vim transcript mode
46
46
 
47
47
  While Vim transcript mode is active, plain Vim keys are captured for transcript
@@ -58,12 +58,12 @@ active.
58
58
 
59
59
  The focus-safe chords remain available as a fallback:
60
60
 
61
- - `Ctrl+Alt+j` or `Ctrl+Alt+Down`: next transcript anchor
62
- - `Ctrl+Alt+k` or `Ctrl+Alt+Up`: previous transcript anchor
63
- - `Ctrl+Alt+g` or `Ctrl+Alt+Home`: first transcript anchor
64
- - `Ctrl+Alt+G` or `Ctrl+Alt+End`: last transcript anchor
65
- - `Ctrl+Alt+{` or `Ctrl+Alt+PageUp`: previous user message
66
- - `Ctrl+Alt+}` or `Ctrl+Alt+PageDown`: next user message
61
+ - `Cmd+Option+j` or `Cmd+Option+Down`: next transcript anchor
62
+ - `Cmd+Option+k` or `Cmd+Option+Up`: previous transcript anchor
63
+ - `Cmd+Option+g` or `Cmd+Option+Home`: first transcript anchor
64
+ - `Cmd+Option+G` or `Cmd+Option+End`: last transcript anchor
65
+ - `Cmd+Option+{` or `Cmd+Option+PageUp`: previous user message
66
+ - `Cmd+Option+}` or `Cmd+Option+PageDown`: next user message
67
67
 
68
68
  Panel commands support:
69
69
 
@@ -72,9 +72,9 @@ Panel commands support:
72
72
  - `:42` or `42`: jump by visible ruler number
73
73
  - `:latest assistant` / `:latest user`: jump by role
74
74
 
75
- These commands are implemented as OneWhack app actions, not only as panel-local
75
+ These commands are implemented as ClankerBend app actions, not only as panel-local
76
76
  keyboard parsing. External clients can call the same actions through
77
- `POST /onewhack/apps/onewill.vim-nav/actions`.
77
+ `POST /clankerbend/apps/onewill.vim-nav/actions`.
78
78
 
79
79
  ## Writing Another App
80
80
 
@@ -90,9 +90,9 @@ Create an object with:
90
90
  Then register it with one host:
91
91
 
92
92
  ```js
93
- import { OneWhackHost, createMockTranscriptAdapter } from "../../host/src/index.js";
93
+ import { ClankerBendHost, createMockTranscriptAdapter } from "../../host/src/index.js";
94
94
 
95
- const host = new OneWhackHost({
95
+ const host = new ClankerBendHost({
96
96
  transcriptAdapter: createMockTranscriptAdapter({ defaultAppId: "example.app" })
97
97
  });
98
98
 
@@ -101,7 +101,7 @@ await host.start();
101
101
  ```
102
102
 
103
103
  The host serves all mounted apps through one central server and routes commands
104
- through `/onewhack/apps/:appId/actions`.
104
+ through `/clankerbend/apps/:appId/actions`.
105
105
 
106
106
  ## Real Desktop Validation
107
107
 
@@ -109,15 +109,15 @@ The default `npm test` path is mock/protocol-only and does not launch Codex
109
109
  Desktop. To run the real Desktop regression harness:
110
110
 
111
111
  ```sh
112
- cd onewhack
112
+ cd clankerbend
113
113
  npm run test:vim-nav:desktop-real
114
114
  ```
115
115
 
116
- The harness starts the Vim Navigator host, launches Codex Desktop with CDP,
116
+ The harness starts the VimNav host, launches Codex Desktop with CDP,
117
117
  creates a fresh chat, seeds deterministic transcript turns, injects the host
118
118
  renderer bridge, and verifies:
119
119
 
120
- - `Ctrl+Alt` toggles Vim transcript mode without moving scroll
120
+ - `Cmd+Option` toggles Vim transcript mode without moving scroll
121
121
  - `G` lands on the last known transcript item
122
122
  - repeated `k` walks to transcript item `1` without skipping or renumbering
123
123
  - `12G`, `gg`, and oversized `number+G` land on the expected anchors
@@ -1,8 +1,8 @@
1
1
  {
2
- "oneWhackVersion": "0.1",
2
+ "clankerbendVersion": "0.1",
3
3
  "appId": "onewill.vim-nav",
4
4
  "version": "0.1.0",
5
- "name": "OneWill VimNav",
5
+ "name": "VimNav",
6
6
  "description": "Public reference app for keyboard transcript navigation in Codex Desktop.",
7
7
  "distribution": {
8
8
  "kind": "local",
@@ -15,7 +15,7 @@
15
15
  "entrypoint": {
16
16
  "kind": "module",
17
17
  "module": "./src/vim-nav-app.js",
18
- "factory": "createVimNavigatorApp",
18
+ "factory": "createVimNavApp",
19
19
  "publicDir": "./public"
20
20
  },
21
21
  "platform": {
@@ -45,7 +45,7 @@
45
45
  "appServerRollback": false
46
46
  },
47
47
  "panel": {
48
- "title": "Vim Nav",
48
+ "title": "VimNav",
49
49
  "reloadPolicy": "preserve",
50
50
  "preferredWidth": 360
51
51
  },
@@ -1,13 +1,13 @@
1
1
  {
2
- "name": "onewhack-vim-nav",
2
+ "name": "clankerbend-vim-nav",
3
3
  "version": "0.1.0",
4
4
  "type": "module",
5
- "description": "Public OneWhack reference app for Vim-like Codex Desktop transcript navigation.",
5
+ "description": "Public ClankerBend reference app for Vim-like Codex Desktop transcript navigation.",
6
6
  "license": "MIT",
7
7
  "scripts": {
8
8
  "start": "node server.mjs",
9
9
  "start:mock": "node server.mjs --mock",
10
- "test": "node test-onewhack.mjs",
10
+ "test": "node test-clankerbend.mjs",
11
11
  "test:desktop-real": "node test-desktop-real.mjs"
12
12
  },
13
13
  "engines": {
@@ -20,7 +20,7 @@ const token = readToken();
20
20
 
21
21
  els.openPanel.addEventListener("click", async () => {
22
22
  try {
23
- await postJson("/onewhack/panel/open", {});
23
+ await postJson("/clankerbend/panel/open", {});
24
24
  } catch (err) {
25
25
  els.commandStatus.textContent = err.message;
26
26
  }
@@ -36,7 +36,7 @@ bootstrap();
36
36
 
37
37
  async function bootstrap() {
38
38
  try {
39
- const state = await getJson("/onewhack/state");
39
+ const state = await getJson("/clankerbend/state");
40
40
  render(state, true);
41
41
  connectEvents();
42
42
  } catch (err) {
@@ -47,11 +47,44 @@ async function bootstrap() {
47
47
 
48
48
  function readToken() {
49
49
  const params = new URLSearchParams(location.hash.startsWith("#") ? location.hash.slice(1) : location.hash);
50
- const value = params.get("onewhack_token") || "";
51
- if (value) history.replaceState(null, "", location.pathname + location.search);
50
+ const fragmentToken = params.get("clankerbend_token") || "";
51
+ const bootstrapToken = window.__CLANKERBEND_TOKEN || "";
52
+ const metaToken = document.querySelector("meta[name='clankerbend-token']")?.content || "";
53
+ const inlineToken = readInlineBootstrapToken();
54
+ const cachedToken = readCachedToken();
55
+ const value = fragmentToken || bootstrapToken || metaToken || inlineToken || cachedToken;
56
+ if (fragmentToken) history.replaceState(null, "", location.pathname + location.search);
57
+ if (value) writeCachedToken(value);
52
58
  return value;
53
59
  }
54
60
 
61
+ function readInlineBootstrapToken() {
62
+ for (const script of document.scripts) {
63
+ const match = script.textContent?.match(/window\.__CLANKERBEND_TOKEN=("[^"]*");?/);
64
+ if (!match) continue;
65
+ try {
66
+ return JSON.parse(match[1]);
67
+ } catch {
68
+ return "";
69
+ }
70
+ }
71
+ return "";
72
+ }
73
+
74
+ function readCachedToken() {
75
+ try {
76
+ return sessionStorage.getItem("clankerbend_token") || "";
77
+ } catch {
78
+ return "";
79
+ }
80
+ }
81
+
82
+ function writeCachedToken(value) {
83
+ try {
84
+ sessionStorage.setItem("clankerbend_token", value);
85
+ } catch {}
86
+ }
87
+
55
88
  function headers(extra = {}) {
56
89
  return {
57
90
  ...extra,
@@ -61,17 +94,17 @@ function headers(extra = {}) {
61
94
 
62
95
  async function connectEvents() {
63
96
  if (!token) {
64
- const events = new EventSource("/onewhack/events");
97
+ const events = new EventSource("/clankerbend/events");
65
98
  events.addEventListener("state", (event) => render(JSON.parse(event.data)));
66
- events.addEventListener("app-state", () => getJson("/onewhack/state").then((state) => render(state)));
67
- events.addEventListener("action", () => getJson("/onewhack/state").then((state) => render(state)));
99
+ events.addEventListener("app-state", () => getJson("/clankerbend/state").then((state) => render(state)));
100
+ events.addEventListener("action", () => getJson("/clankerbend/state").then((state) => render(state)));
68
101
  events.addEventListener("error", () => {
69
102
  els.subtitle.textContent = "event stream interrupted";
70
103
  });
71
104
  return;
72
105
  }
73
106
 
74
- const res = await fetch("/onewhack/events", { headers: headers() });
107
+ const res = await fetch("/clankerbend/events", { headers: headers() });
75
108
  if (!res.ok || !res.body) throw new Error(`events failed: ${res.status}`);
76
109
  const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
77
110
  let buffer = "";
@@ -94,7 +127,7 @@ function handleSseChunk(chunk) {
94
127
  const data = lines.filter((line) => line.startsWith("data: ")).map((line) => line.slice(6)).join("\n");
95
128
  if (!data || event === "heartbeat") return;
96
129
  if (event === "state") render(JSON.parse(data));
97
- if (event === "app-state" || event === "action") getJson("/onewhack/state").then((state) => render(state));
130
+ if (event === "app-state" || event === "action") getJson("/clankerbend/state").then((state) => render(state));
98
131
  }
99
132
 
100
133
  async function runCommand(raw) {
@@ -193,8 +226,8 @@ async function runAction(type, payload = {}, anchorId) {
193
226
  payload,
194
227
  requestedAt: new Date().toISOString()
195
228
  };
196
- const result = await postJson(`/onewhack/apps/${encodeURIComponent(APP_ID)}/actions`, { action });
197
- if (result.ok) getJson("/onewhack/state").then((state) => render(state));
229
+ const result = await postJson(`/clankerbend/apps/${encodeURIComponent(APP_ID)}/actions`, { action });
230
+ if (result.ok) getJson("/clankerbend/state").then((state) => render(state));
198
231
  return result.ok ? { ok: true, message: result.status || "ok" } : { ok: false, error: result.error || "action failed" };
199
232
  }
200
233
 
@@ -3,14 +3,14 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Vim Nav</title>
6
+ <title>VimNav</title>
7
7
  <link rel="stylesheet" href="./styles.css">
8
8
  </head>
9
9
  <body>
10
10
  <main class="shell">
11
11
  <header class="topbar">
12
12
  <div>
13
- <strong>Vim Nav</strong>
13
+ <strong>VimNav</strong>
14
14
  <p id="subtitle">Transcript keyboard navigation</p>
15
15
  </div>
16
16
  <button id="open-panel" type="button">Panel</button>
@@ -1,10 +1,10 @@
1
1
  import { dirname, join } from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
- import { launchOneWhackCodex } from "../../server.mjs";
3
+ import { launchClankerBendCodex } from "../../server.mjs";
4
4
 
5
5
  const __dirname = dirname(fileURLToPath(import.meta.url));
6
6
 
7
- await launchOneWhackCodex({
7
+ await launchClankerBendCodex({
8
8
  mock: process.argv.includes("--mock"),
9
9
  runDir: join(__dirname, "run")
10
10
  });
@@ -1,9 +1,9 @@
1
1
  export const VIM_NAV_APP_ID = "onewill.vim-nav";
2
2
 
3
- export function createVimNavigatorApp(options = {}) {
3
+ export function createVimNavApp(options = {}) {
4
4
  return {
5
5
  appId: VIM_NAV_APP_ID,
6
- name: "Vim Navigator",
6
+ name: "VimNav",
7
7
  publicDir: options.publicDir,
8
8
  contributes: {
9
9
  panel: true,
@@ -21,16 +21,16 @@ export function createVimNavigatorApp(options = {}) {
21
21
  appServerRollback: false
22
22
  },
23
23
  panel: {
24
- title: "Vim Nav",
24
+ title: "VimNav",
25
25
  reloadPolicy: "preserve",
26
26
  preferredWidth: 360
27
27
  },
28
28
 
29
29
  getManifest(context) {
30
30
  return {
31
- oneWhackVersion: context.protocolVersion,
31
+ clankerbendVersion: context.protocolVersion,
32
32
  appId: VIM_NAV_APP_ID,
33
- name: "Vim Navigator",
33
+ name: "VimNav",
34
34
  entry: context.entry,
35
35
  contributes: this.contributes,
36
36
  permissions: this.permissions,
Binary file