@centry-digital/bukku-cli 2.0.0

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 (53) hide show
  1. package/README.md +266 -0
  2. package/build/commands/config.d.ts +3 -0
  3. package/build/commands/config.d.ts.map +1 -0
  4. package/build/commands/config.js +68 -0
  5. package/build/commands/custom/archive.d.ts +9 -0
  6. package/build/commands/custom/archive.d.ts.map +1 -0
  7. package/build/commands/custom/archive.js +82 -0
  8. package/build/commands/custom/file-upload.d.ts +9 -0
  9. package/build/commands/custom/file-upload.d.ts.map +1 -0
  10. package/build/commands/custom/file-upload.js +47 -0
  11. package/build/commands/custom/journal-entry.d.ts +10 -0
  12. package/build/commands/custom/journal-entry.d.ts.map +1 -0
  13. package/build/commands/custom/journal-entry.js +82 -0
  14. package/build/commands/custom/location-write.d.ts +10 -0
  15. package/build/commands/custom/location-write.d.ts.map +1 -0
  16. package/build/commands/custom/location-write.js +95 -0
  17. package/build/commands/custom/reference-data.d.ts +13 -0
  18. package/build/commands/custom/reference-data.d.ts.map +1 -0
  19. package/build/commands/custom/reference-data.js +95 -0
  20. package/build/commands/custom/search-accounts.d.ts +12 -0
  21. package/build/commands/custom/search-accounts.d.ts.map +1 -0
  22. package/build/commands/custom/search-accounts.js +59 -0
  23. package/build/commands/factory.d.ts +9 -0
  24. package/build/commands/factory.d.ts.map +1 -0
  25. package/build/commands/factory.js +371 -0
  26. package/build/commands/wrapper.d.ts +23 -0
  27. package/build/commands/wrapper.d.ts.map +1 -0
  28. package/build/commands/wrapper.js +60 -0
  29. package/build/config/auth.d.ts +23 -0
  30. package/build/config/auth.d.ts.map +1 -0
  31. package/build/config/auth.js +69 -0
  32. package/build/config/rc.d.ts +20 -0
  33. package/build/config/rc.d.ts.map +1 -0
  34. package/build/config/rc.js +65 -0
  35. package/build/index.d.ts +2 -0
  36. package/build/index.d.ts.map +1 -0
  37. package/build/index.js +1685 -0
  38. package/build/input/json.d.ts +10 -0
  39. package/build/input/json.d.ts.map +1 -0
  40. package/build/input/json.js +42 -0
  41. package/build/output/dry-run.d.ts +21 -0
  42. package/build/output/dry-run.d.ts.map +1 -0
  43. package/build/output/dry-run.js +23 -0
  44. package/build/output/error.d.ts +27 -0
  45. package/build/output/error.d.ts.map +1 -0
  46. package/build/output/error.js +33 -0
  47. package/build/output/json.d.ts +6 -0
  48. package/build/output/json.d.ts.map +1 -0
  49. package/build/output/json.js +7 -0
  50. package/build/output/table.d.ts +13 -0
  51. package/build/output/table.d.ts.map +1 -0
  52. package/build/output/table.js +123 -0
  53. package/package.json +37 -0
