claude-code-extensions 0.1.0

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.
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Always Show Context
3
+ *
4
+ * Shows context usage percentage at all times, not just when approaching
5
+ * the limit. The built-in TokenWarning only fires within 20k tokens of
6
+ * the threshold — on a 1M context window, that's ~98% full, far too late.
7
+ *
8
+ * Addresses: https://github.com/anthropics/claude-code/issues/18456 (51 👍)
9
+ *
10
+ * Strategy:
11
+ * 1. Find TokenWarning by its unique "Context low" string
12
+ * 2. Remove the isAboveWarningThreshold gate so the indicator always renders
13
+ * 3. Fix the warning color to be neutral when below threshold
14
+ * 4. Soften "Context low" label to "Context" for always-on display
15
+ *
16
+ * For auto-compact users (the default): shows a dim "X% context used"
17
+ * line at all times. For manual-compact users: shows "Context (X% remaining)"
18
+ * in neutral color when below threshold, escalating to warning/error colors
19
+ * as context fills up.
20
+ */
21
+
22
+ export default {
23
+ id: 'always-show-context',
24
+ name: 'Always Show Context',
25
+ description: 'Always display context usage percentage, not just when near limit',
26
+
27
+ apply(ctx) {
28
+ const { ast, editor, find, index, assert, src } = ctx;
29
+ const { findFirst } = find;
30
+
31
+ // Find TokenWarning function via its unique "Context low" marker.
32
+ // Check indexed literals first (string concat), then template elements.
33
+ let marker = null;
34
+ for (const [value, nodes] of index.literalsByValue) {
35
+ if (typeof value === 'string' && value.includes('Context low')) {
36
+ marker = nodes[0];
37
+ break;
38
+ }
39
+ }
40
+ if (!marker) {
41
+ marker = findFirst(ast, n =>
42
+ n.type === 'TemplateElement' &&
43
+ n.value?.raw?.includes('Context low')
44
+ );
45
+ }
46
+ assert(marker, 'Could not find "Context low" marker in bundle');
47
+
48
+ const fn = index.enclosingFunction(marker);
49
+ assert(fn, 'Could not find TokenWarning function');
50
+
51
+ // Find the early-return gate:
52
+ // if (!isAboveWarningThreshold || suppressWarning) { return null; }
53
+ // Pattern: IfStatement → test: !X || Y, consequent: return null,
54
+ // and it must appear before the "Context low" string.
55
+ const gate = findFirst(fn, n => {
56
+ if (n.type !== 'IfStatement' || n.start >= marker.start) return false;
57
+ const t = n.test;
58
+ if (t.type !== 'LogicalExpression' || t.operator !== '||') return false;
59
+ if (t.left.type !== 'UnaryExpression' || t.left.operator !== '!') return false;
60
+ return findFirst(n.consequent, r =>
61
+ r.type === 'ReturnStatement' &&
62
+ r.argument?.type === 'Literal' &&
63
+ r.argument.value === null
64
+ ) !== null;
65
+ });
66
+ assert(gate, 'Could not find early-return gate: if(!X||Y){return null}');
67
+
68
+ // Save the minified name for isAboveWarningThreshold before editing
69
+ const warnVar = gate.test.left.argument;
70
+ assert(warnVar.type === 'Identifier',
71
+ 'Expected Identifier for isAboveWarningThreshold');
72
+
73
+ // Edit 1: Remove threshold check from gate, keep suppress check.
74
+ // !isAboveWarningThreshold || suppressWarning → suppressWarning
75
+ editor.replaceRange(gate.test.start, gate.test.end, src(gate.test.right));
76
+
77
+ // Edit 2: Neutral color when below warning threshold.
78
+ // Find: isAboveErrorThreshold ? "error" : "warning"
79
+ // Replace "warning" with: isAboveWarningThreshold ? "warning" : void 0
80
+ // This makes text render in default color when context usage is low.
81
+ const colorTernary = findFirst(fn, n =>
82
+ n.type === 'ConditionalExpression' &&
83
+ n.consequent.type === 'Literal' && n.consequent.value === 'error' &&
84
+ n.alternate.type === 'Literal' && n.alternate.value === 'warning'
85
+ );
86
+ if (colorTernary) {
87
+ editor.replaceRange(
88
+ colorTernary.alternate.start,
89
+ colorTernary.alternate.end,
90
+ `${warnVar.name}?"warning":void 0`
91
+ );
92
+ }
93
+
94
+ // Edit 3: Soften "Context low" → "Context" for neutral always-on display.
95
+ const fnSrc = src(fn);
96
+ const needle = 'Context low';
97
+ let pos = 0;
98
+ while ((pos = fnSrc.indexOf(needle, pos)) !== -1) {
99
+ editor.replaceRange(
100
+ fn.start + pos,
101
+ fn.start + pos + needle.length,
102
+ 'Context'
103
+ );
104
+ pos += needle.length;
105
+ }
106
+
107
+ // Edit 4: Change "X% until auto-compact" → "X% context used"
108
+ // The bundle has: reactiveOnlyMode ? `${100-var}% context used` : `${var}% until auto-compact`
109
+ // reactiveOnlyMode is always false, so patch the alternate branch to also show context used.
110
+ const autocompactTpl = findFirst(fn, n =>
111
+ n.type === 'TemplateLiteral' &&
112
+ n.quasis.some(q => q.value?.raw?.includes('% until auto-compact'))
113
+ );
114
+ if (autocompactTpl) {
115
+ const varName = src(autocompactTpl.expressions[0]);
116
+ editor.replaceRange(
117
+ autocompactTpl.start,
118
+ autocompactTpl.end,
119
+ `\`\${100-${varName}}% context used\``
120
+ );
121
+ }
122
+ },
123
+ };
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Always Show Thinking Patch
3
+ *
4
+ * Makes Claude's thinking blocks always display expanded instead of
5
+ * collapsed behind "∴ Thinking (ctrl+o to expand)".
6
+ *
7
+ * Addresses: https://github.com/anthropics/claude-code/issues/8477 (195 👍)
8
+ *
9
+ * In AssistantThinkingMessage, the gate is:
10
+ * const shouldShowFullThinking = isTranscriptMode || verbose;
11
+ * if (!shouldShowFullThinking) { return <collapsed view> }
12
+ *
13
+ * We find the function by its unique "∴ Thinking" string and replace
14
+ * the negated OR gate with `false` so the expanded view always renders.
15
+ */
16
+
17
+ export default {
18
+ id: 'always-show-thinking',
19
+ name: 'Always Show Thinking',
20
+ description: 'Show thinking block content inline instead of collapsed',
21
+
22
+ apply(ctx) {
23
+ const { ast, editor, find, index, assert, src } = ctx;
24
+ const { findFirst } = find;
25
+
26
+ // Find the AssistantThinkingMessage function by its unique string literal.
27
+ // The "∴" character (U+2234, "therefore") only appears in this component.
28
+ // Use the literal index to find the marker, then walk up to its enclosing function.
29
+ let thinkingMarker = null;
30
+ for (const [value, nodes] of index.literalsByValue) {
31
+ if (typeof value === 'string' && value.includes('\u2234 Thinking')) {
32
+ thinkingMarker = nodes[0];
33
+ break;
34
+ }
35
+ }
36
+ assert(thinkingMarker, 'Could not find "∴ Thinking" literal');
37
+ const thinkingFn = index.enclosingFunction(thinkingMarker);
38
+ assert(thinkingFn, 'Could not find AssistantThinkingMessage function (marker: "∴ Thinking")');
39
+
40
+ // Find the gate: if(!(Y||O)) where Y=isTranscriptMode, O=verbose.
41
+ // Structure: IfStatement whose test is UnaryExpression(!) wrapping LogicalExpression(||)
42
+ // This is the FIRST such pattern in the function, appearing before the "∴ Thinking" strings.
43
+ const gate = findFirst(thinkingFn, n =>
44
+ n.type === 'IfStatement'
45
+ && n.test.type === 'UnaryExpression'
46
+ && n.test.operator === '!'
47
+ && n.test.argument.type === 'LogicalExpression'
48
+ && n.test.argument.operator === '||'
49
+ );
50
+ assert(gate, 'Could not find thinking display gate: if(!(X||Y))');
51
+
52
+ // Verify this is the right gate by checking it appears before the "∴ Thinking" string
53
+ const firstThinkingLiteral = findFirst(thinkingFn, n =>
54
+ n.type === 'Literal' && typeof n.value === 'string'
55
+ && n.value.includes('\u2234 Thinking'));
56
+ assert(
57
+ gate.start < firstThinkingLiteral.start,
58
+ 'Gate should appear before "∴ Thinking" literal'
59
+ );
60
+
61
+ // Replace the test expression with `false` so the collapsed branch is never taken.
62
+ // This makes thinking always display in expanded mode.
63
+ editor.replaceRange(gate.test.start, gate.test.end, 'false');
64
+ },
65
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Attribution Banner Patch
3
+ *
4
+ * Changes "Claude Code" to "Claude Code · @wormcoffee" on the title line.
5
+ * Targets the bold <Text> in the condensed layout and the border title
6
+ * in the boxed layout. No extra elements, no layout changes.
7
+ */
8
+
9
+ export default {
10
+ id: 'banner',
11
+ name: 'Attribution Banner',
12
+ description: 'Show "@wormcoffee" on the Claude Code title line',
13
+
14
+ apply(ctx) {
15
+ const { ast, editor, find, index, src, assert } = ctx;
16
+ const { findFirst } = find;
17
+
18
+ // ── Condensed layout: createElement(T, {bold:true}, "Claude Code") ──
19
+ // Use literal index to find "Claude Code", then walk up to the createElement call.
20
+
21
+ const claudeCodeLiterals = index.literalsByValue.get('Claude Code') || [];
22
+ let boldTextCall = null;
23
+ for (const lit of claudeCodeLiterals) {
24
+ const call = index.ancestor(lit, 'CallExpression');
25
+ if (!call || call.callee.type !== 'MemberExpression' || call.callee.property.name !== 'createElement') continue;
26
+ const hasBold = call.arguments.some(a =>
27
+ a?.type === 'ObjectExpression' &&
28
+ a.properties.some(p => p.key?.type === 'Identifier' && p.key.name === 'bold'));
29
+ if (hasBold) { boldTextCall = call; break; }
30
+ }
31
+ assert(boldTextCall, 'Could not find createElement(T, {bold}, "Claude Code")');
32
+
33
+ const textLiteral = boldTextCall.arguments.find(a =>
34
+ a.type === 'Literal' && a.value === 'Claude Code');
35
+ editor.replaceRange(textLiteral.start, textLiteral.end,
36
+ '"Claude Code Extensions (cx) by x.com/@wormcoffee"');
37
+
38
+ // ── Boxed layout: b7("claude",o)("Claude Code") in the border title ──
39
+
40
+ // ── Boxed layout: b7("claude",o)("Claude Code") ──
41
+ // Find "Claude Code" literal whose parent CallExpression's callee is another call with "claude"
42
+ let titleCall = null;
43
+ for (const lit of claudeCodeLiterals) {
44
+ const call = index.parentMap.get(lit);
45
+ if (!call || call.type !== 'CallExpression' || call.arguments.length !== 1) continue;
46
+ if (call.callee.type === 'CallExpression' &&
47
+ call.callee.arguments[0]?.type === 'Literal' &&
48
+ call.callee.arguments[0].value === 'claude') {
49
+ titleCall = call;
50
+ break;
51
+ }
52
+ }
53
+ if (titleCall) {
54
+ editor.replaceRange(titleCall.arguments[0].start, titleCall.arguments[0].end,
55
+ '"Claude Code Extensions (cx) by x.com/@wormcoffee"');
56
+ }
57
+ },
58
+ };
@@ -0,0 +1,104 @@
1
+ /**
2
+ * /cd Command Patch
3
+ *
4
+ * Adds a /cd <path> slash command to change the working directory
5
+ * mid-session without losing conversation context.
6
+ *
7
+ * Addresses: https://github.com/anthropics/claude-code/issues/3473 (54 👍)
8
+ *
9
+ * AST strategy: find the bundled setCwd function (via its "tengu_shell_set_cwd"
10
+ * telemetry string) and getCwdState (via its named export mapping), then inject
11
+ * a new LocalCommand into the memoized COMMANDS array. The command calls setCwd
12
+ * and reports the new working directory.
13
+ */
14
+
15
+ export default {
16
+ id: 'cd-command',
17
+ name: '/cd Command',
18
+ description: '/cd <path> — change where bash commands run (same as shell cd, keeps project settings)',
19
+
20
+ apply(ctx) {
21
+ const { ast, editor, find, query, index, src, assert } = ctx;
22
+
23
+ // ── 1. Find setCwd function via telemetry string ─────────────────
24
+ // setCwd logs "tengu_shell_set_cwd" on success. The shell exec function
25
+ // also logs it on failure, but inside an async arrow — setCwd is the
26
+ // only sync FunctionDeclaration containing this string.
27
+ const setCwdFns = query.findFunctionsContainingStrings(
28
+ ast, 'tengu_shell_set_cwd',
29
+ );
30
+ assert(setCwdFns.length >= 1, 'Could not find functions with tengu_shell_set_cwd');
31
+ const setCwdFn = setCwdFns.find(fn =>
32
+ fn.type === 'FunctionDeclaration' && !fn.async);
33
+ assert(setCwdFn, 'Could not find setCwd (sync FunctionDeclaration)');
34
+ const setCwdName = ctx.getFunctionName(setCwdFn);
35
+ assert(setCwdName, 'Could not determine setCwd function name');
36
+
37
+ // ── 2. Find getCwdState via export mapping ───────────────────────
38
+ // The state module exports: getCwdState:()=>XX
39
+ // This is a Property with Identifier key and zero-param ArrowFunction value.
40
+ const getCwdProp = find.findFirst(ast, n =>
41
+ n.type === 'Property' &&
42
+ n.key?.type === 'Identifier' && n.key.name === 'getCwdState' &&
43
+ n.value?.type === 'ArrowFunctionExpression' &&
44
+ n.value.params.length === 0 &&
45
+ n.value.body?.type === 'Identifier');
46
+ assert(getCwdProp, 'Could not find getCwdState export mapping');
47
+ const getCwdName = getCwdProp.value.body.name;
48
+
49
+ // ── 3. Find the COMMANDS array ───────────────────────────────────
50
+ // Locate via the compact command: find its definition object, trace to
51
+ // the exported variable, then find the large array containing it.
52
+ const compactObj = query.findObjectWithStringProps(ast, [
53
+ ['name', 'compact'],
54
+ ['type', 'local'],
55
+ ]);
56
+ assert(compactObj, 'Could not find compact command definition');
57
+
58
+ // Walk up to: localVar = {name:"compact",...}
59
+ let assignNode = index.parentMap.get(compactObj);
60
+ while (assignNode && assignNode.type !== 'AssignmentExpression') {
61
+ assignNode = index.parentMap.get(assignNode);
62
+ }
63
+ assert(
64
+ assignNode?.type === 'AssignmentExpression' &&
65
+ assignNode.left?.type === 'Identifier',
66
+ 'Could not find compact assignment',
67
+ );
68
+ const localVar = assignNode.left.name;
69
+
70
+ // Find re-export: exportVar = localVar
71
+ const reExport = find.findFirst(ast, n =>
72
+ n.type === 'AssignmentExpression' &&
73
+ n.right?.type === 'Identifier' && n.right.name === localVar &&
74
+ n.left?.type === 'Identifier' && n.left.name !== localVar);
75
+ assert(reExport, 'Could not find compact re-export');
76
+ const exportVar = reExport.left.name;
77
+
78
+ // Find the array containing exportVar (COMMANDS has 40+ elements)
79
+ const commandsArr = find.findFirst(ast, n => {
80
+ if (n.type !== 'ArrayExpression') return false;
81
+ if (n.elements.length < 20) return false;
82
+ return n.elements.some(el =>
83
+ el?.type === 'Identifier' && el.name === exportVar);
84
+ });
85
+ assert(commandsArr, 'Could not find COMMANDS array');
86
+
87
+ // ── 4. Inject /cd command ────────────────────────────────────────
88
+ // Insert as last concrete element, before the closing bracket.
89
+ // The command calls setCwd (which validates path, resolves symlinks,
90
+ // updates internal state) and returns the new CWD.
91
+ const lastEl = commandsArr.elements[commandsArr.elements.length - 1];
92
+ const cdCmd =
93
+ `,{type:"local",name:"cd",description:"Change working directory"` +
94
+ `,argumentHint:"<path>",supportsNonInteractive:true` +
95
+ `,load:()=>Promise.resolve({call:async(q)=>{` +
96
+ `let p=q.trim();` +
97
+ `if(!p)return{type:"text",value:"Current directory: "+${getCwdName}()};` +
98
+ `if(p.startsWith("~"))p=(process.env.HOME||"")+p.slice(1);` +
99
+ `try{${setCwdName}(p);return{type:"text",value:"Changed to "+${getCwdName}()}}` +
100
+ `catch(e){return{type:"text",value:e.message||"Failed to change directory"}}` +
101
+ `}})}`;
102
+ editor.insertAt(lastEl.end, cdCmd);
103
+ },
104
+ };
@@ -0,0 +1,112 @@
1
+ /**
2
+ * CX Badge Patch
3
+ *
4
+ * Adds a small "cx" indicator to the left of the permission mode text
5
+ * (e.g. "bypass permissions on", "accept edits on") in the footer.
6
+ * When no mode is active, the badge still shows on the footer line.
7
+ */
8
+
9
+ export default {
10
+ id: 'cx-badge',
11
+ name: 'CX Badge',
12
+ description: 'Show a persistent "cx" indicator in the prompt footer',
13
+
14
+ apply(ctx) {
15
+ const { ast, editor, find, src, assert } = ctx;
16
+ const { findFirst } = find;
17
+
18
+ // Find the ModeIndicator function via the unique "? for shortcuts" string.
19
+ // Then locate the final return that builds <Box height={1} overflow="hidden">
20
+ // which contains modePart, tasksPart, and parts.
21
+
22
+ // Strategy: find the string "? for shortcuts" — it's inside a JSX element
23
+ // in ModeIndicator. Then find the enclosing function.
24
+ const shortcutsHint = findFirst(ast, n =>
25
+ n.type === 'Literal' && n.value === '? for shortcuts');
26
+ assert(shortcutsHint, 'Could not find "? for shortcuts" literal');
27
+
28
+ // Walk up to find the function containing this
29
+ const modeIndicatorFn = findFirst(ast, node => {
30
+ if (node.type !== 'FunctionDeclaration' && node.type !== 'FunctionExpression') return false;
31
+ // Check if the "? for shortcuts" literal is inside this function
32
+ let found = false;
33
+ const check = n => {
34
+ if (n === shortcutsHint) { found = true; return; }
35
+ if (found) return;
36
+ for (const key of Object.keys(n)) {
37
+ if (key === 'type' || key === 'start' || key === 'end') continue;
38
+ const val = n[key];
39
+ if (val && typeof val === 'object') {
40
+ if (Array.isArray(val)) {
41
+ for (const item of val) {
42
+ if (item && typeof item.type === 'string') check(item);
43
+ if (found) return;
44
+ }
45
+ } else if (typeof val.type === 'string') {
46
+ check(val);
47
+ if (found) return;
48
+ }
49
+ }
50
+ }
51
+ };
52
+ check(node);
53
+ return found;
54
+ });
55
+ assert(modeIndicatorFn, 'Could not find ModeIndicator function');
56
+
57
+ // Find the createElement call with height:1 and overflow:"hidden" props
58
+ // This is the final return: <Box height={1} overflow="hidden">
59
+ const boxWithHeight1 = findFirst(modeIndicatorFn, n => {
60
+ if (n.type !== 'CallExpression') return false;
61
+ // Look for an object argument with height:1 and overflow:"hidden"
62
+ return n.arguments.some(arg => {
63
+ if (arg?.type !== 'ObjectExpression') return false;
64
+ let hasHeight = false;
65
+ let hasOverflow = false;
66
+ for (const p of arg.properties) {
67
+ if (p.key?.name === 'height' && p.value?.value === 1) hasHeight = true;
68
+ if (p.key?.name === 'overflow' && p.value?.value === 'hidden') hasOverflow = true;
69
+ }
70
+ return hasHeight && hasOverflow;
71
+ });
72
+ });
73
+ assert(boxWithHeight1, 'Could not find <Box height={1} overflow="hidden">');
74
+
75
+ // Get the React namespace from the createElement call
76
+ // The call looks like: R.createElement(Box, {height:1, overflow:"hidden"}, ...children)
77
+ const callee = boxWithHeight1.callee;
78
+ let R;
79
+ if (callee.type === 'MemberExpression') {
80
+ R = src(callee.object);
81
+ } else {
82
+ // Could be jsxs or jsx direct call
83
+ R = src(callee);
84
+ }
85
+
86
+ // Find the Box component reference (first argument to createElement)
87
+ const BoxRef = src(boxWithHeight1.arguments[0]);
88
+
89
+ // Find the Text component — look for a createElement call with " · " string
90
+ const separatorCall = findFirst(modeIndicatorFn, n => {
91
+ if (n.type !== 'CallExpression') return false;
92
+ return n.arguments.some(a => a?.type === 'Literal' && a.value === ' · ');
93
+ });
94
+ assert(separatorCall, 'Could not find separator " · " createElement');
95
+ const TextRef = src(separatorCall.arguments[0]);
96
+
97
+ // Insert a CX badge as the first child of the Box.
98
+ // The children start after the props object.
99
+ // Find the props object position
100
+ const propsArg = boxWithHeight1.arguments.find(a =>
101
+ a?.type === 'ObjectExpression' &&
102
+ a.properties.some(p => p.key?.name === 'height'));
103
+
104
+ // Insert right after the props argument (before the first child)
105
+ const insertPos = propsArg.end;
106
+
107
+ // CX badge: inverse claude orange
108
+ const cxBadge = `,${R}.createElement(${BoxRef},{flexShrink:0},${R}.createElement(${TextRef},{inverse:true,color:"claude"},"cx"),${R}.createElement(${TextRef},null," "))`;
109
+
110
+ editor.insertAt(insertPos, cxBadge);
111
+ },
112
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * cx Resume Commands Patch
3
+ *
4
+ * Rewrites user-facing resume/continue command hints so they point at
5
+ * the cx wrapper instead of bare claude.
6
+ *
7
+ * AST strategy: scan string literals and template chunks for the stable
8
+ * command snippets that Claude prints to users, then replace only those
9
+ * substrings in-place. This covers shutdown hints, cross-project resume
10
+ * commands, and resume tips without relying on minified variable names.
11
+ */
12
+
13
+ export default {
14
+ id: 'cx-resume-commands',
15
+ name: 'cx Resume Commands',
16
+ description: 'Show cx instead of claude in resume/continue command hints',
17
+
18
+ apply(ctx) {
19
+ const { ast, editor, find, src, assert } = ctx;
20
+ const { findAll } = find;
21
+
22
+ const replacements = [
23
+ ['claude --continue', 'cx --continue'],
24
+ ['claude --resume', 'cx --resume'],
25
+ ['claude -p --resume', 'cx -p --resume'],
26
+ ];
27
+
28
+ let changed = 0;
29
+ const rewrite = (node, text) => {
30
+ let next = text;
31
+ for (const [from, to] of replacements) {
32
+ next = next.split(from).join(to);
33
+ }
34
+ if (next !== text) {
35
+ editor.replaceRange(node.start, node.end, JSON.stringify(next));
36
+ changed++;
37
+ }
38
+ };
39
+
40
+ for (const node of findAll(ast, n => n.type === 'Literal' && typeof n.value === 'string')) {
41
+ rewrite(node, node.value);
42
+ }
43
+
44
+ for (const node of findAll(ast, n => n.type === 'TemplateElement' && typeof n.value?.raw === 'string')) {
45
+ const raw = src(node);
46
+ let nextRaw = raw;
47
+ for (const [from, to] of replacements) {
48
+ nextRaw = nextRaw.split(from).join(to);
49
+ }
50
+ if (nextRaw !== raw) {
51
+ editor.replaceRange(node.start, node.end, nextRaw);
52
+ changed++;
53
+ }
54
+ }
55
+
56
+ assert(changed >= 4, `Expected at least 4 resume/continue command rewrites, found ${changed}`);
57
+ },
58
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Disable Paste Collapse
3
+ *
4
+ * Prevents pasted text from being collapsed into "[Pasted text #N +X lines]"
5
+ * placeholders. All pasted content is inserted inline so you can review and
6
+ * edit it before submitting.
7
+ *
8
+ * Addresses: https://github.com/anthropics/claude-code/issues/23134 (77 👍)
9
+ *
10
+ * Strategy: find the paste-collapse condition in onTextPaste() — identified
11
+ * by the call to formatPastedTextRef (Xd8) which contains the stable string
12
+ * literal "Pasted text #" — and replace the guard condition with `false`.
13
+ */
14
+
15
+ export default {
16
+ id: 'disable-paste-collapse',
17
+ name: 'Disable Paste Collapse',
18
+ description: 'Show pasted text inline instead of collapsing into [Pasted text #N]',
19
+
20
+ apply(ctx) {
21
+ const { ast, editor, find, src, assert } = ctx;
22
+ const { findFirst } = find;
23
+
24
+ // Find the formatPastedTextRef function by its "Pasted text #" template literal.
25
+ const fmtFn = findFirst(ast, n =>
26
+ n.type === 'FunctionDeclaration' &&
27
+ src(n).includes('Pasted text #'));
28
+ assert(fmtFn, 'Could not find formatPastedTextRef function');
29
+ const fmtName = fmtFn.id.name; // e.g. "Xd8"
30
+
31
+ // Find the onTextPaste function: contains a call to formatPastedTextRef
32
+ // and an if-statement whose consequent calls it.
33
+ // We look for the IfStatement whose consequent block contains a call
34
+ // to fmtName — that's the collapse gate.
35
+ const collapseIf = findFirst(ast, n => {
36
+ if (n.type !== 'IfStatement') return false;
37
+ // The consequent must contain a call to fmtName
38
+ const hasFmtCall = findFirst(n.consequent, c =>
39
+ c.type === 'CallExpression' &&
40
+ c.callee.type === 'Identifier' &&
41
+ c.callee.name === fmtName);
42
+ if (!hasFmtCall) return false;
43
+ // The alternate should insert text directly (else branch)
44
+ return n.alternate !== null;
45
+ });
46
+ assert(collapseIf, 'Could not find paste-collapse if-statement');
47
+
48
+ // Replace the test expression with `false`
49
+ const test = collapseIf.test;
50
+ editor.replaceRange(test.start, test.end, 'false');
51
+ },
52
+ };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Disable Telemetry Patch
3
+ *
4
+ * Strips Datadog and 1P (first-party) analytics calls.
5
+ *
6
+ * Three surgical cuts:
7
+ * 1. Kill the analytics sink — logEvent() queues forever, nothing drains
8
+ * 2. Noop Datadog HTTP flush + blank endpoint — no POST to Datadog
9
+ * 3. Noop 1P event logging init — prevents exporter + retry of old events
10
+ */
11
+
12
+ export default {
13
+ id: 'disable-telemetry',
14
+ name: 'Disable Telemetry',
15
+ description: 'Strip Datadog and 1P analytics calls',
16
+
17
+ apply(ctx) {
18
+ const { ast, editor, find, query, assert } = ctx;
19
+ const { findFirst } = find;
20
+
21
+ // ── 1. Kill the analytics sink ───────────────────────────────────────
22
+ // initializeAnalyticsSink() calls attachAnalyticsSink({logEvent, logEventAsync}).
23
+ // Replace that call with void 0 so the sink never attaches. Without a
24
+ // sink, logEvent() just pushes to an internal queue that never drains —
25
+ // neither Datadog nor 1P receives anything.
26
+ //
27
+ // Marker: object literal with preserved keys "logEvent" + "logEventAsync"
28
+ // passed as sole argument to a call expression.
29
+
30
+ const sinkCall = findFirst(ast, n => {
31
+ if (n.type !== 'CallExpression' || n.arguments.length !== 1) return false;
32
+ const arg = n.arguments[0];
33
+ if (arg.type !== 'ObjectExpression') return false;
34
+ const keys = arg.properties
35
+ .filter(p => p.type === 'Property')
36
+ .map(p => p.key.name || p.key.value);
37
+ return keys.includes('logEvent') && keys.includes('logEventAsync');
38
+ });
39
+ assert(sinkCall, 'Could not find attachAnalyticsSink({logEvent, logEventAsync})');
40
+ editor.replaceRange(sinkCall.start, sinkCall.end, 'void 0');
41
+
42
+ // ── 2. Noop Datadog HTTP flush + blank endpoint ─────────────────────
43
+ // shutdownDatadog() calls flushLogs() on exit, which would POST
44
+ // whatever's in logBatch. Insert early return to prevent any HTTP call.
45
+ //
46
+ // Marker: "DD-API-KEY" header string. Take the smallest zero-param
47
+ // function containing it (flushLogs, not a module wrapper).
48
+
49
+ const ddCandidates = query.findFunctionsContainingStrings(ast, 'DD-API-KEY');
50
+ const flushFns = ddCandidates
51
+ .filter(fn => fn.params.length === 0)
52
+ .sort((a, b) => (a.end - a.start) - (b.end - b.start));
53
+ assert(flushFns.length >= 1, 'Could not find Datadog flushLogs function');
54
+ const flushBody = flushFns[0].body;
55
+ assert(flushBody.type === 'BlockStatement', 'flushLogs: expected block body');
56
+ editor.insertAt(flushBody.start + 1, 'return;');
57
+
58
+ // Blank the endpoint URL as extra safety (token is not a plain literal
59
+ // in the bundle, so we target the URL instead).
60
+ const endpointLit = findFirst(ast, n =>
61
+ n.type === 'Literal' &&
62
+ n.value === 'https://http-intake.logs.us5.datadoghq.com/api/v2/logs');
63
+ assert(endpointLit, 'Could not find Datadog endpoint URL');
64
+ editor.replaceRange(endpointLit.start, endpointLit.end, '""');
65
+
66
+ // ── 3. Noop 1P event logging init ───────────────────────────────────
67
+ // Prevents the OpenTelemetry exporter from being created, which also
68
+ // stops retryPreviousBatches from re-sending old failed events.
69
+ //
70
+ // Marker: "com.anthropic.claude_code.events" instrumentation scope.
71
+ // Take the smallest zero-param function containing it.
72
+
73
+ const init1PCandidates = query.findFunctionsContainingStrings(
74
+ ast, 'com.anthropic.claude_code.events');
75
+ const init1PFns = init1PCandidates
76
+ .filter(fn => fn.params.length === 0)
77
+ .sort((a, b) => (a.end - a.start) - (b.end - b.start));
78
+ assert(init1PFns.length >= 1, 'Could not find initialize1PEventLogging');
79
+ const init1PBody = init1PFns[0].body;
80
+ assert(init1PBody.type === 'BlockStatement',
81
+ 'initialize1PEventLogging: expected block body');
82
+ editor.insertAt(init1PBody.start + 1, 'return;');
83
+ },
84
+ };