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 +2 -1
- package/bin/lib/cli.js +2 -2
- package/bin/lib/main.js +14 -18
- package/bin/lib/setup.js +24 -18
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
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.
|
|
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', '
|
|
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-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
if (result.sign_in
|
|
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
|
|
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,
|
|
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 =>
|
|
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 =
|
|
59
|
-
if (parent ===
|
|
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(
|
|
65
|
-
else symlinkSync(relative(parent,
|
|
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
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
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
|
-
|
|
88
|
-
|
|
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`. */
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hirify-cli",
|
|
3
|
-
"version": "0.6.
|
|
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.
|
|
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