@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 +10 -5
- package/dist/index.js +154 -191
- package/package.json +1 -1
- package/skills/SKILL.md +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @heyamiko/amiko-cli (v0.9.9
|
|
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
|
|
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 →
|
|
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
|
-
|
|
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((
|
|
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(
|
|
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
|
|
25836
|
-
var
|
|
25837
|
-
|
|
25838
|
-
|
|
25839
|
-
|
|
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
|
-
|
|
25841
|
+
if (!statSync(OPENCLAW_ROOT).isDirectory())
|
|
25842
|
+
return false;
|
|
25842
25843
|
} catch {
|
|
25843
|
-
return
|
|
25844
|
+
return false;
|
|
25844
25845
|
}
|
|
25846
|
+
return process.cwd().startsWith(OPENCLAW_WORKSPACE_PREFIX);
|
|
25845
25847
|
}
|
|
25846
|
-
function
|
|
25847
|
-
|
|
25848
|
-
|
|
25849
|
-
|
|
25850
|
-
|
|
25851
|
-
|
|
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
|
|
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
|
|
25861
|
-
|
|
25862
|
-
|
|
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(
|
|
25866
|
-
twinId: asString(
|
|
25867
|
-
userId: asString(
|
|
25868
|
-
platformUrl: asString(
|
|
25869
|
-
apiBase: asString(
|
|
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(
|
|
25876
|
-
}
|
|
25895
|
+
if (!auth.twinToken)
|
|
25896
|
+
throw new Error(NO_TOKEN_HINT);
|
|
25877
25897
|
if (!auth.twinId) {
|
|
25878
|
-
throw new Error("No twin id
|
|
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
|
|
26862
|
-
const
|
|
26863
|
-
|
|
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((
|
|
27005
|
+
return new Promise((resolve) => {
|
|
27013
27006
|
rl.question(question, (answer) => {
|
|
27014
27007
|
rl.close();
|
|
27015
|
-
|
|
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.
|
|
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.
|
|
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:
|
|
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:
|
|
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(
|
|
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:
|
|
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((
|
|
27705
|
+
return new Promise((resolve) => {
|
|
27767
27706
|
rl.question(question, (answer) => {
|
|
27768
27707
|
rl.close();
|
|
27769
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
28257
|
-
var
|
|
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
|
|
28262
|
-
var
|
|
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
|
|
28267
|
-
if (!
|
|
28205
|
+
function syncOpenclawInstall() {
|
|
28206
|
+
if (!isOpenclawEnvironment())
|
|
28207
|
+
return false;
|
|
28208
|
+
if (!fs.existsSync(OPENCLAW_DATA_CLI))
|
|
28268
28209
|
return false;
|
|
28269
|
-
const src =
|
|
28210
|
+
const src = OPENCLAW_NPM_GLOBAL_CANDIDATES.find((p) => fs.existsSync(p));
|
|
28270
28211
|
if (!src)
|
|
28271
28212
|
return false;
|
|
28272
|
-
|
|
28273
|
-
|
|
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(
|
|
28219
|
+
fs.unlinkSync(OPENCLAW_BIN_TARGET);
|
|
28276
28220
|
} catch {}
|
|
28277
|
-
fs.symlinkSync(path.join(
|
|
28221
|
+
fs.symlinkSync(path.join(OPENCLAW_DATA_CLI, "dist/index.js"), OPENCLAW_BIN_TARGET);
|
|
28278
28222
|
try {
|
|
28279
|
-
fs.chmodSync(
|
|
28223
|
+
fs.chmodSync(OPENCLAW_BIN_TARGET, 493);
|
|
28280
28224
|
} catch {}
|
|
28281
|
-
const skillSrc = path.join(
|
|
28225
|
+
const skillSrc = path.join(OPENCLAW_DATA_CLI, "skills/SKILL.md");
|
|
28282
28226
|
if (fs.existsSync(skillSrc)) {
|
|
28283
|
-
for (const dir of
|
|
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
|
-
|
|
28257
|
+
execFileSync("bun", ["install", "-g", installSpec], { stdio: "pipe" });
|
|
28314
28258
|
} else {
|
|
28315
|
-
|
|
28259
|
+
execFileSync("npm", ["install", "-g", installSpec], { stdio: "pipe" });
|
|
28316
28260
|
}
|
|
28317
28261
|
spinner.stop();
|
|
28318
|
-
|
|
28319
|
-
|
|
28320
|
-
|
|
28321
|
-
|
|
28322
|
-
|
|
28323
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
28843
|
-
const res = await
|
|
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.
|
|
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.
|
|
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
|
|
30917
|
-
import { dirname, isAbsolute, normalize, relative, resolve
|
|
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 =
|
|
31055
|
+
const root = resolve(opts.path || DEFAULT_WORKSPACE);
|
|
31093
31056
|
const candidateIndexes = [
|
|
31094
|
-
|
|
31095
|
-
|
|
31096
|
-
].filter((p) =>
|
|
31097
|
-
const memoryDir =
|
|
31098
|
-
const memoryDirExists =
|
|
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 :
|
|
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 (!
|
|
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 =
|
|
31203
|
+
const abs = resolve(dir, name);
|
|
31241
31204
|
let st;
|
|
31242
31205
|
try {
|
|
31243
31206
|
st = lstatSync(abs);
|
package/package.json
CHANGED
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
|
|
120
|
-
amiko wallets bridge send 10 --from solana --to
|
|
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
|