@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,10 @@
1
+ /**
2
+ * Read JSON input from --data flag or stdin pipe.
3
+ *
4
+ * Precedence:
5
+ * 1. If opts.data is set, parse it as JSON
6
+ * 2. If stdin is piped (!process.stdin.isTTY), read and parse stdin
7
+ * 3. Otherwise, exit with validation error
8
+ */
9
+ export declare function readJsonInput(opts: Record<string, unknown>): Promise<unknown>;
10
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/input/json.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAyCnF"}
@@ -0,0 +1,42 @@
1
+ import { outputError, ExitCode } from '../output/error.js';
2
+ /**
3
+ * Read JSON input from --data flag or stdin pipe.
4
+ *
5
+ * Precedence:
6
+ * 1. If opts.data is set, parse it as JSON
7
+ * 2. If stdin is piped (!process.stdin.isTTY), read and parse stdin
8
+ * 3. Otherwise, exit with validation error
9
+ */
10
+ export async function readJsonInput(opts) {
11
+ if (opts.data != null) {
12
+ try {
13
+ return JSON.parse(opts.data);
14
+ }
15
+ catch (err) {
16
+ outputError({
17
+ error: 'Invalid JSON input: ' + (err instanceof SyntaxError ? err.message : String(err)),
18
+ code: 'VALIDATION_ERROR',
19
+ }, ExitCode.VALIDATION);
20
+ }
21
+ }
22
+ if (!process.stdin.isTTY) {
23
+ const chunks = [];
24
+ for await (const chunk of process.stdin) {
25
+ chunks.push(chunk);
26
+ }
27
+ const raw = Buffer.concat(chunks).toString('utf-8');
28
+ try {
29
+ return JSON.parse(raw);
30
+ }
31
+ catch (err) {
32
+ outputError({
33
+ error: 'Invalid JSON input: ' + (err instanceof SyntaxError ? err.message : String(err)),
34
+ code: 'VALIDATION_ERROR',
35
+ }, ExitCode.VALIDATION);
36
+ }
37
+ }
38
+ outputError({
39
+ error: 'JSON input required. Use --data flag or pipe JSON to stdin',
40
+ code: 'VALIDATION_ERROR',
41
+ }, ExitCode.VALIDATION);
42
+ }
@@ -0,0 +1,21 @@
1
+ export interface DryRunOutput {
2
+ dry_run: true;
3
+ method: string;
4
+ url: string;
5
+ headers: Record<string, string>;
6
+ body?: unknown;
7
+ }
8
+ /**
9
+ * Output a dry-run request preview without executing the API call.
10
+ *
11
+ * Prints method, full URL, masked auth header, and optional body as JSON to stdout.
12
+ * Token is masked to first 3 characters + **** for safety.
13
+ */
14
+ export declare function outputDryRun(opts: {
15
+ method: string;
16
+ path: string;
17
+ token: string;
18
+ subdomain: string;
19
+ body?: unknown;
20
+ }): void;
21
+ //# sourceMappingURL=dry-run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dry-run.d.ts","sourceRoot":"","sources":["../../src/output/dry-run.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,IAAI,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,GAAG,IAAI,CAgBP"}
@@ -0,0 +1,23 @@
1
+ import { outputJson } from './json.js';
2
+ /**
3
+ * Output a dry-run request preview without executing the API call.
4
+ *
5
+ * Prints method, full URL, masked auth header, and optional body as JSON to stdout.
6
+ * Token is masked to first 3 characters + **** for safety.
7
+ */
8
+ export function outputDryRun(opts) {
9
+ const maskedToken = opts.token.length > 6 ? opts.token.slice(0, 3) + '****' : '****';
10
+ const output = {
11
+ dry_run: true,
12
+ method: opts.method,
13
+ url: `https://api.bukku.my${opts.path}`,
14
+ headers: {
15
+ Authorization: `Bearer ${maskedToken}`,
16
+ 'Company-Subdomain': opts.subdomain,
17
+ },
18
+ };
19
+ if (opts.body !== undefined) {
20
+ output.body = opts.body;
21
+ }
22
+ outputJson(output);
23
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Exit codes for CLI.
3
+ */
4
+ export declare const ExitCode: {
5
+ readonly SUCCESS: 0;
6
+ readonly GENERAL: 1;
7
+ readonly AUTH: 2;
8
+ readonly API: 3;
9
+ readonly VALIDATION: 4;
10
+ };
11
+ /**
12
+ * Structured CLI error shape written to stderr.
13
+ */
14
+ export interface CliError {
15
+ error: string;
16
+ code: string;
17
+ details?: unknown;
18
+ }
19
+ /**
20
+ * Map error code strings to numeric exit codes.
21
+ */
22
+ export declare function mapExitCode(errorCode: string): number;
23
+ /**
24
+ * Write structured error JSON to stderr and exit with code.
25
+ */
26
+ export declare function outputError(err: CliError, exitCode: number): never;
27
+ //# sourceMappingURL=error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/output/error.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,QAAQ;;;;;;CAMX,CAAC;AAEX;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAYrD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK,CAGlE"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Exit codes for CLI.
3
+ */
4
+ export const ExitCode = {
5
+ SUCCESS: 0,
6
+ GENERAL: 1,
7
+ AUTH: 2,
8
+ API: 3,
9
+ VALIDATION: 4,
10
+ };
11
+ /**
12
+ * Map error code strings to numeric exit codes.
13
+ */
14
+ export function mapExitCode(errorCode) {
15
+ switch (errorCode) {
16
+ case 'AUTH_MISSING':
17
+ return ExitCode.AUTH;
18
+ case 'API_ERROR':
19
+ case 'API_HTTP_ERROR':
20
+ return ExitCode.API;
21
+ case 'VALIDATION_ERROR':
22
+ return ExitCode.VALIDATION;
23
+ default:
24
+ return ExitCode.GENERAL;
25
+ }
26
+ }
27
+ /**
28
+ * Write structured error JSON to stderr and exit with code.
29
+ */
30
+ export function outputError(err, exitCode) {
31
+ process.stderr.write(JSON.stringify(err) + '\n');
32
+ process.exit(exitCode);
33
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * JSON output formatter.
3
+ * Writes valid JSON to stdout for machine consumption.
4
+ */
5
+ export declare function outputJson(data: unknown): void;
6
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/output/json.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAE9C"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * JSON output formatter.
3
+ * Writes valid JSON to stdout for machine consumption.
4
+ */
5
+ export function outputJson(data) {
6
+ process.stdout.write(JSON.stringify(data, null, 2) + '\n');
7
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Table output formatter with per-resource column definitions.
3
+ * Zero external dependencies — renders plain-text aligned tables to stdout.
4
+ */
5
+ /**
6
+ * Output data as a formatted table to stdout.
7
+ *
8
+ * @param data - Array of resource objects
9
+ * @param columns - Optional string array where first element is the entity name
10
+ * (kept as string[] for backwards compatibility with factory calls)
11
+ */
12
+ export declare function outputTable(data: unknown, columns?: string[]): void;
13
+ //# sourceMappingURL=table.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../src/output/table.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA2HH;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAanE"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Table output formatter with per-resource column definitions.
3
+ * Zero external dependencies — renders plain-text aligned tables to stdout.
4
+ */
5
+ function col(key, header, width, align = 'left') {
6
+ return { key, header, width, align };
7
+ }
8
+ function id(width = 6) {
9
+ return col('id', 'ID', width, 'right');
10
+ }
11
+ const paymentColumns = [
12
+ id(6),
13
+ col('date', 'Date', 12),
14
+ col('number', 'Number', 15),
15
+ col('contact_name', 'Contact', 25),
16
+ col('total', 'Amount', 12, 'right'),
17
+ ];
18
+ function transactionColumns() {
19
+ return [
20
+ id(6),
21
+ col('date', 'Date', 12),
22
+ col('number', 'Number', 15),
23
+ col('contact_name', 'Contact', 25),
24
+ col('total', 'Total', 12, 'right'),
25
+ col('status', 'Status', 12),
26
+ ];
27
+ }
28
+ const DEFAULT_COLUMNS = [
29
+ id(8),
30
+ col('name', 'Name', 30),
31
+ col('description', 'Description', 40),
32
+ ];
33
+ const TABLE_COLUMNS = {
34
+ // Sales / Purchase transaction types
35
+ 'sales-invoice': transactionColumns(),
36
+ 'sales-quote': transactionColumns(),
37
+ 'sales-order': transactionColumns(),
38
+ 'sales-credit-note': transactionColumns(),
39
+ 'sales-payment': paymentColumns,
40
+ 'sales-refund': paymentColumns,
41
+ 'delivery-order': [id(6), col('date', 'Date', 12), col('number', 'Number', 15), col('contact_name', 'Contact', 25), col('status', 'Status', 12)],
42
+ 'purchase-bill': transactionColumns(),
43
+ 'purchase-order': transactionColumns(),
44
+ 'purchase-credit-note': transactionColumns(),
45
+ 'purchase-payment': paymentColumns,
46
+ 'purchase-refund': paymentColumns,
47
+ 'goods-received-note': [id(6), col('date', 'Date', 12), col('number', 'Number', 15), col('contact_name', 'Contact', 25), col('status', 'Status', 12)],
48
+ // Banking
49
+ 'bank-money-in': transactionColumns(),
50
+ 'bank-money-out': transactionColumns(),
51
+ 'bank-transfer': [id(6), col('date', 'Date', 12), col('number', 'Number', 15), col('total', 'Amount', 12, 'right'), col('status', 'Status', 12)],
52
+ // Contacts
53
+ 'contact': [id(6), col('name', 'Name', 30), col('email', 'Email', 25), col('type', 'Type', 10), col('phone', 'Phone', 15)],
54
+ 'contact-group': [id(6), col('name', 'Name', 30), col('contacts_count', 'Contacts', 10, 'right')],
55
+ // Products
56
+ 'product': [id(6), col('name', 'Name', 30), col('code', 'Code', 12), col('type', 'Type', 10), col('sale_price', 'Price', 12, 'right')],
57
+ 'product-bundle': [id(6), col('name', 'Name', 30), col('code', 'Code', 12), col('sale_price', 'Price', 12, 'right')],
58
+ 'product-group': [id(6), col('name', 'Name', 30)],
59
+ // Accounting
60
+ 'journal-entry': [id(6), col('date', 'Date', 12), col('number', 'Number', 15), col('description', 'Description', 30), col('status', 'Status', 12)],
61
+ 'account': [id(6), col('code', 'Code', 8), col('name', 'Name', 30), col('category', 'Category', 12), col('balance', 'Balance', 12, 'right')],
62
+ // Files
63
+ 'file': [id(6), col('name', 'Name', 35), col('content_type', 'Type', 20), col('size', 'Size', 10, 'right')],
64
+ // Control Panel
65
+ 'location': [id(6), col('name', 'Name', 30), col('is_archived', 'Archived', 10)],
66
+ 'tag': [id(6), col('name', 'Name', 30), col('tag_group_id', 'Group ID', 10, 'right')],
67
+ 'tag-group': [id(6), col('name', 'Name', 30)],
68
+ };
69
+ /**
70
+ * Convert a value to a display string.
71
+ */
72
+ function toStr(value) {
73
+ if (value == null)
74
+ return '';
75
+ if (typeof value === 'boolean')
76
+ return value ? 'yes' : 'no';
77
+ return String(value);
78
+ }
79
+ /**
80
+ * Pad/truncate a string to exactly `width` characters.
81
+ */
82
+ function fit(text, width, align) {
83
+ if (text.length > width) {
84
+ return width > 3 ? text.slice(0, width - 3) + '...' : text.slice(0, width);
85
+ }
86
+ return align === 'right' ? text.padStart(width) : text.padEnd(width);
87
+ }
88
+ /**
89
+ * Format rows into a plain-text table string.
90
+ */
91
+ function formatTable(rows, columns) {
92
+ const gap = ' ';
93
+ const lines = [];
94
+ // Header row
95
+ lines.push(columns.map((c) => fit(c.header, c.width, c.align)).join(gap));
96
+ // Separator row
97
+ lines.push(columns.map((c) => '-'.repeat(c.width)).join(gap));
98
+ // Data rows
99
+ for (const row of rows) {
100
+ lines.push(columns
101
+ .map((c) => fit(toStr(row[c.key]), c.width, c.align))
102
+ .join(gap));
103
+ }
104
+ return lines.join('\n') + '\n';
105
+ }
106
+ /**
107
+ * Output data as a formatted table to stdout.
108
+ *
109
+ * @param data - Array of resource objects
110
+ * @param columns - Optional string array where first element is the entity name
111
+ * (kept as string[] for backwards compatibility with factory calls)
112
+ */
113
+ export function outputTable(data, columns) {
114
+ const items = Array.isArray(data) ? data : [data];
115
+ if (items.length === 0) {
116
+ process.stdout.write('(no results)\n');
117
+ return;
118
+ }
119
+ const entityName = columns?.[0];
120
+ const colDefs = (entityName && TABLE_COLUMNS[entityName]) || DEFAULT_COLUMNS;
121
+ const rows = items;
122
+ process.stdout.write(formatTable(rows, colDefs));
123
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@centry-digital/bukku-cli",
3
+ "version": "2.0.0",
4
+ "description": "Bukku accounting CLI - manage invoices, contacts, and accounting from the command line",
5
+ "type": "module",
6
+ "main": "build/index.js",
7
+ "bin": {
8
+ "bukku": "build/index.js"
9
+ },
10
+ "files": ["build/", "LICENSE", "README.md"],
11
+ "scripts": {
12
+ "build": "tsc --build",
13
+ "test": "node --test --import tsx/esm 'src/**/*.test.ts'"
14
+ },
15
+ "keywords": [
16
+ "bukku", "accounting", "cli", "invoicing", "bookkeeping", "malaysia"
17
+ ],
18
+ "homepage": "https://github.com/centry-digital/bukku",
19
+ "bugs": {
20
+ "url": "https://github.com/centry-digital/bukku/issues"
21
+ },
22
+ "author": "Centry Digital",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/centry-digital/bukku.git"
27
+ },
28
+ "engines": {
29
+ "node": ">=20.0.0"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "dependencies": {
35
+ "commander": "^14.0.3"
36
+ }
37
+ }