bamboohr-cli 1.0.7 → 1.0.9

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 (109) hide show
  1. package/dist/index.js +601 -31
  2. package/package.json +6 -5
  3. package/dist/commands/employee/directory.d.ts +0 -3
  4. package/dist/commands/employee/directory.d.ts.map +0 -1
  5. package/dist/commands/employee/directory.js +0 -15
  6. package/dist/commands/employee/directory.js.map +0 -1
  7. package/dist/commands/employee/get.d.ts +0 -3
  8. package/dist/commands/employee/get.d.ts.map +0 -1
  9. package/dist/commands/employee/get.js +0 -25
  10. package/dist/commands/employee/get.js.map +0 -1
  11. package/dist/commands/employee/goals.d.ts +0 -3
  12. package/dist/commands/employee/goals.d.ts.map +0 -1
  13. package/dist/commands/employee/goals.js +0 -22
  14. package/dist/commands/employee/goals.js.map +0 -1
  15. package/dist/commands/employee/index.d.ts +0 -3
  16. package/dist/commands/employee/index.d.ts.map +0 -1
  17. package/dist/commands/employee/index.js +0 -24
  18. package/dist/commands/employee/index.js.map +0 -1
  19. package/dist/commands/employee/photo.d.ts +0 -3
  20. package/dist/commands/employee/photo.d.ts.map +0 -1
  21. package/dist/commands/employee/photo.js +0 -27
  22. package/dist/commands/employee/photo.js.map +0 -1
  23. package/dist/commands/employee/search.d.ts +0 -3
  24. package/dist/commands/employee/search.d.ts.map +0 -1
  25. package/dist/commands/employee/search.js +0 -59
  26. package/dist/commands/employee/search.js.map +0 -1
  27. package/dist/commands/file/get.d.ts +0 -3
  28. package/dist/commands/file/get.d.ts.map +0 -1
  29. package/dist/commands/file/get.js +0 -19
  30. package/dist/commands/file/get.js.map +0 -1
  31. package/dist/commands/file/index.d.ts +0 -3
  32. package/dist/commands/file/index.d.ts.map +0 -1
  33. package/dist/commands/file/index.js +0 -15
  34. package/dist/commands/file/index.js.map +0 -1
  35. package/dist/commands/file/list.d.ts +0 -3
  36. package/dist/commands/file/list.d.ts.map +0 -1
  37. package/dist/commands/file/list.js +0 -14
  38. package/dist/commands/file/list.js.map +0 -1
  39. package/dist/commands/meta/departments.d.ts +0 -3
  40. package/dist/commands/meta/departments.d.ts.map +0 -1
  41. package/dist/commands/meta/departments.js +0 -16
  42. package/dist/commands/meta/departments.js.map +0 -1
  43. package/dist/commands/meta/divisions.d.ts +0 -3
  44. package/dist/commands/meta/divisions.d.ts.map +0 -1
  45. package/dist/commands/meta/divisions.js +0 -16
  46. package/dist/commands/meta/divisions.js.map +0 -1
  47. package/dist/commands/meta/fields.d.ts +0 -3
  48. package/dist/commands/meta/fields.d.ts.map +0 -1
  49. package/dist/commands/meta/fields.js +0 -15
  50. package/dist/commands/meta/fields.js.map +0 -1
  51. package/dist/commands/meta/index.d.ts +0 -3
  52. package/dist/commands/meta/index.d.ts.map +0 -1
  53. package/dist/commands/meta/index.js +0 -21
  54. package/dist/commands/meta/index.js.map +0 -1
  55. package/dist/commands/meta/locations.d.ts +0 -3
  56. package/dist/commands/meta/locations.d.ts.map +0 -1
  57. package/dist/commands/meta/locations.js +0 -16
  58. package/dist/commands/meta/locations.js.map +0 -1
  59. package/dist/commands/timeoff/balance.d.ts +0 -3
  60. package/dist/commands/timeoff/balance.d.ts.map +0 -1
  61. package/dist/commands/timeoff/balance.js +0 -21
  62. package/dist/commands/timeoff/balance.js.map +0 -1
  63. package/dist/commands/timeoff/create.d.ts +0 -3
  64. package/dist/commands/timeoff/create.d.ts.map +0 -1
  65. package/dist/commands/timeoff/create.js +0 -36
  66. package/dist/commands/timeoff/create.js.map +0 -1
  67. package/dist/commands/timeoff/index.d.ts +0 -3
  68. package/dist/commands/timeoff/index.d.ts.map +0 -1
  69. package/dist/commands/timeoff/index.js +0 -27
  70. package/dist/commands/timeoff/index.js.map +0 -1
  71. package/dist/commands/timeoff/requests.d.ts +0 -3
  72. package/dist/commands/timeoff/requests.d.ts.map +0 -1
  73. package/dist/commands/timeoff/requests.js +0 -51
  74. package/dist/commands/timeoff/requests.js.map +0 -1
  75. package/dist/commands/timeoff/types.d.ts +0 -3
  76. package/dist/commands/timeoff/types.d.ts.map +0 -1
  77. package/dist/commands/timeoff/types.js +0 -21
  78. package/dist/commands/timeoff/types.js.map +0 -1
  79. package/dist/commands/timeoff/update-status.d.ts +0 -3
  80. package/dist/commands/timeoff/update-status.d.ts.map +0 -1
  81. package/dist/commands/timeoff/update-status.js +0 -25
  82. package/dist/commands/timeoff/update-status.js.map +0 -1
  83. package/dist/commands/timeoff/whos-out.d.ts +0 -3
  84. package/dist/commands/timeoff/whos-out.d.ts.map +0 -1
  85. package/dist/commands/timeoff/whos-out.js +0 -22
  86. package/dist/commands/timeoff/whos-out.js.map +0 -1
  87. package/dist/index.d.ts +0 -3
  88. package/dist/index.d.ts.map +0 -1
  89. package/dist/index.js.map +0 -1
  90. package/dist/utils/cached.d.ts +0 -8
  91. package/dist/utils/cached.d.ts.map +0 -1
  92. package/dist/utils/cached.js +0 -17
  93. package/dist/utils/cached.js.map +0 -1
  94. package/dist/utils/client.d.ts +0 -3
  95. package/dist/utils/client.d.ts.map +0 -1
  96. package/dist/utils/client.js +0 -19
  97. package/dist/utils/client.js.map +0 -1
  98. package/dist/utils/output.d.ts +0 -3
  99. package/dist/utils/output.d.ts.map +0 -1
  100. package/dist/utils/output.js +0 -49
  101. package/dist/utils/output.js.map +0 -1
  102. package/dist/utils/resolve-employee.d.ts +0 -6
  103. package/dist/utils/resolve-employee.d.ts.map +0 -1
  104. package/dist/utils/resolve-employee.js +0 -10
  105. package/dist/utils/resolve-employee.js.map +0 -1
  106. package/dist/utils/strip-response.d.ts +0 -6
  107. package/dist/utils/strip-response.d.ts.map +0 -1
  108. package/dist/utils/strip-response.js +0 -29
  109. package/dist/utils/strip-response.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,32 +1,603 @@
