ccx-relay 2.3.0 → 2.4.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.
- package/bin/ccx.js +63 -57
- package/package.json +1 -1
package/bin/ccx.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
import { load } from '../src/config.js';
|
|
5
5
|
import { enhance, RateLimitError, AuthError, ModelError, TimeoutError, NetworkError } from '../src/gemini.js';
|
|
6
|
-
import { start as spinnerStart, stop as spinnerStop } from '../src/ui.js';
|
|
7
|
-
import {
|
|
6
|
+
import { start as spinnerStart, stop as spinnerStop, clear as spinnerClear } from '../src/ui.js';
|
|
7
|
+
import { createInputHandler } from '../src/input.js';
|
|
8
8
|
|
|
9
9
|
const argv = process.argv.slice(2);
|
|
10
10
|
|
|
@@ -42,8 +42,8 @@ if (argv[0] === 'completion') {
|
|
|
42
42
|
process.exit(1);
|
|
43
43
|
}
|
|
44
44
|
process.stdout.write(scripts[shell] + '\n');
|
|
45
|
-
if (shell === 'bash')
|
|
46
|
-
else if (shell === 'zsh')
|
|
45
|
+
if (shell === 'bash') process.stdout.write('# eval "$(ccx completion bash)"\n');
|
|
46
|
+
else if (shell === 'zsh') process.stdout.write('# eval "$(ccx completion zsh)"\n');
|
|
47
47
|
else if (shell === 'powershell') process.stdout.write('# Invoke-Expression (ccx completion powershell)\n');
|
|
48
48
|
process.exit(0);
|
|
49
49
|
}
|
|
@@ -111,68 +111,74 @@ process.stdout.on('resize', () => {
|
|
|
111
111
|
ptyProcess.resize(process.stdout.columns, process.stdout.rows);
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
-
// ──
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
114
|
+
// ── Erase current input (single or multi-line) ────────────────────────────────
|
|
115
|
+
function eraseInput(line, cursor) {
|
|
116
|
+
const parts = line.split('\n');
|
|
117
|
+
const extra = parts.length - 1;
|
|
118
|
+
if (extra === 0) {
|
|
119
|
+
const after = line.length - cursor;
|
|
120
|
+
if (after > 0) ptyProcess.write(`\x1b[${after}C`);
|
|
121
|
+
ptyProcess.write(Buffer.alloc(line.length, 0x7f));
|
|
122
|
+
} else {
|
|
123
|
+
ptyProcess.write('\x1b[2K');
|
|
124
|
+
for (let i = 0; i < extra; i++) ptyProcess.write('\x1b[1A\x1b[2K');
|
|
125
|
+
ptyProcess.write('\x1b[G');
|
|
126
126
|
}
|
|
127
|
-
return false;
|
|
128
127
|
}
|
|
129
128
|
|
|
130
|
-
// ──
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
let
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
129
|
+
// ── Input handler ──────────────────────────────────────────────────────────────
|
|
130
|
+
process.stdin.setRawMode(true);
|
|
131
|
+
process.stdin.resume();
|
|
132
|
+
|
|
133
|
+
const handler = createInputHandler({
|
|
134
|
+
marker: config.marker,
|
|
135
|
+
|
|
136
|
+
onEnhance: async (line, cursor) => {
|
|
137
|
+
const toSend = line.endsWith(config.marker)
|
|
138
|
+
? line.slice(0, -config.marker.length)
|
|
139
|
+
: line;
|
|
140
|
+
|
|
141
|
+
handler.setBusy(true);
|
|
142
|
+
spinnerStart();
|
|
143
|
+
|
|
144
|
+
let improved;
|
|
145
|
+
try {
|
|
146
|
+
const context = contextLines.length > 0 ? contextLines.join('\n') : null;
|
|
147
|
+
improved = await enhance(toSend, config, context);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
let msg = 'Enhancement failed';
|
|
150
|
+
if (err instanceof RateLimitError) msg = 'Quota exceeded';
|
|
151
|
+
else if (err instanceof AuthError) msg = 'Invalid key — run ccx-init';
|
|
152
|
+
else if (err instanceof ModelError) msg = 'Model not found — run ccx-init';
|
|
153
|
+
else if (err instanceof TimeoutError) msg = `Timed out after ${config.timeoutSeconds}s — original restored`;
|
|
154
|
+
else if (err instanceof NetworkError) msg = 'No connection — original restored';
|
|
155
|
+
await spinnerStop('error', msg);
|
|
156
|
+
eraseInput(line, cursor);
|
|
157
|
+
ptyProcess.write(toSend);
|
|
158
|
+
handler.setBusy(false);
|
|
159
|
+
handler.setLine(toSend);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
155
162
|
|
|
156
|
-
|
|
163
|
+
await spinnerStop('success', 'Enhanced');
|
|
164
|
+
eraseInput(line, cursor);
|
|
165
|
+
ptyProcess.write(improved);
|
|
166
|
+
handler.setBusy(false);
|
|
167
|
+
handler.setLine(improved);
|
|
168
|
+
},
|
|
157
169
|
|
|
158
|
-
|
|
159
|
-
ptyProcess.write('\x15'); // Ctrl+U = kill line (clears Claude Code's current input)
|
|
160
|
-
// Small pause so Claude Code processes the Ctrl+U before we type the new text
|
|
161
|
-
await new Promise(r => setTimeout(r, 30));
|
|
162
|
-
ptyProcess.write(improved);
|
|
163
|
-
busy = false;
|
|
164
|
-
}
|
|
170
|
+
onSubmit: () => {},
|
|
165
171
|
|
|
166
|
-
|
|
167
|
-
process.stdin.setRawMode(true);
|
|
168
|
-
process.stdin.resume();
|
|
172
|
+
onPassthrough: (chunk) => ptyProcess.write(chunk),
|
|
169
173
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
+
onCtrlC: () => {
|
|
175
|
+
spinnerClear();
|
|
176
|
+
handler.reset();
|
|
177
|
+
},
|
|
174
178
|
});
|
|
175
179
|
|
|
180
|
+
process.stdin.on('data', chunk => handler.processChunk(chunk));
|
|
181
|
+
|
|
176
182
|
// ── Cleanup ────────────────────────────────────────────────────────────────────
|
|
177
183
|
function cleanupAndExit(code) {
|
|
178
184
|
try { if (process.stdin.isTTY) process.stdin.setRawMode(false); } catch (_) {}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "ccx-relay",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.4.0",
|
|
5
5
|
"description": "Transparent PTY wrapper for Claude Code (or any CLI) that lets you refine your prompt with Gemini AI before submitting it, without leaving your terminal session.",
|
|
6
6
|
"main": "bin/ccx.js",
|
|
7
7
|
"bin": {
|