@net-protocol/cli 0.1.24 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -472,6 +472,7 @@ netp profile set-x-username \
472
472
  - `--chain-id` (optional): Chain ID. Can also be set via `NET_CHAIN_ID` environment variable
473
473
  - `--rpc-url` (optional): Custom RPC URL. Can also be set via `NET_RPC_URL` environment variable
474
474
  - `--encode-only` (optional): Output transaction data as JSON instead of executing
475
+ - `--address` (optional): Wallet address to preserve existing metadata for (used with --encode-only)
475
476
 
476
477
  **Example:**
477
478
 
@@ -485,9 +486,10 @@ netp profile set-x-username \
485
486
  --username "@myusername" \
486
487
  --chain-id 8453
487
488
 
488
- # Encode-only
489
+ # Encode-only (--address preserves existing bio, display name, token address)
489
490
  netp profile set-x-username \
490
491
  --username "myusername" \
492
+ --address 0xYourWalletAddress \
491
493
  --chain-id 8453 \
492
494
  --encode-only
493
495
  ```
@@ -512,6 +514,7 @@ netp profile set-bio \
512
514
  - `--chain-id` (optional): Chain ID. Can also be set via `NET_CHAIN_ID` environment variable
513
515
  - `--rpc-url` (optional): Custom RPC URL. Can also be set via `NET_RPC_URL` environment variable
514
516
  - `--encode-only` (optional): Output transaction data as JSON instead of executing
517
+ - `--address` (optional): Wallet address to preserve existing metadata for (used with --encode-only)
515
518
 
516
519
  **Example:**
517
520
 
@@ -521,9 +524,10 @@ netp profile set-bio \
521
524
  --bio "Building cool stuff on Net Protocol" \
522
525
  --chain-id 8453
523
526
 
524
- # Encode-only
527
+ # Encode-only (--address preserves existing x_username, display name, token address)
525
528
  netp profile set-bio \
526
529
  --bio "Building cool stuff on Net Protocol" \
530
+ --address 0xYourWalletAddress \
527
531
  --chain-id 8453 \
528
532
  --encode-only
529
533
  ```
@@ -548,6 +552,7 @@ netp profile set-display-name \
548
552
  - `--chain-id` (optional): Chain ID. Can also be set via `NET_CHAIN_ID` environment variable
549
553
  - `--rpc-url` (optional): Custom RPC URL. Can also be set via `NET_RPC_URL` environment variable
550
554
  - `--encode-only` (optional): Output transaction data as JSON instead of executing
555
+ - `--address` (optional): Wallet address to preserve existing metadata for (used with --encode-only)
551
556
 
552
557
  **Example:**
553
558
 
@@ -557,9 +562,10 @@ netp profile set-display-name \
557
562
  --name "Alice" \
558
563
  --chain-id 8453
559
564
 
560
- # Encode-only
565
+ # Encode-only (--address preserves existing bio, x_username, token address)
561
566
  netp profile set-display-name \
562
567
  --name "Alice" \
568
+ --address 0xYourWalletAddress \
563
569
  --chain-id 8453 \
564
570
  --encode-only
565
571
  ```
@@ -584,6 +590,7 @@ netp profile set-token-address \
584
590
  - `--chain-id` (optional): Chain ID. Can also be set via `NET_CHAIN_ID` environment variable
585
591
  - `--rpc-url` (optional): Custom RPC URL. Can also be set via `NET_RPC_URL` environment variable
586
592
  - `--encode-only` (optional): Output transaction data as JSON instead of executing
593
+ - `--address` (optional): Wallet address to preserve existing metadata for (used with --encode-only)
587
594
 
588
595
  **Example:**
589
596
 
@@ -593,9 +600,10 @@ netp profile set-token-address \
593
600
  --token-address 0x1234567890abcdef1234567890abcdef12345678 \
594
601
  --chain-id 8453
595
602
 
596
- # Encode-only (get transaction data without executing)
603
+ # Encode-only (--address preserves existing bio, x_username, display name)
597
604
  netp profile set-token-address \
598
605
  --token-address 0x1234567890abcdef1234567890abcdef12345678 \
606
+ --address 0xYourWalletAddress \
599
607
  --chain-id 8453 \
600
608
  --encode-only
601
609
  ```
