🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

trackly-cli

Package Overview
Dependencies
Maintainers
1
Versions
53
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

trackly-cli - npm Package Compare versions

Comparing version
0.10.1
to
0.10.2
+7
-67
mcp/apply-tools.js
'use strict';
const { createHash } = require('node:crypto');
const { z } = require('zod');

@@ -23,21 +22,2 @@ const { apiRequest } = require('../lib/client');

function sensitiveRevocationConfirmation(profileResponse) {
const profile = profileResponse?.profile || {};
const currentRevision = Number(profile.revision);
const fields = profile.fields || {};
// Stored answer rows serialize an `encrypted` boolean plus their PERSISTED
// row sensitivity — the same predicate the backend DELETE uses. Profile-column
// and backfilled entries never carry `encrypted` and are never deleted.
const affectedKeys = Object.keys(fields)
.filter((key) => {
const entry = fields[key];
return entry && typeof entry.encrypted === 'boolean'
&& (entry.sensitivity === 'sensitive' || entry.sensitivity === 'restricted');
})
.sort();
const confirmationToken = createHash('sha256')
.update(`trackly:sensitive-revocation:v1:${currentRevision}:${affectedKeys.join(',')}`, 'utf8')
.digest('hex');
return { currentRevision, affectedKeys, confirmationToken };
}
const SAFE_OBSERVATION_CODE = /^[a-z0-9][a-z0-9_:-]{0,99}$/;

@@ -178,3 +158,3 @@ const SAFE_IDEMPOTENCY_KEY = /^[\x20-\x7e]+$/;

'trackly_update_application_profile',
'Update confirmed profile answers with optimistic concurrency. Use global scope only for an explicit always-answer preference. Setting sensitiveStorageConsent=false permanently deletes every stored sensitive and restricted answer and is a two-step action: the first call saves nothing and returns a confirmation challenge; retry with the echoed sensitiveRevocationConfirmToken to proceed.',
'Update confirmed profile answers with optimistic concurrency. Use global scope only for an explicit always-answer preference. Setting sensitiveStorageConsent=false deletes every stored sensitive and restricted answer (an admin-recoverable archive is kept for 30 days, then purged) and is a two-step action: the first call saves nothing and returns a confirmation challenge; retry with the echoed sensitiveRevocationConfirmToken to proceed.',
{

@@ -212,48 +192,8 @@ expectedRevision: z.number().int().min(1),

wrapTool(async (params) => {
const { sensitiveRevocationConfirmToken, ...body } = params;
if (body.sensitiveStorageConsent === false) {
const profileResponse = await apiRequest('GET', '/api/jobscout/application-profile', null, false, false, MCP_USER_AGENT);
const rawRevision = Number(profileResponse?.profile?.revision);
if (!Number.isSafeInteger(rawRevision) || rawRevision < 1) {
throw {
status: 502,
code: 'invalid_profile_revision',
error: 'Trackly did not return a valid profile revision. No changes were saved.',
};
}
const profileFieldsShape = profileResponse?.profile?.fields;
const profileEntriesValid = !!profileFieldsShape
&& typeof profileFieldsShape === 'object'
&& !Array.isArray(profileFieldsShape)
&& Object.keys(profileFieldsShape).length > 0
&& Object.values(profileFieldsShape).every((entry) => entry
&& typeof entry === 'object' && !Array.isArray(entry)
&& typeof entry.state === 'string'
&& (entry.sensitivity === 'standard' || entry.sensitivity === 'sensitive' || entry.sensitivity === 'restricted')
&& (!('encrypted' in entry) || typeof entry.encrypted === 'boolean'));
if (!profileEntriesValid) {
throw {
status: 502,
code: 'invalid_profile_response',
error: 'Trackly returned an incomplete profile, so the deletion scope cannot be verified. No changes were saved.',
};
}
const { currentRevision, affectedKeys, confirmationToken } =
sensitiveRevocationConfirmation(profileResponse);
if (sensitiveRevocationConfirmToken !== confirmationToken || body.expectedRevision !== currentRevision) {
throw {
status: 409,
code: 'sensitive_revocation_confirmation_required',
error: 'Revoking sensitive storage consent permanently deletes every stored sensitive and restricted answer. No changes were saved.',
confirmation: {
currentRevision,
affectedKeys,
alsoDeletes: 'Every provider- and company-scoped sensitive or restricted answer is also deleted, along with any stored rows not visible in this profile view; those keys are not listed here.',
confirmationToken,
instructions: 'Do not retry automatically. Show the user the affected keys and get explicit confirmation, then retry with the FULL original request body (including any changes, education, or confirmProfile fields) plus expectedRevision=currentRevision and sensitiveRevocationConfirmToken=confirmationToken. If the original call carried other changes, re-read the profile first and reconcile them against the current revision instead of resending stale changes blindly. The token becomes invalid whenever the profile revision changes.',
},
};
}
}
return apiRequest('PATCH', '/api/jobscout/application-profile', body, false, false, MCP_USER_AGENT);
// The revocation guard lives in the backend service layer (#1294): it
// recomputes the persisted-sensitivity inventory in-transaction and 409s
// with the challenge unless the token matches. Forward the full params —
// including sensitiveRevocationConfirmToken — so no client-side copy of
// the guard can drift or be bypassed by version skew.
return apiRequest('PATCH', '/api/jobscout/application-profile', params, false, false, MCP_USER_AGENT);
}, 'Failed to update application profile')

@@ -260,0 +200,0 @@ );

+5
-0

@@ -102,2 +102,7 @@ 'use strict';

if (error?.status === 409 && error?.error === 'sensitive_revocation_confirmation_required' && error?.confirmation) {
payload.code = error.error;
payload.confirmation = error.confirmation;
}
if (error?.status === 409 && error?.error === 'preference_revision_conflict') {

@@ -104,0 +109,0 @@ payload.preferences = error.preferences;

{
"name": "trackly-cli",
"version": "0.10.1",
"version": "0.10.2",
"lockfileVersion": 3,

@@ -9,3 +9,3 @@ "requires": true,

"name": "trackly-cli",
"version": "0.10.1",
"version": "0.10.2",
"license": "MIT",

@@ -12,0 +12,0 @@ "dependencies": {

{
"name": "trackly-cli",
"version": "0.10.1",
"version": "0.10.2",
"mcpName": "io.github.trackly-app/trackly",

@@ -5,0 +5,0 @@ "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.",

@@ -20,3 +20,3 @@ {

],
"version": "0.10.1",
"version": "0.10.2",
"packages": [

@@ -26,3 +26,3 @@ {

"identifier": "trackly-cli",
"version": "0.10.1",
"version": "0.10.2",
"runtimeHint": "npx",

@@ -29,0 +29,0 @@ "runtimeArguments": [