httpcat-cli 0.0.26 → 0.0.27-rc.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/.github/workflows/README.md +19 -2
- package/.github/workflows/ci.yml +31 -20
- package/.github/workflows/homebrew-tap.yml +1 -1
- package/.github/workflows/rc-publish.yml +169 -0
- package/.github/workflows/release.yml +223 -71
- package/README.md +94 -76
- package/bun.lock +2933 -0
- package/dist/commands/account.d.ts.map +1 -1
- package/dist/commands/account.js +14 -7
- package/dist/commands/account.js.map +1 -1
- package/dist/commands/balances.d.ts.map +1 -0
- package/dist/commands/balances.js +171 -0
- package/dist/commands/balances.js.map +1 -0
- package/dist/commands/buy.d.ts.map +1 -1
- package/dist/commands/buy.js +739 -32
- package/dist/commands/buy.js.map +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +467 -906
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/claim.d.ts.map +1 -0
- package/dist/commands/claim.js +65 -0
- package/dist/commands/claim.js.map +1 -0
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +0 -1
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/info.d.ts.map +1 -1
- package/dist/commands/info.js +128 -26
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +30 -23
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/positions.d.ts.map +1 -1
- package/dist/commands/positions.js +178 -105
- package/dist/commands/positions.js.map +1 -1
- package/dist/commands/sell.d.ts.map +1 -1
- package/dist/commands/sell.js +713 -24
- package/dist/commands/sell.js.map +1 -1
- package/dist/index.js +315 -99
- package/dist/index.js.map +1 -1
- package/dist/interactive/shell.d.ts.map +1 -1
- package/dist/interactive/shell.js +328 -179
- package/dist/interactive/shell.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +8 -8
- package/dist/mcp/tools.js.map +1 -1
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +66 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/formatting.d.ts.map +1 -1
- package/dist/utils/formatting.js +3 -5
- package/dist/utils/formatting.js.map +1 -1
- package/dist/utils/token-resolver.d.ts.map +1 -1
- package/dist/utils/token-resolver.js +71 -40
- package/dist/utils/token-resolver.js.map +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +4 -3
- package/dist/utils/validation.js.map +1 -1
- package/jest.config.js +1 -1
- package/package.json +19 -13
- package/.claude/settings.local.json +0 -41
- package/dist/commands/balance.d.ts.map +0 -1
- package/dist/commands/balance.js +0 -112
- package/dist/commands/balance.js.map +0 -1
- package/homebrew-httpcat/Formula/httpcat.rb +0 -18
- package/homebrew-httpcat/README.md +0 -31
- package/homebrew-httpcat/homebrew-httpcat/Formula/httpcat.rb +0 -18
- package/homebrew-httpcat/homebrew-httpcat/README.md +0 -31
package/dist/commands/chat.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createInterface } from "readline";
|
|
2
1
|
import WebSocket from "ws";
|
|
3
2
|
import chalk from "chalk";
|
|
4
3
|
// @ts-ignore - neo-blessed doesn't have types, but @types/blessed provides compatible types
|
|
@@ -133,7 +132,7 @@ function formatMessageText(msg, isOwn = false) {
|
|
|
133
132
|
const authorShort = msg.authorShort || formatAddress(msg.author, 6);
|
|
134
133
|
return `${chalk.dim(`[${timeStr}]`)} ${authorColor(authorShort)}: ${messageColor(msg.message)}`;
|
|
135
134
|
}
|
|
136
|
-
function displayMessage(msg, isOwn = false, isPending = false, messageLogBox
|
|
135
|
+
function displayMessage(msg, isOwn = false, isPending = false, messageLogBox) {
|
|
137
136
|
// Skip if we've already displayed this message
|
|
138
137
|
if (displayedMessageIds.has(msg.messageId)) {
|
|
139
138
|
return;
|
|
@@ -145,16 +144,8 @@ function displayMessage(msg, isOwn = false, isPending = false, messageLogBox, rl
|
|
|
145
144
|
messageLogBox.log(messageText);
|
|
146
145
|
messageLogBox.setScrollPerc(100); // Auto-scroll to bottom
|
|
147
146
|
}
|
|
148
|
-
else if (rl) {
|
|
149
|
-
// Fallback to readline approach (for JSON mode or edge cases)
|
|
150
|
-
process.stdout.write("\r\x1b[K");
|
|
151
|
-
process.stdout.write(messageText);
|
|
152
|
-
process.stdout.write("\n");
|
|
153
|
-
if (rl)
|
|
154
|
-
rl.prompt(true);
|
|
155
|
-
}
|
|
156
147
|
else {
|
|
157
|
-
// Simple console.log when no UI active
|
|
148
|
+
// Simple console.log when no UI active (JSON mode)
|
|
158
149
|
console.log(messageText);
|
|
159
150
|
}
|
|
160
151
|
}
|
|
@@ -180,7 +171,9 @@ function formatTimeRemaining(expiresAt) {
|
|
|
180
171
|
function formatBuyResultCompact(result) {
|
|
181
172
|
const graduationStatus = result.graduationReached
|
|
182
173
|
? "✅ GRADUATED!"
|
|
183
|
-
:
|
|
174
|
+
: result.graduationProgress !== undefined
|
|
175
|
+
? `${result.graduationProgress.toFixed(2)}%`
|
|
176
|
+
: "N/A";
|
|
184
177
|
return chalk.green("✅ Buy successful! ") +
|
|
185
178
|
`Received ${chalk.cyan(formatTokenAmount(result.tokensReceived))} tokens, ` +
|
|
186
179
|
`spent ${chalk.yellow(formatCurrency(result.amountSpent))}, ` +
|
|
@@ -191,11 +184,14 @@ function formatBuyResultCompact(result) {
|
|
|
191
184
|
* Format sell result compactly for chat log
|
|
192
185
|
*/
|
|
193
186
|
function formatSellResultCompact(result) {
|
|
187
|
+
const graduationStatus = result.graduationProgress !== undefined
|
|
188
|
+
? `${result.graduationProgress.toFixed(2)}%`
|
|
189
|
+
: "N/A";
|
|
194
190
|
return chalk.green("✅ Sell successful! ") +
|
|
195
191
|
`Sold ${chalk.cyan(formatTokenAmount(result.tokensSold))} tokens, ` +
|
|
196
192
|
`received ${chalk.yellow(formatCurrency(result.usdcReceived))}, ` +
|
|
197
193
|
`new price ${chalk.cyan(formatCurrency(result.newPrice))}, ` +
|
|
198
|
-
`graduation ${chalk.magenta(
|
|
194
|
+
`graduation ${chalk.magenta(graduationStatus)}`;
|
|
199
195
|
}
|
|
200
196
|
function updateHeaderBox(headerBox, leaseInfo, tokenName, isConnected = false) {
|
|
201
197
|
const title = tokenName
|
|
@@ -265,7 +261,6 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
|
|
|
265
261
|
let userAddress;
|
|
266
262
|
let ws = null;
|
|
267
263
|
let wsUrl = null; // Store websocket URL for reconnection
|
|
268
|
-
let rl = null;
|
|
269
264
|
let leaseCheckInterval = null;
|
|
270
265
|
let headerUpdateInterval = null;
|
|
271
266
|
let isExiting = false;
|
|
@@ -401,15 +396,10 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
|
|
|
401
396
|
inputBox.clearValue();
|
|
402
397
|
screen?.render();
|
|
403
398
|
}
|
|
404
|
-
else if (rl) {
|
|
405
|
-
// Fallback to readline
|
|
406
|
-
process.stdout.write("\n");
|
|
407
|
-
process.stdout.write("\r\x1b[K");
|
|
408
|
-
}
|
|
409
399
|
// Display the message (only if not already displayed)
|
|
410
400
|
// In JSON mode, displayMessage should never be called, but add explicit check
|
|
411
401
|
if (!displayedMessageIds.has(msg.messageId)) {
|
|
412
|
-
displayMessage(msg, isOwn, false, messageLogBox || undefined
|
|
402
|
+
displayMessage(msg, isOwn, false, messageLogBox || undefined);
|
|
413
403
|
}
|
|
414
404
|
// Re-enable input - clear the input line completely
|
|
415
405
|
isSending = false;
|
|
@@ -421,17 +411,12 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
|
|
|
421
411
|
inputBox.focus();
|
|
422
412
|
screen.render();
|
|
423
413
|
}
|
|
424
|
-
else if (rl) {
|
|
425
|
-
// Fallback to readline
|
|
426
|
-
process.stdout.write("\r\x1b[K");
|
|
427
|
-
rl.prompt();
|
|
428
|
-
}
|
|
429
414
|
}
|
|
430
415
|
else {
|
|
431
416
|
// Not our message, just display it normally
|
|
432
417
|
// In JSON mode, displayMessage should never be called, but add explicit check
|
|
433
418
|
if (!displayedMessageIds.has(msg.messageId)) {
|
|
434
|
-
displayMessage(msg, isOwn, false, messageLogBox || undefined
|
|
419
|
+
displayMessage(msg, isOwn, false, messageLogBox || undefined);
|
|
435
420
|
}
|
|
436
421
|
}
|
|
437
422
|
}
|
|
@@ -448,14 +433,6 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
|
|
|
448
433
|
messageLogBox.setScrollPerc(100);
|
|
449
434
|
screen?.render();
|
|
450
435
|
}
|
|
451
|
-
else if (rl) {
|
|
452
|
-
process.stdout.write("\r\x1b[K");
|
|
453
|
-
console.log(chalk.yellow("⏱️ Your lease has expired. Type /renew to continue chatting."));
|
|
454
|
-
rl.prompt(true);
|
|
455
|
-
}
|
|
456
|
-
else {
|
|
457
|
-
console.log(chalk.yellow("⏱️ Your lease has expired. Type /renew to continue chatting."));
|
|
458
|
-
}
|
|
459
436
|
}
|
|
460
437
|
leaseInfo = null;
|
|
461
438
|
if (headerBox) {
|
|
@@ -476,14 +453,6 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
|
|
|
476
453
|
messageLogBox.setScrollPerc(100);
|
|
477
454
|
screen?.render();
|
|
478
455
|
}
|
|
479
|
-
else if (rl) {
|
|
480
|
-
process.stdout.write("\r\x1b[K");
|
|
481
|
-
console.log(chalk.red(`❌ Error: ${event.error || "Unknown error"}`));
|
|
482
|
-
rl.prompt(true);
|
|
483
|
-
}
|
|
484
|
-
else {
|
|
485
|
-
console.log(chalk.red(`❌ Error: ${event.error || "Unknown error"}`));
|
|
486
|
-
}
|
|
487
456
|
}
|
|
488
457
|
}
|
|
489
458
|
}
|
|
@@ -508,14 +477,6 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
|
|
|
508
477
|
messageLogBox.setScrollPerc(100);
|
|
509
478
|
screen?.render();
|
|
510
479
|
}
|
|
511
|
-
else if (rl) {
|
|
512
|
-
process.stdout.write("\r\x1b[K");
|
|
513
|
-
console.log(chalk.red(`❌ WebSocket error: ${errorMsg}`));
|
|
514
|
-
rl.prompt(true);
|
|
515
|
-
}
|
|
516
|
-
else {
|
|
517
|
-
console.log(chalk.red(`❌ WebSocket error: ${errorMsg}`));
|
|
518
|
-
}
|
|
519
480
|
}
|
|
520
481
|
});
|
|
521
482
|
websocket.on("close", (code, reason) => {
|
|
@@ -557,14 +518,6 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
|
|
|
557
518
|
messageLogBox.setScrollPerc(100);
|
|
558
519
|
screen?.render();
|
|
559
520
|
}
|
|
560
|
-
else if (rl) {
|
|
561
|
-
process.stdout.write("\r\x1b[K");
|
|
562
|
-
console.log(chalk.yellow(message));
|
|
563
|
-
rl.prompt(true);
|
|
564
|
-
}
|
|
565
|
-
else {
|
|
566
|
-
console.log(chalk.yellow(message));
|
|
567
|
-
}
|
|
568
521
|
}
|
|
569
522
|
});
|
|
570
523
|
};
|
|
@@ -586,9 +539,6 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
|
|
|
586
539
|
messageLogBox.setScrollPerc(100);
|
|
587
540
|
screen?.render();
|
|
588
541
|
}
|
|
589
|
-
else if (rl) {
|
|
590
|
-
console.log(chalk.yellow(`🔄 Retrying WebSocket connection (attempt ${retryCount + 1}/${maxRetries})...`));
|
|
591
|
-
}
|
|
592
542
|
}
|
|
593
543
|
await new Promise((resolve) => setTimeout(resolve, backoffDelay));
|
|
594
544
|
}
|
|
@@ -672,13 +622,6 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
|
|
|
672
622
|
messageLogBox.setScrollPerc(100);
|
|
673
623
|
screen?.render();
|
|
674
624
|
}
|
|
675
|
-
else if (rl) {
|
|
676
|
-
console.log(chalk.red(`❌ ${finalError}`));
|
|
677
|
-
console.log(chalk.yellow("💡 Try typing /renew to get a fresh lease"));
|
|
678
|
-
}
|
|
679
|
-
else {
|
|
680
|
-
console.log(chalk.red(`❌ ${finalError}`));
|
|
681
|
-
}
|
|
682
625
|
}
|
|
683
626
|
throw new Error(finalError);
|
|
684
627
|
}
|
|
@@ -702,9 +645,6 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
|
|
|
702
645
|
messageLogBox.setScrollPerc(100);
|
|
703
646
|
screen?.render();
|
|
704
647
|
}
|
|
705
|
-
else if (rl) {
|
|
706
|
-
console.log(chalk.yellow("⏱️ Your lease has expired. Type /renew to continue chatting."));
|
|
707
|
-
}
|
|
708
648
|
leaseInfo = null;
|
|
709
649
|
if (headerBox) {
|
|
710
650
|
updateHeaderBox(headerBox, null, tokenIdentifier, true);
|
|
@@ -712,157 +652,90 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
|
|
|
712
652
|
}
|
|
713
653
|
}
|
|
714
654
|
}, 30000);
|
|
715
|
-
// Set up blessed UI
|
|
655
|
+
// Set up blessed UI (only in interactive mode)
|
|
716
656
|
if (!jsonMode) {
|
|
717
|
-
|
|
718
|
-
// Check if terminal supports blessed (basic compatibility check)
|
|
719
|
-
// If not a TTY, silently fall back to readline
|
|
657
|
+
// Check if terminal supports blessed
|
|
720
658
|
if (!process.stdout.isTTY) {
|
|
721
|
-
|
|
722
|
-
}
|
|
723
|
-
// Helper function to set up readline interface
|
|
724
|
-
const setupReadline = () => {
|
|
725
|
-
rl = createInterface({
|
|
726
|
-
input: process.stdin,
|
|
727
|
-
output: process.stdout,
|
|
728
|
-
prompt: "",
|
|
729
|
-
});
|
|
730
|
-
// Display header information via console
|
|
731
|
-
console.log();
|
|
732
|
-
console.log(chalk.cyan("═".repeat(80)));
|
|
733
|
-
console.log(chalk.cyan("💬 httpcat Chat Stream"));
|
|
734
|
-
console.log(chalk.cyan("═".repeat(80)));
|
|
735
|
-
console.log();
|
|
736
|
-
console.log(chalk.green("✅ Connected to chat stream"));
|
|
737
|
-
console.log();
|
|
738
|
-
console.log(chalk.dim("💰 Entry fee: $0.01 USDC (10 min lease)"));
|
|
739
|
-
console.log(chalk.dim("💬 Per message: $0.01 USDC"));
|
|
740
|
-
if (leaseInfo) {
|
|
741
|
-
const timeRemaining = formatTimeRemaining(leaseInfo.leaseExpiresAt);
|
|
742
|
-
console.log(chalk.dim(`⏱️ Lease expires in: ${timeRemaining}`));
|
|
743
|
-
}
|
|
744
|
-
console.log();
|
|
745
|
-
console.log(chalk.dim("💡 Type your message and press Enter to send"));
|
|
746
|
-
console.log(chalk.dim("💡 Type /exit or Ctrl+C to quit"));
|
|
747
|
-
console.log(chalk.dim("💡 Type /renew to renew your lease"));
|
|
748
|
-
if (tokenIdentifier) {
|
|
749
|
-
console.log(chalk.dim("💡 Type /buy <amount> to buy tokens"));
|
|
750
|
-
console.log(chalk.dim("💡 Type /sell <amount> to sell tokens"));
|
|
751
|
-
}
|
|
752
|
-
console.log(chalk.cyan("─".repeat(80)));
|
|
753
|
-
console.log();
|
|
754
|
-
};
|
|
755
|
-
// Try to create blessed screen with error handling
|
|
756
|
-
if (useBlessed) {
|
|
757
|
-
try {
|
|
758
|
-
// Create blessed screen and widgets
|
|
759
|
-
screen = blessed.screen({
|
|
760
|
-
smartCSR: true,
|
|
761
|
-
title: "httpcat Chat",
|
|
762
|
-
fullUnicode: true,
|
|
763
|
-
// Add error handling for terminal compatibility
|
|
764
|
-
fastCSR: false, // Disable fast CSR to avoid rendering issues
|
|
765
|
-
});
|
|
766
|
-
// Calculate header height (approximately 10 lines)
|
|
767
|
-
const headerHeight = 10;
|
|
768
|
-
const inputHeight = 3;
|
|
769
|
-
// Create header box (fixed at top)
|
|
770
|
-
headerBox = blessed.box({
|
|
771
|
-
top: 0,
|
|
772
|
-
left: 0,
|
|
773
|
-
width: "100%",
|
|
774
|
-
height: headerHeight,
|
|
775
|
-
content: "",
|
|
776
|
-
tags: false, // Disable tags to avoid rendering issues
|
|
777
|
-
wrap: true,
|
|
778
|
-
scrollable: false,
|
|
779
|
-
alwaysScroll: false,
|
|
780
|
-
padding: {
|
|
781
|
-
left: 1,
|
|
782
|
-
right: 1,
|
|
783
|
-
top: 0,
|
|
784
|
-
bottom: 0,
|
|
785
|
-
},
|
|
786
|
-
style: {
|
|
787
|
-
fg: "white",
|
|
788
|
-
bg: "black",
|
|
789
|
-
},
|
|
790
|
-
});
|
|
791
|
-
// Create message log box (scrollable, middle section)
|
|
792
|
-
messageLogBox = blessed.log({
|
|
793
|
-
top: headerHeight,
|
|
794
|
-
left: 0,
|
|
795
|
-
width: "100%",
|
|
796
|
-
height: `100%-${headerHeight + inputHeight}`,
|
|
797
|
-
tags: true,
|
|
798
|
-
scrollable: true,
|
|
799
|
-
alwaysScroll: true,
|
|
800
|
-
scrollbar: {
|
|
801
|
-
ch: " ",
|
|
802
|
-
inverse: true,
|
|
803
|
-
},
|
|
804
|
-
style: {
|
|
805
|
-
fg: "white",
|
|
806
|
-
bg: "black",
|
|
807
|
-
},
|
|
808
|
-
});
|
|
809
|
-
// Create input box (fixed at bottom)
|
|
810
|
-
inputBox = blessed.textbox({
|
|
811
|
-
bottom: 0,
|
|
812
|
-
left: 0,
|
|
813
|
-
width: "100%",
|
|
814
|
-
height: inputHeight,
|
|
815
|
-
content: "",
|
|
816
|
-
inputOnFocus: true,
|
|
817
|
-
tags: true,
|
|
818
|
-
keys: true,
|
|
819
|
-
style: {
|
|
820
|
-
fg: "cyan",
|
|
821
|
-
bg: "black",
|
|
822
|
-
focus: {
|
|
823
|
-
fg: "white",
|
|
824
|
-
bg: "blue",
|
|
825
|
-
},
|
|
826
|
-
},
|
|
827
|
-
});
|
|
828
|
-
// Append widgets to screen
|
|
829
|
-
screen.append(headerBox);
|
|
830
|
-
screen.append(messageLogBox);
|
|
831
|
-
screen.append(inputBox);
|
|
832
|
-
// Test render to catch early errors
|
|
833
|
-
screen.render();
|
|
834
|
-
// Initial header update
|
|
835
|
-
updateHeaderBox(headerBox, leaseInfo, tokenIdentifier, false);
|
|
836
|
-
screen.render();
|
|
837
|
-
}
|
|
838
|
-
catch (blessedError) {
|
|
839
|
-
// Blessed failed - fallback to readline
|
|
840
|
-
useBlessed = false;
|
|
841
|
-
// Only show error if it's not a TTY issue (which we already handled silently)
|
|
842
|
-
if (process.stdout.isTTY) {
|
|
843
|
-
console.error(chalk.yellow("⚠️ Blessed UI initialization failed, falling back to readline interface"));
|
|
844
|
-
console.error(chalk.dim(` Error: ${blessedError instanceof Error ? blessedError.message : String(blessedError)}`));
|
|
845
|
-
}
|
|
846
|
-
// Clean up any partial blessed setup
|
|
847
|
-
if (screen) {
|
|
848
|
-
try {
|
|
849
|
-
screen.destroy();
|
|
850
|
-
}
|
|
851
|
-
catch {
|
|
852
|
-
// Ignore cleanup errors
|
|
853
|
-
}
|
|
854
|
-
screen = null;
|
|
855
|
-
headerBox = null;
|
|
856
|
-
messageLogBox = null;
|
|
857
|
-
inputBox = null;
|
|
858
|
-
}
|
|
859
|
-
setupReadline();
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
else {
|
|
863
|
-
// Not a TTY or blessed disabled - use readline directly
|
|
864
|
-
setupReadline();
|
|
659
|
+
throw new Error("Chat requires an interactive terminal (TTY). Use --json mode for non-interactive usage.");
|
|
865
660
|
}
|
|
661
|
+
// Create blessed screen and widgets
|
|
662
|
+
screen = blessed.screen({
|
|
663
|
+
smartCSR: true,
|
|
664
|
+
title: "httpcat Chat",
|
|
665
|
+
fullUnicode: true,
|
|
666
|
+
fastCSR: false, // Disable fast CSR to avoid rendering issues
|
|
667
|
+
});
|
|
668
|
+
// Calculate header height (approximately 10 lines)
|
|
669
|
+
const headerHeight = 10;
|
|
670
|
+
const inputHeight = 3;
|
|
671
|
+
// Create header box (fixed at top)
|
|
672
|
+
headerBox = blessed.box({
|
|
673
|
+
top: 0,
|
|
674
|
+
left: 0,
|
|
675
|
+
width: "100%",
|
|
676
|
+
height: headerHeight,
|
|
677
|
+
content: "",
|
|
678
|
+
tags: false, // Disable tags to avoid rendering issues
|
|
679
|
+
wrap: true,
|
|
680
|
+
scrollable: false,
|
|
681
|
+
alwaysScroll: false,
|
|
682
|
+
padding: {
|
|
683
|
+
left: 1,
|
|
684
|
+
right: 1,
|
|
685
|
+
top: 0,
|
|
686
|
+
bottom: 0,
|
|
687
|
+
},
|
|
688
|
+
style: {
|
|
689
|
+
fg: "white",
|
|
690
|
+
bg: "black",
|
|
691
|
+
},
|
|
692
|
+
});
|
|
693
|
+
// Create message log box (scrollable, middle section)
|
|
694
|
+
messageLogBox = blessed.log({
|
|
695
|
+
top: headerHeight,
|
|
696
|
+
left: 0,
|
|
697
|
+
width: "100%",
|
|
698
|
+
height: `100%-${headerHeight + inputHeight}`,
|
|
699
|
+
tags: true,
|
|
700
|
+
scrollable: true,
|
|
701
|
+
alwaysScroll: true,
|
|
702
|
+
scrollbar: {
|
|
703
|
+
ch: " ",
|
|
704
|
+
inverse: true,
|
|
705
|
+
},
|
|
706
|
+
style: {
|
|
707
|
+
fg: "white",
|
|
708
|
+
bg: "black",
|
|
709
|
+
},
|
|
710
|
+
});
|
|
711
|
+
// Create input box (fixed at bottom)
|
|
712
|
+
inputBox = blessed.textbox({
|
|
713
|
+
bottom: 0,
|
|
714
|
+
left: 0,
|
|
715
|
+
width: "100%",
|
|
716
|
+
height: inputHeight,
|
|
717
|
+
content: "",
|
|
718
|
+
inputOnFocus: true,
|
|
719
|
+
tags: true,
|
|
720
|
+
keys: true,
|
|
721
|
+
style: {
|
|
722
|
+
fg: "cyan",
|
|
723
|
+
bg: "black",
|
|
724
|
+
focus: {
|
|
725
|
+
fg: "white",
|
|
726
|
+
bg: "blue",
|
|
727
|
+
},
|
|
728
|
+
},
|
|
729
|
+
});
|
|
730
|
+
// Append widgets to screen
|
|
731
|
+
screen.append(headerBox);
|
|
732
|
+
screen.append(messageLogBox);
|
|
733
|
+
screen.append(inputBox);
|
|
734
|
+
// Initial render
|
|
735
|
+
screen.render();
|
|
736
|
+
// Initial header update
|
|
737
|
+
updateHeaderBox(headerBox, leaseInfo, tokenIdentifier, false);
|
|
738
|
+
screen.render();
|
|
866
739
|
// Display last messages
|
|
867
740
|
if (joinResult.lastMessages.length > 0) {
|
|
868
741
|
const sortedMessages = [...joinResult.lastMessages].sort((a, b) => {
|
|
@@ -874,210 +747,218 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
|
|
|
874
747
|
displayedMessageIds.add(msg.messageId);
|
|
875
748
|
const isOwn = msg.author === userAddress;
|
|
876
749
|
// This is inside !jsonMode block, but add explicit check for safety
|
|
877
|
-
displayMessage(msg, isOwn, false, messageLogBox || undefined
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
// Only set up blessed-specific handlers if using blessed
|
|
881
|
-
if (useBlessed && screen) {
|
|
882
|
-
// Handle terminal resize
|
|
883
|
-
screen.on("resize", () => {
|
|
884
|
-
screen?.render();
|
|
750
|
+
displayMessage(msg, isOwn, false, messageLogBox || undefined);
|
|
885
751
|
});
|
|
886
|
-
// Update header with lease countdown every second
|
|
887
|
-
headerUpdateInterval = setInterval(() => {
|
|
888
|
-
if (leaseInfo && headerBox && screen && !isExiting) {
|
|
889
|
-
updateHeaderBox(headerBox, leaseInfo, tokenIdentifier, true);
|
|
890
|
-
screen.render();
|
|
891
|
-
}
|
|
892
|
-
}, 1000);
|
|
893
752
|
}
|
|
753
|
+
// Handle terminal resize
|
|
754
|
+
screen.on("resize", () => {
|
|
755
|
+
screen?.render();
|
|
756
|
+
});
|
|
757
|
+
// Update header with lease countdown every second
|
|
758
|
+
headerUpdateInterval = setInterval(() => {
|
|
759
|
+
if (leaseInfo && headerBox && screen && !isExiting) {
|
|
760
|
+
updateHeaderBox(headerBox, leaseInfo, tokenIdentifier, true);
|
|
761
|
+
screen.render();
|
|
762
|
+
}
|
|
763
|
+
}, 1000);
|
|
894
764
|
// Handle input submission
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
765
|
+
inputBox.on("submit", async (value) => {
|
|
766
|
+
const trimmed = value.trim();
|
|
767
|
+
// Ignore input while sending
|
|
768
|
+
if (isSending || !trimmed) {
|
|
769
|
+
inputBox?.clearValue();
|
|
770
|
+
inputBox?.focus();
|
|
771
|
+
screen?.render();
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
// Handle commands
|
|
775
|
+
if (trimmed.startsWith("/")) {
|
|
776
|
+
const [cmd] = trimmed.split(" ");
|
|
777
|
+
switch (cmd) {
|
|
778
|
+
case "/exit":
|
|
779
|
+
case "/quit":
|
|
780
|
+
isExiting = true;
|
|
781
|
+
if (leaseCheckInterval)
|
|
782
|
+
clearInterval(leaseCheckInterval);
|
|
783
|
+
if (headerUpdateInterval)
|
|
784
|
+
clearInterval(headerUpdateInterval);
|
|
785
|
+
if (ws)
|
|
786
|
+
ws.close();
|
|
787
|
+
screen?.destroy();
|
|
788
|
+
console.log();
|
|
789
|
+
printCat("sleeping");
|
|
790
|
+
console.log(chalk.cyan("Chat disconnected. Goodbye! 👋"));
|
|
791
|
+
process.exit(0);
|
|
792
|
+
return;
|
|
793
|
+
case "/renew": {
|
|
794
|
+
try {
|
|
795
|
+
inputBox?.setValue("⏱️ Renewing lease...");
|
|
796
|
+
screen?.render();
|
|
797
|
+
const renewal = await renewLease(client, userAddress, leaseInfo?.leaseId);
|
|
798
|
+
leaseInfo = {
|
|
799
|
+
leaseId: renewal.leaseId,
|
|
800
|
+
leaseExpiresAt: new Date(renewal.leaseExpiresAt),
|
|
801
|
+
};
|
|
802
|
+
// Update header first
|
|
803
|
+
if (headerBox) {
|
|
804
|
+
updateHeaderBox(headerBox, leaseInfo, tokenIdentifier, false);
|
|
805
|
+
}
|
|
806
|
+
if (messageLogBox) {
|
|
807
|
+
messageLogBox.log(chalk.green(`✅ Lease renewed! Expires in ${formatTimeRemaining(leaseInfo.leaseExpiresAt)}`));
|
|
808
|
+
messageLogBox.log(chalk.yellow("🔄 Reconnecting to chat stream..."));
|
|
809
|
+
messageLogBox.setScrollPerc(100);
|
|
928
810
|
screen?.render();
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
//
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
if (messageLogBox) {
|
|
939
|
-
messageLogBox.log(chalk.green(`✅ Lease renewed! Expires in ${formatTimeRemaining(leaseInfo.leaseExpiresAt)}`));
|
|
940
|
-
messageLogBox.log(chalk.yellow("🔄 Reconnecting to chat stream..."));
|
|
941
|
-
messageLogBox.setScrollPerc(100);
|
|
942
|
-
screen?.render();
|
|
943
|
-
}
|
|
944
|
-
// Close old connection properly
|
|
945
|
-
if (ws) {
|
|
946
|
-
// Remove all listeners to prevent conflicts
|
|
947
|
-
ws.removeAllListeners();
|
|
948
|
-
// Ensure connection is fully closed
|
|
949
|
-
// WebSocket.OPEN = 1, WebSocket.CONNECTING = 0
|
|
950
|
-
if (ws.readyState === 1 || ws.readyState === 0) {
|
|
951
|
-
ws.close();
|
|
952
|
-
}
|
|
953
|
-
ws = null;
|
|
811
|
+
}
|
|
812
|
+
// Close old connection properly
|
|
813
|
+
if (ws) {
|
|
814
|
+
// Remove all listeners to prevent conflicts
|
|
815
|
+
ws.removeAllListeners();
|
|
816
|
+
// Ensure connection is fully closed
|
|
817
|
+
// WebSocket.OPEN = 1, WebSocket.CONNECTING = 0
|
|
818
|
+
if (ws.readyState === 1 || ws.readyState === 0) {
|
|
819
|
+
ws.close();
|
|
954
820
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
messageLogBox.setScrollPerc(100);
|
|
979
|
-
screen?.render();
|
|
980
|
-
}
|
|
981
|
-
await new Promise((resolve) => setTimeout(resolve, backoffDelay));
|
|
982
|
-
}
|
|
983
|
-
// Create new connection and wait for it to open
|
|
984
|
-
const newWsUrl = wsUrl; // TypeScript: ensure it's not null
|
|
985
|
-
await new Promise((resolve, reject) => {
|
|
986
|
-
const newWs = new WebSocket(newWsUrl);
|
|
987
|
-
// Attach permanent handlers FIRST (before temporary handlers)
|
|
988
|
-
attachWebSocketHandlers(newWs);
|
|
989
|
-
// Set up temporary handlers for connection establishment
|
|
990
|
-
let timeout = null;
|
|
991
|
-
let openHandler = null;
|
|
992
|
-
let closeHandler = null;
|
|
993
|
-
let errorHandler = null;
|
|
994
|
-
const cleanup = () => {
|
|
995
|
-
if (timeout)
|
|
996
|
-
clearTimeout(timeout);
|
|
997
|
-
if (openHandler)
|
|
998
|
-
newWs.removeListener("open", openHandler);
|
|
999
|
-
if (closeHandler)
|
|
1000
|
-
newWs.removeListener("close", closeHandler);
|
|
1001
|
-
if (errorHandler)
|
|
1002
|
-
newWs.removeListener("error", errorHandler);
|
|
1003
|
-
};
|
|
1004
|
-
timeout = setTimeout(() => {
|
|
1005
|
-
cleanup();
|
|
1006
|
-
newWs.close();
|
|
1007
|
-
reject(new Error("Connection timeout - lease may not be ready yet"));
|
|
1008
|
-
}, 10000);
|
|
1009
|
-
openHandler = () => {
|
|
1010
|
-
cleanup();
|
|
1011
|
-
ws = newWs; // Assign to outer variable only after successful connection
|
|
1012
|
-
resolve();
|
|
1013
|
-
};
|
|
1014
|
-
closeHandler = (code, reason) => {
|
|
1015
|
-
cleanup();
|
|
1016
|
-
const reasonStr = reason.toString();
|
|
1017
|
-
if (code === 1008 && reasonStr.includes("lease")) {
|
|
1018
|
-
reject(new Error("Lease validation failed - please try /renew again"));
|
|
1019
|
-
}
|
|
1020
|
-
else {
|
|
1021
|
-
reject(new Error(`Connection closed: ${code} - ${reasonStr}`));
|
|
1022
|
-
}
|
|
1023
|
-
};
|
|
1024
|
-
errorHandler = (error) => {
|
|
1025
|
-
cleanup();
|
|
1026
|
-
reject(error);
|
|
1027
|
-
};
|
|
1028
|
-
// Attach temporary handlers AFTER permanent handlers
|
|
1029
|
-
newWs.once("open", openHandler);
|
|
1030
|
-
newWs.once("close", closeHandler);
|
|
1031
|
-
newWs.once("error", errorHandler);
|
|
1032
|
-
});
|
|
1033
|
-
connected = true;
|
|
1034
|
-
// Connection established
|
|
1035
|
-
if (headerBox) {
|
|
1036
|
-
updateHeaderBox(headerBox, leaseInfo, tokenIdentifier, true);
|
|
1037
|
-
}
|
|
1038
|
-
if (messageLogBox) {
|
|
1039
|
-
messageLogBox.log(chalk.green("✅ Reconnected to chat stream"));
|
|
1040
|
-
messageLogBox.setScrollPerc(100);
|
|
1041
|
-
}
|
|
1042
|
-
screen?.render();
|
|
1043
|
-
}
|
|
1044
|
-
catch (error) {
|
|
1045
|
-
retryCount++;
|
|
1046
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1047
|
-
if (retryCount >= maxRetries) {
|
|
1048
|
-
// Final failure
|
|
1049
|
-
throw new Error(`Failed to reconnect after ${maxRetries} attempts: ${errorMsg}`);
|
|
1050
|
-
}
|
|
1051
|
-
// Log retry attempt
|
|
821
|
+
ws = null;
|
|
822
|
+
}
|
|
823
|
+
// Wait for database transaction to commit and old connection to fully close
|
|
824
|
+
// The server validates the lease in the open handler, so we need to ensure
|
|
825
|
+
// the database update is visible before connecting
|
|
826
|
+
// Increased delay to 2 seconds for better database consistency
|
|
827
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
828
|
+
// Reconnect with new lease - with retry logic
|
|
829
|
+
if (wsUrl) {
|
|
830
|
+
// Normalize WebSocket URL protocol based on agent URL
|
|
831
|
+
const agentUrl = client.getAgentUrl();
|
|
832
|
+
const normalizedWsUrl = normalizeWebSocketUrl(wsUrl, agentUrl);
|
|
833
|
+
const wsUrlObj = new URL(normalizedWsUrl);
|
|
834
|
+
wsUrlObj.searchParams.set("leaseId", leaseInfo.leaseId);
|
|
835
|
+
wsUrl = wsUrlObj.toString(); // Update stored URL
|
|
836
|
+
// Retry logic with exponential backoff
|
|
837
|
+
let retryCount = 0;
|
|
838
|
+
const maxRetries = 3;
|
|
839
|
+
let connected = false;
|
|
840
|
+
while (!connected && retryCount < maxRetries) {
|
|
841
|
+
try {
|
|
842
|
+
if (retryCount > 0) {
|
|
843
|
+
const backoffDelay = Math.min(1000 * Math.pow(2, retryCount - 1), 5000);
|
|
1052
844
|
if (messageLogBox) {
|
|
1053
|
-
messageLogBox.log(chalk.yellow(
|
|
845
|
+
messageLogBox.log(chalk.yellow(`🔄 Retry ${retryCount}/${maxRetries} in ${backoffDelay}ms...`));
|
|
1054
846
|
messageLogBox.setScrollPerc(100);
|
|
1055
847
|
screen?.render();
|
|
1056
848
|
}
|
|
849
|
+
await new Promise((resolve) => setTimeout(resolve, backoffDelay));
|
|
850
|
+
}
|
|
851
|
+
// Create new connection and wait for it to open
|
|
852
|
+
const newWsUrl = wsUrl; // TypeScript: ensure it's not null
|
|
853
|
+
await new Promise((resolve, reject) => {
|
|
854
|
+
const newWs = new WebSocket(newWsUrl);
|
|
855
|
+
// Attach permanent handlers FIRST (before temporary handlers)
|
|
856
|
+
attachWebSocketHandlers(newWs);
|
|
857
|
+
// Set up temporary handlers for connection establishment
|
|
858
|
+
let timeout = null;
|
|
859
|
+
let openHandler = null;
|
|
860
|
+
let closeHandler = null;
|
|
861
|
+
let errorHandler = null;
|
|
862
|
+
const cleanup = () => {
|
|
863
|
+
if (timeout)
|
|
864
|
+
clearTimeout(timeout);
|
|
865
|
+
if (openHandler)
|
|
866
|
+
newWs.removeListener("open", openHandler);
|
|
867
|
+
if (closeHandler)
|
|
868
|
+
newWs.removeListener("close", closeHandler);
|
|
869
|
+
if (errorHandler)
|
|
870
|
+
newWs.removeListener("error", errorHandler);
|
|
871
|
+
};
|
|
872
|
+
timeout = setTimeout(() => {
|
|
873
|
+
cleanup();
|
|
874
|
+
newWs.close();
|
|
875
|
+
reject(new Error("Connection timeout - lease may not be ready yet"));
|
|
876
|
+
}, 10000);
|
|
877
|
+
openHandler = () => {
|
|
878
|
+
cleanup();
|
|
879
|
+
ws = newWs; // Assign to outer variable only after successful connection
|
|
880
|
+
resolve();
|
|
881
|
+
};
|
|
882
|
+
closeHandler = (code, reason) => {
|
|
883
|
+
cleanup();
|
|
884
|
+
const reasonStr = reason.toString();
|
|
885
|
+
if (code === 1008 && reasonStr.includes("lease")) {
|
|
886
|
+
reject(new Error("Lease validation failed - please try /renew again"));
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
reject(new Error(`Connection closed: ${code} - ${reasonStr}`));
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
errorHandler = (error) => {
|
|
893
|
+
cleanup();
|
|
894
|
+
reject(error);
|
|
895
|
+
};
|
|
896
|
+
// Attach temporary handlers AFTER permanent handlers
|
|
897
|
+
newWs.once("open", openHandler);
|
|
898
|
+
newWs.once("close", closeHandler);
|
|
899
|
+
newWs.once("error", errorHandler);
|
|
900
|
+
});
|
|
901
|
+
connected = true;
|
|
902
|
+
// Connection established
|
|
903
|
+
if (headerBox) {
|
|
904
|
+
updateHeaderBox(headerBox, leaseInfo, tokenIdentifier, true);
|
|
905
|
+
}
|
|
906
|
+
if (messageLogBox) {
|
|
907
|
+
messageLogBox.log(chalk.green("✅ Reconnected to chat stream"));
|
|
908
|
+
messageLogBox.setScrollPerc(100);
|
|
909
|
+
}
|
|
910
|
+
screen?.render();
|
|
911
|
+
}
|
|
912
|
+
catch (error) {
|
|
913
|
+
retryCount++;
|
|
914
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
915
|
+
if (retryCount >= maxRetries) {
|
|
916
|
+
// Final failure
|
|
917
|
+
throw new Error(`Failed to reconnect after ${maxRetries} attempts: ${errorMsg}`);
|
|
918
|
+
}
|
|
919
|
+
// Log retry attempt
|
|
920
|
+
if (messageLogBox) {
|
|
921
|
+
messageLogBox.log(chalk.yellow(`⚠️ Connection attempt ${retryCount} failed: ${errorMsg}`));
|
|
922
|
+
messageLogBox.setScrollPerc(100);
|
|
923
|
+
screen?.render();
|
|
1057
924
|
}
|
|
1058
925
|
}
|
|
1059
926
|
}
|
|
1060
|
-
inputBox?.clearValue();
|
|
1061
|
-
inputBox?.focus();
|
|
1062
|
-
screen?.render();
|
|
1063
927
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
928
|
+
inputBox?.clearValue();
|
|
929
|
+
inputBox?.focus();
|
|
930
|
+
screen?.render();
|
|
931
|
+
}
|
|
932
|
+
catch (error) {
|
|
933
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
934
|
+
if (messageLogBox) {
|
|
935
|
+
messageLogBox.log(chalk.red(`❌ ${errorMsg}`));
|
|
936
|
+
messageLogBox.setScrollPerc(100);
|
|
937
|
+
}
|
|
938
|
+
inputBox?.clearValue();
|
|
939
|
+
inputBox?.focus();
|
|
940
|
+
screen?.render();
|
|
941
|
+
}
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
case "/buy": {
|
|
945
|
+
// Only allow in token-specific chats
|
|
946
|
+
if (!tokenIdentifier) {
|
|
947
|
+
if (messageLogBox) {
|
|
948
|
+
messageLogBox.log(chalk.yellow("⚠️ /buy command only works in token-specific chats"));
|
|
949
|
+
messageLogBox.setScrollPerc(100);
|
|
1072
950
|
screen?.render();
|
|
1073
951
|
}
|
|
952
|
+
inputBox?.clearValue();
|
|
953
|
+
inputBox?.focus();
|
|
954
|
+
screen?.render();
|
|
1074
955
|
return;
|
|
1075
956
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
if (
|
|
957
|
+
try {
|
|
958
|
+
const parts = trimmed.split(" ");
|
|
959
|
+
if (parts.length < 2) {
|
|
1079
960
|
if (messageLogBox) {
|
|
1080
|
-
messageLogBox.log(chalk.yellow("⚠️ /buy
|
|
961
|
+
messageLogBox.log(chalk.yellow("⚠️ Usage: /buy <amount> (e.g., /buy 0.05)"));
|
|
1081
962
|
messageLogBox.setScrollPerc(100);
|
|
1082
963
|
screen?.render();
|
|
1083
964
|
}
|
|
@@ -1086,52 +967,52 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
|
|
|
1086
967
|
screen?.render();
|
|
1087
968
|
return;
|
|
1088
969
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
inputBox?.focus();
|
|
1099
|
-
screen?.render();
|
|
1100
|
-
return;
|
|
1101
|
-
}
|
|
1102
|
-
const amount = parts[1];
|
|
1103
|
-
inputBox?.setValue(`💰 Buying tokens...`);
|
|
970
|
+
const amount = parts[1];
|
|
971
|
+
inputBox?.setValue(`💰 Buying tokens...`);
|
|
972
|
+
screen?.render();
|
|
973
|
+
const isTestMode = client.getNetwork().includes("sepolia");
|
|
974
|
+
const result = await buyToken(client, tokenIdentifier, amount, isTestMode, true // silent mode
|
|
975
|
+
);
|
|
976
|
+
if (messageLogBox) {
|
|
977
|
+
messageLogBox.log(formatBuyResultCompact(result));
|
|
978
|
+
messageLogBox.setScrollPerc(100);
|
|
1104
979
|
screen?.render();
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
980
|
+
}
|
|
981
|
+
inputBox?.clearValue();
|
|
982
|
+
inputBox?.focus();
|
|
983
|
+
screen?.render();
|
|
984
|
+
}
|
|
985
|
+
catch (error) {
|
|
986
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
987
|
+
if (messageLogBox) {
|
|
988
|
+
messageLogBox.log(chalk.red(`❌ Buy failed: ${errorMsg}`));
|
|
989
|
+
messageLogBox.setScrollPerc(100);
|
|
1115
990
|
screen?.render();
|
|
1116
991
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
992
|
+
inputBox?.clearValue();
|
|
993
|
+
inputBox?.focus();
|
|
994
|
+
screen?.render();
|
|
995
|
+
}
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
case "/sell": {
|
|
999
|
+
// Only allow in token-specific chats
|
|
1000
|
+
if (!tokenIdentifier) {
|
|
1001
|
+
if (messageLogBox) {
|
|
1002
|
+
messageLogBox.log(chalk.yellow("⚠️ /sell command only works in token-specific chats"));
|
|
1003
|
+
messageLogBox.setScrollPerc(100);
|
|
1126
1004
|
screen?.render();
|
|
1127
1005
|
}
|
|
1006
|
+
inputBox?.clearValue();
|
|
1007
|
+
inputBox?.focus();
|
|
1008
|
+
screen?.render();
|
|
1128
1009
|
return;
|
|
1129
1010
|
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
if (
|
|
1011
|
+
try {
|
|
1012
|
+
const parts = trimmed.split(" ");
|
|
1013
|
+
if (parts.length < 2) {
|
|
1133
1014
|
if (messageLogBox) {
|
|
1134
|
-
messageLogBox.log(chalk.yellow("⚠️ /sell
|
|
1015
|
+
messageLogBox.log(chalk.yellow("⚠️ Usage: /sell <amount|all|percentage> (e.g., /sell 0.05, /sell all, /sell 50%)"));
|
|
1135
1016
|
messageLogBox.setScrollPerc(100);
|
|
1136
1017
|
screen?.render();
|
|
1137
1018
|
}
|
|
@@ -1140,493 +1021,177 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
|
|
|
1140
1021
|
screen?.render();
|
|
1141
1022
|
return;
|
|
1142
1023
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
}
|
|
1163
|
-
const tokenInfo = await getTokenInfo(client, tokenIdentifier, userAddress, true // silent mode
|
|
1164
|
-
);
|
|
1165
|
-
if (!tokenInfo.userPosition || tokenInfo.userPosition.tokensOwned === "0") {
|
|
1166
|
-
throw new Error("You do not own any of this token");
|
|
1167
|
-
}
|
|
1168
|
-
// Parse sell amount
|
|
1169
|
-
const tokenAmount = parseTokenAmount(amountStr, tokenInfo.userPosition.tokensOwned);
|
|
1170
|
-
const result = await sellToken(client, tokenIdentifier, tokenAmount, true // silent mode
|
|
1171
|
-
);
|
|
1172
|
-
if (messageLogBox) {
|
|
1173
|
-
messageLogBox.log(formatSellResultCompact(result));
|
|
1174
|
-
messageLogBox.setScrollPerc(100);
|
|
1175
|
-
screen?.render();
|
|
1176
|
-
}
|
|
1177
|
-
inputBox?.clearValue();
|
|
1178
|
-
inputBox?.focus();
|
|
1179
|
-
screen?.render();
|
|
1180
|
-
}
|
|
1181
|
-
catch (error) {
|
|
1182
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1183
|
-
if (messageLogBox) {
|
|
1184
|
-
messageLogBox.log(chalk.red(`❌ Sell failed: ${errorMsg}`));
|
|
1185
|
-
messageLogBox.setScrollPerc(100);
|
|
1186
|
-
screen?.render();
|
|
1187
|
-
}
|
|
1188
|
-
inputBox?.clearValue();
|
|
1189
|
-
inputBox?.focus();
|
|
1190
|
-
screen?.render();
|
|
1191
|
-
}
|
|
1192
|
-
return;
|
|
1193
|
-
}
|
|
1194
|
-
case "/help":
|
|
1195
|
-
if (messageLogBox) {
|
|
1196
|
-
const helpText = tokenIdentifier
|
|
1197
|
-
? chalk.dim("Commands: /exit, /quit, /renew, /buy, /sell, /help")
|
|
1198
|
-
: chalk.dim("Commands: /exit, /quit, /renew, /help");
|
|
1199
|
-
messageLogBox.log(helpText);
|
|
1200
|
-
messageLogBox.setScrollPerc(100);
|
|
1024
|
+
const amountStr = parts[1];
|
|
1025
|
+
inputBox?.setValue(`💸 Selling tokens...`);
|
|
1026
|
+
screen?.render();
|
|
1027
|
+
// Get token info to check balance
|
|
1028
|
+
if (!userAddress) {
|
|
1029
|
+
throw new Error("User address not available");
|
|
1030
|
+
}
|
|
1031
|
+
const tokenInfo = await getTokenInfo(client, tokenIdentifier, userAddress, true // silent mode
|
|
1032
|
+
);
|
|
1033
|
+
if (!tokenInfo.userPosition || tokenInfo.userPosition.tokensOwned === "0") {
|
|
1034
|
+
throw new Error("You do not own any of this token");
|
|
1035
|
+
}
|
|
1036
|
+
// Parse sell amount
|
|
1037
|
+
const tokenAmount = parseTokenAmount(amountStr, tokenInfo.userPosition.tokensOwned);
|
|
1038
|
+
const result = await sellToken(client, tokenIdentifier, tokenAmount, true // silent mode
|
|
1039
|
+
);
|
|
1040
|
+
if (messageLogBox) {
|
|
1041
|
+
messageLogBox.log(formatSellResultCompact(result));
|
|
1042
|
+
messageLogBox.setScrollPerc(100);
|
|
1201
1043
|
screen?.render();
|
|
1202
1044
|
}
|
|
1203
1045
|
inputBox?.clearValue();
|
|
1204
1046
|
inputBox?.focus();
|
|
1205
1047
|
screen?.render();
|
|
1206
|
-
|
|
1207
|
-
|
|
1048
|
+
}
|
|
1049
|
+
catch (error) {
|
|
1050
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1208
1051
|
if (messageLogBox) {
|
|
1209
|
-
messageLogBox.log(chalk.
|
|
1052
|
+
messageLogBox.log(chalk.red(`❌ Sell failed: ${errorMsg}`));
|
|
1210
1053
|
messageLogBox.setScrollPerc(100);
|
|
1211
1054
|
screen?.render();
|
|
1212
1055
|
}
|
|
1213
1056
|
inputBox?.clearValue();
|
|
1214
1057
|
inputBox?.focus();
|
|
1215
1058
|
screen?.render();
|
|
1216
|
-
|
|
1059
|
+
}
|
|
1060
|
+
return;
|
|
1217
1061
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
messageLogBox.log(chalk.red(`❌ WebSocket is not connected (state: ${stateMsg}). Please wait for connection or type /renew.`));
|
|
1230
|
-
messageLogBox.setScrollPerc(100);
|
|
1062
|
+
case "/help":
|
|
1063
|
+
if (messageLogBox) {
|
|
1064
|
+
const helpText = tokenIdentifier
|
|
1065
|
+
? chalk.dim("Commands: /exit, /quit, /renew, /buy, /sell, /help")
|
|
1066
|
+
: chalk.dim("Commands: /exit, /quit, /renew, /help");
|
|
1067
|
+
messageLogBox.log(helpText);
|
|
1068
|
+
messageLogBox.setScrollPerc(100);
|
|
1069
|
+
screen?.render();
|
|
1070
|
+
}
|
|
1071
|
+
inputBox?.clearValue();
|
|
1072
|
+
inputBox?.focus();
|
|
1231
1073
|
screen?.render();
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
messageLogBox.log(chalk.yellow("⏱️ Your lease has expired. Type /renew to continue chatting."));
|
|
1242
|
-
messageLogBox.setScrollPerc(100);
|
|
1074
|
+
return;
|
|
1075
|
+
default:
|
|
1076
|
+
if (messageLogBox) {
|
|
1077
|
+
messageLogBox.log(chalk.yellow(`Unknown command: ${cmd}. Type /help for commands.`));
|
|
1078
|
+
messageLogBox.setScrollPerc(100);
|
|
1079
|
+
screen?.render();
|
|
1080
|
+
}
|
|
1081
|
+
inputBox?.clearValue();
|
|
1082
|
+
inputBox?.focus();
|
|
1243
1083
|
screen?.render();
|
|
1244
|
-
}
|
|
1245
|
-
inputBox?.clearValue();
|
|
1246
|
-
inputBox?.focus();
|
|
1247
|
-
screen?.render();
|
|
1248
|
-
return;
|
|
1249
|
-
}
|
|
1250
|
-
// Send message
|
|
1251
|
-
isSending = true;
|
|
1252
|
-
currentInput = trimmed;
|
|
1253
|
-
// Show pulsing animation in input box
|
|
1254
|
-
let pulseCount = 0;
|
|
1255
|
-
let pulseInterval = null;
|
|
1256
|
-
const tempMessageId = `pending-${Date.now()}-${Math.random()}`;
|
|
1257
|
-
const updatePulse = () => {
|
|
1258
|
-
if (!inputBox || isCleaningUp)
|
|
1259
1084
|
return;
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
// Check if websocket is still connected
|
|
1088
|
+
// WebSocket.OPEN = 1
|
|
1089
|
+
if (!ws || ws.readyState !== 1) {
|
|
1090
|
+
const stateMsg = ws
|
|
1091
|
+
? ws.readyState === 0 ? "connecting"
|
|
1092
|
+
: ws.readyState === 2 ? "closing"
|
|
1093
|
+
: ws.readyState === 3 ? "closed"
|
|
1094
|
+
: "unknown"
|
|
1095
|
+
: "not initialized";
|
|
1096
|
+
if (messageLogBox) {
|
|
1097
|
+
messageLogBox.log(chalk.red(`❌ WebSocket is not connected (state: ${stateMsg}). Please wait for connection or type /renew.`));
|
|
1098
|
+
messageLogBox.setScrollPerc(100);
|
|
1263
1099
|
screen?.render();
|
|
1264
|
-
};
|
|
1265
|
-
// Initial pulse
|
|
1266
|
-
updatePulse();
|
|
1267
|
-
pulseInterval = setInterval(updatePulse, 500);
|
|
1268
|
-
pulseIntervals.set(tempMessageId, pulseInterval);
|
|
1269
|
-
try {
|
|
1270
|
-
// Ensure lease is valid before sending
|
|
1271
|
-
leaseInfo = await ensureLeaseValid(client, userAddress, leaseInfo, messageLogBox || undefined, screen || undefined);
|
|
1272
|
-
if (headerBox) {
|
|
1273
|
-
updateHeaderBox(headerBox, leaseInfo, tokenIdentifier, true);
|
|
1274
|
-
screen?.render();
|
|
1275
|
-
}
|
|
1276
|
-
// Double-check websocket is still connected after lease renewal
|
|
1277
|
-
// WebSocket.OPEN = 1
|
|
1278
|
-
if (!ws || ws.readyState !== 1) {
|
|
1279
|
-
throw new Error("WebSocket connection lost. Please wait for reconnection.");
|
|
1280
|
-
}
|
|
1281
|
-
const result = await sendChatMessage(client, trimmed, leaseInfo.leaseId, userAddress);
|
|
1282
|
-
userAddress = result.author;
|
|
1283
|
-
// Track this message
|
|
1284
|
-
pendingMessages.set(result.messageId, tempMessageId);
|
|
1285
|
-
pendingMessages.set(`text:${trimmed}`, tempMessageId);
|
|
1286
|
-
// Keep pulsing until WebSocket confirms
|
|
1287
1100
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
}
|
|
1299
|
-
inputBox?.clearValue();
|
|
1300
|
-
inputBox?.focus();
|
|
1301
|
-
isSending = false;
|
|
1302
|
-
currentInput = "";
|
|
1101
|
+
inputBox?.clearValue();
|
|
1102
|
+
inputBox?.focus();
|
|
1103
|
+
screen?.render();
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
// Check if lease is valid
|
|
1107
|
+
if (!leaseInfo || leaseInfo.leaseExpiresAt.getTime() <= Date.now()) {
|
|
1108
|
+
if (messageLogBox) {
|
|
1109
|
+
messageLogBox.log(chalk.yellow("⏱️ Your lease has expired. Type /renew to continue chatting."));
|
|
1110
|
+
messageLogBox.setScrollPerc(100);
|
|
1303
1111
|
screen?.render();
|
|
1304
1112
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1113
|
+
inputBox?.clearValue();
|
|
1114
|
+
inputBox?.focus();
|
|
1115
|
+
screen?.render();
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
// Send message
|
|
1119
|
+
isSending = true;
|
|
1120
|
+
currentInput = trimmed;
|
|
1121
|
+
// Show pulsing animation in input box
|
|
1122
|
+
let pulseCount = 0;
|
|
1123
|
+
let pulseInterval = null;
|
|
1124
|
+
const tempMessageId = `pending-${Date.now()}-${Math.random()}`;
|
|
1125
|
+
const updatePulse = () => {
|
|
1126
|
+
if (!inputBox || isCleaningUp)
|
|
1317
1127
|
return;
|
|
1128
|
+
pulseCount++;
|
|
1129
|
+
const pulseChar = pulseCount % 2 === 0 ? "●" : "○";
|
|
1130
|
+
inputBox.setValue(`${trimmed} ${pulseChar}`);
|
|
1131
|
+
screen?.render();
|
|
1132
|
+
};
|
|
1133
|
+
// Initial pulse
|
|
1134
|
+
updatePulse();
|
|
1135
|
+
pulseInterval = setInterval(updatePulse, 500);
|
|
1136
|
+
pulseIntervals.set(tempMessageId, pulseInterval);
|
|
1137
|
+
try {
|
|
1138
|
+
// Ensure lease is valid before sending
|
|
1139
|
+
leaseInfo = await ensureLeaseValid(client, userAddress, leaseInfo, messageLogBox || undefined, screen || undefined);
|
|
1140
|
+
if (headerBox) {
|
|
1141
|
+
updateHeaderBox(headerBox, leaseInfo, tokenIdentifier, true);
|
|
1142
|
+
screen?.render();
|
|
1318
1143
|
}
|
|
1319
|
-
//
|
|
1320
|
-
|
|
1321
|
-
const [cmd] = trimmed.split(" ");
|
|
1322
|
-
switch (cmd) {
|
|
1323
|
-
case "/exit":
|
|
1324
|
-
case "/quit":
|
|
1325
|
-
isExiting = true;
|
|
1326
|
-
if (leaseCheckInterval)
|
|
1327
|
-
clearInterval(leaseCheckInterval);
|
|
1328
|
-
if (headerUpdateInterval)
|
|
1329
|
-
clearInterval(headerUpdateInterval);
|
|
1330
|
-
if (ws)
|
|
1331
|
-
ws.close();
|
|
1332
|
-
readlineInterface.close();
|
|
1333
|
-
console.log();
|
|
1334
|
-
printCat("sleeping");
|
|
1335
|
-
console.log(chalk.cyan("Chat disconnected. Goodbye! 👋"));
|
|
1336
|
-
process.exit(0);
|
|
1337
|
-
return;
|
|
1338
|
-
case "/renew": {
|
|
1339
|
-
try {
|
|
1340
|
-
console.log(chalk.yellow("⏱️ Renewing lease..."));
|
|
1341
|
-
const renewal = await renewLease(client, userAddress, leaseInfo?.leaseId);
|
|
1342
|
-
leaseInfo = {
|
|
1343
|
-
leaseId: renewal.leaseId,
|
|
1344
|
-
leaseExpiresAt: new Date(renewal.leaseExpiresAt),
|
|
1345
|
-
};
|
|
1346
|
-
console.log(chalk.green(`✅ Lease renewed! Expires in ${formatTimeRemaining(leaseInfo.leaseExpiresAt)}`));
|
|
1347
|
-
console.log(chalk.yellow("🔄 Reconnecting to chat stream..."));
|
|
1348
|
-
// Close old connection properly
|
|
1349
|
-
if (ws) {
|
|
1350
|
-
ws.removeAllListeners();
|
|
1351
|
-
// WebSocket.OPEN = 1, WebSocket.CONNECTING = 0
|
|
1352
|
-
if (ws.readyState === 1 || ws.readyState === 0) {
|
|
1353
|
-
ws.close();
|
|
1354
|
-
}
|
|
1355
|
-
ws = null;
|
|
1356
|
-
}
|
|
1357
|
-
// Wait for database transaction to commit
|
|
1358
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1359
|
-
// Reconnect with new lease
|
|
1360
|
-
if (wsUrl) {
|
|
1361
|
-
// Normalize WebSocket URL protocol based on agent URL
|
|
1362
|
-
const agentUrl = client.getAgentUrl();
|
|
1363
|
-
const normalizedWsUrl = normalizeWebSocketUrl(wsUrl, agentUrl);
|
|
1364
|
-
const wsUrlObj = new URL(normalizedWsUrl);
|
|
1365
|
-
wsUrlObj.searchParams.set("leaseId", leaseInfo.leaseId);
|
|
1366
|
-
wsUrl = wsUrlObj.toString();
|
|
1367
|
-
// Retry logic
|
|
1368
|
-
let retryCount = 0;
|
|
1369
|
-
const maxRetries = 3;
|
|
1370
|
-
let connected = false;
|
|
1371
|
-
while (!connected && retryCount < maxRetries) {
|
|
1372
|
-
try {
|
|
1373
|
-
if (retryCount > 0) {
|
|
1374
|
-
const backoffDelay = Math.min(1000 * Math.pow(2, retryCount - 1), 5000);
|
|
1375
|
-
console.log(chalk.yellow(`🔄 Retry ${retryCount}/${maxRetries} in ${backoffDelay}ms...`));
|
|
1376
|
-
await new Promise((resolve) => setTimeout(resolve, backoffDelay));
|
|
1377
|
-
}
|
|
1378
|
-
const newWsUrl = wsUrl;
|
|
1379
|
-
await new Promise((resolve, reject) => {
|
|
1380
|
-
const newWs = new WebSocket(newWsUrl);
|
|
1381
|
-
attachWebSocketHandlers(newWs);
|
|
1382
|
-
let timeout = null;
|
|
1383
|
-
let openHandler = null;
|
|
1384
|
-
let closeHandler = null;
|
|
1385
|
-
let errorHandler = null;
|
|
1386
|
-
const cleanup = () => {
|
|
1387
|
-
if (timeout)
|
|
1388
|
-
clearTimeout(timeout);
|
|
1389
|
-
if (openHandler)
|
|
1390
|
-
newWs.removeListener("open", openHandler);
|
|
1391
|
-
if (closeHandler)
|
|
1392
|
-
newWs.removeListener("close", closeHandler);
|
|
1393
|
-
if (errorHandler)
|
|
1394
|
-
newWs.removeListener("error", errorHandler);
|
|
1395
|
-
};
|
|
1396
|
-
timeout = setTimeout(() => {
|
|
1397
|
-
cleanup();
|
|
1398
|
-
newWs.close();
|
|
1399
|
-
reject(new Error("Connection timeout"));
|
|
1400
|
-
}, 10000);
|
|
1401
|
-
openHandler = () => {
|
|
1402
|
-
cleanup();
|
|
1403
|
-
ws = newWs;
|
|
1404
|
-
resolve();
|
|
1405
|
-
};
|
|
1406
|
-
closeHandler = (code, reason) => {
|
|
1407
|
-
cleanup();
|
|
1408
|
-
const reasonStr = reason.toString();
|
|
1409
|
-
if (code === 1008 && reasonStr.includes("lease")) {
|
|
1410
|
-
reject(new Error("Lease validation failed"));
|
|
1411
|
-
}
|
|
1412
|
-
else {
|
|
1413
|
-
reject(new Error(`Connection closed: ${code} - ${reasonStr}`));
|
|
1414
|
-
}
|
|
1415
|
-
};
|
|
1416
|
-
errorHandler = (error) => {
|
|
1417
|
-
cleanup();
|
|
1418
|
-
reject(error);
|
|
1419
|
-
};
|
|
1420
|
-
newWs.once("open", openHandler);
|
|
1421
|
-
newWs.once("close", closeHandler);
|
|
1422
|
-
newWs.once("error", errorHandler);
|
|
1423
|
-
});
|
|
1424
|
-
connected = true;
|
|
1425
|
-
console.log(chalk.green("✅ Reconnected to chat stream"));
|
|
1426
|
-
}
|
|
1427
|
-
catch (error) {
|
|
1428
|
-
retryCount++;
|
|
1429
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1430
|
-
if (retryCount >= maxRetries) {
|
|
1431
|
-
throw new Error(`Failed to reconnect after ${maxRetries} attempts: ${errorMsg}`);
|
|
1432
|
-
}
|
|
1433
|
-
console.log(chalk.yellow(`⚠️ Connection attempt ${retryCount} failed: ${errorMsg}`));
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
if (readlineInterface)
|
|
1438
|
-
readlineInterface.prompt();
|
|
1439
|
-
}
|
|
1440
|
-
catch (error) {
|
|
1441
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1442
|
-
console.log(chalk.red(`❌ ${errorMsg}`));
|
|
1443
|
-
if (readlineInterface)
|
|
1444
|
-
readlineInterface.prompt();
|
|
1445
|
-
}
|
|
1446
|
-
return;
|
|
1447
|
-
}
|
|
1448
|
-
case "/buy": {
|
|
1449
|
-
// Only allow in token-specific chats
|
|
1450
|
-
if (!tokenIdentifier) {
|
|
1451
|
-
console.log(chalk.yellow("⚠️ /buy command only works in token-specific chats"));
|
|
1452
|
-
if (readlineInterface)
|
|
1453
|
-
readlineInterface.prompt();
|
|
1454
|
-
return;
|
|
1455
|
-
}
|
|
1456
|
-
try {
|
|
1457
|
-
const parts = trimmed.split(" ");
|
|
1458
|
-
if (parts.length < 2) {
|
|
1459
|
-
console.log(chalk.yellow("⚠️ Usage: /buy <amount> (e.g., /buy 0.05)"));
|
|
1460
|
-
if (readlineInterface)
|
|
1461
|
-
readlineInterface.prompt();
|
|
1462
|
-
return;
|
|
1463
|
-
}
|
|
1464
|
-
const amount = parts[1];
|
|
1465
|
-
console.log(chalk.yellow("💰 Buying tokens..."));
|
|
1466
|
-
const isTestMode = client.getNetwork().includes("sepolia");
|
|
1467
|
-
const result = await buyToken(client, tokenIdentifier, amount, isTestMode, true // silent mode
|
|
1468
|
-
);
|
|
1469
|
-
console.log(formatBuyResultCompact(result));
|
|
1470
|
-
if (readlineInterface)
|
|
1471
|
-
readlineInterface.prompt();
|
|
1472
|
-
}
|
|
1473
|
-
catch (error) {
|
|
1474
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1475
|
-
console.log(chalk.red(`❌ Buy failed: ${errorMsg}`));
|
|
1476
|
-
if (readlineInterface)
|
|
1477
|
-
readlineInterface.prompt();
|
|
1478
|
-
}
|
|
1479
|
-
return;
|
|
1480
|
-
}
|
|
1481
|
-
case "/sell": {
|
|
1482
|
-
// Only allow in token-specific chats
|
|
1483
|
-
if (!tokenIdentifier) {
|
|
1484
|
-
console.log(chalk.yellow("⚠️ /sell command only works in token-specific chats"));
|
|
1485
|
-
if (readlineInterface)
|
|
1486
|
-
readlineInterface.prompt();
|
|
1487
|
-
return;
|
|
1488
|
-
}
|
|
1489
|
-
try {
|
|
1490
|
-
const parts = trimmed.split(" ");
|
|
1491
|
-
if (parts.length < 2) {
|
|
1492
|
-
console.log(chalk.yellow("⚠️ Usage: /sell <amount|all|percentage> (e.g., /sell 0.05, /sell all, /sell 50%)"));
|
|
1493
|
-
if (readlineInterface)
|
|
1494
|
-
readlineInterface.prompt();
|
|
1495
|
-
return;
|
|
1496
|
-
}
|
|
1497
|
-
const amountStr = parts[1];
|
|
1498
|
-
console.log(chalk.yellow("💸 Selling tokens..."));
|
|
1499
|
-
// Get token info to check balance
|
|
1500
|
-
if (!userAddress) {
|
|
1501
|
-
throw new Error("User address not available");
|
|
1502
|
-
}
|
|
1503
|
-
const tokenInfo = await getTokenInfo(client, tokenIdentifier, userAddress, true // silent mode
|
|
1504
|
-
);
|
|
1505
|
-
if (!tokenInfo.userPosition || tokenInfo.userPosition.tokensOwned === "0") {
|
|
1506
|
-
throw new Error("You do not own any of this token");
|
|
1507
|
-
}
|
|
1508
|
-
// Parse sell amount
|
|
1509
|
-
const tokenAmount = parseTokenAmount(amountStr, tokenInfo.userPosition.tokensOwned);
|
|
1510
|
-
const result = await sellToken(client, tokenIdentifier, tokenAmount, true // silent mode
|
|
1511
|
-
);
|
|
1512
|
-
console.log(formatSellResultCompact(result));
|
|
1513
|
-
if (readlineInterface)
|
|
1514
|
-
readlineInterface.prompt();
|
|
1515
|
-
}
|
|
1516
|
-
catch (error) {
|
|
1517
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1518
|
-
console.log(chalk.red(`❌ Sell failed: ${errorMsg}`));
|
|
1519
|
-
if (readlineInterface)
|
|
1520
|
-
readlineInterface.prompt();
|
|
1521
|
-
}
|
|
1522
|
-
return;
|
|
1523
|
-
}
|
|
1524
|
-
case "/help":
|
|
1525
|
-
const helpText = tokenIdentifier
|
|
1526
|
-
? chalk.dim("Commands: /exit, /quit, /renew, /buy, /sell, /help")
|
|
1527
|
-
: chalk.dim("Commands: /exit, /quit, /renew, /help");
|
|
1528
|
-
console.log(helpText);
|
|
1529
|
-
if (readlineInterface)
|
|
1530
|
-
readlineInterface.prompt();
|
|
1531
|
-
return;
|
|
1532
|
-
default:
|
|
1533
|
-
console.log(chalk.yellow(`Unknown command: ${cmd}. Type /help for commands.`));
|
|
1534
|
-
if (readlineInterface)
|
|
1535
|
-
readlineInterface.prompt();
|
|
1536
|
-
return;
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
// Check if websocket is still connected
|
|
1144
|
+
// Double-check websocket is still connected after lease renewal
|
|
1145
|
+
// WebSocket.OPEN = 1
|
|
1540
1146
|
if (!ws || ws.readyState !== 1) {
|
|
1541
|
-
|
|
1542
|
-
? ws.readyState === 0 ? "connecting"
|
|
1543
|
-
: ws.readyState === 2 ? "closing"
|
|
1544
|
-
: ws.readyState === 3 ? "closed"
|
|
1545
|
-
: "unknown"
|
|
1546
|
-
: "not initialized";
|
|
1547
|
-
console.log(chalk.red(`❌ WebSocket is not connected (state: ${stateMsg}). Please wait for connection or type /renew.`));
|
|
1548
|
-
if (readlineInterface)
|
|
1549
|
-
readlineInterface.prompt();
|
|
1550
|
-
return;
|
|
1147
|
+
throw new Error("WebSocket connection lost. Please wait for reconnection.");
|
|
1551
1148
|
}
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
leaseInfo = await ensureLeaseValid(client, userAddress, leaseInfo, undefined, undefined);
|
|
1565
|
-
// Double-check websocket is still connected
|
|
1566
|
-
if (!ws || ws.readyState !== 1) {
|
|
1567
|
-
throw new Error("WebSocket connection lost. Please wait for reconnection.");
|
|
1568
|
-
}
|
|
1569
|
-
const result = await sendChatMessage(client, trimmed, leaseInfo.leaseId, userAddress);
|
|
1570
|
-
// Track this message
|
|
1571
|
-
const tempMessageId = `pending-${Date.now()}-${Math.random()}`;
|
|
1572
|
-
pendingMessages.set(result.messageId, tempMessageId);
|
|
1573
|
-
pendingMessages.set(`text:${trimmed}`, tempMessageId);
|
|
1574
|
-
// Message sent - will be displayed when received via WebSocket
|
|
1149
|
+
const result = await sendChatMessage(client, trimmed, leaseInfo.leaseId, userAddress);
|
|
1150
|
+
userAddress = result.author;
|
|
1151
|
+
// Track this message
|
|
1152
|
+
pendingMessages.set(result.messageId, tempMessageId);
|
|
1153
|
+
pendingMessages.set(`text:${trimmed}`, tempMessageId);
|
|
1154
|
+
// Keep pulsing until WebSocket confirms
|
|
1155
|
+
}
|
|
1156
|
+
catch (error) {
|
|
1157
|
+
// Stop pulsing
|
|
1158
|
+
if (pulseInterval) {
|
|
1159
|
+
clearInterval(pulseInterval);
|
|
1160
|
+
pulseIntervals.delete(tempMessageId);
|
|
1575
1161
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
currentInput = "";
|
|
1162
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1163
|
+
if (messageLogBox) {
|
|
1164
|
+
messageLogBox.log(chalk.red(`❌ ${errorMsg}`));
|
|
1165
|
+
messageLogBox.setScrollPerc(100);
|
|
1581
1166
|
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
});
|
|
1585
|
-
// Handle Ctrl+C for readline
|
|
1586
|
-
readlineInterface.on("SIGINT", () => {
|
|
1587
|
-
isExiting = true;
|
|
1588
|
-
isSending = false;
|
|
1589
|
-
pulseIntervals.forEach((interval) => clearInterval(interval));
|
|
1590
|
-
pulseIntervals.clear();
|
|
1591
|
-
if (leaseCheckInterval)
|
|
1592
|
-
clearInterval(leaseCheckInterval);
|
|
1593
|
-
if (headerUpdateInterval)
|
|
1594
|
-
clearInterval(headerUpdateInterval);
|
|
1595
|
-
if (ws)
|
|
1596
|
-
ws.close();
|
|
1597
|
-
if (readlineInterface)
|
|
1598
|
-
readlineInterface.close();
|
|
1599
|
-
console.log();
|
|
1600
|
-
printCat("sleeping");
|
|
1601
|
-
console.log(chalk.cyan("Chat disconnected. Goodbye! 👋"));
|
|
1602
|
-
process.exit(0);
|
|
1603
|
-
});
|
|
1604
|
-
}
|
|
1605
|
-
// Handle Ctrl+C (for blessed mode)
|
|
1606
|
-
if (useBlessed && screen) {
|
|
1607
|
-
screen.key(["C-c"], () => {
|
|
1608
|
-
isExiting = true;
|
|
1167
|
+
inputBox?.clearValue();
|
|
1168
|
+
inputBox?.focus();
|
|
1609
1169
|
isSending = false;
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
if (leaseCheckInterval)
|
|
1613
|
-
clearInterval(leaseCheckInterval);
|
|
1614
|
-
if (headerUpdateInterval)
|
|
1615
|
-
clearInterval(headerUpdateInterval);
|
|
1616
|
-
if (ws)
|
|
1617
|
-
ws.close();
|
|
1618
|
-
screen?.destroy();
|
|
1619
|
-
console.log();
|
|
1620
|
-
printCat("sleeping");
|
|
1621
|
-
console.log(chalk.cyan("Chat disconnected. Goodbye! 👋"));
|
|
1622
|
-
process.exit(0);
|
|
1623
|
-
});
|
|
1624
|
-
// Focus input box and render (only for blessed)
|
|
1625
|
-
if (inputBox) {
|
|
1626
|
-
inputBox.focus();
|
|
1627
|
-
screen.render();
|
|
1170
|
+
currentInput = "";
|
|
1171
|
+
screen?.render();
|
|
1628
1172
|
}
|
|
1629
|
-
}
|
|
1173
|
+
});
|
|
1174
|
+
// Handle Ctrl+C
|
|
1175
|
+
screen.key(["C-c"], () => {
|
|
1176
|
+
isExiting = true;
|
|
1177
|
+
isSending = false;
|
|
1178
|
+
pulseIntervals.forEach((interval) => clearInterval(interval));
|
|
1179
|
+
pulseIntervals.clear();
|
|
1180
|
+
if (leaseCheckInterval)
|
|
1181
|
+
clearInterval(leaseCheckInterval);
|
|
1182
|
+
if (headerUpdateInterval)
|
|
1183
|
+
clearInterval(headerUpdateInterval);
|
|
1184
|
+
if (ws)
|
|
1185
|
+
ws.close();
|
|
1186
|
+
screen?.destroy();
|
|
1187
|
+
console.log();
|
|
1188
|
+
printCat("sleeping");
|
|
1189
|
+
console.log(chalk.cyan("Chat disconnected. Goodbye! 👋"));
|
|
1190
|
+
process.exit(0);
|
|
1191
|
+
});
|
|
1192
|
+
// Focus input box and render
|
|
1193
|
+
inputBox.focus();
|
|
1194
|
+
screen.render();
|
|
1630
1195
|
}
|
|
1631
1196
|
else {
|
|
1632
1197
|
// JSON mode: read messages from stdin and output JSON to stdout
|
|
@@ -1929,10 +1494,6 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
|
|
|
1929
1494
|
if (screen) {
|
|
1930
1495
|
screen.destroy();
|
|
1931
1496
|
}
|
|
1932
|
-
// Only close readline if not in JSON mode (readline should never exist in JSON mode)
|
|
1933
|
-
if (rl && !jsonMode) {
|
|
1934
|
-
rl.close();
|
|
1935
|
-
}
|
|
1936
1497
|
}
|
|
1937
1498
|
catch (cleanupError) {
|
|
1938
1499
|
// Ignore cleanup errors
|