@@ -778,12 +786,20 @@ src/
778
786
  │ ├── profile/ # Profile command module
779
787
  │ │ ├── index.ts # Profile command definition
780
788
  │ │ ├── get.ts # Profile get logic
781
- │ │ ├── set-picture.ts # Set profile picture
782
- │ │ ├── set-username.ts # Set X username
789
+ │ │ ├── set-picture.ts # Set profile picture
790
+ │ │ ├── set-username.ts # Set X username
791
+ │ │ ├── set-bio.ts # Set profile bio
792
+ │ │ ├── set-display-name.ts # Set display name
783
793
  │ │ ├── set-token-address.ts # Set token address
794
+ │ │ ├── set-canvas.ts # Set profile canvas
795
+ │ │ ├── get-canvas.ts # Get profile canvas
784
796
  │ │ └── types.ts # Profile-specific types
797
+ │ ├── feed/ # Feed command module
785
798
  │ ├── message/ # Message command module
786
- └── token/ # Token command module
799
+ ├── token/ # Token command module
800
+ │ ├── bazaar/ # NFT Bazaar command module
801
+ │ ├── chains/ # Chains listing command
802
+ │ └── info/ # Contract info command
787
803
  └── shared/ # Shared utilities across commands
788
804
  └── types.ts # Common types (CommonOptions, etc.)
