job-application-agent 2.0.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/LICENSE +21 -0
- package/README.md +179 -0
- package/bin/job-application-agent.mjs +7 -0
- package/installer/src/cli.mjs +85 -0
- package/installer/src/installer.mjs +110 -0
- package/installer/src/runner.mjs +23 -0
- package/installer/src/scheduler.mjs +79 -0
- package/job-application-agent/SKILL.md +88 -0
- package/job-application-agent/agents/openai.yaml +4 -0
- package/job-application-agent/references/ANALYTICS.md +65 -0
- package/job-application-agent/references/APPLICATION_GUIDANCE.md +12 -0
- package/job-application-agent/references/SCHEMAS.md +156 -0
- package/job-application-agent/scripts/job-application.mjs +890 -0
- package/job-application-agent/scripts/telemetry-client.mjs +176 -0
- package/job-application-agent/scripts/telemetry-schema.mjs +161 -0
- package/job-application-agent/tests/job-application.test.mjs +432 -0
- package/job-application-agent/tests/privacy-audit.test.mjs +42 -0
- package/job-application-agent/tests/telemetry-client.test.mjs +132 -0
- package/job-application-agent/tests/telemetry-schema.test.mjs +98 -0
- package/package.json +46 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
canonicalizeJobUrl,
|
|
6
|
+
createTelemetryEnvelope,
|
|
7
|
+
jobIdentity,
|
|
8
|
+
validateEvent,
|
|
9
|
+
} from '../scripts/telemetry-schema.mjs';
|
|
10
|
+
|
|
11
|
+
const baseJob = {
|
|
12
|
+
company: 'Example AI',
|
|
13
|
+
title: 'Staff Product Engineer',
|
|
14
|
+
jobHash: 'a'.repeat(64),
|
|
15
|
+
domain: 'jobs.example.com',
|
|
16
|
+
ats: 'greenhouse',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
test('canonicalizes job URLs and hashes the destination without query data', async () => {
|
|
20
|
+
assert.equal(canonicalizeJobUrl('https://Jobs.Example.com/role/123/?utm_source=x#apply'), 'https://jobs.example.com/role/123');
|
|
21
|
+
const first = await jobIdentity('https://jobs.example.com/role/123?ref=friend');
|
|
22
|
+
const second = await jobIdentity('https://jobs.example.com/role/123?utm_source=x');
|
|
23
|
+
assert.equal(first.jobHash, second.jobHash);
|
|
24
|
+
assert.equal(first.domain, 'jobs.example.com');
|
|
25
|
+
assert.equal(first.jobHash.length, 64);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('accepts rich structured job and workflow events', () => {
|
|
29
|
+
const event = validateEvent({
|
|
30
|
+
event: 'job_assessed',
|
|
31
|
+
properties: {
|
|
32
|
+
...baseJob,
|
|
33
|
+
fitScore: 84,
|
|
34
|
+
eligibility: 'eligible',
|
|
35
|
+
decision: 'review',
|
|
36
|
+
matchTags: ['role_family', 'skills', 'remote'],
|
|
37
|
+
gapTags: ['salary_unknown'],
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
assert.equal(event.properties.company, 'Example AI');
|
|
41
|
+
assert.equal(event.properties.fitScore, 84);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('rejects direct identity, free-form content, unknown properties, and oversized payloads', () => {
|
|
45
|
+
const valid = { event: 'job_assessed', properties: { ...baseJob, fitScore: 80, eligibility: 'eligible', decision: 'review', matchTags: [], gapTags: [] } };
|
|
46
|
+
assert.throws(() => validateEvent({ ...valid, properties: { ...valid.properties, company: 'candidate@example.com' } }), /identity/i);
|
|
47
|
+
assert.throws(() => validateEvent({ ...valid, properties: { ...valid.properties, title: 'Call +1 555 555 1212' } }), /identity/i);
|
|
48
|
+
assert.throws(() => validateEvent({ ...valid, properties: { ...valid.properties, candidateLocation: 'Toronto' } }), /unknown/i);
|
|
49
|
+
assert.throws(() => validateEvent({ event: 'skill_error', properties: { errorCode: 'network_failure', stage: 'submission', recoverable: true, rawError: 'private text' } }), /unknown/i);
|
|
50
|
+
assert.throws(() => validateEvent({ ...valid, properties: { ...valid.properties, company: 'x'.repeat(5000) } }), /company/i);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('creates a bounded relay envelope without client timestamps or direct identity', () => {
|
|
54
|
+
const envelope = createTelemetryEnvelope({
|
|
55
|
+
installationId: '11111111-1111-4111-8111-111111111111',
|
|
56
|
+
token: 'signed-token',
|
|
57
|
+
event: { event: 'command_completed', properties: { command: 'search', result: 'success', durationBucket: '1-5s' } },
|
|
58
|
+
skillVersion: '1.1.0',
|
|
59
|
+
});
|
|
60
|
+
assert.deepEqual(Object.keys(envelope).sort(), ['event', 'installationId', 'properties', 'schemaVersion', 'skillVersion', 'token']);
|
|
61
|
+
assert.ok(Buffer.byteLength(JSON.stringify(envelope)) < 4096);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('validates a representative payload for every documented event', () => {
|
|
65
|
+
const samples = [
|
|
66
|
+
{ event: 'installation_started', properties: { osFamily: 'macos', nodeMajor: 24, submissionMode: 'unconfigured' } },
|
|
67
|
+
{ event: 'command_completed', properties: { command: 'search', result: 'success', durationBucket: '1-5s' } },
|
|
68
|
+
{ event: 'job_discovered', properties: { ...baseJob, source: 'x', jobCountry: 'United States', workMode: 'remote', seniority: 'staff', employmentType: 'full-time', roleFamily: 'product-engineering', salaryCurrency: 'USD', salaryMin: 100000, salaryMax: 180000 } },
|
|
69
|
+
{ event: 'job_assessed', properties: { ...baseJob, fitScore: 75, eligibility: 'unclear', decision: 'ask', matchTags: ['skills'], gapTags: ['authorization_unclear'] } },
|
|
70
|
+
{ event: 'application_started', properties: { jobHash: baseJob.jobHash, ats: 'ashby', approvalMode: 'review-each', requiredFieldCount: 12, resumeRequired: true, coverLetterRequired: false, referralPresent: false } },
|
|
71
|
+
{ event: 'application_step', properties: { jobHash: baseJob.jobHash, ats: 'lever', stage: 'questions', fieldCategory: 'short-answer', retryCount: 1, durationBucket: '1-2m' } },
|
|
72
|
+
{ event: 'application_paused', properties: { jobHash: baseJob.jobHash, ats: 'workday', stage: 'legal', reason: 'legal' } },
|
|
73
|
+
{ event: 'application_skipped', properties: { jobHash: baseJob.jobHash, reason: 'closed', fitScore: 82, eligibility: 'eligible' } },
|
|
74
|
+
{ event: 'application_submitted', properties: { ...baseJob, durationBucket: '5-15m', fieldsFilled: 14, shortAnswerCount: 2, resumeUploaded: true, approvalMode: 'routine-auto' } },
|
|
75
|
+
{ event: 'round_completed', properties: { requestedCount: 10, submittedCount: 7, assessedCount: 15, skippedCount: 5, pausedCount: 2, errorCount: 1, durationBucket: '15m-plus' } },
|
|
76
|
+
{ event: 'outcome_recorded', properties: { ...baseJob, outcome: 'interview', daysSinceSubmission: 4, interviewQuality: 'promising', failurePoint: 'process' } },
|
|
77
|
+
{ event: 'review_generated', properties: { submissionCount: 10, interviewCount: 1, rejectionCount: 2, offerCount: 0, withdrawalCount: 0, reviewDue: true } },
|
|
78
|
+
{ event: 'skill_error', properties: { errorCode: 'site_changed', stage: 'application', ats: 'comeet', jobHash: baseJob.jobHash, recoverable: true } },
|
|
79
|
+
];
|
|
80
|
+
for (const sample of samples) assert.equal(validateEvent(sample).event, sample.event);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('keeps interview-learning telemetry bounded and rejects free-form notes', () => {
|
|
84
|
+
const event = validateEvent({
|
|
85
|
+
event: 'outcome_recorded',
|
|
86
|
+
properties: { ...baseJob, outcome: 'rejected', daysSinceSubmission: 12, interviewQuality: 'weak', failurePoint: 'interviewer' },
|
|
87
|
+
});
|
|
88
|
+
assert.equal(event.properties.interviewQuality, 'weak');
|
|
89
|
+
assert.equal(event.properties.failurePoint, 'interviewer');
|
|
90
|
+
assert.throws(() => validateEvent({
|
|
91
|
+
event: 'outcome_recorded',
|
|
92
|
+
properties: { ...baseJob, outcome: 'rejected', daysSinceSubmission: 12, interviewQuality: 'weak', failurePoint: 'interviewer', note: 'private interview notes' },
|
|
93
|
+
}), /unknown/i);
|
|
94
|
+
assert.throws(() => validateEvent({
|
|
95
|
+
event: 'outcome_recorded',
|
|
96
|
+
properties: { ...baseJob, outcome: 'rejected', daysSinceSubmission: 12, failurePoint: 'interviewer' },
|
|
97
|
+
}), /requires interviewQuality/i);
|
|
98
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "job-application-agent",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "A self-updating Codex skill for evidence-based job discovery, application completion, and outcome tracking.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"job-application-agent": "bin/job-application-agent.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"installer/src/",
|
|
13
|
+
"job-application-agent/",
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"codex",
|
|
19
|
+
"skill",
|
|
20
|
+
"job-search",
|
|
21
|
+
"job-application",
|
|
22
|
+
"career"
|
|
23
|
+
],
|
|
24
|
+
"homepage": "https://github.com/vaibhavarora14/job-application-agent#readme",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/vaibhavarora14/job-application-agent.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/vaibhavarora14/job-application-agent/issues"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public",
|
|
34
|
+
"provenance": true
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"test": "node --test job-application-agent/tests/*.test.mjs telemetry-worker/tests/*.test.mjs installer/tests/*.test.mjs",
|
|
38
|
+
"privacy-audit": "node --test job-application-agent/tests/privacy-audit.test.mjs",
|
|
39
|
+
"smoke:package": "node scripts/smoke-package.mjs",
|
|
40
|
+
"check": "node --check job-application-agent/scripts/job-application.mjs && node --check job-application-agent/scripts/telemetry-client.mjs && node --check job-application-agent/scripts/telemetry-schema.mjs && node --check telemetry-worker/src/worker.mjs && node --check installer/src/cli.mjs && node --check installer/src/installer.mjs && node --check installer/src/runner.mjs && node --check installer/src/scheduler.mjs && node --check bin/job-application-agent.mjs",
|
|
41
|
+
"prepack": "npm run check && npm test && npm run privacy-audit"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=20"
|
|
45
|
+
}
|
|
46
|
+
}
|