hirify-cli 0.6.4 → 0.6.5

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/bin/lib/cli.js CHANGED
@@ -103,7 +103,7 @@ export function parse(argv) {
103
103
  }
104
104
  export function help(topic = '') {
105
105
  const entries = Object.entries(COMMANDS).filter(([name]) => !topic || name === topic || name.startsWith(topic + ' '))
106
- return `hirify - job search for AI agents\n\n${(entries.length ? entries : Object.entries(COMMANDS)).map(([name, spec]) => ` hirify ${(name + (ARGUMENTS[name] ? ' ' + ARGUMENTS[name] : '')).padEnd(35)} ${spec.description}${spec.flags?.length ? `\n ${spec.flags.map(f => '--' + f + (BOOLEAN_FLAGS.has(f) ? '' : ' <value>')).join(' ')}` : ''}`).join('\n')}\n\nGlobal options: --json, --fields a,b, --error-format json|text, --timeout <seconds>, --debug, --help.\nUse -- to end options; --name=value and --name value are supported.\nLists and search are free. Vacancy read, reveal and apply have separate allowances.\nCheck them with hirify account show. Apply sends a real application: ask the person first.\nLocal agents reuse saved sign-in when allowed. Token renewal needs write access.\nSandbox access denied: request permission for the command; do not copy tokens.\nHeadless/CI: HIRIFY_KEY or hirify auth --stdin. Manual login: --no-browser.\nSSH: forward the chosen --callback-port to the same loopback port on the CLI host.\nSet up the CLI, the skill and sign-in: hirify init. The skill alone: hirify skill\n`
106
+ return `hirify - job search for AI agents\n\n${(entries.length ? entries : Object.entries(COMMANDS)).map(([name, spec]) => ` hirify ${(name + (ARGUMENTS[name] ? ' ' + ARGUMENTS[name] : '')).padEnd(35)} ${spec.description}${spec.flags?.length ? `\n ${spec.flags.map(f => '--' + f + (BOOLEAN_FLAGS.has(f) ? '' : ' <value>')).join(' ')}` : ''}`).join('\n')}\n\nGlobal options: --json, --fields a,b, --error-format json|text, --timeout <seconds>, --debug, --help.\nUse -- to end options; --name=value and --name value are supported.\nStart with hirify intro: the working method, kept current by Hirify.\nAllowances: hirify account show. Apply sends a real application: ask your user first.\nLocal agents reuse saved sign-in when allowed. Token renewal needs write access.\nSandbox access denied: request permission for the command; do not copy tokens.\nHeadless/CI: HIRIFY_KEY or hirify auth --stdin. Manual login: --no-browser.\nSSH: forward the chosen --callback-port to the same loopback port on the CLI host.\nSet up the CLI, the skill and sign-in: hirify init. The skill alone: hirify skill\n`
107
107
  }
