@net-protocol/cli 0.1.25 → 0.1.27

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,7 +4,7 @@ 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';
7
+ import * as fs6 from 'fs';
8
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';
@@ -13,7 +13,7 @@ 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
19
  import { join } from 'path';
@@ -1257,7 +1257,7 @@ async function encodeStorageUpload(options) {
1257
1257
  } else {
1258
1258
  operatorAddress = "0x0000000000000000000000000000000000000000";
1259
1259
  }
1260
- const fileContent = fs4.readFileSync(options.filePath, "utf-8");
1260
+ const fileContent = fs6.readFileSync(options.filePath, "utf-8");
1261
1261
  const fileSize = Buffer.byteLength(fileContent, "utf-8");
1262
1262
  const client = new StorageClient({
1263
1263
  chainId: readOnlyOptions.chainId
@@ -1560,8 +1560,8 @@ function registerStorageCommand(program2) {
1560
1560
  console.log(chalk4.blue(`\u{1F4C1} Reading file: ${options.file}`));
1561
1561
  console.log(chalk4.blue(`\u{1F517} Using relay API: ${options.apiUrl}`));
1562
1562
  const result = await uploadFileWithRelay(uploadRelayOptions);
1563
- const { privateKeyToAccount: privateKeyToAccount25 } = await import('viem/accounts');
1564
- const userAccount = privateKeyToAccount25(commonOptions.privateKey);
1563
+ const { privateKeyToAccount: privateKeyToAccount26 } = await import('viem/accounts');
1564
+ const userAccount = privateKeyToAccount26(commonOptions.privateKey);
1565
1565
  const storageUrl = generateStorageUrl(
1566
1566
  userAccount.address,
1567
1567
  commonOptions.chainId,
@@ -2396,7 +2396,22 @@ async function executeProfileGet(options) {
2396
2396
  throw error;
2397
2397
  }
2398
2398
  }
2399
- 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;
2400
2415
  if (options.json) {
2401
2416
  const output = {
2402
2417
  address: options.address,
@@ -2406,6 +2421,7 @@ async function executeProfileGet(options) {
2406
2421
  bio: bio || null,
2407
2422
  tokenAddress: tokenAddress || null,
2408
2423
  canvas: canvasSize ? { size: canvasSize, isDataUri: canvasIsDataUri } : null,
2424
+ css: cssSize ? { size: cssSize } : null,
2409
2425
  hasProfile
2410
2426
  };
2411
2427
  console.log(JSON.stringify(output, null, 2));
@@ -2429,6 +2445,9 @@ async function executeProfileGet(options) {
2429
2445
  console.log(
2430
2446
  ` ${chalk4.cyan("Canvas:")} ${canvasSize ? `${canvasSize} bytes${canvasIsDataUri ? " (data URI)" : ""}` : chalk4.gray("(not set)")}`
2431
2447
  );
2448
+ console.log(
2449
+ ` ${chalk4.cyan("Custom CSS:")} ${cssSize ? `${cssSize} bytes` : chalk4.gray("(not set)")}`
2450
+ );
2432
2451
  if (!hasProfile) {
2433
2452
  console.log(chalk4.yellow("\n No profile data found for this address."));
2434
2453
  }
@@ -2991,10 +3010,10 @@ async function executeProfileSetCanvas(options) {
2991
3010
  let canvasContent;
2992
3011
  if (options.file) {
2993
3012
  const filePath = path.resolve(options.file);
2994
- if (!fs4.existsSync(filePath)) {
3013
+ if (!fs6.existsSync(filePath)) {
2995
3014
  exitWithError(`File not found: ${filePath}`);
2996
3015
  }
2997
- const buffer = fs4.readFileSync(filePath);
3016
+ const buffer = fs6.readFileSync(filePath);
2998
3017
  if (buffer.length > MAX_CANVAS_SIZE) {
2999
3018
  exitWithError(
3000
3019
  `File too large: ${buffer.length} bytes exceeds maximum of ${MAX_CANVAS_SIZE} bytes (60KB).`
@@ -3169,12 +3188,12 @@ async function executeProfileGetCanvas(options) {
3169
3188
  if (!path.extname(outputPath)) {
3170
3189
  finalPath = outputPath + getExtensionFromMimeType(mimeType);
3171
3190
  }
3172
- fs4.writeFileSync(finalPath, buffer);
3191
+ fs6.writeFileSync(finalPath, buffer);
3173
3192
  console.log(
3174
3193
  chalk4.green(`Canvas written to: ${finalPath} (${buffer.length} bytes)`)
3175
3194
  );
3176
3195
  } else {
3177
- fs4.writeFileSync(outputPath, canvasContent, "utf-8");
3196
+ fs6.writeFileSync(outputPath, canvasContent, "utf-8");
3178
3197
  console.log(
3179
3198
  chalk4.green(
3180
3199
  `Canvas written to: ${outputPath} (${canvasContent.length} bytes)`
@@ -3190,6 +3209,197 @@ async function executeProfileGetCanvas(options) {
3190
3209
  );
3191
3210
  }
3192
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
+ }
3193
3403
 
3194
3404
  // src/commands/profile/index.ts
3195
3405
  function registerProfileCommand(program2) {
@@ -3380,6 +3590,51 @@ function registerProfileCommand(program2) {
3380
3590
  json: options.json
3381
3591
  });
3382
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
+ });
3383
3638
  profileCommand.addCommand(getCommand);
3384
3639
  profileCommand.addCommand(setPictureCommand);
3385
3640
  profileCommand.addCommand(setUsernameCommand);
@@ -3388,6 +3643,9 @@ function registerProfileCommand(program2) {
3388
3643
  profileCommand.addCommand(setTokenAddressCommand);
3389
3644
  profileCommand.addCommand(setCanvasCommand);
3390
3645
  profileCommand.addCommand(getCanvasCommand);
3646
+ profileCommand.addCommand(setCSSCommand);
3647
+ profileCommand.addCommand(getCSSCommand);
3648
+ profileCommand.addCommand(cssPromptCommand);
3391
3649
  }
3392
3650
 
3393
3651
  // src/commands/bazaar/format.ts
@@ -5474,14 +5732,14 @@ var MAX_HISTORY_ENTRIES = 100;
5474
5732
  var STATE_DIR = path.join(os.homedir(), ".botchan");
5475
5733
  var STATE_FILE = path.join(STATE_DIR, "state.json");
5476
5734
  function ensureStateDir() {
5477
- if (!fs4.existsSync(STATE_DIR)) {
5478
- fs4.mkdirSync(STATE_DIR, { recursive: true });
5735
+ if (!fs6.existsSync(STATE_DIR)) {
5736
+ fs6.mkdirSync(STATE_DIR, { recursive: true });
5479
5737
  }
5480
5738
  }
5481
5739
  function loadState() {
5482
5740
  try {
5483
- if (fs4.existsSync(STATE_FILE)) {
5484
- const data = fs4.readFileSync(STATE_FILE, "utf-8");
5741
+ if (fs6.existsSync(STATE_FILE)) {
5742
+ const data = fs6.readFileSync(STATE_FILE, "utf-8");
5485
5743
  return JSON.parse(data);
5486
5744
  }
5487
5745
  } catch {
@@ -5491,8 +5749,8 @@ function loadState() {
5491
5749
  function saveState(state) {
5492
5750
  ensureStateDir();
5493
5751
  const tempFile = `${STATE_FILE}.tmp`;
5494
- fs4.writeFileSync(tempFile, JSON.stringify(state, null, 2));
5495
- fs4.renameSync(tempFile, STATE_FILE);
5752
+ fs6.writeFileSync(tempFile, JSON.stringify(state, null, 2));
5753
+ fs6.renameSync(tempFile, STATE_FILE);
5496
5754
  }
5497
5755
  function getLastSeenTimestamp(feedName) {
5498
5756
  const state = loadState();
@@ -5533,8 +5791,8 @@ function getFullState() {
5533
5791
  return loadState();
5534
5792
  }
5535
5793
  function resetState() {
5536
- if (fs4.existsSync(STATE_FILE)) {
5537
- fs4.unlinkSync(STATE_FILE);
5794
+ if (fs6.existsSync(STATE_FILE)) {
5795
+ fs6.unlinkSync(STATE_FILE);
5538
5796
  }
5539
5797
  }
5540
5798
  function getStateFilePath() {
@@ -6336,10 +6594,10 @@ async function confirm(message) {
6336
6594
  input: process.stdin,
6337
6595
  output: process.stdout
6338
6596
  });
6339
- return new Promise((resolve3) => {
6597
+ return new Promise((resolve5) => {
6340
6598
  rl.question(`${message} (y/N): `, (answer) => {
6341
6599
  rl.close();
6342
- resolve3(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
6600
+ resolve5(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
6343
6601
  });
6344
6602
  });
6345
6603
  }
@@ -6776,7 +7034,7 @@ await program.parseAsync();
6776
7034
  try {
6777
7035
  const updateInfo = await Promise.race([
6778
7036
  updatePromise,
6779
- new Promise((resolve3) => setTimeout(() => resolve3(null), 1500))
7037
+ new Promise((resolve5) => setTimeout(() => resolve5(null), 1500))
6780
7038
  ]);
6781
7039
  if (updateInfo) {
6782
7040
  printUpdateBanner(version, updateInfo.latest);