@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.
- package/README.md +266 -0
- package/build/commands/config.d.ts +3 -0
- package/build/commands/config.d.ts.map +1 -0
- package/build/commands/config.js +68 -0
- package/build/commands/custom/archive.d.ts +9 -0
- package/build/commands/custom/archive.d.ts.map +1 -0
- package/build/commands/custom/archive.js +82 -0
- package/build/commands/custom/file-upload.d.ts +9 -0
- package/build/commands/custom/file-upload.d.ts.map +1 -0
- package/build/commands/custom/file-upload.js +47 -0
- package/build/commands/custom/journal-entry.d.ts +10 -0
- package/build/commands/custom/journal-entry.d.ts.map +1 -0
- package/build/commands/custom/journal-entry.js +82 -0
- package/build/commands/custom/location-write.d.ts +10 -0
- package/build/commands/custom/location-write.d.ts.map +1 -0
- package/build/commands/custom/location-write.js +95 -0
- package/build/commands/custom/reference-data.d.ts +13 -0
- package/build/commands/custom/reference-data.d.ts.map +1 -0
- package/build/commands/custom/reference-data.js +95 -0
- package/build/commands/custom/search-accounts.d.ts +12 -0
- package/build/commands/custom/search-accounts.d.ts.map +1 -0
- package/build/commands/custom/search-accounts.js +59 -0
- package/build/commands/factory.d.ts +9 -0
- package/build/commands/factory.d.ts.map +1 -0
- package/build/commands/factory.js +371 -0
- package/build/commands/wrapper.d.ts +23 -0
- package/build/commands/wrapper.d.ts.map +1 -0
- package/build/commands/wrapper.js +60 -0
- package/build/config/auth.d.ts +23 -0
- package/build/config/auth.d.ts.map +1 -0
- package/build/config/auth.js +69 -0
- package/build/config/rc.d.ts +20 -0
- package/build/config/rc.d.ts.map +1 -0
- package/build/config/rc.js +65 -0
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +1685 -0
- package/build/input/json.d.ts +10 -0
- package/build/input/json.d.ts.map +1 -0
- package/build/input/json.js +42 -0
- package/build/output/dry-run.d.ts +21 -0
- package/build/output/dry-run.d.ts.map +1 -0
- package/build/output/dry-run.js +23 -0
- package/build/output/error.d.ts +27 -0
- package/build/output/error.d.ts.map +1 -0
- package/build/output/error.js +33 -0
- package/build/output/json.d.ts +6 -0
- package/build/output/json.d.ts.map +1 -0
- package/build/output/json.js +7 -0
- package/build/output/table.d.ts +13 -0
- package/build/output/table.d.ts.map +1 -0
- package/build/output/table.js +123 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# @centry-digital/bukku-cli
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@centry-digital/bukku-cli)
|
|
4
|
+
|
|
5
|
+
A command-line interface for [Bukku](https://bukku.my), a Malaysian accounting platform. Manage invoices, contacts, products, and more from your terminal.
|
|
6
|
+
|
|
7
|
+
## What can it do?
|
|
8
|
+
|
|
9
|
+
The CLI exposes **169 commands** across 9 categories, covering the full Bukku API:
|
|
10
|
+
|
|
11
|
+
| Category | Commands | What you can do |
|
|
12
|
+
|----------|----------|-----------------|
|
|
13
|
+
| **Sales** | 42 | Quotes, orders, delivery orders, invoices, credit notes, payments, refunds |
|
|
14
|
+
| **Purchases** | 36 | Purchase orders, bills, credit notes, goods received notes, payments, refunds |
|
|
15
|
+
| **Banking** | 18 | Money in, money out, bank transfers |
|
|
16
|
+
| **Contacts** | 12 | Customers, suppliers, contact groups |
|
|
17
|
+
| **Products** | 18 | Products, bundles, product groups |
|
|
18
|
+
| **Accounting** | 13 | Chart of accounts, journal entries |
|
|
19
|
+
| **Files** | 3 | Upload and manage file attachments |
|
|
20
|
+
| **Control Panel** | 21 | Locations, tags, tag groups |
|
|
21
|
+
| **Reference Data** | 10 | Tax codes, currencies, payment methods, terms, and more |
|
|
22
|
+
|
|
23
|
+
Every list/get/create/update/delete operation available in the Bukku web UI is available from the command line.
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
Try it without installing:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx @centry-digital/bukku-cli --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or install globally:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install -g @centry-digital/bukku-cli
|
|
37
|
+
bukku --help
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Configuration
|
|
41
|
+
|
|
42
|
+
The CLI needs two things: your API token and company subdomain. There are three ways to provide them, listed in order of precedence:
|
|
43
|
+
|
|
44
|
+
### 1. CLI flags (highest precedence)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
bukku --api-token YOUR_TOKEN --company-subdomain YOUR_SUB sales invoices list
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Environment variables
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
export BUKKU_API_TOKEN=your-token-here
|
|
54
|
+
export BUKKU_COMPANY_SUBDOMAIN=your-subdomain
|
|
55
|
+
bukku sales invoices list
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. Config file (~/.bukkurc)
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
bukku config set api_token your-token-here
|
|
62
|
+
bukku config set company_subdomain your-subdomain
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Verify your configuration:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
bukku config show
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
This shows the resolved value for each field and where it came from (flag, environment variable, or config file).
|
|
72
|
+
|
|
73
|
+
## Usage Examples
|
|
74
|
+
|
|
75
|
+
### Sales
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# List recent invoices
|
|
79
|
+
bukku sales invoices list --limit 5
|
|
80
|
+
|
|
81
|
+
# Get a specific invoice
|
|
82
|
+
bukku sales invoices get 123
|
|
83
|
+
|
|
84
|
+
# Create an invoice
|
|
85
|
+
bukku sales invoices create --data '{
|
|
86
|
+
"contact_id": 1,
|
|
87
|
+
"date": "2025-01-15",
|
|
88
|
+
"currency_code": "MYR",
|
|
89
|
+
"exchange_rate": 1,
|
|
90
|
+
"tax_mode": "exclusive",
|
|
91
|
+
"payment_mode": "credit",
|
|
92
|
+
"status": "draft",
|
|
93
|
+
"form_items": [{"account_id": 100, "description": "Consulting", "amount": 5000}],
|
|
94
|
+
"term_items": [{"payment_due": "100%", "date": "2025-02-15"}]
|
|
95
|
+
}'
|
|
96
|
+
|
|
97
|
+
# List quotes in table format
|
|
98
|
+
bukku sales quotes list --format table
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Purchases
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# List bills
|
|
105
|
+
bukku purchases bills list --format table
|
|
106
|
+
|
|
107
|
+
# Get a specific bill
|
|
108
|
+
bukku purchases bills get 456
|
|
109
|
+
|
|
110
|
+
# List purchase orders
|
|
111
|
+
bukku purchases orders list --limit 10
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Banking
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# List money-in transactions
|
|
118
|
+
bukku banking money-in list --limit 10
|
|
119
|
+
|
|
120
|
+
# List money-out transactions
|
|
121
|
+
bukku banking money-out list --format table
|
|
122
|
+
|
|
123
|
+
# List bank transfers
|
|
124
|
+
bukku banking transfers list
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Contacts
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# List all contacts
|
|
131
|
+
bukku contacts contacts list --format table
|
|
132
|
+
|
|
133
|
+
# Get a specific contact
|
|
134
|
+
bukku contacts contacts get 789
|
|
135
|
+
|
|
136
|
+
# List contact groups
|
|
137
|
+
bukku contacts contact-groups list
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Products
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# List products
|
|
144
|
+
bukku products products list --format table
|
|
145
|
+
|
|
146
|
+
# Get a specific product
|
|
147
|
+
bukku products products get 123
|
|
148
|
+
|
|
149
|
+
# List product groups
|
|
150
|
+
bukku products product-groups list
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Accounting
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# List journal entries
|
|
157
|
+
bukku accounting journal-entries list
|
|
158
|
+
|
|
159
|
+
# Search chart of accounts
|
|
160
|
+
bukku accounting accounts list --format table
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Files
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Upload a file
|
|
167
|
+
bukku files upload /path/to/receipt.pdf
|
|
168
|
+
|
|
169
|
+
# List uploaded files
|
|
170
|
+
bukku files files list
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Control Panel
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# List locations
|
|
177
|
+
bukku control-panel locations list
|
|
178
|
+
|
|
179
|
+
# List tags
|
|
180
|
+
bukku control-panel tags list --format table
|
|
181
|
+
|
|
182
|
+
# List tag groups
|
|
183
|
+
bukku control-panel tag-groups list
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Reference Data
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
# List tax codes
|
|
190
|
+
bukku ref-data tax-codes
|
|
191
|
+
|
|
192
|
+
# List currencies
|
|
193
|
+
bukku ref-data currencies
|
|
194
|
+
|
|
195
|
+
# List payment methods
|
|
196
|
+
bukku ref-data payment-methods
|
|
197
|
+
|
|
198
|
+
# List classification codes (Malaysia e-Invoice)
|
|
199
|
+
bukku ref-data classification-codes
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Input Formats
|
|
203
|
+
|
|
204
|
+
For create and update commands, provide JSON data in one of these ways:
|
|
205
|
+
|
|
206
|
+
**Inline with `--data` flag:**
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
bukku sales invoices create --data '{"contact_id": 1, "date": "2025-01-15", ...}'
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Piped from a file:**
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
cat invoice.json | bukku sales invoices create
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Dry run (preview without sending):**
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
bukku sales invoices create --data '{"contact_id": 1, ...}' --dry-run
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Dry run shows the HTTP method, URL, and request body that would be sent, without making the API call.
|
|
225
|
+
|
|
226
|
+
## Output Formats
|
|
227
|
+
|
|
228
|
+
**JSON (default):** Machine-readable output, easy to pipe to `jq`:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
bukku sales invoices list | jq '.[0].total'
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Table:** Human-readable table format:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
bukku sales invoices list --format table
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Errors are written to stderr as structured JSON, so they don't interfere with piped output.
|
|
241
|
+
|
|
242
|
+
## Exit Codes
|
|
243
|
+
|
|
244
|
+
| Code | Meaning |
|
|
245
|
+
|------|---------|
|
|
246
|
+
| 0 | Success |
|
|
247
|
+
| 1 | General error |
|
|
248
|
+
| 2 | Authentication error (missing or invalid credentials) |
|
|
249
|
+
| 3 | API error (Bukku returned an error) |
|
|
250
|
+
| 4 | Validation error (invalid input or config) |
|
|
251
|
+
|
|
252
|
+
## Getting Your API Token
|
|
253
|
+
|
|
254
|
+
1. Log into your [Bukku](https://bukku.my) account
|
|
255
|
+
2. Go to **Control Panel > Integrations > API Access**
|
|
256
|
+
3. Generate a new API token (or copy your existing one)
|
|
257
|
+
4. Note your company subdomain from the URL (e.g. `mycompany` from `mycompany.bukku.my`)
|
|
258
|
+
|
|
259
|
+
## Related
|
|
260
|
+
|
|
261
|
+
- [@centry-digital/bukku-mcp](https://www.npmjs.com/package/@centry-digital/bukku-mcp) -- MCP server for connecting AI assistants (Claude, etc.) to Bukku
|
|
262
|
+
- [GitHub repository](https://github.com/centry-digital/bukku)
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,eAAO,MAAM,aAAa,SACgB,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { readRc, writeRc, checkPermissions } from '../config/rc.js';
|
|
3
|
+
import { maskToken } from '../config/auth.js';
|
|
4
|
+
const VALID_KEYS = ['api_token', 'company_subdomain'];
|
|
5
|
+
function isValidKey(key) {
|
|
6
|
+
return VALID_KEYS.includes(key);
|
|
7
|
+
}
|
|
8
|
+
export const configCommand = new Command('config')
|
|
9
|
+
.description('Manage CLI configuration');
|
|
10
|
+
// config set <key> <value>
|
|
11
|
+
configCommand
|
|
12
|
+
.command('set')
|
|
13
|
+
.description('Set a config value')
|
|
14
|
+
.argument('<key>', 'Config key (api_token, company_subdomain)')
|
|
15
|
+
.argument('<value>', 'Config value')
|
|
16
|
+
.action(async (key, value) => {
|
|
17
|
+
if (!isValidKey(key)) {
|
|
18
|
+
console.error(JSON.stringify({
|
|
19
|
+
error: 'Invalid config key',
|
|
20
|
+
code: 'VALIDATION_ERROR',
|
|
21
|
+
details: { valid_keys: [...VALID_KEYS] },
|
|
22
|
+
}));
|
|
23
|
+
process.exit(4);
|
|
24
|
+
}
|
|
25
|
+
await writeRc(key, value);
|
|
26
|
+
console.log(JSON.stringify({ ok: true, key, message: 'Config updated' }));
|
|
27
|
+
const perms = await checkPermissions();
|
|
28
|
+
if (!perms.ok) {
|
|
29
|
+
console.error(`Warning: ~/.bukkurc has permissions ${perms.mode}, expected 0600. Run: chmod 600 ~/.bukkurc`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
// config show
|
|
33
|
+
configCommand
|
|
34
|
+
.command('show')
|
|
35
|
+
.description('Show resolved configuration')
|
|
36
|
+
.action(async function () {
|
|
37
|
+
const rc = await readRc();
|
|
38
|
+
// Resolve each field with precedence: flags > env > rc
|
|
39
|
+
const parentOpts = this.parent?.parent?.opts() ?? {};
|
|
40
|
+
const config = {};
|
|
41
|
+
// api_token
|
|
42
|
+
if (parentOpts.apiToken) {
|
|
43
|
+
config['api_token'] = { value: maskToken(parentOpts.apiToken), source: 'flags' };
|
|
44
|
+
}
|
|
45
|
+
else if (process.env['BUKKU_API_TOKEN']) {
|
|
46
|
+
config['api_token'] = { value: maskToken(process.env['BUKKU_API_TOKEN']), source: 'env' };
|
|
47
|
+
}
|
|
48
|
+
else if (rc['api_token']) {
|
|
49
|
+
config['api_token'] = { value: maskToken(rc['api_token']), source: 'rc' };
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
config['api_token'] = { value: null, source: 'not set' };
|
|
53
|
+
}
|
|
54
|
+
// company_subdomain
|
|
55
|
+
if (parentOpts.companySubdomain) {
|
|
56
|
+
config['company_subdomain'] = { value: parentOpts.companySubdomain, source: 'flags' };
|
|
57
|
+
}
|
|
58
|
+
else if (process.env['BUKKU_COMPANY_SUBDOMAIN']) {
|
|
59
|
+
config['company_subdomain'] = { value: process.env['BUKKU_COMPANY_SUBDOMAIN'], source: 'env' };
|
|
60
|
+
}
|
|
61
|
+
else if (rc['company_subdomain']) {
|
|
62
|
+
config['company_subdomain'] = { value: rc['company_subdomain'], source: 'rc' };
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
config['company_subdomain'] = { value: null, source: 'not set' };
|
|
66
|
+
}
|
|
67
|
+
console.log(JSON.stringify({ config }));
|
|
68
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Register archive and unarchive commands for contacts, products, bundles, accounts, locations.
|
|
4
|
+
*
|
|
5
|
+
* These commands are added as subcommands under existing resource commands
|
|
6
|
+
* created by the factory (registerEntityCommands must run first).
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerArchiveCommands(program: Command): void;
|
|
9
|
+
//# sourceMappingURL=archive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../../src/commands/custom/archive.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiCzC;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoD9D"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { withAuth } from '../wrapper.js';
|
|
2
|
+
import { outputJson } from '../../output/json.js';
|
|
3
|
+
import { outputDryRun } from '../../output/dry-run.js';
|
|
4
|
+
import { outputError, ExitCode } from '../../output/error.js';
|
|
5
|
+
/**
|
|
6
|
+
* Archive/unarchive configs for entities that support is_archived toggling.
|
|
7
|
+
*
|
|
8
|
+
* Note: locations use singular /location/{id} path (API inconsistency).
|
|
9
|
+
*/
|
|
10
|
+
const ARCHIVE_CONFIGS = [
|
|
11
|
+
{ group: 'contacts', resource: 'contacts', apiPath: '/contacts', description: 'contact' },
|
|
12
|
+
{ group: 'products', resource: 'products', apiPath: '/products', description: 'product' },
|
|
13
|
+
{ group: 'products', resource: 'bundles', apiPath: '/products/bundles', description: 'product bundle' },
|
|
14
|
+
{ group: 'accounting', resource: 'accounts', apiPath: '/accounts', description: 'account' },
|
|
15
|
+
{ group: 'control-panel', resource: 'locations', apiPath: '/location', description: 'location' },
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Parse and validate a positional ID argument.
|
|
19
|
+
*/
|
|
20
|
+
function parseId(idArg) {
|
|
21
|
+
const parsed = parseInt(idArg, 10);
|
|
22
|
+
if (isNaN(parsed) || parsed <= 0) {
|
|
23
|
+
outputError({ error: 'ID must be a positive integer', code: 'VALIDATION_ERROR' }, ExitCode.VALIDATION);
|
|
24
|
+
}
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Register archive and unarchive commands for contacts, products, bundles, accounts, locations.
|
|
29
|
+
*
|
|
30
|
+
* These commands are added as subcommands under existing resource commands
|
|
31
|
+
* created by the factory (registerEntityCommands must run first).
|
|
32
|
+
*/
|
|
33
|
+
export function registerArchiveCommands(program) {
|
|
34
|
+
for (const config of ARCHIVE_CONFIGS) {
|
|
35
|
+
// Find the group command (already created by factory)
|
|
36
|
+
const groupCmd = program.commands.find((c) => c.name() === config.group);
|
|
37
|
+
if (!groupCmd)
|
|
38
|
+
continue;
|
|
39
|
+
// Find the resource subcommand (already created by factory)
|
|
40
|
+
const resourceCmd = groupCmd.commands.find((c) => c.name() === config.resource);
|
|
41
|
+
if (!resourceCmd)
|
|
42
|
+
continue;
|
|
43
|
+
// Add archive subcommand
|
|
44
|
+
const archiveHandler = withAuth(async ({ client, opts, auth }) => {
|
|
45
|
+
const id = opts._entityId;
|
|
46
|
+
if (opts.dryRun) {
|
|
47
|
+
outputDryRun({ method: 'PATCH', path: `${config.apiPath}/${id}`, token: auth.apiToken, subdomain: auth.companySubdomain, body: { is_archived: true } });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const data = await client.patch(`${config.apiPath}/${id}`, { is_archived: true });
|
|
51
|
+
outputJson(data);
|
|
52
|
+
});
|
|
53
|
+
resourceCmd
|
|
54
|
+
.command('archive <id>')
|
|
55
|
+
.description(`Archive a ${config.description}`)
|
|
56
|
+
.option('--dry-run', 'Show request details without executing', false)
|
|
57
|
+
.action(function (idArg, ...rest) {
|
|
58
|
+
const id = parseId(idArg);
|
|
59
|
+
this.setOptionValue('_entityId', id);
|
|
60
|
+
return archiveHandler.call(this, idArg, ...rest);
|
|
61
|
+
});
|
|
62
|
+
// Add unarchive subcommand
|
|
63
|
+
const unarchiveHandler = withAuth(async ({ client, opts, auth }) => {
|
|
64
|
+
const id = opts._entityId;
|
|
65
|
+
if (opts.dryRun) {
|
|
66
|
+
outputDryRun({ method: 'PATCH', path: `${config.apiPath}/${id}`, token: auth.apiToken, subdomain: auth.companySubdomain, body: { is_archived: false } });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const data = await client.patch(`${config.apiPath}/${id}`, { is_archived: false });
|
|
70
|
+
outputJson(data);
|
|
71
|
+
});
|
|
72
|
+
resourceCmd
|
|
73
|
+
.command('unarchive <id>')
|
|
74
|
+
.description(`Unarchive a ${config.description}`)
|
|
75
|
+
.option('--dry-run', 'Show request details without executing', false)
|
|
76
|
+
.action(function (idArg, ...rest) {
|
|
77
|
+
const id = parseId(idArg);
|
|
78
|
+
this.setOptionValue('_entityId', id);
|
|
79
|
+
return unarchiveHandler.call(this, idArg, ...rest);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Register the file upload command.
|
|
4
|
+
*
|
|
5
|
+
* Uploads a file to Bukku via multipart/form-data using BukkuClient.postMultipart.
|
|
6
|
+
* Validates the file exists before uploading.
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerFileUploadCommand(program: Command): void;
|
|
9
|
+
//# sourceMappingURL=file-upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-upload.d.ts","sourceRoot":"","sources":["../../../src/commands/custom/file-upload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQzC;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAyChE"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
import { access } from 'node:fs/promises';
|
|
3
|
+
import { withAuth } from '../wrapper.js';
|
|
4
|
+
import { outputJson } from '../../output/json.js';
|
|
5
|
+
import { outputDryRun } from '../../output/dry-run.js';
|
|
6
|
+
import { outputError, ExitCode } from '../../output/error.js';
|
|
7
|
+
/**
|
|
8
|
+
* Register the file upload command.
|
|
9
|
+
*
|
|
10
|
+
* Uploads a file to Bukku via multipart/form-data using BukkuClient.postMultipart.
|
|
11
|
+
* Validates the file exists before uploading.
|
|
12
|
+
*/
|
|
13
|
+
export function registerFileUploadCommand(program) {
|
|
14
|
+
// Find or create the files group command.
|
|
15
|
+
// The factory creates a 'files' group for the file entity (list only),
|
|
16
|
+
// so it should already exist. If not, create it.
|
|
17
|
+
let groupCmd = program.commands.find((c) => c.name() === 'files');
|
|
18
|
+
if (!groupCmd) {
|
|
19
|
+
groupCmd = program
|
|
20
|
+
.command('files')
|
|
21
|
+
.description('File uploads and attachments');
|
|
22
|
+
}
|
|
23
|
+
const wrappedHandler = withAuth(async ({ client, opts, auth }) => {
|
|
24
|
+
const filePath = opts._filePath;
|
|
25
|
+
// Validate file exists
|
|
26
|
+
try {
|
|
27
|
+
await access(filePath);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
outputError({ error: 'File not found: ' + filePath, code: 'VALIDATION_ERROR' }, ExitCode.VALIDATION);
|
|
31
|
+
}
|
|
32
|
+
if (opts.dryRun) {
|
|
33
|
+
outputDryRun({ method: 'POST', path: '/files', token: auth.apiToken, subdomain: auth.companySubdomain, body: { file: basename(filePath) } });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const data = await client.postMultipart('/files', filePath);
|
|
37
|
+
outputJson(data);
|
|
38
|
+
});
|
|
39
|
+
groupCmd
|
|
40
|
+
.command('upload <path>')
|
|
41
|
+
.description('Upload a file to Bukku')
|
|
42
|
+
.option('--dry-run', 'Show request details without executing', false)
|
|
43
|
+
.action(function (pathArg, ...rest) {
|
|
44
|
+
this.setOptionValue('_filePath', pathArg);
|
|
45
|
+
return wrappedHandler.call(this, pathArg, ...rest);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Register journal entry create and update commands with double-entry validation.
|
|
4
|
+
*
|
|
5
|
+
* The factory generates list, get, and delete for journal entries.
|
|
6
|
+
* Create and update need custom handling to validate that debits equal credits
|
|
7
|
+
* before submitting to the API.
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerJournalEntryCommands(program: Command): void;
|
|
10
|
+
//# sourceMappingURL=journal-entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal-entry.d.ts","sourceRoot":"","sources":["../../../src/commands/custom/journal-entry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsBzC;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA0EnE"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { validateDoubleEntry } from 'core';
|
|
2
|
+
import { withAuth } from '../wrapper.js';
|
|
3
|
+
import { outputJson } from '../../output/json.js';
|
|
4
|
+
import { outputDryRun } from '../../output/dry-run.js';
|
|
5
|
+
import { outputError, ExitCode } from '../../output/error.js';
|
|
6
|
+
import { readJsonInput } from '../../input/json.js';
|
|
7
|
+
/**
|
|
8
|
+
* Parse and validate a positional ID argument.
|
|
9
|
+
*/
|
|
10
|
+
function parseId(idArg) {
|
|
11
|
+
const parsed = parseInt(idArg, 10);
|
|
12
|
+
if (isNaN(parsed) || parsed <= 0) {
|
|
13
|
+
outputError({ error: 'ID must be a positive integer', code: 'VALIDATION_ERROR' }, ExitCode.VALIDATION);
|
|
14
|
+
}
|
|
15
|
+
return parsed;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Register journal entry create and update commands with double-entry validation.
|
|
19
|
+
*
|
|
20
|
+
* The factory generates list, get, and delete for journal entries.
|
|
21
|
+
* Create and update need custom handling to validate that debits equal credits
|
|
22
|
+
* before submitting to the API.
|
|
23
|
+
*/
|
|
24
|
+
export function registerJournalEntryCommands(program) {
|
|
25
|
+
// Find existing accounting group and journal-entries resource
|
|
26
|
+
const groupCmd = program.commands.find((c) => c.name() === 'accounting');
|
|
27
|
+
if (!groupCmd)
|
|
28
|
+
return;
|
|
29
|
+
const resourceCmd = groupCmd.commands.find((c) => c.name() === 'journal-entries');
|
|
30
|
+
if (!resourceCmd)
|
|
31
|
+
return;
|
|
32
|
+
// Add create subcommand
|
|
33
|
+
resourceCmd
|
|
34
|
+
.command('create')
|
|
35
|
+
.description('Create a new journal entry')
|
|
36
|
+
.option('--data <json>', 'JSON data (or pipe to stdin)')
|
|
37
|
+
.option('--dry-run', 'Show request details without executing', false)
|
|
38
|
+
.action(withAuth(async ({ client, opts, auth }) => {
|
|
39
|
+
const body = await readJsonInput(opts);
|
|
40
|
+
// Validate double-entry balance if journal_items present
|
|
41
|
+
if (body.journal_items && Array.isArray(body.journal_items)) {
|
|
42
|
+
const validation = validateDoubleEntry(body.journal_items);
|
|
43
|
+
if (!validation.valid) {
|
|
44
|
+
outputError({ error: validation.error, code: 'VALIDATION_ERROR' }, ExitCode.VALIDATION);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (opts.dryRun) {
|
|
48
|
+
outputDryRun({ method: 'POST', path: '/journal_entries', token: auth.apiToken, subdomain: auth.companySubdomain, body });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const data = await client.post('/journal_entries', body);
|
|
52
|
+
outputJson(data);
|
|
53
|
+
}));
|
|
54
|
+
// Add update subcommand
|
|
55
|
+
const updateHandler = withAuth(async ({ client, opts, auth }) => {
|
|
56
|
+
const id = opts._entityId;
|
|
57
|
+
const body = await readJsonInput(opts);
|
|
58
|
+
// Validate double-entry balance if journal_items present in update
|
|
59
|
+
if (body.journal_items && Array.isArray(body.journal_items)) {
|
|
60
|
+
const validation = validateDoubleEntry(body.journal_items);
|
|
61
|
+
if (!validation.valid) {
|
|
62
|
+
outputError({ error: validation.error, code: 'VALIDATION_ERROR' }, ExitCode.VALIDATION);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (opts.dryRun) {
|
|
66
|
+
outputDryRun({ method: 'PUT', path: `/journal_entries/${id}`, token: auth.apiToken, subdomain: auth.companySubdomain, body });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const data = await client.put(`/journal_entries/${id}`, body);
|
|
70
|
+
outputJson(data);
|
|
71
|
+
});
|
|
72
|
+
resourceCmd
|
|
73
|
+
.command('update <id>')
|
|
74
|
+
.description('Update a journal entry')
|
|
75
|
+
.option('--data <json>', 'JSON data (or pipe to stdin)')
|
|
76
|
+
.option('--dry-run', 'Show request details without executing', false)
|
|
77
|
+
.action(function (idArg, ...rest) {
|
|
78
|
+
const id = parseId(idArg);
|
|
79
|
+
this.setOptionValue('_entityId', id);
|
|
80
|
+
return updateHandler.call(this, idArg, ...rest);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Register location get/update/delete commands using singular /location/{id} path.
|
|
4
|
+
*
|
|
5
|
+
* The Bukku API uses /locations (plural) for list/create but /location/{id} (singular)
|
|
6
|
+
* for get/update/delete. The factory only generates list and create for locations,
|
|
7
|
+
* so these custom commands handle the singular-path operations.
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerLocationWriteCommands(program: Command): void;
|
|
10
|
+
//# sourceMappingURL=location-write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"location-write.d.ts","sourceRoot":"","sources":["../../../src/commands/custom/location-write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsBzC;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA4EpE"}
|