@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 +23 -7
- package/dist/cli/index.mjs +389 -22
- 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/dist/profile/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import chalk3 from 'chalk';
|
|
3
3
|
import { StorageClient, chunkDataForStorage, CHUNKED_STORAGE_CONTRACT } from '@net-protocol/storage';
|
|
4
|
-
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';
|
|
4
|
+
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';
|
|
5
5
|
import { createWalletClient, http, publicActions, encodeFunctionData } from 'viem';
|
|
6
6
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
7
7
|
import { base } from 'viem/chains';
|
|
@@ -131,7 +131,22 @@ async function executeProfileGet(options) {
|
|
|
131
131
|
throw error;
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
-
|
|
134
|
+
let cssSize;
|
|
135
|
+
try {
|
|
136
|
+
const cssResult = await client.readStorageData({
|
|
137
|
+
key: PROFILE_CSS_STORAGE_KEY,
|
|
138
|
+
operator: options.address
|
|
139
|
+
});
|
|
140
|
+
if (cssResult.data) {
|
|
141
|
+
cssSize = cssResult.data.length;
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
145
|
+
if (errorMessage !== "StoredDataNotFound") {
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const hasProfile = profilePicture || xUsername || bio || tokenAddress || canvasSize || cssSize;
|
|
135
150
|
if (options.json) {
|
|
136
151
|
const output = {
|
|
137
152
|
address: options.address,
|
|
@@ -141,6 +156,7 @@ async function executeProfileGet(options) {
|
|
|
141
156
|
bio: bio || null,
|
|
142
157
|
tokenAddress: tokenAddress || null,
|
|
143
158
|
canvas: canvasSize ? { size: canvasSize, isDataUri: canvasIsDataUri } : null,
|
|
159
|
+
css: cssSize ? { size: cssSize } : null,
|
|
144
160
|
hasProfile
|
|
145
161
|
};
|
|
146
162
|
console.log(JSON.stringify(output, null, 2));
|
|
@@ -164,6 +180,9 @@ async function executeProfileGet(options) {
|
|
|
164
180
|
console.log(
|
|
165
181
|
` ${chalk3.cyan("Canvas:")} ${canvasSize ? `${canvasSize} bytes${canvasIsDataUri ? " (data URI)" : ""}` : chalk3.gray("(not set)")}`
|
|
166
182
|
);
|
|
183
|
+
console.log(
|
|
184
|
+
` ${chalk3.cyan("Custom CSS:")} ${cssSize ? `${cssSize} bytes` : chalk3.gray("(not set)")}`
|
|
185
|
+
);
|
|
167
186
|
if (!hasProfile) {
|
|
168
187
|
console.log(chalk3.yellow("\n No profile data found for this address."));
|
|
169
188
|
}
|
|
@@ -940,6 +959,197 @@ async function executeProfileGetCanvas(options) {
|
|
|
940
959
|
);
|
|
941
960
|
}
|
|
942
961
|
}
|
|
962
|
+
async function executeProfileSetCSS(options) {
|
|
963
|
+
const sourceCount = [options.file, options.content, options.theme].filter(
|
|
964
|
+
Boolean
|
|
965
|
+
).length;
|
|
966
|
+
if (sourceCount === 0) {
|
|
967
|
+
exitWithError(
|
|
968
|
+
"Must provide one of --file, --content, or --theme to set CSS."
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
if (sourceCount > 1) {
|
|
972
|
+
exitWithError("Cannot provide more than one of --file, --content, --theme.");
|
|
973
|
+
}
|
|
974
|
+
let cssContent;
|
|
975
|
+
if (options.theme) {
|
|
976
|
+
const theme = DEMO_THEMES[options.theme];
|
|
977
|
+
if (!theme) {
|
|
978
|
+
const available = Object.entries(DEMO_THEMES).map(([key, val]) => ` ${key} \u2014 ${val.name}`).join("\n");
|
|
979
|
+
exitWithError(
|
|
980
|
+
`Unknown theme: "${options.theme}"
|
|
981
|
+
|
|
982
|
+
Available themes:
|
|
983
|
+
${available}`
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
cssContent = theme.css;
|
|
987
|
+
console.log(chalk3.gray(` Using theme: ${theme.name}`));
|
|
988
|
+
} else if (options.file) {
|
|
989
|
+
const filePath = path.resolve(options.file);
|
|
990
|
+
if (!fs.existsSync(filePath)) {
|
|
991
|
+
exitWithError(`File not found: ${filePath}`);
|
|
992
|
+
}
|
|
993
|
+
const buffer = fs.readFileSync(filePath);
|
|
994
|
+
if (buffer.length > MAX_CSS_SIZE) {
|
|
995
|
+
exitWithError(
|
|
996
|
+
`File too large: ${buffer.length} bytes exceeds maximum of ${MAX_CSS_SIZE} bytes (10KB).`
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
cssContent = buffer.toString("utf-8");
|
|
1000
|
+
} else {
|
|
1001
|
+
cssContent = options.content;
|
|
1002
|
+
const contentSize = Buffer.byteLength(cssContent, "utf-8");
|
|
1003
|
+
if (contentSize > MAX_CSS_SIZE) {
|
|
1004
|
+
exitWithError(
|
|
1005
|
+
`Content too large: ${contentSize} bytes exceeds maximum of ${MAX_CSS_SIZE} bytes (10KB).`
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
if (!isValidCSS(cssContent)) {
|
|
1010
|
+
exitWithError(
|
|
1011
|
+
"Invalid CSS: content is empty, too large, or contains disallowed patterns (script injection)."
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
const storageArgs = getProfileCSSStorageArgs(cssContent);
|
|
1015
|
+
if (options.encodeOnly) {
|
|
1016
|
+
const readOnlyOptions = parseReadOnlyOptions({
|
|
1017
|
+
chainId: options.chainId,
|
|
1018
|
+
rpcUrl: options.rpcUrl
|
|
1019
|
+
});
|
|
1020
|
+
const encoded = encodeTransaction(
|
|
1021
|
+
{
|
|
1022
|
+
to: STORAGE_CONTRACT.address,
|
|
1023
|
+
abi: STORAGE_CONTRACT.abi,
|
|
1024
|
+
functionName: "put",
|
|
1025
|
+
args: [storageArgs.bytesKey, storageArgs.topic, storageArgs.bytesValue]
|
|
1026
|
+
},
|
|
1027
|
+
readOnlyOptions.chainId
|
|
1028
|
+
);
|
|
1029
|
+
console.log(JSON.stringify(encoded, null, 2));
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
const commonOptions = parseCommonOptions(
|
|
1033
|
+
{
|
|
1034
|
+
privateKey: options.privateKey,
|
|
1035
|
+
chainId: options.chainId,
|
|
1036
|
+
rpcUrl: options.rpcUrl
|
|
1037
|
+
},
|
|
1038
|
+
true
|
|
1039
|
+
);
|
|
1040
|
+
try {
|
|
1041
|
+
const account = privateKeyToAccount(commonOptions.privateKey);
|
|
1042
|
+
const rpcUrls = getChainRpcUrls({
|
|
1043
|
+
chainId: commonOptions.chainId,
|
|
1044
|
+
rpcUrl: commonOptions.rpcUrl
|
|
1045
|
+
});
|
|
1046
|
+
const client = createWalletClient({
|
|
1047
|
+
account,
|
|
1048
|
+
chain: base,
|
|
1049
|
+
transport: http(rpcUrls[0])
|
|
1050
|
+
}).extend(publicActions);
|
|
1051
|
+
console.log(chalk3.blue(`Setting profile CSS...`));
|
|
1052
|
+
console.log(
|
|
1053
|
+
chalk3.gray(
|
|
1054
|
+
` Content size: ${Buffer.byteLength(cssContent, "utf-8")} bytes`
|
|
1055
|
+
)
|
|
1056
|
+
);
|
|
1057
|
+
console.log(chalk3.gray(` Address: ${account.address}`));
|
|
1058
|
+
const hash = await client.writeContract({
|
|
1059
|
+
address: STORAGE_CONTRACT.address,
|
|
1060
|
+
abi: STORAGE_CONTRACT.abi,
|
|
1061
|
+
functionName: "put",
|
|
1062
|
+
args: [storageArgs.bytesKey, storageArgs.topic, storageArgs.bytesValue]
|
|
1063
|
+
});
|
|
1064
|
+
console.log(chalk3.blue(`Waiting for confirmation...`));
|
|
1065
|
+
const receipt = await client.waitForTransactionReceipt({ hash });
|
|
1066
|
+
if (receipt.status === "success") {
|
|
1067
|
+
console.log(
|
|
1068
|
+
chalk3.green(
|
|
1069
|
+
`
|
|
1070
|
+
CSS updated successfully!
|
|
1071
|
+
Transaction: ${hash}
|
|
1072
|
+
Content size: ${Buffer.byteLength(cssContent, "utf-8")} bytes`
|
|
1073
|
+
)
|
|
1074
|
+
);
|
|
1075
|
+
} else {
|
|
1076
|
+
exitWithError(`Transaction failed: ${hash}`);
|
|
1077
|
+
}
|
|
1078
|
+
} catch (error) {
|
|
1079
|
+
exitWithError(
|
|
1080
|
+
`Failed to set CSS: ${error instanceof Error ? error.message : String(error)}`
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
async function executeProfileGetCSS(options) {
|
|
1085
|
+
const readOnlyOptions = parseReadOnlyOptions({
|
|
1086
|
+
chainId: options.chainId,
|
|
1087
|
+
rpcUrl: options.rpcUrl
|
|
1088
|
+
});
|
|
1089
|
+
const client = new StorageClient({
|
|
1090
|
+
chainId: readOnlyOptions.chainId,
|
|
1091
|
+
overrides: options.rpcUrl ? { rpcUrls: [options.rpcUrl] } : void 0
|
|
1092
|
+
});
|
|
1093
|
+
try {
|
|
1094
|
+
let cssContent;
|
|
1095
|
+
try {
|
|
1096
|
+
const result = await client.readStorageData({
|
|
1097
|
+
key: PROFILE_CSS_STORAGE_KEY,
|
|
1098
|
+
operator: options.address
|
|
1099
|
+
});
|
|
1100
|
+
if (result.data) {
|
|
1101
|
+
cssContent = result.data;
|
|
1102
|
+
}
|
|
1103
|
+
} catch (error) {
|
|
1104
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1105
|
+
if (errorMessage !== "StoredDataNotFound") {
|
|
1106
|
+
throw error;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
if (options.json) {
|
|
1110
|
+
const output = {
|
|
1111
|
+
address: options.address,
|
|
1112
|
+
chainId: readOnlyOptions.chainId,
|
|
1113
|
+
css: cssContent || null,
|
|
1114
|
+
hasCSS: !!cssContent,
|
|
1115
|
+
contentLength: cssContent ? cssContent.length : 0
|
|
1116
|
+
};
|
|
1117
|
+
console.log(JSON.stringify(output, null, 2));
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
if (!cssContent) {
|
|
1121
|
+
exitWithError(`No custom CSS found for address: ${options.address}`);
|
|
1122
|
+
}
|
|
1123
|
+
if (options.output) {
|
|
1124
|
+
const outputPath = path.resolve(options.output);
|
|
1125
|
+
fs.writeFileSync(outputPath, cssContent, "utf-8");
|
|
1126
|
+
console.log(
|
|
1127
|
+
chalk3.green(
|
|
1128
|
+
`CSS written to: ${outputPath} (${cssContent.length} bytes)`
|
|
1129
|
+
)
|
|
1130
|
+
);
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
console.log(cssContent);
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
exitWithError(
|
|
1136
|
+
`Failed to read CSS: ${error instanceof Error ? error.message : String(error)}`
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
async function executeProfileCSSPrompt(options) {
|
|
1141
|
+
if (options.listThemes) {
|
|
1142
|
+
console.log("Available demo themes:\n");
|
|
1143
|
+
for (const [key, theme] of Object.entries(DEMO_THEMES)) {
|
|
1144
|
+
console.log(` ${key} \u2014 ${theme.name}`);
|
|
1145
|
+
}
|
|
1146
|
+
console.log(
|
|
1147
|
+
"\nUse with: net profile set-css --theme <name>"
|
|
1148
|
+
);
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
console.log(buildCSSPrompt());
|
|
1152
|
+
}
|
|
943
1153
|
|
|
944
1154
|
// src/commands/profile/index.ts
|
|
945
1155
|
function registerProfileCommand(program) {
|
|
@@ -1130,6 +1340,51 @@ function registerProfileCommand(program) {
|
|
|
1130
1340
|
json: options.json
|
|
1131
1341
|
});
|
|
1132
1342
|
});
|
|
1343
|
+
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(
|
|
1344
|
+
"--private-key <key>",
|
|
1345
|
+
"Private key (0x-prefixed hex, 66 characters). Can also be set via NET_PRIVATE_KEY env var"
|
|
1346
|
+
).option(
|
|
1347
|
+
"--chain-id <id>",
|
|
1348
|
+
"Chain ID. Can also be set via NET_CHAIN_ID env var",
|
|
1349
|
+
(value) => parseInt(value, 10)
|
|
1350
|
+
).option(
|
|
1351
|
+
"--rpc-url <url>",
|
|
1352
|
+
"Custom RPC URL. Can also be set via NET_RPC_URL env var"
|
|
1353
|
+
).option(
|
|
1354
|
+
"--encode-only",
|
|
1355
|
+
"Output transaction data as JSON instead of executing"
|
|
1356
|
+
).action(async (options) => {
|
|
1357
|
+
await executeProfileSetCSS({
|
|
1358
|
+
file: options.file,
|
|
1359
|
+
content: options.content,
|
|
1360
|
+
theme: options.theme,
|
|
1361
|
+
privateKey: options.privateKey,
|
|
1362
|
+
chainId: options.chainId,
|
|
1363
|
+
rpcUrl: options.rpcUrl,
|
|
1364
|
+
encodeOnly: options.encodeOnly
|
|
1365
|
+
});
|
|
1366
|
+
});
|
|
1367
|
+
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(
|
|
1368
|
+
"--chain-id <id>",
|
|
1369
|
+
"Chain ID. Can also be set via NET_CHAIN_ID env var",
|
|
1370
|
+
(value) => parseInt(value, 10)
|
|
1371
|
+
).option(
|
|
1372
|
+
"--rpc-url <url>",
|
|
1373
|
+
"Custom RPC URL. Can also be set via NET_RPC_URL env var"
|
|
1374
|
+
).option("--json", "Output in JSON format").action(async (options) => {
|
|
1375
|
+
await executeProfileGetCSS({
|
|
1376
|
+
address: options.address,
|
|
1377
|
+
output: options.output,
|
|
1378
|
+
chainId: options.chainId,
|
|
1379
|
+
rpcUrl: options.rpcUrl,
|
|
1380
|
+
json: options.json
|
|
1381
|
+
});
|
|
1382
|
+
});
|
|
1383
|
+
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) => {
|
|
1384
|
+
await executeProfileCSSPrompt({
|
|
1385
|
+
listThemes: options.listThemes
|
|
1386
|
+
});
|
|
1387
|
+
});
|
|
1133
1388
|
profileCommand.addCommand(getCommand);
|
|
1134
1389
|
profileCommand.addCommand(setPictureCommand);
|
|
1135
1390
|
profileCommand.addCommand(setUsernameCommand);
|
|
@@ -1138,6 +1393,9 @@ function registerProfileCommand(program) {
|
|
|
1138
1393
|
profileCommand.addCommand(setTokenAddressCommand);
|
|
1139
1394
|
profileCommand.addCommand(setCanvasCommand);
|
|
1140
1395
|
profileCommand.addCommand(getCanvasCommand);
|
|
1396
|
+
profileCommand.addCommand(setCSSCommand);
|
|
1397
|
+
profileCommand.addCommand(getCSSCommand);
|
|
1398
|
+
profileCommand.addCommand(cssPromptCommand);
|
|
1141
1399
|
}
|
|
1142
1400
|
|
|
1143
1401
|
export { registerProfileCommand };
|