Skip to content

Commit 0b60a2b

Browse files
phase3devclaude
andauthored
Release prep: hardened webview fixes, launcher routing scrub, README quick start (#8)
* fix(md-copy): leading semicolon on the injected payload (ASI safety) When appended after a bundle whose final statement is an expression with no trailing semicolon, the IIFE would parse as a call on that expression and throw before the webview app initializes. Prefix the payload with ';' so the patch is safe regardless of the bundle's final token. Regenerated the three embeds; adds a node-exec test on a no-semicolon bundle. Not triggered by the current 2.1.170 bundle (it ends with '}'); this hardens against future builds. Codex PR review, P2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(md-copy): single-icon copy with real-success gating; drop plain-text + --open Fix the "says Copied but nothing copied" bug and redesign the injected controls: - copyText now reports REAL success: synchronous execCommand("copy") first (gesture-safe, secure-context-independent), Clipboard API fallback; the icon flips to a checkmark only on a genuine copy, and empty text is never success - replace the "Copy" text + caret + "Copy as plain text" menu with a single clipboard icon (Markdown only); checkmark for ~2s, no "Copied" label - decorate is idempotent (prunes duplicates) -> fixes the double rows of buttons - whole-conversation control is one gated icon (conversation view only), pinned top-right (top:26px right:4px) - cc-export.py: drop the --open flag (open raw .jsonl) per product decision - regenerate embeds into launcher/claudemax, claudemax.win.js, add-md-copy.py - tests: add tests/test_md_clipboard.py (copyText honesty: empty/exec/api/no-false -success); replace the export --open test with a rejection test; 91 tests green * feat(launcher): routing scrub + local-env anchor; docs: quick start, demote unmaintained thinking options Release-prep batch landing the hardened webview fixes plus launcher and docs work. - context-icon: match the guard pair by shape (variable-name captures) and store the matched names in the ownership marker, so patch and undo survive minified renames (t/c, Z/U) across bundles 2.1.108-2.1.172. - md-copy: keep visible thinking summaries copyable, gate tool-only turns so no stale/misplaced copy icon appears, and handle DETAILS/SUMMARY in the converter. - launcher: add CC_SCRUB_ROUTING (default off) to clear third-party model routing before launch, plus a `ccwa-local-env` injection anchor for personal builds; documented in the header toggle list. Covered by new bash + Windows tests. - docs: README Quick start with two downloads and three screenshots; demote the unmaintained extension.js patch and local proxy to short TECHNICAL.md links (kept as test-covered scripts); Compatibility refreshed through 2.1.172. 108 tests pass; gen-embeds --check --strict, bash -n, node --check, git diff --check clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): date the fixes 2026-06-10; cap screenshots at max-width 100% - Bump all three workaround entries to [updated 2026-06-10]. - Render the screenshots via <img style="max-width:100%;height:auto"> so they never overflow the container (GitHub already caps; this also covers the site). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(context-icon): var-agnostic reversal of legacy bare markers Codex review on #8: a bundle an older var-agnostic launcher patched on a non-t/c build (the docs call out Z/U) carries a BARE /*ccwa-context-icon*/ marker on renamed guard vars. undo matched bare forms with literal t/c, so a Z/U bare form fell through to the broad >=101->>=50 fallback (bash), which reverted the gate but LEFT the marker; apply then exits early on the marker and commits the original 50% gate, hiding the icon. The Windows launcher and standalone patcher had the same gap (no fallback, so they left the bare form and warned). - bash + win.js + fix-context-icon.py: recognize the both-guards bare marker by shape (variable-name captures) and restore pristine, plus a final pass that strips any leftover bare marker so apply is never wedged by an unrecognized form. - Tests: a legacy Z/U bare marker upgrades to the metadata form on both launchers (enabled + disabled) and the standalone patcher. 113 tests pass. The ===0-removed bare form on non-t/c vars (if(U>=101)...}/*marker*/) is irrecoverable by construction - the original guard var was deleted with no metadata - and was never produced (bare markers were t/c-only; var-agnostic patching shipped with the metadata marker that records the removed var). The orphan-marker strip keeps even that hypothetical from wedging apply. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(context-icon): heal already-wedged bundle; drop over-broad bash undo Codex audit follow-up to 7495f82. Two bash-only gaps vs the Windows launcher and fix-context-icon.py, both of which already handled these: P1 - already-wedged state not recovered. _cc_undo_context_icon returned early unless it saw `>=101)return null}`, so a file an older buggy undo left as `if(Z===0)return null;if(U>=50)return null}/*ccwa-context-icon*/` (gate already reverted to >=50, bare marker still appended) skipped the orphan-marker strip. apply then early-exits on the leftover marker and the icon stays hidden. The early-out now also fires on a leftover marker, so the strip runs and heals it (pristine -> re-patch). As a side effect the emergency backup now captures the true pristine form, not the wedged intermediate. P2 - over-broad unowned rewrite. The generic `>=101)return null}` -> `>=50)return null}` fallback rewrote upstream code that merely resembled a patched value (e.g. an unowned `if(U>=101)return null}`), violating the ownership invariant. Removed it; every form we actually write is covered by the scoped substitutions above, so nothing real is lost. Tests: add the Z/U `>=50 + bare marker` wedged-heal regression (enabled and disabled) and an unowned `>=101` no-rewrite guard, across bash, Windows, and the standalone patcher. 113 -> 121 tests, all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): update context-icon screenshot Replace the context-usage screenshot and correct the alt text to match: the new capture shows the icon and tooltip at 51 percent context used (49 percent remaining until auto-compact). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6e076f3 commit 0b60a2b

