trackly-cli 0.4.0 → 0.4.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/bin/trackly CHANGED
@@ -295,13 +295,16 @@ async function cmdLogin() {
295
295
 
296
296
  console.log(color('cyan', 'Opening browser for Google login...'));
297
297
 
298
- function tryLogin(port) {
298
+ let activeServer = null;
299
+
300
+ function tryLogin() {
299
301
  return new Promise((resolve, reject) => {
300
302
  let settled = false;
301
303
  let timeoutId = null;
302
304
 
303
305
  const server = http.createServer((req, res) => {
304
- const url = new URL(req.url, `http://localhost:${port}`);
306
+ // Base is only used to parse req.url's path + query; the port is irrelevant.
307
+ const url = new URL(req.url, 'http://127.0.0.1');
305
308
  if (url.pathname === '/callback') {
306
309
  const returnedState = url.searchParams.get('state');
307
310
  if (returnedState !== state) {
@@ -387,15 +390,20 @@ p{color:#a1a1aa;font-size:15px;line-height:1.5}
387
390
  }
388
391
  };
389
392
 
393
+ activeServer = server;
394
+
390
395
  server.on('error', (err) => {
391
- if (err.code === 'EADDRINUSE') {
392
- reject(err);
393
- } else {
394
- settle(err);
395
- }
396
+ settle(err);
396
397
  });
397
398
 
398
- server.listen(port, '127.0.0.1', () => {
399
+ // Port 0 → OS-assigned ephemeral port, always free (no fixed-range collision,
400
+ // so no retry loop needed). Verify it lands in the backend's accepted range.
401
+ server.listen(0, '127.0.0.1', () => {
402
+ const port = server.address().port;
403
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) {
404
+ settle(new Error(`OS assigned an out-of-range callback port (${port}); expected 1024-65535.`));
405
+ return;
406
+ }
399
407
  console.log(color('dim', `Callback server on port ${port}`));
400
408
  const loginUrl = `${baseUrl}/auth/google/cli?port=${port}&state=${state}`;
401
409
  if (!openExternalUrl(loginUrl)) {
@@ -409,18 +417,20 @@ p{color:#a1a1aa;font-size:15px;line-height:1.5}
409
417
  });
410
418
  }
411
419
 
412
- const MAX_PORT_RETRIES = 3;
413
- for (let attempt = 0; attempt < MAX_PORT_RETRIES; attempt++) {
414
- const port = 19847 + Math.floor(Math.random() * 1000);
415
- try {
416
- return await tryLogin(port);
417
- } catch (err) {
418
- if (err.code === 'EADDRINUSE' && attempt < MAX_PORT_RETRIES - 1) {
419
- console.log(color('dim', `Port ${port} in use, trying another...`));
420
- continue;
421
- }
422
- throw err;
420
+ // One SIGINT handler for the whole login (not per-attempt): close the active
421
+ // callback server and exit cleanly on Ctrl-C, then always remove the handler so
422
+ // it can't leak. (No EADDRINUSE retry path now, so no handler-leak edge case.)
423
+ const onSigint = () => {
424
+ if (activeServer) {
425
+ try { activeServer.close(); } catch (_) {}
423
426
  }
427
+ process.exit(130);
428
+ };
429
+ process.on('SIGINT', onSigint);
430
+ try {
431
+ await tryLogin();
432
+ } finally {
433
+ process.removeListener('SIGINT', onSigint);
424
434
  }
425
435
  }
426
436
 
@@ -801,6 +811,15 @@ async function cmdConfig(args) {
801
811
  if (args['api-key'] === null) failNow('API key value required. Usage: trackly config --api-key <key>');
802
812
  if (args['base-url'] === null) failNow('Base URL value required. Usage: trackly config --base-url <url>');
803
813
 
814
+ // Reject contradictory set+clear in the same invocation rather than silently
815
+ // letting last-write-win produce surprising results.
816
+ if (args['api-key'] !== undefined && args['clear-api-key']) {
817
+ failNow('Cannot use --api-key and --clear-api-key together.');
818
+ }
819
+ if (args['base-url'] !== undefined && args['clear-base-url']) {
820
+ failNow('Cannot use --base-url and --clear-base-url together.');
821
+ }
822
+
804
823
  const config = loadConfig();
805
824
  let updated = false;
806
825
 
package/lib/formatters.js CHANGED
@@ -6,7 +6,19 @@ const c = {
6
6
  magenta: '\x1b[35m', cyan: '\x1b[36m', red: '\x1b[31m',
7
7
  };
8
8
 
9
- function color(name, text) {
9
+ // Decide whether to emit ANSI color for a given output stream.
10
+ // Precedence (no-color.org + the common FORCE_COLOR convention):
11
+ // NO_COLOR present and non-empty → never color
12
+ // FORCE_COLOR set and not "0"/"false" → always color
13
+ // otherwise → only when the stream is a TTY
14
+ function shouldColor(stream = process.stdout) {
15
+ if ('NO_COLOR' in process.env && process.env.NO_COLOR !== '') return false;
16
+ if (process.env.FORCE_COLOR && !['0', 'false'].includes(process.env.FORCE_COLOR)) return true;
17
+ return Boolean(stream && stream.isTTY);
18
+ }
19
+
20
+ function color(name, text, stream = process.stdout) {
21
+ if (!shouldColor(stream)) return String(text);
10
22
  return `${c[name] || ''}${text}${c.reset}`;
11
23
  }
12
24
 
@@ -314,7 +326,8 @@ function padRight(str, len) {
314
326
  }
315
327
 
316
328
  function outputError(msg) {
317
- console.error(color('red', `Error: ${msg}`));
329
+ // Errors go to stderr, so gate their color on stderr's TTY-ness, not stdout's.
330
+ console.error(color('red', `Error: ${msg}`, process.stderr));
318
331
  }
319
332
 
320
- module.exports = { color, isJSON, outputJobs, outputCompanies, outputStats, outputContacts, outputReferralCampaign, outputNetworkBrief, outputCompanyBrief, outputCompanyWorkspace, outputError };
333
+ module.exports = { color, shouldColor, isJSON, outputJobs, outputCompanies, outputStats, outputContacts, outputReferralCampaign, outputNetworkBrief, outputCompanyBrief, outputCompanyWorkspace, outputError };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackly-cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "mcpName": "io.github.trackly-app/trackly",
5
5
  "description": "AI job search CLI + hosted MCP server with OAuth. 128K+ jobs, 1,900+ companies, 40+ ATS. Works with Claude, ChatGPT, Cursor, Windsurf, Codex via hosted streamable-http or local stdio.",
6
6
  "bin": {
package/server.json CHANGED
@@ -17,12 +17,12 @@
17
17
  ]
18
18
  }
19
19
  ],
20
- "version": "0.4.0",
20
+ "version": "0.4.1",
21
21
  "packages": [
22
22
  {
23
23
  "registryType": "npm",
24
24
  "identifier": "trackly-cli",
25
- "version": "0.4.0",
25
+ "version": "0.4.1",
26
26
  "runtimeHint": "npx",
27
27
  "runtimeArguments": [
28
28
  {