hirify-cli 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,8 @@ npx -y hirify-cli@latest init
14
14
 
15
15
  One command installs the CLI globally, installs the skill for the agents found on this computer
16
16
  (Claude Code, Codex, Cursor, OpenCode, Gemini CLI, Antigravity) and signs you in through the browser.
17
- Run it yourself or let your agent run it. `--no-install` and `--no-login` skip a step.
17
+ Run it yourself or let your agent run it. It installs the newest release, whichever version
18
+ started it; a pinned version (`HIRIFY_VERSION_PIN`, `HIRIFY_NO_AUTO_UPDATE=1`) is kept.
18
19
 
19
20
  The same steps one by one:
20
21
 
package/bin/lib/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { CliError, redact, safeText } from './errors.js'
2
2
  const ARGUMENTS = { auth: '[<key>]', update: '[<version>]', 'vacancy search': '[<query>]', 'vacancy read': '<slug>', 'vacancy reveal': '<slug>', 'vacancy apply': '<slug>', 'vacancy hide': '<slug>...', 'company hide': '[<name>...]', 'feed show': '<id>', 'feed create': '<name>', 'feed deliver': '<id>', 'webhook create': '<name> <url>', 'feedback send': '<bug|feature> <title>', 'api call': '<capability>', 'capabilities show': '<capability>' }
3
3
  const GLOBAL = ['json', 'fields', 'error-format', 'debug', 'timeout', 'help']
