@zoralabs/cli 1.2.0 → 1.4.0
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/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.tsx
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command17 } from "commander";
|
|
5
5
|
import { ExitPromptError } from "@inquirer/core";
|
|
6
6
|
import "fs";
|
|
7
7
|
import { setApiBaseUrl } from "@zoralabs/coins-sdk";
|
|
@@ -175,6 +175,9 @@ function bannedCoinMessage(address) {
|
|
|
175
175
|
function bannedCoinBuyMessage(address) {
|
|
176
176
|
return `Unable to buy ${address} because it violates the Zora terms of service. Already own this coin? Run zora sell ${address} --all to exit your position.`;
|
|
177
177
|
}
|
|
178
|
+
function bannedProfileMessage(identifier) {
|
|
179
|
+
return `This account (${identifier}) has been blocked for violating the Zora terms of service.`;
|
|
180
|
+
}
|
|
178
181
|
function fsErrorMessage(err, path) {
|
|
179
182
|
if (!(err instanceof Error)) return String(err);
|
|
180
183
|
const code = err.code;
|
|
@@ -1466,7 +1469,7 @@ var getClient = () => {
|
|
|
1466
1469
|
return client;
|
|
1467
1470
|
};
|
|
1468
1471
|
var commonProperties = () => ({
|
|
1469
|
-
cli_version: true ? "1.
|
|
1472
|
+
cli_version: true ? "1.4.0" : "development",
|
|
1470
1473
|
os: process.platform,
|
|
1471
1474
|
arch: process.arch,
|
|
1472
1475
|
node_version: process.version
|
|
@@ -2754,6 +2757,23 @@ async function createFirstPost(params) {
|
|
|
2754
2757
|
};
|
|
2755
2758
|
}
|
|
2756
2759
|
|
|
2760
|
+
// src/lib/agent/api-key.ts
|
|
2761
|
+
var CREATE_API_KEY_MUTATION = "mutation CreateApiKeyMutation($apiKeyName: String!, $hosts: [String!]) { createApiKey(apiKeyName: $apiKeyName, hosts: $hosts) { apiKey }}";
|
|
2762
|
+
async function createApiKey(token, apiKeyName, hosts) {
|
|
2763
|
+
const { data, errors, status } = await graphqlRequest(
|
|
2764
|
+
token,
|
|
2765
|
+
CREATE_API_KEY_MUTATION,
|
|
2766
|
+
"CreateApiKeyMutation",
|
|
2767
|
+
{ apiKeyName, hosts: hosts ?? null }
|
|
2768
|
+
);
|
|
2769
|
+
const apiKey = data?.createApiKey?.apiKey;
|
|
2770
|
+
if (apiKey) {
|
|
2771
|
+
return apiKey;
|
|
2772
|
+
}
|
|
2773
|
+
const lastError = errors?.[0]?.message ?? `HTTP ${status}`;
|
|
2774
|
+
throw new Error(`createApiKey failed: ${lastError}`);
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2757
2777
|
// src/lib/agent/onboard.ts
|
|
2758
2778
|
var DEFAULT_BASE_RPC = "https://mainnet.base.org";
|
|
2759
2779
|
var ZORA_BASE_URL = "https://zora.co";
|
|
@@ -2777,6 +2797,15 @@ async function onboardAgent(opts) {
|
|
|
2777
2797
|
const isNewUser = session.isNewUser;
|
|
2778
2798
|
progress("profile", "creating the Zora profile");
|
|
2779
2799
|
let profile = await createAgentProfile(session.accessToken);
|
|
2800
|
+
progress("apiKey", "creating an API key");
|
|
2801
|
+
const apiKey = await createApiKey(session.accessToken, "AGENT_API_KEY");
|
|
2802
|
+
try {
|
|
2803
|
+
saveApiKey(apiKey);
|
|
2804
|
+
} catch (err) {
|
|
2805
|
+
throw new Error(
|
|
2806
|
+
`Failed to save API key: ${fsErrorMessage(err, getConfigPath())}`
|
|
2807
|
+
);
|
|
2808
|
+
}
|
|
2780
2809
|
if (opts.username !== void 0 || opts.bio !== void 0 || opts.avatar !== void 0) {
|
|
2781
2810
|
const chosen = [
|
|
2782
2811
|
opts.username !== void 0 ? "username" : void 0,
|
|
@@ -2839,7 +2868,7 @@ async function onboardAgent(opts) {
|
|
|
2839
2868
|
dryRun,
|
|
2840
2869
|
profileUrl: `${ZORA_BASE_URL}/@${profile.username}`
|
|
2841
2870
|
};
|
|
2842
|
-
if (opts.
|
|
2871
|
+
if (!opts.skipCoin) {
|
|
2843
2872
|
progress(
|
|
2844
2873
|
"coin",
|
|
2845
2874
|
dryRun ? "simulating the creator coin" : "minting the creator coin"
|
|
@@ -3068,12 +3097,12 @@ function resolveAgentKey(json, override, { allowGenerate = true } = {}) {
|
|
|
3068
3097
|
return { key: generated, source: getWalletPath(), generated: true };
|
|
3069
3098
|
}
|
|
3070
3099
|
var agentCommand = new Command("agent").description(
|
|
3071
|
-
"Create and manage a Zora agent identity.\nStands up an identity from an EOA \u2014 Privy account, profile,
|
|
3100
|
+
"Create and manage a Zora agent identity.\nStands up an identity from an EOA \u2014 Privy account, profile, smart wallet, and creator coin \u2014 with no human interaction."
|
|
3072
3101
|
).action(function() {
|
|
3073
3102
|
this.outputHelp();
|
|
3074
3103
|
});
|
|
3075
3104
|
agentCommand.command("create").description(
|
|
3076
|
-
"Create a Zora agent from an EOA, end to end and unattended: headless Privy account, profile,
|
|
3105
|
+
"Create a Zora agent from an EOA, end to end and unattended: headless Privy account, profile, smart wallet, and creator coin. A first post is published when --caption and --image are supplied. Every on-chain step is sponsored, so the agent needs no ETH. Skip the coin with --skip-coin."
|
|
3077
3106
|
).option(
|
|
3078
3107
|
"--private-key <key>",
|
|
3079
3108
|
"EOA private key to sign in with (default: ZORA_PRIVATE_KEY, then your saved wallet, else a new one is generated)"
|
|
@@ -3083,8 +3112,8 @@ agentCommand.command("create").description(
|
|
|
3083
3112
|
String(DEFAULT_SIWE_CHAIN_ID)
|
|
3084
3113
|
).option("--rpc-url <url>", "Base RPC URL (defaults to the public endpoint)").option(
|
|
3085
3114
|
"--dry-run",
|
|
3086
|
-
"Create the account, profile, and smart wallet, but simulate the
|
|
3087
|
-
).option("--
|
|
3115
|
+
"Create the account, profile, and smart wallet, but simulate the coin + post instead of minting them"
|
|
3116
|
+
).option("--skip-coin", "Skip minting the agent's creator coin").option("--skip-post", "Skip publishing the first post").option(
|
|
3088
3117
|
"--username <name>",
|
|
3089
3118
|
"Set the agent's username (also sets the display name; must be available). Default: an auto-assigned handle."
|
|
3090
3119
|
).option("--bio <text>", "Set the agent's bio. Default: an auto-assigned bio.").option(
|
|
@@ -3158,16 +3187,29 @@ agentCommand.command("create").description(
|
|
|
3158
3187
|
}
|
|
3159
3188
|
}
|
|
3160
3189
|
const resolved = resolveAgentKey(json, options.privateKey);
|
|
3161
|
-
const wouldMint =
|
|
3190
|
+
const wouldMint = !options.skipCoin || hasCaption && hasImage;
|
|
3162
3191
|
if (!options.dryRun && wouldMint) {
|
|
3163
3192
|
const existingAgent = peekAgentWallet();
|
|
3164
3193
|
if (existingAgent) {
|
|
3165
3194
|
await confirmAgentAction({
|
|
3166
3195
|
json,
|
|
3167
3196
|
force: options.force,
|
|
3168
|
-
warning:
|
|
3169
|
-
|
|
3170
|
-
|
|
3197
|
+
warning: (() => {
|
|
3198
|
+
const willMintCoin = !options.skipCoin;
|
|
3199
|
+
const willMintPost = hasCaption && hasImage;
|
|
3200
|
+
const what = [
|
|
3201
|
+
willMintCoin && "creator coin",
|
|
3202
|
+
willMintPost && "first post"
|
|
3203
|
+
].filter(Boolean).join(" and ");
|
|
3204
|
+
return `You already have an agent: @${existingAgent.username} (smart wallet ${existingAgent.smartWalletAddress}).
|
|
3205
|
+
Re-running 'agent create' will mint another ${what} for it.`;
|
|
3206
|
+
})(),
|
|
3207
|
+
question: (() => {
|
|
3208
|
+
const willMintCoin = !options.skipCoin;
|
|
3209
|
+
const willMintPost = hasCaption && hasImage;
|
|
3210
|
+
const what = [willMintCoin && "coin", willMintPost && "post"].filter(Boolean).join("/");
|
|
3211
|
+
return `Create another ${what} for @${existingAgent.username}?`;
|
|
3212
|
+
})()
|
|
3171
3213
|
});
|
|
3172
3214
|
}
|
|
3173
3215
|
}
|
|
@@ -3180,7 +3222,7 @@ Re-running 'agent create' will mint another creator coin and/or first post for i
|
|
|
3180
3222
|
chainId,
|
|
3181
3223
|
rpcUrl: options.rpcUrl,
|
|
3182
3224
|
dryRun: Boolean(options.dryRun),
|
|
3183
|
-
|
|
3225
|
+
skipCoin: Boolean(options.skipCoin),
|
|
3184
3226
|
skipPost: Boolean(options.skipPost),
|
|
3185
3227
|
username: options.username,
|
|
3186
3228
|
bio: options.bio,
|
|
@@ -3196,7 +3238,7 @@ Re-running 'agent create' will mint another creator coin and/or first post for i
|
|
|
3196
3238
|
return outputErrorAndExit(
|
|
3197
3239
|
json,
|
|
3198
3240
|
`Agent onboarding failed: ${formatError(err)}`,
|
|
3199
|
-
"Re-run to retry \u2014 the profile and smart wallet are idempotent.
|
|
3241
|
+
"Re-run to retry \u2014 the profile and smart wallet are idempotent."
|
|
3200
3242
|
);
|
|
3201
3243
|
}
|
|
3202
3244
|
const walletPath = getWalletPath();
|
|
@@ -3224,7 +3266,7 @@ Re-running 'agent create' will mint another creator coin and/or first post for i
|
|
|
3224
3266
|
generated_wallet: resolved.generated,
|
|
3225
3267
|
saved_to_wallet: savedToWallet,
|
|
3226
3268
|
dry_run: result.dryRun,
|
|
3227
|
-
|
|
3269
|
+
skip_coin: Boolean(options.skipCoin),
|
|
3228
3270
|
minted_coin: Boolean(result.coin?.hash),
|
|
3229
3271
|
minted_post: Boolean(result.post?.hash),
|
|
3230
3272
|
coin_failed: Boolean(result.coinError),
|
|
@@ -3249,8 +3291,10 @@ Re-running 'agent create' will mint another creator coin and/or first post for i
|
|
|
3249
3291
|
},
|
|
3250
3292
|
render: () => {
|
|
3251
3293
|
const simulated = result.dryRun && (result.coin || result.post);
|
|
3294
|
+
const simulatedWhat = [result.coin && "coin", result.post && "post"].filter(Boolean).join(" + ");
|
|
3252
3295
|
console.log(
|
|
3253
|
-
simulated ?
|
|
3296
|
+
simulated ? `
|
|
3297
|
+
\u2713 Agent ready (dry run \u2014 ${simulatedWhat} simulated, not minted)` : result.dryRun ? "\n\u2713 Agent ready (dry run \u2014 account + smart wallet created; coin + post skipped)" : "\n\u2713 Agent ready"
|
|
3254
3298
|
);
|
|
3255
3299
|
console.log(` Profile: @${result.username}`);
|
|
3256
3300
|
if (options.bio !== void 0) {
|
|
@@ -3270,8 +3314,11 @@ Re-running 'agent create' will mint another creator coin and/or first post for i
|
|
|
3270
3314
|
);
|
|
3271
3315
|
} else if (result.coinError) {
|
|
3272
3316
|
console.log(` Creator coin: failed \u2014 ${result.coinError}`);
|
|
3273
|
-
|
|
3274
|
-
|
|
3317
|
+
console.log(" Retry with `zora agent coin`.");
|
|
3318
|
+
} else if (!result.dryRun && options.skipCoin) {
|
|
3319
|
+
console.log(
|
|
3320
|
+
" Creator coin: skipped \u2014 add one later with `zora agent coin`"
|
|
3321
|
+
);
|
|
3275
3322
|
}
|
|
3276
3323
|
if (result.post) {
|
|
3277
3324
|
console.log(
|
|
@@ -3929,9 +3976,7 @@ budgetCommand.command("record").description(
|
|
|
3929
3976
|
"\u2022 No global budget configured \u2014 nothing to record. Set one with `zora agent budget set <amount>`."
|
|
3930
3977
|
);
|
|
3931
3978
|
} else {
|
|
3932
|
-
console.log(
|
|
3933
|
-
"\u2022 Budget opted out (no limit) \u2014 nothing to record."
|
|
3934
|
-
);
|
|
3979
|
+
console.log("\u2022 Budget opted out (no limit) \u2014 nothing to record.");
|
|
3935
3980
|
}
|
|
3936
3981
|
}
|
|
3937
3982
|
});
|
|
@@ -6917,10 +6962,17 @@ var fetchProfile = async (address) => {
|
|
|
6917
6962
|
address,
|
|
6918
6963
|
handle,
|
|
6919
6964
|
displayName: profile?.displayName ?? handle,
|
|
6920
|
-
avatarUrl: profile?.avatar?.previewImage?.small ?? null
|
|
6965
|
+
avatarUrl: profile?.avatar?.previewImage?.small ?? null,
|
|
6966
|
+
platformBlocked: profile?.platformBlocked ?? false
|
|
6921
6967
|
};
|
|
6922
6968
|
} catch {
|
|
6923
|
-
return {
|
|
6969
|
+
return {
|
|
6970
|
+
address,
|
|
6971
|
+
handle: null,
|
|
6972
|
+
displayName: null,
|
|
6973
|
+
avatarUrl: null,
|
|
6974
|
+
platformBlocked: false
|
|
6975
|
+
};
|
|
6924
6976
|
}
|
|
6925
6977
|
};
|
|
6926
6978
|
var resolveProfiles = async (addresses, _token, onProgress) => {
|
|
@@ -6935,7 +6987,8 @@ var resolveProfiles = async (addresses, _token, onProgress) => {
|
|
|
6935
6987
|
address,
|
|
6936
6988
|
handle: cached.handle,
|
|
6937
6989
|
displayName: cached.displayName,
|
|
6938
|
-
avatarUrl: cached.avatarUrl
|
|
6990
|
+
avatarUrl: cached.avatarUrl,
|
|
6991
|
+
platformBlocked: cached.platformBlocked ?? false
|
|
6939
6992
|
});
|
|
6940
6993
|
} else {
|
|
6941
6994
|
stale.push(address);
|
|
@@ -6960,6 +7013,7 @@ var resolveProfiles = async (addresses, _token, onProgress) => {
|
|
|
6960
7013
|
handle: profile.handle,
|
|
6961
7014
|
displayName: profile.displayName,
|
|
6962
7015
|
avatarUrl: profile.avatarUrl,
|
|
7016
|
+
platformBlocked: profile.platformBlocked,
|
|
6963
7017
|
fetchedAt: now
|
|
6964
7018
|
};
|
|
6965
7019
|
}
|
|
@@ -7085,7 +7139,7 @@ var resolveClient = async (json) => {
|
|
|
7085
7139
|
);
|
|
7086
7140
|
}
|
|
7087
7141
|
const token = await auth.getApiToken();
|
|
7088
|
-
const { createMessagingClient } = await import("./client-
|
|
7142
|
+
const { createMessagingClient } = await import("./client-M3K6L2ZM.js");
|
|
7089
7143
|
const client2 = await createMessagingClient(auth.signerSpec, {
|
|
7090
7144
|
// Register the CLI installation with the Zora backend so it shows up in the
|
|
7091
7145
|
// user's device list and counts against the install cap. Best-effort — see
|
|
@@ -7293,6 +7347,19 @@ dmCommand.command("send").description(
|
|
|
7293
7347
|
).argument("<address>", "Recipient: Zora handle (@name) or 0x address").argument("[message]", "Message text").action(async function(address, message) {
|
|
7294
7348
|
const json = getJson(this);
|
|
7295
7349
|
const peer = await resolvePeer(json, address);
|
|
7350
|
+
const profiles = await resolveProfiles([peer]);
|
|
7351
|
+
const profile = profiles.get(peer);
|
|
7352
|
+
if (profile?.platformBlocked) {
|
|
7353
|
+
track("cli_dm_send", {
|
|
7354
|
+
output_format: json ? "json" : "text",
|
|
7355
|
+
success: false,
|
|
7356
|
+
blocked_profile: true
|
|
7357
|
+
});
|
|
7358
|
+
return outputErrorAndExit(
|
|
7359
|
+
json,
|
|
7360
|
+
bannedProfileMessage(profile.handle ?? peer)
|
|
7361
|
+
);
|
|
7362
|
+
}
|
|
7296
7363
|
if (!message || !message.trim()) {
|
|
7297
7364
|
return outputErrorAndExit(
|
|
7298
7365
|
json,
|
|
@@ -7330,6 +7397,64 @@ dmCommand.command("send").description(
|
|
|
7330
7397
|
await client2.close();
|
|
7331
7398
|
}
|
|
7332
7399
|
});
|
|
7400
|
+
dmCommand.command("listen").description(
|
|
7401
|
+
"Stream incoming DMs in real time (no polling \u2014 uses XMTP's server-push stream to avoid rate limits)"
|
|
7402
|
+
).action(async function() {
|
|
7403
|
+
const json = getJson(this);
|
|
7404
|
+
const { client: client2 } = await resolveClient(json);
|
|
7405
|
+
if (!json) {
|
|
7406
|
+
console.log("Listening for new DMs\u2026 (Ctrl+C to stop)\n");
|
|
7407
|
+
}
|
|
7408
|
+
const shutdown = () => {
|
|
7409
|
+
client2.close().finally(() => process.exit(0));
|
|
7410
|
+
};
|
|
7411
|
+
process.on("SIGINT", shutdown);
|
|
7412
|
+
process.on("SIGTERM", shutdown);
|
|
7413
|
+
const labelCache = /* @__PURE__ */ new Map();
|
|
7414
|
+
const cachedPeerLabel = async (peer) => {
|
|
7415
|
+
const cached = labelCache.get(peer);
|
|
7416
|
+
if (cached) return cached;
|
|
7417
|
+
const label = await peerLabel(peer);
|
|
7418
|
+
labelCache.set(peer, label);
|
|
7419
|
+
return label;
|
|
7420
|
+
};
|
|
7421
|
+
let messageCount = 0;
|
|
7422
|
+
track("cli_dm_listen_start", {
|
|
7423
|
+
output_format: json ? "json" : "text"
|
|
7424
|
+
});
|
|
7425
|
+
try {
|
|
7426
|
+
for await (const msg of client2.streamAllMessages()) {
|
|
7427
|
+
if (msg.fromSelf) continue;
|
|
7428
|
+
messageCount++;
|
|
7429
|
+
const who = msg.peerAddress ? await cachedPeerLabel(msg.peerAddress) : "unknown";
|
|
7430
|
+
if (json) {
|
|
7431
|
+
console.log(
|
|
7432
|
+
JSON.stringify({
|
|
7433
|
+
from: who,
|
|
7434
|
+
address: msg.peerAddress,
|
|
7435
|
+
text: msg.text,
|
|
7436
|
+
contentType: msg.contentType,
|
|
7437
|
+
sentAt: new Date(msg.sentAtMs).toISOString()
|
|
7438
|
+
})
|
|
7439
|
+
);
|
|
7440
|
+
} else {
|
|
7441
|
+
const body = msg.text ? sanitizeMessageText(msg.text) : `[${msg.contentType}]`;
|
|
7442
|
+
const [first = "", ...rest] = body.split("\n");
|
|
7443
|
+
console.log(
|
|
7444
|
+
`\u2190 ${who} ${dim(formatAge(msg.sentAtMs))}
|
|
7445
|
+
${first}`
|
|
7446
|
+
);
|
|
7447
|
+
for (const line of rest) console.log(` ${line}`);
|
|
7448
|
+
}
|
|
7449
|
+
}
|
|
7450
|
+
} finally {
|
|
7451
|
+
track("cli_dm_listen_end", {
|
|
7452
|
+
output_format: json ? "json" : "text",
|
|
7453
|
+
message_count: messageCount
|
|
7454
|
+
});
|
|
7455
|
+
await client2.close();
|
|
7456
|
+
}
|
|
7457
|
+
});
|
|
7333
7458
|
var consentSubcommand = (name, consent, description) => {
|
|
7334
7459
|
dmCommand.command(name).description(description).argument("<address>", "Zora handle (@name) or 0x address").action(async function(address) {
|
|
7335
7460
|
const json = getJson(this);
|
|
@@ -7688,10 +7813,219 @@ var exploreCommand = new Command8("explore").description("Browse top, new, and h
|
|
|
7688
7813
|
}
|
|
7689
7814
|
});
|
|
7690
7815
|
|
|
7691
|
-
// src/commands/
|
|
7816
|
+
// src/commands/follow.ts
|
|
7817
|
+
import { getProfile as getProfile2, setApiKey as setApiKey6 } from "@zoralabs/coins-sdk";
|
|
7692
7818
|
import { Command as Command9 } from "commander";
|
|
7819
|
+
import { erc20Abi as erc20Abi3, isAddress as isAddress7 } from "viem";
|
|
7820
|
+
|
|
7821
|
+
// src/lib/follow.ts
|
|
7822
|
+
var FOLLOW_MUTATION = "mutation CliFollow($followeeId: String!) { follow(followeeId: $followeeId) { handle profileId vcFollowingStatus } }";
|
|
7823
|
+
var UNFOLLOW_MUTATION = "mutation CliUnfollow($followeeId: String!) { unfollow(followeeId: $followeeId) { handle profileId vcFollowingStatus } }";
|
|
7824
|
+
async function mutateFollow(token, followeeId, mutation, operationName, field) {
|
|
7825
|
+
const { data, errors, status } = await graphqlRequest(
|
|
7826
|
+
token,
|
|
7827
|
+
mutation,
|
|
7828
|
+
operationName,
|
|
7829
|
+
{ followeeId }
|
|
7830
|
+
);
|
|
7831
|
+
const profile = data?.[field];
|
|
7832
|
+
if (profile?.profileId) {
|
|
7833
|
+
return {
|
|
7834
|
+
// The API returns the full address as both fields when the target has no
|
|
7835
|
+
// profile; fall back to profileId so handle is never empty.
|
|
7836
|
+
handle: profile.handle ?? profile.profileId,
|
|
7837
|
+
profileId: profile.profileId,
|
|
7838
|
+
followingStatus: profile.vcFollowingStatus ?? "UNKNOWN"
|
|
7839
|
+
};
|
|
7840
|
+
}
|
|
7841
|
+
const lastError = errors?.[0]?.message ?? `HTTP ${status}`;
|
|
7842
|
+
throw new Error(`${field} failed: ${lastError}`);
|
|
7843
|
+
}
|
|
7844
|
+
function followProfile(token, followeeId) {
|
|
7845
|
+
return mutateFollow(
|
|
7846
|
+
token,
|
|
7847
|
+
followeeId,
|
|
7848
|
+
FOLLOW_MUTATION,
|
|
7849
|
+
"CliFollow",
|
|
7850
|
+
"follow"
|
|
7851
|
+
);
|
|
7852
|
+
}
|
|
7853
|
+
function unfollowProfile(token, followeeId) {
|
|
7854
|
+
return mutateFollow(
|
|
7855
|
+
token,
|
|
7856
|
+
followeeId,
|
|
7857
|
+
UNFOLLOW_MUTATION,
|
|
7858
|
+
"CliUnfollow",
|
|
7859
|
+
"unfollow"
|
|
7860
|
+
);
|
|
7861
|
+
}
|
|
7862
|
+
|
|
7863
|
+
// src/commands/follow.ts
|
|
7864
|
+
function isPlaceholderName(name) {
|
|
7865
|
+
return name.startsWith("0x") || name.includes("\u2026") || name.includes("...");
|
|
7866
|
+
}
|
|
7867
|
+
function displayName(result) {
|
|
7868
|
+
return isPlaceholderName(result.handle) ? result.profileId : `@${result.handle}`;
|
|
7869
|
+
}
|
|
7870
|
+
function relationshipNote(status) {
|
|
7871
|
+
if (status === "MUTUAL_FOLLOWING") return "You follow each other.";
|
|
7872
|
+
if (status === "FOLLOWED") return "They still follow you.";
|
|
7873
|
+
return void 0;
|
|
7874
|
+
}
|
|
7875
|
+
async function requireCreatorCoinHolding(json, identifier) {
|
|
7876
|
+
const apiKey = getApiKey();
|
|
7877
|
+
if (apiKey) setApiKey6(apiKey);
|
|
7878
|
+
let profile;
|
|
7879
|
+
try {
|
|
7880
|
+
const response = await getProfile2({ identifier });
|
|
7881
|
+
profile = response?.data?.profile;
|
|
7882
|
+
} catch (err) {
|
|
7883
|
+
return outputErrorAndExit(
|
|
7884
|
+
json,
|
|
7885
|
+
`Couldn't look up "${identifier}": ${formatError(err)}`
|
|
7886
|
+
);
|
|
7887
|
+
}
|
|
7888
|
+
if (!profile) {
|
|
7889
|
+
return outputErrorAndExit(
|
|
7890
|
+
json,
|
|
7891
|
+
`No Zora profile found for "${identifier}".`,
|
|
7892
|
+
"Provide an existing Zora username or wallet address."
|
|
7893
|
+
);
|
|
7894
|
+
}
|
|
7895
|
+
const label = profile.handle && !isPlaceholderName(profile.handle) ? `@${profile.handle}` : identifier;
|
|
7896
|
+
const coinAddress = profile.creatorCoin?.address;
|
|
7897
|
+
if (!coinAddress || !isAddress7(coinAddress)) {
|
|
7898
|
+
return outputErrorAndExit(
|
|
7899
|
+
json,
|
|
7900
|
+
`${label} doesn't have a creator coin yet, so there's nothing to buy.`,
|
|
7901
|
+
"Following requires holding the profile's creator coin."
|
|
7902
|
+
);
|
|
7903
|
+
}
|
|
7904
|
+
const { privateKeyAccount, smartWalletAccount } = await resolveAccounts();
|
|
7905
|
+
const wallet = smartWalletAccount?.address ?? privateKeyAccount.address;
|
|
7906
|
+
const { publicClient } = createClients(privateKeyAccount, smartWalletAccount);
|
|
7907
|
+
let balance;
|
|
7908
|
+
try {
|
|
7909
|
+
balance = await publicClient.readContract({
|
|
7910
|
+
abi: erc20Abi3,
|
|
7911
|
+
address: coinAddress,
|
|
7912
|
+
functionName: "balanceOf",
|
|
7913
|
+
args: [wallet]
|
|
7914
|
+
});
|
|
7915
|
+
} catch (err) {
|
|
7916
|
+
return outputErrorAndExit(
|
|
7917
|
+
json,
|
|
7918
|
+
`Couldn't check your creator-coin balance: ${formatError(err)}`
|
|
7919
|
+
);
|
|
7920
|
+
}
|
|
7921
|
+
if (balance === 0n) {
|
|
7922
|
+
return outputErrorAndExit(
|
|
7923
|
+
json,
|
|
7924
|
+
`You must hold ${label}'s creator coin to follow them.`,
|
|
7925
|
+
`Buy some first: zora buy ${coinAddress} --eth 0.001`
|
|
7926
|
+
);
|
|
7927
|
+
}
|
|
7928
|
+
}
|
|
7929
|
+
async function resolveToken(json, key) {
|
|
7930
|
+
try {
|
|
7931
|
+
const session = await ensurePrivySession({ privateKey: normalizeKey(key) });
|
|
7932
|
+
return session.accessToken;
|
|
7933
|
+
} catch (err) {
|
|
7934
|
+
return outputErrorAndExit(json, `Sign-in failed: ${formatError(err)}`);
|
|
7935
|
+
}
|
|
7936
|
+
}
|
|
7937
|
+
async function runFollow(command, action, identifierArg) {
|
|
7938
|
+
const json = getJson(command);
|
|
7939
|
+
const followeeId = (identifierArg ?? "").replace(/^@/, "").trim();
|
|
7940
|
+
if (!followeeId) {
|
|
7941
|
+
return outputErrorAndExit(
|
|
7942
|
+
json,
|
|
7943
|
+
`Missing user to ${action}.`,
|
|
7944
|
+
`Usage: zora ${action} <username | address>`
|
|
7945
|
+
);
|
|
7946
|
+
}
|
|
7947
|
+
const key = process.env.ZORA_PRIVATE_KEY || getPrivateKey();
|
|
7948
|
+
if (!key) {
|
|
7949
|
+
return outputErrorAndExit(
|
|
7950
|
+
json,
|
|
7951
|
+
"No wallet configured.",
|
|
7952
|
+
"Run 'zora agent create' to set up your Zora agent."
|
|
7953
|
+
);
|
|
7954
|
+
}
|
|
7955
|
+
if (action === "follow") {
|
|
7956
|
+
await requireCreatorCoinHolding(json, followeeId);
|
|
7957
|
+
}
|
|
7958
|
+
const token = await resolveToken(json, key);
|
|
7959
|
+
let result;
|
|
7960
|
+
try {
|
|
7961
|
+
result = action === "follow" ? await followProfile(token, followeeId) : await unfollowProfile(token, followeeId);
|
|
7962
|
+
} catch (err) {
|
|
7963
|
+
track("cli_follow", {
|
|
7964
|
+
action,
|
|
7965
|
+
output_format: json ? "json" : "static",
|
|
7966
|
+
success: false,
|
|
7967
|
+
error_type: err instanceof Error ? err.constructor.name : "unknown"
|
|
7968
|
+
});
|
|
7969
|
+
await shutdownAnalytics();
|
|
7970
|
+
const message = formatError(err);
|
|
7971
|
+
if (action === "follow" && /yourself/i.test(message)) {
|
|
7972
|
+
return outputErrorAndExit(json, "You can't follow yourself.");
|
|
7973
|
+
}
|
|
7974
|
+
return outputErrorAndExit(
|
|
7975
|
+
json,
|
|
7976
|
+
`Failed to ${action} "${followeeId}": ${message}`,
|
|
7977
|
+
"Check the username or address is a real Zora profile and try again."
|
|
7978
|
+
);
|
|
7979
|
+
}
|
|
7980
|
+
if (result.followingStatus === "SELF") {
|
|
7981
|
+
track("cli_follow", {
|
|
7982
|
+
action,
|
|
7983
|
+
output_format: json ? "json" : "static",
|
|
7984
|
+
success: false,
|
|
7985
|
+
error_type: "self"
|
|
7986
|
+
});
|
|
7987
|
+
await shutdownAnalytics();
|
|
7988
|
+
return outputErrorAndExit(json, `You can't ${action} yourself.`);
|
|
7989
|
+
}
|
|
7990
|
+
const label = displayName(result);
|
|
7991
|
+
const profileUrl = isPlaceholderName(result.handle) ? void 0 : `https://zora.co/@${result.handle}`;
|
|
7992
|
+
const note2 = relationshipNote(result.followingStatus);
|
|
7993
|
+
track("cli_follow", {
|
|
7994
|
+
action,
|
|
7995
|
+
output_format: json ? "json" : "static",
|
|
7996
|
+
success: true,
|
|
7997
|
+
following_status: result.followingStatus
|
|
7998
|
+
});
|
|
7999
|
+
outputData(json, {
|
|
8000
|
+
json: {
|
|
8001
|
+
action,
|
|
8002
|
+
followee: result.profileId,
|
|
8003
|
+
handle: result.handle,
|
|
8004
|
+
followingStatus: result.followingStatus,
|
|
8005
|
+
...profileUrl ? { profileUrl } : {}
|
|
8006
|
+
},
|
|
8007
|
+
render: () => {
|
|
8008
|
+
console.log(
|
|
8009
|
+
`
|
|
8010
|
+
\u2713 ${action === "follow" ? "Following" : "Unfollowed"} ${label}`
|
|
8011
|
+
);
|
|
8012
|
+
if (note2) console.log(` ${note2}`);
|
|
8013
|
+
if (profileUrl) console.log(` ${profileUrl}`);
|
|
8014
|
+
console.log("");
|
|
8015
|
+
}
|
|
8016
|
+
});
|
|
8017
|
+
}
|
|
8018
|
+
var followCommand = new Command9("follow").description("Follow a Zora user whose creator coin you hold").argument("[identifier]", "Username (@handle), wallet address, or account id").action(async function(identifier) {
|
|
8019
|
+
await runFollow(this, "follow", identifier);
|
|
8020
|
+
});
|
|
8021
|
+
var unfollowCommand = new Command9("unfollow").description("Unfollow a Zora user by username or address").argument("[identifier]", "Username (@handle), wallet address, or account id").action(async function(identifier) {
|
|
8022
|
+
await runFollow(this, "unfollow", identifier);
|
|
8023
|
+
});
|
|
8024
|
+
|
|
8025
|
+
// src/commands/get.tsx
|
|
8026
|
+
import { Command as Command10 } from "commander";
|
|
7693
8027
|
import { Box as Box12, Text as Text12 } from "ink";
|
|
7694
|
-
import { setApiKey as
|
|
8028
|
+
import { setApiKey as setApiKey7, getCoinHolders, getCoinSwaps } from "@zoralabs/coins-sdk";
|
|
7695
8029
|
|
|
7696
8030
|
// src/components/CoinDetail.tsx
|
|
7697
8031
|
import { Box as Box7, Text as Text7 } from "ink";
|
|
@@ -8186,7 +8520,7 @@ function formatCoinJson(coin) {
|
|
|
8186
8520
|
var resolveApiKey2 = () => {
|
|
8187
8521
|
const apiKey = getApiKey();
|
|
8188
8522
|
if (apiKey) {
|
|
8189
|
-
|
|
8523
|
+
setApiKey7(apiKey);
|
|
8190
8524
|
}
|
|
8191
8525
|
};
|
|
8192
8526
|
var CoinResolutionError = class extends Error {
|
|
@@ -8315,7 +8649,7 @@ async function fetchRecentTrades(address) {
|
|
|
8315
8649
|
return [];
|
|
8316
8650
|
}
|
|
8317
8651
|
}
|
|
8318
|
-
var getCommand = new
|
|
8652
|
+
var getCommand = new Command10("get").description("Look up a coin by address or name").argument("[typeOrId]", "Type prefix (creator-coin, trend) or identifier").argument(
|
|
8319
8653
|
"[identifier]",
|
|
8320
8654
|
"Coin address (0x...) or name (when type prefix is given)"
|
|
8321
8655
|
).option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
@@ -8845,15 +9179,15 @@ import confirm6 from "@inquirer/confirm";
|
|
|
8845
9179
|
import {
|
|
8846
9180
|
createQuote as createQuote2,
|
|
8847
9181
|
getCoin as getCoin4,
|
|
8848
|
-
setApiKey as
|
|
9182
|
+
setApiKey as setApiKey8,
|
|
8849
9183
|
tradeCoin as tradeCoin2,
|
|
8850
9184
|
tradeCoinSmartWallet as tradeCoinSmartWallet2
|
|
8851
9185
|
} from "@zoralabs/coins-sdk";
|
|
8852
|
-
import { Command as
|
|
9186
|
+
import { Command as Command11 } from "commander";
|
|
8853
9187
|
import {
|
|
8854
|
-
erc20Abi as
|
|
9188
|
+
erc20Abi as erc20Abi4,
|
|
8855
9189
|
formatUnits as formatUnits5,
|
|
8856
|
-
isAddress as
|
|
9190
|
+
isAddress as isAddress8,
|
|
8857
9191
|
parseUnits as parseUnits2
|
|
8858
9192
|
} from "viem";
|
|
8859
9193
|
function printSellQuote(output, info) {
|
|
@@ -8930,7 +9264,7 @@ function printSellResult(output, info) {
|
|
|
8930
9264
|
console.log(` Tx ${info.txHash}
|
|
8931
9265
|
`);
|
|
8932
9266
|
}
|
|
8933
|
-
var sellCommand = new
|
|
9267
|
+
var sellCommand = new Command11("sell").description("Sell a coin").argument(
|
|
8934
9268
|
"[typeOrId]",
|
|
8935
9269
|
"Type prefix (creator-coin, trend) or coin address/name"
|
|
8936
9270
|
).argument("[identifier]", "Coin name (when type prefix is given)").option("--amount <value>", "Sell specific number of coins").option("--usd <value>", "Sell USD equivalent worth of coins").option("--percent <value>", "Sell percentage of coin balance").option("--all", "Sell entire coin balance").option("--to <asset>", "Receive asset: eth, usdc, zora", "eth").option("--token <asset>", "Receive asset: eth, usdc, zora (alias for --to)").option("--quote", "Print quote and exit without trading").option("--yes", "Skip confirmation and execute directly").option("--slippage <pct>", "Slippage tolerance percent", "1").option("--debug", "Print full quote request/response JSON").action(async function(typeOrId, identifier, opts) {
|
|
@@ -8947,12 +9281,12 @@ var sellCommand = new Command10("sell").description("Sell a coin").argument(
|
|
|
8947
9281
|
}
|
|
8948
9282
|
const apiKey = getApiKey();
|
|
8949
9283
|
if (apiKey) {
|
|
8950
|
-
|
|
9284
|
+
setApiKey8(apiKey);
|
|
8951
9285
|
}
|
|
8952
9286
|
let coinAddress;
|
|
8953
9287
|
let earlyAccounts;
|
|
8954
9288
|
if (parsed.kind === "address") {
|
|
8955
|
-
if (!
|
|
9289
|
+
if (!isAddress8(parsed.address)) {
|
|
8956
9290
|
return outputErrorAndExit(json, `Invalid address: ${parsed.address}`);
|
|
8957
9291
|
}
|
|
8958
9292
|
coinAddress = parsed.address;
|
|
@@ -8968,7 +9302,7 @@ var sellCommand = new Command10("sell").description("Sell a coin").argument(
|
|
|
8968
9302
|
ambResult = await resolveAmbiguousByNameAndBalance(
|
|
8969
9303
|
parsed.name,
|
|
8970
9304
|
(addr) => earlyPublicClient.readContract({
|
|
8971
|
-
abi:
|
|
9305
|
+
abi: erc20Abi4,
|
|
8972
9306
|
address: addr,
|
|
8973
9307
|
functionName: "balanceOf",
|
|
8974
9308
|
args: [earlyWalletAddress]
|
|
@@ -9112,7 +9446,7 @@ var sellCommand = new Command10("sell").description("Sell a coin").argument(
|
|
|
9112
9446
|
}
|
|
9113
9447
|
} else {
|
|
9114
9448
|
const balance = await publicClient.readContract({
|
|
9115
|
-
abi:
|
|
9449
|
+
abi: erc20Abi4,
|
|
9116
9450
|
address: coinAddress,
|
|
9117
9451
|
functionName: "balanceOf",
|
|
9118
9452
|
args: [walletAddress]
|
|
@@ -9374,13 +9708,13 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
9374
9708
|
});
|
|
9375
9709
|
|
|
9376
9710
|
// src/commands/profile.tsx
|
|
9377
|
-
import { Command as
|
|
9711
|
+
import { Command as Command12 } from "commander";
|
|
9378
9712
|
import { Box as Box17, Text as Text17 } from "ink";
|
|
9379
9713
|
import {
|
|
9380
9714
|
getProfileCoins,
|
|
9381
9715
|
getProfileBalances as getProfileBalances2,
|
|
9382
9716
|
getWalletTradeActivity,
|
|
9383
|
-
setApiKey as
|
|
9717
|
+
setApiKey as setApiKey9
|
|
9384
9718
|
} from "@zoralabs/coins-sdk";
|
|
9385
9719
|
import { privateKeyToAccount as privateKeyToAccount8 } from "viem/accounts";
|
|
9386
9720
|
|
|
@@ -9762,7 +10096,7 @@ import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
|
9762
10096
|
var resolveApiKey3 = () => {
|
|
9763
10097
|
const apiKey = getApiKey();
|
|
9764
10098
|
if (apiKey) {
|
|
9765
|
-
|
|
10099
|
+
setApiKey9(apiKey);
|
|
9766
10100
|
}
|
|
9767
10101
|
};
|
|
9768
10102
|
var formatTradeJson2 = (trade, rank) => ({
|
|
@@ -9885,7 +10219,7 @@ var resolveIdentifier = (identifierArg, json) => {
|
|
|
9885
10219
|
);
|
|
9886
10220
|
}
|
|
9887
10221
|
};
|
|
9888
|
-
var profileCommand = new
|
|
10222
|
+
var profileCommand = new Command12("profile").description("View profile activity (posts, holdings, and trades)").argument(
|
|
9889
10223
|
"[identifier]",
|
|
9890
10224
|
"Wallet address or profile handle (defaults to your wallet)"
|
|
9891
10225
|
).option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
@@ -10429,18 +10763,18 @@ profileCommand.command("trades").description("View profile trade activity (buys
|
|
|
10429
10763
|
// src/commands/send.ts
|
|
10430
10764
|
import confirm7 from "@inquirer/confirm";
|
|
10431
10765
|
import {
|
|
10432
|
-
getProfile as
|
|
10766
|
+
getProfile as getProfile3,
|
|
10433
10767
|
prepareUserOperation as prepareUserOperation2,
|
|
10434
|
-
setApiKey as
|
|
10768
|
+
setApiKey as setApiKey10,
|
|
10435
10769
|
submitUserOperation as submitUserOperation2,
|
|
10436
10770
|
toGenericCall as toGenericCall2,
|
|
10437
10771
|
toUserOperationCalls as toUserOperationCalls2
|
|
10438
10772
|
} from "@zoralabs/coins-sdk";
|
|
10439
|
-
import { Command as
|
|
10773
|
+
import { Command as Command13 } from "commander";
|
|
10440
10774
|
import {
|
|
10441
|
-
erc20Abi as
|
|
10775
|
+
erc20Abi as erc20Abi5,
|
|
10442
10776
|
formatUnits as formatUnits6,
|
|
10443
|
-
isAddress as
|
|
10777
|
+
isAddress as isAddress9,
|
|
10444
10778
|
parseUnits as parseUnits3
|
|
10445
10779
|
} from "viem";
|
|
10446
10780
|
var SEND_AMOUNT_CHECKS = {
|
|
@@ -10449,16 +10783,16 @@ var SEND_AMOUNT_CHECKS = {
|
|
|
10449
10783
|
all: (opts) => opts.all === true
|
|
10450
10784
|
};
|
|
10451
10785
|
var KNOWN_TOKEN_NAMES = /* @__PURE__ */ new Set(["eth", "usdc", "zora"]);
|
|
10452
|
-
function
|
|
10786
|
+
function isPlaceholderName2(name) {
|
|
10453
10787
|
return name.startsWith("0x") || name.includes("\u2026") || name.includes("...");
|
|
10454
10788
|
}
|
|
10455
10789
|
async function resolveRecipient(identifier, json = false) {
|
|
10456
|
-
const isIdentifierAddress =
|
|
10790
|
+
const isIdentifierAddress = isAddress9(identifier);
|
|
10457
10791
|
try {
|
|
10458
|
-
const response = await
|
|
10792
|
+
const response = await getProfile3({ identifier });
|
|
10459
10793
|
const profile = response?.data?.profile;
|
|
10460
10794
|
const address = isIdentifierAddress ? identifier : profile?.publicWallet?.walletAddress;
|
|
10461
|
-
if (!address || !
|
|
10795
|
+
if (!address || !isAddress9(address)) {
|
|
10462
10796
|
return outputErrorAndExit(
|
|
10463
10797
|
json,
|
|
10464
10798
|
!address ? `No Zora profile or wallet found for "${identifier}".` : "Provide a valid 0x address or an existing Zora profile name."
|
|
@@ -10466,9 +10800,10 @@ async function resolveRecipient(identifier, json = false) {
|
|
|
10466
10800
|
}
|
|
10467
10801
|
return {
|
|
10468
10802
|
address,
|
|
10469
|
-
handle: profile?.handle && !
|
|
10470
|
-
username: profile?.username && !
|
|
10471
|
-
displayName: profile?.displayName && !
|
|
10803
|
+
handle: profile?.handle && !isPlaceholderName2(profile.handle) ? `@${profile.handle}` : void 0,
|
|
10804
|
+
username: profile?.username && !isPlaceholderName2(profile.username) ? profile.username : void 0,
|
|
10805
|
+
displayName: profile?.displayName && !isPlaceholderName2(profile.displayName) ? profile.displayName : void 0,
|
|
10806
|
+
platformBlocked: profile?.platformBlocked ?? false
|
|
10472
10807
|
};
|
|
10473
10808
|
} catch (err) {
|
|
10474
10809
|
return isIdentifierAddress ? { address: identifier } : outputErrorAndExit(
|
|
@@ -10547,7 +10882,7 @@ async function sendCallViaSmartWallet(call, bundlerClient, account) {
|
|
|
10547
10882
|
}
|
|
10548
10883
|
return receipt.receipt.transactionHash;
|
|
10549
10884
|
}
|
|
10550
|
-
var sendCommand = new
|
|
10885
|
+
var sendCommand = new Command13("send").description("Send coins or ETH to an address or Zora profile").argument(
|
|
10551
10886
|
"[typeOrId]",
|
|
10552
10887
|
"Token (eth, usdc, zora), type prefix (creator-coin, trend), or coin address/name"
|
|
10553
10888
|
).argument("[identifier]", "Coin name (when type prefix is given)").option("--to <recipient>", "Recipient: address (0x...) or Zora profile name").option("--amount <value>", "Send specific amount").option("--percent <value>", "Send percentage of balance (1-100)").option("--all", "Send entire balance").option("--yes", "Skip confirmation").action(async function(firstArg, secondArg, opts) {
|
|
@@ -10561,9 +10896,22 @@ var sendCommand = new Command12("send").description("Send coins or ETH to an add
|
|
|
10561
10896
|
}
|
|
10562
10897
|
const apiKey = getApiKey();
|
|
10563
10898
|
if (apiKey) {
|
|
10564
|
-
|
|
10899
|
+
setApiKey10(apiKey);
|
|
10565
10900
|
}
|
|
10566
10901
|
const resolvedRecipient = await resolveRecipient(opts.to, json);
|
|
10902
|
+
if (resolvedRecipient.platformBlocked) {
|
|
10903
|
+
track("cli_send", {
|
|
10904
|
+
output_format: json ? "json" : "text",
|
|
10905
|
+
success: false,
|
|
10906
|
+
blocked_profile: true
|
|
10907
|
+
});
|
|
10908
|
+
return outputErrorAndExit(
|
|
10909
|
+
json,
|
|
10910
|
+
bannedProfileMessage(
|
|
10911
|
+
resolvedRecipient.handle ?? resolvedRecipient.address
|
|
10912
|
+
)
|
|
10913
|
+
);
|
|
10914
|
+
}
|
|
10567
10915
|
const amountMode = getAmountMode(
|
|
10568
10916
|
json,
|
|
10569
10917
|
opts,
|
|
@@ -10824,7 +11172,7 @@ var sendCommand = new Command12("send").description("Send coins or ETH to an add
|
|
|
10824
11172
|
let symbol;
|
|
10825
11173
|
if (knownToken) {
|
|
10826
11174
|
balance = await publicClient.readContract({
|
|
10827
|
-
abi:
|
|
11175
|
+
abi: erc20Abi5,
|
|
10828
11176
|
address: tokenAddress,
|
|
10829
11177
|
functionName: "balanceOf",
|
|
10830
11178
|
args: [walletAddress]
|
|
@@ -10834,18 +11182,18 @@ var sendCommand = new Command12("send").description("Send coins or ETH to an add
|
|
|
10834
11182
|
} else {
|
|
10835
11183
|
const results = await Promise.all([
|
|
10836
11184
|
publicClient.readContract({
|
|
10837
|
-
abi:
|
|
11185
|
+
abi: erc20Abi5,
|
|
10838
11186
|
address: tokenAddress,
|
|
10839
11187
|
functionName: "balanceOf",
|
|
10840
11188
|
args: [walletAddress]
|
|
10841
11189
|
}),
|
|
10842
11190
|
publicClient.readContract({
|
|
10843
|
-
abi:
|
|
11191
|
+
abi: erc20Abi5,
|
|
10844
11192
|
address: tokenAddress,
|
|
10845
11193
|
functionName: "decimals"
|
|
10846
11194
|
}),
|
|
10847
11195
|
publicClient.readContract({
|
|
10848
|
-
abi:
|
|
11196
|
+
abi: erc20Abi5,
|
|
10849
11197
|
address: tokenAddress,
|
|
10850
11198
|
functionName: "symbol"
|
|
10851
11199
|
})
|
|
@@ -10955,7 +11303,7 @@ var sendCommand = new Command12("send").description("Send coins or ETH to an add
|
|
|
10955
11303
|
try {
|
|
10956
11304
|
txHash = smartWalletAccount ? await sendCallViaSmartWallet(
|
|
10957
11305
|
{
|
|
10958
|
-
abi:
|
|
11306
|
+
abi: erc20Abi5,
|
|
10959
11307
|
address: tokenAddress,
|
|
10960
11308
|
functionName: "transfer",
|
|
10961
11309
|
args: [resolvedRecipient.address, amount]
|
|
@@ -10963,7 +11311,7 @@ var sendCommand = new Command12("send").description("Send coins or ETH to an add
|
|
|
10963
11311
|
bundlerClient,
|
|
10964
11312
|
smartWalletAccount
|
|
10965
11313
|
) : await walletClient.writeContract({
|
|
10966
|
-
abi:
|
|
11314
|
+
abi: erc20Abi5,
|
|
10967
11315
|
address: tokenAddress,
|
|
10968
11316
|
functionName: "transfer",
|
|
10969
11317
|
args: [resolvedRecipient.address, amount]
|
|
@@ -11028,7 +11376,7 @@ var sendCommand = new Command12("send").description("Send coins or ETH to an add
|
|
|
11028
11376
|
});
|
|
11029
11377
|
|
|
11030
11378
|
// src/commands/setup.tsx
|
|
11031
|
-
import { Command as
|
|
11379
|
+
import { Command as Command14 } from "commander";
|
|
11032
11380
|
import { Text as Text18, Box as Box18 } from "ink";
|
|
11033
11381
|
|
|
11034
11382
|
// src/lib/strings.ts
|
|
@@ -11196,7 +11544,7 @@ ${BOLD}${DIM}[${step}/${total}]${RESET} ${BOLD}${title}${RESET}`
|
|
|
11196
11544
|
console.log(`${DIM}${"\u2500".repeat(Math.max(cols, 20))}${RESET}
|
|
11197
11545
|
`);
|
|
11198
11546
|
}
|
|
11199
|
-
var setupCommand = new
|
|
11547
|
+
var setupCommand = new Command14("setup").description("Guided first-time setup").option("--create", "Create a new wallet without prompting").option("--force", "Overwrite existing wallet without prompting").option("--yes", "Skip interactive prompt and execute directly").action(async function(options) {
|
|
11200
11548
|
const json = getJson(this);
|
|
11201
11549
|
const nonInteractive = getYes(this);
|
|
11202
11550
|
if (!json) stepLine(1, 3, "Set up wallet");
|
|
@@ -11333,91 +11681,114 @@ async function promptAndSaveApiKey(json, nonInteractive = false) {
|
|
|
11333
11681
|
}
|
|
11334
11682
|
|
|
11335
11683
|
// src/commands/skills.ts
|
|
11336
|
-
import { Command as
|
|
11684
|
+
import { Command as Command15 } from "commander";
|
|
11685
|
+
import { createHash as createHash2 } from "crypto";
|
|
11337
11686
|
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
|
|
11338
11687
|
import { resolve, join as join3 } from "path";
|
|
11339
11688
|
var DEFAULT_SKILLS_BASE_URL = "https://agents.zora.com/skill";
|
|
11340
11689
|
var getSkillsBaseUrl = () => process.env.ZORA_SKILLS_BASE_URL || DEFAULT_SKILLS_BASE_URL;
|
|
11341
11690
|
var SKILLS = [
|
|
11691
|
+
// Core
|
|
11692
|
+
{
|
|
11693
|
+
name: "cli",
|
|
11694
|
+
category: "Core",
|
|
11695
|
+
description: "The agent's full interface to Zora \u2014 set up an identity and trade, browse, look up coins, send tokens, and handle DMs from the CLI",
|
|
11696
|
+
integrity: "sha256-PyvDxJ7pbQ8PI5Lg/p4k7ryJNGHapqt9lRSjAV1KBDY="
|
|
11697
|
+
},
|
|
11342
11698
|
// Onboarding
|
|
11343
11699
|
{
|
|
11344
11700
|
name: "onboarding",
|
|
11345
11701
|
category: "Onboarding",
|
|
11346
|
-
description: "Set up on Zora for the first time \u2014 publish your profile, create your smart wallet and creator coin, and post your first meme"
|
|
11702
|
+
description: "Set up on Zora for the first time \u2014 publish your profile, create your smart wallet and creator coin, and post your first meme",
|
|
11703
|
+
integrity: "sha256-8ZSloIyoC232S4QZDyb6PXL94n3OWlhBopHuTpt4Txo="
|
|
11347
11704
|
},
|
|
11348
11705
|
// Discovery
|
|
11349
11706
|
{
|
|
11350
11707
|
name: "early-buyer",
|
|
11351
11708
|
category: "Discovery",
|
|
11352
|
-
description: "Auto-buy new coin launches from creators you follow"
|
|
11709
|
+
description: "Auto-buy new coin launches from creators you follow",
|
|
11710
|
+
integrity: "sha256-MsU1e7kShm2X8jLY4nqNh8N+2ZNTKs0FVTzOVXf/rTQ="
|
|
11353
11711
|
},
|
|
11354
11712
|
{
|
|
11355
11713
|
name: "watchlist",
|
|
11356
11714
|
category: "Discovery",
|
|
11357
|
-
description: "Track coins and alert when market cap hits configured thresholds"
|
|
11715
|
+
description: "Track coins and alert when market cap hits configured thresholds",
|
|
11716
|
+
integrity: "sha256-jWtGdWJ5gZBE4449BPOA6csCLP8b6dq5svubs/cljBk="
|
|
11358
11717
|
},
|
|
11359
11718
|
{
|
|
11360
11719
|
name: "trend-sniper",
|
|
11361
11720
|
category: "Discovery",
|
|
11362
|
-
description: "Watch the global trending feed and snipe new trend coins on appearance or a volume spike"
|
|
11721
|
+
description: "Watch the global trending feed and snipe new trend coins on appearance or a volume spike",
|
|
11722
|
+
integrity: "sha256-eb6f+uK43inyv10TEXCLOTxZcaMiatnrbhilUdkIm/0="
|
|
11363
11723
|
},
|
|
11364
11724
|
{
|
|
11365
11725
|
name: "new-coin-screener",
|
|
11366
11726
|
category: "Discovery",
|
|
11367
|
-
description: "Poll the global new-coin feed and auto-buy launches that pass a market-cap/holder screen"
|
|
11727
|
+
description: "Poll the global new-coin feed and auto-buy launches that pass a market-cap/holder screen",
|
|
11728
|
+
integrity: "sha256-P/IZ6vn94w+vTwNhxhxMyJInv90ZTlFJJbDJbWGNESs="
|
|
11368
11729
|
},
|
|
11369
11730
|
{
|
|
11370
11731
|
name: "whale-watcher",
|
|
11371
11732
|
category: "Discovery",
|
|
11372
|
-
description: "Watch top holders and large trades on chosen coins, then alert or auto-trade on whale moves"
|
|
11733
|
+
description: "Watch top holders and large trades on chosen coins, then alert or auto-trade on whale moves",
|
|
11734
|
+
integrity: "sha256-9SMJMageM2VlxlMmoZpja2s0VecSymwSQUP0XSaodfI="
|
|
11373
11735
|
},
|
|
11374
11736
|
// Social
|
|
11375
11737
|
{
|
|
11376
11738
|
name: "copy-trader",
|
|
11377
11739
|
category: "Social",
|
|
11378
|
-
description: "Mirror another user's trades \u2014 existing holdings, future trades, or both"
|
|
11740
|
+
description: "Mirror another user's trades \u2014 existing holdings, future trades, or both",
|
|
11741
|
+
integrity: "sha256-Pj6Idrr52zzdwC+byLwRFjcS9EfVItemygm1q2+hbmQ="
|
|
11379
11742
|
},
|
|
11380
11743
|
{
|
|
11381
11744
|
name: "dm-responder",
|
|
11382
11745
|
category: "Social",
|
|
11383
|
-
description: "Auto-triage and respond to DMs \u2014 approve/deny requests, greet new conversations, and flag keyword matches by rule"
|
|
11746
|
+
description: "Auto-triage and respond to DMs \u2014 approve/deny requests, greet new conversations, and flag keyword matches by rule",
|
|
11747
|
+
integrity: "sha256-ankYlTwsh6xdjL+uZZhKn4y9jZSwHRzYXxAcfrb3yqU="
|
|
11384
11748
|
},
|
|
11385
11749
|
{
|
|
11386
11750
|
name: "comment-engager",
|
|
11387
11751
|
category: "Social",
|
|
11388
|
-
description: "Read and reply to comments on coins you hold, in your own voice, to build social presence"
|
|
11752
|
+
description: "Read and reply to comments on coins you hold, in your own voice, to build social presence",
|
|
11753
|
+
integrity: "sha256-oHbXs3JIOwaEJ/yubo/xIDC3rMIU5Rq7u3SGT7vIKv0="
|
|
11389
11754
|
},
|
|
11390
11755
|
{
|
|
11391
11756
|
name: "social-trader",
|
|
11392
11757
|
category: "Social",
|
|
11393
|
-
description: "Follow specific creators and buy their new post coins or growing creator coins"
|
|
11758
|
+
description: "Follow specific creators and buy their new post coins or growing creator coins",
|
|
11759
|
+
integrity: "sha256-T/txP3ctq2NNaWkXOr7nWj4JuZJlKTMMMCWjh6qsmk8="
|
|
11394
11760
|
},
|
|
11395
11761
|
{
|
|
11396
11762
|
name: "auto-poster",
|
|
11397
11763
|
category: "Social",
|
|
11398
|
-
description: "Publish a new post on a schedule to keep your agent active and in-character"
|
|
11764
|
+
description: "Publish a new post on a schedule to keep your agent active and in-character",
|
|
11765
|
+
integrity: "sha256-7vhcFa9fCArAOKu5hDUaMmR0Pw4SvTjXeVXU8BB9550="
|
|
11399
11766
|
},
|
|
11400
11767
|
// Risk
|
|
11401
11768
|
{
|
|
11402
11769
|
name: "take-profit",
|
|
11403
11770
|
category: "Risk",
|
|
11404
|
-
description: "Auto-sell positions at configured take-profit or stop-loss price targets"
|
|
11771
|
+
description: "Auto-sell positions at configured take-profit or stop-loss price targets",
|
|
11772
|
+
integrity: "sha256-CHtXieeBhnmfYAzazwIGHk7af1sJYHO2ox3nVDJD3yg="
|
|
11405
11773
|
},
|
|
11406
11774
|
{
|
|
11407
11775
|
name: "dca",
|
|
11408
11776
|
category: "Risk",
|
|
11409
|
-
description: "Dollar-cost-average a fixed amount into chosen coins each iteration, with budget caps"
|
|
11777
|
+
description: "Dollar-cost-average a fixed amount into chosen coins each iteration, with budget caps",
|
|
11778
|
+
integrity: "sha256-xiSCYid81h0btfQV5gNlfsV0sLjc+1DvQSU3ORgYGJI="
|
|
11410
11779
|
},
|
|
11411
11780
|
{
|
|
11412
11781
|
name: "portfolio-rebalancer",
|
|
11413
11782
|
category: "Risk",
|
|
11414
|
-
description: "Rebalance holdings back to target allocations when they drift past a tolerance band"
|
|
11783
|
+
description: "Rebalance holdings back to target allocations when they drift past a tolerance band",
|
|
11784
|
+
integrity: "sha256-LzeWI1kfOhOr1oeXmLABAfrrUtJ3jm+llOb+wGjo5/Q="
|
|
11415
11785
|
},
|
|
11416
11786
|
// Reporting
|
|
11417
11787
|
{
|
|
11418
11788
|
name: "portfolio-digest",
|
|
11419
11789
|
category: "Reporting",
|
|
11420
|
-
description: "Read-only periodic portfolio and PnL digest, optionally delivered to the operator by DM"
|
|
11790
|
+
description: "Read-only periodic portfolio and PnL digest, optionally delivered to the operator by DM",
|
|
11791
|
+
integrity: "sha256-tp8GQTSzRfqdKKT1H/ox8GOv3nRHSDLXbh9iaHTmjp4="
|
|
11421
11792
|
}
|
|
11422
11793
|
];
|
|
11423
11794
|
var AGENT_ORDER = [
|
|
@@ -11448,7 +11819,11 @@ var detectAgent = (cwd) => {
|
|
|
11448
11819
|
}
|
|
11449
11820
|
return null;
|
|
11450
11821
|
};
|
|
11451
|
-
var
|
|
11822
|
+
var computeIntegrity = (content) => {
|
|
11823
|
+
const hash = createHash2("sha256").update(content, "utf8").digest("base64");
|
|
11824
|
+
return `sha256-${hash}`;
|
|
11825
|
+
};
|
|
11826
|
+
var fetchSkill = async (name, expectedIntegrity, skipVerify) => {
|
|
11452
11827
|
const url = `${getSkillsBaseUrl()}/${name}.md`;
|
|
11453
11828
|
const response = await fetch(url);
|
|
11454
11829
|
if (!response.ok) {
|
|
@@ -11456,17 +11831,30 @@ var fetchSkill = async (name) => {
|
|
|
11456
11831
|
`Failed to fetch ${url}: ${response.status} ${response.statusText}`
|
|
11457
11832
|
);
|
|
11458
11833
|
}
|
|
11459
|
-
|
|
11834
|
+
const content = await response.text();
|
|
11835
|
+
if (!skipVerify) {
|
|
11836
|
+
const actual = computeIntegrity(content);
|
|
11837
|
+
if (actual !== expectedIntegrity) {
|
|
11838
|
+
throw new Error(
|
|
11839
|
+
`Skill integrity check failed for "${name}".
|
|
11840
|
+
Expected: ${expectedIntegrity}
|
|
11841
|
+
Received: ${actual}
|
|
11842
|
+
This could indicate a compromised download. If you trust the source, use --skip-verify.`
|
|
11843
|
+
);
|
|
11844
|
+
}
|
|
11845
|
+
}
|
|
11846
|
+
return content;
|
|
11460
11847
|
};
|
|
11461
|
-
var skillsCommand = new
|
|
11848
|
+
var skillsCommand = new Command15("skills").description(
|
|
11462
11849
|
"Install pre-built agent skills \u2014 onboarding plus discovery, social, risk, and reporting strategies (run `skills list` to see them all)"
|
|
11463
11850
|
).action(function() {
|
|
11464
11851
|
this.outputHelp();
|
|
11465
11852
|
});
|
|
11853
|
+
var getPublicSkills = () => SKILLS.map(({ integrity: _, ...rest }) => rest);
|
|
11466
11854
|
skillsCommand.command("list").description("List available skills").action(function() {
|
|
11467
11855
|
const json = getJson(this);
|
|
11468
11856
|
outputData(json, {
|
|
11469
|
-
json: { skills:
|
|
11857
|
+
json: { skills: getPublicSkills() },
|
|
11470
11858
|
render: () => {
|
|
11471
11859
|
console.log("Available skills:\n");
|
|
11472
11860
|
for (const s of SKILLS) {
|
|
@@ -11482,12 +11870,13 @@ skillsCommand.command("add [name]").description(
|
|
|
11482
11870
|
).option("--all", "Install all skills").option(
|
|
11483
11871
|
"--agent <agent>",
|
|
11484
11872
|
"Target agent: claude, cursor, windsurf, openclaw, hermes (default: auto-detect)"
|
|
11485
|
-
).option("--dir <path>", "Explicit directory to install into").action(async function(name) {
|
|
11873
|
+
).option("--dir <path>", "Explicit directory to install into").option("--skip-verify", "Skip integrity verification (development only)").action(async function(name) {
|
|
11486
11874
|
const json = getJson(this);
|
|
11487
11875
|
const opts = this.opts();
|
|
11488
11876
|
const installAll = opts.all === true;
|
|
11489
11877
|
const agentFlag = opts.agent;
|
|
11490
11878
|
const dirFlag = opts.dir;
|
|
11879
|
+
const skipVerify = opts.skipVerify === true;
|
|
11491
11880
|
if (!installAll && !name) {
|
|
11492
11881
|
return outputErrorAndExit(
|
|
11493
11882
|
json,
|
|
@@ -11534,28 +11923,72 @@ skillsCommand.command("add [name]").description(
|
|
|
11534
11923
|
mkdirSync3(outDir, { recursive: true });
|
|
11535
11924
|
const installed = [];
|
|
11536
11925
|
const errors = [];
|
|
11537
|
-
for (const
|
|
11926
|
+
for (const skillName of names) {
|
|
11927
|
+
const skill = SKILLS.find((s) => s.name === skillName);
|
|
11538
11928
|
try {
|
|
11539
|
-
const content = await fetchSkill(
|
|
11540
|
-
|
|
11929
|
+
const content = await fetchSkill(
|
|
11930
|
+
skillName,
|
|
11931
|
+
skill.integrity,
|
|
11932
|
+
skipVerify
|
|
11933
|
+
);
|
|
11934
|
+
const skillDir = join3(outDir, `${SKILL_PREFIX}${skillName}`);
|
|
11541
11935
|
mkdirSync3(skillDir, { recursive: true });
|
|
11542
11936
|
const outPath = join3(skillDir, "SKILL.md");
|
|
11543
11937
|
writeFileSync3(outPath, content);
|
|
11544
|
-
installed.push({ name: `${SKILL_PREFIX}${
|
|
11938
|
+
installed.push({ name: `${SKILL_PREFIX}${skillName}`, path: outPath });
|
|
11545
11939
|
} catch (err) {
|
|
11546
11940
|
errors.push({
|
|
11547
|
-
name:
|
|
11941
|
+
name: skillName,
|
|
11548
11942
|
error: err instanceof Error ? err.message : String(err)
|
|
11549
11943
|
});
|
|
11550
11944
|
}
|
|
11551
11945
|
}
|
|
11552
11946
|
if (errors.length > 0 && installed.length === 0) {
|
|
11947
|
+
const integrityError = errors.find(
|
|
11948
|
+
(e) => e.error.includes("integrity check failed")
|
|
11949
|
+
);
|
|
11553
11950
|
return outputErrorAndExit(
|
|
11554
11951
|
json,
|
|
11555
11952
|
`Failed to install: ${errors.map((e) => e.name).join(", ")}`,
|
|
11556
|
-
"Check your network connection and retry."
|
|
11953
|
+
integrityError ? integrityError.error : "Check your network connection and retry."
|
|
11557
11954
|
);
|
|
11558
11955
|
}
|
|
11956
|
+
const hasIntegrityErrors = errors.some(
|
|
11957
|
+
(e) => e.error.includes("integrity check failed")
|
|
11958
|
+
);
|
|
11959
|
+
if (hasIntegrityErrors) {
|
|
11960
|
+
outputData(json, {
|
|
11961
|
+
json: {
|
|
11962
|
+
installed,
|
|
11963
|
+
errors,
|
|
11964
|
+
agent: resolvedAgent,
|
|
11965
|
+
dir: outDir
|
|
11966
|
+
},
|
|
11967
|
+
render: () => {
|
|
11968
|
+
if (resolvedAgent && resolvedAgent !== "custom") {
|
|
11969
|
+
console.log(`\x1B[2mDetected agent: ${resolvedAgent}\x1B[0m`);
|
|
11970
|
+
}
|
|
11971
|
+
for (const { name: name2, path } of installed) {
|
|
11972
|
+
console.log(`\x1B[32m\u2713\x1B[0m Installed ${name2} \u2192 ${path}`);
|
|
11973
|
+
}
|
|
11974
|
+
for (const { name: name2, error } of errors) {
|
|
11975
|
+
console.error(`\x1B[31m\u2717\x1B[0m ${name2}: ${error}`);
|
|
11976
|
+
}
|
|
11977
|
+
console.error(
|
|
11978
|
+
"\n\x1B[31mIntegrity check failed for some skills. This could indicate compromised downloads.\x1B[0m"
|
|
11979
|
+
);
|
|
11980
|
+
}
|
|
11981
|
+
});
|
|
11982
|
+
track("cli_skills_add", {
|
|
11983
|
+
installed_count: installed.length,
|
|
11984
|
+
error_count: errors.length,
|
|
11985
|
+
integrity_errors: true,
|
|
11986
|
+
all: installAll,
|
|
11987
|
+
agent: resolvedAgent ?? "unknown",
|
|
11988
|
+
output_format: json ? "json" : "text"
|
|
11989
|
+
});
|
|
11990
|
+
process.exit(1);
|
|
11991
|
+
}
|
|
11559
11992
|
outputData(json, {
|
|
11560
11993
|
json: {
|
|
11561
11994
|
installed,
|
|
@@ -11592,8 +12025,8 @@ Invoke by typing /${firstSkill} in your agent to get started.`
|
|
|
11592
12025
|
});
|
|
11593
12026
|
|
|
11594
12027
|
// src/commands/wallet.ts
|
|
11595
|
-
import { Command as
|
|
11596
|
-
import { isAddress as
|
|
12028
|
+
import { Command as Command16 } from "commander";
|
|
12029
|
+
import { isAddress as isAddress10 } from "viem";
|
|
11597
12030
|
import { privateKeyToAccount as privateKeyToAccount10 } from "viem/accounts";
|
|
11598
12031
|
var resolvePrivateKey2 = () => {
|
|
11599
12032
|
const envKey = process.env.ZORA_PRIVATE_KEY;
|
|
@@ -11609,15 +12042,15 @@ var resolvePrivateKey2 = () => {
|
|
|
11609
12042
|
var resolveSmartWalletAddress3 = () => {
|
|
11610
12043
|
const envAddress = process.env.ZORA_SMART_WALLET_ADDRESS;
|
|
11611
12044
|
if (envAddress) {
|
|
11612
|
-
return
|
|
12045
|
+
return isAddress10(envAddress) ? { address: envAddress, source: "env" } : { invalid: true, source: "env" };
|
|
11613
12046
|
}
|
|
11614
12047
|
const fileAddress = getSmartWalletAddress();
|
|
11615
12048
|
if (fileAddress !== void 0) {
|
|
11616
|
-
return
|
|
12049
|
+
return isAddress10(fileAddress) ? { address: fileAddress, source: "file" } : { invalid: true, source: "file" };
|
|
11617
12050
|
}
|
|
11618
12051
|
return void 0;
|
|
11619
12052
|
};
|
|
11620
|
-
var walletCommand = new
|
|
12053
|
+
var walletCommand = new Command16("wallet").description("Manage your Zora wallet").action(function() {
|
|
11621
12054
|
this.outputHelp();
|
|
11622
12055
|
});
|
|
11623
12056
|
walletCommand.command("info").description("Show wallet address and storage location").action(function() {
|
|
@@ -12113,7 +12546,7 @@ async function maybeNotifyNewDms() {
|
|
|
12113
12546
|
privateKey: normalizeKey(key)
|
|
12114
12547
|
});
|
|
12115
12548
|
const auth = createSmartWalletAuth(provider);
|
|
12116
|
-
const { createMessagingClient } = await import("./client-
|
|
12549
|
+
const { createMessagingClient } = await import("./client-M3K6L2ZM.js");
|
|
12117
12550
|
const client2 = await createMessagingClient(auth.signerSpec);
|
|
12118
12551
|
try {
|
|
12119
12552
|
await client2.sync(["unknown"]);
|
|
@@ -12154,7 +12587,7 @@ import { jsx as jsx24 } from "react/jsx-runtime";
|
|
|
12154
12587
|
if (process.env.ZORA_API_TARGET) {
|
|
12155
12588
|
setApiBaseUrl(process.env.ZORA_API_TARGET);
|
|
12156
12589
|
}
|
|
12157
|
-
var version = true ? "1.
|
|
12590
|
+
var version = true ? "1.4.0" : JSON.parse(
|
|
12158
12591
|
readFileSync5(new URL("../package.json", import.meta.url), "utf-8")
|
|
12159
12592
|
).version;
|
|
12160
12593
|
function styledHelpWriteOut(showHeader) {
|
|
@@ -12174,7 +12607,7 @@ function styledHelpWriteOut(showHeader) {
|
|
|
12174
12607
|
};
|
|
12175
12608
|
}
|
|
12176
12609
|
var buildProgram = () => {
|
|
12177
|
-
const program2 = new
|
|
12610
|
+
const program2 = new Command17().name("zora").description("Trade what's trending. Run `zora setup` to get started.").version(version).option("--json", "Output as JSON (for scripts and automation)", false);
|
|
12178
12611
|
const helpWidth = (process.stdout.columns || 80) - 4;
|
|
12179
12612
|
program2.configureHelp({
|
|
12180
12613
|
helpWidth,
|
|
@@ -12195,6 +12628,8 @@ var buildProgram = () => {
|
|
|
12195
12628
|
program2.addCommand(createCommand);
|
|
12196
12629
|
program2.addCommand(dmCommand);
|
|
12197
12630
|
program2.addCommand(exploreCommand);
|
|
12631
|
+
program2.addCommand(followCommand);
|
|
12632
|
+
program2.addCommand(unfollowCommand);
|
|
12198
12633
|
program2.addCommand(getCommand);
|
|
12199
12634
|
program2.addCommand(profileCommand);
|
|
12200
12635
|
program2.addCommand(setupCommand);
|
|
@@ -12210,7 +12645,11 @@ var buildProgram = () => {
|
|
|
12210
12645
|
}
|
|
12211
12646
|
};
|
|
12212
12647
|
applyToSubcommands(program2);
|
|
12213
|
-
const argOptionalCommands = /* @__PURE__ */ new Set([
|
|
12648
|
+
const argOptionalCommands = /* @__PURE__ */ new Set([
|
|
12649
|
+
"profile",
|
|
12650
|
+
"agent budget set",
|
|
12651
|
+
"skills add"
|
|
12652
|
+
]);
|
|
12214
12653
|
const fullCommandPath = (cmd) => {
|
|
12215
12654
|
const parts = [];
|
|
12216
12655
|
let c = cmd;
|