@heyamiko/amiko-cli 0.9.9 → 0.10.0-beta.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.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @heyamiko/amiko-cli (v0.9.9-beta.8)
1
+ # @heyamiko/amiko-cli (v0.9.9)
2
2
 
3
3
  Manage wallets, credits, swaps, MPP marketplace services, and your Amiko twin (identity, documents, voice, avatar, friends, feed, Composio apps) from the terminal. Works for both human users and AI agents running on OpenClaw.
4
4
 
@@ -86,7 +86,7 @@ amiko credits topup 10000 --wallet Gu2b...QVya # specific wallet (else de
86
86
  ```
87
87
 
88
88
  **Supported tokens:** AMIKO, SOL, USDC, USDT
89
- **Supported chains:** Solana, Base, Ethereum, Tempo
89
+ **Supported chains:** Solana, Base, Ethereum
90
90
 
91
91
  **After every paid command, the CLI prints a `Balance: N credits (~$X.XX)` line** so agents can relay the remaining balance to the user without a separate `credits balance` call.
92
92
 
@@ -152,7 +152,7 @@ amiko wallets bridge routes # available bridge routes
152
152
  amiko wallets bridge limits # min/max limits per route
153
153
  ```
154
154
 
155
- Default route: Solana → Tempo. Use `--from` and `--to` flags for other chains. Like `swap send`, `bridge send` refuses to run in a non-interactive shell without `--yes` so agents must quote the Across fee + gas to the user before executing.
155
+ Default route: Solana → Base. Use `--from` and `--to` flags for other chains. Like `swap send`, `bridge send` refuses to run in a non-interactive shell without `--yes` so agents must quote the Across fee + gas to the user before executing.
156
156
 
157
157
  ## MPP Services
158
158
 
@@ -313,8 +313,7 @@ src/
313
313
  ├── format.ts # terminal output helpers + renderOutput(--json)
314
314
  ├── confirm.ts # requireApproval + confirmDestructive
315
315
  ├── mpp-topup.ts # MPP SDK topup helper
316
- ├── across.ts # Across Protocol bridge
317
- └── tempo.ts # Tempo chain helpers
316
+ └── across.ts # Across Protocol bridge
318
317
  ```
319
318
 
320
319
  ## Twin & Social Commands (v0.8.0)
@@ -392,6 +391,12 @@ npm publish
392
391
 
393
392
  ## Changelog
394
393
 
394
+ ### 0.9.9
395
+
396
+ - **Tempo support removed (breaking).** `wallets bridge` default destination is now Base instead of Tempo (see "Default route: Solana → Base" above and the existing `--from`/`--to` flags). The `tempo` entry is gone from `across.ts` and `lib/tempo.ts` is deleted, so `--to tempo` will now error with an unknown-chain message — there is no escape hatch. **Update any scripts or agents that relied on the old default**: pass `--to base` (or another supported chain) explicitly. `search` no longer falls back to the Tempo explorer either.
397
+ - `lib/api.ts` fetches now wrap an `AbortController` with a 30 s default timeout — a stalled MPP endpoint no longer hangs the agent process. `markets service call` adds a 60 s timeout on the paid call and 15 s on the openapi.json price probe.
398
+ - **Security fix.** `markets service call <method> <path>` now rejects any `path` that doesn't start with `/`, closing a credential-leakage hole. The previous behavior treated `http(s)://…` paths as full URLs, which let a caller redirect the paid request — including the `X-Payment-Tx` and `X-Twin-Id` headers — to an arbitrary external host, leaking the on-chain payment proof + twin identity.
399
+
395
400
  ### 0.9.9-beta.8
396
401
 
397
402
  - `friends reports request --pay` now does a **free preflight** (`POST /v1/friend-reports/preflight`) before signing the on-chain AMIKO transfer. Validates User → Friendship → consent → personality profile against amiko-web. If anything's wrong (wrong id shape, missing consent, missing profile), the CLI aborts with a targeted tip — no AMIKO is transferred. Closes a hole where every retry against a bad `--id` burned ~3 AMIKO via the irreversible X-Payment-Tx flow.
