refresh-cv 0.1.1 → 0.1.2
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/refresh-cv.js +118 -16
- package/package.json +1 -1
package/bin/refresh-cv.js
CHANGED
|
@@ -4,7 +4,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
|
|
7
|
-
const PACKAGE_VERSION = '0.1.
|
|
7
|
+
const PACKAGE_VERSION = '0.1.2';
|
|
8
8
|
const PRODUCTION_MCP_URL = 'https://mcp.refresh.cv/mcp';
|
|
9
9
|
const LOCAL_MCP_URL = 'http://localhost:8010/mcp';
|
|
10
10
|
const DEFAULT_SERVER_NAME = 'refresh_cv';
|
|
@@ -72,16 +72,17 @@ async function main() {
|
|
|
72
72
|
assertValidMcpUrl(mcpUrl);
|
|
73
73
|
assertCommandAvailable('codex');
|
|
74
74
|
|
|
75
|
+
printHeader({ mcpUrl, options, serverName });
|
|
76
|
+
|
|
75
77
|
const existing = getCodexMcp(serverName);
|
|
76
78
|
if (existing.exists) {
|
|
77
79
|
const existingUrl = existing.url;
|
|
78
80
|
if (existingUrl === mcpUrl) {
|
|
79
|
-
|
|
80
|
-
`refresh.cv MCP is already configured for Codex as "${serverName}".`,
|
|
81
|
-
);
|
|
81
|
+
logOk(`Codex already has "${serverName}" configured.`);
|
|
82
82
|
} else if (options.force && options.skipLogin) {
|
|
83
83
|
writeCodexMcpConfig(serverName, mcpUrl);
|
|
84
84
|
} else if (options.force) {
|
|
85
|
+
logStep(`Replacing existing Codex MCP "${serverName}"...`);
|
|
85
86
|
runCodex(['mcp', 'remove', serverName], {
|
|
86
87
|
errorMessage: `Failed to remove existing Codex MCP server "${serverName}".`,
|
|
87
88
|
});
|
|
@@ -99,23 +100,22 @@ async function main() {
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
if (options.skipLogin) {
|
|
102
|
-
|
|
103
|
-
`Skipped login. Run this when ready: codex mcp login ${serverName}`,
|
|
104
|
-
);
|
|
103
|
+
logMuted(`Login skipped. Run when ready: codex mcp login ${serverName}`);
|
|
105
104
|
return;
|
|
106
105
|
}
|
|
107
106
|
|
|
108
107
|
if (options.loginStartedDuringAdd) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
);
|
|
108
|
+
logStep('Codex started OAuth during setup.');
|
|
109
|
+
logMuted('Approve the browser prompt to finish connecting refresh.cv.');
|
|
112
110
|
return;
|
|
113
111
|
}
|
|
114
112
|
|
|
115
|
-
|
|
113
|
+
logStep('Opening refresh.cv sign-in through Codex...');
|
|
116
114
|
await runCodexStreaming(['mcp', 'login', serverName], {
|
|
117
115
|
errorMessage: `Codex login did not complete. Retry with: codex mcp login ${serverName}`,
|
|
116
|
+
mode: 'codex-login',
|
|
118
117
|
});
|
|
118
|
+
logOk('Done. refresh.cv is ready in Codex.');
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
function parseArgs(args) {
|
|
@@ -268,11 +268,12 @@ function getCodexMcp(serverName) {
|
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
async function addCodexMcp(serverName, mcpUrl, options) {
|
|
271
|
-
|
|
271
|
+
logStep(`Adding refresh.cv MCP to Codex as "${serverName}"...`);
|
|
272
272
|
const result = await runCodexStreaming(
|
|
273
273
|
['mcp', 'add', serverName, '--url', mcpUrl],
|
|
274
274
|
{
|
|
275
275
|
errorMessage: 'Failed to add refresh.cv MCP to Codex.',
|
|
276
|
+
mode: 'codex-login',
|
|
276
277
|
},
|
|
277
278
|
);
|
|
278
279
|
|
|
@@ -291,20 +292,29 @@ function runCodex(args, { errorMessage }) {
|
|
|
291
292
|
}
|
|
292
293
|
}
|
|
293
294
|
|
|
294
|
-
function runCodexStreaming(args, { errorMessage }) {
|
|
295
|
+
function runCodexStreaming(args, { errorMessage, mode = 'raw' }) {
|
|
295
296
|
return new Promise((resolve) => {
|
|
296
297
|
const child = spawn('codex', args, { stdio: ['inherit', 'pipe', 'pipe'] });
|
|
298
|
+
const printer = mode === 'codex-login' ? createCodexLoginPrinter() : null;
|
|
297
299
|
let output = '';
|
|
298
300
|
|
|
299
301
|
child.stdout.on('data', (chunk) => {
|
|
300
302
|
const text = chunk.toString();
|
|
301
303
|
output += text;
|
|
302
|
-
|
|
304
|
+
if (printer) {
|
|
305
|
+
printer.write(text, process.stdout);
|
|
306
|
+
} else {
|
|
307
|
+
process.stdout.write(text);
|
|
308
|
+
}
|
|
303
309
|
});
|
|
304
310
|
child.stderr.on('data', (chunk) => {
|
|
305
311
|
const text = chunk.toString();
|
|
306
312
|
output += text;
|
|
307
|
-
|
|
313
|
+
if (printer) {
|
|
314
|
+
printer.write(text, process.stderr);
|
|
315
|
+
} else {
|
|
316
|
+
process.stderr.write(text);
|
|
317
|
+
}
|
|
308
318
|
});
|
|
309
319
|
child.on('error', (error) => {
|
|
310
320
|
if (error.code === 'ENOENT') {
|
|
@@ -313,6 +323,7 @@ function runCodexStreaming(args, { errorMessage }) {
|
|
|
313
323
|
fail(`${errorMessage} ${error.message}`);
|
|
314
324
|
});
|
|
315
325
|
child.on('close', (status) => {
|
|
326
|
+
printer?.flush();
|
|
316
327
|
if (status !== 0) {
|
|
317
328
|
fail(errorMessage);
|
|
318
329
|
}
|
|
@@ -321,6 +332,58 @@ function runCodexStreaming(args, { errorMessage }) {
|
|
|
321
332
|
});
|
|
322
333
|
}
|
|
323
334
|
|
|
335
|
+
function createCodexLoginPrinter() {
|
|
336
|
+
let pending = '';
|
|
337
|
+
let waitingForUrl = false;
|
|
338
|
+
let printedUrl = false;
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
write(text, stream) {
|
|
342
|
+
pending += text;
|
|
343
|
+
const lines = pending.split(/\r?\n/);
|
|
344
|
+
pending = lines.pop() ?? '';
|
|
345
|
+
for (const line of lines) {
|
|
346
|
+
handleCodexLoginLine(line, stream);
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
flush() {
|
|
350
|
+
if (pending) {
|
|
351
|
+
handleCodexLoginLine(pending, process.stdout);
|
|
352
|
+
pending = '';
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
function handleCodexLoginLine(rawLine, stream) {
|
|
358
|
+
const line = rawLine.trim();
|
|
359
|
+
if (!line) return;
|
|
360
|
+
|
|
361
|
+
if (/^Authorize `[^`]+` by opening this URL in your browser:?$/.test(line)) {
|
|
362
|
+
waitingForUrl = true;
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if ((waitingForUrl || line.includes('/oauth/authorize')) && isUrl(line)) {
|
|
367
|
+
printAuthorizeUrl(line);
|
|
368
|
+
waitingForUrl = false;
|
|
369
|
+
printedUrl = true;
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (line.startsWith('http') && isUrl(line)) {
|
|
374
|
+
printAuthorizeUrl(line);
|
|
375
|
+
printedUrl = true;
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (printedUrl && /authorize|opening this url/i.test(line)) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
stream.write(` ${line}\n`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
324
387
|
function codexAddStartedOAuth(output) {
|
|
325
388
|
return (
|
|
326
389
|
output.includes('Detected OAuth support') ||
|
|
@@ -329,6 +392,45 @@ function codexAddStartedOAuth(output) {
|
|
|
329
392
|
);
|
|
330
393
|
}
|
|
331
394
|
|
|
395
|
+
function printHeader({ mcpUrl, options, serverName }) {
|
|
396
|
+
console.log('refresh.cv MCP');
|
|
397
|
+
console.log('');
|
|
398
|
+
console.log(' Client Codex');
|
|
399
|
+
console.log(` Profile ${serverName}`);
|
|
400
|
+
console.log(` Endpoint ${mcpUrl}`);
|
|
401
|
+
console.log(` Mode ${options.local ? 'local' : 'production'}`);
|
|
402
|
+
console.log('');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function logStep(message) {
|
|
406
|
+
console.log(`[..] ${message}`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function logOk(message) {
|
|
410
|
+
console.log(`[ok] ${message}`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function logMuted(message) {
|
|
414
|
+
console.log(` ${message}`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function printAuthorizeUrl(url) {
|
|
418
|
+
console.log('');
|
|
419
|
+
console.log('Open this link to approve refresh.cv:');
|
|
420
|
+
console.log(` ${url}`);
|
|
421
|
+
console.log('');
|
|
422
|
+
console.log('Waiting for Codex to finish the login...');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function isUrl(value) {
|
|
426
|
+
try {
|
|
427
|
+
new URL(value);
|
|
428
|
+
return true;
|
|
429
|
+
} catch {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
332
434
|
function writeCodexMcpConfig(serverName, mcpUrl) {
|
|
333
435
|
const codexHome = process.env.CODEX_HOME || join(homedir(), '.codex');
|
|
334
436
|
const configPath = join(codexHome, 'config.toml');
|
|
@@ -344,7 +446,7 @@ url = ${tomlString(mcpUrl)}
|
|
|
344
446
|
`;
|
|
345
447
|
|
|
346
448
|
writeFileSync(configPath, next.trimStart());
|
|
347
|
-
|
|
449
|
+
logOk(`Configured "${serverName}" in Codex.`);
|
|
348
450
|
}
|
|
349
451
|
|
|
350
452
|
function removeCodexMcpSection(config, serverName) {
|