@agentgrant.cash/cli 1.0.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -141,7 +141,7 @@ async function gatherExtras(ctx) {
141
141
  }
142
142
  async function gather(cmd) {
143
143
  const ctx = buildContext(cmd);
144
- const [prefsR, moneyR, extrasR, agentR] = await Promise.allSettled([
144
+ const [prefsR, moneyR, extrasR, agentR, creditR] = await Promise.allSettled([
145
145
  ctx.money.connected ? loadDisplayPrefs(ctx) : Promise.resolve(DEFAULT_PREFS),
146
146
  ctx.money.connected
147
147
  ? ctx.money.getPortfolio("full")
@@ -150,6 +150,9 @@ async function gather(cmd) {
150
150
  ctx.agent.connected
151
151
  ? ctx.agent.balance()
152
152
  : Promise.resolve(null),
153
+ ctx.agent.connected
154
+ ? ctx.agent.credit()
155
+ : Promise.resolve(null),
153
156
  ]);
154
157
  return {
155
158
  ctx,
@@ -159,6 +162,7 @@ async function gather(cmd) {
159
162
  moneyFailed: moneyR.status === "rejected",
160
163
  agent: agentR.status === "fulfilled" ? agentR.value : null,
161
164
  agentFailed: agentR.status === "rejected",
165
+ credit: creditR.status === "fulfilled" ? creditR.value : null,
162
166
  };
163
167
  }
164
168
  /** Indent every line of a block by two spaces. */
@@ -171,7 +175,7 @@ function indent(block) {
171
175
  export function registerPortfolio(program) {
172
176
  const run = async (cmd) => {
173
177
  const g = await gather(cmd);
174
- const { ctx, prefs, money, extras, moneyFailed, agent, agentFailed } = g;
178
+ const { ctx, prefs, money, extras, moneyFailed, agent, agentFailed, credit } = g;
175
179
  emit(ctx, {
176
180
  investments: money
177
181
  ? {
@@ -186,6 +190,8 @@ export function registerPortfolio(program) {
186
190
  }
187
191
  : null,
188
192
  spending: agent,
193
+ // The free-credit / wallet split (parity with the app's Spending screen).
194
+ credit,
189
195
  }, () => {
190
196
  if (!ctx.money.connected && !ctx.agent.connected)
191
197
  return "Not connected yet. Run `grant login`.";
@@ -208,7 +214,7 @@ export function registerPortfolio(program) {
208
214
  lines.push(ui.dim(" Not connected."));
209
215
  }
210
216
  else if (agent) {
211
- lines.push(formatAgentFunds(agent));
217
+ lines.push(formatAgentFunds(agent, credit));
212
218
  }
213
219
  else {
214
220
  lines.push(ui.amber(` ${agentFailed ? "Temporarily unavailable." : "—"}`));
package/dist/cli/index.js CHANGED
@@ -37,6 +37,7 @@ import { registerAccount } from "./perfolio-commands/account.js";
37
37
  import { registerTx } from "./perfolio-commands/tx.js";
38
38
  import { registerSession } from "./perfolio-commands/session.js";
39
39
  import { registerLoans } from "./perfolio-commands/loans.js";
40
+ import { registerSpending } from "./perfolio-commands/spending.js";
40
41
  let version = "0.0.0";
41
42
  try {
42
43
  version =
@@ -66,6 +67,7 @@ registerEarn(program);
66
67
  registerHyperliquid(program);
67
68
  registerPolymarket(program);
68
69
  registerLoans(program);
70
+ registerSpending(program);
69
71
  registerAccount(program);
70
72
  registerTx(program);
71
73
  registerSession(program);
@@ -0,0 +1,57 @@
1
+ import { buildCtx, requireAuth, out, fail, resolveCash } from '../money-helpers.js';
2
+ import { describeCashResolution } from '../../lib/index.js';
3
+ import { waitForTx } from '../../lib/verify.js';
4
+ /**
5
+ * Spending account — the agent's pay-per-use balance (USDC on Base). It is
6
+ * funded by bridging cash out of the main account, mirroring the other
7
+ * account-to-account transfers already in the CLI:
8
+ * - cash ↔ trading → `hl deposit` / `hl withdraw`
9
+ * - cash ↔ predictions → `polymarket deposit` / `polymarket withdraw`
10
+ * - cash → spending → `spending deposit` (this file)
11
+ *
12
+ * The reverse (spending → cash) has NO backend route — the spending balance is
13
+ * receive-only, exactly as in the web app (AccountTransferFlow) — so we offer
14
+ * only the inbound transfer and don't fabricate a withdraw.
15
+ */
16
+ export function registerSpending(program) {
17
+ const spending = program
18
+ .command('spending')
19
+ .description('Your agent spending account (pay-per-use balance)');
20
+ spending
21
+ .command('deposit')
22
+ .alias('add')
23
+ .alias('top-up')
24
+ .description('Move cash from your account into your agent spending balance (autonomous — no browser)')
25
+ .requiredOption('--amount <amount>', 'cash to move into spending — in your display currency by default; prefix $/₹ to force one, or pass --usd')
26
+ .option('--usd', 'interpret --amount as US dollars, ignoring your display currency')
27
+ .option('--no-wait', 'return immediately without waiting for the bridge tx to confirm')
28
+ .action(async (opts, cmd) => {
29
+ const ctx = buildCtx(cmd);
30
+ requireAuth(ctx);
31
+ // Autonomous: POST /tx/bridge-base builds the USDT-approve + Across bridge
32
+ // (ETH USDT → Base USDC) and executes them via the Biconomy session grant
33
+ // (agent-signed) — no browser. Two-stage settlement, same as `hl deposit`:
34
+ // the bridge tx confirms the Ethereum leg, but Across fills onto Base a few
35
+ // MINUTES later. So a confirmed tx means the funds are on their way, NOT
36
+ // yet spendable — we confirm the tx and say so honestly (no false "done").
37
+ const m = await resolveCash(ctx, opts.amount, 6, { usd: !!opts.usd });
38
+ const wait = opts.wait !== false;
39
+ const label = `Move ${describeCashResolution(m)} cash → spending`;
40
+ const res = await ctx.client.bridgeToBase({ amountUsdt: m.usdString });
41
+ const txHash = res?.txHash;
42
+ const tail = txHash ? ` (tx ${txHash})` : '';
43
+ if (!wait) {
44
+ out(ctx, `Submitted: ${label}${tail}.\nFunds arrive in your spending balance a few minutes after the bridge tx confirms — check \`grant portfolio\`.`, { submitted: true, confirmed: false, status: res?.status ?? 'submitted', txHash, quote: res?.quote });
45
+ return;
46
+ }
47
+ const txResult = await waitForTx(ctx.client, txHash);
48
+ if (txResult.status === 'failed')
49
+ fail(`${label} failed on-chain${tail}.`, ctx.json);
50
+ if (txResult.status === 'success') {
51
+ out(ctx, `✓ ${label}: bridge tx confirmed${tail}. Your spending balance updates in a few minutes once the funds land on Base — check \`grant portfolio\`.`, { submitted: true, confirmed: true, settledOnBase: false, status: 'success', txHash, quote: res?.quote });
52
+ }
53
+ else {
54
+ out(ctx, `${label}: submitted${tail} but not yet confirmed. Re-check with \`grant tx status${txHash ? ` ${txHash}` : ''}\`.`, { submitted: true, confirmed: false, settledOnBase: false, status: txResult.status, txHash, quote: res?.quote });
55
+ }
56
+ });
57
+ }
@@ -74,6 +74,15 @@ export class AgentClient {
74
74
  balance() {
75
75
  return this.request("GET", "/balance", { auth: true });
76
76
  }
77
+ /**
78
+ * The chain-agnostic spendable split — sign-up free credit + funds you added +
79
+ * total. Hits `GET /credit` (dual-auth, so the api-key works). This is the same
80
+ * figure the app shows on the Spending screen; `balance()` (/balance) carries
81
+ * the session status, so the portfolio uses both.
82
+ */
83
+ credit() {
84
+ return this.request("GET", "/credit", { auth: true });
85
+ }
77
86
  /**
78
87
  * Find a payable endpoint in the curated catalog. Hits `GET /marketplace/x402`
79
88
  * (the grouped, public catalog route) — NOT `/marketplace`, which does not
@@ -260,6 +260,19 @@ export class PerfolioClient {
260
260
  * so its "submitted" was a false positive that moved no funds.)
261
261
  */
262
262
  hlDeposit(b) { return this.post(this.urls.api, '/tx/bridge-deposit', b); }
263
+ /**
264
+ * Autonomous cash → spending bridge. Hits POST /tx/bridge-base, which builds
265
+ * the USDT-approve + Across bridge (ETH USDT → Base USDC) and executes them via
266
+ * the Biconomy defi session grant (agent-signed) — no user signature, no
267
+ * browser. Funds the Grant Cash agent's USDC spending balance on Base. A
268
+ * returned txHash means the Ethereum leg was SUBMITTED; the spending balance
269
+ * updates on Base a few minutes later once Across fills. Mirrors hlDeposit
270
+ * (/tx/bridge-deposit) — same Across Swap API + session execution; only the
271
+ * destination (Base 8453) and output token (USDC) differ.
272
+ */
273
+ bridgeToBase(b) {
274
+ return this.post(this.urls.api, '/tx/bridge-base', b);
275
+ }
263
276
  /** Autonomous HL → Ethereum withdrawal (server-signs via Privy; no browser). */
264
277
  hlWithdraw(b) {
265
278
  return this.post(this.urls.api, '/hl/withdraw-to-evm/execute', b);
@@ -189,8 +189,15 @@ function usdcMinorPlain(minor) {
189
189
  const n = Number(minor) / 1e6;
190
190
  return `$${n.toFixed(n !== 0 && Math.abs(n) < 0.01 ? 4 : 2)}`;
191
191
  }
192
- /** Render the agent (spending) funds block as indented lines (no section title). */
193
- export function formatAgentFunds(bal) {
192
+ /**
193
+ * Render the agent (spending) funds block as indented lines (no section title).
194
+ *
195
+ * When the chain-agnostic `credit` read (GET /credit) is supplied, we render the
196
+ * full split — total spendable, the sign-up free credit, and (if the user has
197
+ * funded a wallet) the funds they added — for parity with the app. Without it we
198
+ * fall back to the `/balance` status shape (USDC/USDT or sign-up-credit total).
199
+ */
200
+ export function formatAgentFunds(bal, credit) {
194
201
  const status = bal.status;
195
202
  if (status === "not_set_up" || status === "no_active_session") {
196
203
  return [
@@ -203,10 +210,22 @@ export function formatAgentFunds(bal) {
203
210
  }
204
211
  if (status === "credit_exhausted") {
205
212
  return [
206
- ` Sign-up credit used up — balance ${usdcMinorPlain(bal.totalMinor)}`,
213
+ ` Sign-up credit used up — balance ${usdcMinorPlain(bal.totalMinor ?? credit?.totalMinor)}`,
207
214
  " Add funds to keep spending (`grant fetch`/`grant transfer` need a balance).",
208
215
  ].join("\n");
209
216
  }
217
+ // Prefer the chain-agnostic credit split (free credit + funds you added) when
218
+ // available — this is the same figure the app shows on the Spending screen.
219
+ if (credit && credit.totalMinor !== undefined) {
220
+ const lines = [` Spendable: ${usdcMinorPlain(credit.totalMinor)}`];
221
+ lines.push(` Free credit: ${usdcMinorPlain(credit.grantMinor)}`);
222
+ if (credit.walletMinor !== undefined) {
223
+ lines.push(` Your wallet: ${usdcMinorPlain(credit.walletMinor)}`);
224
+ }
225
+ if (bal.address)
226
+ lines.push(` Address: ${bal.address}`);
227
+ return lines.join("\n");
228
+ }
210
229
  // active. A funded on-chain session carries live USDC/USDT; a grant-only user
211
230
  // carries the chain-agnostic sign-up credit total in minor units.
212
231
  if (bal.USDC !== undefined || bal.USDT !== undefined) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentgrant.cash/cli",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "description": "Grant Cash — one CLI for your money (gold) and your agent (pay-per-use services). Routes to the Perfolio backend and the Agent-mode backend behind a single, plain-language surface.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -126,10 +126,18 @@ Every **cash** amount is in the user's display currency — a bare `50` means
126
126
  - `grant schema <slug>` — the exact pay contract for a catalogued service (no charge).
127
127
  - `grant fetch <url> [-m POST] [-b '<json>'] [--price <amount>] [--asset USDC|USDT]` (alias `pay`) — pay for and run a service.
128
128
  - `grant transfer <recipient> <amount>` — send money to an address.
129
+ - `grant spending deposit --amount <cash>` (aliases `add`, `top-up`) — move cash from the Investments side into the Spending balance (the agent's pay-per-use money). Amount is in the user's currency — a bare `50` means ₹50 for an INR user, $50 for a USD user. Funds land a few minutes after submitting. There is no reverse: Spending is funded from cash, not the other way around.
129
130
  - `grant redeem <code>` — claim an invite or bonus credit toward spending.
130
131
  - `grant upgrade` — get a one-time link to secure a full account and add spending funds.
131
132
  - `grant revoke` (alias `stop`) — stop all agent spending immediately.
132
133
 
134
+ **Keeping Spending funded (assistant guidance).** The Spending balance only goes down as the agent pays for services — top it up from the user's cash with `grant spending deposit`:
135
+ - **User asks explicitly** ("move ₹2,000 to spending", "top up my spending balance") → run `grant spending deposit --amount <cash>` directly. No need to ask again.
136
+ - **Running low** (a paid action is about to exceed — or only barely fits — the remaining Spending balance) → before paying, tell the user the balance is low and ask whether to transfer some cash over first. Don't transfer without a yes.
137
+ - **Empty / zero balance** (a `grant fetch`/pay fails for lack of funds, or `grant portfolio` shows Spending at 0) → tell the user their Spending account is empty and suggest moving some money in, **quoted in their local currency** — e.g. *"Your spending balance is empty — shall I move ₹2,000 (≈ $24) over from your cash?"* Then, on confirmation, run `grant spending deposit`.
138
+
139
+ Always state amounts in the user's display currency (never raw USD/USDT or chains), and only transfer proactively after the user agrees — an explicit request is the go-ahead; a low/empty balance is a prompt to ask.
140
+
133
141
  ### Transactions & self-orientation
134
142
  - `grant tx status <txHash>` — check whether a transaction confirmed. `grant tx history [--limit <n>]` — recent transactions.
135
143
  - `grant session status` / `grant session grant` — your agent-access (session) status and how to enable it.
@@ -164,6 +172,7 @@ grant polymarket bet 0x1234 --side yes --amount 25 # bet $25 on "yes"
164
172
  grant search "web search" --limit 5 # find a live service (Spending)
165
173
  grant check https://api.example.com/run # what it costs + needs, no charge
166
174
  grant fetch https://api.example.com/run -b '{"q":"gold news today"}' # pay + run
175
+ grant spending deposit --amount 25 # move $25 of cash into Spending
167
176
  grant activity --json # full combined history
168
177
  grant revoke # stop all agent spending
169
178
  ```