hufi-cli 1.0.0 → 1.0.2

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.
Files changed (3) hide show
  1. package/README.md +18 -8
  2. package/dist/cli.js +186 -50
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -5,16 +5,16 @@ CLI tool for the [hu.fi](https://hu.finance) DeFi platform.
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- bun install -g hufi-cli
8
+ bun install -g hufi
9
9
  ```
10
10
 
11
11
  Or run without installing:
12
12
 
13
13
  ```bash
14
- bunx hufi-cli <command>
14
+ bunx hufi <command>
15
15
  ```
16
16
 
17
- All examples below assume global install. Otherwise replace `hufi` with `bunx hufi-cli`.
17
+ All examples below assume global install. Otherwise replace `hufi` with `bunx hufi`.
18
18
 
19
19
  ## Quick Start
20
20
 
@@ -70,10 +70,14 @@ hufi campaign progress --address 0x... --watch --interval 3000
70
70
  hufi campaign leaderboard --address 0x... # leaderboard
71
71
  ```
72
72
 
73
+ `campaign list` and `campaign get` print exact campaign timestamps and round token balances for human-readable text output.
74
+
73
75
  #### Campaign Create
74
76
 
75
77
  Requires staked HMT, gas, and fund tokens (USDT/USDC). Creates an escrow contract on-chain.
76
78
 
79
+ Before broadcasting, the CLI validates the campaign type-specific target, checks the 6-hour to 100-day duration window, verifies your minimum HMT stake when possible, inspects fund token balance, and estimates gas for approval plus creation.
80
+
77
81
  ```bash
78
82
  # Market Making
79
83
  hufi campaign create \
@@ -110,11 +114,16 @@ Running `campaign status/join/progress/leaderboard` without `-a` shows help.
110
114
 
111
115
  ```bash
112
116
  hufi exchange register --name mexc --api-key <key> --secret-key <secret>
117
+ hufi exchange register --name bitmart --api-key <key> --secret-key <secret> --bitmart-memo <memo>
113
118
  hufi exchange list
114
- hufi exchange revalidate --name mexc
115
- hufi exchange delete --name mexc
119
+ hufi exchange revalidate mexc
120
+ hufi exchange delete mexc
116
121
  ```
117
122
 
123
+ `exchange register` expects the CCXT exchange name in `--name` and accepts `--bitmart-memo` for Bitmart accounts that require an extra memo value.
124
+
125
+ You must run `hufi auth login` before `exchange register`, `exchange list`, `exchange delete`, or `exchange revalidate`.
126
+
118
127
  ### staking
119
128
 
120
129
  | Command | Description |
@@ -129,8 +138,8 @@ hufi exchange delete --name mexc
129
138
  hufi staking deposit # show address QR code
130
139
  hufi staking status # check your staking
131
140
  hufi staking status --address 0x... # check another address
132
- hufi staking stake -a 1000 # stake 1000 HMT
133
- hufi staking unstake -a 500 # unstake 500 HMT
141
+ hufi staking stake 1000 # stake 1000 HMT
142
+ hufi staking unstake 500 # unstake 500 HMT
134
143
  hufi staking withdraw # withdraw unlocked tokens
135
144
  ```
136
145
 
@@ -156,7 +165,7 @@ hufi dashboard --export json
156
165
  | `-V, --version` | Show version |
157
166
  | `-h, --help` | Show help |
158
167
 
159
- All commands support `--json` for machine-readable output.
168
+ Most command actions support `--json` for machine-readable output. Help output remains text, and commands that intentionally show help when required arguments are missing do not emit a JSON error envelope.
160
169
 
161
170
  ## Configuration
162
171
 
@@ -179,6 +188,7 @@ bun install # install deps
179
188
  bun run dev -- --help # run from source
180
189
  bun run build # build to dist/cli.js
181
190
  bun test # unit tests
191
+ ./test-cli.sh # full CLI integration coverage
182
192
  bun run test:cli # integration tests
183
193
  bun run typecheck # type check
184
194
  ```
package/dist/cli.js CHANGED
@@ -23063,9 +23063,33 @@ function requireAuthAddress() {
23063
23063
  }
23064
23064
 
23065
23065
  // src/commands/exchange.ts
23066
+ var authHintByAction = {
23067
+ register: "Run: hufi auth login --private-key <key> before registering exchange API keys.",
23068
+ list: "Run: hufi auth login --private-key <key> before listing exchange API keys.",
23069
+ delete: "Run: hufi auth login --private-key <key> before deleting exchange API keys.",
23070
+ revalidate: "Run: hufi auth login --private-key <key> before revalidating exchange API keys."
23071
+ };
23072
+ function isUnauthorizedError(err) {
23073
+ return err instanceof ApiError && err.status === 401;
23074
+ }
23075
+ function formatExchangeCommandErrorMessage(action, err) {
23076
+ const message = err instanceof Error ? err.message : String(err);
23077
+ const commandTarget = action === "list" ? "exchange API keys" : "exchange API key";
23078
+ if (isUnauthorizedError(err)) {
23079
+ return `Failed to ${action} ${commandTarget}: ${message}. ${authHintByAction[action]}`;
23080
+ }
23081
+ return `Failed to ${action} ${commandTarget}: ${message}`;
23082
+ }
23083
+ function formatRevalidateText(exchangeName, result) {
23084
+ const lines = [`${exchangeName}: ${result.is_valid ? "valid" : "invalid"}`];
23085
+ if ((result.missing_permissions?.length ?? 0) > 0) {
23086
+ lines.push(`Missing permissions: ${result.missing_permissions?.join(", ")}`);
23087
+ }
23088
+ return lines;
23089
+ }
23066
23090
  function createExchangeCommand() {
23067
23091
  const exchange = new Command("exchange").description("Exchange API key management");
23068
- exchange.command("register").description("Register a read-only exchange API key").requiredOption("-n, --name <name>", "Exchange name (e.g. binance, mexc)").requiredOption("--api-key <key>", "Read-only API key").requiredOption("--secret-key <key>", "Read-only API secret").option("--bitmart-memo <memo>", "Bitmart memo (only for bitmart)").option("--json", "Output as JSON").action(async (opts) => {
23092
+ exchange.command("register").description("Register a read-only exchange API key").requiredOption("-n, --name <name>", "CCXT exchange name (e.g. binance, mexc)").requiredOption("--api-key <key>", "Read-only API key").requiredOption("--secret-key <key>", "Read-only API secret").option("--bitmart-memo <memo>", "Bitmart memo (only for bitmart)").option("--json", "Output as JSON").action(async (opts) => {
23069
23093
  const { baseUrl, accessToken } = requireAuthToken();
23070
23094
  try {
23071
23095
  const result = await registerExchangeApiKey(baseUrl, accessToken, opts.name, opts.apiKey, opts.secretKey, opts.bitmartMemo);
@@ -23078,8 +23102,7 @@ function createExchangeCommand() {
23078
23102
  }
23079
23103
  }
23080
23104
  } catch (err) {
23081
- const message = err instanceof Error ? err.message : String(err);
23082
- printText(`Failed to register exchange API key: ${message}`);
23105
+ printText(formatExchangeCommandErrorMessage("register", err));
23083
23106
  process.exitCode = 1;
23084
23107
  }
23085
23108
  });
@@ -23100,42 +23123,49 @@ function createExchangeCommand() {
23100
23123
  }
23101
23124
  }
23102
23125
  } catch (err) {
23103
- const message = err instanceof Error ? err.message : String(err);
23104
- printText(`Failed to list exchange API keys: ${message}`);
23126
+ printText(formatExchangeCommandErrorMessage("list", err));
23105
23127
  process.exitCode = 1;
23106
23128
  }
23107
23129
  });
23108
- exchange.command("delete").description("Delete API keys for an exchange").requiredOption("-n, --name <name>", "Exchange name (e.g. mexc, bybit)").option("--json", "Output as JSON").action(async (opts) => {
23130
+ exchange.command("delete [name]").description("Delete API keys for an exchange").usage("[name] [options]").option("-n, --name <name>", "Exchange name (e.g. mexc, bybit)").option("--json", "Output as JSON").action(async (nameArg, opts) => {
23131
+ const exchangeName = nameArg ?? opts.name;
23132
+ if (!exchangeName) {
23133
+ printText("Exchange name is required. Usage: hufi exchange delete <name>");
23134
+ process.exitCode = 1;
23135
+ return;
23136
+ }
23109
23137
  const { baseUrl, accessToken } = requireAuthToken();
23110
23138
  try {
23111
- await deleteExchangeApiKey(baseUrl, accessToken, opts.name);
23139
+ await deleteExchangeApiKey(baseUrl, accessToken, exchangeName);
23112
23140
  if (opts.json) {
23113
- printJson({ deleted: true, exchange_name: opts.name });
23141
+ printJson({ deleted: true, exchange_name: exchangeName });
23114
23142
  } else {
23115
- printText(`Deleted API keys for ${opts.name}.`);
23143
+ printText(`Deleted API keys for ${exchangeName}.`);
23116
23144
  }
23117
23145
  } catch (err) {
23118
- const message = err instanceof Error ? err.message : String(err);
23119
- printText(`Failed to delete exchange API keys: ${message}`);
23146
+ printText(formatExchangeCommandErrorMessage("delete", err));
23120
23147
  process.exitCode = 1;
23121
23148
  }
23122
23149
  });
23123
- exchange.command("revalidate").description("Revalidate exchange API key").requiredOption("-n, --name <name>", "Exchange name (e.g. mexc, bybit)").option("--json", "Output as JSON").action(async (opts) => {
23150
+ exchange.command("revalidate [name]").description("Revalidate exchange API key").usage("[name] [options]").option("-n, --name <name>", "Exchange name (e.g. mexc, bybit)").option("--json", "Output as JSON").action(async (nameArg, opts) => {
23151
+ const exchangeName = nameArg ?? opts.name;
23152
+ if (!exchangeName) {
23153
+ printText("Exchange name is required. Usage: hufi exchange revalidate <name>");
23154
+ process.exitCode = 1;
23155
+ return;
23156
+ }
23124
23157
  const { baseUrl, accessToken } = requireAuthToken();
23125
23158
  try {
23126
- const result = await revalidateExchangeApiKey(baseUrl, accessToken, opts.name);
23159
+ const result = await revalidateExchangeApiKey(baseUrl, accessToken, exchangeName);
23127
23160
  if (opts.json) {
23128
23161
  printJson(result);
23129
23162
  } else {
23130
- const status = result.is_valid ? "valid" : "invalid";
23131
- printText(`${opts.name}: ${status}`);
23132
- if (result.missing_permissions.length > 0) {
23133
- printText(`Missing permissions: ${result.missing_permissions.join(", ")}`);
23163
+ for (const line of formatRevalidateText(exchangeName, result)) {
23164
+ printText(line);
23134
23165
  }
23135
23166
  }
23136
23167
  } catch (err) {
23137
- const message = err instanceof Error ? err.message : String(err);
23138
- printText(`Failed to revalidate exchange API key: ${message}`);
23168
+ printText(formatExchangeCommandErrorMessage("revalidate", err));
23139
23169
  process.exitCode = 1;
23140
23170
  }
23141
23171
  });
@@ -24966,6 +24996,66 @@ async function runWatchLoop(fn, options = {}) {
24966
24996
  }
24967
24997
 
24968
24998
  // src/commands/campaign.ts
24999
+ function formatCampaignTimestamp(value) {
25000
+ if (!value)
25001
+ return "-";
25002
+ return value.replace("T", " ").replace(/\.\d+Z$/, "").replace(/Z$/, "");
25003
+ }
25004
+ function formatUtcTimestamp(value) {
25005
+ if (typeof value !== "string")
25006
+ return "-";
25007
+ const date = new Date(value);
25008
+ if (Number.isNaN(date.getTime()))
25009
+ return String(value);
25010
+ return date.toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC");
25011
+ }
25012
+ function formatMetricValue(value) {
25013
+ if (value === null || value === undefined)
25014
+ return "-";
25015
+ if (typeof value === "number") {
25016
+ return Number.isInteger(value) ? String(value) : value.toFixed(4).replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1");
25017
+ }
25018
+ if (typeof value === "string") {
25019
+ const trimmed = value.trim();
25020
+ if (trimmed && /^-?\d+(\.\d+)?$/.test(trimmed)) {
25021
+ const num = Number(trimmed);
25022
+ if (!Number.isNaN(num)) {
25023
+ return formatMetricValue(num);
25024
+ }
25025
+ }
25026
+ return value;
25027
+ }
25028
+ return JSON.stringify(value);
25029
+ }
25030
+ function printAlignedMetric(label, value, labelWidth = 14) {
25031
+ printText(` ${label.padEnd(labelWidth)} ${formatMetricValue(value)}`);
25032
+ }
25033
+ function printCampaignProgressCard(result) {
25034
+ const myMeta = result.my_meta && typeof result.my_meta === "object" ? result.my_meta : {};
25035
+ const totalMeta = result.total_meta && typeof result.total_meta === "object" ? result.total_meta : {};
25036
+ const myScore = Number(result.my_score ?? 0);
25037
+ const totalScore = Number(totalMeta.total_score ?? 0);
25038
+ const scoreShare = totalScore > 0 && Number.isFinite(myScore) ? `${(myScore / totalScore * 100).toFixed(2)}%` : "-";
25039
+ printText("Campaign Progress");
25040
+ printText("-----------------");
25041
+ printText("[Window]");
25042
+ printAlignedMetric("From", formatUtcTimestamp(result.from));
25043
+ printAlignedMetric("To", formatUtcTimestamp(result.to));
25044
+ printText("");
25045
+ printText("[Mine]");
25046
+ printAlignedMetric("Score", result.my_score);
25047
+ printAlignedMetric("Token balance", myMeta.token_balance);
25048
+ printAlignedMetric("Score share", scoreShare);
25049
+ printText("");
25050
+ printText("[Totals]");
25051
+ printAlignedMetric("Total score", totalMeta.total_score);
25052
+ printAlignedMetric("Total balance", totalMeta.total_balance);
25053
+ }
25054
+ function formatTokenAmount(value, decimals, displayDecimals = 2) {
25055
+ const amount = new bignumber_default(value).dividedBy(new bignumber_default(10).pow(decimals));
25056
+ const rounded = amount.decimalPlaces(displayDecimals, bignumber_default.ROUND_HALF_UP);
25057
+ return rounded.toFormat().replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1");
25058
+ }
24969
25059
  function formatCampaignCreateProgress(confirmations) {
24970
25060
  if (confirmations <= 0) {
24971
25061
  return "Transaction submitted. Waiting for confirmations...";
@@ -24976,6 +25066,34 @@ function getLauncherUrl() {
24976
25066
  const config = loadConfig();
24977
25067
  return (config.launcherApiUrl ?? "https://cl.hu.finance").replace(/\/+$/, "");
24978
25068
  }
25069
+ function printCampaignSummary(campaign, options = {}) {
25070
+ const indent = options.indent ?? "";
25071
+ const joinedTag = options.joinedTag ?? "";
25072
+ const showLauncher = options.showLauncher ?? true;
25073
+ const exchange = String(campaign.exchange_name ?? "?");
25074
+ const symbol = String(campaign.symbol ?? "?");
25075
+ const type = String(campaign.type ?? "?");
25076
+ const chainId = String(campaign.chain_id ?? "-");
25077
+ const address = String(campaign.address ?? campaign.escrow_address ?? "-");
25078
+ const status = String(campaign.status ?? "-");
25079
+ const startDate = typeof campaign.start_date === "string" ? campaign.start_date : undefined;
25080
+ const endDate = typeof campaign.end_date === "string" ? campaign.end_date : undefined;
25081
+ const launcher = typeof campaign.launcher === "string" ? campaign.launcher : undefined;
25082
+ const decimals = Number(campaign.fund_token_decimals ?? 0);
25083
+ const fundAmount = new bignumber_default(String(campaign.fund_amount ?? 0));
25084
+ const balanceNum = new bignumber_default(String(campaign.balance ?? 0));
25085
+ const pct = fundAmount.gt(0) ? balanceNum.dividedBy(fundAmount).times(100).toFixed(1) : "0.0";
25086
+ const fundTokenSymbol = String(campaign.fund_token_symbol ?? campaign.fund_token ?? "-");
25087
+ printText(`${indent}${exchange} ${symbol} (${type})${joinedTag}`);
25088
+ printText(`${indent} chain: ${chainId}`);
25089
+ printText(`${indent} address: ${address}`);
25090
+ printText(`${indent} status: ${status}`);
25091
+ printText(`${indent} duration: ${formatCampaignTimestamp(startDate)} ~ ${formatCampaignTimestamp(endDate)}`);
25092
+ printText(`${indent} funded: ${formatTokenAmount(String(campaign.fund_amount ?? 0), decimals)} ${fundTokenSymbol} paid: ${formatTokenAmount(String(campaign.amount_paid ?? 0), decimals)} balance: ${formatTokenAmount(String(campaign.balance ?? 0), decimals)} (${pct}%)`);
25093
+ if (showLauncher && launcher) {
25094
+ printText(`${indent} launcher: ${launcher}`);
25095
+ }
25096
+ }
24979
25097
  function createCampaignCommand() {
24980
25098
  const campaign = new Command("campaign").description("Campaign management commands");
24981
25099
  campaign.command("list").description("List available campaigns").option("-c, --chain-id <id>", "Chain ID (default: from config)", Number, getDefaultChainId()).option("-s, --status <status>", "Filter by status (active, completed, cancelled, to_cancel)", "active").option("--page <n>", "Page number", Number, 1).option("--page-size <n>", "Items per page", Number, 20).option("-l, --limit <n>", "Max results", Number, 20).option("--json", "Output as JSON").action(async (opts) => {
@@ -25007,20 +25125,10 @@ function createCampaignCommand() {
25007
25125
  const key = `${c.chain_id}:${c.address}`;
25008
25126
  const joined = joinedKeys.has(key);
25009
25127
  const tag = joined ? " [JOINED]" : "";
25010
- const decimals = c.fund_token_decimals ?? 0;
25011
- const fmt = (v) => {
25012
- const bn = new bignumber_default(v).dividedBy(new bignumber_default(10).pow(decimals));
25013
- return bn.toFormat();
25014
- };
25015
- const fundAmount = new bignumber_default(c.fund_amount);
25016
- const balanceNum = new bignumber_default(c.balance);
25017
- const pct = fundAmount.gt(0) ? balanceNum.dividedBy(fundAmount).times(100).toFixed(1) : "0.0";
25018
- printText(` ${c.exchange_name} ${c.symbol} (${c.type})${tag}`);
25019
- printText(` chain: ${c.chain_id}`);
25020
- printText(` address: ${c.address}`);
25021
- printText(` status: ${c.status}`);
25022
- printText(` duration: ${c.start_date?.split("T")[0] ?? "-"} ~ ${c.end_date?.split("T")[0] ?? "-"}`);
25023
- printText(` funded: ${fmt(c.fund_amount)} ${c.fund_token_symbol} paid: ${fmt(c.amount_paid)} balance: ${fmt(c.balance)} (${pct}%)`);
25128
+ printCampaignSummary(c, {
25129
+ indent: " ",
25130
+ joinedTag: tag
25131
+ });
25024
25132
  printText("");
25025
25133
  }
25026
25134
  if (opts.status === "active") {
@@ -25048,12 +25156,11 @@ function createCampaignCommand() {
25048
25156
  printText(` chain: ${c.chain_id}`);
25049
25157
  printText(` status: ${c.status}`);
25050
25158
  const showDecimals = c.fund_token_decimals ?? 0;
25051
- const showFmt = (v) => new bignumber_default(v).dividedBy(new bignumber_default(10).pow(showDecimals)).toFormat();
25052
- printText(` funded: ${showFmt(c.fund_amount)} ${c.fund_token_symbol}`);
25053
- printText(` balance: ${showFmt(c.balance)} ${c.fund_token_symbol}`);
25054
- printText(` paid: ${showFmt(c.amount_paid)} ${c.fund_token_symbol}`);
25055
- printText(` start: ${c.start_date}`);
25056
- printText(` end: ${c.end_date}`);
25159
+ printText(` funded: ${formatTokenAmount(c.fund_amount, showDecimals)} ${c.fund_token_symbol}`);
25160
+ printText(` balance: ${formatTokenAmount(c.balance, showDecimals)} ${c.fund_token_symbol}`);
25161
+ printText(` paid: ${formatTokenAmount(c.amount_paid, showDecimals)} ${c.fund_token_symbol}`);
25162
+ printText(` start: ${formatCampaignTimestamp(c.start_date)}`);
25163
+ printText(` end: ${formatCampaignTimestamp(c.end_date)}`);
25057
25164
  printText(` launcher: ${c.launcher}`);
25058
25165
  }
25059
25166
  } catch (err) {
@@ -25073,10 +25180,24 @@ function createCampaignCommand() {
25073
25180
  if (campaigns.length === 0) {
25074
25181
  printText("No joined campaigns found.");
25075
25182
  } else {
25076
- printText(`Joined campaigns (${campaigns.length}):`);
25183
+ printText(`Joined campaigns (${campaigns.length}):
25184
+ `);
25077
25185
  for (const c of campaigns) {
25078
- const label = c.campaign_name ?? c.name ?? c.id;
25079
- printText(` - ${label}`);
25186
+ const record = c;
25187
+ const hasListMetadata = Boolean(record.exchange_name ?? record.symbol ?? record.type ?? record.address ?? record.escrow_address);
25188
+ if (hasListMetadata) {
25189
+ printCampaignSummary(record, {
25190
+ indent: " ",
25191
+ showLauncher: typeof record.launcher === "string"
25192
+ });
25193
+ } else {
25194
+ const exchange = String(record.exchange_name ?? "").trim();
25195
+ const symbol = String(record.symbol ?? "").trim();
25196
+ const exchangeSymbol = [exchange, symbol].filter(Boolean).join(" ");
25197
+ const label = record.campaign_name ?? record.name ?? (exchangeSymbol || undefined) ?? record.address ?? record.escrow_address ?? c.id ?? "(unnamed campaign)";
25198
+ printText(` - ${label}`);
25199
+ }
25200
+ printText("");
25080
25201
  }
25081
25202
  }
25082
25203
  }
@@ -25166,9 +25287,12 @@ function createCampaignCommand() {
25166
25287
  const r = result;
25167
25288
  if (r.message) {
25168
25289
  printText(String(r.message));
25290
+ } else if ("from" in r || "to" in r || "my_score" in r || "my_meta" in r || "total_meta" in r) {
25291
+ printCampaignProgressCard(r);
25169
25292
  } else {
25170
25293
  for (const [key, value] of Object.entries(r)) {
25171
- printText(` ${key}: ${value}`);
25294
+ const displayValue = value !== null && typeof value === "object" ? JSON.stringify(value) : String(value);
25295
+ printText(` ${key}: ${displayValue}`);
25172
25296
  }
25173
25297
  }
25174
25298
  }
@@ -25401,11 +25525,17 @@ function createStakingCommand() {
25401
25525
  process.exitCode = 1;
25402
25526
  }
25403
25527
  });
25404
- staking.command("stake").description("Stake HMT tokens").requiredOption("-a, --amount <amount>", "Amount of HMT to stake").option("-c, --chain-id <id>", "Chain ID (default: from config)", Number, getDefaultChainId()).option("--json", "Output as JSON").action(async (opts) => {
25528
+ staking.command("stake [amount]").description("Stake HMT tokens").usage("[amount] [options]").option("-a, --amount <amount>", "Amount of HMT to stake").option("-c, --chain-id <id>", "Chain ID (default: from config)", Number, getDefaultChainId()).option("--json", "Output as JSON").action(async (amountArg, opts) => {
25529
+ const amount = amountArg ?? opts.amount;
25530
+ if (!amount) {
25531
+ printText("Amount is required. Usage: hufi staking stake <amount>");
25532
+ process.exitCode = 1;
25533
+ return;
25534
+ }
25405
25535
  const privateKey = requireKey();
25406
25536
  try {
25407
- printText(`Staking ${opts.amount} HMT on chain ${opts.chainId}...`);
25408
- const hash2 = await stakeHMT(privateKey, opts.amount, opts.chainId);
25537
+ printText(`Staking ${amount} HMT on chain ${opts.chainId}...`);
25538
+ const hash2 = await stakeHMT(privateKey, amount, opts.chainId);
25409
25539
  if (opts.json) {
25410
25540
  printJson({ txHash: hash2 });
25411
25541
  } else {
@@ -25418,11 +25548,17 @@ function createStakingCommand() {
25418
25548
  process.exitCode = 1;
25419
25549
  }
25420
25550
  });
25421
- staking.command("unstake").description("Initiate unstaking (tokens will be locked for the lock period)").requiredOption("-a, --amount <amount>", "Amount of HMT to unstake").option("-c, --chain-id <id>", "Chain ID (default: from config)", Number, getDefaultChainId()).option("--json", "Output as JSON").action(async (opts) => {
25551
+ staking.command("unstake [amount]").description("Initiate unstaking (tokens will be locked for the lock period)").usage("[amount] [options]").option("-a, --amount <amount>", "Amount of HMT to unstake").option("-c, --chain-id <id>", "Chain ID (default: from config)", Number, getDefaultChainId()).option("--json", "Output as JSON").action(async (amountArg, opts) => {
25552
+ const amount = amountArg ?? opts.amount;
25553
+ if (!amount) {
25554
+ printText("Amount is required. Usage: hufi staking unstake <amount>");
25555
+ process.exitCode = 1;
25556
+ return;
25557
+ }
25422
25558
  const privateKey = requireKey();
25423
25559
  try {
25424
- printText(`Unstaking ${opts.amount} HMT on chain ${opts.chainId}...`);
25425
- const hash2 = await unstakeHMT(privateKey, opts.amount, opts.chainId);
25560
+ printText(`Unstaking ${amount} HMT on chain ${opts.chainId}...`);
25561
+ const hash2 = await unstakeHMT(privateKey, amount, opts.chainId);
25426
25562
  if (opts.json) {
25427
25563
  printJson({ txHash: hash2 });
25428
25564
  } else {
@@ -25595,7 +25731,7 @@ function createDashboardCommand() {
25595
25731
 
25596
25732
  // src/cli.ts
25597
25733
  var program2 = new Command;
25598
- program2.name("hufi").description("CLI tool for hu.fi DeFi platform").version("1.0.0").option("--config-file <path>", "Custom config file path (default: ~/.hufi-cli/config.json)").option("--key-file <path>", "Custom key file path (default: ~/.hufi-cli/key.json)").hook("preAction", (thisCommand) => {
25734
+ program2.name("hufi").description("CLI tool for hu.fi DeFi platform").version("1.0.2").option("--config-file <path>", "Custom config file path (default: ~/.hufi-cli/config.json)").option("--key-file <path>", "Custom key file path (default: ~/.hufi-cli/key.json)").hook("preAction", (thisCommand) => {
25599
25735
  const opts = thisCommand.opts();
25600
25736
  if (opts.configFile) {
25601
25737
  setConfigFile(opts.configFile);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hufi-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "hufi": "./dist/cli.js"