wanas-zcrm-extractor 1.0.7 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,16 +10,32 @@ A robust, production-quality CLI utility and local web dashboard designed to aut
10
10
 
11
11
  ---
12
12
 
13
+ > ### ⚠️ Important: read-only — it never modifies your CRM
14
+ >
15
+ > `zcrm` is strictly **read-only**: it **never creates, updates, or deletes** anything in Zoho
16
+ > CRM. By **default** it pulls **schema/metadata only** — module and field definitions, layouts,
17
+ > picklists, related lists, tags, profiles, roles, webhooks, org-wide settings, and custom
18
+ > function source code — with **no access to your CRM records**.
19
+ >
20
+ > Reading actual record data is always **opt-in and explicit**: per-module record *counts* only
21
+ > with `zcrm pull --with-counts` (~50 API credits/module), and audit-log data only via the
22
+ > `zcrm audit` command. Even then, the tool only ever *reads* — it cannot change anything in your CRM.
23
+
24
+ ---
25
+
13
26
  ## 🌟 Key Features
14
27
 
15
28
  1. **Global CLI Wrapper (`zcrm`):** Command-line tools for authentication, data extraction, and starting the local web explorer dashboard.
16
29
  2. **Standard ZCRM CLI Directory Layout:** Writes module structures, layouts, fields, profiles, and roles in a format fully compatible with standard Zoho CRM developer layouts.
17
30
  3. **Deluge Script Downloader:** Pulls custom function source codes, organizes them into folders by namespace, and saves them as local Deluge `.ds` files.
18
31
  4. **Interactive Dashboard:** Launch a premium, glassmorphic dark-theme dashboard to review extracted modules, view field properties, search and filter APIs, and download structured ZIP archives of the exports.
19
- 5. **Robust Pipeline & Error Handling:**
32
+ 5. **AI Skill Generator (`zcrm skill`):** Generates ready-to-use skill/rules files for Claude Code, Cursor, Windsurf, GitHub Copilot, Cline, Gemini, Codex/AGENTS.md, and plain Markdown — so your AI assistant can read the extracted schema and write valid Deluge using exact API names.
33
+ 6. **Robust Pipeline & Error Handling:**
34
+ - Concurrent extraction with a configurable, edition-aware cap on simultaneous API calls for fast pulls.
35
+ - One-way sync: overwrites changed files and removes local metadata deleted on Zoho — without wiping data on a failed run.
20
36
  - Interactive progress indicators showing modules and deluge functions as they download.
21
37
  - Resilient Axios client with auto-refreshing OAuth tokens.
22
- - Exponential backoff retry system to handle API rate limits (HTTP 429).
38
+ - Exponential backoff retry system to handle API rate/concurrency limits (HTTP 429).
23
39
  - Rich console results screen showing execution statistics.
24
40
 
25
41
  ---
@@ -101,7 +117,7 @@ zcrm login [options]
101
117
  ---
102
118
 
103
119
  ### 2. `zcrm pull`
104
- Crawls all Zoho CRM endpoints to extract metadata schemas and custom functions deluge scripts.
120
+ Crawls all Zoho CRM endpoints (read-only) to extract metadata schemas and custom function Deluge scripts, and **syncs** them into your local folder.
105
121
 
