wanas-zcrm-extractor 1.7.1 → 1.7.2

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
@@ -322,11 +322,12 @@ zcrm skill --ide claude --metadata-dir ./crm-meta
322
322
  <details>
323
323
  <summary><h3>7. <code>zcrm audit</code></h3></summary>
324
324
 
325
- Triggers an asynchronous export of the Zoho CRM audit log, polls until it completes (with a bounded timeout), and downloads the resulting CSV/ZIP to `storage/audit_logs/`.
325
+ Triggers an asynchronous export of the Zoho CRM audit log, polls until it completes (with a bounded timeout), and downloads the resulting CSV/ZIP into the result folder's CRM root — `metadata/crm/audit_logs/` by default (override with `-o, --output`). A subsequent `zcrm pull` never deletes this folder.
326
326
 
327
327
  ```bash
328
- zcrm audit # last 3 years by default
328
+ zcrm audit # last 3 years by default → ./metadata/crm/audit_logs/
329
329
  zcrm audit --filter input.json # custom export criteria
330
+ zcrm audit -o ./my-export # save into ./my-export/crm/audit_logs/
330
331
  ```
331
332
 
332
333
  > **Requires the audit-log OAuth scopes** — `ZohoCRM.settings.audit_logs.CREATE` and `ZohoCRM.settings.audit_logs.READ` (both in the default scopes). If you authenticated *before* they were added you'll see `401 invalid oauth scope` — just re-authenticate: `zcrm logout` then `zcrm login`. *(If the download step later reports a file-scope error, add `ZohoFiles.files.READ` to `ZOHO_SCOPES` and log in again.)*
package/bin/cli.js CHANGED
@@ -319,8 +319,13 @@ program
319
319
  dc = dc || answers.dc;
320
320
  }
321
321
 
322
+ // Record the resolved, non-sensitive parameters of this run. Secrets
323
+ // (client secret, refresh token) are deliberately never written to disk.
324
+ logInfo(`Login config — DC: ${dc || 'com'}, redirect URI: ${redirectUri}, client ID set: ${!!clientId}.`, 'cli');
325
+
322
326
  // 1. Headless flow using direct refresh token
