jobhunt-kit 0.3.0 → 0.5.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.
Files changed (40) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +58 -11
  3. package/bin/agents.mjs +66 -0
  4. package/bin/install-options.mjs +21 -18
  5. package/bin/jobhunt-kit.mjs +17 -6
  6. package/installer-assets/gitignore.txt +1 -0
  7. package/installer-assets/runtime-lock.json +56 -2
  8. package/installer-assets/workspace-lock.json +56 -2
  9. package/package.json +3 -1
  10. package/plugins/jobhunt-kit/.claude-plugin/plugin.json +1 -1
  11. package/plugins/jobhunt-kit/.codex-plugin/plugin.json +1 -1
  12. package/plugins/jobhunt-kit/package-lock.json +56 -2
  13. package/plugins/jobhunt-kit/package.json +2 -1
  14. package/plugins/jobhunt-kit/references/cli.md +30 -2
  15. package/plugins/jobhunt-kit/references/harnesses.md +12 -0
  16. package/plugins/jobhunt-kit/references/privacy.md +24 -0
  17. package/plugins/jobhunt-kit/references/storage.md +11 -5
  18. package/plugins/jobhunt-kit/schemas/draft.schema.json +13 -0
  19. package/plugins/jobhunt-kit/schemas/finish.schema.json +11 -0
  20. package/plugins/jobhunt-kit/schemas/policy.schema.json +36 -0
  21. package/plugins/jobhunt-kit/schemas/profile.schema.json +80 -0
  22. package/plugins/jobhunt-kit/schemas/status.schema.json +12 -0
  23. package/plugins/jobhunt-kit/schemas/vacancy.schema.json +29 -0
  24. package/plugins/jobhunt-kit/scripts/commands.mjs +57 -11
  25. package/plugins/jobhunt-kit/scripts/extract-resume.mjs +5 -3
  26. package/plugins/jobhunt-kit/scripts/files.mjs +69 -0
  27. package/plugins/jobhunt-kit/scripts/maintenance.mjs +292 -0
  28. package/plugins/jobhunt-kit/scripts/profile.mjs +23 -20
  29. package/plugins/jobhunt-kit/scripts/resume.mjs +42 -38
  30. package/plugins/jobhunt-kit/scripts/setup.mjs +8 -10
  31. package/plugins/jobhunt-kit/scripts/tracker.mjs +188 -33
  32. package/plugins/jobhunt-kit/scripts/validation.mjs +30 -0
  33. package/plugins/jobhunt-kit/skills/job-apply/SKILL.md +4 -2
  34. package/plugins/jobhunt-kit/skills/job-resume/SKILL.md +3 -2
  35. package/plugins/jobhunt-kit/skills/job-track/SKILL.md +8 -0
  36. package/scripts/check-package.mjs +7 -0
  37. package/scripts/pack-smoke.mjs +53 -0
  38. package/tests/hardening.test.mjs +337 -0
  39. package/tests/installer.test.mjs +47 -2
  40. package/tests/tracker.test.mjs +3 -0
@@ -8,7 +8,7 @@
8
8
  "name": "jobhunt-kit",
9
9
  "source": "./plugins/jobhunt-kit",
10
10
  "description": "Candidate intake, resume review, Hirify search and local tracking",
11
- "version": "0.3.0"
11
+ "version": "0.5.0"
12
12
  }
13
13
  ]
14
14
  }
package/README.md CHANGED
@@ -7,7 +7,7 @@ npx jobhunt-kit install
7
7
  Поиск работы вместе с AI-агентом: профиль кандидата, проверка резюме, подбор вакансий
8
8
  через Hirify, сопроводительные письма и история откликов.
9
9
 
10
- Нужны **Node.js 24+ и Codex или Claude Code**. Установщик предложит выбрать
10
+ Нужны **Node.js 24+ и агент с поддержкой навыков**. Установщик предложит выбрать
11
11
  агентов и область установки: рабочая папка или глобально для пользователя.
12
12
  При установке в рабочую папку он создаст `my-jobhunt`, установит зависимости
13
13
  и подключит навык к выбранным агентам. Другой путь: `install ./my-folder`.
@@ -55,6 +55,11 @@ npx jobhunt-kit profile check
55
55
  npx jobhunt-kit search
56
56
  npx jobhunt-kit track
57
57
  npx jobhunt-kit report
