trackly-cli 0.7.2 → 0.8.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 +30 -5
- package/bin/trackly +224 -21
- package/contracts/trackly-apply-tools.json +26 -7
- package/docs/trackly-tools.md +26 -2
- package/lib/agent.js +197 -11
- package/lib/client.js +86 -2
- package/mcp/apply-tools.js +666 -0
- package/mcp/server.js +124 -210
- package/npm-shrinkwrap.json +1177 -0
- package/package.json +7 -2
- package/scripts/verify-audit-exceptions.js +190 -0
- package/security/audit-exceptions.json +70 -0
- package/server.json +2 -2
- package/skills/trackly-apply/SKILL.md +23 -16
- package/skills/trackly-apply/agents/openai.yaml +1 -1
- package/skills/trackly-apply/references/batch-orchestration.md +187 -0
- package/skills/trackly-apply/references/browser-lifecycle.md +93 -0
- package/skills/trackly-apply/references/review-handoff.md +53 -2
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ trackly jobs --function product
|
|
|
35
35
|
|
|
36
36
|
## At a Glance
|
|
37
37
|
|
|
38
|
-
1,900+ companies | 128K+ jobs | 40+ ATS types | CLI + MCP |
|
|
38
|
+
1,900+ companies | 128K+ jobs | 40+ ATS types | CLI + MCP | 36 local MCP tools
|
|
39
39
|
|
|
40
40
|
## CLI Commands
|
|
41
41
|
|
|
@@ -54,6 +54,9 @@ trackly companies search "fintech" # Semantic company search
|
|
|
54
54
|
trackly search "fintech" # Alias for semantic company search
|
|
55
55
|
trackly stats # Show metrics
|
|
56
56
|
trackly status # Alias for stats
|
|
57
|
+
trackly preferences # Show discovery preferences and experience limits
|
|
58
|
+
trackly preferences experience product=2 strategy=5 # Replace role-specific limits
|
|
59
|
+
trackly preferences experience clear # Turn role-specific experience filtering off
|
|
57
60
|
trackly apply 1234 # Mark as applied
|
|
58
61
|
trackly save 1234 # Save a job
|
|
59
62
|
trackly dismiss 1234 # Dismiss a job
|
|
@@ -78,6 +81,8 @@ trackly agent doctor # Verify setup, profile, resume, and compat
|
|
|
78
81
|
|
|
79
82
|
Add `--json` to any command for JSON output. Use `--api-key <key>` or `--base-url <url>` as one-off global flags when needed.
|
|
80
83
|
|
|
84
|
+
`trackly preferences --json` returns only `success`, `experienceFilterV2Available`, and `preferences`. The availability flag controls whether this client may edit role-specific limits; it does not claim that feed or alert enforcement is active. Every update checks that flag and the latest preference revision before writing.
|
|
85
|
+
|
|
81
86
|
## MCP Server Setup
|
|
82
87
|
|
|
83
88
|
### Hosted (Claude co-work, Claude Desktop, ChatGPT)
|
|
@@ -160,6 +165,8 @@ Then use natural language in any of these clients:
|
|
|
160
165
|
| trackly_search_companies | Semantic company search |
|
|
161
166
|
| trackly_list_companies | List all tracked companies |
|
|
162
167
|
| trackly_get_stats | Job tracker metrics and status counts |
|
|
168
|
+
| trackly_get_preferences | Read bounded discovery preferences, edit availability, and save revision |
|
|
169
|
+
| trackly_update_experience_limits | Atomically replace role-specific limits on a job's stated minimum years |
|
|
163
170
|
| trackly_update_status | Mark jobs as applied, saved, or dismissed |
|
|
164
171
|
| trackly_ask | Natural language job search (20/day) |
|
|
165
172
|
| trackly_get_job_brief | Get network brief for a job (company signal, top contact, actions) |
|
|
@@ -170,11 +177,23 @@ Then use natural language in any of these clients:
|
|
|
170
177
|
| trackly_get_application_profile | Get versioned profile answers and provenance |
|
|
171
178
|
| trackly_get_profile_onboarding | Get schema plus missing questions |
|
|
172
179
|
| trackly_update_application_profile | Save scoped answers with optimistic concurrency |
|
|
173
|
-
|
|
|
180
|
+
| trackly_create_apply_batch | Freeze an exact recent-first approved batch |
|
|
181
|
+
| trackly_get_active_apply_batch | Recover the newest unexpired active batch after context loss |
|
|
182
|
+
| trackly_get_apply_batch | Read frozen membership with opaque pagination |
|
|
183
|
+
| trackly_claim_apply_batch | Acquire or renew a batch mutation lease |
|
|
184
|
+
| trackly_checkpoint_apply_batch | Bulk-record redacted inspection checkpoints and human actions |
|
|
185
|
+
| trackly_bind_apply_surface | Bind an initial or recovered browser surface to the existing run and exact requisition URL |
|
|
186
|
+
| trackly_record_apply_surface_evidence | Record current-epoch inventory, missing-tab, close-receipt, and post-close absence evidence |
|
|
187
|
+
| trackly_record_apply_submission_evidence | Record redacted submit-request, success-page, user-confirmation, or provider-receipt evidence |
|
|
188
|
+
| trackly_approve_apply_batch_resume | Approve one exact default resume for the current frozen run set |
|
|
189
|
+
| trackly_certify_apply_batch_truth | Certify final answer and wording fingerprints after every other review-readiness gate |
|
|
190
|
+
| trackly_start_apply_run | Start or reuse a manual-submit browser run |
|
|
174
191
|
| trackly_get_apply_evidence | Get aggregate, value-free beta evidence and release readiness |
|
|
175
192
|
| trackly_get_apply_protocol | Get current workflow and compatibility rules |
|
|
176
193
|
| trackly_report_apply_observation | Send redacted ATS mechanics feedback |
|
|
194
|
+
| trackly_report_apply_observations | Bulk-send up to 20 leased, batch-bound redacted observations |
|
|
177
195
|
| trackly_record_application_outcome | Record review or confirmed submission outcome |
|
|
196
|
+
| trackly_record_application_outcomes | Bulk-record up to 20 leased batch outcomes with per-member conflicts |
|
|
178
197
|
| trackly_prepare_resume | Prepare a private expiring resume file for upload |
|
|
179
198
|
| trackly_verify_prepared_resume | Recheck the confirmed resume immediately before attachment |
|
|
180
199
|
|
|
@@ -188,9 +207,15 @@ trackly login
|
|
|
188
207
|
|
|
189
208
|
Opens your browser for Google sign-in. Tokens are stored locally at `~/.trackly/config.json`.
|
|
190
209
|
|
|
210
|
+
Trackly is currently opening new memberships through a limited, invite-based
|
|
211
|
+
rollout. Existing members can keep using OAuth and API keys normally. If a new
|
|
212
|
+
account is not yet eligible, the CLI returns an invitation-specific message
|
|
213
|
+
instead of suggesting repeated login attempts; request a future seat at
|
|
214
|
+
[usetrackly.app/early-access](https://usetrackly.app/early-access).
|
|
215
|
+
|
|
191
216
|
### Option 2: API Key
|
|
192
217
|
|
|
193
|
-
|
|
218
|
+
Existing members can use an API key for firewalls, headless servers, or CI:
|
|
194
219
|
|
|
195
220
|
1. Sign in at [usetrackly.app](https://usetrackly.app)
|
|
196
221
|
2. Go to **Settings → API Keys → Create**
|
|
@@ -236,7 +261,7 @@ trackly config --base-url http://127.0.0.1:3000 # Point at a different backend
|
|
|
236
261
|
| Job search + filters | Yes | Yes | Yes |
|
|
237
262
|
| Apply/save/dismiss | Yes | Yes | Yes |
|
|
238
263
|
| AI-powered search | Yes (trackly ask) | Yes | Yes |
|
|
239
|
-
| MCP integration | Yes (
|
|
264
|
+
| MCP integration | Yes (36 local tools) | -- | -- |
|
|
240
265
|
| Browser required | No | Yes | No |
|
|
241
266
|
| Best for | Terminal + AI agents | Visual browsing | Custom integrations |
|
|
242
267
|
|
|
@@ -250,7 +275,7 @@ Install trackly-cli (`npm install -g trackly-cli`), authenticate with `trackly l
|
|
|
250
275
|
|
|
251
276
|
**What MCP servers exist for job searching?**
|
|
252
277
|
|
|
253
|
-
trackly-cli includes a built-in MCP server with
|
|
278
|
+
trackly-cli includes a built-in MCP server with 36 tools for job search, company lookup, discovery preferences, application tracking, frozen-batch orchestration, profile onboarding, beta evidence, and manual-submit form preparation. Run `trackly mcp` or use `trackly agent setup --client claude`.
|
|
254
279
|
|
|
255
280
|
**How do I use Claude Code for job hunting?**
|
|
256
281
|
|
package/bin/trackly
CHANGED
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
DEFAULT_BASE_URL,
|
|
6
6
|
apiRequest,
|
|
7
7
|
clearConfig,
|
|
8
|
+
createTracklyAccessError,
|
|
8
9
|
getApiKey,
|
|
9
10
|
getAuthMethod,
|
|
10
11
|
getBaseUrl,
|
|
@@ -65,6 +66,7 @@ const COMMAND_FLAGS = {
|
|
|
65
66
|
search: [],
|
|
66
67
|
stats: [],
|
|
67
68
|
status: [],
|
|
69
|
+
preferences: [],
|
|
68
70
|
apply: [],
|
|
69
71
|
save: [],
|
|
70
72
|
dismiss: [],
|
|
@@ -80,15 +82,32 @@ const COMMAND_FLAGS = {
|
|
|
80
82
|
agent: ['client', 'window-days', 'target-reviewed-runs'],
|
|
81
83
|
};
|
|
82
84
|
const AUTH_HELP_TEXT = `Not authenticated.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
Existing member: trackly login (Google OAuth)
|
|
86
|
+
Existing API key: trackly config --api-key trk_xxx
|
|
87
|
+
New member: Trackly is in a limited rollout. Request access at
|
|
88
|
+
https://usetrackly.app/early-access`;
|
|
86
89
|
const STATUS_LABELS = {
|
|
87
90
|
applied: 'applied',
|
|
88
91
|
saved: 'saved',
|
|
89
92
|
dismissed: 'dismissed',
|
|
90
93
|
};
|
|
91
94
|
const WORK_ARRANGEMENT_VALUES = ['remote', 'hybrid', 'in_person', 'unspecified'];
|
|
95
|
+
const JOB_FUNCTIONS = [
|
|
96
|
+
'product', 'engineering', 'design', 'data', 'marketing', 'sales', 'partnerships',
|
|
97
|
+
'finance', 'strategy', 'operations', 'people', 'legal', 'support', 'other',
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
function loginAccessError(code) {
|
|
101
|
+
return createTracklyAccessError({ code });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function writeLoginProgress(message) {
|
|
105
|
+
if (isJSON()) {
|
|
106
|
+
console.error(message);
|
|
107
|
+
} else {
|
|
108
|
+
console.log(message);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
92
111
|
|
|
93
112
|
function parseArgs(args) {
|
|
94
113
|
const result = { _: [], command: null };
|
|
@@ -225,15 +244,20 @@ function formatMaintenanceForHuman(maintenance) {
|
|
|
225
244
|
|
|
226
245
|
function setApiErrorExit(error, fallbackMessage, exitCode = 1) {
|
|
227
246
|
const maintenance = maintenanceOutput(error);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
247
|
+
const accessError = createTracklyAccessError(error, error?.status);
|
|
248
|
+
if (isJSON() && accessError) {
|
|
249
|
+
printJson({
|
|
250
|
+
status: accessError.status,
|
|
251
|
+
code: accessError.code,
|
|
252
|
+
message: accessError.message,
|
|
253
|
+
retryable: accessError.retryable,
|
|
254
|
+
});
|
|
255
|
+
} else if (maintenance && isJSON()) {
|
|
234
256
|
printJson(maintenance);
|
|
235
|
-
} else {
|
|
257
|
+
} else if (maintenance) {
|
|
236
258
|
outputError(formatMaintenanceForHuman(maintenance));
|
|
259
|
+
} else {
|
|
260
|
+
outputError(error?.error || error?.message || fallbackMessage);
|
|
237
261
|
}
|
|
238
262
|
process.exitCode = exitCode;
|
|
239
263
|
}
|
|
@@ -252,6 +276,39 @@ function parseJobId(value) {
|
|
|
252
276
|
return id;
|
|
253
277
|
}
|
|
254
278
|
|
|
279
|
+
function parseExperienceLimits(values) {
|
|
280
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
281
|
+
throw new Error('Provide at least one role limit or `clear` (for example, `product=2 strategy=5`).');
|
|
282
|
+
}
|
|
283
|
+
if (values.includes('clear')) {
|
|
284
|
+
if (values.length !== 1) throw new Error('`clear` cannot be combined with role limits.');
|
|
285
|
+
return {};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const limits = {};
|
|
289
|
+
for (const value of values) {
|
|
290
|
+
const match = /^([^=]+)=([^=]+)$/.exec(String(value));
|
|
291
|
+
if (!match) throw new Error(`Invalid limit "${value}". Use role=years (for example, product=2).`);
|
|
292
|
+
const jobFunction = match[1].trim().toLowerCase();
|
|
293
|
+
const rawYears = match[2].trim();
|
|
294
|
+
if (!JOB_FUNCTIONS.includes(jobFunction)) {
|
|
295
|
+
throw new Error(`Unknown job function "${jobFunction}". Use: ${JOB_FUNCTIONS.join(', ')}.`);
|
|
296
|
+
}
|
|
297
|
+
if (Object.prototype.hasOwnProperty.call(limits, jobFunction)) {
|
|
298
|
+
throw new Error(`Duplicate limit for ${jobFunction}. Provide each job function once.`);
|
|
299
|
+
}
|
|
300
|
+
if (!/^\d+$/.test(rawYears)) {
|
|
301
|
+
throw new Error(`Limit for ${jobFunction} must be an integer from 0 to 60.`);
|
|
302
|
+
}
|
|
303
|
+
const years = Number(rawYears);
|
|
304
|
+
if (!Number.isInteger(years) || years < 0 || years > 60) {
|
|
305
|
+
throw new Error(`Limit for ${jobFunction} must be an integer from 0 to 60.`);
|
|
306
|
+
}
|
|
307
|
+
limits[jobFunction] = years;
|
|
308
|
+
}
|
|
309
|
+
return limits;
|
|
310
|
+
}
|
|
311
|
+
|
|
255
312
|
// Flags deprecated in the API-docs-plan fixup. Still accepted by parseArgs so old
|
|
256
313
|
// scripts don't ERR_NOT_FOUND, but cmdJobs hard-rejects them with a migration hint
|
|
257
314
|
// — silent no-op would be worse than noise (per PR #13 Codex P1 finding).
|
|
@@ -321,11 +378,11 @@ function getPublicConfig() {
|
|
|
321
378
|
|
|
322
379
|
// -- Commands --
|
|
323
380
|
|
|
324
|
-
async function cmdLogin() {
|
|
381
|
+
async function cmdLogin({ openUrl = openExternalUrl } = {}) {
|
|
325
382
|
const baseUrl = getBaseUrl();
|
|
326
383
|
const state = crypto.randomBytes(16).toString('hex');
|
|
327
384
|
|
|
328
|
-
|
|
385
|
+
writeLoginProgress(color('cyan', 'Opening browser for Google login...'));
|
|
329
386
|
|
|
330
387
|
let activeServer = null;
|
|
331
388
|
|
|
@@ -348,6 +405,21 @@ async function cmdLogin() {
|
|
|
348
405
|
|
|
349
406
|
const token = url.searchParams.get('token');
|
|
350
407
|
const refreshToken = url.searchParams.get('refreshToken');
|
|
408
|
+
const accessError = loginAccessError(url.searchParams.get('error'));
|
|
409
|
+
if (accessError) {
|
|
410
|
+
res.writeHead(403, {
|
|
411
|
+
'Content-Type': 'text/html',
|
|
412
|
+
'Referrer-Policy': 'no-referrer',
|
|
413
|
+
'Cache-Control': 'no-store',
|
|
414
|
+
});
|
|
415
|
+
res.end('<html><body><h2>Trackly access is limited right now.</h2><p>Return to your terminal for next steps.</p></body></html>');
|
|
416
|
+
const loginError = new Error(accessError.message);
|
|
417
|
+
loginError.code = accessError.code;
|
|
418
|
+
loginError.status = accessError.status;
|
|
419
|
+
loginError.retryable = accessError.retryable;
|
|
420
|
+
settle(loginError);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
351
423
|
if (token) {
|
|
352
424
|
const config = loadConfig();
|
|
353
425
|
const hadApiKey = Boolean(config.apiKey);
|
|
@@ -382,9 +454,9 @@ p{color:#a1a1aa;font-size:15px;line-height:1.5}
|
|
|
382
454
|
</div>
|
|
383
455
|
</body>
|
|
384
456
|
</html>`);
|
|
385
|
-
|
|
457
|
+
writeLoginProgress(color('green', 'Login successful!'));
|
|
386
458
|
if (hadApiKey) {
|
|
387
|
-
|
|
459
|
+
writeLoginProgress('Switched to OAuth. Stored API key cleared.');
|
|
388
460
|
}
|
|
389
461
|
settle();
|
|
390
462
|
} else {
|
|
@@ -405,10 +477,12 @@ p{color:#a1a1aa;font-size:15px;line-height:1.5}
|
|
|
405
477
|
|
|
406
478
|
const finish = () => {
|
|
407
479
|
if (error) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
480
|
+
if (!createTracklyAccessError(error, 403)) {
|
|
481
|
+
writeLoginProgress('\nLogin failed. Existing members can try API key auth:');
|
|
482
|
+
writeLoginProgress(' 1. Sign in at https://usetrackly.app');
|
|
483
|
+
writeLoginProgress(' 2. Go to Settings → API Keys → Create');
|
|
484
|
+
writeLoginProgress(' 3. Run: trackly config --api-key <your-key>');
|
|
485
|
+
}
|
|
412
486
|
reject(error);
|
|
413
487
|
} else {
|
|
414
488
|
resolve();
|
|
@@ -436,10 +510,10 @@ p{color:#a1a1aa;font-size:15px;line-height:1.5}
|
|
|
436
510
|
settle(new Error(`OS assigned an out-of-range callback port (${port}); expected 1024-65535.`));
|
|
437
511
|
return;
|
|
438
512
|
}
|
|
439
|
-
|
|
513
|
+
writeLoginProgress(color('dim', `Callback server on port ${port}`));
|
|
440
514
|
const loginUrl = `${baseUrl}/auth/google/cli?port=${port}&state=${state}`;
|
|
441
|
-
if (!
|
|
442
|
-
|
|
515
|
+
if (!openUrl(loginUrl)) {
|
|
516
|
+
writeLoginProgress(`Open this URL in your browser:\n${loginUrl}`);
|
|
443
517
|
}
|
|
444
518
|
});
|
|
445
519
|
|
|
@@ -461,6 +535,7 @@ p{color:#a1a1aa;font-size:15px;line-height:1.5}
|
|
|
461
535
|
process.on('SIGINT', onSigint);
|
|
462
536
|
try {
|
|
463
537
|
await tryLogin();
|
|
538
|
+
if (isJSON()) printJson({ success: true, authMethod: 'oauth' });
|
|
464
539
|
} finally {
|
|
465
540
|
process.removeListener('SIGINT', onSigint);
|
|
466
541
|
}
|
|
@@ -612,6 +687,119 @@ async function cmdStats() {
|
|
|
612
687
|
}
|
|
613
688
|
}
|
|
614
689
|
|
|
690
|
+
function renderPreferences(result) {
|
|
691
|
+
const user = result?.user || {};
|
|
692
|
+
const preferences = user.preferences || result?.preferences || {};
|
|
693
|
+
const limits = preferences.experienceLimitsByJobFunction || {};
|
|
694
|
+
|
|
695
|
+
console.log(color('bold', 'Discovery preferences'));
|
|
696
|
+
const selected = Array.isArray(preferences.wantedJobFunctions)
|
|
697
|
+
? preferences.wantedJobFunctions
|
|
698
|
+
: [];
|
|
699
|
+
console.log(` Target roles: ${selected.length > 0 ? selected.join(', ') : 'All roles'}`);
|
|
700
|
+
console.log(` Experience limits: ${Object.keys(limits).length > 0 ? 'Configured' : 'Not configured'}`);
|
|
701
|
+
for (const [jobFunction, years] of Object.entries(limits)) {
|
|
702
|
+
console.log(` ${jobFunction}: Up to ${years} year${years === 1 ? '' : 's'} required`);
|
|
703
|
+
}
|
|
704
|
+
console.log(color('dim', ' Jobs with no stated minimum remain visible.'));
|
|
705
|
+
console.log(color('dim', ' This confirms what you saved; it does not show whether Trackly is currently applying the limits.'));
|
|
706
|
+
if (Number.isSafeInteger(preferences.discoveryPreferenceRevision)) {
|
|
707
|
+
console.log(color('dim', ` Revision: ${preferences.discoveryPreferenceRevision}`));
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function projectPreferenceResponse(result, experienceFilterV2Available = null) {
|
|
712
|
+
const user = result?.user || {};
|
|
713
|
+
return {
|
|
714
|
+
success: result?.success === true,
|
|
715
|
+
experienceFilterV2Available: experienceFilterV2Available === null
|
|
716
|
+
? user.experienceFilterV2Available === true
|
|
717
|
+
: experienceFilterV2Available === true,
|
|
718
|
+
preferences: user.preferences || result?.preferences || {},
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function handlePreferenceConflict(error) {
|
|
723
|
+
const message = 'Preferences changed on another device. Review `trackly preferences`, then run the update again.';
|
|
724
|
+
if (isJSON()) {
|
|
725
|
+
printJson({
|
|
726
|
+
status: error.status,
|
|
727
|
+
code: error.error,
|
|
728
|
+
message,
|
|
729
|
+
preferences: error.preferences,
|
|
730
|
+
discoveryPreferenceRevision: error.discoveryPreferenceRevision,
|
|
731
|
+
});
|
|
732
|
+
} else {
|
|
733
|
+
outputError(message);
|
|
734
|
+
}
|
|
735
|
+
process.exitCode = 1;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
async function cmdPreferences(args) {
|
|
739
|
+
if (!hasAuth()) failNow(AUTH_HELP_TEXT);
|
|
740
|
+
|
|
741
|
+
const subcommand = args._[0];
|
|
742
|
+
if (subcommand == null || subcommand === 'show') {
|
|
743
|
+
if (args._.length > (subcommand === 'show' ? 1 : 0)) {
|
|
744
|
+
failNow('Usage: trackly preferences [show]');
|
|
745
|
+
}
|
|
746
|
+
try {
|
|
747
|
+
const result = await apiRequest('GET', '/api/jobscout/me');
|
|
748
|
+
if (isJSON()) return printJson(projectPreferenceResponse(result));
|
|
749
|
+
renderPreferences(result);
|
|
750
|
+
return;
|
|
751
|
+
} catch (error) {
|
|
752
|
+
setApiErrorExit(error, 'Failed to fetch preferences');
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (subcommand !== 'experience') {
|
|
758
|
+
failNow('Unknown preferences subcommand. Use: trackly preferences [show] | trackly preferences experience <role=years...|clear>');
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
let experienceLimitsByJobFunction;
|
|
762
|
+
try {
|
|
763
|
+
experienceLimitsByJobFunction = parseExperienceLimits(args._.slice(1));
|
|
764
|
+
} catch (error) {
|
|
765
|
+
failNow(error.message);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
try {
|
|
769
|
+
// Read immediately before the write and send the authoritative revision.
|
|
770
|
+
// The backend rejects a stale value with 409 instead of letting this
|
|
771
|
+
// process overwrite a newer iOS, web, macOS, CLI, or MCP save.
|
|
772
|
+
const current = await apiRequest('GET', '/api/jobscout/me');
|
|
773
|
+
if (current?.user?.experienceFilterV2Available !== true) {
|
|
774
|
+
failNow('Role-specific experience preferences are not available for this account yet. No changes were saved.');
|
|
775
|
+
}
|
|
776
|
+
const expectedPreferenceRevision = current?.user?.preferences?.discoveryPreferenceRevision;
|
|
777
|
+
if (!Number.isSafeInteger(expectedPreferenceRevision) || expectedPreferenceRevision < 0) {
|
|
778
|
+
failNow('Trackly did not return a valid preference revision. No changes were saved.');
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const result = await apiRequest('PUT', '/api/jobscout/preferences', {
|
|
782
|
+
experienceLimitsByJobFunction,
|
|
783
|
+
experienceFilterVersion: 2,
|
|
784
|
+
expectedPreferenceRevision,
|
|
785
|
+
});
|
|
786
|
+
if (isJSON()) return printJson(projectPreferenceResponse(result, true));
|
|
787
|
+
console.log(color(
|
|
788
|
+
'green',
|
|
789
|
+
Object.keys(experienceLimitsByJobFunction).length > 0
|
|
790
|
+
? 'Role-specific experience limits updated.'
|
|
791
|
+
: 'Role-specific experience filtering turned off.',
|
|
792
|
+
));
|
|
793
|
+
renderPreferences(result);
|
|
794
|
+
} catch (error) {
|
|
795
|
+
if (error?.status === 409 && error?.error === 'preference_revision_conflict') {
|
|
796
|
+
handlePreferenceConflict(error);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
setApiErrorExit(error, 'Failed to update experience limits');
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
615
803
|
const ACTION_TO_COMMAND = { applied: 'apply', saved: 'save', dismissed: 'dismiss' };
|
|
616
804
|
|
|
617
805
|
// CLI surface accepts applied|saved|dismissed (legacy human-friendly terms).
|
|
@@ -971,6 +1159,8 @@ async function cmdAgent(args) {
|
|
|
971
1159
|
console.log(color('bold', 'Trackly Apply doctor'));
|
|
972
1160
|
console.log(`Overall: ${color(report.ok ? 'green' : 'yellow', report.ok ? 'ready' : 'needs attention')}`);
|
|
973
1161
|
console.log(`Authenticated: ${report.authenticated ? 'yes' : 'no'}`);
|
|
1162
|
+
console.log(`CLI: ${report.cliVersion}; MCP contract: ${report.mcpContractVersion}`);
|
|
1163
|
+
console.log(`Skill: ${report.skillVersion}; digest: ${report.skillPackIntegrity.expectedDigest || 'unavailable'}`);
|
|
974
1164
|
console.log(`Protocol: ${report.protocol?.version || 'unavailable'}; compatible=${report.compatible ? 'yes' : 'no'}`);
|
|
975
1165
|
console.log(`Semantic browser + computer use: ${report.browserControl.semanticReady ? 'ready' : 'not verified'}`);
|
|
976
1166
|
for (const client of report.clients) {
|
|
@@ -1064,6 +1254,8 @@ ${color('bold', 'Commands:')}
|
|
|
1064
1254
|
${color('cyan', 'search <query>')} Semantic company search
|
|
1065
1255
|
${color('cyan', 'stats')} Show metrics dashboard
|
|
1066
1256
|
${color('cyan', 'status')} Alias for stats
|
|
1257
|
+
${color('cyan', 'preferences')} Show discovery preferences
|
|
1258
|
+
${color('cyan', 'preferences experience')} Replace role limits (role=years; clear turns off)
|
|
1067
1259
|
${color('cyan', 'apply <id>')} Mark job as applied
|
|
1068
1260
|
${color('cyan', 'save <id>')} Save a job
|
|
1069
1261
|
${color('cyan', 'dismiss <id>')} Dismiss a job
|
|
@@ -1095,6 +1287,10 @@ ${color('bold', 'Flags:')}
|
|
|
1095
1287
|
${color('bold', 'Filter values:')}
|
|
1096
1288
|
--function product, engineering, design, data, marketing, sales, partnerships,
|
|
1097
1289
|
finance, strategy, operations, people, legal, support, other (comma-separated)
|
|
1290
|
+
experience limits use the same role values, for example:
|
|
1291
|
+
trackly preferences experience product=2 strategy=5
|
|
1292
|
+
When enforcement is active, includes jobs whose stated minimum is up to the limit.
|
|
1293
|
+
Jobs with no stated minimum remain visible.
|
|
1098
1294
|
--region us, non_us, all, or a region tag (europe, canada, remote, ...)
|
|
1099
1295
|
Use a comma-separated list of region tags for multiple regions;
|
|
1100
1296
|
us/non_us/all cannot be combined.
|
|
@@ -1208,6 +1404,9 @@ async function main() {
|
|
|
1208
1404
|
case 'status':
|
|
1209
1405
|
await cmdStats();
|
|
1210
1406
|
break;
|
|
1407
|
+
case 'preferences':
|
|
1408
|
+
await cmdPreferences(args);
|
|
1409
|
+
break;
|
|
1211
1410
|
case 'apply':
|
|
1212
1411
|
await cmdUpdateStatus(args._[0], 'applied');
|
|
1213
1412
|
break;
|
|
@@ -1276,14 +1475,18 @@ if (require.main === module) {
|
|
|
1276
1475
|
module.exports = {
|
|
1277
1476
|
COMMAND_FLAGS,
|
|
1278
1477
|
UNIVERSAL_FLAGS,
|
|
1478
|
+
cmdLogin,
|
|
1279
1479
|
formatMaintenanceForHuman,
|
|
1280
1480
|
getPublicConfig,
|
|
1281
1481
|
hasJobListFilters,
|
|
1282
1482
|
levenshtein,
|
|
1483
|
+
loginAccessError,
|
|
1283
1484
|
main,
|
|
1284
1485
|
nearestFlag,
|
|
1285
1486
|
normalizeBaseUrlValue,
|
|
1286
1487
|
openExternalUrl,
|
|
1287
1488
|
parseArgs,
|
|
1489
|
+
parseExperienceLimits,
|
|
1288
1490
|
parseJobId,
|
|
1491
|
+
writeLoginProgress,
|
|
1289
1492
|
};
|
|
@@ -1,22 +1,41 @@
|
|
|
1
1
|
{
|
|
2
|
-
"contractVersion": "3.
|
|
2
|
+
"contractVersion": "3.3.1",
|
|
3
3
|
"constants": {
|
|
4
4
|
"applyScenarioCodes": ["browser_reclaim", "resume_upload", "resume_parser_recheck", "semantic_boolean_commit", "custom_select_commit", "multi_step_navigation", "free_text_voice", "required_error_sweep", "final_consent", "handoff_reclaim", "critical_contact_integrity", "manual_submit_boundary", "job_identity_match"],
|
|
5
|
-
"applyBrowserSurfaces": ["codex_in_app", "chrome_extension", "claude_in_chrome"]
|
|
5
|
+
"applyBrowserSurfaces": ["codex_in_app", "chrome_extension", "claude_in_chrome"],
|
|
6
|
+
"applyCheckpointActionCodes": ["answer/unknown", "auth/sign_in", "auth/otp", "captcha/before_form", "captcha/at_submit", "artifact/upload_required", "legal/decision_required", "consent/decision_required", "review/manual_submit", "trust/origin_mismatch", "observability/unverifiable_state"],
|
|
7
|
+
"applyCheckpointPacketPhases": ["first_pass", "delta"],
|
|
8
|
+
"applySurfaceBindingReasons": ["initial_binding", "recovery_binding"],
|
|
9
|
+
"applySurfaceEvidenceTypes": ["surface_inventory_reconciled", "surface_missing", "surface_close_attempted", "surface_close_receipt", "surface_post_close_absent", "surface_close_failed"],
|
|
10
|
+
"applySurfaceOwnershipStates": ["controller_owned", "user_owned", "controller_user_union", "unresolved"],
|
|
11
|
+
"applySubmissionEvidenceTypes": ["request_accepted", "confirmation_detected", "provider_receipt_detected"],
|
|
12
|
+
"applySubmissionEvidenceSources": ["submission_request", "success_page", "user_confirmation", "provider_receipt", "post_submit_receipt"]
|
|
6
13
|
},
|
|
7
14
|
"tools": {
|
|
8
|
-
"trackly_get_apply_queue": "{limit:z.number().int().min(1).max(100).optional()}",
|
|
15
|
+
"trackly_get_apply_queue": "{limit:z.number().int().min(1).max(100).optional(),cursor:z.string().min(1).max(2048).optional()}",
|
|
9
16
|
"trackly_get_application_profile": "{includeSensitive:z.boolean().optional(),provider:z.string().max(100).optional(),companyId:z.string().max(100).optional()}",
|
|
10
17
|
"trackly_get_profile_onboarding": "{}",
|
|
11
18
|
"trackly_update_application_profile": "{expectedRevision:z.number().int().min(1),source:z.enum(['web','ios','macos','codex','claude','mcp']).optional(),changes:z.array(z.discriminatedUnion('scope',[z.object({key:z.string().min(1).max(200),state:z.enum(['unknown','answered','intentionally_blank','declined']),value:z.any().optional(),scope:z.literal('global'),questionLabel:z.string().max(1000).optional()}),z.object({key:z.string().min(1).max(200),state:z.enum(['unknown','answered','intentionally_blank','declined']),value:z.any().optional(),scope:z.literal('provider'),scopeValue:z.string().min(1).max(200),questionLabel:z.string().max(1000).optional()}),z.object({key:z.string().min(1).max(200),state:z.enum(['unknown','answered','intentionally_blank','declined']),value:z.any().optional(),scope:z.literal('company'),scopeValue:z.string().min(1).max(200),questionLabel:z.string().max(1000).optional()})])).max(100).optional(),education:z.array(z.object({school:z.string().min(1).max(500),degree:z.string().max(500).nullable().optional(),fieldOfStudy:z.string().max(500).nullable().optional(),gpa:z.string().max(50).nullable().optional(),startDate:z.string().max(50).nullable().optional(),endDate:z.string().max(50).nullable().optional()})).max(20).optional(),confirmProfile:z.boolean().optional(),sensitiveStorageConsent:z.boolean().optional()}",
|
|
12
|
-
"
|
|
19
|
+
"trackly_create_apply_batch": "{limit:z.number().int().min(1).max(100),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY)}",
|
|
20
|
+
"trackly_get_active_apply_batch": "{limit:z.number().int().min(1).max(APPLY_BATCH_MAX_MEMBERS).optional(),cursor:z.string().min(1).max(2048).optional(),actionLimit:z.number().int().min(1).max(APPLY_BATCH_MAX_MEMBERS).optional(),actionCursor:z.string().min(1).max(2048).optional()}",
|
|
21
|
+
"trackly_get_apply_batch": "{batchId:z.number().int().min(1),limit:z.number().int().min(1).max(APPLY_BATCH_MAX_MEMBERS).optional(),cursor:z.string().min(1).max(2048).optional(),actionLimit:z.number().int().min(1).max(APPLY_BATCH_MAX_MEMBERS).optional(),actionCursor:z.string().min(1).max(2048).optional()}",
|
|
22
|
+
"trackly_claim_apply_batch": "{batchId:z.number().int().min(1),expectedRevision:z.number().int().min(1),leaseOwner:z.string().min(1).max(1024),leaseToken:z.string().min(1).max(1024),leaseDurationMs:z.number().int().min(15000).max(300000)}",
|
|
23
|
+
"trackly_checkpoint_apply_batch": "{batchId:z.number().int().min(1),leaseToken:z.string().min(1).max(1024),checkpoints:z.array(z.object({memberId:z.number().int().min(1),runId:z.number().int().min(1),expectedMemberVersion:z.number().int().min(1),expectedInspectionEpoch:z.number().int().min(0),inspectionEpoch:z.number().int().min(0),packetPhase:z.enum(APPLY_CHECKPOINT_PACKET_PHASES).optional(),knownFieldsCommitted:z.boolean(),resolvedActionIds:z.array(z.string().regex(/^[1-9][0-9]*$/)).max(APPLY_BATCH_MAX_ACTIONS_PER_CHECKPOINT).optional(),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY),actions:z.array(z.object({actionCode:z.enum(APPLY_CHECKPOINT_ACTION_CODES),continuationAllowed:z.boolean(),fieldFingerprint:z.string().regex(/^[a-f0-9]{64}$/).optional()})).min(1).max(APPLY_BATCH_MAX_ACTIONS_PER_CHECKPOINT)})).min(1).max(APPLY_BATCH_MAX_CHECKPOINTS_PER_REQUEST)}",
|
|
24
|
+
"trackly_bind_apply_surface": "{batchId:z.number().int().min(1),memberId:z.number().int().min(1),runId:z.number().int().min(1),expectedMemberVersion:z.number().int().min(1),expectedInspectionEpoch:z.number().int().min(0),leaseToken:z.string().min(1).max(1024),browserBindingHash:z.string().regex(/^[a-f0-9]{64}$/),browserSurface:z.enum(APPLY_BROWSER_SURFACES),adapterCode:z.string().regex(SAFE_OBSERVATION_CODE),bindingReason:z.enum(APPLY_SURFACE_BINDING_REASONS),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY)}",
|
|
25
|
+
"trackly_record_apply_surface_evidence": "{batchId:z.number().int().min(1),memberId:z.number().int().min(1),runId:z.number().int().min(1),expectedMemberVersion:z.number().int().min(1),expectedInspectionEpoch:z.number().int().min(1),leaseToken:z.string().min(1).max(1024),browserBindingHash:z.string().regex(/^[a-f0-9]{64}$/),browserSurface:z.enum(APPLY_BROWSER_SURFACES),adapterCode:z.string().regex(SAFE_OBSERVATION_CODE),ownershipState:z.enum(APPLY_SURFACE_OWNERSHIP_STATES),completeInventory:z.boolean(),evidenceType:z.enum(APPLY_SURFACE_EVIDENCE_TYPES),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY)}",
|
|
26
|
+
"trackly_record_apply_submission_evidence": "{batchId:z.number().int().min(1),memberId:z.number().int().min(1),runId:z.number().int().min(1),expectedMemberVersion:z.number().int().min(1),expectedInspectionEpoch:z.number().int().min(1),leaseToken:z.string().min(1).max(1024),browserBindingHash:z.string().regex(/^[a-f0-9]{64}$/),evidenceType:z.enum(APPLY_SUBMISSION_EVIDENCE_TYPES),evidenceSource:z.enum(APPLY_SUBMISSION_EVIDENCE_SOURCES),evidenceFingerprint:z.string().regex(/^[a-f0-9]{64}$/),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY)}",
|
|
27
|
+
"trackly_approve_apply_batch_resume": "{batchId:z.number().int().min(1),leaseToken:z.string().min(1).max(1024),membershipHash:z.string().regex(/^[a-f0-9]{64}$/),profileRevision:z.number().int().min(0),resumeId:z.number().int().min(1),resumeSha256:z.string().regex(/^[a-f0-9]{64}$/),resumeFilename:z.string().min(1).max(255),resumeSizeBytes:z.number().int().min(1),memberRuns:z.array(z.object({memberId:z.number().int().min(1),runId:z.number().int().min(1),memberVersion:z.number().int().min(1),inspectionEpoch:z.number().int().min(0)})).min(1).max(100),expiresAt:z.string().datetime(),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY)}",
|
|
28
|
+
"trackly_certify_apply_batch_truth": "truthCertificationSchema",
|
|
29
|
+
"trackly_start_apply_run": "startApplyRunSchema",
|
|
13
30
|
"trackly_get_apply_evidence": "{windowDays:z.number().int().min(1).max(365).optional(),targetReviewedRuns:z.number().int().min(1).max(1000).optional()}",
|
|
14
31
|
"trackly_get_apply_protocol": "{}",
|
|
15
|
-
"trackly_report_apply_observation": "{runId:z.number().int().min(1),provider:z.string().regex(SAFE_OBSERVATION_CODE),fieldLabel:z.string().min(1).max(1000),observationType:z.string().regex(SAFE_OBSERVATION_CODE),resolutionCode:z.string().regex(SAFE_OBSERVATION_CODE).optional(),metadata:z.object({controlType:z.string().regex(SAFE_OBSERVATION_CODE).optional(),required:z.boolean().optional(),errorCode:z.string().regex(SAFE_OBSERVATION_CODE).optional(),committed:z.boolean(),scenarioCode:z.enum(APPLY_SCENARIO_CODES),browserSurface:z.enum(APPLY_BROWSER_SURFACES),browserBindingHash:z.string().regex(/^[a-f0-9]{64}$/).optional(),resumedAfterHandoff:z.boolean().optional()})}",
|
|
16
|
-
"
|
|
32
|
+
"trackly_report_apply_observation": "{runId:z.number().int().min(1),batchId:z.number().int().min(1).optional(),memberId:z.number().int().min(1).optional(),inspectionEpoch:z.number().int().min(0).optional(),leaseToken:z.string().min(1).max(1024).optional(),provider:z.string().regex(SAFE_OBSERVATION_CODE),fieldLabel:z.string().min(1).max(1000),observationType:z.string().regex(SAFE_OBSERVATION_CODE),resolutionCode:z.string().regex(SAFE_OBSERVATION_CODE).optional(),metadata:z.object({controlType:z.string().regex(SAFE_OBSERVATION_CODE).optional(),required:z.boolean().optional(),errorCode:z.string().regex(SAFE_OBSERVATION_CODE).optional(),committed:z.boolean(),scenarioCode:z.enum(APPLY_SCENARIO_CODES),browserSurface:z.enum(APPLY_BROWSER_SURFACES),browserBindingHash:z.string().regex(/^[a-f0-9]{64}$/).optional(),resumedAfterHandoff:z.boolean().optional()})}",
|
|
33
|
+
"trackly_report_apply_observations": "{observations:z.array(z.object({runId:z.number().int().min(1),batchId:z.number().int().min(1),memberId:z.number().int().min(1),inspectionEpoch:z.number().int().min(0),leaseToken:z.string().min(1).max(1024),provider:z.string().regex(SAFE_OBSERVATION_CODE),fieldLabel:z.string().min(1).max(1000),observationType:z.string().regex(SAFE_OBSERVATION_CODE),resolutionCode:z.string().regex(SAFE_OBSERVATION_CODE).optional(),metadata:z.object({controlType:z.string().regex(SAFE_OBSERVATION_CODE).optional(),required:z.boolean().optional(),errorCode:z.string().regex(SAFE_OBSERVATION_CODE).optional(),committed:z.boolean(),scenarioCode:z.enum(APPLY_SCENARIO_CODES),browserSurface:z.enum(APPLY_BROWSER_SURFACES),browserBindingHash:z.string().regex(/^[a-f0-9]{64}$/).optional(),resumedAfterHandoff:z.boolean().optional()})})).min(1).max(APPLY_BATCH_MAX_BULK_MUTATIONS)}",
|
|
34
|
+
"trackly_record_application_outcome": "{runId:z.number().int().min(1),batchId:z.number().int().min(1).optional(),memberId:z.number().int().min(1).optional(),inspectionEpoch:z.number().int().min(0).optional(),leaseToken:z.string().min(1).max(1024).optional(),outcome:z.enum(['review_ready','submitted','failed','blocked']),confirmation:z.enum(['user_confirmation','success_page']).optional()}",
|
|
35
|
+
"trackly_record_application_outcomes": "{outcomes:z.array(z.object({runId:z.number().int().min(1),batchId:z.number().int().min(1),memberId:z.number().int().min(1),inspectionEpoch:z.number().int().min(0),leaseToken:z.string().min(1).max(1024),outcome:z.enum(['review_ready','submitted','failed','blocked']),confirmation:z.enum(['user_confirmation','success_page']).optional()})).min(1).max(APPLY_BATCH_MAX_BULK_MUTATIONS)}",
|
|
17
36
|
"trackly_prepare_resume": "{runId:z.number().int().min(1),browserSurface:z.enum(APPLY_BROWSER_SURFACES),browserBindingHash:z.string().regex(/^[a-f0-9]{64}$/)}",
|
|
18
37
|
"trackly_verify_prepared_resume": {
|
|
19
|
-
"local": "{runId:z.number().int().min(1),confirmationId:z.string().min(1).max(200),exactLocalPath:z.string().min(1).max(4096),sha256:z.string().regex(/^[a-f0-9]{64}$/i),sizeBytes:z.number().int().min(1),expiresAt:z.string().datetime()}",
|
|
38
|
+
"local": "{runId:z.number().int().min(1),resumeId:z.number().int().min(1),confirmationId:z.string().min(1).max(200),exactLocalPath:z.string().min(1).max(4096),sha256:z.string().regex(/^[a-f0-9]{64}$/i),sizeBytes:z.number().int().min(1),expiresAt:z.string().datetime()}",
|
|
20
39
|
"hosted": "{runId:z.number().int().min(1),confirmationId:z.string().min(1).max(200)}"
|
|
21
40
|
}
|
|
22
41
|
}
|
package/docs/trackly-tools.md
CHANGED
|
@@ -65,6 +65,11 @@ trackly config --api-key trk_xxxxxxxxxxxxxxxxxxxx
|
|
|
65
65
|
|
|
66
66
|
You can also pass `TRACKLY_API_KEY` as an environment variable for one-off runs.
|
|
67
67
|
|
|
68
|
+
Existing memberships work across the CLI and local MCP server. New memberships
|
|
69
|
+
are invite-only during the limited rollout; access errors link to
|
|
70
|
+
https://usetrackly.app/early-access instead of recommending repeated OAuth or
|
|
71
|
+
API-key creation attempts.
|
|
72
|
+
|
|
68
73
|
### Available Tools
|
|
69
74
|
|
|
70
75
|
- **trackly_search_jobs** — Search/filter jobs.
|
|
@@ -82,6 +87,12 @@ You can also pass `TRACKLY_API_KEY` as an environment variable for one-off runs.
|
|
|
82
87
|
- **trackly_search_companies** — Semantic company search
|
|
83
88
|
- **trackly_list_companies** — List all tracked companies with job counts
|
|
84
89
|
- **trackly_get_stats** — Job tracker metrics dashboard
|
|
90
|
+
- **trackly_get_preferences** — Read a bounded preference response containing only `success`, `experienceFilterV2Available`, and the authenticated user's discovery `preferences`, including selected roles, saved role-specific limits, and the revision required for a safe update. Availability authorizes editing; it does not report feed or alert enforcement.
|
|
91
|
+
- **trackly_update_experience_limits** — Atomically replace the complete role-specific experience-limit map.
|
|
92
|
+
- `experienceLimitsByJobFunction`: object whose keys are the 14 canonical job-function values and whose values are integer years from `0` through `60`. An empty object turns this filter off.
|
|
93
|
+
- `expectedPreferenceRevision`: non-negative safe integer from the latest `trackly_get_preferences` result. A stale revision is rejected; refetch and reconcile with the user rather than retrying blindly.
|
|
94
|
+
- The tool checks `experienceFilterV2Available` immediately before writing and refuses without a PUT unless it is exactly `true`.
|
|
95
|
+
- When server-side enforcement is active, a job remains visible when its stated minimum is less than or equal to that role's limit. Jobs with no stated minimum remain visible.
|
|
85
96
|
- **trackly_update_status** — Mark a job as applied, saved, or dismissed
|
|
86
97
|
- **trackly_ask** — Natural language job search (20/day limit)
|
|
87
98
|
- **trackly_get_job_brief** — Get network brief for a job (company signal, top contact, actions)
|
|
@@ -92,15 +103,27 @@ You can also pass `TRACKLY_API_KEY` as an environment variable for one-off runs.
|
|
|
92
103
|
- **trackly_get_application_profile** — Get the versioned, scoped application profile.
|
|
93
104
|
- **trackly_get_profile_onboarding** — Get backend-owned questions plus only missing/unconfirmed answers.
|
|
94
105
|
- **trackly_update_application_profile** — Save explicit answer states with optimistic concurrency and global/provider/company scope.
|
|
95
|
-
- **
|
|
106
|
+
- **trackly_create_apply_batch** — Freeze an exact recent-first set of approved jobs with an idempotency key.
|
|
107
|
+
- **trackly_get_active_apply_batch** — Recover the newest unexpired active batch before creating another after chat or browser context loss.
|
|
108
|
+
- **trackly_get_apply_batch** — Page a server-owned frozen batch without reordering or replacing members.
|
|
109
|
+
- **trackly_claim_apply_batch** — Acquire or renew the optimistic browser-mutation lease.
|
|
110
|
+
- **trackly_checkpoint_apply_batch** — Bulk-record up to 20 value-free inspection checkpoints, typed human actions, and per-member conflicts.
|
|
111
|
+
- **trackly_bind_apply_surface** — Bind an initial or recovered browser surface to the existing frozen member/run, increment its inspection epoch, and return the exact stored requisition URL.
|
|
112
|
+
- **trackly_record_apply_surface_evidence** — Record current-epoch, value-free inventory, missing-tab, close-receipt, post-close absence, or close-failure evidence.
|
|
113
|
+
- **trackly_record_apply_submission_evidence** — Record typed, redacted submit-request, success-page, explicit user-confirmation, or provider-receipt evidence without page text or external references.
|
|
114
|
+
- **trackly_approve_apply_batch_resume** — Approve one exact default-resume identity and content hash for the complete current frozen run set.
|
|
115
|
+
- **trackly_certify_apply_batch_truth** — Record an expiring late truthfulness certification over final answer and wording fingerprints after every other review-readiness gate has passed.
|
|
116
|
+
- **trackly_start_apply_run** — Start or reuse a manual-submit browser run with the complete frozen batch/member/lease binding.
|
|
96
117
|
- **trackly_get_apply_evidence** — Get the authenticated user's aggregate beta evidence and release gate without returning profile answers or contact values.
|
|
97
118
|
- **trackly_get_apply_protocol** — Get the current ATS support, browser integrity rules, and compatible skill version.
|
|
98
119
|
- **trackly_report_apply_observation** — Report redacted ATS mechanics and actual scenario coverage without answer values or page text.
|
|
120
|
+
- **trackly_report_apply_observations** — Bulk-report up to 20 leased, current-epoch, batch-bound redacted observations in one request.
|
|
99
121
|
- **trackly_record_application_outcome** — Record review readiness or a confirmed manual submission.
|
|
122
|
+
- **trackly_record_application_outcomes** — Bulk-record up to 20 leased, batch-bound outcomes while returning per-member conflicts explicitly.
|
|
100
123
|
- **trackly_prepare_resume** — Local MCP only: materialize the default resume in a private, expiring mode-0600 cache and return filename, size, SHA-256, exact local path, and visual-confirmation metadata. Hosted MCP returns a manual/local-agent requirement.
|
|
101
124
|
- **trackly_verify_prepared_resume** — Local MCP only: immediately before attachment, recompute the user-confirmed resume hash and size, validate the exact path/run/expiration, and lock the file read-only. Any mismatch requires a fresh preview and confirmation.
|
|
102
125
|
|
|
103
|
-
Apply contract v3 intentionally gives this verifier different local and hosted schemas: local MCP receives the full proof needed to inspect the private file, while hosted MCP accepts only run and confirmation identifiers and returns the manual/local-agent requirement. Local paths
|
|
126
|
+
Apply contract v3 intentionally gives this verifier different local and hosted schemas: local MCP receives the full proof needed to inspect the private file, while hosted MCP accepts only run and confirmation identifiers and returns the manual/local-agent requirement. Local paths are never sent remotely. Resume fingerprints are sent only to authenticated Trackly resume approval and truth-certification endpoints, never observations or employer forms. Version 3.1 also records universal value-free evidence for critical-contact integrity and the manual-submit boundary. Version 3.2 authorizes the exact stored HTTPS origin for jobs Trackly ingested from employer careers sources, without granting redirect, iframe, or hostname-suffix privileges. Version 3.3.1 adds active-batch recovery, epoch-bound observations/outcomes, and truth certification for forms with no resume control.
|
|
104
127
|
|
|
105
128
|
### Maintenance behavior
|
|
106
129
|
|
|
@@ -114,4 +137,5 @@ REST, refresh, download, CLI, and local/hosted MCP surfaces use canonical `code:
|
|
|
114
137
|
- "What remote engineering roles are available?"
|
|
115
138
|
- "Show me jobs at Stripe"
|
|
116
139
|
- "Mark job 1234 as applied"
|
|
140
|
+
- "Show my Trackly preferences, then set Product to up to 2 years and Strategy to up to 5 years"
|
|
117
141
|
- "Use Trackly Apply to fill my next saved application and stop before Submit"
|