4
- export const BOOLEAN_FLAGS = new Set(['json', 'debug', 'help', 'force', 'no-browser', 'no-install', 'no-login', 'stdin', 'telegram', 'no-telegram', 'no-webhook', 'check', 'rollback', 'undo'])
4
+ export const BOOLEAN_FLAGS = new Set(['json', 'debug', 'help', 'force', 'no-browser', 'stdin', 'telegram', 'no-telegram', 'no-webhook', 'check', 'rollback', 'undo'])
5
5
  export const COMMANDS = {
6
6
  intro: { args: [0, 0], description: 'what this can do, and in what order' },
7
7
  version: { args: [0, 0], description: 'installed version' },
8
- init: { args: [0, 0], flags: ['no-install', 'no-login', 'no-browser', 'callback-port'], description: 'install the CLI and the skill, then sign in' },
8
+ init: { args: [0, 0], flags: ['no-browser', 'callback-port'], description: 'install the CLI and the skill, then sign in' },
9
9
  skill: { args: [0, 0], description: 'install the working rules for your agent' },
10
10
  login: { args: [0, 0], flags: ['force', 'no-browser', 'callback-port'], description: 'sign in through your browser' },
11
11
  logout: { args: [0, 0], description: 'sign out on this computer' },
package/bin/lib/main.js CHANGED
@@ -73,30 +73,26 @@ export async function main(argv = process.argv.slice(2), { baseRoot = ownRoot, b
73
73
  const { installGlobally, installSkill, describeSkill } = await import('./setup.js')
74
74
  const result = {}
75
75
  let skillRoot = ownRoot
76
- if (!command.values['no-install']) {
77
- output.progress('Installing the Hirify CLI...')
78
- // A newer CLI found on this computer stays, and the skill is taken from it.
79
- const { root, ...cli } = await installGlobally({ version: pkg.version, signal, env: config.env })
80
- result.cli = cli
81
- if (root) skillRoot = root
82
- }
76
+ output.progress('Installing the Hirify CLI...')
77
+ const { root, ...cli } = await installGlobally({ version: pkg.version, latest: updater.check, signal, env: config.env })
78
+ result.cli = cli
79
+ // The skill comes from the CLI that is installed, so the two match.
80
+ if (cli.installed && cli.version !== pkg.version) skillRoot = root
83
81
  try { result.skill = installSkill({ packageRoot: skillRoot, env: config.env }) }
84
82
  catch (error) { if (!(error instanceof CliError)) throw error; result.skill = { error: error.code, message: error.message, agents: [] } }
85
- if (!command.values['no-login']) {
86
- let source = 'none'
87
- try { source = auth.status().source } catch {}
88
- if (['environment', 'key'].includes(source)) result.sign_in = { signed_in: true, already_signed_in: true, source }
89
- else try { result.sign_in = await auth.login({ noBrowser: Boolean(command.values['no-browser']), port: Number(command.values['callback-port'] || 0), consentMs: Math.min(command.timeout, 300000) }) }
90
- catch (error) { if (!(error instanceof CliError) || signal.aborted) throw error; result.sign_in = { signed_in: false, error: error.code, message: error.message } }
91
- }
83
+ try {
84
+ const { source } = auth.status()
85
+ // A configured key is a finished sign-in: no browser, no waiting.
86
+ result.sign_in = ['environment', 'key'].includes(source) ? { signed_in: true, already_signed_in: true, source } : await auth.login({ noBrowser: Boolean(command.values['no-browser']), port: Number(command.values['callback-port'] || 0), consentMs: Math.min(command.timeout, 300000) })
87
+ } catch (error) { if (!(error instanceof CliError) || signal.aborted) throw error; result.sign_in = { signed_in: false, error: error.code, message: error.message } }
92
88
  if (command.json) output.json(result)
93
89
  else {
94
- if (result.cli) output.console.log(result.cli.installed ? `Hirify CLI ${result.cli.version} is installed. The command is: hirify` : `The CLI could not be installed globally (${result.cli.reason}). Every command still works as: npx -y hirify-cli <command>`)
90
+ output.console.log(result.cli.installed ? `Hirify CLI ${result.cli.version} is installed. The command is: hirify` : `The CLI could not be installed globally (${result.cli.reason}). Every command still works as: npx -y hirify-cli <command>`)
95
91
  if (result.skill.error) output.console.log(result.skill.message); else for (const line of describeSkill(result.skill)) output.console.log(line)
96
- if (result.sign_in) output.console.log(result.sign_in.signed_in ? (result.sign_in.already_signed_in ? 'Already signed in.' : 'Signed in. Access saved on this computer.') : result.sign_in.message)
97
- if (result.sign_in?.signed_in) output.console.log('Next: ask your agent to look at your Hirify profile and saved searches.')
92
+ output.console.log(result.sign_in.signed_in ? (result.sign_in.already_signed_in ? 'Already signed in.' : 'Signed in. Access saved on this computer.') : result.sign_in.message)
93
+ if (result.sign_in.signed_in) output.console.log('Next: ask your agent to look at your Hirify profile and saved searches.')
98
94
  }
99
- if (result.cli?.installed === false || result.skill.error || result.skill.agents.some(item => !item.installed) || result.sign_in?.signed_in === false) exitCode = 1
95
+ if (!result.cli.installed || result.skill.error || result.skill.agents.some(item => !item.installed) || !result.sign_in.signed_in) exitCode = 1
100
96
  } else if (command.name === 'auth') {
101
97
  secrets.add(command.words[0])
102
98
  await store.commit({ kind: 'key', access_token: command.words[0] })
package/bin/lib/setup.js CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, cpSync, rmSync, symlinkSync, lstatSync, realpathSync, readFileSync } from 'node:fs'
1
+ import { existsSync, mkdirSync, cpSync, rmSync, symlinkSync, realpathSync, readFileSync } from 'node:fs'
2
2
  import { homedir } from 'node:os'
3
3
  import { join, relative } from 'node:path'
4
4
  import semver from 'semver'
@@ -26,11 +26,8 @@ export function agentDirectories({ env = process.env, home = homedir() } = {}) {
26
26
  ]
27
27
  }
28
28
 
29
- const entry = path => { try { return lstatSync(path) } catch (error) { if (error.code === 'ENOENT') return null; throw error } }
30
- const real = path => { try { return realpathSync(path) } catch { return path } }
31
29
  // Removing a link removes the link, never the directory it points to.
32
- const remove = path => { if (entry(path)) rmSync(path, { recursive: true, force: true }) }
33
- const readJSON = file => { try { return JSON.parse(readFileSync(file, 'utf8')) } catch { return null } }
30
+ const remove = path => rmSync(path, { recursive: true, force: true })
34
31
  // OS messages may carry private paths, so only the code is reported.
35
32
  const reason = error => ['EACCES', 'EPERM', 'EROFS', 'ENOSPC', 'EDQUOT', 'ENOTDIR', 'ENOENT', 'EEXIST', 'ELOOP'].includes(error?.code) ? error.code : 'unknown'
36
33
 
@@ -55,14 +52,14 @@ export function installSkill({ packageRoot, env = process.env, home = homedir(),
55
52
  try {
56
53
  mkdirSync(agent.skills, { recursive: true })
57
54
  // An agent whose skills directory already is the shared one needs no link of its own.
58
- const parent = real(agent.skills)
59
- if (parent === real(shared)) { agents.push({ agent: agent.name, installed: true }); continue }
55
+ const parent = realpathSync(agent.skills)
56
+ if (parent === realpathSync(shared)) { agents.push({ agent: agent.name, installed: true }); continue }
60
57
  const target = join(parent, SKILL_NAME)
61
58
  remove(target)
62
59
  try {
63
60
  // A junction needs no elevated rights on Windows; elsewhere the link stays relative.
64
- if (platform === 'win32') symlinkSync(real(path), target, 'junction')
65
- else symlinkSync(relative(parent, real(path)), target, 'dir')
61
+ if (platform === 'win32') symlinkSync(realpathSync(path), target, 'junction')
62
+ else symlinkSync(relative(parent, realpathSync(path)), target, 'dir')
66
63
  } catch { cpSync(source, target, { recursive: true }) }
67
64
  agents.push({ agent: agent.name, installed: true })
68
65
  } catch (error) { agents.push({ agent: agent.name, installed: false, reason: reason(error) }) }
@@ -71,25 +68,34 @@ export function installSkill({ packageRoot, env = process.env, home = homedir(),
71
68
  }
72
69
 
73
70
  /**
74
- * Put the CLI on PATH with the person's own npm and its settings: the environment goes to npm as
75
- * it is, because what npx adds to it is the same configuration, already resolved. A failure is a
76
- * result, not an error: without a global installation every command still runs as
77
- * `npx -y hirify-cli <command>`. A newer global installation is kept, never downgraded, and its
78
- * `root` says where the skill that matches it is.
71
+ * Put the newest release of the CLI on PATH, whichever package started the command, with the
72
+ * person's own npm and its settings: the environment goes to npm as it is, because what npx adds
73
+ * to it is the same configuration, already resolved. A pinned version and switched-off updates
74
+ * keep the running release, as they do for automatic updates. A failure is a result, not an
75
+ * error: without a global installation every command still runs as `npx -y hirify-cli <command>`.
76
+ * An installation that is already at that release or past it is kept. `root` is where the
77
+ * installed package is, and with it the skill that matches it.
79
78
  */
80
- export async function installGlobally({ version, signal, env = process.env, run = runProcess } = {}) {
79
+ export async function installGlobally({ version, latest, signal, env = process.env, run = runProcess } = {}) {
81
80
  const options = { signal, env }
81
+ if (!env.HIRIFY_VERSION_PIN && env.HIRIFY_NO_AUTO_UPDATE !== '1') {
82
+ try { version = (await latest()).version }
83
+ catch (error) { if (signal?.aborted || !(error instanceof CliError)) throw error; return { installed: false, reason: error.code } }
84
+ }
82
85
  let root
83
86
  try { root = await run('npm', ['root', '--global'], { ...options, timeout: 10000 }) }
84
87
  catch (error) { if (signal?.aborted) throw error; return { installed: false, reason: 'npm_unavailable' } }
85
88
  if (root.code !== 0 || !root.stdout.trim()) return { installed: false, reason: 'npm_unavailable' }
86
89
  const location = join(root.stdout.trim(), PACKAGE_NAME)
87
- const current = readJSON(join(location, 'package.json'))
88
- if (current?.name === PACKAGE_NAME && semver.valid(current.version) && semver.gte(current.version, version)) return { installed: true, version: current.version, changed: false, ...(semver.gt(current.version, version) && { root: location }) }
90
+ let current
91
+ // Only a package that is not there counts as absent; one that cannot be read is reported.
92
+ try { current = JSON.parse(readFileSync(join(location, 'package.json'), 'utf8')) }
93
+ catch (error) { if (error.code !== 'ENOENT') return { installed: false, reason: 'global_package_unreadable' } }
94
+ if (current?.name === PACKAGE_NAME && semver.valid(current.version) && semver.gte(current.version, version)) return { installed: true, version: current.version, changed: false, root: location }
89
95
  let result
90
96
  try { result = await run('npm', ['install', '--global', '--ignore-scripts', '--no-audit', '--no-fund', `${PACKAGE_NAME}@${version}`], { ...options, timeout: 180000 }) }
91
97
  catch (error) { if (signal?.aborted) throw error; return { installed: false, reason: 'install_failed' } }
92
- return result.code === 0 ? { installed: true, version, changed: true } : { installed: false, reason: 'install_failed' }
98
+ return result.code === 0 ? { installed: true, version, changed: true, root: location } : { installed: false, reason: 'install_failed' }
93
99
  }
94
100
 
95
101
  /** Lines a person reads after `hirify skill` or `hirify init`. */
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "hirify-cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "hirify-cli",
9
- "version": "0.6.0",
9
+ "version": "0.6.1",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "cross-spawn": "7.0.6",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hirify-cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Hirify CLI for AI agents: browser sign-in, job feeds, search, reading a vacancy in full, contacts to apply to, and applying on Hirify.",
5
5
  "bin": {
6
6
  "hirify": "bin/hirify.js"