trackly-cli 0.5.2 → 0.6.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 CHANGED
@@ -44,6 +44,7 @@ trackly jobs # List jobs
44
44
  trackly jobs --remote # Filter remote jobs (sets usStates=REMOTE)
45
45
  trackly jobs --region us # Filter by region (us, non_us, all, or a region tag: europe, canada, remote, ...)
46
46
  trackly jobs --job-type internship # Filter by employment type (full_time, internship, all)
47
+ trackly jobs --work-arrangement hybrid,remote # Filter by work arrangement independently
47
48
  trackly jobs --function product # Filter by function
48
49
  trackly jobs --company 243 # Filter by company ID
49
50
  trackly job 1234 # Get job details
@@ -152,7 +153,7 @@ Then use natural language in any of these clients:
152
153
 
153
154
  | Tool | Description |
154
155
  |------|-------------|
155
- | trackly_search_jobs | Search and filter jobs by function, company, location, modality, status |
156
+ | trackly_search_jobs | Search and filter jobs by function, company, region, employment type, work arrangement, and status |
156
157
  | trackly_get_job | Get full details for a specific job |
157
158
  | trackly_search_companies | Semantic company search |
158
159
  | trackly_list_companies | List all tracked companies |
package/bin/trackly CHANGED
@@ -57,7 +57,7 @@ const COMMAND_FLAGS = {
57
57
  login: [],
58
58
  logout: [],
59
59
  whoami: [],
60
- jobs: ['function', 'company', 'region', 'job-type', 'remote', 'status', 'sort', 'limit', 'offset', 'keywords', 'location', 'modality'],
60
+ jobs: ['function', 'company', 'region', 'job-type', 'work-arrangement', 'remote', 'status', 'sort', 'limit', 'offset', 'keywords', 'location', 'modality'],
61
61
  job: [],
62
62
  companies: ['limit', 'offset'],
63
63
  search: [],
@@ -86,6 +86,7 @@ const STATUS_LABELS = {
86
86
  saved: 'saved',
87
87
  dismissed: 'dismissed',
88
88
  };
89
+ const WORK_ARRANGEMENT_VALUES = ['remote', 'hybrid', 'in_person', 'unspecified'];
89
90
 
90
91
  function parseArgs(args) {
91
92
  const result = { _: [], command: null };
@@ -262,7 +263,7 @@ function hasJobListFilters(args) {
262
263
  // (which then raises the migration error) instead of cmdJob(42) silently ignoring
263
264
  // the filter. See PR #13 Cursor Bugbot finding.
264
265
  const deprecated = Object.keys(DEPRECATED_JOB_FLAGS);
265
- const active = ['function', 'company', 'keywords', 'limit', 'region', 'job-type', 'remote', 'offset', 'sort', 'status'];
266
+ const active = ['function', 'company', 'keywords', 'limit', 'region', 'job-type', 'work-arrangement', 'remote', 'offset', 'sort', 'status'];
266
267
  return [...active, ...deprecated].some((key) => args[key] !== undefined);
267
268
  }
268
269
 
@@ -506,11 +507,24 @@ async function cmdJobs(args) {
506
507
  failNow('--remote cannot be combined with --region. Use one or the other (e.g. --region remote).');
507
508
  }
508
509
 
510
+ let workArrangements;
511
+ if (args['work-arrangement'] !== undefined) {
512
+ if (typeof args['work-arrangement'] !== 'string' || !args['work-arrangement'].trim()) {
513
+ failNow('--work-arrangement requires a comma-separated value: remote, hybrid, in_person, unspecified.');
514
+ }
515
+ workArrangements = [...new Set(args['work-arrangement'].split(',').map((value) => value.trim()))];
516
+ const invalid = workArrangements.filter((value) => !WORK_ARRANGEMENT_VALUES.includes(value));
517
+ if (invalid.length > 0) {
518
+ failNow(`Invalid --work-arrangement value "${invalid.join(',')}". Use: ${WORK_ARRANGEMENT_VALUES.join(', ')}.`);
519
+ }
520
+ }
521
+
509
522
  const params = new URLSearchParams();
510
523
  if (args.function) params.set('jobFunction', args.function);
511
524
  if (args.company) params.set('companyId', args.company);
512
525
  if (args.region) params.set('locationFilter', args.region);
513
526
  if (args['job-type']) params.set('jobModality', args['job-type']);
527
+ if (workArrangements) params.set('workArrangements', workArrangements.join(','));
514
528
  if (args.remote) params.set('usStates', 'REMOTE');
515
529
  if (args.status) params.set('status', args.status);
516
530
  if (args.sort) params.set('sort', args.sort);
@@ -994,7 +1008,7 @@ ${color('bold', 'Commands:')}
994
1008
  ${color('cyan', 'login')} Authenticate via Google OAuth
995
1009
  ${color('cyan', 'logout')} Clear credentials
996
1010
  ${color('cyan', 'whoami')} Show current user
997
- ${color('cyan', 'jobs')} List jobs (--function, --company <id>, --region <tag>, --job-type <type>, --remote, --status, --sort, --limit, --offset, --keywords)
1011
+ ${color('cyan', 'jobs')} List jobs (--function, --company <id>, --region <tag>, --job-type <type>, --work-arrangement <values>, --remote, --status, --sort, --limit, --offset, --keywords)
998
1012
  ${color('cyan', 'job <id>')} Get full job detail
999
1013
  ${color('cyan', 'jobs <id>')} Alias for job <id>
1000
1014
  ${color('cyan', 'companies')} List companies
@@ -1036,6 +1050,8 @@ ${color('bold', 'Filter values:')}
1036
1050
  Use a comma-separated list of region tags for multiple regions;
1037
1051
  us/non_us/all cannot be combined.
1038
1052
  --job-type full_time, internship, all (employment type, not work-location)
1053
+ --work-arrangement
1054
+ remote, hybrid, in_person, unspecified (comma-separated)
1039
1055
  --remote Flag (no value). Filters for remote jobs (equivalent to usStates=REMOTE).
1040
1056
  --status new, applying, applied_confirmed, check_later, not_interested, all
1041
1057
  (filters by YOUR application pipeline state)
@@ -72,7 +72,8 @@ You can also pass `TRACKLY_API_KEY` as an environment variable for one-off runs.
72
72
  - `locationFilter`: pass ONE of
73
73
  - a single scalar: `us`, `non_us`, `all`, or a region tag (`europe`, `latam`, `middle_east`, `asia`, `africa`, `canada`, `oceania`, `remote`, `unknown`), OR
74
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 filtering, use the `remote` boolean or `locationFilter: "remote"`. Hybrid and onsite are not exposed as filters.
75
+ - `jobModality`: `full_time`, `internship`, `all` — employment type, NOT work arrangement.
76
+ - `workArrangements`: one or more of `remote`, `hybrid`, `in_person`, `unspecified`. This is an independent axis and combines with region, employment type, and function filters.
76
77
  - `remote` (boolean): filter to remote jobs only. Maps to `usStates=REMOTE`.
77
78
  - `status`: your application pipeline state. Values: `new`, `applied_confirmed`, `check_later`, `not_interested`, `all`. Older `applying` requests are accepted by the backend as a private compatibility alias for `check_later`, but new clients must not emit it.
78
79
  - `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.
package/mcp/server.js CHANGED
@@ -40,6 +40,10 @@ const STATUS_VALUES = ['new', 'applied_confirmed', 'check_later', 'not_intereste
40
40
  // `jobModality` enum matches `jobscout.ts:2870-2875`. Employment type, NOT work-location.
41
41
  const JOB_MODALITIES = ['full_time', 'internship', 'all'];
42
42
 
43
+ // Independent from geography and employment type. Matches the backend's
44
+ // workArrangements query contract and job_postings constraint.
45
+ const WORK_ARRANGEMENTS = ['remote', 'hybrid', 'in_person', 'unspecified'];
46
+
43
47
  // `sort` enum matches backend handler at `jobscout.ts:3053` — NOT the pre-fix
44
48
  // `newest|oldest|company` (backend rejects oldest/company with HTTP 400).
45
49
  const SORT_VALUES = ['newest', 'match'];
@@ -126,7 +130,7 @@ function createServer() {
126
130
 
127
131
  server.tool(
128
132
  'trackly_search_jobs',
129
- '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.',
133
+ '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). Work arrangement is independent from region and employment type: use workArrangements for remote, hybrid, in-person, or unspecified classifications.',
130
134
  {
131
135
  function: z.enum(JOB_FUNCTIONS).optional().describe('Job function filter. One of: ' + JOB_FUNCTIONS.join(', ')),
132
136
  companyId: z.number().optional().describe('Filter jobs by company ID (get from trackly_search_companies)'),
@@ -138,7 +142,10 @@ function createServer() {
138
142
  "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."
139
143
  ),
140
144
  jobModality: z.enum(JOB_MODALITIES).optional().describe(
141
- "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."
145
+ 'Employment type (NOT work arrangement). full_time = full-time roles, internship = internships, all = both. Use workArrangements for remote, hybrid, or in-person classification.'
146
+ ),
147
+ workArrangements: z.array(z.enum(WORK_ARRANGEMENTS)).min(1).max(4).optional().describe(
148
+ 'Work arrangement filter, independent from geography and employment type. Values: remote, hybrid, in_person, unspecified. Multiple values use OR semantics.'
142
149
  ),
143
150
  remote: z.boolean().optional().describe('Filter to remote jobs only (maps to usStates=REMOTE).'),
144
151
  status: z.enum(STATUS_VALUES).optional().describe(
@@ -168,6 +175,7 @@ function createServer() {
168
175
  qs.set('locationFilter', value);
169
176
  }
170
177
  if (params.jobModality !== undefined) qs.set('jobModality', params.jobModality);
178
+ if (params.workArrangements !== undefined) qs.set('workArrangements', params.workArrangements.join(','));
171
179
  if (params.remote === true) qs.set('usStates', 'REMOTE');
172
180
  if (params.status !== undefined) qs.set('status', params.status);
173
181
  if (params.sort !== undefined) qs.set('sort', params.sort);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackly-cli",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
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.5.2",
20
+ "version": "0.6.0",
21
21
  "packages": [
22
22
  {
23
23
  "registryType": "npm",
24
24
  "identifier": "trackly-cli",
25
- "version": "0.5.2",
25
+ "version": "0.6.0",
26
26
  "runtimeHint": "npx",
27
27
  "runtimeArguments": [
28
28
  {