@net-protocol/cli 0.1.25 → 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 +23 -7
- package/dist/cli/index.mjs +279 -21
- package/dist/cli/index.mjs.map +1 -1
- package/dist/profile/index.mjs +260 -2
- package/dist/profile/index.mjs.map +1 -1
- package/package.json +2 -2
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 (
|
|
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
|
|
782
|
-
│ │ ├── set-username.ts
|
|
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
|
-
│
|
|
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
|
```
|
package/dist/cli/index.mjs
CHANGED
|
@@ -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
|
|
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 =
|
|
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:
|
|
1564
|
-
const userAccount =
|
|
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
|
-
|
|
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 (!
|
|
3013
|
+
if (!fs6.existsSync(filePath)) {
|
|
2995
3014
|
exitWithError(`File not found: ${filePath}`);
|
|
2996
3015
|
}
|
|
2997
|
-
const buffer =
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
5478
|
-
|
|
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 (
|
|
5484
|
-
const data =
|
|
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
|
-
|
|
5495
|
-
|
|
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 (
|
|
5537
|
-
|
|
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((
|
|
6597
|
+
return new Promise((resolve5) => {
|
|
6340
6598
|
rl.question(`${message} (y/N): `, (answer) => {
|
|
6341
6599
|
rl.close();
|
|
6342
|
-
|
|
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((
|
|
7037
|
+
new Promise((resolve5) => setTimeout(() => resolve5(null), 1500))
|
|
6780
7038
|
]);
|
|
6781
7039
|
if (updateInfo) {
|
|
6782
7040
|
printUpdateBanner(version, updateInfo.latest);
|