trackly-cli 0.1.12 → 0.2.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/README.md +37 -9
- package/bin/trackly +52 -15
- package/docs/trackly-tools.md +29 -5
- package/lib/formatters.js +6 -6
- package/mcp/server.js +58 -16
- package/package.json +11 -3
- package/server.json +8 -6
package/README.md
CHANGED
|
@@ -7,9 +7,23 @@
|
|
|
7
7
|
|
|
8
8
|
The only job tracking CLI built for AI agents.
|
|
9
9
|
|
|
10
|
-
Search 128,000+ jobs across 1,900+ companies and 40+ ATS types. Track applications, get AI-powered recommendations, and manage your job search -- from the terminal or through Claude
|
|
10
|
+
Search 128,000+ jobs across 1,900+ companies and 40+ ATS types. Track applications, get AI-powered recommendations, and manage your job search -- from the terminal or through Claude, ChatGPT, Cursor, and other MCP-compatible AI agents.
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Two ways to connect
|
|
13
|
+
|
|
14
|
+
### 🚀 Option 1: One-click in Claude co-work, Claude Desktop, ChatGPT (no install)
|
|
15
|
+
|
|
16
|
+
Use Trackly directly inside your AI — zero config:
|
|
17
|
+
|
|
18
|
+
1. Open Settings → Connectors → **Add custom connector**
|
|
19
|
+
2. URL: `https://mcp.usetrackly.app/api/mcp`
|
|
20
|
+
3. Click **Add** → sign in with Google → done
|
|
21
|
+
|
|
22
|
+
**[Full setup guide with screenshots →](https://usetrackly.app/connect)**
|
|
23
|
+
|
|
24
|
+
Works in: Claude co-work (web), Claude Desktop, ChatGPT Connectors, and any MCP client that supports remote/streamable-http connectors.
|
|
25
|
+
|
|
26
|
+
### 💻 Option 2: CLI install (for Cursor, Windsurf, or terminal use)
|
|
13
27
|
|
|
14
28
|
```bash
|
|
15
29
|
npm install -g trackly-cli # may need: sudo npm install -g trackly-cli
|
|
@@ -27,8 +41,10 @@ trackly jobs --function product
|
|
|
27
41
|
|
|
28
42
|
```bash
|
|
29
43
|
trackly jobs # List jobs
|
|
30
|
-
trackly jobs --
|
|
31
|
-
trackly jobs --
|
|
44
|
+
trackly jobs --remote # Filter remote jobs (sets usStates=REMOTE)
|
|
45
|
+
trackly jobs --region us # Filter by region (us, non_us, all, or a region tag: europe, canada, remote, ...)
|
|
46
|
+
trackly jobs --job-type internship # Filter by employment type (full_time, internship, all)
|
|
47
|
+
trackly jobs --function product # Filter by function
|
|
32
48
|
trackly jobs --company 243 # Filter by company ID
|
|
33
49
|
trackly job 1234 # Get job details
|
|
34
50
|
trackly jobs 1234 # Alias for job details
|
|
@@ -60,7 +76,19 @@ Add `--json` to any command for JSON output. Use `--api-key <key>` or `--base-ur
|
|
|
60
76
|
|
|
61
77
|
## MCP Server Setup
|
|
62
78
|
|
|
63
|
-
###
|
|
79
|
+
### Hosted (Claude co-work, Claude Desktop, ChatGPT)
|
|
80
|
+
|
|
81
|
+
No install. In your AI tool, open **Settings → Connectors → Add custom connector** and enter:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
https://mcp.usetrackly.app/api/mcp
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Sign in with Google when prompted. [Full visual guide →](https://usetrackly.app/connect)
|
|
88
|
+
|
|
89
|
+
### Local (CLI via stdio, for Cursor / Windsurf / Claude Code)
|
|
90
|
+
|
|
91
|
+
#### Claude Code one-liner
|
|
64
92
|
|
|
65
93
|
```bash
|
|
66
94
|
claude mcp add --scope user trackly -- trackly mcp
|
|
@@ -72,7 +100,7 @@ Or equivalently:
|
|
|
72
100
|
claude mcp add-json --scope user trackly '{"command":"trackly","args":["mcp"]}'
|
|
73
101
|
```
|
|
74
102
|
|
|
75
|
-
|
|
103
|
+
#### Claude Code manual config
|
|
76
104
|
|
|
77
105
|
Add to `~/.claude/settings.json`:
|
|
78
106
|
|
|
@@ -87,9 +115,9 @@ Add to `~/.claude/settings.json`:
|
|
|
87
115
|
}
|
|
88
116
|
```
|
|
89
117
|
|
|
90
|
-
|
|
118
|
+
#### Cursor / Windsurf
|
|
91
119
|
|
|
92
|
-
Add to `.cursor/mcp.json` or `~/.cursor/mcp.json
|
|
120
|
+
Add to `.cursor/mcp.json` or `~/.cursor/mcp.json` (same schema works for Windsurf):
|
|
93
121
|
|
|
94
122
|
```json
|
|
95
123
|
{
|
|
@@ -102,7 +130,7 @@ Add to `.cursor/mcp.json` or `~/.cursor/mcp.json`:
|
|
|
102
130
|
}
|
|
103
131
|
```
|
|
104
132
|
|
|
105
|
-
Then use natural language in
|
|
133
|
+
Then use natural language in any of these clients:
|
|
106
134
|
|
|
107
135
|
- "Find me PM jobs at fintech companies"
|
|
108
136
|
- "What remote engineering roles are available?"
|
package/bin/trackly
CHANGED
|
@@ -19,17 +19,17 @@ const crypto = require('crypto');
|
|
|
19
19
|
const { spawnSync } = require('child_process');
|
|
20
20
|
const { version: VERSION } = require('../package.json');
|
|
21
21
|
|
|
22
|
-
const BOOLEAN_FLAGS = new Set(['help', 'h', 'json', 'version', 'v', 'clear-api-key', 'clear-base-url', 'refresh']);
|
|
22
|
+
const BOOLEAN_FLAGS = new Set(['help', 'h', 'json', 'version', 'v', 'clear-api-key', 'clear-base-url', 'refresh', 'remote']);
|
|
23
23
|
const VALUE_FLAGS = new Set([
|
|
24
24
|
'api-key',
|
|
25
25
|
'base-url',
|
|
26
26
|
'function',
|
|
27
|
+
'job-type',
|
|
27
28
|
'keywords',
|
|
28
29
|
'limit',
|
|
29
|
-
'location',
|
|
30
|
-
'modality',
|
|
31
30
|
'name',
|
|
32
31
|
'offset',
|
|
32
|
+
'region',
|
|
33
33
|
'search',
|
|
34
34
|
'sort',
|
|
35
35
|
'status',
|
|
@@ -128,9 +128,21 @@ function parseJobId(value) {
|
|
|
128
128
|
return id;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
// Flags deprecated in the API-docs-plan fixup. Still accepted by parseArgs so old
|
|
132
|
+
// scripts don't ERR_NOT_FOUND, but cmdJobs hard-rejects them with a migration hint
|
|
133
|
+
// — silent no-op would be worse than noise (per PR #13 Codex P1 finding).
|
|
134
|
+
const DEPRECATED_JOB_FLAGS = Object.freeze({
|
|
135
|
+
location: '--location was removed. Use --region <tag> (us, non_us, all, or a region tag like europe, canada, remote). See `trackly --help` for accepted values.',
|
|
136
|
+
modality: '--modality was removed. Use --job-type <full_time|internship|all> for employment type, and/or --remote / --region remote for work-location.',
|
|
137
|
+
});
|
|
138
|
+
|
|
131
139
|
function hasJobListFilters(args) {
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
// Include deprecated flags so `trackly jobs 42 --location us` routes to cmdJobs
|
|
141
|
+
// (which then raises the migration error) instead of cmdJob(42) silently ignoring
|
|
142
|
+
// the filter. See PR #13 Cursor Bugbot finding.
|
|
143
|
+
const deprecated = Object.keys(DEPRECATED_JOB_FLAGS);
|
|
144
|
+
const active = ['function', 'company', 'keywords', 'limit', 'region', 'job-type', 'remote', 'offset', 'sort', 'status'];
|
|
145
|
+
return [...active, ...deprecated].some((key) => args[key] !== undefined);
|
|
134
146
|
}
|
|
135
147
|
|
|
136
148
|
function openExternalUrl(url) {
|
|
@@ -350,11 +362,25 @@ async function cmdWhoami() {
|
|
|
350
362
|
async function cmdJobs(args) {
|
|
351
363
|
if (!hasAuth()) failNow(AUTH_HELP_TEXT);
|
|
352
364
|
|
|
365
|
+
// Reject deprecated flags with migration hints instead of silently ignoring them
|
|
366
|
+
// (scripts from older releases would otherwise appear to succeed while returning
|
|
367
|
+
// an unfiltered feed — misleading and hard to debug).
|
|
368
|
+
for (const [flag, hint] of Object.entries(DEPRECATED_JOB_FLAGS)) {
|
|
369
|
+
if (args[flag] !== undefined) failNow(hint);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// --remote is a shorthand that conflicts with --region (they both control location).
|
|
373
|
+
// Help text documents them as alternatives; enforce it explicitly here.
|
|
374
|
+
if (args.remote && args.region) {
|
|
375
|
+
failNow('--remote cannot be combined with --region. Use one or the other (e.g. --region remote).');
|
|
376
|
+
}
|
|
377
|
+
|
|
353
378
|
const params = new URLSearchParams();
|
|
354
379
|
if (args.function) params.set('jobFunction', args.function);
|
|
355
380
|
if (args.company) params.set('companyId', args.company);
|
|
356
|
-
if (args.
|
|
357
|
-
if (args
|
|
381
|
+
if (args.region) params.set('locationFilter', args.region);
|
|
382
|
+
if (args['job-type']) params.set('jobModality', args['job-type']);
|
|
383
|
+
if (args.remote) params.set('usStates', 'REMOTE');
|
|
358
384
|
if (args.status) params.set('status', args.status);
|
|
359
385
|
if (args.sort) params.set('sort', args.sort);
|
|
360
386
|
if (args.limit) params.set('limit', args.limit);
|
|
@@ -384,16 +410,15 @@ async function cmdJob(args) {
|
|
|
384
410
|
|
|
385
411
|
const job = result.job || result;
|
|
386
412
|
console.log(color('bold', job.title));
|
|
387
|
-
console.log(`Company: ${color('blue', job.
|
|
413
|
+
console.log(`Company: ${color('blue', job.companyName || (job.company && job.company.name) || '')}`);
|
|
388
414
|
if (job.location) console.log(`Location: ${job.location}`);
|
|
389
|
-
if (job.
|
|
390
|
-
if (job.
|
|
391
|
-
if (job.
|
|
392
|
-
if (job.description_text) {
|
|
415
|
+
if (job.jobUrl) console.log(`URL: ${color('dim', job.jobUrl)}`);
|
|
416
|
+
if (job.postedAt) console.log(`Posted: ${job.postedAt}`);
|
|
417
|
+
if (typeof job.description === 'string' && job.description.length > 0) {
|
|
393
418
|
console.log();
|
|
394
419
|
console.log(color('bold', 'Description:'));
|
|
395
|
-
console.log(job.
|
|
396
|
-
if (job.
|
|
420
|
+
console.log(job.description.slice(0, 1000));
|
|
421
|
+
if (job.description.length > 1000) console.log(color('dim', '... (truncated)'));
|
|
397
422
|
}
|
|
398
423
|
} catch (e) {
|
|
399
424
|
setErrorExit(e.error || e.message || 'Failed to fetch job');
|
|
@@ -700,7 +725,7 @@ ${color('bold', 'Commands:')}
|
|
|
700
725
|
${color('cyan', 'login')} Authenticate via Google OAuth
|
|
701
726
|
${color('cyan', 'logout')} Clear credentials
|
|
702
727
|
${color('cyan', 'whoami')} Show current user
|
|
703
|
-
${color('cyan', 'jobs')} List jobs (--function, --company <id>, --
|
|
728
|
+
${color('cyan', 'jobs')} List jobs (--function, --company <id>, --region <tag>, --job-type <type>, --remote, --status, --sort, --limit, --offset, --keywords)
|
|
704
729
|
${color('cyan', 'job <id>')} Get full job detail
|
|
705
730
|
${color('cyan', 'jobs <id>')} Alias for job <id>
|
|
706
731
|
${color('cyan', 'companies')} List companies
|
|
@@ -732,6 +757,18 @@ ${color('bold', 'Flags:')}
|
|
|
732
757
|
--version Show version
|
|
733
758
|
--help Show this help
|
|
734
759
|
|
|
760
|
+
${color('bold', 'Filter values:')}
|
|
761
|
+
--function product, engineering, design, data, marketing, sales, partnerships,
|
|
762
|
+
finance, strategy, operations, people, legal, support, other (comma-separated)
|
|
763
|
+
--region us, non_us, all, or a region tag (europe, canada, remote, ...)
|
|
764
|
+
Use a comma-separated list of region tags for multiple regions;
|
|
765
|
+
us/non_us/all cannot be combined.
|
|
766
|
+
--job-type full_time, internship, all (employment type, not work-location)
|
|
767
|
+
--remote Flag (no value). Filters for remote jobs (equivalent to usStates=REMOTE).
|
|
768
|
+
--status new, applying, applied_confirmed, check_later, not_interested, all
|
|
769
|
+
(filters by YOUR application pipeline state)
|
|
770
|
+
--sort newest, oldest, company
|
|
771
|
+
|
|
735
772
|
${color('bold', 'MCP Setup:')}
|
|
736
773
|
Claude Code: claude mcp add --scope user trackly -- trackly mcp
|
|
737
774
|
Cursor: add {"command":"trackly","args":["mcp"]} to .cursor/mcp.json
|
package/docs/trackly-tools.md
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
## Trackly Job Tracker (MCP)
|
|
2
2
|
|
|
3
|
-
Trackly MCP server provides job search and tracking tools.
|
|
3
|
+
Trackly MCP server provides job search and tracking tools. Two ways to connect:
|
|
4
|
+
|
|
5
|
+
### Claude co-work, Claude Desktop, ChatGPT (hosted, no install)
|
|
6
|
+
|
|
7
|
+
In your AI tool, open **Settings → Connectors → Add custom connector**:
|
|
8
|
+
|
|
9
|
+
- URL: `https://mcp.usetrackly.app/api/mcp`
|
|
10
|
+
- Leave any optional OAuth fields empty — **Client ID**, **Client Secret**, **Authorization URL**, **Token URL** — Trackly uses OAuth 2.1 Dynamic Client Registration and will register the client automatically
|
|
11
|
+
- If the client UI uses different labels or requires you to continue past an OAuth section, keep those fields blank and proceed to sign in. Do not invent client credentials or endpoint URLs.
|
|
12
|
+
|
|
13
|
+
Sign in with Google when prompted. [Visual guide →](https://usetrackly.app/connect)
|
|
14
|
+
|
|
15
|
+
### Cursor, Windsurf, Claude Code (CLI via stdio)
|
|
16
|
+
|
|
17
|
+
Install and start the stdio server:
|
|
4
18
|
|
|
5
19
|
```bash
|
|
20
|
+
npm install -g trackly-cli
|
|
6
21
|
trackly mcp
|
|
7
22
|
```
|
|
8
23
|
|
|
9
|
-
|
|
24
|
+
#### Claude Code
|
|
10
25
|
|
|
11
26
|
```json
|
|
12
27
|
{
|
|
@@ -19,9 +34,9 @@ trackly mcp
|
|
|
19
34
|
}
|
|
20
35
|
```
|
|
21
36
|
|
|
22
|
-
|
|
37
|
+
#### Cursor / Windsurf
|
|
23
38
|
|
|
24
|
-
Add to `.cursor/mcp.json
|
|
39
|
+
Add to `.cursor/mcp.json` (same schema for Windsurf):
|
|
25
40
|
|
|
26
41
|
```json
|
|
27
42
|
{
|
|
@@ -52,7 +67,16 @@ You can also pass `TRACKLY_API_KEY` as an environment variable for one-off runs.
|
|
|
52
67
|
|
|
53
68
|
### Available Tools
|
|
54
69
|
|
|
55
|
-
- **trackly_search_jobs** — Search/filter jobs
|
|
70
|
+
- **trackly_search_jobs** — Search/filter jobs.
|
|
71
|
+
- `function` (14 values): `product`, `engineering`, `design`, `data`, `marketing`, `sales`, `partnerships`, `finance`, `strategy`, `operations`, `people`, `legal`, `support`, `other`
|
|
72
|
+
- `locationFilter`: pass ONE of
|
|
73
|
+
- a single scalar: `us`, `non_us`, `all`, or a region tag (`europe`, `latam`, `middle_east`, `asia`, `africa`, `canada`, `oceania`, `remote`, `unknown`), OR
|
|
74
|
+
- an array of region tags for multi-region (e.g. `["europe", "canada"]`). The array form excludes `us` — combining `us` with other tags in an array causes the backend to silently drop the others. To get "not US" use the scalar `non_us` alone.
|
|
75
|
+
- `jobModality`: `full_time`, `internship`, `all` — employment type, NOT work-location style. For remote/hybrid/onsite filtering, use the `remote` boolean or `locationFilter: "remote"`; hybrid and onsite are not exposed as filters.
|
|
76
|
+
- `remote` (boolean): filter to remote jobs only. Maps to `usStates=REMOTE`.
|
|
77
|
+
- `status`: your application pipeline state. Values: `new`, `applying`, `applied_confirmed`, `check_later`, `not_interested`, `all`.
|
|
78
|
+
- `sort`: `newest`, `oldest`, `company`.
|
|
79
|
+
- `keywords`, `companyId`, `limit`, `offset`.
|
|
56
80
|
- **trackly_get_job** — Get full job details by ID
|
|
57
81
|
- **trackly_search_companies** — Semantic company search
|
|
58
82
|
- **trackly_list_companies** — List all tracked companies with job counts
|
package/lib/formatters.js
CHANGED
|
@@ -39,14 +39,14 @@ function outputJobs(jobs) {
|
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
for (const job of jobs) {
|
|
42
|
-
const modality = job.modality ? color('cyan', `[${job.modality}]`) : '';
|
|
43
42
|
const location = job.location ? color('dim', job.location) : '';
|
|
44
|
-
|
|
45
|
-
console.log(
|
|
43
|
+
const companyName = job.companyName || (job.company && job.company.name) || '';
|
|
44
|
+
console.log(`${color('bold', job.title)}`);
|
|
45
|
+
console.log(` ${color('blue', companyName)} | ${location}`);
|
|
46
46
|
const fundingLine = formatFundingLine(job.company);
|
|
47
47
|
if (fundingLine) console.log(` ${color('magenta', fundingLine)}`);
|
|
48
|
-
console.log(` ID: ${color('dim', job.id)} | Posted: ${color('dim', job.
|
|
49
|
-
if (job.
|
|
48
|
+
console.log(` ID: ${color('dim', job.id)} | Posted: ${color('dim', job.postedAt || job.firstSeenAt || 'unknown')}`);
|
|
49
|
+
if (job.jobUrl) console.log(` ${color('dim', job.jobUrl)}`);
|
|
50
50
|
console.log();
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -58,7 +58,7 @@ function outputCompanies(companies) {
|
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
60
|
for (const co of companies) {
|
|
61
|
-
const count = co.
|
|
61
|
+
const count = co.totalJobCount || 0;
|
|
62
62
|
console.log(`${color('bold', co.name)} — ${color('cyan', count + ' jobs')}`);
|
|
63
63
|
if (co.domain) console.log(` ${color('dim', co.domain)}`);
|
|
64
64
|
console.log();
|
package/mcp/server.js
CHANGED
|
@@ -9,6 +9,33 @@ const { version: PACKAGE_VERSION } = require('../package.json');
|
|
|
9
9
|
const MCP_USER_AGENT = `trackly-mcp/${PACKAGE_VERSION}`;
|
|
10
10
|
const AUTH_HINT = 'Run `trackly login` or set TRACKLY_API_KEY. Get a key at https://usetrackly.app (sign in → Settings → API Keys).';
|
|
11
11
|
|
|
12
|
+
// Mirrors `granola-followup-app/src/services/region-classifier.ts:8` REGION_TAGS.
|
|
13
|
+
// Keep in sync when the backend enum changes.
|
|
14
|
+
const REGION_TAGS = [
|
|
15
|
+
'us', 'europe', 'latam', 'middle_east', 'asia', 'africa', 'canada', 'oceania', 'remote', 'unknown',
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
// REGION_TAGS values that are safe to combine in a comma-list with other tags.
|
|
19
|
+
// `us` is excluded because combining it with other tags (e.g. ['us', 'europe']) is a trap:
|
|
20
|
+
// the backend parser at granola-followup-app/src/routes/jobscout-filter-utils.ts:73-90
|
|
21
|
+
// supports `us` ONLY as a single-value scalar; in a comma-list it behaves identically to
|
|
22
|
+
// the scalar branch and any non-us members are ignored. Callers who want 'us + europe' should
|
|
23
|
+
// use the scalar `all` or two separate calls. Callers who want 'not us' should use `non_us`.
|
|
24
|
+
const REGION_TAGS_ARRAY_SAFE = REGION_TAGS.filter((t) => t !== 'us');
|
|
25
|
+
|
|
26
|
+
// `jobFunction` enum matches `granola-followup-app/src/routes/jobscout-filter-utils.ts:17-21`
|
|
27
|
+
// (ALL_JOB_FUNCTIONS). 14 canonical values.
|
|
28
|
+
const JOB_FUNCTIONS = [
|
|
29
|
+
'product', 'engineering', 'design', 'data', 'marketing', 'sales', 'partnerships',
|
|
30
|
+
'finance', 'strategy', 'operations', 'people', 'legal', 'support', 'other',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// `status` enum matches the backend allowlist at `jobscout.ts:2949`.
|
|
34
|
+
const STATUS_VALUES = ['new', 'applying', 'applied_confirmed', 'check_later', 'not_interested', 'all'];
|
|
35
|
+
|
|
36
|
+
// `jobModality` enum matches `jobscout.ts:2870-2875`. Employment type, NOT work-location.
|
|
37
|
+
const JOB_MODALITIES = ['full_time', 'internship', 'all'];
|
|
38
|
+
|
|
12
39
|
function createErrorResult(error, fallbackMessage, extra = {}) {
|
|
13
40
|
const payload = {
|
|
14
41
|
error: error?.error || error?.message || fallbackMessage,
|
|
@@ -66,31 +93,46 @@ function createServer() {
|
|
|
66
93
|
|
|
67
94
|
server.tool(
|
|
68
95
|
'trackly_search_jobs',
|
|
69
|
-
'Search and filter job postings. Returns matching jobs with title, company, location,
|
|
96
|
+
'Search and filter job postings. Returns matching jobs with title, company, location, and structured fields. Use companyId to filter jobs at a specific company (get companyId from trackly_search_companies first). Pass `remote: true` for remote-only jobs.',
|
|
70
97
|
{
|
|
71
|
-
function: z.enum(
|
|
98
|
+
function: z.enum(JOB_FUNCTIONS).optional().describe('Job function filter. One of: ' + JOB_FUNCTIONS.join(', ')),
|
|
72
99
|
companyId: z.number().optional().describe('Filter jobs by company ID (get from trackly_search_companies)'),
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
100
|
+
locationFilter: z.union([
|
|
101
|
+
z.enum(['us', 'non_us', 'all']),
|
|
102
|
+
z.enum(REGION_TAGS),
|
|
103
|
+
z.array(z.enum(REGION_TAGS_ARRAY_SAFE)).min(1),
|
|
104
|
+
]).optional().describe(
|
|
105
|
+
"Region tag filter. Pass ONE of: (a) a single scalar from 'us', 'non_us', 'all', or a REGION_TAGS value ('europe', 'latam', 'middle_east', 'asia', 'africa', 'canada', 'oceania', 'remote', 'unknown'); or (b) an array of region tags for multi-region (e.g. ['europe', 'canada']). The array form excludes 'us' — combining 'us' with other tags causes the backend to silently drop the others. For 'not US' use the scalar 'non_us' alone."
|
|
106
|
+
),
|
|
107
|
+
jobModality: z.enum(JOB_MODALITIES).optional().describe(
|
|
108
|
+
"Employment type (NOT work-location style). full_time = full-time roles, internship = internships, all = both. For remote filtering, use the `remote` boolean or locationFilter='remote'. Hybrid and onsite are not exposed as filters."
|
|
109
|
+
),
|
|
110
|
+
remote: z.boolean().optional().describe('Filter to remote jobs only (maps to usStates=REMOTE).'),
|
|
111
|
+
status: z.enum(STATUS_VALUES).optional().describe(
|
|
112
|
+
'Filter by YOUR application pipeline state. Not a generic job-posting status. Values: ' + STATUS_VALUES.join(', ')
|
|
113
|
+
),
|
|
76
114
|
sort: z.enum(['newest', 'oldest', 'company']).optional().describe('Sort order: newest, oldest, company'),
|
|
77
115
|
limit: z.number().max(50).optional().describe('Max results (default 20, max 50)'),
|
|
78
116
|
offset: z.number().min(0).optional().describe('Pagination offset'),
|
|
79
|
-
keywords: z.string().max(500).optional().describe('Keyword search in title
|
|
117
|
+
keywords: z.string().max(500).optional().describe('Keyword search in title, company, or description'),
|
|
80
118
|
},
|
|
81
119
|
wrapTool(async (params) => {
|
|
82
|
-
// Map MCP param names to backend query param names
|
|
83
|
-
const paramMap = {
|
|
84
|
-
function: 'jobFunction',
|
|
85
|
-
keywords: 'search',
|
|
86
|
-
};
|
|
87
120
|
const qs = new URLSearchParams();
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
121
|
+
if (params.function !== undefined) qs.set('jobFunction', params.function);
|
|
122
|
+
if (params.companyId !== undefined) qs.set('companyId', String(params.companyId));
|
|
123
|
+
if (params.locationFilter !== undefined) {
|
|
124
|
+
const value = Array.isArray(params.locationFilter)
|
|
125
|
+
? params.locationFilter.join(',')
|
|
126
|
+
: params.locationFilter;
|
|
127
|
+
qs.set('locationFilter', value);
|
|
93
128
|
}
|
|
129
|
+
if (params.jobModality !== undefined) qs.set('jobModality', params.jobModality);
|
|
130
|
+
if (params.remote === true) qs.set('usStates', 'REMOTE');
|
|
131
|
+
if (params.status !== undefined) qs.set('status', params.status);
|
|
132
|
+
if (params.sort !== undefined) qs.set('sort', params.sort);
|
|
133
|
+
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
|
134
|
+
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
|
135
|
+
if (params.keywords !== undefined) qs.set('search', params.keywords);
|
|
94
136
|
return apiRequest('GET', `/api/jobscout/jobs?${qs.toString()}`, null, false, false, MCP_USER_AGENT);
|
|
95
137
|
}, 'Failed to search jobs')
|
|
96
138
|
);
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackly-cli",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"mcpName": "io.github.kevinastuhuaman/trackly",
|
|
5
|
-
"description": "CLI + MCP server
|
|
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": {
|
|
7
7
|
"trackly": "./bin/trackly"
|
|
8
8
|
},
|
|
@@ -25,8 +25,16 @@
|
|
|
25
25
|
"mcp",
|
|
26
26
|
"mcp-server",
|
|
27
27
|
"model-context-protocol",
|
|
28
|
-
"
|
|
28
|
+
"oauth",
|
|
29
|
+
"claude",
|
|
29
30
|
"claude-code",
|
|
31
|
+
"claude-cowork",
|
|
32
|
+
"claude-connector",
|
|
33
|
+
"chatgpt",
|
|
34
|
+
"chatgpt-connector",
|
|
35
|
+
"cursor",
|
|
36
|
+
"windsurf",
|
|
37
|
+
"cli",
|
|
30
38
|
"ai-agent",
|
|
31
39
|
"ai-tools",
|
|
32
40
|
"job-search",
|
package/server.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.kevinastuhuaman/trackly",
|
|
4
4
|
"title": "Trackly CLI",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "AI job search for Claude, ChatGPT, Cursor. 128K+ jobs, 1,900+ companies. OAuth or stdio.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"url": "https://github.com/kevinastuhuaman/trackly-cli",
|
|
8
8
|
"source": "github"
|
|
@@ -12,15 +12,17 @@
|
|
|
12
12
|
{
|
|
13
13
|
"src": "https://usetrackly.app/favicon-512x512.png?v=5",
|
|
14
14
|
"mimeType": "image/png",
|
|
15
|
-
"sizes": [
|
|
15
|
+
"sizes": [
|
|
16
|
+
"512x512"
|
|
17
|
+
]
|
|
16
18
|
}
|
|
17
19
|
],
|
|
18
|
-
"version": "0.1
|
|
20
|
+
"version": "0.2.1",
|
|
19
21
|
"packages": [
|
|
20
22
|
{
|
|
21
23
|
"registryType": "npm",
|
|
22
24
|
"identifier": "trackly-cli",
|
|
23
|
-
"version": "0.1
|
|
25
|
+
"version": "0.2.1",
|
|
24
26
|
"runtimeHint": "npx",
|
|
25
27
|
"runtimeArguments": [
|
|
26
28
|
{
|
|
@@ -46,8 +48,8 @@
|
|
|
46
48
|
"headers": [
|
|
47
49
|
{
|
|
48
50
|
"name": "Authorization",
|
|
49
|
-
"description": "Bearer API key
|
|
50
|
-
"isRequired":
|
|
51
|
+
"description": "Optional Bearer trk_* API key for remote clients that don't support OAuth 2.1 Dynamic Client Registration. Clients that do support DCR can leave this blank \u2014 the server will guide them through an interactive OAuth flow. Create an API key at https://usetrackly.app/settings",
|
|
52
|
+
"isRequired": false,
|
|
51
53
|
"isSecret": true
|
|
52
54
|
}
|
|
53
55
|
]
|