21 files changed

Lines changed: 1217 additions & 445 deletions

‎README.md‎

Lines changed: 45 additions & 71 deletions
Large diffs are not rendered by default.

‎TECHNICAL.md‎

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ The launcher intentionally sets no environment of its own by default; it only ad
9696
9797
### Option 2: extension.js patch
9898
99+
> **Unmaintained.** Documented for reference. VS Code only, and must be re-applied after every extension update; the launcher (Option 1) is the supported fix.
100+
99101
A one-line change so the non-interactive spawn always forwards the flag:
100102
101103
```js
@@ -113,6 +115,8 @@ Toggle idea (untested): changing the line to `l.display || (process.env.CC_THINK
113115
114116
### Option 3: local proxy (design)
115117
118+
> **Unmaintained and untested.** A design and a working starting point, not a turnkey fix; it sits in the path of your live auth token. The launcher (Option 1) is the supported fix.
119+
116120
The most thorough fix: a small localhost forward proxy that injects the field at the wire level, so it is surface-agnostic (VS Code + CLI + SDK), needs no install edits, and survives updates. It works because every Claude Code surface resolves the API host as:
117121
118122
```
@@ -172,17 +176,17 @@ function FJe({usedTokens:e, contextWindow:t, onCompact:i, buttonClassName:n}) {
172176
}
173177
```
174178

175-
`c` is the percent of context *remaining*, so `c >= 50` hides the icon whenever at least half the window is free, i.e. it only appears once more than 50% is used. With a 1M window that is 500k tokens. Older bundles (`2.1.131`, `2.1.128`) do not contain this gate; it appeared around `2.1.165`, which matches users' recollection that the icon used to be visible.
179+
`c` is the percent of context *remaining*, so `c >= 50` hides the icon whenever at least half the window is free, i.e. it only appears once more than 50% is used. With a 1M window that is 500k tokens. The same guard shape is present in the locally inspected `2.1.131` (`Z/U`) and `2.1.170` (`t/c`) bundles, with different minified names.
176180

177181
The `CLAUDE_CODE_DISABLE_1M_CONTEXT=1` env var that circulates in the issue threads only shrinks the window to 200k so 50% (100k) is reached sooner. It does not touch the threshold and forces giving up the 1M window, so it is not a real fix.
178182

179183
## The fix
180184

181185
```text
182-
if(c>=50)return null -> if(c>=101)return null}/*ccwa-context-icon*/
186+
if(t===0)return null;if(c>=50)return null} -> if(c>=101)return null}/*ccwa-context-icon:t:c*/
183187
```
184188

185-
`c` is in `[0, 100]`, so `c >= 101` is never true and the gate never hides the icon. The separate `if (t === 0) return null` guard is left intact, so nothing renders before a context window is known. Using `>=101` (rather than deleting the line) is the smallest, most legible, greppable, reversible change, and it preserves the surrounding structure for a clean string substitution. The patch is anchored on the literal `>=50)return null}`, which is stable across builds even though the minified names around it are not, and which occurs exactly once in `2.1.169`. The trailing `/*ccwa-context-icon*/` is an ownership marker: the launcher's reconcile reverses only the marked form, so it can never corrupt upstream code that merely matches a patched value, and a pre-existing unmarked patch from an older launcher is left as-is and re-marked on the next fresh bundle.
189+
`c` is in `[0, 100]`, so `c >= 101` is never true and the gate never hides the icon. Removing `if (t === 0) return null` keeps the icon visible across window-reload gaps where the webview has not repopulated usage data yet; the tooltip can briefly read `0%` until the next usage event arrives. Using `>=101` (rather than deleting the line) is the smallest, most legible, greppable, reversible threshold change, while removing the adjacent startup guard addresses the reload case explicitly. The patch is anchored on the combined guard shape `if(<id>===0)return null;if(<id>>=50)return null}`, not the exact minified names. The trailing `/*ccwa-context-icon:<first-var>:<remaining-var>*/` is an ownership marker that stores the matched names: the launcher's reconcile reverses only known patch fingerprints, so it can never corrupt unrelated upstream code; pre-existing legacy patched forms are normalized to pristine and re-applied on the next launch.
186190
187191
There is no integrity or subresource check on the webview bundle (the only `sha256` references in `extension.js` belong to a bundled crypto library), so an edited `index.js` loads normally.
188192
@@ -197,8 +201,8 @@ The wrapper discovers `index.js` two ways:
197201
198202
The edit is made safe:
199203
200-
* Idempotent: writes only when the recomputed bytes differ, and skips (rather than guesses) if the `>=50)return null}` anchor is absent because the extension changed.
201-
* Ownership-marked: the edit carries a `/*ccwa-context-icon*/` marker; reconcile reverses only its own marked edit and never touches upstream code that merely resembles a patched value.
204+
* Idempotent: writes only when the recomputed bytes differ, and skips (rather than guesses) if the combined guard shape is absent because the extension changed.
205+
* Ownership-marked: the edit carries a `/*ccwa-context-icon:<first-var>:<remaining-var>*/` marker; reconcile reverses only its own marked edit and never touches upstream code that merely resembles a patched value. Older `/*ccwa-context-icon*/` forms are treated as legacy fingerprints.
202206
* Atomic: written to a temp file and moved into place only after it is verified non-empty and actually patched, so a failed or partial write cannot corrupt the bundle.
203207
* Metadata-preserving via `cp -p` (portable; the GNU-only `chmod`/`chown --reference` is avoided so it also works on macOS/BSD). The Windows launcher writes with `fs.writeFileSync` + `fs.renameSync`, inheriting the parent directory's ACLs (it does not preserve the file mode - the one intentional bash/node asymmetry).
204208
* Fully guarded so it never blocks the launch (a read-only file, a renamed bundle, or a missing tool simply no-ops).
@@ -251,9 +255,9 @@ React.createElement(FJe, {
251255
});
252256
```
253257
254-
`usageData` initializes to all zeros and is only filled by usage events that arrive during an assistant turn. There is no seeding from `get_claude_state` on resume, so immediately after a window reload of a continued conversation, before any new turn, the store is still `{0,0,0,0}`: `usedTokens = 0` (the tooltip reads "0% used") and `contextWindow = 0 - 0 - 13000 = -13000` (negative, so the `t === 0` guard does not fire and, after the patch, the icon still renders at 0%). This self-corrects on the next turn, when a usage event fills the store with the real totals. `/context` does not use this store; it queries the CLI directly, so it always shows the true number.
258+
`usageData` initializes to all zeros and is only filled by usage events that arrive during an assistant turn. There is no seeding from `get_claude_state` on resume, so immediately after a window reload of a continued conversation, before any new turn, the store is still empty: the tooltip can read "0% used" until a usage event fills the store with the real totals. The patch removes the `t === 0` hide guard so this state still renders an icon instead of hiding it for the whole reload gap. `/context` does not use this store; it queries the CLI directly, so it always shows the true number.
255259
256-
If showing a transient 0% is undesirable, changing `if(t===0)return null` to `if(t<=0)return null` hides the icon while the store is empty (`t` is negative then) and shows it with the correct value after the first turn. This is documented as an optional manual tweak and is not applied by default.
260+
The transient `0%` is deliberate: it is the same stale-data symptom the extension can already show, and it is less confusing than no icon at all after a reload.
257261
258262
### The glyph is a coarse 3-state gauge
259263
@@ -269,4 +273,4 @@ The pie button's `onClick` is `onCompact`: clicking the icon triggers compaction
269273

270274
## Compatibility
271275

272-
Confirmed on VS Code extension `2.1.169` (native-binary CLI) on Windows 11 and Ubuntu 24.04. The `>50% used` gate appeared around `2.1.165` (absent in `2.1.131` / `2.1.128`). The patch keys off the stable substring `>=50)return null}`, not the minified component name; if a future build changes that exact substring, the launcher safely no-ops (the icon goes missing again) until the anchor is updated. The standalone [`fix-context-icon.py`](fixes/context-icon/fix-context-icon.py) applies the same change directly and supports `--revert`.
276+
Confirmed on VS Code extension `2.1.169` (native-binary CLI) on Windows 11 and Ubuntu 24.04. The same guard shape has been observed with different minified names (`Z/U` in `2.1.131`, `t/c` in `2.1.170`). The patch keys off that shape, not the minified component name or exact variable names; if a future build changes the shape, the launcher safely no-ops (the icon goes missing again) until the anchor is updated. The standalone [`fix-context-icon.py`](fixes/context-icon/fix-context-icon.py) applies the same change directly and supports `--revert`.

‎fixes/context-icon/README.md‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
Restores the always-visible context-usage icon in the VS Code chat input.
66
Extension builds 2.1.165+ hide that icon until you have used more than 50% of the
77
context window. With the 1M context window that is ~500k tokens, so it is
8-
effectively never shown. This fix flips the threshold so the icon renders
9-
whenever a context window is known, at any usage level.
8+
effectively never shown. This fix removes the startup hide guard and flips the
9+
threshold so the icon renders at any usage level, including the reload gap before
10+
fresh usage data arrives.
1011

1112
## Standalone usage
1213

@@ -29,12 +30,16 @@ launcher reverts our edit on the next launch.
2930

3031
## Maintenance Contract
3132

32-
- Anchors / selectors: `>=50)return null}` (component `FJe` in `webview/index.js`).
33-
- Ownership marker: `/*ccwa-context-icon*/`. Apply rewrites `>=50)return null}` ->
34-
`>=101)return null}/*ccwa-context-icon*/`; undo reverses only that marked form.
35-
- Failure mode if an anchor moves: if the anchor string changes, apply no-ops with
36-
a one-line warning (the icon goes missing again) until the anchor is updated. A
37-
bare upstream `>=101)return null}` with no marker is never touched.
33+
- Anchors / selectors: `if(<id>===0)return null;if(<id>>=50)return null}`
34+
(component `FJe` in `webview/index.js`). The identifier names are captured, not
35+
hardcoded.
36+
- Ownership marker: `/*ccwa-context-icon:<first-var>:<remaining-var>*/`. Apply
37+
rewrites the combined guard to `if(<remaining-var>>=101)return null}<marker>`;
38+
undo uses the marker metadata to restore the pristine combined guard with the
39+
same variable names. Older `/*ccwa-context-icon*/` markers are recognized as
40+
legacy fingerprints.
41+
- Failure mode if an anchor moves: if the guard shape changes, apply no-ops with a
42+
one-line warning (the icon goes missing again) until the anchor is updated.
3843
- Launcher registry entry: feature id `context-icon`, file `webview/index.js`,
3944
apply = marked swap, undo = reverse of the marked form only.
4045
- Test fixture: `tests/test_reconcile.py` (launcher engine, both platforms) and

‎fixes/context-icon/fix-context-icon.py‎

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,20 @@
1717
}
1818
1919
With the 1M context window, 50% used = 500,000 tokens, so the icon stays hidden
20-
for virtually an entire normal session. This script flips the threshold so the
21-
icon is visible whenever a context window is known (t>0), at any usage level.
20+
for virtually an entire normal session. This script removes the startup guard and
21+
flips the threshold so the icon stays visible across reload gaps and then
22+
self-corrects when fresh usage data arrives.
2223
23-
if(c>=50)return null -> if(c>=101)return null}/*ccwa-context-icon*/ (marked; c maxes at 100)
24+
if(t===0)return null;if(c>=50)return null}
25+
-> if(c>=101)return null}/*ccwa-context-icon:t:c*/ (marked; c maxes at 100)
2426
25-
The (t===0) guard is left intact. In a resumed window, the webview can still show
26-
a transient 0% before the first fresh response updates context metadata. After
27-
that first response the icon stays visible.
27+
The minified variable names are not stable across builds; the patcher matches the
28+
guard shape with any ASCII JS identifier pair and stores the matched names in the
29+
marker so undo can restore the same pristine names.
30+
31+
In a resumed window, the webview can still show a transient 0% before the first
32+
fresh response updates context metadata. After that first response the icon
33+
corrects.
2834
2935
SAFE & REVERSIBLE
3036
-----------------
@@ -48,14 +54,54 @@
4854
"""
4955
import glob
5056
import os
57+
import re
5158
import shutil
5259
import sys
5360
import tempfile
5461

55-
OLD = ">=50)return null}"
56-
NEW = ">=101)return null}/*ccwa-context-icon*/"
62+
IDENT = r"[A-Za-z_$][A-Za-z0-9_$]*"
63+
OLD_RE = re.compile(rf"if\(({IDENT})===0\)return null;if\(({IDENT})>=50\)return null\}}")
64+
MARKED_RE = re.compile(
65+
rf"if\(({IDENT})>=101\)return null\}}/\*ccwa-context-icon:({IDENT}):\1\*/"
66+
)
67+
# Legacy bare (metadata-less) marker on arbitrary guard names: an older
68+
# var-agnostic write could leave the both-guards >=101 form + bare marker on a
69+
# non-t/c build (e.g. Z/U). Recognize it by shape, not the fixed t/c.
70+
LEGACY_NEW_RE = re.compile(
71+
rf"if\(({IDENT})===0\)return null;if\(({IDENT})>=101\)return null\}}/\*ccwa-context-icon\*/"
72+
)
73+
# Strip any leftover bare marker so patch_file (which only re-applies on the
74+
# pristine guard pair) is never wedged by an unrecognized bare-marked form.
75+
ORPHAN_MARKER_RE = re.compile(r"\)return null\}/\*ccwa-context-icon\*/")
76+
OLD = "if(t===0)return null;if(c>=50)return null}"
77+
NEW_BARE = "if(c>=101)return null}"
78+
NEW_LEGACY = NEW_BARE + "/*ccwa-context-icon*/"
79+
NEW = "if(c>=101)return null}/*ccwa-context-icon:t:c*/"
80+
LEGACY_BARE = "if(t===0)return null;if(c>=101)return null}"
5781
BACKUP_SUFFIX = ".bak-context-icon"
5882

83+
84+
def old_guard(first_var, remaining_var):
85+
return f"if({first_var}===0)return null;if({remaining_var}>=50)return null}}"
86+
87+
88+
def marked_guard(first_var, remaining_var):
89+
return (
90+
f"if({remaining_var}>=101)return null}}"
91+
f"/*ccwa-context-icon:{first_var}:{remaining_var}*/"
92+
)
93+
94+
95+
def undo_known_patches(data):
96+
data = MARKED_RE.sub(lambda m: old_guard(m.group(2), m.group(1)), data)
97+
data = LEGACY_NEW_RE.sub(lambda m: old_guard(m.group(1), m.group(2)), data)
98+
data = (
99+
data.replace(NEW_LEGACY, OLD)
100+
.replace(LEGACY_BARE, OLD)
101+
.replace(NEW_BARE, OLD)
102+
)
103+
return ORPHAN_MARKER_RE.sub(")return null}", data)
104+
59105
DISCOVERY_GLOBS = [
60106
os.path.expanduser("~/.vscode/extensions/anthropic.claude-code-*/webview/index.js"),
61107
os.path.expanduser("~/.vscode-server/extensions/anthropic.claude-code-*/webview/index.js"),
@@ -103,18 +149,22 @@ def write_atomic_preserving_metadata(path, text):
103149
def patch_file(path):
104150
with open(path, "r", encoding="utf-8", newline="") as f:
105151
data = f.read()
106-
if NEW in data:
152+
if MARKED_RE.search(data):
107153
return "already-patched"
108-
n = data.count(OLD)
154+
data = undo_known_patches(data)
155+
matches = list(OLD_RE.finditer(data))
156+
n = len(matches)
109157
if n == 0:
110158
return "gate-not-found (extension version changed? re-inspect FJe in index.js)"
111159
if n > 1:
112160
return f"ambiguous ({n} matches) — skipped for safety"
161+
match = matches[0]
162+
patched = data[: match.start()] + marked_guard(match.group(1), match.group(2)) + data[match.end() :]
113163
backup = path + BACKUP_SUFFIX
114164
if not os.path.exists(backup):
115165
with open(backup, "w", encoding="utf-8", newline="") as b:
116166
b.write(data)
117-
write_atomic_preserving_metadata(path, data.replace(OLD, NEW, 1))
167+
write_atomic_preserving_metadata(path, patched)
118168
return "PATCHED"
119169

120170

‎fixes/markdown-copy-export/README.md‎

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
The Claude Code VS Code chat has no way to copy a whole message or the whole
66
conversation as Markdown (only code blocks have a copy button). This adds a
7-
per-message copy control (Markdown primary, plain text secondary) on every user
8-
and assistant message, a "copy entire conversation" control, and a standalone
9-
CLI that exports a session transcript to Markdown/plain text or opens the raw
10-
`.jsonl`. Affects the VS Code extension webview; the CLI is independent of it.
7+
single-click copy icon (Markdown) on every user and assistant message - it flips
8+
to a checkmark only when the copy actually lands - plus a floating "copy
9+
conversation" icon, and a standalone CLI that exports a session transcript to
10+
Markdown or plain text. Affects the VS Code extension webview; the CLI is
11+
independent of it.
1112

1213
## Standalone usage
1314

@@ -27,7 +28,6 @@ Session exporter (independent of the webview):
2728
python3 fixes/markdown-copy-export/cc-export.py --format text
2829
python3 fixes/markdown-copy-export/cc-export.py --include-thinking --include-tools
2930
python3 fixes/markdown-copy-export/cc-export.py --session ID -o out.md
30-
python3 fixes/markdown-copy-export/cc-export.py --open # open raw .jsonl in VS Code
3131

3232
## Launcher toggle
3333

@@ -39,10 +39,13 @@ controls and reverts ours on the next launch. `CC_WORKAROUNDS=0` reverts it too.
3939
- Anchors / selectors: user bubble `[class*="userMessageContainer_"]`; assistant
4040
bubble `[data-testid="assistant-message"]` (NOT `[data-message-rating]` — that is
4141
the nested, experiment+analytics-gated rating widget, which the sanitizer strips);
42-
chrome strip-prefixes `toolUse_`/`toolResult_`/`toolReference_`/`thinking_`/
43-
`unknownContent_` plus `[data-message-rating]` and `button`;
44-
`navigator.clipboard.writeText` (proven to work via the built-in copy-code
45-
button). Optional refinements pinned at install: the messages container and the
42+
chrome strip-prefixes `toolUse_`/`toolResult_`/`toolReference_`/
43+
`unknownContent_` plus `[data-message-rating]` and `button`; visible
44+
`thinking_` summaries are content and remain copyable;
45+
clipboard write via a synchronous `document.execCommand("copy")` first
46+
(gesture-safe and works without a secure context, e.g. remote / code-server),
47+
falling back to `navigator.clipboard.writeText`; the icon only flips to a
48+
checkmark when a copy actually succeeds. Optional refinements pinned at install: the messages container and the
4649
single all-content wrapper, if any (see the inject source constants
4750
`MESSAGES_CONTAINER` / `ASSISTANT_CONTENT`). When a bundle update renames the
4851
bubble anchor or a chrome hook, the Phase-9 selector guard fails loudly; re-pin

‎fixes/markdown-copy-export/add-md-copy.py‎

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

‎fixes/markdown-copy-export/cc-export.py‎

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
Reads the session JSONL directly (the high-fidelity source: exact text, no
55
re-conversion), so it is independent of the VS Code webview. Default output is
66
the readable conversation only (user text + assistant text); thinking and tool
7-
calls are opt-in. `--open` opens the raw .jsonl in the editor instead.
7+
calls are opt-in.
88
99
python3 cc-export.py # latest session in this cwd -> stdout (markdown)
1010
python3 cc-export.py --session ID # a specific session id
1111
python3 cc-export.py --format text # plain text
1212
python3 cc-export.py --include-thinking --include-tools
1313
python3 cc-export.py -o out.md # write to a file
14-
python3 cc-export.py --open # open the raw transcript in VS Code
1514
python3 cc-export.py --cwd /path/to/proj # resolve as if launched from there
1615
1716
The JSONL is an internal format that can change, so each row's shape is validated
@@ -21,7 +20,6 @@
2120
import glob
2221
import json
2322
import os
24-
import subprocess
2523
import sys
2624
import unicodedata
2725

@@ -157,7 +155,6 @@ def main(argv=None, config=None, env=None):
157155
ap.add_argument("--format", choices=("markdown", "text"), default="markdown")
158156
ap.add_argument("--include-thinking", action="store_true")
159157
ap.add_argument("--include-tools", action="store_true")
160-
ap.add_argument("--open", action="store_true", help="open the raw .jsonl in VS Code")
161158
ap.add_argument("-o", "--output", default=None, help="write to FILE (default: stdout)")
162159
args = ap.parse_args(argv)
163160

@@ -168,15 +165,6 @@ def main(argv=None, config=None, env=None):
168165
sys.stderr.write("cc-export: no matching session transcript found\n")
169166
return 1
170167

171-
if args.open:
172-
editor = env.get("CC_EDITOR", "code")
173-
try:
174-
subprocess.run([editor, path], check=False, env=env)
175-
except FileNotFoundError:
176-
sys.stderr.write(f"cc-export: editor '{editor}' not found on PATH\n")
177-
return 1
178-
return 0
179-
180168
out = render(
181169
read_rows(path),
182170
fmt=args.format,

0 commit comments

Comments
 (0)