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 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,3 @@
1
+ import { Command } from 'commander';
2
+ import { ResourceDef } from '../resources';
3
+ export declare function makeCommand(res: ResourceDef): Command;
@@ -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
+ }
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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);