@candledottv/cli 0.11.37 → 0.11.39

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 +2 -2
  2. package/dist/index.js +978 -392
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -46,6 +46,43 @@ var __export = (target, all) => {
46
46
  };
47
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
49
+ var __dispose = Symbol.dispose || /* @__PURE__ */ Symbol.for("Symbol.dispose");
50
+ var __asyncDispose = Symbol.asyncDispose || /* @__PURE__ */ Symbol.for("Symbol.asyncDispose");
51
+ var __using = (stack, value, async) => {
52
+ if (value != null) {
53
+ if (typeof value !== "object" && typeof value !== "function")
54
+ throw TypeError('Object expected to be assigned to "using" declaration');
55
+ var dispose;
56
+ if (async)
57
+ dispose = value[__asyncDispose];
58
+ if (dispose === undefined)
59
+ dispose = value[__dispose];
60
+ if (typeof dispose !== "function")
61
+ throw TypeError("Object not disposable");
62
+ stack.push([async, dispose, value]);
63
+ } else if (async) {
64
+ stack.push([async]);
65
+ }
66
+ return value;
67
+ };
68
+ var __callDispose = (stack, error, hasError) => {
69
+ var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
70
+ return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
71
+ }, fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e), next = (it) => {
72
+ while (it = stack.pop()) {
73
+ try {
74
+ var result = it[1] && it[1].call(it[2]);
75
+ if (it[0])
76
+ return Promise.resolve(result).then(next, (e) => (fail(e), next()));
77
+ } catch (e) {
78
+ fail(e);
79
+ }
80
+ }
81
+ if (hasError)
82
+ throw error;
83
+ };
84
+ return next();
85
+ };
49
86
 
50
87
  // src/client.ts