package/dist/index.js CHANGED
@@ -7146,7 +7146,7 @@ function getRpcTransportWithRequestCoalescing(transport, getDeduplicationKey) {
7146
7146
  coalescedRequest.numConsumers++;
7147
7147
  if (signal) {
7148
7148
  const responsePromise = coalescedRequest.responsePromise;
7149
- return await new Promise((resolve2, reject) => {
7149
+ return await new Promise((resolve, reject) => {
7150
7150
  const handleAbort = (e22) => {
7151
7151
  signal.removeEventListener("abort", handleAbort);
7152
7152
  coalescedRequest.numConsumers -= 1;
@@ -7159,7 +7159,7 @@ function getRpcTransportWithRequestCoalescing(transport, getDeduplicationKey) {
7159
7159
  reject(e22.target.reason);
7160
7160
  };
7161
7161
  signal.addEventListener("abort", handleAbort);
7162
- responsePromise.then(resolve2).catch(reject).finally(() => {
7162
+ responsePromise.then(resolve).catch(reject).finally(() => {
7163
7163
  signal.removeEventListener("abort", handleAbort);
7164
7164
  });
7165
7165
  });
@@ -25831,51 +25831,71 @@ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
25831
25831
  var source_default = chalk;
25832
25832
 
25833
25833
  // src/lib/agent-config.ts
25834
- import { existsSync, readFileSync } from "node:fs";
25835
- import { join, resolve } from "node:path";
25836
- var OPENCLAW_FALLBACK_PATH = "/data/.openclaw/workspace/.amiko.json";
25837
- function tryRead(filePath) {
25838
- if (!existsSync(filePath))
25839
- return null;
25834
+ import { existsSync, readFileSync, statSync } from "node:fs";
25835
+ import { join } from "node:path";
25836
+ var OPENCLAW_ROOT = "/data/.openclaw";
25837
+ var OPENCLAW_WORKSPACE_PREFIX = "/data/.openclaw/workspace";
25838
+ var OPENCLAW_AMIKO_JSON = "/data/.openclaw/workspace/.amiko.json";
25839
+ function isOpenclawEnvironment() {
25840
25840
  try {
25841
- return JSON.parse(readFileSync(filePath, "utf-8"));
25841
+ if (!statSync(OPENCLAW_ROOT).isDirectory())
25842
+ return false;
25842
25843
  } catch {
25843
- return null;
25844
+ return false;
25844
25845
  }
25846
+ return process.cwd().startsWith(OPENCLAW_WORKSPACE_PREFIX);
25845
25847
  }
25846
- function findAmikoJson() {
25847
- let dir = process.cwd();
25848
- const root = resolve("/");
25849
- while (dir !== root) {
25850
- const parsed = tryRead(join(dir, ".amiko.json"));
25851
- if (parsed)
25852
- return parsed;
25853
- dir = resolve(dir, "..");
25848
+ function findAmikoJsonPath() {
25849
+ const cwdPath = join(process.cwd(), ".amiko.json");
25850
+ if (existsSync(cwdPath))
25851
+ return cwdPath;
25852
+ if (isOpenclawEnvironment() && existsSync(OPENCLAW_AMIKO_JSON)) {
25853
+ return OPENCLAW_AMIKO_JSON;
25854
25854
  }
25855
- return tryRead(OPENCLAW_FALLBACK_PATH);
25855
+ return null;
25856
25856
  }
25857
25857
  function asString(v) {
25858
25858
  return typeof v === "string" && v.length > 0 ? v : undefined;
25859
25859
  }
25860
- function resolveAuth() {
25861
- const parsed = findAmikoJson();
25862
- if (!parsed)
25863
- return {};
25860
+ function readFromFile(path) {
25861
+ try {
25862
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
25863
+ return {
25864
+ twinToken: asString(parsed.amikoTwinToken) ?? asString(parsed.AMIKO_TWIN_TOKEN) ?? asString(parsed.AMIKO_USER_TOKEN),
25865
+ twinId: asString(parsed.amikoTwinId) ?? asString(parsed.AMIKO_TWIN_ID),
25866
+ userId: asString(parsed.amikoUserId) ?? asString(parsed.AMIKO_USER_ID),
25867
+ platformUrl: asString(parsed.amikoPlatformUrl) ?? asString(parsed.AMIKO_PLATFORM_URL),
25868
+ apiBase: asString(parsed.amikoApiBase) ?? asString(parsed.AMIKO_API_BASE) ?? asString(parsed.apiBase)
25869
+ };
25870
+ } catch {
25871
+ return null;
25872
+ }
25873
+ }
25874
+ function readFromEnv() {
25864
25875
  return {
25865
- twinToken: asString(parsed.amikoTwinToken) ?? asString(parsed.AMIKO_TWIN_TOKEN) ?? asString(parsed.AMIKO_USER_TOKEN),
25866
- twinId: asString(parsed.amikoTwinId) ?? asString(parsed.AMIKO_TWIN_ID),
25867
- userId: asString(parsed.amikoUserId) ?? asString(parsed.AMIKO_USER_ID),
25868
- platformUrl: asString(parsed.amikoPlatformUrl) ?? asString(parsed.AMIKO_PLATFORM_URL),
25869
- apiBase: asString(parsed.apiBase)
25876
+ twinToken: asString(process.env.AMIKO_TWIN_TOKEN) ?? asString(process.env.AMIKO_USER_TOKEN),
25877
+ twinId: asString(process.env.AMIKO_TWIN_ID),
25878
+ userId: asString(process.env.AMIKO_USER_ID),
25879
+ platformUrl: asString(process.env.AMIKO_PLATFORM_URL),
25880
+ apiBase: asString(process.env.AMIKO_API_BASE)
25870
25881
  };
25871
25882
  }
25883
+ function resolveAuth() {
25884
+ const filePath = findAmikoJsonPath();
25885
+ if (filePath) {
25886
+ const fromFile = readFromFile(filePath);
25887
+ if (fromFile)
25888
+ return fromFile;
25889
+ }
25890
+ return readFromEnv();
25891
+ }
25892
+ var NO_TOKEN_HINT = "No twin token found. Set AMIKO_TWIN_TOKEN env var, or place .amiko.json with amikoTwinToken in the current directory.";
25872
25893
  function requireAuth() {
25873
25894
  const auth = resolveAuth();
25874
- if (!auth.twinToken) {
25875
- throw new Error("No twin token found. Place an .amiko.json file with amikoTwinToken in the current directory or any parent (or at /data/.openclaw/workspace/.amiko.json).");
25876
- }
25895
+ if (!auth.twinToken)
25896
+ throw new Error(NO_TOKEN_HINT);
25877
25897
  if (!auth.twinId) {
25878
- throw new Error("No twin id in .amiko.json. Add amikoTwinId alongside amikoTwinToken.");
25898
+ throw new Error("No twin id found. Set AMIKO_TWIN_ID env var, or add amikoTwinId to .amiko.json.");
25879
25899
  }
25880
25900
  return auth;
25881
25901
  }
@@ -26855,13 +26875,26 @@ function ora(options) {
26855
26875
  }
26856
26876
 
26857
26877
  // src/lib/api.ts
26878
+ var DEFAULT_TIMEOUT_MS = 30000;
26858
26879
  function mppBase() {
26859
26880
  return trimBase(loadConfig().apiBase);
26860
26881
  }
26861
- async function get(path) {
26862
- const res = await fetch(`${mppBase()}${path}`, {
26863
- headers: { "Content-Type": "application/json" }
26864
- });
26882
+ async function fetchWithTimeout(url, init = {}, timeoutMs = DEFAULT_TIMEOUT_MS) {
26883
+ const controller = new AbortController;
26884
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
26885
+ try {
26886
+ return await fetch(url, { ...init, signal: controller.signal });
26887
+ } catch (err) {
26888
+ if (err instanceof Error && err.name === "AbortError") {
26889
+ throw new Error(`Request timed out after ${timeoutMs}ms: ${url}`);
26890
+ }
26891
+ throw err;
26892
+ } finally {
26893
+ clearTimeout(timer);
26894
+ }
26895
+ }
26896
+ async function get(path, timeoutMs) {
26897
+ const res = await fetchWithTimeout(`${mppBase()}${path}`, { headers: { "Content-Type": "application/json" } }, timeoutMs);
26865
26898
  await assertOk(res);
26866
26899
  return res.json();
26867
26900
  }
@@ -26962,46 +26995,6 @@ function registerDiscoveryCommand(program2) {
26962
26995
  });
26963
26996
  }
26964
26997
 
26965
- // src/lib/tempo.ts
26966
- import { execFile, execFileSync } from "node:child_process";
26967
- import { existsSync as existsSync2 } from "node:fs";
26968
- import { homedir } from "node:os";
26969
- import { join as join2 } from "node:path";
26970
- var TEMPO_PATHS = [join2(homedir(), ".tempo", "bin", "tempo"), "tempo"];
26971
- function findTempo() {
26972
- for (const p of TEMPO_PATHS) {
26973
- if (p === "tempo") {
26974
- try {
26975
- execFileSync(p, ["--version"], { stdio: "pipe", timeout: 3000 });
26976
- return p;
26977
- } catch {
26978
- continue;
26979
- }
26980
- } else if (existsSync2(p)) {
26981
- return p;
26982
- }
26983
- }
26984
- return null;
26985
- }
26986
- function hasTempo() {
26987
- return findTempo() !== null;
26988
- }
26989
- function runTempo(args, timeoutMs = 60000) {
26990
- const bin = findTempo();
26991
- if (!bin)
26992
- throw new Error("Tempo CLI not installed. Install: curl -fsSL https://tempo.xyz/install | bash");
26993
- return new Promise((resolve2, reject) => {
26994
- execFile(bin, args, { timeout: timeoutMs, maxBuffer: 1024 * 1024 * 10 }, (err, stdout, stderr) => {
26995
- if (err) {
26996
- const msg = stderr?.trim() || stdout?.trim() || err.message;
26997
- reject(new Error(msg));
26998
- return;
26999
- }
27000
- resolve2(stdout.trim() || stderr.trim());
27001
- });
27002
- });
27003
- }
27004
-
27005
26998
  // src/lib/confirm.ts
27006
26999
  import readline from "node:readline";
27007
27000
  function promptYesNo(question) {
@@ -27009,10 +27002,10 @@ function promptYesNo(question) {
27009
27002
  input: process.stdin,
27010
27003
  output: process.stdout
27011
27004
  });
27012
- return new Promise((resolve2) => {
27005
+ return new Promise((resolve) => {
27013
27006
  rl.question(question, (answer) => {
27014
27007
  rl.close();
27015
- resolve2(/^y(es)?$/i.test(answer.trim()));
27008
+ resolve(/^y(es)?$/i.test(answer.trim()));
27016
27009
  });
27017
27010
  });
27018
27011
  }
@@ -27074,7 +27067,7 @@ function baseUrl(auth) {
27074
27067
  function authHeader(auth) {
27075
27068
  if (auth.twinToken)
27076
27069
  return `Bearer ${auth.twinToken}`;
27077
- throw new Error("No twin token found. Place an .amiko.json file with amikoTwinToken in the current directory or any parent.");
27070
+ throw new Error("No twin token found. Set AMIKO_TWIN_TOKEN env var, or place .amiko.json with amikoTwinToken in the current directory.");
27078
27071
  }
27079
27072
  async function amikoWebFetch(auth, path, opts = {}) {
27080
27073
  const url = new URL(`${baseUrl(auth)}${path}`);
@@ -27173,7 +27166,6 @@ var CHAINS = {
27173
27166
  optimism: 10,
27174
27167
  polygon: 137,
27175
27168
  arbitrum: 42161,
27176
- tempo: 4217,
27177
27169
  base: 8453,
27178
27170
  solana: 34268394551451,
27179
27171
  linea: 59144,
@@ -27184,7 +27176,6 @@ var CHAIN_NAMES = {
27184
27176
  [CHAINS.optimism]: "Optimism",
27185
27177
  [CHAINS.polygon]: "Polygon",
27186
27178
  [CHAINS.arbitrum]: "Arbitrum",
27187
- [CHAINS.tempo]: "Tempo",
27188
27179
  [CHAINS.base]: "Base",
27189
27180
  [CHAINS.solana]: "Solana",
27190
27181
  [CHAINS.linea]: "Linea",
@@ -27207,7 +27198,6 @@ var CHAIN_NAMES = {
27207
27198
  };
27208
27199
  var USDC_ADDRESSES = {
27209
27200
  [CHAINS.solana]: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
27210
- [CHAINS.tempo]: "0x20C000000000000000000000b9537d11c60E8b50",
27211
27201
  [CHAINS.ethereum]: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
27212
27202
  [CHAINS.optimism]: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
27213
27203
  [CHAINS.arbitrum]: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
@@ -27249,7 +27239,7 @@ async function getSwapQuote(opts) {
27249
27239
  depositor: opts.depositor,
27250
27240
  recipient: opts.recipient,
27251
27241
  slippage: opts.slippage ?? "auto",
27252
- skipOriginTxEstimation: false
27242
+ skipOriginTxEstimation: "false"
27253
27243
  });
27254
27244
  }
27255
27245
  async function getDepositStatus(depositTxHash, originChainId) {
@@ -27288,7 +27278,6 @@ function resolveChainId(input) {
27288
27278
  arb: CHAINS.arbitrum,
27289
27279
  optimism: CHAINS.optimism,
27290
27280
  op: CHAINS.optimism,
27291
- tempo: CHAINS.tempo,
27292
27281
  base: CHAINS.base,
27293
27282
  polygon: CHAINS.polygon,
27294
27283
  matic: CHAINS.polygon,
@@ -27311,10 +27300,10 @@ function getUsdcAddress(chainId) {
27311
27300
 
27312
27301
  // src/commands/bridge.ts
27313
27302
  var DEFAULT_ORIGIN = CHAINS.solana;
27314
- var DEFAULT_DESTINATION = CHAINS.tempo;
27303
+ var DEFAULT_DESTINATION = CHAINS.base;
27315
27304
  function registerBridgeCommand(program2) {
27316
27305
  const cmd = program2.command("bridge").description("Bridge USDC across chains via Across Protocol");
27317
- cmd.command("quote").description("Get a bridge quote for USDC transfer").argument("<amount>", "Amount of USDC to bridge (e.g., 10, 5.50)").option("--from <chain>", "Origin chain (default: solana)", "solana").option("--to <chain>", "Destination chain (default: tempo)", "tempo").option("--recipient <address>", "Recipient address on destination chain (required for Solana ↔ EVM)").option("--depositor <address>", "Depositor address on origin chain (defaults to your wallet on that chain)").action(async (amountStr, opts) => {
27306
+ cmd.command("quote").description("Get a bridge quote for USDC transfer").argument("<amount>", "Amount of USDC to bridge (e.g., 10, 5.50)").option("--from <chain>", "Origin chain (default: solana)", "solana").option("--to <chain>", "Destination chain (default: base)", "base").option("--recipient <address>", "Recipient address on destination chain (required for Solana ↔ EVM)").option("--depositor <address>", "Depositor address on origin chain (defaults to your wallet on that chain)").action(async (amountStr, opts) => {
27318
27307
  const spinner = ora("Fetching bridge quote...").start();
27319
27308
  try {
27320
27309
  const originChainId = resolveChainId(opts.from);
@@ -27386,7 +27375,7 @@ function registerBridgeCommand(program2) {
27386
27375
  process.exit(1);
27387
27376
  }
27388
27377
  });
27389
- cmd.command("send").description("Execute a USDC bridge transfer via Across").argument("<amount>", "Amount of USDC to bridge (e.g., 10, 5.50)").option("--from <chain>", "Origin chain (default: solana)", "solana").option("--to <chain>", "Destination chain (default: tempo)", "tempo").option("--recipient <address>", "Recipient address on destination chain (required for Solana ↔ EVM)").option("--depositor <address>", "Depositor address on origin chain (defaults to your wallet on that chain)").option("--raw", "Output raw transaction JSON for manual signing").option("--yes", "Skip confirmation (only after the user has approved)").action(async (amountStr, opts) => {
27378
+ cmd.command("send").description("Execute a USDC bridge transfer via Across").argument("<amount>", "Amount of USDC to bridge (e.g., 10, 5.50)").option("--from <chain>", "Origin chain (default: solana)", "solana").option("--to <chain>", "Destination chain (default: base)", "base").option("--recipient <address>", "Recipient address on destination chain (required for Solana ↔ EVM)").option("--depositor <address>", "Depositor address on origin chain (defaults to your wallet on that chain)").option("--raw", "Output raw transaction JSON for manual signing").option("--yes", "Skip confirmation (only after the user has approved)").action(async (amountStr, opts) => {
27390
27379
  const spinner = ora("Preparing bridge transaction...").start();
27391
27380
  try {
27392
27381
  const originChainId = resolveChainId(opts.from);
@@ -27456,14 +27445,6 @@ function registerBridgeCommand(program2) {
27456
27445
  console.log(label("Expires", new Date(quote.quoteExpiryTimestamp * 1000).toISOString()));
27457
27446
  return;
27458
27447
  }
27459
- if (!hasTempo()) {
27460
- console.log("");
27461
- console.log(warn("Tempo CLI not installed — cannot auto-sign transaction."));
27462
- console.log(dim("Install Tempo: curl -fsSL https://tempo.xyz/install | bash"));
27463
- console.log("");
27464
- console.log(dim("Or use --raw to get transaction data for manual signing."));
27465
- process.exit(1);
27466
- }
27467
27448
  if (!quote.swapTx) {
27468
27449
  console.log("");
27469
27450
  console.log(warn("No swap transaction returned — the route may require manual steps."));
@@ -27471,55 +27452,13 @@ function registerBridgeCommand(program2) {
27471
27452
  process.exit(1);
27472
27453
  }
27473
27454
  if (quote.approvalTxns && quote.approvalTxns.length > 0) {
27474
- for (let i = 0;i < quote.approvalTxns.length; i++) {
27475
- const approvalSpinner = ora(`Submitting approval ${i + 1}/${quote.approvalTxns.length}...`).start();
27476
- try {
27477
- const txn = quote.approvalTxns[i];
27478
- await runTempo([
27479
- "tx",
27480
- "send",
27481
- "--chain",
27482
- String(txn.chainId),
27483
- "--to",
27484
- txn.to,
27485
- "--data",
27486
- txn.data
27487
- ], 120000);
27488
- approvalSpinner.succeed(`Approval ${i + 1}/${quote.approvalTxns.length} confirmed`);
27489
- } catch (err) {
27490
- approvalSpinner.fail(`Approval ${i + 1} failed`);
27491
- console.error(error(err instanceof Error ? err.message : "Approval transaction failed"));
27492
- process.exit(1);
27493
- }
27494
- }
27495
- }
27496
- const bridgeSpinner = ora("Submitting bridge transaction...").start();
27497
- try {
27498
- const tx = quote.swapTx;
27499
- const output = await runTempo([
27500
- "tx",
27501
- "send",
27502
- "--chain",
27503
- String(tx.chainId),
27504
- "--to",
27505
- tx.to,
27506
- "--data",
27507
- tx.data,
27508
- ...tx.value && tx.value !== "0" ? ["--value", tx.value] : []
27509
- ], 180000);
27510
- bridgeSpinner.stop();
27511
27455
  console.log("");
27512
- console.log(success("Bridge transaction submitted"));
27513
- console.log("");
27514
- console.log(dim(output.trim()));
27515
- console.log("");
27516
- console.log(dim("Track status:"));
27517
- console.log(dim(` amiko wallets bridge status <txHash> --chain ${originChainId}`));
27518
- } catch (err) {
27519
- bridgeSpinner.fail("Bridge transaction failed");
27520
- console.error(error(err instanceof Error ? err.message : "Transaction failed"));
27521
- process.exit(1);
27456
+ console.log(warn("Approval transaction data is required before bridging."));
27522
27457
  }
27458
+ console.log("");
27459
+ console.log(warn("Automatic bridge signing is not available in this CLI build."));
27460
+ console.log(dim("Run the same command with --raw and sign the returned transaction data with your wallet."));
27461
+ process.exit(1);
27523
27462
  } catch (err) {
27524
27463
  spinner.stop();
27525
27464
  console.error(error(err instanceof Error ? err.message : "Bridge failed"));
@@ -27627,7 +27566,7 @@ function registerBridgeCommand(program2) {
27627
27566
  process.exit(1);
27628
27567
  }
27629
27568
  });
27630
- cmd.command("limits").description("Check transfer limits for a USDC bridge route").option("--from <chain>", "Origin chain (default: solana)", "solana").option("--to <chain>", "Destination chain (default: tempo)", "tempo").action(async (opts) => {
27569
+ cmd.command("limits").description("Check transfer limits for a USDC bridge route").option("--from <chain>", "Origin chain (default: solana)", "solana").option("--to <chain>", "Destination chain (default: base)", "base").action(async (opts) => {
27631
27570
  const spinner = ora("Fetching limits...").start();
27632
27571
  try {
27633
27572
  const originChainId = resolveChainId(opts.from);
@@ -27763,10 +27702,10 @@ function promptYesNo2(question) {
27763
27702
  input: process.stdin,
27764
27703
  output: process.stdout
27765
27704
  });
27766
- return new Promise((resolve2) => {
27705
+ return new Promise((resolve) => {
27767
27706
  rl.question(question, (answer) => {
27768
27707
  rl.close();
27769
- resolve2(/^y(es)?$/i.test(answer.trim()));
27708
+ resolve(/^y(es)?$/i.test(answer.trim()));
27770
27709
  });
27771
27710
  });
27772
27711
  }
@@ -27871,7 +27810,7 @@ function registerCreditsCommand(program2) {
27871
27810
  }
27872
27811
  if (options2.method === "mpp") {
27873
27812
  if (!auth.userId) {
27874
- console.error(error("--method mpp requires amikoUserId in .amiko.json."));
27813
+ console.error(error("--method mpp requires user id. Set AMIKO_USER_ID env var, or add amikoUserId to .amiko.json."));
27875
27814
  process.exit(1);
27876
27815
  }
27877
27816
  if (!options2.keyId) {
@@ -28177,12 +28116,12 @@ function registerSwapCommand(program2) {
28177
28116
  }
28178
28117
 
28179
28118
  // src/commands/update.ts
28180
- import { execFileSync as execFileSync2 } from "node:child_process";
28119
+ import { execFileSync } from "node:child_process";
28181
28120
  import fs from "node:fs";
28182
28121
  import path from "node:path";
28183
28122
  function detectPackageManager() {
28184
28123
  try {
28185
- execFileSync2("bun", ["--version"], { stdio: "ignore" });
28124
+ execFileSync("bun", ["--version"], { stdio: "ignore" });
28186
28125
  return "bun";
28187
28126
  } catch {
28188
28127
  return "npm";
@@ -28253,34 +28192,39 @@ async function resolveTargetVersion(pkg, currentVersion) {
28253
28192
  return { version: latest, channel: "latest" };
28254
28193
  return { version: "latest", channel: "latest" };
28255
28194
  }
28256
- var DATA_CLI = "/data/.amiko-cli";
28257
- var NPM_GLOBAL_CANDIDATES = [
28195
+ var OPENCLAW_DATA_CLI = "/data/.amiko-cli";
28196
+ var OPENCLAW_NPM_GLOBAL_CANDIDATES = [
28258
28197
  "/usr/local/lib/node_modules/@heyamiko/amiko-cli",
28259
28198
  "/usr/lib/node_modules/@heyamiko/amiko-cli"
28260
28199
  ];
28261
- var BIN_TARGET = "/usr/local/bin/amiko";
28262
- var TWIN_SKILL_DIRS = [
28200
+ var OPENCLAW_BIN_TARGET = "/usr/local/bin/amiko";
28201
+ var OPENCLAW_SKILL_DIRS = [
28263
28202
  "/data/.openclaw/skills/amiko-cli",
28264
28203
  "/data/.openclaw/workspace/skills/amiko-cli"
28265
28204
  ];
28266
- function syncTwinInstall() {
28267
- if (!fs.existsSync(DATA_CLI))
28205
+ function syncOpenclawInstall() {
28206
+ if (!isOpenclawEnvironment())
28207
+ return false;
28208
+ if (!fs.existsSync(OPENCLAW_DATA_CLI))
28268
28209
  return false;
28269
- const src = NPM_GLOBAL_CANDIDATES.find((p) => fs.existsSync(p));
28210
+ const src = OPENCLAW_NPM_GLOBAL_CANDIDATES.find((p) => fs.existsSync(p));
28270
28211
  if (!src)
28271
28212
  return false;
28272
- fs.rmSync(DATA_CLI, { recursive: true, force: true });
28273
- execFileSync2("cp", ["-rL", src, DATA_CLI]);
28213
+ const tmp = `${OPENCLAW_DATA_CLI}.tmp`;
28214
+ fs.rmSync(tmp, { recursive: true, force: true });
28215
+ execFileSync("cp", ["-rL", src, tmp]);
28216
+ fs.rmSync(OPENCLAW_DATA_CLI, { recursive: true, force: true });
28217
+ fs.renameSync(tmp, OPENCLAW_DATA_CLI);
28274
28218
  try {
28275
- fs.unlinkSync(BIN_TARGET);
28219
+ fs.unlinkSync(OPENCLAW_BIN_TARGET);
28276
28220
  } catch {}
28277
- fs.symlinkSync(path.join(DATA_CLI, "dist/index.js"), BIN_TARGET);
28221
+ fs.symlinkSync(path.join(OPENCLAW_DATA_CLI, "dist/index.js"), OPENCLAW_BIN_TARGET);
28278
28222
  try {
28279
- fs.chmodSync(BIN_TARGET, 493);
28223
+ fs.chmodSync(OPENCLAW_BIN_TARGET, 493);
28280
28224
  } catch {}
28281
- const skillSrc = path.join(DATA_CLI, "skills/SKILL.md");
28225
+ const skillSrc = path.join(OPENCLAW_DATA_CLI, "skills/SKILL.md");
28282
28226
  if (fs.existsSync(skillSrc)) {
28283
- for (const dir of TWIN_SKILL_DIRS) {
28227
+ for (const dir of OPENCLAW_SKILL_DIRS) {
28284
28228
  const parent = path.dirname(dir);
28285
28229
  if (!fs.existsSync(parent))
28286
28230
  continue;
@@ -28310,25 +28254,28 @@ function registerUpdateCommand(program2) {
28310
28254
  const spinner = ora(`Installing ${installSpec}...`).start();
28311
28255
  try {
28312
28256
  if (pm === "bun") {
28313
- execFileSync2("bun", ["install", "-g", installSpec], { stdio: "pipe" });
28257
+ execFileSync("bun", ["install", "-g", installSpec], { stdio: "pipe" });
28314
28258
  } else {
28315
- execFileSync2("npm", ["install", "-g", installSpec], { stdio: "pipe" });
28259
+ execFileSync("npm", ["install", "-g", installSpec], { stdio: "pipe" });
28316
28260
  }
28317
28261
  spinner.stop();
28318
- let syncedTwin = false;
28319
- try {
28320
- const twinSpinner = ora("Refreshing twin install...").start();
28321
- syncedTwin = syncTwinInstall();
28322
- twinSpinner.stop();
28323
- if (syncedTwin) {
28262
+ if (isOpenclawEnvironment()) {
28263
+ const twinSpinner = ora("Refreshing openclaw install...").start();
28264
+ let synced = false;
28265
+ try {
28266
+ synced = syncOpenclawInstall();
28267
+ } catch (err) {
28268
+ console.log(dim(`Openclaw sync failed: ${err instanceof Error ? err.message : String(err)}. Container restart will apply the update.`));
28269
+ } finally {
28270
+ twinSpinner.stop();
28271
+ }
28272
+ if (synced) {
28324
28273
  console.log(dim("Refreshed /data/.amiko-cli, symlink, and skill files."));
28325
28274
  }
28326
- } catch (err) {
28327
- console.log(dim(`Twin sync failed: ${err instanceof Error ? err.message : String(err)}. Container restart will apply the update.`));
28328
28275
  }
28329
28276
  let newVersion = "latest";
28330
28277
  try {
28331
- newVersion = execFileSync2("amiko", ["--version"], {
28278
+ newVersion = execFileSync("amiko", ["--version"], {
28332
28279
  encoding: "utf-8"
28333
28280
  }).trim();
28334
28281
  } catch {}
@@ -28428,8 +28375,7 @@ function registerSearchCommand(program2) {
28428
28375
  spinner.stop();
28429
28376
  if (!res.ok) {
28430
28377
  await reportHttpError(res, "Search");
28431
- const explorer = chain === "solana" ? `https://solscan.io/tx/${txHash}` : `https://explorer.tempo.xyz/tx/${txHash}`;
28432
- console.log(dim(`Payment tx: ${explorer}`));
28378
+ console.log(dim(`Payment tx: https://solscan.io/tx/${txHash}`));
28433
28379
  process.exit(1);
28434
28380
  }
28435
28381
  const data = await res.json();
@@ -28798,11 +28744,14 @@ function usdToAmiko(usd2, pricePerAmiko) {
28798
28744
  return Math.ceil(amount2 * 100) / 100 + "";
28799
28745
  }
28800
28746
  async function payAndCall(mppBase2, method, path2, auth, wallet, amountOrAuto, body, onProgress, token = "AMIKO") {
28747
+ if (!path2.startsWith("/")) {
28748
+ throw new Error(`path must start with "/" — got: ${path2}`);
28749
+ }
28801
28750
  let amount2 = amountOrAuto;
28802
28751
  if (amount2 === "auto") {
28803
28752
  onProgress?.("Fetching price...");
28804
28753
  try {
28805
- const specRes = await fetch(`${mppBase2}/openapi.json`);
28754
+ const specRes = await fetchWithTimeout2(`${mppBase2}/openapi.json`, {}, 15000);
28806
28755
  if (specRes.ok) {
28807
28756
  const spec = await specRes.json();
28808
28757
  const pathSpec = spec.paths?.[path2];
@@ -28839,10 +28788,24 @@ async function payAndCall(mppBase2, method, path2, auth, wallet, amountOrAuto, b
28839
28788
  if (body && method !== "GET") {
28840
28789
  fetchOpts.body = JSON.stringify(body);
28841
28790
  }
28842
- const url2 = path2.startsWith("http") ? path2 : `${mppBase2}${path2}`;
28843
- const res = await fetch(url2, fetchOpts);
28791
+ const url2 = `${mppBase2}${path2}`;
28792
+ const res = await fetchWithTimeout2(url2, fetchOpts, 60000);
28844
28793
  return { res, txHash, amikoSpent: amount2 };
28845
28794
  }
28795
+ async function fetchWithTimeout2(url2, init = {}, timeoutMs) {
28796
+ const controller = new AbortController;
28797
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
28798
+ try {
28799
+ return await fetch(url2, { ...init, signal: controller.signal });
28800
+ } catch (err) {
28801
+ if (err instanceof Error && err.name === "AbortError") {
28802
+ throw new Error(`Request timed out after ${timeoutMs}ms: ${url2}`);
28803
+ }
28804
+ throw err;
28805
+ } finally {
28806
+ clearTimeout(timer);
28807
+ }
28808
+ }
28846
28809
  function registerServicesCommand(program2) {
28847
28810
  const svc = program2.command("service").alias("svc").description("Call any MPP service — pays with AMIKO tokens");
28848
28811
  svc.command("tts <voiceId> <text>").description("Text-to-speech — 1 AMIKO").option("--raw", "Output raw response").option("--wallet <address>", "Wallet to pay from (defaults to your Solana wallet — run `amiko wallets create --chain solana` if none)").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (voiceId, text, opts) => {
@@ -29063,7 +29026,7 @@ function registerAccountsCommand(program2) {
29063
29026
  console.log(label("Active twin", d.activeTwinId));
29064
29027
  if (!d.authenticated) {
29065
29028
  console.log("");
29066
- console.log(error("No credentials found. Place .amiko.json with amikoTwinToken."));
29029
+ console.log(error("No credentials found. Set AMIKO_TWIN_TOKEN env var, or place .amiko.json with amikoTwinToken in the current directory."));
29067
29030
  }
29068
29031
  }, { json: opts.json });
29069
29032
  });
@@ -29750,7 +29713,7 @@ async function runPaidRegenerate(args) {
29750
29713
  process.exit(1);
29751
29714
  }
29752
29715
  if (!auth.userId) {
29753
- console.error(error("Paid regenerate requires your user id. Add 'amikoUserId' to your .amiko.json (alongside amikoTwinToken/amikoTwinId)."));
29716
+ console.error(error("Paid regenerate requires your user id. Set AMIKO_USER_ID env var, or add 'amikoUserId' to your .amiko.json."));
29754
29717
  process.exit(1);
29755
29718
  }
29756
29719
  const preflightSpinner = args.json ? null : ora("Validating request (no charge)...").start();
@@ -30913,8 +30876,8 @@ function registerNotificationsCommand(program2) {
30913
30876
  }
30914
30877
 
30915
30878
  // src/commands/memory.ts
30916
- import { existsSync as existsSync3, lstatSync, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
30917
- import { dirname, isAbsolute, normalize, relative, resolve as resolve2 } from "node:path";
30879
+ import { existsSync as existsSync2, lstatSync, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
30880
+ import { dirname, isAbsolute, normalize, relative, resolve } from "node:path";
30918
30881
  function isInsideRoot(abs, root) {
30919
30882
  const rel = relative(root, abs);
30920
30883
  return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
@@ -31089,13 +31052,13 @@ function registerMemoryCommand(program2) {
31089
31052
  }
31090
31053
  });
31091
31054
  memory.command("sync").description("Sync MEMORY.md, its referenced files, and every .md under <workspace>/memory/ (one-way: local → server)").option("--path <dir>", `Workspace dir (default ${DEFAULT_WORKSPACE}). Looks for MEMORY.md, memory/MEMORY.md, and memory/**/*.md`).option("--dry-run", "Print what would be synced without uploading").option("--prune", "Delete server-side memory files that no longer exist locally").option("--json", "Output as JSON").action(async (opts) => {
31092
- const root = resolve2(opts.path || DEFAULT_WORKSPACE);
31055
+ const root = resolve(opts.path || DEFAULT_WORKSPACE);
31093
31056
  const candidateIndexes = [
31094
- resolve2(root, "MEMORY.md"),
31095
- resolve2(root, "memory", "MEMORY.md")
31096
- ].filter((p) => existsSync3(p));
31097
- const memoryDir = resolve2(root, "memory");
31098
- const memoryDirExists = existsSync3(memoryDir) && statSync(memoryDir).isDirectory();
31057
+ resolve(root, "MEMORY.md"),
31058
+ resolve(root, "memory", "MEMORY.md")
31059
+ ].filter((p) => existsSync2(p));
31060
+ const memoryDir = resolve(root, "memory");
31061
+ const memoryDirExists = existsSync2(memoryDir) && statSync2(memoryDir).isDirectory();
31099
31062
  if (!candidateIndexes.length && !memoryDirExists) {
31100
31063
  fail(`No MEMORY.md or memory/ folder found under ${root}. Expected ${root}/MEMORY.md or ${root}/memory/.`);
31101
31064
  }
@@ -31195,12 +31158,12 @@ function collectMemoryFiles(root, indexPaths, memoryDir) {
31195
31158
  for (const ref of extractMarkdownLinks(indexContent)) {
31196
31159
  if (isExternal(ref))
31197
31160
  continue;
31198
- const abs = normalize(isAbsolute(ref) ? ref : resolve2(indexDir, ref));
31161
+ const abs = normalize(isAbsolute(ref) ? ref : resolve(indexDir, ref));
31199
31162
  if (!isInsideRoot(abs, root)) {
31200
31163
  errors3.push(`Skipping ${ref}: outside workspace ${root}`);
31201
31164
  continue;
31202
31165
  }
31203
- if (!existsSync3(abs)) {
31166
+ if (!existsSync2(abs)) {
31204
31167
  errors3.push(`Referenced file missing: ${ref}`);
31205
31168
  continue;
31206
31169
  }
@@ -31237,7 +31200,7 @@ function walkMarkdown(dir, errors3) {
31237
31200
  return out;
31238
31201
  }
31239
31202
  for (const name of names) {
31240
- const abs = resolve2(dir, name);
31203
+ const abs = resolve(dir, name);
31241
31204
  let st;
31242
31205
  try {
31243
31206
  st = lstatSync(abs);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyamiko/amiko-cli",
3
- "version": "0.9.9",
3
+ "version": "0.10.0-beta.0",
4
4
  "description": "Amiko CLI — swap tokens, manage credits, bridge cross-chain, and call marketplace agents",
5
5
  "type": "module",
6
6
  "bin": {
package/skills/SKILL.md CHANGED
@@ -116,8 +116,8 @@ amiko wallets transfer --to <addr> --amount 100 --token amiko --yes # send toke
116
116
  amiko wallets swap quote 1.0 SOL USDC # Jupiter quote (free)
117
117
  amiko wallets swap send 1.0 SOL USDC --yes # --wallet defaults to your Solana wallet
118
118
  amiko wallets swap tokens # supported tokens list
119
- amiko wallets bridge quote 10 --from solana --to tempo --recipient <addr>
120
- amiko wallets bridge send 10 --from solana --to tempo --recipient <addr> --yes
119
+ amiko wallets bridge quote 10 --from solana --to base --recipient <addr>
120
+ amiko wallets bridge send 10 --from solana --to base --recipient <addr> --yes
121
121
  amiko wallets bridge status <txHash>
122
122
  amiko wallets bridge routes
123
123
  amiko wallets bridge limits