trackly-cli 0.20.0 → 0.21.0
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 +26 -2
- package/bin/trackly +1 -1
- package/docs/plans/2026-08-05-001-trackly-apply-durable-recovery-reconciliation-plan.md +26 -26
- package/docs/release-verification.md +107 -0
- package/docs/solutions/integration-issues/analytics-must-preserve-strict-mcp-schemas.md +39 -0
- package/docs/solutions/integration-issues/backend-owned-sensitive-answer-deletion-guard.md +4 -4
- package/docs/solutions/integration-issues/durable-exact-recovery-after-browser-state-loss.md +2 -2
- package/docs/solutions/integration-issues/openai-mcp-discovery-probe-oauth-scope-failure.md +2 -2
- package/docs/trackly-tools.md +20 -5
- package/lib/agent.js +1 -1
- package/mcp/analytics.js +7 -4
- package/mcp/apply-tools.js +4 -4
- package/mcp/server.js +51 -12
- package/npm-shrinkwrap.json +2 -2
- package/package.json +8 -3
- package/server.json +3 -3
- package/skills/trackly-apply/SKILL.md +3 -3
- package/skills/trackly-apply/references/answer-resolution.md +3 -1
- package/skills/trackly-apply/references/handoff-compatibility.md +1 -1
- package/skills/trackly-apply/scripts/validate-phase-checkpoint.js +3 -2
- package/skills/trackly-apply/scripts/validate-profile-lookup.js +3 -2
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ trackly agent setup --client codex # or claude / both
|
|
|
121
121
|
trackly agent doctor
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
For an existing MCP configuration or a Cloud harness, use `trackly agent setup --client both --skills-only`. This installs the same checksummed skill for both native clients without invoking their MCP commands or changing authentication. CLI 0.20.
|
|
124
|
+
For an existing MCP configuration or a Cloud harness, use `trackly agent setup --client both --skills-only`. This installs the same checksummed skill for both native clients without invoking their MCP commands or changing authentication. CLI 0.20.1 carries universal skill 4.9.1; its policy revision is the same numeric version, superseding the personal revisions. The policy accepts a documented `markHandoff()` guarantee with awaited calls, exact current tab checks, and revalidation on later turns. These observations are never reported as returned persistence receipts. Browser capabilities and manual Submit remain separate requirements; installation does not make a Linux browser supported. The separately installed `trackly:trackly-apply` plugin is updated through its plugin manager; plugin 1.1.1 carries the same browser and profile lookup policies. This setup command does not patch plugin caches.
|
|
125
125
|
|
|
126
126
|
The skill uses the profile and default resume in your Trackly account, asks only missing questions, fills user-approved saved jobs, and always stops before Submit. “Fill the next N” continues through the original recent-first Check Later snapshot until N unauthenticated forms are durably ready for your manual review; authentication walls and exclusions are reported separately and do not consume the target. An explicit “inspect the next N records” request still uses one fixed immutable batch. Support is fetched from Trackly at the start of every run: Greenhouse is full, Ashby and Lever are best effort, and 27 additional named ATS/provider classes use constrained guided mode. Employer-hosted unknown forms run only when Trackly binds them to a verified company domain. LinkedIn-hosted forms and unverified origins remain manual-only; a separately stored external application URL is evaluated under its own ATS and origin policy.
|
|
127
127
|
|
|
@@ -175,6 +175,30 @@ Then use natural language in any of these clients:
|
|
|
175
175
|
- "What remote engineering roles are available?"
|
|
176
176
|
- "Mark job 1234 as applied"
|
|
177
177
|
|
|
178
|
+
### Search filters and updates
|
|
179
|
+
|
|
180
|
+
`trackly_search_jobs` supports multiple roles and regions, US states and Canadian
|
|
181
|
+
provinces, internship education levels, favorite companies, posting windows,
|
|
182
|
+
annual USD salary floors, incremental sync, work arrangement, sponsorship and
|
|
183
|
+
pipeline status. [Exact values and examples](docs/trackly-tools.md#available-tools).
|
|
184
|
+
Search returns 20 jobs by default; request up to 100 and use `offset` to page.
|
|
185
|
+
|
|
186
|
+
For sponsorship, “exclude no” keeps yes **and not mentioned**. Use `exclude_no`;
|
|
187
|
+
`only_yes` excludes jobs with unknown sponsorship. Ask the user when intent is
|
|
188
|
+
ambiguous. Salary floors also keep unknown and non-comparable salaries visible.
|
|
189
|
+
Unsupported industry/specialization/literal-search controls are rejected.
|
|
190
|
+
|
|
191
|
+
For a local server without a global package install, run `npx -y trackly-cli mcp`.
|
|
192
|
+
For Codex, use `codex mcp add trackly -- npx -y trackly-cli mcp`; Cursor and
|
|
193
|
+
Windsurf can use `"command": "npx", "args": ["-y", "trackly-cli", "mcp"]`.
|
|
194
|
+
Authenticate with `npx -y trackly-cli login` first, or configure `TRACKLY_API_KEY`.
|
|
195
|
+
|
|
196
|
+
Existing global installs require `npm install --global trackly-cli@latest`.
|
|
197
|
+
Restart the MCP process/client and refresh tool discovery after updating.
|
|
198
|
+
For native skills and marketplace plugin updates, follow the
|
|
199
|
+
[release and update guide](docs/release-verification.md#existing-public-users).
|
|
200
|
+
Hosted and local releases are separate: the current client schema is authoritative.
|
|
201
|
+
|
|
178
202
|
## MCP Tools Reference
|
|
179
203
|
|
|
180
204
|
The local stdio server (`trackly mcp`) registers the 58 tools below. Four are
|
|
@@ -185,7 +209,7 @@ The hosted server omits those four and adds `trackly_chat`, for 55 hosted tools.
|
|
|
185
209
|
|
|
186
210
|
| Tool | Description |
|
|
187
211
|
|------|-------------|
|
|
188
|
-
| trackly_search_jobs | Search
|
|
212
|
+
| trackly_search_jobs | Search by roles, regions, states/provinces, internship level, favorites, recency, annual USD salary floor, sponsorship, work arrangement, and pipeline status |
|
|
189
213
|
| trackly_get_job | Get full details for a specific job |
|
|
190
214
|
| trackly_search_companies | Semantic company search |
|
|
191
215
|
| trackly_list_companies | List all tracked companies |
|
package/bin/trackly
CHANGED
|
@@ -885,7 +885,7 @@ async function cmdAsk(args) {
|
|
|
885
885
|
if (result.jobsUrl) {
|
|
886
886
|
// Path allowlist: /ask can return any string for jobsUrl. normalizeEndpoint already
|
|
887
887
|
// blocks cross-origin fetches (baseUrl mismatch), but a compromised backend could
|
|
888
|
-
// still emit
|
|
888
|
+
// still emit an unrelated privileged same-origin path. Restrict to the two
|
|
889
889
|
// legitimate handlers that /ask is designed to route to.
|
|
890
890
|
const JOBS_URL_ALLOWLIST = /^\/api\/(v1|jobscout)\/jobs(\?|$)/;
|
|
891
891
|
if (!JOBS_URL_ALLOWLIST.test(result.jobsUrl)) {
|
|
@@ -16,7 +16,7 @@ execution: code
|
|
|
16
16
|
|
|
17
17
|
- **Objective:** Make Trackly Apply recover the exact interrupted jobs, preserve cumulative completion, reconcile every member in a submitted handoff, and fail closed around browser cleanup and resume upload.
|
|
18
18
|
- **Authority:** The backend owns membership, lifecycle, completion credit, idempotency, and current state. The browser harness owns live DOM evidence and local tab identity. The user owns Submit, credentials, verification challenges, and ambiguous answers.
|
|
19
|
-
- **Execution profile:** Deep, cross-repository reliability work in
|
|
19
|
+
- **Execution profile:** Deep, cross-repository reliability work in the private backend and `trackly-cli`. Use fresh worktrees from current `origin/main`; keep base checkouts read-only.
|
|
20
20
|
- **Stop conditions:** Stop browser mutation on unexpected recovery membership, stale inspection authority, ambiguous submission state, incomplete tab inventories, unproven attachment state, or incompatible protocol versions.
|
|
21
21
|
- **Tail ownership:** Merge and deploy the backend first. Then publish and install the compatible CLI and skill. Finish with no-submit fixtures and a manually submitted dogfood recovery flow.
|
|
22
22
|
|
|
@@ -168,7 +168,7 @@ None of these receipts implies another.
|
|
|
168
168
|
|
|
169
169
|
### Success Criteria
|
|
170
170
|
|
|
171
|
-
- The faithful
|
|
171
|
+
- The faithful database sequence from the August 5 incident passes without projection contradictions.
|
|
172
172
|
- Exact recovery never mutates `saved_at`, never substitutes newer jobs, and never uses old-epoch evidence.
|
|
173
173
|
- Every grouped submission report produces one result per handoff member.
|
|
174
174
|
- No finalizer call receives an empty or partial keep set when expected tabs exist.
|
|
@@ -208,7 +208,7 @@ None of these receipts implies another.
|
|
|
208
208
|
### Key Technical Decisions
|
|
209
209
|
|
|
210
210
|
- KTD1. **Persist completion credit in an append-only achievement ledger.** (session-settled: user-approved — chosen over concurrent unsubmitted inventory: once a job is ready or submitted it remains part of the requested total.) Create one value-free credit per `(execution_id, user_id, frozen_job_id)`, constrained to reviewed sources such as `review_ready` and `submitted_repair`. Insert the credit in the same transaction as the durable outcome. Expose `achievementCount` as the raw unique effective-credit count and compute target-facing `completed = LEAST(achievementCount, target)`. Credits are the sole target-achievement input; current ready/submitted counts remain operator projections. Do not add mutable columns to immutable execution candidates. A credit later proven to originate from invalid evidence may be neutralized only by an append-only compensating revocation through an audited internal repair operation; later submission or lifecycle change is never a revocation reason. Governs R1-R3.
|
|
211
|
-
- KTD2. **Characterize the live state sequence before changing accounting.** Current unit tests already cover `ready + submitted`, so implementation begins with a
|
|
211
|
+
- KTD2. **Characterize the live state sequence before changing accounting.** Current unit tests already cover `ready + submitted`, so implementation begins with a database-backed checkpoint, certification, bulk-outcome, submission, and progress-read reproduction. A passing unit calculator test is not sufficient evidence. Governs R1-R3, R8.
|
|
212
212
|
- KTD3. **Model exact recovery as a new execution mode over stable source candidates.** The original August 5 decision used `recover_exact_members` with `sourceExecutionId`, exact execution-candidate IDs proven inside the source snapshot hash, optimistic revision, and idempotency key. It persisted `source_execution_id`, `source_candidate_id`, and the attempt-specific `source_member_id` when present; identity assertion was atomic, eligibility could be partial (superseded—see the shipped-contract addendum immediately below), and the server created no substitutes. Governs R4-R7.
|
|
213
213
|
|
|
214
214
|
**Shipped-contract addendum (2026-08-13):** Backend implementation and adversarial review superseded partial eligibility with an atomic all-or-nothing eligibility check. If any confirmed candidate is no longer eligible, the server creates no recovery execution and no substitutes. AE4 and U4 below reflect this shipped contract while the original decision remains recorded here.
|
|
@@ -221,7 +221,7 @@ None of these receipts implies another.
|
|
|
221
221
|
- KTD10. **Reuse resume approval only with exact lineage.** A recovery may reuse execution-scoped content approval only when the backend proves the same resume hash, profile revision, snapshot lineage, and unexpired approval. Every run still performs immediate local file verification. Otherwise the user approves the resume again. Governs R16, R22.
|
|
222
222
|
- KTD11. **Ship additive coordinated versions.** Expected releases are Apply protocol 3.6.0, MCP contract 3.7.0, skill 4.5.0, and CLI 0.14.0. Recompute versions and migration maximum immediately before implementation. Older active work keeps its published recovery path. Governs R20-R22.
|
|
223
223
|
- KTD12. **Persist request-key idempotency for outcomes.** Single and bulk outcomes require an idempotency key. Atomically store operation type, canonical payload hash, and exact item results; exact retries return those saved results. Current-state inference remains a bounded repair fallback, not the primary replay mechanism. Governs R8-R12.
|
|
224
|
-
- KTD13. **Retain recovery identity beyond operational expiry.** Add `recoverable_until`, computed by
|
|
224
|
+
- KTD13. **Retain recovery identity beyond operational expiry.** Add `recoverable_until`, computed by the database from `expires_at` plus 30 calendar days. The stored value is immutable and cannot be extended. Daily purge may delete source execution data only after that boundary. Record only value-free interruption-to-resume duration metrics. Purge for the new mode remains feature-flagged until dogfood/private-beta evidence is reviewed and the product owner approves the 30-day recovery SLO; until then, retain the additive recovery manifest under normal account-retention rules. Governs R4-R7, R23.
|
|
225
225
|
|
|
226
226
|
### High-Level Technical Design
|
|
227
227
|
|
|
@@ -285,7 +285,7 @@ The backend owns durable identity, membership, milestones, and reconciliation. T
|
|
|
285
285
|
| Recovery reuses stale browser evidence | Increment inspection epoch and require fresh binding, identity, contact, and review evidence. |
|
|
286
286
|
| A group phrase marks the wrong jobs submitted | Resolve it to an exact handoff receipt; inspect every staged member; ask only about unresolved members. |
|
|
287
287
|
| Provider strategy becomes remote executable browser code | Publish bounded declarative strategy enums and semantic evidence requirements only. |
|
|
288
|
-
| Completion credit and current ready counts drift | Update/read them under the same owner lock and assert invariants in
|
|
288
|
+
| Completion credit and current ready counts drift | Update/read them under the same owner lock and assert invariants in database integration tests. |
|
|
289
289
|
| Operational expiry destroys exact recovery identity | Separate execution expiry from bounded recovery retention and purge only after `recoverable_until`. |
|
|
290
290
|
| Old clients misread the new execution mode | Roll out compatible readers first; they must recognize or safely reject `recover_exact_members` before writes are enabled. |
|
|
291
291
|
| Local tab helper creates a false sense of visibility | Return set-safety only; keep user-visible presentation as a separate receipt. |
|
|
@@ -300,8 +300,8 @@ The backend owns durable identity, membership, milestones, and reconciliation. T
|
|
|
300
300
|
|
|
301
301
|
- **Goal:** Reproduce the production accounting and review-replay failure before changing persistent state.
|
|
302
302
|
- **Requirements:** R1-R3, R8, AE1-AE2.
|
|
303
|
-
- **Repositories:**
|
|
304
|
-
- **Files:**
|
|
303
|
+
- **Repositories:** the private backend.
|
|
304
|
+
- **Files:** backend execution-service tests, backend application-profile tests, and a database integration test beside the existing application-profile integration fixtures.
|
|
305
305
|
- **Approach:** Exercise real execution, batch, member, run, evidence, and attestation rows through checkpoint, truth certification, bulk review outcome, one submission, and progress read. Assert both current counts and cumulative status. Preserve the existing pure calculator test as a smaller unit check.
|
|
306
306
|
- **Test scenarios:** Exact duplicate review intent; stale epoch; one of two submitted; both submitted; ambiguous transport followed by refetch; concurrent submit and advance.
|
|
307
307
|
- **Verification:** At least one characterization fails on pre-fix code for the same durable state mismatch observed in the incident.
|
|
@@ -311,19 +311,19 @@ The backend owns durable identity, membership, milestones, and reconciliation. T
|
|
|
311
311
|
- **Goal:** Make target achievement durable and independent from later lifecycle or attestation projection changes.
|
|
312
312
|
- **Requirements:** R1-R3, R13, AE1.
|
|
313
313
|
- **Dependencies:** U1.
|
|
314
|
-
- **Repositories:**
|
|
315
|
-
- **Files:** next sequential migration, migration runner/test if required by index strategy,
|
|
314
|
+
- **Repositories:** the private backend.
|
|
315
|
+
- **Files:** next sequential migration, migration runner/test if required by index strategy, backend batch service, backend application-profile service, application-profile service tests.
|
|
316
316
|
- **Approach:** Create a separate append-only achievement ledger rather than weakening immutable candidate rows. Insert one owner-bound credit with `ON CONFLICT DO NOTHING` only after durable review outcome or authorized submission repair, in the same transaction as execution status. Expose raw `achievementCount`, compute target-facing `completed = LEAST(achievementCount, target)`, and retain current `durablyReviewReady` and `submitted` for operator detail. Make credit the sole target-achievement input. Before migration, produce a dry-run report grouped by provable source predicate and ambiguous exclusion reason. Backfill every provable submitted row with authorized evidence, or review-ready row with an `awaiting_manual_submit` run and matching active truth attestation for the certified epoch/set; do not discard valid historical overflow above target.
|
|
317
317
|
- **Test scenarios:** Credit set once; submission preserves credit; sibling submission does not reduce total; profile/attestation changes do not erase credit; concurrent writers cannot double-credit; account/execution deletion cascades correctly; ambiguous legacy rows remain uncredited; valid historical overflow remains visible; invalid-evidence revocation is append-only, audited, and cannot be triggered by ordinary lifecycle change.
|
|
318
|
-
- **Verification:** AE1 passes in pure and
|
|
318
|
+
- **Verification:** AE1 passes in pure and database-backed tests, and an advance after target achievement produces no child wave. Before historical writes, a migration-owner review must approve per-predicate counts, sampled source evidence, and ambiguous-exclusion totals; any sample mismatch ships prospective crediting only and defers backfill.
|
|
319
319
|
|
|
320
320
|
### U3. Make review outcomes replay-safe and projections explicit
|
|
321
321
|
|
|
322
322
|
- **Goal:** Return existing durable milestones for exact replay and make historical/current state legible.
|
|
323
323
|
- **Requirements:** R8-R9, R13, AE2.
|
|
324
324
|
- **Dependencies:** U1-U2.
|
|
325
|
-
- **Repositories:**
|
|
326
|
-
- **Files:**
|
|
325
|
+
- **Repositories:** the private backend.
|
|
326
|
+
- **Files:** backend application-profile service, backend Apply HTTP routes, route/runtime tests, service tests, batch projection tests.
|
|
327
327
|
- **Approach:** Require an idempotency key for single and bulk outcomes. Extract a transaction-aware outcome writer, acquire the orchestration-owner lock, process bulk items in canonical member order with per-item savepoints, and persist the canonical request hash plus every item result in the same outer transaction. Return results in original request order; exact retries return `transition=replayed` from the stored result journal. Preserve partial bulk successes. State inference remains a bounded fallback for repairs created before request idempotency. Create a first-class execution-owned handoff receipt only after the ordered ready set is durable. Add explicit historical and current projection fields without mutating old rows.
|
|
328
328
|
- **Test scenarios:** Exact replay; mismatched epoch; mismatched member; partial bulk replay/commit/conflict; old batch with current submitted run; repeated request after lost transport response.
|
|
329
329
|
- **Verification:** Identical replay performs no version/event mutation and returns `awaiting_manual_submit`; historical views cannot be mistaken for actionable current state.
|
|
@@ -333,8 +333,8 @@ The backend owns durable identity, membership, milestones, and reconciliation. T
|
|
|
333
333
|
- **Goal:** Reauthorize only explicitly requested eligible jobs after an expired or terminal execution.
|
|
334
334
|
- **Requirements:** R4-R7, R13, R23, AE3-AE4, AE8.
|
|
335
335
|
- **Dependencies:** U2-U3.
|
|
336
|
-
- **Repositories:**
|
|
337
|
-
- **Files:**
|
|
336
|
+
- **Repositories:** the private backend.
|
|
337
|
+
- **Files:** backend execution contract, backend batch service, backend application-profile service, backend Apply HTTP routes, backend hosted MCP registry, execution service/route/MCP contract tests.
|
|
338
338
|
- **Approach:** Add an authenticated discovery read returning only the user's recoverable execution IDs, snapshot hashes, stable candidate IDs, bounded lifecycle/eligibility codes, and expiry. It must not return profile values, browser data, or local paths. After explicit exact-set confirmation, create recovery with source execution, stable execution-candidate list, source snapshot hash, and idempotency key. Validate ownership and no competing mutable execution under the documented lock order. Evaluate eligibility for the entire asserted source set before creation; if any confirmed candidate is ineligible, fail atomically without creating an execution or substituting another candidate. When all are eligible, preserve source execution/candidate/member lineage, create one immutable non-replenishing recovery execution for the exact confirmed set, and require fresh epochs and browser bindings. Separate operational expiry from a bounded recovery-retention boundary so the nightly purge cannot erase eligible identity mid-window.
|
|
339
339
|
- **Test scenarios:** Complete local context loss followed by discovery; cross-user discovery rejection; explicit exact-set confirmation; newer queue candidates present; saved timestamps unchanged; wholly eligible set; mixed eligible/applied/inactive set rejected with no execution; duplicate IDs; changed requisition; foreign user; concurrent identical recovery; concurrent different recovery; old evidence rejected; second interruption; recovery immediately before and after operational expiry; recovery after daily purge but before `recoverable_until`; rejection after retention expiry.
|
|
340
340
|
- **Verification:** AE3-AE4 pass; AE4 explicitly proves that an ineligible confirmed member yields zero recovery executions, and every execution created by an eligible case has a response membership hash that exactly matches the full asserted source set.
|
|
@@ -344,7 +344,7 @@ The backend owns durable identity, membership, milestones, and reconciliation. T
|
|
|
344
344
|
- **Goal:** Make exact recovery and handoff receipts safely callable from hosted and local agents.
|
|
345
345
|
- **Requirements:** R7, R9-R10, R19-R22.
|
|
346
346
|
- **Dependencies:** U3-U4.
|
|
347
|
-
- **Repositories:**
|
|
347
|
+
- **Repositories:** the private backend, then `trackly-cli`.
|
|
348
348
|
- **Files:** backend protocol route and hosted MCP schemas; `mcp/apply-tools.js`, contract history/fixture, `docs/trackly-tools.md`, MCP schema and hosted-contract tests, package changelog/version files.
|
|
349
349
|
- **Approach:** First ship compatible readers that recognize or safely reject `recover_exact_members`; only then enable writes behind a feature flag. Advertise protocol capability and additive versions. Add public recovery-discovery and exact-recovery tools and result schemas. Include requested-set hash, stable candidate eligibility, fresh-epoch requirements, cumulative `completed`, handoff receipt, and persisted transition result. Keep local-only helpers out of hosted parity expectations through the existing documented exception pattern.
|
|
350
350
|
- **Test scenarios:** Compatible and incompatible clients; hosted/local public-schema equality; cross-user rejection; replay response; missing capability; older active execution recovery.
|
|
@@ -355,7 +355,7 @@ The backend owns durable identity, membership, milestones, and reconciliation. T
|
|
|
355
355
|
- **Goal:** Reconcile every job in the exact staged handoff after the user reports submission.
|
|
356
356
|
- **Requirements:** R9-R13, R19, R21-R22, AE5.
|
|
357
357
|
- **Dependencies:** U3, U5.
|
|
358
|
-
- **Repositories:**
|
|
358
|
+
- **Repositories:** the private backend, `trackly-cli`.
|
|
359
359
|
- **Files:** backend ATS capability/protocol definitions; `skills/trackly-apply/SKILL.md`; `skills/trackly-apply/references/ats-playbook.md`, `review-handoff.md`, `batch-orchestration.md`, `form-integrity.md`; prompt-resource and contract tests.
|
|
360
360
|
- **Approach:** Publish bounded provider strategy enums. Define the local `DetectedSubmission` boundary and require the skill to resolve group language to a handoff receipt, inspect each member's live DOM, classify each as `detected`, `user_confirmed`, `unresolved`, or `contradictory`, record positive evidence through the existing backend writer, preserve unmatched members, and ask once only for unresolved submission authority. URL/title remain identity and navigation signals only.
|
|
361
361
|
- **Test scenarios:** Greenhouse redirect; Ashby unchanged route/banner; Lever redirect and same-route confirmation; one of two succeeds; both succeed quickly; explicit user confirmation preserves stronger stored evidence; contradictory “already applied” state.
|
|
@@ -377,7 +377,7 @@ The backend owns durable identity, membership, milestones, and reconciliation. T
|
|
|
377
377
|
- **Goal:** Make recovery state and the next human/agent action obvious while completing cross-repository release gates.
|
|
378
378
|
- **Requirements:** R7, R19-R22.
|
|
379
379
|
- **Dependencies:** U1-U7.
|
|
380
|
-
- **Repositories:**
|
|
380
|
+
- **Repositories:** the private backend, `trackly-cli`.
|
|
381
381
|
- **Files:** `skills/trackly-apply/references/review-handoff.md`, browser lifecycle/recovery references, MCP prompt resources, changelogs, release notes, doctor/consumer tests.
|
|
382
382
|
- **Approach:** Add deterministic normal-review, recovery, visibility-unverified, upload-manual, and post-submission templates. Each recovery template shows tab, form, and authority receipts separately. Deploy/migrate backend, verify live protocol, publish/install CLI and canonical skill, run doctor, then dogfood exact recovery and grouped reconciliation without agent submission.
|
|
383
383
|
- **Test scenarios:** Restart with blank forms; restored tab with expired authority; unresolved auth member; upload unavailable; one staged member unresolved; no active work; older compatible active execution.
|
|
@@ -389,11 +389,11 @@ The backend owns durable identity, membership, milestones, and reconciliation. T
|
|
|
389
389
|
|
|
390
390
|
| Gate | Repository | Command or check | Required result |
|
|
391
391
|
|---|---|---|---|
|
|
392
|
-
| Type safety |
|
|
393
|
-
| Build |
|
|
394
|
-
| Lint |
|
|
395
|
-
| Targeted Apply tests |
|
|
396
|
-
| Full backend suite |
|
|
392
|
+
| Type safety | the private backend | `npm run typecheck` | No TypeScript errors. |
|
|
393
|
+
| Build | the private backend | `npm run build` | Production bundle succeeds. |
|
|
394
|
+
| Lint | the private backend | `npm run lint` | No lint errors in changed code. |
|
|
395
|
+
| Targeted Apply tests | the private backend | Run the application-profile service, execution, route, MCP-contract, migration, and new database integration test files with Vitest | Every lifecycle and concurrency scenario passes. |
|
|
396
|
+
| Full backend suite | the private backend | `npm test` | Full suite passes. |
|
|
397
397
|
| CLI tests | `trackly-cli` | `npm test` | Full Node test suite passes. |
|
|
398
398
|
| CLI contract fixture | `trackly-cli` | `npm run test:contract-fixture` | Skill and MCP contract assertions pass. |
|
|
399
399
|
| Hosted/local parity | `trackly-cli` | `npm run test:hosted-contract` | Public tool schemas match the deployed backend; only documented local helpers differ. |
|
|
@@ -449,10 +449,10 @@ The backend owns durable identity, membership, milestones, and reconciliation. T
|
|
|
449
449
|
|
|
450
450
|
### Current implementation anchors
|
|
451
451
|
|
|
452
|
-
-
|
|
453
|
-
-
|
|
454
|
-
-
|
|
455
|
-
-
|
|
452
|
+
- backend batch service owns parent executions, immutable child waves, access dispositions, and the pure progress calculator.
|
|
453
|
+
- backend application-profile service reconstructs progress, records outcomes, applies evidence repair, and updates current job state.
|
|
454
|
+
- backend Apply HTTP routes own authenticated HTTP contracts.
|
|
455
|
+
- backend hosted MCP registry owns hosted MCP schemas and prompt resources.
|
|
456
456
|
- `trackly-cli:mcp/apply-tools.js` owns local/hosted HTTP wrappers and local-only helpers.
|
|
457
457
|
- `trackly-cli:skills/trackly-apply/` owns reusable browser mechanics.
|
|
458
458
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Trackly release verification
|
|
2
|
+
|
|
3
|
+
This checklist is mandatory for every Trackly CLI upgrade. A release makes the
|
|
4
|
+
current Trackly tools available in its public packages and deployed MCP surfaces;
|
|
5
|
+
installation verification proves that the named environments are using it.
|
|
6
|
+
Release closeout requires both. Keep publication, installation and authenticated
|
|
7
|
+
browser readiness as separate states.
|
|
8
|
+
|
|
9
|
+
## Before publication
|
|
10
|
+
|
|
11
|
+
- Record the intended npm CLI version, MCP contract version, native Apply skill
|
|
12
|
+
version, plugin version, plugin adaptation lock and any required harness pins.
|
|
13
|
+
These identifiers belong to different packages and need not share a number.
|
|
14
|
+
- Run the repository CI checks: tests, dependency audit and audit policy, checked-in
|
|
15
|
+
hosted contract, plugin submission preflight and package smoke test.
|
|
16
|
+
- When tools or schemas change, compare the final backend candidate with
|
|
17
|
+
the test:hosted-contract npm script and TRACKLY_BACKEND_DIR set to its checkout.
|
|
18
|
+
Verify complete tool names and input schemas, including the reviewed differences
|
|
19
|
+
between local MCP, hosted MCP and the plugin facade. Equal tool counts alone
|
|
20
|
+
do not establish parity; distinct facade allowlists must remain intact.
|
|
21
|
+
- Coordinate the CLI and backend owners and update cloud installer pins and their
|
|
22
|
+
captured tool-contract fixtures through the normal reviewed PR workflow.
|
|
23
|
+
|
|
24
|
+
## Published artifacts and live tools
|
|
25
|
+
|
|
26
|
+
- Verify the exact merged commit, GitHub release, npm latest version, published
|
|
27
|
+
tarball integrity and provenance attestations. Check successful npm and MCP
|
|
28
|
+
Registry publication steps rather than relying only on a release tag.
|
|
29
|
+
- Inspect the published tarball, rather than only the source checkout. Verify
|
|
30
|
+
CLI/server versions, dependency declarations against the source lock, MCP code
|
|
31
|
+
and native skill contents. Boot
|
|
32
|
+
the published MCP server and obtain the full paginated tools/list inventory.
|
|
33
|
+
Compare tool names and input schemas against the intended release contract.
|
|
34
|
+
- Check the production hosted MCP and plugin facade with approved existing
|
|
35
|
+
authentication. Verify their complete discoverable tools and schemas and the
|
|
36
|
+
deployed backend contract. Source fixtures and unauthenticated HTTP challenges
|
|
37
|
+
do not prove authenticated live discovery. Do not run application mutations or
|
|
38
|
+
paid AI calls as release probes.
|
|
39
|
+
- Verify the plugin manifest and adaptation lock against the installed package.
|
|
40
|
+
A newer Git marketplace package does not prove that a separately reviewed
|
|
41
|
+
plugin catalog or connector listing has been updated. Record its actual
|
|
42
|
+
submission, approval and publication state independently.
|
|
43
|
+
|
|
44
|
+
## Installation matrix
|
|
45
|
+
|
|
46
|
+
Record a fresh receipt for each target below. Include its account and environment
|
|
47
|
+
identity, timestamp with time zone, npm package/CLI version, supported Node
|
|
48
|
+
runtime, actual MCP server version, complete tool names and schema digest, native
|
|
49
|
+
skill version and file hashes, installed plugin version and lock hashes, and any
|
|
50
|
+
applicable harness version. Keep credentials and profile answers out of receipts.
|
|
51
|
+
|
|
52
|
+
| Target | Required evidence |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| M1 | Verify the actual application account and its installed package, native skills, plugin and MCP discovery; an SSH login to another account is insufficient. |
|
|
55
|
+
| M4 | Verify the installed package, native skills, plugin manager result and MCP discovery. |
|
|
56
|
+
| Conductor Cloud image | Verify the successful active build, exact merged installer source, pinned package/plugin versions and MCP schema checks. |
|
|
57
|
+
| Conductor retained homes | Verify each in-scope retained home separately; rebuilding the shared image does not update them. Enumerate any dormant or archived homes left unverified. |
|
|
58
|
+
| Current Codex Cloud | Verify the signed-in account, actual helper outputs, saved and published environment revision, then installation and MCP discovery in a restored workspace. A setup-chat success alone is insufficient. |
|
|
59
|
+
|
|
60
|
+
Use the client plugin manager to update installed plugins; never patch versioned
|
|
61
|
+
plugin caches. Verify all applicable clients that consume the package. Where a
|
|
62
|
+
client supports native skills instead of this plugin format, record that
|
|
63
|
+
difference and verify the native skill. A genuinely unsupported plugin manager,
|
|
64
|
+
missing authentication or unreachable environment is an explicit verification
|
|
65
|
+
gap, not a passing result. Preserve unrelated settings and report intentional
|
|
66
|
+
configuration changes separately from configuration preservation checks.
|
|
67
|
+
|
|
68
|
+
## Existing public users
|
|
69
|
+
|
|
70
|
+
An existing global npm install remains at its installed version until updated.
|
|
71
|
+
The supported local update sequence is:
|
|
72
|
+
|
|
73
|
+
~~~sh
|
|
74
|
+
npm install --global trackly-cli@latest
|
|
75
|
+
trackly --version
|
|
76
|
+
trackly agent setup --client both --skills-only
|
|
77
|
+
trackly agent doctor
|
|
78
|
+
~~~
|
|
79
|
+
|
|
80
|
+
The skills-only setup preserves existing MCP registration and authentication.
|
|
81
|
+
Users with one client can select codex or claude. Restart the configured local
|
|
82
|
+
MCP server or its client after updating, refresh discovery, and verify the new
|
|
83
|
+
server version and tools. A running old process keeps the old tool implementation.
|
|
84
|
+
Check the actual Node runtime against the package engines requirement.
|
|
85
|
+
|
|
86
|
+
For the official Git marketplace plugin, use the documented Codex plugin manager:
|
|
87
|
+
|
|
88
|
+
~~~sh
|
|
89
|
+
# Register the official marketplace once, if it is not already configured.
|
|
90
|
+
codex plugin marketplace add trackly-app/trackly-cli --ref main
|
|
91
|
+
codex plugin marketplace upgrade trackly-cli
|
|
92
|
+
codex plugin add trackly@trackly-cli --json
|
|
93
|
+
~~~
|
|
94
|
+
|
|
95
|
+
Verify the returned installed version and package hashes, then reload the client.
|
|
96
|
+
Other plugin catalogs follow their own supported update and approval lifecycle.
|
|
97
|
+
Hosted MCP users receive the deployed server implementation; reconnect or refresh
|
|
98
|
+
client discovery after a tool change and verify the intended authenticated surface.
|
|
99
|
+
|
|
100
|
+
## Closeout
|
|
101
|
+
|
|
102
|
+
Attach the release version set, published artifact evidence, live tool-contract
|
|
103
|
+
checks, installation matrix, changed-configuration checks and remaining gaps to
|
|
104
|
+
the release record. Every applicable row must pass before claiming global parity.
|
|
105
|
+
When only package publication is verified, report publication; when installations
|
|
106
|
+
are verified but live discovery is unavailable, report that narrower result.
|
|
107
|
+
An upgrade remains incomplete until required verification gaps are resolved.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Analytics instrumentation must preserve strict MCP schemas
|
|
3
|
+
category: integration-issues
|
|
4
|
+
module: MCP analytics
|
|
5
|
+
component: observability
|
|
6
|
+
date: 2026-10-19
|
|
7
|
+
problem_type: integration_issue
|
|
8
|
+
severity: medium
|
|
9
|
+
symptoms:
|
|
10
|
+
- Unsupported search arguments reached the handler without an MCP validation error
|
|
11
|
+
- Unknown controls disappeared when optional analytics context was installed
|
|
12
|
+
root_cause: missing_validation
|
|
13
|
+
resolution_type: code_fix
|
|
14
|
+
applies_when:
|
|
15
|
+
- Adding fields to registered MCP tool schemas
|
|
16
|
+
- Wrapping tools with optional analytics context
|
|
17
|
+
tags: [mcp, analytics, zod, strict-schema, sdk]
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
The MCP SDK's registered-tool `update({ paramsSchema })` shape API rebuilds an
|
|
21
|
+
object schema. With SDK 1.32.1 this loses the strict/catchall policy of a real Zod
|
|
22
|
+
object. An unsupported search control can therefore disappear before the
|
|
23
|
+
handler, leaving a successful response to a different request.
|
|
24
|
+
|
|
25
|
+
Extend an existing object schema with its own `extend` method and update the
|
|
26
|
+
callback separately. Preserve the shape-based path only for SDK-created empty
|
|
27
|
+
Mini schemas that do not expose `extend`. Strip the optional analytics context
|
|
28
|
+
before calling the business handler, while retaining all original validation.
|
|
29
|
+
|
|
30
|
+
The regression must use a real MCP client and linked server transport with
|
|
31
|
+
analytics enabled. A schema-only assertion before instrumentation cannot expose
|
|
32
|
+
this defect. Send an unknown argument and assert an MCP error plus zero handler
|
|
33
|
+
calls. Also check that valid calls, optional context, strict/catchall policies,
|
|
34
|
+
and tools without input fields remain compatible.
|
|
35
|
+
|
|
36
|
+
Verification: `node --test test/mcp-analytics.test.js test/mcp-schema.test.js`
|
|
37
|
+
and `npm test`. The real transport regression failed with the old instrumentation
|
|
38
|
+
and passed after the fix. Keyword telemetry continues to retain length only;
|
|
39
|
+
this change does not permit raw query or context storage.
|
|
@@ -40,7 +40,7 @@ Adding more checks to every caller would not close the version-skew hole. Compat
|
|
|
40
40
|
|
|
41
41
|
## Solution
|
|
42
42
|
|
|
43
|
-
Move the mandatory challenge-and-confirm invariant to the backend operation that performs the deletion, then keep MCP callers as thin protocol adapters. The backend implementation is
|
|
43
|
+
Move the mandatory challenge-and-confirm invariant to the backend operation that performs the deletion, then keep MCP callers as thin protocol adapters. The backend implementation is recorded in private repositories; public [trackly-cli PR #90](https://github.com/trackly-app/trackly-cli/pull/90) documents the service-authoritative design.
|
|
44
44
|
|
|
45
45
|
The current CLI publishes `sensitiveRevocationConfirmToken` in the tool schema and forwards the complete request to the application-profile endpoint:
|
|
46
46
|
|
|
@@ -67,7 +67,7 @@ Every backend-classified agent caller, including stale CLI and MCP versions, mus
|
|
|
67
67
|
|
|
68
68
|
The separation also removes semantic duplication. The backend owns the authoritative deletion decision, confirmation validity, revision binding, archive behavior, and mutation. MCP owns discoverability and transport: publish the token field, explain the two-step interaction, preserve the full request, and relay the service response.
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
Private backend tracking records indicate that the scoped-inventory work remains administratively open, but current backend main appears to satisfy its acceptance criteria: the service inventories sensitive and restricted rows across scopes, serializes scope identity into `affectedKeys`, and uses the same user-and-sensitivity predicate for archival and deletion. Close the issue only after confirming the fix is deployed and behaviorally verified; an open tracker alone is not evidence of a current functional gap.
|
|
71
71
|
|
|
72
72
|
## Prevention
|
|
73
73
|
|
|
@@ -83,6 +83,6 @@ Internal/private [close-ai issue #1314](https://github.com/trackly-app/close-ai/
|
|
|
83
83
|
|
|
84
84
|
- [trackly-cli PR #89](https://github.com/trackly-app/trackly-cli/pull/89) — initial caller-owned guard, retained as the superseded attempt
|
|
85
85
|
- [trackly-cli PR #90](https://github.com/trackly-app/trackly-cli/pull/90) — thin caller relay and service-authoritative correction
|
|
86
|
-
-
|
|
87
|
-
-
|
|
86
|
+
- Private backend records document enforcement in the coordinated fix
|
|
87
|
+
- Private scoped-inventory tracking remains open pending deployment and behavior confirmation
|
|
88
88
|
- [Durable exact recovery after browser state loss](durable-exact-recovery-after-browser-state-loss.md) — related Trackly Apply authority-boundary learning
|
package/docs/solutions/integration-issues/durable-exact-recovery-after-browser-state-loss.md
CHANGED
|
@@ -78,7 +78,7 @@ Verification: all 19 skill files matched the release manifest; the CLI suite
|
|
|
78
78
|
passed 721 tests with 20 skipped; the installer test proved no MCP client command
|
|
79
79
|
or configuration write; the Cloud fixture rejected failed skill installation.
|
|
80
80
|
|
|
81
|
-
The coordinated durable recovery work shipped through [trackly-cli PR #100](https://github.com/trackly-app/trackly-cli/pull/100), which enforces exact-set discovery and response validation in the CLI/MCP/skill layer, and
|
|
81
|
+
The coordinated durable recovery work shipped through [trackly-cli PR #100](https://github.com/trackly-app/trackly-cli/pull/100), which enforces exact-set discovery and response validation in the CLI/MCP/skill layer, and a coordinated private backend change, which owns atomic all-or-nothing recovery creation.
|
|
82
82
|
|
|
83
83
|
### 1. Discover a bounded, value-free recovery menu
|
|
84
84
|
|
|
@@ -143,5 +143,5 @@ Inspection epochs close stale-evidence reuse. Field provenance protects user cor
|
|
|
143
143
|
## Related Issues
|
|
144
144
|
|
|
145
145
|
- [Trackly CLI PR #100](https://github.com/trackly-app/trackly-cli/pull/100) — CLI, MCP, and skill-side durable exact recovery contract.
|
|
146
|
-
-
|
|
146
|
+
- Private backend release records document durable recovery lineage and the exact-member control plane.
|
|
147
147
|
- [Durable recovery implementation plan](../../plans/2026-08-05-001-trackly-apply-durable-recovery-reconciliation-plan.md) — historical design record aligned with the released all-or-nothing recovery contract.
|
|
@@ -37,7 +37,7 @@ The OpenAI Platform could authenticate to Trackly but could not complete its man
|
|
|
37
37
|
|
|
38
38
|
OpenAI's current MCP client probes legacy servers with the exact JSON-RPC method `server/discover` before falling back to the initialization-based protocol. Trackly's strict scope gateway treated every unknown method as a scoped operation and returned HTTP 403, so the client interpreted a compatibility probe as an OAuth authorization failure.
|
|
39
39
|
|
|
40
|
-
The backend fix
|
|
40
|
+
The coordinated backend compatibility fix added only `server/discover` to the immutable scope-free protocol-method set. It did not add a tool or grant access to user data. The existing legacy SDK then returns JSON-RPC `-32601 Method not found`, which is the compatibility signal the OpenAI client needs to fall back to `initialize` and `tools/list`.
|
|
41
41
|
|
|
42
42
|
The CLI contract alignment in `trackly-app/trackly-cli#106` records the exact backend source and merge provenance, locks the scope-free set and middleware behavior, and verifies the hosted catalog without broadening tool scopes. The coordinated verifier also checks the published Apply schemas, including `truthCertificationInputSchema`, in `scripts/verify-hosted-contract.js`.
|
|
43
43
|
|
|
@@ -55,5 +55,5 @@ HTTP 401/403 communicates an authentication or authorization failure, so a dual-
|
|
|
55
55
|
|
|
56
56
|
## Related Issues
|
|
57
57
|
|
|
58
|
-
-
|
|
58
|
+
- Private backend release records document the compatibility fix.
|
|
59
59
|
- `trackly-app/trackly-cli#106` — packaged contract and verifier alignment.
|
package/docs/trackly-tools.md
CHANGED
|
@@ -74,17 +74,32 @@ API-key creation attempts.
|
|
|
74
74
|
|
|
75
75
|
- **get_more_tools** — Report what the agent wanted to do when no Trackly tool fit, so Trackly can improve its MCP surface. The tool is always available; report delivery is best-effort and occurs only when usage analytics are enabled.
|
|
76
76
|
- **trackly_search_jobs** — Search/filter jobs.
|
|
77
|
-
- `function` (14 values
|
|
78
|
-
- `
|
|
77
|
+
- `function` (one value) or `jobFunctions` (nonempty array, OR; use one spelling only). The 14 values are: `product`, `engineering`, `design`, `data`, `marketing`, `sales`, `partnerships`, `finance`, `strategy`, `operations`, `people`, `legal`, `support`, `other`
|
|
78
|
+
- `region`: nonempty array of `us`, `europe`, `latam`, `middle_east`, `asia`, `africa`, `canada`, `oceania`, `remote`, `unknown`, combined with OR. Use instead of `locationFilter`; unlike its legacy list form, this supports US plus other regions.
|
|
79
|
+
- `usStates`: nonempty array of US codes `AL`, `AK`, `AZ`, `AR`, `CA`, `CO`, `CT`, `DC`, `DE`, `FL`, `GA`, `HI`, `ID`, `IL`, `IN`, `IA`, `KS`, `KY`, `LA`, `ME`, `MD`, `MA`, `MI`, `MN`, `MS`, `MO`, `MT`, `NE`, `NV`, `NH`, `NJ`, `NM`, `NY`, `NC`, `ND`, `OH`, `OK`, `OR`, `PA`, `RI`, `SC`, `SD`, `TN`, `TX`, `UT`, `VT`, `VA`, `WA`, `WV`, `WI`, `WY`, `PR`, `REMOTE`.
|
|
80
|
+
- `caProvinces`: nonempty array of `AB`, `BC`, `MB`, `NB`, `NL`, `NS`, `NT`, `NU`, `ON`, `PE`, `QC`, `SK`, `YT`, `REMOTE`. Geographic children follow the backend location rollout: parent-scoped OR applies under canonical enforcement; legacy compatibility remains before that. `REMOTE` alongside codes also includes remote jobs.
|
|
81
|
+
- `internshipLevels`: nonempty array of `undergraduate`, `graduate`, `mba`, `not_specified`, combined with OR. Applies to internships when `jobModality=internship` or `all`; full-time jobs remain unaffected. Set `jobModality` explicitly when you want internships; education levels do not change your saved/default modality.
|
|
82
|
+
- `favoriteCompaniesOnly` (boolean): restrict to your favorited companies; `false` explicitly disables it.
|
|
83
|
+
- `postedWithinDays`: `0`, `1`, `3`, `7`, `14`, `30`; `0` disables the saved posting window. Uses the backend discovery-date policy; omitted uses saved/default preferences.
|
|
84
|
+
- `salaryFloorUsd`: integer `0`–`1000000`. Excludes comparable listed annual-USD minimums below this floor. Unknown, hourly and non-USD salaries remain visible; this is not a guaranteed salary threshold. `0` disables the floor; omitted uses saved/default preferences.
|
|
85
|
+
- `since`: ISO-8601 date or timestamp within the past 90 days, never in the future. Filters when Trackly first saw a job, rather than the employer posting date. Prefer a timestamp with timezone for incremental sync. Other saved filters still apply: use `postedWithinDays:0` and `salaryFloorUsd:0` to disable those saved constraints.
|
|
86
|
+
- `locationFilter`: compatibility alias; use instead of `region`. Pass ONE of
|
|
79
87
|
- a single scalar: `us`, `non_us`, `all`, or a region tag (`europe`, `latam`, `middle_east`, `asia`, `africa`, `canada`, `oceania`, `remote`, `unknown`), OR
|
|
80
88
|
- an array of region tags for multi-region (e.g. `["europe", "canada"]`). The array form excludes `us` — combining `us` with other tags in an array causes the backend to silently drop the others. To get "not US" use the scalar `non_us` alone.
|
|
81
89
|
- `jobModality`: `full_time`, `internship`, `all` — employment type, NOT work arrangement.
|
|
82
90
|
- `workArrangements`: one or more of `remote`, `hybrid`, `in_person`, `unspecified`. This is an independent axis and combines with region, employment type, and function filters.
|
|
83
|
-
- `remote` (boolean): filter to remote jobs only. Maps to `usStates=REMOTE`.
|
|
91
|
+
- `remote` (boolean): filter to remote jobs only. Maps to `usStates=REMOTE`. Do not combine `remote=true` with geographic child arrays; use their `REMOTE` token for alternatives. Do not combine a parent region with `remote=true` or only `REMOTE` children: that backend form overrides the parent. Use `workArrangements:["remote"]` plus your region for remote work within that geography.
|
|
84
92
|
- `status`: your application pipeline state. Values: `new`, `applied_confirmed`, `check_later`, `not_interested`, `all`.
|
|
85
93
|
- `sort`: `newest` (default) or `match` (highest match score first; requires a resume on file). Backend rejects the deprecated values `oldest` and `company` with HTTP 400.
|
|
86
|
-
- `sponsorship`: `all` (
|
|
87
|
-
- `keywords
|
|
94
|
+
- `sponsorship`: `all` (no restriction), `exclude_no` (keep yes **and unknown/not mentioned**, exclude reliable explicit no), `only_yes` (reliable explicit yes only). “Not no” means `exclude_no`, never `only_yes`; ask when intent is ambiguous. Filtering requires `status=new`, which is defaulted when omitted; other pipeline views ignore this filter.
|
|
95
|
+
- `keywords`: legacy keyword search, maximum 500 characters. Title/company/location matching is supported; description recall depends on the backend provider. This is not guaranteed full-catalog literal, phrase or exclusion search.
|
|
96
|
+
- `companyId`: positive integer from company search.
|
|
97
|
+
- `limit`: integer `1`–`100`, MCP default `20`; `offset`: non-negative safe integer, default `0`. Keep all filters and sort unchanged while paging. The live catalog may change between calls.
|
|
98
|
+
- Unsupported keys are rejected before HTTP, including when usage analytics is enabled. Industry, specialization and literal-search controls are not exposed until their backend contract is deployed.
|
|
99
|
+
|
|
100
|
+
Example: `{"jobFunctions":["finance","partnerships"],"region":["us","europe"],"sponsorship":"exclude_no","postedWithinDays":7,"limit":20}`.
|
|
101
|
+
The local package and hosted service have separate release lifecycles. Refresh the client's tool discovery after upgrading or reconnecting and use only parameters its current schema advertises.
|
|
102
|
+
|
|
88
103
|
- **trackly_get_job** — Get full job details by ID
|
|
89
104
|
- **trackly_search_companies** — Semantic company search
|
|
90
105
|
- **trackly_list_companies** — List all tracked companies with job counts
|
package/lib/agent.js
CHANGED
|
@@ -8,7 +8,7 @@ const { spawnSync } = require('node:child_process');
|
|
|
8
8
|
const { apiRequest, downloadFile, getConfigPaths, hasAuth, maintenanceOutput } = require('./client');
|
|
9
9
|
const { version: PACKAGE_VERSION } = require('../package.json');
|
|
10
10
|
const { contractVersion: MCP_CONTRACT_VERSION } = require('../contracts/trackly-apply-tools.json');
|
|
11
|
-
const SKILL_VERSION = '4.9.
|
|
11
|
+
const SKILL_VERSION = '4.9.1';
|
|
12
12
|
const SKILL_POLICY_REVISION = SKILL_VERSION;
|
|
13
13
|
const CLI_USER_AGENT = `trackly-cli/${PACKAGE_VERSION}`;
|
|
14
14
|
const MCP_USER_AGENT = `trackly-mcp/${PACKAGE_VERSION}`;
|
package/mcp/analytics.js
CHANGED
|
@@ -615,11 +615,14 @@ function addOptionalContextToTools(server) {
|
|
|
615
615
|
if (Object.hasOwn(shape, 'context')) continue;
|
|
616
616
|
|
|
617
617
|
const originalHandler = tool.handler;
|
|
618
|
+
const schema = tool.inputSchema;
|
|
619
|
+
const context = z.string().optional().describe(CONTEXT_DESCRIPTION);
|
|
620
|
+
// SDK update(paramsSchema) rebuilds objects and drops strict/catchall rules.
|
|
621
|
+
// Retain real object schemas; SDK-created empty Mini schemas need the shape API.
|
|
622
|
+
const paramsSchema = typeof schema.extend === 'function' ? null : { ...shape, context };
|
|
623
|
+
if (paramsSchema === null) tool.inputSchema = schema.extend({ context });
|
|
618
624
|
tool.update({
|
|
619
|
-
paramsSchema: {
|
|
620
|
-
...shape,
|
|
621
|
-
context: z.string().optional().describe(CONTEXT_DESCRIPTION),
|
|
622
|
-
},
|
|
625
|
+
...(paramsSchema === null ? {} : { paramsSchema }),
|
|
623
626
|
callback: (args, extra) => {
|
|
624
627
|
if (!args || typeof args !== 'object' || !Object.hasOwn(args, 'context')) {
|
|
625
628
|
return originalHandler(args, extra);
|
package/mcp/apply-tools.js
CHANGED
|
@@ -791,7 +791,7 @@ const startApplyRunSchema = z.object({
|
|
|
791
791
|
}
|
|
792
792
|
});
|
|
793
793
|
|
|
794
|
-
const APPLY_RELIABILITY_PROMPT = 'Protocol 3.7 / skill 4.9.
|
|
794
|
+
const APPLY_RELIABILITY_PROMPT = 'Protocol 3.7 / skill 4.9.1 reliability gate: recover active work first. Consume the exact proposedWave and frozen accessKnowledge receipts before opening any browser. Rank OPEN ahead of neutral VARIES/UNKNOWN, then fresh ACCOUNT WALL; never auto-select an active user deferment. Historical OPEN never authorizes fill_form; freshLiveProbeRequired remains true. When nextAction is access_review, including ordinary OPEN or neutral proposals, display the exact accessProposal and accessProposal.approvalHash. If the proposal has nonempty members, obtain explicit approval for those exact ordered jobIds and call the hash-bound approval continuation. If the proposal has zero members because all remaining candidates are deferred or exact recovery is blocked by a user deferment, surface the deferred count and stable deferment IDs, offer clear-deferment only for explicitly returned IDs, stop, or expiry, and never send an empty approval. List or create deferments only at job, company, or provider scope through the jobId-bound tools; provider scope applies across companies until explicitly cleared. Do not open a browser or report the queue exhausted. Retain the latest explicit target as hard, prove genuine applicant fields before counting access, obtain approval for the exact accessible jobs before form mutation, fill all deterministic fields before one true-gap question packet, and validate each value-free phase checkpoint. Every checkpoint action must use its canonical continuationAllowed value; review/manual_submit, captcha/at_submit, trust/origin_mismatch, and observability/unverifiable_state require false. Fail closed on a rejected checkpoint. After full local context loss, list recoverable executions, show only stable job identity, obtain explicit confirmation of the exact candidate set, and call exact-member recovery without substitutions. An active personal deferment blocks exact recovery until cleared. Treat recovered tab presence, form state, and mutation authority as three separate facts; reacquire a fresh browser binding and inspection epoch before mutation. Before resolving broad submission statements, list active review handoffs for the execution; use only an explicit receipt or the sole returned active receipt, classify every member as detected, user_confirmed, unresolved, or contradictory, and claim that exact handoff before writing outcomes. Use provider-specific positive success evidence; an unchanged URL or title is never negative evidence. Validate an exact expected browser keep set locally before finalization. For resume uploads, negotiate the browser surface capabilities, identify the semantic control, arm the chooser before clicking, attach the immediately verified file, prove the user-facing filename committed, and recheck parser-modified fields. Use compact snapshots and server-provided mutability. Preserve user-edited and unknown non-empty fields. Never reopen parked work without explicit user resumption. Never click Submit.';
|
|
795
795
|
|
|
796
796
|
function registerApplyTools(
|
|
797
797
|
server,
|
|
@@ -1973,7 +1973,7 @@ function registerApplyTools(
|
|
|
1973
1973
|
content: { type: 'text', text: APPLY_RELIABILITY_PROMPT },
|
|
1974
1974
|
}, {
|
|
1975
1975
|
role: 'user',
|
|
1976
|
-
content: { type: 'text', text: 'Before generating questions or filling controls, run the skill 4.9.
|
|
1976
|
+
content: { type: 'text', text: 'Before generating questions or filling controls, run the skill 4.9.1 deterministic answer resolver and profile lookup gate. Request the current contextual projection for every visible canonical key; compact omissions are not unknown answers. Commit compatible known answers before questions and run scripts/validate-profile-lookup.js on value-free accounting. Redacted or unavailable lookups are access problems, not missing facts; optional unknown demographics may remain blank; live consent stays separate. Classify every visible answer as exact_profile, safe_derivation, supported_draft, missing_fact, live_consent, or forbidden_inference; validate the control type; fill only the first three; and ask only currently visible unresolved needs. Treat the current profile revision as reusable authority, never transcript, screenshot, parser, autocomplete, or cached values. Accessible-first is a hard scheduler invariant: park known authentication, account-creation, OTP, and pre-form-CAPTCHA work without starting a draft while an accessible candidate remains. Curated OPEN and recent accessibility never change allowedOperations to fill_form.' },
|
|
1977
1977
|
}, {
|
|
1978
1978
|
role: 'user',
|
|
1979
1979
|
content: { type: 'text', text: 'Only active=true identifies resumable execution work. Active=false and preserved=true is terminal read-only reconciliation evidence.' },
|
|
@@ -1981,7 +1981,7 @@ function registerApplyTools(
|
|
|
1981
1981
|
role: 'user',
|
|
1982
1982
|
content: {
|
|
1983
1983
|
type: 'text',
|
|
1984
|
-
text: `Protocol 3.7.0 reliability gate for new work: require MCP contract ${APPLY_CONTRACT.contractVersion} and skill 4.9.
|
|
1984
|
+
text: `Protocol 3.7.0 reliability gate for new work: require MCP contract ${APPLY_CONTRACT.contractVersion} and skill 4.9.1. Consume the exact proposedWave with frozen accessKnowledge before opening any browser; same-key advance replay returns identical member IDs, order, and rationale. When nextAction is access_review, including ordinary OPEN or neutral proposals, display the exact access-review receipt and do not report exhaustion. For a nonempty proposal, obtain explicit approval for the unchanged ordered job IDs and server approval hash before probing. For an all-deferred or recovery-blocked proposal with zero members, show the deferred count and stable job/company/provider deferment IDs, offer clear-deferment only for explicitly returned IDs, stop, or expiry, and never send an empty approval. Defer or clear only through jobId-scoped job, company, or provider tools; provider scope applies across companies until explicitly cleared. Never submit provider names, URLs, or raw chat. After complete local context loss, list bounded recovery candidates, obtain explicit confirmation of the exact set, and recover only that set. An active personal deferment blocks exact recovery until cleared. Treat tab recovery, form-state recovery, and mutation authority as independent. List active handoff receipts for the execution before resolving grouped submission statements; use the named receipt or the sole returned active receipt, classify every member, and claim that receipt before recording outcomes. Validate tab keep sets and resume upload stages locally. Never send raw browser values or click Submit.`,
|
|
1985
1985
|
},
|
|
1986
1986
|
}, {
|
|
1987
1987
|
role: 'user',
|
|
@@ -1999,7 +1999,7 @@ function registerApplyTools(
|
|
|
1999
1999
|
role: 'user',
|
|
2000
2000
|
content: {
|
|
2001
2001
|
type: 'text',
|
|
2002
|
-
text: 'Browser preservation clarification: The following conditional rules supersede any unconditional complete-inventory wording earlier in this prompt. For the session-finalizer path, require complete controller and user inventories, build the non-empty keep list, and run the finalizer once. For the documented per-tab durable-handoff path, do not require unavailable inventories; preserve each ledger-mapped live tab with an exact persistence receipt. If no mapped live application tabs remain, skip both finalization and per-tab handoff. For reachability and visibility on the per-tab path, an exact current tab-bound user-visible handoff receipt is valid alternative proof. A user-confirmed direct tab closure may retire its ledger entry only after either complete-union absence or an exact current tab-bound user-side closure/absence receipt. Agent-initiated closure still requires the full close-proof gate. Policy 4.9.
|
|
2002
|
+
text: 'Browser preservation clarification: The following conditional rules supersede any unconditional complete-inventory wording earlier in this prompt. For the session-finalizer path, require complete controller and user inventories, build the non-empty keep list, and run the finalizer once. For the documented per-tab durable-handoff path, do not require unavailable inventories; preserve each ledger-mapped live tab with an exact persistence receipt. If no mapped live application tabs remain, skip both finalization and per-tab handoff. For reachability and visibility on the per-tab path, an exact current tab-bound user-visible handoff receipt is valid alternative proof. A user-confirmed direct tab closure may retire its ledger entry only after either complete-union absence or an exact current tab-bound user-side closure/absence receipt. Agent-initiated closure still requires the full close-proof gate. Policy 4.9.1 also accepts the documented markHandoff path described in the installed skill references/handoff-compatibility.md; this path supersedes earlier receipt-only readiness requirements. Before form mutation, verify that the runtime documentation promises to keep the tab across normal turn cleanup and that every mapped application tab is available. Bind each exact job/run/tab, await its markHandoff(), and check the controller inventory and user inventory where exposed; failed calls or missing tabs block mutation. Before ending every turn, await markHandoff() for every mapped live application tab, then check the controller inventory and user inventory where exposed. Failed marks or missing tabs block the handoff. Report incomplete inventories as unverified, never as closure proof. On the next turn, reclaim each exact tab, recheck identity and required fields, and re-mark before any later handoff. markHandoff returns Promise<void>; successful completion and inventory checks are observations, not a native persistence receipt or proof that draft state survives a crash. Never label mark-only evidence durable_handoff_receipt or change validators to manufacture receipt proof. Keep manual Submit, visible form verification, and the installed skill platform capability requirements.',
|
|
2003
2003
|
},
|
|
2004
2004
|
}, {
|
|
2005
2005
|
role: 'user',
|
package/mcp/server.js
CHANGED
|
@@ -52,6 +52,14 @@ const JOB_MODALITIES = ['full_time', 'internship', 'all'];
|
|
|
52
52
|
// Independent from geography and employment type. Matches the backend's
|
|
53
53
|
// workArrangements query contract and job_postings constraint.
|
|
54
54
|
const WORK_ARRANGEMENTS = ['remote', 'hybrid', 'in_person', 'unspecified'];
|
|
55
|
+
const US_STATES = [
|
|
56
|
+
'AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'GA', 'HI', 'ID',
|
|
57
|
+
'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO',
|
|
58
|
+
'MT', 'NE', 'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA',
|
|
59
|
+
'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', 'WY', 'PR', 'REMOTE',
|
|
60
|
+
];
|
|
61
|
+
const CA_PROVINCES = ['AB', 'BC', 'MB', 'NB', 'NL', 'NS', 'NT', 'NU', 'ON', 'PE', 'QC', 'SK', 'YT', 'REMOTE'];
|
|
62
|
+
const INTERNSHIP_LEVELS = ['undergraduate', 'graduate', 'mba', 'not_specified'];
|
|
55
63
|
|
|
56
64
|
const MCP_MISSING_CAPABILITY_VALUES = [
|
|
57
65
|
'search', 'inspect', 'compare', 'rank', 'recommend', 'track', 'update', 'apply',
|
|
@@ -254,12 +262,21 @@ function createServer() {
|
|
|
254
262
|
version: PACKAGE_VERSION,
|
|
255
263
|
});
|
|
256
264
|
|
|
257
|
-
server.
|
|
265
|
+
server.registerTool(
|
|
258
266
|
'trackly_search_jobs',
|
|
259
|
-
'Search and filter job postings. Returns matching
|
|
260
|
-
{
|
|
267
|
+
{ description: 'Search and filter job postings. Returns one matching page with title, company, location, structured fields and backend pagination. Use jobFunctions for multiple roles, region for multiple regions, and usStates/caProvinces for geographic detail. Work arrangement is independent from geography and employment type. Unknown sponsorship and non-comparable salary data stay visible with exclude_no and salaryFloorUsd. Never invent industry, specialization, exclusion or literal-search controls; ask when sponsorship intent is ambiguous.',
|
|
268
|
+
inputSchema: z.object({
|
|
261
269
|
function: z.enum(JOB_FUNCTIONS).optional().describe('Job function filter. One of: ' + JOB_FUNCTIONS.join(', ')),
|
|
262
|
-
|
|
270
|
+
jobFunctions: z.array(z.enum(JOB_FUNCTIONS)).min(1).max(14).optional().describe('Multiple job functions, combined with OR. Use instead of function. Omit both to request all 14 functions.'),
|
|
271
|
+
companyId: z.number().int().positive().optional().describe('Filter jobs by company ID (get from trackly_search_companies)'),
|
|
272
|
+
region: z.array(z.enum(REGION_TAGS)).min(1).max(10).optional().describe('Region tags combined with OR, including us. Use instead of locationFilter; this avoids its legacy mixed-US list limitation. Geographic child behavior follows the backend location rollout.'),
|
|
273
|
+
usStates: z.array(z.enum(US_STATES)).min(1).max(53).optional().describe('US state codes, DC, PR, or REMOTE. Multiple values select alternatives. REMOTE alongside codes also includes remote jobs. Under canonical location enforcement children select their parent; legacy rollout keeps compatibility behavior. Do not combine with remote=true.'),
|
|
274
|
+
caProvinces: z.array(z.enum(CA_PROVINCES)).min(1).max(14).optional().describe('Canadian province/territory codes or REMOTE. Multiple values select alternatives. REMOTE alongside codes also includes remote jobs. Parent-scoped OR requires canonical location enforcement. Do not combine with remote=true.'),
|
|
275
|
+
internshipLevels: z.array(z.enum(INTERNSHIP_LEVELS)).min(1).max(4).optional().describe('Internship education levels, combined with OR: undergraduate, graduate, mba, not_specified. Applies to internships in jobModality=internship or all; full-time jobs remain unaffected. This does not change the saved/default modality: set jobModality explicitly when you want internships.'),
|
|
276
|
+
favoriteCompaniesOnly: z.boolean().optional().describe('true limits results to your favorited companies; false explicitly disables this restriction.'),
|
|
277
|
+
postedWithinDays: z.union([z.literal(0), z.literal(1), z.literal(3), z.literal(7), z.literal(14), z.literal(30)]).optional().describe('Posting-recency window in days: 1, 3, 7, 14, or 30; 0 disables the saved window. Uses the backend discovery-date policy; not an incremental-sync cursor. Omit to use the saved/default window.'),
|
|
278
|
+
salaryFloorUsd: z.number().int().min(0).max(1_000_000).optional().describe('Annual USD minimum salary floor. Excludes comparable listed annual-USD minimums below this amount; unknown, hourly and non-USD salaries remain visible. 0 disables the floor; omitted uses saved/default preferences. This is not a guaranteed salary threshold.'),
|
|
279
|
+
since: z.string().regex(/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})?)?$/).optional().describe('Incremental sync: jobs first seen by Trackly since this ISO-8601 date/timestamp, not employer posting dates. Must be within the past 90 days and not in the future; backend validates the range. Prefer a timestamp with timezone. Other saved filters still apply; use postedWithinDays=0 and salaryFloorUsd=0 to disable those saved constraints.'),
|
|
263
280
|
locationFilter: z.union([
|
|
264
281
|
z.enum(['us', 'non_us', 'all']),
|
|
265
282
|
z.enum(REGION_TAGS),
|
|
@@ -273,19 +290,34 @@ function createServer() {
|
|
|
273
290
|
workArrangements: z.array(z.enum(WORK_ARRANGEMENTS)).min(1).max(4).optional().describe(
|
|
274
291
|
'Work arrangement filter, independent from geography and employment type. Values: remote, hybrid, in_person, unspecified. Multiple values use OR semantics.'
|
|
275
292
|
),
|
|
276
|
-
remote: z.boolean().optional().describe('Filter to remote jobs
|
|
293
|
+
remote: z.boolean().optional().describe('Filter to global remote-only jobs (maps to usStates=REMOTE). Do not combine with region/locationFilter or geographic child arrays. For remote work within a region, use workArrangements=["remote"] with region instead.'),
|
|
277
294
|
status: z.enum(STATUS_VALUES).optional().describe(
|
|
278
295
|
'Filter by YOUR application pipeline state. Not a generic job-posting status. Values: ' + STATUS_VALUES.join(', ')
|
|
279
296
|
),
|
|
280
297
|
sort: z.enum(SORT_VALUES).optional().describe('Sort order: newest (default) or match (highest match score first; requires resume). Backend rejects legacy oldest/company with HTTP 400.'),
|
|
281
298
|
sponsorship: z.enum(SPONSORSHIP_VALUES).optional().describe(
|
|
282
|
-
"Visa sponsorship
|
|
299
|
+
"Visa sponsorship: all applies no filter; exclude_no removes reliable explicit no statements and keeps yes plus unknown/not mentioned; only_yes requires reliable explicit yes and excludes unknown. 'Not no' means exclude_no, never only_yes. Ask when intent is ambiguous. Filtering requires status=new (defaulted when omitted); the backend ignores sponsorship on other pipeline views."
|
|
283
300
|
),
|
|
284
|
-
limit: z.number().max(
|
|
285
|
-
offset: z.number().min(0).optional().describe('Pagination offset'),
|
|
286
|
-
keywords: z.string().max(500).optional().describe('
|
|
287
|
-
},
|
|
301
|
+
limit: z.number().int().min(1).max(100).optional().describe('Page size, integer 1-100; MCP default 20 for concise agent results. Request up to the full API limit of 100 when needed.'),
|
|
302
|
+
offset: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER).optional().describe('Pagination offset, non-negative integer. Keep filters and sort unchanged when paging; live catalog changes can move rows.'),
|
|
303
|
+
keywords: z.string().max(500).optional().describe('Legacy keyword search. Title/company/location matches are supported; description recall depends on the backend provider. Not a guaranteed full-catalog literal/phrase/exclusion search.'),
|
|
304
|
+
}).strict() },
|
|
288
305
|
wrapTool(async (params) => {
|
|
306
|
+
if (params.function !== undefined && params.jobFunctions !== undefined) {
|
|
307
|
+
throw new Error('Use either function or jobFunctions, not both.');
|
|
308
|
+
}
|
|
309
|
+
if (params.region !== undefined && params.locationFilter !== undefined) {
|
|
310
|
+
throw new Error('Use either region or locationFilter, not both.');
|
|
311
|
+
}
|
|
312
|
+
if (params.remote === true && (params.usStates !== undefined || params.caProvinces !== undefined)) {
|
|
313
|
+
throw new Error('Use remote=true alone for remote-only jobs, or include REMOTE in the geographic arrays for alternatives.');
|
|
314
|
+
}
|
|
315
|
+
const hasParentLocation = params.region !== undefined || params.locationFilter !== undefined;
|
|
316
|
+
const childLocations = [...(params.usStates ?? []), ...(params.caProvinces ?? [])];
|
|
317
|
+
const remoteOnlyChildren = childLocations.length > 0 && childLocations.every((value) => value === 'REMOTE');
|
|
318
|
+
if (hasParentLocation && (params.remote === true || remoteOnlyChildren)) {
|
|
319
|
+
throw new Error('REMOTE-only location overrides the parent region. Use workArrangements=["remote"] with region or locationFilter for remote work within that geography.');
|
|
320
|
+
}
|
|
289
321
|
const qs = new URLSearchParams();
|
|
290
322
|
// When `function` isn't specified, request ALL canonical functions so the
|
|
291
323
|
// backend takes the all-roles short-circuit (granola-followup-app
|
|
@@ -296,6 +328,13 @@ function createServer() {
|
|
|
296
328
|
// Health (id=3350) — both had non-PM-only job sets and search_jobs
|
|
297
329
|
// returned total=0 without an explicit function filter.
|
|
298
330
|
qs.set('jobFunction', params.function !== undefined ? params.function : JOB_FUNCTIONS.join(','));
|
|
331
|
+
if (params.jobFunctions !== undefined) qs.set('jobFunction', params.jobFunctions.join(','));
|
|
332
|
+
for (const key of ['region', 'usStates', 'caProvinces', 'internshipLevels']) {
|
|
333
|
+
if (params[key] !== undefined) qs.set(key, params[key].join(','));
|
|
334
|
+
}
|
|
335
|
+
for (const key of ['favoriteCompaniesOnly', 'postedWithinDays', 'salaryFloorUsd', 'since']) {
|
|
336
|
+
if (params[key] !== undefined) qs.set(key, String(params[key]));
|
|
337
|
+
}
|
|
299
338
|
if (params.companyId !== undefined) qs.set('companyId', String(params.companyId));
|
|
300
339
|
if (params.locationFilter !== undefined) {
|
|
301
340
|
const value = Array.isArray(params.locationFilter)
|
|
@@ -318,7 +357,7 @@ function createServer() {
|
|
|
318
357
|
qs.set('status', 'new');
|
|
319
358
|
}
|
|
320
359
|
}
|
|
321
|
-
|
|
360
|
+
qs.set('limit', String(params.limit ?? 20));
|
|
322
361
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
|
323
362
|
if (params.keywords !== undefined) qs.set('search', params.keywords);
|
|
324
363
|
return apiRequest('GET', `/api/jobscout/jobs?${qs.toString()}`, null, false, false, MCP_USER_AGENT);
|
|
@@ -462,7 +501,7 @@ function createServer() {
|
|
|
462
501
|
if (askResult.jobsUrl) {
|
|
463
502
|
// Path allowlist: /ask returns a jobsUrl string. normalizeEndpoint already blocks
|
|
464
503
|
// cross-origin fetches, but a compromised backend could emit a same-origin path
|
|
465
|
-
//
|
|
504
|
+
// outside the job API. Only follow the two handlers /ask is designed to
|
|
466
505
|
// route to. Mirrors the same guard in bin/trackly:cmdAsk.
|
|
467
506
|
const JOBS_URL_ALLOWLIST = /^\/api\/(v1|jobscout)\/jobs(\?|$)/;
|
|
468
507
|
if (JOBS_URL_ALLOWLIST.test(askResult.jobsUrl)) {
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackly-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "trackly-cli",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.21.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@modelcontextprotocol/sdk": "1.32.1",
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackly-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"mcpName": "io.github.trackly-app/trackly",
|
|
5
|
-
"description": "
|
|
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": {
|
|
7
7
|
"trackly": "./bin/trackly"
|
|
8
8
|
},
|
|
@@ -52,7 +52,12 @@
|
|
|
52
52
|
"job-application",
|
|
53
53
|
"job-hunting",
|
|
54
54
|
"career",
|
|
55
|
-
"terminal"
|
|
55
|
+
"terminal",
|
|
56
|
+
"codex",
|
|
57
|
+
"job-search-mcp",
|
|
58
|
+
"visa-sponsorship",
|
|
59
|
+
"internships",
|
|
60
|
+
"remote-jobs"
|
|
56
61
|
],
|
|
57
62
|
"author": "Kevin Astuhuaman <hello@usetrackly.app>",
|
|
58
63
|
"license": "MIT",
|
package/server.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.trackly-app/trackly",
|
|
4
4
|
"title": "Trackly CLI",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Search 250K+ jobs, 5,400+ companies. MCP for Claude, ChatGPT, Codex and Cursor.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"url": "https://github.com/trackly-app/trackly-cli",
|
|
8
8
|
"source": "github"
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
]
|
|
18
18
|
}
|
|
19
19
|
],
|
|
20
|
-
"version": "0.
|
|
20
|
+
"version": "0.21.0",
|
|
21
21
|
"packages": [
|
|
22
22
|
{
|
|
23
23
|
"registryType": "npm",
|
|
24
24
|
"identifier": "trackly-cli",
|
|
25
|
-
"version": "0.
|
|
25
|
+
"version": "0.21.0",
|
|
26
26
|
"runtimeHint": "npx",
|
|
27
27
|
"runtimeArguments": [
|
|
28
28
|
{
|
|
@@ -9,7 +9,7 @@ Use Trackly as the source of truth for profile answers, documents, queue decisio
|
|
|
9
9
|
|
|
10
10
|
## Skill version
|
|
11
11
|
|
|
12
|
-
Skill version: `4.9.
|
|
12
|
+
Skill version: `4.9.1`. Before browser readiness read [references/handoff-compatibility.md](references/handoff-compatibility.md). This policy takes precedence over returned-persistence-receipt or finalizer requirements elsewhere when using the documented markHandoff path. Other review and safety gates remain unchanged.
|
|
13
13
|
|
|
14
14
|
## Non-negotiable rules
|
|
15
15
|
|
|
@@ -39,7 +39,7 @@ value-free phase receipt with `scripts/validate-phase-checkpoint.js` before
|
|
|
39
39
|
claiming that phase complete.
|
|
40
40
|
Read [references/legal-clarifications.md](references/legal-clarifications.md) when a form asks for a legal, regulatory, consent, or employer-relationship decision.
|
|
41
41
|
|
|
42
|
-
1. Call `trackly_get_apply_protocol`. Skill 4.9.
|
|
42
|
+
1. Call `trackly_get_apply_protocol`. Skill 4.9.1 requires protocol 3.7.0 or newer for new reliability work. Protocol 3.2 remains valid only for an already-active explicit legacy single run; an already-active explicit 3.2 single run may finish through its legacy path. Preserve the protocol's documented recovery paths for already-active older work. Require `compatibleSkillMajor: 4` and `compatibleSkillMinimumVersion` no newer than this installed skill. Reject an older or incompatible version for new work and report that the backend must finish updating or `trackly agent setup` must update the skill. Protocol 3.2 added exact-origin trust for jobs Trackly ingested directly from employer careers sources; do not recreate the retired ownership-timestamp gate in the client.
|
|
43
43
|
2. Call `trackly_get_profile_onboarding` or fetch both the profile schema and application profile. When present, render `schema.screens` in ascending `order` as one grouped question packet per screen. Within a screen, preserve category `order` and then field `order`; use each field's user-facing `rationale` when the user asks why it is needed. Ask only unknown or confirmation-needed fields and honor `consistencyRules` before submitting profile changes, resolving contradictions with the user instead of guessing. When `schema.screens` is absent on a legacy backend, fall back to the existing category-based onboarding behavior. Treat `completeness.percent` as required onboarding readiness only. Use `coverage.missingReusableKeys` to explain reusable optional gaps, while `coverage.contextualKeys` are intentionally asked only on the relevant employer form. Do not claim that 100% required completeness answers every possible application question.
|
|
44
44
|
3. Save answers with `trackly_update_application_profile`:
|
|
45
45
|
- Use `answered`, `intentionally_blank`, `declined`, or `unknown` exactly.
|
|
@@ -85,7 +85,7 @@ Read [references/legal-clarifications.md](references/legal-clarifications.md) wh
|
|
|
85
85
|
- For each fixed batch member, preserve an explicit job ID -> application run ID -> browser tab mapping. Complete the full start -> conditional resume preparation/confirmation/verification when an upload control exists -> form completion -> `review_ready` lifecycle for every member. A review-ready run does not block the next member. Never submit any of them.
|
|
86
86
|
6. When the selected job or current fixed batch member has no `runId`, call `trackly_start_apply_run` with the complete `batchId`, `memberId`, `expectedMemberVersion`, `expectedInspectionEpoch`, and `leaseToken` returned by Trackly. This is the sanctioned idempotent lookup for a recovered member whose `runId` is absent; call it with the exact recovered binding and Trackly will reuse the bound active run. When the member already has a `runId`, do not call `trackly_start_apply_run` in this or any subsequent start step: reuse that exact run and bind or recover its browser surface through `trackly_bind_apply_surface` as required. Never create a replacement run because browser control was interrupted.
|
|
87
87
|
- If a bound start returns a transport failure, a non-access HTTP 5xx response, or an error explicitly marked `retryable: true`, preserve the frozen member and browser state. Refetch the active batch, renew its lease, and retry the same complete binding once. Classify the retry response independently with these same rules: route canonical `maintenance_mode` or legacy `planned_maintenance` directly through **Resume after maintenance**; surface controlled-access/request errors marked `retryable: false` and every other HTTP 4xx response unchanged; only a second transport failure, non-access HTTP 5xx response, or explicitly retryable error becomes `backend_run_start_unavailable`. Route maintenance on either attempt without consuming or relabeling the retry, and never relabel a permanent retry response as an outage. For `backend_run_start_unavailable`, continue other members and report a Trackly control-plane failure. Do not call `trackly_checkpoint_apply_batch` for this condition because start failure has not produced the required run ID. The unchanged frozen member is the durable resume point. Never switch that frozen member to an unbound legacy run or blame the employer form.
|
|
88
|
-
- Require `run.protocolVersion` to be 3.1.0 or newer and to share protocol major 3 with the fetched protocol. A new reliability execution member requires 3.7.0 or newer; already-active older work may use only the recovery path published by its fetched protocol. Never continue a pre-evidence 3.0.x run under skill 4.9.
|
|
88
|
+
- Require `run.protocolVersion` to be 3.1.0 or newer and to share protocol major 3 with the fetched protocol. A new reliability execution member requires 3.7.0 or newer; already-active older work may use only the recovery path published by its fetched protocol. Never continue a pre-evidence 3.0.x run under skill 4.9.1. Preserve that run instead of starting a replacement, record it `blocked` with a value-free protocol-upgrade reason when possible, and tell the user the saved job can be retried only after the stale run is cleared through Trackly's supported lifecycle. Stop and refetch the protocol and active execution or batch if support level, execution mode, provider, required scenarios, authorized origin policy, member version, or inspection epoch changes after run creation.
|
|
89
89
|
- In guided mode, inspect the page before preparing any resume bytes. Confirm the employer, role, HTTPS origin, reachable review path, semantic controls, whether an attachment control exists, and absence of a credential, verification, CAPTCHA, or submit-only wall. A missing file input is not itself a blocker; skip the resume path when the application has no attachment control. Any other failed precondition is an execution blocker, not permission to improvise.
|
|
90
90
|
7. Pass the browser readiness gate before preparing resume bytes:
|
|
91
91
|
- Use semantic browser control through Codex in-app browser controls, Chrome MCP/extension browser control, or Claude in Chrome.
|
|
@@ -36,7 +36,9 @@ an observation.
|
|
|
36
36
|
|
|
37
37
|
Inventory all visible questions first. Map their canonical keys against the current schema, including optional diversity fields. Fetch one current contextual application profile with sensitive values when existing storage consent permits, supplying the actual provider, company ID and jurisdiction. Reuse this fetch for the packet; do not ask from an incomplete compact projection. If required values are redacted or unavailable, report the access limitation and recover it instead of calling the answer unknown.
|
|
38
38
|
|
|
39
|
-
For each visible control, retain a value-free receipt with its fingerprint, whether schema and relevant profile projection were fetched, resolved state (`answered`, `unknown`, `redacted`, `unavailable`, or `live_consent`), required flag, intended disposition (`fill`, `preserve`, `ask`, `defer_optional`, or `blocked`), and committed flag. Run `node scripts/validate-profile-lookup.js receipt.json` before presenting a packet. This validator checks local accounting; it does not call Trackly or prove a fetch happened. Retain actual lookup and committed-control evidence locally. Answered compatible fields must be filled and verified or preserve a user edit; they cannot appear in the questions. Optional unknown demographics may stay blank without interrupting the user. Required unknown facts and genuinely live decisions can be asked together after known answers commit. An unavailable profile lookup is a lookup problem, never evidence that the user has no saved answer.
|
|
39
|
+
For each visible control, retain a value-free receipt with its fingerprint, whether schema and relevant profile projection were fetched, resolved state (`answered`, `unknown`, `redacted`, `unavailable`, or `live_consent`), required flag, intended disposition (`fill`, `preserve`, `ask`, `defer_optional`, or `blocked`), and committed flag. Run `node "<skill-dir>/scripts/validate-profile-lookup.js" receipt.json` before presenting a packet. This validator checks local accounting; it does not call Trackly or prove a fetch happened. Retain actual lookup and committed-control evidence locally. Answered compatible fields must be filled and verified or preserve a user edit; they cannot appear in the questions. Optional unknown demographics may stay blank without interrupting the user. Required unknown facts and genuinely live decisions can be asked together after known answers commit. An unavailable profile lookup is a lookup problem, never evidence that the user has no saved answer.
|
|
40
|
+
Resolve `<skill-dir>` to the loaded skill’s absolute directory before invoking the command, regardless of the application repository working directory. Include explicit `workMode` (`accessible_execution` or `fixed_inspection`) in the value-free lookup receipt. `profileRevision` must be a positive safe integer for accessible execution; fixed inspection also permits zero. Missing or unknown work mode fails closed.
|
|
41
|
+
|
|
40
42
|
|
|
41
43
|
For diversity surveys check the schema's gender identity, transgender, sexual orientation, ethnicity, age range, disability, veteran and any other visible concepts. Match supported option synonyms, such as Hispanic/Latino to Hispanic/Latine, without inventing identity. A negative disability or veteran answer excludes that option; it does not prove “None of the above” for a multi-select community question that also asks about parenthood, immigration or neurodiversity. Leave unsupported optional options unselected.
|
|
42
44
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Browser handoff compatibility — 4.9.
|
|
1
|
+
# Browser handoff compatibility — 4.9.1
|
|
2
2
|
|
|
3
3
|
This preservation-only policy supersedes contradictory returned-receipt and finalizer requirements in this skill. All backend, submission, resume, origin, ownership, form integrity, and actual visibility gates remain unchanged. Never fabricate evidence.
|
|
4
4
|
|
|
@@ -419,8 +419,9 @@ function validateSelection(receipt, expectedContext) {
|
|
|
419
419
|
const normalized = receipt.approvedJobIds.filter((id) => Number.isSafeInteger(id) && id > 0);
|
|
420
420
|
if (normalized.length !== receipt.approvedJobIds.length) errors.push('approvedJobIds must contain positive safe integers');
|
|
421
421
|
if (new Set(normalized).size !== normalized.length) errors.push('approvedJobIds must be unique');
|
|
422
|
-
if (receipt.approvedJobIds.length === 0
|
|
423
|
-
|
|
422
|
+
if (receipt.approvedJobIds.length === 0
|
|
423
|
+
&& (receipt.workMode !== 'accessible_execution' || receipt.queueExhausted !== true)) {
|
|
424
|
+
errors.push('approvedJobIds may be empty only for accessible_execution when queueExhausted is true');
|
|
424
425
|
}
|
|
425
426
|
}
|
|
426
427
|
requireTrue(errors, receipt, 'approvalRecorded');
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
function validate(receipt) {
|
|
5
5
|
const errors = [];
|
|
6
|
-
if (!receipt || !Number.isSafeInteger(receipt.profileRevision) || receipt.profileRevision < 1 || !Array.isArray(receipt.controls) || !receipt.controls.length) return ['invalid_receipt'];
|
|
7
|
-
if (Object.keys(receipt).some(k => !['profileRevision', 'controls'].includes(k))) errors.push('unexpected_receipt_fields');
|
|
6
|
+
if (!receipt || !Number.isSafeInteger(receipt.profileRevision) || receipt.profileRevision < (receipt.workMode === 'fixed_inspection' ? 0 : 1) || !Array.isArray(receipt.controls) || !receipt.controls.length) return ['invalid_receipt'];
|
|
7
|
+
if (Object.keys(receipt).some(k => !['workMode', 'profileRevision', 'controls'].includes(k))) errors.push('unexpected_receipt_fields');
|
|
8
|
+
if (!['accessible_execution', 'fixed_inspection'].includes(receipt.workMode)) errors.push('invalid_work_mode');
|
|
8
9
|
const seen = new Set();
|
|
9
10
|
for (const c of receipt.controls) {
|
|
10
11
|
if (!c || typeof c !== 'object' || Array.isArray(c)) { errors.push('invalid_control'); continue; }
|