@agentgrant.cash/cli 1.4.2 → 1.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/cli/commands/agent.d.ts +8 -0
  2. package/dist/cli/commands/auth.d.ts +20 -0
  3. package/dist/cli/commands/meta.d.ts +2 -0
  4. package/dist/cli/commands/money.d.ts +2 -0
  5. package/dist/cli/commands/onboard.d.ts +2 -0
  6. package/dist/cli/commands/portfolio.d.ts +14 -0
  7. package/dist/cli/index.d.ts +2 -0
  8. package/dist/cli/money-helpers.d.ts +100 -0
  9. package/dist/cli/perfolio-commands/account.d.ts +35 -0
  10. package/dist/cli/perfolio-commands/borrow.d.ts +2 -0
  11. package/dist/cli/perfolio-commands/discover.d.ts +2 -0
  12. package/dist/cli/perfolio-commands/earn.d.ts +9 -0
  13. package/dist/cli/perfolio-commands/hyperliquid.d.ts +32 -0
  14. package/dist/cli/perfolio-commands/loans.d.ts +8 -0
  15. package/dist/cli/perfolio-commands/market.d.ts +11 -0
  16. package/dist/cli/perfolio-commands/polymarket.d.ts +29 -0
  17. package/dist/cli/perfolio-commands/session.d.ts +2 -0
  18. package/dist/cli/perfolio-commands/spending.d.ts +14 -0
  19. package/dist/cli/perfolio-commands/trade.d.ts +2 -0
  20. package/dist/cli/perfolio-commands/tx.d.ts +2 -0
  21. package/dist/lib/agent-client.d.ts +78 -0
  22. package/dist/lib/amounts.d.ts +14 -0
  23. package/dist/lib/assets.d.ts +12 -0
  24. package/dist/lib/auth-session.d.ts +57 -0
  25. package/dist/lib/client.d.ts +295 -0
  26. package/dist/lib/config.d.ts +13 -0
  27. package/dist/lib/context.d.ts +21 -0
  28. package/dist/lib/currency.d.ts +70 -0
  29. package/dist/lib/device.d.ts +78 -0
  30. package/dist/lib/dotenv.d.ts +12 -0
  31. package/dist/lib/errors.d.ts +33 -0
  32. package/dist/lib/format.d.ts +5 -0
  33. package/dist/lib/index.d.ts +22 -0
  34. package/dist/lib/kyc-status.d.ts +10 -0
  35. package/dist/lib/money-client.d.ts +66 -0
  36. package/dist/lib/money-input.d.ts +97 -0
  37. package/dist/lib/output.d.ts +22 -0
  38. package/dist/lib/polygon-balance.d.ts +43 -0
  39. package/dist/lib/portfolio-format.d.ts +79 -0
  40. package/dist/lib/relay.d.ts +8 -0
  41. package/dist/lib/sign.d.ts +11 -0
  42. package/dist/lib/tx-wait.d.ts +19 -0
  43. package/dist/lib/types.d.ts +377 -0
  44. package/dist/lib/verify.d.ts +21 -0
  45. package/package.json +7 -1
  46. package/skills/grant-cash/SKILL.md +19 -1
