huntr-cli 1.0.9
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/.env.example +7 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +43 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +29 -0
- package/.github/labels.json +92 -0
- package/.github/pull_request_template.md +64 -0
- package/.github/workflows/ci.yml +87 -0
- package/.github/workflows/labels.yml +27 -0
- package/.github/workflows/manual-publish.yml +105 -0
- package/.github/workflows/publish.yml +57 -0
- package/.github/workflows/release.yml +124 -0
- package/.github/workflows/security-audit.yml +44 -0
- package/.husky/pre-commit +12 -0
- package/.husky/pre-push +27 -0
- package/.lintstagedrc.json +3 -0
- package/AGENTS.md +449 -0
- package/CHANGELOG.md +38 -0
- package/CHANGES.md +259 -0
- package/LICENSE +15 -0
- package/PUBLISHING.md +191 -0
- package/README.md +385 -0
- package/ROADMAP.md +158 -0
- package/SETUP-COMPLETE.md +446 -0
- package/WORKFLOW-SUMMARY.md +368 -0
- package/completions/_huntr +168 -0
- package/completions/huntr.1 +266 -0
- package/completions/huntr.bash +91 -0
- package/dist/api/client.d.ts +14 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +74 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/personal/activities.d.ts +20 -0
- package/dist/api/personal/activities.d.ts.map +1 -0
- package/dist/api/personal/activities.js +50 -0
- package/dist/api/personal/activities.js.map +1 -0
- package/dist/api/personal/boards.d.ts +9 -0
- package/dist/api/personal/boards.d.ts.map +1 -0
- package/dist/api/personal/boards.js +16 -0
- package/dist/api/personal/boards.js.map +1 -0
- package/dist/api/personal/index.d.ts +17 -0
- package/dist/api/personal/index.d.ts.map +1 -0
- package/dist/api/personal/index.js +37 -0
- package/dist/api/personal/index.js.map +1 -0
- package/dist/api/personal/jobs.d.ts +13 -0
- package/dist/api/personal/jobs.d.ts.map +1 -0
- package/dist/api/personal/jobs.js +31 -0
- package/dist/api/personal/jobs.js.map +1 -0
- package/dist/api/personal/user.d.ts +8 -0
- package/dist/api/personal/user.d.ts.map +1 -0
- package/dist/api/personal/user.js +13 -0
- package/dist/api/personal/user.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +501 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/capture-session.d.ts +10 -0
- package/dist/commands/capture-session.d.ts.map +1 -0
- package/dist/commands/capture-session.js +478 -0
- package/dist/commands/capture-session.js.map +1 -0
- package/dist/config/clerk-session-manager.d.ts +44 -0
- package/dist/config/clerk-session-manager.d.ts.map +1 -0
- package/dist/config/clerk-session-manager.js +232 -0
- package/dist/config/clerk-session-manager.js.map +1 -0
- package/dist/config/config-manager.d.ts +15 -0
- package/dist/config/config-manager.d.ts.map +1 -0
- package/dist/config/config-manager.js +51 -0
- package/dist/config/config-manager.js.map +1 -0
- package/dist/config/keychain-manager.d.ts +6 -0
- package/dist/config/keychain-manager.d.ts.map +1 -0
- package/dist/config/keychain-manager.js +37 -0
- package/dist/config/keychain-manager.js.map +1 -0
- package/dist/config/token-capture.d.ts +11 -0
- package/dist/config/token-capture.d.ts.map +1 -0
- package/dist/config/token-capture.js +252 -0
- package/dist/config/token-capture.js.map +1 -0
- package/dist/config/token-manager.d.ts +38 -0
- package/dist/config/token-manager.d.ts.map +1 -0
- package/dist/config/token-manager.js +153 -0
- package/dist/config/token-manager.js.map +1 -0
- package/dist/lib/list-options.d.ts +69 -0
- package/dist/lib/list-options.d.ts.map +1 -0
- package/dist/lib/list-options.js +299 -0
- package/dist/lib/list-options.js.map +1 -0
- package/dist/types/personal.d.ts +113 -0
- package/dist/types/personal.d.ts.map +1 -0
- package/dist/types/personal.js +4 -0
- package/dist/types/personal.js.map +1 -0
- package/docs/AUTOMATIC-PUBLISHING.md +520 -0
- package/docs/CHANGELOG-AUTOMATION.md +418 -0
- package/docs/CI-CD-SETUP.md +582 -0
- package/docs/DEV-SETUP.md +512 -0
- package/docs/ENHANCEMENT-PLAN.md +204 -0
- package/docs/ENTITY-TYPES.md +462 -0
- package/docs/GITHUB-ACTIONS-GUIDE.md +367 -0
- package/docs/NPM-PUBLISHING.md +324 -0
- package/docs/OUTPUT-EXAMPLES.md +414 -0
- package/docs/OUTPUT-FORMATS.md +299 -0
- package/docs/TESTING.md +216 -0
- package/eslint.config.js +68 -0
- package/package.json +64 -0
- package/src/api/client.ts +88 -0
- package/src/api/personal/activities.ts +66 -0
- package/src/api/personal/boards.ts +14 -0
- package/src/api/personal/index.ts +25 -0
- package/src/api/personal/jobs.ts +33 -0
- package/src/api/personal/user.ts +10 -0
- package/src/cli.ts +487 -0
- package/src/commands/capture-session.ts +582 -0
- package/src/config/clerk-session-manager.ts +263 -0
- package/src/config/config-manager.ts +56 -0
- package/src/config/keychain-manager.ts +30 -0
- package/src/config/token-capture.ts +233 -0
- package/src/config/token-manager.ts +139 -0
- package/src/lib/list-options.ts +370 -0
- package/src/types/personal.ts +114 -0
- package/tests/example.test.ts +130 -0
- package/tsconfig.json +19 -0
package/docs/TESTING.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Testing Guide
|
|
2
|
+
|
|
3
|
+
Guide to testing huntr-cli following the development workflow.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Tests use **Vitest** (Vite-native test framework) and document the complete huntr-cli workflow:
|
|
8
|
+
- Git workflow (conventional commits, hooks)
|
|
9
|
+
- Publishing workflow (automatic releases)
|
|
10
|
+
- Output formatting features
|
|
11
|
+
- Command structure
|
|
12
|
+
|
|
13
|
+
## Running Tests
|
|
14
|
+
|
|
15
|
+
### All Tests
|
|
16
|
+
```bash
|
|
17
|
+
npm test
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Specific Test Suite
|
|
21
|
+
```bash
|
|
22
|
+
npm test -- --testNamePattern="Publishing"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Watch Mode
|
|
26
|
+
```bash
|
|
27
|
+
npm test -- --watch
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Coverage Report
|
|
31
|
+
```bash
|
|
32
|
+
npm test -- --coverage
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Test Structure
|
|
36
|
+
|
|
37
|
+
### tests/example.test.ts
|
|
38
|
+
|
|
39
|
+
Documents the complete workflow:
|
|
40
|
+
|
|
41
|
+
| Section | Purpose |
|
|
42
|
+
|---------|---------|
|
|
43
|
+
| **Command Structure** | Verifies CLI commands and output formats |
|
|
44
|
+
| **Build & Compilation** | TypeScript and ESLint pass in CI |
|
|
45
|
+
| **Git Workflow** | Conventional commits and git hooks |
|
|
46
|
+
| **Publishing Workflow** | Automatic npm publishing and versioning |
|
|
47
|
+
| **Output Formatting** | Field selection and export formats |
|
|
48
|
+
| **Documentation** | Guides and man pages exist |
|
|
49
|
+
|
|
50
|
+
## Workflow Verification
|
|
51
|
+
|
|
52
|
+
### ✅ Local Development
|
|
53
|
+
```bash
|
|
54
|
+
# 1. Create feature branch
|
|
55
|
+
git checkout -b feat/my-feature
|
|
56
|
+
|
|
57
|
+
# 2. Make changes
|
|
58
|
+
npm run lint:fix
|
|
59
|
+
npm run build
|
|
60
|
+
|
|
61
|
+
# 3. Commit (pre-commit hook runs)
|
|
62
|
+
git commit -m "feat: add feature"
|
|
63
|
+
|
|
64
|
+
# 4. Push (pre-push hook runs: typecheck → lint → build)
|
|
65
|
+
git push origin feat/my-feature
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### ✅ Pull Request
|
|
69
|
+
- Create PR on GitHub
|
|
70
|
+
- CI runs automatically (lint, typecheck, build, test)
|
|
71
|
+
- All checks must pass
|
|
72
|
+
- Code review and approval
|
|
73
|
+
|
|
74
|
+
### ✅ Publishing
|
|
75
|
+
```bash
|
|
76
|
+
# 1. Bump version
|
|
77
|
+
git checkout main
|
|
78
|
+
npm version minor # Creates commit + tag
|
|
79
|
+
|
|
80
|
+
# 2. Push with tags
|
|
81
|
+
git push origin main --tags
|
|
82
|
+
|
|
83
|
+
# 3. Release created automatically
|
|
84
|
+
# - release.yml creates GitHub Release with artifacts
|
|
85
|
+
# - publish.yml triggers on release
|
|
86
|
+
# - npm package published automatically
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### ✅ Version Verification
|
|
90
|
+
```bash
|
|
91
|
+
# Check npm registry
|
|
92
|
+
npm view huntr-cli version
|
|
93
|
+
|
|
94
|
+
# Install latest
|
|
95
|
+
npm install -g huntr-cli@latest
|
|
96
|
+
|
|
97
|
+
# Verify
|
|
98
|
+
huntr --version
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Test Examples
|
|
102
|
+
|
|
103
|
+
### Example: Command Structure
|
|
104
|
+
```typescript
|
|
105
|
+
it('should support boards list command', () => {
|
|
106
|
+
const commands = ['boards', 'jobs', 'activities'];
|
|
107
|
+
expect(commands).toContain('boards');
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Example: Publishing Workflow
|
|
112
|
+
```typescript
|
|
113
|
+
it('should create artifacts on version bump', () => {
|
|
114
|
+
// release.yml detects version change
|
|
115
|
+
// Creates tar.gz and zip archives
|
|
116
|
+
// Creates GitHub Release with artifacts
|
|
117
|
+
expect(true).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Example: Git Workflow
|
|
122
|
+
```typescript
|
|
123
|
+
it('should trigger pre-push hook on git push', () => {
|
|
124
|
+
// Pre-push hook runs: typecheck → lint → build
|
|
125
|
+
expect(true).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## CI Test Execution
|
|
130
|
+
|
|
131
|
+
GitHub Actions runs tests automatically:
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
# .github/workflows/ci.yml
|
|
135
|
+
- name: Run tests
|
|
136
|
+
run: npm test
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Tests verify:
|
|
140
|
+
- Command structure is correct
|
|
141
|
+
- All output formats available
|
|
142
|
+
- Publishing workflow documented
|
|
143
|
+
- Git workflow enforced
|
|
144
|
+
- Documentation complete
|
|
145
|
+
|
|
146
|
+
## Writing New Tests
|
|
147
|
+
|
|
148
|
+
When adding features, add tests to `tests/example.test.ts`:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
describe('New Feature', () => {
|
|
152
|
+
it('should do something', () => {
|
|
153
|
+
const result = newFeature();
|
|
154
|
+
expect(result).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Test structure:
|
|
160
|
+
- **Describe:** Feature or workflow area
|
|
161
|
+
- **It:** Specific behavior or requirement
|
|
162
|
+
- **Expect:** Assertion of correct behavior
|
|
163
|
+
|
|
164
|
+
## Continuous Integration
|
|
165
|
+
|
|
166
|
+
### On Every Push
|
|
167
|
+
```
|
|
168
|
+
→ Pre-commit hook (lint)
|
|
169
|
+
→ Pre-push hook (typecheck, lint, build)
|
|
170
|
+
→ GitHub Actions CI (lint, typecheck, build, test)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### On Pull Request
|
|
174
|
+
```
|
|
175
|
+
→ Same CI checks
|
|
176
|
+
→ Code review required
|
|
177
|
+
→ Tests must pass
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### On Main Branch Push
|
|
181
|
+
```
|
|
182
|
+
→ All CI checks pass
|
|
183
|
+
→ Version bump triggers release.yml
|
|
184
|
+
→ GitHub Release created with artifacts
|
|
185
|
+
→ npm publishing triggered
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Troubleshooting
|
|
189
|
+
|
|
190
|
+
### Tests Failing
|
|
191
|
+
```bash
|
|
192
|
+
# Run with verbose output
|
|
193
|
+
npm test -- --reporter=verbose
|
|
194
|
+
|
|
195
|
+
# Check test names
|
|
196
|
+
npm test -- --listTests
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Missing Dependencies
|
|
200
|
+
```bash
|
|
201
|
+
npm install
|
|
202
|
+
npm run build:shared # For workspace tests
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### ESLint/Typecheck Failing
|
|
206
|
+
```bash
|
|
207
|
+
npm run lint:fix
|
|
208
|
+
npm run typecheck
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## See Also
|
|
212
|
+
|
|
213
|
+
- [DEV-SETUP.md](./DEV-SETUP.md) — Development workflow
|
|
214
|
+
- [CI-CD-SETUP.md](./CI-CD-SETUP.md) — Hook configuration
|
|
215
|
+
- [AUTOMATIC-PUBLISHING.md](./AUTOMATIC-PUBLISHING.md) — Publishing workflow
|
|
216
|
+
- [.github/workflows/ci.yml](../.github/workflows/ci.yml) — CI configuration
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import js from '@eslint/js';
|
|
2
|
+
import tsParser from '@typescript-eslint/parser';
|
|
3
|
+
import tsPlugin from '@typescript-eslint/eslint-plugin';
|
|
4
|
+
|
|
5
|
+
export default [
|
|
6
|
+
{
|
|
7
|
+
ignores: ['dist/**', 'node_modules/**', '*.config.js'],
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
files: ['src/**/*.ts'],
|
|
11
|
+
languageOptions: {
|
|
12
|
+
parser: tsParser,
|
|
13
|
+
parserOptions: {
|
|
14
|
+
ecmaVersion: 2022,
|
|
15
|
+
sourceType: 'module',
|
|
16
|
+
project: './tsconfig.json',
|
|
17
|
+
},
|
|
18
|
+
globals: {
|
|
19
|
+
console: 'readonly',
|
|
20
|
+
process: 'readonly',
|
|
21
|
+
Buffer: 'readonly',
|
|
22
|
+
setTimeout: 'readonly',
|
|
23
|
+
fetch: 'readonly',
|
|
24
|
+
URL: 'readonly',
|
|
25
|
+
require: 'readonly',
|
|
26
|
+
WebSocket: 'readonly',
|
|
27
|
+
MessageEvent: 'readonly',
|
|
28
|
+
Blob: 'readonly',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
plugins: {
|
|
32
|
+
'@typescript-eslint': tsPlugin,
|
|
33
|
+
},
|
|
34
|
+
rules: {
|
|
35
|
+
...js.configs.recommended.rules,
|
|
36
|
+
...tsPlugin.configs.recommended.rules,
|
|
37
|
+
'@typescript-eslint/no-unused-vars': [
|
|
38
|
+
'warn',
|
|
39
|
+
{
|
|
40
|
+
argsIgnorePattern: '^_',
|
|
41
|
+
varsIgnorePattern: '^_',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
45
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
46
|
+
'@typescript-eslint/no-floating-promises': 'warn',
|
|
47
|
+
'@typescript-eslint/no-misused-promises': 'warn',
|
|
48
|
+
'no-console': 'off',
|
|
49
|
+
'no-process-exit': 'off',
|
|
50
|
+
'curly': ['error', 'multi-line'],
|
|
51
|
+
'eol-last': ['error', 'always'],
|
|
52
|
+
'no-trailing-spaces': 'error',
|
|
53
|
+
'no-multiple-empty-lines': ['error', { max: 1 }],
|
|
54
|
+
'quotes': ['error', 'single', { avoidEscape: true }],
|
|
55
|
+
'semi': ['error', 'always'],
|
|
56
|
+
'comma-dangle': [
|
|
57
|
+
'error',
|
|
58
|
+
{
|
|
59
|
+
arrays: 'always-multiline',
|
|
60
|
+
objects: 'always-multiline',
|
|
61
|
+
imports: 'always-multiline',
|
|
62
|
+
exports: 'always-multiline',
|
|
63
|
+
functions: 'always-multiline',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "huntr-cli",
|
|
3
|
+
"version": "1.0.9",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "CLI tool for managing your Huntr job search board. Track activities, search jobs, and manage your application pipeline from the terminal.",
|
|
6
|
+
"main": "dist/cli.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"huntr": "./dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsx src/cli.ts",
|
|
13
|
+
"start": "node dist/cli.js",
|
|
14
|
+
"lint": "eslint src --max-warnings 13",
|
|
15
|
+
"lint:fix": "eslint src --fix",
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest watch",
|
|
19
|
+
"prepare": "husky install",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/mattmck/huntr-cli.git"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"huntr",
|
|
28
|
+
"job-search",
|
|
29
|
+
"cli",
|
|
30
|
+
"job-tracking",
|
|
31
|
+
"activities",
|
|
32
|
+
"command-line"
|
|
33
|
+
],
|
|
34
|
+
"author": "Matt McKnight <mcknight.matthew@gmail.com>",
|
|
35
|
+
"license": "ISC",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/mattmck/huntr-cli/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/mattmck/huntr-cli#readme",
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@inquirer/prompts": "^8.2.1",
|
|
45
|
+
"axios": "^1.13.5",
|
|
46
|
+
"commander": "^14.0.3",
|
|
47
|
+
"dotenv": "^17.3.1",
|
|
48
|
+
"exceljs": "^4.4.0",
|
|
49
|
+
"keytar": "^7.9.0",
|
|
50
|
+
"pdfkit": "^0.13.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@eslint/js": "^8.56.0",
|
|
54
|
+
"@types/node": "^25.3.0",
|
|
55
|
+
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
56
|
+
"@typescript-eslint/parser": "^7.0.0",
|
|
57
|
+
"eslint": "^8.56.0",
|
|
58
|
+
"husky": "^9.0.0",
|
|
59
|
+
"lint-staged": "^15.0.0",
|
|
60
|
+
"tsx": "^4.21.0",
|
|
61
|
+
"typescript": "^5.9.3",
|
|
62
|
+
"vitest": "^1.6.0"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import axios, { AxiosInstance, AxiosError } from 'axios';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
|
|
4
|
+
// Load .env file silently (debug: false suppresses info messages)
|
|
5
|
+
dotenv.config({ debug: false });
|
|
6
|
+
|
|
7
|
+
/** A static token string OR an async function that returns a fresh token. */
|
|
8
|
+
export type TokenProvider = string | (() => Promise<string>);
|
|
9
|
+
|
|
10
|
+
export class HuntrApiClient {
|
|
11
|
+
private axiosInstance: AxiosInstance;
|
|
12
|
+
private tokenProvider: () => Promise<string>;
|
|
13
|
+
|
|
14
|
+
constructor(tokenProvider: TokenProvider, baseURL: string = 'https://api.huntr.co/org') {
|
|
15
|
+
this.tokenProvider =
|
|
16
|
+
typeof tokenProvider === 'string'
|
|
17
|
+
? async () => tokenProvider
|
|
18
|
+
: tokenProvider;
|
|
19
|
+
|
|
20
|
+
this.axiosInstance = axios.create({ baseURL });
|
|
21
|
+
|
|
22
|
+
// Inject a fresh Authorization header before every request
|
|
23
|
+
this.axiosInstance.interceptors.request.use(async (config) => {
|
|
24
|
+
const token = await this.tokenProvider();
|
|
25
|
+
if (!token) throw new Error('No API token available.');
|
|
26
|
+
config.headers = config.headers ?? {};
|
|
27
|
+
config.headers['Authorization'] = `Bearer ${token}`;
|
|
28
|
+
config.headers['Content-Type'] = 'application/json';
|
|
29
|
+
return config;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
this.axiosInstance.interceptors.response.use(
|
|
33
|
+
(response) => response,
|
|
34
|
+
(error: AxiosError) => {
|
|
35
|
+
if (error.response) {
|
|
36
|
+
const status = error.response.status;
|
|
37
|
+
const apiError = error.response.data as any;
|
|
38
|
+
const message = apiError?.error?.message ?? apiError?.message ?? 'API request failed';
|
|
39
|
+
throw new Error(`HTTP ${status}: ${message}`);
|
|
40
|
+
}
|
|
41
|
+
if (error.request) {
|
|
42
|
+
throw new Error('No response from API - check your network connection');
|
|
43
|
+
}
|
|
44
|
+
throw error;
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async get<T>(endpoint: string, params?: Record<string, any>): Promise<T> {
|
|
50
|
+
const response = await this.axiosInstance.get<T>(endpoint, { params });
|
|
51
|
+
return response.data;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async post<T>(endpoint: string, data?: any): Promise<T> {
|
|
55
|
+
const response = await this.axiosInstance.post<T>(endpoint, data);
|
|
56
|
+
return response.data;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async put<T>(endpoint: string, data?: any): Promise<T> {
|
|
60
|
+
const response = await this.axiosInstance.put<T>(endpoint, data);
|
|
61
|
+
return response.data;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async delete<T>(endpoint: string): Promise<T> {
|
|
65
|
+
const response = await this.axiosInstance.delete<T>(endpoint);
|
|
66
|
+
return response.data;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async *paginate<T>(
|
|
70
|
+
endpoint: string,
|
|
71
|
+
params: Record<string, any> = {},
|
|
72
|
+
limit: number = 100,
|
|
73
|
+
): AsyncGenerator<T[], void, undefined> {
|
|
74
|
+
let next: string | undefined = undefined;
|
|
75
|
+
|
|
76
|
+
do {
|
|
77
|
+
const queryParams: Record<string, any> = { ...params, limit };
|
|
78
|
+
if (next) queryParams.next = next;
|
|
79
|
+
const response = await this.get<{ data: T[]; next?: string }>(endpoint, queryParams);
|
|
80
|
+
yield response.data;
|
|
81
|
+
next = response.next;
|
|
82
|
+
} while (next);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const createClient = (tokenProvider: TokenProvider, baseURL?: string): HuntrApiClient => {
|
|
87
|
+
return new HuntrApiClient(tokenProvider, baseURL);
|
|
88
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { HuntrApiClient } from '../client';
|
|
2
|
+
import { PersonalAction, PersonalJob, PersonalJobsResponse } from '../../types/personal';
|
|
3
|
+
|
|
4
|
+
export class PersonalActionsApi {
|
|
5
|
+
constructor(private client: HuntrApiClient) {}
|
|
6
|
+
|
|
7
|
+
// Returns object map { [actionId]: PersonalAction }
|
|
8
|
+
async listByBoard(boardId: string): Promise<Record<string, PersonalAction>> {
|
|
9
|
+
return this.client.get<Record<string, PersonalAction>>(`/board/${boardId}/actions`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Returns flattened array sorted by date desc, optionally filtered by action types
|
|
13
|
+
async listByBoardFlat(
|
|
14
|
+
boardId: string,
|
|
15
|
+
opts?: { since?: Date; types?: string[] },
|
|
16
|
+
): Promise<PersonalAction[]> {
|
|
17
|
+
const raw = await this.listByBoard(boardId);
|
|
18
|
+
let actions = Object.values(raw);
|
|
19
|
+
|
|
20
|
+
if (opts?.since) {
|
|
21
|
+
const cutoff = opts.since.getTime();
|
|
22
|
+
actions = actions.filter(a => new Date(a.date || a.createdAt).getTime() >= cutoff);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (opts?.types && opts.types.length > 0) {
|
|
26
|
+
actions = actions.filter(a => opts.types!.includes(a.actionType));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return actions.sort((a, b) =>
|
|
30
|
+
new Date(b.date || b.createdAt).getTime() - new Date(a.date || a.createdAt).getTime(),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Joins actions with job data to produce enriched rows
|
|
35
|
+
async weekSummary(boardId: string): Promise<Array<{
|
|
36
|
+
date: string;
|
|
37
|
+
actionType: string;
|
|
38
|
+
company: string;
|
|
39
|
+
jobTitle: string;
|
|
40
|
+
status: string;
|
|
41
|
+
url: string;
|
|
42
|
+
}>> {
|
|
43
|
+
const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
44
|
+
const [actions, jobsResponse] = await Promise.all([
|
|
45
|
+
this.listByBoardFlat(boardId, { since }),
|
|
46
|
+
this.client.get<PersonalJobsResponse>(`/board/${boardId}/jobs`),
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
const jobs = jobsResponse.jobs;
|
|
50
|
+
|
|
51
|
+
return actions
|
|
52
|
+
.filter(a => a.actionType !== 'ACTIVITY_CREATED')
|
|
53
|
+
.map(a => {
|
|
54
|
+
const jobId = a.data?._job;
|
|
55
|
+
const job: PersonalJob | undefined = jobId ? jobs[jobId] : undefined;
|
|
56
|
+
return {
|
|
57
|
+
date: new Date(a.date || a.createdAt).toISOString().substring(0, 16),
|
|
58
|
+
actionType: a.actionType,
|
|
59
|
+
company: a.data?.company?.name ?? '',
|
|
60
|
+
jobTitle: a.data?.job?.title ?? '',
|
|
61
|
+
status: a.data?.toList?.name ?? '',
|
|
62
|
+
url: job?.url ?? '',
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { HuntrApiClient } from '../client';
|
|
2
|
+
import { Board } from '../../types/personal';
|
|
3
|
+
|
|
4
|
+
export class PersonalBoardsApi {
|
|
5
|
+
constructor(private client: HuntrApiClient) {}
|
|
6
|
+
|
|
7
|
+
async list(): Promise<Board[]> {
|
|
8
|
+
return this.client.get<Board[]>('/boards');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async get(boardId: string): Promise<Board> {
|
|
12
|
+
return this.client.get<Board>(`/boards/${boardId}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createClient, TokenProvider } from '../client';
|
|
2
|
+
import { PersonalUserApi } from './user';
|
|
3
|
+
import { PersonalBoardsApi } from './boards';
|
|
4
|
+
import { PersonalJobsApi } from './jobs';
|
|
5
|
+
import { PersonalActionsApi } from './activities';
|
|
6
|
+
|
|
7
|
+
export class HuntrPersonalApi {
|
|
8
|
+
public user: PersonalUserApi;
|
|
9
|
+
public boards: PersonalBoardsApi;
|
|
10
|
+
public jobs: PersonalJobsApi;
|
|
11
|
+
public actions: PersonalActionsApi;
|
|
12
|
+
|
|
13
|
+
constructor(tokenProvider: TokenProvider) {
|
|
14
|
+
const client = createClient(tokenProvider, 'https://api.huntr.co/api');
|
|
15
|
+
this.user = new PersonalUserApi(client);
|
|
16
|
+
this.boards = new PersonalBoardsApi(client);
|
|
17
|
+
this.jobs = new PersonalJobsApi(client);
|
|
18
|
+
this.actions = new PersonalActionsApi(client);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export * from './user';
|
|
23
|
+
export * from './boards';
|
|
24
|
+
export * from './jobs';
|
|
25
|
+
export * from './activities';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { HuntrApiClient } from '../client';
|
|
2
|
+
import { PersonalJob, PersonalJobsResponse } from '../../types/personal';
|
|
3
|
+
|
|
4
|
+
export class PersonalJobsApi {
|
|
5
|
+
constructor(private client: HuntrApiClient) {}
|
|
6
|
+
|
|
7
|
+
// API returns { jobs: { [id]: PersonalJob } } — an object map, not an array
|
|
8
|
+
async listByBoard(boardId: string): Promise<PersonalJobsResponse> {
|
|
9
|
+
return this.client.get<PersonalJobsResponse>(`/board/${boardId}/jobs`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Convenience: returns flat array
|
|
13
|
+
async listByBoardFlat(boardId: string): Promise<PersonalJob[]> {
|
|
14
|
+
const response = await this.listByBoard(boardId);
|
|
15
|
+
return Object.values(response.jobs);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async get(boardId: string, jobId: string): Promise<PersonalJob> {
|
|
19
|
+
return this.client.get<PersonalJob>(`/board/${boardId}/jobs/${jobId}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async create(boardId: string, job: Partial<PersonalJob>): Promise<PersonalJob> {
|
|
23
|
+
return this.client.post<PersonalJob>(`/board/${boardId}/jobs`, job);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async update(boardId: string, jobId: string, updates: Partial<PersonalJob>): Promise<PersonalJob> {
|
|
27
|
+
return this.client.put<PersonalJob>(`/board/${boardId}/jobs/${jobId}`, updates);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async delete(boardId: string, jobId: string): Promise<void> {
|
|
31
|
+
return this.client.delete<void>(`/board/${boardId}/jobs/${jobId}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { HuntrApiClient } from '../client';
|
|
2
|
+
import { UserProfile } from '../../types/personal';
|
|
3
|
+
|
|
4
|
+
export class PersonalUserApi {
|
|
5
|
+
constructor(private client: HuntrApiClient) {}
|
|
6
|
+
|
|
7
|
+
async getProfile(): Promise<UserProfile> {
|
|
8
|
+
return this.client.get<UserProfile>('/me');
|
|
9
|
+
}
|
|
10
|
+
}
|