trackly-cli 0.4.0 → 0.4.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/trackly +38 -19
- package/lib/formatters.js +16 -3
- package/package.json +1 -1
- package/server.json +2 -2
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
392
|
-
reject(err);
|
|
393
|
-
} else {
|
|
394
|
-
settle(err);
|
|
395
|
-
}
|
|
396
|
+
settle(err);
|
|
396
397
|
});
|
|
397
398
|
|
|
398
|
-
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.4.2",
|
|
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.
|
|
20
|
+
"version": "0.4.2",
|
|
21
21
|
"packages": [
|
|
22
22
|
{
|
|
23
23
|
"registryType": "npm",
|
|
24
24
|
"identifier": "trackly-cli",
|
|
25
|
-
"version": "0.4.
|
|
25
|
+
"version": "0.4.2",
|
|
26
26
|
"runtimeHint": "npx",
|
|
27
27
|
"runtimeArguments": [
|
|
28
28
|
{
|