incwo-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitleaks.toml +15 -0
- package/.husky/pre-commit +26 -0
- package/README.md +179 -0
- package/dist/api.d.ts +11 -0
- package/dist/api.js +87 -0
- package/dist/commands/generic.d.ts +3 -0
- package/dist/commands/generic.js +163 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +144 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +29 -0
- package/dist/resources.d.ts +22 -0
- package/dist/resources.js +497 -0
- package/dist/ui.d.ts +9 -0
- package/dist/ui.js +58 -0
- package/package.json +33 -0
- package/src/api.ts +93 -0
- package/src/commands/generic.ts +156 -0
- package/src/config.ts +118 -0
- package/src/index.ts +33 -0
- package/src/resources.ts +560 -0
- package/src/ui.ts +57 -0
- package/tsconfig.json +17 -0
package/.gitleaks.toml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[extend]
|
|
2
|
+
useDefault = true
|
|
3
|
+
|
|
4
|
+
[allowlist]
|
|
5
|
+
description = "Allow variable names and example values"
|
|
6
|
+
regexes = [
|
|
7
|
+
# Variable declarations (not actual values)
|
|
8
|
+
'''(password|login|token|secret)\s*:\s*string''',
|
|
9
|
+
# Placeholder examples in strings
|
|
10
|
+
'''mycompany\.incwo\.com''',
|
|
11
|
+
'''your[-_]?(password|login|token|api[-_]?key)''',
|
|
12
|
+
]
|
|
13
|
+
paths = [
|
|
14
|
+
'''package-lock\.json''',
|
|
15
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Check for potential secrets before committing
|
|
3
|
+
|
|
4
|
+
# Match assignments like: password = "abc", apiKey = 'xyz', TOKEN="..."
|
|
5
|
+
# Does NOT match JSON keys like "password": string or type declarations
|
|
6
|
+
PATTERNS='(password|api_key|apikey|secret|auth_token|access_token)[[:space:]]*=[[:space:]]*["\x27][A-Za-z0-9+/=_\-]{8,}'
|
|
7
|
+
|
|
8
|
+
# Only scan staged files
|
|
9
|
+
STAGED=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(ts|js|json|env|yml|yaml)$' || true)
|
|
10
|
+
|
|
11
|
+
if [ -z "$STAGED" ]; then
|
|
12
|
+
exit 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
FOUND=$(echo "$STAGED" | tr '\n' '\0' | xargs -0 grep -lEi "$PATTERNS" 2>/dev/null) || true
|
|
16
|
+
|
|
17
|
+
if [ -n "$FOUND" ]; then
|
|
18
|
+
echo ""
|
|
19
|
+
echo " ✗ Potential secrets detected in:"
|
|
20
|
+
echo "$FOUND" | sed 's/^/ /'
|
|
21
|
+
echo ""
|
|
22
|
+
echo " Review the file(s) before committing."
|
|
23
|
+
echo " To bypass (only if you are sure): git commit --no-verify"
|
|
24
|
+
echo ""
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
package/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# incwo CLI
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
██╗███╗ ██╗ ██████╗██╗ ██╗ ██████╗ ██████╗██╗ ██╗
|
|
5
|
+
██║████╗ ██║██╔════╝██║ ██║██╔═══██╗ ██╔════╝██║ ██║
|
|
6
|
+
██║██╔██╗ ██║██║ ██║ █╗ ██║██║ ██║ ██║ ██║ ██║
|
|
7
|
+
██║██║╚██╗██║██║ ██║███╗██║██║ ██║ ██║ ██║ ██║
|
|
8
|
+
██║██║ ╚████║╚██████╗╚███╔███╔╝╚██████╔╝ ╚██████╗███████╗██║
|
|
9
|
+
╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══╝╚══╝ ╚═════╝ ╚═════╝╚══════╝╚═╝
|
|
10
|
+
|
|
11
|
+
Your CRM/ERP · incwo.com
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Command-line interface for [incwo](https://www.incwo.com) CRM/ERP.
|
|
15
|
+
|
|
16
|
+
Access all your incwo data from the terminal — list, search, and inspect any object across CRM, sales, invoicing, inventory, projects, HR, and POS.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g incwo-cli
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or from source:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
git clone https://github.com/incwo/incwo-cli
|
|
28
|
+
cd incwo-cli
|
|
29
|
+
npm install
|
|
30
|
+
npm run build
|
|
31
|
+
npm link
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Setup
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
incwo config
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
You'll be prompted to configure your server — two modes available:
|
|
41
|
+
|
|
42
|
+
**Option A — full endpoint URL:**
|
|
43
|
+
```
|
|
44
|
+
Endpoint URL: mycompany.incwo.com/12345
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Option B — shard + business file ID separately:**
|
|
48
|
+
```
|
|
49
|
+
Shard (leave empty for www.incwo.com): mycompany
|
|
50
|
+
Business file ID: 12345
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then your login and password. Config is saved to `~/.incwo/config.json`.
|
|
54
|
+
|
|
55
|
+
API access must be enabled in your incwo account under Settings → Users → APIs.
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
Every resource supports two sub-commands: `list` and `get`.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
incwo <resource> list [options]
|
|
63
|
+
incwo <resource> get <id>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Common options
|
|
67
|
+
|
|
68
|
+
| Flag | Description |
|
|
69
|
+
|------|-------------|
|
|
70
|
+
| `--search <q>` | Full-text search |
|
|
71
|
+
| `--filter <json>` | ufilters JSON (see below) |
|
|
72
|
+
| `--page <n>` | Page number (default: 1) |
|
|
73
|
+
| `--from <date>` | Start date, YYYY-MM-DD |
|
|
74
|
+
| `--to <date>` | End date, YYYY-MM-DD |
|
|
75
|
+
|
|
76
|
+
### Filtering
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Exact match
|
|
80
|
+
incwo contacts list --filter '{"last_name":{"eq":"Dupont"}}'
|
|
81
|
+
|
|
82
|
+
# Contains
|
|
83
|
+
incwo firms list --filter '{"name":{"like":"Acme"}}'
|
|
84
|
+
|
|
85
|
+
# Unpaid invoices
|
|
86
|
+
incwo bill_sheets list --filter '{"balance":{"gt":"0"}}'
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Available resources
|
|
90
|
+
|
|
91
|
+
### CRM
|
|
92
|
+
| Command | Description |
|
|
93
|
+
|---------|-------------|
|
|
94
|
+
| `contacts` | Contacts (people) |
|
|
95
|
+
| `firms` | Companies / organisations |
|
|
96
|
+
| `leads` | Sales opportunities |
|
|
97
|
+
| `contact_items` | Contact details (emails, phones…) |
|
|
98
|
+
| `contact_addresses` | Contact addresses |
|
|
99
|
+
| `contact_lists` | Contact lists |
|
|
100
|
+
| `missions` | Missions |
|
|
101
|
+
|
|
102
|
+
### Sales
|
|
103
|
+
| Command | Description |
|
|
104
|
+
|---------|-------------|
|
|
105
|
+
| `proposal_sheets` | Quotes, orders, delivery notes, purchase orders (use `--sheet-type`) |
|
|
106
|
+
| `bill_sheets` | Customer invoices |
|
|
107
|
+
| `emitted_payments` | Vendor bills / purchase invoices |
|
|
108
|
+
| `customer_products` | Product / service catalog |
|
|
109
|
+
| `customer_product_categories` | Product categories |
|
|
110
|
+
| `customer_pricings` | Customer pricing rules |
|
|
111
|
+
| `vendor_pricings` | Vendor pricing rules |
|
|
112
|
+
| `bank_accounts` | Bank accounts |
|
|
113
|
+
| `campaigns` | Marketing campaigns |
|
|
114
|
+
|
|
115
|
+
### Inventory
|
|
116
|
+
| Command | Description |
|
|
117
|
+
|---------|-------------|
|
|
118
|
+
| `stock_movements` | Stock movements |
|
|
119
|
+
| `serial_numbers` | Serial numbers |
|
|
120
|
+
| `serial_lots` | Serial lot numbers |
|
|
121
|
+
| `warehouses` | Warehouses / depots |
|
|
122
|
+
|
|
123
|
+
### Projects & Time
|
|
124
|
+
| Command | Description |
|
|
125
|
+
|---------|-------------|
|
|
126
|
+
| `projects` | Projects |
|
|
127
|
+
| `tasks` | Tasks |
|
|
128
|
+
| `timesheets` | Timesheets |
|
|
129
|
+
|
|
130
|
+
### HR
|
|
131
|
+
| Command | Description |
|
|
132
|
+
|---------|-------------|
|
|
133
|
+
| `staff_members` | Staff members |
|
|
134
|
+
| `expense_sheets` | Expense reports |
|
|
135
|
+
| `vacation_requests` | Vacation requests |
|
|
136
|
+
| `salary_campaigns` | Salary campaigns |
|
|
137
|
+
| `contracts` | Contracts |
|
|
138
|
+
| `interviews` | HR interviews |
|
|
139
|
+
|
|
140
|
+
### Misc
|
|
141
|
+
| Command | Description |
|
|
142
|
+
|---------|-------------|
|
|
143
|
+
| `notes` | Notes |
|
|
144
|
+
| `upload_files` | Files / attachments |
|
|
145
|
+
| `custom_labels` | Custom labels |
|
|
146
|
+
| `conversations` | Conversations / messaging |
|
|
147
|
+
| `webhooks` | Configured webhooks |
|
|
148
|
+
| `bookables` | Bookable resources |
|
|
149
|
+
| `fleets` | Vehicle fleets |
|
|
150
|
+
|
|
151
|
+
## Examples
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# List contacts, search by name
|
|
155
|
+
incwo contacts list --search "Martin"
|
|
156
|
+
|
|
157
|
+
# Get a specific invoice
|
|
158
|
+
incwo bill_sheets get 1042
|
|
159
|
+
|
|
160
|
+
# Unpaid invoices since January
|
|
161
|
+
incwo bill_sheets list --filter '{"balance":{"gt":"0"}}' --from 2026-01-01
|
|
162
|
+
|
|
163
|
+
# Quotes from Q1
|
|
164
|
+
incwo proposal_sheets list --sheet-type quote --from 2026-01-01 --to 2026-03-31
|
|
165
|
+
|
|
166
|
+
# Tasks assigned to a project
|
|
167
|
+
incwo tasks list --filter '{"project_id":{"eq":"99"}}'
|
|
168
|
+
|
|
169
|
+
# Stock movements this month
|
|
170
|
+
incwo stock_movements list --from 2026-04-01
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Rate limits
|
|
174
|
+
|
|
175
|
+
incwo API: 8 req/s · 240 req/min · 4 800 req/h
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IncwoConfig } from './config';
|
|
2
|
+
export declare class IncwoClient {
|
|
3
|
+
private http;
|
|
4
|
+
private base;
|
|
5
|
+
constructor(config: IncwoConfig);
|
|
6
|
+
list(resource: string, params?: Record<string, string>): Promise<any[]>;
|
|
7
|
+
get(resource: string, id: string): Promise<any>;
|
|
8
|
+
create(resource: string, data: Record<string, any>): Promise<any>;
|
|
9
|
+
update(resource: string, id: string, data: Record<string, any>): Promise<any>;
|
|
10
|
+
destroy(resource: string, id: string): Promise<void>;
|
|
11
|
+
}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.IncwoClient = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const xml2js_1 = require("xml2js");
|
|
9
|
+
const config_1 = require("./config");
|
|
10
|
+
class IncwoClient {
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.base = (0, config_1.baseUrl)(config);
|
|
13
|
+
this.http = axios_1.default.create({
|
|
14
|
+
baseURL: this.base,
|
|
15
|
+
auth: { username: config.login, password: config.password },
|
|
16
|
+
headers: { 'Content-Type': 'application/xml', 'Accept': 'application/xml' },
|
|
17
|
+
timeout: 15000,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
// GET /{resource}.xml with optional query params
|
|
21
|
+
async list(resource, params = {}) {
|
|
22
|
+
const response = await this.http.get(`/${resource}.xml`, { params });
|
|
23
|
+
return parseXmlList(response.data);
|
|
24
|
+
}
|
|
25
|
+
// GET /{resource}/{id}.xml
|
|
26
|
+
async get(resource, id) {
|
|
27
|
+
const response = await this.http.get(`/${resource}/${id}.xml`);
|
|
28
|
+
const parsed = await parseXmlList(response.data);
|
|
29
|
+
return parsed[0] ?? null;
|
|
30
|
+
}
|
|
31
|
+
// POST /{resource}.xml — accepts a plain JS object, converts to XML
|
|
32
|
+
async create(resource, data) {
|
|
33
|
+
const singularResource = toSingular(resource);
|
|
34
|
+
const xml = jsonToXml(singularResource, data);
|
|
35
|
+
const response = await this.http.post(`/${resource}.xml`, xml);
|
|
36
|
+
const parsed = await parseXmlList(response.data);
|
|
37
|
+
return parsed[0] ?? null;
|
|
38
|
+
}
|
|
39
|
+
// PUT /{resource}/{id}.xml — accepts a plain JS object, converts to XML
|
|
40
|
+
async update(resource, id, data) {
|
|
41
|
+
const singularResource = toSingular(resource);
|
|
42
|
+
const xml = jsonToXml(singularResource, data);
|
|
43
|
+
const response = await this.http.put(`/${resource}/${id}.xml`, xml);
|
|
44
|
+
const parsed = await parseXmlList(response.data);
|
|
45
|
+
return parsed[0] ?? null;
|
|
46
|
+
}
|
|
47
|
+
// DELETE /{resource}/{id}.xml
|
|
48
|
+
async destroy(resource, id) {
|
|
49
|
+
await this.http.delete(`/${resource}/${id}.xml`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.IncwoClient = IncwoClient;
|
|
53
|
+
// Converts a flat JS object to an XML string with the given root element
|
|
54
|
+
// e.g. jsonToXml('contact', { first_name: 'Bob' }) → <contact><first_name>Bob</first_name></contact>
|
|
55
|
+
function jsonToXml(rootElement, data) {
|
|
56
|
+
const builder = new xml2js_1.Builder({ rootName: rootElement, headless: true });
|
|
57
|
+
return builder.buildObject(data);
|
|
58
|
+
}
|
|
59
|
+
// Naive singularization: bill_sheets → bill_sheet, contacts → contact
|
|
60
|
+
function toSingular(resource) {
|
|
61
|
+
if (resource.endsWith('ies'))
|
|
62
|
+
return resource.slice(0, -3) + 'y';
|
|
63
|
+
if (resource.endsWith('s'))
|
|
64
|
+
return resource.slice(0, -1);
|
|
65
|
+
return resource;
|
|
66
|
+
}
|
|
67
|
+
async function parseXmlList(xml) {
|
|
68
|
+
const parsed = await (0, xml2js_1.parseStringPromise)(xml, {
|
|
69
|
+
explicitArray: false,
|
|
70
|
+
ignoreAttrs: true,
|
|
71
|
+
trim: true,
|
|
72
|
+
});
|
|
73
|
+
if (!parsed)
|
|
74
|
+
return [];
|
|
75
|
+
const rootKey = Object.keys(parsed)[0];
|
|
76
|
+
const root = parsed[rootKey];
|
|
77
|
+
if (!root)
|
|
78
|
+
return [];
|
|
79
|
+
const childKey = Object.keys(root)[0];
|
|
80
|
+
if (childKey && Array.isArray(root[childKey])) {
|
|
81
|
+
return root[childKey];
|
|
82
|
+
}
|
|
83
|
+
if (childKey && typeof root[childKey] === 'object') {
|
|
84
|
+
return [root[childKey]];
|
|
85
|
+
}
|
|
86
|
+
return [root];
|
|
87
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.makeCommand = makeCommand;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const config_1 = require("../config");
|
|
10
|
+
const api_1 = require("../api");
|
|
11
|
+
const ui_1 = require("../ui");
|
|
12
|
+
function makeCommand(res) {
|
|
13
|
+
const cmd = new commander_1.Command(res.command).description(res.description);
|
|
14
|
+
// ── list ──────────────────────────────────────────────────────────────────
|
|
15
|
+
const listCmd = cmd
|
|
16
|
+
.command('list')
|
|
17
|
+
.description(`List ${res.description.toLowerCase()}`)
|
|
18
|
+
.option('--page <n>', 'Page number', '1')
|
|
19
|
+
.option('--search <q>', 'Full-text search')
|
|
20
|
+
.option('--filter <json>', 'ufilters JSON (e.g. \'{"status":{"eq":"active"}}\')');
|
|
21
|
+
if (res.hasSheetType) {
|
|
22
|
+
listCmd.option('--sheet-type <type>', 'Filter by sheet type (proposal, sale_order, delivery_note, purchase_order…)');
|
|
23
|
+
}
|
|
24
|
+
if (res.hasDateFilter) {
|
|
25
|
+
listCmd
|
|
26
|
+
.option('--from <date>', 'Start date (YYYY-MM-DD)')
|
|
27
|
+
.option('--to <date>', 'End date (YYYY-MM-DD)');
|
|
28
|
+
}
|
|
29
|
+
listCmd.action(async (opts) => {
|
|
30
|
+
const config = (0, config_1.loadConfig)();
|
|
31
|
+
if (!config)
|
|
32
|
+
return (0, ui_1.error)('No configuration found. Run: incwo config');
|
|
33
|
+
const spinner = (0, ora_1.default)('Loading…').start();
|
|
34
|
+
try {
|
|
35
|
+
const params = {
|
|
36
|
+
page: opts.page,
|
|
37
|
+
...res.defaultParams,
|
|
38
|
+
};
|
|
39
|
+
if (opts.search)
|
|
40
|
+
params['search'] = opts.search;
|
|
41
|
+
if (opts.filter)
|
|
42
|
+
params['ufilters'] = opts.filter;
|
|
43
|
+
if (opts.sheetType)
|
|
44
|
+
params['sheet_type'] = opts.sheetType;
|
|
45
|
+
if (opts.from)
|
|
46
|
+
params['filter_from_date'] = opts.from;
|
|
47
|
+
if (opts.to)
|
|
48
|
+
params['filter_to_date'] = opts.to;
|
|
49
|
+
const client = new api_1.IncwoClient(config);
|
|
50
|
+
const rows = await client.list(res.endpoint, params);
|
|
51
|
+
spinner.stop();
|
|
52
|
+
(0, ui_1.printTable)(rows, res.columns);
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
spinner.stop();
|
|
56
|
+
(0, ui_1.error)(e.response?.data ?? e.message);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
// ── get ───────────────────────────────────────────────────────────────────
|
|
60
|
+
cmd
|
|
61
|
+
.command('get <id>')
|
|
62
|
+
.description(`Show a ${res.description.toLowerCase()} by ID`)
|
|
63
|
+
.action(async (id) => {
|
|
64
|
+
const config = (0, config_1.loadConfig)();
|
|
65
|
+
if (!config)
|
|
66
|
+
return (0, ui_1.error)('No configuration found. Run: incwo config');
|
|
67
|
+
const spinner = (0, ora_1.default)('Loading…').start();
|
|
68
|
+
try {
|
|
69
|
+
const client = new api_1.IncwoClient(config);
|
|
70
|
+
const obj = await client.get(res.endpoint, id);
|
|
71
|
+
spinner.stop();
|
|
72
|
+
if (!obj)
|
|
73
|
+
return (0, ui_1.error)(`${res.command} #${id} not found`);
|
|
74
|
+
(0, ui_1.printObject)(obj, `${res.command} #${id}`);
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
spinner.stop();
|
|
78
|
+
(0, ui_1.error)(e.response?.data ?? e.message);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
// ── create ────────────────────────────────────────────────────────────────
|
|
82
|
+
cmd
|
|
83
|
+
.command('create')
|
|
84
|
+
.description(`Create a new ${res.description.toLowerCase()}`)
|
|
85
|
+
.requiredOption('--data <json>', 'Fields as JSON (e.g. \'{"first_name":"Bob","last_name":"Smith"}\')')
|
|
86
|
+
.action(async (opts) => {
|
|
87
|
+
const config = (0, config_1.loadConfig)();
|
|
88
|
+
if (!config)
|
|
89
|
+
return (0, ui_1.error)('No configuration found. Run: incwo config');
|
|
90
|
+
let data;
|
|
91
|
+
try {
|
|
92
|
+
data = JSON.parse(opts.data);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return (0, ui_1.error)('Invalid JSON in --data');
|
|
96
|
+
}
|
|
97
|
+
const spinner = (0, ora_1.default)('Creating…').start();
|
|
98
|
+
try {
|
|
99
|
+
const client = new api_1.IncwoClient(config);
|
|
100
|
+
const obj = await client.create(res.endpoint, data);
|
|
101
|
+
spinner.stop();
|
|
102
|
+
if (!obj)
|
|
103
|
+
return (0, ui_1.error)('Created but no response returned');
|
|
104
|
+
(0, ui_1.success)(`${res.command} created (id: ${obj.id ?? '?'})`);
|
|
105
|
+
(0, ui_1.printObject)(obj);
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
spinner.stop();
|
|
109
|
+
(0, ui_1.error)(e.response?.data ?? e.message);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// ── update ────────────────────────────────────────────────────────────────
|
|
113
|
+
cmd
|
|
114
|
+
.command('update <id>')
|
|
115
|
+
.description(`Update a ${res.description.toLowerCase()}`)
|
|
116
|
+
.requiredOption('--data <json>', 'Fields to update as JSON (e.g. \'{"first_name":"Bob"}\')')
|
|
117
|
+
.action(async (id, opts) => {
|
|
118
|
+
const config = (0, config_1.loadConfig)();
|
|
119
|
+
if (!config)
|
|
120
|
+
return (0, ui_1.error)('No configuration found. Run: incwo config');
|
|
121
|
+
let data;
|
|
122
|
+
try {
|
|
123
|
+
data = JSON.parse(opts.data);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return (0, ui_1.error)('Invalid JSON in --data');
|
|
127
|
+
}
|
|
128
|
+
const spinner = (0, ora_1.default)('Updating…').start();
|
|
129
|
+
try {
|
|
130
|
+
const client = new api_1.IncwoClient(config);
|
|
131
|
+
const obj = await client.update(res.endpoint, id, data);
|
|
132
|
+
spinner.stop();
|
|
133
|
+
(0, ui_1.success)(`${res.command} #${id} updated`);
|
|
134
|
+
if (obj)
|
|
135
|
+
(0, ui_1.printObject)(obj);
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
spinner.stop();
|
|
139
|
+
(0, ui_1.error)(e.response?.data ?? e.message);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
// ── delete ────────────────────────────────────────────────────────────────
|
|
143
|
+
cmd
|
|
144
|
+
.command('delete <id>')
|
|
145
|
+
.description(`Delete a ${res.description.toLowerCase()}`)
|
|
146
|
+
.action(async (id) => {
|
|
147
|
+
const config = (0, config_1.loadConfig)();
|
|
148
|
+
if (!config)
|
|
149
|
+
return (0, ui_1.error)('No configuration found. Run: incwo config');
|
|
150
|
+
const spinner = (0, ora_1.default)('Deleting…').start();
|
|
151
|
+
try {
|
|
152
|
+
const client = new api_1.IncwoClient(config);
|
|
153
|
+
await client.destroy(res.endpoint, id);
|
|
154
|
+
spinner.stop();
|
|
155
|
+
(0, ui_1.success)(`${res.command} #${id} deleted`);
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
spinner.stop();
|
|
159
|
+
(0, ui_1.error)(e.response?.data ?? e.message);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
return cmd;
|
|
163
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface IncwoConfig {
|
|
2
|
+
server: string;
|
|
3
|
+
business_file_id: string;
|
|
4
|
+
login: string;
|
|
5
|
+
password: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function loadConfig(): IncwoConfig | null;
|
|
8
|
+
export declare function saveConfig(config: IncwoConfig): void;
|
|
9
|
+
export declare function baseUrl(config: IncwoConfig): string;
|
|
10
|
+
export declare function runConfigWizard(): Promise<void>;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.loadConfig = loadConfig;
|
|
40
|
+
exports.saveConfig = saveConfig;
|
|
41
|
+
exports.baseUrl = baseUrl;
|
|
42
|
+
exports.runConfigWizard = runConfigWizard;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const os = __importStar(require("os"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
47
|
+
const CONFIG_DIR = path.join(os.homedir(), '.incwo');
|
|
48
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
49
|
+
function loadConfig() {
|
|
50
|
+
if (!fs.existsSync(CONFIG_PATH))
|
|
51
|
+
return null;
|
|
52
|
+
try {
|
|
53
|
+
const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
|
54
|
+
return JSON.parse(raw);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function saveConfig(config) {
|
|
61
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
62
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
|
65
|
+
}
|
|
66
|
+
function baseUrl(config) {
|
|
67
|
+
return `https://${config.server}/${config.business_file_id}`;
|
|
68
|
+
}
|
|
69
|
+
async function runConfigWizard() {
|
|
70
|
+
const chalk = (await Promise.resolve().then(() => __importStar(require('chalk')))).default;
|
|
71
|
+
console.log(chalk.bold('\nincwo CLI — Configuration\n'));
|
|
72
|
+
const { inputMode } = await inquirer_1.default.prompt([
|
|
73
|
+
{
|
|
74
|
+
type: 'list',
|
|
75
|
+
name: 'inputMode',
|
|
76
|
+
message: 'How would you like to configure the server?',
|
|
77
|
+
choices: [
|
|
78
|
+
{ name: 'Enter the full endpoint URL (e.g. mycompany.incwo.com/12345)', value: 'url' },
|
|
79
|
+
{ name: 'Enter the shard and business file ID separately', value: 'manual' },
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
]);
|
|
83
|
+
let server;
|
|
84
|
+
let business_file_id;
|
|
85
|
+
if (inputMode === 'url') {
|
|
86
|
+
const { endpoint } = await inquirer_1.default.prompt([
|
|
87
|
+
{
|
|
88
|
+
type: 'input',
|
|
89
|
+
name: 'endpoint',
|
|
90
|
+
message: 'Endpoint URL (e.g. mycompany.incwo.com/12345 or www.incwo.com/12345):',
|
|
91
|
+
validate: (v) => {
|
|
92
|
+
const clean = v.replace(/^https?:\/\//, '').replace(/\/$/, '');
|
|
93
|
+
const parts = clean.split('/');
|
|
94
|
+
if (parts.length < 2 || !parts[0] || !parts[1]) {
|
|
95
|
+
return 'Expected format: shard.incwo.com/business_file_id';
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
const clean = endpoint.replace(/^https?:\/\//, '').replace(/\/$/, '');
|
|
102
|
+
const slashIdx = clean.indexOf('/');
|
|
103
|
+
server = clean.substring(0, slashIdx);
|
|
104
|
+
business_file_id = clean.substring(slashIdx + 1).split('/')[0];
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const answers = await inquirer_1.default.prompt([
|
|
108
|
+
{
|
|
109
|
+
type: 'input',
|
|
110
|
+
name: 'shard',
|
|
111
|
+
message: 'Shard (leave empty if no shard → www.incwo.com):',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
type: 'input',
|
|
115
|
+
name: 'business_file_id',
|
|
116
|
+
message: 'Business file ID:',
|
|
117
|
+
validate: (v) => v.trim() !== '' || 'Required',
|
|
118
|
+
},
|
|
119
|
+
]);
|
|
120
|
+
server = answers.shard.trim()
|
|
121
|
+
? `${answers.shard.trim()}.incwo.com`
|
|
122
|
+
: 'www.incwo.com';
|
|
123
|
+
business_file_id = answers.business_file_id.trim();
|
|
124
|
+
}
|
|
125
|
+
const { login, password } = await inquirer_1.default.prompt([
|
|
126
|
+
{
|
|
127
|
+
type: 'input',
|
|
128
|
+
name: 'login',
|
|
129
|
+
message: 'Login:',
|
|
130
|
+
validate: (v) => v.trim() !== '' || 'Required',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
type: 'password',
|
|
134
|
+
name: 'password',
|
|
135
|
+
message: 'Password:',
|
|
136
|
+
mask: '*',
|
|
137
|
+
validate: (v) => v.trim() !== '' || 'Required',
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
const config = { server, business_file_id, login, password };
|
|
141
|
+
saveConfig(config);
|
|
142
|
+
console.log(chalk.green(`\n✓ Configuration saved to ${CONFIG_PATH}`));
|
|
143
|
+
console.log(chalk.dim(` Endpoint: https://${server}/${business_file_id}\n`));
|
|
144
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const ui_1 = require("./ui");
|
|
6
|
+
const config_1 = require("./config");
|
|
7
|
+
const generic_1 = require("./commands/generic");
|
|
8
|
+
const resources_1 = require("./resources");
|
|
9
|
+
const program = new commander_1.Command();
|
|
10
|
+
program
|
|
11
|
+
.name('incwo')
|
|
12
|
+
.description('CLI for incwo CRM/ERP')
|
|
13
|
+
.version('0.1.0');
|
|
14
|
+
// Banner sur --help global ou sans arguments
|
|
15
|
+
if (process.argv.length <= 2 || process.argv[2] === '--help' || process.argv[2] === '-h') {
|
|
16
|
+
(0, ui_1.printBanner)();
|
|
17
|
+
}
|
|
18
|
+
// incwo config
|
|
19
|
+
program
|
|
20
|
+
.command('config')
|
|
21
|
+
.description('Configure incwo access (server, credentials)')
|
|
22
|
+
.action(async () => {
|
|
23
|
+
await (0, config_1.runConfigWizard)();
|
|
24
|
+
});
|
|
25
|
+
// Toutes les ressources via le factory générique
|
|
26
|
+
for (const resource of resources_1.RESOURCES) {
|
|
27
|
+
program.addCommand((0, generic_1.makeCommand)(resource));
|
|
28
|
+
}
|
|
29
|
+
program.parse(process.argv);
|