@16pxh/cli-bridge 1.0.2 → 1.0.4
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/lib/claude.mjs +2 -1
- package/lib/server.mjs +57 -8
- package/package.json +1 -1
package/lib/claude.mjs
CHANGED
|
@@ -25,9 +25,10 @@ export async function checkClaudeInstalled() {
|
|
|
25
25
|
* @param {{ timeoutMs?: number }} [options]
|
|
26
26
|
* @returns {Promise<{ result: string, session_id: string, duration_ms: number }>}
|
|
27
27
|
*/
|
|
28
|
-
export function runPrompt(prompt, { timeoutMs = 60000 } = {}) {
|
|
28
|
+
export function runPrompt(prompt, { timeoutMs = 60000, sessionId } = {}) {
|
|
29
29
|
return new Promise((resolve, reject) => {
|
|
30
30
|
const args = ['-p', prompt, '--output-format', 'json'];
|
|
31
|
+
if (sessionId) args.push('--resume', sessionId);
|
|
31
32
|
const child = spawn('claude', args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
32
33
|
|
|
33
34
|
activeProcess = child;
|
package/lib/server.mjs
CHANGED
|
@@ -5,6 +5,40 @@ import { checkClaudeInstalled, runPrompt } from './claude.mjs';
|
|
|
5
5
|
const PORT = 1676;
|
|
6
6
|
const HOST = '127.0.0.1';
|
|
7
7
|
|
|
8
|
+
// ─── Colored logging ─────────────────────────────────────────────────────────
|
|
9
|
+
const c = {
|
|
10
|
+
reset: '\x1b[0m',
|
|
11
|
+
dim: '\x1b[2m',
|
|
12
|
+
green: '\x1b[32m',
|
|
13
|
+
yellow: '\x1b[33m',
|
|
14
|
+
red: '\x1b[31m',
|
|
15
|
+
cyan: '\x1b[36m',
|
|
16
|
+
magenta: '\x1b[35m',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function truncateWords(text, maxWords = 16) {
|
|
20
|
+
const words = text.replace(/\s+/g, ' ').trim().split(' ');
|
|
21
|
+
if (words.length <= maxWords) return words.join(' ');
|
|
22
|
+
return words.slice(0, maxWords).join(' ') + '…';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function truncateChars(text, maxChars = 64) {
|
|
26
|
+
const clean = text.replace(/\s+/g, ' ').trim();
|
|
27
|
+
if (clean.length <= maxChars) return clean;
|
|
28
|
+
return clean.slice(0, maxChars) + '…';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function timestamp() {
|
|
32
|
+
return new Date().toLocaleTimeString('en-US', { hour12: false });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function logRequest(method, path, status, extra = '') {
|
|
36
|
+
const statusColor = status < 400 ? c.green : status < 500 ? c.yellow : c.red;
|
|
37
|
+
console.log(
|
|
38
|
+
`${c.dim}${timestamp()}${c.reset} ${c.cyan}${method}${c.reset} ${path} ${statusColor}${status}${c.reset}${extra ? ' ' + extra : ''}`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
8
42
|
const CORS_HEADERS = {
|
|
9
43
|
'Access-Control-Allow-Origin': '*',
|
|
10
44
|
'Access-Control-Allow-Headers': 'x-bridge-token, content-type',
|
|
@@ -78,9 +112,12 @@ export function startServer() {
|
|
|
78
112
|
|
|
79
113
|
// GET /health
|
|
80
114
|
if (req.method === 'GET' && url.pathname === '/health') {
|
|
81
|
-
if (!authenticate(req, res))
|
|
82
|
-
|
|
115
|
+
if (!authenticate(req, res)) {
|
|
116
|
+
logRequest('GET', '/health', 403);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
83
119
|
const version = await checkClaudeInstalled();
|
|
120
|
+
logRequest('GET', '/health', 200, `${c.dim}cli=${version ? 'ok' : 'missing'}${c.reset}`);
|
|
84
121
|
json(res, 200, {
|
|
85
122
|
status: 'ok',
|
|
86
123
|
claude_cli: version !== null,
|
|
@@ -91,34 +128,46 @@ export function startServer() {
|
|
|
91
128
|
|
|
92
129
|
// POST /prompt
|
|
93
130
|
if (req.method === 'POST' && url.pathname === '/prompt') {
|
|
94
|
-
if (!authenticate(req, res))
|
|
131
|
+
if (!authenticate(req, res)) {
|
|
132
|
+
logRequest('POST', '/prompt', 403);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
95
135
|
|
|
96
136
|
let body;
|
|
97
137
|
try {
|
|
98
138
|
const raw = await readBody(req);
|
|
99
139
|
body = JSON.parse(raw);
|
|
100
140
|
} catch {
|
|
141
|
+
logRequest('POST', '/prompt', 400, `${c.red}invalid JSON${c.reset}`);
|
|
101
142
|
json(res, 400, { error: 'Invalid JSON body' });
|
|
102
143
|
return;
|
|
103
144
|
}
|
|
104
145
|
|
|
105
|
-
const { prompt, images } = body;
|
|
146
|
+
const { prompt, images, session_id } = body;
|
|
106
147
|
if (!prompt || typeof prompt !== 'string') {
|
|
148
|
+
logRequest('POST', '/prompt', 400, `${c.red}missing prompt${c.reset}`);
|
|
107
149
|
json(res, 400, { error: 'Missing or invalid "prompt" field' });
|
|
108
150
|
return;
|
|
109
151
|
}
|
|
110
152
|
|
|
153
|
+
const promptPreview = truncateWords(prompt, 16);
|
|
154
|
+
console.log(`${c.dim}${timestamp()}${c.reset} ${c.magenta}→${c.reset} ${c.dim}${promptPreview}${c.reset}`);
|
|
155
|
+
|
|
111
156
|
let fullPrompt = prompt;
|
|
112
157
|
if (Array.isArray(images) && images.length > 0) {
|
|
113
|
-
for (const
|
|
114
|
-
fullPrompt += `\n\nAnalyze this image: ${
|
|
158
|
+
for (const imgUrl of images) {
|
|
159
|
+
fullPrompt += `\n\nAnalyze this image: ${imgUrl}`;
|
|
115
160
|
}
|
|
116
161
|
}
|
|
117
162
|
|
|
118
163
|
try {
|
|
119
|
-
const { result, session_id, duration_ms } = await runPrompt(fullPrompt);
|
|
120
|
-
|
|
164
|
+
const { result, session_id: responseSessionId, duration_ms } = await runPrompt(fullPrompt, { sessionId: session_id });
|
|
165
|
+
const resultPreview = truncateChars(result, 64);
|
|
166
|
+
logRequest('POST', '/prompt', 200, `${c.dim}${duration_ms}ms${c.reset}`);
|
|
167
|
+
console.log(`${c.dim}${timestamp()}${c.reset} ${c.green}←${c.reset} ${c.dim}${resultPreview}${c.reset}`);
|
|
168
|
+
json(res, 200, { success: true, result, session_id: responseSessionId, duration_ms });
|
|
121
169
|
} catch (err) {
|
|
170
|
+
logRequest('POST', '/prompt', 500, `${c.red}${err.message}${c.reset}`);
|
|
122
171
|
json(res, 500, { error: err.message });
|
|
123
172
|
}
|
|
124
173
|
return;
|