trackly-cli 0.14.0 → 0.14.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 +17 -5
- package/bin/trackly +10 -6
- package/docs/trackly-tools.md +1 -0
- package/lib/client.js +194 -8
- package/mcp/analytics.js +903 -0
- package/mcp/apply-tools.js +31 -10
- package/mcp/server.js +123 -8
- package/npm-shrinkwrap.json +56 -3
- package/package.json +4 -2
- package/server.json +2 -2
package/mcp/apply-tools.js
CHANGED
|
@@ -284,6 +284,7 @@ function registerApplyTools(
|
|
|
284
284
|
idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : undefined,
|
|
285
285
|
)
|
|
286
286
|
);
|
|
287
|
+
const discoveredRecoverableSources = new Map();
|
|
287
288
|
const discoveredHandoffBindings = new Map();
|
|
288
289
|
const discoveredHandoffIdsByExecution = new Map();
|
|
289
290
|
server.tool(
|
|
@@ -418,9 +419,19 @@ function registerApplyTools(
|
|
|
418
419
|
'trackly_list_recoverable_apply_executions',
|
|
419
420
|
'List bounded, value-free exact-member recovery candidates after local context loss. Show the stable job identities to the user and obtain explicit confirmation before recovery; never infer or substitute candidates.',
|
|
420
421
|
{},
|
|
421
|
-
wrapTool(async () =>
|
|
422
|
-
|
|
423
|
-
|
|
422
|
+
wrapTool(async () => {
|
|
423
|
+
const response = recoverableExecutionsResponseSchema.parse(await applyControlRequest(
|
|
424
|
+
'GET', '/api/jobscout/apply/executions/recoverable',
|
|
425
|
+
));
|
|
426
|
+
discoveredRecoverableSources.clear();
|
|
427
|
+
for (const source of response.sources) {
|
|
428
|
+
discoveredRecoverableSources.set(source.sourceExecutionId, {
|
|
429
|
+
sourceSnapshotHash: source.sourceSnapshotHash,
|
|
430
|
+
candidateIds: new Set(source.candidates.map(({ candidateId }) => candidateId)),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
return response;
|
|
434
|
+
}, 'Failed to list recoverable apply executions')
|
|
424
435
|
);
|
|
425
436
|
|
|
426
437
|
server.tool(
|
|
@@ -436,13 +447,23 @@ function registerApplyTools(
|
|
|
436
447
|
explicitExactSetConfirmation: z.literal(true),
|
|
437
448
|
idempotencyKey: z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY),
|
|
438
449
|
},
|
|
439
|
-
wrapTool(async ({ idempotencyKey, ...input }) =>
|
|
440
|
-
input
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
450
|
+
wrapTool(async ({ idempotencyKey, ...input }) => {
|
|
451
|
+
const source = discoveredRecoverableSources.get(input.sourceExecutionId);
|
|
452
|
+
if (
|
|
453
|
+
!source
|
|
454
|
+
|| source.sourceSnapshotHash !== input.sourceSnapshotHash
|
|
455
|
+
|| input.candidateIds.some((candidateId) => !source.candidateIds.has(candidateId))
|
|
456
|
+
) {
|
|
457
|
+
throw new Error('Exact recovery must use one source and candidate set from the latest discovery response.');
|
|
458
|
+
}
|
|
459
|
+
return validateExactRecoveryResponse(
|
|
460
|
+
input,
|
|
461
|
+
exactRecoveryResponseSchema.parse(await applyControlRequest('POST', '/api/jobscout/apply/executions/recover', {
|
|
462
|
+
mode: 'recover_exact_members',
|
|
463
|
+
...input,
|
|
464
|
+
}, idempotencyKey)),
|
|
465
|
+
);
|
|
466
|
+
}, 'Failed to recover exact apply members')
|
|
446
467
|
);
|
|
447
468
|
|
|
448
469
|
server.tool(
|
package/mcp/server.js
CHANGED
|
@@ -7,6 +7,7 @@ const { z } = require('zod');
|
|
|
7
7
|
const { apiRequest, createTracklyAccessError, hasAuth, maintenanceOutput } = require('../lib/client');
|
|
8
8
|
const { version: PACKAGE_VERSION } = require('../package.json');
|
|
9
9
|
const { registerApplyTools } = require('./apply-tools');
|
|
10
|
+
const { configureMcpAnalytics, shutdownMcpAnalytics } = require('./analytics');
|
|
10
11
|
|
|
11
12
|
const MCP_USER_AGENT = `trackly-mcp/${PACKAGE_VERSION}`;
|
|
12
13
|
const MCP_MAINTENANCE_ERROR_CODE = -32002;
|
|
@@ -15,6 +16,8 @@ const MCP_AUTH_ERROR_CODE = -32004;
|
|
|
15
16
|
const AUTH_HINT =
|
|
16
17
|
'Existing members: run `trackly login` or set TRACKLY_API_KEY. ' +
|
|
17
18
|
'New members need a private invite during the limited rollout; request access at https://usetrackly.app/early-access.';
|
|
19
|
+
const MCP_ANALYTICS_CONTEXT_DESCRIPTION =
|
|
20
|
+
'Briefly explain why this tool helps the current job-search goal. Never include resume text, profile answers, demographic or work-authorization answers, application notes, credentials, or secrets.';
|
|
18
21
|
|
|
19
22
|
// Mirrors `granola-followup-app/src/services/region-classifier.ts:8` REGION_TAGS.
|
|
20
23
|
// Keep in sync when the backend enum changes.
|
|
@@ -48,6 +51,22 @@ const JOB_MODALITIES = ['full_time', 'internship', 'all'];
|
|
|
48
51
|
// workArrangements query contract and job_postings constraint.
|
|
49
52
|
const WORK_ARRANGEMENTS = ['remote', 'hybrid', 'in_person', 'unspecified'];
|
|
50
53
|
|
|
54
|
+
const MCP_MISSING_CAPABILITY_VALUES = [
|
|
55
|
+
'search', 'inspect', 'compare', 'rank', 'recommend', 'track', 'update', 'apply',
|
|
56
|
+
'submit', 'export', 'notify', 'schedule', 'integrate', 'authenticate', 'debug', 'other',
|
|
57
|
+
];
|
|
58
|
+
const MCP_MISSING_ACTION_VALUES = [
|
|
59
|
+
'find', 'read', 'compare', 'rank', 'create', 'update', 'delete', 'submit',
|
|
60
|
+
'export', 'notify', 'schedule', 'connect', 'debug', 'other',
|
|
61
|
+
];
|
|
62
|
+
const MCP_MISSING_RESOURCE_VALUES = [
|
|
63
|
+
'job', 'company', 'application', 'contact', 'profile', 'resume', 'preference',
|
|
64
|
+
'analytics', 'account', 'tool', 'other',
|
|
65
|
+
];
|
|
66
|
+
const MCP_MISSING_DESTINATION_VALUES = [
|
|
67
|
+
'trackly', 'external_site', 'file', 'email', 'calendar', 'crm', 'browser', 'other',
|
|
68
|
+
];
|
|
69
|
+
|
|
51
70
|
// `sort` enum matches backend handler at `jobscout.ts:3053` — NOT the pre-fix
|
|
52
71
|
// `newest|oldest|company` (backend rejects oldest/company with HTTP 400).
|
|
53
72
|
const SORT_VALUES = ['newest', 'match'];
|
|
@@ -169,7 +188,7 @@ function throwMcpResourceError(error) {
|
|
|
169
188
|
}
|
|
170
189
|
|
|
171
190
|
function wrapTool(handler, fallbackMessage) {
|
|
172
|
-
return async (params) => {
|
|
191
|
+
return async (params, extra) => {
|
|
173
192
|
try {
|
|
174
193
|
if (!hasAuth()) {
|
|
175
194
|
return createAuthErrorResult();
|
|
@@ -180,6 +199,11 @@ function wrapTool(handler, fallbackMessage) {
|
|
|
180
199
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
181
200
|
};
|
|
182
201
|
} catch (error) {
|
|
202
|
+
if (extra && typeof extra === 'object') {
|
|
203
|
+
extra.__mcp_analytics_error = error instanceof Error
|
|
204
|
+
? error
|
|
205
|
+
: new Error(error?.message || fallbackMessage, { cause: error });
|
|
206
|
+
}
|
|
183
207
|
return createErrorResult(
|
|
184
208
|
error,
|
|
185
209
|
fallbackMessage,
|
|
@@ -485,28 +509,119 @@ function createServer() {
|
|
|
485
509
|
mcpUserAgent: MCP_USER_AGENT,
|
|
486
510
|
throwMcpResourceError,
|
|
487
511
|
});
|
|
512
|
+
|
|
513
|
+
server.tool(
|
|
514
|
+
'get_more_tools',
|
|
515
|
+
'Report a capability Trackly does not currently provide. Use structured categories; context is optional and is classified but never stored as raw text.',
|
|
516
|
+
{
|
|
517
|
+
requestedCapability: z.enum(MCP_MISSING_CAPABILITY_VALUES).optional(),
|
|
518
|
+
requestedAction: z.enum(MCP_MISSING_ACTION_VALUES).optional(),
|
|
519
|
+
requestedResource: z.enum(MCP_MISSING_RESOURCE_VALUES).optional(),
|
|
520
|
+
requestedDestination: z.enum(MCP_MISSING_DESTINATION_VALUES).optional(),
|
|
521
|
+
context: z.string().optional().describe(MCP_ANALYTICS_CONTEXT_DESCRIPTION),
|
|
522
|
+
},
|
|
523
|
+
async () => ({
|
|
524
|
+
content: [{
|
|
525
|
+
type: 'text',
|
|
526
|
+
text: JSON.stringify({ accepted: true, delivery: 'best_effort' }),
|
|
527
|
+
}],
|
|
528
|
+
}),
|
|
529
|
+
);
|
|
530
|
+
|
|
488
531
|
return server;
|
|
489
532
|
}
|
|
490
533
|
|
|
491
|
-
|
|
534
|
+
// Keep `createServer` a deterministic catalog factory for embedders and
|
|
535
|
+
// contract verification. Analytics belongs at the executable startup boundary,
|
|
536
|
+
// after every source tool has been registered.
|
|
537
|
+
function configureServerAnalytics(server, options = {}) {
|
|
538
|
+
if (typeof options.configureAnalytics === 'function') {
|
|
539
|
+
return options.configureAnalytics(server, options.analytics);
|
|
540
|
+
}
|
|
541
|
+
return configureMcpAnalytics(server, options.analytics);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async function startMcpServer(options = {}) {
|
|
492
545
|
const server = createServer();
|
|
493
|
-
|
|
494
|
-
|
|
546
|
+
configureServerAnalytics(server, options);
|
|
547
|
+
const transport = options.transport || new StdioServerTransport();
|
|
548
|
+
const shutdownAnalytics = options.shutdownAnalytics || shutdownMcpAnalytics;
|
|
549
|
+
const previousOnClose = server.server.onclose;
|
|
550
|
+
const beginAnalyticsShutdown = () => {
|
|
551
|
+
try {
|
|
552
|
+
Promise.resolve(shutdownAnalytics(server)).catch(() => {});
|
|
553
|
+
} catch {
|
|
554
|
+
// Analytics teardown must not affect transport teardown.
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
server.server.onclose = () => {
|
|
558
|
+
try {
|
|
559
|
+
previousOnClose?.();
|
|
560
|
+
} catch {
|
|
561
|
+
// Preserve fail-open behavior if an instrumentation callback misbehaves.
|
|
562
|
+
}
|
|
563
|
+
beginAnalyticsShutdown();
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
try {
|
|
567
|
+
await server.connect(transport);
|
|
568
|
+
} catch (error) {
|
|
569
|
+
try {
|
|
570
|
+
await shutdownAnalytics(server);
|
|
571
|
+
} catch {
|
|
572
|
+
// Preserve the original transport failure.
|
|
573
|
+
}
|
|
574
|
+
throw error;
|
|
575
|
+
}
|
|
495
576
|
return server;
|
|
496
577
|
}
|
|
497
578
|
|
|
579
|
+
function installMcpSignalHandlers(server, options = {}) {
|
|
580
|
+
const signalTarget = options.signalTarget || process;
|
|
581
|
+
const exit = options.exit || ((code) => process.exit(code));
|
|
582
|
+
const shutdownAnalytics = options.shutdownAnalytics || shutdownMcpAnalytics;
|
|
583
|
+
let terminating = false;
|
|
584
|
+
const handlers = new Map();
|
|
585
|
+
const cleanup = () => {
|
|
586
|
+
for (const [signal, handler] of handlers) {
|
|
587
|
+
signalTarget.removeListener(signal, handler);
|
|
588
|
+
}
|
|
589
|
+
handlers.clear();
|
|
590
|
+
};
|
|
591
|
+
for (const [signal, exitCode] of [['SIGINT', 130], ['SIGTERM', 143]]) {
|
|
592
|
+
const handler = () => {
|
|
593
|
+
if (terminating) return;
|
|
594
|
+
terminating = true;
|
|
595
|
+
void Promise.resolve()
|
|
596
|
+
.then(() => shutdownAnalytics(server))
|
|
597
|
+
.catch(() => {})
|
|
598
|
+
.finally(() => {
|
|
599
|
+
cleanup();
|
|
600
|
+
exit(exitCode);
|
|
601
|
+
});
|
|
602
|
+
};
|
|
603
|
+
handlers.set(signal, handler);
|
|
604
|
+
signalTarget.on(signal, handler);
|
|
605
|
+
}
|
|
606
|
+
return cleanup;
|
|
607
|
+
}
|
|
608
|
+
|
|
498
609
|
if (require.main === module) {
|
|
499
|
-
startMcpServer()
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
610
|
+
startMcpServer()
|
|
611
|
+
.then((server) => installMcpSignalHandlers(server))
|
|
612
|
+
.catch((error) => {
|
|
613
|
+
console.error('MCP server error:', error);
|
|
614
|
+
process.exit(1);
|
|
615
|
+
});
|
|
503
616
|
}
|
|
504
617
|
|
|
505
618
|
module.exports = {
|
|
506
619
|
AUTH_HINT,
|
|
507
620
|
createAuthErrorResult,
|
|
508
621
|
createErrorResult,
|
|
622
|
+
configureServerAnalytics,
|
|
509
623
|
createServer,
|
|
624
|
+
installMcpSignalHandlers,
|
|
510
625
|
startMcpServer,
|
|
511
626
|
throwMcpResourceError,
|
|
512
627
|
};
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackly-cli",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "trackly-cli",
|
|
9
|
-
"version": "0.14.
|
|
9
|
+
"version": "0.14.1",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
13
|
+
"@posthog/mcp": "0.11.0",
|
|
13
14
|
"fast-uri": "3.1.5",
|
|
14
15
|
"hono": "4.13.0",
|
|
15
16
|
"ip-address": "10.4.0",
|
|
17
|
+
"posthog-node": "5.48.1",
|
|
16
18
|
"zod": "^3.23.0"
|
|
17
19
|
},
|
|
18
20
|
"bin": {
|
|
@@ -23,7 +25,7 @@
|
|
|
23
25
|
"acorn": "8.18.0"
|
|
24
26
|
},
|
|
25
27
|
"engines": {
|
|
26
|
-
"node": ">=
|
|
28
|
+
"node": "^20.20.0 || >=22.22.0"
|
|
27
29
|
}
|
|
28
30
|
},
|
|
29
31
|
"node_modules/@babel/helper-string-parser": {
|
|
@@ -128,6 +130,37 @@
|
|
|
128
130
|
}
|
|
129
131
|
}
|
|
130
132
|
},
|
|
133
|
+
"node_modules/@posthog/core": {
|
|
134
|
+
"version": "1.46.9",
|
|
135
|
+
"resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.46.9.tgz",
|
|
136
|
+
"integrity": "sha512-EXO6y5ih+jBkTCpUCuYgQmajuDZuvy6vMvflkub6pLQyi0GPlCPWVSvZZkOeQw9e2MxoD5GteeGCt9R8+UJ/yQ==",
|
|
137
|
+
"license": "MIT",
|
|
138
|
+
"dependencies": {
|
|
139
|
+
"@posthog/types": "^1.402.2"
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
"node_modules/@posthog/mcp": {
|
|
143
|
+
"version": "0.11.0",
|
|
144
|
+
"resolved": "https://registry.npmjs.org/@posthog/mcp/-/mcp-0.11.0.tgz",
|
|
145
|
+
"integrity": "sha512-wxvIvx2/MuXqDH9Zy99hqop6Hyhymn8aoh7e7wYOH3AUdeohZhom2W8E2UDgBO63eR/vm8QGH/fidyA6H+KNyA==",
|
|
146
|
+
"license": "MIT",
|
|
147
|
+
"dependencies": {
|
|
148
|
+
"@posthog/core": "^1.46.9"
|
|
149
|
+
},
|
|
150
|
+
"engines": {
|
|
151
|
+
"node": "^20.20.0 || >=22.22.0"
|
|
152
|
+
},
|
|
153
|
+
"peerDependencies": {
|
|
154
|
+
"@modelcontextprotocol/sdk": ">=1.26.0",
|
|
155
|
+
"posthog-node": "^5.0.0"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"node_modules/@posthog/types": {
|
|
159
|
+
"version": "1.402.2",
|
|
160
|
+
"resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.402.2.tgz",
|
|
161
|
+
"integrity": "sha512-ZZTiS4dLwF4/D0YTzS3gGSLFnhuNOY5yu4d9VGS9trGe5GW6FjIXo20p18K5BPW2RZSYSld8sdnSAY3AUXleUQ==",
|
|
162
|
+
"license": "MIT"
|
|
163
|
+
},
|
|
131
164
|
"node_modules/accepts": {
|
|
132
165
|
"version": "2.0.0",
|
|
133
166
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
|
@@ -909,6 +942,26 @@
|
|
|
909
942
|
"node": ">=16.20.0"
|
|
910
943
|
}
|
|
911
944
|
},
|
|
945
|
+
"node_modules/posthog-node": {
|
|
946
|
+
"version": "5.48.1",
|
|
947
|
+
"resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.48.1.tgz",
|
|
948
|
+
"integrity": "sha512-BxLX2SqGEQhPqCPTalpyo0RRv1NMbf7UaN7q9d/ED77ksD6XOmE7ko2vIKO8F0zPL1NtKxIi+DYXap9lvR0RaA==",
|
|
949
|
+
"license": "MIT",
|
|
950
|
+
"dependencies": {
|
|
951
|
+
"@posthog/core": "^1.46.9"
|
|
952
|
+
},
|
|
953
|
+
"engines": {
|
|
954
|
+
"node": "^20.20.0 || >=22.22.0"
|
|
955
|
+
},
|
|
956
|
+
"peerDependencies": {
|
|
957
|
+
"rxjs": "^7.0.0"
|
|
958
|
+
},
|
|
959
|
+
"peerDependenciesMeta": {
|
|
960
|
+
"rxjs": {
|
|
961
|
+
"optional": true
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
},
|
|
912
965
|
"node_modules/proxy-addr": {
|
|
913
966
|
"version": "2.0.7",
|
|
914
967
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackly-cli",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
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": {
|
|
@@ -63,13 +63,15 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
66
|
+
"@posthog/mcp": "0.11.0",
|
|
66
67
|
"fast-uri": "3.1.5",
|
|
67
68
|
"hono": "4.13.0",
|
|
68
69
|
"ip-address": "10.4.0",
|
|
70
|
+
"posthog-node": "5.48.1",
|
|
69
71
|
"zod": "^3.23.0"
|
|
70
72
|
},
|
|
71
73
|
"engines": {
|
|
72
|
-
"node": ">=
|
|
74
|
+
"node": "^20.20.0 || >=22.22.0"
|
|
73
75
|
},
|
|
74
76
|
"overrides": {
|
|
75
77
|
"@hono/node-server": "2.0.12",
|
package/server.json
CHANGED
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
]
|
|
18
18
|
}
|
|
19
19
|
],
|
|
20
|
-
"version": "0.14.
|
|
20
|
+
"version": "0.14.1",
|
|
21
21
|
"packages": [
|
|
22
22
|
{
|
|
23
23
|
"registryType": "npm",
|
|
24
24
|
"identifier": "trackly-cli",
|
|
25
|
-
"version": "0.14.
|
|
25
|
+
"version": "0.14.1",
|
|
26
26
|
"runtimeHint": "npx",
|
|
27
27
|
"runtimeArguments": [
|
|
28
28
|
{
|