botchan 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/dist/cli/index.mjs +456 -24
- package/dist/cli/index.mjs.map +1 -1
- package/dist/tui/index.mjs +10 -9
- package/dist/tui/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/cli/index.mjs
CHANGED
|
@@ -343,9 +343,106 @@ function resetState() {
|
|
|
343
343
|
function getStateFilePath() {
|
|
344
344
|
return STATE_FILE;
|
|
345
345
|
}
|
|
346
|
-
|
|
346
|
+
function addHistoryEntry(entry) {
|
|
347
|
+
const state = loadState();
|
|
348
|
+
const history = state.history ?? [];
|
|
349
|
+
const newEntry = {
|
|
350
|
+
...entry,
|
|
351
|
+
timestamp: Math.floor(Date.now() / 1e3)
|
|
352
|
+
};
|
|
353
|
+
history.unshift(newEntry);
|
|
354
|
+
if (history.length > MAX_HISTORY_ENTRIES) {
|
|
355
|
+
history.length = MAX_HISTORY_ENTRIES;
|
|
356
|
+
}
|
|
357
|
+
state.history = history;
|
|
358
|
+
saveState(state);
|
|
359
|
+
}
|
|
360
|
+
function getHistory(limit) {
|
|
361
|
+
const state = loadState();
|
|
362
|
+
const history = state.history ?? [];
|
|
363
|
+
return limit ? history.slice(0, limit) : history;
|
|
364
|
+
}
|
|
365
|
+
function getHistoryByType(type, limit) {
|
|
366
|
+
const history = getHistory();
|
|
367
|
+
const filtered = history.filter((entry) => entry.type === type);
|
|
368
|
+
return limit ? filtered.slice(0, limit) : filtered;
|
|
369
|
+
}
|
|
370
|
+
function clearHistory() {
|
|
371
|
+
const state = loadState();
|
|
372
|
+
state.history = [];
|
|
373
|
+
saveState(state);
|
|
374
|
+
}
|
|
375
|
+
function getHistoryCount() {
|
|
376
|
+
const state = loadState();
|
|
377
|
+
return state.history?.length ?? 0;
|
|
378
|
+
}
|
|
379
|
+
function isWalletAddress(feed) {
|
|
380
|
+
return /^0x[a-fA-F0-9]{40}$/.test(feed);
|
|
381
|
+
}
|
|
382
|
+
function getContacts() {
|
|
383
|
+
const history = getHistory();
|
|
384
|
+
const contactMap = /* @__PURE__ */ new Map();
|
|
385
|
+
for (const entry of history) {
|
|
386
|
+
if (entry.type === "post" && isWalletAddress(entry.feed)) {
|
|
387
|
+
const address = entry.feed.toLowerCase();
|
|
388
|
+
const existing = contactMap.get(address);
|
|
389
|
+
if (existing) {
|
|
390
|
+
existing.interactionCount++;
|
|
391
|
+
if (entry.timestamp > existing.lastInteraction) {
|
|
392
|
+
existing.lastInteraction = entry.timestamp;
|
|
393
|
+
}
|
|
394
|
+
if (entry.timestamp < existing.firstInteraction) {
|
|
395
|
+
existing.firstInteraction = entry.timestamp;
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
contactMap.set(address, {
|
|
399
|
+
address,
|
|
400
|
+
lastInteraction: entry.timestamp,
|
|
401
|
+
firstInteraction: entry.timestamp,
|
|
402
|
+
interactionCount: 1
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return Array.from(contactMap.values()).sort(
|
|
408
|
+
(a, b) => b.lastInteraction - a.lastInteraction
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
function getActiveFeeds() {
|
|
412
|
+
const history = getHistory();
|
|
413
|
+
const feedMap = /* @__PURE__ */ new Map();
|
|
414
|
+
for (const entry of history) {
|
|
415
|
+
if (isWalletAddress(entry.feed)) continue;
|
|
416
|
+
if (entry.type === "register") continue;
|
|
417
|
+
const feedName = entry.feed.toLowerCase();
|
|
418
|
+
const existing = feedMap.get(feedName);
|
|
419
|
+
if (existing) {
|
|
420
|
+
if (entry.type === "post") existing.postCount++;
|
|
421
|
+
if (entry.type === "comment") existing.commentCount++;
|
|
422
|
+
if (entry.timestamp > existing.lastActivity) {
|
|
423
|
+
existing.lastActivity = entry.timestamp;
|
|
424
|
+
}
|
|
425
|
+
if (entry.timestamp < existing.firstActivity) {
|
|
426
|
+
existing.firstActivity = entry.timestamp;
|
|
427
|
+
}
|
|
428
|
+
} else {
|
|
429
|
+
feedMap.set(feedName, {
|
|
430
|
+
feed: feedName,
|
|
431
|
+
postCount: entry.type === "post" ? 1 : 0,
|
|
432
|
+
commentCount: entry.type === "comment" ? 1 : 0,
|
|
433
|
+
lastActivity: entry.timestamp,
|
|
434
|
+
firstActivity: entry.timestamp
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return Array.from(feedMap.values()).sort(
|
|
439
|
+
(a, b) => b.lastActivity - a.lastActivity
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
var MAX_HISTORY_ENTRIES, STATE_DIR, STATE_FILE;
|
|
347
443
|
var init_state = __esm({
|
|
348
444
|
"src/utils/state.ts"() {
|
|
445
|
+
MAX_HISTORY_ENTRIES = 100;
|
|
349
446
|
STATE_DIR = path.join(os.homedir(), ".botchan");
|
|
350
447
|
STATE_FILE = path.join(STATE_DIR, "state.json");
|
|
351
448
|
}
|
|
@@ -973,7 +1070,7 @@ var init_FeedList = __esm({
|
|
|
973
1070
|
init_hooks();
|
|
974
1071
|
}
|
|
975
1072
|
});
|
|
976
|
-
function
|
|
1073
|
+
function truncateAddress3(address) {
|
|
977
1074
|
if (address.length <= 12) return address;
|
|
978
1075
|
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
979
1076
|
}
|
|
@@ -986,7 +1083,7 @@ function formatFeedName(feedName) {
|
|
|
986
1083
|
name = name.slice(5);
|
|
987
1084
|
}
|
|
988
1085
|
if (isAddress(name)) {
|
|
989
|
-
return `@${
|
|
1086
|
+
return `@${truncateAddress3(name)}`;
|
|
990
1087
|
}
|
|
991
1088
|
return name;
|
|
992
1089
|
}
|
|
@@ -1009,9 +1106,10 @@ function parsePostContent(text) {
|
|
|
1009
1106
|
const body = text.slice(firstNewline + 1).trim();
|
|
1010
1107
|
return { title, body: body || null };
|
|
1011
1108
|
}
|
|
1012
|
-
function
|
|
1013
|
-
|
|
1014
|
-
|
|
1109
|
+
function truncateText2(text, maxLength = 250) {
|
|
1110
|
+
const trimmed = text.trim();
|
|
1111
|
+
if (trimmed.length <= maxLength) return trimmed;
|
|
1112
|
+
return trimmed.slice(0, maxLength - 3) + "...";
|
|
1015
1113
|
}
|
|
1016
1114
|
function PostList({
|
|
1017
1115
|
feedName,
|
|
@@ -1065,7 +1163,7 @@ function PostList({
|
|
|
1065
1163
|
const postKey = `${post.sender}:${post.timestamp}`;
|
|
1066
1164
|
const commentCount = commentCounts.get(postKey) ?? 0;
|
|
1067
1165
|
const { title, body } = parsePostContent(post.text);
|
|
1068
|
-
const displayTitle = title ?
|
|
1166
|
+
const displayTitle = title ? truncateText2(title) : null;
|
|
1069
1167
|
const hasMore = body !== null;
|
|
1070
1168
|
return /* @__PURE__ */ jsxs(
|
|
1071
1169
|
Box,
|
|
@@ -1087,7 +1185,7 @@ function PostList({
|
|
|
1087
1185
|
),
|
|
1088
1186
|
/* @__PURE__ */ jsxs(Text, { color: "gray", dimColor: true, children: [
|
|
1089
1187
|
" ",
|
|
1090
|
-
|
|
1188
|
+
truncateAddress3(post.sender),
|
|
1091
1189
|
" \xB7 ",
|
|
1092
1190
|
formatTimestamp2(post.timestamp),
|
|
1093
1191
|
" \xB7 ",
|
|
@@ -1112,7 +1210,7 @@ var init_PostList = __esm({
|
|
|
1112
1210
|
init_hooks();
|
|
1113
1211
|
}
|
|
1114
1212
|
});
|
|
1115
|
-
function
|
|
1213
|
+
function truncateAddress4(address) {
|
|
1116
1214
|
if (address.length <= 12) return address;
|
|
1117
1215
|
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
1118
1216
|
}
|
|
@@ -1125,10 +1223,10 @@ function formatTimestamp3(timestamp) {
|
|
|
1125
1223
|
minute: "2-digit"
|
|
1126
1224
|
});
|
|
1127
1225
|
}
|
|
1128
|
-
function
|
|
1129
|
-
const
|
|
1130
|
-
if (
|
|
1131
|
-
return
|
|
1226
|
+
function truncateText3(text, maxLength = 500) {
|
|
1227
|
+
const trimmed = text.trim();
|
|
1228
|
+
if (trimmed.length <= maxLength) return trimmed;
|
|
1229
|
+
return trimmed.slice(0, maxLength - 3) + "...";
|
|
1132
1230
|
}
|
|
1133
1231
|
function getTextLines(text, width) {
|
|
1134
1232
|
const lines = [];
|
|
@@ -1208,7 +1306,7 @@ function CommentTree({
|
|
|
1208
1306
|
] }),
|
|
1209
1307
|
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
1210
1308
|
" ",
|
|
1211
|
-
|
|
1309
|
+
truncateAddress4(post.sender),
|
|
1212
1310
|
" \xB7 ",
|
|
1213
1311
|
formatTimestamp3(post.timestamp)
|
|
1214
1312
|
] }),
|
|
@@ -1229,7 +1327,7 @@ function CommentTree({
|
|
|
1229
1327
|
const { comment, depth } = item;
|
|
1230
1328
|
const key = `${comment.sender}:${comment.timestamp}`;
|
|
1231
1329
|
const replyCount = replyCounts.get(key) ?? 0;
|
|
1232
|
-
const displayText = comment.text ?
|
|
1330
|
+
const displayText = comment.text ? truncateText3(comment.text) : null;
|
|
1233
1331
|
const indent = " ".repeat(depth);
|
|
1234
1332
|
const replyIndicator = depth > 0 ? "\u21B3 " : "";
|
|
1235
1333
|
return /* @__PURE__ */ jsxs(
|
|
@@ -1255,7 +1353,7 @@ function CommentTree({
|
|
|
1255
1353
|
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
1256
1354
|
indent,
|
|
1257
1355
|
" ",
|
|
1258
|
-
|
|
1356
|
+
truncateAddress4(comment.sender),
|
|
1259
1357
|
" \xB7 ",
|
|
1260
1358
|
formatTimestamp3(comment.timestamp),
|
|
1261
1359
|
replyCount > 0 && ` \xB7 ${replyCount} ${replyCount === 1 ? "reply" : "replies"}`
|
|
@@ -1277,7 +1375,7 @@ var init_CommentTree = __esm({
|
|
|
1277
1375
|
"src/tui/components/CommentTree.tsx"() {
|
|
1278
1376
|
}
|
|
1279
1377
|
});
|
|
1280
|
-
function
|
|
1378
|
+
function truncateAddress5(address) {
|
|
1281
1379
|
if (address.length <= 12) return address;
|
|
1282
1380
|
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
1283
1381
|
}
|
|
@@ -1358,7 +1456,7 @@ function Profile({
|
|
|
1358
1456
|
isSelected ? "\u25B6 " : " ",
|
|
1359
1457
|
/* @__PURE__ */ jsxs(Text, { color: isSelected ? "green" : "magenta", children: [
|
|
1360
1458
|
"@",
|
|
1361
|
-
|
|
1459
|
+
truncateAddress5(address)
|
|
1362
1460
|
] }),
|
|
1363
1461
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: " \xB7 view their feed" })
|
|
1364
1462
|
]
|
|
@@ -1390,7 +1488,7 @@ function Profile({
|
|
|
1390
1488
|
}
|
|
1391
1489
|
}
|
|
1392
1490
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
|
|
1393
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children:
|
|
1491
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: truncateAddress5(address) }),
|
|
1394
1492
|
/* @__PURE__ */ jsxs(Box, { marginTop: 1, marginBottom: 1, children: [
|
|
1395
1493
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Their Feed" }),
|
|
1396
1494
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: " \xB7 " }),
|
|
@@ -1418,7 +1516,7 @@ var init_Profile = __esm({
|
|
|
1418
1516
|
init_hooks();
|
|
1419
1517
|
}
|
|
1420
1518
|
});
|
|
1421
|
-
function
|
|
1519
|
+
function truncateAddress6(address) {
|
|
1422
1520
|
if (address.length <= 12) return address;
|
|
1423
1521
|
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
1424
1522
|
}
|
|
@@ -1431,7 +1529,7 @@ function formatFeedName2(feedName) {
|
|
|
1431
1529
|
name = name.slice(5);
|
|
1432
1530
|
}
|
|
1433
1531
|
if (isAddress2(name)) {
|
|
1434
|
-
return `@${
|
|
1532
|
+
return `@${truncateAddress6(name)}`;
|
|
1435
1533
|
}
|
|
1436
1534
|
return name;
|
|
1437
1535
|
}
|
|
@@ -1453,7 +1551,7 @@ function Header({ viewMode, chainId, feedName, senderFilter, profileAddress }) {
|
|
|
1453
1551
|
if (viewMode === "profile" && profileAddress) {
|
|
1454
1552
|
parts.push(/* @__PURE__ */ jsx(Text, { color: "white", children: "profile" }, "profile"));
|
|
1455
1553
|
parts.push(/* @__PURE__ */ jsx(Text, { color: "gray", children: " \xB7 " }, "sep"));
|
|
1456
|
-
parts.push(/* @__PURE__ */ jsx(Text, { color: "magenta", children:
|
|
1554
|
+
parts.push(/* @__PURE__ */ jsx(Text, { color: "magenta", children: truncateAddress6(profileAddress) }, "address"));
|
|
1457
1555
|
} else if (viewMode === "feeds" || viewMode === "search") {
|
|
1458
1556
|
parts.push(/* @__PURE__ */ jsx(Text, { color: "white", children: "home" }, "home"));
|
|
1459
1557
|
} else if (feedName) {
|
|
@@ -1467,7 +1565,7 @@ function Header({ viewMode, chainId, feedName, senderFilter, profileAddress }) {
|
|
|
1467
1565
|
parts.push(/* @__PURE__ */ jsx(Text, { color: "gray", children: " " }, "filter-sep"));
|
|
1468
1566
|
parts.push(/* @__PURE__ */ jsxs(Text, { color: "magenta", children: [
|
|
1469
1567
|
"[",
|
|
1470
|
-
|
|
1568
|
+
truncateAddress6(senderFilter),
|
|
1471
1569
|
"]"
|
|
1472
1570
|
] }, "filter"));
|
|
1473
1571
|
}
|
|
@@ -2422,6 +2520,13 @@ async function executeRegister(feedName, options) {
|
|
|
2422
2520
|
console.log(chalk10.blue(`Registering feed "${feedName}"...`));
|
|
2423
2521
|
try {
|
|
2424
2522
|
const hash = await executeTransaction(walletClient, txConfig);
|
|
2523
|
+
addHistoryEntry({
|
|
2524
|
+
type: "register",
|
|
2525
|
+
txHash: hash,
|
|
2526
|
+
chainId: commonOptions.chainId,
|
|
2527
|
+
feed: feedName,
|
|
2528
|
+
sender: walletClient.account.address
|
|
2529
|
+
});
|
|
2425
2530
|
console.log(
|
|
2426
2531
|
chalk10.green(
|
|
2427
2532
|
`Feed registered successfully!
|
|
@@ -2502,12 +2607,39 @@ ${options.body}` : message;
|
|
|
2502
2607
|
console.log(chalk10.blue(`Posting to feed "${normalizedFeed}"...`));
|
|
2503
2608
|
try {
|
|
2504
2609
|
const hash = await executeTransaction(walletClient, txConfig);
|
|
2610
|
+
const senderAddress = walletClient.account.address;
|
|
2611
|
+
let postId;
|
|
2612
|
+
try {
|
|
2613
|
+
const posts = await client.getFeedPosts({
|
|
2614
|
+
topic: normalizedFeed,
|
|
2615
|
+
maxPosts: 10
|
|
2616
|
+
});
|
|
2617
|
+
const ourPost = posts.find(
|
|
2618
|
+
(p) => p.sender.toLowerCase() === senderAddress.toLowerCase() && p.text === fullMessage
|
|
2619
|
+
);
|
|
2620
|
+
if (ourPost) {
|
|
2621
|
+
postId = `${ourPost.sender}:${ourPost.timestamp}`;
|
|
2622
|
+
}
|
|
2623
|
+
} catch {
|
|
2624
|
+
}
|
|
2625
|
+
addHistoryEntry({
|
|
2626
|
+
type: "post",
|
|
2627
|
+
txHash: hash,
|
|
2628
|
+
chainId: commonOptions.chainId,
|
|
2629
|
+
feed: normalizedFeed,
|
|
2630
|
+
sender: senderAddress,
|
|
2631
|
+
text: fullMessage,
|
|
2632
|
+
postId
|
|
2633
|
+
// Now we have the actual post ID for checking comments later
|
|
2634
|
+
});
|
|
2505
2635
|
const displayText = options.body ? `${message} (+ body)` : message;
|
|
2636
|
+
const postIdInfo = postId ? `
|
|
2637
|
+
Post ID: ${postId}` : "";
|
|
2506
2638
|
console.log(
|
|
2507
2639
|
chalk10.green(
|
|
2508
2640
|
`Message posted successfully!
|
|
2509
2641
|
Transaction: ${hash}
|
|
2510
|
-
Feed: ${normalizedFeed}
|
|
2642
|
+
Feed: ${normalizedFeed}${postIdInfo}
|
|
2511
2643
|
Text: ${displayText}`
|
|
2512
2644
|
)
|
|
2513
2645
|
);
|
|
@@ -2599,6 +2731,15 @@ async function executeComment(feed, postId, message, options) {
|
|
|
2599
2731
|
console.log(chalk10.blue(`Commenting on post ${postId}...`));
|
|
2600
2732
|
try {
|
|
2601
2733
|
const hash = await executeTransaction(walletClient, txConfig);
|
|
2734
|
+
addHistoryEntry({
|
|
2735
|
+
type: "comment",
|
|
2736
|
+
txHash: hash,
|
|
2737
|
+
chainId: commonOptions.chainId,
|
|
2738
|
+
feed: normalizedFeed,
|
|
2739
|
+
sender: walletClient.account.address,
|
|
2740
|
+
text: message,
|
|
2741
|
+
postId
|
|
2742
|
+
});
|
|
2602
2743
|
console.log(
|
|
2603
2744
|
chalk10.green(
|
|
2604
2745
|
`Comment posted successfully!
|
|
@@ -2651,6 +2792,7 @@ async function executeConfig(options) {
|
|
|
2651
2792
|
This includes:`));
|
|
2652
2793
|
console.log(chalk10.white(` - All "last seen" timestamps for feeds`));
|
|
2653
2794
|
console.log(chalk10.white(` - Your configured address`));
|
|
2795
|
+
console.log(chalk10.white(` - Your activity history`));
|
|
2654
2796
|
if (!options.force) {
|
|
2655
2797
|
const confirmed = await confirm(chalk10.red("\nAre you sure you want to reset?"));
|
|
2656
2798
|
if (!confirmed) {
|
|
@@ -2683,6 +2825,8 @@ This includes:`));
|
|
|
2683
2825
|
console.log(chalk10.white(`My address: ${myAddress ?? chalk10.gray("(not set)")}`));
|
|
2684
2826
|
const feedCount = Object.keys(state.feeds).length;
|
|
2685
2827
|
console.log(chalk10.white(`Tracked feeds: ${feedCount}`));
|
|
2828
|
+
const historyCount = getHistoryCount();
|
|
2829
|
+
console.log(chalk10.white(`History entries: ${historyCount}`));
|
|
2686
2830
|
if (feedCount > 0 && feedCount <= 20) {
|
|
2687
2831
|
console.log(chalk10.gray("\nLast seen timestamps:"));
|
|
2688
2832
|
for (const [feed, data] of Object.entries(state.feeds)) {
|
|
@@ -2693,6 +2837,37 @@ This includes:`));
|
|
|
2693
2837
|
console.log(chalk10.gray(`
|
|
2694
2838
|
(${feedCount} feeds tracked, use --json for full list)`));
|
|
2695
2839
|
}
|
|
2840
|
+
const activeFeeds = getActiveFeeds();
|
|
2841
|
+
if (activeFeeds.length > 0) {
|
|
2842
|
+
console.log(chalk10.cyan("\nActive Feeds:"));
|
|
2843
|
+
const displayFeeds = activeFeeds.slice(0, 10);
|
|
2844
|
+
for (const feed of displayFeeds) {
|
|
2845
|
+
const activity = [];
|
|
2846
|
+
if (feed.postCount > 0) activity.push(`${feed.postCount} post${feed.postCount !== 1 ? "s" : ""}`);
|
|
2847
|
+
if (feed.commentCount > 0) activity.push(`${feed.commentCount} comment${feed.commentCount !== 1 ? "s" : ""}`);
|
|
2848
|
+
const lastActive = formatTimestamp(feed.lastActivity);
|
|
2849
|
+
console.log(chalk10.white(` ${feed.feed}`) + chalk10.gray(` \u2022 ${activity.join(", ")} \u2022 ${lastActive}`));
|
|
2850
|
+
}
|
|
2851
|
+
if (activeFeeds.length > 10) {
|
|
2852
|
+
console.log(chalk10.gray(` ... and ${activeFeeds.length - 10} more`));
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
const contacts = getContacts();
|
|
2856
|
+
if (contacts.length > 0) {
|
|
2857
|
+
console.log(chalk10.cyan("\nRecent Contacts (DMs):"));
|
|
2858
|
+
const displayContacts = contacts.slice(0, 10);
|
|
2859
|
+
for (const contact of displayContacts) {
|
|
2860
|
+
const truncAddr = `${contact.address.slice(0, 6)}...${contact.address.slice(-4)}`;
|
|
2861
|
+
const msgCount = contact.interactionCount;
|
|
2862
|
+
const lastActive = formatTimestamp(contact.lastInteraction);
|
|
2863
|
+
console.log(
|
|
2864
|
+
chalk10.white(` ${truncAddr}`) + chalk10.gray(` \u2022 ${msgCount} message${msgCount !== 1 ? "s" : ""} \u2022 ${lastActive}`)
|
|
2865
|
+
);
|
|
2866
|
+
}
|
|
2867
|
+
if (contacts.length > 10) {
|
|
2868
|
+
console.log(chalk10.gray(` ... and ${contacts.length - 10} more`));
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2696
2871
|
}
|
|
2697
2872
|
function registerConfigCommand(program2) {
|
|
2698
2873
|
program2.command("config").description("View or modify botchan configuration").option("--my-address <address>", "Set your address (to filter out own posts with --unseen)").option("--clear-address", "Clear your configured address").option("--show", "Show current configuration (default)").option("--reset", "Reset all state (clears last-seen timestamps and address)").option("--force", "Skip confirmation prompt for --reset").action(async (options) => {
|
|
@@ -2700,6 +2875,261 @@ function registerConfigCommand(program2) {
|
|
|
2700
2875
|
});
|
|
2701
2876
|
}
|
|
2702
2877
|
|
|
2878
|
+
// src/commands/history.ts
|
|
2879
|
+
init_utils();
|
|
2880
|
+
async function confirm2(message) {
|
|
2881
|
+
const rl = readline.createInterface({
|
|
2882
|
+
input: process.stdin,
|
|
2883
|
+
output: process.stdout
|
|
2884
|
+
});
|
|
2885
|
+
return new Promise((resolve) => {
|
|
2886
|
+
rl.question(`${message} (y/N): `, (answer) => {
|
|
2887
|
+
rl.close();
|
|
2888
|
+
resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
2889
|
+
});
|
|
2890
|
+
});
|
|
2891
|
+
}
|
|
2892
|
+
function truncateAddress2(address) {
|
|
2893
|
+
if (address.length <= 12) return address;
|
|
2894
|
+
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
2895
|
+
}
|
|
2896
|
+
function formatHistoryEntry(entry, index) {
|
|
2897
|
+
const timestamp = formatTimestamp(entry.timestamp);
|
|
2898
|
+
const typeColor = entry.type === "post" ? chalk10.green : entry.type === "comment" ? chalk10.blue : chalk10.yellow;
|
|
2899
|
+
const lines = [
|
|
2900
|
+
chalk10.cyan(`[${index}]`) + ` ${chalk10.gray(timestamp)} ` + typeColor(entry.type.toUpperCase()),
|
|
2901
|
+
` ${chalk10.white("Feed:")} ${entry.feed}`,
|
|
2902
|
+
` ${chalk10.white("Tx:")} ${entry.txHash}`
|
|
2903
|
+
];
|
|
2904
|
+
if (entry.sender) {
|
|
2905
|
+
lines.push(` ${chalk10.white("Sender:")} ${truncateAddress2(entry.sender)}`);
|
|
2906
|
+
}
|
|
2907
|
+
if (entry.text) {
|
|
2908
|
+
const truncatedText = entry.text.length > 80 ? entry.text.slice(0, 80) + "..." : entry.text;
|
|
2909
|
+
lines.push(` ${chalk10.white("Text:")} ${truncatedText}`);
|
|
2910
|
+
}
|
|
2911
|
+
if (entry.type === "post" && entry.postId) {
|
|
2912
|
+
lines.push(` ${chalk10.white("Post ID:")} ${entry.postId}`);
|
|
2913
|
+
} else if (entry.type === "comment" && entry.postId) {
|
|
2914
|
+
lines.push(` ${chalk10.white("Reply to:")} ${entry.postId}`);
|
|
2915
|
+
}
|
|
2916
|
+
if (entry.type === "post" && entry.postId) {
|
|
2917
|
+
lines.push(
|
|
2918
|
+
chalk10.gray(` \u2192 Check replies: botchan comments ${entry.feed} ${entry.postId}`)
|
|
2919
|
+
);
|
|
2920
|
+
} else if (entry.type === "post" && entry.sender) {
|
|
2921
|
+
lines.push(
|
|
2922
|
+
chalk10.gray(` \u2192 Find post: botchan read ${entry.feed} --sender ${entry.sender} --json`)
|
|
2923
|
+
);
|
|
2924
|
+
} else if (entry.type === "comment" && entry.postId) {
|
|
2925
|
+
lines.push(
|
|
2926
|
+
chalk10.gray(` \u2192 See thread: botchan comments ${entry.feed} ${entry.postId}`)
|
|
2927
|
+
);
|
|
2928
|
+
}
|
|
2929
|
+
return lines.join("\n");
|
|
2930
|
+
}
|
|
2931
|
+
function historyEntryToJson(entry, index) {
|
|
2932
|
+
const result = {
|
|
2933
|
+
index,
|
|
2934
|
+
type: entry.type,
|
|
2935
|
+
timestamp: entry.timestamp,
|
|
2936
|
+
txHash: entry.txHash,
|
|
2937
|
+
chainId: entry.chainId,
|
|
2938
|
+
feed: entry.feed
|
|
2939
|
+
};
|
|
2940
|
+
if (entry.sender) {
|
|
2941
|
+
result.sender = entry.sender;
|
|
2942
|
+
}
|
|
2943
|
+
if (entry.text) {
|
|
2944
|
+
result.text = entry.text;
|
|
2945
|
+
}
|
|
2946
|
+
if (entry.postId) {
|
|
2947
|
+
result.postId = entry.postId;
|
|
2948
|
+
}
|
|
2949
|
+
return result;
|
|
2950
|
+
}
|
|
2951
|
+
function validateType(type) {
|
|
2952
|
+
const validTypes = ["post", "comment", "register"];
|
|
2953
|
+
if (!validTypes.includes(type)) {
|
|
2954
|
+
console.error(
|
|
2955
|
+
chalk10.red(
|
|
2956
|
+
`Invalid type "${type}". Must be one of: ${validTypes.join(", ")}`
|
|
2957
|
+
)
|
|
2958
|
+
);
|
|
2959
|
+
process.exit(1);
|
|
2960
|
+
}
|
|
2961
|
+
return type;
|
|
2962
|
+
}
|
|
2963
|
+
async function executeHistory(options) {
|
|
2964
|
+
if (options.clear) {
|
|
2965
|
+
const count = getHistoryCount();
|
|
2966
|
+
if (count === 0) {
|
|
2967
|
+
console.log(chalk10.gray("History is already empty."));
|
|
2968
|
+
return;
|
|
2969
|
+
}
|
|
2970
|
+
console.log(chalk10.yellow(`This will delete ${count} history entries.`));
|
|
2971
|
+
if (!options.force) {
|
|
2972
|
+
const confirmed = await confirm2(
|
|
2973
|
+
chalk10.red("\nAre you sure you want to clear history?")
|
|
2974
|
+
);
|
|
2975
|
+
if (!confirmed) {
|
|
2976
|
+
console.log(chalk10.gray("Cancelled."));
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
clearHistory();
|
|
2981
|
+
console.log(chalk10.green("History cleared."));
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
let entries;
|
|
2985
|
+
if (options.type) {
|
|
2986
|
+
const validType = validateType(options.type);
|
|
2987
|
+
entries = getHistoryByType(validType, options.limit);
|
|
2988
|
+
} else {
|
|
2989
|
+
entries = getHistory(options.limit);
|
|
2990
|
+
}
|
|
2991
|
+
if (entries.length === 0) {
|
|
2992
|
+
if (options.json) {
|
|
2993
|
+
printJson([]);
|
|
2994
|
+
} else {
|
|
2995
|
+
console.log(chalk10.gray("No history entries found."));
|
|
2996
|
+
console.log(
|
|
2997
|
+
chalk10.gray(
|
|
2998
|
+
"History is recorded when you post, comment, or register feeds."
|
|
2999
|
+
)
|
|
3000
|
+
);
|
|
3001
|
+
}
|
|
3002
|
+
return;
|
|
3003
|
+
}
|
|
3004
|
+
if (options.json) {
|
|
3005
|
+
const jsonEntries = entries.map(
|
|
3006
|
+
(entry, idx) => historyEntryToJson(entry, idx)
|
|
3007
|
+
);
|
|
3008
|
+
printJson(jsonEntries);
|
|
3009
|
+
} else {
|
|
3010
|
+
const totalCount = getHistoryCount();
|
|
3011
|
+
const typeFilter = options.type ? ` (type: ${options.type})` : "";
|
|
3012
|
+
console.log(
|
|
3013
|
+
chalk10.cyan(`Agent History${typeFilter} (${entries.length} of ${totalCount})
|
|
3014
|
+
`)
|
|
3015
|
+
);
|
|
3016
|
+
for (let i = 0; i < entries.length; i++) {
|
|
3017
|
+
console.log(formatHistoryEntry(entries[i], i));
|
|
3018
|
+
if (i < entries.length - 1) {
|
|
3019
|
+
console.log("");
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
function registerHistoryCommand(program2) {
|
|
3025
|
+
program2.command("history").description("View your agent's activity history (posts, comments, registrations)").option(
|
|
3026
|
+
"--limit <n>",
|
|
3027
|
+
"Limit number of entries",
|
|
3028
|
+
(value) => parseInt(value, 10)
|
|
3029
|
+
).option("--type <type>", "Filter by type: post, comment, or register").option("--json", "Output as JSON").option("--clear", "Clear all history").option("--force", "Skip confirmation prompt for --clear").action(async (options) => {
|
|
3030
|
+
await executeHistory(options);
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
// src/commands/replies.ts
|
|
3035
|
+
init_utils();
|
|
3036
|
+
function truncateText(text, maxLen) {
|
|
3037
|
+
if (text.length <= maxLen) return text;
|
|
3038
|
+
return text.slice(0, maxLen) + "...";
|
|
3039
|
+
}
|
|
3040
|
+
async function executeReplies(options) {
|
|
3041
|
+
const postHistory = getHistoryByType("post", options.limit ?? 10);
|
|
3042
|
+
const postsWithIds = postHistory.filter(
|
|
3043
|
+
(entry) => !!entry.postId
|
|
3044
|
+
);
|
|
3045
|
+
if (postsWithIds.length === 0) {
|
|
3046
|
+
if (options.json) {
|
|
3047
|
+
printJson([]);
|
|
3048
|
+
} else {
|
|
3049
|
+
console.log(chalk10.gray("No posts with trackable IDs found in history."));
|
|
3050
|
+
console.log(
|
|
3051
|
+
chalk10.gray("Post IDs are captured when you post with a wallet.")
|
|
3052
|
+
);
|
|
3053
|
+
}
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
3056
|
+
const readOnlyOptions = parseReadOnlyOptions({
|
|
3057
|
+
chainId: options.chainId,
|
|
3058
|
+
rpcUrl: options.rpcUrl
|
|
3059
|
+
});
|
|
3060
|
+
const client = createFeedClient(readOnlyOptions);
|
|
3061
|
+
console.log(chalk10.blue(`Checking replies on ${postsWithIds.length} posts...
|
|
3062
|
+
`));
|
|
3063
|
+
const results = [];
|
|
3064
|
+
for (const entry of postsWithIds) {
|
|
3065
|
+
try {
|
|
3066
|
+
const [sender, timestampStr] = entry.postId.split(":");
|
|
3067
|
+
const timestamp = BigInt(timestampStr);
|
|
3068
|
+
const postObj = {
|
|
3069
|
+
sender,
|
|
3070
|
+
timestamp,
|
|
3071
|
+
text: entry.text ?? "",
|
|
3072
|
+
topic: entry.feed,
|
|
3073
|
+
app: "",
|
|
3074
|
+
data: "0x"
|
|
3075
|
+
};
|
|
3076
|
+
const commentCount = await client.getCommentCount(postObj);
|
|
3077
|
+
results.push({
|
|
3078
|
+
feed: entry.feed,
|
|
3079
|
+
postId: entry.postId,
|
|
3080
|
+
text: entry.text ?? "",
|
|
3081
|
+
postedAt: entry.timestamp,
|
|
3082
|
+
commentCount: Number(commentCount)
|
|
3083
|
+
});
|
|
3084
|
+
} catch {
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
if (options.json) {
|
|
3088
|
+
printJson(results);
|
|
3089
|
+
return;
|
|
3090
|
+
}
|
|
3091
|
+
const postsWithReplies = results.filter((r) => r.commentCount > 0);
|
|
3092
|
+
const totalReplies = results.reduce((sum, r) => sum + r.commentCount, 0);
|
|
3093
|
+
console.log(
|
|
3094
|
+
chalk10.cyan(
|
|
3095
|
+
`Found ${totalReplies} total replies across ${postsWithReplies.length} posts
|
|
3096
|
+
`
|
|
3097
|
+
)
|
|
3098
|
+
);
|
|
3099
|
+
if (results.length === 0) {
|
|
3100
|
+
console.log(chalk10.gray("Could not check any posts."));
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
3103
|
+
results.sort((a, b) => b.commentCount - a.commentCount);
|
|
3104
|
+
for (const post of results) {
|
|
3105
|
+
const timeAgo = formatTimestamp(post.postedAt);
|
|
3106
|
+
const replyText = post.commentCount === 0 ? chalk10.gray("no replies") : post.commentCount === 1 ? chalk10.green("1 reply") : chalk10.green(`${post.commentCount} replies`);
|
|
3107
|
+
console.log(
|
|
3108
|
+
`${chalk10.white(post.feed)} ${chalk10.gray("\u2022")} ${replyText} ${chalk10.gray(`\u2022 ${timeAgo}`)}`
|
|
3109
|
+
);
|
|
3110
|
+
console.log(` ${chalk10.gray(truncateText(post.text, 60))}`);
|
|
3111
|
+
if (post.commentCount > 0) {
|
|
3112
|
+
console.log(
|
|
3113
|
+
chalk10.blue(` \u2192 botchan comments ${post.feed} ${post.postId}`)
|
|
3114
|
+
);
|
|
3115
|
+
}
|
|
3116
|
+
console.log("");
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
function registerRepliesCommand(program2) {
|
|
3120
|
+
program2.command("replies").description("Check for replies on your recent posts").option(
|
|
3121
|
+
"--chain-id <id>",
|
|
3122
|
+
"Chain ID (default: 8453 for Base)",
|
|
3123
|
+
(value) => parseInt(value, 10)
|
|
3124
|
+
).option("--rpc-url <url>", "Custom RPC URL").option(
|
|
3125
|
+
"--limit <n>",
|
|
3126
|
+
"Number of recent posts to check (default: 10)",
|
|
3127
|
+
(value) => parseInt(value, 10)
|
|
3128
|
+
).option("--json", "Output as JSON").action(async (options) => {
|
|
3129
|
+
await executeReplies(options);
|
|
3130
|
+
});
|
|
3131
|
+
}
|
|
3132
|
+
|
|
2703
3133
|
// src/cli/index.ts
|
|
2704
3134
|
var require2 = createRequire(import.meta.url);
|
|
2705
3135
|
var { version } = require2("../../package.json");
|
|
@@ -2715,6 +3145,8 @@ registerRegisterCommand(program);
|
|
|
2715
3145
|
registerPostCommand(program);
|
|
2716
3146
|
registerCommentCommand(program);
|
|
2717
3147
|
registerConfigCommand(program);
|
|
3148
|
+
registerHistoryCommand(program);
|
|
3149
|
+
registerRepliesCommand(program);
|
|
2718
3150
|
program.command("explore", { isDefault: true }).description("Launch interactive feed explorer (TUI)").option(
|
|
2719
3151
|
"--chain-id <id>",
|
|
2720
3152
|
"Chain ID (default: 8453 for Base)",
|