106
122
  ```bash
107
123
  zcrm pull [options]
@@ -109,15 +125,36 @@ zcrm pull [options]
109
125
 
110
126
  **Options:**
111
127
  * `-o, --output <dir>` - Destination folder path (defaults to `./metadata` in the current working directory).
128
+ * `-c, --concurrency <n>` - Maximum number of simultaneous Zoho API calls, `1`–`25` (defaults to `5`).
129
+ * `--with-counts` - Also fetch each module's **record count**. This reads record data (not just schema) and costs **~50 API credits per module**, so it is **off by default**. Omit it to keep the pull strictly metadata-only.
112
130
 
113
131
  **What happens during a pull:**
114
132
  1. Verifies authentication and refreshes active OAuth access tokens automatically.
115
133
  2. Fetches Zoho Org information and sets up local project mapping configs.
116
- 3. Crawls every module to pull field configurations and page layout schemas (with a beautiful progress indicator).
134
+ 3. Crawls every module to pull field configurations and page layout schemas (with a live progress indicator).
117
135
  4. Fetches Profiles, Roles, and Webhooks settings.
118
- 5. Downloads all custom deluge function scripts.
136
+ 5. Downloads all custom Deluge function scripts.
119
137
  6. Compiles a consolidated database of schemas and summaries.
120
- 7. Prints a rich-text CLI banner and statistics summary showing the number of modules, layouts, and fields processed.
138
+ 7. Prints a rich-text CLI statistics summary showing modules, layouts, fields, and stale files removed.
139
+
140
+ #### ⚡ Concurrency (faster pulls, safely)
141
+ A pull makes many independent metadata requests, so they are run **in parallel** with a global cap on simultaneous calls. Zoho enforces a per-org **concurrency limit** based on your edition — **5** (Free), **10** (Standard/Starter), **15** (Professional), **20** (Enterprise/Zoho One), **25** (Ultimate/CRM Plus). The metadata endpoints used here are 1 credit each and are *not* subject to Zoho's stricter sub-concurrency limits.
142
+
143
+ The default of `5` is safe for **every** edition. If you are on a higher edition and want faster pulls, raise it up to your edition's limit:
144
+
145
+ ```bash
146
+ zcrm pull -o ./metadata --concurrency 15
147
+ ```
148
+
149
+ If the limit is ever exceeded (HTTP 429 `TOO_MANY_REQUESTS`), the client automatically backs off exponentially and retries, so a pull never fails just because it was briefly throttled. Leave headroom if other integrations share the same org.
150
+
151
+ #### 🔄 Sync behavior (overwrite + remove stale)
152
+ A pull is a **one-way sync from Zoho into your folder**:
153
+ * Existing files are **overwritten in place** with the latest metadata.
154
+ * Files that no longer correspond to anything on Zoho (a deleted module, field, layout, custom view, related list, profile, role, or function) are **removed locally** — but only within scopes whose authoritative fetch succeeded this run.
155
+ * If a fetch **fails** (e.g. a transient network/permission error), the previous local data for that scope is **kept**, never deleted. A partial or interrupted pull therefore never wipes your data.
156
+
157
+ The summary reports how many stale files were removed. Your own files outside the managed `crm/` and `.zcrm/` trees are never touched.
121
158
 
122
159
  ---
123
160
 
@@ -157,7 +194,60 @@ zcrm llm
157
194
 
158
195
  ---
159
196
 
160
- ### 6. `zcrm logout`
197
+ ### 6. `zcrm skill`
198
+ Generates an **AI assistant skill / rules file** for your IDE or AI coding tool, derived from the `zcrm llm` guide. The generated file teaches your assistant how to read the extracted metadata snapshot and produce valid Deluge / REST code using exact API names — and clearly states that the snapshot is read-only schema with no CRM record access.
199
+
200
+ ```bash
201
+ zcrm skill [options]
202
+ ```
203
+
204
+ **Options:**
205
+ * `--ide <ide>` - Target tool (skips the interactive prompt). One of: `claude`, `cursor`, `windsurf`, `copilot`, `cline`, `gemini`, `codex`, `markdown`.
206
+ * `-d, --dir <dir>` - Project directory to write into (defaults to the current directory).
207
+ * `-m, --metadata-dir <dir>` - Path to your extracted metadata, referenced inside the skill (defaults to `./metadata`).
208
+ * `-f, --force` - Overwrite an existing dedicated skill file without prompting.
209
+
210
+ When run without `--ide`, it prompts you to choose your tool and writes the file to the right place:
211
+
212
+ | Tool | Generated file |
213
+ | :--- | :--- |
214
+ | **Claude Code** | `.claude/skills/zoho-crm-deluge/SKILL.md` |
215
+ | **Cursor** | `.cursor/rules/zoho-crm-deluge.mdc` |
216
+ | **Windsurf** | `.windsurf/rules/zoho-crm-deluge.md` |
217
+ | **GitHub Copilot** | `.github/copilot-instructions.md` |
218
+ | **Cline / Roo Code** | `.clinerules/zoho-crm-deluge.md` |
219
+ | **Gemini CLI** | `GEMINI.md` |
220
+ | **Codex / generic** | `AGENTS.md` |
221
+ | **Plain Markdown** | `ZOHO_CRM_AI_CONTEXT.md` |
222
+
223
+ > For shared files (`AGENTS.md`, `GEMINI.md`, `.github/copilot-instructions.md`), the command inserts a clearly-delimited `ZCRM` block and re-generates it in place on subsequent runs, leaving the rest of your file untouched.
224
+
225
+ **Examples:**
226
+ ```bash
227
+ # Interactive — pick your IDE from a menu
228
+ zcrm skill
229
+
230
+ # Non-interactive — generate a Claude Code skill referencing ./crm-meta
231
+ zcrm skill --ide claude --metadata-dir ./crm-meta
232
+ ```
233
+
234
+ ---
235
+
236
+ ### `zcrm audit`
237
+ Triggers an asynchronous export of the Zoho CRM audit log. By default, it exports all audit logs for the last 3 years. It automatically polls the API until the job is complete and downloads the resulting CSV or ZIP file to `storage/audit_logs/`.
238
+
239
+ ```bash
240
+ # Export all audit logs
241
+ zcrm audit
242
+
243
+ # Export with specific criteria using a JSON file
244
+ zcrm audit --filter input.json
245
+ ```
246
+ *(See Zoho CRM documentation for the format of the filter JSON).*
247
+
248
+ ---
249
+
250
+ ### 7. `zcrm logout`
161
251
  Clears stored configuration credentials, OAuth tokens, and active sessions from the workspace.
162
252
 
163
253
  ```bash
