@bli-cockpit/cli 0.2.160 → 0.2.161

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.
@@ -55,6 +55,8 @@ async function planCareersRequest(command) {
55
55
  // BLI-12183: `{}` sends the second-chance email now; `--record-sent-at`
56
56
  // records one sent outside Tower and sends nothing.
57
57
  case 'followup': return { path: `${application}/followup`, method: 'POST', body: command.recordSentAt ? { record_sent_at: command.recordSentAt } : {} };
58
+ // BLI-12198: `--at` books or reschedules an interview Edward booked himself; `--clear` undoes it.
59
+ case 'interview': return { path: `${application}/interview`, method: 'POST', body: command.clear ? { clear: true } : { at: command.interviewAt } };
58
60
  case 'grade': return { path: `${application}/grade`, method: 'POST', body: { now: command.now === true } };
59
61
  case 'decide': return { path: `${application}/decide`, method: 'POST', body: { decision: command.decision } };
60
62
  // `resume` never arrives here: `runCareers` hands it to `careers-resume.ts`
@@ -1,14 +1,14 @@
1
1
  import { optionalNonEmpty, optionalUrl, parseNamedArgs } from './local-arg-values.js';
2
- const ACTIONS = ['list', 'show', 'rescreen', 'invite', 'decide', 'followup', 'grade', 'resume', 'takehome-show', 'takehome-set', 'settings-show', 'settings-set'];
2
+ const ACTIONS = ['list', 'show', 'rescreen', 'invite', 'decide', 'followup', 'interview', 'grade', 'resume', 'takehome-show', 'takehome-set', 'settings-show', 'settings-set'];
3
3
  /** Typed word → the one action it means. The left side is what a person reads on the button. */
4
4
  const ALSO_SPELLED = { 'send-takehome': 'invite' };
5
5
  const DECISIONS = ['passed_on', 'archived', 'reopen'];
6
6
  /** Verbs that name an application id, a role slug, or nothing at all. */
7
- const NEEDS_ID = ['show', 'rescreen', 'invite', 'decide', 'followup', 'grade', 'resume'];
7
+ const NEEDS_ID = ['show', 'rescreen', 'invite', 'decide', 'followup', 'interview', 'grade', 'resume'];
8
8
  const NEEDS_ROLE = ['takehome-show', 'takehome-set'];
9
9
  export function parseCareersArgs(args) {
10
- const valueFlags = ['--role', '--min-score', '--since', '--home', '--dashboard-url', '--decision', '--stage', '--subject', '--body-file', '--link', '--pass-score', '--account', '--auto-invite-min-score', '--attention-min-score', '--enabled', '--booking-link', '--grader-model', '--out', '--record-sent-at', '--followup-enabled', '--followup-after-days', '--takehome-window-hours'];
11
- const values = parseNamedArgs(args, { allowedFlags: [...valueFlags, '--json', '--now', '--force'], valueFlags });
10
+ const valueFlags = ['--role', '--min-score', '--since', '--home', '--dashboard-url', '--decision', '--stage', '--subject', '--body-file', '--link', '--pass-score', '--account', '--auto-invite-min-score', '--attention-min-score', '--enabled', '--booking-link', '--grader-model', '--out', '--record-sent-at', '--at', '--followup-enabled', '--followup-after-days', '--takehome-window-hours'];
11
+ const values = parseNamedArgs(args, { allowedFlags: [...valueFlags, '--json', '--now', '--force', '--clear'], valueFlags });
12
12
  const positionals = [...values.positionals];
13
13
  // `takehome show` and `settings set` are two words a person types and one
14
14
  // action everything downstream reads.
@@ -56,6 +56,21 @@ export function parseCareersArgs(args) {
56
56
  throw new Error('--record-sent-at belongs to `cockpit careers followup <id>`.');
57
57
  if (recordSentAt !== undefined && !Number.isFinite(Date.parse(recordSentAt)))
58
58
  throw new Error('--record-sent-at must be an ISO date-time, e.g. 2026-10-03T08:00:00Z.');
59
+ // BLI-12198: an interview Edward booked himself. Exactly one of `--at <iso>`
60
+ // (book or reschedule) or `--clear`, refused here as well as on the door.
61
+ const interviewAt = optionalNonEmpty(values.flags.get('--at'));
62
+ const clear = values.booleans.has('--clear');
63
+ if (interviewAt !== undefined && verb !== 'interview')
64
+ throw new Error('--at belongs to `cockpit careers interview <id>`.');
65
+ if (clear && verb !== 'interview')
66
+ throw new Error('--clear belongs to `cockpit careers interview <id>`.');
67
+ if (verb === 'interview') {
68
+ if ((interviewAt === undefined) === !clear)
69
+ throw new Error('careers interview needs exactly one of --at <iso with offset> or --clear.');
70
+ if (interviewAt !== undefined && (!/(Z|[+-]\d{2}:\d{2})$/i.test(interviewAt) || !Number.isFinite(Date.parse(interviewAt)))) {
71
+ throw new Error('--at must be an ISO date-time with an offset, e.g. 2026-10-05T10:00:00-07:00.');
72
+ }
73
+ }
59
74
  const followupEnabledRaw = optionalNonEmpty(values.flags.get('--followup-enabled'));
60
75
  if (followupEnabledRaw !== undefined && followupEnabledRaw !== 'true' && followupEnabledRaw !== 'false')
61
76
  throw new Error('--followup-enabled must be true or false.');
@@ -84,6 +99,7 @@ export function parseCareersArgs(args) {
84
99
  graderModel: optionalNonEmpty(values.flags.get('--grader-model')),
85
100
  now: values.booleans.has('--now'),
86
101
  outPath, force,
102
+ interviewAt: interviewAt ? new Date(interviewAt).toISOString() : undefined, clear: clear || undefined,
87
103
  recordSentAt: recordSentAt ? new Date(recordSentAt).toISOString() : undefined,
88
104
  followupEnabled: followupEnabledRaw === undefined ? undefined : followupEnabledRaw === 'true',
89
105
  followupAfterDays, takehomeWindowHours,
@@ -25,13 +25,14 @@ export const TOWER_COMMAND_HELP = [
25
25
  [
26
26
  "careers",
27
27
  [
28
- "Usage: cockpit careers [list|show <id>|rescreen <id>|send-takehome <id>|decide <id> --decision <d>|followup <id>|grade <id>|resume <id>|takehome show <role>|takehome set <role>|settings show|settings set] [flags]",
28
+ "Usage: cockpit careers [list|show <id>|rescreen <id>|send-takehome <id>|decide <id> --decision <d>|followup <id>|interview <id>|grade <id>|resume <id>|takehome show <role>|takehome set <role>|settings show|settings set] [flags]",
29
29
  "",
30
30
  "Super-admin application review and pipeline. Lists at most 100 matches with total and has_more; use filters to narrow.",
31
- "list [--role <slug>] [--min-score <n>] [--since <date>] [--stage <screened|invited|submitted|graded|booking_sent|passed_on|archived>] — the board, newest first, with the two thresholds in force.",
31
+ "list [--role <slug>] [--min-score <n>] [--since <date>] [--stage <screened|invited|submitted|graded|booking_sent|interview|passed_on|archived>] — the board, newest first, with the two thresholds in force.",
32
32
  "send-takehome <id> (also `invite <id>`): sends that role's take-home from the configured mailbox, whatever the screening scored. The same act as Tower's Send take-home button, and only a row still at `screened`.",
33
33
  "decide <id> --decision <passed_on|archived|reopen> — your own call on a row; `reopen` puts it back to `screened`.",
34
34
  "followup <id> [--record-sent-at <iso>]: sends the one second-chance email now, in the take-home thread (the Send follow-up button), or with --record-sent-at records one you sent by hand and sends nothing (Already followed up). A second follow-up is refused. list and show carry each row's deadline label: overdue, second_chance, no_submission or incomplete.",
35
+ "interview <id> --at <iso with offset> [--clear]: records an interview you booked yourself (the Interview booked control). The row moves to the interview stage and its chip reads Interview plus the time; booking again reschedules; --clear (instead of --at) returns the row to where it stood. An archived row is refused. list --stage interview lists them.",
35
36
  "settings set also takes --followup-enabled <true|false> (default false), --followup-after-days <1-60> (default 7) and --takehome-window-hours <1-720> (default 72).",
36
37
  "grade <id> [--now]: puts one submission back in the take-home grader's queue. The grader runs on Railway every 5 minutes; --now says so rather than pretending the run happened here.",
37
38
  "resume <id> [--out <path>] [--force]: the candidate's resume, the same file the drawer's Open resume button shows. Bare, it prints a signed link that dies in 60 seconds; --out downloads the file to that path and refuses to overwrite one already there unless you pass --force.",
@@ -127,7 +127,7 @@ export function localCommandHelp(command) {
127
127
  " cockpit project [list] [--archived] [--dashboard-url <url>] [--json]",
128
128
  ` cockpit search "<words>" [--kind ${SEARCH_KINDS.join(",")}] [--limit <n>] [--dashboard-url <url>] [--json]`,
129
129
  " cockpit release [--dry-run] [--skip-checks] [--no-floor] [--tag <tag>] [--access <public|restricted>] [--otp <code>]",
130
- " cockpit careers [list|show <id>|rescreen <id>|send-takehome <id>|decide <id> --decision <passed_on|archived|reopen>|followup <id> [--record-sent-at <iso>]|grade <id> [--now]|resume <id> [--out <path>]|takehome show <role>|takehome set <role> --body-file <path>|settings show|settings set --enabled <true|false> --followup-enabled <true|false> --followup-after-days <n> --takehome-window-hours <n>] [--role <slug>] [--min-score <n>] [--since <date>] [--stage <s>] [--force] [--json]",
130
+ " cockpit careers [list|show <id>|rescreen <id>|send-takehome <id>|decide <id> --decision <passed_on|archived|reopen>|followup <id> [--record-sent-at <iso>]|interview <id> <--at <iso>|--clear>|grade <id> [--now]|resume <id> [--out <path>]|takehome show <role>|takehome set <role> --body-file <path>|settings show|settings set --enabled <true|false> --followup-enabled <true|false> --followup-after-days <n> --takehome-window-hours <n>] [--role <slug>] [--min-score <n>] [--since <date>] [--stage <s>] [--force] [--json]",
131
131
  " cockpit usage sessions [--task <BLI-NNNN> | --subject <name>] [--repo <label>] [--since <window>] [--json]",
132
132
  " cockpit usage sessions --session <id> [--from-window <n>] [--json]",
133
133
  " --by-topic groups work types; --by-subject groups open subjects from session summaries.",
@@ -21,7 +21,7 @@ export async function runCockpitCli(argv, io) {
21
21
  }
22
22
 
23
23
  if (command === "--version" || command === "-V" || command === "version") {
24
- writeLine(io?.stdout ?? process.stdout, "0.2.160");
24
+ writeLine(io?.stdout ?? process.stdout, "0.2.161");
25
25
  return 0;
26
26
  }
27
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/cli",
3
- "version": "0.2.160",
3
+ "version": "0.2.161",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,8 +27,13 @@
27
27
  "test": "node dist/cli.js --help && node ../../scripts/assert-public-cli-routing.mjs && node ../../scripts/assert-public-cli-verb-help.mjs && node ../../scripts/assert-public-cli-surface-doors.mjs && node ../../scripts/assert-public-cli-runtime-files.mjs && node ../../scripts/assert-public-cli-exit-contract.mjs && node ../../scripts/assert-public-cli-no-fleet-posts.mjs && node ../../scripts/assert-public-cli-doctor-json.mjs && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
28
28
  },
29
29
  "dependencies": {
30
- "@bli-cockpit/memory-mcp": "0.1.44",
31
- "@bli-cockpit/mcp": "0.1.78",
32
- "@bli-cockpit/telemetry-core": "0.1.56"
30
+ "@bli-cockpit/memory-mcp": "0.1.45",
31
+ "@bli-cockpit/mcp": "0.1.79",
32
+ "@bli-cockpit/telemetry-core": "0.1.57"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/BLI-INC/bli-cockpit.git",
37
+ "directory": "packages/cockpit-cli"
33
38
  }
34
39
  }