hirify-cli 0.4.3 → 0.4.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/hirify.js +84 -3
- package/package.json +1 -1
- package/skills/hirify/SKILL.md +3 -2
- package/skills/hirify/reference.md +4 -4
package/bin/hirify.js
CHANGED
|
@@ -271,14 +271,73 @@ const options = (args) => {
|
|
|
271
271
|
const count = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null)
|
|
272
272
|
|
|
273
273
|
const out = (data, text) => {
|
|
274
|
-
if (process.argv.includes('--json'))
|
|
275
|
-
|
|
274
|
+
if (process.argv.includes('--json')) {
|
|
275
|
+
// --json hands over the whole envelope, so a `meta.notice` on it travels structured and
|
|
276
|
+
// untouched. The human notice below is only for text mode.
|
|
277
|
+
console.log(JSON.stringify(data, null, 2))
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
text()
|
|
281
|
+
printNotice(data?.meta?.notice)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Print a structured notice the server attached to a successful answer (`meta.notice`). The
|
|
286
|
+
* call still succeeded, so this comes after the useful result, not in place of it. The CLI
|
|
287
|
+
* shows the text the server wrote and reads nothing into the policy behind it: it knows the
|
|
288
|
+
* notice object, not what produced it. No notice, or one with no message, prints nothing.
|
|
289
|
+
*/
|
|
290
|
+
function printNotice(notice) {
|
|
291
|
+
const message = typeof notice?.message === 'string' ? notice.message.trim() : ''
|
|
292
|
+
if (!message) return
|
|
293
|
+
console.log(`\nNotice: ${message}`)
|
|
276
294
|
}
|
|
277
295
|
|
|
278
296
|
// Text our own server wrote for the agent to read: application rules, address rejections.
|
|
279
297
|
// Passing it through beats paraphrasing, because paraphrasing drifts from the real rule.
|
|
280
298
|
const serverMessage = (body) => (typeof body?.message === 'string' && body.message ? body.message : null)
|
|
281
299
|
|
|
300
|
+
/**
|
|
301
|
+
* The one-line report for a policy restriction: a canonical 403 that names itself
|
|
302
|
+
* `access_restricted`. It is kept apart from the quota and rate-limit walls on purpose,
|
|
303
|
+
* because those clear on their own and this one does not. The CLI states what the server
|
|
304
|
+
* sent - the message it wrote, when the restriction lifts, where to ask for a review - and
|
|
305
|
+
* adds no rule of its own about why it was applied.
|
|
306
|
+
*/
|
|
307
|
+
function accessRestrictedMessage(body) {
|
|
308
|
+
const err = (body && typeof body.error === 'object' && body.error) || {}
|
|
309
|
+
const line = (v) => (typeof v === 'string' && v.trim() ? v.trim() : null)
|
|
310
|
+
const parts = [line(err.message) || line(body?.message) || 'access on this account is restricted (403).']
|
|
311
|
+
const until = line(err.restricted_until)
|
|
312
|
+
if (until) parts.push(`It stays in place until ${until}.`)
|
|
313
|
+
const appeal = line(err.appeal_url)
|
|
314
|
+
if (appeal) parts.push(`To ask us to look at it again: ${appeal}`)
|
|
315
|
+
return parts.join('\n ')
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* The report for a blocking notice: a canonical 409 that names itself `action_required`.
|
|
320
|
+
* Unlike a `meta.notice` on a success, this one stands in place of the answer - the server is
|
|
321
|
+
* holding the result until the person confirms - so the CLI shows the text the server wrote and
|
|
322
|
+
* the one command that clears it, and reads nothing into the policy behind it. That command is
|
|
323
|
+
* the acknowledgement capability the server lists in its manifest, reached the same way as any
|
|
324
|
+
* other, so no route to it is written here. It carries the notice's own id and its first
|
|
325
|
+
* advertised action, both as the server sent them, so the printed line runs as it stands.
|
|
326
|
+
*/
|
|
327
|
+
function actionRequiredMessage(body) {
|
|
328
|
+
const notice = (body && typeof body.error === 'object' && body.error && typeof body.error.notice === 'object' && body.error.notice) || {}
|
|
329
|
+
const line = (v) => (typeof v === 'string' && v.trim() ? v.trim() : null)
|
|
330
|
+
const parts = [line(notice.message) || 'this needs your confirmation before it can go ahead (409).']
|
|
331
|
+
|
|
332
|
+
const id = line(notice.id)
|
|
333
|
+
const action = Array.isArray(notice.actions) ? notice.actions.map(line).find(Boolean) : null
|
|
334
|
+
const call = 'hirify api call security.notices.ack'
|
|
335
|
+
parts.push(id && action
|
|
336
|
+
? `To continue, acknowledge this notice: ${call} --data '${JSON.stringify({ id, action })}'`
|
|
337
|
+
: `To continue, acknowledge this notice: ${call}`)
|
|
338
|
+
return parts.join('\n ')
|
|
339
|
+
}
|
|
340
|
+
|
|
282
341
|
const b64url = (buf) => buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
|
|
283
342
|
const now = () => Math.floor(Date.now() / 1000)
|
|
284
343
|
|
|
@@ -384,6 +443,27 @@ async function api(path, { method = 'GET', payload = null, allow = [], raw = fal
|
|
|
384
443
|
return { status: res.status, body, text }
|
|
385
444
|
}
|
|
386
445
|
|
|
446
|
+
// A policy restriction is a canonical 403 that names itself `access_restricted`. It is not a
|
|
447
|
+
// used-up allowance and not a rate limit, and retrying cannot lift it: the CLI reports it once
|
|
448
|
+
// and never retries. Checked ahead of `allow` so a caller that reads its own 403s still cannot
|
|
449
|
+
// take this one for an ordinary refusal. The clone leaves the body readable for the paths below.
|
|
450
|
+
if (res.status === 403) {
|
|
451
|
+
const restriction = await res.clone().json().catch(() => null)
|
|
452
|
+
if (restriction?.error?.code === 'access_restricted') die(accessRestrictedMessage(restriction))
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// A blocking notice is a canonical 409 that names itself `action_required`. Unlike a
|
|
456
|
+
// `meta.notice` on a success, it stands in place of the answer: the server is holding the
|
|
457
|
+
// result until the person acknowledges the notice, so the CLI reports it, exits unsuccessfully,
|
|
458
|
+
// and never retries - retrying cannot clear it. Checked ahead of `allow`, like the restriction
|
|
459
|
+
// above, so a caller that reads its own 409s cannot take this one for an ordinary conflict; a
|
|
460
|
+
// 409 without this code is left to the handling below, unchanged. The clone leaves the body
|
|
461
|
+
// readable for those paths.
|
|
462
|
+
if (res.status === 409) {
|
|
463
|
+
const blocked = await res.clone().json().catch(() => null)
|
|
464
|
+
if (blocked?.error?.code === 'action_required') die(actionRequiredMessage(blocked))
|
|
465
|
+
}
|
|
466
|
+
|
|
387
467
|
if (allow.includes(res.status)) {
|
|
388
468
|
return {
|
|
389
469
|
status: res.status,
|
|
@@ -1516,7 +1596,8 @@ Where to apply
|
|
|
1516
1596
|
|
|
1517
1597
|
Applying
|
|
1518
1598
|
Most vacancies here came from somewhere else: company pages, Telegram channels, other
|
|
1519
|
-
boards. For those, reveal brings back the address and the
|
|
1599
|
+
boards. For those, reveal brings back the address, and the application is made there rather
|
|
1600
|
+
than through Hirify.
|
|
1520
1601
|
Vacancies hosted on Hirify can be applied to from here:
|
|
1521
1602
|
|
|
1522
1603
|
hirify profile list
|
package/package.json
CHANGED
package/skills/hirify/SKILL.md
CHANGED
|
@@ -75,8 +75,9 @@ the guide, it says so; then ask the user what to filter on.
|
|
|
75
75
|
Two cases, two commands:
|
|
76
76
|
|
|
77
77
|
- **Hosted on Hirify** -> `hirify vacancy apply <slug>` sends the application through Hirify.
|
|
78
|
-
- **From somewhere else** -> `hirify vacancy reveal <slug>` gives the link or contact, and
|
|
79
|
-
|
|
78
|
+
- **From somewhere else** -> `hirify vacancy reveal <slug>` gives the link or contact, and the
|
|
79
|
+
application goes there rather than through Hirify. `vacancy apply` refuses these and says so.
|
|
80
|
+
What you do with that destination is between you and your user.
|
|
80
81
|
|
|
81
82
|
- **Ask first, every time, and show what you are about to send.** An application cannot be taken
|
|
82
83
|
back. Never apply to a list on a single "yes".
|
|
@@ -148,8 +148,8 @@ The last line before that says which way to apply:
|
|
|
148
148
|
|
|
149
149
|
- `Apply on Hirify: hirify vacancy apply <slug>` - the vacancy is hosted here.
|
|
150
150
|
- `Where to apply: hirify vacancy reveal <slug> (uses 1 reveal)` - it came from elsewhere, so the
|
|
151
|
-
|
|
152
|
-
`vacancy apply`.
|
|
151
|
+
application goes to that destination rather than through Hirify. Take this from `vacancy read`
|
|
152
|
+
rather than finding out from a refusal on `vacancy apply`.
|
|
153
153
|
|
|
154
154
|
Contacts, the apply destination and where the vacancy came from are never in this answer. That is
|
|
155
155
|
what `vacancy reveal` is for.
|
|
@@ -199,8 +199,8 @@ Answers:
|
|
|
199
199
|
where they choose to.
|
|
200
200
|
- `there is no vacancy with that slug.`
|
|
201
201
|
- a refusal in the server's own words, which covers archived, flagged, someone else's profile, and
|
|
202
|
-
**vacancies not hosted on Hirify**. For those, use `vacancy reveal` and
|
|
203
|
-
|
|
202
|
+
**vacancies not hosted on Hirify**. For those, use `vacancy reveal` and apply at the destination it
|
|
203
|
+
returns.
|
|
204
204
|
- `the application could not be sent right now.` means our side failed, not the user's data.
|
|
205
205
|
|
|
206
206
|
## Saved searches and delivery
|