@heyamiko/amiko-cli 0.7.9 → 0.8.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 +83 -4
- package/dist/index.js +1308 -91
- package/package.json +3 -2
- package/skills/SKILL.md +76 -2
package/dist/index.js
CHANGED
|
@@ -71810,12 +71810,14 @@ function resetConfig() {
|
|
|
71810
71810
|
return true;
|
|
71811
71811
|
}
|
|
71812
71812
|
function getPrivyToken(config) {
|
|
71813
|
-
return config.privyToken
|
|
71813
|
+
return config.privyToken;
|
|
71814
71814
|
}
|
|
71815
71815
|
function normalizeConfig(config) {
|
|
71816
71816
|
const normalized = { ...config };
|
|
71817
|
-
|
|
71818
|
-
|
|
71817
|
+
const raw = normalized;
|
|
71818
|
+
if (!normalized.privyToken && typeof raw.creatorToken === "string") {
|
|
71819
|
+
normalized.privyToken = raw.creatorToken;
|
|
71820
|
+
delete raw.creatorToken;
|
|
71819
71821
|
}
|
|
71820
71822
|
if (LEGACY_SITE_BASES.has(normalized.siteBase)) {
|
|
71821
71823
|
normalized.siteBase = DEFAULTS.siteBase;
|
|
@@ -71996,6 +71998,13 @@ function success(text) {
|
|
|
71996
71998
|
function warn(text) {
|
|
71997
71999
|
return source_default.yellow("⚠ ") + text;
|
|
71998
72000
|
}
|
|
72001
|
+
function renderOutput(data, human, opts = {}) {
|
|
72002
|
+
if (opts.json) {
|
|
72003
|
+
console.log(JSON.stringify(data, null, 2));
|
|
72004
|
+
return;
|
|
72005
|
+
}
|
|
72006
|
+
human(data);
|
|
72007
|
+
}
|
|
71999
72008
|
|
|
72000
72009
|
// src/onboarding.ts
|
|
72001
72010
|
var SKIP_COMMANDS = new Set([
|
|
@@ -72984,10 +72993,7 @@ function authHeaders() {
|
|
|
72984
72993
|
}
|
|
72985
72994
|
async function get(path) {
|
|
72986
72995
|
const res = await fetch(`${mppBase()}${path}`, { headers: authHeaders() });
|
|
72987
|
-
|
|
72988
|
-
const text = await res.text().catch(() => "");
|
|
72989
|
-
throw new Error(`${res.status} ${res.statusText}: ${text}`);
|
|
72990
|
-
}
|
|
72996
|
+
await assertOk(res);
|
|
72991
72997
|
return res.json();
|
|
72992
72998
|
}
|
|
72993
72999
|
async function bootstrapPrivyAccount(token, base = loadConfig().billingBase) {
|
|
@@ -72997,10 +73003,7 @@ async function bootstrapPrivyAccount(token, base = loadConfig().billingBase) {
|
|
|
72997
73003
|
"Content-Type": "application/json"
|
|
72998
73004
|
}
|
|
72999
73005
|
});
|
|
73000
|
-
|
|
73001
|
-
const text = await res.text().catch(() => "");
|
|
73002
|
-
throw new Error(`${res.status} ${res.statusText}: ${text}`);
|
|
73003
|
-
}
|
|
73006
|
+
await assertOk(res);
|
|
73004
73007
|
return res.json();
|
|
73005
73008
|
}
|
|
73006
73009
|
async function createConnectedKeyTopupCheckout(input) {
|
|
@@ -73017,15 +73020,18 @@ async function createConnectedKeyTopupCheckout(input) {
|
|
|
73017
73020
|
cancelUrl: `${siteBase}/?topup=cancelled`
|
|
73018
73021
|
})
|
|
73019
73022
|
});
|
|
73020
|
-
|
|
73021
|
-
const text = await res.text().catch(() => "");
|
|
73022
|
-
throw new Error(`${res.status} ${res.statusText}: ${text}`);
|
|
73023
|
-
}
|
|
73023
|
+
await assertOk(res);
|
|
73024
73024
|
return res.json();
|
|
73025
73025
|
}
|
|
73026
73026
|
function trimBase(base) {
|
|
73027
73027
|
return base.replace(/\/$/, "");
|
|
73028
73028
|
}
|
|
73029
|
+
async function assertOk(res) {
|
|
73030
|
+
if (!res.ok) {
|
|
73031
|
+
const text = await res.text().catch(() => "");
|
|
73032
|
+
throw new Error(`${res.status} ${res.statusText}: ${text}`);
|
|
73033
|
+
}
|
|
73034
|
+
}
|
|
73029
73035
|
|
|
73030
73036
|
// src/lib/tempo.ts
|
|
73031
73037
|
import { execFile, execFileSync as execFileSync2 } from "node:child_process";
|
|
@@ -73321,7 +73327,6 @@ var VALID_KEYS = [
|
|
|
73321
73327
|
"wallet",
|
|
73322
73328
|
"apiKey",
|
|
73323
73329
|
"privyToken",
|
|
73324
|
-
"creatorToken",
|
|
73325
73330
|
"userId",
|
|
73326
73331
|
"twinId",
|
|
73327
73332
|
"twinToken",
|
|
@@ -73475,7 +73480,6 @@ import { execFileSync as execFileSync4 } from "node:child_process";
|
|
|
73475
73480
|
function buildVerifiedAuthConfig(token, bootstrap) {
|
|
73476
73481
|
return {
|
|
73477
73482
|
privyToken: token,
|
|
73478
|
-
creatorToken: undefined,
|
|
73479
73483
|
userId: bootstrap.userId,
|
|
73480
73484
|
connectedKeyId: bootstrap.connectedKey?.id,
|
|
73481
73485
|
connectedKeyName: bootstrap.connectedKey?.name,
|
|
@@ -73690,7 +73694,6 @@ Run "amiko login" again to re-authenticate, or "amiko logout" to sign out.
|
|
|
73690
73694
|
}
|
|
73691
73695
|
saveConfig({
|
|
73692
73696
|
privyToken: undefined,
|
|
73693
|
-
creatorToken: undefined,
|
|
73694
73697
|
userId: undefined,
|
|
73695
73698
|
connectedKeyId: undefined,
|
|
73696
73699
|
connectedKeyName: undefined,
|
|
@@ -74166,7 +74169,8 @@ function registerBridgeCommand(program2) {
|
|
|
74166
74169
|
params.originChainId = resolveChainId(opts.from);
|
|
74167
74170
|
if (opts.to)
|
|
74168
74171
|
params.destinationChainId = resolveChainId(opts.to);
|
|
74169
|
-
const
|
|
74172
|
+
const routesData = await getAvailableRoutes(params);
|
|
74173
|
+
const routes = Array.isArray(routesData) ? routesData : [];
|
|
74170
74174
|
const usdcRoutes = routes.filter((r19) => r19.originTokenSymbol === "USDC" || r19.destinationTokenSymbol === "USDC");
|
|
74171
74175
|
spinner.stop();
|
|
74172
74176
|
console.log(heading("USDC Bridge Routes"));
|
|
@@ -74407,6 +74411,9 @@ Tip: you can also generate a connect link from your Amiko settings`));
|
|
|
74407
74411
|
});
|
|
74408
74412
|
}
|
|
74409
74413
|
|
|
74414
|
+
// src/commands/credits.ts
|
|
74415
|
+
import readline from "node:readline";
|
|
74416
|
+
|
|
74410
74417
|
// src/lib/mpp-topup.ts
|
|
74411
74418
|
async function mppCreditsTopup(params) {
|
|
74412
74419
|
const config2 = loadConfig();
|
|
@@ -74468,6 +74475,21 @@ async function mppCreditsTopup(params) {
|
|
|
74468
74475
|
}
|
|
74469
74476
|
|
|
74470
74477
|
// src/commands/credits.ts
|
|
74478
|
+
function promptYesNo(question) {
|
|
74479
|
+
if (!process.stdin.isTTY) {
|
|
74480
|
+
return Promise.resolve(false);
|
|
74481
|
+
}
|
|
74482
|
+
const rl = readline.createInterface({
|
|
74483
|
+
input: process.stdin,
|
|
74484
|
+
output: process.stdout
|
|
74485
|
+
});
|
|
74486
|
+
return new Promise((resolve2) => {
|
|
74487
|
+
rl.question(question, (answer) => {
|
|
74488
|
+
rl.close();
|
|
74489
|
+
resolve2(/^y(es)?$/i.test(answer.trim()));
|
|
74490
|
+
});
|
|
74491
|
+
});
|
|
74492
|
+
}
|
|
74471
74493
|
function registerCreditsCommand(program2) {
|
|
74472
74494
|
const credits = program2.command("credits").description("Manage Amiko credit balance and top-ups");
|
|
74473
74495
|
credits.command("balance").description("Show credit balance and agent wallet balances").action(async () => {
|
|
@@ -74574,12 +74596,41 @@ function registerCreditsCommand(program2) {
|
|
|
74574
74596
|
process.exit(1);
|
|
74575
74597
|
}
|
|
74576
74598
|
});
|
|
74577
|
-
credits.command("topup
|
|
74578
|
-
const
|
|
74579
|
-
if (
|
|
74580
|
-
console.error(error(
|
|
74599
|
+
credits.command("topup [amount]").description("Top up credits by spending tokens from your agent wallet. <amount> is in credits (10,000 = $1.00). Use --usd <n> to specify dollars, or --spend <n> to specify a token amount (e.g. 1 SOL worth).").option("--token <TOKEN>", "Token to use for top-up").option("--chain <CHAIN>", "Chain to use for top-up").option("--wallet <ADDR>", "Wallet address to use").option("--method <METHOD>", "Payment method: auto, crossmint, mpp", "auto").option("--usd <amount>", "Amount in USD instead of credits (e.g. --usd 1 for $1 = 10,000 credits)").option("--spend <amount>", "Spend this many units of --token and convert to credits at current price (e.g. --spend 1 --token SOL for '1 SOL worth of credits')").option("--yes", "Skip the interactive Proceed? prompt (summary still prints)").action(async (amountStr, options) => {
|
|
74600
|
+
const inputs = [amountStr, options.usd, options.spend].filter((v8) => v8 != null && v8 !== "");
|
|
74601
|
+
if (inputs.length !== 1) {
|
|
74602
|
+
console.error(error("Specify exactly one of: <amount> (credits), --usd <dollars>, or --spend <token amount>."));
|
|
74581
74603
|
process.exit(1);
|
|
74582
74604
|
}
|
|
74605
|
+
let presetCredits = null;
|
|
74606
|
+
let presetUsd = null;
|
|
74607
|
+
let presetSpend = null;
|
|
74608
|
+
if (options.usd != null) {
|
|
74609
|
+
const usd2 = parseFloat(String(options.usd).replace(/,/g, ""));
|
|
74610
|
+
if (!isFinite(usd2) || usd2 <= 0) {
|
|
74611
|
+
console.error(error(`Invalid --usd value: ${options.usd}`));
|
|
74612
|
+
process.exit(1);
|
|
74613
|
+
}
|
|
74614
|
+
presetUsd = usd2;
|
|
74615
|
+
} else if (options.spend != null) {
|
|
74616
|
+
const spend = parseFloat(String(options.spend).replace(/,/g, ""));
|
|
74617
|
+
if (!isFinite(spend) || spend <= 0) {
|
|
74618
|
+
console.error(error(`Invalid --spend value: ${options.spend}`));
|
|
74619
|
+
process.exit(1);
|
|
74620
|
+
}
|
|
74621
|
+
if (!options.token) {
|
|
74622
|
+
console.error(error("--spend requires --token (e.g. --spend 1 --token SOL)"));
|
|
74623
|
+
process.exit(1);
|
|
74624
|
+
}
|
|
74625
|
+
presetSpend = spend;
|
|
74626
|
+
} else {
|
|
74627
|
+
const credits2 = parseInt(String(amountStr).replace(/,/g, ""), 10);
|
|
74628
|
+
if (isNaN(credits2) || credits2 <= 0) {
|
|
74629
|
+
console.error(error(`Invalid amount: ${amountStr}`));
|
|
74630
|
+
process.exit(1);
|
|
74631
|
+
}
|
|
74632
|
+
presetCredits = credits2;
|
|
74633
|
+
}
|
|
74583
74634
|
const spinner = ora("Resolving auth...").start();
|
|
74584
74635
|
try {
|
|
74585
74636
|
const auth = resolveAuth(loadConfig());
|
|
@@ -74605,39 +74656,11 @@ function registerCreditsCommand(program2) {
|
|
|
74605
74656
|
"Content-Type": "application/json"
|
|
74606
74657
|
};
|
|
74607
74658
|
spinner.stop();
|
|
74608
|
-
if (options.method === "mpp" && auth.userId && auth.connectedKeyId) {
|
|
74609
|
-
const spinner2 = ora("Topping up credits via MPP...").start();
|
|
74610
|
-
const mppResult = await mppCreditsTopup({
|
|
74611
|
-
userId: auth.userId,
|
|
74612
|
-
keyId: auth.connectedKeyId,
|
|
74613
|
-
amount: creditsAmount / 1e4
|
|
74614
|
-
});
|
|
74615
|
-
spinner2.stop();
|
|
74616
|
-
if (!mppResult.success) {
|
|
74617
|
-
console.error(error(mppResult.error || "MPP top-up failed"));
|
|
74618
|
-
process.exit(1);
|
|
74619
|
-
}
|
|
74620
|
-
console.log(heading("Top-up Complete (MPP)"));
|
|
74621
|
-
console.log("");
|
|
74622
|
-
if (mppResult.credits?.balance)
|
|
74623
|
-
console.log(label("Balance", mppResult.credits.balance));
|
|
74624
|
-
console.log(success("Credits topped up via MPP."));
|
|
74625
|
-
return;
|
|
74626
|
-
}
|
|
74627
|
-
const agentWallet = auth.agentWallets?.find((w5) => w5.source === "agent");
|
|
74628
|
-
const walletAddr = options.wallet || agentWallet?.address || auth.agentWallets?.[0]?.address;
|
|
74629
|
-
const walletChain = options.chain || auth.agentWallets?.find((w5) => w5.address === walletAddr)?.chain || "solana";
|
|
74630
74659
|
const token = options.token || "AMIKO";
|
|
74631
|
-
if (!walletAddr) {
|
|
74632
|
-
console.error(error("No wallet found. Specify --wallet <address>"));
|
|
74633
|
-
process.exit(1);
|
|
74634
|
-
}
|
|
74635
|
-
const usdAmount = creditsAmount / 1e4;
|
|
74636
|
-
let tokenAmount;
|
|
74637
|
-
let pricePerToken = null;
|
|
74638
74660
|
const stablecoins = ["usdc", "usdt", "usdc.e", "usdt0"];
|
|
74639
|
-
|
|
74640
|
-
|
|
74661
|
+
const isStable = stablecoins.includes(token.toLowerCase());
|
|
74662
|
+
let pricePerToken = null;
|
|
74663
|
+
if (isStable) {
|
|
74641
74664
|
pricePerToken = 1;
|
|
74642
74665
|
} else {
|
|
74643
74666
|
const spinner2 = ora(`Fetching ${token} price...`).start();
|
|
@@ -74663,7 +74686,6 @@ function registerCreditsCommand(program2) {
|
|
|
74663
74686
|
}
|
|
74664
74687
|
const jupData = await jupRes.json();
|
|
74665
74688
|
pricePerToken = parseFloat(jupData.outAmount) / 1e6;
|
|
74666
|
-
tokenAmount = usdAmount / pricePerToken;
|
|
74667
74689
|
spinner2.stop();
|
|
74668
74690
|
} catch {
|
|
74669
74691
|
spinner2.stop();
|
|
@@ -74671,6 +74693,62 @@ function registerCreditsCommand(program2) {
|
|
|
74671
74693
|
process.exit(1);
|
|
74672
74694
|
}
|
|
74673
74695
|
}
|
|
74696
|
+
let creditsAmount;
|
|
74697
|
+
let usdAmount;
|
|
74698
|
+
let tokenAmount;
|
|
74699
|
+
if (presetSpend != null) {
|
|
74700
|
+
tokenAmount = presetSpend;
|
|
74701
|
+
usdAmount = tokenAmount * pricePerToken;
|
|
74702
|
+
creditsAmount = Math.round(usdAmount * 1e4);
|
|
74703
|
+
} else if (presetUsd != null) {
|
|
74704
|
+
usdAmount = presetUsd;
|
|
74705
|
+
creditsAmount = Math.round(usdAmount * 1e4);
|
|
74706
|
+
tokenAmount = usdAmount / pricePerToken;
|
|
74707
|
+
} else {
|
|
74708
|
+
creditsAmount = presetCredits;
|
|
74709
|
+
usdAmount = creditsAmount / 1e4;
|
|
74710
|
+
tokenAmount = usdAmount / pricePerToken;
|
|
74711
|
+
}
|
|
74712
|
+
if (options.method === "mpp" && auth.userId && auth.connectedKeyId) {
|
|
74713
|
+
if (!options.yes) {
|
|
74714
|
+
console.log(heading("Top-up Summary (MPP)"));
|
|
74715
|
+
console.log("");
|
|
74716
|
+
console.log(label("Credits", creditsAmount.toLocaleString()));
|
|
74717
|
+
console.log(label("USD", `$${usdAmount.toFixed(2)}`));
|
|
74718
|
+
console.log(dim("Charged via MPP connected key."));
|
|
74719
|
+
console.log("");
|
|
74720
|
+
const ok = await promptYesNo("Proceed? [y/N] ");
|
|
74721
|
+
if (!ok) {
|
|
74722
|
+
console.log(dim("Top-up cancelled."));
|
|
74723
|
+
process.exit(0);
|
|
74724
|
+
}
|
|
74725
|
+
console.log("");
|
|
74726
|
+
}
|
|
74727
|
+
const spinner2 = ora("Topping up credits via MPP...").start();
|
|
74728
|
+
const mppResult = await mppCreditsTopup({
|
|
74729
|
+
userId: auth.userId,
|
|
74730
|
+
keyId: auth.connectedKeyId,
|
|
74731
|
+
amount: usdAmount
|
|
74732
|
+
});
|
|
74733
|
+
spinner2.stop();
|
|
74734
|
+
if (!mppResult.success) {
|
|
74735
|
+
console.error(error(mppResult.error || "MPP top-up failed"));
|
|
74736
|
+
process.exit(1);
|
|
74737
|
+
}
|
|
74738
|
+
console.log(heading("Top-up Complete (MPP)"));
|
|
74739
|
+
console.log("");
|
|
74740
|
+
if (mppResult.credits?.balance)
|
|
74741
|
+
console.log(label("Balance", String(mppResult.credits.balance)));
|
|
74742
|
+
console.log(success("Credits topped up via MPP."));
|
|
74743
|
+
return;
|
|
74744
|
+
}
|
|
74745
|
+
const agentWallet = auth.agentWallets?.find((w5) => w5.source === "agent");
|
|
74746
|
+
const walletAddr = options.wallet || agentWallet?.address || auth.agentWallets?.[0]?.address;
|
|
74747
|
+
const walletChain = options.chain || auth.agentWallets?.find((w5) => w5.address === walletAddr)?.chain || "solana";
|
|
74748
|
+
if (!walletAddr) {
|
|
74749
|
+
console.error(error("No wallet found. Specify --wallet <address>"));
|
|
74750
|
+
process.exit(1);
|
|
74751
|
+
}
|
|
74674
74752
|
const shortWallet = walletAddr.length > 12 ? `${walletAddr.slice(0, 6)}...${walletAddr.slice(-4)}` : walletAddr;
|
|
74675
74753
|
console.log(heading("Top-up Summary"));
|
|
74676
74754
|
console.log("");
|
|
@@ -74682,6 +74760,14 @@ function registerCreditsCommand(program2) {
|
|
|
74682
74760
|
console.log(label("From", `wallet ${shortWallet} (${walletChain})`));
|
|
74683
74761
|
console.log(dim("Tokens will be transferred from your agent wallet to the treasury."));
|
|
74684
74762
|
console.log("");
|
|
74763
|
+
if (!options.yes) {
|
|
74764
|
+
const confirmed = await promptYesNo("Proceed? [y/N] ");
|
|
74765
|
+
if (!confirmed) {
|
|
74766
|
+
console.log(dim("Top-up cancelled."));
|
|
74767
|
+
process.exit(0);
|
|
74768
|
+
}
|
|
74769
|
+
console.log("");
|
|
74770
|
+
}
|
|
74685
74771
|
if (["base", "ethereum", "tempo"].includes(walletChain)) {
|
|
74686
74772
|
console.error(error("EVM topup not supported yet. Use a Solana wallet with --token AMIKO, USDC, USDT, or SOL."));
|
|
74687
74773
|
process.exit(1);
|
|
@@ -74922,8 +75008,8 @@ function registerSwapCommand(program2) {
|
|
|
74922
75008
|
console.log("");
|
|
74923
75009
|
const isInteractive2 = process.stdin.isTTY && !opts.yes;
|
|
74924
75010
|
if (isInteractive2) {
|
|
74925
|
-
const
|
|
74926
|
-
const rl =
|
|
75011
|
+
const readline2 = await import("readline");
|
|
75012
|
+
const rl = readline2.createInterface({
|
|
74927
75013
|
input: process.stdin,
|
|
74928
75014
|
output: process.stdout
|
|
74929
75015
|
});
|
|
@@ -75005,6 +75091,8 @@ function registerSwapCommand(program2) {
|
|
|
75005
75091
|
|
|
75006
75092
|
// src/commands/update.ts
|
|
75007
75093
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
75094
|
+
import fs from "node:fs";
|
|
75095
|
+
import path from "node:path";
|
|
75008
75096
|
function detectPackageManager() {
|
|
75009
75097
|
try {
|
|
75010
75098
|
execFileSync6("bun", ["--version"], { stdio: "ignore" });
|
|
@@ -75013,6 +75101,43 @@ function detectPackageManager() {
|
|
|
75013
75101
|
return "npm";
|
|
75014
75102
|
}
|
|
75015
75103
|
}
|
|
75104
|
+
var DATA_CLI = "/data/.amiko-cli";
|
|
75105
|
+
var NPM_GLOBAL_CANDIDATES = [
|
|
75106
|
+
"/usr/local/lib/node_modules/@heyamiko/amiko-cli",
|
|
75107
|
+
"/usr/lib/node_modules/@heyamiko/amiko-cli"
|
|
75108
|
+
];
|
|
75109
|
+
var BIN_TARGET = "/usr/local/bin/amiko";
|
|
75110
|
+
var TWIN_SKILL_DIRS = [
|
|
75111
|
+
"/data/.openclaw/skills/amiko-cli",
|
|
75112
|
+
"/data/.openclaw/workspace/skills/amiko-cli"
|
|
75113
|
+
];
|
|
75114
|
+
function syncTwinInstall() {
|
|
75115
|
+
if (!fs.existsSync(DATA_CLI))
|
|
75116
|
+
return false;
|
|
75117
|
+
const src = NPM_GLOBAL_CANDIDATES.find((p13) => fs.existsSync(p13));
|
|
75118
|
+
if (!src)
|
|
75119
|
+
return false;
|
|
75120
|
+
fs.rmSync(DATA_CLI, { recursive: true, force: true });
|
|
75121
|
+
execFileSync6("cp", ["-rL", src, DATA_CLI]);
|
|
75122
|
+
try {
|
|
75123
|
+
fs.unlinkSync(BIN_TARGET);
|
|
75124
|
+
} catch {}
|
|
75125
|
+
fs.symlinkSync(path.join(DATA_CLI, "dist/index.js"), BIN_TARGET);
|
|
75126
|
+
try {
|
|
75127
|
+
fs.chmodSync(BIN_TARGET, 493);
|
|
75128
|
+
} catch {}
|
|
75129
|
+
const skillSrc = path.join(DATA_CLI, "skills/SKILL.md");
|
|
75130
|
+
if (fs.existsSync(skillSrc)) {
|
|
75131
|
+
for (const dir of TWIN_SKILL_DIRS) {
|
|
75132
|
+
const parent = path.dirname(dir);
|
|
75133
|
+
if (!fs.existsSync(parent))
|
|
75134
|
+
continue;
|
|
75135
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
75136
|
+
fs.copyFileSync(skillSrc, path.join(dir, "SKILL.md"));
|
|
75137
|
+
}
|
|
75138
|
+
}
|
|
75139
|
+
return true;
|
|
75140
|
+
}
|
|
75016
75141
|
function registerUpdateCommand(program2) {
|
|
75017
75142
|
program2.command("update").description("Update amiko-cli to the latest version").action(async () => {
|
|
75018
75143
|
const pm = detectPackageManager();
|
|
@@ -75034,6 +75159,17 @@ function registerUpdateCommand(program2) {
|
|
|
75034
75159
|
});
|
|
75035
75160
|
}
|
|
75036
75161
|
spinner.stop();
|
|
75162
|
+
let syncedTwin = false;
|
|
75163
|
+
try {
|
|
75164
|
+
const twinSpinner = ora("Refreshing twin install...").start();
|
|
75165
|
+
syncedTwin = syncTwinInstall();
|
|
75166
|
+
twinSpinner.stop();
|
|
75167
|
+
if (syncedTwin) {
|
|
75168
|
+
console.log(dim("Refreshed /data/.amiko-cli, symlink, and skill files."));
|
|
75169
|
+
}
|
|
75170
|
+
} catch (err) {
|
|
75171
|
+
console.log(dim(`Twin sync failed: ${err instanceof Error ? err.message : String(err)}. Container restart will apply the update.`));
|
|
75172
|
+
}
|
|
75037
75173
|
let newVersion = "latest";
|
|
75038
75174
|
try {
|
|
75039
75175
|
newVersion = execFileSync6("amiko", ["--version"], {
|
|
@@ -75056,10 +75192,123 @@ function registerUpdateCommand(program2) {
|
|
|
75056
75192
|
});
|
|
75057
75193
|
}
|
|
75058
75194
|
|
|
75195
|
+
// src/lib/balance.ts
|
|
75196
|
+
async function fetchCreditsBalance(auth) {
|
|
75197
|
+
const billingToken = auth.platformKey || auth.twinToken || auth.privyToken;
|
|
75198
|
+
if (!billingToken)
|
|
75199
|
+
return null;
|
|
75200
|
+
const base2 = (auth.billingBase || "https://billing.heyamiko.com").replace(/\/$/, "");
|
|
75201
|
+
try {
|
|
75202
|
+
const res = await fetch(`${base2}/api/credits`, {
|
|
75203
|
+
headers: { Authorization: `Bearer ${billingToken}` }
|
|
75204
|
+
});
|
|
75205
|
+
if (!res.ok)
|
|
75206
|
+
return null;
|
|
75207
|
+
const data = await res.json();
|
|
75208
|
+
const raw = data.displayBalance ?? Number(data.credits?.balance ?? 0);
|
|
75209
|
+
return typeof raw === "number" && isFinite(raw) ? Math.round(raw) : null;
|
|
75210
|
+
} catch {
|
|
75211
|
+
return null;
|
|
75212
|
+
}
|
|
75213
|
+
}
|
|
75214
|
+
async function printBalanceRemaining(auth) {
|
|
75215
|
+
const balance = await fetchCreditsBalance(auth);
|
|
75216
|
+
if (balance == null) {
|
|
75217
|
+
console.log(dim("Balance: (could not fetch)"));
|
|
75218
|
+
return;
|
|
75219
|
+
}
|
|
75220
|
+
const usd2 = (balance / 1e4).toFixed(2);
|
|
75221
|
+
console.log(label("Balance", `${balance.toLocaleString()} credits (~$${usd2})`));
|
|
75222
|
+
}
|
|
75223
|
+
|
|
75224
|
+
// src/lib/errors.ts
|
|
75225
|
+
async function reportHttpError(res, label2) {
|
|
75226
|
+
const text = await res.text().catch(() => "");
|
|
75227
|
+
console.error(error(`${label2} failed: ${res.status} ${res.statusText || ""}`.trim()));
|
|
75228
|
+
if (!text)
|
|
75229
|
+
return;
|
|
75230
|
+
let pretty = text;
|
|
75231
|
+
try {
|
|
75232
|
+
const parsed = JSON.parse(text);
|
|
75233
|
+
pretty = parsed?.error ?? parsed?.message ?? parsed?.detail ?? JSON.stringify(parsed, null, 2);
|
|
75234
|
+
} catch {}
|
|
75235
|
+
console.error(dim("Response body:"));
|
|
75236
|
+
console.error(String(pretty).slice(0, 2000));
|
|
75237
|
+
}
|
|
75238
|
+
|
|
75239
|
+
// src/lib/confirm.ts
|
|
75240
|
+
import readline2 from "node:readline";
|
|
75241
|
+
function promptYesNo2(question) {
|
|
75242
|
+
const rl = readline2.createInterface({
|
|
75243
|
+
input: process.stdin,
|
|
75244
|
+
output: process.stdout
|
|
75245
|
+
});
|
|
75246
|
+
return new Promise((resolve2) => {
|
|
75247
|
+
rl.question(question, (answer) => {
|
|
75248
|
+
rl.close();
|
|
75249
|
+
resolve2(/^y(es)?$/i.test(answer.trim()));
|
|
75250
|
+
});
|
|
75251
|
+
});
|
|
75252
|
+
}
|
|
75253
|
+
async function confirmDestructive(args) {
|
|
75254
|
+
if (args.yes)
|
|
75255
|
+
return;
|
|
75256
|
+
if (process.stdin.isTTY) {
|
|
75257
|
+
console.log("");
|
|
75258
|
+
console.log(warn(args.action));
|
|
75259
|
+
if (args.detail)
|
|
75260
|
+
console.log(dim(args.detail));
|
|
75261
|
+
const ok = await promptYesNo2("Proceed? [y/N] ");
|
|
75262
|
+
if (!ok) {
|
|
75263
|
+
console.log(dim("Cancelled."));
|
|
75264
|
+
process.exit(0);
|
|
75265
|
+
}
|
|
75266
|
+
return;
|
|
75267
|
+
}
|
|
75268
|
+
console.error(error(`Refusing to run without --yes (non-interactive shell).`));
|
|
75269
|
+
console.error("");
|
|
75270
|
+
console.error(`Action: ${args.action}`);
|
|
75271
|
+
if (args.detail)
|
|
75272
|
+
console.error(dim(args.detail));
|
|
75273
|
+
console.error("");
|
|
75274
|
+
console.error(dim("Re-run with --yes to confirm:"));
|
|
75275
|
+
console.error(dim(` ${args.commandExample} --yes`));
|
|
75276
|
+
process.exit(2);
|
|
75277
|
+
}
|
|
75278
|
+
async function requireApproval(args) {
|
|
75279
|
+
if (args.yes)
|
|
75280
|
+
return;
|
|
75281
|
+
if (process.stdin.isTTY) {
|
|
75282
|
+
console.log("");
|
|
75283
|
+
console.log(`About to run: ${args.summary}`);
|
|
75284
|
+
console.log(`Cost: ${args.cost}`);
|
|
75285
|
+
const ok = await promptYesNo2("Proceed? [y/N] ");
|
|
75286
|
+
if (!ok) {
|
|
75287
|
+
console.log(dim("Cancelled."));
|
|
75288
|
+
process.exit(0);
|
|
75289
|
+
}
|
|
75290
|
+
return;
|
|
75291
|
+
}
|
|
75292
|
+
console.error(error(`Refusing to run without --yes (non-interactive shell detected).`));
|
|
75293
|
+
console.error("");
|
|
75294
|
+
console.error(`This command will cost ${args.cost}.`);
|
|
75295
|
+
console.error(`Action: ${args.summary}`);
|
|
75296
|
+
console.error("");
|
|
75297
|
+
console.error(dim(`Quote the cost to the user, get their explicit approval, then re-run with --yes:`));
|
|
75298
|
+
console.error(dim(` ${args.commandExample} --yes`));
|
|
75299
|
+
process.exit(2);
|
|
75300
|
+
}
|
|
75301
|
+
|
|
75059
75302
|
// src/commands/search.ts
|
|
75060
75303
|
var TREASURY = "FPiZAf3jEnNfCwfvbv46jdPfHbmSepD4Q6iHiLJDQXvb";
|
|
75061
75304
|
function registerSearchCommand(program2) {
|
|
75062
|
-
program2.command("search <query>").description("X Search — pays 1 AMIKO token per search").option("--raw", "Output raw JSON").option("--token <token>", "Payment token (AMIKO, USDC, SOL)", "AMIKO").action(async (query, opts) => {
|
|
75305
|
+
program2.command("search <query>").description("X Search — pays 1 AMIKO token per search").option("--raw", "Output raw JSON").option("--token <token>", "Payment token (AMIKO, USDC, SOL)", "AMIKO").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (query, opts) => {
|
|
75306
|
+
await requireApproval({
|
|
75307
|
+
cost: `1 ${opts.token || "AMIKO"}`,
|
|
75308
|
+
summary: `X/Twitter search: ${query.slice(0, 80)}${query.length > 80 ? "…" : ""}`,
|
|
75309
|
+
yes: opts.yes,
|
|
75310
|
+
commandExample: `amiko search ${JSON.stringify(query)}${opts.token && opts.token !== "AMIKO" ? ` --token ${opts.token}` : ""}`
|
|
75311
|
+
});
|
|
75063
75312
|
const spinner = ora("X Search...").start();
|
|
75064
75313
|
try {
|
|
75065
75314
|
const config2 = loadConfig();
|
|
@@ -75125,8 +75374,7 @@ function registerSearchCommand(program2) {
|
|
|
75125
75374
|
});
|
|
75126
75375
|
spinner.stop();
|
|
75127
75376
|
if (!res.ok) {
|
|
75128
|
-
|
|
75129
|
-
console.error(error(`Search failed: ${res.status} ${text.slice(0, 200)}`));
|
|
75377
|
+
await reportHttpError(res, "Search");
|
|
75130
75378
|
const explorer = chain === "solana" ? `https://solscan.io/tx/${txHash}` : `https://explorer.tempo.xyz/tx/${txHash}`;
|
|
75131
75379
|
console.log(dim(`Payment tx: ${explorer}`));
|
|
75132
75380
|
process.exit(1);
|
|
@@ -75155,6 +75403,7 @@ function registerSearchCommand(program2) {
|
|
|
75155
75403
|
}
|
|
75156
75404
|
}
|
|
75157
75405
|
console.log(success(`Paid ${amount2} ${token}`) + dim(` tx: ${txHash.slice(0, 16)}...`));
|
|
75406
|
+
await printBalanceRemaining(auth);
|
|
75158
75407
|
} catch (err) {
|
|
75159
75408
|
spinner.stop();
|
|
75160
75409
|
console.error(error(err instanceof Error ? err.message : "Search failed"));
|
|
@@ -75166,8 +75415,41 @@ function registerSearchCommand(program2) {
|
|
|
75166
75415
|
// src/commands/image.ts
|
|
75167
75416
|
var TREASURY2 = "FPiZAf3jEnNfCwfvbv46jdPfHbmSepD4Q6iHiLJDQXvb";
|
|
75168
75417
|
function registerImageCommand(program2) {
|
|
75169
|
-
program2.command("image <prompt>").alias("img").description("Generate an image — costs 5 AMIKO").option("--size <size>", "Image size: 1024x1024, 1024x1792, 1792x1024", "1024x1024").option("--quality <q>", "Quality: auto, low, medium, high (gpt-image-1) or standard, hd (dall-e-3)", "auto").option("--model <model>", "Model: gpt-image-1 or dall-e-3", "gpt-image-1").option("--background <bg>", "Background: transparent, opaque, auto (gpt-image-1 only)").option("--format <fmt>", "Output: png, webp, jpeg (gpt-image-1 only)", "png").option("--raw", "Output raw JSON").action(async (prompt2, opts) => {
|
|
75170
|
-
|
|
75418
|
+
program2.command("image <prompt>").alias("img").description("Generate an image — costs 5 AMIKO").option("--size <size>", "Image size: 1024x1024, 1024x1792, 1792x1024", "1024x1024").option("--quality <q>", "Quality: auto, low, medium, high (gpt-image-1) or standard, hd (dall-e-3)", "auto").option("--model <model>", "Model: gpt-image-1 or dall-e-3", "gpt-image-1").option("--background <bg>", "Background: transparent, opaque, auto (gpt-image-1 only)").option("--format <fmt>", "Output: png, webp, jpeg (gpt-image-1 only)", "png").option("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (prompt2, opts) => {
|
|
75419
|
+
await requireApproval({
|
|
75420
|
+
cost: "5 AMIKO",
|
|
75421
|
+
summary: `Generate image: ${prompt2.slice(0, 80)}${prompt2.length > 80 ? "…" : ""}`,
|
|
75422
|
+
yes: opts.yes,
|
|
75423
|
+
commandExample: `amiko image ${JSON.stringify(prompt2)}`
|
|
75424
|
+
});
|
|
75425
|
+
const isTTY = process.stderr.isTTY;
|
|
75426
|
+
const spinner = isTTY ? ora("Generating image...").start() : null;
|
|
75427
|
+
let progressTimer = null;
|
|
75428
|
+
const started = Date.now();
|
|
75429
|
+
const setStatus = (text) => {
|
|
75430
|
+
if (spinner) {
|
|
75431
|
+
spinner.text = text;
|
|
75432
|
+
} else {
|
|
75433
|
+
console.error(text);
|
|
75434
|
+
}
|
|
75435
|
+
};
|
|
75436
|
+
const startProgress = (text) => {
|
|
75437
|
+
setStatus(text);
|
|
75438
|
+
if (!spinner && !progressTimer) {
|
|
75439
|
+
progressTimer = setInterval(() => {
|
|
75440
|
+
const elapsed = Math.round((Date.now() - started) / 1000);
|
|
75441
|
+
console.error(` …still working (${elapsed}s elapsed)`);
|
|
75442
|
+
}, 3000);
|
|
75443
|
+
}
|
|
75444
|
+
};
|
|
75445
|
+
const stopProgress = () => {
|
|
75446
|
+
if (progressTimer) {
|
|
75447
|
+
clearInterval(progressTimer);
|
|
75448
|
+
progressTimer = null;
|
|
75449
|
+
}
|
|
75450
|
+
if (spinner)
|
|
75451
|
+
spinner.stop();
|
|
75452
|
+
};
|
|
75171
75453
|
try {
|
|
75172
75454
|
const config2 = loadConfig();
|
|
75173
75455
|
const auth = resolveAuth(config2);
|
|
@@ -75175,18 +75457,18 @@ function registerImageCommand(program2) {
|
|
|
75175
75457
|
const billingBase = auth.billingBase || "https://billing.heyamiko.com";
|
|
75176
75458
|
const billingToken = auth.platformKey || auth.twinToken || auth.privyToken;
|
|
75177
75459
|
if (!billingToken) {
|
|
75178
|
-
|
|
75460
|
+
stopProgress();
|
|
75179
75461
|
console.error(error("Not authenticated. Check .amiko.json credentials."));
|
|
75180
75462
|
process.exit(1);
|
|
75181
75463
|
}
|
|
75182
75464
|
const allWallets = auth.agentWallets || config2.agentWallets || [];
|
|
75183
75465
|
const solanaWallet = allWallets.find((w5) => w5.chain === "solana")?.address;
|
|
75184
75466
|
if (!solanaWallet) {
|
|
75185
|
-
|
|
75467
|
+
stopProgress();
|
|
75186
75468
|
console.error(error("No Solana wallet for AMIKO payment."));
|
|
75187
75469
|
process.exit(1);
|
|
75188
75470
|
}
|
|
75189
|
-
|
|
75471
|
+
setStatus("Paying 5 AMIKO...");
|
|
75190
75472
|
const transferRes = await fetch(`${billingBase}/api/crossmint/wallets/${encodeURIComponent(solanaWallet)}/transfer`, {
|
|
75191
75473
|
method: "POST",
|
|
75192
75474
|
headers: {
|
|
@@ -75201,7 +75483,7 @@ function registerImageCommand(program2) {
|
|
|
75201
75483
|
})
|
|
75202
75484
|
});
|
|
75203
75485
|
if (!transferRes.ok) {
|
|
75204
|
-
|
|
75486
|
+
stopProgress();
|
|
75205
75487
|
const err = await transferRes.json().catch(() => ({ error: "Transfer failed" }));
|
|
75206
75488
|
console.error(error(err.error || `Payment failed: ${transferRes.status}`));
|
|
75207
75489
|
process.exit(1);
|
|
@@ -75209,11 +75491,11 @@ function registerImageCommand(program2) {
|
|
|
75209
75491
|
const transferData = await transferRes.json();
|
|
75210
75492
|
const txHash = transferData.txHash;
|
|
75211
75493
|
if (!txHash) {
|
|
75212
|
-
|
|
75494
|
+
stopProgress();
|
|
75213
75495
|
console.error(error("Payment sent but no tx hash returned."));
|
|
75214
75496
|
process.exit(1);
|
|
75215
75497
|
}
|
|
75216
|
-
|
|
75498
|
+
startProgress("Generating image...");
|
|
75217
75499
|
const res = await fetch(`${mppBase2}/v1/ai/openai/images`, {
|
|
75218
75500
|
method: "POST",
|
|
75219
75501
|
headers: {
|
|
@@ -75229,10 +75511,9 @@ function registerImageCommand(program2) {
|
|
|
75229
75511
|
...opts.model === "gpt-image-1" && { output_format: opts.format || "png" }
|
|
75230
75512
|
})
|
|
75231
75513
|
});
|
|
75232
|
-
|
|
75514
|
+
stopProgress();
|
|
75233
75515
|
if (!res.ok) {
|
|
75234
|
-
|
|
75235
|
-
console.error(error(`Image generation failed: ${res.status} ${text.slice(0, 300)}`));
|
|
75516
|
+
await reportHttpError(res, "Image generation");
|
|
75236
75517
|
console.log(dim(`Payment tx: https://solscan.io/tx/${txHash}`));
|
|
75237
75518
|
process.exit(1);
|
|
75238
75519
|
}
|
|
@@ -75261,8 +75542,9 @@ function registerImageCommand(program2) {
|
|
|
75261
75542
|
}
|
|
75262
75543
|
console.log("");
|
|
75263
75544
|
console.log(success("Paid 5 AMIKO") + dim(` tx: ${txHash.slice(0, 16)}...`));
|
|
75545
|
+
await printBalanceRemaining(auth);
|
|
75264
75546
|
} catch (err) {
|
|
75265
|
-
|
|
75547
|
+
stopProgress();
|
|
75266
75548
|
console.error(error(err instanceof Error ? err.message : "Image generation failed"));
|
|
75267
75549
|
process.exit(1);
|
|
75268
75550
|
}
|
|
@@ -75291,7 +75573,13 @@ async function payAmiko(billingBase, billingToken, solanaWallet, amount2) {
|
|
|
75291
75573
|
}
|
|
75292
75574
|
function registerAmazonCommand(program2) {
|
|
75293
75575
|
const cmd = program2.command("amazon").description("Search and buy products on Amazon — paid with AMIKO");
|
|
75294
|
-
cmd.command("search <query>").description("Search Amazon products — 1 AMIKO").option("--limit <n>", "Number of results", "5").option("--raw", "Output raw JSON").action(async (query, opts) => {
|
|
75576
|
+
cmd.command("search <query>").description("Search Amazon products — 1 AMIKO").option("--limit <n>", "Number of results", "5").option("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (query, opts) => {
|
|
75577
|
+
await requireApproval({
|
|
75578
|
+
cost: "1 AMIKO",
|
|
75579
|
+
summary: `Amazon product search: ${query.slice(0, 80)}${query.length > 80 ? "…" : ""}`,
|
|
75580
|
+
yes: opts.yes,
|
|
75581
|
+
commandExample: `amiko amazon search ${JSON.stringify(query)}`
|
|
75582
|
+
});
|
|
75295
75583
|
const spinner = ora("Searching Amazon...").start();
|
|
75296
75584
|
try {
|
|
75297
75585
|
const config2 = loadConfig();
|
|
@@ -75322,8 +75610,7 @@ function registerAmazonCommand(program2) {
|
|
|
75322
75610
|
});
|
|
75323
75611
|
spinner.stop();
|
|
75324
75612
|
if (!res.ok) {
|
|
75325
|
-
|
|
75326
|
-
console.error(error(`Search failed: ${res.status} ${text.slice(0, 200)}`));
|
|
75613
|
+
await reportHttpError(res, "Amazon search");
|
|
75327
75614
|
process.exit(1);
|
|
75328
75615
|
}
|
|
75329
75616
|
const data = await res.json();
|
|
@@ -75353,6 +75640,7 @@ function registerAmazonCommand(program2) {
|
|
|
75353
75640
|
console.log(dim(" No results found."));
|
|
75354
75641
|
}
|
|
75355
75642
|
console.log(success("Paid 1 AMIKO") + dim(` tx: ${txHash.slice(0, 16)}...`));
|
|
75643
|
+
await printBalanceRemaining(auth);
|
|
75356
75644
|
} catch (err) {
|
|
75357
75645
|
spinner.stop();
|
|
75358
75646
|
console.error(error(err instanceof Error ? err.message : "Search failed"));
|
|
@@ -75385,8 +75673,7 @@ function registerAmazonCommand(program2) {
|
|
|
75385
75673
|
});
|
|
75386
75674
|
spinner.stop();
|
|
75387
75675
|
if (!res.ok) {
|
|
75388
|
-
|
|
75389
|
-
console.error(error(`Quote failed: ${res.status} ${text.slice(0, 300)}`));
|
|
75676
|
+
await reportHttpError(res, "Amazon quote");
|
|
75390
75677
|
process.exit(1);
|
|
75391
75678
|
}
|
|
75392
75679
|
const data = await res.json();
|
|
@@ -75433,7 +75720,7 @@ function usdToAmiko(usd2, pricePerAmiko) {
|
|
|
75433
75720
|
const amount2 = usd2 / pricePerAmiko;
|
|
75434
75721
|
return Math.ceil(amount2 * 100) / 100 + "";
|
|
75435
75722
|
}
|
|
75436
|
-
async function payAndCall(mppBase2, method,
|
|
75723
|
+
async function payAndCall(mppBase2, method, path2, billingBase, billingToken, solanaWallet, amountOrAuto, body, onProgress, token = "AMIKO") {
|
|
75437
75724
|
let amount2 = amountOrAuto;
|
|
75438
75725
|
if (amount2 === "auto") {
|
|
75439
75726
|
onProgress?.("Fetching price...");
|
|
@@ -75441,7 +75728,7 @@ async function payAndCall(mppBase2, method, path, billingBase, billingToken, sol
|
|
|
75441
75728
|
const specRes = await fetch(`${mppBase2}/openapi.json`);
|
|
75442
75729
|
if (specRes.ok) {
|
|
75443
75730
|
const spec = await specRes.json();
|
|
75444
|
-
const pathSpec = spec.paths?.[
|
|
75731
|
+
const pathSpec = spec.paths?.[path2];
|
|
75445
75732
|
const methodSpec = pathSpec?.[method.toLowerCase()];
|
|
75446
75733
|
const usdPrice = parseFloat(methodSpec?.["x-payment-info"]?.price || "0");
|
|
75447
75734
|
if (usdPrice > 0) {
|
|
@@ -75488,13 +75775,19 @@ async function payAndCall(mppBase2, method, path, billingBase, billingToken, sol
|
|
|
75488
75775
|
if (body && method !== "GET") {
|
|
75489
75776
|
fetchOpts.body = JSON.stringify(body);
|
|
75490
75777
|
}
|
|
75491
|
-
const url2 =
|
|
75778
|
+
const url2 = path2.startsWith("http") ? path2 : `${mppBase2}${path2}`;
|
|
75492
75779
|
const res = await fetch(url2, fetchOpts);
|
|
75493
75780
|
return { res, txHash, amikoSpent: amount2 };
|
|
75494
75781
|
}
|
|
75495
75782
|
function registerServicesCommand(program2) {
|
|
75496
75783
|
const svc = program2.command("service").alias("svc").description("Call any MPP service — pays with AMIKO tokens");
|
|
75497
|
-
svc.command("tts <voiceId> <text>").description("Text-to-speech — 1 AMIKO").option("--raw", "Output raw response").action(async (voiceId, text, opts) => {
|
|
75784
|
+
svc.command("tts <voiceId> <text>").description("Text-to-speech — 1 AMIKO").option("--raw", "Output raw response").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (voiceId, text, opts) => {
|
|
75785
|
+
await requireApproval({
|
|
75786
|
+
cost: "1 AMIKO",
|
|
75787
|
+
summary: `Text-to-speech (voice ${voiceId}): "${text.slice(0, 60)}${text.length > 60 ? "…" : ""}"`,
|
|
75788
|
+
yes: opts.yes,
|
|
75789
|
+
commandExample: `amiko service tts ${voiceId} ${JSON.stringify(text)}`
|
|
75790
|
+
});
|
|
75498
75791
|
await callService({
|
|
75499
75792
|
name: "Text-to-Speech",
|
|
75500
75793
|
method: "POST",
|
|
@@ -75504,7 +75797,13 @@ function registerServicesCommand(program2) {
|
|
|
75504
75797
|
opts
|
|
75505
75798
|
});
|
|
75506
75799
|
});
|
|
75507
|
-
svc.command("stt <audioUrl>").description("Speech-to-text — 1 AMIKO").option("--raw", "Output raw response").action(async (audioUrl, opts) => {
|
|
75800
|
+
svc.command("stt <audioUrl>").description("Speech-to-text — 1 AMIKO").option("--raw", "Output raw response").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (audioUrl, opts) => {
|
|
75801
|
+
await requireApproval({
|
|
75802
|
+
cost: "1 AMIKO",
|
|
75803
|
+
summary: `Speech-to-text on ${audioUrl}`,
|
|
75804
|
+
yes: opts.yes,
|
|
75805
|
+
commandExample: `amiko service stt ${JSON.stringify(audioUrl)}`
|
|
75806
|
+
});
|
|
75508
75807
|
await callService({
|
|
75509
75808
|
name: "Speech-to-Text",
|
|
75510
75809
|
method: "POST",
|
|
@@ -75532,7 +75831,13 @@ function registerServicesCommand(program2) {
|
|
|
75532
75831
|
console.log(JSON.stringify(data, null, 2));
|
|
75533
75832
|
}
|
|
75534
75833
|
});
|
|
75535
|
-
svc.command("chat <message>").description("AI chat — 1 AMIKO").option("--model <model>", "Model to use").option("--raw", "Output raw response").action(async (message, opts) => {
|
|
75834
|
+
svc.command("chat <message>").description("AI chat — 1 AMIKO").option("--model <model>", "Model to use").option("--raw", "Output raw response").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (message, opts) => {
|
|
75835
|
+
await requireApproval({
|
|
75836
|
+
cost: "1 AMIKO",
|
|
75837
|
+
summary: `AI chat${opts.model ? ` (model ${opts.model})` : ""}: "${message.slice(0, 60)}${message.length > 60 ? "…" : ""}"`,
|
|
75838
|
+
yes: opts.yes,
|
|
75839
|
+
commandExample: `amiko service chat ${JSON.stringify(message)}${opts.model ? ` --model ${opts.model}` : ""}`
|
|
75840
|
+
});
|
|
75536
75841
|
const body = { message };
|
|
75537
75842
|
if (opts.model)
|
|
75538
75843
|
body.model = opts.model;
|
|
@@ -75545,7 +75850,14 @@ function registerServicesCommand(program2) {
|
|
|
75545
75850
|
opts
|
|
75546
75851
|
});
|
|
75547
75852
|
});
|
|
75548
|
-
svc.command("call <method> <path> [body]").description("Call any MPP endpoint — pays in AMIKO or USDC on Solana").option("--amiko <amount>", "AMIKO tokens to pay (default: auto-price from USD)").option("--usdc <amount>", "Pay in USDC instead of AMIKO").option("--raw", "Output raw response").action(async (method,
|
|
75853
|
+
svc.command("call <method> <path> [body]").description("Call any MPP endpoint — pays in AMIKO or USDC on Solana").option("--amiko <amount>", "AMIKO tokens to pay (default: auto-price from USD)").option("--usdc <amount>", "Pay in USDC instead of AMIKO").option("--raw", "Output raw response").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (method, path2, bodyStr, opts) => {
|
|
75854
|
+
const costLabel = opts.usdc ? `${opts.usdc} USDC` : opts.amiko ? `${opts.amiko} AMIKO` : "auto-priced AMIKO (check /v1/ list for endpoint price first)";
|
|
75855
|
+
await requireApproval({
|
|
75856
|
+
cost: costLabel,
|
|
75857
|
+
summary: `${method.toUpperCase()} ${path2}`,
|
|
75858
|
+
yes: opts.yes,
|
|
75859
|
+
commandExample: `amiko service call ${method.toUpperCase()} ${path2}${bodyStr ? ` ${JSON.stringify(bodyStr)}` : ""}${opts.amiko ? ` --amiko ${opts.amiko}` : ""}${opts.usdc ? ` --usdc ${opts.usdc}` : ""}`
|
|
75860
|
+
});
|
|
75549
75861
|
let body;
|
|
75550
75862
|
if (bodyStr) {
|
|
75551
75863
|
try {
|
|
@@ -75555,9 +75867,9 @@ function registerServicesCommand(program2) {
|
|
|
75555
75867
|
}
|
|
75556
75868
|
}
|
|
75557
75869
|
await callService({
|
|
75558
|
-
name: `${method.toUpperCase()} ${
|
|
75870
|
+
name: `${method.toUpperCase()} ${path2}`,
|
|
75559
75871
|
method: method.toUpperCase(),
|
|
75560
|
-
path,
|
|
75872
|
+
path: path2,
|
|
75561
75873
|
body,
|
|
75562
75874
|
amiko: opts.amiko || (opts.usdc ? undefined : "auto"),
|
|
75563
75875
|
usdc: opts.usdc,
|
|
@@ -75575,11 +75887,11 @@ function registerServicesCommand(program2) {
|
|
|
75575
75887
|
console.log(heading(`MPP Services
|
|
75576
75888
|
`));
|
|
75577
75889
|
const paths = spec.paths || {};
|
|
75578
|
-
for (const [
|
|
75890
|
+
for (const [path2, methods] of Object.entries(paths)) {
|
|
75579
75891
|
for (const [method, op] of Object.entries(methods)) {
|
|
75580
75892
|
const price = op["x-payment-info"]?.price;
|
|
75581
75893
|
const priceLabel = price === "0" || !price ? source_default.green("FREE") : source_default.yellow(`$${parseFloat(price).toFixed(2)}`);
|
|
75582
|
-
console.log(` ${source_default.bold(method.toUpperCase().padEnd(6))} ${
|
|
75894
|
+
console.log(` ${source_default.bold(method.toUpperCase().padEnd(6))} ${path2.padEnd(40)} ${priceLabel}`);
|
|
75583
75895
|
if (op.summary)
|
|
75584
75896
|
console.log(dim(` ${op.summary}`));
|
|
75585
75897
|
}
|
|
@@ -75615,11 +75927,9 @@ async function callService(params) {
|
|
|
75615
75927
|
const { res, txHash, amikoSpent } = await payAndCall(mppBase2, params.method, params.path, billingBase, billingToken, solanaWallet, token === "USDC" ? amount2 : amount2, params.body, (msg) => {
|
|
75616
75928
|
spinner.text = msg;
|
|
75617
75929
|
});
|
|
75618
|
-
if (params.usdc) {}
|
|
75619
75930
|
spinner.stop();
|
|
75620
75931
|
if (!res.ok) {
|
|
75621
|
-
|
|
75622
|
-
console.error(error(`${params.name} failed: ${res.status} ${text.slice(0, 300)}`));
|
|
75932
|
+
await reportHttpError(res, params.name);
|
|
75623
75933
|
console.log(dim(`Payment tx: https://solscan.io/tx/${txHash}`));
|
|
75624
75934
|
process.exit(1);
|
|
75625
75935
|
}
|
|
@@ -75641,6 +75951,7 @@ async function callService(params) {
|
|
|
75641
75951
|
}
|
|
75642
75952
|
console.log("");
|
|
75643
75953
|
console.log(success(`Paid ${amikoSpent} ${token}`) + dim(` tx: ${txHash.slice(0, 16)}...`));
|
|
75954
|
+
await printBalanceRemaining(auth);
|
|
75644
75955
|
} catch (err) {
|
|
75645
75956
|
spinner.stop();
|
|
75646
75957
|
console.error(error(err instanceof Error ? err.message : "Service call failed"));
|
|
@@ -75648,6 +75959,904 @@ async function callService(params) {
|
|
|
75648
75959
|
}
|
|
75649
75960
|
}
|
|
75650
75961
|
|
|
75962
|
+
// src/lib/amiko-web.ts
|
|
75963
|
+
var DEFAULT_PLATFORM_URL = "https://platform.heyamiko.com";
|
|
75964
|
+
function baseUrl(auth) {
|
|
75965
|
+
const url2 = auth.platformUrl || DEFAULT_PLATFORM_URL;
|
|
75966
|
+
return url2.replace(/\/$/, "");
|
|
75967
|
+
}
|
|
75968
|
+
function authHeader(auth) {
|
|
75969
|
+
if (auth.privyToken)
|
|
75970
|
+
return `Bearer am-${auth.privyToken}`;
|
|
75971
|
+
if (auth.twinToken)
|
|
75972
|
+
return `Bearer ${auth.twinToken}`;
|
|
75973
|
+
throw new Error("No amiko-web auth available. Run `amiko login` to link your account.");
|
|
75974
|
+
}
|
|
75975
|
+
async function amikoWebFetch(auth, path2, opts = {}) {
|
|
75976
|
+
const url2 = new URL(`${baseUrl(auth)}${path2}`);
|
|
75977
|
+
if (opts.query) {
|
|
75978
|
+
for (const [k6, v8] of Object.entries(opts.query)) {
|
|
75979
|
+
if (v8 !== undefined && v8 !== null)
|
|
75980
|
+
url2.searchParams.set(k6, String(v8));
|
|
75981
|
+
}
|
|
75982
|
+
}
|
|
75983
|
+
const headers = {
|
|
75984
|
+
Authorization: authHeader(auth),
|
|
75985
|
+
...opts.headers
|
|
75986
|
+
};
|
|
75987
|
+
let body;
|
|
75988
|
+
if (opts.multipart) {
|
|
75989
|
+
body = opts.multipart;
|
|
75990
|
+
} else if (opts.rawBody) {
|
|
75991
|
+
body = opts.rawBody;
|
|
75992
|
+
if (opts.rawContentType)
|
|
75993
|
+
headers["Content-Type"] = opts.rawContentType;
|
|
75994
|
+
} else if (opts.body) {
|
|
75995
|
+
body = JSON.stringify(opts.body);
|
|
75996
|
+
headers["Content-Type"] = "application/json";
|
|
75997
|
+
}
|
|
75998
|
+
const controller = new AbortController;
|
|
75999
|
+
const timeoutMs = opts.timeoutMs ?? 30000;
|
|
76000
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
76001
|
+
let res;
|
|
76002
|
+
try {
|
|
76003
|
+
res = await fetch(url2, {
|
|
76004
|
+
method: opts.method ?? "GET",
|
|
76005
|
+
headers,
|
|
76006
|
+
body,
|
|
76007
|
+
signal: controller.signal
|
|
76008
|
+
});
|
|
76009
|
+
} finally {
|
|
76010
|
+
clearTimeout(timer);
|
|
76011
|
+
}
|
|
76012
|
+
if (!res.ok) {
|
|
76013
|
+
const text = await res.text().catch(() => "");
|
|
76014
|
+
const err = new Error(formatError2(res.status, text, path2));
|
|
76015
|
+
err.status = res.status;
|
|
76016
|
+
err.body = text;
|
|
76017
|
+
throw err;
|
|
76018
|
+
}
|
|
76019
|
+
const ctype = res.headers.get("content-type") || "";
|
|
76020
|
+
if (ctype.includes("application/json")) {
|
|
76021
|
+
return await res.json();
|
|
76022
|
+
}
|
|
76023
|
+
return await res.text();
|
|
76024
|
+
}
|
|
76025
|
+
function formatError2(status, body, path2) {
|
|
76026
|
+
const prefix = `${status} ${path2}`;
|
|
76027
|
+
if (status === 401) {
|
|
76028
|
+
return `${prefix}: unauthorized. Run \`amiko connect\` to refresh your session.`;
|
|
76029
|
+
}
|
|
76030
|
+
if (status === 403) {
|
|
76031
|
+
return `${prefix}: forbidden. The twin token does not have access to this resource.`;
|
|
76032
|
+
}
|
|
76033
|
+
if (!body)
|
|
76034
|
+
return prefix;
|
|
76035
|
+
const snippet = body.length > 500 ? `${body.slice(0, 500)}...` : body;
|
|
76036
|
+
return `${prefix}: ${snippet}`;
|
|
76037
|
+
}
|
|
76038
|
+
|
|
76039
|
+
// src/lib/twin-context.ts
|
|
76040
|
+
class TwinResolutionError extends Error {
|
|
76041
|
+
constructor(message) {
|
|
76042
|
+
super(message);
|
|
76043
|
+
}
|
|
76044
|
+
}
|
|
76045
|
+
function resolveTwinId(args) {
|
|
76046
|
+
const { flag, config: config2, auth } = args;
|
|
76047
|
+
if (flag)
|
|
76048
|
+
return flag;
|
|
76049
|
+
if (auth.twinId)
|
|
76050
|
+
return auth.twinId;
|
|
76051
|
+
if (config2.twinId)
|
|
76052
|
+
return config2.twinId;
|
|
76053
|
+
throw new TwinResolutionError("No twin configured. Pass --twin <id> or provision one via .amiko.json.");
|
|
76054
|
+
}
|
|
76055
|
+
|
|
76056
|
+
// src/commands/accounts.ts
|
|
76057
|
+
function registerAccountsCommand(program2) {
|
|
76058
|
+
program2.command("accounts").description("Show resolved identity (platform, user, twin, wallets)").option("--json", "Output as JSON").action(async (opts) => {
|
|
76059
|
+
const config2 = loadConfig();
|
|
76060
|
+
const auth = resolveAuth(config2);
|
|
76061
|
+
const data = {
|
|
76062
|
+
mode: auth.mode,
|
|
76063
|
+
platformUrl: auth.platformUrl ?? config2.platformUrl ?? config2.siteBase,
|
|
76064
|
+
userId: auth.userId ?? null,
|
|
76065
|
+
activeTwinId: auth.twinId ?? config2.twinId ?? null,
|
|
76066
|
+
wallets: auth.agentWallets ?? [],
|
|
76067
|
+
hasTwinToken: Boolean(auth.twinToken),
|
|
76068
|
+
hasPlatformKey: Boolean(auth.platformKey)
|
|
76069
|
+
};
|
|
76070
|
+
renderOutput(data, (d13) => {
|
|
76071
|
+
console.log(heading("Amiko Account"));
|
|
76072
|
+
console.log(label("Mode", d13.mode));
|
|
76073
|
+
if (d13.platformUrl)
|
|
76074
|
+
console.log(label("Platform", d13.platformUrl));
|
|
76075
|
+
if (d13.userId)
|
|
76076
|
+
console.log(label("User ID", d13.userId));
|
|
76077
|
+
if (d13.activeTwinId)
|
|
76078
|
+
console.log(label("Active twin", d13.activeTwinId));
|
|
76079
|
+
console.log(label("Wallets", String(d13.wallets.length)));
|
|
76080
|
+
if (d13.mode === "none") {
|
|
76081
|
+
console.log("");
|
|
76082
|
+
console.log(error("No credentials found. Provision auth via .amiko.json or an API key."));
|
|
76083
|
+
}
|
|
76084
|
+
}, { json: opts.json });
|
|
76085
|
+
});
|
|
76086
|
+
program2.command("info").description("Get twin info (name, description, voice, avatar)").option("--twin <idOrName>", "Target twin id or name").option("--json", "Output as JSON").action(async (opts) => {
|
|
76087
|
+
const config2 = loadConfig();
|
|
76088
|
+
const auth = resolveAuth(config2);
|
|
76089
|
+
let twinId;
|
|
76090
|
+
try {
|
|
76091
|
+
twinId = resolveTwinId({ flag: opts.twin, config: config2, auth });
|
|
76092
|
+
} catch (e8) {
|
|
76093
|
+
if (e8 instanceof TwinResolutionError) {
|
|
76094
|
+
console.error(error(e8.message));
|
|
76095
|
+
process.exit(1);
|
|
76096
|
+
}
|
|
76097
|
+
throw e8;
|
|
76098
|
+
}
|
|
76099
|
+
const spinner = opts.json ? null : ora("Fetching twin info...").start();
|
|
76100
|
+
try {
|
|
76101
|
+
const twin = await amikoWebFetch(auth, `/api/agents/${twinId}`);
|
|
76102
|
+
spinner?.stop();
|
|
76103
|
+
renderOutput(twin, (t17) => {
|
|
76104
|
+
console.log(success(`${t17.name}`));
|
|
76105
|
+
console.log(label("ID", t17.id));
|
|
76106
|
+
if (t17.description)
|
|
76107
|
+
console.log(label("Description", t17.description));
|
|
76108
|
+
console.log(label("Public", t17.is_public ? "yes" : "no"));
|
|
76109
|
+
if (t17.voice_status)
|
|
76110
|
+
console.log(label("Voice", t17.voice_status));
|
|
76111
|
+
if (t17.avatar_url)
|
|
76112
|
+
console.log(label("Avatar", t17.avatar_url));
|
|
76113
|
+
if (t17.shipped_at)
|
|
76114
|
+
console.log(label("Shipped", t17.shipped_at));
|
|
76115
|
+
if (t17.updated_at)
|
|
76116
|
+
console.log(dim(`Updated ${t17.updated_at}`));
|
|
76117
|
+
}, { json: opts.json });
|
|
76118
|
+
} catch (e8) {
|
|
76119
|
+
spinner?.stop();
|
|
76120
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76121
|
+
process.exit(1);
|
|
76122
|
+
}
|
|
76123
|
+
});
|
|
76124
|
+
}
|
|
76125
|
+
|
|
76126
|
+
// src/commands/twin.ts
|
|
76127
|
+
function parseBool(raw, flag) {
|
|
76128
|
+
const v8 = raw.toLowerCase();
|
|
76129
|
+
if (v8 === "true" || v8 === "1" || v8 === "yes")
|
|
76130
|
+
return true;
|
|
76131
|
+
if (v8 === "false" || v8 === "0" || v8 === "no")
|
|
76132
|
+
return false;
|
|
76133
|
+
throw new Error(`${flag} must be true or false, got "${raw}"`);
|
|
76134
|
+
}
|
|
76135
|
+
function registerTwinCommand(program2) {
|
|
76136
|
+
program2.command("twin:update").description("Update twin name, description, or public visibility").option("--name <name>", "Display name").option("--description <text>", "Description").option("--public <bool>", "Public visibility (true/false)").option("--twin <idOrName>", "Target twin id or name").option("--yes", "Skip confirmation when changing visibility").option("--json", "Output as JSON").action(async (opts) => {
|
|
76137
|
+
if (opts.name === undefined && opts.description === undefined && opts.public === undefined) {
|
|
76138
|
+
console.error(error("Pass at least one of --name, --description, --public"));
|
|
76139
|
+
process.exit(1);
|
|
76140
|
+
}
|
|
76141
|
+
const config2 = loadConfig();
|
|
76142
|
+
const auth = resolveAuth(config2);
|
|
76143
|
+
let twinId;
|
|
76144
|
+
try {
|
|
76145
|
+
twinId = resolveTwinId({ flag: opts.twin, config: config2, auth });
|
|
76146
|
+
} catch (e8) {
|
|
76147
|
+
if (e8 instanceof TwinResolutionError) {
|
|
76148
|
+
console.error(error(e8.message));
|
|
76149
|
+
process.exit(1);
|
|
76150
|
+
}
|
|
76151
|
+
throw e8;
|
|
76152
|
+
}
|
|
76153
|
+
const body = {};
|
|
76154
|
+
if (opts.name !== undefined)
|
|
76155
|
+
body.name = opts.name;
|
|
76156
|
+
if (opts.description !== undefined)
|
|
76157
|
+
body.description = opts.description;
|
|
76158
|
+
if (opts.public !== undefined) {
|
|
76159
|
+
let next;
|
|
76160
|
+
try {
|
|
76161
|
+
next = parseBool(opts.public, "--public");
|
|
76162
|
+
} catch (e8) {
|
|
76163
|
+
console.error(error(e8.message));
|
|
76164
|
+
process.exit(1);
|
|
76165
|
+
}
|
|
76166
|
+
body.is_public = next;
|
|
76167
|
+
await confirmDestructive({
|
|
76168
|
+
action: `Set twin visibility to ${next ? "PUBLIC" : "PRIVATE"}`,
|
|
76169
|
+
detail: next ? "Anyone on amiko.com will be able to find and chat with this twin." : "The twin will no longer be discoverable.",
|
|
76170
|
+
yes: opts.yes,
|
|
76171
|
+
commandExample: `amiko twin:update --public ${next}`
|
|
76172
|
+
});
|
|
76173
|
+
}
|
|
76174
|
+
const spinner = opts.json ? null : ora("Updating twin...").start();
|
|
76175
|
+
try {
|
|
76176
|
+
const updated = await amikoWebFetch(auth, `/api/agents/${twinId}`, { method: "PATCH", body });
|
|
76177
|
+
spinner?.stop();
|
|
76178
|
+
renderOutput(updated, (u10) => {
|
|
76179
|
+
console.log(success("Twin updated"));
|
|
76180
|
+
if (u10.name)
|
|
76181
|
+
console.log(label("Name", String(u10.name)));
|
|
76182
|
+
if (u10.description !== undefined)
|
|
76183
|
+
console.log(label("Description", String(u10.description ?? "")));
|
|
76184
|
+
if (u10.is_public !== undefined)
|
|
76185
|
+
console.log(label("Public", u10.is_public ? "yes" : "no"));
|
|
76186
|
+
}, { json: opts.json });
|
|
76187
|
+
} catch (e8) {
|
|
76188
|
+
spinner?.stop();
|
|
76189
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76190
|
+
process.exit(1);
|
|
76191
|
+
}
|
|
76192
|
+
});
|
|
76193
|
+
}
|
|
76194
|
+
|
|
76195
|
+
// src/lib/file-input.ts
|
|
76196
|
+
import { readFile } from "node:fs/promises";
|
|
76197
|
+
import { basename, extname } from "node:path";
|
|
76198
|
+
var MAX_BYTES = 100 * 1024 * 1024;
|
|
76199
|
+
var FETCH_TIMEOUT_MS = 30000;
|
|
76200
|
+
async function resolveFileInput(src, opts = {}) {
|
|
76201
|
+
if (src === "-" || opts.stdin) {
|
|
76202
|
+
const buffer4 = await readStdin();
|
|
76203
|
+
const name2 = opts.hintName ?? "stdin";
|
|
76204
|
+
const mime2 = opts.hintMime ?? detectMime(buffer4, name2);
|
|
76205
|
+
return { buffer: buffer4, name: name2, mime: mime2, size: buffer4.length };
|
|
76206
|
+
}
|
|
76207
|
+
if (/^https?:\/\//i.test(src)) {
|
|
76208
|
+
const buffer4 = await fetchRemote(src);
|
|
76209
|
+
const urlPath = new URL(src).pathname;
|
|
76210
|
+
const name2 = opts.hintName ?? (basename(urlPath) || "download");
|
|
76211
|
+
const mime2 = opts.hintMime ?? detectMime(buffer4, name2);
|
|
76212
|
+
return { buffer: buffer4, name: name2, mime: mime2, size: buffer4.length };
|
|
76213
|
+
}
|
|
76214
|
+
const buffer3 = await readFile(src);
|
|
76215
|
+
if (buffer3.length > MAX_BYTES) {
|
|
76216
|
+
throw new Error(`File too large: ${buffer3.length} bytes exceeds ${MAX_BYTES} byte cap.`);
|
|
76217
|
+
}
|
|
76218
|
+
const name = opts.hintName ?? basename(src);
|
|
76219
|
+
const mime = opts.hintMime ?? detectMime(buffer3, name);
|
|
76220
|
+
return { buffer: buffer3, name, mime, size: buffer3.length };
|
|
76221
|
+
}
|
|
76222
|
+
async function readStdin() {
|
|
76223
|
+
const chunks = [];
|
|
76224
|
+
let total = 0;
|
|
76225
|
+
for await (const chunk of process.stdin) {
|
|
76226
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
76227
|
+
total += buf.length;
|
|
76228
|
+
if (total > MAX_BYTES) {
|
|
76229
|
+
throw new Error(`stdin exceeded ${MAX_BYTES} byte cap.`);
|
|
76230
|
+
}
|
|
76231
|
+
chunks.push(buf);
|
|
76232
|
+
}
|
|
76233
|
+
return Buffer.concat(chunks);
|
|
76234
|
+
}
|
|
76235
|
+
async function fetchRemote(url2) {
|
|
76236
|
+
const controller = new AbortController;
|
|
76237
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
76238
|
+
try {
|
|
76239
|
+
const res = await fetch(url2, { signal: controller.signal });
|
|
76240
|
+
if (!res.ok) {
|
|
76241
|
+
throw new Error(`Fetch ${url2} failed: ${res.status} ${res.statusText}`);
|
|
76242
|
+
}
|
|
76243
|
+
const len = Number(res.headers.get("content-length") || 0);
|
|
76244
|
+
if (len > MAX_BYTES) {
|
|
76245
|
+
throw new Error(`Remote file too large: ${len} bytes exceeds ${MAX_BYTES} byte cap.`);
|
|
76246
|
+
}
|
|
76247
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
76248
|
+
if (buf.length > MAX_BYTES) {
|
|
76249
|
+
throw new Error(`Remote file too large: ${buf.length} bytes exceeds ${MAX_BYTES} byte cap.`);
|
|
76250
|
+
}
|
|
76251
|
+
return buf;
|
|
76252
|
+
} finally {
|
|
76253
|
+
clearTimeout(timer);
|
|
76254
|
+
}
|
|
76255
|
+
}
|
|
76256
|
+
var MAGIC = [
|
|
76257
|
+
[Buffer.from([37, 80, 68, 70]), "application/pdf"],
|
|
76258
|
+
[Buffer.from([137, 80, 78, 71]), "image/png"],
|
|
76259
|
+
[Buffer.from([255, 216, 255]), "image/jpeg"],
|
|
76260
|
+
[Buffer.from([71, 73, 70, 56]), "image/gif"],
|
|
76261
|
+
[Buffer.from([82, 73, 70, 70]), "audio/wav"],
|
|
76262
|
+
[Buffer.from([73, 68, 51]), "audio/mpeg"],
|
|
76263
|
+
[Buffer.from([255, 251]), "audio/mpeg"],
|
|
76264
|
+
[Buffer.from([80, 75, 3, 4]), "application/zip"]
|
|
76265
|
+
];
|
|
76266
|
+
var EXT_MIME = {
|
|
76267
|
+
".pdf": "application/pdf",
|
|
76268
|
+
".png": "image/png",
|
|
76269
|
+
".jpg": "image/jpeg",
|
|
76270
|
+
".jpeg": "image/jpeg",
|
|
76271
|
+
".gif": "image/gif",
|
|
76272
|
+
".webp": "image/webp",
|
|
76273
|
+
".txt": "text/plain",
|
|
76274
|
+
".md": "text/markdown",
|
|
76275
|
+
".json": "application/json",
|
|
76276
|
+
".wav": "audio/wav",
|
|
76277
|
+
".mp3": "audio/mpeg",
|
|
76278
|
+
".m4a": "audio/mp4",
|
|
76279
|
+
".ogg": "audio/ogg",
|
|
76280
|
+
".zip": "application/zip"
|
|
76281
|
+
};
|
|
76282
|
+
function detectMime(buffer3, name) {
|
|
76283
|
+
for (const [sig, mime] of MAGIC) {
|
|
76284
|
+
if (buffer3.length >= sig.length && buffer3.subarray(0, sig.length).equals(sig)) {
|
|
76285
|
+
return mime;
|
|
76286
|
+
}
|
|
76287
|
+
}
|
|
76288
|
+
const ext = extname(name).toLowerCase();
|
|
76289
|
+
return EXT_MIME[ext] ?? "application/octet-stream";
|
|
76290
|
+
}
|
|
76291
|
+
|
|
76292
|
+
// src/commands/docs.ts
|
|
76293
|
+
function twinFromFlag(opts) {
|
|
76294
|
+
const config2 = loadConfig();
|
|
76295
|
+
const auth = resolveAuth(config2);
|
|
76296
|
+
try {
|
|
76297
|
+
return { twinId: resolveTwinId({ flag: opts.twin, config: config2, auth }), auth };
|
|
76298
|
+
} catch (e8) {
|
|
76299
|
+
if (e8 instanceof TwinResolutionError) {
|
|
76300
|
+
console.error(error(e8.message));
|
|
76301
|
+
process.exit(1);
|
|
76302
|
+
}
|
|
76303
|
+
throw e8;
|
|
76304
|
+
}
|
|
76305
|
+
}
|
|
76306
|
+
function fmtBytes(n20) {
|
|
76307
|
+
if (!n20 || n20 <= 0)
|
|
76308
|
+
return "-";
|
|
76309
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
76310
|
+
let v8 = n20;
|
|
76311
|
+
let i15 = 0;
|
|
76312
|
+
while (v8 >= 1024 && i15 < units.length - 1) {
|
|
76313
|
+
v8 /= 1024;
|
|
76314
|
+
i15++;
|
|
76315
|
+
}
|
|
76316
|
+
return `${v8.toFixed(v8 >= 10 ? 0 : 1)} ${units[i15]}`;
|
|
76317
|
+
}
|
|
76318
|
+
function registerDocsCommand(program2) {
|
|
76319
|
+
program2.command("docs").description("List uploaded documents for a twin").option("--twin <idOrName>", "Target twin id or name").option("--limit <n>", "Max results", "50").option("--json", "Output as JSON").action(async (opts) => {
|
|
76320
|
+
const { twinId, auth } = twinFromFlag(opts);
|
|
76321
|
+
const spinner = opts.json ? null : ora("Loading documents...").start();
|
|
76322
|
+
try {
|
|
76323
|
+
const data = await amikoWebFetch(auth, `/api/agents/${twinId}/docs`, { query: { limit: opts.limit ?? "50" } });
|
|
76324
|
+
spinner?.stop();
|
|
76325
|
+
renderOutput(data, (d13) => {
|
|
76326
|
+
if (!d13.docs || d13.docs.length === 0) {
|
|
76327
|
+
console.log(dim("No documents."));
|
|
76328
|
+
return;
|
|
76329
|
+
}
|
|
76330
|
+
console.log(heading(`Documents (${d13.total ?? d13.docs.length})`));
|
|
76331
|
+
const rows = [
|
|
76332
|
+
[
|
|
76333
|
+
dim("ID"),
|
|
76334
|
+
dim("NAME"),
|
|
76335
|
+
dim("SIZE"),
|
|
76336
|
+
dim("STATUS"),
|
|
76337
|
+
dim("CREATED")
|
|
76338
|
+
],
|
|
76339
|
+
...d13.docs.map((doc2) => [
|
|
76340
|
+
doc2.id.slice(0, 8),
|
|
76341
|
+
doc2.title || doc2.filename,
|
|
76342
|
+
fmtBytes(doc2.file_size),
|
|
76343
|
+
doc2.status || "-",
|
|
76344
|
+
doc2.created_at?.slice(0, 10) || "-"
|
|
76345
|
+
])
|
|
76346
|
+
];
|
|
76347
|
+
console.log(table(rows));
|
|
76348
|
+
}, { json: opts.json });
|
|
76349
|
+
} catch (e8) {
|
|
76350
|
+
spinner?.stop();
|
|
76351
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76352
|
+
process.exit(1);
|
|
76353
|
+
}
|
|
76354
|
+
});
|
|
76355
|
+
program2.command("docs:upload <source>").description("Upload a document (path | https URL | - for stdin)").option("--twin <idOrName>", "Target twin id or name").option("--name <filename>", "Override filename").option("--title <title>", "Document title (defaults to filename)").option("--description <text>", "Document description").option("--doc-type <type>", "Document type (e.g. 'other', 'memory')", "other").option("--stdin", "Read from stdin (source must be -)").option("--json", "Output as JSON").action(async (source, opts) => {
|
|
76356
|
+
const { twinId, auth } = twinFromFlag(opts);
|
|
76357
|
+
const spinner = opts.json ? null : ora("Reading input...").start();
|
|
76358
|
+
try {
|
|
76359
|
+
const file2 = await resolveFileInput(source, {
|
|
76360
|
+
stdin: opts.stdin,
|
|
76361
|
+
hintName: opts.name
|
|
76362
|
+
});
|
|
76363
|
+
if (spinner)
|
|
76364
|
+
spinner.text = `Uploading ${file2.name}...`;
|
|
76365
|
+
const form2 = new FormData;
|
|
76366
|
+
form2.set("file", new Blob([new Uint8Array(file2.buffer)], { type: file2.mime }), file2.name);
|
|
76367
|
+
const upload = await amikoWebFetch(auth, `/api/agents/${twinId}/docs/upload`, {
|
|
76368
|
+
method: "POST",
|
|
76369
|
+
multipart: form2
|
|
76370
|
+
});
|
|
76371
|
+
if (!upload.url || !upload.filename || !upload.fileType) {
|
|
76372
|
+
throw new Error(`Upload response missing required fields: ${JSON.stringify(upload)}`);
|
|
76373
|
+
}
|
|
76374
|
+
if (spinner)
|
|
76375
|
+
spinner.text = `Registering ${upload.filename}...`;
|
|
76376
|
+
const title = opts.title || upload.filename.replace(/\.[^.]+$/, "");
|
|
76377
|
+
const doc2 = await amikoWebFetch(auth, `/api/agents/${twinId}/docs`, {
|
|
76378
|
+
method: "POST",
|
|
76379
|
+
body: {
|
|
76380
|
+
filename: upload.filename,
|
|
76381
|
+
fileUrl: upload.url,
|
|
76382
|
+
fileType: upload.fileType,
|
|
76383
|
+
title,
|
|
76384
|
+
description: opts.description ?? undefined,
|
|
76385
|
+
doc_type: opts.docType ?? "other"
|
|
76386
|
+
}
|
|
76387
|
+
});
|
|
76388
|
+
spinner?.stop();
|
|
76389
|
+
renderOutput(doc2, (d13) => {
|
|
76390
|
+
console.log(success(`Uploaded ${d13.filename}`));
|
|
76391
|
+
console.log(label("Doc ID", d13.id));
|
|
76392
|
+
console.log(label("Title", d13.title || "-"));
|
|
76393
|
+
if (upload.fileSize)
|
|
76394
|
+
console.log(label("Size", fmtBytes(upload.fileSize)));
|
|
76395
|
+
if (upload.fileType)
|
|
76396
|
+
console.log(label("Type", upload.fileType));
|
|
76397
|
+
if (upload.url)
|
|
76398
|
+
console.log(label("URL", upload.url));
|
|
76399
|
+
}, { json: opts.json });
|
|
76400
|
+
} catch (e8) {
|
|
76401
|
+
spinner?.stop();
|
|
76402
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76403
|
+
process.exit(1);
|
|
76404
|
+
}
|
|
76405
|
+
});
|
|
76406
|
+
program2.command("docs:delete <docId>").description("Delete a document").option("--twin <idOrName>", "Target twin id or name").option("--yes", "Skip confirmation").option("--json", "Output as JSON").action(async (docId, opts) => {
|
|
76407
|
+
const { twinId, auth } = twinFromFlag(opts);
|
|
76408
|
+
await confirmDestructive({
|
|
76409
|
+
action: `Delete document ${docId}`,
|
|
76410
|
+
detail: "This will remove the document and its embeddings from the twin's RAG.",
|
|
76411
|
+
yes: opts.yes,
|
|
76412
|
+
commandExample: `amiko docs:delete ${docId}`
|
|
76413
|
+
});
|
|
76414
|
+
const spinner = opts.json ? null : ora("Deleting document...").start();
|
|
76415
|
+
try {
|
|
76416
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/docs/${docId}`, { method: "DELETE" });
|
|
76417
|
+
spinner?.stop();
|
|
76418
|
+
renderOutput(result, () => console.log(success(`Deleted ${docId}`)), { json: opts.json });
|
|
76419
|
+
} catch (e8) {
|
|
76420
|
+
spinner?.stop();
|
|
76421
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76422
|
+
process.exit(1);
|
|
76423
|
+
}
|
|
76424
|
+
});
|
|
76425
|
+
program2.command("docs:presign <source>").description("Get a presigned upload URL for a document (no upload)").option("--twin <idOrName>", "Target twin id or name").option("--name <filename>", "Override filename used in the request").option("--json", "Output as JSON").action(async (source, opts) => {
|
|
76426
|
+
const { twinId, auth } = twinFromFlag(opts);
|
|
76427
|
+
const filename = opts.name ?? (source === "-" ? "stdin" : source.split("/").pop() ?? source);
|
|
76428
|
+
try {
|
|
76429
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/docs/presigned-url`, { method: "POST", body: { filename } });
|
|
76430
|
+
renderOutput(result, (r19) => console.log(JSON.stringify(r19, null, 2)), { json: opts.json });
|
|
76431
|
+
} catch (e8) {
|
|
76432
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76433
|
+
process.exit(1);
|
|
76434
|
+
}
|
|
76435
|
+
});
|
|
76436
|
+
}
|
|
76437
|
+
|
|
76438
|
+
// src/commands/voice.ts
|
|
76439
|
+
function twinFromFlag2(opts) {
|
|
76440
|
+
const config2 = loadConfig();
|
|
76441
|
+
const auth = resolveAuth(config2);
|
|
76442
|
+
try {
|
|
76443
|
+
return { twinId: resolveTwinId({ flag: opts.twin, config: config2, auth }), auth };
|
|
76444
|
+
} catch (e8) {
|
|
76445
|
+
if (e8 instanceof TwinResolutionError) {
|
|
76446
|
+
console.error(error(e8.message));
|
|
76447
|
+
process.exit(1);
|
|
76448
|
+
}
|
|
76449
|
+
throw e8;
|
|
76450
|
+
}
|
|
76451
|
+
}
|
|
76452
|
+
function registerVoiceCommand(program2) {
|
|
76453
|
+
program2.command("voice:design").description("Design a voice from a text description").requiredOption("--description <text>", "Voice description (min 20 chars)").option("--twin <idOrName>", "Target twin id or name").option("--json", "Output as JSON").action(async (opts) => {
|
|
76454
|
+
if (opts.description.trim().length < 20) {
|
|
76455
|
+
console.error(error("--description must be at least 20 characters"));
|
|
76456
|
+
process.exit(1);
|
|
76457
|
+
}
|
|
76458
|
+
const { twinId, auth } = twinFromFlag2(opts);
|
|
76459
|
+
const spinner = opts.json ? null : ora("Generating voice previews...").start();
|
|
76460
|
+
try {
|
|
76461
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/voice/design`, {
|
|
76462
|
+
method: "POST",
|
|
76463
|
+
body: { voiceDescription: opts.description.trim() },
|
|
76464
|
+
timeoutMs: 120000
|
|
76465
|
+
});
|
|
76466
|
+
spinner?.stop();
|
|
76467
|
+
renderOutput(result, (r19) => {
|
|
76468
|
+
console.log(heading(`Voice Previews (${r19.previews?.length ?? 0})`));
|
|
76469
|
+
for (const [i15, p13] of (r19.previews ?? []).entries()) {
|
|
76470
|
+
console.log(label(`#${i15 + 1} id`, p13.generated_voice_id ?? "?"));
|
|
76471
|
+
}
|
|
76472
|
+
console.log("");
|
|
76473
|
+
console.log(dim("Pick one with: amiko voice:create --sample <id>"));
|
|
76474
|
+
}, { json: opts.json });
|
|
76475
|
+
} catch (e8) {
|
|
76476
|
+
spinner?.stop();
|
|
76477
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76478
|
+
process.exit(1);
|
|
76479
|
+
}
|
|
76480
|
+
});
|
|
76481
|
+
program2.command("voice:create").description("Create a voice from a previously designed preview").requiredOption("--sample <id>", "generated_voice_id from voice:design").option("--description <text>", "Optional voice description").option("--twin <idOrName>", "Target twin id or name").option("--json", "Output as JSON").action(async (opts) => {
|
|
76482
|
+
const { twinId, auth } = twinFromFlag2(opts);
|
|
76483
|
+
const spinner = opts.json ? null : ora("Creating voice...").start();
|
|
76484
|
+
try {
|
|
76485
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/voice/create`, {
|
|
76486
|
+
method: "POST",
|
|
76487
|
+
body: {
|
|
76488
|
+
generatedVoiceId: opts.sample,
|
|
76489
|
+
voiceDescription: opts.description
|
|
76490
|
+
},
|
|
76491
|
+
timeoutMs: 120000
|
|
76492
|
+
});
|
|
76493
|
+
spinner?.stop();
|
|
76494
|
+
renderOutput(result, (r19) => {
|
|
76495
|
+
console.log(success("Voice created"));
|
|
76496
|
+
if (r19.voice_id)
|
|
76497
|
+
console.log(label("Voice ID", String(r19.voice_id)));
|
|
76498
|
+
if (r19.voice_status)
|
|
76499
|
+
console.log(label("Status", String(r19.voice_status)));
|
|
76500
|
+
}, { json: opts.json });
|
|
76501
|
+
} catch (e8) {
|
|
76502
|
+
spinner?.stop();
|
|
76503
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76504
|
+
process.exit(1);
|
|
76505
|
+
}
|
|
76506
|
+
});
|
|
76507
|
+
program2.command("voice:clone <source>").description("Clone a voice from an audio file (path | https URL | -)").option("--twin <idOrName>", "Target twin id or name").option("--name <filename>", "Override filename").option("--stdin", "Read from stdin (source must be -)").option("--json", "Output as JSON").action(async (source, opts) => {
|
|
76508
|
+
const { twinId, auth } = twinFromFlag2(opts);
|
|
76509
|
+
const spinner = opts.json ? null : ora("Reading audio...").start();
|
|
76510
|
+
try {
|
|
76511
|
+
const file2 = await resolveFileInput(source, {
|
|
76512
|
+
stdin: opts.stdin,
|
|
76513
|
+
hintName: opts.name,
|
|
76514
|
+
hintMime: "audio/mpeg"
|
|
76515
|
+
});
|
|
76516
|
+
if (spinner)
|
|
76517
|
+
spinner.text = `Cloning voice from ${file2.name}...`;
|
|
76518
|
+
const form2 = new FormData;
|
|
76519
|
+
form2.set("audio", new Blob([new Uint8Array(file2.buffer)], { type: file2.mime }), file2.name);
|
|
76520
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/voice/clone`, { method: "POST", multipart: form2, timeoutMs: 180000 });
|
|
76521
|
+
spinner?.stop();
|
|
76522
|
+
renderOutput(result, (r19) => {
|
|
76523
|
+
console.log(success("Voice cloned"));
|
|
76524
|
+
if (r19.voice_id)
|
|
76525
|
+
console.log(label("Voice ID", String(r19.voice_id)));
|
|
76526
|
+
if (r19.voice_status)
|
|
76527
|
+
console.log(label("Status", String(r19.voice_status)));
|
|
76528
|
+
}, { json: opts.json });
|
|
76529
|
+
} catch (e8) {
|
|
76530
|
+
spinner?.stop();
|
|
76531
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76532
|
+
process.exit(1);
|
|
76533
|
+
}
|
|
76534
|
+
});
|
|
76535
|
+
program2.command("voice:reset").description("Reset the twin's voice to default").option("--twin <idOrName>", "Target twin id or name").option("--yes", "Skip confirmation").option("--json", "Output as JSON").action(async (opts) => {
|
|
76536
|
+
const { twinId, auth } = twinFromFlag2(opts);
|
|
76537
|
+
await confirmDestructive({
|
|
76538
|
+
action: "Reset voice",
|
|
76539
|
+
detail: "This removes the custom voice. You can redesign or clone a new one later.",
|
|
76540
|
+
yes: opts.yes,
|
|
76541
|
+
commandExample: "amiko voice:reset"
|
|
76542
|
+
});
|
|
76543
|
+
const spinner = opts.json ? null : ora("Resetting voice...").start();
|
|
76544
|
+
try {
|
|
76545
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/voice/reset`, { method: "POST" });
|
|
76546
|
+
spinner?.stop();
|
|
76547
|
+
renderOutput(result, () => console.log(success("Voice reset")), { json: opts.json });
|
|
76548
|
+
} catch (e8) {
|
|
76549
|
+
spinner?.stop();
|
|
76550
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76551
|
+
process.exit(1);
|
|
76552
|
+
}
|
|
76553
|
+
});
|
|
76554
|
+
}
|
|
76555
|
+
|
|
76556
|
+
// src/commands/avatar.ts
|
|
76557
|
+
function registerAvatarCommand(program2) {
|
|
76558
|
+
program2.command("avatar:update").description("Update twin avatar image (path | https URL | - for stdin)").requiredOption("--file <source>", "Image source").option("--twin <idOrName>", "Target twin id or name").option("--stdin", "Read from stdin (file must be -)").option("--name <filename>", "Override filename").option("--yes", "Skip confirmation").option("--json", "Output as JSON").action(async (opts) => {
|
|
76559
|
+
const config2 = loadConfig();
|
|
76560
|
+
const auth = resolveAuth(config2);
|
|
76561
|
+
let twinId;
|
|
76562
|
+
try {
|
|
76563
|
+
twinId = resolveTwinId({ flag: opts.twin, config: config2, auth });
|
|
76564
|
+
} catch (e8) {
|
|
76565
|
+
if (e8 instanceof TwinResolutionError) {
|
|
76566
|
+
console.error(error(e8.message));
|
|
76567
|
+
process.exit(1);
|
|
76568
|
+
}
|
|
76569
|
+
throw e8;
|
|
76570
|
+
}
|
|
76571
|
+
await confirmDestructive({
|
|
76572
|
+
action: "Replace twin avatar",
|
|
76573
|
+
detail: "The twin's public avatar will change immediately.",
|
|
76574
|
+
yes: opts.yes,
|
|
76575
|
+
commandExample: `amiko avatar:update --file ${opts.file}`
|
|
76576
|
+
});
|
|
76577
|
+
const spinner = opts.json ? null : ora("Reading image...").start();
|
|
76578
|
+
try {
|
|
76579
|
+
const file2 = await resolveFileInput(opts.file, {
|
|
76580
|
+
stdin: opts.stdin,
|
|
76581
|
+
hintName: opts.name
|
|
76582
|
+
});
|
|
76583
|
+
if (!file2.mime.startsWith("image/")) {
|
|
76584
|
+
spinner?.stop();
|
|
76585
|
+
console.error(error(`File is not an image (mime: ${file2.mime})`));
|
|
76586
|
+
process.exit(1);
|
|
76587
|
+
}
|
|
76588
|
+
if (file2.size > 5 * 1024 * 1024) {
|
|
76589
|
+
spinner?.stop();
|
|
76590
|
+
console.error(error(`Image too large: ${file2.size} bytes (max 5 MB)`));
|
|
76591
|
+
process.exit(1);
|
|
76592
|
+
}
|
|
76593
|
+
if (spinner)
|
|
76594
|
+
spinner.text = `Uploading avatar (${file2.name})...`;
|
|
76595
|
+
const form2 = new FormData;
|
|
76596
|
+
form2.set("image", new Blob([new Uint8Array(file2.buffer)], { type: file2.mime }), file2.name);
|
|
76597
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/avatar`, {
|
|
76598
|
+
method: "POST",
|
|
76599
|
+
multipart: form2
|
|
76600
|
+
});
|
|
76601
|
+
spinner?.stop();
|
|
76602
|
+
renderOutput(result, (r19) => {
|
|
76603
|
+
console.log(success("Avatar updated"));
|
|
76604
|
+
if (r19.avatar_url)
|
|
76605
|
+
console.log(label("URL", r19.avatar_url));
|
|
76606
|
+
}, { json: opts.json });
|
|
76607
|
+
} catch (e8) {
|
|
76608
|
+
spinner?.stop();
|
|
76609
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76610
|
+
process.exit(1);
|
|
76611
|
+
}
|
|
76612
|
+
});
|
|
76613
|
+
}
|
|
76614
|
+
|
|
76615
|
+
// src/commands/friends.ts
|
|
76616
|
+
function registerFriendsCommand(program2) {
|
|
76617
|
+
program2.command("friends").description("List friends").option("--type <user|twin>", "Filter by friend type").option("--json", "Output as JSON").action(async (opts) => {
|
|
76618
|
+
const auth = resolveAuth(loadConfig());
|
|
76619
|
+
const spinner = opts.json ? null : ora("Loading friends...").start();
|
|
76620
|
+
try {
|
|
76621
|
+
const data = await amikoWebFetch(auth, "/api/friends", { query: { type: opts.type } });
|
|
76622
|
+
spinner?.stop();
|
|
76623
|
+
renderOutput(data, (d13) => {
|
|
76624
|
+
const items = d13.items ?? [];
|
|
76625
|
+
if (items.length === 0) {
|
|
76626
|
+
console.log(dim("No friends."));
|
|
76627
|
+
return;
|
|
76628
|
+
}
|
|
76629
|
+
console.log(heading(`Friends (${items.length})`));
|
|
76630
|
+
const rows = [
|
|
76631
|
+
[dim("ID"), dim("NAME"), dim("HANDLE"), dim("TYPE"), dim("STATUS")],
|
|
76632
|
+
...items.map((f13) => [
|
|
76633
|
+
f13.id.slice(0, 8),
|
|
76634
|
+
f13.friend.name ?? "-",
|
|
76635
|
+
f13.friend.handle ?? "-",
|
|
76636
|
+
f13.friend.type,
|
|
76637
|
+
f13.status
|
|
76638
|
+
])
|
|
76639
|
+
];
|
|
76640
|
+
console.log(table(rows));
|
|
76641
|
+
}, { json: opts.json });
|
|
76642
|
+
} catch (e8) {
|
|
76643
|
+
spinner?.stop();
|
|
76644
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76645
|
+
process.exit(1);
|
|
76646
|
+
}
|
|
76647
|
+
});
|
|
76648
|
+
program2.command("friends:add").description("Send a friend request to a user by id").requiredOption("--id <userId>", "Target user id").option("--json", "Output as JSON").action(async (opts) => {
|
|
76649
|
+
const auth = resolveAuth(loadConfig());
|
|
76650
|
+
const spinner = opts.json ? null : ora("Sending friend request...").start();
|
|
76651
|
+
try {
|
|
76652
|
+
const data = await amikoWebFetch(auth, "/api/friends", {
|
|
76653
|
+
method: "POST",
|
|
76654
|
+
body: { friend_id: opts.id }
|
|
76655
|
+
});
|
|
76656
|
+
spinner?.stop();
|
|
76657
|
+
renderOutput(data, (d13) => {
|
|
76658
|
+
console.log(success(d13.auto_accepted ? "Auto-accepted" : "Request sent"));
|
|
76659
|
+
if (d13.friendship_id)
|
|
76660
|
+
console.log(label("Friendship ID", d13.friendship_id));
|
|
76661
|
+
}, { json: opts.json });
|
|
76662
|
+
} catch (e8) {
|
|
76663
|
+
spinner?.stop();
|
|
76664
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76665
|
+
process.exit(1);
|
|
76666
|
+
}
|
|
76667
|
+
});
|
|
76668
|
+
program2.command("friends:accept <friendshipId>").description("Accept a pending friend request").option("--json", "Output as JSON").action(async (friendshipId, opts) => {
|
|
76669
|
+
const auth = resolveAuth(loadConfig());
|
|
76670
|
+
const spinner = opts.json ? null : ora("Accepting...").start();
|
|
76671
|
+
try {
|
|
76672
|
+
const data = await amikoWebFetch(auth, `/api/friends/${friendshipId}`, { method: "PATCH", body: { action: "accept" } });
|
|
76673
|
+
spinner?.stop();
|
|
76674
|
+
renderOutput(data, () => console.log(success(`Accepted ${friendshipId}`)), { json: opts.json });
|
|
76675
|
+
} catch (e8) {
|
|
76676
|
+
spinner?.stop();
|
|
76677
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76678
|
+
process.exit(1);
|
|
76679
|
+
}
|
|
76680
|
+
});
|
|
76681
|
+
program2.command("friends:remove <friendshipId>").description("Remove a friend").option("--yes", "Skip confirmation").option("--json", "Output as JSON").action(async (friendshipId, opts) => {
|
|
76682
|
+
await confirmDestructive({
|
|
76683
|
+
action: `Remove friend ${friendshipId}`,
|
|
76684
|
+
yes: opts.yes,
|
|
76685
|
+
commandExample: `amiko friends:remove ${friendshipId}`
|
|
76686
|
+
});
|
|
76687
|
+
const auth = resolveAuth(loadConfig());
|
|
76688
|
+
const spinner = opts.json ? null : ora("Removing...").start();
|
|
76689
|
+
try {
|
|
76690
|
+
const data = await amikoWebFetch(auth, `/api/friends/${friendshipId}`, { method: "DELETE" });
|
|
76691
|
+
spinner?.stop();
|
|
76692
|
+
renderOutput(data, () => console.log(success(`Removed ${friendshipId}`)), { json: opts.json });
|
|
76693
|
+
} catch (e8) {
|
|
76694
|
+
spinner?.stop();
|
|
76695
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76696
|
+
process.exit(1);
|
|
76697
|
+
}
|
|
76698
|
+
});
|
|
76699
|
+
}
|
|
76700
|
+
|
|
76701
|
+
// src/commands/feed.ts
|
|
76702
|
+
function registerFeedCommand(program2) {
|
|
76703
|
+
program2.command("feed").description("Get feed posts").option("--type <for_you|friends>", "Feed type", "friends").option("--hashtag <tag>", "Filter by hashtag").option("--limit <n>", "Max results (default 10, max 100)").option("--cursor <id>", "Pagination cursor").option("--json", "Output as JSON").action(async (opts) => {
|
|
76704
|
+
const auth = resolveAuth(loadConfig());
|
|
76705
|
+
const spinner = opts.json ? null : ora("Loading feed...").start();
|
|
76706
|
+
try {
|
|
76707
|
+
const data = await amikoWebFetch(auth, "/api/feed", {
|
|
76708
|
+
query: {
|
|
76709
|
+
type: opts.type,
|
|
76710
|
+
hashtag: opts.hashtag,
|
|
76711
|
+
limit: opts.limit,
|
|
76712
|
+
cursor: opts.cursor
|
|
76713
|
+
}
|
|
76714
|
+
});
|
|
76715
|
+
spinner?.stop();
|
|
76716
|
+
const posts = data.items ?? data.posts ?? [];
|
|
76717
|
+
renderOutput({ ...data, posts }, (d13) => {
|
|
76718
|
+
if (posts.length === 0) {
|
|
76719
|
+
console.log(dim("No posts."));
|
|
76720
|
+
return;
|
|
76721
|
+
}
|
|
76722
|
+
console.log(heading(`Feed (${opts.type ?? "friends"}) — ${posts.length} posts`));
|
|
76723
|
+
for (const p13 of posts) {
|
|
76724
|
+
const author = p13.twin?.name ?? p13.user?.name ?? p13.user?.handle ?? "unknown";
|
|
76725
|
+
console.log("");
|
|
76726
|
+
console.log(label(author, p13.created_at?.slice(0, 16) ?? ""));
|
|
76727
|
+
console.log(p13.content);
|
|
76728
|
+
if (p13._count) {
|
|
76729
|
+
console.log(dim(`${p13._count.likes ?? 0} likes · ${p13._count.comments ?? 0} comments · ${p13._count.bookmarks ?? 0} bookmarks`));
|
|
76730
|
+
}
|
|
76731
|
+
console.log(dim(`id: ${p13.id}`));
|
|
76732
|
+
}
|
|
76733
|
+
if (d13.nextCursor) {
|
|
76734
|
+
console.log("");
|
|
76735
|
+
console.log(dim(`Next: --cursor ${d13.nextCursor}`));
|
|
76736
|
+
}
|
|
76737
|
+
}, { json: opts.json });
|
|
76738
|
+
} catch (e8) {
|
|
76739
|
+
spinner?.stop();
|
|
76740
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76741
|
+
process.exit(1);
|
|
76742
|
+
}
|
|
76743
|
+
});
|
|
76744
|
+
program2.command("post:comment").description("Comment on a post").requiredOption("--id <postId>", "Target post id").requiredOption("--comment <text>", "Comment body").option("--twin <idOrName>", "Post as this twin (agent mode)").option("--json", "Output as JSON").action(async (opts) => {
|
|
76745
|
+
const config2 = loadConfig();
|
|
76746
|
+
const auth = resolveAuth(config2);
|
|
76747
|
+
let twinId;
|
|
76748
|
+
if (opts.twin || auth.twinId) {
|
|
76749
|
+
try {
|
|
76750
|
+
twinId = resolveTwinId({ flag: opts.twin, config: config2, auth });
|
|
76751
|
+
} catch (e8) {
|
|
76752
|
+
if (e8 instanceof TwinResolutionError) {
|
|
76753
|
+
twinId = undefined;
|
|
76754
|
+
} else {
|
|
76755
|
+
throw e8;
|
|
76756
|
+
}
|
|
76757
|
+
}
|
|
76758
|
+
}
|
|
76759
|
+
const spinner = opts.json ? null : ora("Posting comment...").start();
|
|
76760
|
+
try {
|
|
76761
|
+
const body = { comment: opts.comment };
|
|
76762
|
+
if (twinId)
|
|
76763
|
+
body.twin_id = twinId;
|
|
76764
|
+
const data = await amikoWebFetch(auth, `/api/posts/${opts.id}/comments`, { method: "POST", body });
|
|
76765
|
+
spinner?.stop();
|
|
76766
|
+
renderOutput(data, (d13) => {
|
|
76767
|
+
console.log(success("Comment posted"));
|
|
76768
|
+
if (d13.id)
|
|
76769
|
+
console.log(label("Comment ID", String(d13.id)));
|
|
76770
|
+
}, { json: opts.json });
|
|
76771
|
+
} catch (e8) {
|
|
76772
|
+
spinner?.stop();
|
|
76773
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76774
|
+
process.exit(1);
|
|
76775
|
+
}
|
|
76776
|
+
});
|
|
76777
|
+
}
|
|
76778
|
+
|
|
76779
|
+
// src/commands/composio.ts
|
|
76780
|
+
function twinFromFlag3(opts) {
|
|
76781
|
+
const config2 = loadConfig();
|
|
76782
|
+
const auth = resolveAuth(config2);
|
|
76783
|
+
try {
|
|
76784
|
+
return { twinId: resolveTwinId({ flag: opts.twin, config: config2, auth }), auth };
|
|
76785
|
+
} catch (e8) {
|
|
76786
|
+
if (e8 instanceof TwinResolutionError) {
|
|
76787
|
+
console.error(error(e8.message));
|
|
76788
|
+
process.exit(1);
|
|
76789
|
+
}
|
|
76790
|
+
throw e8;
|
|
76791
|
+
}
|
|
76792
|
+
}
|
|
76793
|
+
function registerComposioCommand(program2) {
|
|
76794
|
+
program2.command("composio:connect").description("Start Composio OAuth for an app (gmail, github, ...)").requiredOption("--app <slug>", "App name (e.g. gmail)").option("--twin <idOrName>", "Target twin id or name").option("--redirect <url>", "Override OAuth redirect URL").option("--force", "Force reconnect if already connected").option("--json", "Output as JSON").action(async (opts) => {
|
|
76795
|
+
const { twinId, auth } = twinFromFlag3(opts);
|
|
76796
|
+
const spinner = opts.json ? null : ora(`Connecting ${opts.app}...`).start();
|
|
76797
|
+
try {
|
|
76798
|
+
const data = await amikoWebFetch(auth, `/api/agents/${twinId}/composio/connect`, {
|
|
76799
|
+
method: "POST",
|
|
76800
|
+
body: {
|
|
76801
|
+
appName: opts.app,
|
|
76802
|
+
redirectUrl: opts.redirect,
|
|
76803
|
+
forceReconnect: opts.force ?? true
|
|
76804
|
+
}
|
|
76805
|
+
});
|
|
76806
|
+
spinner?.stop();
|
|
76807
|
+
renderOutput(data, (d13) => {
|
|
76808
|
+
if (d13.alreadyConnected) {
|
|
76809
|
+
console.log(success(`${opts.app} is already connected`));
|
|
76810
|
+
if (d13.connectionId)
|
|
76811
|
+
console.log(label("Connection ID", d13.connectionId));
|
|
76812
|
+
return;
|
|
76813
|
+
}
|
|
76814
|
+
console.log(success(`OAuth URL ready for ${opts.app}`));
|
|
76815
|
+
if (d13.redirectUrl) {
|
|
76816
|
+
console.log("");
|
|
76817
|
+
console.log(d13.redirectUrl);
|
|
76818
|
+
console.log("");
|
|
76819
|
+
console.log(dim("Open this URL in a browser to complete the connection."));
|
|
76820
|
+
}
|
|
76821
|
+
if (d13.connectionId)
|
|
76822
|
+
console.log(label("Connection ID", d13.connectionId));
|
|
76823
|
+
}, { json: opts.json });
|
|
76824
|
+
} catch (e8) {
|
|
76825
|
+
spinner?.stop();
|
|
76826
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76827
|
+
process.exit(1);
|
|
76828
|
+
}
|
|
76829
|
+
});
|
|
76830
|
+
program2.command("composio:disconnect").description("Disconnect a Composio app for the twin").requiredOption("--app <slug>", "App name (e.g. gmail)").option("--twin <idOrName>", "Target twin id or name").option("--yes", "Skip confirmation").option("--json", "Output as JSON").action(async (opts) => {
|
|
76831
|
+
const { twinId, auth } = twinFromFlag3(opts);
|
|
76832
|
+
await confirmDestructive({
|
|
76833
|
+
action: `Disconnect ${opts.app}`,
|
|
76834
|
+
detail: "The twin will lose access to the app's tools until reconnected.",
|
|
76835
|
+
yes: opts.yes,
|
|
76836
|
+
commandExample: `amiko composio:disconnect --app ${opts.app}`
|
|
76837
|
+
});
|
|
76838
|
+
const spinner = opts.json ? null : ora("Finding connection...").start();
|
|
76839
|
+
try {
|
|
76840
|
+
const connections = await amikoWebFetch(auth, `/api/agents/${twinId}/composio/connections`);
|
|
76841
|
+
const match = connections.details?.find((d13) => d13.appName?.toLowerCase() === opts.app.toLowerCase());
|
|
76842
|
+
if (!match?.id) {
|
|
76843
|
+
spinner?.stop();
|
|
76844
|
+
console.error(error(`No active connection for "${opts.app}". Connected: ${connections.connections?.join(", ") || "none"}`));
|
|
76845
|
+
process.exit(1);
|
|
76846
|
+
}
|
|
76847
|
+
if (spinner)
|
|
76848
|
+
spinner.text = `Disconnecting ${opts.app}...`;
|
|
76849
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/composio/connections`, { method: "DELETE", body: { connectionId: match.id } });
|
|
76850
|
+
spinner?.stop();
|
|
76851
|
+
renderOutput(result, () => console.log(success(`Disconnected ${opts.app}`)), { json: opts.json });
|
|
76852
|
+
} catch (e8) {
|
|
76853
|
+
spinner?.stop();
|
|
76854
|
+
console.error(error(e8 instanceof Error ? e8.message : String(e8)));
|
|
76855
|
+
process.exit(1);
|
|
76856
|
+
}
|
|
76857
|
+
});
|
|
76858
|
+
}
|
|
76859
|
+
|
|
75651
76860
|
// src/index.ts
|
|
75652
76861
|
var require2 = createRequire2(import.meta.url);
|
|
75653
76862
|
var { version: PKG_VERSION } = require2("../package.json");
|
|
@@ -75669,4 +76878,12 @@ registerConfigCommand(program2);
|
|
|
75669
76878
|
registerServicesCommand(program2);
|
|
75670
76879
|
registerDiscoveryCommand(program2);
|
|
75671
76880
|
registerUpdateCommand(program2);
|
|
76881
|
+
registerAccountsCommand(program2);
|
|
76882
|
+
registerTwinCommand(program2);
|
|
76883
|
+
registerDocsCommand(program2);
|
|
76884
|
+
registerVoiceCommand(program2);
|
|
76885
|
+
registerAvatarCommand(program2);
|
|
76886
|
+
registerFriendsCommand(program2);
|
|
76887
|
+
registerFeedCommand(program2);
|
|
76888
|
+
registerComposioCommand(program2);
|
|
75672
76889
|
program2.parse();
|