789
805
  ```
@@ -4,8 +4,8 @@ import 'dotenv/config';
4
4
  import { Command } from 'commander';
5
5
  import { createRequire } from 'module';
6
6
  import chalk4 from 'chalk';
7
- import * as fs4 from 'fs';
8
- import { readFileSync } from 'fs';
7
+ import * as fs6 from 'fs';
8
+ import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
9
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
10
  import { stringToHex, createWalletClient, http, hexToString, parseEther, encodeFunctionData, publicActions, defineChain } from 'viem';
11
11
  import { privateKeyToAccount } from 'viem/accounts';
@@ -13,11 +13,13 @@ import { getNetContract, getChainName, getPublicClient, getChainRpcUrls, NetClie
13
13
  import { createRelayX402Client, createRelaySession, checkBackendWalletBalance, fundBackendWallet, batchTransactions, submitTransactionsViaRelay, waitForConfirmations, retryFailedTransactions as retryFailedTransactions$1 } from '@net-protocol/relay';
14
14
  import { FeedRegistryClient, FeedClient, AgentRegistryClient } from '@net-protocol/feeds';
15
15
  import { isNetrSupportedChain, NetrClient } from '@net-protocol/netr';
16
- import { PROFILE_PICTURE_STORAGE_KEY, PROFILE_METADATA_STORAGE_KEY, parseProfileMetadata, PROFILE_CANVAS_STORAGE_KEY, isValidUrl, getProfilePictureStorageArgs, STORAGE_CONTRACT, isValidXUsername, getProfileMetadataStorageArgs, isValidBio, isValidDisplayName, isValidTokenAddress } from '@net-protocol/profiles';
16
+ 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';
17
17
  import { base } from 'viem/chains';
18
18
  import * as path from 'path';
19
+ import { join } from 'path';
19
20
  import { BazaarClient } from '@net-protocol/bazaar';
20
21
  import * as os from 'os';
22
+ import { homedir } from 'os';
21
23
  import * as readline from 'readline';
22
24
 
23
25
  var DEFAULT_CHAIN_ID = 8453;
@@ -1255,7 +1257,7 @@ async function encodeStorageUpload(options) {
1255
1257
  } else {
1256
1258
  operatorAddress = "0x0000000000000000000000000000000000000000";
1257
1259
  }
1258
- const fileContent = fs4.readFileSync(options.filePath, "utf-8");
1260
+ const fileContent = fs6.readFileSync(options.filePath, "utf-8");
1259
1261
  const fileSize = Buffer.byteLength(fileContent, "utf-8");
1260
1262
  const client = new StorageClient({
1261
1263
  chainId: readOnlyOptions.chainId
@@ -1558,8 +1560,8 @@ function registerStorageCommand(program2) {
1558
1560
  console.log(chalk4.blue(`\u{1F4C1} Reading file: ${options.file}`));
1559
1561
  console.log(chalk4.blue(`\u{1F517} Using relay API: ${options.apiUrl}`));
1560
1562
  const result = await uploadFileWithRelay(uploadRelayOptions);
1561
- const { privateKeyToAccount: privateKeyToAccount25 } = await import('viem/accounts');
1562
- const userAccount = privateKeyToAccount25(commonOptions.privateKey);
1563
+ const { privateKeyToAccount: privateKeyToAccount26 } = await import('viem/accounts');
1564
+ const userAccount = privateKeyToAccount26(commonOptions.privateKey);
1563
1565
  const storageUrl = generateStorageUrl(
1564
1566
  userAccount.address,
1565
1567
  commonOptions.chainId,
@@ -2394,7 +2396,22 @@ async function executeProfileGet(options) {
2394
2396
  throw error;
2395
2397
  }
2396
2398
  }
2397
- const hasProfile = profilePicture || xUsername || bio || tokenAddress || canvasSize;
2399
+ let cssSize;
2400
+ try {
2401
+ const cssResult = await client.readStorageData({
2402
+ key: PROFILE_CSS_STORAGE_KEY,
2403
+ operator: options.address
2404
+ });
2405
+ if (cssResult.data) {
2406
+ cssSize = cssResult.data.length;
2407
+ }
2408
+ } catch (error) {
2409
+ const errorMessage = error instanceof Error ? error.message : String(error);
2410
+ if (errorMessage !== "StoredDataNotFound") {
2411
+ throw error;
2412
+ }
2413
+ }
2414
+ const hasProfile = profilePicture || xUsername || bio || tokenAddress || canvasSize || cssSize;
2398
2415
  if (options.json) {
2399
2416
  const output = {
2400
2417
  address: options.address,
@@ -2404,6 +2421,7 @@ async function executeProfileGet(options) {
2404
2421
  bio: bio || null,
2405
2422
  tokenAddress: tokenAddress || null,
2406
2423
  canvas: canvasSize ? { size: canvasSize, isDataUri: canvasIsDataUri } : null,
2424
+ css: cssSize ? { size: cssSize } : null,
2407
2425
  hasProfile
2408
2426
  };
2409
2427
  console.log(JSON.stringify(output, null, 2));
@@ -2427,6 +2445,9 @@ async function executeProfileGet(options) {
2427
2445
  console.log(
2428
2446
  ` ${chalk4.cyan("Canvas:")} ${canvasSize ? `${canvasSize} bytes${canvasIsDataUri ? " (data URI)" : ""}` : chalk4.gray("(not set)")}`
2429
2447
  );
2448
+ console.log(
2449
+ ` ${chalk4.cyan("Custom CSS:")} ${cssSize ? `${cssSize} bytes` : chalk4.gray("(not set)")}`
2450
+ );
2430
2451
  if (!hasProfile) {
2431
2452
  console.log(chalk4.yellow("\n No profile data found for this address."));
2432
2453
  }
@@ -2989,10 +3010,10 @@ async function executeProfileSetCanvas(options) {
2989
3010
  let canvasContent;
2990
3011
  if (options.file) {
2991
3012
  const filePath = path.resolve(options.file);
2992
- if (!fs4.existsSync(filePath)) {
3013
+ if (!fs6.existsSync(filePath)) {
2993
3014
  exitWithError(`File not found: ${filePath}`);
2994
3015
  }
2995
- const buffer = fs4.readFileSync(filePath);
3016
+ const buffer = fs6.readFileSync(filePath);
2996
3017
  if (buffer.length > MAX_CANVAS_SIZE) {
2997
3018
  exitWithError(
2998
3019
  `File too large: ${buffer.length} bytes exceeds maximum of ${MAX_CANVAS_SIZE} bytes (60KB).`
@@ -3167,12 +3188,12 @@ async function executeProfileGetCanvas(options) {
3167
3188
  if (!path.extname(outputPath)) {
3168
3189
  finalPath = outputPath + getExtensionFromMimeType(mimeType);
3169
3190
  }
3170
- fs4.writeFileSync(finalPath, buffer);
3191
+ fs6.writeFileSync(finalPath, buffer);
3171
3192
  console.log(
3172
3193
  chalk4.green(`Canvas written to: ${finalPath} (${buffer.length} bytes)`)
3173
3194
  );
3174
3195
  } else {
3175
- fs4.writeFileSync(outputPath, canvasContent, "utf-8");
3196
+ fs6.writeFileSync(outputPath, canvasContent, "utf-8");
3176
3197
  console.log(
3177
3198
  chalk4.green(
3178
3199
  `Canvas written to: ${outputPath} (${canvasContent.length} bytes)`
@@ -3188,6 +3209,197 @@ async function executeProfileGetCanvas(options) {
3188
3209
  );
3189
3210
  }
3190
3211
  }
3212
+ async function executeProfileSetCSS(options) {
3213
+ const sourceCount = [options.file, options.content, options.theme].filter(
3214
+ Boolean
3215
+ ).length;
3216
+ if (sourceCount === 0) {
3217
+ exitWithError(
3218
+ "Must provide one of --file, --content, or --theme to set CSS."
3219
+ );
3220
+ }
3221
+ if (sourceCount > 1) {
3222
+ exitWithError("Cannot provide more than one of --file, --content, --theme.");
3223
+ }
3224
+ let cssContent;
3225
+ if (options.theme) {
3226
+ const theme = DEMO_THEMES[options.theme];
3227
+ if (!theme) {
3228
+ const available = Object.entries(DEMO_THEMES).map(([key, val]) => ` ${key} \u2014 ${val.name}`).join("\n");
3229
+ exitWithError(
3230
+ `Unknown theme: "${options.theme}"
3231
+
3232
+ Available themes:
3233
+ ${available}`
3234
+ );
3235
+ }
3236
+ cssContent = theme.css;
3237
+ console.log(chalk4.gray(` Using theme: ${theme.name}`));
3238
+ } else if (options.file) {
3239
+ const filePath = path.resolve(options.file);
3240
+ if (!fs6.existsSync(filePath)) {
3241
+ exitWithError(`File not found: ${filePath}`);
3242
+ }
3243
+ const buffer = fs6.readFileSync(filePath);
3244
+ if (buffer.length > MAX_CSS_SIZE) {
3245
+ exitWithError(
3246
+ `File too large: ${buffer.length} bytes exceeds maximum of ${MAX_CSS_SIZE} bytes (10KB).`
3247
+ );
3248
+ }
3249
+ cssContent = buffer.toString("utf-8");
3250
+ } else {
3251
+ cssContent = options.content;
3252
+ const contentSize = Buffer.byteLength(cssContent, "utf-8");
3253
+ if (contentSize > MAX_CSS_SIZE) {
3254
+ exitWithError(
3255
+ `Content too large: ${contentSize} bytes exceeds maximum of ${MAX_CSS_SIZE} bytes (10KB).`
3256
+ );
3257
+ }
3258
+ }
3259
+ if (!isValidCSS(cssContent)) {
3260
+ exitWithError(
3261
+ "Invalid CSS: content is empty, too large, or contains disallowed patterns (script injection)."
3262
+ );
3263
+ }
3264
+ const storageArgs = getProfileCSSStorageArgs(cssContent);
3265
+ if (options.encodeOnly) {
3266
+ const readOnlyOptions = parseReadOnlyOptions({
3267
+ chainId: options.chainId,
3268
+ rpcUrl: options.rpcUrl
3269
+ });
3270
+ const encoded = encodeTransaction(
3271
+ {
3272
+ to: STORAGE_CONTRACT.address,
3273
+ abi: STORAGE_CONTRACT.abi,
3274
+ functionName: "put",
3275
+ args: [storageArgs.bytesKey, storageArgs.topic, storageArgs.bytesValue]
3276
+ },
3277
+ readOnlyOptions.chainId
3278
+ );
3279
+ console.log(JSON.stringify(encoded, null, 2));
3280
+ return;
3281
+ }
3282
+ const commonOptions = parseCommonOptions(
3283
+ {
3284
+ privateKey: options.privateKey,
3285
+ chainId: options.chainId,
3286
+ rpcUrl: options.rpcUrl
3287
+ },
3288
+ true
3289
+ );
3290
+ try {
3291
+ const account = privateKeyToAccount(commonOptions.privateKey);
3292
+ const rpcUrls = getChainRpcUrls({
3293
+ chainId: commonOptions.chainId,
3294
+ rpcUrl: commonOptions.rpcUrl
3295
+ });
3296
+ const client = createWalletClient({
3297
+ account,
3298
+ chain: base,
3299
+ transport: http(rpcUrls[0])
3300
+ }).extend(publicActions);
3301
+ console.log(chalk4.blue(`Setting profile CSS...`));
3302
+ console.log(
3303
+ chalk4.gray(
3304
+ ` Content size: ${Buffer.byteLength(cssContent, "utf-8")} bytes`
3305
+ )
3306
+ );
3307
+ console.log(chalk4.gray(` Address: ${account.address}`));
3308
+ const hash = await client.writeContract({
3309
+ address: STORAGE_CONTRACT.address,
3310
+ abi: STORAGE_CONTRACT.abi,
3311
+ functionName: "put",
3312
+ args: [storageArgs.bytesKey, storageArgs.topic, storageArgs.bytesValue]
3313
+ });
3314
+ console.log(chalk4.blue(`Waiting for confirmation...`));
3315
+ const receipt = await client.waitForTransactionReceipt({ hash });
3316
+ if (receipt.status === "success") {
3317
+ console.log(
3318
+ chalk4.green(
3319
+ `
3320
+ CSS updated successfully!
3321
+ Transaction: ${hash}
3322
+ Content size: ${Buffer.byteLength(cssContent, "utf-8")} bytes`
3323
+ )
3324
+ );
3325
+ } else {
3326
+ exitWithError(`Transaction failed: ${hash}`);
3327
+ }
3328
+ } catch (error) {
3329
+ exitWithError(
3330
+ `Failed to set CSS: ${error instanceof Error ? error.message : String(error)}`
3331
+ );
3332
+ }
3333
+ }
3334
+ async function executeProfileGetCSS(options) {
3335
+ const readOnlyOptions = parseReadOnlyOptions({
3336
+ chainId: options.chainId,
3337
+ rpcUrl: options.rpcUrl
3338
+ });
3339
+ const client = new StorageClient({
3340
+ chainId: readOnlyOptions.chainId,
3341
+ overrides: options.rpcUrl ? { rpcUrls: [options.rpcUrl] } : void 0
3342
+ });
3343
+ try {
3344
+ let cssContent;
3345
+ try {
3346
+ const result = await client.readStorageData({
3347
+ key: PROFILE_CSS_STORAGE_KEY,
3348
+ operator: options.address
3349
+ });
3350
+ if (result.data) {
3351
+ cssContent = result.data;
3352
+ }
3353
+ } catch (error) {
3354
+ const errorMessage = error instanceof Error ? error.message : String(error);
3355
+ if (errorMessage !== "StoredDataNotFound") {
3356
+ throw error;
3357
+ }
3358
+ }
3359
+ if (options.json) {
3360
+ const output = {
3361
+ address: options.address,
3362
+ chainId: readOnlyOptions.chainId,
3363
+ css: cssContent || null,
3364
+ hasCSS: !!cssContent,
3365
+ contentLength: cssContent ? cssContent.length : 0
3366
+ };
3367
+ console.log(JSON.stringify(output, null, 2));
3368
+ return;
3369
+ }
3370
+ if (!cssContent) {
3371
+ exitWithError(`No custom CSS found for address: ${options.address}`);
3372
+ }
3373
+ if (options.output) {
3374
+ const outputPath = path.resolve(options.output);
3375
+ fs6.writeFileSync(outputPath, cssContent, "utf-8");
3376
+ console.log(
3377
+ chalk4.green(
3378
+ `CSS written to: ${outputPath} (${cssContent.length} bytes)`
3379
+ )
3380
+ );
3381
+ return;
3382
+ }
3383
+ console.log(cssContent);
3384
+ } catch (error) {
3385
+ exitWithError(
3386
+ `Failed to read CSS: ${error instanceof Error ? error.message : String(error)}`
3387
+ );
3388
+ }
3389
+ }
3390
+ async function executeProfileCSSPrompt(options) {
3391
+ if (options.listThemes) {
3392
+ console.log("Available demo themes:\n");
3393
+ for (const [key, theme] of Object.entries(DEMO_THEMES)) {
3394
+ console.log(` ${key} \u2014 ${theme.name}`);
3395
+ }
3396
+ console.log(
3397
+ "\nUse with: net profile set-css --theme <name>"
3398
+ );
3399
+ return;
3400
+ }
3401
+ console.log(buildCSSPrompt());
3402
+ }
3191
3403
 
3192
3404
  // src/commands/profile/index.ts
3193
3405
  function registerProfileCommand(program2) {
@@ -3378,6 +3590,51 @@ function registerProfileCommand(program2) {
3378
3590
  json: options.json
3379
3591
  });
3380
3592
  });
3593
+ const setCSSCommand = new Command("set-css").description("Set your profile custom CSS theme").option("--file <path>", "Path to CSS file").option("--content <css>", "CSS content (inline)").option("--theme <name>", "Use a built-in demo theme (e.g. hotPink, midnightGrunge, ocean)").option(
3594
+ "--private-key <key>",
3595
+ "Private key (0x-prefixed hex, 66 characters). Can also be set via NET_PRIVATE_KEY env var"
3596
+ ).option(
3597
+ "--chain-id <id>",
3598
+ "Chain ID. Can also be set via NET_CHAIN_ID env var",
3599
+ (value) => parseInt(value, 10)
3600
+ ).option(
3601
+ "--rpc-url <url>",
3602
+ "Custom RPC URL. Can also be set via NET_RPC_URL env var"
3603
+ ).option(
3604
+ "--encode-only",
3605
+ "Output transaction data as JSON instead of executing"
3606
+ ).action(async (options) => {
3607
+ await executeProfileSetCSS({
3608
+ file: options.file,
3609
+ content: options.content,
3610
+ theme: options.theme,
3611
+ privateKey: options.privateKey,
3612
+ chainId: options.chainId,
3613
+ rpcUrl: options.rpcUrl,
3614
+ encodeOnly: options.encodeOnly
3615
+ });
3616
+ });
3617
+ const getCSSCommand = new Command("get-css").description("Get profile custom CSS for an address").requiredOption("--address <address>", "Wallet address to get CSS for").option("--output <path>", "Write CSS content to file instead of stdout").option(
3618
+ "--chain-id <id>",
3619
+ "Chain ID. Can also be set via NET_CHAIN_ID env var",
3620
+ (value) => parseInt(value, 10)
3621
+ ).option(
3622
+ "--rpc-url <url>",
3623
+ "Custom RPC URL. Can also be set via NET_RPC_URL env var"
3624
+ ).option("--json", "Output in JSON format").action(async (options) => {
3625
+ await executeProfileGetCSS({
3626
+ address: options.address,
3627
+ output: options.output,
3628
+ chainId: options.chainId,
3629
+ rpcUrl: options.rpcUrl,
3630
+ json: options.json
3631
+ });
3632
+ });
3633
+ const cssPromptCommand = new Command("css-prompt").description("Print the AI prompt for generating profile CSS themes").option("--list-themes", "List available demo themes instead of the prompt").action(async (options) => {
3634
+ await executeProfileCSSPrompt({
3635
+ listThemes: options.listThemes
3636
+ });
3637
+ });
3381
3638
  profileCommand.addCommand(getCommand);
3382
3639
  profileCommand.addCommand(setPictureCommand);
3383
3640
  profileCommand.addCommand(setUsernameCommand);
@@ -3386,6 +3643,9 @@ function registerProfileCommand(program2) {
3386
3643
  profileCommand.addCommand(setTokenAddressCommand);
3387
3644
  profileCommand.addCommand(setCanvasCommand);
3388
3645
  profileCommand.addCommand(getCanvasCommand);
3646
+ profileCommand.addCommand(setCSSCommand);
3647
+ profileCommand.addCommand(getCSSCommand);
3648
+ profileCommand.addCommand(cssPromptCommand);
3389
3649
  }
3390
3650
 
3391
3651
  // src/commands/bazaar/format.ts
@@ -5472,14 +5732,14 @@ var MAX_HISTORY_ENTRIES = 100;
5472
5732
  var STATE_DIR = path.join(os.homedir(), ".botchan");
5473
5733
  var STATE_FILE = path.join(STATE_DIR, "state.json");
5474
5734
  function ensureStateDir() {
5475
- if (!fs4.existsSync(STATE_DIR)) {
5476
- fs4.mkdirSync(STATE_DIR, { recursive: true });
5735
+ if (!fs6.existsSync(STATE_DIR)) {
5736
+ fs6.mkdirSync(STATE_DIR, { recursive: true });
5477
5737
  }
5478
5738
  }
5479
5739
  function loadState() {
5480
5740
  try {
5481
- if (fs4.existsSync(STATE_FILE)) {
5482
- const data = fs4.readFileSync(STATE_FILE, "utf-8");
5741
+ if (fs6.existsSync(STATE_FILE)) {
5742
+ const data = fs6.readFileSync(STATE_FILE, "utf-8");
5483
5743
  return JSON.parse(data);
5484
5744
  }
5485
5745
  } catch {
@@ -5489,8 +5749,8 @@ function loadState() {
5489
5749
  function saveState(state) {
5490
5750
  ensureStateDir();
5491
5751
  const tempFile = `${STATE_FILE}.tmp`;
5492
- fs4.writeFileSync(tempFile, JSON.stringify(state, null, 2));
5493
- fs4.renameSync(tempFile, STATE_FILE);
5752
+ fs6.writeFileSync(tempFile, JSON.stringify(state, null, 2));
5753
+ fs6.renameSync(tempFile, STATE_FILE);
5494
5754
  }
5495
5755
  function getLastSeenTimestamp(feedName) {
5496
5756
  const state = loadState();
@@ -5531,8 +5791,8 @@ function getFullState() {
5531
5791
  return loadState();
5532
5792
  }
5533
5793
  function resetState() {
5534
- if (fs4.existsSync(STATE_FILE)) {
5535
- fs4.unlinkSync(STATE_FILE);
5794
+ if (fs6.existsSync(STATE_FILE)) {
5795
+ fs6.unlinkSync(STATE_FILE);
5536
5796
  }
5537
5797
  }
5538
5798
  function getStateFilePath() {
@@ -6334,10 +6594,10 @@ async function confirm(message) {
6334
6594
  input: process.stdin,
6335
6595
  output: process.stdout
6336
6596
  });
6337
- return new Promise((resolve3) => {
6597
+ return new Promise((resolve5) => {
6338
6598
  rl.question(`${message} (y/N): `, (answer) => {
6339
6599
  rl.close();
6340
- resolve3(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
6600
+ resolve5(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
6341
6601
  });
6342
6602
  });
6343
6603
  }
@@ -6654,6 +6914,86 @@ function registerFeedCommand(program2) {
6654
6914
  registerFeedHistoryCommand(feedCommand);
6655
6915
  registerAgentRegisterCommand(feedCommand);
6656
6916
  }
6917
+ var CACHE_DIR = join(homedir(), ".netp");
6918
+ var CACHE_FILE = join(CACHE_DIR, "update-check.json");
6919
+ var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
6920
+ var FETCH_TIMEOUT_MS = 5e3;
6921
+ function isNewerVersion(latest, current) {
6922
+ const l = latest.split(".").map(Number);
6923
+ const c = current.split(".").map(Number);
6924
+ for (let i = 0; i < 3; i++) {
6925
+ if ((l[i] || 0) > (c[i] || 0)) return true;
6926
+ if ((l[i] || 0) < (c[i] || 0)) return false;
6927
+ }
6928
+ return false;
6929
+ }
6930
+ function readCache() {
6931
+ try {
6932
+ if (existsSync(CACHE_FILE)) {
6933
+ return JSON.parse(readFileSync(CACHE_FILE, "utf-8"));
6934
+ }
6935
+ } catch {
6936
+ }
6937
+ return null;
6938
+ }
6939
+ function writeCache(cache) {
6940
+ try {
6941
+ if (!existsSync(CACHE_DIR)) {
6942
+ mkdirSync(CACHE_DIR, { recursive: true });
6943
+ }
6944
+ writeFileSync(CACHE_FILE, JSON.stringify(cache));
6945
+ } catch {
6946
+ }
6947
+ }
6948
+ async function fetchLatestVersion(pkg) {
6949
+ try {
6950
+ const controller = new AbortController();
6951
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
6952
+ const res = await fetch(
6953
+ `https://registry.npmjs.org/${encodeURIComponent(pkg).replace("%40", "@")}/latest`,
6954
+ { signal: controller.signal }
6955
+ );
6956
+ clearTimeout(timeout);
6957
+ if (res.ok) {
6958
+ const data = await res.json();
6959
+ return data.version;
6960
+ }
6961
+ } catch {
6962
+ }
6963
+ return null;
6964
+ }
6965
+ async function getUpdateInfo(currentVersion) {
6966
+ const cache = readCache();
6967
+ if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL_MS) {
6968
+ if (cache.latestVersion && isNewerVersion(cache.latestVersion, currentVersion)) {
6969
+ return { latest: cache.latestVersion };
6970
+ }
6971
+ return null;
6972
+ }
6973
+ const latest = await fetchLatestVersion("@net-protocol/cli");
6974
+ writeCache({
6975
+ lastCheck: Date.now(),
6976
+ latestVersion: latest
6977
+ });
6978
+ if (latest && isNewerVersion(latest, currentVersion)) {
6979
+ return { latest };
6980
+ }
6981
+ return null;
6982
+ }
6983
+ function printUpdateBanner(current, latest) {
6984
+ console.error("");
6985
+ console.error(
6986
+ chalk4.yellow(
6987
+ ` Update available: ${chalk4.gray(current)} \u2192 ${chalk4.green(latest)}`
6988
+ )
6989
+ );
6990
+ console.error(
6991
+ chalk4.yellow(
6992
+ ` Run ${chalk4.cyan("npm install -g @net-protocol/cli@latest")} to update`
6993
+ )
6994
+ );
6995
+ console.error("");
6996
+ }
6657
6997
 
