🎩 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.0
to
0.10.1
+2
-2
contracts/trackly-apply-tools.json
{
"contractVersion": "3.3.3",
"contractVersion": "3.4.0",
"constants": {

@@ -18,3 +18,3 @@ "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"],

"trackly_get_profile_onboarding": "{}",
"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()}",
"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(),sensitiveRevocationConfirmToken:z.string().regex(/^[a-f0-9]{64}$/).optional()}",
"trackly_create_apply_batch": "{limit:z.number().int().min(1).max(100),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY)}",

@@ -21,0 +21,0 @@ "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()}",

@@ -105,3 +105,3 @@ ## Trackly Job Tracker (MCP)

- **trackly_get_profile_onboarding** — Get backend-owned questions plus only missing/unconfirmed answers.
- **trackly_update_application_profile** — Save explicit answer states with optimistic concurrency and global/provider/company scope.
- **trackly_update_application_profile** — Save explicit answer states with optimistic concurrency and global/provider/company scope. Setting `sensitiveStorageConsent=false` is destructive and requires a second call echoing the `sensitiveRevocationConfirmToken` from the returned challenge.
- **trackly_create_apply_batch** — Freeze an exact recent-first set of approved jobs with an idempotency key.

@@ -108,0 +108,0 @@ - **trackly_get_active_apply_batch** — Recover the newest unexpired active batch before creating another after chat or browser context loss.

'use strict';
const { createHash } = require('node:crypto');
const { z } = require('zod');

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

const APPLY_BATCH_MAX_BULK_MUTATIONS = 20;
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}$/;

@@ -157,3 +178,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.',
'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.',
{

@@ -188,4 +209,52 @@ expectedRevision: z.number().int().min(1),

sensitiveStorageConsent: z.boolean().optional(),
sensitiveRevocationConfirmToken: z.string().regex(/^[a-f0-9]{64}$/).optional(),
},
wrapTool(async (params) => apiRequest('PATCH', '/api/jobscout/application-profile', params, false, false, MCP_USER_AGENT), 'Failed to update application profile')
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);
}, 'Failed to update application profile')
);

@@ -192,0 +261,0 @@

@@ -91,2 +91,5 @@ 'use strict';

|| error?.code === 'invalid_preference_revision'
|| error?.code === 'sensitive_revocation_confirmation_required'
|| error?.code === 'invalid_profile_revision'
|| error?.code === 'invalid_profile_response'
) {

@@ -96,2 +99,6 @@ payload.code = error.code;

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

@@ -98,0 +105,0 @@ payload.preferences = error.preferences;

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

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

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

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

{
"name": "trackly-cli",
"version": "0.10.0",
"version": "0.10.1",
"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.",

@@ -179,3 +179,3 @@ [![npm](https://img.shields.io/npm/v/trackly-cli.svg)](https://www.npmjs.com/package/trackly-cli)

| trackly_get_profile_onboarding | Get schema plus missing questions |
| trackly_update_application_profile | Save scoped answers with optimistic concurrency |
| trackly_update_application_profile | Save scoped answers with optimistic concurrency; revoking sensitive storage takes a two-step confirmation |
| trackly_create_apply_batch | Freeze an exact recent-first approved batch |

@@ -182,0 +182,0 @@ | trackly_get_active_apply_batch | Recover the newest unexpired active batch after context loss |

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

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

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

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

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