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.
Files changed (2) hide show
  1. package/bin/refresh-cv.js +118 -16
  2. 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.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
- console.log(
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
- console.log(
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
- console.log(
110
- `Codex started the OAuth login flow for "${serverName}". Finish the browser approval if prompted.`,
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
- console.log(`Opening Codex OAuth login for "${serverName}"...`);
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
- console.log(`Adding refresh.cv MCP to Codex as "${serverName}"...`);
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
- process.stdout.write(text);
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
- process.stderr.write(text);
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
- console.log(`Configured refresh.cv MCP for Codex as "${serverName}".`);
449
+ logOk(`Configured "${serverName}" in Codex.`);
348
450
  }
349
451
 
350
452
  function removeCodexMcpSection(config, serverName) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "refresh-cv",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Install refresh.cv MCP connections for supported agent clients.",
5
5
  "type": "module",
6
6
  "bin": {