@@ -0,0 +1,23 @@
1
+ import type { Command } from 'commander';
2
+ import { BukkuClient } from 'core';
3
+ /**
4
+ * Context passed to commands wrapped by withAuth.
5
+ */
6
+ export interface CommandContext {
7
+ client: BukkuClient;
8
+ opts: Record<string, unknown>;
9
+ auth: {
10
+ apiToken: string;
11
+ companySubdomain: string;
12
+ };
13
+ }
14
+ /**
15
+ * Wrap a command handler with auth resolution and error handling.
16
+ *
17
+ * Resolves credentials via 3-tier precedence (flags > env > rc),
18
+ * creates a BukkuClient, and passes it to the handler.
19
+ *
20
+ * Must use a regular function (not arrow) to preserve Commander's `this` context.
21
+ */
22
+ export declare function withAuth(handler: (ctx: CommandContext) => Promise<void>): (this: Command, ...args: unknown[]) => Promise<void>;
23
+ //# sourceMappingURL=wrapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../../src/commands/wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,MAAM,CAAC;AAInC;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,CAAC;CACtD;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAC9C,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CA6DtD"}
@@ -0,0 +1,60 @@
1
+ import { BukkuClient } from 'core';
2
+ import { resolveAuth, AuthMissingError } from '../config/auth.js';
3
+ import { outputError, ExitCode } from '../output/error.js';
4
+ /**
5
+ * Wrap a command handler with auth resolution and error handling.
6
+ *
7
+ * Resolves credentials via 3-tier precedence (flags > env > rc),
8
+ * creates a BukkuClient, and passes it to the handler.
9
+ *
10
+ * Must use a regular function (not arrow) to preserve Commander's `this` context.
11
+ */
12
+ export function withAuth(handler) {
13
+ return async function () {
14
+ const mergedOpts = this.optsWithGlobals();
15
+ let auth;
16
+ try {
17
+ auth = await resolveAuth({
18
+ apiToken: mergedOpts.apiToken,
19
+ companySubdomain: mergedOpts.companySubdomain,
20
+ });
21
+ }
22
+ catch (err) {
23
+ if (err instanceof AuthMissingError) {
24
+ outputError({
25
+ error: 'Authentication required. Set credentials via --api-token flag, BUKKU_API_TOKEN env var, or bukku config set',
26
+ code: 'AUTH_MISSING',
27
+ details: null,
28
+ }, ExitCode.AUTH);
29
+ }
30
+ throw err;
31
+ }
32
+ const client = new BukkuClient({
33
+ apiToken: auth.apiToken,
34
+ companySubdomain: auth.companySubdomain,
35
+ });
36
+ try {
37
+ await handler({ client, opts: mergedOpts, auth });
38
+ }
39
+ catch (err) {
40
+ if (err instanceof Response) {
41
+ let body = {};
42
+ try {
43
+ body = (await err.json());
44
+ }
45
+ catch {
46
+ // response body not parseable
47
+ }
48
+ outputError({
49
+ error: body['message'] || 'API error',
50
+ code: 'API_ERROR',
51
+ details: body['errors'] || null,
52
+ }, ExitCode.API);
53
+ }
54
+ outputError({
55
+ error: err instanceof Error ? err.message : 'Unknown error',
56
+ code: 'GENERAL_ERROR',
57
+ }, ExitCode.GENERAL);
58
+ }
59
+ };
60
+ }
@@ -0,0 +1,23 @@
1
+ export interface ResolvedAuth {
2
+ apiToken: string;
3
+ companySubdomain: string;
4
+ source: Record<string, 'flags' | 'env' | 'rc'>;
5
+ }
6
+ export declare class AuthMissingError extends Error {
7
+ readonly code = "AUTH_MISSING";
8
+ readonly missingFields: string[];
9
+ constructor(missingFields: string[]);
10
+ }
11
+ /**
12
+ * Resolve auth credentials with 3-tier precedence: flags > env > rc.
13
+ */
14
+ export declare function resolveAuth(flags: {
15
+ apiToken?: string;
16
+ companySubdomain?: string;
17
+ }): Promise<ResolvedAuth>;
18
+ /**
19
+ * Mask an API token for display.
20
+ * Shows first 4 + ... + last 4 chars if length >= 8, otherwise ****.
21
+ */
22
+ export declare function maskToken(token: string): string;
23
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/config/auth.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC;CAChD;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,kBAAkB;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC;gBAErB,aAAa,EAAE,MAAM,EAAE;CAKpC;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,OAAO,CAAC,YAAY,CAAC,CA4CxB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAK/C"}
@@ -0,0 +1,69 @@
1
+ import { readRc } from './rc.js';
2
+ export class AuthMissingError extends Error {
3
+ code = 'AUTH_MISSING';
4
+ missingFields;
5
+ constructor(missingFields) {
6
+ super(`Missing required auth: ${missingFields.join(', ')}. Set via --api-token/--company-subdomain flags, BUKKU_API_TOKEN/BUKKU_COMPANY_SUBDOMAIN env vars, or ~/.bukkurc config file.`);
7
+ this.name = 'AuthMissingError';
8
+ this.missingFields = missingFields;
9
+ }
10
+ }
11
+ /**
12
+ * Resolve auth credentials with 3-tier precedence: flags > env > rc.
13
+ */
14
+ export async function resolveAuth(flags) {
15
+ const rc = await readRc();
16
+ const source = {};
17
+ // Resolve apiToken: flags > env > rc
18
+ let apiToken;
19
+ if (flags.apiToken) {
20
+ apiToken = flags.apiToken;
21
+ source['apiToken'] = 'flags';
22
+ }
23
+ else if (process.env['BUKKU_API_TOKEN']) {
24
+ apiToken = process.env['BUKKU_API_TOKEN'];
25
+ source['apiToken'] = 'env';
26
+ }
27
+ else if (rc['api_token']) {
28
+ apiToken = rc['api_token'];
29
+ source['apiToken'] = 'rc';
30
+ }
31
+ // Resolve companySubdomain: flags > env > rc
32
+ let companySubdomain;
33
+ if (flags.companySubdomain) {
34
+ companySubdomain = flags.companySubdomain;
35
+ source['companySubdomain'] = 'flags';
36
+ }
37
+ else if (process.env['BUKKU_COMPANY_SUBDOMAIN']) {
38
+ companySubdomain = process.env['BUKKU_COMPANY_SUBDOMAIN'];
39
+ source['companySubdomain'] = 'env';
40
+ }
41
+ else if (rc['company_subdomain']) {
42
+ companySubdomain = rc['company_subdomain'];
43
+ source['companySubdomain'] = 'rc';
44
+ }
45
+ // Check for missing fields
46
+ const missing = [];
47
+ if (!apiToken)
48
+ missing.push('apiToken');
49
+ if (!companySubdomain)
50
+ missing.push('companySubdomain');
51
+ if (missing.length > 0) {
52
+ throw new AuthMissingError(missing);
53
+ }
54
+ return {
55
+ apiToken: apiToken,
56
+ companySubdomain: companySubdomain,
57
+ source,
58
+ };
59
+ }
60
+ /**
61
+ * Mask an API token for display.
62
+ * Shows first 4 + ... + last 4 chars if length >= 8, otherwise ****.
63
+ */
64
+ export function maskToken(token) {
65
+ if (token.length >= 8) {
66
+ return token.slice(0, 4) + '...' + token.slice(-4);
67
+ }
68
+ return '****';
69
+ }
@@ -0,0 +1,20 @@
1
+ export declare const RC_PATH: string;
2
+ /**
3
+ * Read ~/.bukkurc INI-format file into a key-value record.
4
+ * Returns empty object if file doesn't exist.
5
+ */
6
+ export declare function readRc(): Promise<Record<string, string>>;
7
+ /**
8
+ * Write a key-value pair to ~/.bukkurc.
9
+ * Merges with existing content and sets 0o600 permissions.
10
+ */
11
+ export declare function writeRc(key: string, value: string): Promise<void>;
12
+ /**
13
+ * Check that ~/.bukkurc has 0o600 permissions.
14
+ * Returns ok=true if file doesn't exist or on Windows.
15
+ */
16
+ export declare function checkPermissions(): Promise<{
17
+ ok: boolean;
18
+ mode: string;
19
+ }>;
20
+ //# sourceMappingURL=rc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc.d.ts","sourceRoot":"","sources":["../../src/config/rc.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,OAAO,QAA8B,CAAC;AAEnD;;;GAGG;AACH,wBAAsB,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAsB9D;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMvE;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAgB/E"}
@@ -0,0 +1,65 @@
1
+ import { readFile, writeFile, stat } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ export const RC_PATH = join(homedir(), '.bukkurc');
5
+ /**
6
+ * Read ~/.bukkurc INI-format file into a key-value record.
7
+ * Returns empty object if file doesn't exist.
8
+ */
9
+ export async function readRc() {
10
+ let content;
11
+ try {
12
+ content = await readFile(RC_PATH, 'utf-8');
13
+ }
14
+ catch (err) {
15
+ if (err.code === 'ENOENT') {
16
+ return {};
17
+ }
18
+ throw err;
19
+ }
20
+ const result = {};
21
+ for (const line of content.split('\n')) {
22
+ const trimmed = line.trim();
23
+ if (!trimmed || trimmed.startsWith('#'))
24
+ continue;
25
+ const eqIndex = trimmed.indexOf('=');
26
+ if (eqIndex === -1)
27
+ continue;
28
+ const key = trimmed.slice(0, eqIndex).trim();
29
+ const value = trimmed.slice(eqIndex + 1).trim();
30
+ if (key)
31
+ result[key] = value;
32
+ }
33
+ return result;
34
+ }
35
+ /**
36
+ * Write a key-value pair to ~/.bukkurc.
37
+ * Merges with existing content and sets 0o600 permissions.
38
+ */
39
+ export async function writeRc(key, value) {
40
+ const existing = await readRc();
41
+ existing[key] = value;
42
+ const lines = Object.entries(existing).map(([k, v]) => `${k} = ${v}`);
43
+ await writeFile(RC_PATH, lines.join('\n') + '\n', { mode: 0o600 });
44
+ }
45
+ /**
46
+ * Check that ~/.bukkurc has 0o600 permissions.
47
+ * Returns ok=true if file doesn't exist or on Windows.
48
+ */
49
+ export async function checkPermissions() {
50
+ if (process.platform === 'win32') {
51
+ return { ok: true, mode: '0600' };
52
+ }
53
+ try {
54
+ const st = await stat(RC_PATH);
55
+ const mode = st.mode & 0o777;
56
+ const modeStr = '0' + mode.toString(8);
57
+ return { ok: mode === 0o600, mode: modeStr };
58
+ }
59
+ catch (err) {
60
+ if (err.code === 'ENOENT') {
61
+ return { ok: true, mode: '0000' };
62
+ }
63
+ throw err;
64
+ }
65
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}