@aomi-labs/client 0.1.16 → 0.1.18

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 CHANGED
@@ -126,6 +126,7 @@ npx @aomi-labs/client chat "swap 1 ETH" --verbose # stream tool calls + r
126
126
  npx @aomi-labs/client app list # list available apps
127
127
  npx @aomi-labs/client model list # list available models
128
128
  npx @aomi-labs/client model set claude-sonnet-4 # switch the current session model
129
+ npx @aomi-labs/client session new # create a fresh active session
129
130
  npx @aomi-labs/client secret list # list configured secret handles
130
131
  npx @aomi-labs/client --secret ALCHEMY_API_KEY=... # ingest a secret for the active session
131
132
  npx @aomi-labs/client log # show full conversation history
@@ -149,6 +150,31 @@ npx @aomi-labs/client chat "send 0 ETH to myself" \
149
150
  The address is persisted in the state file, so subsequent commands in the same
150
151
  session don't need it again.
151
152
 
153
+ ### Chain selection
154
+
155
+ Use `--chain <id>` for the current command when the task is chain-specific:
156
+
157
+ ```bash
158
+ $ npx @aomi-labs/client chat "swap 1 POL for USDC on Polygon" --chain 137
159
+ ```
160
+
161
+ Use `AOMI_CHAIN_ID` when several consecutive commands should share the same
162
+ chain context.
163
+
164
+ ### Fresh sessions
165
+
166
+ Use `--new-session` when you want a command to start a fresh backend/local
167
+ session instead of reusing the currently active one:
168
+
169
+ ```bash
170
+ $ npx @aomi-labs/client chat "show my balances" --new-session
171
+ $ npx @aomi-labs/client --secret ALCHEMY_API_KEY=... --new-session
172
+ $ npx @aomi-labs/client session new
173
+ ```
174
+
175
+ This is useful when starting a new operator flow or a new external chat thread
176
+ and you do not want stale session state to bleed into the next run.
177
+
152
178
  ### Model selection
153
179
 
154
180
  The CLI can discover and switch backend models for the active session:
@@ -281,7 +307,7 @@ All config can be passed as flags (which take priority over env vars):
281
307
 
282
308
  | Flag | Env Variable | Default | Description |
283
309
  | ----------------------- | ----------------- | ---------------------- | -------------------------------------------- |
284
- | `--backend-url` | `AOMI_BASE_URL` | `https://api.aomi.dev` | Backend URL |
310
+ | `--backend-url` | `AOMI_BACKEND_URL` | `https://api.aomi.dev` | Backend URL |
285
311
  | `--api-key` | `AOMI_API_KEY` | — | API key for non-default apps |
286
312
  | `--app` | `AOMI_APP` | `default` | App |
287
313
  | `--model` | `AOMI_MODEL` | — | Model rig to apply before chat |
package/dist/cli.js CHANGED
@@ -22,7 +22,7 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
22
22
  // package.json
23
23
  var package_default = {
24
24
  name: "@aomi-labs/client",
25
- version: "0.1.16",
25
+ version: "0.1.18",
26
26
  description: "Platform-agnostic TypeScript client for the Aomi backend API",
27
27
  type: "module",
28
28
  main: "./dist/index.cjs",
@@ -68,17 +68,17 @@ var CliExit = class extends Error {
68
68
  };
69
69
  function fatal(message) {
70
70
  const RED = "\x1B[31m";
71
- const DIM3 = "\x1B[2m";
72
- const RESET3 = "\x1B[0m";
71
+ const DIM2 = "\x1B[2m";
72
+ const RESET2 = "\x1B[0m";
73
73
  const lines = message.split("\n");
74
74
  const [headline, ...details] = lines;
75
- console.error(`${RED}\u274C ${headline}${RESET3}`);
75
+ console.error(`${RED}\u274C ${headline}${RESET2}`);
76
76
  for (const detail of details) {
77
77
  if (!detail.trim()) {
78
78
  console.error("");
79
79
  continue;
80
80
  }
81
- console.error(`${DIM3}${detail}${RESET3}`);
81
+ console.error(`${DIM2}${detail}${RESET2}`);
82
82
  }
83
83
  throw new CliExit(1);
84
84
  }
@@ -139,6 +139,12 @@ function parseSecret(value, secrets) {
139
139
  secrets[value.slice(0, eqIdx)] = value.slice(eqIdx + 1);
140
140
  }
141
141
  }