@@ -185,31 +275,52 @@ metadata/
185
275
  │ └── functions.json # Extracted functions catalog
186
276
 
187
277
  ├── crm/ # Standard Zoho CRM metadata layout
188
- └── meta/
189
- ├── modules/ # Individual Module schemas
190
- └── <Module_API_Name>/
191
- ├── <Module_API_Name>.modules-meta.json
192
- ├── summary.json # Custom module summary (counts, fields, picklists, formulas)
193
- ├── fields/ # Field configurations
194
- └── <Field_API_Name>.fields-meta.json
195
- ├── layouts/ # Page Layout configurations
196
- └── <Layout_API_Name>.layouts-meta.json
197
- ├── custom_views/ # Custom View configurations
198
- └── <View_API_Name>.custom_views-meta.json
199
- └── related_lists/ # Related List configurations
200
- └── <List_API_Name>.related_lists-meta.json
201
- ├── profiles/ # Profile Permissions mapping
202
- └── <Profile_Name>.profiles-meta.json
203
- │ ├── roles/ # Role Hierarchy definitions
204
- └── <Role_Name>.roles-meta.json
205
- └── functions/ # Custom Deluge function files (.ds)
206
- ├── standalone/ # Deluge scripts sorted by namespace
207
- └── standalone.<Function_API_Name>.ds
208
- ├── automation/
209
- ├── button/
210
- ├── related_list/
211
- ├── schedule/
212
- └── salessignals/
278
+ ├── meta/
279
+ ├── custom_links/ # Global Custom Links
280
+ ├── data_sharing/ # Data Sharing Settings
281
+ ├── data_sharing_actions_summary/ # Data Sharing Actions Summary
282
+ ├── data_sharing_rules/ # Data Sharing Rules
283
+ ├── email_templates/ # Email Templates
284
+ ├── global_picklists/ # Global Picklists
285
+ ├── inventory_templates/# Inventory Templates
286
+ ├── territories/ # Territories
287
+ ├── variables/ # Global Variables
288
+ ├── wizards/ # CRM Wizards
289
+ ├── workflow_rules/ # Global Workflow Rules
290
+ ├── modules/ # Individual Module schemas
291
+ │ │ └── <Module_API_Name>/
292
+ │ ├── <Module_API_Name>.modules-meta.json
293
+ │ │ ├── summary.json # Custom module summary (counts, fields, picklists, formulas, records_count)
294
+ │ ├── duplicate_check_preference.json # Duplicate Check Preferences
295
+ │ │ ├── fields/ # Field configurations
296
+ │ │ │ └── <Field_API_Name>.fields-meta.json
297
+ │ ├── layouts/ # Page Layout configurations
298
+ │ │ │ ├── <Layout_API_Name>.layouts-meta.json
299
+ │ │ │ └── map_dependencies/
300
+ │ │ │ └── <Layout_ID>.json
301
+ │ │ ├── custom_views/ # Custom View configurations
302
+ │ │ │ └── <View_API_Name>.custom_views-meta.json
303
+ │ │ │ ├── record_locking_configurations/ # Record Locking Rules
304
+ │ │ │ │ └── <Config_ID>.json
305
+ │ │ │ ├── related_lists/ # Related List configurations
306
+ │ │ │ │ └── <List_API_Name>.related_lists-meta.json
307
+ │ │ │ ├── tags/ # Module Tags
308
+ │ │ │ │ └── <Tag_ID>.json
309
+ │ │ │ └── workflow_configurations/ # Module Workflow Configurations
310
+ │ │ │ └── <Config_ID>.json
311
+ │ │ ├── profiles/ # Profile Permissions mapping
312
+ │ │ │ └── <Profile_Name>.profiles-meta.json
313
+ │ │ └── roles/ # Role Hierarchy definitions
314
+ │ │ └── <Role_Name>.roles-meta.json
315
+ │ └── functions/ # Custom Deluge function files (.ds)
316
+ │ ├── functions.json # Complete list of extracted functions
317
+ │ ├── standalone/ # Deluge scripts sorted by namespace
318
+ │ │ └── standalone.<Function_API_Name>.ds
319
+ │ ├── automation/
320
+ │ ├── button/
321
+ │ ├── related_list/
322
+ │ ├── schedule/
323
+ │ └── salessignals/
213
324
 
214
325
  ├── zcrm-project.json # ZCRM CLI project settings config
215
326
  ├── meta.json # ZCRM resource configuration
