@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.
- package/README.md +2 -2
- package/dist/index.js +978 -392
- 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.
|
|
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 :
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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] =
|
|
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
|
|
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
|
|
76880
|
-
const { feeRaw, postFeeAmountRaw } = transferFeeFor(profile, raw,
|
|
76881
|
-
feeLines.push(`post-fee ${postFeeAmountRaw} raw arrives (${feeRaw} raw withheld by the mint at 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
|
|
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
|
-
|
|
83076
|
-
const { feeRaw, postFeeAmountRaw } = transferFeeFor(profile, amount,
|
|
83077
|
-
say(` ${acct.mint}: ${postFeeAmountRaw} raw will arrive; ${feeRaw} raw is withheld by the mint at 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
|
|
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
|
|
85458
|
-
if (parsed.positionals.length !== 1 || !flags["--percent"] || !/^\d+(\.\d{1,2})?$/.test(flags["--percent"]) || !(
|
|
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:
|
|
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 :
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
95164
|
+
write(`${ascii ? "OK" : "✓"} ${label}
|
|
94619
95165
|
`);
|
|
94620
95166
|
},
|
|
94621
95167
|
fail(label) {
|
|
94622
95168
|
clearSpinner();
|
|
94623
|
-
write(
|
|
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
|
-
|
|
94936
|
-
|
|
94937
|
-
|
|
94938
|
-
|
|
94939
|
-
|
|
94940
|
-
|
|
94941
|
-
|
|
94942
|
-
|
|
94943
|
-
|
|
94944
|
-
|
|
94945
|
-
|
|
94946
|
-
|
|
94947
|
-
|
|
94948
|
-
|
|
94949
|
-
|
|
94950
|
-
|
|
94951
|
-
|
|
94952
|
-
|
|
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
|
-
|
|
94955
|
-
|
|
95507
|
+
} else {
|
|
95508
|
+
deps.stdout.write(`Installed by Homebrew. Run: brew upgrade candle
|
|
94956
95509
|
`);
|
|
95510
|
+
}
|
|
95511
|
+
return 0;
|
|
94957
95512
|
}
|
|
94958
|
-
|
|
94959
|
-
|
|
94960
|
-
|
|
94961
|
-
|
|
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
|
-
|
|
94966
|
-
|
|
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
|
-
|
|
94970
|
-
|
|
94971
|
-
|
|
94972
|
-
|
|
94973
|
-
|
|
94974
|
-
|
|
94975
|
-
|
|
94976
|
-
|
|
94977
|
-
|
|
94978
|
-
|
|
94979
|
-
|
|
94980
|
-
|
|
94981
|
-
|
|
94982
|
-
|
|
94983
|
-
|
|
94984
|
-
|
|
94985
|
-
|
|
94986
|
-
|
|
94987
|
-
}
|
|
94988
|
-
|
|
94989
|
-
|
|
94990
|
-
|
|
94991
|
-
|
|
94992
|
-
|
|
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
|
-
|
|
94997
|
-
|
|
95549
|
+
} else if (order < 0) {
|
|
95550
|
+
deps.stdout.write(`candle ${CLI_VERSION}; ${target.version} available. Run: candle update
|
|
94998
95551
|
`);
|
|
94999
|
-
|
|
95000
|
-
|
|
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
|
-
|
|
95004
|
-
|
|
95005
|
-
|
|
95006
|
-
|
|
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
|
-
|
|
95011
|
-
|
|
95563
|
+
} else {
|
|
95564
|
+
deps.stdout.write(`candle ${CLI_VERSION} is up to date
|
|
95012
95565
|
`);
|
|
95566
|
+
}
|
|
95567
|
+
return 0;
|
|
95013
95568
|
}
|
|
95014
|
-
|
|
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
|
-
|
|
95020
|
-
|
|
95021
|
-
|
|
95022
|
-
|
|
95023
|
-
|
|
95024
|
-
|
|
95025
|
-
|
|
95026
|
-
|
|
95027
|
-
|
|
95028
|
-
|
|
95029
|
-
|
|
95030
|
-
|
|
95031
|
-
|
|
95032
|
-
|
|
95033
|
-
|
|
95034
|
-
|
|
95035
|
-
|
|
95036
|
-
|
|
95037
|
-
|
|
95038
|
-
|
|
95039
|
-
|
|
95040
|
-
|
|
95041
|
-
|
|
95042
|
-
|
|
95043
|
-
|
|
95044
|
-
|
|
95045
|
-
|
|
95046
|
-
|
|
95047
|
-
|
|
95048
|
-
|
|
95049
|
-
|
|
95050
|
-
|
|
95051
|
-
|
|
95052
|
-
|
|
95053
|
-
|
|
95054
|
-
|
|
95055
|
-
|
|
95056
|
-
|
|
95057
|
-
|
|
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
|
-
|
|
95061
|
-
|
|
95062
|
-
|
|
95063
|
-
|
|
95064
|
-
|
|
95065
|
-
|
|
95066
|
-
|
|
95067
|
-
|
|
95068
|
-
|
|
95069
|
-
|
|
95070
|
-
|
|
95071
|
-
|
|
95072
|
-
|
|
95073
|
-
|
|
95074
|
-
|
|
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:
|
|
95097
|
-
expectedSha256:
|
|
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:
|
|
95637
|
+
label: ""
|
|
95104
95638
|
});
|
|
95105
|
-
if (!
|
|
95639
|
+
if (!binary.ok) {
|
|
95106
95640
|
await discardAll();
|
|
95107
|
-
writeLocalFailure(deps,
|
|
95641
|
+
writeLocalFailure(deps, binary.failure, json);
|
|
95108
95642
|
return 1;
|
|
95109
95643
|
}
|
|
95110
|
-
helper =
|
|
95111
|
-
|
|
95112
|
-
|
|
95113
|
-
|
|
95114
|
-
|
|
95115
|
-
|
|
95116
|
-
|
|
95117
|
-
|
|
95118
|
-
|
|
95119
|
-
|
|
95120
|
-
|
|
95121
|
-
|
|
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(
|
|
95667
|
+
await deps.rename(binary.tmpPath, realExec);
|
|
95124
95668
|
} catch (error) {
|
|
95125
95669
|
steps.fail("installing");
|
|
95126
|
-
await
|
|
95127
|
-
|
|
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
|
-
|
|
95136
|
-
|
|
95137
|
-
|
|
95138
|
-
|
|
95139
|
-
|
|
95140
|
-
|
|
95141
|
-
|
|
95142
|
-
|
|
95143
|
-
|
|
95144
|
-
|
|
95145
|
-
|
|
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
|
-
|
|
95148
|
-
|
|
95700
|
+
} else {
|
|
95701
|
+
deps.stdout.write(`Updated candle ${CLI_VERSION} -> ${target.version}
|
|
95149
95702
|
`);
|
|
95150
|
-
|
|
95151
|
-
|
|
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
|
|
96828
|
+
let wrap;
|
|
96271
96829
|
try {
|
|
96272
|
-
|
|
96830
|
+
wrap = await wrapDekForPrf(vault.dek, prfOutput, envelope, vault.file);
|
|
96273
96831
|
} finally {
|
|
96274
96832
|
wipe(prfOutput);
|
|
96275
96833
|
}
|
|
96276
|
-
sealed = { ...envelope, wrap
|
|
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
|
-
|
|
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
|
-
|
|
96428
|
-
|
|
96429
|
-
|
|
96430
|
-
|
|
96431
|
-
|
|
96432
|
-
|
|
96433
|
-
await deps.
|
|
96434
|
-
|
|
96435
|
-
|
|
96436
|
-
|
|
96437
|
-
|
|
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
|
|
97141
|
+
let wrap;
|
|
96577
97142
|
try {
|
|
96578
|
-
|
|
97143
|
+
wrap = await wrapDekForPrf(vault.dek, prfOutput, envelope, vault.file);
|
|
96579
97144
|
} finally {
|
|
96580
97145
|
wipe(prfOutput);
|
|
96581
97146
|
}
|
|
96582
|
-
const sealed = { ...envelope, wrap
|
|
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
|
|
97343
|
+
let wrap;
|
|
96779
97344
|
try {
|
|
96780
|
-
|
|
97345
|
+
wrap = await wrapDekForKek(vault.dek, kek, envelope, vault.file);
|
|
96781
97346
|
} finally {
|
|
96782
97347
|
wipe(kek);
|
|
96783
97348
|
}
|
|
96784
|
-
sealed = { ...envelope, wrap
|
|
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
|
|
96900
|
-
const sealedEnvelope = { ...envelope, wrap
|
|
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
|
|
97337
|
-
const sealed = { ...envelope, wrap
|
|
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
|
|
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
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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),
|