142
+ function normalizePrivateKey(value) {
143
+ if (value === void 0) return void 0;
144
+ const trimmed = value.trim();
145
+ if (!trimmed) return void 0;
146
+ return trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
147
+ }
142
148
  function parseArgs(argv) {
143
149
  const raw = argv.slice(2);
144
150
  const command = raw[0] && !raw[0].startsWith("-") ? raw[0] : void 0;
@@ -196,12 +202,15 @@ function getConfig(parsed) {
196
202
  fatal("`--aa-provider` and `--aa-mode` cannot be used with `--eoa`.");
197
203
  }
198
204
  return {
199
- baseUrl: (_d = (_c = parsed.flags["backend-url"]) != null ? _c : process.env.AOMI_BASE_URL) != null ? _d : "https://api.aomi.dev",
205
+ baseUrl: (_d = (_c = parsed.flags["backend-url"]) != null ? _c : process.env.AOMI_BACKEND_URL) != null ? _d : "https://api.aomi.dev",
200
206
  apiKey: (_e = parsed.flags["api-key"]) != null ? _e : process.env.AOMI_API_KEY,
201
207
  app: (_g = (_f = parsed.flags["app"]) != null ? _f : process.env.AOMI_APP) != null ? _g : "default",
202
208
  model: (_h = parsed.flags["model"]) != null ? _h : process.env.AOMI_MODEL,
209
+ freshSession: parsed.flags["new-session"] === "true",
203
210
  publicKey: (_i = parsed.flags["public-key"]) != null ? _i : process.env.AOMI_PUBLIC_KEY,
204
- privateKey: (_j = parsed.flags["private-key"]) != null ? _j : process.env.PRIVATE_KEY,
211
+ privateKey: normalizePrivateKey(
212
+ (_j = parsed.flags["private-key"]) != null ? _j : process.env.PRIVATE_KEY
213
+ ),
205
214
  chainRpcUrl: (_k = parsed.flags["rpc-url"]) != null ? _k : process.env.CHAIN_RPC_URL,
206
215
  chain: parseChainId((_l = parsed.flags["chain"]) != null ? _l : process.env.AOMI_CHAIN_ID),
207
216
  secrets: parsed.secrets,
@@ -1148,6 +1157,31 @@ var AomiClient = class {
1148
1157
  }
1149
1158
  return postState(this.baseUrl, "/api/control/model", payload, sessionId, apiKey);
1150
1159
  }
1160
+ // ===========================================================================
1161
+ // Batch Simulation
1162
+ // ===========================================================================
1163
+ /**
1164
+ * Simulate pending transactions as an atomic batch.
1165
+ * Each tx sees state changes from previous txs (e.g., approve → swap).
1166
+ */
1167
+ async simulateBatch(sessionId, txIds) {
1168
+ const url = joinApiPath(this.baseUrl, "/api/simulate");
1169
+ const headers = new Headers(
1170
+ withSessionHeader(sessionId, { "Content-Type": "application/json" })
1171
+ );
1172
+ if (this.apiKey) {
1173
+ headers.set(API_KEY_HEADER, this.apiKey);
1174
+ }
1175
+ const response = await fetch(url, {
1176
+ method: "POST",
1177
+ headers,
1178
+ body: JSON.stringify({ tx_ids: txIds })
1179
+ });
1180
+ if (!response.ok) {
1181
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1182
+ }
1183
+ return await response.json();
1184
+ }
1151
1185
  };
1152
1186
 
1153
1187
  // src/types.ts
@@ -1792,20 +1826,29 @@ function buildCliUserState(publicKey, chainId) {
1792
1826
  }
1793
1827
 
