claude-code-extensions 0.1.0 → 0.1.1
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.
- package/dist/ast.d.ts +43 -0
- package/dist/ast.js +308 -0
- package/dist/ast.js.map +1 -0
- package/dist/cli-setup.d.ts +14 -0
- package/dist/cli-setup.js +110 -0
- package/dist/cli-setup.js.map +1 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +218 -0
- package/dist/cli.js.map +1 -0
- package/dist/patch-worker.d.ts +6 -0
- package/dist/patch-worker.js +27 -0
- package/dist/patch-worker.js.map +1 -0
- package/dist/patches/always-show-context.d.ts +23 -0
- package/dist/patches/always-show-context.js +97 -0
- package/dist/patches/always-show-context.js.map +1 -0
- package/dist/patches/always-show-thinking.d.ts +18 -0
- package/dist/patches/always-show-thinking.js +55 -0
- package/dist/patches/always-show-thinking.js.map +1 -0
- package/dist/patches/banner.d.ts +10 -0
- package/dist/patches/banner.js +60 -0
- package/dist/patches/banner.js.map +1 -0
- package/dist/patches/cd-command.d.ts +16 -0
- package/dist/patches/cd-command.js +89 -0
- package/dist/patches/cd-command.js.map +1 -0
- package/dist/patches/cx-badge.d.ts +10 -0
- package/dist/patches/cx-badge.js +115 -0
- package/dist/patches/cx-badge.js.map +1 -0
- package/dist/patches/cx-resume-commands.d.ts +14 -0
- package/dist/patches/cx-resume-commands.js +53 -0
- package/dist/patches/cx-resume-commands.js.map +1 -0
- package/dist/patches/disable-paste-collapse.d.ts +16 -0
- package/dist/patches/disable-paste-collapse.js +49 -0
- package/dist/patches/disable-paste-collapse.js.map +1 -0
- package/dist/patches/disable-telemetry.d.ts +13 -0
- package/dist/patches/disable-telemetry.js +76 -0
- package/dist/patches/disable-telemetry.js.map +1 -0
- package/{patches/index.js → dist/patches/index.d.ts} +4 -0
- package/dist/patches/index.js +22 -0
- package/dist/patches/index.js.map +1 -0
- package/dist/patches/no-attribution.d.ts +15 -0
- package/dist/patches/no-attribution.js +42 -0
- package/dist/patches/no-attribution.js.map +1 -0
- package/dist/patches/no-feedback.d.ts +12 -0
- package/dist/patches/no-feedback.js +31 -0
- package/dist/patches/no-feedback.js.map +1 -0
- package/dist/patches/no-npm-warning.d.ts +9 -0
- package/dist/patches/no-npm-warning.js +31 -0
- package/dist/patches/no-npm-warning.js.map +1 -0
- package/dist/patches/no-tips.d.ts +10 -0
- package/dist/patches/no-tips.js +26 -0
- package/dist/patches/no-tips.js.map +1 -0
- package/dist/patches/persist-max-effort.d.ts +15 -0
- package/dist/patches/persist-max-effort.js +75 -0
- package/dist/patches/persist-max-effort.js.map +1 -0
- package/dist/patches/queue.d.ts +10 -0
- package/dist/patches/queue.js +202 -0
- package/dist/patches/queue.js.map +1 -0
- package/dist/patches/random-clawd.d.ts +9 -0
- package/dist/patches/random-clawd.js +49 -0
- package/dist/patches/random-clawd.js.map +1 -0
- package/dist/patches/reload.d.ts +10 -0
- package/dist/patches/reload.js +50 -0
- package/dist/patches/reload.js.map +1 -0
- package/dist/patches/session-export.d.ts +16 -0
- package/dist/patches/session-export.js +93 -0
- package/dist/patches/session-export.js.map +1 -0
- package/dist/patches/session-timer.d.ts +18 -0
- package/dist/patches/session-timer.js +217 -0
- package/dist/patches/session-timer.js.map +1 -0
- package/dist/patches/show-file-in-collapsed-read.d.ts +17 -0
- package/dist/patches/show-file-in-collapsed-read.js +151 -0
- package/dist/patches/show-file-in-collapsed-read.js.map +1 -0
- package/dist/patches/simple-spinner.d.ts +12 -0
- package/dist/patches/simple-spinner.js +30 -0
- package/dist/patches/simple-spinner.js.map +1 -0
- package/dist/patches/swap-enter-submit.d.ts +26 -0
- package/dist/patches/swap-enter-submit.js +155 -0
- package/dist/patches/swap-enter-submit.js.map +1 -0
- package/dist/setup.d.ts +11 -0
- package/dist/setup.js +268 -0
- package/dist/setup.js.map +1 -0
- package/dist/transform-worker.d.ts +10 -0
- package/dist/transform-worker.js +35 -0
- package/dist/transform-worker.js.map +1 -0
- package/dist/transform.d.ts +12 -0
- package/dist/transform.js +83 -0
- package/dist/transform.js.map +1 -0
- package/dist/types.d.ts +105 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +16 -11
- package/ast.js +0 -276
- package/cx +0 -228
- package/cx-setup +0 -101
- package/patch-worker.js +0 -31
- package/patches/always-show-context.js +0 -123
- package/patches/always-show-thinking.js +0 -65
- package/patches/banner.js +0 -58
- package/patches/cd-command.js +0 -104
- package/patches/cx-badge.js +0 -112
- package/patches/cx-resume-commands.js +0 -58
- package/patches/disable-paste-collapse.js +0 -52
- package/patches/disable-telemetry.js +0 -84
- package/patches/no-attribution.js +0 -55
- package/patches/no-npm-warning.js +0 -32
- package/patches/no-tips.js +0 -29
- package/patches/persist-max-effort.js +0 -70
- package/patches/queue.js +0 -215
- package/patches/random-clawd.js +0 -52
- package/patches/reload.js +0 -68
- package/patches/show-file-in-collapsed-read.js +0 -178
- package/patches/swap-enter-submit.js +0 -188
- package/setup.js +0 -222
- package/transform-worker.js +0 -38
- package/transform.js +0 -99
package/cx-setup
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* cx-setup — Configure Claude Code Extensions
|
|
4
|
-
*
|
|
5
|
-
* Standalone tool for managing which patches cx applies.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* cx-setup Interactive TUI
|
|
9
|
-
* cx-setup list Show patch status
|
|
10
|
-
* cx-setup enable <id> Enable a patch
|
|
11
|
-
* cx-setup disable <id> Disable a patch
|
|
12
|
-
* cx-setup reset Re-enable all patches
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { existsSync, readFileSync, writeFileSync, rmSync } from 'fs';
|
|
16
|
-
import { resolve, dirname } from 'path';
|
|
17
|
-
import { fileURLToPath } from 'url';
|
|
18
|
-
import { listPatches } from './transform.js';
|
|
19
|
-
|
|
20
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
|
-
const CONFIG_PATH = resolve(__dirname, '.cx-patches.json');
|
|
22
|
-
const CACHE_DIR = resolve(__dirname, '.cache');
|
|
23
|
-
|
|
24
|
-
const DIM = '\x1b[2m', BOLD = '\x1b[1m', GREEN = '\x1b[32m', YELLOW = '\x1b[33m', RED = '\x1b[31m', RESET = '\x1b[0m';
|
|
25
|
-
|
|
26
|
-
// ── Config helpers ────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
function loadConfig() {
|
|
29
|
-
if (!existsSync(CONFIG_PATH)) return {};
|
|
30
|
-
try { return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')).patches || {}; } catch { return {}; }
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function saveConfig(patches) {
|
|
34
|
-
writeFileSync(CONFIG_PATH, JSON.stringify({ patches }, null, 2) + '\n');
|
|
35
|
-
invalidateCache();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function invalidateCache() {
|
|
39
|
-
try { rmSync(CACHE_DIR, { recursive: true, force: true }); } catch { /* ok */ }
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ── Commands ──────────────────────────────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
const cmd = process.argv[2];
|
|
45
|
-
const arg = process.argv[3];
|
|
46
|
-
|
|
47
|
-
if (!cmd) {
|
|
48
|
-
// Interactive TUI
|
|
49
|
-
const { default: setup } = await import('./setup.js');
|
|
50
|
-
setup();
|
|
51
|
-
|
|
52
|
-
} else if (cmd === 'list') {
|
|
53
|
-
const config = loadConfig();
|
|
54
|
-
const all = listPatches();
|
|
55
|
-
console.log(`\n ${BOLD}cx patches${RESET}\n`);
|
|
56
|
-
for (const p of all) {
|
|
57
|
-
const on = config[p.id] !== false;
|
|
58
|
-
const icon = on ? `${GREEN}✔${RESET}` : `${DIM}○${RESET}`;
|
|
59
|
-
console.log(` ${icon} ${p.id.padEnd(16)}${DIM}${p.description}${RESET}`);
|
|
60
|
-
}
|
|
61
|
-
console.log(`\n Run ${BOLD}cx-setup${RESET} to toggle interactively.\n`);
|
|
62
|
-
|
|
63
|
-
} else if (cmd === 'enable') {
|
|
64
|
-
if (!arg) { console.error('Usage: cx-setup enable <patch-id>'); process.exit(1); }
|
|
65
|
-
const all = listPatches();
|
|
66
|
-
if (!all.find(p => p.id === arg)) {
|
|
67
|
-
console.error(`Unknown patch: "${arg}". Available: ${all.map(p => p.id).join(', ')}`);
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
70
|
-
const config = loadConfig();
|
|
71
|
-
config[arg] = true;
|
|
72
|
-
saveConfig(config);
|
|
73
|
-
console.log(` ${GREEN}✔${RESET} ${BOLD}${arg}${RESET} enabled`);
|
|
74
|
-
|
|
75
|
-
} else if (cmd === 'disable') {
|
|
76
|
-
if (!arg) { console.error('Usage: cx-setup disable <patch-id>'); process.exit(1); }
|
|
77
|
-
const all = listPatches();
|
|
78
|
-
if (!all.find(p => p.id === arg)) {
|
|
79
|
-
console.error(`Unknown patch: "${arg}". Available: ${all.map(p => p.id).join(', ')}`);
|
|
80
|
-
process.exit(1);
|
|
81
|
-
}
|
|
82
|
-
const config = loadConfig();
|
|
83
|
-
config[arg] = false;
|
|
84
|
-
saveConfig(config);
|
|
85
|
-
console.log(` ${DIM}○${RESET} ${BOLD}${arg}${RESET} disabled`);
|
|
86
|
-
|
|
87
|
-
} else if (cmd === 'reset') {
|
|
88
|
-
try { rmSync(CONFIG_PATH, { force: true }); } catch { /* ok */ }
|
|
89
|
-
invalidateCache();
|
|
90
|
-
console.log(` ${GREEN}✔${RESET} Config reset — all patches enabled.`);
|
|
91
|
-
|
|
92
|
-
} else {
|
|
93
|
-
console.error(`Unknown command: ${cmd}`);
|
|
94
|
-
console.error(`\nUsage:`);
|
|
95
|
-
console.error(` cx-setup Interactive configurator`);
|
|
96
|
-
console.error(` cx-setup list Show patch status`);
|
|
97
|
-
console.error(` cx-setup enable <id> Enable a patch`);
|
|
98
|
-
console.error(` cx-setup disable <id> Disable a patch`);
|
|
99
|
-
console.error(` cx-setup reset Re-enable all patches`);
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
package/patch-worker.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Worker thread that runs a single patch and returns its edits.
|
|
3
|
-
* Receives: { source, patchId, patchesDir }
|
|
4
|
-
* Returns: { edits: [{pos, deleteCount, text}] } or { error: string }
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { workerData, parentPort } from 'worker_threads';
|
|
8
|
-
import * as acorn from 'acorn';
|
|
9
|
-
import { ASTIndex, buildContext } from './ast.js';
|
|
10
|
-
|
|
11
|
-
const { source, patchId, patchesDir } = workerData;
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const ast = acorn.parse(source, { ecmaVersion: 'latest', sourceType: 'module', allowHashBang: true });
|
|
15
|
-
const index = new ASTIndex(ast);
|
|
16
|
-
parentPort.postMessage({ type: 'ready' });
|
|
17
|
-
|
|
18
|
-
const edits = [];
|
|
19
|
-
const editor = {
|
|
20
|
-
insertAt(pos, text) { edits.push({ pos, deleteCount: 0, text }); },
|
|
21
|
-
replaceRange(start, end, text) { edits.push({ pos: start, deleteCount: end - start, text }); },
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const ctx = buildContext(source, index, editor);
|
|
25
|
-
const patchModule = await import(`${patchesDir}/${patchId}.js`);
|
|
26
|
-
patchModule.default.apply(ctx);
|
|
27
|
-
|
|
28
|
-
parentPort.postMessage({ type: 'done', edits });
|
|
29
|
-
} catch (err) {
|
|
30
|
-
parentPort.postMessage({ type: 'error', error: err.message });
|
|
31
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,65 +0,0 @@
|
|
|
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
|
-
};
|
package/patches/banner.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
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
|
-
};
|
package/patches/cd-command.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
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
|
-
};
|
package/patches/cx-badge.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,58 +0,0 @@
|
|
|
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
|
-
};
|