1
1
  #!/usr/bin/env node
2
- import { styleText } from 'node:util';
3
- import { Command } from 'commander';
4
- import { registerEmployeeCommands } from './commands/employee/index.js';
5
- import { registerFileCommands } from './commands/file/index.js';
6
- import { registerMetaCommands } from './commands/meta/index.js';
7
- import { registerTimeoffCommands } from './commands/timeoff/index.js';
8
- import { handleError } from './utils/output.js';
9
- const DIM = '\x1b[2m';
10
- const RESET = '\x1b[0m';
11
- const program = new Command();
12
- program
13
- .name('bamboohr')
14
- .description('BambooHR CLI')
15
- .version('1.0.0')
16
- .configureHelp({
17
- styleTitle: (str) => styleText('bold', str),
18
- styleUsage: (str) => styleText('dim', str),
19
- styleCommandDescription: (str) => styleText('dim', str),
20
- styleOptionDescription: (str) => styleText('dim', str),
21
- styleSubcommandDescription: (str) => styleText('dim', str),
22
- })
23
- .addHelpText('beforeAll', `\n${styleText('bold', 'bamboohr')} ${DIM}— BambooHR CLI${RESET}\n`)
24
- .addHelpText('after', `
25
- ${styleText('bold', 'Environment:')}
2
+
3
+ // src/index.ts
4
+ import { styleText } from "util";
5
+ import { Command as Command6 } from "commander";
6
+
7
+ // ../../cli-utils/dist/cache.js
8
+ import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
9
+ import { homedir } from "os";
10
+ import { join } from "path";
11
+ var DEFAULT_TTL = 36e5;
12
+ function getCacheDir(name) {
13
+ return join(homedir(), ".cache", name);
14
+ }
15
+ function getCachePath(name, key) {
16
+ return join(getCacheDir(name), `${key}.json`);
17
+ }
18
+ function cacheGet(options, key) {
19
+ const ttl = options.ttl ?? DEFAULT_TTL;
20
+ const path = getCachePath(options.name, key);
21
+ try {
22
+ const stat = statSync(path);
23
+ if (Date.now() - stat.mtimeMs > ttl)
24
+ return null;
25
+ const raw = readFileSync(path, "utf-8");
26
+ const entry = JSON.parse(raw);
27
+ return entry.data;
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+ function cacheSet(options, key, data) {
33
+ const dir = getCacheDir(options.name);
34
+ const path = getCachePath(options.name, key);
35
+ const entry = { data, timestamp: Date.now() };
36
+ try {
37
+ mkdirSync(dir, { recursive: true });
38
+ writeFileSync(path, JSON.stringify(entry));
39
+ } catch {
40
+ }
41
+ }
42
+ async function cacheGetOrFetch(options, key, fetcher) {
43
+ const cached = cacheGet(options, key);
44
+ if (cached !== null)
45
+ return cached;
46
+ const data = await fetcher();
47
+ cacheSet(options, key, data);
48
+ return data;
49
+ }
50
+
51
+ // src/utils/cached.ts
52
+ var CACHE_NAME = "bamboohr";
53
+ var TWELVE_HOURS = 432e5;
54
+ var SEVEN_DAYS = 6048e5;
55
+ function getDirectory(client) {
56
+ return cacheGetOrFetch({ name: CACHE_NAME, ttl: TWELVE_HOURS }, "directory", () => client.employees.getDirectory());
57
+ }
58
+ function getTimeOffTypes(client) {
59
+ return cacheGetOrFetch({ name: CACHE_NAME, ttl: SEVEN_DAYS }, "timeoff-types", () => client.timeOff.getTypes());
60
+ }
61
+ function getMetaFields(client) {
62
+ return cacheGetOrFetch({ name: CACHE_NAME, ttl: SEVEN_DAYS }, "meta-fields", () => client.meta.getFields());
63
+ }
64
+
65
+ // src/utils/client.ts
66
+ import { BambooHRClient } from "bamboohr-client";
67
+ function getClient() {
68
+ const apiToken = process.env.BAMBOO_TOKEN;
69
+ const companyDomain = process.env.BAMBOO_COMPANY_DOMAIN;
70
+ if (!apiToken || !companyDomain) {
71
+ const missing = [...!apiToken ? ["BAMBOO_TOKEN"] : [], ...!companyDomain ? ["BAMBOO_COMPANY_DOMAIN"] : []];
72
+ process.stderr.write(
73
+ `${JSON.stringify({
74
+ error: `Missing required environment variables: ${missing.join(", ")}`,
75
+ setup: {
76
+ BAMBOO_TOKEN: "BambooHR API token \u2014 generate in BambooHR > Settings > API Keys",
77
+ BAMBOO_COMPANY_DOMAIN: 'Company subdomain (e.g., "mycompany" for mycompany.bamboohr.com)'
78
+ },
79
+ hint: "Export these in your shell profile (e.g., ~/.zshrc)."
80
+ })}
81
+ `
82
+ );
83
+ process.exit(1);
84
+ }
85
+ return new BambooHRClient({ apiToken, companyDomain });
86
+ }
87
+
88
+ // src/utils/strip-response.ts
89
+ function stripResponse(obj) {
90
+ if (Array.isArray(obj)) {
91
+ return obj.map(stripResponse);
92
+ }
93
+ if (obj === null || typeof obj !== "object") {
94
+ return obj;
95
+ }
96
+ const record = obj;
97
+ const result = {};
98
+ for (const [key, value] of Object.entries(record)) {
99
+ if (key === "self" || key === "_links" || key === "_expandable" || key === "expand" || key === "avatarUrls" || key === "avatarId" || key === "iconUrl") {
100
+ continue;
101
+ }
102
+ result[key] = stripResponse(value);
103
+ }
104
+ return result;
105
+ }
106
+
107
+ // src/utils/output.ts
108
+ function output(data) {
109
+ process.stdout.write(`${JSON.stringify(stripResponse(data), null, 2)}
110
+ `);
111
+ }
112
+ function handleError(err) {
113
+ const message = err instanceof Error ? err.message : String(err);
114
+ const axiosStatus = err?.response?.status;
115
+ if (axiosStatus === 400) {
116
+ const responseData = err?.response?.data;
117
+ const apiErrors = responseData && typeof responseData === "object" ? responseData : void 0;
118
+ process.stderr.write(
119
+ `${JSON.stringify({
120
+ error: "Bad request (HTTP 400)",
121
+ detail: apiErrors ?? message,
122
+ hint: "Check that all parameters are valid (employee IDs, field names, date formats, etc.)."
123
+ })}
124
+ `
125
+ );
126
+ } else if (axiosStatus === 401) {
127
+ process.stderr.write(
128
+ `${JSON.stringify({
129
+ error: "Authentication failed (HTTP 401)",
130
+ hint: "Verify that BAMBOO_TOKEN and BAMBOO_COMPANY_DOMAIN are set correctly. The token may be expired or invalid."
131
+ })}
132
+ `
133
+ );
134
+ } else if (axiosStatus === 403) {
135
+ const responseData = err?.response?.data;
136
+ process.stderr.write(
137
+ `${JSON.stringify({
138
+ error: "Forbidden (HTTP 403)",
139
+ detail: responseData && typeof responseData === "object" ? responseData : message,
140
+ hint: "Your account does not have permission for this operation. Check your BambooHR access level."
141
+ })}
142
+ `
143
+ );
144
+ } else if (axiosStatus === 404) {
145
+ process.stderr.write(
146
+ `${JSON.stringify({
147
+ error: "Not found (HTTP 404)",
148
+ hint: "Resource not found. Note: BambooHR also returns 404 for invalid credentials \u2014 verify that BAMBOO_TOKEN and BAMBOO_COMPANY_DOMAIN are correct."
149
+ })}
150
+ `
151
+ );
152
+ } else if (message.includes("ENOTFOUND") || message.includes("ECONNREFUSED") || message.includes("ECONNRESET")) {
153
+ process.stderr.write(
154
+ `${JSON.stringify({
155
+ error: `Cannot connect to BambooHR API: ${message}`,
156
+ hint: "Verify that BAMBOO_COMPANY_DOMAIN is correct and you have internet connectivity."
157
+ })}
158
+ `
159
+ );
160
+ } else {
161
+ process.stderr.write(`${JSON.stringify({ error: message })}
162
+ `);
163
+ }
164
+ process.exit(1);
165
+ }
166
+
167
+ // src/commands/employee/directory.ts
168
+ function directory(parent) {
169
+ parent.command("directory").description("Get the company employee directory").addHelpText("after", "\nExamples:\n $ bamboohr employee directory").action(async () => {
170
+ const client = getClient();
171
+ const result = await getDirectory(client);
172
+ output(result);
173
+ });
174
+ }
175
+
176
+ // src/commands/employee/get.ts
177
+ function get(parent) {
178
+ 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").addHelpText(
179
+ "after",
180
+ `
181
+ Examples:
182
+ $ bamboohr employee get
183
+ $ bamboohr employee get 123
184
+ $ bamboohr employee get 123 --fields "firstName,lastName,department,hireDate"
185
+ $ bamboohr employee get 123 --include-future`
186
+ ).action(async (id, opts) => {
187
+ const client = getClient();
188
+ const result = await client.employees.get({
189
+ id: id ?? "0",
190
+ fields: opts.fields,
191
+ onlyCurrent: !opts.includeFuture
192
+ });
193
+ output(result);
194
+ });
195
+ }
196
+
197
+ // src/commands/employee/goals.ts
198
+ import { Option } from "commander";
199
+ function goals(parent) {
200
+ parent.command("goals <id>").description("Get employee performance goals").addOption(
201
+ new Option("--filter <filter>", "Filter goals by status").choices(["open", "closed", "all"]).default("all")
202
+ ).addHelpText(
203
+ "after",
204
+ `
205
+ Examples:
206
+ $ bamboohr employee goals 123
207
+ $ bamboohr employee goals 123 --filter open`
208
+ ).action(async (id, opts) => {
209
+ const client = getClient();
210
+ const result = await client.goals.get({
211
+ employeeId: id,
212
+ filter: opts.filter === "all" ? void 0 : opts.filter
213
+ });
214
+ output(result);
215
+ });
216
+ }
217
+
218
+ // src/commands/employee/photo.ts
219
+ import { writeFileSync as writeFileSync2 } from "fs";
220
+ import { Option as Option2 } from "commander";
221
+ function photo(parent) {
222
+ parent.command("photo <id>").description("Download employee photo").requiredOption("--output <path>", "File path to save photo").addOption(
223
+ new Option2("--size <size>", "Photo size").choices(["original", "large", "medium", "small", "xs", "tiny"]).default("medium")
224
+ ).addHelpText(
225
+ "after",
226
+ `
227
+ Examples:
228
+ $ bamboohr employee photo 123 --output ./photo.jpg
229
+ $ bamboohr employee photo 123 --output ./photo.jpg --size large`
230
+ ).action(async (id, opts) => {
231
+ const client = getClient();
232
+ const buffer = await client.employees.getPhoto({
233
+ employeeId: id,
234
+ size: opts.size
235
+ });
236
+ writeFileSync2(opts.output, buffer);
237
+ output({ saved: true, path: opts.output, size: buffer.length });
238
+ });
239
+ }
240
+
241
+ // src/utils/resolve-employee.ts
242
+ async function resolveEmployeeId(client, employeeId) {
243
+ if (employeeId) return employeeId;
244
+ const me = await client.employees.get({ id: "0", fields: "id" });
245
+ return me.id;
246
+ }
247
+
248
+ // src/commands/employee/search.ts
249
+ function search(parent) {
250
+ parent.command("search [query]").description("Search and filter employees").option("--supervisor <name>", 'Filter by supervisor name (use "me" for current user)').option("--department <name>", "Filter by department").option("--location <name>", "Filter by location").option("--division <name>", "Filter by division").addHelpText(
251
+ "after",
252
+ `
253
+ Examples:
254
+ bamboohr employee search john
255
+ bamboohr employee search --supervisor me
256
+ bamboohr employee search --department "R&D" --location Sofia
257
+ bamboohr employee search --supervisor "Borislav Mihaylov"`
258
+ ).action(
259
+ async (query, opts) => {
260
+ const client = getClient();
261
+ const dir = await getDirectory(client);
262
+ let matches = dir.employees;
263
+ if (query) {
264
+ const q = query.toLowerCase();
265
+ matches = matches.filter((e) => {
266
+ const name = `${e.firstName ?? ""} ${e.lastName ?? ""} ${e.preferredName ?? ""} ${e.displayName ?? ""}`.toLowerCase();
267
+ return name.includes(q);
268
+ });
269
+ }
270
+ if (opts.supervisor) {
271
+ let supervisorName = opts.supervisor;
272
+ if (supervisorName.toLowerCase() === "me") {
273
+ const myId = await resolveEmployeeId(client);
274
+ const me = dir.employees.find((e) => e.id === myId);
275
+ supervisorName = me?.displayName ?? `${me?.firstName ?? ""} ${me?.lastName ?? ""}`;
276
+ }
277
+ const s = supervisorName.toLowerCase();
278
+ matches = matches.filter((e) => (e.supervisor ?? "").toLowerCase().includes(s));
279
+ }
280
+ if (opts.department) {
281
+ const d = opts.department.toLowerCase();
282
+ matches = matches.filter((e) => (e.department ?? "").toLowerCase().includes(d));
283
+ }
284
+ if (opts.location) {
285
+ const l = opts.location.toLowerCase();
286
+ matches = matches.filter((e) => (e.location ?? "").toLowerCase().includes(l));
287
+ }
288
+ if (opts.division) {
289
+ const dv = opts.division.toLowerCase();
290
+ matches = matches.filter((e) => (e.division ?? "").toLowerCase().includes(dv));
291
+ }
292
+ output(matches);
293
+ }
294
+ );
295
+ }
296
+
297
+ // src/commands/employee/index.ts
298
+ function registerEmployeeCommands(program2) {
299
+ const employee = program2.command("employee").description("Employee operations").addHelpText(
300
+ "after",
301
+ `
302
+ Examples:
303
+ $ bamboohr employee get
304
+ $ bamboohr employee get 123 --fields "firstName,lastName,department"
305
+ $ bamboohr employee directory
306
+ $ bamboohr employee photo 123 --output ./photo.jpg
307
+ $ bamboohr employee goals 123 --filter open
308
+ `
309
+ );
310
+ get(employee);
311
+ search(employee);
312
+ directory(employee);
313
+ photo(employee);
314
+ goals(employee);
315
+ }
316
+
317
+ // src/commands/file/get.ts
318
+ import { writeFileSync as writeFileSync3 } from "fs";
319
+ function get2(parent) {
320
+ parent.command("get <fileId>").description("Download a company file").requiredOption("--output <path>", "File path to save to").addHelpText(
321
+ "after",
322
+ `
323
+ Examples:
324
+ $ bamboohr file get 42 --output ./handbook.pdf`
325
+ ).action(async (fileId, opts) => {
326
+ const client = getClient();
327
+ const buffer = await client.files.get({ fileId });
328
+ writeFileSync3(opts.output, buffer);
329
+ output({ saved: true, path: opts.output, size: buffer.length });
330
+ });
331
+ }
332
+
333
+ // src/commands/file/list.ts
334
+ function list(parent) {
335
+ parent.command("list").description("List all company files and categories").addHelpText("after", "\nExamples:\n $ bamboohr file list").action(async () => {
336
+ const client = getClient();
337
+ const result = await client.files.list();
338
+ output(result);
339
+ });
340
+ }
341
+
342
+ // src/commands/file/index.ts
343
+ function registerFileCommands(program2) {
344
+ const file = program2.command("file").description("Company file operations").addHelpText(
345
+ "after",
346
+ `
347
+ Examples:
348
+ $ bamboohr file list
349
+ $ bamboohr file get 42 --output ./handbook.pdf
350
+ `
351
+ );
352
+ list(file);
353
+ get2(file);
354
+ }
355
+
356
+ // src/commands/meta/departments.ts
357
+ function departments(parent) {
358
+ parent.command("departments").description("List all departments").addHelpText("after", "\nExamples:\n bamboohr meta departments").action(async () => {
359
+ const client = getClient();
360
+ const dir = await getDirectory(client);
361
+ const unique = [...new Set(dir.employees.map((e) => e.department).filter(Boolean))].sort();
362
+ output(unique);
363
+ });
364
+ }
365
+
366
+ // src/commands/meta/divisions.ts
367
+ function divisions(parent) {
368
+ parent.command("divisions").description("List all divisions").addHelpText("after", "\nExamples:\n bamboohr meta divisions").action(async () => {
369
+ const client = getClient();
370
+ const dir = await getDirectory(client);
371
+ const unique = [...new Set(dir.employees.map((e) => e.division).filter(Boolean))].sort();
372
+ output(unique);
373
+ });
374
+ }
375
+
376
+ // src/commands/meta/fields.ts
377
+ function fields(parent) {
378
+ parent.command("fields").description("List all available BambooHR fields").addHelpText("after", "\nExamples:\n $ bamboohr meta fields").action(async () => {
379
+ const client = getClient();
380
+ const result = await getMetaFields(client);
381
+ output(result);
382
+ });
383
+ }
384
+
385
+ // src/commands/meta/locations.ts
386
+ function locations(parent) {
387
+ parent.command("locations").description("List all office locations").addHelpText("after", "\nExamples:\n bamboohr meta locations").action(async () => {
388
+ const client = getClient();
389
+ const dir = await getDirectory(client);
390
+ const unique = [...new Set(dir.employees.map((e) => e.location).filter(Boolean))].sort();
391
+ output(unique);
392
+ });
393
+ }
394
+
395
+ // src/commands/meta/index.ts
396
+ function registerMetaCommands(program2) {
397
+ const meta = program2.command("meta").description("Metadata operations").addHelpText(
398
+ "after",
399
+ `
400
+ Examples:
401
+ $ bamboohr meta fields
402
+ $ bamboohr meta departments
403
+ $ bamboohr meta locations
404
+ $ bamboohr meta divisions
405
+ `
406
+ );
407
+ fields(meta);
408
+ departments(meta);
409
+ locations(meta);
410
+ divisions(meta);
411
+ }
412
+
413
+ // src/commands/timeoff/balance.ts
414
+ function balance(parent) {
415
+ parent.command("balance [employeeId]").description("Estimate time off balance (defaults to current user)").option("--date <date>", "Future date to estimate balance for (YYYY-MM-DD)").addHelpText(
416
+ "after",
417
+ `
418
+ Examples:
419
+ $ bamboohr timeoff balance
420
+ $ bamboohr timeoff balance 123
421
+ $ bamboohr timeoff balance --date 2026-06-30`
422
+ ).action(async (employeeId, opts) => {
423
+ const client = getClient();
424
+ const id = await resolveEmployeeId(client, employeeId);
425
+ const result = await client.timeOff.getBalance({ employeeId: id, date: opts.date });
426
+ output(result);
427
+ });
428
+ }
429
+
430
+ // src/commands/timeoff/create.ts
431
+ import { Option as Option3 } from "commander";
432
+ function create(parent) {
433
+ 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(
434
+ new Option3("--status <status>", "Request status").choices(["requested", "approved"]).default("requested")
435
+ ).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(
436
+ "after",
437
+ `
438
+ Examples:
439
+ $ bamboohr timeoff create --start 2026-04-01 --end 2026-04-01 --type-id 83 --amount 1
440
+ $ bamboohr timeoff create 504 --start 2026-04-01 --end 2026-04-01 --type-id 83 --amount 1 --note "Doctor appointment"`
441
+ ).action(
442
+ async (employeeId, opts) => {
443
+ const client = getClient();
444
+ const id = await resolveEmployeeId(client, employeeId);
445
+ const result = await client.timeOff.createRequest({
446
+ employeeId: id,
447
+ status: opts.status,
448
+ start: opts.start,
449
+ end: opts.end,
450
+ timeOffTypeId: opts.typeId,
451
+ amount: opts.amount,
452
+ notes: opts.note ? [{ from: "employee", note: opts.note }] : void 0,
453
+ dates: opts.dates ? JSON.parse(opts.dates) : void 0
454
+ });
455
+ output(result || { created: true, employeeId: id, start: opts.start, end: opts.end });
456
+ }
457
+ );
458
+ }
459
+
460
+ // src/commands/timeoff/requests.ts
461
+ import { Option as Option4 } from "commander";
462
+ function requests(parent) {
463
+ 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(
464
+ new Option4("--status <status>", "Filter by request status").choices([
465
+ "approved",
466
+ "denied",
467
+ "superceded",
468
+ "requested",
469
+ "canceled"
470
+ ])
471
+ ).option("--type <typeId>", "Filter by time off type ID").addHelpText(
472
+ "after",
473
+ `
474
+ Examples:
475
+ $ bamboohr timeoff requests
476
+ $ bamboohr timeoff requests --status requested
477
+ $ bamboohr timeoff requests --all --action approve --status requested
478
+ $ bamboohr timeoff requests --employee 123 --start 2026-01-01 --end 2026-03-31`
479
+ ).action(
480
+ async (opts) => {
481
+ const year = (/* @__PURE__ */ new Date()).getFullYear();
482
+ const start = opts.start ?? `${year}-01-01`;
483
+ const end = opts.end ?? `${year}-12-31`;
484
+ const client = getClient();
485
+ let employeeId = opts.employee;
486
+ if (!employeeId && !opts.all) {
487
+ employeeId = await resolveEmployeeId(client);
488
+ }
489
+ const result = await client.timeOff.getRequests({
490
+ id: opts.id,
491
+ action: opts.action,
492
+ employeeId,
493
+ start,
494
+ end,
495
+ status: opts.status,
496
+ type: opts.type
497
+ });
498
+ output(result);
499
+ }
500
+ );
501
+ }
502
+
503
+ // src/commands/timeoff/types.ts
504
+ function types(parent) {
505
+ parent.command("types").description("List available time off types").option("--requestable", "Only show types the user can request").addHelpText(
506
+ "after",
507
+ `
508
+ Examples:
509
+ $ bamboohr timeoff types
510
+ $ bamboohr timeoff types --requestable`
511
+ ).action(async (opts) => {
512
+ const client = getClient();
513
+ const result = opts.requestable ? await client.timeOff.getTypes({ mode: "request" }) : await getTimeOffTypes(client);
514
+ output(result);
515
+ });
516
+ }
517
+
518
+ // src/commands/timeoff/update-status.ts
519
+ import { Option as Option5 } from "commander";
520
+ function updateStatus(parent) {
521
+ parent.command("update-status <requestId>").description("Update time off request status (approve, deny, cancel)").addOption(
522
+ new Option5("--status <status>", "New status").choices(["approved", "denied", "canceled"]).makeOptionMandatory()
523
+ ).option("--note <text>", "Note to attach to the status change").addHelpText(
524
+ "after",
525
+ `
526
+ Examples:
527
+ $ bamboohr timeoff update-status 7890 --status approved
528
+ $ bamboohr timeoff update-status 7890 --status denied --note "Team at capacity that week"
529
+ $ bamboohr timeoff update-status 7890 --status canceled`
530
+ ).action(async (requestId, opts) => {
531
+ const client = getClient();
532
+ const result = await client.timeOff.updateRequestStatus({
533
+ requestId,
534
+ status: opts.status,
535
+ note: opts.note
536
+ });
537
+ output(result || { updated: true, requestId, status: opts.status });
538
+ });
539
+ }
540
+
541
+ // src/commands/timeoff/whos-out.ts
542
+ function whosOut(parent) {
543
+ parent.command("whos-out").description("View who's out \u2014 upcoming time off and holidays").option("--start <date>", "Start date (YYYY-MM-DD, defaults to today)").option("--end <date>", "End date (YYYY-MM-DD, defaults to 14 days from start)").addHelpText(
544
+ "after",
545
+ `
546
+ Examples:
547
+ $ bamboohr timeoff whos-out
548
+ $ bamboohr timeoff whos-out --start 2026-03-20 --end 2026-04-03`
549
+ ).action(async (opts) => {
550
+ const client = getClient();
551
+ const result = await client.timeOff.getWhosOut({
552
+ start: opts.start,
553
+ end: opts.end
554
+ });
555
+ output(result);
556
+ });
557
+ }
558
+
559
+ // src/commands/timeoff/index.ts
560
+ function registerTimeoffCommands(program2) {
561
+ const timeoff = program2.command("timeoff").description("Time off operations").addHelpText(
562
+ "after",
563
+ `
564
+ Examples:
565
+ $ bamboohr timeoff types --requestable
566
+ $ bamboohr timeoff create 504 --start 2026-04-01 --end 2026-04-01 --type-id 83 --amount 1
567
+ $ bamboohr timeoff requests --status requested --start 2026-01-01 --end 2026-12-31
568
+ $ bamboohr timeoff balance 504
569
+ $ bamboohr timeoff whos-out
570
+ $ bamboohr timeoff update-status 7890 --status approved
571
+ `
572
+ );
573
+ types(timeoff);
574
+ create(timeoff);
575
+ requests(timeoff);
576
+ balance(timeoff);
577
+ whosOut(timeoff);
578
+ updateStatus(timeoff);
579
+ }
580
+
581
+ // src/index.ts
582
+ var DIM = "\x1B[2m";
583
+ var RESET = "\x1B[0m";
584
+ var program = new Command6();
585
+ program.name("bamboohr").description("BambooHR CLI").version("1.0.0").configureHelp({
586
+ styleTitle: (str) => styleText("bold", str),
587
+ styleUsage: (str) => styleText("dim", str),
588
+ styleCommandDescription: (str) => styleText("dim", str),
589
+ styleOptionDescription: (str) => styleText("dim", str),
590
+ styleSubcommandDescription: (str) => styleText("dim", str)
591
+ }).addHelpText("beforeAll", `
592
+ ${styleText("bold", "bamboohr")} ${DIM}\u2014 BambooHR CLI${RESET}
593
+ `).addHelpText(
594
+ "after",
595
+ `
596
+ ${styleText("bold", "Environment:")}
26
597
  BAMBOO_TOKEN BambooHR API token ${DIM}(generate in BambooHR > Settings > API Keys)${RESET}
27
598
  BAMBOO_COMPANY_DOMAIN Company subdomain ${DIM}(e.g., "mycompany" for mycompany.bamboohr.com)${RESET}
28
599
 
29
- ${styleText('bold', 'Examples:')}
600
+ ${styleText("bold", "Examples:")}
30
601
  ${DIM}$${RESET} bamboohr employee get
31
602
  ${DIM}$${RESET} bamboohr employee get 123 --fields "firstName,lastName,department"
32
603
  ${DIM}$${RESET} bamboohr employee directory
@@ -39,15 +610,14 @@ ${styleText('bold', 'Examples:')}
39
610
  ${DIM}$${RESET} bamboohr timeoff update-status 7890 --status approved
40
611
  ${DIM}$${RESET} bamboohr file list
41
612
  ${DIM}$${RESET} bamboohr meta fields
42
- `);
613
+ `
614
+ );
43
615
  registerEmployeeCommands(program);