58
+ npx jobhunt-kit recover inspect
59
+ npx jobhunt-kit recover packet <attempt_id>
60
+ npx jobhunt-kit backup ../jobhunt-backup
61
+ npx jobhunt-kit backup-verify ../jobhunt-backup
62
+ npx jobhunt-kit privacy map
58
63
  ```
59
64
 
60
65
  `resume` извлекает текст PDF, DOCX, TXT или Markdown и готовит материалы для проверки
@@ -77,28 +82,68 @@ npx jobhunt-kit report
77
82
  | `reports/latest.md` | Сводный отчёт |
78
83
 
79
84
  Папка `local/` исключена из Git. Храните личные файлы в ней; для резервной копии
80
- сохраняйте всю папку при остановленных процессах. Данные на диске не зашифрованы.
85
+ используйте `backup` и затем `backup-verify`. Restore принимает только проверяемую
86
+ копию, требует точное подтверждение и предварительно делает сырой quarantine-снимок,
87
+ который сохраняется даже тогда, когда текущие JSON или SQLite уже повреждены.
88
+ Restore и очистка history всегда отзывают auto-разрешение и подтверждение профиля.
89
+ Данные на диске не зашифрованы.
90
+
91
+ `doctor` проверяет JSON Schema профиля/policy, SQLite integrity и foreign keys,
92
+ незавершённые поиски/отправки и журнал атомарных файловых операций. `privacy map`
93
+ показывает размещение PII; `privacy export`, `redact` и `purge` дают контролируемый
94
+ экспорт и удаление. Redact/purge требуют точных подтверждений, а purge не запускается
95
+ без отдельной резервной копии.
81
96
 
82
97
  ## Выбор агентов
83
98
 
84
99
  Без интерактивных вопросов:
85
100
 
86
101
  ```sh
87
- npx jobhunt-kit install --agents codex,claude
88
- npx jobhunt-kit install --agents codex
89
- npx jobhunt-kit install --agents claude --scope global
102
+ npx jobhunt-kit install --agents codex,cursor,gemini
103
+ npx jobhunt-kit install --agents all
104
+ npx jobhunt-kit install --providers opencode,pi --scope global
105
+ npx jobhunt-kit agents
90
106
  ```
91
107
 
92
- В режиме `project` навыки устанавливаются в созданную рабочую папку:
93
- Codex`.agents/skills/jobhunt-kit`, Claude Code — `.claude/skills/jobhunt-kit`.
94
- В режиме `global` используются те же каталоги в домашней папке пользователя;
95
- рабочая папка и профиль создаются позже при обращении к агенту.
108
+ В режиме `project` навык устанавливается в созданную рабочую папку,
109
+ в режиме `global` в домашнюю папку пользователя.
110
+
111
+ | Агент | Значение `--agents` | Рабочая папка | Глобально (от `~`) |
112
+ |---|---|---|---|
113
+ | Codex | `codex` | `.agents/skills/` | `.agents/skills/` |
114
+ | Claude Code | `claude` | `.claude/skills/` | `.claude/skills/` |
115
+ | Cursor | `cursor` | `.cursor/skills/` | `.cursor/skills/` |
116
+ | Gemini CLI | `gemini` | `.gemini/skills/` | `.gemini/skills/` |
117
+ | GitHub Copilot | `github` | `.github/skills/` | `.copilot/skills/` |
118
+ | OpenCode | `opencode` | `.opencode/skills/` | `.config/opencode/skills/` |
119
+ | Antigravity | `antigravity` | `.agent/skills/` | `.gemini/config/skills/` |
120
+ | Pi | `pi` | `.pi/skills/` | `.pi/agent/skills/` |
121
+ | Grok Build | `grok` | `.grok/skills/` | `.grok/skills/` |
122
+ | Hermes Agent | `hermes` | `.hermes/skills/` | `.hermes/skills/` |
123
+ | DeepSeek Harness | `dsh` | `.dsh/skills/` | `.dsh/skills/` |
124
+ | Kiro | `kiro` | `.kiro/skills/` | `.kiro/skills/` |
125
+ | Qoder | `qoder` | `.qoder/skills/` | `.qoder/skills/` |
126
+ | Trae | `trae` | `.trae/skills/` | `.trae/skills/` |
127
+ | Trae CN | `trae-cn` | `.trae-cn/skills/` | `.trae-cn/skills/` |
128
+ | Rovo Dev | `rovo-dev` | `.rovodev/skills/` | `.rovodev/skills/` |
129
+ | Mistral Vibe | `vibe` | `.vibe/skills/` | `.vibe/skills/` |
130
+ | Veto | `veto` | — | `.veto/skills/` |
131
+
132
+ В каждом каталоге создаётся отдельная папка `jobhunt-kit` с навыком и его ресурсами.
133
+ `--agents all` выбирает всех агентов для указанной области; Veto доступен только
134
+ глобально. `--providers` — синоним `--agents`. Можно использовать имена
135
+ `copilot`, `claude-code`, `deepseek` и `grok-build`.
136
+
137
+ OpenCode учитывает `OPENCODE_CONFIG_DIR` и `XDG_CONFIG_HOME`; Hermes и DeepSeek —
138
+ `HERMES_HOME` и `DSH_HOME`. Глобальные пути должны находиться в домашней папке.
139
+ При установке Hermes в проект выполните `hermes skills trust` в рабочей папке.
140
+ В остальных агентах при необходимости включите поддержку навыков и доверие к проекту.
96
141
 
97
142
  После установки откройте новую сессию и попросите использовать **jobhunt-kit**.
98
143
  В Claude Code можно вызвать `/jobhunt-kit`. Отдельные команды marketplace
99
144
  и `--plugin-dir` для этого способа не нужны.
100
145
 
101
- `--yes` выбирает обнаруженных агентов (или обоих, если ничего не обнаружено)
146
+ `--yes` выбирает обнаруженных агентов (или Codex и Claude Code, если ничего не обнаружено)
102
147
  и область `project`. Обнаружение использует каталоги агентов, а не запускает их.
103
148
  Для скриптов задавайте `--agents` и `--scope` явно.
104
149
 
@@ -112,12 +157,14 @@ Codex — `.agents/skills/jobhunt-kit`, Claude Code — `.claude/skills/jobhunt-
112
157
  npm ci --ignore-scripts