6658
6998
  // src/cli/index.ts
6659
6999
  var proxyUrl = process.env.https_proxy || process.env.HTTPS_PROXY;
@@ -6673,6 +7013,33 @@ registerTokenCommand(program);
6673
7013
  registerProfileCommand(program);
6674
7014
  registerBazaarCommand(program);
6675
7015
  registerFeedCommand(program);
6676
- program.parse();
7016
+ program.command("update").description("Update netp to the latest version").action(async () => {
7017
+ const { execSync } = await import('child_process');
7018
+ console.log("Updating @net-protocol/cli...");
7019
+ try {
7020
+ execSync("npm install -g @net-protocol/cli@latest", {
7021
+ stdio: "inherit"
7022
+ });
7023
+ console.log(chalk4.green("\n\u2713 netp updated successfully"));
7024
+ } catch {
7025
+ console.error(
7026
+ chalk4.red(
7027
+ "Failed to update. Try manually: npm install -g @net-protocol/cli@latest"
7028
+ )
7029
+ );
7030
+ }
7031
+ });
7032
+ var updatePromise = getUpdateInfo(version).catch(() => null);
7033
+ await program.parseAsync();
7034
+ try {
7035
+ const updateInfo = await Promise.race([
7036
+ updatePromise,
7037
+ new Promise((resolve5) => setTimeout(() => resolve5(null), 1500))
7038
+ ]);
7039
+ if (updateInfo) {
7040
+ printUpdateBanner(version, updateInfo.latest);
7041
+ }
7042
+ } catch {
7043
+ }
6677
7044
  //# sourceMappingURL=index.mjs.map
6678
7045
  //# sourceMappingURL=index.mjs.map