autonomo-sdk 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +142 -3
- package/dist/cli.d.ts +24 -0
- package/dist/cli.js +305 -0
- package/dist/index.d.ts +251 -0
- package/dist/index.js +582 -0
- package/package.json +48 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Autonomo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,142 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# Autonomo SDK for TypeScript
|
|
2
|
+
|
|
3
|
+
One call for AI agents. Say what you need, and the SDK:
|
|
4
|
+
|
|
5
|
+
1. **Finds the right API** on [Autonomo](https://autonomo.fun), from your words and the kind of input you give
|
|
6
|
+
(a token or wallet address, a transaction hash, a link).
|
|
7
|
+
2. **Pays for it** with x402 on Robinhood Chain, in USDG or $AUTONOMO, whichever the wallet can afford.
|
|
8
|
+
3. **Pays privately through [Gloam](https://gloam.trade)** when you ask, so the payment doesn't link to your wallet.
|
|
9
|
+
4. **Borrows from a [Borro](https://borro.markets) credit line** when the wallet is short of USDG.
|
|
10
|
+
5. **Keeps to your caps**: a most-per-call and a daily budget, checked before anything is signed.
|
|
11
|
+
6. **Returns the answer and a receipt**: which API, how it was paid, the amount and the transaction link.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install autonomo-sdk
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Full examples for each way to pay: [autonomo.fun/docs/sdk](https://autonomo.fun/docs/sdk).
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { Autonomo } from "autonomo-sdk";
|
|
21
|
+
|
|
22
|
+
const autonomo = new Autonomo({ privateKey: process.env.AUTONOMO_WALLET_KEY });
|
|
23
|
+
|
|
24
|
+
const { data, receipt } = await autonomo.call("is this token safe", {
|
|
25
|
+
input: "0x86b57bcb9d249cf096cf8e21426c9dc9cafdec49",
|
|
26
|
+
});
|
|
27
|
+
console.log(data);
|
|
28
|
+
console.log(receipt.route, receipt.amount, receipt.token, receipt.explorer);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Use a **new wallet just for the agent**, holding only what it may spend. A little USDG covers many calls (most APIs
|
|
32
|
+
cost 0.01 USDG). Paying in $AUTONOMO needs a little ETH once, for a Permit2 approval. Node 20 or later.
|
|
33
|
+
|
|
34
|
+
## Finding APIs without paying
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
const autonomo = new Autonomo(); // no key needed to look
|
|
38
|
+
await autonomo.find("wallet holdings"); // best matches first
|
|
39
|
+
await autonomo.quote("explain this transaction", { input: "0x…" }); // the API, the URL and every price
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
To skip the search, name the API: `autonomo.call("", { slug: "token-risk", path: "/risk/0x…" })`.
|
|
43
|
+
|
|
44
|
+
## From the terminal
|
|
45
|
+
|
|
46
|
+
The package also installs a command, for people and for agents that run shell commands. `find`, `quote`, `spend` and
|
|
47
|
+
`--dry-run` need no wallet:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx autonomo-sdk find "wallet holdings"
|
|
51
|
+
npx autonomo-sdk risk 0x86b57bcb9d249cf096cf8e21426c9dc9cafdec49 --dry-run # the price, without paying
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
To pay, put the agent wallet's key in `AUTONOMO_WALLET_KEY`. It's never taken as a flag, so it stays out of your
|
|
55
|
+
shell history.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx autonomo-sdk risk 0x86b57bcb9d249cf096cf8e21426c9dc9cafdec49
|
|
59
|
+
npx autonomo-sdk call "explain this transaction" 0x… --max 0.02 --daily 0.5 --json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Command | |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `find [words] [input]` | List APIs, best matches first |
|
|
65
|
+
| `quote <words> [input]` | The API a call would use, and its prices |
|
|
66
|
+
| `call <words> [input]` | Find the right API, pay for one call, print the answer |
|
|
67
|
+
| `price [token]`, `launches [n]`, `risk <token>`, `tx <hash>`, `portfolio <wallet>`, `read <link>`, `offers <words>` | Shortcuts for the official APIs |
|
|
68
|
+
| `spend` | Today's spending against your limits |
|
|
69
|
+
|
|
70
|
+
`--max` and `--daily` set the USDG limits (`--max-autonomo` and `--daily-autonomo` for $AUTONOMO), `--pay-with`
|
|
71
|
+
picks the token and `--borro <credit line>` borrows when the wallet is short. At a terminal it shows the price and
|
|
72
|
+
asks before paying; `--yes` skips the question, and scripts are never asked. The answer goes to stdout and the
|
|
73
|
+
receipt to stderr, so piping into `jq` works, and `--json` prints `{ data, receipt }` as one object. Exit codes: 0
|
|
74
|
+
done, 1 failed, 2 bad usage, 3 over a limit, 4 can't pay, 5 payment refused, 6 no such API.
|
|
75
|
+
|
|
76
|
+
After `npm install -g autonomo-sdk` the command is `autonomo`. (`npx autonomo` is someone else's package.) Private
|
|
77
|
+
Gloam payments aren't in the command yet; use the SDK for those.
|
|
78
|
+
|
|
79
|
+
## Limits
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
new Autonomo({
|
|
83
|
+
privateKey,
|
|
84
|
+
payWith: "auto", // or "usdg" / "autonomo"
|
|
85
|
+
limits: {
|
|
86
|
+
perCall: { usdg: "0.05", autonomo: "5000" }, // the defaults
|
|
87
|
+
daily: { usdg: "1", autonomo: "10000" }, // per UTC day; Gloam and Borro count as USDG
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
A call over a limit is refused before anything is signed, with the reason. Spending is kept in
|
|
93
|
+
`~/.autonomo/spend.json` (shared with the Python SDK); pass `spendStore: memoryStore()` or your own store to change
|
|
94
|
+
that.
|
|
95
|
+
|
|
96
|
+
## Private payments with Gloam
|
|
97
|
+
|
|
98
|
+
`private: true` pays from a private Gloam balance through Gloam's own MCP server, `@gloamtrade/mcp`, running on your
|
|
99
|
+
machine with its key and limits in `~/.gloam/agent.env`. The SDK calls its `gloam_fetch_paid` tool; Gloam cashes out
|
|
100
|
+
exactly the price through its relay and calls the API with it. The SDK holds no Gloam keys, and no wallet key is needed.
|
|
101
|
+
See the [SDK guide](https://autonomo.fun/docs/sdk) for the setup and a full example.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
const autonomo = new Autonomo({
|
|
105
|
+
gloam: {
|
|
106
|
+
fetchPaid: async (args) => {
|
|
107
|
+
// gloam: an MCP client for @gloamtrade/mcp. A private payment can take a few minutes, so wait up to 5.
|
|
108
|
+
const out = await gloam.callTool({ name: "gloam_fetch_paid", arguments: args }, undefined, { timeout: 300_000 });
|
|
109
|
+
return JSON.parse((out.content as Array<{ text: string }>)[0].text);
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
await autonomo.call("newest launches", { private: true });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
A private call only ever pays through Gloam. If the API doesn't offer it, or Gloam isn't set up, it's refused, never
|
|
117
|
+
sent as a public payment. The price includes Gloam's cash-out fee (about 0.0105 USDG for a 0.01 call).
|
|
118
|
+
|
|
119
|
+
## Borrowing with Borro
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
const autonomo = new Autonomo({ privateKey, borro: { account: "0xYourCreditLine" } });
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
When the wallet can't afford a call, the SDK asks Borro's MCP server to prepare a borrow of just what it's short,
|
|
126
|
+
sends it from the wallet (this needs a little ETH for gas), waits for the USDG and pays. The line needs x402 bills
|
|
127
|
+
turned on; see [Pay with a Borro credit line](https://autonomo.fun/docs/borro). Borro's own daily limit still applies.
|
|
128
|
+
|
|
129
|
+
## The receipt
|
|
130
|
+
|
|
131
|
+
| Field | |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| `api` | The API's slug and name |
|
|
134
|
+
| `route` | `usdg`, `autonomo`, `gloam` or `borro` |
|
|
135
|
+
| `amount`, `token` | What was paid |
|
|
136
|
+
| `payer`, `payTo` | The wallet (or "private (Gloam)") and the API's payment address |
|
|
137
|
+
| `tx`, `explorer` | The payment transaction and its Blockscout link |
|
|
138
|
+
| `borrowTxs`, `approveTx` | Borro borrows and the one-time Permit2 approval, when there were any |
|
|
139
|
+
| `spentToday` | Today's total against the daily budget |
|
|
140
|
+
|
|
141
|
+
Errors are `AutonomoError` with a `code` such as `cannot_pay`, `over_limit`, `no_private_option` or `payment_refused`.
|
|
142
|
+
If an API fails after a payment is signed, the payment doesn't settle, so you aren't charged.
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { type SpendStore } from "./index.js";
|
|
3
|
+
/** Everything the CLI touches outside itself, so tests can swap it. */
|
|
4
|
+
export type Io = {
|
|
5
|
+
env: Record<string, string | undefined>;
|
|
6
|
+
out: (text: string) => void;
|
|
7
|
+
err: (text: string) => void;
|
|
8
|
+
/** True when a person is at the terminal, so a paid call asks first. */
|
|
9
|
+
interactive: boolean;
|
|
10
|
+
confirm: (question: string) => Promise<boolean>;
|
|
11
|
+
fetch?: typeof fetch;
|
|
12
|
+
spendStore?: SpendStore;
|
|
13
|
+
};
|
|
14
|
+
export declare const EXIT: {
|
|
15
|
+
readonly ok: 0;
|
|
16
|
+
readonly failed: 1;
|
|
17
|
+
readonly usage: 2;
|
|
18
|
+
readonly overLimit: 3;
|
|
19
|
+
readonly cannotPay: 4;
|
|
20
|
+
readonly refused: 5;
|
|
21
|
+
readonly notFound: 6;
|
|
22
|
+
};
|
|
23
|
+
/** Runs one command and returns its exit code. `argv` is what follows the program name. */
|
|
24
|
+
export declare function run(argv: string[], io: Io): Promise<number>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Autonomo CLI: the SDK from a terminal, for people and for agents that run shell commands.
|
|
3
|
+
//
|
|
4
|
+
// npx autonomo-sdk find "wallet holdings" look, free
|
|
5
|
+
// npx autonomo-sdk risk 0x… --dry-run the price, without paying
|
|
6
|
+
// AUTONOMO_WALLET_KEY=0x… npx autonomo-sdk risk 0x… pay and print the answer
|
|
7
|
+
//
|
|
8
|
+
// The answer goes to stdout and everything else (the receipt, questions, errors) to stderr, so `autonomo … | jq`
|
|
9
|
+
// works. The wallet key is only ever read from AUTONOMO_WALLET_KEY, never from a flag, so it stays out of shell
|
|
10
|
+
// history. The SDK's per-call cap and daily budget apply to every paid call.
|
|
11
|
+
import { realpathSync } from "node:fs";
|
|
12
|
+
import { createInterface } from "node:readline/promises";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { parseArgs } from "node:util";
|
|
15
|
+
import { formatUnits } from "viem";
|
|
16
|
+
import { Autonomo, AutonomoError, DEFAULT_LIMITS, TOKENS, VERSION, fileStore } from "./index.js";
|
|
17
|
+
export const EXIT = { ok: 0, failed: 1, usage: 2, overLimit: 3, cannotPay: 4, refused: 5, notFound: 6 };
|
|
18
|
+
const HELP = `Autonomo CLI ${VERSION}: find and pay for APIs on autonomo.fun
|
|
19
|
+
|
|
20
|
+
Usage
|
|
21
|
+
autonomo find [words] [input] List APIs, best matches first. Free.
|
|
22
|
+
autonomo quote <words> [input] The API a call would use and its prices. Free.
|
|
23
|
+
autonomo call <words> [input] Find the right API, pay for one call, print the answer.
|
|
24
|
+
autonomo spend Today's spending against your limits.
|
|
25
|
+
|
|
26
|
+
Shortcuts for the official APIs
|
|
27
|
+
autonomo price [token address] $AUTONOMO's price, or any launchpad token's
|
|
28
|
+
autonomo launches [how many] Newest token launches
|
|
29
|
+
autonomo risk <token address> Rug-risk check
|
|
30
|
+
autonomo tx <transaction hash> A transaction in plain words
|
|
31
|
+
autonomo portfolio <wallet> What a wallet holds
|
|
32
|
+
autonomo read <https link> A web page's text
|
|
33
|
+
autonomo offers <what you need> Matching offers on Ostrapad
|
|
34
|
+
|
|
35
|
+
Options
|
|
36
|
+
--max <usdg> Most to pay for one call (default ${DEFAULT_LIMITS.perCall.usdg})
|
|
37
|
+
--daily <usdg> Most to spend per UTC day (default ${DEFAULT_LIMITS.daily.usdg})
|
|
38
|
+
--max-autonomo <amount> The same two limits when paying in $AUTONOMO
|
|
39
|
+
--daily-autonomo <amount> (defaults ${DEFAULT_LIMITS.perCall.autonomo} and ${DEFAULT_LIMITS.daily.autonomo})
|
|
40
|
+
--pay-with <token> usdg, autonomo or auto (default: USDG, then $AUTONOMO)
|
|
41
|
+
--borro <credit line> Borrow from this Borro line when the wallet is short of USDG
|
|
42
|
+
--slug <api> --path <p> With call or quote: use this API and path, skipping the search
|
|
43
|
+
--dry-run Show the price instead of paying
|
|
44
|
+
--yes Don't ask before paying
|
|
45
|
+
--json One JSON object on stdout: { data, receipt }
|
|
46
|
+
--help, --version
|
|
47
|
+
|
|
48
|
+
Wallet
|
|
49
|
+
Set AUTONOMO_WALLET_KEY to the private key of a wallet made just for the agent, holding only what it may spend.
|
|
50
|
+
find, quote, spend and --dry-run need no wallet.
|
|
51
|
+
|
|
52
|
+
Exit codes
|
|
53
|
+
0 done 1 failed 2 bad usage 3 over a limit 4 can't pay 5 payment refused 6 no such API`;
|
|
54
|
+
const ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
55
|
+
const TX_HASH = /^0x[0-9a-fA-F]{64}$/;
|
|
56
|
+
const AMOUNT = /^\d+(\.\d+)?$/;
|
|
57
|
+
class UsageError extends Error {
|
|
58
|
+
}
|
|
59
|
+
const usage = (message) => {
|
|
60
|
+
throw new UsageError(message);
|
|
61
|
+
};
|
|
62
|
+
/** Each shortcut names its API and builds the path, so nothing is searched. */
|
|
63
|
+
const SHORTCUTS = {
|
|
64
|
+
price: ([token]) => {
|
|
65
|
+
if (token === undefined)
|
|
66
|
+
return { slug: "token-price", path: "/price/autonomo" };
|
|
67
|
+
if (!ADDRESS.test(token))
|
|
68
|
+
usage("price takes a token contract address (0x and 40 hex characters), or nothing for $AUTONOMO.");
|
|
69
|
+
return { slug: "token-lookup", path: `/price/${token}` };
|
|
70
|
+
},
|
|
71
|
+
launches: ([count = "10"]) => {
|
|
72
|
+
if (!/^\d{1,3}$/.test(count) || Number(count) < 1)
|
|
73
|
+
usage("launches takes how many to show, e.g. autonomo launches 5.");
|
|
74
|
+
return { slug: "new-launches", path: `/launches?limit=${Number(count)}` };
|
|
75
|
+
},
|
|
76
|
+
risk: ([token]) => {
|
|
77
|
+
if (!token || !ADDRESS.test(token))
|
|
78
|
+
usage("risk takes a token contract address: autonomo risk 0x…");
|
|
79
|
+
return { slug: "token-risk", path: `/risk/${token}` };
|
|
80
|
+
},
|
|
81
|
+
tx: ([hash]) => {
|
|
82
|
+
if (!hash || !TX_HASH.test(hash))
|
|
83
|
+
usage("tx takes a transaction hash (0x and 64 hex characters).");
|
|
84
|
+
return { slug: "tx-explainer", path: `/tx/${hash}` };
|
|
85
|
+
},
|
|
86
|
+
portfolio: ([wallet]) => {
|
|
87
|
+
if (!wallet || !ADDRESS.test(wallet))
|
|
88
|
+
usage("portfolio takes a wallet address: autonomo portfolio 0x…");
|
|
89
|
+
return { slug: "wallet-portfolio", path: `/portfolio/${wallet}` };
|
|
90
|
+
},
|
|
91
|
+
read: ([link]) => {
|
|
92
|
+
if (!link || !/^https:\/\/\S+$/i.test(link))
|
|
93
|
+
usage("read takes an https:// link.");
|
|
94
|
+
return { slug: "page-reader", path: `/page?url=${encodeURIComponent(link)}` };
|
|
95
|
+
},
|
|
96
|
+
offers: (words) => {
|
|
97
|
+
const q = words.join(" ").trim();
|
|
98
|
+
if (!q)
|
|
99
|
+
usage('offers takes what you need done: autonomo offers "summarize a document"');
|
|
100
|
+
return { slug: "ostrapad-offer-finder", path: `/offers?q=${encodeURIComponent(q)}` };
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
const price = (api) => {
|
|
104
|
+
const parts = [api.price.usdg !== undefined && `${api.price.usdg} USDG`, api.price.autonomo !== undefined && `${api.price.autonomo} AUTONOMO`].filter(Boolean);
|
|
105
|
+
return parts.join(" or ") || "see quote";
|
|
106
|
+
};
|
|
107
|
+
function exitFor(e) {
|
|
108
|
+
if (e instanceof UsageError)
|
|
109
|
+
return EXIT.usage;
|
|
110
|
+
if (!(e instanceof AutonomoError))
|
|
111
|
+
return EXIT.failed;
|
|
112
|
+
if (e.code === "over_limit")
|
|
113
|
+
return EXIT.overLimit;
|
|
114
|
+
// cannot_pay covers both a limit and an empty wallet; the limit ones say "cap" or "budget".
|
|
115
|
+
if (e.code === "cannot_pay")
|
|
116
|
+
return /per-call cap|budget/.test(e.message) && !/the wallet holds/.test(e.message) ? EXIT.overLimit : EXIT.cannotPay;
|
|
117
|
+
if (e.code === "no_wallet" || e.code === "bad_key")
|
|
118
|
+
return EXIT.cannotPay;
|
|
119
|
+
if (e.code === "payment_refused" || e.code === "paid_not_served")
|
|
120
|
+
return EXIT.refused;
|
|
121
|
+
if (e.code === "not_found")
|
|
122
|
+
return EXIT.notFound;
|
|
123
|
+
return EXIT.failed;
|
|
124
|
+
}
|
|
125
|
+
/** Runs one command and returns its exit code. `argv` is what follows the program name. */
|
|
126
|
+
export async function run(argv, io) {
|
|
127
|
+
let json = argv.includes("--json");
|
|
128
|
+
try {
|
|
129
|
+
const { values, positionals } = parseArgs({
|
|
130
|
+
args: argv,
|
|
131
|
+
allowPositionals: true,
|
|
132
|
+
options: {
|
|
133
|
+
max: { type: "string" },
|
|
134
|
+
daily: { type: "string" },
|
|
135
|
+
"max-autonomo": { type: "string" },
|
|
136
|
+
"daily-autonomo": { type: "string" },
|
|
137
|
+
"pay-with": { type: "string" },
|
|
138
|
+
borro: { type: "string" },
|
|
139
|
+
slug: { type: "string" },
|
|
140
|
+
path: { type: "string" },
|
|
141
|
+
"dry-run": { type: "boolean" },
|
|
142
|
+
yes: { type: "boolean", short: "y" },
|
|
143
|
+
json: { type: "boolean" },
|
|
144
|
+
help: { type: "boolean", short: "h" },
|
|
145
|
+
version: { type: "boolean", short: "v" },
|
|
146
|
+
key: { type: "string" },
|
|
147
|
+
"private-key": { type: "string" },
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
json = Boolean(values.json);
|
|
151
|
+
if (values.key !== undefined || values["private-key"] !== undefined) {
|
|
152
|
+
usage("The wallet key isn't taken as a flag, so it stays out of your shell history. Set AUTONOMO_WALLET_KEY instead.");
|
|
153
|
+
}
|
|
154
|
+
if (values.version)
|
|
155
|
+
return io.out(VERSION), EXIT.ok;
|
|
156
|
+
const [command, ...rest] = positionals;
|
|
157
|
+
if (values.help || command === undefined || command === "help")
|
|
158
|
+
return io.out(HELP), EXIT.ok;
|
|
159
|
+
const amount = (flag) => {
|
|
160
|
+
const v = values[flag];
|
|
161
|
+
if (v !== undefined && !AMOUNT.test(v))
|
|
162
|
+
usage(`--${flag} takes an amount in whole tokens, e.g. --${flag} 0.05.`);
|
|
163
|
+
return v;
|
|
164
|
+
};
|
|
165
|
+
const limits = {
|
|
166
|
+
perCall: { usdg: amount("max") ?? DEFAULT_LIMITS.perCall.usdg, autonomo: amount("max-autonomo") ?? DEFAULT_LIMITS.perCall.autonomo },
|
|
167
|
+
daily: { usdg: amount("daily") ?? DEFAULT_LIMITS.daily.usdg, autonomo: amount("daily-autonomo") ?? DEFAULT_LIMITS.daily.autonomo },
|
|
168
|
+
};
|
|
169
|
+
const payWith = values["pay-with"] ?? "auto";
|
|
170
|
+
if (payWith !== "usdg" && payWith !== "autonomo" && payWith !== "auto")
|
|
171
|
+
usage("--pay-with takes usdg, autonomo or auto.");
|
|
172
|
+
const borro = values.borro ?? io.env.AUTONOMO_BORRO_ACCOUNT;
|
|
173
|
+
if (borro !== undefined && !ADDRESS.test(borro))
|
|
174
|
+
usage("--borro takes the credit line's address.");
|
|
175
|
+
const store = io.spendStore ?? fileStore();
|
|
176
|
+
const client = (withWallet) => new Autonomo({
|
|
177
|
+
privateKey: withWallet ? io.env.AUTONOMO_WALLET_KEY || undefined : undefined,
|
|
178
|
+
payWith: payWith,
|
|
179
|
+
limits,
|
|
180
|
+
borro: borro ? { account: borro } : undefined,
|
|
181
|
+
spendStore: store,
|
|
182
|
+
fetch: io.fetch,
|
|
183
|
+
});
|
|
184
|
+
const print = (value) => io.out(typeof value === "string" ? value : JSON.stringify(value, null, 2));
|
|
185
|
+
if (command === "find") {
|
|
186
|
+
const [words = "", input] = rest;
|
|
187
|
+
let apis;
|
|
188
|
+
if (input !== undefined) {
|
|
189
|
+
apis = await client(false).find(words, input);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
// Browsing: with no input to fit, show every API whose text matches, including the ones that need an input.
|
|
193
|
+
const terms = words.toLowerCase().split(/[^a-z0-9$]+/).filter((w) => w.length > 2);
|
|
194
|
+
const hits = (a) => terms.filter((w) => `${a.slug} ${a.name} ${a.description}`.toLowerCase().includes(w)).length;
|
|
195
|
+
apis = (await client(false).find()).filter((a) => terms.length === 0 || hits(a) > 0).sort((x, y) => hits(y) - hits(x));
|
|
196
|
+
}
|
|
197
|
+
if (json)
|
|
198
|
+
return print(apis), EXIT.ok;
|
|
199
|
+
if (apis.length === 0)
|
|
200
|
+
return io.err(`No API matches "${words}". Run autonomo find to see them all.`), EXIT.notFound;
|
|
201
|
+
for (const a of apis)
|
|
202
|
+
io.out(`${a.slug} ${price(a)}${a.verified ? " verified" : ""}\n ${a.name}: ${a.description}\n ${a.path}`);
|
|
203
|
+
return EXIT.ok;
|
|
204
|
+
}
|
|
205
|
+
if (command === "spend") {
|
|
206
|
+
const day = new Date().toISOString().slice(0, 10);
|
|
207
|
+
const spent = await store.get(day);
|
|
208
|
+
const row = (t) => ({ spent: formatUnits(spent[t] ?? 0n, TOKENS[t].decimals), daily: limits.daily[t], perCall: limits.perCall[t] });
|
|
209
|
+
const report = { day, wallet: client(true).address, usdg: row("usdg"), autonomo: row("autonomo") };
|
|
210
|
+
if (json)
|
|
211
|
+
return print(report), EXIT.ok;
|
|
212
|
+
io.out(`Today (${day}, UTC)`);
|
|
213
|
+
io.out(` USDG ${report.usdg.spent} of ${report.usdg.daily} spent, up to ${report.usdg.perCall} a call`);
|
|
214
|
+
io.out(` AUTONOMO ${report.autonomo.spent} of ${report.autonomo.daily} spent, up to ${report.autonomo.perCall} a call`);
|
|
215
|
+
io.out(` Wallet ${report.wallet ?? "none set (AUTONOMO_WALLET_KEY)"}`);
|
|
216
|
+
return EXIT.ok;
|
|
217
|
+
}
|
|
218
|
+
// Everything else is a call: a shortcut, or `call` / `quote` with words.
|
|
219
|
+
let words = "";
|
|
220
|
+
let opts;
|
|
221
|
+
const quoteOnly = command === "quote" || Boolean(values["dry-run"]);
|
|
222
|
+
if (command === "call" || command === "quote") {
|
|
223
|
+
if (values.slug) {
|
|
224
|
+
opts = { slug: values.slug, path: values.path, input: rest[0] };
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
if (values.path)
|
|
228
|
+
usage("--path needs --slug, the API it belongs to.");
|
|
229
|
+
if (!rest[0])
|
|
230
|
+
usage(`${command} takes what you need in words: autonomo ${command} "is this token safe" 0x…`);
|
|
231
|
+
if (rest.length > 2)
|
|
232
|
+
usage(`Put the words in quotes: autonomo ${command} "is this token safe" 0x…`);
|
|
233
|
+
words = rest[0];
|
|
234
|
+
opts = { input: rest[1] };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else if (Object.hasOwn(SHORTCUTS, command)) {
|
|
238
|
+
if (values.slug || values.path)
|
|
239
|
+
usage(`${command} already names its API. Use call with --slug and --path instead.`);
|
|
240
|
+
opts = SHORTCUTS[command](rest);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
return usage(`Unknown command "${command}". Run autonomo --help.`);
|
|
244
|
+
}
|
|
245
|
+
if (quoteOnly) {
|
|
246
|
+
const q = await client(false).quote(words, opts);
|
|
247
|
+
if (json)
|
|
248
|
+
return print(q), EXIT.ok;
|
|
249
|
+
io.out(`${q.api.name} (${q.api.slug})\n ${q.url}`);
|
|
250
|
+
for (const o of q.offers)
|
|
251
|
+
io.out(` ${o.amount} ${o.token}${o.private ? " private, through Gloam" : ""}`);
|
|
252
|
+
return EXIT.ok;
|
|
253
|
+
}
|
|
254
|
+
const autonomo = client(true);
|
|
255
|
+
if (!autonomo.address) {
|
|
256
|
+
throw new AutonomoError("No wallet set. Put the agent wallet's private key in AUTONOMO_WALLET_KEY, or add --dry-run to see the price.", "no_wallet");
|
|
257
|
+
}
|
|
258
|
+
if (io.interactive && !values.yes) {
|
|
259
|
+
const q = await autonomo.quote(words, opts);
|
|
260
|
+
const prices = q.offers.filter((o) => !o.private).map((o) => `${o.amount} ${o.token}`).join(" or ");
|
|
261
|
+
if (!(await io.confirm(`${q.api.name} costs ${prices}. Pay from ${autonomo.address}? [y/N] `)))
|
|
262
|
+
return io.err("Not paid."), EXIT.ok;
|
|
263
|
+
}
|
|
264
|
+
const { data, receipt } = await autonomo.call(words, opts);
|
|
265
|
+
if (json)
|
|
266
|
+
return print({ data, receipt }), EXIT.ok;
|
|
267
|
+
print(data);
|
|
268
|
+
io.err(`Paid ${receipt.amount} ${receipt.token} to ${receipt.api.name}${receipt.route === "borro" ? " on Borro credit" : ""}. Today: ${receipt.spentToday}.`);
|
|
269
|
+
if (receipt.explorer)
|
|
270
|
+
io.err(receipt.explorer);
|
|
271
|
+
return EXIT.ok;
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
const code = e instanceof AutonomoError ? e.code : e instanceof UsageError || e?.code?.startsWith?.("ERR_PARSE_ARGS") ? "usage" : "failed";
|
|
275
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
276
|
+
io.err(json ? JSON.stringify({ error: { code, message } }) : code === "usage" ? `${message}\nRun autonomo --help for the commands.` : message);
|
|
277
|
+
return code === "usage" ? EXIT.usage : exitFor(e);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const isMain = (() => {
|
|
281
|
+
try {
|
|
282
|
+
return Boolean(process.argv[1]) && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
})();
|
|
288
|
+
if (isMain) {
|
|
289
|
+
const code = await run(process.argv.slice(2), {
|
|
290
|
+
env: process.env,
|
|
291
|
+
out: (text) => console.log(text),
|
|
292
|
+
err: (text) => console.error(text),
|
|
293
|
+
interactive: Boolean(process.stdin.isTTY && process.stderr.isTTY),
|
|
294
|
+
confirm: async (question) => {
|
|
295
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
296
|
+
try {
|
|
297
|
+
return /^y(es)?$/i.test((await rl.question(question)).trim());
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
rl.close();
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
process.exitCode = code;
|
|
305
|
+
}
|