113
158
  npm test
114
159
  npm run check
160
+ npm run pack:smoke
115
161
  ```
116
162
 
117
163
  Тесты работают с вымышленными данными и не отправляют реальные отклики.
118
164
 
119
165
  Руководства: [формат данных](plugins/jobhunt-kit/references/storage.md),
120
166
  [проверка резюме](plugins/jobhunt-kit/references/resume-guidance.md),
121
- [сопроводительные письма](plugins/jobhunt-kit/references/cover-guidance.md).
167
+ [сопроводительные письма](plugins/jobhunt-kit/references/cover-guidance.md),
168
+ [карта приватных данных](plugins/jobhunt-kit/references/privacy.md).
122
169
 
123
170
  Автор этого шаблона никак не аффилирован с Hirify.
package/bin/agents.mjs ADDED
@@ -0,0 +1,66 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
3
+
4
+ // Directory conventions checked against Impeccable's providers.rs and README.
5
+ export const AGENTS = [
6
+ {id:'codex', name:'Codex', dir:'.agents', detect:'.codex', aliases:['agents']},
7
+ {id:'claude', name:'Claude Code', dir:'.claude', aliases:['claude-code']},
8
+ {id:'cursor', name:'Cursor', dir:'.cursor'},
9
+ {id:'gemini', name:'Gemini CLI', dir:'.gemini'},
10
+ {id:'github', name:'GitHub Copilot', dir:'.github', global:'.copilot/skills', detect:'.copilot', aliases:['copilot']},
11
+ {id:'opencode', name:'OpenCode', dir:'.opencode', global:'.config/opencode/skills'},
12
+ {id:'antigravity', name:'Antigravity', dir:'.agent', global:'.gemini/config/skills', aliases:['agent']},
13
+ {id:'pi', name:'Pi', dir:'.pi', global:'.pi/agent/skills'},
14
+ {id:'grok', name:'Grok Build', dir:'.grok', aliases:['grok-build','xai']},
15
+ {id:'hermes', name:'Hermes Agent', dir:'.hermes'},
16
+ {id:'dsh', name:'DeepSeek Harness', dir:'.dsh', aliases:['deepseek','deepseek-harness']},
17
+ {id:'kiro', name:'Kiro', dir:'.kiro'},
18
+ {id:'qoder', name:'Qoder', dir:'.qoder'},
19
+ {id:'trae', name:'Trae', dir:'.trae'},
20
+ {id:'trae-cn', name:'Trae CN', dir:'.trae-cn'},
21
+ {id:'rovo-dev', name:'Rovo Dev', dir:'.rovodev', aliases:['rovodev']},
22
+ {id:'vibe', name:'Mistral Vibe', dir:'.vibe'},
23
+ {id:'veto', name:'Veto', dir:'.veto', globalOnly:true}
24
+ ];
25
+
26
+ export function parseAgents(value) {
27
+ const selected = value.split(',').map(x => x.trim().toLowerCase().replace(/^\./,''));
28
+ if (selected.length === 1 && selected[0] === 'all') return AGENTS.map(a=>a.id);
29
+ return [...new Set(selected.map(id => {
30
+ const found=AGENTS.find(a=>a.id===id || a.aliases?.includes(id));
31
+ if (!found) throw new Error(`Choose agents: ${AGENTS.map(a=>a.id).join(', ')} (or all)`);
32
+ return found.id;
33
+ }))];
34
+ }
35
+
36
+ export function skillDirectory(id, {scope, home, cwd, env = process.env}) {
37
+ const agent=AGENTS.find(a=>a.id===id);
38
+ if (!agent) throw new Error(`Unknown agent: ${id}`);
39
+ if (scope==='project') {
40
+ if (agent.globalOnly) throw new Error(`${agent.name} supports --scope global only`);
41
+ return join(cwd,agent.dir,'skills','jobhunt-kit');
42
+ }
43
+ let folder=join(home, agent.global || join(agent.dir,'skills'));
44
+ if (id==='opencode') {
45
+ if (env.OPENCODE_CONFIG_DIR) folder=join(resolve(cwd,env.OPENCODE_CONFIG_DIR),'skills');
46
+ else if (env.XDG_CONFIG_HOME) folder=join(resolve(cwd,env.XDG_CONFIG_HOME),'opencode','skills');
47
+ }
48
+ const override=id==='hermes' ? env.HERMES_HOME : id==='dsh' ? env.DSH_HOME : null;
49
+ if (override) folder=join(resolve(cwd,override),'skills');
50
+ const delta=relative(resolve(home),resolve(folder));
51
+ if (isAbsolute(delta) || delta==='..' || delta.startsWith(`..${sep}`)) {
52
+ throw new Error(`${agent.name}: global configuration path must be inside the home directory: ${folder}`);
53
+ }
54
+ return join(folder,'jobhunt-kit');
55
+ }
56
+
57
+ export function detectAgents({home,cwd,env=process.env}) {
58
+ return AGENTS.filter(a=>{
59
+ const project=a.id==='github' ? join(cwd,'.github/skills') : join(cwd,a.dir);
60
+ let global;
61
+ try { global=dirname(dirname(skillDirectory(a.id,{scope:'global',home,cwd,env}))); }
62
+ catch { global=null; }
63
+ return (!a.globalOnly && existsSync(project)) || global && existsSync(global)
64
+ || a.detect && existsSync(join(home,a.detect));
65
+ }).map(a=>a.id);
66
+ }
@@ -1,40 +1,38 @@
1
1
  import { parseArgs } from 'node:util';
2
2
  import { createInterface } from 'node:readline/promises';
3
- import { existsSync } from 'node:fs';
4
3
  import { homedir } from 'node:os';
5
- import { join } from 'node:path';
4
+ import { AGENTS, detectAgents, parseAgents } from './agents.mjs';
5
+ export { parseAgents } from './agents.mjs';
6
6
 
7
- export function parseAgents(value) {
8
- const agents = [...new Set(value.split(',').map(x => x.trim().toLowerCase()))];
9
- if (!agents.length || agents.some(x => !['codex', 'claude'].includes(x))) {
10
- throw new Error('Choose agents: codex, claude or codex,claude');
11
- }
12
- return agents;
13
- }
14
-
15
- export async function installOptions(args, { input = process.stdin, output = process.stdout, home = homedir(), cwd = process.cwd(), ask } = {}) {
7
+ export async function installOptions(args, { input = process.stdin, output = process.stdout, home = homedir(), cwd = process.cwd(), env = process.env, ask } = {}) {
16
8
  const {values, positionals} = parseArgs({args, allowPositionals: true, options: {
17
- agents: {type:'string'}, scope: {type:'string'}, yes: {type:'boolean', short:'y'}
9
+ agents: {type:'string'}, providers: {type:'string'}, scope: {type:'string'}, yes: {type:'boolean', short:'y'}
18
10
  }});
19
11
  if (positionals.length > 1) throw new Error('Expected at most one destination folder');
20
- let agents = values.agents ? parseAgents(values.agents) : null;
12
+ if (values.agents && values.providers) throw new Error('Use either --agents or --providers');
13
+ const selection = values.agents ?? values.providers;
14
+ let agents = selection !== undefined ? parseAgents(selection) : null;
15
+ let allSelected = selection?.trim().toLowerCase()==='all';
21
16
  let scope = values.scope;
22
17
  if (scope && !['project','global'].includes(scope)) throw new Error('Scope must be project or global');
23
- const detected = ['codex','claude'].filter(a => existsSync(join(home, `.${a}`)) || existsSync(join(cwd, a === 'codex' ? '.agents' : '.claude')));
18
+ const detected = detectAgents({home,cwd,env});
24
19
  const defaults = detected.length ? detected : ['codex','claude'];
25
20
  let terminal;
26
21
  try {
27
- if (!agents && !values.yes || !scope && !values.yes && !values.agents) {
22
+ if (!agents && !values.yes || !scope && !values.yes && selection === undefined) {
28
23
  if (!ask && (!input.isTTY || !output.isTTY)) throw new Error('Non-interactive install: pass --agents codex,claude --scope project, or --yes');
29
24
  if (!ask) {
30
25
  terminal = createInterface({input, output});
31
26
  ask = question => terminal.question(question);
32
27
  terminal.on('SIGINT', () => terminal.close());
33
28
  }
34
- output.write(`\nJobhunt Kit\nDetected: ${detected.join(', ') || 'none'}\n`);
29
+ output.write(`\nJobhunt Kit\n${AGENTS.map(a=>`${a.id}: ${a.name}${a.globalOnly?' (global only)':''}`).join('\n')}\nDetected: ${detected.join(', ') || 'none'}\n`);
35
30
  while (!agents) {
36
- const answer = await ask(`Agents — codex, claude, or codex,claude [${defaults.join(',')}]: `);
37
- try { agents = parseAgents(answer.trim() || defaults.join(',')); }
31
+ const answer = await ask(`Agents — comma-separated names or all [${defaults.join(',')}]: `);
32
+ try {
33
+ const chosen = answer.trim() || defaults.join(',');
34
+ agents = parseAgents(chosen); allSelected=chosen.toLowerCase()==='all';
35
+ }
38
36
  catch (e) { output.write(`${e.message}\n`); }
39
37
  }
40
38
  while (!scope) {
@@ -46,6 +44,11 @@ export async function installOptions(args, { input = process.stdin, output = pro
46
44
  } finally { terminal?.close(); }
47
45
  scope ||= 'project';
48
46
  agents ||= defaults;
47
+ if (scope==='project') {
48
+ const globalOnly = agents.filter(id=>AGENTS.find(a=>a.id===id).globalOnly);
49
+ if (allSelected) agents=agents.filter(id=>!globalOnly.includes(id));
50
+ else if (globalOnly.length) throw new Error(`${globalOnly.join(', ')} requires --scope global`);
51
+ }
49
52
  if (scope === 'global' && positionals.length) throw new Error('Global installation does not take a workspace folder');
50
53
  return {destination: positionals[0] || './my-jobhunt', agents, scope};
51
54
  }
@@ -1,15 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, writeFileSync, realpathSync } from 'node:fs';
3
- import { dirname, join, relative, resolve } from 'node:path';
3
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url';
5
5
  import { spawnSync } from 'node:child_process';
6
6
  import { homedir } from 'node:os';
7
7
  import { installOptions } from './install-options.mjs';
8
+ import { AGENTS, skillDirectory } from './agents.mjs';
8
9
 
9
10
  const SOURCE = resolve(dirname(fileURLToPath(import.meta.url)), '..');
10
11
  const roots = ['bin', 'installer-assets', 'plugins/jobhunt-kit', 'scripts', 'tests',
11
12
  'AGENTS.md', 'CLAUDE.md', 'README.md', 'package.json',
12
13
  '.agents/plugins/marketplace.json', '.claude-plugin/marketplace.json'];
14
+ export function pathInside(parent, child, pathApi = { relative, isAbsolute, sep }) {
15
+ const delta = pathApi.relative(parent, child);
16
+ return delta === '' || (!pathApi.isAbsolute(delta) && delta !== '..' && !delta.startsWith(`..${pathApi.sep}`));
17
+ }
13
18
  function collect(path) {
14
19
  const stat = lstatSync(path);
15
20
  if (stat.isSymbolicLink()) throw new Error(`Symlink in template: ${path}`);
@@ -26,13 +31,13 @@ function noSymlinks(path) {
26
31
  current = parent;
27
32
  }
28
33
  }
29
- export function install(destination, { source = SOURCE, installDependencies = true, agents = [], scope = 'project', home = homedir() } = {}) {
34
+ export function install(destination, { source = SOURCE, installDependencies = true, agents = [], scope = 'project', home = homedir(), env = process.env } = {}) {
30
35
  if (Number(process.versions.node.split('.')[0]) < 24) throw new Error('Node.js 24 or newer is required');
31
- if (!['project', 'global'].includes(scope) || agents.some(a => !['codex', 'claude'].includes(a))) throw new Error('Invalid installation scope or agents');
36
+ if (!['project', 'global'].includes(scope) || agents.some(a => !AGENTS.some(p=>p.id===a))) throw new Error('Invalid installation scope or agents');
32
37
  if (scope === 'global' && !agents.length) throw new Error('Global installation requires at least one agent');
33
38
  const target = resolve(scope === 'global' ? home : destination);
34
39
  source = resolve(source);
35
- if (target === source || !relative(source, target).startsWith('..')) throw new Error('Choose a destination outside the installer package');
40
+ if (pathInside(source, target)) throw new Error('Choose a destination outside the installer package');
36
41
  const files = new Map();
37
42
  if (scope === 'project') {
38
43
  for (const root of roots) for (const path of collect(join(source, root))) files.set(relative(source, path), path);
@@ -43,7 +48,7 @@ export function install(destination, { source = SOURCE, installDependencies = tr
43
48
  }
44
49
  const destinations = [];
45
50
  for (const agent of [...new Set(agents)]) {
46
- const native = join(agent === 'codex' ? '.agents' : '.claude', 'skills', 'jobhunt-kit');
51
+ const native = relative(target,skillDirectory(agent,{scope,home:resolve(home),cwd:scope==='project'?target:process.cwd(),env}));
47
52
  destinations.push({agent, path: join(target, native)});
48
53
  files.set(join(native, 'SKILL.md'), join(source, 'installer-assets/native-SKILL.md'));
49
54
  const plugin = join(source, 'plugins/jobhunt-kit');
@@ -87,8 +92,13 @@ export function install(destination, { source = SOURCE, installDependencies = tr
87
92
  return { directory: target, copied: pending.length, dependencies_installed: installDependencies, scope, agents: destinations };
88
93
  }
89
94
  export async function main(args) {
95
+ if (args[0] === 'agents') {
96
+ if (args.length!==1) throw new Error('Usage: jobhunt-kit agents');
97
+ console.log(AGENTS.map(a=>`${a.id.padEnd(14)} ${a.name}${a.globalOnly?' (global only)':''}`).join('\n'));
98
+ return;
99
+ }
90
100
  if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
91
- console.log('Usage: jobhunt-kit <command> [arguments] [--workspace dir | --data dir]\n\ninstall [directory] Choose agents and install (default: ./my-jobhunt)\n --agents codex,claude Select agents without prompts\n --scope project|global Workspace skills or user-wide skills\n --yes, -y Use detected agents and project scope\ninit <directory> Alias with an explicit destination\nprofile init|show|check Initialize/view/validate local profile\nprofile save --input file Save profile and clear confirmation\nprofile confirm --note text Record explicit candidate confirmation\nresume [file] Import file, fingerprint and mechanical checks\nresume check|reviewed Inspect file / record agent review (--input file)\nsearch [plan] Prepare context for agent; no live search\nsearch start|event|record|finish Persist search operations (--input file)\napply preview|export|begin slug Review/export/reserve application\napply prepare|approve|send|finish|resolve --input file\ntrack list|show slug|status slug --input file\nhistory | runs | report Inspect history or generate Markdown report\npolicy show|set --input file Inspect/set application policy\nschedule --input file Generate scheduler prompt; does not schedule\ndoctor Check local setup without network\n\nNode.js 24+ required. Default data: ./local/jobhunt-kit. Only apply send performs a live application call.');
101
+ console.log('Usage: jobhunt-kit <command> [arguments] [--workspace dir | --data dir]\n\ninstall [directory] Choose agents and install (default: ./my-jobhunt)\n --agents codex,cursor,... Select agents (or all); --providers is an alias\n agents List supported agents\n --scope project|global Workspace skills or user-wide skills\n --yes, -y Use detected agents and project scope\ninit <directory> Alias with an explicit destination\nprofile init|show|check Initialize/view/validate local profile\nprofile save --input file Save profile and clear confirmation\nprofile confirm --note text Record explicit candidate confirmation\nresume [file] Import file, fingerprint and mechanical checks\nresume check|reviewed Inspect file / record agent review (--input file)\nsearch [plan] Prepare context for agent; no live search\nsearch start|event|record|finish Persist search operations (--input file)\napply preview|export|begin slug Review/export/reserve application\napply prepare|approve|send|finish|resolve --input file\ntrack list|show slug|status slug --input file\nhistory | runs | report Inspect history or generate Markdown report\npolicy show|set --input file Inspect/set application policy\nschedule --input file Generate scheduler prompt; does not schedule\nbackup/backup-verify <directory> Create or verify a local backup\nrestore --input file Restore after exact typed confirmation\nrecover inspect|apply|packet Inspect, resolve, or recreate an attempt packet\nprivacy map|export|redact|purge Inspect, export or remove local PII\ndoctor Check schemas, database and recovery state without network\n\nNode.js 24+ required. Default data: ./local/jobhunt-kit. Only apply send performs a live application call.');
92
102
  return;
93
103
  }
94
104
  if (!['init', 'install'].includes(args[0])) {
@@ -107,6 +117,7 @@ export async function main(args) {
107
117
  const result = install(options.destination, options);
108
118
  console.log(`\nJobhunt Kit ready: ${result.directory}\nCopied files: ${result.copied}`);
109
119
  for (const agent of result.agents) console.log(`${agent.agent}: ${agent.path}`);
120
+ if (result.scope==='project' && result.agents.some(a=>a.agent==='hermes')) console.log('Hermes: run hermes skills trust from this workspace before using project skills.');
110
121
  console.log(result.scope === 'global' ? 'Open your working folder in a new agent session.' : 'Open this folder in a new agent session.');
111
122
  console.log('Ask: use jobhunt-kit to initialize my job search profile.\nNo profile, schedule, search or application was created.');
112
123
  }
@@ -20,3 +20,4 @@ local/
20
20
  __pycache__/
21
21
  .tmp/
22
22
  *.tgz
23
+ plan/
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "jobhunt-kit-plugin",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "jobhunt-kit-plugin",
9
- "version": "0.3.0",
9
+ "version": "0.5.0",
10
10
  "dependencies": {
11
+ "ajv": "8.20.0",
11
12
  "hirify-cli": "0.4.5",
12
13
  "mammoth": "1.12.2",
13
14
  "pdf-parse": "2.4.5"
@@ -224,6 +225,22 @@
224
225
  "node": ">=10.0.0"
225
226
  }
226
227
  },
228
+ "node_modules/ajv": {
229
+ "version": "8.20.0",
230
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
231
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
232
+ "license": "MIT",
233
+ "dependencies": {
234
+ "fast-deep-equal": "^3.1.3",
235
+ "fast-uri": "^3.0.1",
236
+ "json-schema-traverse": "^1.0.0",
237
+ "require-from-string": "^2.0.2"
238
+ },
239
+ "funding": {
240
+ "type": "github",
241
+ "url": "https://github.com/sponsors/epoberezkin"
242
+ }
243
+ },
227
244
  "node_modules/argparse": {
228
245
  "version": "1.0.10",
229
246
  "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -280,6 +297,28 @@
280
297
  "underscore": "^1.13.1"
281
298
  }
282
299
  },
300
+ "node_modules/fast-deep-equal": {
301
+ "version": "3.1.3",
302
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
303
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
304
+ "license": "MIT"
305
+ },
306
+ "node_modules/fast-uri": {
307
+ "version": "3.1.7",
308
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
309
+ "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
310
+ "funding": [
311
+ {
312
+ "type": "github",
313
+ "url": "https://github.com/sponsors/fastify"
314
+ },
315
+ {
316
+ "type": "opencollective",
317
+ "url": "https://opencollective.com/fastify"
318
+ }
319
+ ],
320
+ "license": "BSD-3-Clause"
321
+ },
283
322
  "node_modules/hirify-cli": {
284
323
  "version": "0.4.5",
285
324
  "resolved": "https://registry.npmjs.org/hirify-cli/-/hirify-cli-0.4.5.tgz",
@@ -310,6 +349,12 @@
310
349
  "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
311
350
  "license": "MIT"
312
351
  },
352
+ "node_modules/json-schema-traverse": {
353
+ "version": "1.0.0",
354
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
355
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
356
+ "license": "MIT"
357
+ },
313
358
  "node_modules/jszip": {
314
359
  "version": "3.10.1",
315
360
  "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
@@ -440,6 +485,15 @@
440
485
  "util-deprecate": "~1.0.1"
441
486
  }
442
487
  },
488
+ "node_modules/require-from-string": {
489
+ "version": "2.0.2",
490
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
491
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
492
+ "license": "MIT",
493
+ "engines": {
494
+ "node": ">=0.10.0"
495
+ }
496
+ },
443
497
  "node_modules/safe-buffer": {
444
498
  "version": "5.1.2",
445
499
  "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "jobhunt-kit",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "jobhunt-kit",
9
- "version": "0.3.0",
9
+ "version": "0.5.0",
10
10
  "dependencies": {
11
+ "ajv": "8.20.0",
11
12
  "hirify-cli": "0.4.5",
12
13
  "mammoth": "1.12.2",
13
14
  "pdf-parse": "2.4.5"
@@ -227,6 +228,22 @@
227
228
  "node": ">=10.0.0"
228
229
  }
229
230
  },
231
+ "node_modules/ajv": {
232
+ "version": "8.20.0",
233
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
234
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
235
+ "license": "MIT",
236
+ "dependencies": {
237
+ "fast-deep-equal": "^3.1.3",
238
+ "fast-uri": "^3.0.1",
239
+ "json-schema-traverse": "^1.0.0",
240
+ "require-from-string": "^2.0.2"
241
+ },
242
+ "funding": {
243
+ "type": "github",
244
+ "url": "https://github.com/sponsors/epoberezkin"
245
+ }
246
+ },
230
247
  "node_modules/argparse": {
231
248
  "version": "1.0.10",
232
249
  "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -283,6 +300,28 @@
283
300
  "underscore": "^1.13.1"
284
301
  }
285
302
  },
303
+ "node_modules/fast-deep-equal": {
304
+ "version": "3.1.3",
305
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
306
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
307
+ "license": "MIT"
308
+ },
309
+ "node_modules/fast-uri": {
310
+ "version": "3.1.7",
311
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
312
+ "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
313
+ "funding": [
314
+ {
315
+ "type": "github",
316
+ "url": "https://github.com/sponsors/fastify"
317
+ },
318
+ {
319
+ "type": "opencollective",
320
+ "url": "https://opencollective.com/fastify"
321
+ }
322
+ ],
323
+ "license": "BSD-3-Clause"
324
+ },
286
325
  "node_modules/hirify-cli": {
287
326
  "version": "0.4.5",
288
327
  "resolved": "https://registry.npmjs.org/hirify-cli/-/hirify-cli-0.4.5.tgz",
@@ -313,6 +352,12 @@
313
352
  "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
314
353
  "license": "MIT"
315
354
  },
355
+ "node_modules/json-schema-traverse": {
356
+ "version": "1.0.0",
357
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
358
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
359
+ "license": "MIT"
360
+ },
316
361
  "node_modules/jszip": {
317
362
  "version": "3.10.1",
318
363
  "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
@@ -443,6 +488,15 @@
443
488
  "util-deprecate": "~1.0.1"
444
489
  }
445
490
  },
491
+ "node_modules/require-from-string": {
492
+ "version": "2.0.2",
493
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
494
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
495
+ "license": "MIT",
496
+ "engines": {
497
+ "node": ">=0.10.0"
498
+ }
499
+ },
446
500
  "node_modules/safe-buffer": {
447
501
  "version": "5.1.2",
448
502
  "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jobhunt-kit",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "bin": {
5
5
  "jobhunt-kit": "bin/jobhunt-kit.mjs"
6
6
  },
@@ -19,12 +19,14 @@
19
19
  "hirify": "hirify",
20
20
  "test": "node --test tests/*.test.mjs",
21
21
  "check": "node scripts/check-package.mjs",
22
+ "pack:smoke": "node scripts/pack-smoke.mjs",
22
23
  "prepublishOnly": "npm test && npm run check"
23
24
  },
24
25
  "engines": {
25
26
  "node": ">=24.0.0"
26
27
  },
27
28
  "dependencies": {
29
+ "ajv": "8.20.0",
28
30
  "hirify-cli": "0.4.5",
29
31
  "mammoth": "1.12.2",
30
32
  "pdf-parse": "2.4.5"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jobhunt-kit",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Reusable candidate intake, resume review, Hirify search and local application tracking",
5
5
  "author": {
6
6
  "name": "Jobhunt Kit contributors"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jobhunt-kit",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Reusable candidate intake, resume review, Hirify search and local application tracking",
5
5
  "author": {
6
6
  "name": "Jobhunt Kit contributors"