1794
1828
  // src/cli/context.ts
1795
- function getOrCreateSession(runtime) {
1829
+ function buildSessionState(runtime) {
1830
+ const { config } = runtime;
1831
+ return {
1832
+ sessionId: crypto.randomUUID(),
1833
+ baseUrl: config.baseUrl,
1834
+ app: config.app,
1835
+ model: config.model,
1836
+ apiKey: config.apiKey,
1837
+ publicKey: config.publicKey,
1838
+ chainId: config.chain,
1839
+ clientId: crypto.randomUUID()
1840
+ };
1841
+ }
1842
+ function createFreshSessionState(runtime) {
1843
+ const state = buildSessionState(runtime);
1844
+ writeState(state);
1845
+ return state;
1846
+ }
1847
+ function getOrCreateSession(runtime, options = {}) {
1796
1848
  const { config } = runtime;
1797
- let state = readState();
1849
+ let state = options.fresh || config.freshSession ? createFreshSessionState(runtime) : readState();
1798
1850
  if (!state) {
1799
- state = {
1800
- sessionId: crypto.randomUUID(),
1801
- baseUrl: config.baseUrl,
1802
- app: config.app,
1803
- apiKey: config.apiKey,
1804
- publicKey: config.publicKey,
1805
- chainId: config.chain,
1806
- clientId: crypto.randomUUID()
1807
- };
1808
- writeState(state);
1851
+ state = createFreshSessionState(runtime);
1809
1852
  } else {
1810
1853
  let changed = false;
1811
1854
  if (config.baseUrl !== state.baseUrl) {
@@ -1987,7 +2030,9 @@ async function chatCommand(runtime) {
1987
2030
  fatal("Usage: aomi chat <message>");
1988
2031
  }
1989
2032
  const verbose = runtime.parsed.flags["verbose"] === "true" || runtime.parsed.flags["v"] === "true";
1990
- const { session, state } = getOrCreateSession(runtime);
2033
+ const { session, state } = getOrCreateSession(runtime, {
2034
+ fresh: runtime.config.freshSession
2035
+ });
1991
2036
  try {
1992
2037
  await ingestSecretsIfPresent(runtime, state, session.client);
1993
2038
  await applyRequestedModelIfPresent(runtime, session, state);
@@ -2740,7 +2785,9 @@ async function ingestSecretsCommand(runtime) {
2740
2785
  if (secretEntries.length === 0) {
2741
2786
  fatal("Usage: aomi --secret NAME=value [NAME=value ...]");
2742
2787
  }
2743
- const { session, state } = getOrCreateSession(runtime);
2788
+ const { session, state } = getOrCreateSession(runtime, {
2789
+ fresh: runtime.config.freshSession
2790
+ });
2744
2791
  try {
2745
2792
  const handles = await ingestSecretsIfPresent(
2746
2793
  runtime,
@@ -2873,6 +2920,12 @@ async function sessionsCommand(_runtime) {
2873
2920
  async function sessionCommand(runtime) {
2874
2921
  const subcommand = runtime.parsed.positional[0];
2875
2922
  const selector = runtime.parsed.positional[1];
2923
+ if (subcommand === "new") {
2924
+ const state = createFreshSessionState(runtime);
2925
+ console.log(`Active session set to ${state.sessionId} (new).`);
2926
+ printDataFileLocation();
2927
+ return;
2928
+ }
2876
2929
  if (subcommand === "resume") {
2877
2930
  if (!selector) {
2878
2931
  fatal("Usage: aomi session resume <session-id|session-N|N>");
@@ -2908,10 +2961,82 @@ async function sessionCommand(runtime) {
2908
2961
  return;
2909
2962
  }
2910
2963
  fatal(
2911
- "Usage: aomi session list\n aomi session resume <session-id|session-N|N>\n aomi session delete <session-id|session-N|N>"
2964
+ "Usage: aomi session list\n aomi session new\n aomi session resume <session-id|session-N|N>\n aomi session delete <session-id|session-N|N>"
2912
2965
  );
2913
2966
  }
2914
2967
 
2968
+ // src/cli/commands/simulate.ts
2969
+ function requirePendingTx(state, txId) {
2970
+ var _a3;
2971
+ const pendingTx = ((_a3 = state.pendingTxs) != null ? _a3 : []).find((tx) => tx.id === txId);
2972
+ if (!pendingTx) {
2973
+ fatal(
2974
+ `No pending transaction with id "${txId}".
2975
+ Run \`aomi tx\` to see available IDs.`
2976
+ );
2977
+ }
2978
+ return pendingTx;
2979
+ }
2980
+ async function simulateCommand(runtime) {
2981
+ var _a3;
2982
+ const state = readState();
2983
+ if (!state) {
2984
+ fatal("No active session. Run `aomi chat` first.");
2985
+ }
2986
+ const txIds = runtime.parsed.positional;
2987
+ if (txIds.length === 0) {
2988
+ fatal("Usage: aomi simulate <tx-id> [<tx-id> ...]\nRun `aomi tx` to see available IDs.");
2989
+ }
2990
+ for (const txId of txIds) {
2991
+ requirePendingTx(state, txId);
2992
+ }
2993
+ console.log(
2994
+ `${DIM}Simulating ${txIds.length} transaction(s) as atomic batch...${RESET}`
2995
+ );
2996
+ const client = new AomiClient({
2997
+ baseUrl: state.baseUrl,
2998
+ apiKey: state.apiKey
2999
+ });
3000
+ const response = await client.simulateBatch(state.sessionId, txIds);
3001
+ const { result } = response;
3002
+ const modeLabel = result.stateful ? "stateful (Anvil snapshot)" : "stateless (independent eth_call)";
3003
+ console.log(`
3004
+ Batch simulation (${modeLabel}):`);
3005
+ console.log(`From: ${result.from} | Network: ${result.network}
3006
+ `);
3007
+ for (const step of result.steps) {
3008
+ const icon = step.success ? `${GREEN}\u2713${RESET}` : `\x1B[31m\u2717${RESET}`;
3009
+ const label = step.label || `Step ${step.step}`;
3010
+ const gasInfo = step.gas_used ? ` | gas: ${step.gas_used.toLocaleString()}` : "";
3011
+ console.log(` ${icon} ${step.step}. ${label}`);
3012
+ console.log(` ${DIM}to: ${step.tx.to} | value: ${step.tx.value_eth} ETH${gasInfo}${RESET}`);
3013
+ if (!step.success && step.revert_reason) {
3014
+ console.log(` \x1B[31mRevert: ${step.revert_reason}${RESET}`);
3015
+ }
3016
+ }
3017
+ if (result.total_gas) {
3018
+ console.log(`
3019
+ ${DIM}Total gas: ${result.total_gas.toLocaleString()}${RESET}`);
3020
+ }
3021
+ if (result.fee) {
3022
+ const feeEth = (Number(result.fee.amount_wei) / 1e18).toFixed(6);
3023
+ console.log(
3024
+ `Service fee: ${feeEth} ETH \u2192 ${result.fee.recipient}`
3025
+ );
3026
+ }
3027
+ console.log();
3028
+ if (result.batch_success) {
3029
+ console.log(
3030
+ `${GREEN}All steps passed.${RESET} Run \`aomi sign ${txIds.join(" ")}\` to execute.`
3031
+ );
3032
+ } else {
3033
+ const failed = result.steps.find((s) => !s.success);
3034
+ console.log(
3035
+ `\x1B[31mBatch failed at step ${(_a3 = failed == null ? void 0 : failed.step) != null ? _a3 : "?"}.${RESET} Fix the issue and re-queue, or run \`aomi sign\` on the successful prefix.`
3036
+ );
3037
+ }
3038
+ }
3039
+
2915
3040
  // src/cli/commands/wallet.ts
2916
3041
  import { createWalletClient, http } from "viem";
2917
3042
  import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
@@ -3463,7 +3588,7 @@ function txCommand() {
3463
3588
  }
3464
3589
  printDataFileLocation();
3465
3590
  }
3466
- function requirePendingTx(state, txId) {
3591
+ function requirePendingTx2(state, txId) {
3467
3592
  var _a3;
3468
3593
  const pendingTx = ((_a3 = state.pendingTxs) != null ? _a3 : []).find((tx) => tx.id === txId);
3469
3594
  if (!pendingTx) {
@@ -3479,7 +3604,7 @@ function requirePendingTxs(state, txIds) {
3479
3604
  if (uniqueIds.length !== txIds.length) {
3480
3605
  fatal("Duplicate transaction IDs are not allowed in a single `aomi sign` call.");
3481
3606
  }
3482
- return uniqueIds.map((txId) => requirePendingTx(state, txId));
3607
+ return uniqueIds.map((txId) => requirePendingTx2(state, txId));
3483
3608
  }
3484
3609
  function rewriteSessionState(runtime, state) {
3485
3610
  let changed = false;
@@ -3641,7 +3766,7 @@ async function executeTransactionWithFallback(params) {
3641
3766
  }
3642
3767
  }
3643
3768
  async function signCommand(runtime) {
3644
- var _a3, _b;
3769
+ var _a3, _b, _c, _d;
3645
3770
  const txIds = runtime.parsed.positional;
3646
3771
  if (txIds.length === 0) {
3647
3772
  fatal(
@@ -3711,6 +3836,34 @@ async function signCommand(runtime) {
3711
3836
  if (callList.length > 1 && rpcUrl && new Set(callList.map((call) => call.chainId)).size > 1) {
3712
3837
  fatal("A single `--rpc-url` override cannot be used for a mixed-chain multi-sign request.");
3713
3838
  }
3839
+ try {
3840
+ const simResponse = await session.client.simulateBatch(
3841
+ state.sessionId,
3842
+ pendingTxs.map((tx) => tx.id)
3843
+ );
3844
+ const { result: sim } = simResponse;
3845
+ if (!sim.batch_success) {
3846
+ const failed = sim.steps.find((s) => !s.success);
3847
+ fatal(
3848
+ `Simulation failed at step ${(_c = failed == null ? void 0 : failed.step) != null ? _c : "?"}: ${(_d = failed == null ? void 0 : failed.revert_reason) != null ? _d : "unknown"}`
3849
+ );
3850
+ }
3851
+ if (sim.fee) {
3852
+ const feeEth = (Number(sim.fee.amount_wei) / 1e18).toFixed(6);
3853
+ console.log(
3854
+ `Fee: ${feeEth} ETH \u2192 ${sim.fee.recipient.slice(0, 10)}...`
3855
+ );
3856
+ callList.push({
3857
+ to: sim.fee.recipient,
3858
+ value: sim.fee.amount_wei,
3859
+ chainId: primaryChainId
3860
+ });
3861
+ }
3862
+ } catch (e) {
3863
+ console.log(
3864
+ `${DIM}Simulation unavailable, skipping fee injection.${RESET}`
3865
+ );
3866
+ }
3714
3867
  const decision = resolveCliExecutionDecision({
3715
3868
  config: runtime.config,
3716
3869
  chain,
@@ -3840,12 +3993,15 @@ Usage:
3840
3993
  aomi model set <rig> Set the active model for the current session
3841
3994
  aomi chain list List supported chains
3842
3995
  aomi session list List local sessions with metadata
3996
+ aomi session new Start a fresh local/backend session and make it active
3843
3997
  aomi session resume <id>
3844
3998
  Resume a local session (session-id or session-N)
3845
3999
  aomi session delete <id>
3846
4000
  Delete a local session file (session-id or session-N)
3847
4001
  aomi log Show full conversation history with tool results
3848
4002
  aomi tx List pending and signed transactions
4003
+ aomi simulate <tx-id> [<tx-id> ...]
4004
+ Batch-simulate pending txs atomically (approve \u2192 swap)
3849
4005
  aomi sign <tx-id> [<tx-id> ...] [--eoa | --aa] [--aa-provider <name>] [--aa-mode <mode>]
3850
4006
  Sign and submit a pending transaction
3851
4007
  aomi secret list List configured secrets for the active session
@@ -3860,6 +4016,8 @@ Options:
3860
4016
  --api-key <key> API key for non-default apps
3861
4017
  --app <name> App (default: "default")
3862
4018
  --model <rig> Set the active model for this session
4019
+ --new-session Create a fresh active session for this command
4020
+ --chain <id> Active chain for chat/session context
3863
4021
  --public-key <addr> Wallet address (so the agent knows your wallet)
3864
4022
  --private-key <key> Hex private key for signing
3865
4023
  --rpc-url <url> RPC URL for transaction submission
@@ -3883,10 +4041,11 @@ Default signing behavior:
3883
4041
  fall back to EOA automatically if AA is unavailable
3884
4042
 
3885
4043
  Environment (overridden by flags):
3886
- AOMI_BASE_URL Backend URL
4044
+ AOMI_BACKEND_URL Backend URL
3887
4045
  AOMI_API_KEY API key
3888
4046
  AOMI_APP App
3889
4047
  AOMI_MODEL Model rig
4048
+ AOMI_CHAIN_ID Active chain for chat/session context
3890
4049
  AOMI_PUBLIC_KEY Wallet address
3891
4050
  AOMI_AA_PROVIDER AA provider: alchemy | pimlico
3892
4051
  AOMI_AA_MODE AA mode: 4337 | 7702
@@ -3927,6 +4086,9 @@ async function main(runtime) {
3927
4086
  case "sign":
3928
4087
  await signCommand(runtime);
3929
4088
  break;
4089
+ case "simulate":
4090
+ await simulateCommand(runtime);
4091
+ break;
3930
4092
  case "status":
3931
4093
  await statusCommand(runtime);
3932
4094
  break;
@@ -3964,7 +4126,7 @@ function isPnpmExecWrapper() {
3964
4126
  async function runCli(argv = process.argv) {
3965
4127
  const runtime = createRuntime(argv);
3966
4128
  const RED = "\x1B[31m";
3967
- const RESET3 = "\x1B[0m";
4129
+ const RESET2 = "\x1B[0m";
3968
4130
  const strictExit = process.env.AOMI_CLI_STRICT_EXIT === "1";
3969
4131
  try {
3970
4132
  await main(runtime);
@@ -3977,7 +4139,7 @@ async function runCli(argv = process.argv) {
3977
4139
  return;
3978
4140
  }
3979
4141
  const message = err instanceof Error ? err.message : String(err);
3980
- console.error(`${RED}\u274C ${message}${RESET3}`);
4142
+ console.error(`${RED}\u274C ${message}${RESET2}`);
3981
4143
  process.exit(1);
3982
4144
  }
3983
4145
  }
package/dist/index.cjs CHANGED
@@ -615,6 +615,31 @@ var AomiClient = class {
615
615
  }
616
616
  return postState(this.baseUrl, "/api/control/model", payload, sessionId, apiKey);
617
617
  }
618
+ // ===========================================================================
619
+ // Batch Simulation
620
+ // ===========================================================================
621
+ /**
622
+ * Simulate pending transactions as an atomic batch.
623
+ * Each tx sees state changes from previous txs (e.g., approve → swap).
624
+ */
625
+ async simulateBatch(sessionId, txIds) {
626
+ const url = joinApiPath(this.baseUrl, "/api/simulate");
627
+ const headers = new Headers(
628
+ withSessionHeader(sessionId, { "Content-Type": "application/json" })
629
+ );
630
+ if (this.apiKey) {
631
+ headers.set(API_KEY_HEADER, this.apiKey);
632
+ }
633
+ const response = await fetch(url, {
634
+ method: "POST",
635
+ headers,
636
+ body: JSON.stringify({ tx_ids: txIds })
637
+ });
638
+ if (!response.ok) {
639
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
640
+ }
641
+ return await response.json();
642
+ }
618
643
  };
619
644
 
620
645
  // src/types.ts