44
616
  registerTimeoffCommands(program);
45
617
  registerFileCommands(program);
46
618
  registerMetaCommands(program);
47
619
  try {
48
- await program.parseAsync();
49
- }
50
- catch (err) {
51
- handleError(err);
620
+ await program.parseAsync();
621
+ } catch (err) {
622
+ handleError(err);
52
623
  }
53
- //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bamboohr-cli",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "publish": true,
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,24 +12,25 @@
12
12
  ],
13
13
  "dependencies": {
14
14
  "commander": "^13.1.0",
15
- "bamboohr-client": "1.0.18",
16
- "cli-utils": "1.0.0"
15
+ "bamboohr-client": "1.0.19"
17
16
  },
18
17
  "devDependencies": {
19
18
  "@types/node": "24.10.4",
20
19
  "dotenv": "17.2.3",
21
20
  "eslint": "^9.39.0",
21
+ "tsup": "^8.5.1",
22
22
  "tsx": "^4.19.2",
23
23
  "typescript": "^5.7.2",
24
24
  "vitest": "^4.0.16",
25
+ "config-eslint": "0.0.0",
25
26
  "config-typescript": "0.0.0",
26
- "config-eslint": "0.0.0"
27
+ "cli-utils": "1.0.0"
27
28
  },
28
29
  "engines": {
29
30
  "node": ">=22.0.0"
30
31
  },
31
32
  "scripts": {
32
- "build": "tsc",
33
+ "build": "tsup",
33
34
  "dev": "tsx src/index.ts",
34
35
  "lint": "eslint src",
35
36
  "lint:fix": "eslint src --fix",
@@ -1,3 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare function directory(parent: Command): void;
3
- //# sourceMappingURL=directory.d.ts.map