@net-protocol/cli 0.1.42 → 0.1.44

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.
@@ -6,16 +6,16 @@ import { createRequire } from 'module';
6
6
  import chalk4 from 'chalk';
7
7
  import * as fs6 from 'fs';
8
8
  import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
9
- import { OPTIMAL_CHUNK_SIZE, StorageClient, detectFileTypeFromBase64, base64ToDataUri, shouldSuggestXmlStorage, getStorageKeyBytes, encodeStorageKeyForUrl, chunkDataForStorage, CHUNKED_STORAGE_CONTRACT, STORAGE_CONTRACT as STORAGE_CONTRACT$1 } from '@net-protocol/storage';
10
- import { stringToHex, createWalletClient, http, hexToString, parseEther, encodeFunctionData, publicActions, concat, defineChain, decodeEventLog, createPublicClient, formatEther } from 'viem';
9
+ import { OPTIMAL_CHUNK_SIZE, StorageClient, detectFileTypeFromBase64, base64ToDataUri, shouldSuggestXmlStorage, getStorageKeyBytes, chunkDataForStorage, CHUNKED_STORAGE_CONTRACT, STORAGE_CONTRACT as STORAGE_CONTRACT$1, encodeStorageKeyForUrl } from '@net-protocol/storage';
10
+ import { createPublicClient, http, createWalletClient, stringToHex, hexToString, parseEther, encodeFunctionData, publicActions, concat, defineChain, decodeEventLog, formatEther, isAddress } from 'viem';
11
+ import { getSupportedChains, getNetContract, getChainName, getChainRpcUrls, getPublicClient, getBaseDataSuffix, NetClient, toBytes32, getChainBlockExplorer, NULL_ADDRESS, getChainSlug } from '@net-protocol/core';
11
12
  import { privateKeyToAccount } from 'viem/accounts';
12
- import { getNetContract, getChainName, getPublicClient, getChainRpcUrls, getBaseDataSuffix, NetClient, toBytes32, NULL_ADDRESS } from '@net-protocol/core';
13
- import { createRelayX402Client, createRelaySession, checkBackendWalletBalance, fundBackendWallet, batchTransactions, submitTransactionsViaRelay, waitForConfirmations, retryFailedTransactions as retryFailedTransactions$1 } from '@net-protocol/relay';
13
+ import { fundBackendWallet, checkBackendWalletBalance, createRelayX402Client, createRelaySession, batchTransactions, submitTransactionsViaRelay, waitForConfirmations, retryFailedTransactions as retryFailedTransactions$1 } from '@net-protocol/relay';
14
14
  import { isCommentTopic, parseCommentData, FeedRegistryClient, FeedClient, AgentRegistryClient, COMMENT_TOPIC_SUFFIX, FEED_TOPIC_PREFIX } from '@net-protocol/feeds';
15
15
  import '@net-protocol/chats';
16
16
  import { isNetrSupportedChain, NetrClient } from '@net-protocol/netr';
17
17
  import { PROFILE_PICTURE_STORAGE_KEY, PROFILE_METADATA_STORAGE_KEY, parseProfileMetadata, PROFILE_CANVAS_STORAGE_KEY, PROFILE_CSS_STORAGE_KEY, isValidUrl, getProfilePictureStorageArgs, STORAGE_CONTRACT, isValidXUsername, getProfileMetadataStorageArgs, isValidBio, isValidDisplayName, isValidTokenAddress, DEMO_THEMES, MAX_CSS_SIZE, isValidCSS, getProfileCSSStorageArgs, buildCSSPrompt } from '@net-protocol/profiles';
18
- import { base } from 'viem/chains';
18
+ import { baseSepolia, base } from 'viem/chains';
19
19
  import * as path from 'path';
20
20
  import { join } from 'path';
21
21
  import { BazaarClient } from '@net-protocol/bazaar';
@@ -23,6 +23,7 @@ import * as os from 'os';
23
23
  import { homedir } from 'os';
24
24
  import * as readline from 'readline';
25
25
  import { discoverTokenPool, PURE_ALPHA_STRATEGY, UNIV234_POOLS_STRATEGY, encodePoolKey, DYNAMIC_SPLIT_STRATEGY, getTokenScoreKey, UPVOTE_PRICE_ETH, UPVOTE_APP, ScoreClient, ALL_STRATEGY_ADDRESSES, NULL_ADDRESS as NULL_ADDRESS$1, UserUpvoteClient, calculateUpvoteCost, USER_UPVOTE_CONTRACT } from '@net-protocol/score';
26
+ import { RELAY_ACCESS_KEY, generateAgentChatTopic, AgentClient, isAgentChatTopic, parseAgentAddressFromTopic, buildSessionTypedData, NET_API_URL, exchangeSessionSignature, buildConversationAuthTypedData, NET_TESTNET_API_URL } from '@net-protocol/agents';
26
27
 
27
28
  var DEFAULT_CHAIN_ID = 8453;
28
29
  function getRequiredChainId(optionValue) {
@@ -129,6 +130,75 @@ function parseCommonOptionsWithDefault(options, supportsEncodeOnly = false) {
129
130
  rpcUrl: getRpcUrlWithBotchanFallback(options.rpcUrl)
130
131
  };
131
132
  }
