trackly-cli 0.10.0 → 0.10.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
@@ -176,7 +176,7 @@ Then use natural language in any of these clients:
176
176
  | trackly_get_apply_queue | Get user-approved jobs ready for execution |
177
177
  | trackly_get_application_profile | Get versioned profile answers and provenance |
178
178
  | trackly_get_profile_onboarding | Get schema plus missing questions |
179
- | trackly_update_application_profile | Save scoped answers with optimistic concurrency |
179
+ | trackly_update_application_profile | Save scoped answers with optimistic concurrency; revoking sensitive storage takes a two-step confirmation |
180
180
  | trackly_create_apply_batch | Freeze an exact recent-first approved batch |
181
181
  | trackly_get_active_apply_batch | Recover the newest unexpired active batch after context loss |
182
182
  | trackly_get_apply_batch | Read frozen membership with opaque pagination |
@@ -1,5 +1,5 @@
1
1
  {
2
- "contractVersion": "3.3.3",
2
+ "contractVersion": "3.4.0",
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
5
  "applyBrowserSurfaces": ["codex_in_app", "chrome_extension", "claude_in_chrome"],
@@ -15,7 +15,7 @@
15
15
  "trackly_get_apply_queue": "{limit:z.number().int().min(1).max(100).optional(),cursor:z.string().min(1).max(2048).optional()}",
16
16
  "trackly_get_application_profile": "{includeSensitive:z.boolean().optional(),provider:z.string().max(100).optional(),companyId:z.string().max(100).optional()}",
17
17
  "trackly_get_profile_onboarding": "{}",
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()}",
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(),sensitiveRevocationConfirmToken:z.string().regex(/^[a-f0-9]{64}$/).optional()}",
19
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
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
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()}",
@@ -102,7 +102,7 @@ API-key creation attempts.
102
102
  - **trackly_get_apply_queue** — Get user-approved check-later jobs in deterministic execution order.
103
103
  - **trackly_get_application_profile** — Get the versioned, scoped application profile.
104
104
  - **trackly_get_profile_onboarding** — Get backend-owned questions plus only missing/unconfirmed answers.
105
- - **trackly_update_application_profile** — Save explicit answer states with optimistic concurrency and global/provider/company scope.
105
+ - **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.
106
106
  - **trackly_create_apply_batch** — Freeze an exact recent-first set of approved jobs with an idempotency key.
107
107
  - **trackly_get_active_apply_batch** — Recover the newest unexpired active batch before creating another after chat or browser context loss.
108
108
  - **trackly_get_apply_batch** — Page a server-owned frozen batch without reordering or replacing members.
@@ -18,6 +18,7 @@ const APPLY_BATCH_MAX_MEMBERS = 100;
18
18
  const APPLY_BATCH_MAX_CHECKPOINTS_PER_REQUEST = 20;
19
19
  const APPLY_BATCH_MAX_ACTIONS_PER_CHECKPOINT = 25;
20
20
  const APPLY_BATCH_MAX_BULK_MUTATIONS = 20;
21
+
21
22
  const SAFE_OBSERVATION_CODE = /^[a-z0-9][a-z0-9_:-]{0,99}$/;
22
23
  const SAFE_IDEMPOTENCY_KEY = /^[\x20-\x7e]+$/;
23
24
 
@@ -153,7 +154,7 @@ function registerApplyTools(
153
154
 
154
155
  server.tool(
155
156
  'trackly_update_application_profile',
156
- 'Update confirmed profile answers with optimistic concurrency. Use global scope only for an explicit always-answer preference.',
157
+ '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.',
157
158
  {
158
159
  expectedRevision: z.number().int().min(1),
159
160
  source: z.enum(['web', 'ios', 'macos', 'codex', 'claude', 'mcp']).optional(),
@@ -183,8 +184,16 @@ function registerApplyTools(
183
184
  })).max(20).optional(),
184
185
  confirmProfile: z.boolean().optional(),
185
186
  sensitiveStorageConsent: z.boolean().optional(),
187
+ sensitiveRevocationConfirmToken: z.string().regex(/^[a-f0-9]{64}$/).optional(),
186
188
  },
187
- wrapTool(async (params) => apiRequest('PATCH', '/api/jobscout/application-profile', params, false, false, MCP_USER_AGENT), 'Failed to update application profile')
189
+ wrapTool(async (params) => {
190
+ // The revocation guard lives in the backend service layer (#1294): it
191
+ // recomputes the persisted-sensitivity inventory in-transaction and 409s
192
+ // with the challenge unless the token matches. Forward the full params —
193
+ // including sensitiveRevocationConfirmToken — so no client-side copy of
194
+ // the guard can drift or be bypassed by version skew.
195
+ return apiRequest('PATCH', '/api/jobscout/application-profile', params, false, false, MCP_USER_AGENT);
196
+ }, 'Failed to update application profile')
188
197
  );
189
198
 
190
199
  server.tool(
package/mcp/server.js CHANGED
@@ -88,10 +88,22 @@ function createErrorResult(error, fallbackMessage, extra = {}) {
88
88
  if (
89
89
  error?.code === 'experience_filter_v2_unavailable'
90
90
  || error?.code === 'invalid_preference_revision'
91
+ || error?.code === 'sensitive_revocation_confirmation_required'
92
+ || error?.code === 'invalid_profile_revision'
93
+ || error?.code === 'invalid_profile_response'
91
94
  ) {
92
95
  payload.code = error.code;
93
96
  }
94
97
 
98
+ if (error?.code === 'sensitive_revocation_confirmation_required' && error?.confirmation) {
99
+ payload.confirmation = error.confirmation;
100
+ }
101
+
102
+ if (error?.status === 409 && error?.error === 'sensitive_revocation_confirmation_required' && error?.confirmation) {
103
+ payload.code = error.error;
104
+ payload.confirmation = error.confirmation;
105
+ }
106
+
95
107
  if (error?.status === 409 && error?.error === 'preference_revision_conflict') {
96
108
  payload.preferences = error.preferences;
97
109
  payload.discoveryPreferenceRevision = error.discoveryPreferenceRevision;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "trackly-cli",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "trackly-cli",
9
- "version": "0.10.0",
9
+ "version": "0.10.2",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@modelcontextprotocol/sdk": "1.30.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackly-cli",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "mcpName": "io.github.trackly-app/trackly",
5
5
  "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.",
6
6
  "bin": {
package/server.json CHANGED
@@ -17,12 +17,12 @@
17
17
  ]
18
18
  }
19
19
  ],
20
- "version": "0.10.0",
20
+ "version": "0.10.2",
21
21
  "packages": [
22
22
  {
23
23
  "registryType": "npm",
24
24
  "identifier": "trackly-cli",
25
- "version": "0.10.0",
25
+ "version": "0.10.2",
26
26
  "runtimeHint": "npx",
27
27
  "runtimeArguments": [
28
28
  {