bamboohr-cli 1.0.17 → 2.0.0-g40cd2b1.10
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 +436 -306
- 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,263 @@ 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/builders.js
|
|
72
|
+
var EXAMPLES = /* @__PURE__ */ new WeakSet();
|
|
73
|
+
function commandPath(cmd) {
|
|
74
|
+
const parts = [];
|
|
75
|
+
for (let c = cmd; c; c = c.parent)
|
|
76
|
+
parts.unshift(c.name());
|
|
77
|
+
return parts.join(" ");
|
|
78
|
+
}
|
|
79
|
+
function exampleLine(path, entry) {
|
|
80
|
+
if (typeof entry === "string")
|
|
81
|
+
return entry ? ` ${path} ${entry}` : ` ${path}`;
|
|
82
|
+
if ("raw" in entry)
|
|
83
|
+
return ` ${entry.raw}`;
|
|
84
|
+
const [args, comment] = entry;
|
|
85
|
+
const line = args ? `${path} ${args}` : path;
|
|
86
|
+
return ` ${line} # ${comment}`;
|
|
87
|
+
}
|
|
88
|
+
function examples(cmd, entries) {
|
|
89
|
+
EXAMPLES.add(cmd);
|
|
90
|
+
return cmd.addHelpText("after", () => {
|
|
91
|
+
const path = commandPath(cmd);
|
|
92
|
+
return `
|
|
93
|
+
Examples:
|
|
94
|
+
${entries.map((e) => exampleLine(path, e)).join("\n")}`;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ../../cli-utils/dist/errors.js
|
|
99
|
+
import { CommanderError } from "commander";
|
|
100
|
+
var EXIT = {
|
|
101
|
+
SUCCESS: 0,
|
|
102
|
+
GENERIC: 1,
|
|
103
|
+
USAGE: 2,
|
|
104
|
+
NOT_FOUND: 3,
|
|
105
|
+
FORBIDDEN: 4,
|
|
106
|
+
CONFLICT: 5,
|
|
107
|
+
AUTH: 6
|
|
108
|
+
};
|
|
109
|
+
var CliAuthError = class extends Error {
|
|
110
|
+
constructor(message) {
|
|
111
|
+
super(message);
|
|
112
|
+
this.name = "CliAuthError";
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var TYPE_EXIT = {
|
|
116
|
+
usage: EXIT.USAGE,
|
|
117
|
+
not_found: EXIT.NOT_FOUND,
|
|
118
|
+
forbidden: EXIT.FORBIDDEN,
|
|
119
|
+
conflict: EXIT.CONFLICT,
|
|
120
|
+
auth: EXIT.AUTH,
|
|
121
|
+
rate_limited: EXIT.GENERIC,
|
|
122
|
+
server: EXIT.GENERIC,
|
|
123
|
+
network: EXIT.GENERIC,
|
|
124
|
+
unknown: EXIT.GENERIC
|
|
125
|
+
};
|
|
126
|
+
var NETWORK_CODES = ["ENOTFOUND", "ECONNREFUSED", "ECONNRESET", "ETIMEDOUT"];
|
|
127
|
+
function httpStatus(err) {
|
|
128
|
+
const e = err;
|
|
129
|
+
return e?.response?.status ?? e?.statusCode;
|
|
130
|
+
}
|
|
131
|
+
function responseDetail(err) {
|
|
132
|
+
const e = err;
|
|
133
|
+
const data = e?.response?.data;
|
|
134
|
+
if (data && typeof data === "object")
|
|
135
|
+
return data;
|
|
136
|
+
const body = e?.body;
|
|
137
|
+
if (typeof body === "string") {
|
|
138
|
+
try {
|
|
139
|
+
const parsed = JSON.parse(body);
|
|
140
|
+
return parsed.error ?? parsed;
|
|
141
|
+
} catch {
|
|
142
|
+
return void 0;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (body && typeof body === "object") {
|
|
146
|
+
return body.error ?? body;
|
|
147
|
+
}
|
|
148
|
+
return void 0;
|
|
149
|
+
}
|
|
150
|
+
function errorCode(err) {
|
|
151
|
+
return err?.code;
|
|
152
|
+
}
|
|
153
|
+
function normalize(err, opts) {
|
|
154
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
155
|
+
if (err instanceof CommanderError) {
|
|
156
|
+
return {
|
|
157
|
+
type: "usage",
|
|
158
|
+
message: message || "Invalid command usage",
|
|
159
|
+
recovery: "Check the command syntax and flags; run the command with --help.",
|
|
160
|
+
retryable: false
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (err instanceof CliAuthError) {
|
|
164
|
+
return { type: "auth", message: message || "Missing credentials", recovery: opts.authRecovery, retryable: false };
|
|
165
|
+
}
|
|
166
|
+
const status = httpStatus(err);
|
|
167
|
+
const detail = responseDetail(err);
|
|
168
|
+
if (status !== void 0) {
|
|
169
|
+
switch (status) {
|
|
170
|
+
case 400:
|
|
171
|
+
return {
|
|
172
|
+
type: "usage",
|
|
173
|
+
status,
|
|
174
|
+
message: "Bad request (HTTP 400)",
|
|
175
|
+
recovery: "Check parameter values (ids, keys, query syntax) against the API.",
|
|
176
|
+
retryable: false,
|
|
177
|
+
detail
|
|
178
|
+
};
|
|
179
|
+
case 401:
|
|
180
|
+
return {
|
|
181
|
+
type: "auth",
|
|
182
|
+
status,
|
|
183
|
+
message: "Authentication failed (HTTP 401)",
|
|
184
|
+
recovery: opts.authRecovery,
|
|
185
|
+
retryable: false
|
|
186
|
+
};
|
|
187
|
+
case 403:
|
|
188
|
+
return {
|
|
189
|
+
type: "forbidden",
|
|
190
|
+
status,
|
|
191
|
+
message: "Forbidden (HTTP 403)",
|
|
192
|
+
recovery: `Your ${opts.service} account lacks permission for this operation; check token scope and resource permissions.`,
|
|
193
|
+
retryable: false,
|
|
194
|
+
detail
|
|
195
|
+
};
|
|
196
|
+
case 404:
|
|
197
|
+
return {
|
|
198
|
+
type: "not_found",
|
|
199
|
+
status,
|
|
200
|
+
message: "Not found (HTTP 404)",
|
|
201
|
+
recovery: "Verify the id/key exists and that you have access to it.",
|
|
202
|
+
retryable: false,
|
|
203
|
+
detail
|
|
204
|
+
};
|
|
205
|
+
case 409:
|
|
206
|
+
return {
|
|
207
|
+
type: "conflict",
|
|
208
|
+
status,
|
|
209
|
+
message: "Conflict (HTTP 409)",
|
|
210
|
+
recovery: "The resource changed or already exists; re-fetch current state and retry.",
|
|
211
|
+
retryable: false,
|
|
212
|
+
detail
|
|
213
|
+
};
|
|
214
|
+
case 429:
|
|
215
|
+
return {
|
|
216
|
+
type: "rate_limited",
|
|
217
|
+
status,
|
|
218
|
+
message: "Rate limited (HTTP 429)",
|
|
219
|
+
recovery: "Wait and retry the request.",
|
|
220
|
+
retryable: true
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (status >= 500) {
|
|
224
|
+
return {
|
|
225
|
+
type: "server",
|
|
226
|
+
status,
|
|
227
|
+
message: `Server error (HTTP ${status})`,
|
|
228
|
+
recovery: `${opts.service} returned an internal error; retry shortly.`,
|
|
229
|
+
retryable: true,
|
|
230
|
+
detail
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
type: "unknown",
|
|
235
|
+
status,
|
|
236
|
+
message: `${opts.service} error (HTTP ${status}): ${message}`,
|
|
237
|
+
recovery: "Inspect the detail field for the API response.",
|
|
238
|
+
retryable: false,
|
|
239
|
+
detail
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const code = errorCode(err);
|
|
243
|
+
if (code && NETWORK_CODES.includes(code) || NETWORK_CODES.some((c) => message.includes(c))) {
|
|
244
|
+
return {
|
|
245
|
+
type: "network",
|
|
246
|
+
message: `Cannot connect to ${opts.service}: ${message}`,
|
|
247
|
+
recovery: opts.networkRecovery ?? "Verify the *_URL is correct, the server is reachable, and you are on the VPN if required.",
|
|
248
|
+
retryable: true
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return { type: "unknown", message, recovery: "Unexpected error; inspect the message.", retryable: false };
|
|
252
|
+
}
|
|
253
|
+
function classifyError(err, opts) {
|
|
254
|
+
const base = normalize(err, opts);
|
|
255
|
+
const n = opts.adapt ? opts.adapt(base, err) : base;
|
|
256
|
+
if (n.type === "auth" && n.detail === void 0 && opts.credentialInfo) {
|
|
257
|
+
n.detail = opts.credentialInfo();
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
envelope: {
|
|
261
|
+
error: {
|
|
262
|
+
type: n.type,
|
|
263
|
+
message: n.message,
|
|
264
|
+
recovery: n.recovery,
|
|
265
|
+
retryable: n.retryable,
|
|
266
|
+
...n.detail !== void 0 ? { detail: n.detail } : {}
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
exitCode: TYPE_EXIT[n.type]
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function createErrorHandler(opts) {
|
|
273
|
+
return (err) => {
|
|
274
|
+
const { envelope, exitCode } = classifyError(err, opts);
|
|
275
|
+
process.stderr.write(`${JSON.stringify(envelope)}
|
|
276
|
+
`);
|
|
277
|
+
return process.exit(exitCode);
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function isCleanCommanderExit(err) {
|
|
281
|
+
return err instanceof CommanderError && (err.exitCode === 0 || err.code === "commander.helpDisplayed" || err.code === "commander.help" || err.code === "commander.version");
|
|
282
|
+
}
|
|
283
|
+
function routeErrors(cmd) {
|
|
284
|
+
cmd.exitOverride();
|
|
285
|
+
cmd.configureOutput({ writeErr: () => void 0 });
|
|
286
|
+
cmd.commands.forEach(routeErrors);
|
|
287
|
+
}
|
|
288
|
+
async function runCli(program, opts) {
|
|
289
|
+
routeErrors(program);
|
|
290
|
+
try {
|
|
291
|
+
await program.parseAsync();
|
|
292
|
+
} catch (err) {
|
|
293
|
+
if (isCleanCommanderExit(err)) {
|
|
294
|
+
process.exit(err.exitCode ?? 0);
|
|
295
|
+
}
|
|
296
|
+
createErrorHandler(opts)(err);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/program.ts
|
|
301
|
+
import { styleText } from "util";
|
|
302
|
+
import { Command as Command6 } from "commander";
|
|
303
|
+
|
|
54
304
|
// src/utils/cached.ts
|
|
55
305
|
var CACHE_NAME = "bamboohr";
|
|
56
306
|
var TWELVE_HOURS = 432e5;
|
|
@@ -67,41 +317,12 @@ function getMetaFields(client) {
|
|
|
67
317
|
|
|
68
318
|
// src/utils/client.ts
|
|
69
319
|
import { BambooHRClient } from "bamboohr-client";
|
|
70
|
-
|
|
71
|
-
// src/utils/credentials.ts
|
|
72
|
-
function getCredentialInfo() {
|
|
73
|
-
const companyDomain = process.env.BAMBOO_COMPANY_DOMAIN;
|
|
74
|
-
const token = process.env.BAMBOO_TOKEN;
|
|
75
|
-
return {
|
|
76
|
-
environment: {
|
|
77
|
-
BAMBOO_COMPANY_DOMAIN: {
|
|
78
|
-
value: companyDomain ?? null,
|
|
79
|
-
description: 'Company subdomain (e.g., "mycompany" for mycompany.bamboohr.com)'
|
|
80
|
-
},
|
|
81
|
-
BAMBOO_TOKEN: {
|
|
82
|
-
value: token ? "<set>" : null,
|
|
83
|
-
description: "BambooHR API token"
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
tokenUrl: `https://${companyDomain ?? "<companyDomain>"}.bamboohr.com/app/settings/permissions/api_keys`,
|
|
87
|
-
hint: "Export environment variables in your shell profile (e.g., ~/.zshrc)."
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// src/utils/client.ts
|
|
92
320
|
function getClient() {
|
|
93
321
|
const apiToken = process.env.BAMBOO_TOKEN;
|
|
94
322
|
const companyDomain = process.env.BAMBOO_COMPANY_DOMAIN;
|
|
95
323
|
if (!apiToken || !companyDomain) {
|
|
96
324
|
const missing = [...!apiToken ? ["BAMBOO_TOKEN"] : [], ...!companyDomain ? ["BAMBOO_COMPANY_DOMAIN"] : []];
|
|
97
|
-
|
|
98
|
-
`${JSON.stringify({
|
|
99
|
-
error: `Missing required environment variables: ${missing.join(", ")}`,
|
|
100
|
-
...getCredentialInfo()
|
|
101
|
-
})}
|
|
102
|
-
`
|
|
103
|
-
);
|
|
104
|
-
process.exit(1);
|
|
325
|
+
throw new CliAuthError(`Missing required environment variables: ${missing.join(", ")}`);
|
|
105
326
|
}
|
|
106
327
|
return new BambooHRClient({ apiToken, companyDomain });
|
|
107
328
|
}
|
|
@@ -115,68 +336,6 @@ function output(data) {
|
|
|
115
336
|
process.stdout.write(`${JSON.stringify(data, null, prettyPrint ? 2 : void 0)}
|
|
116
337
|
`);
|
|
117
338
|
}
|
|
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
339
|
|
|
181
340
|
// src/utils/transformers/base.ts
|
|
182
341
|
function bamboohrBaseUrl() {
|
|
@@ -296,13 +455,9 @@ function transformFields(fields2) {
|
|
|
296
455
|
|
|
297
456
|
// src/commands/employee/directory.ts
|
|
298
457
|
function directory(parent) {
|
|
299
|
-
parent.command("directory").description("Get the company employee directory").option("--with-photos", "Include each employee's signed photo URL and photoUploaded flag (large)")
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
Examples:
|
|
303
|
-
$ bamboohr employee directory
|
|
304
|
-
$ bamboohr employee directory --with-photos`
|
|
305
|
-
).action(async (opts) => {
|
|
458
|
+
const cmd = parent.command("directory").description("Get the company employee directory").option("--with-photos", "Include each employee's signed photo URL and photoUploaded flag (large)");
|
|
459
|
+
examples(cmd, ["", "--with-photos"]);
|
|
460
|
+
cmd.action(async (opts) => {
|
|
306
461
|
const client = getClient();
|
|
307
462
|
const result = await getDirectory(client);
|
|
308
463
|
output(transformEmployeeDirectory(result, { withPhotos: opts.withPhotos }));
|
|
@@ -311,25 +466,26 @@ Examples:
|
|
|
311
466
|
|
|
312
467
|
// src/commands/employee/get.ts
|
|
313
468
|
function get(parent) {
|
|
314
|
-
parent.command("get [
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
469
|
+
const cmd = 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");
|
|
470
|
+
examples(cmd, [
|
|
471
|
+
"",
|
|
472
|
+
"123",
|
|
473
|
+
['123 --fields "firstName,lastName,department,hireDate"', "custom fields"],
|
|
474
|
+
"123 --include-future",
|
|
475
|
+
"123 --with-photos"
|
|
476
|
+
]);
|
|
477
|
+
cmd.action(
|
|
478
|
+
async (employeeId, opts) => {
|
|
479
|
+
const client = getClient();
|
|
480
|
+
const fields2 = opts.withPhotos ? mergeFields(opts.fields, ["photoUrl", "photoUploaded"]) : opts.fields;
|
|
481
|
+
const result = await client.employees.get({
|
|
482
|
+
id: employeeId ?? "0",
|
|
483
|
+
fields: fields2,
|
|
484
|
+
onlyCurrent: !opts.includeFuture
|
|
485
|
+
});
|
|
486
|
+
output(transformEmployee(result, { withPhotos: opts.withPhotos }));
|
|
487
|
+
}
|
|
488
|
+
);
|
|
333
489
|
}
|
|
334
490
|
function mergeFields(existing, extra) {
|
|
335
491
|
const set = new Set(
|
|
@@ -343,16 +499,12 @@ function mergeFields(existing, extra) {
|
|
|
343
499
|
import { Option } from "commander";
|
|
344
500
|
var GOAL_FILTERS = ["open", "closed", "all"];
|
|
345
501
|
function goals(parent) {
|
|
346
|
-
parent.command("goals <
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
Examples:
|
|
350
|
-
$ bamboohr employee goals 123
|
|
351
|
-
$ bamboohr employee goals 123 --filter open`
|
|
352
|
-
).action(async (id, opts) => {
|
|
502
|
+
const cmd = parent.command("goals <employeeId>").description("Get employee performance goals").addOption(new Option("--filter <filter>", "Filter goals by status").choices(GOAL_FILTERS).default("all"));
|
|
503
|
+
examples(cmd, ["123", ["123 --filter open", "only open goals"], ["123 --filter closed", "only closed goals"]]);
|
|
504
|
+
cmd.action(async (employeeId, opts) => {
|
|
353
505
|
const client = getClient();
|
|
354
506
|
const result = await client.goals.get({
|
|
355
|
-
employeeId
|
|
507
|
+
employeeId,
|
|
356
508
|
filter: opts.filter === "all" ? void 0 : opts.filter
|
|
357
509
|
});
|
|
358
510
|
output(transformGoals(result));
|
|
@@ -364,16 +516,12 @@ import { writeFileSync as writeFileSync2 } from "fs";
|
|
|
364
516
|
import { Option as Option2 } from "commander";
|
|
365
517
|
var PHOTO_SIZES = ["original", "large", "medium", "small", "xs", "tiny"];
|
|
366
518
|
function photo(parent) {
|
|
367
|
-
parent.command("photo <
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
Examples:
|
|
371
|
-
$ bamboohr employee photo 123 --output ./photo.jpg
|
|
372
|
-
$ bamboohr employee photo 123 --output ./photo.jpg --size large`
|
|
373
|
-
).action(async (id, opts) => {
|
|
519
|
+
const cmd = 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"));
|
|
520
|
+
examples(cmd, [["123 --output ./photo.jpg", "medium size (default)"], "123 --output ./photo.jpg --size large"]);
|
|
521
|
+
cmd.action(async (employeeId, opts) => {
|
|
374
522
|
const client = getClient();
|
|
375
523
|
const buffer = await client.employees.getPhoto({
|
|
376
|
-
employeeId
|
|
524
|
+
employeeId,
|
|
377
525
|
size: opts.size
|
|
378
526
|
});
|
|
379
527
|
writeFileSync2(opts.output, buffer);
|
|
@@ -390,16 +538,15 @@ async function resolveEmployeeId(client, employeeId) {
|
|
|
390
538
|
|
|
391
539
|
// src/commands/employee/search.ts
|
|
392
540
|
function search(parent) {
|
|
393
|
-
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").option("--with-photos", "Include each result's signed photo URL and photoUploaded flag")
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
).action(
|
|
541
|
+
const cmd = 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").option("--with-photos", "Include each result's signed photo URL and photoUploaded flag");
|
|
542
|
+
examples(cmd, [
|
|
543
|
+
"john",
|
|
544
|
+
"--supervisor me",
|
|
545
|
+
['--department "R&D" --location Sofia', "filter by dept and location"],
|
|
546
|
+
'--supervisor "Borislav Mihaylov"',
|
|
547
|
+
"john --with-photos"
|
|
548
|
+
]);
|
|
549
|
+
cmd.action(
|
|
403
550
|
async (query, opts) => {
|
|
404
551
|
const client = getClient();
|
|
405
552
|
const dir = await getDirectory(client);
|
|
@@ -439,18 +586,15 @@ Examples:
|
|
|
439
586
|
}
|
|
440
587
|
|
|
441
588
|
// src/commands/employee/index.ts
|
|
442
|
-
function registerEmployeeCommands(
|
|
443
|
-
const employee =
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
$ bamboohr employee goals 123 --filter open
|
|
452
|
-
`
|
|
453
|
-
);
|
|
589
|
+
function registerEmployeeCommands(program) {
|
|
590
|
+
const employee = program.command("employee").description("Employee operations");
|
|
591
|
+
examples(employee, [
|
|
592
|
+
"get",
|
|
593
|
+
['get 123 --fields "firstName,lastName,department"', "custom fields"],
|
|
594
|
+
"directory",
|
|
595
|
+
["photo 123 --output ./photo.jpg", "download photo"],
|
|
596
|
+
["goals 123 --filter open", "open goals"]
|
|
597
|
+
]);
|
|
454
598
|
get(employee);
|
|
455
599
|
search(employee);
|
|
456
600
|
directory(employee);
|
|
@@ -461,12 +605,9 @@ Examples:
|
|
|
461
605
|
// src/commands/file/get.ts
|
|
462
606
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
463
607
|
function get2(parent) {
|
|
464
|
-
parent.command("get <fileId>").description("Download a company file").requiredOption("--output <path>", "File path to save to")
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
Examples:
|
|
468
|
-
$ bamboohr file get 42 --output ./handbook.pdf`
|
|
469
|
-
).action(async (fileId, opts) => {
|
|
608
|
+
const cmd = parent.command("get <fileId>").description("Download a company file").requiredOption("--output <path>", "File path to save to");
|
|
609
|
+
examples(cmd, ["42 --output ./handbook.pdf"]);
|
|
610
|
+
cmd.action(async (fileId, opts) => {
|
|
470
611
|
const client = getClient();
|
|
471
612
|
const buffer = await client.files.get({ fileId });
|
|
472
613
|
writeFileSync3(opts.output, buffer);
|
|
@@ -476,7 +617,9 @@ Examples:
|
|
|
476
617
|
|
|
477
618
|
// src/commands/file/list.ts
|
|
478
619
|
function list(parent) {
|
|
479
|
-
parent.command("list").description("List all company files and categories")
|
|
620
|
+
const cmd = parent.command("list").description("List all company files and categories");
|
|
621
|
+
examples(cmd, [""]);
|
|
622
|
+
cmd.action(async () => {
|
|
480
623
|
const client = getClient();
|
|
481
624
|
const result = await client.files.list();
|
|
482
625
|
output(transformCompanyFiles(result));
|
|
@@ -484,22 +627,18 @@ function list(parent) {
|
|
|
484
627
|
}
|
|
485
628
|
|
|
486
629
|
// src/commands/file/index.ts
|
|
487
|
-
function registerFileCommands(
|
|
488
|
-
const file =
|
|
489
|
-
|
|
490
|
-
`
|
|
491
|
-
Examples:
|
|
492
|
-
$ bamboohr file list
|
|
493
|
-
$ bamboohr file get 42 --output ./handbook.pdf
|
|
494
|
-
`
|
|
495
|
-
);
|
|
630
|
+
function registerFileCommands(program) {
|
|
631
|
+
const file = program.command("file").description("Company file operations");
|
|
632
|
+
examples(file, ["list", ["get 42 --output ./handbook.pdf", "download a file"]]);
|
|
496
633
|
list(file);
|
|
497
634
|
get2(file);
|
|
498
635
|
}
|
|
499
636
|
|
|
500
637
|
// src/commands/meta/departments.ts
|
|
501
638
|
function departments(parent) {
|
|
502
|
-
parent.command("departments").description("List all departments")
|
|
639
|
+
const cmd = parent.command("departments").description("List all departments");
|
|
640
|
+
examples(cmd, [""]);
|
|
641
|
+
cmd.action(async () => {
|
|
503
642
|
const client = getClient();
|
|
504
643
|
const dir = await getDirectory(client);
|
|
505
644
|
const unique = [...new Set(dir.employees.map((e) => e.department).filter(Boolean))].sort();
|
|
@@ -509,7 +648,9 @@ function departments(parent) {
|
|
|
509
648
|
|
|
510
649
|
// src/commands/meta/divisions.ts
|
|
511
650
|
function divisions(parent) {
|
|
512
|
-
parent.command("divisions").description("List all divisions")
|
|
651
|
+
const cmd = parent.command("divisions").description("List all divisions");
|
|
652
|
+
examples(cmd, [""]);
|
|
653
|
+
cmd.action(async () => {
|
|
513
654
|
const client = getClient();
|
|
514
655
|
const dir = await getDirectory(client);
|
|
515
656
|
const unique = [...new Set(dir.employees.map((e) => e.division).filter(Boolean))].sort();
|
|
@@ -519,7 +660,9 @@ function divisions(parent) {
|
|
|
519
660
|
|
|
520
661
|
// src/commands/meta/fields.ts
|
|
521
662
|
function fields(parent) {
|
|
522
|
-
parent.command("fields").description("List all available BambooHR fields")
|
|
663
|
+
const cmd = parent.command("fields").description("List all available BambooHR fields");
|
|
664
|
+
examples(cmd, [""]);
|
|
665
|
+
cmd.action(async () => {
|
|
523
666
|
const client = getClient();
|
|
524
667
|
const result = await getMetaFields(client);
|
|
525
668
|
output(transformFields(result));
|
|
@@ -528,7 +671,9 @@ function fields(parent) {
|
|
|
528
671
|
|
|
529
672
|
// src/commands/meta/locations.ts
|
|
530
673
|
function locations(parent) {
|
|
531
|
-
parent.command("locations").description("List all office locations")
|
|
674
|
+
const cmd = parent.command("locations").description("List all office locations");
|
|
675
|
+
examples(cmd, [""]);
|
|
676
|
+
cmd.action(async () => {
|
|
532
677
|
const client = getClient();
|
|
533
678
|
const dir = await getDirectory(client);
|
|
534
679
|
const unique = [...new Set(dir.employees.map((e) => e.location).filter(Boolean))].sort();
|
|
@@ -537,17 +682,9 @@ function locations(parent) {
|
|
|
537
682
|
}
|
|
538
683
|
|
|
539
684
|
// src/commands/meta/index.ts
|
|
540
|
-
function registerMetaCommands(
|
|
541
|
-
const meta =
|
|
542
|
-
|
|
543
|
-
`
|
|
544
|
-
Examples:
|
|
545
|
-
$ bamboohr meta fields
|
|
546
|
-
$ bamboohr meta departments
|
|
547
|
-
$ bamboohr meta locations
|
|
548
|
-
$ bamboohr meta divisions
|
|
549
|
-
`
|
|
550
|
-
);
|
|
685
|
+
function registerMetaCommands(program) {
|
|
686
|
+
const meta = program.command("meta").description("Metadata operations");
|
|
687
|
+
examples(meta, ["fields", "departments", "locations", "divisions"]);
|
|
551
688
|
fields(meta);
|
|
552
689
|
departments(meta);
|
|
553
690
|
locations(meta);
|
|
@@ -556,14 +693,9 @@ Examples:
|
|
|
556
693
|
|
|
557
694
|
// src/commands/timeoff/balance.ts
|
|
558
695
|
function balance(parent) {
|
|
559
|
-
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)")
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
Examples:
|
|
563
|
-
$ bamboohr timeoff balance
|
|
564
|
-
$ bamboohr timeoff balance 123
|
|
565
|
-
$ bamboohr timeoff balance --date 2026-06-30`
|
|
566
|
-
).action(async (employeeId, opts) => {
|
|
696
|
+
const cmd = 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)");
|
|
697
|
+
examples(cmd, ["", "123", ["--date 2026-06-30", "balance at a future date"]]);
|
|
698
|
+
cmd.action(async (employeeId, opts) => {
|
|
567
699
|
const client = getClient();
|
|
568
700
|
const id = await resolveEmployeeId(client, employeeId);
|
|
569
701
|
const result = await client.timeOff.getBalance({ employeeId: id, date: opts.date });
|
|
@@ -573,28 +705,18 @@ Examples:
|
|
|
573
705
|
|
|
574
706
|
// src/commands/timeoff/create.ts
|
|
575
707
|
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
708
|
function create(parent) {
|
|
589
|
-
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(
|
|
709
|
+
const cmd = 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
710
|
new Option3("--status <status>", "Request status").choices(["requested", "approved"]).default("requested")
|
|
591
|
-
).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`)
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
711
|
+
).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`);
|
|
712
|
+
examples(cmd, [
|
|
713
|
+
"--start-date 2026-04-01 --end-date 2026-04-01 --type-id 83 --amount 1",
|
|
714
|
+
[
|
|
715
|
+
'504 --start-date 2026-04-01 --end-date 2026-04-01 --type-id 83 --amount 1 --note "Doctor appointment"',
|
|
716
|
+
"for another employee"
|
|
717
|
+
]
|
|
718
|
+
]);
|
|
719
|
+
cmd.action(
|
|
598
720
|
async (employeeId, opts) => {
|
|
599
721
|
const client = getClient();
|
|
600
722
|
const id = await resolveEmployeeId(client, employeeId);
|
|
@@ -618,7 +740,7 @@ Examples:
|
|
|
618
740
|
// src/commands/timeoff/requests.ts
|
|
619
741
|
import { Option as Option4 } from "commander";
|
|
620
742
|
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(
|
|
743
|
+
const cmd = 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
744
|
new Option4("--status <status>", "Filter by request status").choices([
|
|
623
745
|
"approved",
|
|
624
746
|
"denied",
|
|
@@ -626,32 +748,31 @@ function requests(parent) {
|
|
|
626
748
|
"requested",
|
|
627
749
|
"canceled"
|
|
628
750
|
])
|
|
629
|
-
).option("--type <typeId>", "Filter by time off type ID")
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
).action(
|
|
751
|
+
).option("--type-id <typeId>", "Filter by time off type ID");
|
|
752
|
+
examples(cmd, [
|
|
753
|
+
"",
|
|
754
|
+
"--status requested",
|
|
755
|
+
["--all --action approve --status requested", "all pending approvals"],
|
|
756
|
+
["--employee-id 123 --start-date 2026-01-01 --end-date 2026-03-31", "Q1 for employee 123"]
|
|
757
|
+
]);
|
|
758
|
+
cmd.action(
|
|
638
759
|
async (opts) => {
|
|
639
760
|
const year = (/* @__PURE__ */ new Date()).getFullYear();
|
|
640
761
|
const start = opts.startDate ?? `${year}-01-01`;
|
|
641
762
|
const end = opts.endDate ?? `${year}-12-31`;
|
|
642
763
|
const client = getClient();
|
|
643
|
-
let employeeId = opts.
|
|
764
|
+
let employeeId = opts.employeeId;
|
|
644
765
|
if (!employeeId && !opts.all) {
|
|
645
766
|
employeeId = await resolveEmployeeId(client);
|
|
646
767
|
}
|
|
647
768
|
const result = await client.timeOff.getRequests({
|
|
648
|
-
id: opts.
|
|
769
|
+
id: opts.requestId,
|
|
649
770
|
action: opts.action,
|
|
650
771
|
employeeId,
|
|
651
772
|
start,
|
|
652
773
|
end,
|
|
653
774
|
status: opts.status,
|
|
654
|
-
type: opts.
|
|
775
|
+
type: opts.typeId
|
|
655
776
|
});
|
|
656
777
|
output(transformTimeOffRequests(result));
|
|
657
778
|
}
|
|
@@ -660,13 +781,9 @@ Examples:
|
|
|
660
781
|
|
|
661
782
|
// src/commands/timeoff/types.ts
|
|
662
783
|
function types(parent) {
|
|
663
|
-
parent.command("types").description("List available time off types").option("--requestable", "Only show types the user can request")
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
Examples:
|
|
667
|
-
$ bamboohr timeoff types
|
|
668
|
-
$ bamboohr timeoff types --requestable`
|
|
669
|
-
).action(async (opts) => {
|
|
784
|
+
const cmd = parent.command("types").description("List available time off types").option("--requestable", "Only show types the user can request");
|
|
785
|
+
examples(cmd, ["", ["--requestable", "only types the current user can request"]]);
|
|
786
|
+
cmd.action(async (opts) => {
|
|
670
787
|
const client = getClient();
|
|
671
788
|
const result = opts.requestable ? await client.timeOff.getTypes({ mode: "request" }) : await getTimeOffTypes(client);
|
|
672
789
|
output(transformTimeOffTypesResponse(result));
|
|
@@ -676,16 +793,15 @@ Examples:
|
|
|
676
793
|
// src/commands/timeoff/update-status.ts
|
|
677
794
|
import { Option as Option5 } from "commander";
|
|
678
795
|
function updateStatus(parent) {
|
|
679
|
-
parent.command("update-status <requestId>").description("Update time off request status (approve, deny, cancel)").addOption(
|
|
796
|
+
const cmd = parent.command("update-status <requestId>").description("Update time off request status (approve, deny, cancel)").addOption(
|
|
680
797
|
new Option5("--status <status>", "New status").choices(["approved", "denied", "canceled"]).makeOptionMandatory()
|
|
681
|
-
).option("--note <text>", "Note to attach to the status change")
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
).action(async (requestId, opts) => {
|
|
798
|
+
).option("--note <text>", "Note to attach to the status change");
|
|
799
|
+
examples(cmd, [
|
|
800
|
+
"7890 --status approved",
|
|
801
|
+
['7890 --status denied --note "Team at capacity that week"', "deny with note"],
|
|
802
|
+
"7890 --status canceled"
|
|
803
|
+
]);
|
|
804
|
+
cmd.action(async (requestId, opts) => {
|
|
689
805
|
const client = getClient();
|
|
690
806
|
const result = await client.timeOff.updateRequestStatus({
|
|
691
807
|
requestId,
|
|
@@ -698,13 +814,9 @@ Examples:
|
|
|
698
814
|
|
|
699
815
|
// src/commands/timeoff/whos-out.ts
|
|
700
816
|
function whosOut(parent) {
|
|
701
|
-
parent.command("whos-out").description("View who's out \u2014 upcoming time off and holidays").option("--start-date <date>", "Start date (YYYY-MM-DD, defaults to today)").option("--end-date <date>", "End date (YYYY-MM-DD, defaults to today)")
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
Examples:
|
|
705
|
-
$ bamboohr timeoff whos-out
|
|
706
|
-
$ bamboohr timeoff whos-out --start-date 2026-03-20 --end-date 2026-04-03`
|
|
707
|
-
).action(async (opts) => {
|
|
817
|
+
const cmd = parent.command("whos-out").description("View who's out \u2014 upcoming time off and holidays").option("--start-date <date>", "Start date (YYYY-MM-DD, defaults to today)").option("--end-date <date>", "End date (YYYY-MM-DD, defaults to today)");
|
|
818
|
+
examples(cmd, ["", ["--start-date 2026-03-20 --end-date 2026-04-03", "two-week window"]]);
|
|
819
|
+
cmd.action(async (opts) => {
|
|
708
820
|
const client = getClient();
|
|
709
821
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
710
822
|
const start = opts.startDate ?? today;
|
|
@@ -715,19 +827,16 @@ Examples:
|
|
|
715
827
|
}
|
|
716
828
|
|
|
717
829
|
// src/commands/timeoff/index.ts
|
|
718
|
-
function registerTimeoffCommands(
|
|
719
|
-
const timeoff =
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
$ bamboohr timeoff update-status 7890 --status approved
|
|
729
|
-
`
|
|
730
|
-
);
|
|
830
|
+
function registerTimeoffCommands(program) {
|
|
831
|
+
const timeoff = program.command("timeoff").description("Time off operations");
|
|
832
|
+
examples(timeoff, [
|
|
833
|
+
"types --requestable",
|
|
834
|
+
"create 504 --start-date 2026-04-01 --end-date 2026-04-01 --type-id 83 --amount 1",
|
|
835
|
+
["requests --status requested --start-date 2026-01-01 --end-date 2026-12-31", "pending requests in date range"],
|
|
836
|
+
"balance 504",
|
|
837
|
+
"whos-out",
|
|
838
|
+
"update-status 7890 --status approved"
|
|
839
|
+
]);
|
|
731
840
|
types(timeoff);
|
|
732
841
|
create(timeoff);
|
|
733
842
|
requests(timeoff);
|
|
@@ -736,31 +845,22 @@ Examples:
|
|
|
736
845
|
updateStatus(timeoff);
|
|
737
846
|
}
|
|
738
847
|
|
|
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
|
-
}
|
|
848
|
+
// src/program.ts
|
|
750
849
|
var DIM = "\x1B[2m";
|
|
751
850
|
var RESET = "\x1B[0m";
|
|
752
|
-
|
|
753
|
-
program
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
851
|
+
function buildProgram() {
|
|
852
|
+
const program = new Command6();
|
|
853
|
+
program.name("bamboohr").description("BambooHR CLI").version(readPackageVersion(import.meta.url)).configureHelp({
|
|
854
|
+
styleTitle: (str) => styleText("bold", str),
|
|
855
|
+
styleUsage: (str) => styleText("dim", str),
|
|
856
|
+
styleCommandDescription: (str) => styleText("dim", str),
|
|
857
|
+
styleOptionDescription: (str) => styleText("dim", str),
|
|
858
|
+
styleSubcommandDescription: (str) => styleText("dim", str)
|
|
859
|
+
}).addHelpText("beforeAll", `
|
|
760
860
|
${styleText("bold", "bamboohr")} ${DIM}\u2014 BambooHR CLI${RESET}
|
|
761
861
|
`).addHelpText(
|
|
762
|
-
|
|
763
|
-
|
|
862
|
+
"after",
|
|
863
|
+
`
|
|
764
864
|
${styleText("bold", "Environment:")}
|
|
765
865
|
BAMBOO_TOKEN BambooHR API token ${DIM}(generate in BambooHR > Settings > API Keys)${RESET}
|
|
766
866
|
BAMBOO_COMPANY_DOMAIN Company subdomain ${DIM}(e.g., "mycompany" for mycompany.bamboohr.com)${RESET}
|
|
@@ -772,24 +872,54 @@ ${styleText("bold", "Examples:")}
|
|
|
772
872
|
${DIM}$${RESET} bamboohr employee photo 123 --output ./photo.jpg
|
|
773
873
|
${DIM}$${RESET} bamboohr timeoff types --requestable
|
|
774
874
|
${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
875
|
${DIM}$${RESET} bamboohr timeoff requests --status requested --start-date 2026-01-01 --end-date 2026-12-31
|
|
777
876
|
${DIM}$${RESET} bamboohr timeoff whos-out
|
|
778
877
|
${DIM}$${RESET} bamboohr timeoff update-status 7890 --status approved
|
|
779
878
|
${DIM}$${RESET} bamboohr file list
|
|
780
879
|
${DIM}$${RESET} bamboohr meta fields
|
|
781
880
|
`
|
|
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);
|
|
881
|
+
);
|
|
882
|
+
program.option("--pretty", "Pretty-print JSON output");
|
|
883
|
+
program.hook("preAction", (thisCommand) => {
|
|
884
|
+
if (thisCommand.optsWithGlobals().pretty) setPretty(true);
|
|
885
|
+
});
|
|
886
|
+
registerEmployeeCommands(program);
|
|
887
|
+
registerTimeoffCommands(program);
|
|
888
|
+
registerFileCommands(program);
|
|
889
|
+
registerMetaCommands(program);
|
|
890
|
+
return program;
|
|
795
891
|
}
|
|
892
|
+
|
|
893
|
+
// src/utils/credentials.ts
|
|
894
|
+
function getCredentialInfo() {
|
|
895
|
+
const companyDomain = process.env.BAMBOO_COMPANY_DOMAIN;
|
|
896
|
+
const token = process.env.BAMBOO_TOKEN;
|
|
897
|
+
return {
|
|
898
|
+
environment: {
|
|
899
|
+
BAMBOO_COMPANY_DOMAIN: {
|
|
900
|
+
value: companyDomain ?? null,
|
|
901
|
+
description: 'Company subdomain (e.g., "mycompany" for mycompany.bamboohr.com)'
|
|
902
|
+
},
|
|
903
|
+
BAMBOO_TOKEN: {
|
|
904
|
+
value: token ? "<set>" : null,
|
|
905
|
+
description: "BambooHR API token"
|
|
906
|
+
}
|
|
907
|
+
},
|
|
908
|
+
tokenUrl: `https://${companyDomain ?? "<companyDomain>"}.bamboohr.com/app/settings/permissions/api_keys`
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// src/index.ts
|
|
913
|
+
await runCli(buildProgram(), {
|
|
914
|
+
credentialInfo: getCredentialInfo,
|
|
915
|
+
service: "BambooHR",
|
|
916
|
+
authRecovery: "Set the BAMBOO_TOKEN and BAMBOO_COMPANY_DOMAIN environment variables.",
|
|
917
|
+
networkRecovery: "Verify BAMBOO_COMPANY_DOMAIN is correct and you have internet connectivity.",
|
|
918
|
+
// BambooHR returns HTTP 404 for invalid credentials as well as genuinely
|
|
919
|
+
// missing resources — it cannot distinguish them, so keep not_found but
|
|
920
|
+
// document the ambiguity in the recovery hint (design §4.9).
|
|
921
|
+
adapt: (normalized) => normalized.status === 404 ? {
|
|
922
|
+
...normalized,
|
|
923
|
+
recovery: "BambooHR returns 404 for invalid credentials too \u2014 verify BAMBOO_TOKEN and BAMBOO_COMPANY_DOMAIN, then confirm the resource id exists."
|
|
924
|
+
} : normalized
|
|
925
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bamboohr-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-g40cd2b1.10",
|
|
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
|
}
|