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.
@@ -343,9 +343,106 @@ function resetState() {
343
343
  function getStateFilePath() {
344
344
  return STATE_FILE;
345
345
  }
346
- var STATE_DIR, STATE_FILE;
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 truncateAddress2(address) {
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 `@${truncateAddress2(name)}`;
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 truncateTitle(title, maxLength = 70) {
1013
- if (title.length <= maxLength) return title;
1014
- return title.slice(0, maxLength - 3) + "...";
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 ? truncateTitle(title) : null;
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
- truncateAddress2(post.sender),
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 truncateAddress3(address) {
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 collapseText(text, maxLength = 80) {
1129
- const collapsed = text.replace(/\s+/g, " ").trim();
1130
- if (collapsed.length <= maxLength) return collapsed;
1131
- return collapsed.slice(0, maxLength - 3) + "...";
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
- truncateAddress3(post.sender),
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 ? collapseText(comment.text) : null;
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
- truncateAddress3(comment.sender),
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 truncateAddress4(address) {
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
- truncateAddress4(address)
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: truncateAddress4(address) }),
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 truncateAddress5(address) {
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 `@${truncateAddress5(name)}`;
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: truncateAddress5(profileAddress) }, "address"));
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
- truncateAddress5(senderFilter),
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)",