bamboohr-cli 1.0.17 → 2.0.0-gc22cadf.3

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 (2) hide show
  1. package/dist/index.js +287 -147
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1,12 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/index.ts
4
- import { readFileSync as readFileSync2 } from "fs";
5
- import { dirname, join as join2 } from "path";
6
- import { fileURLToPath } from "url";
7
- import { styleText } from "util";
8
- import { Command as Command6 } from "commander";
9
-
10
3
  // ../../cli-utils/dist/cache.js
11
4
  import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
12
5
  import { homedir } from "os";
@@ -51,6 +44,224 @@ async function cacheGetOrFetch(options, key, fetcher) {
51
44
  return data;
52
45
  }
53
46
 
47
+ // ../../cli-utils/dist/bootstrap.js
48
+ import { readFileSync as readFileSync2 } from "fs";
49
+ import { dirname, join as join2 } from "path";
50
+ import { fileURLToPath } from "url";
51
+ function readPackageVersion(importMetaUrl) {
52
+ try {
53
+ const here = dirname(fileURLToPath(importMetaUrl));
54
+ const pkg = JSON.parse(readFileSync2(join2(here, "..", "package.json"), "utf-8"));
55
+ return pkg.version ?? "0.0.0";
56
+ } catch {
57
+ return "0.0.0";
58
+ }
59
+ }
60
+
61
+ // ../../cli-utils/dist/validators.js
62
+ import { InvalidArgumentError } from "commander";
63
+ function positiveInt(raw) {
64
+ const n = parseInt(raw, 10);
65
+ if (Number.isNaN(n) || n < 1) {
66
+ throw new InvalidArgumentError(`Must be a positive integer (got "${raw}")`);
67
+ }
68
+ return n;
69
+ }
70
+
71
+ // ../../cli-utils/dist/errors.js
72
+ import { CommanderError } from "commander";
73
+ var EXIT = {
74
+ SUCCESS: 0,
75
+ GENERIC: 1,
76
+ USAGE: 2,
77
+ NOT_FOUND: 3,
78
+ FORBIDDEN: 4,
79
+ CONFLICT: 5,
80
+ AUTH: 6
81
+ };
82
+ var TYPE_EXIT = {
83
+ usage: EXIT.USAGE,
84
+ not_found: EXIT.NOT_FOUND,
85
+ forbidden: EXIT.FORBIDDEN,
86
+ conflict: EXIT.CONFLICT,
87
+ auth: EXIT.AUTH,
88
+ rate_limited: EXIT.GENERIC,
89
+ server: EXIT.GENERIC,
90
+ network: EXIT.GENERIC,
91
+ unknown: EXIT.GENERIC
92
+ };
93
+ var NETWORK_CODES = ["ENOTFOUND", "ECONNREFUSED", "ECONNRESET", "ETIMEDOUT"];
94
+ function httpStatus(err) {
95
+ const e = err;
96
+ return e?.response?.status ?? e?.statusCode;
97
+ }
98
+ function responseDetail(err) {
99
+ const e = err;
100
+ const data = e?.response?.data;
101
+ if (data && typeof data === "object")
102
+ return data;
103
+ const body = e?.body;
104
+ if (typeof body === "string") {
105
+ try {
106
+ const parsed = JSON.parse(body);
107
+ return parsed.error ?? parsed;
108
+ } catch {
109
+ return void 0;
110
+ }
111
+ }
112
+ if (body && typeof body === "object") {
113
+ return body.error ?? body;
114
+ }
115
+ return void 0;
116
+ }
117
+ function errorCode(err) {
118
+ return err?.code;
119
+ }
120
+ function normalize(err, opts) {
121
+ const message = err instanceof Error ? err.message : String(err);
122
+ if (err instanceof CommanderError) {
123
+ return {
124
+ type: "usage",
125
+ message: message || "Invalid command usage",
126
+ recovery: "Check the command syntax and flags; run the command with --help.",
127
+ retryable: false
128
+ };
129
+ }
130
+ const status = httpStatus(err);
131
+ const detail = responseDetail(err);
132
+ if (status !== void 0) {
133
+ switch (status) {
134
+ case 400:
135
+ return {
136
+ type: "usage",
137
+ status,
138
+ message: "Bad request (HTTP 400)",
139
+ recovery: "Check parameter values (ids, keys, query syntax) against the API.",
140
+ retryable: false,
141
+ detail
142
+ };
143
+ case 401:
144
+ return {
145
+ type: "auth",
146
+ status,
147
+ message: "Authentication failed (HTTP 401)",
148
+ recovery: opts.authRecovery,
149
+ retryable: false
150
+ };
151
+ case 403:
152
+ return {
153
+ type: "forbidden",
154
+ status,
155
+ message: "Forbidden (HTTP 403)",
156
+ recovery: `Your ${opts.service} account lacks permission for this operation; check token scope and resource permissions.`,
157
+ retryable: false,
158
+ detail
159
+ };
160
+ case 404:
161
+ return {
162
+ type: "not_found",
163
+ status,
164
+ message: "Not found (HTTP 404)",
165
+ recovery: "Verify the id/key exists and that you have access to it.",
166
+ retryable: false,
167
+ detail
168
+ };
169
+ case 409:
170
+ return {
171
+ type: "conflict",
172
+ status,
173
+ message: "Conflict (HTTP 409)",
174
+ recovery: "The resource changed or already exists; re-fetch current state and retry.",
175
+ retryable: false,
176
+ detail
177
+ };
178
+ case 429:
179
+ return {
180
+ type: "rate_limited",
181
+ status,
182
+ message: "Rate limited (HTTP 429)",
183
+ recovery: "Wait and retry the request.",
184
+ retryable: true
185
+ };
186
+ }
187
+ if (status >= 500) {
188
+ return {
189
+ type: "server",
190
+ status,
191
+ message: `Server error (HTTP ${status})`,
192
+ recovery: `${opts.service} returned an internal error; retry shortly.`,
193
+ retryable: true,
194
+ detail
195
+ };
196
+ }
197
+ return {
198
+ type: "unknown",
199
+ status,
200
+ message: `${opts.service} error (HTTP ${status}): ${message}`,
201
+ recovery: "Inspect the detail field for the API response.",
202
+ retryable: false,
203
+ detail
204
+ };
205
+ }
206
+ const code = errorCode(err);
207
+ if (code && NETWORK_CODES.includes(code) || NETWORK_CODES.some((c) => message.includes(c))) {
208
+ return {
209
+ type: "network",
210
+ message: `Cannot connect to ${opts.service}: ${message}`,
211
+ recovery: opts.networkRecovery ?? "Verify the *_URL is correct, the server is reachable, and you are on the VPN if required.",
212
+ retryable: true
213
+ };
214
+ }
215
+ return { type: "unknown", message, recovery: "Unexpected error; inspect the message.", retryable: false };
216
+ }
217
+ function classifyError(err, opts) {
218
+ const base = normalize(err, opts);
219
+ const n = opts.adapt ? opts.adapt(base, err) : base;
220
+ return {
221
+ envelope: {
222
+ error: {
223
+ type: n.type,
224
+ message: n.message,
225
+ recovery: n.recovery,
226
+ retryable: n.retryable,
227
+ ...n.detail !== void 0 ? { detail: n.detail } : {}
228
+ }
229
+ },
230
+ exitCode: TYPE_EXIT[n.type]
231
+ };
232
+ }
233
+ function createErrorHandler(opts) {
234
+ return (err) => {
235
+ const { envelope, exitCode } = classifyError(err, opts);
236
+ process.stderr.write(`${JSON.stringify(envelope)}
237
+ `);
238
+ return process.exit(exitCode);
239
+ };
240
+ }
241
+ function isCleanCommanderExit(err) {
242
+ return err instanceof CommanderError && (err.exitCode === 0 || err.code === "commander.helpDisplayed" || err.code === "commander.help" || err.code === "commander.version");
243
+ }
244
+ function routeErrors(cmd) {
245
+ cmd.exitOverride();
246
+ cmd.configureOutput({ writeErr: () => void 0 });
247
+ cmd.commands.forEach(routeErrors);
248
+ }
249
+ async function runCli(program, opts) {
250
+ routeErrors(program);
251
+ try {
252
+ await program.parseAsync();
253
+ } catch (err) {
254
+ if (isCleanCommanderExit(err)) {
255
+ process.exit(err.exitCode ?? 0);
256
+ }
257
+ createErrorHandler(opts)(err);
258
+ }
259
+ }
260
+
261
+ // src/program.ts
262
+ import { styleText } from "util";
263
+ import { Command as Command6 } from "commander";
264
+
54
265
  // src/utils/cached.ts
