hedgequantx 2.5.6 → 2.5.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.5.6",
3
+ "version": "2.5.8",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -6,7 +6,7 @@
6
6
  const chalk = require('chalk');
7
7
  const ora = require('ora');
8
8
 
9
- const { getLogoWidth, drawBoxHeader, drawBoxFooter, displayBanner } = require('../ui');
9
+ const { getLogoWidth, drawBoxHeader, drawBoxHeaderContinue, drawBoxFooter, displayBanner } = require('../ui');
10
10
  const { prompts } = require('../utils');
11
11
  const aiService = require('../services/ai');
12
12
  const { getCategories, getProvidersByCategory } = require('../services/ai/providers');
@@ -31,7 +31,7 @@ const aiAgentMenu = async () => {
31
31
 
32
32
  console.clear();
33
33
  displayBanner();
34
- drawBoxHeader('AI AGENT', boxWidth);
34
+ drawBoxHeaderContinue('AI AGENT', boxWidth);
35
35
 
36
36
  // Show current status
37
37
  const connection = aiService.getConnection();
@@ -104,7 +104,7 @@ const showExistingTokens = async () => {
104
104
 
105
105
  console.clear();
106
106
  displayBanner();
107
- drawBoxHeader('SCANNING FOR EXISTING SESSIONS...', boxWidth);
107
+ drawBoxHeaderContinue('SCANNING FOR EXISTING SESSIONS...', boxWidth);
108
108
  console.log(makeLine(''));
109
109
  console.log(makeLine(chalk.gray('CHECKING VS CODE, CURSOR, CLAUDE CLI, OPENCODE...')));
110
110
  console.log(makeLine(''));
@@ -80,20 +80,34 @@ const dashboardMenu = async (service) => {
80
80
 
81
81
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
82
82
 
83
- // Menu in 2 columns
84
- const col1Width = Math.floor(W / 2);
85
- const menuRow = (left, right) => {
86
- const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '');
87
- const rightPlain = right.replace(/\x1b\[[0-9;]*m/g, '');
88
- const leftPadded = ' ' + left + ' '.repeat(Math.max(0, col1Width - leftPlain.length - 2));
89
- const rightPadded = right + ' '.repeat(Math.max(0, W - col1Width - rightPlain.length));
90
- console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
83
+ // Menu in 3 columns
84
+ const colWidth = Math.floor(W / 3);
85
+
86
+ const menuRow3 = (col1, col2, col3) => {
87
+ const c1Plain = col1.replace(/\x1b\[[0-9;]*m/g, '');
88
+ const c2Plain = col2.replace(/\x1b\[[0-9;]*m/g, '');
89
+ const c3Plain = col3.replace(/\x1b\[[0-9;]*m/g, '');
90
+
91
+ const c1Padded = ' ' + col1 + ' '.repeat(Math.max(0, colWidth - c1Plain.length - 2));
92
+ const c2Padded = col2 + ' '.repeat(Math.max(0, colWidth - c2Plain.length));
93
+ const c3Padded = col3 + ' '.repeat(Math.max(0, W - colWidth * 2 - c3Plain.length));
94
+
95
+ console.log(chalk.cyan('║') + c1Padded + c2Padded + c3Padded + chalk.cyan('║'));
91
96
  };
92
97
 
93
- menuRow(chalk.cyan('[1] VIEW ACCOUNTS'), chalk.cyan('[2] VIEW STATS'));
94
- menuRow(chalk.cyan('[+] ADD PROP-ACCOUNT'), chalk.magenta('[A] ALGO TRADING'));
95
- menuRow(chalk.magenta('[I] AI AGENT'), chalk.yellow('[U] UPDATE HQX'));
96
- menuRow(chalk.red('[X] DISCONNECT'), '');
98
+ const centerLine = (content) => {
99
+ const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
100
+ const padding = W - plainLen;
101
+ const leftPad = Math.floor(padding / 2);
102
+ console.log(chalk.cyan('║') + ' '.repeat(leftPad) + content + ' '.repeat(padding - leftPad) + chalk.cyan('║'));
103
+ };
104
+
105
+ menuRow3(chalk.cyan('[1] VIEW ACCOUNTS'), chalk.cyan('[2] VIEW STATS'), chalk.cyan('[+] ADD ACCOUNT'));
106
+ menuRow3(chalk.magenta('[A] ALGO TRADING'), chalk.magenta('[I] AI AGENT'), chalk.yellow('[U] UPDATE HQX'));
107
+
108
+ // Separator and disconnect button centered
109
+ console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
110
+ centerLine(chalk.red('[X] DISCONNECT'));
97
111
 
98
112
  console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
99
113
 
@@ -214,11 +214,33 @@ const TOKEN_SOURCES = {
214
214
  name: 'CLAUDE CLI',
215
215
  icon: '🤖',
216
216
  paths: {
217
- darwin: [path.join(homeDir, '.claude')],
218
- linux: [path.join(homeDir, '.claude')],
219
- win32: [path.join(homeDir, '.claude')]
217
+ darwin: [
218
+ path.join(homeDir, '.claude'),
219
+ path.join(homeDir, '.config', 'claude'),
220
+ path.join(getAppDataDir(), 'Claude')
221
+ ],
222
+ linux: [
223
+ path.join(homeDir, '.claude'),
224
+ path.join(homeDir, '.config', 'claude')
225
+ ],
226
+ win32: [
227
+ path.join(homeDir, '.claude'),
228
+ path.join(getAppDataDir(), 'Claude')
229
+ ]
220
230
  },
221
- configFiles: ['.credentials.json', 'credentials.json', 'config.json', '.credentials', 'settings.json', 'settings.local.json', 'auth.json']
231
+ configFiles: ['.credentials.json', 'credentials.json', 'config.json', '.credentials', 'settings.json', 'settings.local.json', 'auth.json', 'claude.json']
232
+ },
233
+
234
+ // Claude config in home directory
235
+ claudeHome: {
236
+ name: 'CLAUDE CONFIG',
237
+ icon: '🤖',
238
+ paths: {
239
+ darwin: [homeDir],
240
+ linux: [homeDir],
241
+ win32: [homeDir]
242
+ },
243
+ configFiles: ['.claude.json', '.clauderc', '.claude_credentials']
222
244
  },
223
245
 
224
246
  opencode: {
@@ -496,6 +518,383 @@ const getFileModTime = (filePath) => {
496
518
  }
497
519
  };
498
520
 
521
+ /**
522
+ * Known credential entries for AI providers (used by all OS)
523
+ * Organized by IDE/App
524
+ */
525
+ const CREDENTIAL_ENTRIES = [
526
+ // VS Code
527
+ { service: 'Claude Code-credentials', provider: 'anthropic', name: 'CLAUDE CODE (VS CODE)', ide: 'vscode' },
528
+ { service: 'vscode.anthropic-credentials', provider: 'anthropic', name: 'VS CODE ANTHROPIC', ide: 'vscode' },
529
+ { service: 'vscode-openai', provider: 'openai', name: 'VS CODE OPENAI', ide: 'vscode' },
530
+ { service: 'copilot-credentials', provider: 'openai', name: 'GITHUB COPILOT', ide: 'vscode' },
531
+
532
+ // VS Code Insiders
533
+ { service: 'Claude Code-credentials-insiders', provider: 'anthropic', name: 'CLAUDE CODE (INSIDERS)', ide: 'vscode-insiders' },
534
+
535
+ // Cursor
536
+ { service: 'Cursor-credentials', provider: 'anthropic', name: 'CURSOR (CLAUDE)', ide: 'cursor' },
537
+ { service: 'cursor.anthropic-credentials', provider: 'anthropic', name: 'CURSOR ANTHROPIC', ide: 'cursor' },
538
+ { service: 'cursor.openai-credentials', provider: 'openai', name: 'CURSOR OPENAI', ide: 'cursor' },
539
+
540
+ // Windsurf
541
+ { service: 'Windsurf-credentials', provider: 'anthropic', name: 'WINDSURF (CLAUDE)', ide: 'windsurf' },
542
+ { service: 'windsurf.anthropic-credentials', provider: 'anthropic', name: 'WINDSURF ANTHROPIC', ide: 'windsurf' },
543
+
544
+ // Zed
545
+ { service: 'Zed-credentials', provider: 'anthropic', name: 'ZED (CLAUDE)', ide: 'zed' },
546
+ { service: 'zed.anthropic-credentials', provider: 'anthropic', name: 'ZED ANTHROPIC', ide: 'zed' },
547
+ { service: 'zed.openai-credentials', provider: 'openai', name: 'ZED OPENAI', ide: 'zed' },
548
+
549
+ // Claude CLI / App
550
+ { service: 'Claude Safe Storage', provider: 'anthropic', name: 'CLAUDE CLI', ide: 'claude-cli' },
551
+ { service: 'claude-cli-credentials', provider: 'anthropic', name: 'CLAUDE CLI', ide: 'claude-cli' },
552
+
553
+ // Continue.dev
554
+ { service: 'Continue-credentials', provider: 'anthropic', name: 'CONTINUE.DEV', ide: 'continue' },
555
+ { service: 'continue.anthropic-credentials', provider: 'anthropic', name: 'CONTINUE ANTHROPIC', ide: 'continue' },
556
+ { service: 'continue.openai-credentials', provider: 'openai', name: 'CONTINUE OPENAI', ide: 'continue' },
557
+
558
+ // Cline
559
+ { service: 'Cline-credentials', provider: 'anthropic', name: 'CLINE', ide: 'cline' },
560
+ { service: 'saoudrizwan.claude-dev-credentials', provider: 'anthropic', name: 'CLINE (CLAUDE DEV)', ide: 'cline' },
561
+
562
+ // OpenCode
563
+ { service: 'OpenCode-credentials', provider: 'anthropic', name: 'OPENCODE', ide: 'opencode' },
564
+ { service: 'opencode.anthropic-credentials', provider: 'anthropic', name: 'OPENCODE ANTHROPIC', ide: 'opencode' },
565
+
566
+ // Aider
567
+ { service: 'Aider-credentials', provider: 'anthropic', name: 'AIDER', ide: 'aider' },
568
+ { service: 'aider.anthropic-credentials', provider: 'anthropic', name: 'AIDER ANTHROPIC', ide: 'aider' },
569
+ { service: 'aider.openai-credentials', provider: 'openai', name: 'AIDER OPENAI', ide: 'aider' },
570
+
571
+ // Generic OpenAI
572
+ { service: 'openai-credentials', provider: 'openai', name: 'OPENAI', ide: 'generic' },
573
+
574
+ // Generic OpenRouter
575
+ { service: 'openrouter-credentials', provider: 'openrouter', name: 'OPENROUTER', ide: 'generic' },
576
+ ];
577
+
578
+ /**
579
+ * Parse credential JSON and extract tokens
580
+ */
581
+ const parseCredentialJson = (output, entry) => {
582
+ const results = [];
583
+
584
+ try {
585
+ const data = JSON.parse(output);
586
+
587
+ // Extract Claude OAuth access token
588
+ if (data.claudeAiOauth?.accessToken) {
589
+ results.push({
590
+ source: `SECURE STORAGE - ${entry.name}`,
591
+ sourceId: 'secureStorage',
592
+ icon: '🔐',
593
+ type: data.claudeAiOauth.subscriptionType === 'max' ? 'session' : 'api_key',
594
+ provider: entry.provider,
595
+ providerName: PROVIDER_PATTERNS[entry.provider]?.displayName || entry.provider.toUpperCase(),
596
+ token: data.claudeAiOauth.accessToken,
597
+ refreshToken: data.claudeAiOauth.refreshToken,
598
+ expiresAt: data.claudeAiOauth.expiresAt,
599
+ subscriptionType: data.claudeAiOauth.subscriptionType,
600
+ lastUsed: new Date()
601
+ });
602
+ }
603
+
604
+ // Extract OpenAI token
605
+ if (data.accessToken && entry.provider === 'openai') {
606
+ results.push({
607
+ source: `SECURE STORAGE - ${entry.name}`,
608
+ sourceId: 'secureStorage',
609
+ icon: '🔐',
610
+ type: 'session',
611
+ provider: entry.provider,
612
+ providerName: PROVIDER_PATTERNS[entry.provider]?.displayName || entry.provider.toUpperCase(),
613
+ token: data.accessToken,
614
+ lastUsed: new Date()
615
+ });
616
+ }
617
+
618
+ // Generic API key in JSON
619
+ if (data.apiKey) {
620
+ results.push({
621
+ source: `SECURE STORAGE - ${entry.name}`,
622
+ sourceId: 'secureStorage',
623
+ icon: '🔐',
624
+ type: 'api_key',
625
+ provider: entry.provider,
626
+ providerName: PROVIDER_PATTERNS[entry.provider]?.displayName || entry.provider.toUpperCase(),
627
+ token: data.apiKey,
628
+ lastUsed: new Date()
629
+ });
630
+ }
631
+ } catch {
632
+ // Not JSON, treat as raw token
633
+ if (output.length > 20) {
634
+ for (const [providerId, provider] of Object.entries(PROVIDER_PATTERNS)) {
635
+ for (const pattern of provider.keyPatterns) {
636
+ pattern.lastIndex = 0;
637
+ if (pattern.test(output)) {
638
+ results.push({
639
+ source: `SECURE STORAGE - ${entry.name}`,
640
+ sourceId: 'secureStorage',
641
+ icon: '🔐',
642
+ type: 'api_key',
643
+ provider: providerId,
644
+ providerName: provider.displayName,
645
+ token: output,
646
+ lastUsed: new Date()
647
+ });
648
+ break;
649
+ }
650
+ }
651
+ }
652
+ }
653
+ }
654
+
655
+ return results;
656
+ };
657
+
658
+ /**
659
+ * Read tokens from macOS Keychain
660
+ */
661
+ const readMacOSKeychain = () => {
662
+ if (platform !== 'darwin') return [];
663
+
664
+ const results = [];
665
+ const { execSync } = require('child_process');
666
+
667
+ for (const entry of CREDENTIAL_ENTRIES) {
668
+ try {
669
+ const output = execSync(
670
+ `security find-generic-password -s "${entry.service}" -w 2>/dev/null`,
671
+ { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }
672
+ ).trim();
673
+
674
+ if (output) {
675
+ results.push(...parseCredentialJson(output, entry));
676
+ }
677
+ } catch {
678
+ // Entry not found or access denied
679
+ }
680
+ }
681
+
682
+ return results;
683
+ };
684
+
685
+ /**
686
+ * Read tokens from Linux Secret Service (libsecret/gnome-keyring)
687
+ */
688
+ const readLinuxSecretService = () => {
689
+ if (platform !== 'linux') return [];
690
+
691
+ const results = [];
692
+ const { execSync } = require('child_process');
693
+
694
+ // Check if secret-tool is available
695
+ try {
696
+ execSync('which secret-tool', { stdio: 'pipe' });
697
+ } catch {
698
+ return results; // secret-tool not available
699
+ }
700
+
701
+ for (const entry of CREDENTIAL_ENTRIES) {
702
+ try {
703
+ // Try different attribute combinations
704
+ const commands = [
705
+ `secret-tool lookup service "${entry.service}" 2>/dev/null`,
706
+ `secret-tool lookup application "${entry.service}" 2>/dev/null`,
707
+ `secret-tool lookup xdg:schema "org.freedesktop.Secret.Generic" service "${entry.service}" 2>/dev/null`,
708
+ ];
709
+
710
+ for (const cmd of commands) {
711
+ try {
712
+ const output = execSync(cmd, {
713
+ encoding: 'utf8',
714
+ timeout: 5000,
715
+ stdio: ['pipe', 'pipe', 'pipe']
716
+ }).trim();
717
+
718
+ if (output) {
719
+ results.push(...parseCredentialJson(output, entry));
720
+ break; // Found it, no need to try other commands
721
+ }
722
+ } catch {
723
+ // Command failed, try next
724
+ }
725
+ }
726
+ } catch {
727
+ // Entry not found
728
+ }
729
+ }
730
+
731
+ // Also check VS Code's pass-based storage on Linux
732
+ const passEntries = [
733
+ 'vscode/Claude Code-credentials',
734
+ 'vscode/Cursor-credentials',
735
+ 'vscode/openai-credentials',
736
+ ];
737
+
738
+ for (const passPath of passEntries) {
739
+ try {
740
+ const output = execSync(`pass show "${passPath}" 2>/dev/null`, {
741
+ encoding: 'utf8',
742
+ timeout: 5000,
743
+ stdio: ['pipe', 'pipe', 'pipe']
744
+ }).trim();
745
+
746
+ if (output) {
747
+ const entry = CREDENTIAL_ENTRIES.find(e => passPath.includes(e.service)) ||
748
+ { service: passPath, provider: 'unknown', name: passPath };
749
+ results.push(...parseCredentialJson(output, entry));
750
+ }
751
+ } catch {
752
+ // pass entry not found
753
+ }
754
+ }
755
+
756
+ return results;
757
+ };
758
+
759
+ /**
760
+ * Read tokens from Windows Credential Manager
761
+ */
762
+ const readWindowsCredentialManager = () => {
763
+ if (platform !== 'win32') return [];
764
+
765
+ const results = [];
766
+ const { execSync } = require('child_process');
767
+
768
+ for (const entry of CREDENTIAL_ENTRIES) {
769
+ try {
770
+ // Use PowerShell to read from Credential Manager
771
+ const psCommand = `
772
+ $cred = Get-StoredCredential -Target "${entry.service}" -ErrorAction SilentlyContinue
773
+ if ($cred) {
774
+ [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
775
+ [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($cred.Password)
776
+ )
777
+ }
778
+ `;
779
+
780
+ const output = execSync(`powershell -Command "${psCommand.replace(/\n/g, ' ')}"`, {
781
+ encoding: 'utf8',
782
+ timeout: 10000,
783
+ stdio: ['pipe', 'pipe', 'pipe']
784
+ }).trim();
785
+
786
+ if (output) {
787
+ results.push(...parseCredentialJson(output, entry));
788
+ }
789
+ } catch {
790
+ // Try cmdkey as fallback (less reliable for getting password)
791
+ try {
792
+ // cmdkey can list but not retrieve passwords directly
793
+ // VS Code on Windows often uses DPAPI-encrypted files instead
794
+ } catch {
795
+ // Entry not found
796
+ }
797
+ }
798
+ }
799
+
800
+ // Also check VS Code's DPAPI-encrypted storage on Windows
801
+ const vscodeCredPath = path.join(
802
+ process.env.APPDATA || '',
803
+ 'Code',
804
+ 'User',
805
+ 'globalStorage',
806
+ 'state.vscdb'
807
+ );
808
+
809
+ if (pathExists(vscodeCredPath)) {
810
+ const dbResults = readVSCodeStateDb(vscodeCredPath);
811
+ results.push(...dbResults);
812
+ }
813
+
814
+ return results;
815
+ };
816
+
817
+ /**
818
+ * Read tokens from OS secure storage (unified function)
819
+ */
820
+ const readSecureStorage = () => {
821
+ switch (platform) {
822
+ case 'darwin':
823
+ return readMacOSKeychain();
824
+ case 'linux':
825
+ return readLinuxSecretService();
826
+ case 'win32':
827
+ return readWindowsCredentialManager();
828
+ default:
829
+ return [];
830
+ }
831
+ };
832
+
833
+ /**
834
+ * Try to read VS Code SQLite state database
835
+ */
836
+ const readVSCodeStateDb = (dbPath) => {
837
+ const results = [];
838
+
839
+ try {
840
+ // Try using sqlite3 CLI if available
841
+ const { execSync } = require('child_process');
842
+
843
+ // Check if sqlite3 is available
844
+ try {
845
+ execSync('which sqlite3', { stdio: 'pipe' });
846
+ } catch {
847
+ return results; // sqlite3 not available
848
+ }
849
+
850
+ // Query for keys that might contain tokens
851
+ const queries = [
852
+ "SELECT key, value FROM ItemTable WHERE key LIKE '%apiKey%' OR key LIKE '%token%' OR key LIKE '%credential%' OR key LIKE '%session%'",
853
+ "SELECT key, value FROM ItemTable WHERE key LIKE '%anthropic%' OR key LIKE '%openai%' OR key LIKE '%claude%'"
854
+ ];
855
+
856
+ for (const query of queries) {
857
+ try {
858
+ const output = execSync(`sqlite3 "${dbPath}" "${query}"`, {
859
+ encoding: 'utf8',
860
+ timeout: 5000,
861
+ stdio: ['pipe', 'pipe', 'pipe']
862
+ });
863
+
864
+ if (output) {
865
+ const lines = output.trim().split('\n');
866
+ for (const line of lines) {
867
+ const [key, value] = line.split('|');
868
+ if (value && value.length > 20) {
869
+ // Try to identify provider
870
+ for (const [providerId, provider] of Object.entries(PROVIDER_PATTERNS)) {
871
+ for (const pattern of provider.keyPatterns) {
872
+ pattern.lastIndex = 0;
873
+ if (pattern.test(value)) {
874
+ results.push({
875
+ type: 'api_key',
876
+ provider: providerId,
877
+ providerName: provider.displayName,
878
+ token: value,
879
+ keyPath: key
880
+ });
881
+ }
882
+ }
883
+ }
884
+ }
885
+ }
886
+ }
887
+ } catch {
888
+ // Query failed, continue
889
+ }
890
+ }
891
+ } catch {
892
+ // SQLite read failed
893
+ }
894
+
895
+ return results;
896
+ };
897
+
499
898
  /**
500
899
  * List files in directory (recursive optional)
501
900
  */
@@ -848,10 +1247,18 @@ const scanSource = (sourceId) => {
848
1247
  const scanAllSources = () => {
849
1248
  const allResults = [];
850
1249
 
851
- // First, scan environment variables (highest priority)
1250
+ // First, scan OS secure storage (Keychain, libsecret, Credential Manager)
1251
+ // This is the most reliable source for IDE tokens
1252
+ try {
1253
+ allResults.push(...readSecureStorage());
1254
+ } catch (err) {
1255
+ // Silent fail
1256
+ }
1257
+
1258
+ // Then scan environment variables
852
1259
  allResults.push(...scanEnvironmentVariables());
853
1260
 
854
- // Then scan all tool sources
1261
+ // Then scan all tool sources (config files)
855
1262
  for (const sourceId of Object.keys(TOKEN_SOURCES)) {
856
1263
  if (sourceId === 'envVars') continue; // Already scanned
857
1264
 
@@ -969,10 +1376,12 @@ const getSystemInfo = () => {
969
1376
  module.exports = {
970
1377
  TOKEN_SOURCES,
971
1378
  PROVIDER_PATTERNS,
1379
+ CREDENTIAL_ENTRIES,
972
1380
  scanAllSources,
973
1381
  scanForProvider,
974
1382
  scanSource,
975
1383
  scanEnvironmentVariables,
1384
+ readSecureStorage,
976
1385
  formatResults,
977
1386
  timeAgo,
978
1387
  hasExistingTokens,
package/src/ui/box.js CHANGED
@@ -55,7 +55,7 @@ const padText = (text, width) => {
55
55
  };
56
56
 
57
57
  /**
58
- * Draw box header with title
58
+ * Draw box header with title (starts new box with ╔)
59
59
  */
60
60
  const drawBoxHeader = (title, width) => {
61
61
  const innerWidth = width - 2;
@@ -64,6 +64,16 @@ const drawBoxHeader = (title, width) => {
64
64
  console.log(chalk.cyan('\u2560' + '\u2550'.repeat(innerWidth) + '\u2563'));
65
65
  };
66
66
 
67
+ /**
68
+ * Draw box header that continues from previous box (uses ╠ instead of ╔)
69
+ */
70
+ const drawBoxHeaderContinue = (title, width) => {
71
+ const innerWidth = width - 2;
72
+ console.log(chalk.cyan('\u2560' + '\u2550'.repeat(innerWidth) + '\u2563'));
73
+ console.log(chalk.cyan('\u2551') + chalk.cyan.bold(centerText(title, innerWidth)) + chalk.cyan('\u2551'));
74
+ console.log(chalk.cyan('\u2560' + '\u2550'.repeat(innerWidth) + '\u2563'));
75
+ };
76
+
67
77
  /**
68
78
  * Draw box footer
69
79
  */
@@ -104,6 +114,7 @@ module.exports = {
104
114
  centerText,
105
115
  padText,
106
116
  drawBoxHeader,
117
+ drawBoxHeaderContinue,
107
118
  drawBoxFooter,
108
119
  drawBoxRow,
109
120
  drawBoxSeparator,
package/src/ui/index.js CHANGED
@@ -10,6 +10,7 @@ const {
10
10
  centerText,
11
11
  padText,
12
12
  drawBoxHeader,
13
+ drawBoxHeaderContinue,
13
14
  drawBoxFooter,
14
15
  drawBoxRow,
15
16
  drawBoxSeparator,
@@ -105,6 +106,7 @@ module.exports = {
105
106
  centerText,
106
107
  padText,
107
108
  drawBoxHeader,
109
+ drawBoxHeaderContinue,
108
110
  drawBoxFooter,
109
111
  drawBoxRow,
110
112
  drawBoxSeparator,