1
0

feat(cli): implement Bigcapital CLI with full module support

- Add CLI package with commander.js for interacting with Bigcapital API
- Implement listing commands for all modules: items, invoices, customers,
  vendors, bills, accounts, expenses, credit-notes, vendor-credits,
  payments, estimates, receipts, journals, inventory, tax-rates,
  warehouses, and users
- Add comprehensive financial reports: balance-sheet, profit-loss,
  cashflow, trial-balance, general-ledger, journal, receivable-aging,
  payable-aging, customer-balance, vendor-balance, sales-by-items,
  purchases-by-items, inventory-valuation, and sales-tax-liability
- Support configuration management for API key, base URL, and org ID
- Add professional table formatting with chalk and cli-table3
- Include loading spinners and error handling

Usage:
  bigcapital config set api-key <key>
  bigcapital items list
  bigcapital reports balance-sheet --from 2024-01-01 --to 2024-12-31

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ahmed Bouhuolia
2026-04-10 18:58:52 +02:00
parent 5944aa3972
commit 9cd21ce11e
74 changed files with 4724 additions and 2 deletions
+3
View File
@@ -9,6 +9,9 @@
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\"",
"dev:server": "lerna run dev --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"",
"build:server": "lerna run build --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"",
"dev:cli": "lerna run dev --scope \"@bigcapital/cli\" --scope \"@bigcapital/sdk-ts\"",
"build:cli": "lerna run build --scope \"@bigcapital/cli\" --scope \"@bigcapital/sdk-ts\"",
"cli": "node packages/cli/dist/index.js",
"serve:server": "lerna run serve --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
"server:start": "lerna run start:dev --scope \"@bigcapital/server\"",
"test:watch": "lerna run test:watch",
+117
View File
@@ -0,0 +1,117 @@
# Bigcapital CLI
A command-line interface for interacting with the Bigcapital API.
## Installation
```bash
npm install -g @bigcapital/cli
```
Or use directly with `npx`:
```bash
npx @bigcapital/cli --help
```
## Configuration
Before using the CLI, you need to configure your API credentials:
```bash
# Set your API key
bigcapital config set api-key your-api-key-here
# Set the base URL of your Bigcapital instance
bigcapital config set base-url https://api.bigcapital.ly
# Optionally set a default organization ID
bigcapital config set organization-id your-org-id
# Verify your configuration
bigcapital config get
```
Configuration is stored in `~/.config/bigcapital-nodejs/config.json`.
## Usage
### Items
List all items/products:
```bash
# List all items
bigcapital items list
# Limit results
bigcapital items list --limit 10
# Paginate
bigcapital items list --page 2 --limit 25
# Filter by type (inventory, service, product)
bigcapital items list --type inventory
# Show only active items
bigcapital items list --active-only
```
### Invoices
List all sale invoices:
```bash
# List all invoices
bigcapital invoices list
# Filter by customer
bigcapital invoices list --customer 123
# Filter by status
bigcapital invoices list --status overdue
# Paginate results
bigcapital invoices list --page 1 --limit 20
```
## Commands
| Command | Description |
|---------|-------------|
| `config set <key> <value>` | Set configuration value (api-key, base-url, organization-id) |
| `config get` | Display current configuration |
| `items list` | List all items/products |
| `invoices list` | List all sale invoices |
## Global Options
| Option | Description |
|--------|-------------|
| `-V, --version` | Output the version number |
| `-h, --help` | Display help for command |
## Environment Variables
The CLI will also read from environment variables if set:
- `BIGCAPITAL_API_KEY` - Your API key
- `BIGCAPITAL_BASE_URL` - The base URL of your Bigcapital instance
- `BIGCAPITAL_ORGANIZATION_ID` - Default organization ID
## Development
```bash
# Build the CLI
pnpm run build
# Watch mode for development
pnpm run dev
# Type check
pnpm run typecheck
```
## License
ISC
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createAccountsCommand(): Command;
+56
View File
@@ -0,0 +1,56 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAccountsCommand = createAccountsCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createAccountsCommand() {
const command = new commander_1.Command('accounts')
.description('Manage chart of accounts');
command
.command('list')
.description('List all accounts')
.option('-t, --type <type>', 'Filter by account type')
.option('--active-only', 'Show only active accounts', false)
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading accounts...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const query = {
...(options.type && { type: options.type }),
...(options.activeOnly && { active: true }),
};
const accounts = await (0, sdk_ts_1.fetchAccounts)(fetcher, query);
spinner.stop();
if (!accounts || accounts.length === 0) {
console.log(chalk_1.default.yellow('No accounts found.'));
return;
}
const table = (0, table_1.createTable)(['ID', 'Code', 'Name', 'Type', 'Balance', 'Status']);
accounts.forEach((account) => {
table.push([
account.id,
account.code || '-',
(0, table_1.truncate)(account.name, 30),
account.accountType || '-',
(0, table_1.formatCurrency)(account.amount),
(0, table_1.formatStatus)(account.active ? 'active' : 'inactive'),
]);
});
console.log(table.toString());
console.log(chalk_1.default.gray(`\nTotal accounts: ${accounts.length}`));
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createBillsCommand(): Command;
+75
View File
@@ -0,0 +1,75 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createBillsCommand = createBillsCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createBillsCommand() {
const command = new commander_1.Command('bills')
.description('Manage bills');
command
.command('list')
.description('List all bills')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('-v, --vendor <id>', 'Filter by vendor ID')
.option('-s, --status <status>', 'Filter by status (draft, published, paid, partial)')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading bills...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.vendor && { vendorId: parseInt(options.vendor, 10) }),
...(options.status && { status: options.status }),
};
const response = await (0, sdk_ts_1.fetchBills)(fetcher, query);
spinner.stop();
const bills = response.bills;
if (!bills || bills.length === 0) {
console.log(chalk_1.default.yellow('No bills found.'));
return;
}
const pagination = response.pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk_1.default.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total bills)\n`));
}
const table = (0, table_1.createTable)(['ID', 'Bill #', 'Vendor', 'Date', 'Due Date', 'Total', 'Balance', 'Status']);
bills.forEach((bill) => {
table.push([
bill.id,
bill.billNumber || '-',
(0, table_1.truncate)(bill.vendor?.displayName, 20),
(0, table_1.formatDate)(bill.billDate),
(0, table_1.formatDate)(bill.dueDate),
(0, table_1.formatCurrency)(bill.total),
(0, table_1.formatCurrency)(bill.balance),
(0, table_1.formatStatus)(bill.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk_1.default.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createConfigCommand(): Command;
+68
View File
@@ -0,0 +1,68 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createConfigCommand = createConfigCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const config_1 = require("../config");
function createConfigCommand() {
const command = new commander_1.Command('config')
.description('Manage CLI configuration');
command
.command('set')
.description('Set a configuration value')
.argument('<key>', 'Configuration key (api-key, base-url, organization-id)')
.argument('<value>', 'Configuration value')
.action((key, value) => {
switch (key.toLowerCase()) {
case 'api-key':
(0, config_1.setApiKey)(value);
console.log(chalk_1.default.green('✓ API key configured successfully'));
break;
case 'base-url':
(0, config_1.setBaseUrl)(value);
console.log(chalk_1.default.green('✓ Base URL configured successfully'));
break;
case 'organization-id':
(0, config_1.setOrganizationId)(value);
console.log(chalk_1.default.green('✓ Organization ID configured successfully'));
break;
default:
console.error(chalk_1.default.red(`Error: Unknown configuration key "${key}"`));
console.log(chalk_1.default.yellow('Valid keys: api-key, base-url, organization-id'));
process.exit(1);
}
});
command
.command('get')
.description('Show current configuration')
.action(() => {
const config = (0, config_1.getConfig)();
console.log(chalk_1.default.bold('\nBigcapital CLI Configuration:'));
console.log(chalk_1.default.gray('─'.repeat(50)));
if (config.apiKey) {
const maskedKey = config.apiKey.substring(0, 4) + '...' + config.apiKey.substring(config.apiKey.length - 4);
console.log(`API Key: ${chalk_1.default.green(maskedKey)}`);
}
else {
console.log(`API Key: ${chalk_1.default.yellow('Not set')}`);
}
if (config.baseUrl) {
console.log(`Base URL: ${chalk_1.default.green(config.baseUrl)}`);
}
else {
console.log(`Base URL: ${chalk_1.default.yellow('Not set')}`);
}
if (config.organizationId) {
console.log(`Organization: ${chalk_1.default.green(config.organizationId)}`);
}
else {
console.log(`Organization: ${chalk_1.default.yellow('Not set')} (optional)`);
}
console.log(chalk_1.default.gray('─'.repeat(50)));
console.log(chalk_1.default.gray('\nConfig file location: ~/.config/bigcapital-nodejs/config.json\n'));
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createCreditNotesCommand(): Command;
+72
View File
@@ -0,0 +1,72 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createCreditNotesCommand = createCreditNotesCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createCreditNotesCommand() {
const command = new commander_1.Command('credit-notes')
.description('Manage credit notes');
command
.command('list')
.description('List all credit notes')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('-c, --customer <id>', 'Filter by customer ID')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading credit notes...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.customer && { customerId: parseInt(options.customer, 10) }),
};
const response = await (0, sdk_ts_1.fetchCreditNotes)(fetcher, query);
spinner.stop();
const creditNotes = response.creditNotes;
if (!creditNotes || creditNotes.length === 0) {
console.log(chalk_1.default.yellow('No credit notes found.'));
return;
}
const pagination = response.pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk_1.default.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total credit notes)\n`));
}
const table = (0, table_1.createTable)(['ID', 'CN #', 'Customer', 'Date', 'Total', 'Balance', 'Status']);
creditNotes.forEach((cn) => {
table.push([
cn.id,
cn.creditNoteNumber || '-',
(0, table_1.truncate)(cn.customer?.displayName, 20),
(0, table_1.formatDate)(cn.creditNoteDate),
(0, table_1.formatCurrency)(cn.total),
(0, table_1.formatCurrency)(cn.balance),
(0, table_1.formatStatus)(cn.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk_1.default.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createCustomersCommand(): Command;
+72
View File
@@ -0,0 +1,72 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createCustomersCommand = createCustomersCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createCustomersCommand() {
const command = new commander_1.Command('customers')
.description('Manage customers');
command
.command('list')
.description('List all customers')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('--active-only', 'Show only active customers', false)
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading customers...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.activeOnly && { active: true }),
};
const response = await (0, sdk_ts_1.fetchCustomers)(fetcher, query);
spinner.stop();
const customers = response.customers;
if (!customers || customers.length === 0) {
console.log(chalk_1.default.yellow('No customers found.'));
return;
}
const pagination = response.pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk_1.default.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total customers)\n`));
}
const table = (0, table_1.createTable)(['ID', 'Name', 'Email', 'Work Phone', 'Personal Phone', 'Balance', 'Status']);
customers.forEach((customer) => {
table.push([
customer.id,
(0, table_1.truncate)(customer.displayName, 25),
(0, table_1.truncate)(customer.email, 25) || '-',
customer.workPhone || '-',
customer.personalPhone || '-',
(0, table_1.formatCurrency)(customer.balance),
(0, table_1.formatStatus)(customer.active ? 'active' : 'inactive'),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk_1.default.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createEstimatesCommand(): Command;
+74
View File
@@ -0,0 +1,74 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createEstimatesCommand = createEstimatesCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createEstimatesCommand() {
const command = new commander_1.Command('estimates')
.description('Manage sale estimates/quotes');
command
.command('list')
.description('List all sale estimates')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('-c, --customer <id>', 'Filter by customer ID')
.option('-s, --status <status>', 'Filter by status (draft, published)')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading estimates...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.customer && { customerId: parseInt(options.customer, 10) }),
...(options.status && { status: options.status }),
};
const response = await (0, sdk_ts_1.fetchSaleEstimates)(fetcher, query);
spinner.stop();
const estimates = response.saleEstimates;
if (!estimates || estimates.length === 0) {
console.log(chalk_1.default.yellow('No estimates found.'));
return;
}
const pagination = response.pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk_1.default.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total estimates)\n`));
}
const table = (0, table_1.createTable)(['ID', 'Estimate #', 'Customer', 'Date', 'Expires', 'Total', 'Status']);
estimates.forEach((estimate) => {
table.push([
estimate.id,
estimate.estimateNumber || '-',
(0, table_1.truncate)(estimate.customer?.displayName, 20),
(0, table_1.formatDate)(estimate.estimateDate),
(0, table_1.formatDate)(estimate.expirationDate),
(0, table_1.formatCurrency)(estimate.total),
(0, table_1.formatStatus)(estimate.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk_1.default.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createExpensesCommand(): Command;
+70
View File
@@ -0,0 +1,70 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createExpensesCommand = createExpensesCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createExpensesCommand() {
const command = new commander_1.Command('expenses')
.description('Manage expenses');
command
.command('list')
.description('List all expenses')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('--active-only', 'Show only active expenses', false)
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading expenses...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.activeOnly && { active: true }),
};
const response = await (0, sdk_ts_1.fetchExpenses)(fetcher, query);
spinner.stop();
const expenses = response.expenses;
if (!expenses || expenses.length === 0) {
console.log(chalk_1.default.yellow('No expenses found.'));
return;
}
const pagination = response.pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk_1.default.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total expenses)\n`));
}
const table = (0, table_1.createTable)(['ID', 'Date', 'Description', 'Total', 'Status']);
expenses.forEach((expense) => {
table.push([
expense.id,
(0, table_1.formatDate)(expense.paymentDate),
(0, table_1.truncate)(expense.description, 35),
(0, table_1.formatCurrency)(expense.total),
(0, table_1.formatStatus)(expense.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk_1.default.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createInventoryCommand(): Command;
+119
View File
@@ -0,0 +1,119 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInventoryCommand = createInventoryCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createInventoryCommand() {
const command = new commander_1.Command('inventory')
.description('Manage inventory');
command
.command('adjustments')
.description('List inventory adjustments')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading inventory adjustments...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
};
const response = await (0, sdk_ts_1.fetchInventoryAdjustments)(fetcher, query);
spinner.stop();
const adjustments = response.inventoryAdjustments;
if (!adjustments || adjustments.length === 0) {
console.log(chalk_1.default.yellow('No inventory adjustments found.'));
return;
}
const pagination = response.pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk_1.default.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total adjustments)\n`));
}
const table = (0, table_1.createTable)(['ID', 'Date', 'Reference', 'Reason', 'Status']);
adjustments.forEach((adj) => {
table.push([
adj.id,
(0, table_1.formatDate)(adj.adjustmentDate),
adj.referenceNo || '-',
(0, table_1.truncate)(adj.reason, 30),
(0, table_1.formatStatus)(adj.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk_1.default.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
command
.command('transfers')
.description('List warehouse transfers')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading warehouse transfers...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
};
const response = await (0, sdk_ts_1.fetchWarehouseTransfers)(fetcher, query);
spinner.stop();
const transfers = response.warehouseTransfers;
if (!transfers || transfers.length === 0) {
console.log(chalk_1.default.yellow('No warehouse transfers found.'));
return;
}
const pagination = response.pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk_1.default.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total transfers)\n`));
}
const table = (0, table_1.createTable)(['ID', 'Date', 'Reference', 'From', 'To', 'Status']);
transfers.forEach((transfer) => {
table.push([
transfer.id,
(0, table_1.formatDate)(transfer.date),
transfer.referenceNo || '-',
(0, table_1.truncate)(transfer.fromWarehouse?.name, 15),
(0, table_1.truncate)(transfer.toWarehouse?.name, 15),
(0, table_1.formatStatus)(transfer.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk_1.default.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createInvoicesCommand(): Command;
+77
View File
@@ -0,0 +1,77 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInvoicesCommand = createInvoicesCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createInvoicesCommand() {
const command = new commander_1.Command('invoices')
.description('Manage sale invoices');
command
.command('list')
.description('List all sale invoices')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('-c, --customer <id>', 'Filter by customer ID')
.option('-s, --status <status>', 'Filter by status (draft, published, paid, partial, overdue)')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading invoices...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.customer && { customerId: parseInt(options.customer, 10) }),
...(options.status && { status: options.status }),
};
const response = await (0, sdk_ts_1.fetchSaleInvoices)(fetcher, query);
spinner.stop();
// The response has 'salesInvoices' array (camelCase from middleware), not 'data'
const invoices = response.salesInvoices;
if (!invoices || invoices.length === 0) {
console.log(chalk_1.default.yellow('No invoices found.'));
return;
}
// Pagination info
const pagination = response.pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk_1.default.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total invoices)\n`));
}
// Create table
const table = (0, table_1.createTable)(['ID', 'Invoice #', 'Customer', 'Date', 'Total', 'Balance', 'Status']);
invoices.forEach((invoice) => {
table.push([
invoice.id,
invoice.invoiceNo || '-',
(0, table_1.truncate)(invoice.customer?.displayName, 25),
(0, table_1.formatDate)(invoice.invoiceDate),
(0, table_1.formatCurrency)(invoice.total),
(0, table_1.formatCurrency)(invoice.balance),
(0, table_1.formatStatus)(invoice.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk_1.default.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createItemsCommand(): Command;
+77
View File
@@ -0,0 +1,77 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createItemsCommand = createItemsCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createItemsCommand() {
const command = new commander_1.Command('items')
.description('Manage items and products');
command
.command('list')
.description('List all items/products')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('-t, --type <type>', 'Filter by item type (inventory, service, product)')
.option('--active-only', 'Show only active items', false)
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading items...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.type && { type: options.type }),
...(options.activeOnly && { active: true }),
};
const response = await (0, sdk_ts_1.fetchItems)(fetcher, query);
spinner.stop();
// The response has 'items' array, not 'data'
const items = response.items;
if (!items || items.length === 0) {
console.log(chalk_1.default.yellow('No items found.'));
return;
}
// Pagination info
const pagination = response.pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk_1.default.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total items)\n`));
}
// Create table
const table = (0, table_1.createTable)(['ID', 'Name', 'Type', 'Sell Price', 'Cost Price', 'Qty', 'Status']);
items.forEach((item) => {
table.push([
item.id,
(0, table_1.truncate)(item.name, 30),
item.type || '-',
(0, table_1.formatCurrency)(item.sellPrice),
(0, table_1.formatCurrency)(item.costPrice),
item.quantityOnHand ?? '-',
(0, table_1.formatStatus)(item.active ? 'active' : 'inactive'),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk_1.default.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createJournalsCommand(): Command;
+66
View File
@@ -0,0 +1,66 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createJournalsCommand = createJournalsCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createJournalsCommand() {
const command = new commander_1.Command('journals')
.description('Manage manual journals');
command
.command('list')
.description('List all manual journals')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading journals...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const response = await (0, sdk_ts_1.fetchManualJournals)(fetcher, {});
spinner.stop();
const journals = response.manualJournals;
if (!journals || journals.length === 0) {
console.log(chalk_1.default.yellow('No manual journals found.'));
return;
}
const pagination = response.pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk_1.default.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total journals)\n`));
}
const table = (0, table_1.createTable)(['ID', 'Journal #', 'Date', 'Reference', 'Description', 'Amount', 'Status']);
journals.forEach((journal) => {
table.push([
journal.id,
journal.journalNumber || '-',
(0, table_1.formatDate)(journal.date),
journal.reference || '-',
(0, table_1.truncate)(journal.description, 25),
journal.amount?.toFixed(2) || '-',
(0, table_1.formatStatus)(journal.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk_1.default.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createPaymentsCommand(): Command;
+80
View File
@@ -0,0 +1,80 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPaymentsCommand = createPaymentsCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createPaymentsCommand() {
const command = new commander_1.Command('payments')
.description('Manage payments');
command
.command('received')
.description('List payments received from customers')
.action(async () => {
const spinner = (0, ora_1.default)('Loading payments received...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const response = await (0, sdk_ts_1.fetchPaymentsReceived)(fetcher);
spinner.stop();
const payments = response.paymentsReceived;
if (!payments || payments.length === 0) {
console.log(chalk_1.default.yellow('No payments received found.'));
return;
}
const table = (0, table_1.createTable)(['ID', 'Customer', 'Date', 'Amount', 'Reference']);
payments.forEach((payment) => {
table.push([
payment.id,
(0, table_1.truncate)(payment.customer?.displayName, 25),
(0, table_1.formatDate)(payment.paymentDate),
(0, table_1.formatCurrency)(payment.amount),
payment.referenceNo || '-',
]);
});
console.log(table.toString());
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
command
.command('made')
.description('List bill payments made to vendors')
.action(async () => {
const spinner = (0, ora_1.default)('Loading payments made...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const response = await (0, sdk_ts_1.fetchBillPayments)(fetcher);
spinner.stop();
const payments = response.billPayments;
if (!payments || payments.length === 0) {
console.log(chalk_1.default.yellow('No payments made found.'));
return;
}
const table = (0, table_1.createTable)(['ID', 'Vendor', 'Date', 'Amount', 'Reference']);
payments.forEach((payment) => {
table.push([
payment.id,
(0, table_1.truncate)(payment.vendor?.displayName, 25),
(0, table_1.formatDate)(payment.paymentDate),
(0, table_1.formatCurrency)(payment.amount),
payment.referenceNo || '-',
]);
});
console.log(table.toString());
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createReceiptsCommand(): Command;
+71
View File
@@ -0,0 +1,71 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createReceiptsCommand = createReceiptsCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createReceiptsCommand() {
const command = new commander_1.Command('receipts')
.description('Manage sale receipts');
command
.command('list')
.description('List all sale receipts')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('-c, --customer <id>', 'Filter by customer ID')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading receipts...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.customer && { customerId: parseInt(options.customer, 10) }),
};
const response = await (0, sdk_ts_1.fetchSaleReceipts)(fetcher, query);
spinner.stop();
const receipts = response.saleReceipts;
if (!receipts || receipts.length === 0) {
console.log(chalk_1.default.yellow('No receipts found.'));
return;
}
const pagination = response.pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk_1.default.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total receipts)\n`));
}
const table = (0, table_1.createTable)(['ID', 'Receipt #', 'Customer', 'Date', 'Total', 'Status']);
receipts.forEach((receipt) => {
table.push([
receipt.id,
receipt.receiptNumber || '-',
(0, table_1.truncate)(receipt.customer?.displayName, 20),
(0, table_1.formatDate)(receipt.receiptDate),
(0, table_1.formatCurrency)(receipt.total),
(0, table_1.formatStatus)(receipt.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk_1.default.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createReportsCommand(): Command;
+448
View File
@@ -0,0 +1,448 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createReportsCommand = createReportsCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function getDateRange(options) {
const toDate = options.to || new Date().toISOString().split('T')[0];
const fromDate = options.from || new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
return { fromDate, toDate };
}
function createReportsCommand() {
const command = new commander_1.Command('reports')
.description('Financial reports');
// Balance Sheet
command
.command('balance-sheet')
.description('Show balance sheet')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading balance sheet...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const { fromDate, toDate } = getDateRange(options);
const report = await (0, sdk_ts_1.fetchBalanceSheetJson)(fetcher, { fromDate, toDate });
spinner.stop();
console.log(chalk_1.default.bold('\n📊 Balance Sheet'));
console.log(chalk_1.default.gray(`${fromDate} to ${toDate}\n`));
// Assets
console.log(chalk_1.default.green.bold('\nASSETS'));
const assetsTable = (0, table_1.createTable)(['Account', 'Amount']);
let totalAssets = 0;
report.assets?.forEach((item) => {
assetsTable.push([item.accountName, (0, table_1.formatCurrency)(item.total)]);
totalAssets += item.total;
});
assetsTable.push(['', '']);
assetsTable.push([chalk_1.default.bold('Total Assets'), chalk_1.default.bold((0, table_1.formatCurrency)(totalAssets))]);
console.log(assetsTable.toString());
// Liabilities
console.log(chalk_1.default.red.bold('\nLIABILITIES'));
const liabilitiesTable = (0, table_1.createTable)(['Account', 'Amount']);
let totalLiabilities = 0;
report.liabilities?.forEach((item) => {
liabilitiesTable.push([item.accountName, (0, table_1.formatCurrency)(item.total)]);
totalLiabilities += item.total;
});
liabilitiesTable.push(['', '']);
liabilitiesTable.push([chalk_1.default.bold('Total Liabilities'), chalk_1.default.bold((0, table_1.formatCurrency)(totalLiabilities))]);
console.log(liabilitiesTable.toString());
// Equity
console.log(chalk_1.default.blue.bold('\nEQUITY'));
const equityTable = (0, table_1.createTable)(['Account', 'Amount']);
let totalEquity = 0;
report.equity?.forEach((item) => {
equityTable.push([item.accountName, (0, table_1.formatCurrency)(item.total)]);
totalEquity += item.total;
});
equityTable.push(['', '']);
equityTable.push([chalk_1.default.bold('Total Equity'), chalk_1.default.bold((0, table_1.formatCurrency)(totalEquity))]);
console.log(equityTable.toString());
// Summary
console.log(chalk_1.default.bold('\n' + '─'.repeat(40)));
console.log(`${chalk_1.default.bold('Total Liabilities + Equity:')} ${(0, table_1.formatCurrency)(totalLiabilities + totalEquity)}`);
console.log(`${chalk_1.default.bold('Balance:')} ${totalAssets === (totalLiabilities + totalEquity) ? chalk_1.default.green('✓ Balanced') : chalk_1.default.red('✗ Unbalanced')}\n`);
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
// Profit & Loss
command
.command('profit-loss')
.alias('pl')
.description('Show profit and loss statement')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading profit & loss...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const { fromDate, toDate } = getDateRange(options);
const report = await (0, sdk_ts_1.fetchProfitLossJson)(fetcher, { fromDate, toDate });
spinner.stop();
console.log(chalk_1.default.bold('\n📈 Profit & Loss Statement'));
console.log(chalk_1.default.gray(`${fromDate} to ${toDate}\n`));
const income = report.income?.total || 0;
const expenses = report.expenses?.total || 0;
const netProfit = income - expenses;
const table = (0, table_1.createTable)(['', 'Amount']);
table.push([chalk_1.default.green('Total Income'), (0, table_1.formatCurrency)(income)]);
table.push([chalk_1.default.red('Total Expenses'), (0, table_1.formatCurrency)(expenses)]);
table.push(['', '']);
table.push([netProfit >= 0 ? chalk_1.default.green.bold('Net Profit') : chalk_1.default.red.bold('Net Loss'), (0, table_1.formatCurrency)(Math.abs(netProfit))]);
console.log(table.toString() + '\n');
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
// Cashflow
command
.command('cashflow')
.description('Show cashflow statement')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading cashflow statement...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const { fromDate, toDate } = getDateRange(options);
const report = await (0, sdk_ts_1.fetchCashflowStatementJson)(fetcher, { fromDate, toDate });
spinner.stop();
console.log(chalk_1.default.bold('\n💰 Cashflow Statement'));
console.log(chalk_1.default.gray(`${fromDate} to ${toDate}\n`));
const operating = report.operating?.total || 0;
const investing = report.investing?.total || 0;
const financing = report.financing?.total || 0;
const netCash = operating + investing + financing;
const table = (0, table_1.createTable)(['Activity', 'Amount']);
table.push(['Operating Activities', (0, table_1.formatCurrency)(operating)]);
table.push(['Investing Activities', (0, table_1.formatCurrency)(investing)]);
table.push(['Financing Activities', (0, table_1.formatCurrency)(financing)]);
table.push(['', '']);
table.push([chalk_1.default.bold('Net Cash Change'), chalk_1.default.bold((0, table_1.formatCurrency)(netCash))]);
console.log(table.toString() + '\n');
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
// Trial Balance
command
.command('trial-balance')
.description('Show trial balance')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading trial balance...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const { fromDate, toDate } = getDateRange(options);
const report = await (0, sdk_ts_1.fetchTrialBalanceJson)(fetcher, {});
spinner.stop();
console.log(chalk_1.default.bold('\n⚖️ Trial Balance'));
console.log(chalk_1.default.gray(`${fromDate} to ${toDate}\n`));
const table = (0, table_1.createTable)(['Account', 'Debit', 'Credit']);
let totalDebit = 0;
let totalCredit = 0;
report.data?.forEach((item) => {
table.push([item.accountName, (0, table_1.formatCurrency)(item.debit), (0, table_1.formatCurrency)(item.credit)]);
totalDebit += item.debit;
totalCredit += item.credit;
});
table.push(['', '', '']);
table.push([chalk_1.default.bold('Total'), chalk_1.default.bold((0, table_1.formatCurrency)(totalDebit)), chalk_1.default.bold((0, table_1.formatCurrency)(totalCredit))]);
console.log(table.toString());
console.log(`\n${chalk_1.default.bold('Balance:')} ${Math.abs(totalDebit - totalCredit) < 0.01 ? chalk_1.default.green('✓ Balanced') : chalk_1.default.red('✗ Unbalanced')}\n`);
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
// General Ledger
command
.command('general-ledger')
.description('Show general ledger')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading general ledger...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const { fromDate, toDate } = getDateRange(options);
const report = await (0, sdk_ts_1.fetchGeneralLedgerJson)(fetcher, {});
spinner.stop();
console.log(chalk_1.default.bold('\n📒 General Ledger'));
console.log(chalk_1.default.gray(`${fromDate} to ${toDate}\n`));
console.log(JSON.stringify(report, null, 2));
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
// Journal
command
.command('journal')
.description('Show journal report')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading journal...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const { fromDate, toDate } = getDateRange(options);
const report = await (0, sdk_ts_1.fetchJournalJson)(fetcher, {});
spinner.stop();
console.log(chalk_1.default.bold('\n📝 Journal Report'));
console.log(chalk_1.default.gray(`${fromDate} to ${toDate}\n`));
const table = (0, table_1.createTable)(['Date', 'Reference', 'Account', 'Debit', 'Credit']);
report.data?.forEach((item) => {
table.push([item.date, item.reference || '-', item.accountName, (0, table_1.formatCurrency)(item.debit), (0, table_1.formatCurrency)(item.credit)]);
});
console.log(table.toString() + '\n');
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
// Receivable Aging
command
.command('receivable-aging')
.description('Show accounts receivable aging')
.action(async () => {
const spinner = (0, ora_1.default)('Loading receivable aging...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const report = await (0, sdk_ts_1.fetchReceivableAgingJson)(fetcher, {});
spinner.stop();
console.log(chalk_1.default.bold('\n📋 Accounts Receivable Aging\n'));
const table = (0, table_1.createTable)(['Customer', 'Current', '1-30', '31-60', '61-90', '90+', 'Total']);
report.data?.forEach((item) => {
table.push([
item.customerName,
(0, table_1.formatCurrency)(item.current),
(0, table_1.formatCurrency)(item.days1to30),
(0, table_1.formatCurrency)(item.days31to60),
(0, table_1.formatCurrency)(item.days61to90),
(0, table_1.formatCurrency)(item.over90),
chalk_1.default.bold((0, table_1.formatCurrency)(item.total)),
]);
});
console.log(table.toString() + '\n');
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
// Payable Aging
command
.command('payable-aging')
.description('Show accounts payable aging')
.action(async () => {
const spinner = (0, ora_1.default)('Loading payable aging...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const report = await (0, sdk_ts_1.fetchPayableAgingJson)(fetcher, {});
spinner.stop();
console.log(chalk_1.default.bold('\n📋 Accounts Payable Aging\n'));
const table = (0, table_1.createTable)(['Vendor', 'Current', '1-30', '31-60', '61-90', '90+', 'Total']);
report.data?.forEach((item) => {
table.push([
item.vendorName,
(0, table_1.formatCurrency)(item.current),
(0, table_1.formatCurrency)(item.days1to30),
(0, table_1.formatCurrency)(item.days31to60),
(0, table_1.formatCurrency)(item.days61to90),
(0, table_1.formatCurrency)(item.over90),
chalk_1.default.bold((0, table_1.formatCurrency)(item.total)),
]);
});
console.log(table.toString() + '\n');
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
// Customer Balance
command
.command('customer-balance')
.description('Show customer balance summary')
.action(async () => {
const spinner = (0, ora_1.default)('Loading customer balances...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const report = await (0, sdk_ts_1.fetchCustomerBalanceJson)(fetcher, {});
spinner.stop();
console.log(chalk_1.default.bold('\n👤 Customer Balance Summary\n'));
const table = (0, table_1.createTable)(['Customer', 'Total']);
report.data?.forEach((item) => {
table.push([item.customerName, (0, table_1.formatCurrency)(item.total)]);
});
console.log(table.toString() + '\n');
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
// Vendor Balance
command
.command('vendor-balance')
.description('Show vendor balance summary')
.action(async () => {
const spinner = (0, ora_1.default)('Loading vendor balances...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const report = await (0, sdk_ts_1.fetchVendorBalanceJson)(fetcher, {});
spinner.stop();
console.log(chalk_1.default.bold('\n🏢 Vendor Balance Summary\n'));
const table = (0, table_1.createTable)(['Vendor', 'Total']);
report.data?.forEach((item) => {
table.push([item.vendorName, (0, table_1.formatCurrency)(item.total)]);
});
console.log(table.toString() + '\n');
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
// Sales by Items
command
.command('sales-by-items')
.description('Show sales by items report')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading sales by items...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const { fromDate, toDate } = getDateRange(options);
const report = await (0, sdk_ts_1.fetchSalesByItemsJson)(fetcher, { fromDate, toDate });
spinner.stop();
console.log(chalk_1.default.bold('\n🛍️ Sales by Items'));
console.log(chalk_1.default.gray(`${fromDate} to ${toDate}\n`));
const table = (0, table_1.createTable)(['Item', 'Quantity', 'Amount']);
report.data?.forEach((item) => {
table.push([item.itemName, item.quantity.toString(), (0, table_1.formatCurrency)(item.amount)]);
});
console.log(table.toString() + '\n');
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
// Purchases by Items
command
.command('purchases-by-items')
.description('Show purchases by items report')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading purchases by items...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const { fromDate, toDate } = getDateRange(options);
const report = await (0, sdk_ts_1.fetchPurchasesByItemsJson)(fetcher, { fromDate, toDate });
spinner.stop();
console.log(chalk_1.default.bold('\n🛒 Purchases by Items'));
console.log(chalk_1.default.gray(`${fromDate} to ${toDate}\n`));
const table = (0, table_1.createTable)(['Item', 'Quantity', 'Amount']);
report.data?.forEach((item) => {
table.push([item.itemName, item.quantity.toString(), (0, table_1.formatCurrency)(item.amount)]);
});
console.log(table.toString() + '\n');
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
// Inventory Valuation
command
.command('inventory-valuation')
.description('Show inventory valuation summary')
.action(async () => {
const spinner = (0, ora_1.default)('Loading inventory valuation...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const report = await (0, sdk_ts_1.fetchInventoryValuationJson)(fetcher, {});
spinner.stop();
console.log(chalk_1.default.bold('\n📦 Inventory Valuation\n'));
const table = (0, table_1.createTable)(['Item', 'Qty on Hand', 'Avg Cost', 'Total Value']);
report.data?.forEach((item) => {
table.push([item.itemName, item.quantityOnHand.toString(), (0, table_1.formatCurrency)(item.averageCost), (0, table_1.formatCurrency)(item.totalValue)]);
});
console.log(table.toString() + '\n');
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
// Inventory Details
command
.command('inventory-details')
.description('Show inventory item details')
.option('--item <id>', 'Filter by item ID')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading inventory details...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const report = await (0, sdk_ts_1.fetchInventoryItemDetailsJson)(fetcher, {});
spinner.stop();
console.log(chalk_1.default.bold('\n📦 Inventory Item Details\n'));
console.log(JSON.stringify(report, null, 2));
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
// Sales Tax Liability
command
.command('sales-tax-liability')
.description('Show sales tax liability report')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading sales tax liability...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const { fromDate, toDate } = getDateRange(options);
const report = await (0, sdk_ts_1.fetchSalesTaxLiabilityJson)(fetcher, { fromDate, toDate });
spinner.stop();
console.log(chalk_1.default.bold('\n💵 Sales Tax Liability'));
console.log(chalk_1.default.gray(`${fromDate} to ${toDate}\n`));
const table = (0, table_1.createTable)(['Tax Rate', 'Taxable Amount', 'Tax Amount']);
report.data?.forEach((item) => {
table.push([item.taxRateName, (0, table_1.formatCurrency)(item.taxableAmount), (0, table_1.formatCurrency)(item.taxAmount)]);
});
console.log(table.toString() + '\n');
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createTaxRatesCommand(): Command;
+49
View File
@@ -0,0 +1,49 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createTaxRatesCommand = createTaxRatesCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createTaxRatesCommand() {
const command = new commander_1.Command('tax-rates')
.description('Manage tax rates');
command
.command('list')
.description('List all tax rates')
.action(async () => {
const spinner = (0, ora_1.default)('Loading tax rates...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const response = await (0, sdk_ts_1.fetchTaxRates)(fetcher);
spinner.stop();
const taxRates = response.taxRates;
if (!taxRates || taxRates.length === 0) {
console.log(chalk_1.default.yellow('No tax rates found.'));
return;
}
const table = (0, table_1.createTable)(['ID', 'Name', 'Code', 'Rate', 'Status']);
taxRates.forEach((tax) => {
table.push([
tax.id,
tax.name || '-',
tax.code || '-',
`${tax.rate || 0}%`,
(0, table_1.formatStatus)(tax.active ? 'active' : 'inactive'),
]);
});
console.log(table.toString());
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createUsersCommand(): Command;
+80
View File
@@ -0,0 +1,80 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createUsersCommand = createUsersCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createUsersCommand() {
const command = new commander_1.Command('users')
.description('Manage users and roles');
command
.command('list')
.description('List all users')
.action(async () => {
const spinner = (0, ora_1.default)('Loading users...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const response = await (0, sdk_ts_1.fetchUsers)(fetcher);
spinner.stop();
const users = response.users;
if (!users || users.length === 0) {
console.log(chalk_1.default.yellow('No users found.'));
return;
}
const table = (0, table_1.createTable)(['ID', 'Name', 'Email', 'Role', 'Status']);
users.forEach((user) => {
const name = `${user.firstName || ''} ${user.lastName || ''}`.trim() || '-';
table.push([
user.id,
name,
user.email || '-',
user.role?.name || '-',
(0, table_1.formatStatus)(user.active ? 'active' : 'inactive'),
]);
});
console.log(table.toString());
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
command
.command('roles')
.description('List all roles')
.action(async () => {
const spinner = (0, ora_1.default)('Loading roles...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const response = await (0, sdk_ts_1.fetchRoles)(fetcher);
spinner.stop();
const roles = response.roles;
if (!roles || roles.length === 0) {
console.log(chalk_1.default.yellow('No roles found.'));
return;
}
const table = (0, table_1.createTable)(['ID', 'Name', 'Slug', 'Description']);
roles.forEach((role) => {
table.push([
role.id,
role.name || '-',
role.slug || '-',
role.description || '-',
]);
});
console.log(table.toString());
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createVendorCreditsCommand(): Command;
+72
View File
@@ -0,0 +1,72 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createVendorCreditsCommand = createVendorCreditsCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createVendorCreditsCommand() {
const command = new commander_1.Command('vendor-credits')
.description('Manage vendor credits');
command
.command('list')
.description('List all vendor credits')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('-v, --vendor <id>', 'Filter by vendor ID')
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading vendor credits...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.vendor && { vendorId: parseInt(options.vendor, 10) }),
};
const response = await (0, sdk_ts_1.fetchVendorCredits)(fetcher, query);
spinner.stop();
const vendorCredits = response.vendorCredits;
if (!vendorCredits || vendorCredits.length === 0) {
console.log(chalk_1.default.yellow('No vendor credits found.'));
return;
}
const pagination = response.pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk_1.default.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total vendor credits)\n`));
}
const table = (0, table_1.createTable)(['ID', 'VC #', 'Vendor', 'Date', 'Total', 'Balance', 'Status']);
vendorCredits.forEach((vc) => {
table.push([
vc.id,
vc.vendorCreditNumber || '-',
(0, table_1.truncate)(vc.vendor?.displayName, 20),
(0, table_1.formatDate)(vc.vendorCreditDate),
(0, table_1.formatCurrency)(vc.total),
(0, table_1.formatCurrency)(vc.balance),
(0, table_1.formatStatus)(vc.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk_1.default.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createVendorsCommand(): Command;
+72
View File
@@ -0,0 +1,72 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createVendorsCommand = createVendorsCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createVendorsCommand() {
const command = new commander_1.Command('vendors')
.description('Manage vendors');
command
.command('list')
.description('List all vendors')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('--active-only', 'Show only active vendors', false)
.action(async (options) => {
const spinner = (0, ora_1.default)('Loading vendors...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.activeOnly && { active: true }),
};
const response = await (0, sdk_ts_1.fetchVendors)(fetcher, query);
spinner.stop();
const vendors = response.vendors;
if (!vendors || vendors.length === 0) {
console.log(chalk_1.default.yellow('No vendors found.'));
return;
}
const pagination = response.pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk_1.default.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total vendors)\n`));
}
const table = (0, table_1.createTable)(['ID', 'Name', 'Email', 'Work Phone', 'Personal Phone', 'Balance', 'Status']);
vendors.forEach((vendor) => {
table.push([
vendor.id,
(0, table_1.truncate)(vendor.displayName, 25),
(0, table_1.truncate)(vendor.email, 25) || '-',
vendor.workPhone || '-',
vendor.personalPhone || '-',
(0, table_1.formatCurrency)(vendor.balance),
(0, table_1.formatStatus)(vendor.active ? 'active' : 'inactive'),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk_1.default.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+2
View File
@@ -0,0 +1,2 @@
import { Command } from 'commander';
export declare function createWarehousesCommand(): Command;
+49
View File
@@ -0,0 +1,49 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createWarehousesCommand = createWarehousesCommand;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config_1 = require("../config");
const errors_1 = require("../utils/errors");
const table_1 = require("../utils/table");
function createWarehousesCommand() {
const command = new commander_1.Command('warehouses')
.description('Manage warehouses');
command
.command('list')
.description('List all warehouses')
.action(async () => {
const spinner = (0, ora_1.default)('Loading warehouses...').start();
try {
const fetcher = (0, config_1.createAuthenticatedFetcher)();
const response = await (0, sdk_ts_1.fetchWarehouses)(fetcher);
spinner.stop();
const warehouses = response.warehouses;
if (!warehouses || warehouses.length === 0) {
console.log(chalk_1.default.yellow('No warehouses found.'));
return;
}
const table = (0, table_1.createTable)(['ID', 'Code', 'Name', 'City', 'Status']);
warehouses.forEach((warehouse) => {
table.push([
warehouse.id,
warehouse.code || '-',
warehouse.name || '-',
warehouse.city || '-',
(0, table_1.formatStatus)(warehouse.active ? 'active' : 'inactive'),
]);
});
console.log(table.toString());
}
catch (error) {
spinner.stop();
(0, errors_1.handleError)(error);
}
});
return command;
}
+13
View File
@@ -0,0 +1,13 @@
import { ApiFetcher } from '@bigcapital/sdk-ts';
interface ConfigSchema {
apiKey: string;
baseUrl: string;
organizationId?: string;
}
export declare function getConfig(): ConfigSchema;
export declare function setApiKey(apiKey: string): void;
export declare function setBaseUrl(baseUrl: string): void;
export declare function setOrganizationId(organizationId: string): void;
export declare function validateConfig(): ConfigSchema;
export declare function createAuthenticatedFetcher(): ApiFetcher;
export {};
+75
View File
@@ -0,0 +1,75 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getConfig = getConfig;
exports.setApiKey = setApiKey;
exports.setBaseUrl = setBaseUrl;
exports.setOrganizationId = setOrganizationId;
exports.validateConfig = validateConfig;
exports.createAuthenticatedFetcher = createAuthenticatedFetcher;
const conf_1 = __importDefault(require("conf"));
const chalk_1 = __importDefault(require("chalk"));
const sdk_ts_1 = require("@bigcapital/sdk-ts");
const config = new conf_1.default({
projectName: 'bigcapital',
schema: {
apiKey: {
type: 'string',
},
baseUrl: {
type: 'string',
},
organizationId: {
type: 'string',
},
},
});
function getConfig() {
return {
apiKey: config.get('apiKey', ''),
baseUrl: config.get('baseUrl', ''),
organizationId: config.get('organizationId'),
};
}
function setApiKey(apiKey) {
config.set('apiKey', apiKey);
}
function setBaseUrl(baseUrl) {
// Remove trailing slash if present
const normalizedUrl = baseUrl.replace(/\/$/, '');
config.set('baseUrl', normalizedUrl);
}
function setOrganizationId(organizationId) {
config.set('organizationId', organizationId);
}
function validateConfig() {
const currentConfig = getConfig();
if (!currentConfig.apiKey) {
console.error(chalk_1.default.red('Error: API key is not configured.'));
console.error(chalk_1.default.yellow('Run: bigcapital config set api-key <your-api-key>'));
process.exit(1);
}
if (!currentConfig.baseUrl) {
console.error(chalk_1.default.red('Error: Base URL is not configured.'));
console.error(chalk_1.default.yellow('Run: bigcapital config set base-url <https://your-api-url>'));
process.exit(1);
}
return currentConfig;
}
function createAuthenticatedFetcher() {
const currentConfig = validateConfig();
const headers = {
'Authorization': `Bearer ${currentConfig.apiKey}`,
};
if (currentConfig.organizationId) {
headers['organization-id'] = currentConfig.organizationId;
}
return (0, sdk_ts_1.createApiFetcher)({
baseUrl: currentConfig.baseUrl,
init: {
headers,
},
});
}
+2
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env node
export {};
Vendored Executable
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env node
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const items_1 = require("./commands/items");
const invoices_1 = require("./commands/invoices");
const config_1 = require("./commands/config");
const customers_1 = require("./commands/customers");
const vendors_1 = require("./commands/vendors");
const bills_1 = require("./commands/bills");
const accounts_1 = require("./commands/accounts");
const expenses_1 = require("./commands/expenses");
const credit_notes_1 = require("./commands/credit-notes");
const vendor_credits_1 = require("./commands/vendor-credits");
const payments_1 = require("./commands/payments");
const estimates_1 = require("./commands/estimates");
const receipts_1 = require("./commands/receipts");
const journals_1 = require("./commands/journals");
const inventory_1 = require("./commands/inventory");
const tax_rates_1 = require("./commands/tax-rates");
const warehouses_1 = require("./commands/warehouses");
const users_1 = require("./commands/users");
const reports_1 = require("./commands/reports");
const chalk_1 = __importDefault(require("chalk"));
const program = new commander_1.Command();
program
.name('bigcapital')
.description('Bigcapital CLI - Interact with Bigcapital API')
.version('1.0.0')
.configureOutput({
writeErr: (str) => process.stderr.write(chalk_1.default.red(str)),
outputError: (str, write) => write(chalk_1.default.red(str)),
});
// Core modules
program.addCommand((0, config_1.createConfigCommand)());
program.addCommand((0, items_1.createItemsCommand)());
program.addCommand((0, invoices_1.createInvoicesCommand)());
program.addCommand((0, customers_1.createCustomersCommand)());
program.addCommand((0, vendors_1.createVendorsCommand)());
program.addCommand((0, bills_1.createBillsCommand)());
// Additional transactional modules
program.addCommand((0, accounts_1.createAccountsCommand)());
program.addCommand((0, expenses_1.createExpensesCommand)());
program.addCommand((0, credit_notes_1.createCreditNotesCommand)());
program.addCommand((0, vendor_credits_1.createVendorCreditsCommand)());
program.addCommand((0, payments_1.createPaymentsCommand)());
program.addCommand((0, estimates_1.createEstimatesCommand)());
program.addCommand((0, receipts_1.createReceiptsCommand)());
program.addCommand((0, journals_1.createJournalsCommand)());
program.addCommand((0, inventory_1.createInventoryCommand)());
program.addCommand((0, tax_rates_1.createTaxRatesCommand)());
program.addCommand((0, warehouses_1.createWarehousesCommand)());
program.addCommand((0, users_1.createUsersCommand)());
// Financial reports
program.addCommand((0, reports_1.createReportsCommand)());
// Global error handling
program.hook('preAction', () => {
process.on('unhandledRejection', (error) => {
console.error(chalk_1.default.red('\nUnhandled error:'), error);
process.exit(1);
});
process.on('uncaughtException', (error) => {
console.error(chalk_1.default.red('\nUncaught exception:'), error);
process.exit(1);
});
});
// Show help if no command provided
if (process.argv.length <= 2) {
program.help();
}
program.parse();
+2
View File
@@ -0,0 +1,2 @@
export declare function handleError(error: unknown): never;
export declare function assertDefined<T>(value: T | undefined | null, name: string): T;
+45
View File
@@ -0,0 +1,45 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleError = handleError;
exports.assertDefined = assertDefined;
const chalk_1 = __importDefault(require("chalk"));
function handleError(error) {
if (error instanceof Error) {
// Check if it's an HTTP error from the SDK
const httpError = error;
if (httpError.status) {
console.error(chalk_1.default.red(`HTTP Error ${httpError.status}: ${httpError.statusText || error.message}`));
if (httpError.status === 401) {
console.error(chalk_1.default.yellow('Your API key may be invalid or expired.'));
console.error(chalk_1.default.yellow('Run: bigcapital config set api-key <your-new-api-key>'));
}
else if (httpError.status === 403) {
console.error(chalk_1.default.yellow('You don\'t have permission to access this resource.'));
}
else if (httpError.status === 404) {
console.error(chalk_1.default.yellow('The requested resource was not found.'));
}
}
else {
console.error(chalk_1.default.red(`Error: ${error.message}`));
}
if (process.env.DEBUG) {
console.error(chalk_1.default.gray(error.stack));
}
}
else {
console.error(chalk_1.default.red('An unknown error occurred'));
console.error(error);
}
process.exit(1);
}
function assertDefined(value, name) {
if (value === undefined || value === null) {
console.error(chalk_1.default.red(`Error: ${name} is required but was not provided.`));
process.exit(1);
}
return value;
}
+6
View File
@@ -0,0 +1,6 @@
import Table from 'cli-table3';
export declare function createTable(headers: string[]): Table.Table;
export declare function formatCurrency(amount: number | string | undefined, currency?: string): string;
export declare function formatDate(dateStr: string | undefined): string;
export declare function truncate(str: string | undefined, maxLength: number): string;
export declare function formatStatus(status: string | undefined): string;
+84
View File
@@ -0,0 +1,84 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createTable = createTable;
exports.formatCurrency = formatCurrency;
exports.formatDate = formatDate;
exports.truncate = truncate;
exports.formatStatus = formatStatus;
const cli_table3_1 = __importDefault(require("cli-table3"));
const chalk_1 = __importDefault(require("chalk"));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function createTable(headers) {
return new cli_table3_1.default({
head: headers.map(h => chalk_1.default.cyan.bold(h)),
style: {
head: [],
border: [],
},
chars: {
top: '─',
'top-mid': '┬',
'top-left': '┌',
'top-right': '┐',
bottom: '─',
'bottom-mid': '┴',
'bottom-left': '└',
'bottom-right': '┘',
left: '│',
'left-mid': '├',
mid: '─',
'mid-mid': '┼',
right: '│',
'right-mid': '┤',
middle: '│',
},
});
}
function formatCurrency(amount, currency = '$') {
if (amount === undefined || amount === null)
return '-';
const num = typeof amount === 'string' ? parseFloat(amount) : amount;
if (isNaN(num))
return '-';
return `${currency}${num.toFixed(2)}`;
}
function formatDate(dateStr) {
if (!dateStr)
return '-';
const date = new Date(dateStr);
if (isNaN(date.getTime()))
return dateStr;
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
});
}
function truncate(str, maxLength) {
if (!str)
return '-';
if (str.length <= maxLength)
return str;
return str.substring(0, maxLength - 3) + '...';
}
function formatStatus(status) {
if (!status)
return '-';
const statusColors = {
published: chalk_1.default.green,
draft: chalk_1.default.gray,
delivered: chalk_1.default.blue,
partial: chalk_1.default.yellow,
paid: chalk_1.default.green.bold,
unpaid: chalk_1.default.red,
overdue: chalk_1.default.red.bold,
active: chalk_1.default.green,
inactive: chalk_1.default.gray,
};
const lowerStatus = status.toLowerCase();
const colorFn = statusColors[lowerStatus] || chalk_1.default.white;
return colorFn(status);
}
+38
View File
@@ -0,0 +1,38 @@
{
"name": "@bigcapital/cli",
"version": "1.0.0",
"description": "Bigcapital CLI - Interact with Bigcapital API",
"main": "./dist/index.js",
"bin": {
"bigcapital": "./dist/index.js"
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"typecheck": "tsc --noEmit",
"prepublishOnly": "npm run build"
},
"keywords": [
"bigcapital",
"cli",
"accounting",
"api"
],
"author": "",
"license": "ISC",
"dependencies": {
"@bigcapital/sdk-ts": "workspace:*",
"chalk": "^4.1.2",
"cli-table3": "^0.6.3",
"commander": "^11.1.0",
"conf": "^10.2.0",
"ora": "^5.4.1"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.1.3"
},
"engines": {
"node": ">=16.0.0"
}
}
+67
View File
@@ -0,0 +1,67 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchAccounts } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatCurrency, truncate, formatStatus } from '../utils/table';
export function createAccountsCommand(): Command {
const command = new Command('accounts')
.description('Manage chart of accounts');
command
.command('list')
.description('List all accounts')
.option('-t, --type <type>', 'Filter by account type')
.option('--active-only', 'Show only active accounts', false)
.action(async (options) => {
const spinner = ora('Loading accounts...').start();
try {
const fetcher = createAuthenticatedFetcher();
const query = {
...(options.type && { type: options.type }),
...(options.activeOnly && { active: true }),
};
const accounts = await fetchAccounts(fetcher, query);
spinner.stop();
if (!accounts || accounts.length === 0) {
console.log(chalk.yellow('No accounts found.'));
return;
}
const table = createTable(['ID', 'Code', 'Name', 'Type', 'Balance', 'Status']);
accounts.forEach((account: {
id: number;
code?: string;
name?: string;
accountType?: string;
amount?: number;
active?: boolean;
}) => {
table.push([
account.id,
account.code || '-',
truncate(account.name, 30),
account.accountType || '-',
formatCurrency(account.amount),
formatStatus(account.active ? 'active' : 'inactive'),
]);
});
console.log(table.toString());
console.log(chalk.gray(`\nTotal accounts: ${accounts.length}`));
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+93
View File
@@ -0,0 +1,93 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchBills } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatCurrency, formatDate, truncate, formatStatus } from '../utils/table';
export function createBillsCommand(): Command {
const command = new Command('bills')
.description('Manage bills');
command
.command('list')
.description('List all bills')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('-v, --vendor <id>', 'Filter by vendor ID')
.option('-s, --status <status>', 'Filter by status (draft, published, paid, partial)')
.action(async (options) => {
const spinner = ora('Loading bills...').start();
try {
const fetcher = createAuthenticatedFetcher();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.vendor && { vendorId: parseInt(options.vendor, 10) }),
...(options.status && { status: options.status }),
};
const response = await fetchBills(fetcher, query);
spinner.stop();
const bills = (response as unknown as { bills: Array<{
id: number;
billNumber?: string;
vendor?: { displayName?: string };
billDate?: string;
dueDate?: string;
total?: number;
balance?: number;
status?: string;
}> }).bills;
if (!bills || bills.length === 0) {
console.log(chalk.yellow('No bills found.'));
return;
}
const pagination = (response as unknown as {
pagination?: { total: number; page: number; pageSize?: number; page_size?: number }
}).pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total bills)\n`));
}
const table = createTable(['ID', 'Bill #', 'Vendor', 'Date', 'Due Date', 'Total', 'Balance', 'Status']);
bills.forEach((bill) => {
table.push([
bill.id,
bill.billNumber || '-',
truncate(bill.vendor?.displayName, 20),
formatDate(bill.billDate),
formatDate(bill.dueDate),
formatCurrency(bill.total),
formatCurrency(bill.balance),
formatStatus(bill.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+68
View File
@@ -0,0 +1,68 @@
import { Command } from 'commander';
import chalk from 'chalk';
import { getConfig, setApiKey, setBaseUrl, setOrganizationId } from '../config';
export function createConfigCommand(): Command {
const command = new Command('config')
.description('Manage CLI configuration');
command
.command('set')
.description('Set a configuration value')
.argument('<key>', 'Configuration key (api-key, base-url, organization-id)')
.argument('<value>', 'Configuration value')
.action((key: string, value: string) => {
switch (key.toLowerCase()) {
case 'api-key':
setApiKey(value);
console.log(chalk.green('✓ API key configured successfully'));
break;
case 'base-url':
setBaseUrl(value);
console.log(chalk.green('✓ Base URL configured successfully'));
break;
case 'organization-id':
setOrganizationId(value);
console.log(chalk.green('✓ Organization ID configured successfully'));
break;
default:
console.error(chalk.red(`Error: Unknown configuration key "${key}"`));
console.log(chalk.yellow('Valid keys: api-key, base-url, organization-id'));
process.exit(1);
}
});
command
.command('get')
.description('Show current configuration')
.action(() => {
const config = getConfig();
console.log(chalk.bold('\nBigcapital CLI Configuration:'));
console.log(chalk.gray('─'.repeat(50)));
if (config.apiKey) {
const maskedKey = config.apiKey.substring(0, 4) + '...' + config.apiKey.substring(config.apiKey.length - 4);
console.log(`API Key: ${chalk.green(maskedKey)}`);
} else {
console.log(`API Key: ${chalk.yellow('Not set')}`);
}
if (config.baseUrl) {
console.log(`Base URL: ${chalk.green(config.baseUrl)}`);
} else {
console.log(`Base URL: ${chalk.yellow('Not set')}`);
}
if (config.organizationId) {
console.log(`Organization: ${chalk.green(config.organizationId)}`);
} else {
console.log(`Organization: ${chalk.yellow('Not set')} (optional)`);
}
console.log(chalk.gray('─'.repeat(50)));
console.log(chalk.gray('\nConfig file location: ~/.config/bigcapital-nodejs/config.json\n'));
});
return command;
}
+89
View File
@@ -0,0 +1,89 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchCreditNotes } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatCurrency, formatDate, truncate, formatStatus } from '../utils/table';
export function createCreditNotesCommand(): Command {
const command = new Command('credit-notes')
.description('Manage credit notes');
command
.command('list')
.description('List all credit notes')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('-c, --customer <id>', 'Filter by customer ID')
.action(async (options) => {
const spinner = ora('Loading credit notes...').start();
try {
const fetcher = createAuthenticatedFetcher();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.customer && { customerId: parseInt(options.customer, 10) }),
};
const response = await fetchCreditNotes(fetcher, query);
spinner.stop();
const creditNotes = (response as unknown as { creditNotes: Array<{
id: number;
creditNoteNumber?: string;
customer?: { displayName?: string };
creditNoteDate?: string;
total?: number;
balance?: number;
status?: string;
}> }).creditNotes;
if (!creditNotes || creditNotes.length === 0) {
console.log(chalk.yellow('No credit notes found.'));
return;
}
const pagination = (response as unknown as {
pagination?: { total: number; page: number; pageSize?: number; page_size?: number }
}).pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total credit notes)\n`));
}
const table = createTable(['ID', 'CN #', 'Customer', 'Date', 'Total', 'Balance', 'Status']);
creditNotes.forEach((cn) => {
table.push([
cn.id,
cn.creditNoteNumber || '-',
truncate(cn.customer?.displayName, 20),
formatDate(cn.creditNoteDate),
formatCurrency(cn.total),
formatCurrency(cn.balance),
formatStatus(cn.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+89
View File
@@ -0,0 +1,89 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchCustomers } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatCurrency, truncate, formatStatus } from '../utils/table';
export function createCustomersCommand(): Command {
const command = new Command('customers')
.description('Manage customers');
command
.command('list')
.description('List all customers')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('--active-only', 'Show only active customers', false)
.action(async (options) => {
const spinner = ora('Loading customers...').start();
try {
const fetcher = createAuthenticatedFetcher();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.activeOnly && { active: true }),
};
const response = await fetchCustomers(fetcher, query);
spinner.stop();
const customers = (response as unknown as { customers: Array<{
id: number;
displayName?: string;
email?: string;
workPhone?: string;
personalPhone?: string;
balance?: number;
active?: boolean;
}> }).customers;
if (!customers || customers.length === 0) {
console.log(chalk.yellow('No customers found.'));
return;
}
const pagination = (response as unknown as {
pagination?: { total: number; page: number; pageSize?: number; page_size?: number }
}).pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total customers)\n`));
}
const table = createTable(['ID', 'Name', 'Email', 'Work Phone', 'Personal Phone', 'Balance', 'Status']);
customers.forEach((customer) => {
table.push([
customer.id,
truncate(customer.displayName, 25),
truncate(customer.email, 25) || '-',
customer.workPhone || '-',
customer.personalPhone || '-',
formatCurrency(customer.balance),
formatStatus(customer.active ? 'active' : 'inactive'),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+91
View File
@@ -0,0 +1,91 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchSaleEstimates } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatCurrency, formatDate, truncate, formatStatus } from '../utils/table';
export function createEstimatesCommand(): Command {
const command = new Command('estimates')
.description('Manage sale estimates/quotes');
command
.command('list')
.description('List all sale estimates')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('-c, --customer <id>', 'Filter by customer ID')
.option('-s, --status <status>', 'Filter by status (draft, published)')
.action(async (options) => {
const spinner = ora('Loading estimates...').start();
try {
const fetcher = createAuthenticatedFetcher();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.customer && { customerId: parseInt(options.customer, 10) }),
...(options.status && { status: options.status }),
};
const response = await fetchSaleEstimates(fetcher, query);
spinner.stop();
const estimates = (response as unknown as { saleEstimates: Array<{
id: number;
estimateNumber?: string;
customer?: { displayName?: string };
estimateDate?: string;
expirationDate?: string;
total?: number;
status?: string;
}> }).saleEstimates;
if (!estimates || estimates.length === 0) {
console.log(chalk.yellow('No estimates found.'));
return;
}
const pagination = (response as unknown as {
pagination?: { total: number; page: number; pageSize?: number; page_size?: number }
}).pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total estimates)\n`));
}
const table = createTable(['ID', 'Estimate #', 'Customer', 'Date', 'Expires', 'Total', 'Status']);
estimates.forEach((estimate) => {
table.push([
estimate.id,
estimate.estimateNumber || '-',
truncate(estimate.customer?.displayName, 20),
formatDate(estimate.estimateDate),
formatDate(estimate.expirationDate),
formatCurrency(estimate.total),
formatStatus(estimate.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+86
View File
@@ -0,0 +1,86 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchExpenses } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatCurrency, formatDate, truncate, formatStatus } from '../utils/table';
export function createExpensesCommand(): Command {
const command = new Command('expenses')
.description('Manage expenses');
command
.command('list')
.description('List all expenses')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('--active-only', 'Show only active expenses', false)
.action(async (options) => {
const spinner = ora('Loading expenses...').start();
try {
const fetcher = createAuthenticatedFetcher();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.activeOnly && { active: true }),
};
const response = await fetchExpenses(fetcher, query);
spinner.stop();
const expenses = (response as unknown as { expenses: Array<{
id: number;
paymentDate?: string;
description?: string;
total?: number;
status?: string;
active?: boolean;
}> }).expenses;
if (!expenses || expenses.length === 0) {
console.log(chalk.yellow('No expenses found.'));
return;
}
const pagination = (response as unknown as {
pagination?: { total: number; page: number; pageSize?: number; page_size?: number }
}).pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total expenses)\n`));
}
const table = createTable(['ID', 'Date', 'Description', 'Total', 'Status']);
expenses.forEach((expense) => {
table.push([
expense.id,
formatDate(expense.paymentDate),
truncate(expense.description, 35),
formatCurrency(expense.total),
formatStatus(expense.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+154
View File
@@ -0,0 +1,154 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchInventoryAdjustments, fetchWarehouseTransfers } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatDate, formatStatus, truncate } from '../utils/table';
export function createInventoryCommand(): Command {
const command = new Command('inventory')
.description('Manage inventory');
command
.command('adjustments')
.description('List inventory adjustments')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.action(async (options) => {
const spinner = ora('Loading inventory adjustments...').start();
try {
const fetcher = createAuthenticatedFetcher();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
};
const response = await fetchInventoryAdjustments(fetcher, query);
spinner.stop();
const adjustments = (response as unknown as { inventoryAdjustments: Array<{
id: number;
adjustmentDate?: string;
referenceNo?: string;
reason?: string;
status?: string;
}> }).inventoryAdjustments;
if (!adjustments || adjustments.length === 0) {
console.log(chalk.yellow('No inventory adjustments found.'));
return;
}
const pagination = (response as unknown as {
pagination?: { total: number; page: number; pageSize?: number; page_size?: number }
}).pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total adjustments)\n`));
}
const table = createTable(['ID', 'Date', 'Reference', 'Reason', 'Status']);
adjustments.forEach((adj) => {
table.push([
adj.id,
formatDate(adj.adjustmentDate),
adj.referenceNo || '-',
truncate(adj.reason, 30),
formatStatus(adj.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
} catch (error) {
spinner.stop();
handleError(error);
}
});
command
.command('transfers')
.description('List warehouse transfers')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.action(async (options) => {
const spinner = ora('Loading warehouse transfers...').start();
try {
const fetcher = createAuthenticatedFetcher();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
};
const response = await fetchWarehouseTransfers(fetcher, query);
spinner.stop();
const transfers = (response as unknown as { warehouseTransfers: Array<{
id: number;
date?: string;
referenceNo?: string;
fromWarehouse?: { name?: string };
toWarehouse?: { name?: string };
status?: string;
}> }).warehouseTransfers;
if (!transfers || transfers.length === 0) {
console.log(chalk.yellow('No warehouse transfers found.'));
return;
}
const pagination = (response as unknown as {
pagination?: { total: number; page: number; pageSize?: number; page_size?: number }
}).pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total transfers)\n`));
}
const table = createTable(['ID', 'Date', 'Reference', 'From', 'To', 'Status']);
transfers.forEach((transfer) => {
table.push([
transfer.id,
formatDate(transfer.date),
transfer.referenceNo || '-',
truncate(transfer.fromWarehouse?.name, 15),
truncate(transfer.toWarehouse?.name, 15),
formatStatus(transfer.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+94
View File
@@ -0,0 +1,94 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchSaleInvoices } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatCurrency, formatDate, truncate, formatStatus } from '../utils/table';
export function createInvoicesCommand(): Command {
const command = new Command('invoices')
.description('Manage sale invoices');
command
.command('list')
.description('List all sale invoices')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('-c, --customer <id>', 'Filter by customer ID')
.option('-s, --status <status>', 'Filter by status (draft, published, paid, partial, overdue)')
.action(async (options) => {
const spinner = ora('Loading invoices...').start();
try {
const fetcher = createAuthenticatedFetcher();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.customer && { customerId: parseInt(options.customer, 10) }),
...(options.status && { status: options.status }),
};
const response = await fetchSaleInvoices(fetcher, query);
spinner.stop();
// The response has 'salesInvoices' array (camelCase from middleware), not 'data'
const invoices = (response as unknown as { salesInvoices: Array<{
id: number;
invoiceNo?: string;
customer?: { displayName?: string };
invoiceDate?: string;
total?: number;
balance?: number;
status?: string;
}> }).salesInvoices;
if (!invoices || invoices.length === 0) {
console.log(chalk.yellow('No invoices found.'));
return;
}
// Pagination info
const pagination = (response as unknown as {
pagination?: { total: number; page: number; pageSize?: number; page_size?: number }
}).pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total invoices)\n`));
}
// Create table
const table = createTable(['ID', 'Invoice #', 'Customer', 'Date', 'Total', 'Balance', 'Status']);
invoices.forEach((invoice) => {
table.push([
invoice.id,
invoice.invoiceNo || '-',
truncate(invoice.customer?.displayName, 25),
formatDate(invoice.invoiceDate),
formatCurrency(invoice.total),
formatCurrency(invoice.balance),
formatStatus(invoice.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+94
View File
@@ -0,0 +1,94 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchItems } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatCurrency, truncate, formatStatus } from '../utils/table';
export function createItemsCommand(): Command {
const command = new Command('items')
.description('Manage items and products');
command
.command('list')
.description('List all items/products')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('-t, --type <type>', 'Filter by item type (inventory, service, product)')
.option('--active-only', 'Show only active items', false)
.action(async (options) => {
const spinner = ora('Loading items...').start();
try {
const fetcher = createAuthenticatedFetcher();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.type && { type: options.type }),
...(options.activeOnly && { active: true }),
};
const response = await fetchItems(fetcher, query);
spinner.stop();
// The response has 'items' array, not 'data'
const items = (response as unknown as { items: Array<{
id: number;
name?: string;
type?: string;
sellPrice?: number;
costPrice?: number;
quantityOnHand?: number;
active?: boolean;
}> }).items;
if (!items || items.length === 0) {
console.log(chalk.yellow('No items found.'));
return;
}
// Pagination info
const pagination = (response as unknown as {
pagination?: { total: number; page: number; pageSize?: number; page_size?: number }
}).pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total items)\n`));
}
// Create table
const table = createTable(['ID', 'Name', 'Type', 'Sell Price', 'Cost Price', 'Qty', 'Status']);
items.forEach((item) => {
table.push([
item.id,
truncate(item.name, 30),
item.type || '-',
formatCurrency(item.sellPrice),
formatCurrency(item.costPrice),
item.quantityOnHand ?? '-',
formatStatus(item.active ? 'active' : 'inactive'),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+82
View File
@@ -0,0 +1,82 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchManualJournals } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatDate, truncate, formatStatus } from '../utils/table';
export function createJournalsCommand(): Command {
const command = new Command('journals')
.description('Manage manual journals');
command
.command('list')
.description('List all manual journals')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.action(async (options) => {
const spinner = ora('Loading journals...').start();
try {
const fetcher = createAuthenticatedFetcher();
const response = await fetchManualJournals(fetcher, {} as never);
spinner.stop();
const journals = (response as unknown as { manualJournals: Array<{
id: number;
journalNumber?: string;
date?: string;
reference?: string;
description?: string;
status?: string;
amount?: number;
}> }).manualJournals;
if (!journals || journals.length === 0) {
console.log(chalk.yellow('No manual journals found.'));
return;
}
const pagination = (response as unknown as {
pagination?: { total: number; page: number; pageSize?: number; page_size?: number }
}).pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total journals)\n`));
}
const table = createTable(['ID', 'Journal #', 'Date', 'Reference', 'Description', 'Amount', 'Status']);
journals.forEach((journal) => {
table.push([
journal.id,
journal.journalNumber || '-',
formatDate(journal.date),
journal.reference || '-',
truncate(journal.description, 25),
journal.amount?.toFixed(2) || '-',
formatStatus(journal.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+102
View File
@@ -0,0 +1,102 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchPaymentsReceived, fetchBillPayments } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatCurrency, formatDate, truncate } from '../utils/table';
export function createPaymentsCommand(): Command {
const command = new Command('payments')
.description('Manage payments');
command
.command('received')
.description('List payments received from customers')
.action(async () => {
const spinner = ora('Loading payments received...').start();
try {
const fetcher = createAuthenticatedFetcher();
const response = await fetchPaymentsReceived(fetcher);
spinner.stop();
const payments = (response as unknown as { paymentsReceived: Array<{
id: number;
customer?: { displayName?: string };
paymentDate?: string;
amount?: number;
referenceNo?: string;
}> }).paymentsReceived;
if (!payments || payments.length === 0) {
console.log(chalk.yellow('No payments received found.'));
return;
}
const table = createTable(['ID', 'Customer', 'Date', 'Amount', 'Reference']);
payments.forEach((payment) => {
table.push([
payment.id,
truncate(payment.customer?.displayName, 25),
formatDate(payment.paymentDate),
formatCurrency(payment.amount),
payment.referenceNo || '-',
]);
});
console.log(table.toString());
} catch (error) {
spinner.stop();
handleError(error);
}
});
command
.command('made')
.description('List bill payments made to vendors')
.action(async () => {
const spinner = ora('Loading payments made...').start();
try {
const fetcher = createAuthenticatedFetcher();
const response = await fetchBillPayments(fetcher);
spinner.stop();
const payments = (response as unknown as { billPayments: Array<{
id: number;
vendor?: { displayName?: string };
paymentDate?: string;
amount?: number;
referenceNo?: string;
}> }).billPayments;
if (!payments || payments.length === 0) {
console.log(chalk.yellow('No payments made found.'));
return;
}
const table = createTable(['ID', 'Vendor', 'Date', 'Amount', 'Reference']);
payments.forEach((payment) => {
table.push([
payment.id,
truncate(payment.vendor?.displayName, 25),
formatDate(payment.paymentDate),
formatCurrency(payment.amount),
payment.referenceNo || '-',
]);
});
console.log(table.toString());
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+87
View File
@@ -0,0 +1,87 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchSaleReceipts } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatCurrency, formatDate, truncate, formatStatus } from '../utils/table';
export function createReceiptsCommand(): Command {
const command = new Command('receipts')
.description('Manage sale receipts');
command
.command('list')
.description('List all sale receipts')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('-c, --customer <id>', 'Filter by customer ID')
.action(async (options) => {
const spinner = ora('Loading receipts...').start();
try {
const fetcher = createAuthenticatedFetcher();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.customer && { customerId: parseInt(options.customer, 10) }),
};
const response = await fetchSaleReceipts(fetcher, query);
spinner.stop();
const receipts = (response as unknown as { saleReceipts: Array<{
id: number;
receiptNumber?: string;
customer?: { displayName?: string };
receiptDate?: string;
total?: number;
status?: string;
}> }).saleReceipts;
if (!receipts || receipts.length === 0) {
console.log(chalk.yellow('No receipts found.'));
return;
}
const pagination = (response as unknown as {
pagination?: { total: number; page: number; pageSize?: number; page_size?: number }
}).pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total receipts)\n`));
}
const table = createTable(['ID', 'Receipt #', 'Customer', 'Date', 'Total', 'Status']);
receipts.forEach((receipt) => {
table.push([
receipt.id,
receipt.receiptNumber || '-',
truncate(receipt.customer?.displayName, 20),
formatDate(receipt.receiptDate),
formatCurrency(receipt.total),
formatStatus(receipt.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+513
View File
@@ -0,0 +1,513 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import {
fetchBalanceSheetJson,
fetchProfitLossJson,
fetchCashflowStatementJson,
fetchTrialBalanceJson,
fetchGeneralLedgerJson,
fetchJournalJson,
fetchReceivableAgingJson,
fetchPayableAgingJson,
fetchCustomerBalanceJson,
fetchVendorBalanceJson,
fetchSalesByItemsJson,
fetchPurchasesByItemsJson,
fetchInventoryValuationJson,
fetchInventoryItemDetailsJson,
fetchSalesTaxLiabilityJson,
} from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatCurrency } from '../utils/table';
interface DateRangeOptions {
from?: string;
to?: string;
}
function getDateRange(options: DateRangeOptions): { fromDate: string; toDate: string } {
const toDate = options.to || new Date().toISOString().split('T')[0];
const fromDate = options.from || new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
return { fromDate, toDate };
}
export function createReportsCommand(): Command {
const command = new Command('reports')
.description('Financial reports');
// Balance Sheet
command
.command('balance-sheet')
.description('Show balance sheet')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options: DateRangeOptions) => {
const spinner = ora('Loading balance sheet...').start();
try {
const fetcher = createAuthenticatedFetcher();
const { fromDate, toDate } = getDateRange(options);
const report = await fetchBalanceSheetJson(fetcher, { fromDate, toDate } as never);
spinner.stop();
console.log(chalk.bold('\n📊 Balance Sheet'));
console.log(chalk.gray(`${fromDate} to ${toDate}\n`));
// Assets
console.log(chalk.green.bold('\nASSETS'));
const assetsTable = createTable(['Account', 'Amount']);
let totalAssets = 0;
(report as unknown as { assets?: Array<{ accountName: string; total: number }> }).assets?.forEach((item) => {
assetsTable.push([item.accountName, formatCurrency(item.total)]);
totalAssets += item.total;
});
assetsTable.push(['', '']);
assetsTable.push([chalk.bold('Total Assets'), chalk.bold(formatCurrency(totalAssets))]);
console.log(assetsTable.toString());
// Liabilities
console.log(chalk.red.bold('\nLIABILITIES'));
const liabilitiesTable = createTable(['Account', 'Amount']);
let totalLiabilities = 0;
(report as unknown as { liabilities?: Array<{ accountName: string; total: number }> }).liabilities?.forEach((item) => {
liabilitiesTable.push([item.accountName, formatCurrency(item.total)]);
totalLiabilities += item.total;
});
liabilitiesTable.push(['', '']);
liabilitiesTable.push([chalk.bold('Total Liabilities'), chalk.bold(formatCurrency(totalLiabilities))]);
console.log(liabilitiesTable.toString());
// Equity
console.log(chalk.blue.bold('\nEQUITY'));
const equityTable = createTable(['Account', 'Amount']);
let totalEquity = 0;
(report as unknown as { equity?: Array<{ accountName: string; total: number }> }).equity?.forEach((item) => {
equityTable.push([item.accountName, formatCurrency(item.total)]);
totalEquity += item.total;
});
equityTable.push(['', '']);
equityTable.push([chalk.bold('Total Equity'), chalk.bold(formatCurrency(totalEquity))]);
console.log(equityTable.toString());
// Summary
console.log(chalk.bold('\n' + '─'.repeat(40)));
console.log(`${chalk.bold('Total Liabilities + Equity:')} ${formatCurrency(totalLiabilities + totalEquity)}`);
console.log(`${chalk.bold('Balance:')} ${totalAssets === (totalLiabilities + totalEquity) ? chalk.green('✓ Balanced') : chalk.red('✗ Unbalanced')}\n`);
} catch (error) {
spinner.stop();
handleError(error);
}
});
// Profit & Loss
command
.command('profit-loss')
.alias('pl')
.description('Show profit and loss statement')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options: DateRangeOptions) => {
const spinner = ora('Loading profit & loss...').start();
try {
const fetcher = createAuthenticatedFetcher();
const { fromDate, toDate } = getDateRange(options);
const report = await fetchProfitLossJson(fetcher, { fromDate, toDate } as never);
spinner.stop();
console.log(chalk.bold('\n📈 Profit & Loss Statement'));
console.log(chalk.gray(`${fromDate} to ${toDate}\n`));
const income = (report as unknown as { income?: { total: number } }).income?.total || 0;
const expenses = (report as unknown as { expenses?: { total: number } }).expenses?.total || 0;
const netProfit = income - expenses;
const table = createTable(['', 'Amount']);
table.push([chalk.green('Total Income'), formatCurrency(income)]);
table.push([chalk.red('Total Expenses'), formatCurrency(expenses)]);
table.push(['', '']);
table.push([netProfit >= 0 ? chalk.green.bold('Net Profit') : chalk.red.bold('Net Loss'), formatCurrency(Math.abs(netProfit))]);
console.log(table.toString() + '\n');
} catch (error) {
spinner.stop();
handleError(error);
}
});
// Cashflow
command
.command('cashflow')
.description('Show cashflow statement')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options: DateRangeOptions) => {
const spinner = ora('Loading cashflow statement...').start();
try {
const fetcher = createAuthenticatedFetcher();
const { fromDate, toDate } = getDateRange(options);
const report = await fetchCashflowStatementJson(fetcher, { fromDate, toDate } as never);
spinner.stop();
console.log(chalk.bold('\n💰 Cashflow Statement'));
console.log(chalk.gray(`${fromDate} to ${toDate}\n`));
const operating = (report as unknown as { operating?: { total: number } }).operating?.total || 0;
const investing = (report as unknown as { investing?: { total: number } }).investing?.total || 0;
const financing = (report as unknown as { financing?: { total: number } }).financing?.total || 0;
const netCash = operating + investing + financing;
const table = createTable(['Activity', 'Amount']);
table.push(['Operating Activities', formatCurrency(operating)]);
table.push(['Investing Activities', formatCurrency(investing)]);
table.push(['Financing Activities', formatCurrency(financing)]);
table.push(['', '']);
table.push([chalk.bold('Net Cash Change'), chalk.bold(formatCurrency(netCash))]);
console.log(table.toString() + '\n');
} catch (error) {
spinner.stop();
handleError(error);
}
});
// Trial Balance
command
.command('trial-balance')
.description('Show trial balance')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options: DateRangeOptions) => {
const spinner = ora('Loading trial balance...').start();
try {
const fetcher = createAuthenticatedFetcher();
const { fromDate, toDate } = getDateRange(options);
const report = await fetchTrialBalanceJson(fetcher, {} as never);
spinner.stop();
console.log(chalk.bold('\n⚖️ Trial Balance'));
console.log(chalk.gray(`${fromDate} to ${toDate}\n`));
const table = createTable(['Account', 'Debit', 'Credit']);
let totalDebit = 0;
let totalCredit = 0;
(report as unknown as { data?: Array<{ accountName: string; debit: number; credit: number }> }).data?.forEach((item) => {
table.push([item.accountName, formatCurrency(item.debit), formatCurrency(item.credit)]);
totalDebit += item.debit;
totalCredit += item.credit;
});
table.push(['', '', '']);
table.push([chalk.bold('Total'), chalk.bold(formatCurrency(totalDebit)), chalk.bold(formatCurrency(totalCredit))]);
console.log(table.toString());
console.log(`\n${chalk.bold('Balance:')} ${Math.abs(totalDebit - totalCredit) < 0.01 ? chalk.green('✓ Balanced') : chalk.red('✗ Unbalanced')}\n`);
} catch (error) {
spinner.stop();
handleError(error);
}
});
// General Ledger
command
.command('general-ledger')
.description('Show general ledger')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options: DateRangeOptions) => {
const spinner = ora('Loading general ledger...').start();
try {
const fetcher = createAuthenticatedFetcher();
const { fromDate, toDate } = getDateRange(options);
const report = await fetchGeneralLedgerJson(fetcher, {} as never);
spinner.stop();
console.log(chalk.bold('\n📒 General Ledger'));
console.log(chalk.gray(`${fromDate} to ${toDate}\n`));
console.log(JSON.stringify(report, null, 2));
} catch (error) {
spinner.stop();
handleError(error);
}
});
// Journal
command
.command('journal')
.description('Show journal report')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options: DateRangeOptions) => {
const spinner = ora('Loading journal...').start();
try {
const fetcher = createAuthenticatedFetcher();
const { fromDate, toDate } = getDateRange(options);
const report = await fetchJournalJson(fetcher, {} as never);
spinner.stop();
console.log(chalk.bold('\n📝 Journal Report'));
console.log(chalk.gray(`${fromDate} to ${toDate}\n`));
const table = createTable(['Date', 'Reference', 'Account', 'Debit', 'Credit']);
(report as unknown as { data?: Array<{ date: string; reference: string; accountName: string; debit: number; credit: number }> }).data?.forEach((item) => {
table.push([item.date, item.reference || '-', item.accountName, formatCurrency(item.debit), formatCurrency(item.credit)]);
});
console.log(table.toString() + '\n');
} catch (error) {
spinner.stop();
handleError(error);
}
});
// Receivable Aging
command
.command('receivable-aging')
.description('Show accounts receivable aging')
.action(async () => {
const spinner = ora('Loading receivable aging...').start();
try {
const fetcher = createAuthenticatedFetcher();
const report = await fetchReceivableAgingJson(fetcher, {} as never);
spinner.stop();
console.log(chalk.bold('\n📋 Accounts Receivable Aging\n'));
const table = createTable(['Customer', 'Current', '1-30', '31-60', '61-90', '90+', 'Total']);
(report as unknown as { data?: Array<{ customerName: string; current: number; days1to30: number; days31to60: number; days61to90: number; over90: number; total: number }> }).data?.forEach((item) => {
table.push([
item.customerName,
formatCurrency(item.current),
formatCurrency(item.days1to30),
formatCurrency(item.days31to60),
formatCurrency(item.days61to90),
formatCurrency(item.over90),
chalk.bold(formatCurrency(item.total)),
]);
});
console.log(table.toString() + '\n');
} catch (error) {
spinner.stop();
handleError(error);
}
});
// Payable Aging
command
.command('payable-aging')
.description('Show accounts payable aging')
.action(async () => {
const spinner = ora('Loading payable aging...').start();
try {
const fetcher = createAuthenticatedFetcher();
const report = await fetchPayableAgingJson(fetcher, {} as never);
spinner.stop();
console.log(chalk.bold('\n📋 Accounts Payable Aging\n'));
const table = createTable(['Vendor', 'Current', '1-30', '31-60', '61-90', '90+', 'Total']);
(report as unknown as { data?: Array<{ vendorName: string; current: number; days1to30: number; days31to60: number; days61to90: number; over90: number; total: number }> }).data?.forEach((item) => {
table.push([
item.vendorName,
formatCurrency(item.current),
formatCurrency(item.days1to30),
formatCurrency(item.days31to60),
formatCurrency(item.days61to90),
formatCurrency(item.over90),
chalk.bold(formatCurrency(item.total)),
]);
});
console.log(table.toString() + '\n');
} catch (error) {
spinner.stop();
handleError(error);
}
});
// Customer Balance
command
.command('customer-balance')
.description('Show customer balance summary')
.action(async () => {
const spinner = ora('Loading customer balances...').start();
try {
const fetcher = createAuthenticatedFetcher();
const report = await fetchCustomerBalanceJson(fetcher, {} as never);
spinner.stop();
console.log(chalk.bold('\n👤 Customer Balance Summary\n'));
const table = createTable(['Customer', 'Total']);
(report as unknown as { data?: Array<{ customerName: string; total: number }> }).data?.forEach((item) => {
table.push([item.customerName, formatCurrency(item.total)]);
});
console.log(table.toString() + '\n');
} catch (error) {
spinner.stop();
handleError(error);
}
});
// Vendor Balance
command
.command('vendor-balance')
.description('Show vendor balance summary')
.action(async () => {
const spinner = ora('Loading vendor balances...').start();
try {
const fetcher = createAuthenticatedFetcher();
const report = await fetchVendorBalanceJson(fetcher, {} as never);
spinner.stop();
console.log(chalk.bold('\n🏢 Vendor Balance Summary\n'));
const table = createTable(['Vendor', 'Total']);
(report as unknown as { data?: Array<{ vendorName: string; total: number }> }).data?.forEach((item) => {
table.push([item.vendorName, formatCurrency(item.total)]);
});
console.log(table.toString() + '\n');
} catch (error) {
spinner.stop();
handleError(error);
}
});
// Sales by Items
command
.command('sales-by-items')
.description('Show sales by items report')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options: DateRangeOptions) => {
const spinner = ora('Loading sales by items...').start();
try {
const fetcher = createAuthenticatedFetcher();
const { fromDate, toDate } = getDateRange(options);
const report = await fetchSalesByItemsJson(fetcher, { fromDate, toDate } as never);
spinner.stop();
console.log(chalk.bold('\n🛍️ Sales by Items'));
console.log(chalk.gray(`${fromDate} to ${toDate}\n`));
const table = createTable(['Item', 'Quantity', 'Amount']);
(report as unknown as { data?: Array<{ itemName: string; quantity: number; amount: number }> }).data?.forEach((item) => {
table.push([item.itemName, item.quantity.toString(), formatCurrency(item.amount)]);
});
console.log(table.toString() + '\n');
} catch (error) {
spinner.stop();
handleError(error);
}
});
// Purchases by Items
command
.command('purchases-by-items')
.description('Show purchases by items report')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options: DateRangeOptions) => {
const spinner = ora('Loading purchases by items...').start();
try {
const fetcher = createAuthenticatedFetcher();
const { fromDate, toDate } = getDateRange(options);
const report = await fetchPurchasesByItemsJson(fetcher, { fromDate, toDate } as never);
spinner.stop();
console.log(chalk.bold('\n🛒 Purchases by Items'));
console.log(chalk.gray(`${fromDate} to ${toDate}\n`));
const table = createTable(['Item', 'Quantity', 'Amount']);
(report as unknown as { data?: Array<{ itemName: string; quantity: number; amount: number }> }).data?.forEach((item) => {
table.push([item.itemName, item.quantity.toString(), formatCurrency(item.amount)]);
});
console.log(table.toString() + '\n');
} catch (error) {
spinner.stop();
handleError(error);
}
});
// Inventory Valuation
command
.command('inventory-valuation')
.description('Show inventory valuation summary')
.action(async () => {
const spinner = ora('Loading inventory valuation...').start();
try {
const fetcher = createAuthenticatedFetcher();
const report = await fetchInventoryValuationJson(fetcher, {} as never);
spinner.stop();
console.log(chalk.bold('\n📦 Inventory Valuation\n'));
const table = createTable(['Item', 'Qty on Hand', 'Avg Cost', 'Total Value']);
(report as unknown as { data?: Array<{ itemName: string; quantityOnHand: number; averageCost: number; totalValue: number }> }).data?.forEach((item) => {
table.push([item.itemName, item.quantityOnHand.toString(), formatCurrency(item.averageCost), formatCurrency(item.totalValue)]);
});
console.log(table.toString() + '\n');
} catch (error) {
spinner.stop();
handleError(error);
}
});
// Inventory Details
command
.command('inventory-details')
.description('Show inventory item details')
.option('--item <id>', 'Filter by item ID')
.action(async (options: { item?: string }) => {
const spinner = ora('Loading inventory details...').start();
try {
const fetcher = createAuthenticatedFetcher();
const report = await fetchInventoryItemDetailsJson(fetcher, {} as never);
spinner.stop();
console.log(chalk.bold('\n📦 Inventory Item Details\n'));
console.log(JSON.stringify(report, null, 2));
} catch (error) {
spinner.stop();
handleError(error);
}
});
// Sales Tax Liability
command
.command('sales-tax-liability')
.description('Show sales tax liability report')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.action(async (options: DateRangeOptions) => {
const spinner = ora('Loading sales tax liability...').start();
try {
const fetcher = createAuthenticatedFetcher();
const { fromDate, toDate } = getDateRange(options);
const report = await fetchSalesTaxLiabilityJson(fetcher, { fromDate, toDate } as never);
spinner.stop();
console.log(chalk.bold('\n💵 Sales Tax Liability'));
console.log(chalk.gray(`${fromDate} to ${toDate}\n`));
const table = createTable(['Tax Rate', 'Taxable Amount', 'Tax Amount']);
(report as unknown as { data?: Array<{ taxRateName: string; taxableAmount: number; taxAmount: number }> }).data?.forEach((item) => {
table.push([item.taxRateName, formatCurrency(item.taxableAmount), formatCurrency(item.taxAmount)]);
});
console.log(table.toString() + '\n');
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+58
View File
@@ -0,0 +1,58 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchTaxRates } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatStatus } from '../utils/table';
export function createTaxRatesCommand(): Command {
const command = new Command('tax-rates')
.description('Manage tax rates');
command
.command('list')
.description('List all tax rates')
.action(async () => {
const spinner = ora('Loading tax rates...').start();
try {
const fetcher = createAuthenticatedFetcher();
const response = await fetchTaxRates(fetcher);
spinner.stop();
const taxRates = (response as unknown as { taxRates: Array<{
id: number;
name?: string;
rate?: number;
code?: string;
active?: boolean;
}> }).taxRates;
if (!taxRates || taxRates.length === 0) {
console.log(chalk.yellow('No tax rates found.'));
return;
}
const table = createTable(['ID', 'Name', 'Code', 'Rate', 'Status']);
taxRates.forEach((tax) => {
table.push([
tax.id,
tax.name || '-',
tax.code || '-',
`${tax.rate || 0}%`,
formatStatus(tax.active ? 'active' : 'inactive'),
]);
});
console.log(table.toString());
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+102
View File
@@ -0,0 +1,102 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchUsers, fetchRoles } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatStatus } from '../utils/table';
export function createUsersCommand(): Command {
const command = new Command('users')
.description('Manage users and roles');
command
.command('list')
.description('List all users')
.action(async () => {
const spinner = ora('Loading users...').start();
try {
const fetcher = createAuthenticatedFetcher();
const response = await fetchUsers(fetcher);
spinner.stop();
const users = (response as unknown as { users: Array<{
id: number;
firstName?: string;
lastName?: string;
email?: string;
role?: { name?: string };
active?: boolean;
}> }).users;
if (!users || users.length === 0) {
console.log(chalk.yellow('No users found.'));
return;
}
const table = createTable(['ID', 'Name', 'Email', 'Role', 'Status']);
users.forEach((user) => {
const name = `${user.firstName || ''} ${user.lastName || ''}`.trim() || '-';
table.push([
user.id,
name,
user.email || '-',
user.role?.name || '-',
formatStatus(user.active ? 'active' : 'inactive'),
]);
});
console.log(table.toString());
} catch (error) {
spinner.stop();
handleError(error);
}
});
command
.command('roles')
.description('List all roles')
.action(async () => {
const spinner = ora('Loading roles...').start();
try {
const fetcher = createAuthenticatedFetcher();
const response = await fetchRoles(fetcher);
spinner.stop();
const roles = (response as unknown as { roles: Array<{
id: number;
name?: string;
description?: string;
slug?: string;
}> }).roles;
if (!roles || roles.length === 0) {
console.log(chalk.yellow('No roles found.'));
return;
}
const table = createTable(['ID', 'Name', 'Slug', 'Description']);
roles.forEach((role) => {
table.push([
role.id,
role.name || '-',
role.slug || '-',
role.description || '-',
]);
});
console.log(table.toString());
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
@@ -0,0 +1,89 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchVendorCredits } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatCurrency, formatDate, truncate, formatStatus } from '../utils/table';
export function createVendorCreditsCommand(): Command {
const command = new Command('vendor-credits')
.description('Manage vendor credits');
command
.command('list')
.description('List all vendor credits')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('-v, --vendor <id>', 'Filter by vendor ID')
.action(async (options) => {
const spinner = ora('Loading vendor credits...').start();
try {
const fetcher = createAuthenticatedFetcher();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.vendor && { vendorId: parseInt(options.vendor, 10) }),
};
const response = await fetchVendorCredits(fetcher, query);
spinner.stop();
const vendorCredits = (response as unknown as { vendorCredits: Array<{
id: number;
vendorCreditNumber?: string;
vendor?: { displayName?: string };
vendorCreditDate?: string;
total?: number;
balance?: number;
status?: string;
}> }).vendorCredits;
if (!vendorCredits || vendorCredits.length === 0) {
console.log(chalk.yellow('No vendor credits found.'));
return;
}
const pagination = (response as unknown as {
pagination?: { total: number; page: number; pageSize?: number; page_size?: number }
}).pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total vendor credits)\n`));
}
const table = createTable(['ID', 'VC #', 'Vendor', 'Date', 'Total', 'Balance', 'Status']);
vendorCredits.forEach((vc) => {
table.push([
vc.id,
vc.vendorCreditNumber || '-',
truncate(vc.vendor?.displayName, 20),
formatDate(vc.vendorCreditDate),
formatCurrency(vc.total),
formatCurrency(vc.balance),
formatStatus(vc.status),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+89
View File
@@ -0,0 +1,89 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchVendors } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatCurrency, truncate, formatStatus } from '../utils/table';
export function createVendorsCommand(): Command {
const command = new Command('vendors')
.description('Manage vendors');
command
.command('list')
.description('List all vendors')
.option('-l, --limit <number>', 'Limit number of results per page', '50')
.option('-p, --page <number>', 'Page number', '1')
.option('--active-only', 'Show only active vendors', false)
.action(async (options) => {
const spinner = ora('Loading vendors...').start();
try {
const fetcher = createAuthenticatedFetcher();
const query = {
page: parseInt(options.page, 10),
pageSize: Math.min(parseInt(options.limit, 10), 100),
...(options.activeOnly && { active: true }),
};
const response = await fetchVendors(fetcher, query);
spinner.stop();
const vendors = (response as unknown as { vendors: Array<{
id: number;
displayName?: string;
email?: string;
workPhone?: string;
personalPhone?: string;
balance?: number;
active?: boolean;
}> }).vendors;
if (!vendors || vendors.length === 0) {
console.log(chalk.yellow('No vendors found.'));
return;
}
const pagination = (response as unknown as {
pagination?: { total: number; page: number; pageSize?: number; page_size?: number }
}).pagination;
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
console.log(chalk.gray(`\nPage ${pagination.page} of ${totalPages} (${pagination.total} total vendors)\n`));
}
const table = createTable(['ID', 'Name', 'Email', 'Work Phone', 'Personal Phone', 'Balance', 'Status']);
vendors.forEach((vendor) => {
table.push([
vendor.id,
truncate(vendor.displayName, 25),
truncate(vendor.email, 25) || '-',
vendor.workPhone || '-',
vendor.personalPhone || '-',
formatCurrency(vendor.balance),
formatStatus(vendor.active ? 'active' : 'inactive'),
]);
});
console.log(table.toString());
if (pagination) {
const pageSize = pagination.pageSize || pagination.page_size || 10;
const totalPages = Math.ceil(pagination.total / pageSize);
if (pagination.page < totalPages) {
console.log(chalk.gray(`\nUse --page ${pagination.page + 1} to see more results`));
}
}
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+59
View File
@@ -0,0 +1,59 @@
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { fetchWarehouses } from '@bigcapital/sdk-ts';
import { createAuthenticatedFetcher } from '../config';
import { handleError } from '../utils/errors';
import { createTable, formatStatus } from '../utils/table';
export function createWarehousesCommand(): Command {
const command = new Command('warehouses')
.description('Manage warehouses');
command
.command('list')
.description('List all warehouses')
.action(async () => {
const spinner = ora('Loading warehouses...').start();
try {
const fetcher = createAuthenticatedFetcher();
const response = await fetchWarehouses(fetcher);
spinner.stop();
const warehouses = (response as unknown as { warehouses: Array<{
id: string;
name?: string;
code?: string;
address?: string;
city?: string;
active?: boolean;
}> }).warehouses;
if (!warehouses || warehouses.length === 0) {
console.log(chalk.yellow('No warehouses found.'));
return;
}
const table = createTable(['ID', 'Code', 'Name', 'City', 'Status']);
warehouses.forEach((warehouse) => {
table.push([
warehouse.id,
warehouse.code || '-',
warehouse.name || '-',
warehouse.city || '-',
formatStatus(warehouse.active ? 'active' : 'inactive'),
]);
});
console.log(table.toString());
} catch (error) {
spinner.stop();
handleError(error);
}
});
return command;
}
+83
View File
@@ -0,0 +1,83 @@
import Conf from 'conf';
import chalk from 'chalk';
import { createApiFetcher, ApiFetcher } from '@bigcapital/sdk-ts';
interface ConfigSchema {
apiKey: string;
baseUrl: string;
organizationId?: string;
}
const config = new Conf<ConfigSchema>({
projectName: 'bigcapital',
schema: {
apiKey: {
type: 'string',
},
baseUrl: {
type: 'string',
},
organizationId: {
type: 'string',
},
},
});
export function getConfig(): ConfigSchema {
return {
apiKey: config.get('apiKey', ''),
baseUrl: config.get('baseUrl', ''),
organizationId: config.get('organizationId'),
};
}
export function setApiKey(apiKey: string): void {
config.set('apiKey', apiKey);
}
export function setBaseUrl(baseUrl: string): void {
// Remove trailing slash if present
const normalizedUrl = baseUrl.replace(/\/$/, '');
config.set('baseUrl', normalizedUrl);
}
export function setOrganizationId(organizationId: string): void {
config.set('organizationId', organizationId);
}
export function validateConfig(): ConfigSchema {
const currentConfig = getConfig();
if (!currentConfig.apiKey) {
console.error(chalk.red('Error: API key is not configured.'));
console.error(chalk.yellow('Run: bigcapital config set api-key <your-api-key>'));
process.exit(1);
}
if (!currentConfig.baseUrl) {
console.error(chalk.red('Error: Base URL is not configured.'));
console.error(chalk.yellow('Run: bigcapital config set base-url <https://your-api-url>'));
process.exit(1);
}
return currentConfig;
}
export function createAuthenticatedFetcher(): ApiFetcher {
const currentConfig = validateConfig();
const headers: Record<string, string> = {
'Authorization': `Bearer ${currentConfig.apiKey}`,
};
if (currentConfig.organizationId) {
headers['organization-id'] = currentConfig.organizationId;
}
return createApiFetcher({
baseUrl: currentConfig.baseUrl,
init: {
headers,
},
});
}
+79
View File
@@ -0,0 +1,79 @@
#!/usr/bin/env node
import { Command } from 'commander';
import { createItemsCommand } from './commands/items';
import { createInvoicesCommand } from './commands/invoices';
import { createConfigCommand } from './commands/config';
import { createCustomersCommand } from './commands/customers';
import { createVendorsCommand } from './commands/vendors';
import { createBillsCommand } from './commands/bills';
import { createAccountsCommand } from './commands/accounts';
import { createExpensesCommand } from './commands/expenses';
import { createCreditNotesCommand } from './commands/credit-notes';
import { createVendorCreditsCommand } from './commands/vendor-credits';
import { createPaymentsCommand } from './commands/payments';
import { createEstimatesCommand } from './commands/estimates';
import { createReceiptsCommand } from './commands/receipts';
import { createJournalsCommand } from './commands/journals';
import { createInventoryCommand } from './commands/inventory';
import { createTaxRatesCommand } from './commands/tax-rates';
import { createWarehousesCommand } from './commands/warehouses';
import { createUsersCommand } from './commands/users';
import { createReportsCommand } from './commands/reports';
import chalk from 'chalk';
const program = new Command();
program
.name('bigcapital')
.description('Bigcapital CLI - Interact with Bigcapital API')
.version('1.0.0')
.configureOutput({
writeErr: (str) => process.stderr.write(chalk.red(str)),
outputError: (str, write) => write(chalk.red(str)),
});
// Core modules
program.addCommand(createConfigCommand());
program.addCommand(createItemsCommand());
program.addCommand(createInvoicesCommand());
program.addCommand(createCustomersCommand());
program.addCommand(createVendorsCommand());
program.addCommand(createBillsCommand());
// Additional transactional modules
program.addCommand(createAccountsCommand());
program.addCommand(createExpensesCommand());
program.addCommand(createCreditNotesCommand());
program.addCommand(createVendorCreditsCommand());
program.addCommand(createPaymentsCommand());
program.addCommand(createEstimatesCommand());
program.addCommand(createReceiptsCommand());
program.addCommand(createJournalsCommand());
program.addCommand(createInventoryCommand());
program.addCommand(createTaxRatesCommand());
program.addCommand(createWarehousesCommand());
program.addCommand(createUsersCommand());
// Financial reports
program.addCommand(createReportsCommand());
// Global error handling
program.hook('preAction', () => {
process.on('unhandledRejection', (error) => {
console.error(chalk.red('\nUnhandled error:'), error);
process.exit(1);
});
process.on('uncaughtException', (error) => {
console.error(chalk.red('\nUncaught exception:'), error);
process.exit(1);
});
});
// Show help if no command provided
if (process.argv.length <= 2) {
program.help();
}
program.parse();
+40
View File
@@ -0,0 +1,40 @@
import chalk from 'chalk';
export function handleError(error: unknown): never {
if (error instanceof Error) {
// Check if it's an HTTP error from the SDK
const httpError = error as { status?: number; statusText?: string };
if (httpError.status) {
console.error(chalk.red(`HTTP Error ${httpError.status}: ${httpError.statusText || error.message}`));
if (httpError.status === 401) {
console.error(chalk.yellow('Your API key may be invalid or expired.'));
console.error(chalk.yellow('Run: bigcapital config set api-key <your-new-api-key>'));
} else if (httpError.status === 403) {
console.error(chalk.yellow('You don\'t have permission to access this resource.'));
} else if (httpError.status === 404) {
console.error(chalk.yellow('The requested resource was not found.'));
}
} else {
console.error(chalk.red(`Error: ${error.message}`));
}
if (process.env.DEBUG) {
console.error(chalk.gray(error.stack));
}
} else {
console.error(chalk.red('An unknown error occurred'));
console.error(error);
}
process.exit(1);
}
export function assertDefined<T>(value: T | undefined | null, name: string): T {
if (value === undefined || value === null) {
console.error(chalk.red(`Error: ${name} is required but was not provided.`));
process.exit(1);
}
return value;
}
+74
View File
@@ -0,0 +1,74 @@
import Table from 'cli-table3';
import chalk from 'chalk';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function createTable(headers: string[]): Table.Table {
return new Table({
head: headers.map(h => chalk.cyan.bold(h)),
style: {
head: [],
border: [],
},
chars: {
top: '─',
'top-mid': '┬',
'top-left': '┌',
'top-right': '┐',
bottom: '─',
'bottom-mid': '┴',
'bottom-left': '└',
'bottom-right': '┘',
left: '│',
'left-mid': '├',
mid: '─',
'mid-mid': '┼',
right: '│',
'right-mid': '┤',
middle: '│',
},
});
}
export function formatCurrency(amount: number | string | undefined, currency = '$'): string {
if (amount === undefined || amount === null) return '-';
const num = typeof amount === 'string' ? parseFloat(amount) : amount;
if (isNaN(num)) return '-';
return `${currency}${num.toFixed(2)}`;
}
export function formatDate(dateStr: string | undefined): string {
if (!dateStr) return '-';
const date = new Date(dateStr);
if (isNaN(date.getTime())) return dateStr;
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
});
}
export function truncate(str: string | undefined, maxLength: number): string {
if (!str) return '-';
if (str.length <= maxLength) return str;
return str.substring(0, maxLength - 3) + '...';
}
export function formatStatus(status: string | undefined): string {
if (!status) return '-';
const statusColors: Record<string, (s: string) => string> = {
published: chalk.green,
draft: chalk.gray,
delivered: chalk.blue,
partial: chalk.yellow,
paid: chalk.green.bold,
unpaid: chalk.red,
overdue: chalk.red.bold,
active: chalk.green,
inactive: chalk.gray,
};
const lowerStatus = status.toLowerCase();
const colorFn = statusColors[lowerStatus] || chalk.white;
return colorFn(status);
}
+18
View File
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"moduleResolution": "node"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
+80 -2
View File
@@ -37,6 +37,34 @@ importers:
specifier: ^9.0.5
version: 9.1.2
packages/cli:
dependencies:
'@bigcapital/sdk-ts':
specifier: workspace:*
version: link:../../shared/sdk-ts
chalk:
specifier: ^4.1.2
version: 4.1.2
cli-table3:
specifier: ^0.6.3
version: 0.6.5
commander:
specifier: ^11.1.0
version: 11.1.0
conf:
specifier: ^10.2.0
version: 10.2.0
ora:
specifier: ^5.4.1
version: 5.4.1
devDependencies:
'@types/node':
specifier: ^20.0.0
version: 20.19.25
typescript:
specifier: ^5.1.3
version: 5.6.3
packages/server:
dependencies:
'@aws-sdk/client-s3':
@@ -6354,6 +6382,10 @@ packages:
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
atomically@1.7.0:
resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==}
engines: {node: '>=10.12.0'}
attr-accept@2.2.2:
resolution: {integrity: sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==}
engines: {node: '>=4'}
@@ -7020,6 +7052,10 @@ packages:
resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
engines: {'0': node >= 6.0}
conf@10.2.0:
resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==}
engines: {node: '>=12'}
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
@@ -7299,6 +7335,10 @@ packages:
de-indent@1.0.2:
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
debounce-fn@4.0.0:
resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==}
engines: {node: '>=10'}
debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
@@ -9406,6 +9446,9 @@ packages:
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
json-schema-typed@7.0.3:
resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==}
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
@@ -9910,6 +9953,10 @@ packages:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
mimic-fn@3.1.0:
resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==}
engines: {node: '>=8'}
mimic-fn@4.0.0:
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
engines: {node: '>=12'}
@@ -10747,6 +10794,10 @@ packages:
pkg-types@1.2.1:
resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==}
pkg-up@3.1.0:
resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
engines: {node: '>=8'}
plaid-threads@11.5.0:
resolution: {integrity: sha512-KS3w5Ydv+aC7wS1XxiWeDUhQHHFQ/5dQOoAQwdg+3DxFHbh5nSPH1L4Zy9qHru5FVDmC5DQ6/9XhJwzZ9BhpTA==}
peerDependencies:
@@ -20181,7 +20232,7 @@ snapshots:
'@types/nodemailer@6.4.17':
dependencies:
'@types/node': 20.5.1
'@types/node': 20.19.25
'@types/normalize-package-data@2.4.4': {}
@@ -21238,6 +21289,8 @@ snapshots:
asynckit@0.4.0: {}
atomically@1.7.0: {}
attr-accept@2.2.2: {}
available-typed-arrays@1.0.7:
@@ -22095,6 +22148,19 @@ snapshots:
readable-stream: 3.6.2
typedarray: 0.0.6
conf@10.2.0:
dependencies:
ajv: 8.17.1
ajv-formats: 2.1.1(ajv@8.17.1)
atomically: 1.7.0
debounce-fn: 4.0.0
dot-prop: 6.0.1
env-paths: 2.2.1
json-schema-typed: 7.0.3
onetime: 5.1.2
pkg-up: 3.1.0
semver: 7.6.3
confbox@0.1.8: {}
config-chain@1.1.13:
@@ -22405,6 +22471,10 @@ snapshots:
de-indent@1.0.2: {}
debounce-fn@4.0.0:
dependencies:
mimic-fn: 3.1.0
debug@2.6.9:
dependencies:
ms: 2.0.0
@@ -25124,6 +25194,8 @@ snapshots:
json-schema-traverse@1.0.0: {}
json-schema-typed@7.0.3: {}
json-stable-stringify-without-jsonify@1.0.1: {}
json-stringify-safe@5.0.1: {}
@@ -25718,6 +25790,8 @@ snapshots:
mimic-fn@2.1.0: {}
mimic-fn@3.1.0: {}
mimic-fn@4.0.0: {}
min-indent@1.0.1: {}
@@ -26700,6 +26774,10 @@ snapshots:
mlly: 1.7.2
pathe: 1.1.2
pkg-up@3.1.0:
dependencies:
find-up: 3.0.0
plaid-threads@11.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@popperjs/core': 2.11.8
@@ -28541,7 +28619,7 @@ snapshots:
stripe@16.10.0:
dependencies:
'@types/node': 20.5.1
'@types/node': 20.19.25
qs: 6.14.0
strnum@1.0.5: {}