55
266
  var CACHE_NAME = "bamboohr";
56
267
  var TWELVE_HOURS = 432e5;
@@ -115,68 +326,6 @@ function output(data) {
115
326
  process.stdout.write(`${JSON.stringify(data, null, prettyPrint ? 2 : void 0)}
116
327
  `);
117
328
  }
118
- function handleError(err) {
119
- const message = err instanceof Error ? err.message : String(err);
120
- const axiosStatus = err?.response?.status;
121
- if (axiosStatus === 400) {
122
- const responseData = err?.response?.data;
123
- const apiErrors = responseData && typeof responseData === "object" ? responseData : void 0;
124
- process.stderr.write(
125
- `${JSON.stringify({
126
- error: "Bad request (HTTP 400)",
127
- detail: apiErrors ?? message,
128
- hint: "Check that all parameters are valid (employee IDs, field names, date formats, etc.)."
129
- })}
130
- `
131
- );
132
- } else if (axiosStatus === 401) {
133
- process.stderr.write(
134
- `${JSON.stringify({
135
- error: "Authentication failed (HTTP 401)",
136
- ...getCredentialInfo()
137
- })}
138
- `
139
- );
140
- } else if (axiosStatus === 403) {
141
- const responseData = err?.response?.data;
142
- process.stderr.write(
143
- `${JSON.stringify({
144
- error: "Forbidden (HTTP 403)",
145
- detail: responseData && typeof responseData === "object" ? responseData : message,
146
- hint: "Your account does not have permission for this operation. Check your BambooHR access level."
147
- })}
148
- `
149
- );
150
- } else if (axiosStatus === 404) {
151
- process.stderr.write(
152
- `${JSON.stringify({
153
- error: "Not found (HTTP 404)",
154
- hint: "Resource not found. Note: BambooHR also returns 404 for invalid credentials \u2014 verify that BAMBOO_TOKEN and BAMBOO_COMPANY_DOMAIN are correct."
155
- })}
156
- `
157
- );
158
- } else if (message.includes("ENOTFOUND") || message.includes("ECONNREFUSED") || message.includes("ECONNRESET")) {
159
- process.stderr.write(
160
- `${JSON.stringify({
161
- error: `Cannot connect to BambooHR API: ${message}`,
162
- hint: "Verify that BAMBOO_COMPANY_DOMAIN is correct and you have internet connectivity."
163
- })}
164
- `
165
- );
166
- } else {
167
- const responseData = err?.response?.data;
168
- const detail = responseData && typeof responseData === "object" ? responseData : void 0;
169
- process.stderr.write(
170
- `${JSON.stringify({
171
- error: message,
172
- ...axiosStatus !== void 0 && { statusCode: axiosStatus },
173
- ...detail && { detail }
174
- })}
175
- `
176
- );
177
- }
178
- process.exit(1);
179
- }
180
329
 
