trackly-cli 0.15.0 → 0.16.0
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/README.md +2 -1
- package/bin/trackly +41 -3
- package/docs/trackly-tools.md +1 -0
- package/mcp/analytics.js +3 -3
- package/mcp/server.js +19 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ trackly jobs --region us # Filter by region (us, non_us, all, or a
|
|
|
46
46
|
trackly jobs --job-type internship # Filter by employment type (full_time, internship, all)
|
|
47
47
|
trackly jobs --work-arrangement hybrid,remote # Filter by work arrangement independently
|
|
48
48
|
trackly jobs --function product # Filter by function
|
|
49
|
+
trackly jobs --sponsorship exclude_no # Visa sponsorship filter (all, exclude_no, only_yes)
|
|
49
50
|
trackly jobs --company 243 # Filter by company ID
|
|
50
51
|
trackly job 1234 # Get job details
|
|
51
52
|
trackly jobs 1234 # Alias for job details
|
|
@@ -161,7 +162,7 @@ Then use natural language in any of these clients:
|
|
|
161
162
|
|
|
162
163
|
| Tool | Description |
|
|
163
164
|
|------|-------------|
|
|
164
|
-
| trackly_search_jobs | Search and filter jobs by function, company, region, employment type, work arrangement, and status |
|
|
165
|
+
| trackly_search_jobs | Search and filter jobs by function, company, region, employment type, work arrangement, visa sponsorship, and status |
|
|
165
166
|
| trackly_get_job | Get full details for a specific job |
|
|
166
167
|
| trackly_search_companies | Semantic company search |
|
|
167
168
|
| trackly_list_companies | List all tracked companies |
|
package/bin/trackly
CHANGED
|
@@ -39,6 +39,7 @@ const VALUE_FLAGS = new Set([
|
|
|
39
39
|
'region',
|
|
40
40
|
'search',
|
|
41
41
|
'sort',
|
|
42
|
+
'sponsorship',
|
|
42
43
|
'status',
|
|
43
44
|
'url',
|
|
44
45
|
'window-days',
|
|
@@ -62,7 +63,7 @@ const COMMAND_FLAGS = {
|
|
|
62
63
|
login: [],
|
|
63
64
|
logout: [],
|
|
64
65
|
whoami: [],
|
|
65
|
-
jobs: ['function', 'company', 'region', 'job-type', 'work-arrangement', 'remote', 'status', 'sort', 'limit', 'offset', 'keywords', 'location', 'modality'],
|
|
66
|
+
jobs: ['function', 'company', 'region', 'job-type', 'work-arrangement', 'remote', 'status', 'sort', 'sponsorship', 'limit', 'offset', 'keywords', 'location', 'modality'],
|
|
66
67
|
job: [],
|
|
67
68
|
companies: ['limit', 'offset'],
|
|
68
69
|
search: [],
|
|
@@ -94,6 +95,15 @@ const STATUS_LABELS = {
|
|
|
94
95
|
dismissed: 'dismissed',
|
|
95
96
|
};
|
|
96
97
|
const WORK_ARRANGEMENT_VALUES = ['remote', 'hybrid', 'in_person', 'unspecified'];
|
|
98
|
+
// Discovery-only visa sponsorship filter (close-ai PR #1578). Mirrors the
|
|
99
|
+
// hosted MCP enum exactly. Absent or 'all' applies no filter.
|
|
100
|
+
const SPONSORSHIP_VALUES = ['all', 'exclude_no', 'only_yes'];
|
|
101
|
+
// Job-detail `sponsorshipStatus` values → user-facing copy. Anything else
|
|
102
|
+
// (including null/'unknown') reads "Not mentioned" — never "unknown".
|
|
103
|
+
const SPONSORSHIP_STATUS_LABELS = {
|
|
104
|
+
yes: 'Sponsors visa',
|
|
105
|
+
no: 'No sponsorship',
|
|
106
|
+
};
|
|
97
107
|
const JOB_FUNCTIONS = [
|
|
98
108
|
'product', 'engineering', 'design', 'data', 'marketing', 'sales', 'partnerships',
|
|
99
109
|
'finance', 'strategy', 'operations', 'people', 'legal', 'support', 'other',
|
|
@@ -324,7 +334,7 @@ function hasJobListFilters(args) {
|
|
|
324
334
|
// (which then raises the migration error) instead of cmdJob(42) silently ignoring
|
|
325
335
|
// the filter. See PR #13 Cursor Bugbot finding.
|
|
326
336
|
const deprecated = Object.keys(DEPRECATED_JOB_FLAGS);
|
|
327
|
-
const active = ['function', 'company', 'keywords', 'limit', 'region', 'job-type', 'work-arrangement', 'remote', 'offset', 'sort', 'status'];
|
|
337
|
+
const active = ['function', 'company', 'keywords', 'limit', 'region', 'job-type', 'work-arrangement', 'remote', 'offset', 'sort', 'sponsorship', 'status'];
|
|
328
338
|
return [...active, ...deprecated].some((key) => args[key] !== undefined);
|
|
329
339
|
}
|
|
330
340
|
|
|
@@ -598,6 +608,17 @@ async function cmdJobs(args) {
|
|
|
598
608
|
}
|
|
599
609
|
}
|
|
600
610
|
|
|
611
|
+
let sponsorship;
|
|
612
|
+
if (args.sponsorship !== undefined) {
|
|
613
|
+
if (typeof args.sponsorship !== 'string' || !args.sponsorship.trim()) {
|
|
614
|
+
failNow(`--sponsorship requires a value. Use: ${SPONSORSHIP_VALUES.join(', ')}.`);
|
|
615
|
+
}
|
|
616
|
+
sponsorship = args.sponsorship.trim();
|
|
617
|
+
if (!SPONSORSHIP_VALUES.includes(sponsorship)) {
|
|
618
|
+
failNow(`Invalid --sponsorship value "${sponsorship}". Use: ${SPONSORSHIP_VALUES.join(', ')}.`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
601
622
|
const params = new URLSearchParams();
|
|
602
623
|
if (args.function) params.set('jobFunction', args.function);
|
|
603
624
|
if (args.company) params.set('companyId', args.company);
|
|
@@ -607,6 +628,13 @@ async function cmdJobs(args) {
|
|
|
607
628
|
if (args.remote) params.set('usStates', 'REMOTE');
|
|
608
629
|
if (args.status) params.set('status', args.status);
|
|
609
630
|
if (args.sort) params.set('sort', args.sort);
|
|
631
|
+
if (sponsorship) {
|
|
632
|
+
params.set('sponsorship', sponsorship);
|
|
633
|
+
// The backend honors the filter only on the literal status=new discovery
|
|
634
|
+
// view — without this default, `--sponsorship exclude_no` alone would be
|
|
635
|
+
// a silent no-op. Mirrors the MCP server's behavior.
|
|
636
|
+
if (sponsorship !== 'all' && !args.status) params.set('status', 'new');
|
|
637
|
+
}
|
|
610
638
|
if (args.limit) params.set('limit', args.limit);
|
|
611
639
|
if (args.offset) params.set('offset', args.offset);
|
|
612
640
|
if (args.keywords) params.set('search', args.keywords);
|
|
@@ -638,6 +666,12 @@ async function cmdJob(args) {
|
|
|
638
666
|
if (job.location) console.log(`Location: ${job.location}`);
|
|
639
667
|
if (job.jobUrl) console.log(`URL: ${color('dim', job.jobUrl)}`);
|
|
640
668
|
if (job.postedAt) console.log(`Posted: ${job.postedAt}`);
|
|
669
|
+
// Field presence, not truthiness: a returned null/'unknown' status is a real
|
|
670
|
+
// "Not mentioned" answer from the backend. Only an older backend that omits
|
|
671
|
+
// the key entirely should render no Sponsorship line at all.
|
|
672
|
+
if (job && typeof job === 'object' && 'sponsorshipStatus' in job) {
|
|
673
|
+
console.log(`Sponsorship: ${SPONSORSHIP_STATUS_LABELS[job.sponsorshipStatus] || 'Not mentioned'}`);
|
|
674
|
+
}
|
|
641
675
|
if (typeof job.description === 'string' && job.description.length > 0) {
|
|
642
676
|
console.log();
|
|
643
677
|
console.log(color('bold', 'Description:'));
|
|
@@ -1277,7 +1311,7 @@ ${color('bold', 'Commands:')}
|
|
|
1277
1311
|
${color('cyan', 'login')} Authenticate via Google OAuth
|
|
1278
1312
|
${color('cyan', 'logout')} Clear credentials
|
|
1279
1313
|
${color('cyan', 'whoami')} Show current user
|
|
1280
|
-
${color('cyan', 'jobs')} List jobs (--function, --company <id>, --region <tag>, --job-type <type>, --work-arrangement <values>, --remote, --status, --sort, --limit, --offset, --keywords)
|
|
1314
|
+
${color('cyan', 'jobs')} List jobs (--function, --company <id>, --region <tag>, --job-type <type>, --work-arrangement <values>, --remote, --status, --sort, --sponsorship, --limit, --offset, --keywords)
|
|
1281
1315
|
${color('cyan', 'job <id>')} Get full job detail
|
|
1282
1316
|
${color('cyan', 'jobs <id>')} Alias for job <id>
|
|
1283
1317
|
${color('cyan', 'companies')} List companies
|
|
@@ -1333,6 +1367,10 @@ ${color('bold', 'Filter values:')}
|
|
|
1333
1367
|
--status new, applied_confirmed, check_later, not_interested, all
|
|
1334
1368
|
(filters by YOUR application pipeline state)
|
|
1335
1369
|
--sort newest (default), match (highest match score first, requires resume)
|
|
1370
|
+
--sponsorship all (default), exclude_no, only_yes
|
|
1371
|
+
exclude_no hides jobs that explicitly state they do not sponsor
|
|
1372
|
+
(~13% of active US jobs); only_yes keeps only jobs that explicitly
|
|
1373
|
+
state they sponsor (~2%). Most postings never state a policy.
|
|
1336
1374
|
|
|
1337
1375
|
${color('bold', 'MCP Setup:')}
|
|
1338
1376
|
Claude Code: claude mcp add --scope user trackly -- trackly mcp
|
package/docs/trackly-tools.md
CHANGED
|
@@ -83,6 +83,7 @@ API-key creation attempts.
|
|
|
83
83
|
- `remote` (boolean): filter to remote jobs only. Maps to `usStates=REMOTE`.
|
|
84
84
|
- `status`: your application pipeline state. Values: `new`, `applied_confirmed`, `check_later`, `not_interested`, `all`.
|
|
85
85
|
- `sort`: `newest` (default) or `match` (highest match score first; requires a resume on file). Backend rejects the deprecated values `oldest` and `company` with HTTP 400.
|
|
86
|
+
- `sponsorship`: `all` (default), `exclude_no`, `only_yes`. `exclude_no` hides jobs with a reliable explicit "does not sponsor" statement (~13% of active US jobs); `only_yes` keeps only jobs with a reliable explicit "sponsors" statement (~2%). Most postings never state a policy either way, so `only_yes` is a very narrow slice.
|
|
86
87
|
- `keywords`, `companyId`, `limit`, `offset`.
|
|
87
88
|
- **trackly_get_job** — Get full job details by ID
|
|
88
89
|
- **trackly_search_companies** — Semantic company search
|
package/mcp/analytics.js
CHANGED
|
@@ -111,11 +111,11 @@ const SAFE_ENUM_VALUES = new Set([
|
|
|
111
111
|
'unspecified', 'us', 'non_us', 'europe', 'latam', 'middle_east', 'asia', 'africa',
|
|
112
112
|
'canada', 'oceania', 'unknown', 'product', 'engineering', 'design', 'data',
|
|
113
113
|
'marketing', 'sales', 'partnerships', 'finance', 'strategy', 'operations', 'people',
|
|
114
|
-
'legal', 'support', 'other', 'newest', 'match',
|
|
114
|
+
'legal', 'support', 'other', 'newest', 'match', 'exclude_no', 'only_yes',
|
|
115
115
|
]);
|
|
116
116
|
const SAFE_ENUM_KEYS = new Set([
|
|
117
117
|
'status', 'stage', 'function', 'jobfunction', 'jobmodality', 'workarrangement',
|
|
118
|
-
'region', 'regiontag', 'locationfilter', 'sort',
|
|
118
|
+
'region', 'regiontag', 'locationfilter', 'sort', 'sponsorship',
|
|
119
119
|
]);
|
|
120
120
|
const SDK_FAILURE_WARNING = /Warning: (?:Failed to instrument server|Failed to setup tool call instrumentation|No PostHog client passed)/i;
|
|
121
121
|
|
|
@@ -474,7 +474,7 @@ function projectMcpParameters(toolName, parameters) {
|
|
|
474
474
|
const allowed = {
|
|
475
475
|
trackly_search_jobs: new Set([
|
|
476
476
|
'function', 'companyId', 'locationFilter', 'jobModality', 'workArrangements',
|
|
477
|
-
'remote', 'status', 'sort', 'limit', 'offset', 'keywords',
|
|
477
|
+
'remote', 'status', 'sort', 'sponsorship', 'limit', 'offset', 'keywords',
|
|
478
478
|
]),
|
|
479
479
|
trackly_get_job: new Set(['id']),
|
|
480
480
|
trackly_search_companies: new Set(['query', 'limit']),
|
package/mcp/server.js
CHANGED
|
@@ -67,6 +67,12 @@ const MCP_MISSING_DESTINATION_VALUES = [
|
|
|
67
67
|
'trackly', 'external_site', 'file', 'email', 'calendar', 'crm', 'browser', 'other',
|
|
68
68
|
];
|
|
69
69
|
|
|
70
|
+
// `sponsorship` enum matches the backend's discovery-only filter (close-ai
|
|
71
|
+
// PR #1578). Absent or 'all' applies no filter; only ~2% of active US jobs
|
|
72
|
+
// carry an explicit "sponsors" statement and ~13% an explicit "does not
|
|
73
|
+
// sponsor" one, so the remaining ~85% never state a policy either way.
|
|
74
|
+
const SPONSORSHIP_VALUES = ['all', 'exclude_no', 'only_yes'];
|
|
75
|
+
|
|
70
76
|
// `sort` enum matches backend handler at `jobscout.ts:3053` — NOT the pre-fix
|
|
71
77
|
// `newest|oldest|company` (backend rejects oldest/company with HTTP 400).
|
|
72
78
|
const SORT_VALUES = ['newest', 'match'];
|
|
@@ -254,6 +260,9 @@ function createServer() {
|
|
|
254
260
|
'Filter by YOUR application pipeline state. Not a generic job-posting status. Values: ' + STATUS_VALUES.join(', ')
|
|
255
261
|
),
|
|
256
262
|
sort: z.enum(SORT_VALUES).optional().describe('Sort order: newest (default) or match (highest match score first; requires resume). Backend rejects legacy oldest/company with HTTP 400.'),
|
|
263
|
+
sponsorship: z.enum(SPONSORSHIP_VALUES).optional().describe(
|
|
264
|
+
"Visa sponsorship filter. 'exclude_no' hides jobs with a reliable explicit \"does not sponsor\" statement (~13% of active US jobs); 'only_yes' keeps only jobs with a reliable explicit \"sponsors\" statement (~2% — most postings never state a policy either way). Default 'all' shows every job. Applies only to the discovery view: when you pass a filtering mode without `status`, status defaults to 'new' (the backend ignores the filter on tracked/pipeline views)."
|
|
265
|
+
),
|
|
257
266
|
limit: z.number().max(50).optional().describe('Max results (default 20, max 50)'),
|
|
258
267
|
offset: z.number().min(0).optional().describe('Pagination offset'),
|
|
259
268
|
keywords: z.string().max(500).optional().describe('Keyword search in title, company, or description'),
|
|
@@ -281,6 +290,16 @@ function createServer() {
|
|
|
281
290
|
if (params.remote === true) qs.set('usStates', 'REMOTE');
|
|
282
291
|
if (params.status !== undefined) qs.set('status', params.status);
|
|
283
292
|
if (params.sort !== undefined) qs.set('sort', params.sort);
|
|
293
|
+
if (params.sponsorship !== undefined) {
|
|
294
|
+
qs.set('sponsorship', String(params.sponsorship));
|
|
295
|
+
// The backend applies the filter only on the literal status=new
|
|
296
|
+
// discovery view; without this default an agent passing a filtering
|
|
297
|
+
// mode alone would get silently unfiltered results. Mirrors
|
|
298
|
+
// close-ai src/mcp/server.ts.
|
|
299
|
+
if (params.sponsorship !== 'all' && params.status === undefined) {
|
|
300
|
+
qs.set('status', 'new');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
284
303
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
|
285
304
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
|
286
305
|
if (params.keywords !== undefined) qs.set('search', params.keywords);
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackly-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "trackly-cli",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.16.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@modelcontextprotocol/sdk": "1.30.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackly-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"mcpName": "io.github.trackly-app/trackly",
|
|
5
5
|
"description": "AI job search CLI + hosted MCP server with OAuth. 170K+ jobs, 3,800+ 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.
|
|
20
|
+
"version": "0.16.0",
|
|
21
21
|
"packages": [
|
|
22
22
|
{
|
|
23
23
|
"registryType": "npm",
|
|
24
24
|
"identifier": "trackly-cli",
|
|
25
|
-
"version": "0.
|
|
25
|
+
"version": "0.16.0",
|
|
26
26
|
"runtimeHint": "npx",
|
|
27
27
|
"runtimeArguments": [
|
|
28
28
|
{
|