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.
- package/dist/index.js +287 -147
- 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 [
|
|
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(
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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 <
|
|
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 (
|
|
503
|
+
).action(async (employeeId, opts) => {
|
|
353
504
|
const client = getClient();
|
|
354
505
|
const result = await client.goals.get({
|
|
355
|
-
employeeId
|
|
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 <
|
|
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 (
|
|
524
|
+
).action(async (employeeId, opts) => {
|
|
374
525
|
const client = getClient();
|
|
375
526
|
const buffer = await client.employees.getPhoto({
|
|
376
|
-
employeeId
|
|
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(
|
|
443
|
-
const employee =
|
|
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(
|
|
488
|
-
const file =
|
|
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(
|
|
541
|
-
const meta =
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
719
|
-
const timeoff =
|
|
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/
|
|
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
|
-
|
|
753
|
-
program
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
-
|
|
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
|
-
|
|
786
|
-
});
|
|
787
|
-
registerEmployeeCommands(program);
|
|
788
|
-
registerTimeoffCommands(program);
|
|
789
|
-
registerFileCommands(program);
|
|
790
|
-
registerMetaCommands(program);
|
|
791
|
-
|
|
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": "
|
|
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-
|
|
27
|
-
"config-
|
|
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
|
|
38
|
+
"test:integration": "vitest run --config vitest.integration.config.ts"
|
|
39
39
|
}
|
|
40
40
|
}
|