bamboohr-cli 1.0.13 → 1.0.15

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.
Files changed (3) hide show
  1. package/README.md +91 -0
  2. package/dist/index.js +43 -12
  3. package/package.json +4 -4
package/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # bamboohr-cli
2
+
3
+ Command-line interface for the [BambooHR API](https://documentation.bamboohr.com/reference). 17 commands across 4 domains — employees, time off, files, and metadata.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g bamboohr-cli
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ Set two environment variables in your shell profile:
14
+
15
+ ```bash
16
+ export BAMBOO_TOKEN="your-api-token" # BambooHR > Settings > API Keys
17
+ export BAMBOO_COMPANY_DOMAIN="mycompany" # the subdomain in mycompany.bamboohr.com
18
+ ```
19
+
20
+ ## Commands
21
+
22
+ All commands output JSON. Add `--pretty` to pretty-print.
23
+
24
+ ### employee
25
+
26
+ | Command | Description |
27
+ |---------|-------------|
28
+ | `bamboohr employee get [id]` | Get employee by ID (defaults to current user) |
29
+ | `bamboohr employee search [query]` | Search employees by name, supervisor, department, location, division |
30
+ | `bamboohr employee directory` | Get the full company directory |
31
+ | `bamboohr employee photo <id>` | Download employee photo (`--size`: original, large, medium, small, xs, tiny) |
32
+ | `bamboohr employee goals <id>` | Get performance goals (`--status`: open, closed, all) |
33
+
34
+ ### timeoff
35
+
36
+ | Command | Description |
37
+ |---------|-------------|
38
+ | `bamboohr timeoff types` | List time off types (`--requestable` to filter) |
39
+ | `bamboohr timeoff create [employeeId]` | Create a time off request (defaults to current user) |
40
+ | `bamboohr timeoff requests` | List requests (`--status`, `--start`, `--end`, `--employee`, `--type`) |
41
+ | `bamboohr timeoff balance [employeeId]` | Estimate time off balance (`--end` to project to future date) |
42
+ | `bamboohr timeoff whos-out` | View who's out today and upcoming |
43
+ | `bamboohr timeoff update-status <requestId>` | Approve, deny, or cancel a request |
44
+
45
+ ### file
46
+
47
+ | Command | Description |
48
+ |---------|-------------|
49
+ | `bamboohr file list` | List all company files and categories |
50
+ | `bamboohr file get <fileId>` | Download a company file |
51
+
52
+ ### meta
53
+
54
+ | Command | Description |
55
+ |---------|-------------|
56
+ | `bamboohr meta fields` | List all available BambooHR fields |
57
+ | `bamboohr meta departments` | List all departments |
58
+ | `bamboohr meta locations` | List all office locations |
59
+ | `bamboohr meta divisions` | List all divisions |
60
+
61
+ ## Pagination
62
+
63
+ List commands accept `--limit` to control page size. Responses include a `nextPage` token — pass it back as `--offset` to fetch the next page. When `nextPage` is `null`, there are no more results.
64
+
65
+ ## Examples
66
+
67
+ ```bash
68
+ # Look up your own employee record
69
+ bamboohr employee get
70
+
71
+ # Find someone by name
72
+ bamboohr employee search "Jane"
73
+
74
+ # Filter by supervisor (use "me" for your direct reports)
75
+ bamboohr employee search --supervisor me
76
+
77
+ # Download a photo
78
+ bamboohr employee photo 123 --output ./photo.jpg --size large
79
+
80
+ # Check who's out this week
81
+ bamboohr timeoff whos-out
82
+
83
+ # Request a day off
84
+ bamboohr timeoff create --start 2026-04-20 --end 2026-04-20 --type-id 83 --amount 1
85
+
86
+ # See pending requests for approval
87
+ bamboohr timeoff requests --status requested --start 2026-01-01 --end 2026-12-31
88
+
89
+ # List available fields for custom queries
90
+ bamboohr meta fields
91
+ ```
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
+ import { readFileSync as readFileSync2 } from "fs";
5
+ import { dirname, join as join2 } from "path";
6
+ import { fileURLToPath } from "url";
4
7
  import { styleText } from "util";
5
8
  import { Command as Command6 } from "commander";
6
9
 
@@ -114,7 +117,7 @@ function stripResponse(obj) {
114
117
  const record = obj;
115
118
  const result = {};
116
119
  for (const [key, value] of Object.entries(record)) {
117
- if (key === "self" || key === "_links" || key === "_expandable" || key === "expand" || key === "avatarUrls" || key === "avatarId" || key === "iconUrl") {
120
+ if (value === null || value === void 0) {
118
121
  continue;
119
122
  }
120
123
  result[key] = stripResponse(value);
@@ -180,8 +183,16 @@ function handleError(err) {
180
183
  `
181
184
  );
182
185
  } else {
183
- process.stderr.write(`${JSON.stringify({ error: message })}
184
- `);
186
+ const responseData = err?.response?.data;
187
+ const detail = responseData && typeof responseData === "object" ? responseData : void 0;
188
+ process.stderr.write(
189
+ `${JSON.stringify({
190
+ error: message,
191
+ ...axiosStatus !== void 0 && { statusCode: axiosStatus },
192
+ ...detail && { detail }
193
+ })}
194
+ `
195
+ );
185
196
  }
186
197
  process.exit(1);
187
198
  }
@@ -218,10 +229,9 @@ Examples:
218
229
 
219
230
  // src/commands/employee/goals.ts
220
231
  import { Option } from "commander";
232
+ var GOAL_FILTERS = ["open", "closed", "all"];
221
233
  function goals(parent) {
222
- parent.command("goals <id>").description("Get employee performance goals").addOption(
223
- new Option("--filter <filter>", "Filter goals by status").choices(["open", "closed", "all"]).default("all")
224
- ).addHelpText(
234
+ parent.command("goals <id>").description("Get employee performance goals").addOption(new Option("--filter <filter>", "Filter goals by status").choices(GOAL_FILTERS).default("all")).addHelpText(
225
235
  "after",
226
236
  `
227
237
  Examples:
@@ -240,10 +250,9 @@ Examples:
240
250
  // src/commands/employee/photo.ts
241
251
  import { writeFileSync as writeFileSync2 } from "fs";
242
252
  import { Option as Option2 } from "commander";
253
+ var PHOTO_SIZES = ["original", "large", "medium", "small", "xs", "tiny"];
243
254
  function photo(parent) {
244
- parent.command("photo <id>").description("Download employee photo").requiredOption("--output <path>", "File path to save photo").addOption(
245
- new Option2("--size <size>", "Photo size").choices(["original", "large", "medium", "small", "xs", "tiny"]).default("medium")
246
- ).addHelpText(
255
+ parent.command("photo <id>").description("Download employee photo").requiredOption("--output <path>", "File path to save photo").addOption(new Option2("--size <size>", "Photo size").choices(PHOTO_SIZES).default("medium")).addHelpText(
247
256
  "after",
248
257
  `
249
258
  Examples:
@@ -451,8 +460,20 @@ Examples:
451
460
 
452
461
  // src/commands/timeoff/create.ts
453
462
  import { Option as Option3 } from "commander";
463
+
464
+ // src/utils/cli.ts
465
+ import { InvalidArgumentError } from "commander";
466
+ function positiveInt(raw) {
467
+ const n = parseInt(raw, 10);
468
+ if (Number.isNaN(n) || n < 1) {
469
+ throw new InvalidArgumentError(`Must be a positive integer (got "${raw}")`);
470
+ }
471
+ return n;
472
+ }
473
+
474
+ // src/commands/timeoff/create.ts
454
475
  function create(parent) {
455
- parent.command("create [employeeId]").description("Create a time off request (defaults to current user)").requiredOption("--start <date>", "Start date (YYYY-MM-DD)").requiredOption("--end <date>", "End date (YYYY-MM-DD)").requiredOption("--type-id <id>", 'Time off type ID (use "timeoff types" to find IDs)', parseInt).requiredOption("--amount <amount>", "Total amount of time off (in days or hours depending on type)", parseFloat).addOption(
476
+ parent.command("create [employeeId]").description("Create a time off request (defaults to current user)").requiredOption("--start <date>", "Start date (YYYY-MM-DD)").requiredOption("--end <date>", "End date (YYYY-MM-DD)").requiredOption("--type-id <id>", 'Time off type ID (use "timeoff types" to find IDs)', positiveInt).requiredOption("--amount <amount>", "Total amount of time off (in days or hours depending on type)", parseFloat).addOption(
456
477
  new Option3("--status <status>", "Request status").choices(["requested", "approved"]).default("requested")
457
478
  ).option("--note <text>", "Note to include with the request").option("--dates <json>", `Per-day amounts as JSON array, e.g. '[{"ymd":"2026-03-21","amount":4}]' for half-day`).addHelpText(
458
479
  "after",
@@ -482,7 +503,7 @@ Examples:
482
503
  // src/commands/timeoff/requests.ts
483
504
  import { Option as Option4 } from "commander";
484
505
  function requests(parent) {
485
- parent.command("requests").description("Get time off requests (defaults to current user)").option("--id <id>", "Specific request ID", parseInt).addOption(new Option4("--action <action>", "Access level filter").choices(["view", "approve"])).option("--employee <id>", "Filter by employee ID").option("--all", "Show all visible requests instead of just current user").option("--start <date>", "Start date (YYYY-MM-DD)").option("--end <date>", "End date (YYYY-MM-DD)").addOption(
506
+ parent.command("requests").description("Get time off requests (defaults to current user)").option("--id <id>", "Specific request ID", positiveInt).addOption(new Option4("--action <action>", "Access level filter").choices(["view", "approve"])).option("--employee <id>", "Filter by employee ID").option("--all", "Show all visible requests instead of just current user").option("--start <date>", "Start date (YYYY-MM-DD)").option("--end <date>", "End date (YYYY-MM-DD)").addOption(
486
507
  new Option4("--status <status>", "Filter by request status").choices([
487
508
  "approved",
488
509
  "denied",
@@ -601,10 +622,20 @@ Examples:
601
622
  }
602
623
 
603
624
  // src/index.ts
625
+ function readPackageVersion() {
626
+ try {
627
+ const here = dirname(fileURLToPath(import.meta.url));
628
+ const pkgPath = join2(here, "..", "package.json");
629
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
630
+ return pkg.version ?? "0.0.0";
631
+ } catch {
632
+ return "0.0.0";
633
+ }
634
+ }
604
635
  var DIM = "\x1B[2m";
605
636
  var RESET = "\x1B[0m";
606
637
  var program = new Command6();
607
- program.name("bamboohr").description("BambooHR CLI").version("1.0.0").configureHelp({
638
+ program.name("bamboohr").description("BambooHR CLI").version(readPackageVersion()).configureHelp({
608
639
  styleTitle: (str) => styleText("bold", str),
609
640
  styleUsage: (str) => styleText("dim", str),
610
641
  styleCommandDescription: (str) => styleText("dim", str),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bamboohr-cli",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "publish": true,
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,7 +12,7 @@
12
12
  ],
13
13
  "dependencies": {
14
14
  "commander": "^13.1.0",
15
- "bamboohr-client": "1.0.20"
15
+ "bamboohr-client": "1.0.22"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "24.10.4",
@@ -22,9 +22,9 @@
22
22
  "tsx": "^4.19.2",
23
23
  "typescript": "^5.7.2",
24
24
  "vitest": "^4.0.16",
25
- "config-typescript": "0.0.0",
25
+ "cli-utils": "1.0.0",
26
26
  "config-eslint": "0.0.0",
27
- "cli-utils": "1.0.0"
27
+ "config-typescript": "0.0.0"
28
28
  },
29
29
  "engines": {
30
30
  "node": ">=22.0.0"