51
88
  function trimTrailingSlashes(url) {
@@ -37368,7 +37405,7 @@ var init_convert = __esm(() => {
37368
37405
  });
37369
37406
 
37370
37407
  // ../mcp/src/version.ts
37371
- var SERVER_VERSION = "0.10.20";
37408
+ var SERVER_VERSION = "0.10.21";
37372
37409
 
37373
37410
  // ../mcp/src/update-notice.ts
37374
37411
  function newer(a, b) {
@@ -39647,18 +39684,53 @@ function perpsCandleWindow(opts, now) {
39647
39684
  return usage3(`interval must be one of ${PERPS_CANDLE_INTERVALS.join(" ")}.`);
39648
39685
  const count = bounded(opts.count, 100, 1, 5000, "count");
39649
39686
  const raw = opts.end;
39650
- const endTime = raw === undefined ? now : typeof raw === "number" || /^\d+$/.test(raw) ? Number(raw) : /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/.test(raw) ? Date.parse(raw) : NaN;
39687
+ const endTime = raw === undefined ? now : epoch(raw);
39651
39688
  const startTime = endTime - count * at(INTERVAL_MS, index);
39652
39689
  if (!Number.isSafeInteger(endTime) || endTime < 0 || endTime > now || startTime < 0)
39653
39690
  return usage3("end must be an ISO 8601 time or epoch milliseconds, no later than now.");
39654
39691
  return { interval, count, startTime, endTime };
39655
39692
  }
39693
+ function epoch(raw) {
39694
+ return typeof raw === "number" || /^\d+$/.test(raw) ? Number(raw) : /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/.test(raw) ? Date.parse(raw) : NaN;
39695
+ }
39696
+ function perpsFundingWindow(opts, now) {
39697
+ const endTime = opts.end === undefined ? now : epoch(opts.end);
39698
+ if (!Number.isSafeInteger(endTime) || endTime < 0 || endTime > now)
39699
+ return usage3("end must be an ISO 8601 time or epoch milliseconds, no later than now.");
39700
+ const startTime = opts.start === undefined ? endTime - 86400000 : epoch(opts.start);
39701
+ if (!Number.isSafeInteger(startTime) || startTime < 0 || startTime > endTime)
39702
+ return usage3("start must be an ISO 8601 time or epoch milliseconds, no later than end.");
39703
+ if (endTime - startTime > PERPS_FUNDING_HISTORY_MAX_MS)
39704
+ return usage3("start must be at most 30 days before end.");
39705
+ return { startTime, endTime };
39706
+ }
39656
39707
  function bounded(value, fallback, min, max, name) {
39657
39708
  const n = value ?? fallback;
39658
39709
  if (!Number.isInteger(n) || n < min || n > max)
39659
39710
  return usage3(`${name} must be an integer from ${min} to ${max}.`);
39660
39711
  return n;
39661
39712
  }
39713
+ function parsePerpsFundingHistory(value, coin, window) {
39714
+ if (!Array.isArray(value))
39715
+ return unavailable2();
39716
+ const funding = [];
39717
+ for (const row of value) {
39718
+ if (!object(row) || row.coin !== coin || !integer(row.time) || !decimal2(row.fundingRate) || !decimal2(row.premium))
39719
+ return unavailable2();
39720
+ if (row.time >= window.startTime && row.time <= window.endTime)
39721
+ funding.push({
39722
+ time: row.time,
39723
+ fundingRate: row.fundingRate,
39724
+ fundingAprPct: fixed(scaled(row.fundingRate) * 876000n, 2),
39725
+ premium: row.premium
39726
+ });
39727
+ }
39728
+ funding.sort((a, b) => a.time - b.time);
39729
+ return {
39730
+ funding: funding.slice(0, PERPS_FUNDING_HISTORY_MAX_ROWS),
39731
+ truncated: funding.length > PERPS_FUNDING_HISTORY_MAX_ROWS
39732
+ };
39733
+ }
39662
39734
 
39663
39735
  class PerpsMarketReader {
39664
39736
  opts;
@@ -39936,8 +40008,46 @@ class PerpsMarketReader {
39936
40008
  candles: candles.slice(-window.count)
39937
40009
  };
39938
40010
  }
40011
+ async fundingHistory(coin, opts = {}) {
40012
+ this.key();
40013
+ const window = perpsFundingWindow(opts, this.now());
40014
+ const [config, core] = await Promise.all([this.config(), this.core()]);
40015
+ const canonical2 = at(core.universe, resolve(core, coin)).name;
40016
+ const value = await info(this.opts.fetcher, this.opts.network, {
40017
+ type: "fundingHistory",
40018
+ coin: canonical2,
40019
+ startTime: window.startTime,
40020
+ endTime: window.endTime
40021
+ });
40022
+ return {
40023
+ ...this.envelope(config),
40024
+ coin: canonical2,
40025
+ startTime: window.startTime,
40026
+ endTime: window.endTime,
40027
+ ...parsePerpsFundingHistory(value, canonical2, window)
40028
+ };
40029
+ }
40030
+ async predictedFunding() {
40031
+ this.key();
40032
+ const [config, core, extras] = await Promise.all([this.config(), this.core(), this.extras()]);
40033
+ if (extras.predicted === null)
40034
+ return unavailable2();
40035
+ const predicted = [];
40036
+ for (const meta of core.universe) {
40037
+ const row = extras.predicted.get(meta.name);
40038
+ if (row)
40039
+ predicted.push({
40040
+ coin: meta.name,
40041
+ fundingRate: row.fundingRate,
40042
+ fundingAprPct: fixed(scaled(row.fundingRate) * 876000n, 2),
40043
+ nextFundingTime: row.nextFundingTime
40044
+ });
40045
+ }
40046
+ predicted.sort((a, b) => a.coin < b.coin ? -1 : a.coin > b.coin ? 1 : 0);
40047
+ return { ...this.envelope(config, extras.warnings, this.extrasCache?.at ?? this.now()), predicted };
40048
+ }
39939
40049
  }
39940
- var INFO_URLS, ONE, DECIMAL, PerpsMarketError, PERPS_CANDLE_INTERVALS, INTERVAL_MS;
40050
+ var INFO_URLS, ONE, DECIMAL, PerpsMarketError, PERPS_CANDLE_INTERVALS, INTERVAL_MS, PERPS_FUNDING_HISTORY_MAX_MS, PERPS_FUNDING_HISTORY_MAX_ROWS = 500;
39941
40051
  var init_hyperliquid_markets = __esm(() => {
39942
40052
  INFO_URLS = {
39943
40053
  mainnet: "https://api.hyperliquid.xyz/info",
@@ -39987,6 +40097,7 @@ var init_hyperliquid_markets = __esm(() => {
39987
40097
  604800000,
39988
40098
  2678400000
39989
40099
  ];
40100
+ PERPS_FUNDING_HISTORY_MAX_MS = 30 * 86400000;
39990
40101
  });
39991
40102
 
39992
40103
  // ../mcp/src/perps-history.ts
@@ -42466,7 +42577,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
42466
42577
  for (const name of PERPS_MARKET_TOOL_NAMES) {
42467
42578
  register(name, {
42468
42579
  title: name.replace("candle_perps_", "Hyperliquid "),
42469
- description: "Read live market data directly from Hyperliquid's public API on this machine. Needs a Candle agent key of any scope, no signer. candle.tradeable is Candle's build gate at read time, not a promise; OI caps may still refuse an order. The hosted connector does not carry these tools.",
42580
+ description: "Read live market data directly from Hyperliquid's public API on this machine. Needs a Candle agent key of any scope, no signer. candle.tradeable is Candle's build gate at read time, not a promise; OI caps may still refuse an order. The hosted connector offers the same four reads at its Read level, read by Candle's server instead of this machine.",
42470
42581
  inputSchema: perpsMarketShapes[name],
42471
42582
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
42472
42583
  }, async (args) => {
@@ -42628,7 +42739,7 @@ var init_tools = __esm(() => {
42628
42739
  init_plans();
42629
42740
  init_update_notice();
42630
42741
  VAMP_CANDIDATES_DESCRIPTION = "Lists Solana tokens that are close to graduating or graduated in the last 6 hours whose ticker other tokens " + "are copying, most-copied in the last 24 hours first. Each entry carries a launch draft (name, ticker, image URL) " + "for `candle_launch_token` or `candle_launch_and_seed`; draft notes identify fields you must supply or adjust. " + "Upload your own image through the agent image upload when the image is unavailable. Use `dryRun: true` first. " + "Counts exclude the candidate itself and cover both Solana and Hood launches at the venues returned in " + "`coverage`, not Candle's own. `coverage.since` is the returned Solana coverage date; Hood history may differ.";
42631
- CUSTOM_CURVE_RULE = 'mode "exp-custom" with curve { virtualQuote, threshold } launches a Solana curve with your own V (the virtual ' + "quote it starts with) and G (the quote it raises before graduating), in whole SOL or CNDL as decimal strings, " + "accepted when V is 1 to 100 SOL and G 1 to 250 SOL (66,000 to 6,600,000 and 66,000 to 16,500,000 CNDL), when " + "every price up to 20 x G stays under the ceiling (V + 20G)^2 < V x X at this stakerAllocationBps, when neither " + "is finer than the quote's decimals, and when the dev buy is at most 10% of G; a curve past a limit is " + "CURVE_OUT_OF_RANGE, and at the ceiling it carries maxThresholdRaw, the largest G that fits. " + "Dry-run it first (dryRun: true): the dry run spends nothing and returns curveTerms, the market cap at start and " + "at graduation and the supply sold by graduation. Only where the API has EXP_CUSTOM_CURVES on; " + "elsewhere exp-custom is refused as an unknown mode.";
42742
+ CUSTOM_CURVE_RULE = 'mode "exp-custom" with curve { virtualQuote, threshold } launches a Solana curve with your own V (the virtual ' + "quote it starts with) and G (the quote it raises before graduating), in whole SOL or CNDL as decimal strings, " + "accepted when V is 1 to 100 SOL and G 1 to 250 SOL (66,000 to 6,600,000 and 66,000 to 16,500,000 CNDL), when " + "every price up to 20 x G stays under the ceiling (V + 20G)^2 < V x X at this stakerAllocationBps, when neither " + "is finer than the quote's decimals, and when the dev buy is at most 0.5 SOL (2,000,000 CNDL), or 10% of G when " + "that is lower (every exp- mode; an operator may set the 0.5 SOL lower); a curve past a limit is " + "CURVE_OUT_OF_RANGE, and at the ceiling it carries maxThresholdRaw, the largest G that fits. " + "Dry-run it first (dryRun: true): the dry run spends nothing and returns curveTerms, the market cap at start and " + "at graduation, the supply sold by graduation, the share your own buyAmount takes, the fees and the migration " + "terms, and devBuyMax, the dev-buy ceiling that applies. Only where the API has EXP_CUSTOM_CURVES on; " + "elsewhere exp-custom is refused as an unknown mode.";
42632
42743
  localProfileTools = new WeakMap;
42633
42744
  stakingWriteShape = {
42634
42745
  payer: exports_external.enum(["main", "linked"]).optional().describe("main is the embedded wallet; linked names a linked or TEE wallet"),
@@ -42707,12 +42818,23 @@ var init_tools = __esm(() => {
42707
42818
  bannerUrl: exports_external.string().optional().describe("Optional https URL to WIDE artwork for the token page's banner strip (wider than 1.5:1, " + "e.g. 1200x630). This is where a share card or OG image belongs. A square image here is " + "rejected with BANNER_WRONG_SHAPE: send the wide original, not a cropped copy. Omit it and the strip " + "falls back to imageUrl"),
42708
42819
  chain: exports_external.string().optional().describe('"solana" or "hood"; defaults to solana'),
42709
42820
  quoteAsset: exports_external.string().optional().describe("Quote asset symbol; defaults per chain"),
42710
- mode: exports_external.string().optional().describe('"open" or "exclusive"; defaults to open. "test-open" / "test-exclusive" launch the ' + "low-threshold test curves (~1/80 economics) where the API has ENABLE_TEST_CURVES on. " + '"exp-custom" launches a Solana curve with your own V and G, given in curve, where the API has it ' + "switched on"),
42821
+ mode: exports_external.string().optional().describe('"open" or "exclusive"; defaults to open. "test-open" / "test-exclusive" launch the ' + "low-threshold test curves (~1/80 economics) where the API has ENABLE_TEST_CURVES on. " + '"exp-a", "exp-b" and "exp-c" are the experimental fast Solana curves (sol or cndl). ' + '"exp-custom" launches a Solana curve with your own V and G, given in curve, where the API has it ' + "switched on"),
42711
42822
  curve: exports_external.object({
42712
42823
  virtualQuote: exports_external.string().describe('V: the virtual quote the curve starts with, in whole quote units, e.g. "12" SOL'),
42713
42824
  threshold: exports_external.string().describe('G: the quote the curve raises before it graduates, in whole quote units, e.g. "40" SOL')
42714
42825
  }).strict().optional().describe('mode "exp-custom" only, and required there. Decimal strings in whole units of the quote asset (sol or ' + "cndl), never raw units. Dry-run first: the API checks the limits and returns the curve's terms"),
42826
+ creatorFeeBps: exports_external.union([exports_external.literal(0), exports_external.literal(100), exports_external.literal(200)]).optional().describe("An exp- mode only: an extra creator fee of 100 or 200 basis points on top of the curve's 1% and the " + "pool's 0.5%, paid to the creator in full. 0 is the same as leaving it out"),
42715
42827
  stakerAllocationBps: exports_external.number().optional().describe("Staker allocation in basis points"),
42828
+ poolFee: exports_external.union([
42829
+ exports_external.object({ tier: exports_external.union([exports_external.literal(25), exports_external.literal(50), exports_external.literal(100)]) }).strict(),
42830
+ exports_external.object({
42831
+ startBps: exports_external.number().int().max(2500),
42832
+ endBps: exports_external.union([exports_external.literal(25), exports_external.literal(50), exports_external.literal(100)]),
42833
+ minutes: exports_external.number().positive().max(30)
42834
+ }).strict()
42835
+ ]).optional().describe("Solana only: the graduated pool's fee. { tier: 25 | 50 | 100 } is a flat fee in bps (default 50), or " + "{ startBps, endBps, minutes } is an anti-snipe fall from startBps (at most 2500, at least endBps) to " + "endBps (25, 50 or 100) over minutes (at most 30). Cannot be combined with creatorFeeBps unless the tier is 50. " + "Omit for the standard 50 bps pool"),
42836
+ poolOpen: exports_external.enum(["legacy", "curve_price"]).optional().describe(`Solana preset tiers only. "curve_price" opens the pool at the curve's last price (the whole reserve is ` + "seeded and the remaining base burned); stakerAllocationBps plus the team allocation must fit the tier's limit, " + "which the dry run returns as graduation.maxStakerCarveBps. Omit for legacy"),
42837
+ expPoolShape: exports_external.enum(["floor", "full_range"]).optional().describe('Solana exp- curves only. "full_range" opens the pool across the whole price range. Omit for floor'),
42716
42838
  dexVersion: exports_external.string().nullable().optional().describe('"v3" or "v4"; required for hood launches'),
42717
42839
  buyAmount: exports_external.union([exports_external.string(), exports_external.number()]).optional().describe("Initial buy, in quote base units (string or number)"),
42718
42840
  description: exports_external.string().optional(),
@@ -70366,7 +70488,7 @@ init_render();
70366
70488
  init_secret_store();
70367
70489
 
70368
70490
  // src/version.ts
70369
- var CLI_VERSION = "0.11.37";
70491
+ var CLI_VERSION = "0.11.39";
70370
70492
 
70371
70493
  // src/commands/auth.ts
70372
70494
  init_keys_embedded_wallet();
@@ -70766,6 +70888,86 @@ async function authStatus(args, ctx) {
70766
70888
  return exitCode;
70767
70889
  }
70768
70890
 
70891
+ // src/terminal-theme.ts
70892
+ var TERMINAL_TOKENS = {
70893
+ web: {
70894
+ primary: "#32fe9f",
70895
+ positive: "#32d583",
70896
+ negative: "#f2536d",
70897
+ warning: "#f5a524",
70898
+ edge: "#26262b"
70899
+ },
70900
+ dark: {
70901
+ brand: { hex: "#32fe9f", ansi256: 48, ansi16: 92 },
70902
+ positive: { hex: "#32d583", ansi256: 42, ansi16: 92 },
70903
+ negative: { hex: "#f2536d", ansi256: 204, ansi16: 91 },
70904
+ warning: { hex: "#f5a524", ansi256: 214, ansi16: 93 }
70905
+ },
70906
+ light: {
70907
+ brand: { hex: "#006b40", ansi256: 22, ansi16: 32 },
70908
+ positive: { hex: "#087443", ansi256: 22, ansi16: 32 },
70909
+ negative: { hex: "#b4233c", ansi256: 125, ansi16: 31 },
70910
+ warning: { hex: "#875400", ansi256: 94, ansi16: 33 }
70911
+ }
70912
+ };
70913
+
70914
+ // src/presentation.ts
70915
+ function terminalStyle(deps, stream, machine = false) {
70916
+ const { env, presentation } = deps;
70917
+ const styled = !(machine || presentation?.plain || env.CANDLE_PLAIN === "1" || !deps.isTTY[stream] || env.CI || env.TERM === "dumb");
70918
+ const noColor = presentation?.noColor || Boolean(env.NO_COLOR);
70919
+ const color = !styled || noColor ? 0 : /truecolor|24bit/i.test(env.COLORTERM ?? "") ? 24 : /256color/i.test(env.TERM ?? "") ? 256 : env.TERM ? 16 : 0;
70920
+ const background = Number(env.COLORFGBG?.split(";").at(-1));
70921
+ const theme = Number.isFinite(background) && background >= 0 && background <= 6 ? "dark" : "light";
70922
+ const columns = deps.columns?.[stream];
70923
+ return {
70924
+ width: columns && Number.isFinite(columns) ? Math.max(20, Math.floor(columns)) : 118,
70925
+ styled,
70926
+ color,
70927
+ theme,
70928
+ motion: styled && !noColor
70929
+ };
70930
+ }
70931
+ function accent(text, style, role = "brand") {
70932
+ if (!style.color)
70933
+ return text;
70934
+ const token = TERMINAL_TOKENS[style.theme][role];
70935
+ const code = style.color === 24 ? `38;2;${token.hex.slice(1).match(/../g)?.map((part) => Number.parseInt(part, 16)).join(";")}` : style.color === 256 ? `38;5;${token.ansi256}` : `${token.ansi16}`;
70936
+ return `\x1B[${code}m${text}\x1B[0m`;
70937
+ }
70938
+ function wrapText(text, width) {
70939
+ const lines = [];
70940
+ let line = "";
70941
+ for (const word3 of text.split(" ")) {
70942
+ if (!line || line.length + word3.length + 1 <= width)
70943
+ line += `${line ? " " : ""}${word3}`;
70944
+ else {
70945
+ lines.push(line);
70946
+ line = word3;
70947
+ }
70948
+ }
70949
+ lines.push(line);
70950
+ return lines;
70951
+ }
70952
+ var WORDMARK = ["█▀▀ ▄▀█ █▄░█ █▀▄ █░░ █▀▀", "█▄▄ █▀█ █░▀█ █▄▀ █▄▄ ██▄"];
70953
+ function brandHeader(style) {
70954
+ if (!style.styled)
70955
+ return [];
70956
+ const lines = [
70957
+ ...style.width >= 80 ? WORDMARK : ["CANDLE"],
70958
+ `CLI ${CLI_VERSION}`,
70959
+ "",
70960
+ ...wrapText("Your wallets and agent tools, one command away.", style.width - 6)
70961
+ ];
70962
+ const width = Math.min(style.width - 2, 68);
70963
+ return [
70964
+ accent(`┌${"─".repeat(width - 2)}┐`, style),
70965
+ ...lines.map((line) => `│ ${accent(line, style)}${" ".repeat(Math.max(0, width - 4 - line.length))} │`),
70966
+ accent(`└${"─".repeat(width - 2)}┘`, style),
70967
+ ""
70968
+ ];
70969
+ }
70970
+
70769
70971
  // src/help.ts
70770
70972
  var GROUPS = [
70771
70973
  "Start here",
@@ -70776,6 +70978,7 @@ var GROUPS = [
70776
70978
  "Maintain"
70777
70979
  ];
70778
70980
  var ENVIRONMENT = [
70981
+ { name: "CANDLE_PLAIN", description: "Set to 1 for --plain output" },
70779
70982
  {
70780
70983
  name: "CANDLE_CONFIG_DIR",
70781
70984
  description: "Where vault.enc, tee-wallets.enc, credentials and config live (default ~/.config/candle). Set it once instead of passing --keystore."
@@ -70838,6 +71041,8 @@ var KEYSTORE_FLAG = {
70838
71041
  description: "The vault file for this invocation. For a permanent location set CANDLE_CONFIG_DIR instead."
70839
71042
  };
70840
71043
  var GLOBAL_FLAGS = [
71044
+ { invocation: "--plain", description: "Plain ASCII: no banner, boxes, color or animation" },
71045
+ { invocation: "--no-color", description: "Suppress color and decorative animation; keep terminal layout" },
70841
71046
  { invocation: "--profile <name>", description: "Act as a named profile" },
70842
71047
  { invocation: "--api-url <url>", description: "Override the API base URL" },
70843
71048
  { invocation: "--json", description: "Machine-readable output: exactly one JSON value on stdout" },
@@ -71006,7 +71211,7 @@ var HELP = {
71006
71211
  perps: {
71007
71212
  group: "Trade",
71008
71213
  summary: "Hyperliquid perpetuals: markets, market, book, candles; deposit, setup, withdraw, open, close, cancel, modify, margin, tpsl, schedule-cancel, flatten, reduce-only, orders, positions, fills, funding, leverage, account, risk, watch, thresholds",
71009
- description: "markets, market, book and candles read directly from Hyperliquid with any Candle agent key and no signer or wallet. Candle supplies only the OI gate config; its build gate is not an execution promise. Trade perpetuals on Hyperliquid's main perp exchange from the EVM TEE wallet bound to this key (scope perps:write, opt-in). A key gets an EVM TEE wallet by promoting one from the vault: candle vault new-key --chain evm, then candle vault promote --from <cold> --to-key <prefix>, and candle tee signer new --key <prefix> on the machine that trades it; candle tee new is Solana only. Candle builds each action within the key's limits; this machine recomputes its hash and checks the action type and Candle's builder before anything is signed, then Candle's relay signs and this machine submits to Hyperliquid. Every plan is eligible for perps, once the deployment has perps enabled. Every plan except Max pays Candle a builder fee on each order (0.1% today); Max pays none. candle plans shows the rate in force. Deposits move SOL, USDC, ETH or USDG onto the Hyperliquid account through Relay; withdrawals move USDC back through Relay to the key's own Solana or Hood TEE wallet (scope perps:withdraw, opt-in, after one vault unlock and a typed confirm). candle pnl --profile <name> shows the profile key's Hyperliquid realized P&L, funding and fees, separately from spot and LP totals. Unrealized perps P&L is shown by candle perps positions and candle portfolio. Account-wide candle pnl and its By agent table cover ledger trades only; a key with only perps activity has no row there. account, positions, risk and watch read Hyperliquid's public API directly from this machine and sign nothing: risk and watch exit 0 (ok), 10 (warn) or 11 (critical), and 1 with a PERPS_* code when no level could be read (unknown), so a script never mistakes \"could not tell\" for ok. A position is warn under its key's warn distance from liquidation (default 15%) or when the cross margin ratio is above the warn ratio (default 0.6); critical under 7% or above 0.8; a position with no Hyperliquid mark is at least warn. risk is one stateless read; watch adds hysteresis (a level clears only past its threshold by the clear band, default 2 points). One watcher at the default 10 s interval spends about 136 of the 1,200 request weight per minute Hyperliquid allows per IP. Funding paid since open is Hyperliquid's sign: positive is paid. Until Candle's server-side monitor exists, nothing warns you unless you poll candle perps risk or run candle perps watch. modify, margin, tpsl, schedule-cancel, flatten and close --pct are built, checked against this machine's own read of Hyperliquid, signed and submitted like every other write; tpsl and flatten are plans of several such steps, confirmed once (or --yes), and a plan that does not finish says so and exits 1 (3 when a signed step's submit did not answer). A key that is reduce-only (reduce-only on) can close, cancel, protect, schedule a cancel and add isolated margin, and nothing that opens or grows exposure; only the owner's device token turns it off, and it cancels no resting order and revokes no signed action.",
71214
+ description: "markets, market, book and candles read directly from Hyperliquid with any Candle agent key and no signer or wallet. Candle supplies only the OI gate config; its build gate is not an execution promise. Trade perpetuals on Hyperliquid's main perp exchange from the EVM TEE wallet bound to this key (scope perps:write, opt-in). A key gets an EVM TEE wallet by promoting one from the vault: candle vault new-key --chain evm, then candle vault promote --from <cold> --to-key <prefix>, and candle tee signer new --key <prefix> on the machine that trades it; candle tee new is Solana only. Candle builds each action within the key's limits; this machine recomputes its hash and checks the action type and Candle's builder before anything is signed, then Candle's relay signs and this machine submits to Hyperliquid. Every plan is eligible for perps, once the deployment has perps enabled. Every plan except Max pays Candle a builder fee on each order (0.1% today); Max pays none. candle plans shows the rate in force. Deposits move SOL, USDC, ETH or USDG onto the Hyperliquid account through Relay; withdrawals move USDC back through Relay to the key's own Solana or Hood TEE wallet (scope perps:withdraw, opt-in, after one vault unlock and a typed confirm). candle pnl --profile <name> shows the profile key's Hyperliquid realized P&L, funding and fees, separately from spot and LP totals. Unrealized perps P&L is shown by candle perps positions and candle portfolio. Account-wide candle pnl and its By agent table cover ledger trades only; a key with only perps activity has no row there. account, positions, risk and watch read Hyperliquid's public API directly from this machine and sign nothing: risk and watch exit 0 (ok), 10 (warn) or 11 (critical), and 1 with a PERPS_* code when no level could be read (unknown), so a script never mistakes \"could not tell\" for ok. A position is warn under its key's warn distance from liquidation (default 15%) or when the cross margin ratio is above the warn ratio (default 0.6); critical under 7% or above 0.8; a position with no Hyperliquid mark is at least warn. risk is one stateless read; watch adds hysteresis (a level clears only past its threshold by the clear band, default 2 points). One watcher at the default 10 s interval spends about 136 of the 1,200 request weight per minute Hyperliquid allows per IP. Funding paid since open is Hyperliquid's sign: positive is paid. Until Candle's server-side monitor exists, nothing warns you unless you poll candle perps risk or run candle perps watch. Candle's server may also watch this wallet; GET /perps/risk reports whether it does. modify, margin, tpsl, schedule-cancel, flatten and close --pct are built, checked against this machine's own read of Hyperliquid, signed and submitted like every other write; tpsl and flatten are plans of several such steps, confirmed once (or --yes), and a plan that does not finish says so and exits 1 (3 when a signed step's submit did not answer). A key that is reduce-only (reduce-only on) can close, cancel, protect, schedule a cancel and add isolated margin, and nothing that opens or grows exposure; only the owner's device token turns it off, and it cancels no resting order and revokes no signed action.",
71010
71215
  usage: ["candle perps <subcommand> [flags]"],
71011
71216
  rows: [
71012
71217
  {
@@ -71241,9 +71446,9 @@ var HELP = {
71241
71446
  launch: {
71242
71447
  group: "Trade",
71243
71448
  summary: "Create a token on Solana or Hood, with an optional first buy",
71244
- description: "Creates a token from a TEE wallet. Without --buy it makes no first buy, so the launch and the position are two decisions, and the buy is a separate candle swap. --buy <amount> adds a first buy from the same wallet, in whole units of the quote asset (SOL, USDC or CNDL on Solana, ETH on Hood; not USDG), within the dev-buy ceilings: on Solana it is its own transaction, sent with the launch as one Jito bundle (an atomic launch), so the launch and the first buy land together or not at all and the bundle pays a Jito tip; on Hood it is a last leg after the curve and the fee. On Solana it also needs the swap:write scope and a Candle deployment with a Jito block engine. Only configured raw caps and USD limits apply; unset means unlimited and zero still binds. When a USD limit is set, Candle reserves the buy against the key's spend window before anything is signed. --buy needs a server with TEE_LAUNCH_BUYS_ENABLED on, which is off by default; while it is off Candle refuses the buy and nothing is built. Needs the launch:write scope and allowLaunch on the wallet, which only the account owner can turn on (candle wallets allow-launch <wallet>, over the device token; an agent key cannot). The wallet decides the chain: a Hood TEE wallet launches on Hood, needs --dex-version, and signs one leg at a time (the curve, then the fee, then the buy with --buy), each only after the one before it landed. Launching from a TEE wallet needs Pro or Max (TIER_REQUIRED otherwise; candle plans). Every plan, Free included, can launch from its embedded wallet with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood), through the MCP tool candle_launch_and_seed or the SDK's launch(); this command does not use that path. The image is one of two flags, never both: --image-url <https-url> for one already hosted, or --image <path> for a PNG, JPEG, GIF or WebP file on this machine, at most 10MB. With --image the file is uploaded to Candle first and the launch is made with the URL that comes back, which --json reports as imageUrl. The upload happens before the confirm prompt and cannot be undone: the file becomes public and permanent even if you then decline or the launch fails. Re-running with the same --client-trade-id resumes the launch without uploading again. --image-url takes only an https:// URL. --banner <file|URL> adds a wide banner: an https URL is passed through; a local file uses the same publication notice and 10MB limit, uploaded with kind=banner so it is never cropped. Banners must be wider than 1.5:1. The result reports bannerUrl, and an atomic retry reuses the saved URL.",
71449
+ description: "Creates a token from a TEE wallet. Without --buy it makes no first buy, so the launch and the position are two decisions, and the buy is a separate candle swap. --buy <amount> adds a first buy from the same wallet, in whole units of the quote asset (SOL, USDC or CNDL on Solana, ETH on Hood; not USDG), within the dev-buy ceilings: on Solana it is its own transaction, sent with the launch as one Jito bundle (an atomic launch), so the launch and the first buy land together or not at all and the bundle pays a Jito tip; on Hood it is a last leg after the curve and the fee. On Solana it also needs the swap:write scope and a Candle deployment with a Jito block engine. Only configured raw caps and USD limits apply; unset means unlimited and zero still binds. When a USD limit is set, Candle reserves the buy against the key's spend window before anything is signed. --buy needs a server with TEE_LAUNCH_BUYS_ENABLED on, which is off by default; while it is off Candle refuses the buy and nothing is built. Needs the launch:write scope and allowLaunch on the wallet, which only the account owner can turn on (candle wallets allow-launch <wallet>, over the device token; an agent key cannot). The wallet decides the chain: a Hood TEE wallet launches on Hood, needs --dex-version, and signs one leg at a time (the curve, then the fee, then the buy with --buy), each only after the one before it landed. Launching from a TEE wallet needs Pro or Max (TIER_REQUIRED otherwise; candle plans). Every plan, Free included, can launch from its embedded wallet with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood), through the MCP tool candle_launch_and_seed or the SDK's launch(); this command does not use that path. The image is one of two flags, never both: --image-url <https-url> for one already hosted, or --image <path> for a PNG, JPEG, GIF or WebP file on this machine, at most 10MB. With --image the file is uploaded to Candle first and the launch is made with the URL that comes back, which --json reports as imageUrl. The upload happens before the confirm prompt and cannot be undone: the file becomes public and permanent even if you then decline or the launch fails. Re-running with the same --client-trade-id resumes the launch without uploading again. --image-url takes only an https:// URL. --banner <file|URL> adds a wide banner: an https URL is passed through; a local file uses the same publication notice and 10MB limit, uploaded with kind=banner so it is never cropped. Banners must be wider than 1.5:1. The result reports bannerUrl, and an atomic retry reuses the saved URL. --mode picks the curve: open or exclusive (the default tier for the pair), test-open or test-exclusive where the server has ENABLE_TEST_CURVES on, or on Solana with sol or cndl the experimental curves exp-a, exp-b, exp-c and exp-custom. exp-custom takes your own curve: --curve-v <V> (the virtual quote it starts with) and --curve-g <G> (what it raises before graduating), in whole units of the quote asset, within limits Candle checks; it works only where the server has EXP_CUSTOM_CURVES on. --creator-fee-bps 100 or 200 adds an extra creator fee on an exp- mode, paid to the creator in full. On an exp- curve the first buy is at most 0.5 SOL (2,000,000 CNDL), or 10% of the threshold when that is lower. --dry-run sends the launch's body to Candle's dry run and prints the preview (the curve's terms, the first buy's share, the fees, how the pool opens at graduation and the dev-buy ceiling) without building, signing or recording anything; the id stays unused. With --image it still uploads the file, which is public and permanent.",
71245
71450
  usage: [
71246
- "candle launch --name <name> --symbol <symbol> --image <path>|--image-url <url> --wallet <tee> [--banner <file|URL>] [--buy <amount>] [--quote-asset <asset>] [--dex-version v3|v4]"
71451
+ "candle launch --name <name> --symbol <symbol> --image <path>|--image-url <url> --wallet <tee> [--banner <file|URL>] [--buy <amount>] [--quote-asset <asset>] [--dex-version v3|v4] [--mode <mode>] [--curve-v <V> --curve-g <G>] [--creator-fee-bps 0|100|200] [--pool-fee <25|50|100>] [--pool-open <shape>] [--exp-pool-shape <shape>] [--dry-run]"
71247
71452
  ],
71248
71453
  rows: [],
71249
71454
  flags: [
@@ -71267,13 +71472,46 @@ var HELP = {
71267
71472
  {
71268
71473
  invocation: "--dex-version v3|v4",
71269
71474
  description: "Hood only, required there: the Uniswap version the curve graduates to"
71475
+ },
71476
+ {
71477
+ invocation: "--pool-fee 25|50|100",
71478
+ description: "Solana only: the graduated pool's fee in bps (default 50), or with --pool-fee-from the rate a falling fee settles at"
71479
+ },
71480
+ {
71481
+ invocation: "--pool-fee-from <bps> --pool-fee-minutes <min>",
71482
+ description: "Solana only, with --pool-fee: an anti-snipe fee that starts at <bps> (at most 2500) and falls to the --pool-fee rate over <min> minutes (at most 30)"
71483
+ },
71484
+ {
71485
+ invocation: "--pool-open legacy|curve_price",
71486
+ description: "Solana preset tiers: curve_price opens the pool at the curve's last price and burns the rest; the staker carve plus team allocation must fit the tier's limit (the dry run returns it)"
71487
+ },
71488
+ {
71489
+ invocation: "--exp-pool-shape floor|full_range",
71490
+ description: "Solana exp- curves: full_range opens the pool across the whole price range"
71491
+ },
71492
+ {
71493
+ invocation: "--mode <mode>",
71494
+ description: "open or exclusive; test-open or test-exclusive (ENABLE_TEST_CURVES); exp-a, exp-b, exp-c or exp-custom (Solana, sol or cndl)"
71495
+ },
71496
+ {
71497
+ invocation: "--curve-v <V> --curve-g <G>",
71498
+ description: "exp-custom only, and required there: the curve's virtual quote and threshold, in whole units of the quote asset"
71499
+ },
71500
+ {
71501
+ invocation: "--creator-fee-bps 0|100|200",
71502
+ description: "An exp- mode only: an extra creator fee on the curve and the pool, paid to the creator in full"
71503
+ },
71504
+ {
71505
+ invocation: "--dry-run",
71506
+ description: "Print Candle's preview of this launch, with the dev-buy ceiling (0.5 SOL, or 10% of the threshold when lower, on an exp- curve), and build, sign and record nothing"
71270
71507
  }
71271
71508
  ],
71272
71509
  examples: [
71273
71510
  "candle launch --name Demo --symbol DEMO --image ./logo.png --wallet AgentOne",
71274
71511
  "candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne",
71275
71512
  "candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet HoodAgent --dex-version v4",
71276
- "candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne --buy 0.1"
71513
+ "candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne --buy 0.1",
71514
+ "candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne --mode exp-custom --curve-v 4 --curve-g 20 --creator-fee-bps 100 --dry-run"
71277
71515
  ],
71278
71516
  env: ENV_API
71279
71517
  },
@@ -71953,36 +72191,15 @@ function documentedFlags(topic) {
71953
72191
  return [...flags].sort();
71954
72192
  }
71955
72193
  var WIDTH = 118;
71956
- function wrap(text, width) {
71957
- if (text === "")
71958
- return [""];
71959
- if (width < 20)
71960
- return [text];
71961
- const lines = [];
71962
- let line = "";
71963
- for (const word3 of text.split(" ")) {
71964
- if (line === "")
71965
- line = word3;
71966
- else if (`${line} ${word3}`.length <= width)
71967
- line = `${line} ${word3}`;
71968
- else {
71969
- lines.push(line);
71970
- line = word3;
71971
- }
71972
- }
71973
- if (line !== "")
71974
- lines.push(line);
71975
- return lines;
71976
- }
71977
- function renderRows(rows, maxColumn) {
72194
+ function renderRows(rows, maxColumn, width = WIDTH) {
71978
72195
  if (rows.length === 0)
71979
72196
  return [];
71980
72197
  const longest = Math.max(...rows.map((row) => row.invocation.length));
71981
- const column = Math.min(2 + longest + 2, maxColumn);
72198
+ const column = Math.min(2 + longest + 2, maxColumn, Math.max(4, width - 24));
71982
72199
  const pad = " ".repeat(column);
71983
72200
  const out = [];
71984
72201
  for (const row of rows) {
71985
- const [first = "", ...rest] = wrap(row.description, WIDTH - column);
72202
+ const [first = "", ...rest] = wrapText(row.description, width - column);
71986
72203
  if (2 + row.invocation.length + 2 <= column)
71987
72204
  out.push(` ${row.invocation.padEnd(column - 2)}${first}`);
71988
72205
  else
@@ -71992,11 +72209,13 @@ function renderRows(rows, maxColumn) {
71992
72209
  }
71993
72210
  return out;
71994
72211
  }
71995
- function renderEnv(names) {
72212
+ function renderEnv(names, width = WIDTH) {
71996
72213
  const vars = names.map((name) => ENVIRONMENT.find((entry) => entry.name === name)).filter((entry) => entry !== undefined);
71997
- return renderRows(vars.map((entry) => ({ invocation: entry.name, description: entry.description })), 32);
72214
+ return renderRows(vars.map((entry) => ({ invocation: entry.name, description: entry.description })), 32, width);
71998
72215
  }
71999
- function renderTopLevel() {
72216
+ function renderTopLevel(style) {
72217
+ if (style)
72218
+ return renderPresentedTopLevel(style);
72000
72219
  const out = [
72001
72220
  "candle: Candle from the terminal: trade, launch, and hold your keys on your own machine",
72002
72221
  "",
@@ -72021,24 +72240,67 @@ function renderTopLevel() {
72021
72240
  `)}
72022
72241
  `;
72023
72242
  }
72024
- function renderTopic(word3) {
72243
+ function renderTopic(word3, style) {
72025
72244
  const topic = topicFor(word3);
72026
72245
  if (!topic)
72027
72246
  return;
72028
72247
  const canonical = Object.keys(HELP).find((key) => HELP[key] === topic) ?? word3;
72029
72248
  const display = displayName(canonical);
72030
- const out = [wrap(`candle ${display}: ${topic.description}`, WIDTH).join(`
72249
+ const width = style?.width ?? WIDTH;
72250
+ const out = [wrapText(`candle ${display}: ${topic.description}`, width).join(`
72031
72251
  `)];
72032
72252
  out.push("", `Usage: ${topic.usage.join(`
72033
72253
  `)}`);
72034
72254
  if (topic.rows.length > 0)
72035
- out.push("", ...renderRows(topic.rows, 58));
72255
+ out.push("", ...renderRows(topic.rows, 58, width));
72036
72256
  if (topic.flags && topic.flags.length > 0) {
72037
- out.push("", `Flags every ${display} subcommand takes`, ...renderRows(topic.flags, 28));
72257
+ out.push("", `Flags every ${display} subcommand takes`, ...renderRows(topic.flags, 28, width));
72038
72258
  }
72039
72259
  out.push("", "Examples", ...topic.examples.map((example) => ` ${example}`));
72040
72260
  if (topic.env && topic.env.length > 0)
72041
- out.push("", "Environment", ...renderEnv(topic.env));
72261
+ out.push("", "Environment", ...renderEnv(topic.env, width));
72262
+ return `${out.join(`
72263
+ `)}
72264
+ `;
72265
+ }
72266
+ function renderPresentedTopLevel(style) {
72267
+ const { width } = style;
72268
+ const heading = (text) => accent(text, style);
72269
+ const out = [
72270
+ ...brandHeader(style),
72271
+ ...!style.styled ? [...wrapText("Candle CLI: Your wallets and agent tools, one command away.", width), ""] : [],
72272
+ heading("Start here"),
72273
+ ...renderRows([
72274
+ { invocation: "candle setup", description: "Connect your account" },
72275
+ { invocation: "candle portfolio", description: "Read your holdings after setup" },
72276
+ { invocation: "candle mcp", description: "Connect an MCP client using this install" }
72277
+ ], 24, width),
72278
+ "",
72279
+ ...wrapText("Usage: candle <command> [<subcommand>] [flags]", width),
72280
+ " candle help <command>"
72281
+ ];
72282
+ const group = (name, available) => [
72283
+ heading(name),
72284
+ ...renderRows(Object.entries(HELP).filter(([, topic]) => topic.group === name).map(([word3, topic]) => ({ invocation: displayName(word3), description: topic.summary })), 18, available)
72285
+ ];
72286
+ for (let i = 0;i < GROUPS.length; i++) {
72287
+ const name = GROUPS[i];
72288
+ if (width >= 100 && (name === "Trade" || name === "Custody, on this machine")) {
72289
+ const column = Math.floor((width - 4) / 2);
72290
+ const left = group(name, column);
72291
+ const right = group(GROUPS[++i], column);
72292
+ out.push("");
72293
+ for (let row = 0;row < Math.max(left.length, right.length); row++) {
72294
+ const first = left[row] ?? "";
72295
+ const visible = first.replace(/\x1b\[[0-9;]*m/g, "").length;
72296
+ out.push(`${first}${" ".repeat(Math.max(4, column - visible + 4))}${right[row] ?? ""}`.trimEnd());
72297
+ }
72298
+ } else
72299
+ out.push("", ...group(name, width));
72300
+ }
72301
+ out.push("", heading("Global flags"), ...renderRows(GLOBAL_FLAGS, 28, width));
72302
+ out.push("", heading("Environment"), ...renderEnv(ENVIRONMENT.map((entry) => entry.name), width));
72303
+ out.push("", heading("Plug-ins"), ...renderRows([PLUGIN_LINE], 26, width));
72042
72304
  return `${out.join(`
72043
72305
  `)}
72044
72306
  `;
@@ -76876,9 +77138,9 @@ async function planTransfer(input) {
76876
77138
  }
76877
77139
  const feeLines = [];
76878
77140
  if (profile.newerTransferFee || profile.olderTransferFee) {
76879
- const epoch = await rpc.getEpoch();
76880
- const { feeRaw, postFeeAmountRaw } = transferFeeFor(profile, raw, epoch);
76881
- feeLines.push(`post-fee ${postFeeAmountRaw} raw arrives (${feeRaw} raw withheld by the mint at epoch ${epoch})`);
77141
+ const epoch2 = await rpc.getEpoch();
77142
+ const { feeRaw, postFeeAmountRaw } = transferFeeFor(profile, raw, epoch2);
77143
+ feeLines.push(`post-fee ${postFeeAmountRaw} raw arrives (${feeRaw} raw withheld by the mint at epoch ${epoch2})`);
76882
77144
  }
76883
77145
  instructions.push(tokenTransferChecked({
76884
77146
  source,
@@ -83029,7 +83291,7 @@ async function sweepEverythingTo(input) {
83029
83291
  });
83030
83292
  }
83031
83293
  }
83032
- let epoch;
83294
+ let epoch2;
83033
83295
  for (const acct of tokenAccounts) {
83034
83296
  const token2022 = acct.programId === TOKEN_2022_PROGRAM_ID;
83035
83297
  if (acct.state !== "initialized") {
@@ -83072,9 +83334,9 @@ async function sweepEverythingTo(input) {
83072
83334
  for (const risk of profile.risks)
83073
83335
  say(` ${acct.mint}: ${risk.message}`);
83074
83336
  if (profile.newerTransferFee || profile.olderTransferFee) {
83075
- epoch ??= await rpc.getEpoch();
83076
- const { feeRaw, postFeeAmountRaw } = transferFeeFor(profile, amount, epoch);
83077
- say(` ${acct.mint}: ${postFeeAmountRaw} raw will arrive; ${feeRaw} raw is withheld by the mint at epoch ${epoch}`);
83337
+ epoch2 ??= await rpc.getEpoch();
83338
+ const { feeRaw, postFeeAmountRaw } = transferFeeFor(profile, amount, epoch2);
83339
+ say(` ${acct.mint}: ${postFeeAmountRaw} raw will arrive; ${feeRaw} raw is withheld by the mint at epoch ${epoch2}`);
83078
83340
  }
83079
83341
  }
83080
83342
  const existing = await rpc.getAccountInfo(encodePubkey(destinationAta));
@@ -83844,10 +84106,10 @@ async function feesClaim(args, ctx) {
83844
84106
  async function help(args, ctx) {
83845
84107
  const word3 = args[0];
83846
84108
  if (word3 === undefined) {
83847
- ctx.deps.stdout.write(renderTopLevel());
84109
+ ctx.deps.stdout.write(renderTopLevel(terminalStyle(ctx.deps, "stdout", ctx.json)));
83848
84110
  return 0;
83849
84111
  }
83850
- const topic = renderTopic(word3);
84112
+ const topic = renderTopic(word3, terminalStyle(ctx.deps, "stdout", ctx.json));
83851
84113
  if (topic === undefined) {
83852
84114
  ctx.deps.stderr.write(`Unknown command: ${word3}
83853
84115
  `);
@@ -84496,8 +84758,51 @@ init_render();
84496
84758
  init_solana_endpoint();
84497
84759
  init_solana_lite();
84498
84760
  init_trading();
84761
+
84762
+ // src/commands/pool-options.ts
84763
+ var POOL_FLAGS = ["--pool-fee", "--pool-fee-from", "--pool-fee-minutes", "--pool-open", "--exp-pool-shape"];
84764
+ var TIERS = ["25", "50", "100"];
84765
+ function poolRequestFields(flags) {
84766
+ const out = {};
84767
+ const fee = flags["--pool-fee"];
84768
+ const from = flags["--pool-fee-from"];
84769
+ const minutes = flags["--pool-fee-minutes"];
84770
+ if ((from !== undefined || minutes !== undefined) && fee === undefined)
84771
+ throw new Error("--pool-fee-from and --pool-fee-minutes need --pool-fee <25|50|100>, the rate the fall ends at");
84772
+ if (fee !== undefined) {
84773
+ if (!TIERS.includes(fee))
84774
+ throw new Error("--pool-fee takes 25, 50 or 100 (bps)");
84775
+ if (from === undefined !== (minutes === undefined))
84776
+ throw new Error("A falling pool fee needs both --pool-fee-from <bps> and --pool-fee-minutes <minutes>");
84777
+ if (from === undefined)
84778
+ out.poolFee = { tier: Number(fee) };
84779
+ else {
84780
+ if (!/^\d+$/.test(from))
84781
+ throw new Error("--pool-fee-from takes whole bps");
84782
+ const m = Number(minutes);
84783
+ if (!Number.isFinite(m) || m <= 0)
84784
+ throw new Error("--pool-fee-minutes takes a positive number of minutes");
84785
+ out.poolFee = { startBps: Number(from), endBps: Number(fee), minutes: m };
84786
+ }
84787
+ }
84788
+ const open8 = flags["--pool-open"];
84789
+ if (open8 !== undefined) {
84790
+ if (open8 !== "legacy" && open8 !== "curve_price")
84791
+ throw new Error("--pool-open takes legacy or curve_price");
84792
+ out.poolOpen = open8;
84793
+ }
84794
+ const shape = flags["--exp-pool-shape"];
84795
+ if (shape !== undefined) {
84796
+ if (shape !== "floor" && shape !== "full_range")
84797
+ throw new Error("--exp-pool-shape takes floor or full_range");
84798
+ out.expPoolShape = shape;
84799
+ }
84800
+ return out;
84801
+ }
84802
+
84803
+ // src/commands/launch.ts
84499
84804
  init_swap();
84500
- var USAGE6 = "Usage: candle launch --name <name> --symbol <symbol> --image <path>|--image-url <https-url> --wallet <tee> [--banner <file|URL>] [--buy <amount>] [--client-trade-id <id>] [--quote-asset sol|usdc|cndl|eth|usdg] [--dex-version v3|v4] [--mode <mode>] [--rpc-url <url>] [--yes]. A Hood TEE wallet launches on Hood and needs --dex-version.";
84805
+ var USAGE6 = "Usage: candle launch --name <name> --symbol <symbol> --image <path>|--image-url <https-url> --wallet <tee> [--banner <file|URL>] [--buy <amount>] [--client-trade-id <id>] [--quote-asset sol|usdc|cndl|eth|usdg] [--dex-version v3|v4] [--mode <mode>] [--curve-v <V> --curve-g <G>] [--creator-fee-bps 0|100|200] [--pool-fee <25|50|100> [--pool-fee-from <bps> --pool-fee-minutes <min>]] [--pool-open legacy|curve_price] [--exp-pool-shape floor|full_range] [--dry-run] [--rpc-url <url>] [--yes]. A Hood TEE wallet launches on Hood and needs --dex-version.";
84501
84806
  var QUOTE_CHAIN = {
84502
84807
  sol: "solana",
84503
84808
  usdc: "solana",
@@ -84530,6 +84835,36 @@ function buyRefusal(error) {
84530
84835
  details: error.details
84531
84836
  });
84532
84837
  }
84838
+ function curveFlagProblem(flags) {
84839
+ const mode = flags["--mode"];
84840
+ const v = flags["--curve-v"];
84841
+ const g = flags["--curve-g"];
84842
+ if (v === undefined !== (g === undefined))
84843
+ return "--curve-v and --curve-g go together: a curve is its V and its G.";
84844
+ if (v !== undefined && mode !== "exp-custom")
84845
+ return "--curve-v and --curve-g take --mode exp-custom.";
84846
+ if (mode === "exp-custom" && v === undefined)
84847
+ return "--mode exp-custom needs --curve-v and --curve-g: the curve's V and G in whole units of the quote asset.";
84848
+ for (const [flag, value] of [
84849
+ ["--curve-v", v],
84850
+ ["--curve-g", g]
84851
+ ]) {
84852
+ if (value !== undefined && !/^\d+(\.\d+)?$/.test(value))
84853
+ return `${flag} takes a whole-unit decimal amount of the quote asset, such as 4 or 0.5.`;
84854
+ }
84855
+ const fee = flags["--creator-fee-bps"];
84856
+ if (fee !== undefined && fee !== "0" && fee !== "100" && fee !== "200")
84857
+ return "--creator-fee-bps takes 0, 100 or 200.";
84858
+ if (fee !== undefined && !mode?.startsWith("exp-"))
84859
+ return "--creator-fee-bps takes an exp- --mode.";
84860
+ return;
84861
+ }
84862
+ function launchCurveFields(flags) {
84863
+ return {
84864
+ ...flags["--curve-v"] !== undefined && flags["--curve-g"] !== undefined ? { curve: { virtualQuote: flags["--curve-v"], threshold: flags["--curve-g"] } } : {},
84865
+ ...flags["--creator-fee-bps"] !== undefined ? { creatorFeeBps: Number(flags["--creator-fee-bps"]) } : {}
84866
+ };
84867
+ }
84533
84868
  function buyNotice(buy, chain2) {
84534
84869
  if (!buy)
84535
84870
  return `Launch creates the token only. Make the first buy with a separate candle swap. Price impact does not apply to creation.
@@ -84570,9 +84905,13 @@ async function launch(args, ctx) {
84570
84905
  "--quote-asset",
84571
84906
  "--mode",
84572
84907
  "--dex-version",
84573
- "--buy"
84908
+ "--buy",
84909
+ ...POOL_FLAGS,
84910
+ "--curve-v",
84911
+ "--curve-g",
84912
+ "--creator-fee-bps"
84574
84913
  ],
84575
- booleanFlags: ["--yes"],
84914
+ booleanFlags: ["--yes", "--dry-run"],
84576
84915
  pathFlags: ["--image", "--banner"]
84577
84916
  });
84578
84917
  if ("error" in parsed) {
@@ -84581,6 +84920,12 @@ async function launch(args, ctx) {
84581
84920
  }
84582
84921
  const flags = parsed.values;
84583
84922
  const id = flags["--client-trade-id"] ?? `launch-${randomUUID5()}`;
84923
+ const dryRun = parsed.booleans.has("--dry-run");
84924
+ const curveProblem = curveFlagProblem(flags);
84925
+ if (curveProblem) {
84926
+ writeUsageFailure(ctx.deps, `${curveProblem} ${USAGE6}`, ctx.json);
84927
+ return 2;
84928
+ }
84584
84929
  if (flags["--image"] !== undefined && flags["--image-url"] !== undefined) {
84585
84930
  writeUsageFailure(ctx.deps, `Pass --image <path> or --image-url <https-url>, not both: a launch has one image. ${USAGE6}`, ctx.json);
84586
84931
  return 2;
@@ -84597,6 +84942,12 @@ async function launch(args, ctx) {
84597
84942
  writeUsageFailure(ctx.deps, USAGE6, ctx.json);
84598
84943
  return 2;
84599
84944
  }
84945
+ try {
84946
+ poolRequestFields(flags);
84947
+ } catch (error) {
84948
+ writeUsageFailure(ctx.deps, `${error.message}. ${USAGE6}`, ctx.json);
84949
+ return 2;
84950
+ }
84600
84951
  try {
84601
84952
  if (flags["--buy"] !== undefined)
84602
84953
  rawAmount(flags["--buy"], 18);
@@ -84616,7 +84967,7 @@ async function launch(args, ctx) {
84616
84967
  return tradingFailure(ctx, error);
84617
84968
  }
84618
84969
  const key = await tradingKey(ctx);
84619
- const prior = await lookupOperation(ctx, key, id, "launch");
84970
+ const prior = dryRun ? null : await lookupOperation(ctx, key, id, "launch");
84620
84971
  let retry;
84621
84972
  if (prior) {
84622
84973
  const local = await savedOperation(ctx, key, id);
@@ -84648,7 +84999,7 @@ async function launch(args, ctx) {
84648
84999
  const atomic = chain2 === "solana" && buy !== undefined;
84649
85000
  if (atomic)
84650
85001
  atomicPreflight(ctx, flags, wallet3, buy);
84651
- const solana = chain2 === "solana" && !atomic ? await tradingSolanaClient(ctx, flags["--rpc-url"]) : undefined;
85002
+ const solana = chain2 === "solana" && !atomic && !dryRun ? await tradingSolanaClient(ctx, flags["--rpc-url"]) : undefined;
84652
85003
  let imageUrl = flags["--image-url"] ?? "";
84653
85004
  if (image && retry?.imageUrl)
84654
85005
  imageUrl = retry.imageUrl;
@@ -84681,6 +85032,8 @@ async function launch(args, ctx) {
84681
85032
  ...image ? { imageUrl: safeText(imageUrl) } : {},
84682
85033
  ...bannerUrl ? { bannerUrl: safeText(bannerUrl) } : {}
84683
85034
  };
85035
+ if (dryRun)
85036
+ return await dryRunLaunch(ctx, key, { flags, id, wallet: wallet3, chain: chain2, atomic, buy, imageUrl, bannerUrl, used });
84684
85037
  if (atomic)
84685
85038
  return await atomicLaunch(ctx, key, {
84686
85039
  flags,
@@ -84708,19 +85061,7 @@ async function launch(args, ctx) {
84708
85061
  throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this launch id; no write was resent.");
84709
85062
  ctx.deps.stderr.write(`Operation: ${id}
84710
85063
  `);
84711
- const built = launchBuildSchema.parse(await request(ctx, key, "/api/v1/launch/self/build", {
84712
- clientLaunchId: id,
84713
- chain: "solana",
84714
- buyAmount: buy ? buy.raw : 0,
84715
- name: flags["--name"],
84716
- symbol: flags["--symbol"],
84717
- imageUrl,
84718
- ...bannerUrl ? { bannerUrl } : {},
84719
- linkedWalletId: wallet3.id,
84720
- ...flags["--description"] ? { description: flags["--description"] } : {},
84721
- ...flags["--quote-asset"] ? { quoteAsset: flags["--quote-asset"] } : {},
84722
- ...flags["--mode"] ? { mode: flags["--mode"] } : {}
84723
- }).catch((error) => {
85064
+ const built = launchBuildSchema.parse(await request(ctx, key, "/api/v1/launch/self/build", solanaSelfBody({ flags, id, wallet: wallet3, buy, imageUrl, bannerUrl })).catch((error) => {
84724
85065
  throw buyRefusal(error);
84725
85066
  }));
84726
85067
  if (!built.transaction || !/^\d+$/.test(built.maxDebitLamports ?? ""))
@@ -84764,9 +85105,109 @@ async function launch(args, ctx) {
84764
85105
  const result = await request(ctx, key, "/api/v1/launch/self/confirm", { clientLaunchId: id, signature });
84765
85106
  return printTradingResult(ctx, { ...result, clientTradeId: id, kind: "launch", quote, ...used });
84766
85107
  } catch (error) {
84767
- return tradingFailure(ctx, error, id);
85108
+ return tradingFailure(ctx, error, dryRun ? undefined : id);
84768
85109
  }
84769
85110
  }
85111
+ function launchFields(args) {
85112
+ const { flags } = args;
85113
+ return {
85114
+ clientLaunchId: args.id,
85115
+ name: flags["--name"],
85116
+ symbol: flags["--symbol"],
85117
+ imageUrl: args.imageUrl,
85118
+ ...args.bannerUrl ? { bannerUrl: args.bannerUrl } : {},
85119
+ ...flags["--description"] ? { description: flags["--description"] } : {},
85120
+ ...flags["--mode"] ? { mode: flags["--mode"] } : {},
85121
+ ...launchCurveFields(flags),
85122
+ ...poolRequestFields(flags)
85123
+ };
85124
+ }
85125
+ function solanaSelfBody(args) {
85126
+ const quoteAsset = args.flags["--quote-asset"];
85127
+ return {
85128
+ ...launchFields(args),
85129
+ chain: "solana",
85130
+ buyAmount: args.buy ? args.buy.raw : 0,
85131
+ linkedWalletId: args.wallet.id,
85132
+ ...quoteAsset ? { quoteAsset } : {}
85133
+ };
85134
+ }
85135
+ function atomicBody(args) {
85136
+ const payer = { type: "linked", linkedWalletId: args.wallet.id };
85137
+ const quoteAsset = args.flags["--quote-asset"];
85138
+ return {
85139
+ ...launchFields(args),
85140
+ chain: "solana",
85141
+ ...quoteAsset ? { quoteAsset } : {},
85142
+ payer,
85143
+ firstBuys: [{ payer, amountRaw: args.buy.raw }]
85144
+ };
85145
+ }
85146
+ function hoodBody(args) {
85147
+ const quoteAsset = args.flags["--quote-asset"];
85148
+ return {
85149
+ ...launchFields(args),
85150
+ chain: "hood",
85151
+ buyAmount: args.buy ? args.buy.raw : 0,
85152
+ linkedWalletId: args.wallet.id,
85153
+ dexVersion: args.flags["--dex-version"],
85154
+ ...quoteAsset ? { quoteAsset: quoteAsset.toLowerCase() } : {}
85155
+ };
85156
+ }
85157
+ var previewSchema = exports_external.object({
85158
+ resolved: exports_external.object({ mode: exports_external.string(), quoteAsset: exports_external.string() }).passthrough().optional(),
85159
+ curveTerms: exports_external.object({
85160
+ symbol: exports_external.string(),
85161
+ raise: exports_external.number(),
85162
+ startFdv: exports_external.number(),
85163
+ bondingFdv: exports_external.number(),
85164
+ supplySoldPct: exports_external.number(),
85165
+ firstBuy: exports_external.object({ basis: exports_external.string(), amountRaw: exports_external.string(), supplyPct: exports_external.number() }).passthrough().optional(),
85166
+ fees: exports_external.object({ curve: exports_external.object({ feeBps: exports_external.number() }), pool: exports_external.object({ feeBps: exports_external.number() }) }).passthrough().optional(),
85167
+ migration: exports_external.object({ venue: exports_external.string(), poolShape: exports_external.string(), openingPriceStepPct: exports_external.number() }).passthrough().optional()
85168
+ }).passthrough().optional(),
85169
+ devBuyMax: exports_external.string().nullable().optional(),
85170
+ fills: exports_external.array(exports_external.object({ index: exports_external.number(), amountRaw: exports_external.string(), supplyPct: exports_external.number() }).passthrough()).optional()
85171
+ }).passthrough();
85172
+ var percent = (value) => `${Number(value.toFixed(4))}%`;
85173
+ function previewLines(answer2, chain2) {
85174
+ const parsed = previewSchema.safeParse(answer2);
85175
+ if (!parsed.success)
85176
+ return "";
85177
+ const { resolved, curveTerms: terms, devBuyMax, fills } = parsed.data;
85178
+ const quote = (resolved?.quoteAsset ?? (chain2 === "hood" ? "eth" : "sol")).toUpperCase();
85179
+ const decimals = (chain2 === "hood" ? HOOD_BASES[quote] : BASES[quote])?.decimals;
85180
+ const whole = (raw) => decimals === undefined ? `${raw} raw` : `${shortAmount(raw, decimals)} ${quote}`;
85181
+ const lines = [];
85182
+ if (terms) {
85183
+ lines.push(`Curve${resolved ? ` (${safeText(resolved.mode)})` : ""}: raises ${terms.raise} ${safeText(terms.symbol)}; market cap ${terms.startFdv} at start and ${terms.bondingFdv} at graduation; ${percent(terms.supplySoldPct)} of the supply sold by graduation.`);
85184
+ if (terms.firstBuy)
85185
+ lines.push(`First buy (${terms.firstBuy.basis === "buyAmount" ? "your --buy" : "a reference buy"}): ${whole(terms.firstBuy.amountRaw)} takes ${percent(terms.firstBuy.supplyPct)} of the supply after the curve fee.`);
85186
+ if (terms.fees)
85187
+ lines.push(`Fees: ${terms.fees.curve.feeBps / 100}% on the curve, ${terms.fees.pool.feeBps / 100}% in the pool after graduation.`);
85188
+ if (terms.migration)
85189
+ lines.push(`Graduation: a ${safeText(terms.migration.poolShape)} pool on ${safeText(terms.migration.venue)}, opening ${percent(terms.migration.openingPriceStepPct)} against the curve's last price, its liquidity locked for good.`);
85190
+ }
85191
+ for (const fill of fills ?? [])
85192
+ lines.push(`First buy ${fill.index}: ${whole(fill.amountRaw)} takes ${percent(fill.supplyPct)} of the supply.`);
85193
+ if (devBuyMax)
85194
+ lines.push(`Dev-buy ceiling: ${whole(devBuyMax)}.`);
85195
+ return lines.length ? `${lines.join(`
85196
+ `)}
85197
+ ` : "";
85198
+ }
85199
+ async function dryRunLaunch(ctx, key, args) {
85200
+ const { chain: chain2, buy } = args;
85201
+ const atomic = args.atomic && buy !== undefined;
85202
+ const path = atomic ? "/api/v1/launch/atomic/dry-run" : "/api/v1/launch/self/dry-run";
85203
+ const body = atomic ? atomicBody({ ...args, buy }) : chain2 === "hood" ? hoodBody(args) : solanaSelfBody(args);
85204
+ const answer2 = await request(ctx, key, path, body).catch((error) => {
85205
+ throw atomic ? atomicRefusal(error) : buyRefusal(error);
85206
+ });
85207
+ ctx.deps.stderr.write(`Dry run: nothing was built, signed or recorded, and launch id ${args.id} is unused.
85208
+ ${previewLines(answer2, chain2)}`);
85209
+ return printTradingResult(ctx, { ...answer2, clientTradeId: args.id, kind: "launch", ...args.used });
85210
+ }
84770
85211
  var DEV_BUY_CEILINGS = {
84771
85212
  sol: { env: "DEV_BUY_MAX_LAMPORTS", fallback: 500000000n, unit: "lamports" },
84772
85213
  usdc: { env: "DEV_BUY_MAX_USDC_RAW", fallback: 500000000n, unit: "USDC raw units" },
@@ -84780,9 +85221,34 @@ function devBuyCeiling(env, quoteAsset) {
84780
85221
  const value = raw && /^\d+$/.test(raw) ? BigInt(raw) : 0n;
84781
85222
  return { ceiling: value > 0n ? value : bounds.fallback, unit: bounds.unit };
84782
85223
  }
85224
+ function shortAmount(raw, decimals) {
85225
+ const full = decimalAmount(raw, decimals);
85226
+ return full.includes(".") ? full.replace(/0+$/, "").replace(/\.$/, "") : full;
85227
+ }
85228
+ var EXP_DEV_BUY_MAX_THRESHOLD_BPS = 1000n;
85229
+ var EXP_PRESET_THRESHOLD_RAW = { sol: 5000000000n, cndl: 330000000000n };
85230
+ function expThresholdRaw(flags, quoteAsset) {
85231
+ const mode = flags["--mode"];
85232
+ if (mode === "exp-custom") {
85233
+ const g = flags["--curve-g"];
85234
+ const decimals = BASES[quoteAsset.toUpperCase()]?.decimals;
85235
+ return g === undefined || decimals === undefined ? undefined : BigInt(rawAmount(g, decimals));
85236
+ }
85237
+ return mode === "exp-a" || mode === "exp-b" || mode === "exp-c" ? EXP_PRESET_THRESHOLD_RAW[quoteAsset] : undefined;
85238
+ }
84783
85239
  function atomicPreflight(ctx, flags, wallet3, buy) {
84784
85240
  const quoteAsset = (flags["--quote-asset"] ?? "sol").toLowerCase();
84785
85241
  const { ceiling, unit } = devBuyCeiling(ctx.deps.env, quoteAsset);
85242
+ const threshold = expThresholdRaw(flags, quoteAsset);
85243
+ if (threshold !== undefined) {
85244
+ const curveCap = threshold * EXP_DEV_BUY_MAX_THRESHOLD_BPS / 10000n;
85245
+ const applies = curveCap < ceiling ? curveCap : ceiling;
85246
+ if (BigInt(buy.raw) > applies) {
85247
+ const symbol = quoteAsset.toUpperCase();
85248
+ const decimals = BASES[symbol]?.decimals ?? 9;
85249
+ throw new TradingError("DEV_BUY_TOO_HIGH", `buyAmount exceeds this launch's dev-buy ceiling of ${shortAmount(applies.toString(), decimals)} ${symbol} (${applies} ${unit}): at most ${shortAmount(ceiling.toString(), decimals)} ${symbol}, or 10% of the threshold when that is lower`, { details: { field: "buyAmount" } });
85250
+ }
85251
+ }
84786
85252
  if (BigInt(buy.raw) > ceiling)
84787
85253
  throw new TradingError("DEV_BUY_TOO_HIGH", `buyAmount exceeds the per-launch dev-buy ceiling of ${ceiling} ${unit} for ${quoteAsset} on solana`, { details: { field: "buyAmount" } });
84788
85254
  if (wallet3.scopes !== undefined && !wallet3.scopes.includes("swap:write"))
@@ -84827,20 +85293,7 @@ async function atomicLaunch(ctx, key, args) {
84827
85293
  throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this launch id; no write was resent.");
84828
85294
  ctx.deps.stderr.write(`Operation: ${id}
84829
85295
  `);
84830
- const payer = { type: "linked", linkedWalletId: wallet3.id };
84831
- const built = atomicBuildSchema.parse(await request(ctx, key, "/api/v1/launch/atomic/build", {
84832
- clientLaunchId: id,
84833
- chain: "solana",
84834
- name: flags["--name"],
84835
- symbol: flags["--symbol"],
84836
- imageUrl: args.imageUrl,
84837
- ...args.bannerUrl ? { bannerUrl: args.bannerUrl } : {},
84838
- ...flags["--description"] ? { description: flags["--description"] } : {},
84839
- ...flags["--quote-asset"] ? { quoteAsset: flags["--quote-asset"] } : {},
84840
- ...flags["--mode"] ? { mode: flags["--mode"] } : {},
84841
- payer,
84842
- firstBuys: [{ payer, amountRaw: buy.raw }]
84843
- }).catch((error) => {
85296
+ const built = atomicBuildSchema.parse(await request(ctx, key, "/api/v1/launch/atomic/build", atomicBody(args)).catch((error) => {
84844
85297
  throw atomicRefusal(error);
84845
85298
  }));
84846
85299
  const [launchLeg, buyLeg] = built.legs;
@@ -84957,25 +85410,14 @@ function resumeAtomicLaunch(ctx, id, local, prior) {
84957
85410
  }
84958
85411
  async function hoodLaunch(ctx, key, args) {
84959
85412
  const { flags, id, wallet: wallet3, buy } = args;
85413
+ if (Object.keys(poolRequestFields(flags)).length)
85414
+ throw new TradingUsage("Pool fee and pool shape choices are Solana only; a Hood launch cannot take them.");
84960
85415
  const allowedLegs = buy ? HOOD_LAUNCH_BUY_LEGS : HOOD_LAUNCH_LEGS;
84961
85416
  if (!await claimOperation(ctx, key, id, "launch"))
84962
85417
  throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this launch id; no write was resent.");
84963
85418
  ctx.deps.stderr.write(`Operation: ${id}
84964
85419
  `);
84965
- const built = await request(ctx, key, "/api/v1/launch/self/build", {
84966
- clientLaunchId: id,
84967
- chain: "hood",
84968
- buyAmount: buy ? buy.raw : 0,
84969
- name: flags["--name"],
84970
- symbol: flags["--symbol"],
84971
- imageUrl: args.imageUrl,
84972
- ...args.bannerUrl ? { bannerUrl: args.bannerUrl } : {},
84973
- linkedWalletId: wallet3.id,
84974
- dexVersion: flags["--dex-version"],
84975
- ...flags["--description"] ? { description: flags["--description"] } : {},
84976
- ...flags["--quote-asset"] ? { quoteAsset: flags["--quote-asset"].toLowerCase() } : {},
84977
- ...flags["--mode"] ? { mode: flags["--mode"] } : {}
84978
- }).catch((error) => {
85420
+ const built = await request(ctx, key, "/api/v1/launch/self/build", hoodBody(args)).catch((error) => {
84979
85421
  throw buyRefusal(error);
84980
85422
  });
84981
85423
  const parsed = sequencedSchema.safeParse(built);
@@ -85454,8 +85896,8 @@ async function lpRemove(args, ctx) {
85454
85896
  return usage4(ctx, parsed.error);
85455
85897
  const flags = parsed.values;
85456
85898
  const id = flags["--client-trade-id"] ?? `lp-${randomUUID6()}`;
85457
- const percent = Number(flags["--percent"]);
85458
- if (parsed.positionals.length !== 1 || !flags["--percent"] || !/^\d+(\.\d{1,2})?$/.test(flags["--percent"]) || !(percent > 0 && percent <= 100) || !validClientId(id))
85899
+ const percent2 = Number(flags["--percent"]);
85900
+ if (parsed.positionals.length !== 1 || !flags["--percent"] || !/^\d+(\.\d{1,2})?$/.test(flags["--percent"]) || !(percent2 > 0 && percent2 <= 100) || !validClientId(id))
85459
85901
  return usage4(ctx, "Usage: candle lp remove <position> --percent <0.01-100> [--wallet <tee>] [--slippage-bps 100] [--client-trade-id <id>] [--rpc-url <url>] [--yes]");
85460
85902
  try {
85461
85903
  const position = solanaAddress(parsed.positionals[0], "The position");
@@ -85470,8 +85912,8 @@ async function lpRemove(args, ctx) {
85470
85912
  wallet: wallet3,
85471
85913
  solana,
85472
85914
  yes: parsed.booleans.has("--yes"),
85473
- intent: percent === 100 ? `Remove all liquidity from position ${position}, claim its fees and close it (rent returns to the wallet)` : `Remove ${percent}% of the liquidity in position ${position}`,
85474
- body: { position, percent, slippageBps }
85915
+ intent: percent2 === 100 ? `Remove all liquidity from position ${position}, claim its fees and close it (rent returns to the wallet)` : `Remove ${percent2}% of the liquidity in position ${position}`,
85916
+ body: { position, percent: percent2, slippageBps }
85475
85917
  });
85476
85918
  } catch (error) {
85477
85919
  return tradingFailure(ctx, error, id);
@@ -86836,18 +87278,55 @@ function perpsCandleWindow2(opts, now) {
86836
87278
  return usage5(`interval must be one of ${PERPS_CANDLE_INTERVALS2.join(" ")}.`);
86837
87279
  const count2 = bounded2(opts.count, 100, 1, 5000, "count");
86838
87280
  const raw = opts.end;
86839
- const endTime = raw === undefined ? now : typeof raw === "number" || /^\d+$/.test(raw) ? Number(raw) : /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/.test(raw) ? Date.parse(raw) : NaN;
87281
+ const endTime = raw === undefined ? now : epoch2(raw);
86840
87282
  const startTime = endTime - count2 * at2(INTERVAL_MS2, index);
86841
87283
  if (!Number.isSafeInteger(endTime) || endTime < 0 || endTime > now || startTime < 0)
86842
87284
  return usage5("end must be an ISO 8601 time or epoch milliseconds, no later than now.");
86843
87285
  return { interval, count: count2, startTime, endTime };
86844
87286
  }
87287
+ function epoch2(raw) {
87288
+ return typeof raw === "number" || /^\d+$/.test(raw) ? Number(raw) : /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/.test(raw) ? Date.parse(raw) : NaN;
87289
+ }
87290
+ var PERPS_FUNDING_HISTORY_MAX_MS2 = 30 * 86400000;
87291
+ var PERPS_FUNDING_HISTORY_MAX_ROWS2 = 500;
87292
+ function perpsFundingWindow2(opts, now) {
87293
+ const endTime = opts.end === undefined ? now : epoch2(opts.end);
87294
+ if (!Number.isSafeInteger(endTime) || endTime < 0 || endTime > now)
87295
+ return usage5("end must be an ISO 8601 time or epoch milliseconds, no later than now.");
87296
+ const startTime = opts.start === undefined ? endTime - 86400000 : epoch2(opts.start);
87297
+ if (!Number.isSafeInteger(startTime) || startTime < 0 || startTime > endTime)
87298
+ return usage5("start must be an ISO 8601 time or epoch milliseconds, no later than end.");
87299
+ if (endTime - startTime > PERPS_FUNDING_HISTORY_MAX_MS2)
87300
+ return usage5("start must be at most 30 days before end.");
87301
+ return { startTime, endTime };
87302
+ }
86845
87303
  function bounded2(value, fallback, min, max, name2) {
86846
87304
  const n = value ?? fallback;
86847
87305
  if (!Number.isInteger(n) || n < min || n > max)
86848
87306
  return usage5(`${name2} must be an integer from ${min} to ${max}.`);
86849
87307
  return n;
86850
87308
  }
87309
+ function parsePerpsFundingHistory2(value, coin, window) {
87310
+ if (!Array.isArray(value))
87311
+ return unavailable4();
87312
+ const funding = [];
87313
+ for (const row of value) {
87314
+ if (!object3(row) || row.coin !== coin || !integer2(row.time) || !decimal5(row.fundingRate) || !decimal5(row.premium))
87315
+ return unavailable4();
87316
+ if (row.time >= window.startTime && row.time <= window.endTime)
87317
+ funding.push({
87318
+ time: row.time,
87319
+ fundingRate: row.fundingRate,
87320
+ fundingAprPct: fixed2(scaled2(row.fundingRate) * 876000n, 2),
87321
+ premium: row.premium
87322
+ });
87323
+ }
87324
+ funding.sort((a, b) => a.time - b.time);
87325
+ return {
87326
+ funding: funding.slice(0, PERPS_FUNDING_HISTORY_MAX_ROWS2),
87327
+ truncated: funding.length > PERPS_FUNDING_HISTORY_MAX_ROWS2
87328
+ };
87329
+ }
86851
87330
 
86852
87331
  class PerpsMarketReader2 {
86853
87332
  opts;
@@ -87125,6 +87604,44 @@ class PerpsMarketReader2 {
87125
87604
  candles: candles.slice(-window.count)
87126
87605
  };
87127
87606
  }
87607
+ async fundingHistory(coin, opts = {}) {
87608
+ this.key();
87609
+ const window = perpsFundingWindow2(opts, this.now());
87610
+ const [config, core] = await Promise.all([this.config(), this.core()]);
87611
+ const canonical4 = at2(core.universe, resolve3(core, coin)).name;
87612
+ const value = await info2(this.opts.fetcher, this.opts.network, {
87613
+ type: "fundingHistory",
87614
+ coin: canonical4,
87615
+ startTime: window.startTime,
87616
+ endTime: window.endTime
87617
+ });
87618
+ return {
87619
+ ...this.envelope(config),
87620
+ coin: canonical4,
87621
+ startTime: window.startTime,
87622
+ endTime: window.endTime,
87623
+ ...parsePerpsFundingHistory2(value, canonical4, window)
87624
+ };
87625
+ }
87626
+ async predictedFunding() {
87627
+ this.key();
87628
+ const [config, core, extras] = await Promise.all([this.config(), this.core(), this.extras()]);
87629
+ if (extras.predicted === null)
87630
+ return unavailable4();
87631
+ const predicted = [];
87632
+ for (const meta of core.universe) {
87633
+ const row = extras.predicted.get(meta.name);
87634
+ if (row)
87635
+ predicted.push({
87636
+ coin: meta.name,
87637
+ fundingRate: row.fundingRate,
87638
+ fundingAprPct: fixed2(scaled2(row.fundingRate) * 876000n, 2),
87639
+ nextFundingTime: row.nextFundingTime
87640
+ });
87641
+ }
87642
+ predicted.sort((a, b) => a.coin < b.coin ? -1 : a.coin > b.coin ? 1 : 0);
87643
+ return { ...this.envelope(config, extras.warnings, this.extrasCache?.at ?? this.now()), predicted };
87644
+ }
87128
87645
  }
87129
87646
 
87130
87647
  // src/perps-history.ts
@@ -88785,7 +89302,7 @@ function typedDataAuthorization(wallet3, typedData) {
88785
89302
  });
88786
89303
  return { body, authorizationSignature: sign4("sha256", Buffer.from(payload), wallet3.signer).toString("base64") };
88787
89304
  }
88788
- function previewLines(build) {
89305
+ function previewLines2(build) {
88789
89306
  const action = build.action;
88790
89307
  const preview = build.preview ?? {};
88791
89308
  const notes = [
@@ -88829,7 +89346,7 @@ async function confirm(ctx, build, address, yes) {
88829
89346
  if (ctx.deps.env.CANDLE_HYPERLIQUID_BUILDER !== undefined)
88830
89347
  output.write(`Builder trust override: ${builderPin2(ctx)} (replaces this release's pin).
88831
89348
  `);
88832
- for (const line of previewLines(build))
89349
+ for (const line of previewLines2(build))
88833
89350
  output.write(`${line}
88834
89351
  `);
88835
89352
  if (yes)
@@ -92883,7 +93400,7 @@ var SKILLS_CLAUDE_COMMAND = "/plugin marketplace add candledottv/agentic";
92883
93400
  var CODING_AGENTS_DOCS = "https://docs.candle.tv/developers/coding-agents";
92884
93401
  function section(deps, title) {
92885
93402
  deps.stdout.write(`
92886
- == ${title} ==
93403
+ ${accent(`== ${title} ==`, terminalStyle(deps, "stdout"))}
92887
93404
  `);
92888
93405
  }
92889
93406
  async function setup(args, ctx) {
@@ -92901,6 +93418,10 @@ async function setup(args, ctx) {
92901
93418
  writeUsageFailure(deps, "setup is an interactive wizard; for machine use, compose `auth login --json` and `doctor --json` directly", json);
92902
93419
  return 2;
92903
93420
  }
93421
+ const style = terminalStyle(deps, "stdout");
93422
+ if (deps.isTTY.stdin)
93423
+ deps.stdout.write(brandHeader(style).join(`
93424
+ `));
92904
93425
  await printIdentity(ctx);
92905
93426
  deps.stdout.write(`candle setup: this wizard authorizes the device, shows funding, and verifies everything.
92906
93427
  `);
@@ -94557,6 +95078,14 @@ async function tui(args, ctx) {
94557
95078
  Usage: candle tui [--profile <name>]`, json);
94558
95079
  return 2;
94559
95080
  }
95081
+ if (json || !deps.isTTY.stdin || !deps.isTTY.stdout || deps.env.TERM === "dumb" || deps.presentation?.plain || deps.env.CANDLE_PLAIN === "1") {
95082
+ writeLocalFailure(deps, {
95083
+ code: "TUI_REQUIRES_TERMINAL",
95084
+ message: "The Candle TUI needs an interactive terminal.",
95085
+ suggestion: "Use candle portfolio or candle help for command-line output."
95086
+ }, json);
95087
+ return 1;
95088
+ }
94560
95089
  const path = findPlugin("tui", deps.env.PATH);
94561
95090
  if (path === undefined) {
94562
95091
  writeLocalFailure(deps, {
@@ -94572,6 +95101,8 @@ Usage: candle tui [--profile <name>]`, json);
94572
95101
  if (value !== undefined)
94573
95102
  env[name2] = value;
94574
95103
  }
95104
+ if (deps.presentation?.noColor)
95105
+ env.NO_COLOR = "1";
94575
95106
  const candle = deps.execPath.endsWith("/node") || deps.execPath.endsWith("/bun") ? "candle" : deps.execPath;
94576
95107
  return deps.runPlugin(path, [...ctx.profile ? ["--profile", ctx.profile] : [], "--candle", candle], env);
94577
95108
  }
@@ -94583,11 +95114,13 @@ import { randomBytes as randomBytes11 } from "node:crypto";
94583
95114
  // src/progress.ts
94584
95115
  var FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
94585
95116
  var INTERVAL_MS3 = 80;
94586
- function stepReporter(write, tty) {
95117
+ function stepReporter(write, tty, ascii = false) {
95118
+ let detach = () => {};
94587
95119
  let timer = null;
94588
95120
  let frame = 0;
94589
95121
  let current = null;
94590
95122
  const clearSpinner = () => {
95123
+ detach();
94591
95124
  if (timer !== null)
94592
95125
  clearInterval(timer);
94593
95126
  timer = null;
@@ -94606,6 +95139,19 @@ function stepReporter(write, tty) {
94606
95139
  }
94607
95140
  write(`${FRAMES[0]} ${label}`);
94608
95141
  frame = 1;
95142
+ const interrupt = (signal) => {
95143
+ clearSpinner();
95144
+ if (process.listenerCount(signal) === 0)
95145
+ process.kill(process.pid, signal);
95146
+ };
95147
+ const sigint = () => interrupt("SIGINT");
95148
+ const sigterm = () => interrupt("SIGTERM");
95149
+ process.once("SIGINT", sigint);
95150
+ process.once("SIGTERM", sigterm);
95151
+ detach = () => {
95152
+ process.off("SIGINT", sigint);
95153
+ process.off("SIGTERM", sigterm);
95154
+ };
94609
95155
  timer = setInterval(() => {
94610
95156
  write(`\r\x1B[2K${FRAMES[frame % FRAMES.length]} ${current}`);
94611
95157
  frame++;
@@ -94615,17 +95161,22 @@ function stepReporter(write, tty) {
94615
95161
  },
94616
95162
  done(label) {
94617
95163
  clearSpinner();
94618
- write(`✓ ${label}
95164
+ write(`${ascii ? "OK" : "✓"} ${label}
94619
95165
  `);
94620
95166
  },
94621
95167
  fail(label) {
94622
95168
  clearSpinner();
94623
- write(`✗ ${label}
95169
+ write(`${ascii ? "FAIL" : "✗"} ${label}
94624
95170
  `);
94625
95171
  },
94626
95172
  stop: clearSpinner
94627
95173
  };
94628
95174
  }
95175
+ function commandSteps(ctx) {
95176
+ const style = terminalStyle(ctx.deps, "stderr", ctx.json);
95177
+ const reporter = stepReporter(ctx.json ? () => {} : (text3) => ctx.deps.stderr.write(text3), style.motion, !style.styled);
95178
+ return { ...reporter, [Symbol.dispose ?? Symbol.for("Symbol.dispose")]: () => reporter.stop() };
95179
+ }
94629
95180
  function formatBytes(n) {
94630
95181
  if (n >= 1024 * 1024)
94631
95182
  return `${(n / (1024 * 1024)).toFixed(1)} MB`;
@@ -94932,226 +95483,233 @@ init_fido2();
94932
95483
  var SIGNATURE_VERIFIED = "signature verified (Sigstore, keyless; signer pinned to the release workflow)";
94933
95484
  var INSTALLER_LINE = "curl -fsSL https://candle.tv/install.sh | bash";
94934
95485
  async function update(args, ctx) {
94935
- const { deps, json } = ctx;
94936
- const parsed = parseArgs(args, { valueFlags: ["--to"], booleanFlags: ["--check"] });
94937
- if ("error" in parsed) {
94938
- writeUsageFailure(deps, parsed.error, json);
94939
- return 2;
94940
- }
94941
- if (parsed.positionals.length > 0) {
94942
- writeUsageFailure(deps, `Unexpected argument: ${parsed.positionals[0]}`, json);
94943
- return 2;
94944
- }
94945
- const check = parsed.booleans.has("--check");
94946
- const pinned = parsed.values["--to"];
94947
- const realExec = await deps.realpath(deps.execPath).catch(() => deps.execPath);
94948
- const method = detectInstall(deps.execPath, realExec);
94949
- if (method === "homebrew") {
94950
- if (json) {
94951
- const payload = { current: CLI_VERSION, latest: null, updated: false, path: realExec, method };
94952
- deps.stdout.write(`${JSON.stringify(payload)}
95486
+ let __stack = [];
95487
+ try {
95488
+ const { deps, json } = ctx;
95489
+ const parsed = parseArgs(args, { valueFlags: ["--to"], booleanFlags: ["--check"] });
95490
+ if ("error" in parsed) {
95491
+ writeUsageFailure(deps, parsed.error, json);
95492
+ return 2;
95493
+ }
95494
+ if (parsed.positionals.length > 0) {
95495
+ writeUsageFailure(deps, `Unexpected argument: ${parsed.positionals[0]}`, json);
95496
+ return 2;
95497
+ }
95498
+ const check = parsed.booleans.has("--check");
95499
+ const pinned = parsed.values["--to"];
95500
+ const realExec = await deps.realpath(deps.execPath).catch(() => deps.execPath);
95501
+ const method = detectInstall(deps.execPath, realExec);
95502
+ if (method === "homebrew") {
95503
+ if (json) {
95504
+ const payload = { current: CLI_VERSION, latest: null, updated: false, path: realExec, method };
95505
+ deps.stdout.write(`${JSON.stringify(payload)}
94953
95506
  `);
94954
- } else {
94955
- deps.stdout.write(`Installed by Homebrew. Run: brew upgrade candle
95507
+ } else {
95508
+ deps.stdout.write(`Installed by Homebrew. Run: brew upgrade candle
94956
95509
  `);
95510
+ }
95511
+ return 0;
94957
95512
  }
94958
- return 0;
94959
- }
94960
- if (method === "script") {
94961
- if (json) {
94962
- const payload = { current: CLI_VERSION, latest: null, updated: false, path: realExec, method };
94963
- deps.stdout.write(`${JSON.stringify(payload)}
95513
+ if (method === "script") {
95514
+ if (json) {
95515
+ const payload = { current: CLI_VERSION, latest: null, updated: false, path: realExec, method };
95516
+ deps.stdout.write(`${JSON.stringify(payload)}
94964
95517
  `);
94965
- } else {
94966
- deps.stdout.write(`Installed with npm (or a dev checkout). Run: npm i -g @candledottv/cli@latest
95518
+ } else {
95519
+ deps.stdout.write(`Installed with npm (or a dev checkout). Run: npm i -g @candledottv/cli@latest
94967
95520
  `);
95521
+ }
95522
+ return 0;
94968
95523
  }
94969
- return 0;
94970
- }
94971
- const base2 = releaseBaseUrl(deps.env);
94972
- const fetched = pinned ? await fetchPinned(deps, base2, pinned) : await fetchLatest(deps, base2);
94973
- if (!fetched.ok) {
94974
- const code = fetched.kind === "invalid" ? "MANIFEST_INVALID" : "UPDATE_UNREACHABLE";
94975
- writeLocalFailure(deps, { code, message: fetched.message }, json);
94976
- return 1;
94977
- }
94978
- const target = fetched.manifest;
94979
- let identityUri;
94980
- try {
94981
- identityUri = releaseIdentityUri(target.version);
94982
- } catch (error) {
94983
- writeLocalFailure(deps, {
94984
- code: "MANIFEST_INVALID",
94985
- message: `Refusing the release manifest at ${base2}: ${messageOf2(error)}.`,
94986
- suggestion: "Nothing was downloaded or installed."
94987
- }, json);
94988
- return 1;
94989
- }
94990
- const order = compareVersions(CLI_VERSION, target.version);
94991
- if (check) {
94992
- if (json) {
94993
- const payload = { current: CLI_VERSION, latest: target.version, updated: false, path: realExec };
94994
- deps.stdout.write(`${JSON.stringify(payload)}
95524
+ const base2 = releaseBaseUrl(deps.env);
95525
+ const fetched = pinned ? await fetchPinned(deps, base2, pinned) : await fetchLatest(deps, base2);
95526
+ if (!fetched.ok) {
95527
+ const code = fetched.kind === "invalid" ? "MANIFEST_INVALID" : "UPDATE_UNREACHABLE";
95528
+ writeLocalFailure(deps, { code, message: fetched.message }, json);
95529
+ return 1;
95530
+ }
95531
+ const target = fetched.manifest;
95532
+ let identityUri;
95533
+ try {
95534
+ identityUri = releaseIdentityUri(target.version);
95535
+ } catch (error) {
95536
+ writeLocalFailure(deps, {
95537
+ code: "MANIFEST_INVALID",
95538
+ message: `Refusing the release manifest at ${base2}: ${messageOf2(error)}.`,
95539
+ suggestion: "Nothing was downloaded or installed."
95540
+ }, json);
95541
+ return 1;
95542
+ }
95543
+ const order = compareVersions(CLI_VERSION, target.version);
95544
+ if (check) {
95545
+ if (json) {
95546
+ const payload = { current: CLI_VERSION, latest: target.version, updated: false, path: realExec };
95547
+ deps.stdout.write(`${JSON.stringify(payload)}
94995
95548
  `);
94996
- } else if (order < 0) {
94997
- deps.stdout.write(`candle ${CLI_VERSION}; ${target.version} available. Run: candle update
95549
+ } else if (order < 0) {
95550
+ deps.stdout.write(`candle ${CLI_VERSION}; ${target.version} available. Run: candle update
94998
95551
  `);
94999
- } else {
95000
- deps.stdout.write(`candle ${CLI_VERSION} is up to date (latest ${target.version})
95552
+ } else {
95553
+ deps.stdout.write(`candle ${CLI_VERSION} is up to date (latest ${target.version})
95001
95554
  `);
95555
+ }
95556
+ return 0;
95002
95557
  }
95003
- return 0;
95004
- }
95005
- if (order === 0 || order > 0 && !pinned) {
95006
- if (json) {
95007
- const payload = { current: CLI_VERSION, latest: target.version, updated: false, path: realExec };
95008
- deps.stdout.write(`${JSON.stringify(payload)}
95558
+ if (order === 0 || order > 0 && !pinned) {
95559
+ if (json) {
95560
+ const payload = { current: CLI_VERSION, latest: target.version, updated: false, path: realExec };
95561
+ deps.stdout.write(`${JSON.stringify(payload)}
95009
95562
  `);
95010
- } else {
95011
- deps.stdout.write(`candle ${CLI_VERSION} is up to date
95563
+ } else {
95564
+ deps.stdout.write(`candle ${CLI_VERSION} is up to date
95012
95565
  `);
95566
+ }
95567
+ return 0;
95013
95568
  }
95014
- return 0;
95015
- }
95016
- if (order > 0 && pinned)
95017
- deps.stderr.write(`Warning: ${target.version} is a downgrade from ${CLI_VERSION}
95569
+ if (order > 0 && pinned)
95570
+ deps.stderr.write(`Warning: ${target.version} is a downgrade from ${CLI_VERSION}
95018
95571
  `);
95019
- if (!deps.platformKey) {
95020
- writeLocalFailure(deps, {
95021
- code: "UPDATE_UNSUPPORTED_PLATFORM",
95022
- message: "No release binary for this platform.",
95023
- suggestion: "Run: npm i -g @candledottv/cli@latest"
95024
- }, json);
95025
- return 1;
95026
- }
95027
- const expectedName = `candle-${deps.platformKey}`;
95028
- const asset = target.assets[deps.platformKey];
95029
- if (!asset) {
95030
- writeLocalFailure(deps, {
95031
- code: "UPDATE_UNSUPPORTED_PLATFORM",
95032
- message: `Release ${target.tag} has no asset for ${deps.platformKey}.`
95033
- }, json);
95034
- return 1;
95035
- }
95036
- if (asset.name !== expectedName) {
95037
- writeLocalFailure(deps, {
95038
- code: "MANIFEST_INVALID",
95039
- message: `Release ${target.tag} names ${asset.name} as the ${deps.platformKey} asset; this platform installs ${expectedName}.`,
95040
- suggestion: "Nothing was downloaded or installed."
95041
- }, json);
95042
- return 1;
95043
- }
95044
- const helperName = helperAssetName(deps.platformKey);
95045
- const helperAsset = target.helpers?.[deps.platformKey];
95046
- if (helperAsset !== undefined && helperAsset.name !== helperName) {
95047
- writeLocalFailure(deps, {
95048
- code: "MANIFEST_INVALID",
95049
- message: `Release ${target.tag} names ${helperAsset.name} as the ${deps.platformKey} security key helper; this platform installs ${helperName}.`,
95050
- suggestion: "Nothing was downloaded or installed."
95051
- }, json);
95052
- return 1;
95053
- }
95054
- const steps = json ? stepReporter(() => {}, false) : stepReporter((text3) => deps.stderr.write(text3), process.stderr.isTTY === true);
95055
- if (!json) {
95056
- const total = asset.size + (helperAsset?.size ?? 0);
95057
- deps.stderr.write(`Updating candle ${CLI_VERSION} -> ${target.version} (${formatBytes(total)})
95572
+ if (!deps.platformKey) {
95573
+ writeLocalFailure(deps, {
95574
+ code: "UPDATE_UNSUPPORTED_PLATFORM",
95575
+ message: "No release binary for this platform.",
95576
+ suggestion: "Run: npm i -g @candledottv/cli@latest"
95577
+ }, json);
95578
+ return 1;
95579
+ }
95580
+ const expectedName = `candle-${deps.platformKey}`;
95581
+ const asset = target.assets[deps.platformKey];
95582
+ if (!asset) {
95583
+ writeLocalFailure(deps, {
95584
+ code: "UPDATE_UNSUPPORTED_PLATFORM",
95585
+ message: `Release ${target.tag} has no asset for ${deps.platformKey}.`
95586
+ }, json);
95587
+ return 1;
95588
+ }
95589
+ if (asset.name !== expectedName) {
95590
+ writeLocalFailure(deps, {
95591
+ code: "MANIFEST_INVALID",
95592
+ message: `Release ${target.tag} names ${asset.name} as the ${deps.platformKey} asset; this platform installs ${expectedName}.`,
95593
+ suggestion: "Nothing was downloaded or installed."
95594
+ }, json);
95595
+ return 1;
95596
+ }
95597
+ const helperName = helperAssetName(deps.platformKey);
95598
+ const helperAsset = target.helpers?.[deps.platformKey];
95599
+ if (helperAsset !== undefined && helperAsset.name !== helperName) {
95600
+ writeLocalFailure(deps, {
95601
+ code: "MANIFEST_INVALID",
95602
+ message: `Release ${target.tag} names ${helperAsset.name} as the ${deps.platformKey} security key helper; this platform installs ${helperName}.`,
95603
+ suggestion: "Nothing was downloaded or installed."
95604
+ }, json);
95605
+ return 1;
95606
+ }
95607
+ const steps = __using(__stack, commandSteps(ctx), 0);
95608
+ if (!json) {
95609
+ const total = asset.size + (helperAsset?.size ?? 0);
95610
+ deps.stderr.write(`Updating candle ${CLI_VERSION} -> ${target.version} (${formatBytes(total)})
95058
95611
  `);
95059
- }
95060
- steps.start("downloading SHA256SUMS");
95061
- const sums = await downloadSums(deps, base2, target.tag);
95062
- if (!sums.ok) {
95063
- steps.fail("downloading SHA256SUMS");
95064
- writeLocalFailure(deps, { code: "UPDATE_UNREACHABLE", message: sums.message }, json);
95065
- return 1;
95066
- }
95067
- steps.done("downloaded SHA256SUMS");
95068
- const dir = realExec.slice(0, realExec.lastIndexOf("/")) || ".";
95069
- const staged = [];
95070
- const discardAll = async () => {
95071
- for (const path of staged)
95072
- await discard(deps, path);
95073
- };
95074
- const binary = await stage(deps, steps, {
95075
- base: base2,
95076
- tag: target.tag,
95077
- name: expectedName,
95078
- expectedSha256: asset.sha256,
95079
- sums: sums.sums,
95080
- identityUri,
95081
- dir,
95082
- version: target.version,
95083
- staged,
95084
- label: ""
95085
- });
95086
- if (!binary.ok) {
95087
- await discardAll();
95088
- writeLocalFailure(deps, binary.failure, json);
95089
- return 1;
95090
- }
95091
- let helper = null;
95092
- if (helperAsset !== undefined) {
95093
- const fetched2 = await stage(deps, steps, {
95612
+ }
95613
+ steps.start("downloading SHA256SUMS");
95614
+ const sums = await downloadSums(deps, base2, target.tag);
95615
+ if (!sums.ok) {
95616
+ steps.fail("downloading SHA256SUMS");
95617
+ writeLocalFailure(deps, { code: "UPDATE_UNREACHABLE", message: sums.message }, json);
95618
+ return 1;
95619
+ }
95620
+ steps.done("downloaded SHA256SUMS");
95621
+ const dir = realExec.slice(0, realExec.lastIndexOf("/")) || ".";
95622
+ const staged = [];
95623
+ const discardAll = async () => {
95624
+ for (const path of staged)
95625
+ await discard(deps, path);
95626
+ };
95627
+ const binary = await stage(deps, steps, {
95094
95628
  base: base2,
95095
95629
  tag: target.tag,
95096
- name: helperName,
95097
- expectedSha256: helperAsset.sha256,
95630
+ name: expectedName,
95631
+ expectedSha256: asset.sha256,
95098
95632
  sums: sums.sums,
95099
95633
  identityUri,
95100
95634
  dir,
95101
95635
  version: target.version,
95102
95636
  staged,
95103
- label: `${helperName} `
95637
+ label: ""
95104
95638
  });
95105
- if (!fetched2.ok) {
95639
+ if (!binary.ok) {
95106
95640
  await discardAll();
95107
- writeLocalFailure(deps, fetched2.failure, json);
95641
+ writeLocalFailure(deps, binary.failure, json);
95108
95642
  return 1;
95109
95643
  }
95110
- helper = { tmpPath: fetched2.tmpPath, target: `${dir}/${HELPER_NAME}` };
95111
- }
95112
- steps.start("installing");
95113
- try {
95114
- await deps.rename(binary.tmpPath, realExec);
95115
- } catch (error) {
95116
- steps.fail("installing");
95117
- await discardAll();
95118
- writeLocalFailure(deps, notWritable(dir, error), json);
95119
- return 1;
95120
- }
95121
- if (helper !== null) {
95644
+ let helper = null;
95645
+ if (helperAsset !== undefined) {
95646
+ const fetched2 = await stage(deps, steps, {
95647
+ base: base2,
95648
+ tag: target.tag,
95649
+ name: helperName,
95650
+ expectedSha256: helperAsset.sha256,
95651
+ sums: sums.sums,
95652
+ identityUri,
95653
+ dir,
95654
+ version: target.version,
95655
+ staged,
95656
+ label: `${helperName} `
95657
+ });
95658
+ if (!fetched2.ok) {
95659
+ await discardAll();
95660
+ writeLocalFailure(deps, fetched2.failure, json);
95661
+ return 1;
95662
+ }
95663
+ helper = { tmpPath: fetched2.tmpPath, target: `${dir}/${HELPER_NAME}` };
95664
+ }
95665
+ steps.start("installing");
95122
95666
  try {
95123
- await deps.rename(helper.tmpPath, helper.target);
95667
+ await deps.rename(binary.tmpPath, realExec);
95124
95668
  } catch (error) {
95125
95669
  steps.fail("installing");
95126
- await discard(deps, helper.tmpPath);
95127
- const failure = notWritable(dir, error);
95128
- writeLocalFailure(deps, {
95129
- ...failure,
95130
- message: `${failure.message} candle ${target.version} was installed to ${realExec}, but its security key helper could not be renamed to ${helper.target}.`
95131
- }, json);
95670
+ await discardAll();
95671
+ writeLocalFailure(deps, notWritable(dir, error), json);
95132
95672
  return 1;
95133
95673
  }
95134
- }
95135
- steps.done(`installed to ${realExec}${helper !== null ? ` and ${helper.target}` : ""}`);
95136
- const helperReport2 = helper !== null ? { name: HELPER_NAME, updated: true } : null;
95137
- if (json) {
95138
- const payload = {
95139
- current: CLI_VERSION,
95140
- latest: target.version,
95141
- updated: true,
95142
- path: realExec,
95143
- helper: helperReport2
95144
- };
95145
- deps.stdout.write(`${JSON.stringify(payload)}
95674
+ if (helper !== null) {
95675
+ try {
95676
+ await deps.rename(helper.tmpPath, helper.target);
95677
+ } catch (error) {
95678
+ steps.fail("installing");
95679
+ await discard(deps, helper.tmpPath);
95680
+ const failure = notWritable(dir, error);
95681
+ writeLocalFailure(deps, {
95682
+ ...failure,
95683
+ message: `${failure.message} candle ${target.version} was installed to ${realExec}, but its security key helper could not be renamed to ${helper.target}.`
95684
+ }, json);
95685
+ return 1;
95686
+ }
95687
+ }
95688
+ steps.done(`installed to ${realExec}${helper !== null ? ` and ${helper.target}` : ""}`);
95689
+ const helperReport2 = helper !== null ? { name: HELPER_NAME, updated: true } : null;
95690
+ if (json) {
95691
+ const payload = {
95692
+ current: CLI_VERSION,
95693
+ latest: target.version,
95694
+ updated: true,
95695
+ path: realExec,
95696
+ helper: helperReport2
95697
+ };
95698
+ deps.stdout.write(`${JSON.stringify(payload)}
95146
95699
  `);
95147
- } else {
95148
- deps.stdout.write(`Updated candle ${CLI_VERSION} -> ${target.version}
95700
+ } else {
95701
+ deps.stdout.write(`Updated candle ${CLI_VERSION} -> ${target.version}
95149
95702
  `);
95150
- if (helper !== null)
95151
- deps.stdout.write(`Installed ${HELPER_NAME} ${target.version} to ${helper.target}
95703
+ if (helper !== null)
95704
+ deps.stdout.write(`Installed ${HELPER_NAME} ${target.version} to ${helper.target}
95152
95705
  `);
95706
+ }
95707
+ return 0;
95708
+ } catch (_catch) {
95709
+ var _err = _catch, _hasErr = 1;
95710
+ } finally {
95711
+ __callDispose(__stack, _err, _hasErr);
95153
95712
  }
95154
- return 0;
95155
95713
  }
95156
95714
  async function stage(deps, steps, opts) {
95157
95715
  const { name: name2, label } = opts;
@@ -96267,13 +96825,13 @@ async function addPasskeyFactor(ctx, parsed, resolvedVault, hold) {
96267
96825
  let committed;
96268
96826
  try {
96269
96827
  const prfOutput = await assertPlatformPrf(deps, session, envelope, vaultId, "derive this vault's key on it");
96270
- let wrap2;
96828
+ let wrap;
96271
96829
  try {
96272
- wrap2 = await wrapDekForPrf(vault.dek, prfOutput, envelope, vault.file);
96830
+ wrap = await wrapDekForPrf(vault.dek, prfOutput, envelope, vault.file);
96273
96831
  } finally {
96274
96832
  wipe(prfOutput);
96275
96833
  }
96276
- sealed = { ...envelope, wrap: wrap2 };
96834
+ sealed = { ...envelope, wrap };
96277
96835
  committed = hold(await commitVault(vault, { index: vault.index, envelopes: [...vault.file.envelopes, sealed] }, deps));
96278
96836
  } catch (error) {
96279
96837
  deps.stderr.write(`${PASSKEY_LEFTOVER_NOTE}
@@ -96358,86 +96916,93 @@ import { randomBytes as randomBytes12 } from "node:crypto";
96358
96916
  init_release();
96359
96917
  init_fido2();
96360
96918
  async function installHelperForThisRelease(ctx) {
96361
- const { deps, json } = ctx;
96362
- const location = await locateFido2Helper(deps);
96363
- if (location.state === "ready")
96364
- return { ok: true, path: location.path, installed: false };
96365
- if (!location.installable)
96366
- return { ok: false, message: location.reason };
96367
- const realExec = await deps.realpath(deps.execPath).catch(() => deps.execPath);
96368
- if (detectInstall(deps.execPath, realExec) === "homebrew") {
96369
- return { ok: false, message: `candle is installed by Homebrew at ${realExec}. Run: brew reinstall candle` };
96370
- }
96371
- if (!deps.platformKey)
96372
- return { ok: false, message: "this CLI ships no security key helper for this platform" };
96373
- const base2 = releaseBaseUrl(deps.env);
96374
- const tag = `cli-v${CLI_VERSION}`;
96375
- const name2 = helperAssetName(deps.platformKey);
96376
- const steps = json ? stepReporter(() => {}, false) : stepReporter((text3) => deps.stderr.write(text3), process.stderr.isTTY === true);
96377
- steps.start(`reading the ${tag} release manifest`);
96378
- const fetched = await fetchPinned(deps, base2, tag);
96379
- if (!fetched.ok) {
96380
- steps.fail(`reading the ${tag} release manifest`);
96381
- return { ok: false, message: fetched.message };
96382
- }
96383
- const asset = fetched.manifest.helpers?.[deps.platformKey];
96384
- if (asset === undefined) {
96385
- steps.fail(`reading the ${tag} release manifest`);
96386
- return { ok: false, message: `release ${tag} declares no ${name2}` };
96387
- }
96388
- if (asset.name !== name2) {
96389
- steps.fail(`reading the ${tag} release manifest`);
96390
- return {
96391
- ok: false,
96392
- message: `release ${tag} names ${asset.name} as the ${deps.platformKey} security key helper; this platform installs ${name2}`
96393
- };
96394
- }
96395
- steps.done(`release ${tag} declares ${name2} (${formatBytes(asset.size)})`);
96396
- steps.start(`downloading ${name2}`);
96397
- const sums = await downloadSums(deps, base2, tag);
96398
- if (!sums.ok) {
96399
- steps.fail(`downloading ${name2}`);
96400
- return { ok: false, message: sums.message };
96401
- }
96402
- const download = await downloadReleaseAsset(deps, base2, tag, name2);
96403
- if (!download.ok) {
96404
- steps.fail(`downloading ${name2}`);
96405
- return { ok: false, message: download.message };
96406
- }
96407
- steps.done(`downloaded ${name2} (${formatBytes(download.bytes.length)})`);
96408
- steps.start("verifying checksum and signature");
96409
- const checked = checkReleaseAsset(deps, {
96410
- name: name2,
96411
- bytes: download.bytes,
96412
- sums: sums.sums,
96413
- bundle: download.bundle,
96414
- expectedSha256: asset.sha256,
96415
- identityUri: releaseIdentityUri(CLI_VERSION)
96416
- });
96417
- if (!checked.ok) {
96418
- steps.fail("verifying checksum and signature");
96419
- return { ok: false, message: `${checked.message}${checked.suggestion ? ` ${checked.suggestion}` : ""}` };
96420
- }
96421
- steps.done("checksum and signature verified (Sigstore, keyless; signer pinned to the release workflow)");
96422
- const dir = realExec.slice(0, realExec.lastIndexOf("/")) || ".";
96423
- const target = `${dir}/${HELPER_NAME}`;
96424
- const tmpPath = `${dir}/.candle-fido2-${CLI_VERSION}-${randomBytes12(6).toString("hex")}`;
96425
- steps.start(`installing ${HELPER_NAME}`);
96919
+ let __stack = [];
96426
96920
  try {
96427
- await deps.writeBytes(tmpPath, download.bytes);
96428
- } catch (error) {
96429
- steps.fail(`installing ${HELPER_NAME}`);
96430
- return { ok: false, message: `cannot write ${dir}: ${messageOf2(error)}` };
96431
- }
96432
- try {
96433
- await deps.rename(tmpPath, target);
96434
- } catch (error) {
96435
- steps.fail(`installing ${HELPER_NAME}`);
96436
- await discard2(deps, tmpPath);
96437
- return { ok: false, message: `cannot write ${dir}: ${messageOf2(error)}` };
96921
+ const { deps } = ctx;
96922
+ const location = await locateFido2Helper(deps);
96923
+ if (location.state === "ready")
96924
+ return { ok: true, path: location.path, installed: false };
96925
+ if (!location.installable)
96926
+ return { ok: false, message: location.reason };
96927
+ const realExec = await deps.realpath(deps.execPath).catch(() => deps.execPath);
96928
+ if (detectInstall(deps.execPath, realExec) === "homebrew") {
96929
+ return { ok: false, message: `candle is installed by Homebrew at ${realExec}. Run: brew reinstall candle` };
96930
+ }
96931
+ if (!deps.platformKey)
96932
+ return { ok: false, message: "this CLI ships no security key helper for this platform" };
96933
+ const base2 = releaseBaseUrl(deps.env);
96934
+ const tag = `cli-v${CLI_VERSION}`;
96935
+ const name2 = helperAssetName(deps.platformKey);
96936
+ const steps = __using(__stack, commandSteps(ctx), 0);
96937
+ steps.start(`reading the ${tag} release manifest`);
96938
+ const fetched = await fetchPinned(deps, base2, tag);
96939
+ if (!fetched.ok) {
96940
+ steps.fail(`reading the ${tag} release manifest`);
96941
+ return { ok: false, message: fetched.message };
96942
+ }
96943
+ const asset = fetched.manifest.helpers?.[deps.platformKey];
96944
+ if (asset === undefined) {
96945
+ steps.fail(`reading the ${tag} release manifest`);
96946
+ return { ok: false, message: `release ${tag} declares no ${name2}` };
96947
+ }
96948
+ if (asset.name !== name2) {
96949
+ steps.fail(`reading the ${tag} release manifest`);
96950
+ return {
96951
+ ok: false,
96952
+ message: `release ${tag} names ${asset.name} as the ${deps.platformKey} security key helper; this platform installs ${name2}`
96953
+ };
96954
+ }
96955
+ steps.done(`release ${tag} declares ${name2} (${formatBytes(asset.size)})`);
96956
+ steps.start(`downloading ${name2}`);
96957
+ const sums = await downloadSums(deps, base2, tag);
96958
+ if (!sums.ok) {
96959
+ steps.fail(`downloading ${name2}`);
96960
+ return { ok: false, message: sums.message };
96961
+ }
96962
+ const download = await downloadReleaseAsset(deps, base2, tag, name2);
96963
+ if (!download.ok) {
96964
+ steps.fail(`downloading ${name2}`);
96965
+ return { ok: false, message: download.message };
96966
+ }
96967
+ steps.done(`downloaded ${name2} (${formatBytes(download.bytes.length)})`);
96968
+ steps.start("verifying checksum and signature");
96969
+ const checked = checkReleaseAsset(deps, {
96970
+ name: name2,
96971
+ bytes: download.bytes,
96972
+ sums: sums.sums,
96973
+ bundle: download.bundle,
96974
+ expectedSha256: asset.sha256,
96975
+ identityUri: releaseIdentityUri(CLI_VERSION)
96976
+ });
96977
+ if (!checked.ok) {
96978
+ steps.fail("verifying checksum and signature");
96979
+ return { ok: false, message: `${checked.message}${checked.suggestion ? ` ${checked.suggestion}` : ""}` };
96980
+ }
96981
+ steps.done("checksum and signature verified (Sigstore, keyless; signer pinned to the release workflow)");
96982
+ const dir = realExec.slice(0, realExec.lastIndexOf("/")) || ".";
96983
+ const target = `${dir}/${HELPER_NAME}`;
96984
+ const tmpPath = `${dir}/.candle-fido2-${CLI_VERSION}-${randomBytes12(6).toString("hex")}`;
96985
+ steps.start(`installing ${HELPER_NAME}`);
96986
+ try {
96987
+ await deps.writeBytes(tmpPath, download.bytes);
96988
+ } catch (error) {
96989
+ steps.fail(`installing ${HELPER_NAME}`);
96990
+ return { ok: false, message: `cannot write ${dir}: ${messageOf2(error)}` };
96991
+ }
96992
+ try {
96993
+ await deps.rename(tmpPath, target);
96994
+ } catch (error) {
96995
+ steps.fail(`installing ${HELPER_NAME}`);
96996
+ await discard2(deps, tmpPath);
96997
+ return { ok: false, message: `cannot write ${dir}: ${messageOf2(error)}` };
96998
+ }
96999
+ steps.done(`installed ${HELPER_NAME} ${CLI_VERSION} to ${target}`);
97000
+ return { ok: true, path: target, installed: true };
97001
+ } catch (_catch) {
97002
+ var _err = _catch, _hasErr = 1;
97003
+ } finally {
97004
+ __callDispose(__stack, _err, _hasErr);
96438
97005
  }
96439
- steps.done(`installed ${HELPER_NAME} ${CLI_VERSION} to ${target}`);
96440
- return { ok: true, path: target, installed: true };
96441
97006
  }
96442
97007
  async function discard2(deps, path) {
96443
97008
  try {
@@ -96573,13 +97138,13 @@ async function addSecurityKeyFactor(ctx, parsed, resolvedVault, hold) {
96573
97138
  wrap: { alg: VAULT_CIPHER, iv: "", ciphertext: "" }
96574
97139
  };
96575
97140
  const prfOutput = await assertPrf(deps, session, envelope, vault.file.vaultId, "derive this vault's key on it");
96576
- let wrap2;
97141
+ let wrap;
96577
97142
  try {
96578
- wrap2 = await wrapDekForPrf(vault.dek, prfOutput, envelope, vault.file);
97143
+ wrap = await wrapDekForPrf(vault.dek, prfOutput, envelope, vault.file);
96579
97144
  } finally {
96580
97145
  wipe(prfOutput);
96581
97146
  }
96582
- const sealed = { ...envelope, wrap: wrap2 };
97147
+ const sealed = { ...envelope, wrap };
96583
97148
  await commitVault(vault, { index: vault.index, envelopes: [...vault.file.envelopes, sealed] }, deps);
96584
97149
  const written = await readVaultRaw(path);
96585
97150
  if (written === null)
@@ -96775,13 +97340,13 @@ async function addTouchIdFactor(ctx, parsed, resolvedVault, hold) {
96775
97340
  kek: { alg: SECURE_ENCLAVE_KEK_ALG, ciphertext },
96776
97341
  wrap: { alg: VAULT_CIPHER, iv: "", ciphertext: "" }
96777
97342
  };
96778
- let wrap2;
97343
+ let wrap;
96779
97344
  try {
96780
- wrap2 = await wrapDekForKek(vault.dek, kek, envelope, vault.file);
97345
+ wrap = await wrapDekForKek(vault.dek, kek, envelope, vault.file);
96781
97346
  } finally {
96782
97347
  wipe(kek);
96783
97348
  }
96784
- sealed = { ...envelope, wrap: wrap2 };
97349
+ sealed = { ...envelope, wrap };
96785
97350
  committed = hold(await commitVault(vault, { index: vault.index, envelopes: [...vault.file.envelopes, sealed] }, deps));
96786
97351
  } catch (error) {
96787
97352
  await removeKey();
@@ -96896,8 +97461,8 @@ async function createVault(request2, clock) {
96896
97461
  }
96897
97462
  const dek = freshDek();
96898
97463
  const file = await withSecret(dek, async (dekBytes) => {
96899
- const wrap2 = await wrapDekForPassphrase(dekBytes, request2.passphrase, envelope, { vaultId }, request2.notice);
96900
- const sealedEnvelope = { ...envelope, wrap: wrap2 };
97464
+ const wrap = await wrapDekForPassphrase(dekBytes, request2.passphrase, envelope, { vaultId }, request2.notice);
97465
+ const sealedEnvelope = { ...envelope, wrap };
96901
97466
  const payloadKey = await derivePayloadKey(dekBytes, unb64u(vaultId, "vaultId"));
96902
97467
  const root = await seal(payloadKey, request2.rootEntropy, rootAad(vaultId));
96903
97468
  let index = seedIndex;
@@ -97333,8 +97898,8 @@ async function vaultFactorAdd(args, ctx) {
97333
97898
  strength: strengthFor(ownPassphrase),
97334
97899
  wrap: { alg: VAULT_CIPHER, iv: "", ciphertext: "" }
97335
97900
  };
97336
- const wrap2 = await wrapDekForPassphrase(vault.dek, passphrase, envelope, vault.file, (line) => deps.stderr.write(line));
97337
- const sealed = { ...envelope, wrap: wrap2 };
97901
+ const wrap = await wrapDekForPassphrase(vault.dek, passphrase, envelope, vault.file, (line) => deps.stderr.write(line));
97902
+ const sealed = { ...envelope, wrap };
97338
97903
  await commitVault(vault, { index: vault.index, envelopes: [...vault.file.envelopes, sealed] }, deps);
97339
97904
  const written = await readVaultRaw(path);
97340
97905
  if (written === null)
@@ -102277,7 +102842,7 @@ function solOf(lamports) {
102277
102842
  const digits = lamports.padStart(10, "0");
102278
102843
  return `${digits.slice(0, -9)}.${digits.slice(-9)}`.replace(/\.?0+$/, "");
102279
102844
  }
102280
- function previewLines2(preview, walletName) {
102845
+ function previewLines3(preview, walletName) {
102281
102846
  const lines = [
102282
102847
  `Close ${preview.accountCount} empty token account${preview.accountCount === 1 ? "" : "s"} on the ${walletName}`,
102283
102848
  `Rent returned to that same wallet: ${safeText(preview.totalSol)} SOL in ${preview.transactions} transaction${preview.transactions === 1 ? "" : "s"}, network fee about ${solOf(preview.estimatedFeeLamports)} SOL, net ${safeText(preview.netSol)} SOL`
@@ -102313,7 +102878,7 @@ async function previewAndConfirm(ctx, preview, walletName, yes) {
102313
102878
  const output = ctx.json ? ctx.deps.stderr : ctx.deps.stdout;
102314
102879
  output.write(`No empty token accounts to close on the ${walletName}.
102315
102880
  `);
102316
- for (const line of previewLines2(preview, walletName).slice(2))
102881
+ for (const line of previewLines3(preview, walletName).slice(2))
102317
102882
  output.write(`${line}
102318
102883
  `);
102319
102884
  return printTradingResult(ctx, {
@@ -102323,7 +102888,7 @@ async function previewAndConfirm(ctx, preview, walletName, yes) {
102323
102888
  preview
102324
102889
  });
102325
102890
  }
102326
- if (!await confirmClose(ctx, previewLines2(preview, walletName), yes))
102891
+ if (!await confirmClose(ctx, previewLines3(preview, walletName), yes))
102327
102892
  return printTradingResult(ctx, {
102328
102893
  success: true,
102329
102894
  status: "cancelled",
@@ -102819,7 +103384,7 @@ async function maybeWriteUpdateNotice(deps, opts = {}) {
102819
103384
  if (prior && prior.version === latest && deps.now() - prior.shownAt < DAY_MS)
102820
103385
  return;
102821
103386
  const line = `Update available: candle ${CLI_VERSION} -> ${latest}. Run: candle update`;
102822
- const tty = process.stderr.isTTY === true;
103387
+ const tty = terminalStyle(deps, "stderr", opts.json).color !== 0;
102823
103388
  deps.stderr.write(tty ? `\x1B[2m${line}\x1B[0m
102824
103389
  ` : `${line}
102825
103390
  `);
@@ -102850,6 +103415,10 @@ function extractGlobalFlags(argv) {
102850
103415
  const arg = argv[i];
102851
103416
  if (arg === "--json")
102852
103417
  flags2.json = true;
103418
+ else if (arg === "--plain")
103419
+ flags2.plain = true;
103420
+ else if (arg === "--no-color")
103421
+ flags2.noColor = true;
102853
103422
  else if (arg === "--help" || arg === "-h")
102854
103423
  flags2.help = true;
102855
103424
  else if (arg === "--version" || arg === "-v")
@@ -103059,6 +103628,16 @@ var NEVER_GUARDED = new Set([
103059
103628
  "tui"
103060
103629
  ]);
103061
103630
  async function run2(argv, deps) {
103631
+ const cosmetic = extractGlobalFlags(argv);
103632
+ if (!("error" in cosmetic)) {
103633
+ deps = {
103634
+ ...deps,
103635
+ presentation: {
103636
+ plain: cosmetic.flags.plain ?? deps.presentation?.plain,
103637
+ noColor: cosmetic.flags.noColor ?? deps.presentation?.noColor
103638
+ }
103639
+ };
103640
+ }
103062
103641
  let code;
103063
103642
  try {
103064
103643
  code = await runCommand(argv, deps);
@@ -103076,7 +103655,8 @@ async function run2(argv, deps) {
103076
103655
  }
103077
103656
  const extractedForNotice = extractGlobalFlags(argv);
103078
103657
  const word3 = "error" in extractedForNotice ? undefined : canonicalCommand(extractedForNotice.rest[0] === "candle" ? extractedForNotice.rest[1] : extractedForNotice.rest[0]);
103079
- await maybeWriteUpdateNotice(deps, { command: word3 });
103658
+ if (!("error" in extractedForNotice) && !extractedForNotice.flags.help && !extractedForNotice.flags.version && word3 !== undefined && word3 !== "help" && word3 !== "completion" && word3 !== "mcp" && !(word3 === "core" && extractedForNotice.rest.includes("mcp")))
103659
+ await maybeWriteUpdateNotice(deps, { command: word3, json: extractedForNotice.flags.json });
103080
103660
  return code;
103081
103661
  }
103082
103662
  async function runCommand(argv, deps) {
@@ -103124,9 +103704,14 @@ async function runCommand(argv, deps) {
103124
103704
  return 0;
103125
103705
  }
103126
103706
  if (flags2.help) {
103127
- deps.stdout.write(renderTopic(canonicalCommand(tokens[0]) ?? "") ?? renderTopLevel());
103707
+ const style = terminalStyle(deps, "stdout", flags2.json);
103708
+ deps.stdout.write(renderTopic(canonicalCommand(tokens[0]) ?? "", style) ?? renderTopLevel(style));
103128
103709
  return 0;
103129
103710
  }
103711
+ if (tokens.length === 0) {
103712
+ deps.stderr.write(renderTopLevel(terminalStyle(deps, "stderr", flags2.json)));
103713
+ return 1;
103714
+ }
103130
103715
  const [rawCmd, sub, ...cmdArgs] = tokens;
103131
103716
  const cmd = canonicalCommand(rawCmd);
103132
103717
  if (cmd === "help" || cmd === "completion") {
@@ -103356,6 +103941,7 @@ async function buildRealDeps() {
103356
103941
  stdout: Boolean(process.stdout.isTTY),
103357
103942
  stderr: Boolean(process.stderr.isTTY)
103358
103943
  },
103944
+ columns: { stdout: process.stdout.columns, stderr: process.stderr.columns },
103359
103945
  execPath: process.execPath,
103360
103946
  argv1: process.argv[1] ?? "",
103361
103947
  platformKey: platformKey(process.platform, process.arch),