trackly-cli 0.21.1 → 0.21.2
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
|
@@ -353,7 +353,9 @@ a missing capability. Run `trackly mcp` or use
|
|
|
353
353
|
|
|
354
354
|
MCP usage analytics are on by default and relay through Trackly so the free
|
|
355
355
|
service can be debugged and improved. They exclude résumé text, profile answers,
|
|
356
|
-
demographic or work-authorization answers, and application notes.
|
|
356
|
+
demographic or work-authorization answers, and application notes. Client attribution
|
|
357
|
+
uses a fixed family label; errors use categories, and search text is retained only
|
|
358
|
+
as length. Raw queries and conversation text are excluded. Turn off future
|
|
357
359
|
collection with **Share usage analytics** in Account Settings. Operators may also
|
|
358
360
|
disable local MCP instrumentation with `TRACKLY_MCP_ANALYTICS_DISABLED=1` (or
|
|
359
361
|
`TRACKLY_MCP_ANALYTICS_ENABLED=0`); the account setting remains authoritative
|
|
@@ -37,3 +37,17 @@ Verification: `node --test test/mcp-analytics.test.js test/mcp-schema.test.js`
|
|
|
37
37
|
and `npm test`. The real transport regression failed with the old instrumentation
|
|
38
38
|
and passed after the fix. Keyword telemetry continues to retain length only;
|
|
39
39
|
this change does not permit raw query or context storage.
|
|
40
|
+
|
|
41
|
+
Error and client attribution also need real SDK coverage. The SDK's error type
|
|
42
|
+
may be a JavaScript constructor name, and its vendor field comes from HTTP
|
|
43
|
+
headers that stdio does not have. Normalize both at the privacy boundary:
|
|
44
|
+
derive a closed error category from the failure and a closed client family from
|
|
45
|
+
available metadata, with explicit `none`, `other` and `unknown` states.
|
|
46
|
+
|
|
47
|
+
The HTTP client rejects response records containing numeric status and an
|
|
48
|
+
`error` field. Capturing only `message` loses that status and substitutes a
|
|
49
|
+
generic fallback. Preserve the status in the telemetry-only exception and give
|
|
50
|
+
it precedence over endpoint words such as "network brief". Verify job detail
|
|
51
|
+
and brief calls through a local HTTP fixture with 404, 429 and 500 responses;
|
|
52
|
+
assert unchanged tool results and classified, content-free telemetry. Sanitize
|
|
53
|
+
the result twice because the SDK hook and backend relay both project events.
|
package/mcp/analytics.js
CHANGED
|
@@ -118,6 +118,28 @@ const SAFE_ENUM_KEYS = new Set([
|
|
|
118
118
|
'region', 'regiontag', 'locationfilter', 'sort', 'sponsorship',
|
|
119
119
|
]);
|
|
120
120
|
const SDK_FAILURE_WARNING = /Warning: (?:Failed to instrument server|Failed to setup tool call instrumentation|No PostHog client passed)/i;
|
|
121
|
+
const ERROR_CATEGORIES = new Set([
|
|
122
|
+
'authentication', 'authorization', 'rate_limit', 'timeout', 'not_found',
|
|
123
|
+
'validation', 'conflict', 'network', 'server', 'unknown',
|
|
124
|
+
]);
|
|
125
|
+
const HTTP_ERROR_CATEGORIES = {
|
|
126
|
+
400: 'validation', 401: 'authentication', 403: 'authorization',
|
|
127
|
+
404: 'not_found', 408: 'timeout', 409: 'conflict', 410: 'not_found',
|
|
128
|
+
413: 'validation', 422: 'validation', 429: 'rate_limit',
|
|
129
|
+
};
|
|
130
|
+
const CLIENT_FAMILIES = [
|
|
131
|
+
['chatgpt', /\bchatgpt\b/],
|
|
132
|
+
['claude', /\bclaude(?: desktop)?\b/],
|
|
133
|
+
['codex', /\bcodex\b/],
|
|
134
|
+
['cursor', /\bcursor\b/],
|
|
135
|
+
['windsurf', /\bwindsurf\b/],
|
|
136
|
+
['vscode', /\b(?:vscode|visual studio code)\b/],
|
|
137
|
+
['continue', /\bcontinue\b/],
|
|
138
|
+
['cline', /\bcline\b/],
|
|
139
|
+
['roo', /\broo(?: code)?\b/],
|
|
140
|
+
['zed', /\bzed\b/],
|
|
141
|
+
['mcp-inspector', /\bmcp inspector\b/],
|
|
142
|
+
];
|
|
121
143
|
|
|
122
144
|
const ALLOWED_EVENT_PROPERTY_KEYS = new Set([
|
|
123
145
|
'$exception_list', '$lib', '$lib_version', '$mcp_client_name',
|
|
@@ -250,6 +272,13 @@ function classifyIntent(toolName, value) {
|
|
|
250
272
|
|
|
251
273
|
function classifyError(value) {
|
|
252
274
|
const text = typeof value === 'string' ? value.toLowerCase() : '';
|
|
275
|
+
if (ERROR_CATEGORIES.has(text)) return text;
|
|
276
|
+
// HTTP status is authoritative; endpoint words such as "network brief"
|
|
277
|
+
// must not turn an upstream 500 into a transport/network classification.
|
|
278
|
+
const status = /\bhttp\s+([45]\d\d)\b/.exec(text)?.[1];
|
|
279
|
+
if (status && HTTP_ERROR_CATEGORIES[status]) return HTTP_ERROR_CATEGORIES[status];
|
|
280
|
+
if (status?.startsWith('5')) return 'server';
|
|
281
|
+
if (status) return 'unknown';
|
|
253
282
|
if (/\b(?:401|unauthenticated|authentication|invalid token|expired token)\b/.test(text)) {
|
|
254
283
|
return 'authentication';
|
|
255
284
|
}
|
|
@@ -264,6 +293,38 @@ function classifyError(value) {
|
|
|
264
293
|
return 'unknown';
|
|
265
294
|
}
|
|
266
295
|
|
|
296
|
+
function canonicalClientFamily(value) {
|
|
297
|
+
if (typeof value !== 'string' || !value.trim() || value.length > 240) return undefined;
|
|
298
|
+
const normalized = value.toLowerCase().replace(/[_-]/g, ' ');
|
|
299
|
+
return CLIENT_FAMILIES.find(([, pattern]) => pattern.test(normalized))?.[0];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function projectEventAttribution(properties, eventName) {
|
|
303
|
+
const clientValues = [
|
|
304
|
+
properties.$mcp_vendor_client,
|
|
305
|
+
properties.$mcp_client_name,
|
|
306
|
+
properties.$mcp_client_user_agent,
|
|
307
|
+
];
|
|
308
|
+
const clientFamily = clientValues.map(canonicalClientFamily).find(Boolean)
|
|
309
|
+
|| (clientValues.some((value) => (
|
|
310
|
+
typeof value === 'string' && value.trim() && value !== 'unknown'
|
|
311
|
+
)) ? 'other' : 'unknown');
|
|
312
|
+
properties.$mcp_vendor_client = clientFamily;
|
|
313
|
+
properties.$mcp_client_name = clientFamily;
|
|
314
|
+
if (properties.$mcp_client_user_agent !== undefined) {
|
|
315
|
+
properties.$mcp_client_user_agent = canonicalClientFamily(properties.$mcp_client_user_agent)
|
|
316
|
+
|| 'other';
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const errorMessage = properties.$mcp_error_message;
|
|
320
|
+
const exceptionMessage = properties.$exception_list?.[0]?.value;
|
|
321
|
+
const isError = eventName === '$exception' || properties.$mcp_is_error === true;
|
|
322
|
+
properties.$mcp_error_type = isError
|
|
323
|
+
? classifyError(errorMessage ?? exceptionMessage ?? properties.$mcp_error_type)
|
|
324
|
+
: 'none';
|
|
325
|
+
return errorMessage;
|
|
326
|
+
}
|
|
327
|
+
|
|
267
328
|
function safeFrameName(value, maxLength = 120) {
|
|
268
329
|
if (typeof value !== 'string' || value === '<local-path>') return undefined;
|
|
269
330
|
const basename = value.replace(/\\/g, '/').split('/').pop() || '';
|
|
@@ -311,7 +372,7 @@ function projectExceptionList(value, errorValue) {
|
|
|
311
372
|
}).filter((frame) => Object.keys(frame).length > 0);
|
|
312
373
|
return {
|
|
313
374
|
type: safeFrameName(exception.type, 80) || 'Error',
|
|
314
|
-
category: classifyError(exception.value ?? errorValue),
|
|
375
|
+
category: classifyError(exception.category ?? exception.value ?? errorValue),
|
|
315
376
|
stacktrace: { frames: projectedFrames },
|
|
316
377
|
};
|
|
317
378
|
});
|
|
@@ -521,6 +582,7 @@ function sanitizeMcpAnalyticsEvent(event) {
|
|
|
521
582
|
}
|
|
522
583
|
const properties = sanitized.properties;
|
|
523
584
|
if (!properties || typeof properties !== 'object') return sanitized;
|
|
585
|
+
const sourceErrorMessage = projectEventAttribution(properties, event.event);
|
|
524
586
|
|
|
525
587
|
// Identity is backend-owned. The CLI may carry only its ephemeral anonymous
|
|
526
588
|
// session identifier; verified user/person properties never cross this relay.
|
|
@@ -582,7 +644,6 @@ function sanitizeMcpAnalyticsEvent(event) {
|
|
|
582
644
|
delete properties.$mcp_intent_source;
|
|
583
645
|
}
|
|
584
646
|
|
|
585
|
-
const sourceErrorMessage = properties.$mcp_error_message;
|
|
586
647
|
if (typeof sourceErrorMessage === 'string') {
|
|
587
648
|
properties.$mcp_error_message = classifyError(sourceErrorMessage);
|
|
588
649
|
}
|
package/mcp/server.js
CHANGED
|
@@ -232,9 +232,13 @@ function wrapTool(handler, fallbackMessage) {
|
|
|
232
232
|
};
|
|
233
233
|
} catch (error) {
|
|
234
234
|
if (extra && typeof extra === 'object') {
|
|
235
|
+
const statusPrefix = Number.isInteger(error?.status) ? `HTTP ${error.status}: ` : '';
|
|
235
236
|
extra.__mcp_analytics_error = error instanceof Error
|
|
236
237
|
? error
|
|
237
|
-
: new Error(
|
|
238
|
+
: new Error(
|
|
239
|
+
`${statusPrefix}${error?.error || error?.message || fallbackMessage}`,
|
|
240
|
+
{ cause: error },
|
|
241
|
+
);
|
|
238
242
|
}
|
|
239
243
|
return createErrorResult(
|
|
240
244
|
error,
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackly-cli",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "trackly-cli",
|
|
9
|
-
"version": "0.21.
|
|
9
|
+
"version": "0.21.2",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@modelcontextprotocol/sdk": "1.32.1",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackly-cli",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.2",
|
|
4
4
|
"mcpName": "io.github.trackly-app/trackly",
|
|
5
5
|
"description": "Search 250K+ jobs across 5,400+ companies with the Trackly CLI and MCP server for Claude, ChatGPT, Codex and Cursor. Filter by role, location, salary and sponsorship; track jobs and prepare applications.",
|
|
6
6
|
"bin": {
|
package/server.json
CHANGED
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
]
|
|
18
18
|
}
|
|
19
19
|
],
|
|
20
|
-
"version": "0.21.
|
|
20
|
+
"version": "0.21.2",
|
|
21
21
|
"packages": [
|
|
22
22
|
{
|
|
23
23
|
"registryType": "npm",
|
|
24
24
|
"identifier": "trackly-cli",
|
|
25
|
-
"version": "0.21.
|
|
25
|
+
"version": "0.21.2",
|
|
26
26
|
"runtimeHint": "npx",
|
|
27
27
|
"runtimeArguments": [
|
|
28
28
|
{
|