@@ -0,0 +1,8 @@
1
+ import type { Command } from "commander";
2
+ /**
3
+ * Agent side — pay-per-use services routed to the Agent-mode backend. Every
4
+ * spend is owned by the session (delegated, revocable, guardrailed); the CLI
5
+ * carries only the API key. Verbs mirror the agent CLI so a model that knows it
6
+ * drives this with no relearning: search → check → fetch.
7
+ */
8
+ export declare function registerAgent(program: Command): void;
@@ -0,0 +1,20 @@
1
+ import type { Command } from "commander";
2
+ import { type Credentials, type DeviceCredentials } from "../../lib/index.js";
3
+ /**
4
+ * `grant login` — one connect, one credential (unified auth).
5
+ *
6
+ * Runs a SINGLE perfolio device handshake: `cli/device/*`, the one-time `sid`
7
+ * relayed to the browser via the connect URL. The logged-in browser completes it
8
+ * server-side; the CLI polls until perfolio returns the credential. The result is
9
+ * the perfolio-signed access JWT (`accessJwt`) plus the opaque `pfr_` refresh
10
+ * token — that ONE JWT is sent as `Authorization: Bearer` to BOTH backends
11
+ * (money + agent). The old agent-side `grant_live_` PKCE leg is retired: there is
12
+ * no second handshake, no `--agent-key`, no separate spending credential.
13
+ */
14
+ /**
15
+ * Map a completed perfolio device flow to the stored credential. A fresh login
16
+ * REPLACES the whole credentials file — no stale agent key is carried over (the
17
+ * `grant_live_` leg is gone) — so the file holds exactly one perfolio credential.
18
+ */
19
+ export declare function loginCredentials(result: DeviceCredentials): Credentials;
20
+ export declare function registerAuth(program: Command): void;
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerMeta(program: Command): void;
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerMoney(program: Command): void;
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerOnboard(program: Command): void;
@@ -0,0 +1,14 @@
1
+ import type { Command } from "commander";
2
+ import { type Ctx, type PortfolioExtras } from "../../lib/index.js";
3
+ /**
4
+ * Prediction available cash + open-bet value. Best-effort; {} on any failure.
5
+ *
6
+ * Available cash comes from the SAME source the predictions screen uses: the
7
+ * backend `/polymarket/positions` `cashUsd` field (an on-chain pUSD read done
8
+ * server-side via a reliable RPC). For older backends that don't return it we
9
+ * fall back to reading the deposit wallet's pUSD balance directly from a public
10
+ * Polygon RPC. Both the positions call and the deposit-wallet lookup run in
11
+ * parallel so the fallback adds no latency in the common case.
12
+ */
13
+ export declare function gatherPredictions(ctx: Ctx): Promise<PortfolioExtras>;
14
+ export declare function registerPortfolio(program: Command): void;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,100 @@
1
+ import { Command } from "commander";
2
+ import { PerfolioClient, AssetResolver, type Credentials, type TxResult, type SignTaskInput, type DisplayPrefs, type MoneyResolution, type CashTargetResolution, type ResolvedAsset } from "../lib/index.js";
3
+ /**
4
+ * Money-side command context — bridges the merged Grant Cash credentials to the
5
+ * ported Perfolio money engine.
6
+ *
7
+ * The standalone Perfolio CLI stored a FLAT credential ({ token, refreshToken,
8
+ * … }); Grant Cash stores a MERGED file ({ money: {...}, agent: {...} }). Both
9
+ * mint the same `pfk_` device token against the same Perfolio backend, so the
10
+ * only adaptation needed is reading/writing the `money` slot. Everything
11
+ * downstream (PerfolioClient, resolvers, sign flow) is reused verbatim.
12
+ */
13
+ export interface Ctx {
14
+ client: PerfolioClient;
15
+ creds: Credentials;
16
+ credsPath: string;
17
+ json: boolean;
18
+ }
19
+ export declare function buildCtx(cmd: Command): Ctx;
20
+ export declare function requireAuth(ctx: Ctx): Credentials;
21
+ /** Load assets once and build a resolver. */
22
+ export declare function resolver(ctx: Ctx): Promise<AssetResolver>;
23
+ /**
24
+ * Resolve a user-entered CASH amount into a USD decimal string for the backend.
25
+ * A bare number is interpreted in the user's display currency; an explicit
26
+ * currency prefix ("$10", "₹50", "100 EUR") always wins. Non-USD amounts are
27
+ * converted via the live FX rate; refuses (throws) if that rate is unavailable.
28
+ */
29
+ /**
30
+ * Force a bare numeric amount to USD by prefixing `$`. An amount the user already
31
+ * qualified with a currency ($/₹/€/ISO code) is left untouched — an explicit
32
+ * currency always wins (same rule money-input enforces). So `--usd` + "50" → "$50",
33
+ * but `--usd` + "₹50" stays ₹50.
34
+ */
35
+ export declare function forceUsdAmount(raw: string): string;
36
+ export declare function resolveCash(ctx: Ctx, raw: string, decimals: number, opts?: {
37
+ usd?: boolean;
38
+ }): Promise<MoneyResolution>;
39
+ /**
40
+ * Live USD unit price for a spot asset, used to size a cash-target sell:
41
+ * gold → gold spot; btc/eth → /prices/crypto; stable → 1:1. Throws if the
42
+ * price can't be resolved (caller refuses to guess a quantity).
43
+ */
44
+ export declare function assetUsdPrice(ctx: Ctx, asset: ResolvedAsset): Promise<number>;
45
+ /**
46
+ * Resolve a cash-denominated SELL/CONVERT target ("sell ₹100 of gold") into a
47
+ * quantity of the asset to sell. Refuses (throws) if either the FX rate or the
48
+ * asset price is unavailable — never guesses a money quantity.
49
+ */
50
+ export declare function resolveCashTargetQty(ctx: Ctx, asset: ResolvedAsset, cashRaw: string): Promise<CashTargetResolution>;
51
+ /**
52
+ * Resolve the user's money-display preferences (currency, USD-vs-local, gold
53
+ * unit, experience mode) plus the live FX rate. Any failure falls back to safe
54
+ * USD defaults rather than breaking a read command.
55
+ */
56
+ export declare function loadDisplayPrefs(ctx: Ctx): Promise<DisplayPrefs>;
57
+ /**
58
+ * Build the money-side SUCCESS envelope. The whole CLI — money and agent sides —
59
+ * speaks one contract: `{ ok: true, ...data }`. A plain object is spread in; an
60
+ * array / primitive is nested under `data` (spreading an array would splay its
61
+ * indices as keys). A redundant top-level `success` marker is dropped so `ok` is
62
+ * the single source of truth (closes the "trade cmds use {success:true}" gap).
63
+ * Pure + exported for unit testing.
64
+ */
65
+ export declare function buildOutPayload(data: unknown): Record<string, unknown>;
66
+ /** Build the money-side FAILURE envelope: `{ ok:false, error:{code,message,recoverable} }`. */
67
+ export declare function buildErrorPayload(msg: string, opts?: {
68
+ code?: string;
69
+ recoverable?: boolean;
70
+ }): {
71
+ ok: false;
72
+ error: {
73
+ code: string;
74
+ message: string;
75
+ recoverable: boolean;
76
+ };
77
+ };
78
+ export declare function out(ctx: Ctx, human: string, data: unknown): void;
79
+ export declare function fail(msg: string, json: boolean, opts?: {
80
+ code?: string;
81
+ recoverable?: boolean;
82
+ }): never;
83
+ /**
84
+ * Report the outcome of a write. With `wait` (the default), poll the tx to a
85
+ * terminal state and report success/failure honestly — never "submitted". A
86
+ * failed tx exits non-zero; a timeout is reported as still-pending.
87
+ */
88
+ export declare function reportTx(ctx: Ctx, res: TxResult, wait: boolean, label: string): Promise<void>;
89
+ /**
90
+ * Browser-sign hand-off for user-signed operations (withdrawal, gift, HL agent
91
+ * approval). The CLI prepared a self-describing sign task; here we hand it to
92
+ * the relay, open the browser for the user to sign + execute, then (for on-chain
93
+ * ops) confirm the resulting tx. The agent never holds a key — the user's Privy
94
+ * wallet signs.
95
+ */
96
+ export declare function runSignFlow(ctx: Ctx, opts: {
97
+ task: SignTaskInput;
98
+ label: string;
99
+ confirmTx?: boolean;
100
+ }): Promise<boolean>;
@@ -0,0 +1,35 @@
1
+ import { Command } from 'commander';
2
+ import { type FiatAccount } from '../../lib/index.js';
3
+ export type DepositGate = {
4
+ ok: true;
5
+ status: string;
6
+ } | {
7
+ ok: false;
8
+ reason: 'KYC_REQUIRED' | 'PROVISIONING';
9
+ status: string;
10
+ };
11
+ /** Decide whether deposit details can be shown, or what's blocking. */
12
+ export declare function decideDepositGate(status: unknown, accounts: FiatAccount[]): DepositGate;
13
+ /**
14
+ * A real fiat deposit target = a non-empty IBAN or account number. Crypto accounts
15
+ * (USDT_ETH/USDC_ETH) come back with a bankDetails object full of EMPTY strings, so a
16
+ * mere "object exists / has keys" check is not enough — test the actual wire target.
17
+ */
18
+ export declare function hasDepositDetails(a: FiatAccount): boolean;
19
+ /** Keep only accounts you can actually wire money to (skips crypto accounts), optionally filtered by currency. */
20
+ export declare function mapDepositAccounts(accounts: FiatAccount[], currency?: string): FiatAccount[];
21
+ /** Render a single account's bank-transfer details, tolerating both field-name variants. */
22
+ export declare function formatBankDetails(a: FiatAccount): string;
23
+ interface GraceOpts {
24
+ timeoutMs: number;
25
+ intervalMs: number;
26
+ sleep?: (ms: number) => Promise<void>;
27
+ now?: () => number;
28
+ }
29
+ /** Poll a KYC status fetcher until approved or a hard timeout. Never blocks past timeoutMs. */
30
+ export declare function gracePollKyc(getStatus: () => Promise<unknown>, opts: GraceOpts): Promise<{
31
+ approved: boolean;
32
+ status: string;
33
+ }>;
34
+ export declare function registerAccount(program: Command): void;
35
+ export {};
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerBorrow(program: Command): void;
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerDiscover(program: Command): void;
@@ -0,0 +1,9 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Whether an earn asset is a stablecoin (cash). Stable amounts are MONEY — entered
4
+ * in the user's currency and FX-resolved to USD (so "100" = ₹100 for an INR user,
5
+ * consistent with buy/borrow). Non-stable assets (WETH) keep asset-QUANTITY input
6
+ * (0.5 WETH = 0.5 WETH), like selling an asset by quantity. Exported for testing.
7
+ */
8
+ export declare function isStableEarnAsset(symbol: string): boolean;
9
+ export declare function registerEarn(program: Command): void;
@@ -0,0 +1,32 @@
1
+ import { Command } from 'commander';
2
+ import { type DisplayPrefs } from '../../lib/index.js';
3
+ /**
4
+ * Hyperliquid's minimum order value ($10 notional). Used for a client-side
5
+ * pre-submit warning; the backend remains authoritative and re-validates.
6
+ */
7
+ export declare const HL_MIN_ORDER_USD = 10;
8
+ /**
9
+ * Pre-submit minimum-order check. Returns a currency-aware warning string when
10
+ * `sizeUsd` is below Hyperliquid's $10 minimum, else null. Exported for testing.
11
+ *
12
+ * Renders the minimum in the user's display currency (e.g. ₹800 for an INR user
13
+ * at rate 80) so CLI traders see an amount in their own terms rather than a bare
14
+ * "$10" — the gap that left CLI users guessing why orders were rejected.
15
+ */
16
+ export declare function minOrderHint(sizeUsd: number, prefs: DisplayPrefs, minUsd?: number): string | null;
17
+ /**
18
+ * Human-readable Hyperliquid account summary. Balance/equity/margin come back as
19
+ * USD strings; localize them. Exported for testing. Shape (from backend
20
+ * GET /api/hl/status): { isSetup, isApproved, balance, equity, availableMargin, marginUsed }.
21
+ */
22
+ export declare function formatHlStatus(data: Record<string, unknown>, prefs: DisplayPrefs): string;
23
+ /**
24
+ * Hyperliquid perpetual trading.
25
+ *
26
+ * Reads + trades are AUTONOMOUS — the backend signs with a server-held,
27
+ * encrypted per-user agent key (HL's native API-wallet). The only step that
28
+ * needs the user is the one-time agent approval (`hl setup`), which signs an
29
+ * EIP-712 ApproveAgent action in the browser via the sign relay. After that,
30
+ * open/close/leverage/tpsl all run with no further interaction.
31
+ */
32
+ export declare function registerHyperliquid(program: Command): void;
@@ -0,0 +1,8 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Loan + funding-address commands ported from the standalone Perfolio CLI. The
4
+ * combined `grant portfolio` / `grant balance` view (which folds in predictions
5
+ * + earn) lives separately in commands/portfolio.ts — these are the
6
+ * loan-detail and deposit-address commands that have no combined-view overlap.
7
+ */
8
+ export declare function registerLoans(program: Command): void;
@@ -0,0 +1,11 @@
1
+ import { Command } from 'commander';
2
+ import { type DisplayPrefs } from '../../lib/index.js';
3
+ /**
4
+ * If a price feed is flagged stale, return a loud inline warning (e.g. " ⚠ stale
5
+ * (16h old)"); '' when fresh/unknown. A quiet `stale:true` boolean was easy to miss
6
+ * — acting on a 16-hour-old gold mark is a real risk, so it must be visible.
7
+ */
8
+ export declare function staleNote(src: unknown): string;
9
+ /** Human-readable price board, localized to the user's currency + gold unit. Exported for tests. */
10
+ export declare function formatPrices(gold: unknown, crypto: unknown, prefs: DisplayPrefs): string;
11
+ export declare function registerMarket(program: Command): void;
@@ -0,0 +1,29 @@
1
+ import { Command } from 'commander';
2
+ import { type DisplayPrefs, type PolymarketMarket, type PolymarketPositionsResult, type PolymarketActivity } from '../../lib/index.js';
3
+ /**
4
+ * True if an error means the betting path is unavailable, so BOTH `--side yes`
5
+ * and `--side no` surface the same friendly line. The configured-off state comes
6
+ * back as 503 / POLYMARKET_TRADING_NOT_CONFIGURED, but the disabled `no` path was
7
+ * observed returning a bare upstream 502 (and 504 is the same class of gateway
8
+ * outage) — treat those as unavailable too rather than leaking a raw "HTTP 502".
9
+ * Exported for unit testing.
10
+ */
11
+ export declare function isTradingDisabled(err: unknown): boolean;
12
+ /** Render a market list into human-readable lines. Exported for unit testing. */
13
+ export declare function formatMarkets(markets: PolymarketMarket[], count: number): string;
14
+ /**
15
+ * Render the prediction portfolio: a per-position table showing where each
16
+ * position has moved (entry → now, %) plus portfolio totals. Money is localized
17
+ * to the user's currency (defaults to USD for callers without prefs). Exported for tests.
18
+ */
19
+ export declare function formatPositions(r: PolymarketPositionsResult, prefs?: DisplayPrefs): string;
20
+ /** Render prediction trade history (most recent first). Money localized. Exported for tests. */
21
+ export declare function formatActivity(activity: PolymarketActivity[], prefs?: DisplayPrefs): string;
22
+ /**
23
+ * Friendly one-liner for a funding-intent status. The backend relays an opaque
24
+ * tracker payload, so we defensively pull a `status` string (top-level or nested
25
+ * under `data`) and map the common terminal states to plain language; anything
26
+ * unrecognized degrades to "still on its way". Exported for tests.
27
+ */
28
+ export declare function formatFundStatus(raw: unknown): string;
29
+ export declare function registerPolymarket(program: Command): void;
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerSession(program: Command): void;
@@ -0,0 +1,14 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Spending account — the agent's pay-per-use balance (USDC on Base). It is
4
+ * funded by bridging cash out of the main account, mirroring the other
5
+ * account-to-account transfers already in the CLI:
6
+ * - cash ↔ trading → `hl deposit` / `hl withdraw`
7
+ * - cash ↔ predictions → `polymarket deposit` / `polymarket withdraw`
8
+ * - cash → spending → `spending deposit` (this file)
9
+ *
10
+ * The reverse (spending → cash) has NO backend route — the spending balance is
11
+ * receive-only, exactly as in the web app (AccountTransferFlow) — so we offer
12
+ * only the inbound transfer and don't fabricate a withdraw.
13
+ */
14
+ export declare function registerSpending(program: Command): void;
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerTrade(program: Command): void;
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerTx(program: Command): void;
@@ -0,0 +1,78 @@
1
+ import type { TokenHolder } from "./auth-session.js";
2
+ export interface AgentClientOpts {
3
+ agent: string;
4
+ /** Shared holder of the perfolio access JWT + refresh (drives refresh-on-401). */
5
+ auth?: TokenHolder;
6
+ fetchImpl?: typeof fetch;
7
+ }
8
+ export declare class AgentClient {
9
+ private base;
10
+ private auth?;
11
+ private f;
12
+ constructor(opts: AgentClientOpts);
13
+ get connected(): boolean;
14
+ private request;
15
+ /**
16
+ * Spendable funds + session state. Hits `GET /balance` (api-key auth), which
17
+ * returns the credit-aware status shape (active + USDC/USDT, not_set_up,
18
+ * credit_exhausted, or sign-up credit). NOT `/me` — that route is JWT-only and
19
+ * carries identity, not funds.
20
+ */
21
+ balance(): Promise<Record<string, unknown>>;
22
+ /**
23
+ * The chain-agnostic spendable split — sign-up free credit + funds you added +
24
+ * total. Hits `GET /credit` (dual-auth, so the api-key works). This is the same
25
+ * figure the app shows on the Spending screen; `balance()` (/balance) carries
26
+ * the session status, so the portfolio uses both.
27
+ */
28
+ credit(): Promise<Record<string, unknown>>;
29
+ /**
30
+ * Find a payable endpoint in the curated catalog. Hits `GET /marketplace/x402`
31
+ * (the grouped, public catalog route) — NOT `/marketplace`, which does not
32
+ * exist on the backend.
33
+ */
34
+ search(query: string, limit?: number): Promise<Record<string, unknown>>;
35
+ /** Exact pay contract (requestSchema) for a URL — no spend. */
36
+ check(url: string): Promise<Record<string, unknown>>;
37
+ /** Recent transactions, newest first. */
38
+ transactions(limit?: number): Promise<Record<string, unknown>>;
39
+ /** One transaction by id. */
40
+ tx(id: string): Promise<Record<string, unknown>>;
41
+ /** Exact POST /x402/pay contract for a catalog entry by slug (requestSchema + price). No spend. */
42
+ schema(slug: string): Promise<Record<string, unknown>>;
43
+ /** Ingest any x402 origin's endpoints into the catalog so they become searchable/payable. */
44
+ discover(origin: string): Promise<Record<string, unknown>>;
45
+ /** Claim an invite / bonus credit by code. Mints or tops up the session's spendable funds. */
46
+ redeem(code: string): Promise<Record<string, unknown>>;
47
+ /** One-time link (+ code) to secure a full account and add funds. Gated by the session key. */
48
+ linkCode(): Promise<{
49
+ url: string;
50
+ code: string;
51
+ expiresAt: number;
52
+ }>;
53
+ /**
54
+ * Pay + run an endpoint via the session. Body matches `POST /x402/pay`'s
55
+ * schema: `method` is required (uppercased), `expectedPriceMinor` (the spend
56
+ * cap) and `asset` are required, and `body` is a RAW STRING passthrough — not
57
+ * a parsed object. The agent never signs; the session owns the spend.
58
+ */
59
+ fetchPay(b: {
60
+ url: string;
61
+ method: string;
62
+ expectedPriceMinor: string;
63
+ asset: string;
64
+ body?: string;
65
+ }): Promise<Record<string, unknown>>;
66
+ /**
67
+ * Gasless transfer to an address. Hits `POST /transactions` (there is no
68
+ * `/transfer` route); body is `{ asset, amount, recipient }` where `amount` is
69
+ * the minor-units decimal string.
70
+ */
71
+ transfer(b: {
72
+ recipient: string;
73
+ amount: string;
74
+ asset: string;
75
+ }): Promise<Record<string, unknown>>;
76
+ /** Stop all spend on this key — revoke the session (terminal). */
77
+ revoke(): Promise<Record<string, unknown>>;
78
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Validate a human decimal amount without converting it. The Perfolio backend
3
+ * accepts decimal strings directly (it runs parseUnits server-side), so the
4
+ * CLI must NOT pre-convert to base units — passing base units would be
5
+ * misread by the Enso heuristic. This gives the same early "too many decimal
6
+ * places" feedback toBaseUnits would, then returns the trimmed decimal string.
7
+ */
8
+ export declare function assertDecimal(value: string, decimals: number): string;
9
+ /** Convert a human decimal string to integer base units (no floats). */
10
+ export declare function toBaseUnits(value: string, decimals: number): string;
11
+ /** Convert integer base units back to a trimmed decimal string. */
12
+ export declare function fromBaseUnits(base: string, decimals: number): string;
13
+ /** Friendly display. Cash renders as currency; other assets as "<n> <friendly>". */
14
+ export declare function formatAmount(base: string, decimals: number, friendly: string): string;
@@ -0,0 +1,12 @@
1
+ import type { AssetDto, ResolvedAsset } from './types.js';
2
+ export declare const FRIENDLY_TERMS: readonly ["gold", "cash", "bitcoin", "ethereum"];
3
+ export declare class AssetResolver {
4
+ private byFriendly;
5
+ private friendlyBySymbol;
6
+ constructor(assets: AssetDto[]);
7
+ resolve(input: string): ResolvedAsset;
8
+ /** Friendly word for a backend symbol; falls back to the symbol itself. */
9
+ label(symbol: string): string;
10
+ /** True when a backend symbol maps to a v1 friendly term (canonical asset). */
11
+ isFriendly(symbol: string): boolean;
12
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Unified-auth shared token holder.
3
+ *
4
+ * `grant login` mints ONE perfolio-signed access JWT; both backend clients
5
+ * (money + agent) send it as `Authorization: Bearer`. A single {@link TokenHolder}
6
+ * owns that JWT and the opaque `pfr_` refresh token, so a 401 from EITHER backend
7
+ * refreshes once and BOTH clients pick up the new JWT.
8
+ *
9
+ * The refresh is single-flight (concurrent 401s coalesce into one network call)
10
+ * and stale-aware ({@link TokenHolder.refreshIfStale}): a client that 401s with a
11
+ * token another client has already rotated past just uses the current token
12
+ * instead of spending the (rotating) refresh token a second time.
13
+ */
14
+ export interface RefreshedTokens {
15
+ accessJwt: string;
16
+ refreshToken: string;
17
+ expiresAt: number;
18
+ }
19
+ /** Exchanges the current `pfr_` refresh token for a fresh access JWT (+ rotated refresh). */
20
+ export type RefreshFn = (refreshToken: string) => Promise<RefreshedTokens>;
21
+ export interface TokenHolderOpts {
22
+ accessJwt?: string;
23
+ refreshToken?: string;
24
+ refresh: RefreshFn;
25
+ /** Persist rotated tokens back to disk so the refresh survives across invocations. */
26
+ onRefresh?: (tokens: RefreshedTokens) => void;
27
+ }
28
+ export declare class TokenHolder {
29
+ private accessJwt?;
30
+ private refreshToken?;
31
+ private readonly doRefresh;
32
+ private readonly onRefresh?;
33
+ /** The in-flight refresh, if any — coalesces concurrent callers (single-flight). */
34
+ private inFlight?;
35
+ constructor(opts: TokenHolderOpts);
36
+ /** The current access JWT to send as `Authorization: Bearer`. */
37
+ get token(): string | undefined;
38
+ /**
39
+ * Refresh only if the caller's token is still the live one. If another client
40
+ * already rotated the token (current !== usedToken), return the current token
41
+ * without a second refresh — the caller should just retry with it.
42
+ */
43
+ refreshIfStale(usedToken?: string): Promise<string | undefined>;
44
+ /**
45
+ * Rotate the access JWT via the refresh token. Single-flight: concurrent calls
46
+ * share one network round-trip. Returns the new JWT, or `undefined` if there is
47
+ * no refresh token or the refresh failed (the old token is left in place).
48
+ */
49
+ refresh(): Promise<string | undefined>;
50
+ }
51
+ /**
52
+ * `POST {api}/cli/token/refresh` — exchange a `pfr_` refresh token for a fresh
53
+ * perfolio access JWT. Per CONTRACT v1 the payload is `{ accessJwt, refreshToken,
54
+ * expiresAt }`; it arrives inside perfolio's standard `{ success, data }` envelope.
55
+ * The legacy `token` field is accepted as a fallback during the migration window.
56
+ */
57
+ export declare function refreshAccessToken(api: string, refreshToken: string, f?: typeof fetch): Promise<RefreshedTokens>;