181
330
  // src/utils/transformers/base.ts
182
331
  function bamboohrBaseUrl() {
@@ -311,7 +460,7 @@ Examples:
311
460
 
312
461
  // src/commands/employee/get.ts
313
462
  function get(parent) {
314
- parent.command("get [id]").description("Get employee data by ID (default: current user)").option("--fields <fields>", "Comma-separated field names", "firstName,lastName,email,jobTitle").option("--include-future", "Include future-dated values from history fields").option("--with-photos", "Include the employee's signed photo URL and photoUploaded flag").addHelpText(
463
+ parent.command("get [employeeId]").description("Get employee data by ID (default: current user)").option("--fields <fields>", "Comma-separated field names", "firstName,lastName,email,jobTitle").option("--include-future", "Include future-dated values from history fields").option("--with-photos", "Include the employee's signed photo URL and photoUploaded flag").addHelpText(
315
464
  "after",
316
465
  `
317
466
  Examples:
@@ -320,16 +469,18 @@ Examples:
320
469
  $ bamboohr employee get 123 --fields "firstName,lastName,department,hireDate"
321
470
  $ bamboohr employee get 123 --include-future
322
471
  $ bamboohr employee get 123 --with-photos`
323
- ).action(async (id, opts) => {
324
- const client = getClient();
325
- const fields2 = opts.withPhotos ? mergeFields(opts.fields, ["photoUrl", "photoUploaded"]) : opts.fields;
326
- const result = await client.employees.get({
327
- id: id ?? "0",
328
- fields: fields2,
329
- onlyCurrent: !opts.includeFuture
330
- });
331
- output(transformEmployee(result, { withPhotos: opts.withPhotos }));
332
- });
472
+ ).action(
473
+ async (employeeId, opts) => {
474
+ const client = getClient();
475
+ const fields2 = opts.withPhotos ? mergeFields(opts.fields, ["photoUrl", "photoUploaded"]) : opts.fields;
476
+ const result = await client.employees.get({
477
+ id: employeeId ?? "0",
478
+ fields: fields2,
479
+ onlyCurrent: !opts.includeFuture
480
+ });
481
+ output(transformEmployee(result, { withPhotos: opts.withPhotos }));
482
+ }
483
+ );
333
484
  }
334
485
  function mergeFields(existing, extra) {
335
486
  const set = new Set(
@@ -343,16 +494,16 @@ function mergeFields(existing, extra) {
343
494
  import { Option } from "commander";
344
495
  var GOAL_FILTERS = ["open", "closed", "all"];
345
496
  function goals(parent) {
346
- parent.command("goals <id>").description("Get employee performance goals").addOption(new Option("--filter <filter>", "Filter goals by status").choices(GOAL_FILTERS).default("all")).addHelpText(
497
+ parent.command("goals <employeeId>").description("Get employee performance goals").addOption(new Option("--filter <filter>", "Filter goals by status").choices(GOAL_FILTERS).default("all")).addHelpText(
347
498
  "after",
348
499
  `
349
500
  Examples:
350
501
  $ bamboohr employee goals 123
351
502
  $ bamboohr employee goals 123 --filter open`
352
- ).action(async (id, opts) => {
503
+ ).action(async (employeeId, opts) => {
353
504
  const client = getClient();
354
505
  const result = await client.goals.get({
355
- employeeId: id,
506
+ employeeId,
356
507
  filter: opts.filter === "all" ? void 0 : opts.filter
357
508
  });
358
509
  output(transformGoals(result));
@@ -364,16 +515,16 @@ import { writeFileSync as writeFileSync2 } from "fs";
364
515
  import { Option as Option2 } from "commander";
365
516
  var PHOTO_SIZES = ["original", "large", "medium", "small", "xs", "tiny"];
366
517
  function photo(parent) {
367
- 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(
518
+ parent.command("photo <employeeId>").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(
368
519
  "after",
369
520
  `
370
521
  Examples:
371
522
  $ bamboohr employee photo 123 --output ./photo.jpg
372
523
  $ bamboohr employee photo 123 --output ./photo.jpg --size large`
373
- ).action(async (id, opts) => {
524
+ ).action(async (employeeId, opts) => {
374
525
  const client = getClient();
375
526
  const buffer = await client.employees.getPhoto({
376
- employeeId: id,
527
+ employeeId,
377
528
  size: opts.size
378
529
  });
379
530
  writeFileSync2(opts.output, buffer);
@@ -439,8 +590,8 @@ Examples:
439
590
  }
440
591
 
441
592
  // src/commands/employee/index.ts
442
- function registerEmployeeCommands(program2) {
443
- const employee = program2.command("employee").description("Employee operations").addHelpText(
593
+ function registerEmployeeCommands(program) {
594
+ const employee = program.command("employee").description("Employee operations").addHelpText(
444
595
  "after",
445
596
  `
446
597
  Examples:
@@ -484,8 +635,8 @@ function list(parent) {
484
635
  }
485
636
 
486
637
  // src/commands/file/index.ts
487
- function registerFileCommands(program2) {
488
- const file = program2.command("file").description("Company file operations").addHelpText(
638
+ function registerFileCommands(program) {
639
+ const file = program.command("file").description("Company file operations").addHelpText(
489
640
  "after",
490
641
  `
491
642
  Examples:
@@ -537,8 +688,8 @@ function locations(parent) {
537
688
  }
538
689
 
539
690
  // src/commands/meta/index.ts
540
- function registerMetaCommands(program2) {
541
- const meta = program2.command("meta").description("Metadata operations").addHelpText(
691
+ function registerMetaCommands(program) {
692
+ const meta = program.command("meta").description("Metadata operations").addHelpText(
542
693
  "after",
543
694
  `
544
695
  Examples:
@@ -573,18 +724,6 @@ Examples:
573
724
 
574
725
  // src/commands/timeoff/create.ts
575
726
  import { Option as Option3 } from "commander";
576
-
577
- // src/utils/cli.ts
578
- import { InvalidArgumentError } from "commander";
579
- function positiveInt(raw) {
580
- const n = parseInt(raw, 10);
581
- if (Number.isNaN(n) || n < 1) {
582
- throw new InvalidArgumentError(`Must be a positive integer (got "${raw}")`);
583
- }
584
- return n;
585
- }
586
-
587
- // src/commands/timeoff/create.ts
588
727
  function create(parent) {
589
728
  parent.command("create [employeeId]").description("Create a time off request (defaults to current user)").requiredOption("--start-date <date>", "Start date (YYYY-MM-DD)").requiredOption("--end-date <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(
590
729
  new Option3("--status <status>", "Request status").choices(["requested", "approved"]).default("requested")
@@ -618,7 +757,7 @@ Examples:
618
757
  // src/commands/timeoff/requests.ts
619
758
  import { Option as Option4 } from "commander";
620
759
  function requests(parent) {
621
- 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 <date>", "Start date (YYYY-MM-DD)").option("--end-date <date>", "End date (YYYY-MM-DD)").addOption(
760
+ parent.command("requests").description("Get time off requests (defaults to current user)").option("--request-id <id>", "Specific request ID", positiveInt).addOption(new Option4("--action <action>", "Access level filter").choices(["view", "approve"])).option("--employee-id <id>", "Filter by employee ID").option("--all", "Show all visible requests instead of just current user").option("--start-date <date>", "Start date (YYYY-MM-DD)").option("--end-date <date>", "End date (YYYY-MM-DD)").addOption(
622
761
  new Option4("--status <status>", "Filter by request status").choices([
623
762
  "approved",
624
763
  "denied",
@@ -626,32 +765,32 @@ function requests(parent) {
626
765
  "requested",
627
766
  "canceled"
628
767
  ])
629
- ).option("--type <typeId>", "Filter by time off type ID").addHelpText(
768
+ ).option("--type-id <typeId>", "Filter by time off type ID").addHelpText(
630
769
  "after",
631
770
  `
632
771
  Examples:
633
772
  $ bamboohr timeoff requests
634
773
  $ bamboohr timeoff requests --status requested
635
774
  $ bamboohr timeoff requests --all --action approve --status requested
636
- $ bamboohr timeoff requests --employee 123 --start-date 2026-01-01 --end-date 2026-03-31`
775
+ $ bamboohr timeoff requests --employee-id 123 --start-date 2026-01-01 --end-date 2026-03-31`
637
776
  ).action(
638
777
  async (opts) => {
639
778
  const year = (/* @__PURE__ */ new Date()).getFullYear();
640
779
  const start = opts.startDate ?? `${year}-01-01`;
641
780
  const end = opts.endDate ?? `${year}-12-31`;
642
781
  const client = getClient();
643
- let employeeId = opts.employee;
782
+ let employeeId = opts.employeeId;
644
783
  if (!employeeId && !opts.all) {
645
784
  employeeId = await resolveEmployeeId(client);
646
785
  }
647
786
  const result = await client.timeOff.getRequests({
648
- id: opts.id,
787
+ id: opts.requestId,
649
788
  action: opts.action,
650
789
  employeeId,
651
790
  start,
652
791
  end,
653
792
  status: opts.status,
654
- type: opts.type
793
+ type: opts.typeId
655
794
  });
656
795
  output(transformTimeOffRequests(result));
657
796
  }
@@ -715,8 +854,8 @@ Examples:
715
854
  }
716
855
 
717
856
  // src/commands/timeoff/index.ts
718
- function registerTimeoffCommands(program2) {
719
- const timeoff = program2.command("timeoff").description("Time off operations").addHelpText(
857
+ function registerTimeoffCommands(program) {
858
+ const timeoff = program.command("timeoff").description("Time off operations").addHelpText(
720
859
  "after",
721
860
  `
722
861
  Examples:
@@ -736,31 +875,22 @@ Examples:
736
875
  updateStatus(timeoff);
737
876
  }
738
877
 
739
- // src/index.ts
740
- function readPackageVersion() {
741
- try {
742
- const here = dirname(fileURLToPath(import.meta.url));
743
- const pkgPath = join2(here, "..", "package.json");
744
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
745
- return pkg.version ?? "0.0.0";
746
- } catch {
747
- return "0.0.0";
748
- }
749
- }
878
+ // src/program.ts
750
879
  var DIM = "\x1B[2m";
751
880
  var RESET = "\x1B[0m";
752
- var program = new Command6();
753
- program.name("bamboohr").description("BambooHR CLI").version(readPackageVersion()).configureHelp({
754
- styleTitle: (str) => styleText("bold", str),
755
- styleUsage: (str) => styleText("dim", str),
756
- styleCommandDescription: (str) => styleText("dim", str),
757
- styleOptionDescription: (str) => styleText("dim", str),
758
- styleSubcommandDescription: (str) => styleText("dim", str)
759
- }).addHelpText("beforeAll", `
881
+ function buildProgram() {
882
+ const program = new Command6();
883
+ program.name("bamboohr").description("BambooHR CLI").version(readPackageVersion(import.meta.url)).configureHelp({
884
+ styleTitle: (str) => styleText("bold", str),
885
+ styleUsage: (str) => styleText("dim", str),
886
+ styleCommandDescription: (str) => styleText("dim", str),
887
+ styleOptionDescription: (str) => styleText("dim", str),
888
+ styleSubcommandDescription: (str) => styleText("dim", str)
889
+ }).addHelpText("beforeAll", `
760
890
  ${styleText("bold", "bamboohr")} ${DIM}\u2014 BambooHR CLI${RESET}
761
891
  `).addHelpText(
762
- "after",
763
- `
892
+ "after",
893
+ `
764
894
  ${styleText("bold", "Environment:")}
765
895
  BAMBOO_TOKEN BambooHR API token ${DIM}(generate in BambooHR > Settings > API Keys)${RESET}
766
896
  BAMBOO_COMPANY_DOMAIN Company subdomain ${DIM}(e.g., "mycompany" for mycompany.bamboohr.com)${RESET}
@@ -772,24 +902,34 @@ ${styleText("bold", "Examples:")}
772
902
  ${DIM}$${RESET} bamboohr employee photo 123 --output ./photo.jpg
773
903
  ${DIM}$${RESET} bamboohr timeoff types --requestable
774
904
  ${DIM}$${RESET} bamboohr timeoff create 504 --start-date 2026-04-01 --end-date 2026-04-01 --type-id 83 --amount 1
775
- ${DIM}$${RESET} bamboohr timeoff create 504 --start-date 2026-04-01 --end-date 2026-04-01 --type-id 83 --amount 1 --note "Doctor appointment"
776
905
  ${DIM}$${RESET} bamboohr timeoff requests --status requested --start-date 2026-01-01 --end-date 2026-12-31
777
906
  ${DIM}$${RESET} bamboohr timeoff whos-out
778
907
  ${DIM}$${RESET} bamboohr timeoff update-status 7890 --status approved
779
908
  ${DIM}$${RESET} bamboohr file list
780
909
  ${DIM}$${RESET} bamboohr meta fields
781
910
  `
782
- );
783
- program.option("--pretty", "Pretty-print JSON output");
784
- program.hook("preAction", (thisCommand) => {
785
- if (thisCommand.optsWithGlobals().pretty) setPretty(true);
786
- });
787
- registerEmployeeCommands(program);
788
- registerTimeoffCommands(program);
789
- registerFileCommands(program);
790
- registerMetaCommands(program);
791
- try {
792
- await program.parseAsync();
793
- } catch (err) {
794
- handleError(err);
911
+ );
912
+ program.option("--pretty", "Pretty-print JSON output");
913
+ program.hook("preAction", (thisCommand) => {
914
+ if (thisCommand.optsWithGlobals().pretty) setPretty(true);
915
+ });
916
+ registerEmployeeCommands(program);
917
+ registerTimeoffCommands(program);
918
+ registerFileCommands(program);
919
+ registerMetaCommands(program);
920
+ return program;
795
921
  }
922
+
923
+ // src/index.ts
924
+ await runCli(buildProgram(), {
925
+ service: "BambooHR",
926
+ authRecovery: "Set BAMBOO_TOKEN and BAMBOO_COMPANY_DOMAIN in your shell profile (~/.zshrc).",
927
+ networkRecovery: "Verify BAMBOO_COMPANY_DOMAIN is correct and you have internet connectivity.",
928
+ // BambooHR returns HTTP 404 for invalid credentials as well as genuinely
929
+ // missing resources — it cannot distinguish them, so keep not_found but
930
+ // document the ambiguity in the recovery hint (design §4.9).
931
+ adapt: (normalized) => normalized.status === 404 ? {
932
+ ...normalized,
933
+ recovery: "BambooHR returns 404 for invalid credentials too \u2014 verify BAMBOO_TOKEN and BAMBOO_COMPANY_DOMAIN, then confirm the resource id exists."
934
+ } : normalized
935
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bamboohr-cli",
3
- "version": "1.0.17",
3
+ "version": "2.0.0-gc22cadf.3",
4
4
  "publish": true,
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -23,8 +23,8 @@
23
23
  "typescript": "^5.7.2",
24
24
  "vitest": "^4.0.16",
25
25
  "cli-utils": "1.0.0",
26
- "config-eslint": "0.0.0",
27
- "config-typescript": "0.0.0"
26
+ "config-typescript": "0.0.0",
27
+ "config-eslint": "0.0.0"
28
28
  },
29
29
  "engines": {
30
30
  "node": ">=22.0.0"
@@ -35,6 +35,6 @@
35
35
  "lint": "eslint src",
36
36
  "lint:fix": "eslint src --fix",
37
37
  "test": "vitest run",
38
- "test:integration": "vitest run tests/integration"
38
+ "test:integration": "vitest run --config vitest.integration.config.ts"
39
39
  }
40
40
  }