@@ -220,14 +331,20 @@ metadata/
220
331
 
221
332
  ## 🏢 About Wanas Apps
222
333
 
223
- **[Wanas Apps](https://www.wanasapps.com)** is an elite **Zoho Premium Partner** and consulting firm in the Middle East and North Africa (MENA) region. We specialize in digital transformation, Zoho implementation, custom CRM customization, full-stack application development on Zoho Catalyst, and building certified business compliance tools (such as Egyptian E-Invoice compliance integrations).
334
+ **[Wanas Apps](https://www.wanasapps.com)** is an elite **Zoho Premium Partner** and advanced technical consulting firm serving the Middle East and North Africa (MENA) region. We bridge the gap between standard business processes and advanced technical architecture, specializing in end-to-end digital transformation.
335
+
336
+ ### 🌟 Our Core Expertise
337
+ - **Zoho Ecosystem Implementation:** Tailored deployments of Zoho CRM, Zoho Books, Zoho Creator, and the wider Zoho suite.
338
+ - **Custom Application Development:** Full-stack, scalable, and serverless solutions built on **Zoho Catalyst**, Node.js, and modern frontends like Angular.
339
+ - **Advanced Integrations & Automations:** We start where off-the-shelf software ends—building robust API gateways, custom webhooks, and complex CRM workflow automations.
340
+ - **Compliance & Localization:** Certified business compliance tools, including seamless native integrations for the Egyptian E-Invoice and E-Receipt systems (ETA).
224
341
 
225
- We start where off-the-shelf software ends, building custom features that fit around your business blueprint.
342
+ *We don't just implement software; we build custom features that fit seamlessly around your unique business blueprint.*
226
343
 
227
344
  ---
228
345
 
229
346
  ## ✉️ Support & Feedback
230
347
 
231
- If you encounter any issues, have feature requests, or need custom Zoho integrations, please reach out to our team at **support@wanasapps.com**.
348
+ If you encounter any issues, have feature requests, or need custom Zoho integrations, please reach out to our team at **support@wanasapps.com** or visit **[wanasapps.com](https://www.wanasapps.com)**.
232
349
 
233
350
  This project is maintained by Wanas Apps and licensed under the **ISC License**.
package/banner.txt CHANGED
@@ -1,7 +1,8 @@
1
- ___
2
- \ \ __ __ _
3
- ___ \ \ \ \ / /_ _ _ __ __ _ ___ / \ _ __ _ __ ___
4
- / / / / \ \ /\ / / _` | '_ \ / _` / __| / _ \ | '_ \| '_ \/ __|
5
- / / /__/ \ V V / (_| | | | | (_| \__ \ / ___ \| |_) | |_) \__ \
6
- \ \ \_/\_/ \__,_|_| |_|\__,_|___/ /_/ \_\ .__/| .__/|___/
7
- \__\ |_| |_|
1
+ __ __ _
2
+ \ \ / /_ _ _ __ __ _ ___ / \ _ __ _ __ ___
3
+ \ \ /\ / / _` | '_ \ / _` / __| / _ \ | '_ \| '_ \/ __|
4
+ \ V V / (_| | | | | (_| \__ \ / ___ \| |_) | |_) \__ \
5
+ \_/\_/ \__,_|_| |_|\__,_|___/ /_/ \_\ .__/| .__/|___/
6
+ |_| |_|
7
+ For Support Contact us on support@wanasapps.com
8
+ www.wanasapps.com
package/bin/cli.js CHANGED
@@ -8,10 +8,44 @@ const inquirer = require('inquirer').default || require('inquirer');
8
8
  const http = require('http');
9
9
  const url = require('url');
10
10
  const path = require('path');
11
- const { exec } = require('child_process');
11
+ const { exec, spawn } = require('child_process');
12
12
  const configStore = require('../src/utils/configStore');
13
13
  const authService = require('../src/services/authService');
14
14
  const crmMetadataService = require('../src/services/metadata/crmMetadataService');
15
+ const auditLogService = require('../src/services/auditLogService');
16
+ const skillGenerator = require('../src/utils/skillGenerator');
17
+
18
+ /**
19
+ * Opens a URL in the user's default browser in a cross-platform-safe way.
20
+ * On Windows, `start` is a cmd builtin whose first quoted token is the window
21
+ * title, so an empty "" placeholder is required before the URL. On macOS/Linux
22
+ * the URL is passed as a separate argv entry (no shell) to avoid metacharacter
23
+ * issues. All failures are swallowed — the URL is also printed for manual use.
24
+ * @param {string} targetUrl - The URL to open.
25
+ */
26
+ function openBrowser(targetUrl) {
27
+ try {
28
+ if (process.platform === 'win32') {
29
+ // Quotes protect the '&' query separators; "" is the required title arg.
30
+ exec(`start "" "${targetUrl.replace(/"/g, '%22')}"`, () => {});
31
+ } else {
32
+ const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open';
33
+ const child = spawn(cmd, [targetUrl], { stdio: 'ignore', detached: true });
34
+ child.on('error', () => {});
35
+ child.unref();
36
+ }
37
+ } catch (err) {
38
+ // User can still click the URL printed to the console.
39
+ }
40
+ }
41
+
42
+ // A short, prominent reminder of the core guarantee: the tool is strictly
43
+ // read-only and never modifies Zoho CRM. By default it pulls schema/metadata
44
+ // only; reading record data is opt-in (pull --with-counts) or explicit (audit).
45
+ const READ_ONLY_NOTICE =
46
+ '\x1b[1;33mℹ Note:\x1b[0m This tool is \x1b[1mread-only\x1b[0m — it \x1b[1mnever creates, updates, or deletes\x1b[0m anything\n' +
47
+ ' in Zoho CRM. By default it extracts \x1b[1mschema/metadata only\x1b[0m (record counts and audit\n' +
48
+ ' logs are read only when you explicitly ask via --with-counts or the audit command).';
15
49
 
16
50
  // Default client credentials for generating Zoho login links
17
51
  const DEFAULT_CLIENT_ID = '1000.M1XE1M7CA4VFDDIRANB8E5AKIBQ72U';
@@ -64,7 +98,8 @@ program
64
98
  .name('zcrm')
65
99
  .description('Zoho CRM V8 Metadata Extractor CLI Utility')
66
100
  .version(pkg.version, '-v, --version', 'output the current version')
67
- .addHelpText('before', getBanner());
101
+ .addHelpText('before', getBanner())
102
+ .addHelpText('after', `\n${READ_ONLY_NOTICE}\n`);
68
103
 
69
104
  /**
70
105
  * CLI Callback Server validation & token retrieval flow.
@@ -176,11 +211,8 @@ function runOAuthServer(clientId, clientSecret, redirectUri, dc) {
176
211
  console.log(`\x1b[4;34m${authUrl}\x1b[0m`);
177
212
  console.log('\x1b[1;33m========================================================================\x1b[0m\n');
178
213
 
179
- // Attempt to auto-open in default browser
180
- const launch = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
181
- exec(`${launch} "${authUrl.replace(/"/g, '\\"')}"`, (execErr) => {
182
- // Suppress errors (user can manually click printed link if this fails)
183
- });
214
+ // Attempt to auto-open in default browser (failures are non-fatal)
215
+ openBrowser(authUrl);
184
216
  });
185
217
  });
186
218
  }
@@ -381,8 +413,10 @@ program
381
413
  // REGISTER COMMAND: pull
382
414
  program
383
415
  .command('pull')
384
- .description('Pull Zoho CRM metadata and save as JSON file directory structure')
416
+ .description('Pull Zoho CRM metadata (read-only) and sync it into a local JSON directory structure')
385
417
  .option('-o, --output <dir>', 'Output folder path', './metadata')
418
+ .option('-c, --concurrency <n>', 'Max concurrent Zoho API calls (1-25; default 5 — safe for all editions)', '5')
419
+ .option('--with-counts', 'Also fetch per-module record counts (reads record data; ~50 API credits per module)')
386
420
  .action(async (options) => {
387
421
  console.log(getBanner());
388
422
  // Reload tokens
@@ -394,94 +428,79 @@ program
394
428
  }
395
429
 
396
430
  const outputDir = path.resolve(options.output);
431
+ let concurrency = parseInt(options.concurrency, 10);
432
+ if (!Number.isFinite(concurrency) || concurrency < 1) concurrency = 5;
433
+ if (concurrency > 25) concurrency = 25;
434
+
397
435
  console.log(`\x1b[36mInitializing CRM Metadata Pulling...\x1b[0m`);
398
- console.log(`Destination Folder: ${outputDir}\n`);
436
+ console.log(`Destination Folder: ${outputDir}`);
437
+ console.log(`Concurrency: ${concurrency} simultaneous API call(s)`);
438
+ if (options.withCounts) {
439
+ console.log('\x1b[33mRecord counts: ON\x1b[0m (reads record data; ~50 API credits per module)');
440
+ }
441
+ console.log('');
399
442
 
400
- let currentModule = 0;
401
- let totalModules = 0;
402
- let currentFunction = 0;
403
- let totalFunctions = 0;
404
443
  let inModulesPhase = false;
405
444
  let inFunctionsPhase = false;
406
445
 
446
+ // Finalizes an in-progress progress bar line with a success message.
447
+ const finishPhaseLine = (text) => {
448
+ const readline = require('readline');
449
+ readline.clearLine(process.stdout, 0);
450
+ readline.cursorTo(process.stdout, 0);
451
+ console.log(`\x1b[32m✔\x1b[0m ${text}`);
452
+ };
453
+
407
454
  try {
408
455
  await crmMetadataService.extract((stage, message, details = {}) => {
409
456
  switch (stage) {
410
457
  case 'FETCH_ORG':
411
- console.log(`\x1b[34mℹ\x1b[0m ${message}`);
412
- break;
413
- case 'FETCH_ORG_SUCCESS':
414
- console.log(`\x1b[32m✔\x1b[0m ${message}`);
415
- break;
416
458
  case 'FETCH_MODULES':
417
459
  console.log(`\x1b[34mℹ\x1b[0m ${message}`);
418
460
  break;
461
+ case 'FETCH_ORG_SUCCESS':
419
462
  case 'FETCH_MODULES_SUCCESS':
420
- console.log(`\x1b[32m✔\x1b[0m ${message}`);
421
- break;
422
- case 'MODULE_START':
423
- if (!inModulesPhase) {
424
- console.log(`\n\x1b[1mExtracting Modules Metadata:\x1b[0m`);
425
- inModulesPhase = true;
426
- }
427
- currentModule = details.index;
428
- totalModules = details.total;
429
- drawProgress(currentModule - 1, totalModules, 'Modules', `Extracting ${details.module}...`);
430
- break;
431
- case 'MODULE_SUCCESS':
432
- if (currentModule === totalModules) {
433
- const readline = require('readline');
434
- readline.clearLine(process.stdout, 0);
435
- readline.cursorTo(process.stdout, 0);
436
- console.log(`\x1b[32m✔\x1b[0m All modules metadata extracted successfully.`);
437
- inModulesPhase = false;
438
- } else {
439
- drawProgress(currentModule, totalModules, 'Modules', `Completed ${details.module || ''}`);
440
- }
441
- break;
442
- case 'FETCH_WEBHOOKS':
443
- console.log(`\n\x1b[34mℹ\x1b[0m ${message}`);
444
- break;
445
463
  case 'FETCH_WEBHOOKS_SUCCESS':
446
- console.log(`\x1b[32m✔\x1b[0m ${message}`);
447
- break;
448
- case 'FETCH_PROFILES':
449
- console.log(`\n\x1b[34mℹ\x1b[0m ${message}`);
450
- break;
451
464
  case 'FETCH_PROFILES_SUCCESS':
452
- console.log(`\x1b[32m✔\x1b[0m ${message}`);
453
- break;
454
- case 'FETCH_ROLES':
455
- console.log(`\n\x1b[34mℹ\x1b[0m ${message}`);
456
- break;
457
465
  case 'FETCH_ROLES_SUCCESS':
458
466
  console.log(`\x1b[32m✔\x1b[0m ${message}`);
459
467
  break;
468
+ case 'FETCH_WEBHOOKS':
469
+ case 'FETCH_PROFILES':
470
+ case 'FETCH_ROLES':
460
471
  case 'FETCH_FUNCTIONS':
461
472
  console.log(`\n\x1b[34mℹ\x1b[0m ${message}`);
462
473
  break;
463
- case 'FETCH_FUNCTIONS_SUCCESS':
464
- console.log(`\x1b[32m✔\x1b[0m ${message}`);
474
+ case 'MODULES_START':
475
+ console.log(`\n\x1b[1mExtracting Modules Metadata:\x1b[0m`);
476
+ inModulesPhase = true;
477
+ if (!details.total) {
478
+ finishPhaseLine('No modules to extract.');
479
+ inModulesPhase = false;
480
+ }
465
481
  break;
466
- case 'FUNCTION_START':
467
- if (!inFunctionsPhase) {
468
- console.log(`\n\x1b[1mExtracting Custom Functions Deluge Scripts:\x1b[0m`);
469
- inFunctionsPhase = true;
482
+ case 'MODULE_PROGRESS':
483
+ if (details.completed >= details.total) {
484
+ finishPhaseLine(`All ${details.total} modules processed${details.removed ? '' : ''}.`);
485
+ inModulesPhase = false;
486
+ } else {
487
+ drawProgress(details.completed, details.total, 'Modules', `${details.module || ''}`);
470
488
  }
471
- currentFunction = details.index;
472
- totalFunctions = details.total;
473
- drawProgress(currentFunction - 1, totalFunctions, 'Functions', `Downloading ${details.name}...`);
474
489
  break;
475
- case 'FUNCTION_SUCCESS':
476
- case 'FUNCTION_WARNING':
477
- if (currentFunction === totalFunctions) {
478
- const readline = require('readline');
479
- readline.clearLine(process.stdout, 0);
480
- readline.cursorTo(process.stdout, 0);
481
- console.log(`\x1b[32m✔\x1b[0m All custom functions deluge scripts extracted.`);
490
+ case 'FUNCTIONS_START':
491
+ console.log(`\n\x1b[1mExtracting Custom Functions Deluge Scripts:\x1b[0m`);
492
+ inFunctionsPhase = true;
493
+ if (!details.total) {
494
+ finishPhaseLine('No custom functions found.');
495
+ inFunctionsPhase = false;
496
+ }
497
+ break;
498
+ case 'FUNCTION_PROGRESS':
499
+ if (details.completed >= details.total) {
500
+ finishPhaseLine('All custom function deluge scripts extracted.');
482
501
  inFunctionsPhase = false;
483
502
  } else {
484
- drawProgress(currentFunction, totalFunctions, 'Functions', `Completed ${details.name || ''}`);
503
+ drawProgress(details.completed, details.total, 'Functions', `${details.name || ''}`);
485
504
  }
486
505
  break;
487
506
  case 'FAILURE':
@@ -500,8 +519,9 @@ program
500
519
  console.log(` \x1b[1mTotal Layouts:\x1b[0m \x1b[32m${details.totalLayouts || 0}\x1b[0m`);
501
520
  console.log(` \x1b[1mRelated Lists:\x1b[0m \x1b[35m${details.totalRelatedLists || 0}\x1b[0m (saved inside crm/meta/modules/)`);
502
521
  console.log(` \x1b[1mCustom Views:\x1b[0m \x1b[35m${details.totalCustomViews || 0}\x1b[0m (saved inside crm/meta/modules/)`);
522
+ console.log(` \x1b[1mStale Files Removed:\x1b[0m \x1b[33m${details.removedStale || 0}\x1b[0m (deleted on Zoho CRM since last pull)`);
503
523
  console.log(` \x1b[1mOutput Directory:\x1b[0m \x1b[34m${outputDir}\x1b[0m`);
504
-
524
+
505
525
  if (details.errors && details.errors.length > 0) {
506
526
  console.log(`\n \x1b[1;33m⚠️ Warnings:\x1b[0m \x1b[33m${details.errors.length} API request(s) failed or were skipped due to permissions.\x1b[0m`);
507
527
  }
@@ -513,7 +533,7 @@ program
513
533
  }
514
534
  break;
515
535
  }
516
- }, outputDir);
536
+ }, outputDir, { concurrency, withCounts: !!options.withCounts });
517
537
 
518
538
  process.exit(0);
519
539
  } catch (error) {
@@ -522,6 +542,40 @@ program
522
542
  }
523
543
  });
524
544
 
545
+ // REGISTER COMMAND: audit
546
+ program
547
+ .command('audit')
548
+ .description('Export the Zoho CRM audit log (asynchronous job)')
549
+ .option('-f, --filter <path>', 'Path to a JSON file containing the audit_log_export criteria')
550
+ .action(async (options) => {
551
+ console.log(getBanner());
552
+ // This command DOES read/export audit-log data, so it gets its own honest
553
+ // notice rather than the generic "metadata only / no data access" one.
554
+ console.log(
555
+ '\x1b[1;33mℹ Note:\x1b[0m This exports your Zoho CRM \x1b[1maudit-log data\x1b[0m (read-only).\n' +
556
+ ' It never creates, updates, or deletes anything in Zoho CRM.\n'
557
+ );
558
+
559
+ // Ensure user is authenticated
560
+ authService.loadTokens();
561
+ if (!authService.isAuthenticated()) {
562
+ console.error('\n❌ You are not logged in. Please run `zcrm login` first.');
563
+ process.exit(1);
564
+ }
565
+
566
+ try {
567
+ await authService.getAccessToken(); // Refresh if necessary
568
+ await auditLogService.exportAuditLog(options.filter);
569
+ console.log('\n\x1b[1;32m====================================================================\x1b[0m');
570
+ console.log('🎉 \x1b[1;32mAudit Log Export Completed Successfully!\x1b[0m');
571
+ console.log('\x1b[1;32m====================================================================\x1b[0m\n');
572
+ process.exit(0);
573
+ } catch (error) {
574
+ console.error(`\n❌ Audit log export failed: ${error.message}`);
575
+ process.exit(1);
576
+ }
577
+ });
578
+
525
579
  // REGISTER COMMAND: llm
526
580
  program
527
581
  .command('llm')
@@ -545,6 +599,96 @@ program
545
599
  }
546
600
  });
547
601
 
602
+ // REGISTER COMMAND: skill
603
+ program
604
+ .command('skill')
605
+ .description('Generate an AI assistant skill/rules file (from llm.md) for your IDE')
606
+ .option('--ide <ide>', 'Target IDE/assistant (claude, cursor, windsurf, copilot, cline, gemini, codex, markdown)')
607
+ .option('-d, --dir <dir>', 'Project directory to write the skill file into', '.')
608
+ .option('-m, --metadata-dir <dir>', 'Path to the extracted metadata, referenced inside the skill', './metadata')
609
+ .option('-f, --force', 'Overwrite an existing skill file without prompting')
610
+ .action(async (options) => {
611
+ console.log(getBanner());
612
+ try {
613
+ let ide = options.ide;
614
+
615
+ // Prompt for the target IDE/assistant when not supplied via --ide.
616
+ if (!ide) {
617
+ const { selected } = await inquirer.prompt([
618
+ {
619
+ type: 'select',
620
+ name: 'selected',
621
+ message: 'Select your IDE / AI assistant:',
622
+ choices: skillGenerator.getChoices()
623
+ }
624
+ ]);
625
+ ide = selected;
626
+ }
627
+
628
+ if (!skillGenerator.TARGETS[ide]) {
629
+ console.error(`\n❌ Unknown IDE/AI target: "${ide}".`);
630
+ console.error(` Valid values: ${Object.keys(skillGenerator.TARGETS).join(', ')}`);
631
+ process.exit(1);
632
+ }
633
+
634
+ const projectDir = path.resolve(options.dir);
635
+ const packageDir = path.join(__dirname, '..');
636
+
637
+ let result = skillGenerator.generateSkill({
638
+ ide,
639
+ projectDir,
640
+ metadataDir: options.metadataDir,
641
+ packageDir,
642
+ force: options.force
643
+ });
644
+
645
+ // Standalone files that already exist require explicit confirmation.
646
+ if (result.status === 'exists') {
647
+ const { overwrite } = await inquirer.prompt([
648
+ {
649
+ type: 'confirm',
650
+ name: 'overwrite',
651
+ message: `File already exists:\n ${result.filePath}\nOverwrite it?`,
652
+ default: false
653
+ }
654
+ ]);
655
+ if (!overwrite) {
656
+ console.log('\n\x1b[33mℹ Cancelled. No file was changed.\x1b[0m\n');
657
+ process.exit(0);
658
+ }
659
+ result = skillGenerator.generateSkill({
660
+ ide,
661
+ projectDir,
662
+ metadataDir: options.metadataDir,
663
+ packageDir,
664
+ force: true
665
+ });
666
+ }
667
+
668
+ const verb = result.status === 'updated' ? 'Updated' : 'Created';
669
+ console.log('\n\x1b[1;32m====================================================================\x1b[0m');
670
+ console.log(`🧩 \x1b[1;32m${verb} ${skillGenerator.TARGETS[ide].label.split('→')[0].trim()} skill file.\x1b[0m`);
671
+ console.log('\x1b[1;32m====================================================================\x1b[0m');
672
+ console.log(` \x1b[1mFile:\x1b[0m \x1b[36m${result.filePath}\x1b[0m`);
673
+ console.log(` \x1b[1mMetadata root:\x1b[0m \x1b[34m${path.resolve(options.metadataDir)}\x1b[0m`);
674
+ console.log('--------------------------------------------------------------------');
675
+ console.log(` ${READ_ONLY_NOTICE}`);
676
+
677
+ // Helpful nudge if no metadata has been pulled yet.
678
+ const metaIndex = path.join(path.resolve(options.metadataDir), '.zcrm', '.store', 'index.json');
679
+ const fs = require('fs');
680
+ if (!fs.existsSync(metaIndex)) {
681
+ console.log('--------------------------------------------------------------------');
682
+ console.log(` \x1b[33mTip:\x1b[0m No metadata found yet. Run \x1b[36mzcrm pull -o "${options.metadataDir}"\x1b[0m to populate it.`);
683
+ }
684
+ console.log('\x1b[1;32m====================================================================\x1b[0m\n');
685
+ process.exit(0);
686
+ } catch (err) {
687
+ console.error(`\n❌ Skill generation failed: ${err.message}`);
688
+ process.exit(1);
689
+ }
690
+ });
691
+
548
692
  // REGISTER COMMAND: dashboard
549
693
  program
550
694
  .command('dashboard')
@@ -552,7 +696,11 @@ program
552
696
  .option('-p, --port <port>', 'Port to run the dashboard on', '3000')
553
697
  .action((options) => {
554
698
  process.env.PORT = options.port;
555
- process.env.ZCRM_CLI_MODE = 'false';
699
+ process.env.ZCRM_VERBOSE = 'true';
700
+ // Keep ZCRM_CLI_MODE='true' (set at startup) so the dashboard shares the
701
+ // same global credential/token store as `zcrm login` and `zcrm pull`.
702
+ // Flipping it to 'false' here would desync the already-initialized
703
+ // authService singleton from the web-mode token path.
556
704
  console.log(`\x1b[36mLaunching Extractor Dashboard...\x1b[0m`);
557
705
  require('../server.js');
558
706
  });