323
327
  if (refreshToken) {
328
+ logInfo('Using headless refresh-token flow (no browser callback).', 'cli');
324
329
  console.log('\n\x1b[34mℹ\x1b[0m Bypassing browser callback. Verifying refresh token credentials...');
325
330
 
326
331
  configStore.save({
@@ -336,6 +341,7 @@ program
336
341
  // Initialize and refresh to verify key validity
337
342
  authService.loadTokens();
338
343
  await authService.refreshAccessToken();
344
+ logInfo('Refresh token verified; access token obtained successfully.', 'cli');
339
345
 
340
346
  console.log('\n\x1b[1;32m====================================================================\x1b[0m');
341
347
  console.log('🎉 \x1b[1;32mCredentials and session authenticated successfully via Refresh Token!\x1b[0m');
@@ -344,7 +350,9 @@ program
344
350
  }
345
351
 
346
352
  // 2. Interactive Browser OAuth2 flow
353
+ logInfo('Starting interactive browser OAuth2 flow.', 'cli');
347
354
  await runOAuthServer(clientId, clientSecret, redirectUri, dc);
355
+ logInfo('OAuth2 flow completed; tokens exchanged and saved.', 'cli');
348
356
  console.log('\n\x1b[1;32m====================================================================\x1b[0m');
349
357
  console.log('🎉 \x1b[1;32mSuccessfully logged in! Access configurations saved.\x1b[0m');
350
358
  console.log('\x1b[1;32m====================================================================\x1b[0m\n');
@@ -359,7 +367,9 @@ program
359
367
  .command('logout')
360
368
  .description('Logout of Zoho CRM and clear stored configurations')
361
369
  .action(() => {
370
+ logInfo('Clearing stored credentials and tokens.', 'cli');
362
371
  configStore.clear();
372
+ logInfo('Stored configuration cleared.', 'cli');
363
373
  console.log('🎉 Stored credentials cleared successfully.');
364
374
  process.exit(0);
365
375
  });
@@ -370,6 +380,7 @@ program
370
380
  .description('Check currently authenticated Zoho CRM user and organization details')
371
381
  .action(async () => {
372
382
  console.log(getBanner());
383
+ logInfo('Loading stored tokens.', 'cli');
373
384
  authService.loadTokens();
374
385
 
375
386
  if (!authService.isAuthenticated()) {
@@ -378,14 +389,16 @@ program
378
389
 
379
390
  try {
380
391
  const apiClient = require('../src/utils/apiClient');
381
-
392
+
382
393
  console.log(`\x1b[36mFetching active user and organization details...\x1b[0m\n`);
383
394
 
384
395
  // Fetch user profile and org details concurrently
396
+ logInfo('Fetching current user (/crm/v8/users) and org (/crm/v8/org).', 'cli');
385
397
  const [userRes, orgRes] = await Promise.all([
386
398
  apiClient.get('/crm/v8/users', { type: 'CurrentUser' }),
387
399
  apiClient.get('/crm/v8/org')
388
400
  ]);
401
+ logInfo('User and organization details fetched successfully.', 'cli');
389
402
 
390
403
  const user = userRes?.users?.[0] || {};
391
404
  const org = orgRes?.org?.[0] || {};
@@ -434,13 +447,16 @@ program
434
447
  console.log(` Callback URI: \x1b[34m${storedConfig.redirect_uri || 'Not configured'}\x1b[0m`);
435
448
  console.log(`--------------------------------------------------------------------`);
436
449
 
450
+ logInfo(`Auth state — has tokens: ${!!hasTokens}, DC: ${storedConfig.dc || 'none'}.`, 'cli');
451
+
437
452
  // 2. Check Local Metadata Directory Status
438
453
  const targetDir = path.resolve(options.dir);
439
454
  const indexPath = path.join(targetDir, '.zcrm', '.store', 'index.json');
440
455
  const functionsDir = path.join(targetDir, 'crm', 'functions');
441
456
 
442
457
  console.log(`\x1b[1m📂 Local Metadata Database (${targetDir}):\x1b[0m`);
443
-
458
+
459
+ logInfo(`Inspecting local metadata directory: ${targetDir}`, 'cli');
444
460
  if (fs.existsSync(indexPath)) {
445
461
  try {
446
462
  const index = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
@@ -458,6 +474,7 @@ program
458
474
  }
459
475
  }
460
476
 
477
+ logInfo(`Metadata database found — ${index.modules_count || 0} modules, ${fnCount} deluge scripts.`, 'cli');
461
478
  console.log(` Status: \x1b[1;32mDatabase Exists\x1b[0m`);
462
479
  console.log(` Organization: \x1b[36m${index.crm_org || 'Unknown'}\x1b[0m`);
463
480
  console.log(` Last Synced: \x1b[32m${index.generated_at ? new Date(index.generated_at).toLocaleString() : 'N/A'}\x1b[0m`);
@@ -467,6 +484,7 @@ program
467
484
  console.log(` Status: \x1b[1;33mInvalid / Corrupted Database\x1b[0m (Error: ${err.message})`);
468
485
  }
469
486
  } else {
487
+ logInfo('No metadata database found at target directory.', 'cli');
470
488
  console.log(` Status: \x1b[1;30mEmpty (No database found at target directory)\x1b[0m`);
471
489
  console.log(` Tip: Run \x1b[36mzcrm pull -o "${options.dir}"\x1b[0m to fetch metadata.`);
472
490
  }
@@ -616,6 +634,7 @@ program
616
634
  .command('audit')
617
635
  .description('Export the Zoho CRM audit log (asynchronous job)')
618
636
  .option('-f, --filter <path>', 'Path to a JSON file containing the audit_log_export criteria')
637
+ .option('-o, --output <dir>', 'Result folder to save the audit log into (its crm/audit_logs/ subfolder)', './metadata')
619
638
  .action(async (options) => {
620
639
  console.log(getBanner());
621
640
  // This command DOES read/export audit-log data, so it gets its own honest
@@ -632,10 +651,17 @@ program
632
651
  }
633
652
 
634
653
  try {
654
+ logInfo(`Audit output directory: ${path.resolve(options.output)}`, 'cli');
655
+ if (options.filter) logInfo(`Audit filter file: ${options.filter}`, 'cli');
656
+ logInfo('Verifying access token before export.', 'cli');
635
657
  await authService.getAccessToken(); // Refresh if necessary
636
- await auditLogService.exportAuditLog(options.filter);
658
+ const savedPath = await auditLogService.exportAuditLog(options.filter, options.output);
637
659
  console.log('\n\x1b[1;32m====================================================================\x1b[0m');
638
660
  console.log('🎉 \x1b[1;32mAudit Log Export Completed Successfully!\x1b[0m');
661
+ console.log('\x1b[1;32m====================================================================\x1b[0m');
662
+ if (savedPath) {
663
+ console.log(` \x1b[1mSaved To:\x1b[0m \x1b[34m${savedPath}\x1b[0m`);
664
+ }
639
665
  console.log('\x1b[1;32m====================================================================\x1b[0m\n');
640
666
  process.exit(0);
641
667
  } catch (error) {
@@ -660,8 +686,10 @@ program
660
686
  try {
661
687
  const fs = require('fs');
662
688
  const mdPath = path.join(__dirname, '../llm.md');
689
+ logInfo(`Reading LLM manual from ${mdPath}.`, 'cli');
663
690
  if (fs.existsSync(mdPath)) {
664
691
  const content = fs.readFileSync(mdPath, 'utf8');
692
+ logInfo(`LLM manual loaded (${content.length} chars); printing to console.`, 'cli');
665
693
  console.log('\n\x1b[36m📘 Wanas Extractor - LLM System Manual:\x1b[0m\n');
666
694
  console.log(content);
667
695
  } else {
@@ -706,6 +734,8 @@ program
706
734
  const projectDir = path.resolve(options.dir);
707
735
  const packageDir = path.join(__dirname, '..');
708
736
 
737
+ logInfo(`Generating "${ide}" skill into ${projectDir} (metadata root: ${path.resolve(options.metadataDir)}).`, 'cli');
738
+
709
739
  let result = skillGenerator.generateSkill({
710
740
  ide,
711
741
  projectDir,
@@ -713,6 +743,7 @@ program
713
743
  packageDir,
714
744
  force: options.force
715
745
  });
746
+ logInfo(`Skill generation result: ${result.status} → ${result.filePath}`, 'cli');
716
747
 
717
748
  // Standalone files that already exist require explicit confirmation.
718
749
  if (result.status === 'exists') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wanas-zcrm-extractor",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "Local Zoho CRM V8 Metadata Extractor CLI and Web Dashboard Utility",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -9,7 +9,7 @@ async function delay(ms) {
9
9
  return new Promise(resolve => setTimeout(resolve, ms));
10
10
  }
11
11
 
12
- async function exportAuditLog(filterFile) {
12
+ async function exportAuditLog(filterFile, outputDir) {
13
13
  try {
14
14
  // For a FULL export Zoho's docs say to "skip the input request" entirely —
15
15
  // i.e. send an EMPTY body. We send `null` (empty body); the apiClient adds
@@ -50,7 +50,9 @@ async function exportAuditLog(filterFile) {
50
50
 
51
51
  let downloadUrl = null;
52
52
  let attempts = 0;
53
- const POLL_INTERVAL_MS = 15000;
53
+ // Poll cadence is overridable (mainly so the test suite doesn't wait real
54
+ // seconds); defaults to the documented 15s between status checks.
55
+ const POLL_INTERVAL_MS = parseInt(process.env.ZCRM_AUDIT_POLL_MS, 10) || 15000;
54
56
  const MAX_ATTEMPTS = 120; // 120 × 15s = 30 minutes, then give up rather than hang forever
55
57
 
56
58
  // Poll until completion (bounded so a stuck job can never hang the CLI).
@@ -88,9 +90,11 @@ async function exportAuditLog(filterFile) {
88
90
  throw new Error(`Audit log export did not finish within ${Math.round(MAX_ATTEMPTS * POLL_INTERVAL_MS / 60000)} minutes (job ${jobId}). Try a narrower filter or re-run later.`);
89
91
  }
90
92
 
91
- // Download the file
92
- const outputDir = path.join(process.cwd(), 'storage', 'audit_logs');
93
- await fs.ensureDir(outputDir);
93
+ // Download the file into the result folder's CRM root, alongside the
94
+ // metadata `zcrm pull` produces (e.g. ./metadata/crm/audit_logs/). The pull
95
+ // sync sweep never touches crm/audit_logs/, so re-pulls won't delete it.
96
+ const targetDir = path.join(path.resolve(outputDir || './metadata'), 'crm', 'audit_logs');
97
+ await fs.ensureDir(targetDir);
94
98
 
95
99
  // Determine the extension from the URL *path* (signed URLs carry a query
96
100
  // string, so endsWith('.zip') on the whole URL would always miss). Default
@@ -103,7 +107,7 @@ async function exportAuditLog(filterFile) {
103
107
  } catch (urlErr) {
104
108
  // Keep the .zip default if the URL can't be parsed.
105
109
  }
106
- const outputPath = path.join(outputDir, `AuditLog_${jobId}${ext}`);
110
+ const outputPath = path.join(targetDir, `AuditLog_${jobId}${ext}`);
107
111
 
108
112
  logInfo(`Downloading result to ${outputPath}...`);
109
113
 
@@ -134,7 +138,9 @@ async function exportAuditLog(filterFile) {
134
138
  }
135
139
 
136
140
  logInfo(`Audit log successfully downloaded and saved to: ${outputPath}`);
137
-
141
+
142
+ return outputPath;
143
+
138
144
  } catch (error) {
139
145
  logError(error, 'auditLogService.exportAuditLog');
140
146
  throw error;