133
+ var WEBSITE_BASE = "https://netprotocol.app";
134
+ var STORAGE_BASE = "https://storedon.net";
135
+ function chainSlug(chainId) {
136
+ return getChainSlug({ chainId }) ?? null;
137
+ }
138
+ function stripFeedPrefix(topic) {
139
+ const lower = topic.toLowerCase();
140
+ return lower.startsWith("feed-") ? lower.slice(5) : lower;
141
+ }
142
+ function feedUrl(chainId, feedName) {
143
+ const slug = chainSlug(chainId);
144
+ if (!slug) return null;
145
+ return `${WEBSITE_BASE}/app/feed/${slug}/${stripFeedPrefix(feedName)}`;
146
+ }
147
+ function walletUrl(chainId, address) {
148
+ const slug = chainSlug(chainId);
149
+ if (!slug) return null;
150
+ return `${WEBSITE_BASE}/app/feed/${slug}/${address.toLowerCase()}`;
151
+ }
152
+ function profileUrl(chainId, address) {
153
+ const slug = chainSlug(chainId);
154
+ if (!slug) return null;
155
+ return `${WEBSITE_BASE}/app/profile/${slug}/${address.toLowerCase()}`;
156
+ }
157
+ function tokenUrl(chainId, tokenAddress) {
158
+ const slug = chainSlug(chainId);
159
+ if (!slug) return null;
160
+ return `${WEBSITE_BASE}/app/token/${slug}/${tokenAddress.toLowerCase()}`;
161
+ }
162
+ function storageUrl(chainId, operatorAddress, key) {
163
+ return `${STORAGE_BASE}/net/${chainId}/storage/load/${operatorAddress.toLowerCase()}/${encodeStorageKeyForUrl(
164
+ key
165
+ )}`;
166
+ }
167
+ function explorerTxUrl(chainId, txHash) {
168
+ const base9 = getChainBlockExplorer({ chainId })?.url;
169
+ if (!base9) return null;
170
+ return `${base9}/tx/${txHash}`;
171
+ }
172
+ function explorerAddressUrl(chainId, address) {
173
+ const base9 = getChainBlockExplorer({ chainId })?.url;
174
+ if (!base9) return null;
175
+ return `${base9}/address/${address}`;
176
+ }
177
+ function postIdToCommentParam(postId) {
178
+ const colon = postId.indexOf(":");
179
+ if (colon === -1) return postId;
180
+ return `${postId.slice(0, colon)}-${postId.slice(colon + 1)}`;
181
+ }
182
+ function postPermalink(chainId, opts) {
183
+ const slug = chainSlug(chainId);
184
+ if (!slug) return null;
185
+ const params = new URLSearchParams();
186
+ if (opts.globalIndex != null) {
187
+ params.set("index", String(opts.globalIndex));
188
+ } else if (opts.topic != null && opts.topicIndex != null) {
189
+ params.set("topic", stripFeedPrefix(opts.topic));
190
+ params.set("index", String(opts.topicIndex));
191
+ } else if (opts.user != null && opts.userIndex != null) {
192
+ params.set("user", opts.user.toLowerCase());
193
+ params.set("index", String(opts.userIndex));
194
+ } else {
195
+ return null;
196
+ }
197
+ if (opts.commentId) {
198
+ params.set("commentId", postIdToCommentParam(opts.commentId));
199
+ }
200
+ return `${WEBSITE_BASE}/app/feed/${slug}/post?${params.toString()}`;
201
+ }
132
202
  async function checkNormalStorageExists(params) {
133
203
  const { storageClient, storageKey, operatorAddress, expectedContent } = params;
134
204
  const existing = await storageClient.get({
@@ -212,7 +282,7 @@ function extractTypedArgsFromTransaction(tx, type) {
212
282
  }
213
283
  function generateStorageUrl(operatorAddress, chainId, storageKey) {
214
284
  if (!operatorAddress) return void 0;
215
- return `https://storedon.net/net/${chainId}/storage/load/${operatorAddress}/${encodeStorageKeyForUrl(storageKey)}`;
285
+ return storageUrl(chainId, operatorAddress, storageKey);
216
286
  }
217
287
  async function checkTransactionExists(params) {
218
288
  const { storageClient, tx, operatorAddress } = params;
@@ -1181,6 +1251,11 @@ async function executeStorageRead(options) {
1181
1251
  key: options.key,
1182
1252
  operator: options.operator,
1183
1253
  chainId: readOnlyOptions.chainId,
1254
+ storageUrl: storageUrl(
1255
+ readOnlyOptions.chainId,
1256
+ options.operator,
1257
+ options.key
1258
+ ),
1184
1259
  text: result.text,
1185
1260
  data: options.raw ? result.data : result.data,
1186
1261
  isXml: result.isXml,
@@ -1373,7 +1448,7 @@ function registerStorageCommand(program2) {
1373
1448
  try {
1374
1449
  console.log(chalk4.blue(`\u{1F4C1} Reading file: ${options.file}`));
1375
1450
  const result = await uploadFile(uploadOptions);
1376
- const storageUrl = generateStorageUrl(
1451
+ const storageUrl2 = generateStorageUrl(
1377
1452
  result.operatorAddress,
1378
1453
  commonOptions.chainId,
1379
1454
  options.key
@@ -1383,8 +1458,8 @@ function registerStorageCommand(program2) {
1383
1458
  chalk4.green(
1384
1459
  `\u2713 All data already stored - skipping upload
1385
1460
  Storage Key: ${options.key}
1386
- Skipped: ${result.transactionsSkipped} transaction(s)${storageUrl ? `
1387
- Storage URL: ${chalk4.cyan(storageUrl)}` : ""}`
1461
+ Skipped: ${result.transactionsSkipped} transaction(s)${storageUrl2 ? `
1462
+ Storage URL: ${chalk4.cyan(storageUrl2)}` : ""}`
1388
1463
  )
1389
1464
  );
1390
1465
  process.exit(0);
@@ -1397,8 +1472,8 @@ function registerStorageCommand(program2) {
1397
1472
  Storage Type: ${result.storageType === "xml" ? "XML" : "Normal"}
1398
1473
  Transactions Sent: ${result.transactionsSent}
1399
1474
  Transactions Skipped: ${result.transactionsSkipped}
1400
- Final Transaction Hash: ${result.finalHash || "N/A"}${storageUrl ? `
1401
- Storage URL: ${chalk4.cyan(storageUrl)}` : ""}`
1475
+ Final Transaction Hash: ${result.finalHash || "N/A"}${storageUrl2 ? `
1476
+ Storage URL: ${chalk4.cyan(storageUrl2)}` : ""}`
1402
1477
  )
1403
1478
  );
1404
1479
  process.exit(0);
@@ -1455,7 +1530,7 @@ function registerStorageCommand(program2) {
1455
1530
  try {
1456
1531
  console.log(chalk4.blue(`\u{1F4C1} Reading file: ${options.file}`));
1457
1532
  const result = await previewFile(previewOptions);
1458
- const storageUrl = generateStorageUrl(
1533
+ const storageUrl2 = generateStorageUrl(
1459
1534
  result.operatorAddress,
1460
1535
  commonOptions.chainId,
1461
1536
  options.key
@@ -1491,8 +1566,8 @@ function registerStorageCommand(program2) {
1491
1566
  console.log(
1492
1567
  ` Operator Address: ${chalk4.gray(result.operatorAddress)}`
1493
1568
  );
1494
- if (storageUrl) {
1495
- console.log(` Storage URL: ${chalk4.cyan(storageUrl)}`);
1569
+ if (storageUrl2) {
1570
+ console.log(` Storage URL: ${chalk4.cyan(storageUrl2)}`);
1496
1571
  }
1497
1572
  if (result.needToStoreChunks === 0 && !result.metadataNeedsStorage) {
1498
1573
  console.log(
@@ -1566,9 +1641,9 @@ function registerStorageCommand(program2) {
1566
1641
  console.log(chalk4.blue(`\u{1F4C1} Reading file: ${options.file}`));
1567
1642
  console.log(chalk4.blue(`\u{1F517} Using relay API: ${options.apiUrl}`));
1568
1643
  const result = await uploadFileWithRelay(uploadRelayOptions);
1569
- const { privateKeyToAccount: privateKeyToAccount26 } = await import('viem/accounts');
1570
- const userAccount = privateKeyToAccount26(commonOptions.privateKey);
1571
- const storageUrl = generateStorageUrl(
1644
+ const { privateKeyToAccount: privateKeyToAccount28 } = await import('viem/accounts');
1645
+ const userAccount = privateKeyToAccount28(commonOptions.privateKey);
1646
+ const storageUrl2 = generateStorageUrl(
1572
1647
  userAccount.address,
1573
1648
  commonOptions.chainId,
1574
1649
  options.key
@@ -1584,8 +1659,8 @@ function registerStorageCommand(program2) {
1584
1659
  Metadata Submitted: ${result.metadataSubmitted ? "Yes" : "No (already exists)"}
1585
1660
  Backend Wallet: ${result.backendWalletAddress}
1586
1661
  Chunk Transaction Hashes: ${result.chunkTransactionHashes.length > 0 ? result.chunkTransactionHashes.join(", ") : "None"}${result.metadataTransactionHash ? `
1587
- Metadata Transaction Hash: ${result.metadataTransactionHash}` : ""}${storageUrl ? `
1588
- Storage URL: ${chalk4.cyan(storageUrl)}` : ""}`
1662
+ Metadata Transaction Hash: ${result.metadataTransactionHash}` : ""}${storageUrl2 ? `
1663
+ Storage URL: ${chalk4.cyan(storageUrl2)}` : ""}`
1589
1664
  )
1590
1665
  );
1591
1666
  process.exit(0);
@@ -1934,33 +2009,25 @@ function registerMessageCommand(program2) {
1934
2009
  messageCommand.addCommand(readCommand);
1935
2010
  messageCommand.addCommand(countCommand);
1936
2011
  }
1937
- var SUPPORTED_CHAINS = [
1938
- { id: 8453, name: "Base", type: "mainnet" },
1939
- { id: 1, name: "Ethereum", type: "mainnet" },
1940
- { id: 666666666, name: "Degen", type: "mainnet" },
1941
- { id: 5112, name: "Ham", type: "mainnet" },
1942
- { id: 57073, name: "Ink", type: "mainnet" },
1943
- { id: 130, name: "Unichain", type: "mainnet" },
1944
- { id: 999, name: "HyperEVM", type: "mainnet" },
1945
- { id: 9745, name: "Plasma", type: "mainnet" },
1946
- { id: 143, name: "Monad", type: "mainnet" },
1947
- { id: 84532, name: "Base Sepolia", type: "testnet" },
1948
- { id: 11155111, name: "Sepolia", type: "testnet" }
1949
- ];
1950
2012
  function registerChainsCommand(program2) {
1951
2013
  program2.command("chains").description("List supported chains").option("--json", "Output in JSON format").action((options) => {
2014
+ const chains = getSupportedChains();
1952
2015
  if (options.json) {
1953
- console.log(JSON.stringify(SUPPORTED_CHAINS, null, 2));
2016
+ console.log(JSON.stringify(chains, null, 2));
1954
2017
  return;
1955
2018
  }
1956
2019
  console.log(chalk4.white.bold("Supported Chains:\n"));
1957
2020
  console.log(chalk4.cyan("Mainnets:"));
1958
- SUPPORTED_CHAINS.filter((c) => c.type === "mainnet").forEach((chain) => {
1959
- console.log(` ${chalk4.white(chain.name)} ${chalk4.gray(`(${chain.id})`)}`);
2021
+ chains.filter((c) => c.type === "mainnet").forEach((chain) => {
2022
+ console.log(
2023
+ ` ${chalk4.white(chain.name)} ${chalk4.gray(`(${chain.chainId})`)}`
2024
+ );
1960
2025
  });
1961
2026
  console.log(chalk4.cyan("\nTestnets:"));
1962
- SUPPORTED_CHAINS.filter((c) => c.type === "testnet").forEach((chain) => {
1963
- console.log(` ${chalk4.white(chain.name)} ${chalk4.gray(`(${chain.id})`)}`);
2027
+ chains.filter((c) => c.type === "testnet").forEach((chain) => {
2028
+ console.log(
2029
+ ` ${chalk4.white(chain.name)} ${chalk4.gray(`(${chain.chainId})`)}`
2030
+ );
1964
2031
  });
1965
2032
  });
1966
2033
  }
@@ -2224,10 +2291,19 @@ async function executeTokenInfo(options) {
2224
2291
  const output = {
2225
2292
  address: tokenAddress,
2226
2293
  chainId: readOnlyOptions.chainId,
2294
+ tokenUrl: tokenUrl(readOnlyOptions.chainId, tokenAddress),
2295
+ explorerAddressUrl: explorerAddressUrl(
2296
+ readOnlyOptions.chainId,
2297
+ tokenAddress
2298
+ ),
2227
2299
  token: {
2228
2300
  name: token.name,
2229
2301
  symbol: token.symbol,
2230
2302
  deployer: token.deployer,
2303
+ deployerProfileUrl: profileUrl(
2304
+ readOnlyOptions.chainId,
2305
+ token.deployer
2306
+ ),
2231
2307
  image: token.image,
2232
2308
  animation: token.animation || null,
2233
2309
  fid: token.fid.toString(),
@@ -2426,6 +2502,8 @@ async function executeProfileGet(options) {
2426
2502
  const output = {
2427
2503
  address: options.address,
2428
2504
  chainId: readOnlyOptions.chainId,
2505
+ profileUrl: profileUrl(readOnlyOptions.chainId, options.address),
2506
+ walletUrl: walletUrl(readOnlyOptions.chainId, options.address),
2429
2507
  profilePicture: profilePicture || null,
2430
2508
  displayName: displayName || null,
2431
2509
  xUsername: xUsername || null,
@@ -5678,38 +5756,70 @@ function formatComment(comment, depth) {
5678
5756
  ];
5679
5757
  return lines.join("\n");
5680
5758
  }
5681
- function postToJson(post, index, commentCount) {
5759
+ function stripFeedPrefix2(topic) {
5760
+ const match = topic.match(/^(.+?):comments:/);
5761
+ const base9 = match ? match[1] : topic;
5762
+ return base9.replace(/^feed-/i, "");
5763
+ }
5764
+ function postToJson(post, options) {
5765
+ const { chainId, topicIndex, userIndex, globalIndex, commentCount } = options;
5766
+ const feedName = stripFeedPrefix2(post.topic);
5767
+ const postId = `${post.sender}:${post.timestamp}`;
5768
+ const permalink = postPermalink(chainId, {
5769
+ globalIndex,
5770
+ topic: post.topic,
5771
+ topicIndex,
5772
+ user: userIndex !== void 0 ? post.sender : void 0,
5773
+ userIndex
5774
+ });
5682
5775
  const result = {
5683
- index,
5776
+ postId,
5777
+ permalink,
5684
5778
  sender: post.sender,
5779
+ senderProfileUrl: profileUrl(chainId, post.sender),
5780
+ senderWalletUrl: walletUrl(chainId, post.sender),
5685
5781
  text: post.text,
5686
5782
  timestamp: Number(post.timestamp),
5783
+ feed: feedName,
5784
+ feedUrl: feedUrl(chainId, feedName),
5687
5785
  topic: post.topic
5688
5786
  };
5689
- if (commentCount !== void 0) {
5690
- result.commentCount = commentCount;
5691
- }
5692
- if (post.data && post.data !== "0x") {
5693
- result.data = post.data;
5694
- }
5787
+ if (topicIndex !== void 0) result.topicIndex = topicIndex;
5788
+ if (userIndex !== void 0) result.userIndex = userIndex;
5789
+ if (globalIndex !== void 0) result.globalIndex = globalIndex;
5790
+ if (commentCount !== void 0) result.commentCount = commentCount;
5791
+ if (post.data && post.data !== "0x") result.data = post.data;
5695
5792
  return result;
5696
5793
  }
5697
- function feedToJson(feed, index) {
5698
- return {
5794
+ function feedToJson(feed, index, chainId) {
5795
+ const result = {
5699
5796
  index,
5700
5797
  feedName: feed.feedName,
5701
5798
  registrant: feed.registrant,
5702
5799
  timestamp: feed.timestamp
5703
5800
  };
5801
+ if (chainId !== void 0) {
5802
+ result.feedUrl = feedUrl(chainId, feed.feedName);
5803
+ }
5804
+ return result;
5704
5805
  }
5705
- function commentToJson(comment, depth) {
5706
- return {
5806
+ function commentToJson(comment, options) {
5807
+ const { chainId, depth, parentPostUrl } = options;
5808
+ const commentParam = `${comment.sender}-${comment.timestamp}`;
5809
+ const permalink = parentPostUrl ? `${parentPostUrl}${parentPostUrl.includes("?") ? "&" : "?"}commentId=${commentParam}` : null;
5810
+ const result = {
5811
+ commentId: `${comment.sender}:${comment.timestamp}`,
5812
+ permalink,
5707
5813
  sender: comment.sender,
5814
+ senderProfileUrl: profileUrl(chainId, comment.sender),
5708
5815
  text: comment.text,
5709
5816
  timestamp: Number(comment.timestamp),
5710
- depth,
5711
- data: comment.data !== "0x" ? comment.data : void 0
5817
+ depth
5712
5818
  };
5819
+ if (comment.data !== "0x") {
5820
+ result.data = comment.data;
5821
+ }
5822
+ return result;
5713
5823
  }
5714
5824
  function formatAgent(agent, index) {
5715
5825
  const timestamp = formatTimestamp(agent.timestamp);
@@ -5719,12 +5829,17 @@ function formatAgent(agent, index) {
5719
5829
  ];
5720
5830
  return lines.join("\n");
5721
5831
  }
5722
- function agentToJson(agent, index) {
5723
- return {
5832
+ function agentToJson(agent, index, chainId) {
5833
+ const result = {
5724
5834
  index,
5725
5835
  address: agent.address,
5726
5836
  timestamp: agent.timestamp
5727
5837
  };
5838
+ if (chainId !== void 0) {
5839
+ result.profileUrl = profileUrl(chainId, agent.address);
5840
+ result.walletUrl = walletUrl(chainId, agent.address);
5841
+ }
5842
+ return result;
5728
5843
  }
5729
5844
  function printJson(data) {
5730
5845
  console.log(JSON.stringify(data, null, 2));
@@ -5742,7 +5857,11 @@ async function executeFeedList(options) {
5742
5857
  maxFeeds: options.limit ?? 50
5743
5858
  });
5744
5859
  if (options.json) {
5745
- printJson(feeds.map((feed, i) => feedToJson(feed, i)));
5860
+ printJson(
5861
+ feeds.map(
5862
+ (feed, i) => feedToJson(feed, i, readOnlyOptions.chainId)
5863
+ )
5864
+ );
5746
5865
  } else {
5747
5866
  if (feeds.length === 0) {
5748
5867
  console.log(chalk4.yellow("No registered feeds found"));
@@ -5968,26 +6087,31 @@ async function executeFeedRead(feed, options) {
5968
6087
  return;
5969
6088
  }
5970
6089
  const fetchLimit = options.sender ? Math.max(limit * 5, 100) : limit;
5971
- let posts = await client.getFeedPosts({
6090
+ const fetched = await client.getFeedPostsWithIndex({
5972
6091
  topic: normalizedFeed,
5973
6092
  maxPosts: fetchLimit
5974
6093
  });
6094
+ let postsWithIndex = fetched.messages.map((post, i) => ({
6095
+ post,
6096
+ topicIndex: fetched.startIndex + i
6097
+ }));
5975
6098
  if (options.sender) {
5976
6099
  const senderLower = options.sender.toLowerCase();
5977
- posts = posts.filter(
5978
- (post) => post.sender.toLowerCase() === senderLower
6100
+ postsWithIndex = postsWithIndex.filter(
6101
+ ({ post }) => post.sender.toLowerCase() === senderLower
5979
6102
  );
5980
- posts = posts.slice(0, limit);
6103
+ postsWithIndex = postsWithIndex.slice(0, limit);
5981
6104
  }
5982
6105
  if (options.unseen) {
5983
6106
  const lastSeen = getLastSeenTimestamp(normalizedFeed);
5984
6107
  const myAddress = getMyAddress();
5985
- posts = posts.filter((post) => {
6108
+ postsWithIndex = postsWithIndex.filter(({ post }) => {
5986
6109
  const isNew = lastSeen === null || Number(post.timestamp) > lastSeen;
5987
6110
  const isFromOther = !myAddress || post.sender.toLowerCase() !== myAddress;
5988
6111
  return isNew && isFromOther;
5989
6112
  });
5990
6113
  }
6114
+ const posts = postsWithIndex.map(({ post }) => post);
5991
6115
  if (options.markSeen) {
5992
6116
  const allPosts = await client.getFeedPosts({
5993
6117
  topic: normalizedFeed,
@@ -6003,7 +6127,13 @@ async function executeFeedRead(feed, options) {
6003
6127
  );
6004
6128
  if (options.json) {
6005
6129
  printJson(
6006
- posts.map((post, i) => postToJson(post, i, commentCounts[i]))
6130
+ postsWithIndex.map(
6131
+ ({ post, topicIndex }, i) => postToJson(post, {
6132
+ chainId: readOnlyOptions.chainId,
6133
+ topicIndex,
6134
+ commentCount: commentCounts[i]
6135
+ })
6136
+ )
6007
6137
  );
6008
6138
  } else {
6009
6139
  if (posts.length === 0) {
@@ -6065,6 +6195,34 @@ async function executeTransaction(walletClient, txConfig) {
6065
6195
  });
6066
6196
  return hash;
6067
6197
  }
6198
+ async function getMessageIndicesFromTx(params) {
6199
+ const publicClient = getPublicClient({
6200
+ chainId: params.chainId,
6201
+ rpcUrl: params.rpcUrl
6202
+ });
6203
+ const netContract = getNetContract(params.chainId);
6204
+ const contractAddress = netContract.address.toLowerCase();
6205
+ const receipt = await publicClient.waitForTransactionReceipt({
6206
+ hash: params.txHash
6207
+ });
6208
+ const indices = [];
6209
+ for (const log of receipt.logs) {
6210
+ if (log.address.toLowerCase() !== contractAddress) continue;
6211
+ try {
6212
+ const decoded = decodeEventLog({
6213
+ abi: netContract.abi,
6214
+ data: log.data,
6215
+ topics: log.topics
6216
+ });
6217
+ if (decoded.eventName === "MessageSent") {
6218
+ const args = decoded.args;
6219
+ indices.push(Number(args.messageIndex));
6220
+ }
6221
+ } catch {
6222
+ }
6223
+ }
6224
+ return indices;
6225
+ }
6068
6226
 
6069
6227
  // src/commands/feed/post.ts
6070
6228
  var MAX_MESSAGE_LENGTH = 4e3;
@@ -6116,11 +6274,23 @@ ${options.body}` : message;
6116
6274
  commonOptions.chainId,
6117
6275
  commonOptions.rpcUrl
6118
6276
  );
6119
- console.log(chalk4.blue(`Posting to feed "${normalizedFeed}"...`));
6277
+ if (!options.json) {
6278
+ console.log(chalk4.blue(`Posting to feed "${normalizedFeed}"...`));
6279
+ }
6120
6280
  try {
6121
6281
  const hash = await executeTransaction(walletClient, txConfig);
6122
6282
  const senderAddress = walletClient.account.address;
6123
6283
  let postId;
6284
+ let globalIndex;
6285
+ try {
6286
+ const indices = await getMessageIndicesFromTx({
6287
+ chainId: commonOptions.chainId,
6288
+ rpcUrl: commonOptions.rpcUrl,
6289
+ txHash: hash
6290
+ });
6291
+ globalIndex = indices[0];
6292
+ } catch {
6293
+ }
6124
6294
  try {
6125
6295
  const posts = await client.getFeedPosts({
6126
6296
  topic: normalizedFeed,
@@ -6142,19 +6312,36 @@ ${options.body}` : message;
6142
6312
  sender: senderAddress,
6143
6313
  text: fullMessage,
6144
6314
  postId
6145
- // Now we have the actual post ID for checking comments later
6146
6315
  });
6316
+ const permalink = postPermalink(commonOptions.chainId, {
6317
+ globalIndex
6318
+ });
6319
+ if (options.json) {
6320
+ printJson({
6321
+ success: true,
6322
+ txHash: hash,
6323
+ explorerTxUrl: explorerTxUrl(commonOptions.chainId, hash),
6324
+ postId,
6325
+ globalIndex,
6326
+ permalink,
6327
+ feed: normalizedFeed,
6328
+ feedUrl: feedUrl(commonOptions.chainId, normalizedFeed),
6329
+ sender: senderAddress,
6330
+ senderProfileUrl: profileUrl(commonOptions.chainId, senderAddress),
6331
+ text: fullMessage
6332
+ });
6333
+ return;
6334
+ }
6147
6335
  const displayText = options.body ? `${message} (+ body)` : message;
6148
- const postIdInfo = postId ? `
6149
- Post ID: ${postId}` : "";
6150
- console.log(
6151
- chalk4.green(
6152
- `Message posted successfully!
6153
- Transaction: ${hash}
6154
- Feed: ${normalizedFeed}${postIdInfo}
6155
- Text: ${displayText}`
6156
- )
6157
- );
6336
+ const lines = [
6337
+ `Message posted successfully!`,
6338
+ ` Transaction: ${hash}`,
6339
+ ` Feed: ${normalizedFeed}`
6340
+ ];
6341
+ if (postId) lines.push(` Post ID: ${postId}`);
6342
+ if (permalink) lines.push(` Permalink: ${permalink}`);
6343
+ lines.push(` Text: ${displayText}`);
6344
+ console.log(chalk4.green(lines.join("\n")));
6158
6345
  } catch (error) {
6159
6346
  exitWithError(
6160
6347
  `Failed to post message: ${error instanceof Error ? error.message : String(error)}`
@@ -6169,7 +6356,10 @@ function registerFeedPostCommand(parent) {
6169
6356
  ).option("--rpc-url <url>", "Custom RPC URL").option("--private-key <key>", "Private key (0x-prefixed)").option(
6170
6357
  "--encode-only",
6171
6358
  "Output transaction data as JSON instead of executing"
6172
- ).option("--data <data>", "Optional data to attach to the post").option("--body <text>", "Post body (message becomes the title)").action(async (feed, message, options) => {
6359
+ ).option("--data <data>", "Optional data to attach to the post").option("--body <text>", "Post body (message becomes the title)").option(
6360
+ "--json",
6361
+ "Output structured JSON (includes permalink and other URLs) after submission"
6362
+ ).action(async (feed, message, options) => {
6173
6363
  await executeFeedPost(feed, message, options);
6174
6364
  });
6175
6365
  }
@@ -6198,11 +6388,6 @@ function parsePostId(postId) {
6198
6388
  timestamp
6199
6389
  };
6200
6390
  }
6201
- function findPostByParsedId(posts, parsedId) {
6202
- return posts.find(
6203
- (p) => p.sender.toLowerCase() === parsedId.sender.toLowerCase() && p.timestamp === parsedId.timestamp
6204
- );
6205
- }
6206
6391
 
6207
6392
  // src/commands/feed/comment-write.ts
6208
6393
  var MAX_MESSAGE_LENGTH2 = 4e3;
@@ -6235,17 +6420,20 @@ async function executeFeedCommentWrite(feed, postId, message, options) {
6235
6420
  `Feed "${normalizedFeed}" has no posts. Cannot find post ${postId}.`
6236
6421
  );
6237
6422
  }
6238
- const posts = await client.getFeedPosts({
6423
+ const fetched = await client.getFeedPostsWithIndex({
6239
6424
  topic: normalizedFeed,
6240
6425
  maxPosts: 100
6241
- // Fetch enough to find the post
6242
6426
  });
6243
- const targetPost = findPostByParsedId(posts, parsedId);
6427
+ const matchOffset = fetched.messages.findIndex(
6428
+ (p) => p.sender.toLowerCase() === parsedId.sender.toLowerCase() && p.timestamp === parsedId.timestamp
6429
+ );
6430
+ const targetPost = matchOffset >= 0 ? fetched.messages[matchOffset] : void 0;
6244
6431
  if (!targetPost) {
6245
6432
  exitWithError(
6246
6433
  `Post not found with ID ${postId} in feed "${normalizedFeed}". Make sure the sender and timestamp are correct.`
6247
6434
  );
6248
6435
  }
6436
+ const parentTopicIndex = fetched.startIndex + matchOffset;
6249
6437
  const txConfig = client.prepareComment({
6250
6438
  post: targetPost,
6251
6439
  text: message
@@ -6269,26 +6457,81 @@ async function executeFeedCommentWrite(feed, postId, message, options) {
6269
6457
  commonOptions.chainId,
6270
6458
  commonOptions.rpcUrl
6271
6459
  );
6272
- console.log(chalk4.blue(`Commenting on post ${postId}...`));
6460
+ if (!options.json) {
6461
+ console.log(chalk4.blue(`Commenting on post ${postId}...`));
6462
+ }
6273
6463
  try {
6274
6464
  const hash = await executeTransaction(walletClient, txConfig);
6465
+ const senderAddress = walletClient.account.address;
6466
+ let globalIndex;
6467
+ try {
6468
+ const indices = await getMessageIndicesFromTx({
6469
+ chainId: commonOptions.chainId,
6470
+ rpcUrl: commonOptions.rpcUrl,
6471
+ txHash: hash
6472
+ });
6473
+ globalIndex = indices[0];
6474
+ } catch {
6475
+ }
6476
+ let commentTimestamp;
6477
+ if (globalIndex !== void 0) {
6478
+ try {
6479
+ const netClient = createNetClient(commonOptions);
6480
+ const fetchedComment = await netClient.getMessageAtIndex({
6481
+ messageIndex: globalIndex
6482
+ });
6483
+ if (fetchedComment) {
6484
+ commentTimestamp = Number(fetchedComment.timestamp);
6485
+ }
6486
+ } catch {
6487
+ }
6488
+ }
6275
6489
  addHistoryEntry({
6276
6490
  type: "comment",
6277
6491
  txHash: hash,
6278
6492
  chainId: commonOptions.chainId,
6279
6493
  feed: normalizedFeed,
6280
- sender: walletClient.account.address,
6494
+ sender: senderAddress,
6281
6495
  text: message,
6282
6496
  postId
6283
6497
  });
6284
- console.log(
6285
- chalk4.green(
6286
- `Comment posted successfully!
6287
- Transaction: ${hash}
6288
- Post: ${postId}
6289
- Comment: ${message}`
6290
- )
6291
- );
6498
+ const parentPostUrl = postPermalink(commonOptions.chainId, {
6499
+ topic: normalizedFeed,
6500
+ topicIndex: parentTopicIndex
6501
+ });
6502
+ const commentPermalink = commentTimestamp !== void 0 ? postPermalink(commonOptions.chainId, {
6503
+ topic: normalizedFeed,
6504
+ topicIndex: parentTopicIndex,
6505
+ commentId: `${senderAddress}-${commentTimestamp}`
6506
+ }) : postPermalink(commonOptions.chainId, { globalIndex });
6507
+ if (options.json) {
6508
+ printJson({
6509
+ success: true,
6510
+ txHash: hash,
6511
+ explorerTxUrl: explorerTxUrl(commonOptions.chainId, hash),
6512
+ globalIndex,
6513
+ permalink: commentPermalink,
6514
+ parentPostId: postId,
6515
+ parentPostUrl,
6516
+ feed: normalizedFeed,
6517
+ feedUrl: feedUrl(commonOptions.chainId, normalizedFeed),
6518
+ sender: senderAddress,
6519
+ senderProfileUrl: profileUrl(commonOptions.chainId, senderAddress),
6520
+ text: message,
6521
+ ...commentTimestamp !== void 0 && {
6522
+ commentId: `${senderAddress}:${commentTimestamp}`
6523
+ }
6524
+ });
6525
+ return;
6526
+ }
6527
+ const lines = [
6528
+ `Comment posted successfully!`,
6529
+ ` Transaction: ${hash}`,
6530
+ ` Post: ${postId}`
6531
+ ];
6532
+ if (commentPermalink) lines.push(` Permalink: ${commentPermalink}`);
6533
+ lines.push(` Comment: ${message}`);
6534
+ console.log(chalk4.green(lines.join("\n")));
6292
6535
  } catch (error) {
6293
6536
  exitWithError(
6294
6537
  `Failed to post comment: ${error instanceof Error ? error.message : String(error)}`
@@ -6305,6 +6548,9 @@ function registerFeedCommentWriteCommand(parent) {
6305
6548
  ).option("--rpc-url <url>", "Custom RPC URL").option("--private-key <key>", "Private key (0x-prefixed)").option(
6306
6549
  "--encode-only",
6307
6550
  "Output transaction data as JSON instead of executing"
6551
+ ).option(
6552
+ "--json",
6553
+ "Output structured JSON (includes permalink and other URLs) after submission"
6308
6554
  ).action(async (feed, postId, message, options) => {
6309
6555
  await executeFeedCommentWrite(feed, postId, message, options);
6310
6556
  });
@@ -6331,17 +6577,25 @@ async function executeFeedCommentRead(feed, postId, options) {
6331
6577
  `Feed "${normalizedFeed}" has no posts. Cannot find post ${postId}.`
6332
6578
  );
6333
6579
  }
6334
- const posts = await client.getFeedPosts({
6580
+ const fetched = await client.getFeedPostsWithIndex({
6335
6581
  topic: normalizedFeed,
6336
6582
  maxPosts: 100
6337
6583
  // Fetch enough to find the post
6338
6584
  });
6339
- const targetPost = findPostByParsedId(posts, parsedId);
6585
+ const matchIndex = fetched.messages.findIndex(
6586
+ (p) => p.sender.toLowerCase() === parsedId.sender.toLowerCase() && p.timestamp === parsedId.timestamp
6587
+ );
6588
+ const targetPost = matchIndex >= 0 ? fetched.messages[matchIndex] : void 0;
6340
6589
  if (!targetPost) {
6341
6590
  exitWithError(
6342
6591
  `Post not found with ID ${postId} in feed "${normalizedFeed}". Make sure the sender and timestamp are correct.`
6343
6592
  );
6344
6593
  }
6594
+ const parentTopicIndex = fetched.startIndex + matchIndex;
6595
+ const parentPostUrl = postPermalink(readOnlyOptions.chainId, {
6596
+ topic: normalizedFeed,
6597
+ topicIndex: parentTopicIndex
6598
+ });
6345
6599
  const commentCount = await client.getCommentCount(targetPost);
6346
6600
  if (commentCount === 0) {
6347
6601
  if (options.json) {
@@ -6362,7 +6616,11 @@ async function executeFeedCommentRead(feed, postId, options) {
6362
6616
  if (options.json) {
6363
6617
  printJson(
6364
6618
  commentsWithDepth.map(
6365
- ({ comment, depth }) => commentToJson(comment, depth)
6619
+ ({ comment, depth }) => commentToJson(comment, {
6620
+ chainId: readOnlyOptions.chainId,
6621
+ depth,
6622
+ parentPostUrl
6623
+ })
6366
6624
  )
6367
6625
  );
6368
6626
  } else {
@@ -6503,8 +6761,10 @@ async function executeFeedReplies(options) {
6503
6761
  rpcUrl: options.rpcUrl
6504
6762
  });
6505
6763
  const client = createFeedClient(readOnlyOptions);
6506
- console.log(chalk4.blue(`Checking replies on ${postsWithIds.length} posts...
6764
+ if (!options.json) {
6765
+ console.log(chalk4.blue(`Checking replies on ${postsWithIds.length} posts...
6507
6766
  `));
6767
+ }
6508
6768
  const results = [];
6509
6769
  for (const entry of postsWithIds) {
6510
6770
  try {
@@ -6519,12 +6779,31 @@ async function executeFeedReplies(options) {
6519
6779
  data: "0x"
6520
6780
  };
6521
6781
  const commentCount = await client.getCommentCount(postObj);
6782
+ let permalink = null;
6783
+ try {
6784
+ const indices = await getMessageIndicesFromTx({
6785
+ chainId: readOnlyOptions.chainId,
6786
+ rpcUrl: readOnlyOptions.rpcUrl,
6787
+ txHash: entry.txHash
6788
+ });
6789
+ if (indices[0] !== void 0) {
6790
+ permalink = postPermalink(readOnlyOptions.chainId, {
6791
+ globalIndex: indices[0]
6792
+ });
6793
+ }
6794
+ } catch {
6795
+ }
6522
6796
  results.push({
6523
6797
  feed: entry.feed,
6524
6798
  postId: entry.postId,
6525
6799
  text: entry.text ?? "",
6526
6800
  postedAt: entry.timestamp,
6527
- commentCount: Number(commentCount)
6801
+ commentCount: Number(commentCount),
6802
+ permalink,
6803
+ feedUrl: feedUrl(readOnlyOptions.chainId, entry.feed),
6804
+ senderProfileUrl: entry.sender ? profileUrl(readOnlyOptions.chainId, entry.sender) : null,
6805
+ explorerTxUrl: explorerTxUrl(readOnlyOptions.chainId, entry.txHash),
6806
+ txHash: entry.txHash
6528
6807
  });
6529
6808
  } catch {
6530
6809
  }
@@ -6609,7 +6888,14 @@ async function executeFeedPosts(address, options) {
6609
6888
  endIndex: count
6610
6889
  });
6611
6890
  if (options.json) {
6612
- printJson(messages.map((msg, i) => postToJson(msg, i)));
6891
+ printJson(
6892
+ messages.map(
6893
+ (msg, i) => postToJson(msg, {
6894
+ chainId: readOnlyOptions.chainId,
6895
+ userIndex: startIndex + i
6896
+ })
6897
+ )
6898
+ );
6613
6899
  } else {
6614
6900
  console.log(
6615
6901
  chalk4.white(`Found ${messages.length} post(s) by ${address}:
@@ -6787,11 +7073,14 @@ function historyEntryToJson(entry, index) {
6787
7073
  type: entry.type,
6788
7074
  timestamp: entry.timestamp,
6789
7075
  txHash: entry.txHash,
7076
+ explorerTxUrl: explorerTxUrl(entry.chainId, entry.txHash),
6790
7077
  chainId: entry.chainId,
6791
- feed: entry.feed
7078
+ feed: entry.feed,
7079
+ feedUrl: feedUrl(entry.chainId, entry.feed)
6792
7080
  };
6793
7081
  if (entry.sender) {
6794
7082
  result.sender = entry.sender;
7083
+ result.senderProfileUrl = profileUrl(entry.chainId, entry.sender);
6795
7084
  }
6796
7085
  if (entry.text) {
6797
7086
  result.text = entry.text;
@@ -6965,7 +7254,9 @@ async function executeListAgents(options) {
6965
7254
  if (options.json) {
6966
7255
  printJson({
6967
7256
  totalCount,
6968
- agents: agents.map((agent, i) => agentToJson(agent, i))
7257
+ agents: agents.map(
7258
+ (agent, i) => agentToJson(agent, i, readOnlyOptions.chainId)
7259
+ )
6969
7260
  });
6970
7261
  } else {
6971
7262
  if (agents.length === 0) {
@@ -7024,9 +7315,13 @@ async function executeFeedVerifyClaim(txHash, options) {
7024
7315
  const netClient = createNetClient(readOnlyOptions);
7025
7316
  const existingHistory = getHistory();
7026
7317
  if (existingHistory.some((entry) => entry.txHash === txHash)) {
7027
- console.log(
7028
- chalk4.yellow("Transaction already recorded in history. Skipping.")
7029
- );
7318
+ if (options.json) {
7319
+ printJson({ alreadyRecorded: true, txHash });
7320
+ } else {
7321
+ console.log(
7322
+ chalk4.yellow("Transaction already recorded in history. Skipping.")
7323
+ );
7324
+ }
7030
7325
  return;
7031
7326
  }
7032
7327
  const receipt = await publicClient.getTransactionReceipt({ hash: txHash }).catch(
@@ -7061,11 +7356,13 @@ async function executeFeedVerifyClaim(txHash, options) {
7061
7356
  "Transaction does not contain any Net protocol messages."
7062
7357
  );
7063
7358
  }
7064
- console.log(
7065
- chalk4.blue(
7066
- `Found ${messageSentEvents.length} message(s) in transaction. Fetching details...`
7067
- )
7068
- );
7359
+ if (!options.json) {
7360
+ console.log(
7361
+ chalk4.blue(
7362
+ `Found ${messageSentEvents.length} message(s) in transaction. Fetching details...`
7363
+ )
7364
+ );
7365
+ }
7069
7366
  const messages = await Promise.all(
7070
7367
  messageSentEvents.map(
7071
7368
  (event) => netClient.getMessageAtIndex({
@@ -7073,41 +7370,40 @@ async function executeFeedVerifyClaim(txHash, options) {
7073
7370
  })
7074
7371
  )
7075
7372
  );
7373
+ const entries = [];
7076
7374
  let recorded = 0;
7077
7375
  for (let i = 0; i < messages.length; i++) {
7078
7376
  const message = messages[i];
7079
7377
  if (!message) {
7080
- console.log(
7081
- chalk4.yellow(
7082
- ` Could not fetch message at index ${messageSentEvents[i].messageIndex}. Skipping.`
7083
- )
7084
- );
7378
+ if (!options.json) {
7379
+ console.log(
7380
+ chalk4.yellow(
7381
+ ` Could not fetch message at index ${messageSentEvents[i].messageIndex}. Skipping.`
7382
+ )
7383
+ );
7384
+ }
7085
7385
  continue;
7086
7386
  }
7387
+ const globalIndex = Number(messageSentEvents[i].messageIndex);
7087
7388
  const feedName = extractFeedName(message.topic);
7088
7389
  const isComment = isCommentTopic(message.topic);
7089
7390
  let type;
7090
7391
  let postId;
7091
- let label;
7392
+ let parentPostId;
7393
+ let permalink = null;
7092
7394
  if (isComment) {
7093
7395
  type = "comment";
7094
7396
  const commentData = parseCommentData(message.data);
7095
- postId = commentData ? `${commentData.parentSender}:${commentData.parentTimestamp}` : void 0;
7096
- label = `Verified comment:
7097
- Feed: ${feedName}
7098
- Sender: ${message.sender}
7099
- Text: ${message.text}
7100
- Parent post: ${postId ?? "unknown"}
7101
- Tx: ${txHash}`;
7397
+ parentPostId = commentData ? `${commentData.parentSender}:${commentData.parentTimestamp}` : void 0;
7398
+ const commentParam = `${message.sender}-${Number(message.timestamp)}`;
7399
+ permalink = postPermalink(readOnlyOptions.chainId, {
7400
+ globalIndex,
7401
+ commentId: commentParam
7402
+ });
7102
7403
  } else {
7103
7404
  type = "post";
7104
7405
  postId = createPostId(message);
7105
- label = `Verified post:
7106
- Feed: ${feedName}
7107
- Sender: ${message.sender}
7108
- Text: ${message.text}
7109
- Post ID: ${postId}
7110
- Tx: ${txHash}`;
7406
+ permalink = postPermalink(readOnlyOptions.chainId, { globalIndex });
7111
7407
  }
7112
7408
  addHistoryEntry({
7113
7409
  type,
@@ -7116,11 +7412,49 @@ async function executeFeedVerifyClaim(txHash, options) {
7116
7412
  feed: feedName,
7117
7413
  sender: message.sender,
7118
7414
  text: message.text,
7119
- postId
7415
+ postId: type === "comment" ? parentPostId : postId
7120
7416
  });
7121
- console.log(chalk4.green(` ${label}`));
7417
+ const entry = {
7418
+ type,
7419
+ txHash,
7420
+ explorerTxUrl: explorerTxUrl(readOnlyOptions.chainId, txHash),
7421
+ globalIndex,
7422
+ permalink,
7423
+ feed: feedName,
7424
+ feedUrl: feedUrl(readOnlyOptions.chainId, feedName),
7425
+ sender: message.sender,
7426
+ senderProfileUrl: profileUrl(
7427
+ readOnlyOptions.chainId,
7428
+ message.sender
7429
+ ),
7430
+ text: message.text,
7431
+ timestamp: Number(message.timestamp)
7432
+ };
7433
+ if (type === "post") entry.postId = postId;
7434
+ if (type === "comment") entry.parentPostId = parentPostId;
7435
+ entries.push(entry);
7436
+ if (!options.json) {
7437
+ const label = type === "comment" ? `Verified comment:
7438
+ Feed: ${feedName}
7439
+ Sender: ${message.sender}
7440
+ Text: ${message.text}
7441
+ Parent post: ${parentPostId ?? "unknown"}
7442
+ Permalink: ${permalink ?? "(unavailable)"}
7443
+ Tx: ${txHash}` : `Verified post:
7444
+ Feed: ${feedName}
7445
+ Sender: ${message.sender}
7446
+ Text: ${message.text}
7447
+ Post ID: ${postId}
7448
+ Permalink: ${permalink ?? "(unavailable)"}
7449
+ Tx: ${txHash}`;
7450
+ console.log(chalk4.green(` ${label}`));
7451
+ }
7122
7452
  recorded++;
7123
7453
  }
7454
+ if (options.json) {
7455
+ printJson({ recorded, entries });
7456
+ return;
7457
+ }
7124
7458
  if (recorded > 0) {
7125
7459
  console.log(
7126
7460
  chalk4.green(`
@@ -7135,7 +7469,10 @@ function registerFeedVerifyClaimCommand(parent) {
7135
7469
  "--chain-id <id>",
7136
7470
  "Chain ID (default: 8453 for Base)",
7137
7471
  (value) => parseInt(value, 10)
7138
- ).option("--rpc-url <url>", "Custom RPC URL").action(async (txHash, options) => {
7472
+ ).option("--rpc-url <url>", "Custom RPC URL").option(
7473
+ "--json",
7474
+ "Output structured JSON (includes permalink and other URLs)"
7475
+ ).action(async (txHash, options) => {
7139
7476
  await executeFeedVerifyClaim(txHash, options);
7140
7477
  });
7141
7478
  }
@@ -7331,6 +7668,7 @@ async function executeGetUpvotes(options) {
7331
7668
  JSON.stringify(
7332
7669
  {
7333
7670
  tokenAddress,
7671
+ tokenUrl: tokenUrl(readOnlyOptions.chainId, tokenAddress),
7334
7672
  scoreKey,
7335
7673
  total,
7336
7674
  strategies: strategyCounts.map((s) => ({
@@ -7499,6 +7837,8 @@ async function executeGetUserUpvotes(options) {
7499
7837
  {
7500
7838
  address: userAddress,
7501
7839
  chainId: readOnlyOptions.chainId,
7840
+ profileUrl: profileUrl(readOnlyOptions.chainId, userAddress),
7841
+ walletUrl: walletUrl(readOnlyOptions.chainId, userAddress),
7502
7842
  upvotesGiven: Number(given),
7503
7843
  upvotesReceived: Number(received),
7504
7844
  upvotePriceWei: upvotePrice.toString(),
@@ -7540,6 +7880,845 @@ function registerUpvoteCommand(program2) {
7540
7880
  registerUpvoteUserCommand(upvoteCommand);
7541
7881
  registerGetUserUpvotesCommand(upvoteCommand);
7542
7882
  }
7883
+ var VALID_RUN_MODES = ["auto", "feeds", "chats"];
7884
+ function addAuthOptions(cmd) {
7885
+ return addCommonOptions(cmd).option("--private-key <key>", "Private key (0x-prefixed)").option(
7886
+ "--session-token <token>",
7887
+ "Pre-existing session token (alternative to --private-key)"
7888
+ ).option(
7889
+ "--operator <address>",
7890
+ "Operator address (required with --session-token)"
7891
+ );
7892
+ }
7893
+ function addCommonOptions(cmd) {
7894
+ return cmd.option("--chain-id <id>", "Chain ID (default: 8453)", (v) => parseInt(v, 10)).option("--rpc-url <url>", "Custom RPC URL").option("--api-url <url>", "Net Protocol API URL");
7895
+ }
7896
+ function addFilterOptions(cmd) {
7897
+ return cmd.option("--include-feed <pattern...>", "Only engage with matching feeds").option("--exclude-feed <pattern...>", "Never engage with matching feeds").option("--preferred-feed <pattern...>", "Prioritize matching feeds").option("--chat-topic <topic...>", "Chat topics to participate in");
7898
+ }
7899
+ function addProfileOptions(cmd) {
7900
+ return cmd.option("--display-name <name>", "Agent display name").option("--bio <text>", "Agent bio");
7901
+ }
7902
+ function buildFilters(options) {
7903
+ const filters = {};
7904
+ if (options.includeFeed?.length) filters.includeFeedPatterns = options.includeFeed;
7905
+ if (options.excludeFeed?.length) filters.excludeFeedPatterns = options.excludeFeed;
7906
+ if (options.preferredFeed?.length) filters.preferredFeedPatterns = options.preferredFeed;
7907
+ if (options.chatTopic?.length) filters.preferredChatTopics = options.chatTopic;
7908
+ return Object.keys(filters).length > 0 ? filters : void 0;
7909
+ }
7910
+ function buildProfile(options) {
7911
+ const profile = {};
7912
+ if (options.displayName) profile.displayName = options.displayName;
7913
+ if (options.bio) profile.bio = options.bio;
7914
+ return Object.keys(profile).length > 0 ? profile : void 0;
7915
+ }
7916
+ function parseRunMode(raw) {
7917
+ const mode = raw ?? "auto";
7918
+ if (!VALID_RUN_MODES.includes(mode)) {
7919
+ exitWithError(`Invalid mode "${mode}". Must be one of: ${VALID_RUN_MODES.join(", ")}`);
7920
+ }
7921
+ return mode;
7922
+ }
7923
+ function resolveReadOnly(options) {
7924
+ const readOnly = parseReadOnlyOptionsWithDefault({
7925
+ chainId: options.chainId,
7926
+ rpcUrl: options.rpcUrl
7927
+ });
7928
+ const apiUrl = options.apiUrl || process.env.NET_API_URL || NET_API_URL;
7929
+ let operator;
7930
+ if (options.operator) {
7931
+ if (!isAddress(options.operator)) {
7932
+ exitWithError(`Invalid operator address: ${options.operator}`);
7933
+ }
7934
+ operator = options.operator;
7935
+ }
7936
+ return { chainId: readOnly.chainId, apiUrl, operator };
7937
+ }
7938
+ async function resolveAuth(options) {
7939
+ const sessionToken = options.sessionToken || process.env.NET_SESSION_TOKEN;
7940
+ const privateKey = options.privateKey || process.env.NET_PRIVATE_KEY || process.env.PRIVATE_KEY;
7941
+ if (sessionToken && options.privateKey) {
7942
+ exitWithError("Cannot use both --session-token and --private-key. Pick one.");
7943
+ }
7944
+ const apiUrl = options.apiUrl || process.env.NET_API_URL || NET_API_URL;
7945
+ if (sessionToken) {
7946
+ if (!options.operator) {
7947
+ exitWithError(
7948
+ "--operator <address> is required when using --session-token. It must match the address that signed the session."
7949
+ );
7950
+ }
7951
+ if (!isAddress(options.operator)) {
7952
+ exitWithError(`Invalid operator address: ${options.operator}`);
7953
+ }
7954
+ const readOnly = parseReadOnlyOptionsWithDefault({
7955
+ chainId: options.chainId,
7956
+ rpcUrl: options.rpcUrl
7957
+ });
7958
+ const client2 = new AgentClient({ apiUrl, chainId: readOnly.chainId });
7959
+ return {
7960
+ client: client2,
7961
+ sessionToken,
7962
+ operatorAddress: options.operator,
7963
+ chainId: readOnly.chainId,
7964
+ apiUrl
7965
+ };
7966
+ }
7967
+ if (!privateKey) {
7968
+ exitWithError(
7969
+ "No auth provided. Use one of:\n --private-key <key> (or NET_PRIVATE_KEY env var)\n --session-token <token> + --operator <address> (for Bankr or other external signers)"
7970
+ );
7971
+ }
7972
+ const commonOptions = parseCommonOptionsWithDefault({
7973
+ privateKey,
7974
+ chainId: options.chainId,
7975
+ rpcUrl: options.rpcUrl
7976
+ });
7977
+ const account = privateKeyToAccount(commonOptions.privateKey);
7978
+ console.log(chalk4.blue("Creating session..."));
7979
+ const { sessionToken: token } = await createRelaySession({
7980
+ apiUrl,
7981
+ chainId: commonOptions.chainId,
7982
+ operatorAddress: account.address,
7983
+ secretKey: RELAY_ACCESS_KEY,
7984
+ account
7985
+ });
7986
+ const client = new AgentClient({ apiUrl, chainId: commonOptions.chainId });
7987
+ return {
7988
+ client,
7989
+ sessionToken: token,
7990
+ account,
7991
+ operatorAddress: account.address,
7992
+ chainId: commonOptions.chainId,
7993
+ apiUrl
7994
+ };
7995
+ }
7996
+ function bigintReplacer5(_key, value) {
7997
+ return typeof value === "bigint" ? value.toString() : value;
7998
+ }
7999
+ function jsonStringify(obj, indent = 2) {
8000
+ return JSON.stringify(obj, bigintReplacer5, indent);
8001
+ }
8002
+
8003
+ // src/commands/agent/create.ts
8004
+ async function executeCreate(name, options) {
8005
+ try {
8006
+ const auth = await resolveAuth(options);
8007
+ const config = {
8008
+ name,
8009
+ systemPrompt: options.systemPrompt
8010
+ };
8011
+ if (options.schedule) config.runIntervalMinutes = options.schedule;
8012
+ const filters = buildFilters(options);
8013
+ if (filters) config.filters = filters;
8014
+ const profile = buildProfile(options);
8015
+ console.log(chalk4.blue(`Creating agent "${name}"...`));
8016
+ const result = await auth.client.createAgent({
8017
+ sessionToken: auth.sessionToken,
8018
+ config,
8019
+ profile
8020
+ });
8021
+ if (!result.success) {
8022
+ exitWithError(result.error || "Failed to create agent");
8023
+ }
8024
+ if (options.json) {
8025
+ console.log(jsonStringify(result));
8026
+ return;
8027
+ }
8028
+ console.log(chalk4.green("Agent created successfully!"));
8029
+ console.log(` Agent ID: ${result.agentId}`);
8030
+ console.log(` Wallet: ${result.agentWalletAddress}`);
8031
+ if (result.scheduleError) {
8032
+ console.log(chalk4.yellow(` Schedule warning: ${result.scheduleError}`));
8033
+ }
8034
+ } catch (error) {
8035
+ exitWithError(
8036
+ `Failed to create agent: ${error instanceof Error ? error.message : String(error)}`
8037
+ );
8038
+ }
8039
+ }
8040
+ function registerAgentCreateCommand(parent) {
8041
+ const cmd = parent.command("create <name>").description("Create a new onchain agent").requiredOption("--system-prompt <prompt>", "Agent system prompt (personality)").option("--schedule <minutes>", "Auto-run interval in minutes", (v) => parseInt(v, 10)).option("--json", "Output as JSON");
8042
+ addAuthOptions(cmd);
8043
+ addFilterOptions(cmd);
8044
+ addProfileOptions(cmd);
8045
+ cmd.action(async (name, options) => {
8046
+ await executeCreate(name, options);
8047
+ });
8048
+ }
8049
+ async function executeList(options) {
8050
+ try {
8051
+ const auth = await resolveAuth(options);
8052
+ const result = await auth.client.listAgents({ sessionToken: auth.sessionToken });
8053
+ if (!result.success) {
8054
+ exitWithError(result.error || "Failed to list agents");
8055
+ }
8056
+ const agents = result.agents || [];
8057
+ const visible = options.showHidden ? agents : agents.filter((a) => !a.config.hidden);
8058
+ if (options.json) {
8059
+ console.log(jsonStringify(visible));
8060
+ return;
8061
+ }
8062
+ if (visible.length === 0) {
8063
+ console.log(chalk4.yellow("No agents found."));
8064
+ return;
8065
+ }
8066
+ console.log(chalk4.bold(`Agents (${visible.length}):
8067
+ `));
8068
+ for (const agent of visible) {
8069
+ const { config, walletAddress } = agent;
8070
+ const schedule = config.runIntervalMinutes ? `every ${config.runIntervalMinutes}m` : "manual";
8071
+ const hidden = config.hidden ? chalk4.gray(" [hidden]") : "";
8072
+ const promptPreview = config.systemPrompt.length > 80 ? `${config.systemPrompt.slice(0, 80)}...` : config.systemPrompt;
8073
+ console.log(` ${chalk4.cyan(config.name)}${hidden}`);
8074
+ console.log(` ID: ${config.id}`);
8075
+ console.log(` Wallet: ${walletAddress}`);
8076
+ console.log(` Schedule: ${schedule}`);
8077
+ console.log(` Prompt: ${promptPreview}`);
8078
+ console.log();
8079
+ }
8080
+ } catch (error) {
8081
+ exitWithError(
8082
+ `Failed to list agents: ${error instanceof Error ? error.message : String(error)}`
8083
+ );
8084
+ }
8085
+ }
8086
+ function registerAgentListCommand(parent) {
8087
+ const cmd = parent.command("list").description("List your onchain agents").option("--json", "Output as JSON").option("--show-hidden", "Include hidden agents");
8088
+ addAuthOptions(cmd);
8089
+ cmd.action(async (options) => {
8090
+ await executeList(options);
8091
+ });
8092
+ }
8093
+ async function executeUpdate(agentId, options) {
8094
+ try {
8095
+ const auth = await resolveAuth(options);
8096
+ const config = {};
8097
+ let hasConfigChanges = false;
8098
+ if (options.name) {
8099
+ config.name = options.name;
8100
+ hasConfigChanges = true;
8101
+ }
8102
+ if (options.systemPrompt) {
8103
+ config.systemPrompt = options.systemPrompt;
8104
+ hasConfigChanges = true;
8105
+ }
8106
+ if (options.schedule) {
8107
+ config.runIntervalMinutes = options.schedule;
8108
+ hasConfigChanges = true;
8109
+ }
8110
+ if (options.disableSchedule) {
8111
+ config.runIntervalMinutes = 0;
8112
+ hasConfigChanges = true;
8113
+ }
8114
+ const filters = buildFilters(options);
8115
+ if (filters) {
8116
+ config.filters = filters;
8117
+ hasConfigChanges = true;
8118
+ }
8119
+ const profile = buildProfile(options);
8120
+ if (!hasConfigChanges && !profile) {
8121
+ exitWithError(
8122
+ "No changes specified. Use --name, --system-prompt, --schedule, --display-name, --bio, or filter options."
8123
+ );
8124
+ }
8125
+ console.log(chalk4.blue(`Updating agent ${agentId}...`));
8126
+ const result = await auth.client.updateAgent({
8127
+ sessionToken: auth.sessionToken,
8128
+ agentId,
8129
+ config: hasConfigChanges ? config : void 0,
8130
+ profile
8131
+ });
8132
+ if (!result.success) {
8133
+ exitWithError(result.error || "Failed to update agent");
8134
+ }
8135
+ if (options.json) {
8136
+ console.log(jsonStringify(result));
8137
+ return;
8138
+ }
8139
+ console.log(chalk4.green("Agent updated successfully!"));
8140
+ if (result.profileError) {
8141
+ console.log(chalk4.yellow(` Profile warning: ${result.profileError}`));
8142
+ }
8143
+ } catch (error) {
8144
+ exitWithError(
8145
+ `Failed to update agent: ${error instanceof Error ? error.message : String(error)}`
8146
+ );
8147
+ }
8148
+ }
8149
+ function registerAgentUpdateCommand(parent) {
8150
+ const cmd = parent.command("update <agentId>").description("Update an existing agent").option("--name <name>", "New agent name").option("--system-prompt <prompt>", "New system prompt").option("--schedule <minutes>", "Auto-run interval in minutes", (v) => parseInt(v, 10)).option("--disable-schedule", "Disable automatic scheduling").option("--json", "Output as JSON");
8151
+ addAuthOptions(cmd);
8152
+ addFilterOptions(cmd);
8153
+ addProfileOptions(cmd);
8154
+ cmd.action(async (agentId, options) => {
8155
+ await executeUpdate(agentId, options);
8156
+ });
8157
+ }
8158
+ async function executeToggleHidden(agentId, options, hide) {
8159
+ const verb = hide ? "Hiding" : "Unhiding";
8160
+ const past = hide ? "hidden" : "unhidden";
8161
+ try {
8162
+ const auth = await resolveAuth(options);
8163
+ console.log(chalk4.blue(`${verb} agent ${agentId}...`));
8164
+ const result = hide ? await auth.client.hideAgent(auth.sessionToken, agentId) : await auth.client.unhideAgent(auth.sessionToken, agentId);
8165
+ if (!result.success) {
8166
+ exitWithError(result.error || `Failed to ${hide ? "hide" : "unhide"} agent`);
8167
+ }
8168
+ console.log(chalk4.green(`Agent ${past} successfully.`));
8169
+ } catch (error) {
8170
+ exitWithError(
8171
+ `Failed to ${hide ? "hide" : "unhide"} agent: ${error instanceof Error ? error.message : String(error)}`
8172
+ );
8173
+ }
8174
+ }
8175
+ function registerAgentHideCommand(parent) {
8176
+ const cmd = parent.command("hide <agentId>").description("Hide an agent (soft-delete)");
8177
+ addAuthOptions(cmd).action(async (agentId, options) => {
8178
+ await executeToggleHidden(agentId, options, true);
8179
+ });
8180
+ }
8181
+ function registerAgentUnhideCommand(parent) {
8182
+ const cmd = parent.command("unhide <agentId>").description("Unhide a previously hidden agent");
8183
+ addAuthOptions(cmd).action(async (agentId, options) => {
8184
+ await executeToggleHidden(agentId, options, false);
8185
+ });
8186
+ }
8187
+ async function executeRun(agentId, options) {
8188
+ try {
8189
+ const auth = await resolveAuth(options);
8190
+ const mode = parseRunMode(options.mode);
8191
+ console.log(chalk4.blue(`Running agent ${agentId} (mode: ${mode})...`));
8192
+ const result = await auth.client.runAgent({
8193
+ sessionToken: auth.sessionToken,
8194
+ agentId,
8195
+ mode
8196
+ });
8197
+ if (options.json) {
8198
+ console.log(jsonStringify(result));
8199
+ return;
8200
+ }
8201
+ if (!result.success) {
8202
+ exitWithError(result.error || "Agent run failed");
8203
+ }
8204
+ console.log(chalk4.green(`Agent run complete: ${result.action}`));
8205
+ if (result.summary) {
8206
+ console.log(` Summary: ${result.summary}`);
8207
+ }
8208
+ if (result.actions.length > 0) {
8209
+ console.log(" Actions:");
8210
+ for (const action of result.actions) {
8211
+ const textPreview = action.text.length > 60 ? `${action.text.slice(0, 60)}...` : action.text;
8212
+ console.log(` - ${action.type} in ${action.topic}: "${textPreview}"`);
8213
+ console.log(` tx: ${action.transactionHash}`);
8214
+ }
8215
+ }
8216
+ if (result.autoFunded) {
8217
+ console.log(
8218
+ chalk4.blue(
8219
+ ` Auto-funded: $${result.autoFunded.amountUsd.toFixed(4)} (${result.autoFunded.amountEth} ETH)`
8220
+ )
8221
+ );
8222
+ }
8223
+ if (result.agentBalanceUsd !== void 0) {
8224
+ console.log(` Agent balance: $${result.agentBalanceUsd.toFixed(4)}`);
8225
+ }
8226
+ if (result.mainBalanceUsd !== void 0) {
8227
+ console.log(` Credits balance: $${result.mainBalanceUsd.toFixed(4)}`);
8228
+ }
8229
+ } catch (error) {
8230
+ exitWithError(
8231
+ `Failed to run agent: ${error instanceof Error ? error.message : String(error)}`
8232
+ );
8233
+ }
8234
+ }
8235
+ function registerAgentRunCommand(parent) {
8236
+ const cmd = parent.command("run <agentId>").description("Execute one agent cycle").option("--mode <mode>", "Run mode: auto, feeds, or chats (default: auto)").option("--json", "Output as JSON");
8237
+ addAuthOptions(cmd).action(async (agentId, options) => {
8238
+ await executeRun(agentId, options);
8239
+ });
8240
+ }
8241
+ async function executeInfo(agentId, options) {
8242
+ try {
8243
+ const auth = await resolveAuth(options);
8244
+ const agent = await auth.client.getAgent(auth.sessionToken, agentId);
8245
+ if (!agent) {
8246
+ exitWithError(`Agent not found: ${agentId}`);
8247
+ return;
8248
+ }
8249
+ if (options.json) {
8250
+ console.log(jsonStringify(agent));
8251
+ return;
8252
+ }
8253
+ const { config, walletAddress } = agent;
8254
+ const schedule = config.runIntervalMinutes ? `every ${config.runIntervalMinutes} minutes` : "manual only";
8255
+ console.log(
8256
+ chalk4.bold(config.name) + (config.hidden ? chalk4.gray(" [hidden]") : "")
8257
+ );
8258
+ console.log();
8259
+ console.log(` ID: ${config.id}`);
8260
+ console.log(` Wallet: ${walletAddress}`);
8261
+ console.log(` Owner: ${config.ownerAddress}`);
8262
+ console.log(` Schedule: ${schedule}`);
8263
+ console.log(` Created: ${new Date(config.createdAt).toLocaleString()}`);
8264
+ console.log(` Updated: ${new Date(config.updatedAt).toLocaleString()}`);
8265
+ console.log();
8266
+ console.log(" System Prompt:");
8267
+ console.log(` ${config.systemPrompt}`);
8268
+ if (config.filters) {
8269
+ console.log();
8270
+ console.log(" Filters:");
8271
+ if (config.filters.includeFeedPatterns?.length) {
8272
+ console.log(` Include feeds: ${config.filters.includeFeedPatterns.join(", ")}`);
8273
+ }
8274
+ if (config.filters.excludeFeedPatterns?.length) {
8275
+ console.log(` Exclude feeds: ${config.filters.excludeFeedPatterns.join(", ")}`);
8276
+ }
8277
+ if (config.filters.preferredFeedPatterns?.length) {
8278
+ console.log(
8279
+ ` Preferred feeds: ${config.filters.preferredFeedPatterns.join(", ")}`
8280
+ );
8281
+ }
8282
+ if (config.filters.preferredChatTopics?.length) {
8283
+ console.log(` Chat topics: ${config.filters.preferredChatTopics.join(", ")}`);
8284
+ }
8285
+ }
8286
+ } catch (error) {
8287
+ exitWithError(
8288
+ `Failed to get agent info: ${error instanceof Error ? error.message : String(error)}`
8289
+ );
8290
+ }
8291
+ }
8292
+ function registerAgentInfoCommand(parent) {
8293
+ const cmd = parent.command("info <agentId>").description("Show detailed agent information").option("--json", "Output as JSON");
8294
+ addAuthOptions(cmd).action(async (agentId, options) => {
8295
+ await executeInfo(agentId, options);
8296
+ });
8297
+ }
8298
+ async function executeDm(agentAddress, message, options) {
8299
+ try {
8300
+ if (!isAddress(agentAddress)) {
8301
+ exitWithError(`Invalid agent address: ${agentAddress}`);
8302
+ }
8303
+ if (options.topicSignature && !options.topic) {
8304
+ exitWithError(
8305
+ "--topic-signature requires --topic (the topic the signature authorizes)"
8306
+ );
8307
+ }
8308
+ const usingSessionToken = !!(options.sessionToken || process.env.NET_SESSION_TOKEN);
8309
+ if (usingSessionToken && (!options.topic || !options.topicSignature)) {
8310
+ exitWithError(
8311
+ "When using --session-token, you must also provide --topic and --topic-signature.\n Obtain the signature with:\n netp agent dm-auth-encode --agent-address <addr> \u2192 produces { typedData, topic }\n [sign .typedData with your external signer, e.g. Bankr /agent/sign]\n Then:\n netp agent dm <addr> <message> --session-token <token> --operator <addr> \\\n --topic <topic> --topic-signature <sig>"
8312
+ );
8313
+ }
8314
+ const auth = await resolveAuth(options);
8315
+ const topic = options.topic ?? generateAgentChatTopic(agentAddress);
8316
+ const isNewConversation = !options.topic;
8317
+ console.log(
8318
+ chalk4.blue(
8319
+ isNewConversation ? `Starting new conversation with ${agentAddress}...` : `Continuing conversation ${topic}...`
8320
+ )
8321
+ );
8322
+ const result = await auth.client.sendMessage(
8323
+ {
8324
+ sessionToken: auth.sessionToken,
8325
+ agentAddress,
8326
+ topic,
8327
+ message,
8328
+ userSignature: options.topicSignature
8329
+ },
8330
+ auth.account
8331
+ );
8332
+ if (options.json) {
8333
+ console.log(jsonStringify(result));
8334
+ return;
8335
+ }
8336
+ console.log();
8337
+ console.log(chalk4.cyan("You: ") + message);
8338
+ console.log(chalk4.green("Agent: ") + result.aiMessage);
8339
+ console.log();
8340
+ console.log(chalk4.gray(` Topic: ${result.topic}`));
8341
+ console.log(chalk4.gray(` TX: ${result.transactionHash}`));
8342
+ if (isNewConversation) {
8343
+ console.log(
8344
+ chalk4.gray(` (Use --topic ${result.topic} to continue this conversation)`)
8345
+ );
8346
+ }
8347
+ } catch (error) {
8348
+ exitWithError(
8349
+ `Failed to send DM: ${error instanceof Error ? error.message : String(error)}`
8350
+ );
8351
+ }
8352
+ }
8353
+ function registerAgentDmCommand(parent) {
8354
+ const cmd = parent.command("dm <agentAddress> <message>").description("Send a DM to an onchain agent").option("--topic <topic>", "Continue an existing conversation").option(
8355
+ "--topic-signature <hex>",
8356
+ "Pre-signed ConversationAuth signature (requires --topic). Obtain via `agent dm-auth-encode` + external signer."
8357
+ ).option("--json", "Output as JSON");
8358
+ addAuthOptions(cmd).action(async (agentAddress, message, options) => {
8359
+ await executeDm(agentAddress, message, options);
8360
+ });
8361
+ }
8362
+ async function executeDmList(options) {
8363
+ try {
8364
+ const { chainId, apiUrl, operator } = resolveReadOnly(options);
8365
+ if (!operator) {
8366
+ exitWithError(
8367
+ "--operator <address> is required (user wallet address to list conversations for)"
8368
+ );
8369
+ }
8370
+ const client = new AgentClient({ apiUrl, chainId });
8371
+ console.log(chalk4.blue("Loading conversations..."));
8372
+ const conversations = await client.listConversations(operator, {
8373
+ limit: options.limit
8374
+ });
8375
+ const agentConversations = conversations.filter((c) => isAgentChatTopic(c.topic));
8376
+ if (options.json) {
8377
+ console.log(jsonStringify(agentConversations));
8378
+ return;
8379
+ }
8380
+ if (agentConversations.length === 0) {
8381
+ console.log(chalk4.yellow("No agent conversations found."));
8382
+ return;
8383
+ }
8384
+ console.log(chalk4.bold(`Agent Conversations (${agentConversations.length}):
8385
+ `));
8386
+ for (const conv of agentConversations) {
8387
+ const agentAddr = parseAgentAddressFromTopic(conv.topic);
8388
+ const encrypted = conv.isEncrypted ? chalk4.yellow(" [encrypted]") : "";
8389
+ const date = new Date(conv.lastMessageTimestamp * 1e3).toLocaleString();
8390
+ const preview = conv.lastMessage ? conv.lastMessage.length > 60 ? `${conv.lastMessage.slice(0, 60)}...` : conv.lastMessage : null;
8391
+ console.log(` ${chalk4.cyan(agentAddr || "unknown")}${encrypted}`);
8392
+ console.log(` Topic: ${conv.topic}`);
8393
+ console.log(` Messages: ${conv.messageCount}`);
8394
+ console.log(` Last: ${date}`);
8395
+ if (preview) console.log(` Preview: ${preview}`);
8396
+ console.log();
8397
+ }
8398
+ } catch (error) {
8399
+ exitWithError(
8400
+ `Failed to list conversations: ${error instanceof Error ? error.message : String(error)}`
8401
+ );
8402
+ }
8403
+ }
8404
+ function registerAgentDmListCommand(parent) {
8405
+ const cmd = parent.command("dm-list").description("List your DM conversations with agents (pure chain read)").requiredOption("--operator <address>", "User wallet address").option("--limit <n>", "Max conversations to fetch", (v) => parseInt(v, 10)).option("--json", "Output as JSON");
8406
+ addCommonOptions(cmd).action(async (options) => {
8407
+ await executeDmList(options);
8408
+ });
8409
+ }
8410
+ async function executeDmHistory(topic, options) {
8411
+ try {
8412
+ const { chainId, apiUrl, operator } = resolveReadOnly(options);
8413
+ if (!operator) {
8414
+ exitWithError(
8415
+ "--operator <address> is required (user wallet address for this conversation)"
8416
+ );
8417
+ }
8418
+ const client = new AgentClient({ apiUrl, chainId });
8419
+ console.log(chalk4.blue("Loading conversation history..."));
8420
+ const messages = await client.getConversationHistory(operator, topic, {
8421
+ limit: options.limit
8422
+ });
8423
+ if (options.json) {
8424
+ console.log(jsonStringify(messages));
8425
+ return;
8426
+ }
8427
+ if (messages.length === 0) {
8428
+ console.log(chalk4.yellow("No messages found."));
8429
+ return;
8430
+ }
8431
+ const agentAddr = parseAgentAddressFromTopic(topic);
8432
+ console.log(
8433
+ chalk4.bold(
8434
+ `Conversation with ${agentAddr || topic} (${messages.length} messages):
8435
+ `
8436
+ )
8437
+ );
8438
+ for (const msg of messages) {
8439
+ const date = new Date(msg.timestamp * 1e3).toLocaleString();
8440
+ const prefix = msg.sender === "user" ? chalk4.cyan("You") : chalk4.green("Agent");
8441
+ const encrypted = msg.encrypted ? chalk4.yellow(" [encrypted]") : "";
8442
+ console.log(` ${prefix}${encrypted} (${chalk4.gray(date)}):`);
8443
+ console.log(` ${msg.text}`);
8444
+ console.log();
8445
+ }
8446
+ } catch (error) {
8447
+ exitWithError(
8448
+ `Failed to load history: ${error instanceof Error ? error.message : String(error)}`
8449
+ );
8450
+ }
8451
+ }
8452
+ function registerAgentDmHistoryCommand(parent) {
8453
+ const cmd = parent.command("dm-history <topic>").description("Read DM conversation history (pure chain read)").requiredOption(
8454
+ "--operator <address>",
8455
+ "User wallet address for this conversation"
8456
+ ).option("--limit <n>", "Max recent messages to fetch", (v) => parseInt(v, 10)).option("--json", "Output as JSON");
8457
+ addCommonOptions(cmd).action(async (topic, options) => {
8458
+ await executeDmHistory(topic, options);
8459
+ });
8460
+ }
8461
+ async function executeDmAuthEncode(options) {
8462
+ try {
8463
+ const { chainId } = parseReadOnlyOptionsWithDefault({
8464
+ chainId: options.chainId
8465
+ });
8466
+ let topic = options.topic;
8467
+ if (!topic) {
8468
+ if (!options.agentAddress) {
8469
+ exitWithError(
8470
+ "Must provide either --topic or --agent-address to generate a topic"
8471
+ );
8472
+ }
8473
+ if (!isAddress(options.agentAddress)) {
8474
+ exitWithError(`Invalid agent address: ${options.agentAddress}`);
8475
+ }
8476
+ topic = generateAgentChatTopic(options.agentAddress);
8477
+ }
8478
+ const result = buildConversationAuthTypedData({ topic, chainId });
8479
+ console.log(jsonStringify(result));
8480
+ } catch (error) {
8481
+ exitWithError(
8482
+ `dm-auth-encode failed: ${error instanceof Error ? error.message : String(error)}`
8483
+ );
8484
+ }
8485
+ }
8486
+ function registerAgentDmAuthEncodeCommand(parent) {
8487
+ parent.command("dm-auth-encode").description(
8488
+ "Emit { typedData, topic } for external signing. Pipe .typedData to your signer, pass .topic + the resulting signature to `agent dm --topic ... --topic-signature ...`."
8489
+ ).option(
8490
+ "--topic <topic>",
8491
+ "Existing topic to authorize (e.g. agent-chat-0x...-nanoid). If omitted, a new topic is generated from --agent-address."
8492
+ ).option(
8493
+ "--agent-address <address>",
8494
+ "Agent address \u2014 used to generate a new topic when --topic is omitted"
8495
+ ).option("--chain-id <id>", "Chain ID (default: 8453)", (v) => parseInt(v, 10)).action(async (options) => {
8496
+ await executeDmAuthEncode(options);
8497
+ });
8498
+ }
8499
+ async function executeSessionEncode(options) {
8500
+ try {
8501
+ if (!isAddress(options.operator)) {
8502
+ exitWithError(`Invalid operator address: ${options.operator}`);
8503
+ }
8504
+ const { chainId } = parseReadOnlyOptionsWithDefault({
8505
+ chainId: options.chainId
8506
+ });
8507
+ const result = buildSessionTypedData({
8508
+ operatorAddress: options.operator,
8509
+ chainId,
8510
+ expiresIn: options.expiresIn
8511
+ });
8512
+ console.log(jsonStringify(result));
8513
+ } catch (error) {
8514
+ exitWithError(
8515
+ `session-encode failed: ${error instanceof Error ? error.message : String(error)}`
8516
+ );
8517
+ }
8518
+ }
8519
+ function registerAgentSessionEncodeCommand(parent) {
8520
+ parent.command("session-encode").description(
8521
+ "Emit { typedData, expiresAt } for external signing (e.g., Bankr). Pipe .typedData to your signer, pass .expiresAt + the resulting signature to `agent session-create`."
8522
+ ).requiredOption(
8523
+ "--operator <address>",
8524
+ "Address that will sign the session (must match signer)"
8525
+ ).option("--chain-id <id>", "Chain ID (default: 8453)", (v) => parseInt(v, 10)).option(
8526
+ "--expires-in <seconds>",
8527
+ "Session lifetime in seconds (default: 3600, max: 86400)",
8528
+ (v) => parseInt(v, 10)
8529
+ ).action(async (options) => {
8530
+ await executeSessionEncode(options);
8531
+ });
8532
+ }
8533
+ async function executeSessionCreate(options) {
8534
+ try {
8535
+ if (!isAddress(options.operator)) {
8536
+ exitWithError(`Invalid operator address: ${options.operator}`);
8537
+ }
8538
+ if (!options.signature.startsWith("0x")) {
8539
+ exitWithError("--signature must be a 0x-prefixed hex string");
8540
+ }
8541
+ if (!options.expiresAt || !Number.isFinite(options.expiresAt)) {
8542
+ exitWithError("--expires-at must be a unix timestamp (seconds)");
8543
+ }
8544
+ const { chainId } = parseReadOnlyOptionsWithDefault({
8545
+ chainId: options.chainId
8546
+ });
8547
+ const apiUrl = options.apiUrl || process.env.NET_API_URL || NET_API_URL;
8548
+ const result = await exchangeSessionSignature({
8549
+ apiUrl,
8550
+ chainId,
8551
+ operatorAddress: options.operator,
8552
+ signature: options.signature,
8553
+ expiresAt: options.expiresAt
8554
+ });
8555
+ console.log(jsonStringify(result));
8556
+ } catch (error) {
8557
+ exitWithError(
8558
+ `session-create failed: ${error instanceof Error ? error.message : String(error)}`
8559
+ );
8560
+ }
8561
+ }
8562
+ function registerAgentSessionCreateCommand(parent) {
8563
+ parent.command("session-create").description(
8564
+ "Exchange an externally-produced signature for a session token. Pair with `agent session-encode`."
8565
+ ).requiredOption(
8566
+ "--operator <address>",
8567
+ "Address that produced the signature (must match the ecrecover result)"
8568
+ ).requiredOption("--signature <hex>", "EIP-712 signature over the session typed data").requiredOption(
8569
+ "--expires-at <timestamp>",
8570
+ "expiresAt value from session-encode (unix seconds)",
8571
+ (v) => parseInt(v, 10)
8572
+ ).option("--chain-id <id>", "Chain ID (default: 8453)", (v) => parseInt(v, 10)).option("--api-url <url>", "Net Protocol API URL").action(async (options) => {
8573
+ await executeSessionCreate(options);
8574
+ });
8575
+ }
8576
+
8577
+ // src/commands/agent/index.ts
8578
+ function registerAgentCommand(program2) {
8579
+ const agentCommand = program2.command("agent").description("Onchain agent operations (create, manage, run, DM)");
8580
+ registerAgentCreateCommand(agentCommand);
8581
+ registerAgentListCommand(agentCommand);
8582
+ registerAgentUpdateCommand(agentCommand);
8583
+ registerAgentHideCommand(agentCommand);
8584
+ registerAgentUnhideCommand(agentCommand);
8585
+ registerAgentRunCommand(agentCommand);
8586
+ registerAgentInfoCommand(agentCommand);
8587
+ registerAgentDmCommand(agentCommand);
8588
+ registerAgentDmListCommand(agentCommand);
8589
+ registerAgentDmHistoryCommand(agentCommand);
8590
+ registerAgentSessionEncodeCommand(agentCommand);
8591
+ registerAgentSessionCreateCommand(agentCommand);
8592
+ registerAgentDmAuthEncodeCommand(agentCommand);
8593
+ }
8594
+ var CHAINS = {
8595
+ 8453: base,
8596
+ 84532: baseSepolia
8597
+ };
8598
+ function getApiUrl(chainId) {
8599
+ return chainId === 84532 ? NET_TESTNET_API_URL : NET_API_URL;
8600
+ }
8601
+ function registerRelayCommand(program2) {
8602
+ const relayCommand = program2.command("relay").description("Relay operations (fund credits, check balance)");
8603
+ relayCommand.command("fund").description("Add Net credits via x402 USDC payment").option("--amount <usd>", "Amount in USD to fund (default: 0.10)", "0.10").option("--chain-id <id>", "Chain ID (default: 8453)").option("--rpc-url <url>", "Custom RPC URL").option("--private-key <key>", "Private key (0x-prefixed)").option("--json", "Output as JSON").action(async (options) => {
8604
+ const { privateKey, chainId, rpcUrl } = parseCommonOptionsWithDefault({
8605
+ privateKey: options.privateKey,
8606
+ chainId: options.chainId ? parseInt(options.chainId, 10) : void 0,
8607
+ rpcUrl: options.rpcUrl
8608
+ });
8609
+ const amount = parseFloat(options.amount);
8610
+ if (isNaN(amount) || amount <= 0) {
8611
+ exitWithError("Invalid amount. Must be a positive number.");
8612
+ }
8613
+ const chain = CHAINS[chainId];
8614
+ if (!chain) {
8615
+ exitWithError(
8616
+ `Chain ${chainId} not supported for relay funding. Use Base (8453) or Base Sepolia (84532).`
8617
+ );
8618
+ }
8619
+ const account = privateKeyToAccount(privateKey);
8620
+ const rpcUrls = getChainRpcUrls({ chainId, rpcUrl });
8621
+ const publicClient = createPublicClient({
8622
+ chain,
8623
+ transport: http(rpcUrls[0])
8624
+ });
8625
+ const walletClient = createWalletClient({
8626
+ account,
8627
+ chain,
8628
+ transport: http(rpcUrls[0])
8629
+ });
8630
+ const signer = {
8631
+ address: account.address,
8632
+ signTypedData: (msg) => walletClient.signTypedData(msg),
8633
+ readContract: (args) => publicClient.readContract(args),
8634
+ sendTransaction: (args) => walletClient.sendTransaction(args)
8635
+ };
8636
+ const { x402Client, wrapFetchWithPayment, x402HTTPClient } = await import('@x402/fetch');
8637
+ const { registerExactEvmScheme } = await import('@x402/evm/exact/client');
8638
+ const client = new x402Client();
8639
+ registerExactEvmScheme(client, { signer });
8640
+ const fetchWithPayment = wrapFetchWithPayment(fetch, client);
8641
+ const httpClient = new x402HTTPClient(client);
8642
+ const apiUrl = getApiUrl(chainId);
8643
+ if (!options.json) {
8644
+ console.log(
8645
+ `Funding $${amount.toFixed(2)} USDC to relay on chain ${chainId}...`
8646
+ );
8647
+ }
8648
+ try {
8649
+ const result = await fundBackendWallet({
8650
+ apiUrl,
8651
+ chainId,
8652
+ operatorAddress: account.address,
8653
+ secretKey: RELAY_ACCESS_KEY,
8654
+ fetchWithPayment,
8655
+ httpClient,
8656
+ amount
8657
+ });
8658
+ if (options.json) {
8659
+ console.log(
8660
+ JSON.stringify(
8661
+ {
8662
+ success: true,
8663
+ paymentTxHash: result.paymentTxHash,
8664
+ backendWalletAddress: result.backendWalletAddress,
8665
+ amountUsd: amount
8666
+ },
8667
+ null,
8668
+ 2
8669
+ )
8670
+ );
8671
+ } else {
8672
+ console.log(chalk4.green(`
8673
+ \u2713 Funded $${amount.toFixed(2)} successfully`));
8674
+ console.log(` Payment tx: ${result.paymentTxHash}`);
8675
+ console.log(
8676
+ ` Backend wallet: ${result.backendWalletAddress}`
8677
+ );
8678
+ }
8679
+ } catch (error) {
8680
+ const msg = error instanceof Error ? error.message : String(error);
8681
+ if (options.json) {
8682
+ console.log(JSON.stringify({ success: false, error: msg }, null, 2));
8683
+ process.exit(1);
8684
+ }
8685
+ exitWithError(`Funding failed: ${msg}`);
8686
+ }
8687
+ });
8688
+ relayCommand.command("balance").description("Check relay backend wallet balance").option("--chain-id <id>", "Chain ID (default: 8453)").option("--private-key <key>", "Private key (0x-prefixed)").option("--json", "Output as JSON").action(async (options) => {
8689
+ const { privateKey, chainId } = parseCommonOptionsWithDefault({
8690
+ privateKey: options.privateKey,
8691
+ chainId: options.chainId ? parseInt(options.chainId, 10) : void 0
8692
+ });
8693
+ const account = privateKeyToAccount(privateKey);
8694
+ const apiUrl = getApiUrl(chainId);
8695
+ try {
8696
+ const result = await checkBackendWalletBalance({
8697
+ apiUrl,
8698
+ chainId,
8699
+ operatorAddress: account.address,
8700
+ secretKey: RELAY_ACCESS_KEY
8701
+ });
8702
+ if (options.json) {
8703
+ console.log(JSON.stringify(result, null, 2));
8704
+ } else {
8705
+ console.log(`Relay Balance`);
8706
+ console.log(` Backend wallet: ${result.backendWalletAddress}`);
8707
+ console.log(` Balance: ${result.balanceEth} ETH`);
8708
+ console.log(
8709
+ ` Sufficient: ${result.sufficientBalance ? chalk4.green("Yes") : chalk4.red("No")} (min: ${result.minRequiredEth} ETH)`
8710
+ );
8711
+ }
8712
+ } catch (error) {
8713
+ const msg = error instanceof Error ? error.message : String(error);
8714
+ if (options.json) {
8715
+ console.log(JSON.stringify({ success: false, error: msg }, null, 2));
8716
+ process.exit(1);
8717
+ }
8718
+ exitWithError(`Balance check failed: ${msg}`);
8719
+ }
8720
+ });
8721
+ }
7543
8722
  var CACHE_DIR = join(homedir(), ".netp");
7544
8723
  var CACHE_FILE = join(CACHE_DIR, "update-check.json");
7545
8724
  var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
@@ -7640,6 +8819,8 @@ registerProfileCommand(program);
7640
8819
  registerBazaarCommand(program);
7641
8820
  registerFeedCommand(program);
7642
8821
  registerUpvoteCommand(program);
8822
+ registerAgentCommand(program);
8823
+ registerRelayCommand(program);
7643
8824
  program.command("update").description("Update netp to the latest version").action(async () => {
7644
8825
  const { execSync } = await import('child_process');
7645
8826
  console.log("Updating @net-protocol/cli...");