108
108
  export function createOutput({ stdout = process.stdout, stderr = process.stderr, json = false, errorJSON = false, secrets = new Set() } = {}) {
109
109
  let closed = false
@@ -110,6 +110,8 @@ function printVacancies(list, meta) {
110
110
  ? true
111
111
  : perPage !== null && list.length >= perPage
112
112
  if (more && page !== null) console.log(`More: add --page ${page + 1}`)
113
+ // Paging is fine; a list this long usually means the criteria do not yet describe the user.
114
+ if (lastPage !== null && lastPage > 3) console.log(`${total ?? 'Many'} vacancies over ${lastPage} pages: narrower criteria usually give a list worth reading in full.`)
113
115
  }
114
116
 
115
117
  /** The vacancies in one saved feed, using the criteria the feed already holds. */
@@ -619,94 +621,24 @@ async function cmdWebhookCreate(args, words) {
619
621
 
620
622
  const INTRO = `hirify - job search for AI agents
621
623
 
622
- Hirify is a job board. This CLI is how an agent works it for someone: the same vacancies, the
623
- same saved filters and the same account they have at hirify.me.
624
+ This is the built-in text: Hirify did not return the playbook. The working method (interviewing your
625
+ user, setting up the search, going through the results, judging a vacancy, applying) is the Hirify
626
+ playbook, kept current on the server: https://hirify.me/agent-playbook.txt. Run hirify intro again
627
+ when Hirify is reachable.
624
628
 
625
629
  Setting up
626
630
  npx -y hirify-cli@latest init installs this CLI and the agent's skill, then opens sign-in.
627
631
 
628
632
  Signing in
629
- hirify login prints a link and tries the browser when available. A person confirms access.
633
+ hirify login prints a link and tries the browser when available. Your user confirms access.
630
634
  Manual sign-in: hirify login --no-browser. For automation, use HIRIFY_KEY or hirify auth --stdin; get a key at
631
635
  hirify.me/account/api-access.
632
636
 
633
- Start with what the account already has
634
- Most people who use Hirify have saved a filter or two on the site. Those are feeds, and they
635
- are the best place to start, because someone has already said in them what they want.
636
-
637
- hirify account show the plan and allowances
638
- hirify feed list what this account has saved
639
- hirify feed show 31 the vacancies in one of them
640
-
641
- When no feed fits, search the whole board. Search takes a phrase, and any criterion the
642
- site's own filter form can express, written as an option:
643
-
644
- hirify vacancy search "senior go"
645
- hirify vacancy search "senior go" --page 2
646
- hirify vacancy search "senior go" --<criterion> <value>
647
-
648
- The criteria are the server's, not this CLI's, and nothing here names one on purpose: a
649
- name written into this text would be a copy, and copies go stale quietly. Ask instead:
650
-
651
- hirify filter guide
652
-
653
- It prints what search can filter on and what the values are, written by the server from
654
- the same source the site searches with. An option is sent on under the name you gave it;
655
- give one twice and the values are joined, the way the site sends a filter with several
656
- values.
657
-
658
- Read before you spend anything
659
- A card is a headline: title, company, terms. Fit is decided in the text.
660
-
661
- hirify vacancy read senior-go-engineer
662
-
663
- This prints the whole vacancy, and it also says which of the two ways to apply this one
664
- takes, so you do not have to work that out or find out from a refusal.
665
-
666
- What costs what
667
- Lists and searches are free.
668
- Reading one vacancy in full spends one of the Agent API's daily vacancy opens. There are
669
- many of them, and reading a vacancy again the same day costs nothing. Read as much as you
670
- need to.
671
- Revealing where to apply spends 1 reveal, and reveals are the scarce one. Protect that
672
- number: shortlist by reading, then reveal only the ones worth applying to.
673
- Applying has a daily allowance of its own. It is not the free step it looks like.
674
- hirify account show has all three, and it is the only place they are current.
675
-
676
- Where to apply
677
- hirify vacancy reveal senior-go-engineer
678
-
679
- Gives the company, its LinkedIn page when we know it, and the address to send the
680
- application to. Revealing the same vacancy again returns the same thing and costs nothing.
681
-
682
- Applying
683
- Most vacancies here came from somewhere else: company pages, Telegram channels, other
684
- boards. For those, reveal brings back the address, and the application is made there rather
685
- than through Hirify.
686
- Vacancies hosted on Hirify can be applied to from here:
687
-
688
- hirify profile list
689
- hirify vacancy apply senior-go-engineer --profile 4 --cover "..."
690
-
691
- An application reaches a real person and cannot be recalled. Ask first, every time, and
692
- show what you are about to send. Nothing follows up afterwards: the recruiter replies
693
- where they choose to.
694
-
695
- Two more things
696
- A saved search can be created from here and delivered to Telegram or to a server of yours:
697
- hirify feed create, hirify feed deliver, hirify webhook list.
698
- Something broken or missing: hirify feedback send bug "<title>" --body "<what happened>". It
699
- reaches the team and costs nothing.
700
-
701
- If a command you need is not here
702
- hirify api call <capability> invokes any capability Hirify lists in its manifest and prints
703
- the answer as it comes back, so a capability without its own command is a detour and not a
704
- dead end. Inputs go in --data as a JSON object. Prefer the commands above where one fits:
705
- they say what a thing costs and what a refusal means, and this one cannot.
706
-
707
- If you are an agent
708
- Install the working rules once: hirify skill. They cover the order
709
- above, what needs the person's permission before you do it, and what each refusal means.
637
+ Commands
638
+ hirify account show, hirify feed list, hirify vacancy search, hirify vacancy read,
639
+ hirify vacancy reveal, hirify vacancy apply, hirify filter guide. Anything else:
640
+ hirify api call <capability>. An application reaches a real person and cannot be recalled:
641
+ ask your user first.
710
642
 
711
643
  Use --json for structured results and --error-format=json for structured errors.
712
644
  The full list of commands: hirify --help`
package/bin/lib/main.js CHANGED
@@ -56,6 +56,7 @@ export async function main(argv = process.argv.slice(2), { baseRoot = ownRoot, b
56
56
  const api = createApi({ config, http, auth, output })
57
57
  const updater = createUpdater({ config, http, signal, output, event, packageRoot: ownRoot, baseRoot, baseVersion })
58
58
  const local = ['auth', 'auth status', 'logout', 'doctor', 'init', 'skill', 'update'].includes(command.name)
59
+ if (!['init', 'skill'].includes(command.name)) { const { refreshSkill } = await import('./setup.js'); refreshSkill({ packageRoot: ownRoot, env: config.env }) }
59
60
  if (!skipUpdate && !local) next = await updater.automatic()
60
61
  if (next) {
61
62
  // Dispose every timer and signal handler before handing off, at most once per invocation.
package/bin/lib/setup.js CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, cpSync, rmSync, symlinkSync, realpathSync, readFileSync } from 'node:fs'
1
+ import { existsSync, mkdirSync, cpSync, rmSync, symlinkSync, realpathSync, readFileSync, writeFileSync, lstatSync } from 'node:fs'
2
2
  import { homedir } from 'node:os'
3
3
  import { dirname, join, relative } from 'node:path'
4
4
  import semver from 'semver'
@@ -52,6 +52,7 @@ export function installSkill({ packageRoot, env = process.env, home = homedir(),
52
52
  mkdirSync(shared, { recursive: true })
53
53
  remove(path)
54
54
  cpSync(source, path, { recursive: true })
55
+ markVersion(path, packageRoot)
55
56
  } catch (error) { throw new CliError('skill_install_failed', `Could not save the skill (${reason(error)}). This command needs write access to the home directory.`) }
56
57
  const agents = []
57
58
  for (const agent of agentDirectories({ env, home })) {
@@ -71,6 +72,39 @@ export function installSkill({ packageRoot, env = process.env, home = homedir(),
71
72
  return { skill: SKILL_NAME, path, agents }
72
73
  }
73
74
 
75
+ // Which CLI release the installed skill came from, so a newer CLI knows to refresh it.
76
+ const VERSION_MARK = '.hirify-cli-version'
77
+ const packageVersion = root => { try { return JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version } catch { return null } }
78
+ function markVersion(path, packageRoot) { const version = packageVersion(packageRoot); if (version) writeFileSync(join(path, VERSION_MARK), version + '\n') }
79
+
80
+ /**
81
+ * Refresh a skill this CLI installed earlier, when the running CLI is newer than the release it
82
+ * came from: an auto-updated CLI brings its skill along, so the working rules on the computer
83
+ * never lag behind the CLI. Only an existing installation is touched - nothing is installed where
84
+ * there was none - and an older CLI never rolls a newer skill back. Agents that got a copy rather
85
+ * than a link get their copy refreshed too. Best effort: any failure leaves things as they were.
86
+ */
87
+ export function refreshSkill({ packageRoot, env = process.env, home = homedir() } = {}) {
88
+ try {
89
+ const source = join(packageRoot, 'skills', SKILL_NAME)
90
+ const path = join(home, '.agents', 'skills', SKILL_NAME)
91
+ const own = packageVersion(packageRoot)
92
+ if (!own || !existsSync(join(path, 'SKILL.md')) || !existsSync(join(source, 'SKILL.md'))) return false
93
+ let installed = null
94
+ try { installed = readFileSync(join(path, VERSION_MARK), 'utf8').trim() } catch {}
95
+ if (semver.valid(installed) && !semver.gt(own, installed)) return false
96
+ remove(path); cpSync(source, path, { recursive: true }); markVersion(path, packageRoot)
97
+ for (const agent of agentDirectories({ env, home })) {
98
+ const target = join(agent.skills, SKILL_NAME)
99
+ try {
100
+ if (!existsSync(join(target, 'SKILL.md')) || lstatSync(target).isSymbolicLink() || realpathSync(target) === realpathSync(path)) continue
101
+ remove(target); cpSync(source, target, { recursive: true })
102
+ } catch {}
103
+ }
104
+ return true
105
+ } catch { return false }
106
+ }
107
+
74
108
  /**
75
109
  * Put the newest release of the CLI on PATH, whichever package started the command, with the
76
110
  * person's own npm and its settings: the environment goes to npm as it is, because what npx adds
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "hirify-cli",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "hirify-cli",
9
- "version": "0.6.4",
9
+ "version": "0.6.5",
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.4",
3
+ "version": "0.6.5",
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"
@@ -5,103 +5,50 @@ description: Job search through Hirify - vacancies from the user's saved feeds,
5
5
 
6
6
  # Hirify job search
7
7
 
8
- Use the Hirify job board through the `hirify` CLI. This file gives the working order; `reference.md`
9
- has every command; `hirify intro` prints the playbook: hirify.me/agent-playbook.txt.
8
+ ## Start here
9
+
10
+ Run `hirify intro` once per session, before anything else. It prints the Hirify playbook, the
11
+ working method: interviewing your user, setting up the search, going through the results, judging
12
+ a vacancy, applying. Hirify keeps it current on the server (it is also at
13
+ https://hirify.me/agent-playbook.txt), so follow it rather than any older notes. This file only
14
+ covers running the CLI; `reference.md` has every command.
15
+
16
+ One rule stands regardless: `vacancy apply` sends a real application that cannot be taken back.
17
+ Ask your user first, every time, and show what you are about to send.
10
18
 
11
19
  ## Setup and automation
12
20
 
13
21
  Package: `hirify-cli`; command: `hirify`; Node >=18.
14
22
  If `hirify` is missing, run `npx -y hirify-cli@latest init`: it installs the CLI and this skill,
15
- then signs in. Until then use `npx -y hirify-cli <command>`. Global installs update themselves;
16
- pin an exact version with `HIRIFY_NO_AUTO_UPDATE=1`.
23
+ then signs in. Until then use `npx -y hirify-cli <command>`. Global installs update themselves,
24
+ and this skill with them; pin an exact version with `HIRIFY_NO_AUTO_UPDATE=1`.
17
25
 
18
- To sign in, run `hirify login`; the person confirms access in the browser within five minutes.
26
+ To sign in, run `hirify login`; your user confirms access in the browser within five minutes.
19
27
  It fails at once over SSH, and without a terminal in CI or with no display. `--no-browser` is
20
28
  manual callback login. SSH needs `--callback-port` with a matching SSH local port forward. Local
21
- agents reuse the person's saved sign-in when permitted to access the same configuration directory.
29
+ agents reuse your user's saved sign-in when permitted to access the same configuration directory.
22
30
  CI and separate containers can use `HIRIFY_KEY` or `hirify auth --stdin`; never put a key in a
23
31
  command, log or chat.
24
32
  `hirify auth status --json` identifies the local source. Environment keys override saved access;
25
33
  logout is local and does not unset them. Use `hirify login --force` to replace expired access.
26
34
 
27
- Fetch mutable facts: `filter guide` for filter names/values, `account show --json` for current
28
- allowances and permissions, `--help` for commands, `capabilities show <id> --json` for inputs,
29
- effects and metering. This file supplies working rules, not a snapshot of server policy.
30
-
31
- ## Two rules before anything else
32
-
33
- **Lists are free. Three commands are metered, and they spend different things.**
34
-
35
- - `vacancy read` spends one of the day's vacancy opens. Re-reading a vacancy the same day is free.
36
- - `vacancy reveal` spends 1 reveal, and reveals are scarce. When they run out, reading still works.
37
- - `vacancy apply` counts against a small daily allowance of its own and **cannot be taken back**. It
38
- sends a real application, with the user's name and profile, to a person who will read it.
39
-
40
- **`hirify account show` is the only place these numbers are true.** Read it before spending; never
41
- state what is left from memory or plan a batch on a stale figure.
42
-
43
- **Ask the user before every apply, and before anything that changes their account.** Reading needs
44
- no permission. Sending, saving and configuring do.
45
-
46
- ## Working order
47
-
48
- 1. `hirify account show` before spending; use `feed list` and `feed show <id>` first.
49
- 2. Search when no saved feed fits. Shortlist from cards; missing contacts are normal.
50
- 3. `vacancy read <slug>` to assess fit from the full text rather than a headline.
51
- 4. `vacancy reveal <slug>` only for vacancies that still fit after reading.
52
- 5. Apply only after the person's approval and the rules below.
53
- 6. `vacancy hide`/`company hide` what the user rejected; `hidden list` shows it.
35
+ ## Where current facts live
54
36
 
55
- ## Searching
37
+ Fetch mutable facts instead of remembering them: `filter guide` for filter names, values and how
38
+ a request becomes a filter; `account show --json` for current allowances and permissions;
39
+ `--help` for commands; `capabilities show <id> --json` for inputs, effects and metering.
56
40
 
57
41
  `vacancy search` takes a phrase and server-defined filter options; `--limit` and `--page` control
58
- pagination. Never guess criterion names or values. An empty result can mean a misspelt value.
59
-
60
- 1. Read `hirify filter guide` and build criteria from the user's request or profile.
61
- 2. Run `hirify api call filters.preview --data '{"filters":{...},"mode":"compact","per_page":20}'`.
62
- 3. Inspect cards and `meta.total`; refine and preview again if empty, broad or irrelevant.
63
- 4. Run `hirify vacancy search` with the validated criteria.
64
-
65
- If the guide is unavailable, ask the user about criteria rather than guessing.
66
-
67
- ## Applying
68
-
69
- Two cases, two commands:
70
-
71
- - **Hosted on Hirify** -> `hirify vacancy apply <slug>` sends the application through Hirify.
72
- - **From somewhere else** -> `hirify vacancy reveal <slug>` gives the link or contact, and the
73
- application goes there rather than through Hirify. `vacancy apply` refuses these and says so.
74
- What you do with that destination is between you and your user.
75
-
76
- - **Ask first, every time, and show what you are about to send.** An application cannot be taken
77
- back. Never apply to a list on a single "yes".
78
- - **Never invent the cover letter.** Draft it from what the user actually said about themselves and
79
- show them the draft; if they gave you nothing, ask rather than fill the gap.
80
- - **Never choose the profile** when the user has several: `hirify profile list` lists them. With
81
- exactly one profile it is chosen automatically.
82
- - After a successful apply, say it was sent and stop. Nobody follows up: the recruiter replies where
83
- they choose to, and Hirify does not track it.
84
-
85
- ## Saved searches and delivery
86
-
87
- These commands change the user's account, so get a yes. Creation leaves delivery off: report that,
88
- offer the exact `feed deliver` command, and run it only after a separate yes.
89
-
90
- ```bash
91
- hirify feed create "<name>" --filters '<json>' # criteria: hirify filter guide
92
- hirify feed deliver <id> --telegram | --no-telegram | --webhook <id> | --no-webhook
93
- hirify webhook list
94
- hirify webhook create "<name>" <url>
95
- ```
42
+ pagination. Check a draft with
43
+ `hirify api call filters.preview --data '{"filters":{...},"mode":"compact","per_page":20}'`.
96
44
 
97
- `--filters` uses the site's filter criteria; read `hirify filter guide` first.
98
- Creating a delivery endpoint returns a **secret shown once**: give it to the user immediately to
99
- store, because it signs every delivery and cannot be shown again.
45
+ Creating a delivery endpoint (`hirify webhook create`) returns a **secret shown once**: give it to
46
+ your user immediately to store, because it signs every delivery and cannot be shown again.
100
47
 
101
48
  ## Feedback and failures
102
49
 
103
50
  `hirify feedback send <bug|feature> "<title>" --body "<text>" [--vacancy <slug>]` is free.
104
- Ask first and send the user's words. Report the actual received/queued status and retain the
51
+ Ask first and send your user's words. Report the actual received/queued status and retain the
105
52
  reference; do not promise a reply or a fix. Keep an explicit `--idempotency-key` for deliberate
106
53
  retries when the server supports it.
107
54
 
@@ -109,7 +56,7 @@ Parse `--json` success output, never human text. Use `--error-format=json` for s
109
56
  errors; do not combine it with debug if expecting one object. Exit 0 means success, 1 failure,
110
57
  2 unsupported server manifest, 130/143 interruption. Read the stable error code and message.
111
58
 
112
- - `interaction_required`: ask the person to sign in or configure a key; do not wait on browser login.
59
+ - `interaction_required`: ask your user to sign in or configure a key; do not wait on browser login.
113
60
  - Storage access failure: compare `doctor`, `version`, and `auth status` with the normal terminal.
114
61
  Request permission for the specific command through the agent's approval mechanism. Reading a
115
62
  current token needs read access; renewal needs write access. Do not treat storage errors as an
@@ -118,6 +65,7 @@ errors; do not combine it with debug if expecting one object. Exit 0 means succe
118
65
  - Authentication failure: inspect `auth status`; ask for `login --force` only when renewal requires
119
66
  a new sign-in. Never expose a token.
120
67
  - 403: permission or plan restriction. 429: pace or a named allowance; inspect `account show`.
68
+ - `action_required`: stop and show your user the message; it concerns their account.
121
69
  - Network/TLS/proxy failure: fix connectivity or trust configuration; never disable TLS verification.
122
70
  - `outcome_unknown`: a metered action or mutation may have succeeded. Check before repeating;
123
71
  the CLI does not automatically retry it. Do not blindly repeat apply, reveal or feedback.