@itpay/cli 2.1.66 → 2.1.67
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/THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/src/main.js +160 -98
- package/dist/src/state/config.js +1 -1
- package/docs/cli-reference/api.md +5 -0
- package/package.json +3 -3
- package/skills/itpay-api/SKILL.md +11 -0
package/THIRD-PARTY-NOTICES.txt
CHANGED
package/dist/src/main.js
CHANGED
|
@@ -6167,7 +6167,7 @@ async function runHubWallet(options) {
|
|
|
6167
6167
|
};
|
|
6168
6168
|
} else result = { available_atoms: bal.available.amount_atoms, held_atoms: bal.held.amount_atoms, currency: bal.available.currency, scale: bal.available.scale, period_limit_atoms: null, topup_url: bal.topup_url ?? null, scope: "account_balance", identity };
|
|
6169
6169
|
result.owner_billing_url = bal.owner_billing_url ?? bal.topup_url ?? null;
|
|
6170
|
-
writeCommandEnvelope({ status: "ready", result, instruction: options.usage || options.transactions ? "\u4EC5\u5F53\u524D\u51ED\u636E\u8303\u56F4\uFF1Bruns \u5305\u542B\u5931\u8D25\uFF0C\u4E0D\u662F\u6210\u529F\u4EA4\u4ED8\u6570\u3002\u673A\u5668\u6D41\u6C34\u53EA\u542B\u672C\u51ED\u636E\u6D88\u8D39/\u6D88\u8D39\u9000\u6B3E\uFF0C\u622A\u65AD\u4E0D\u80FD\u5F53\u5168\u91CF\u3002\u5168\u8D26\u53F7\u5145\u503C\u3001\u51BB\u7ED3\u53CA\u539F\u6B3E\u9000\u6B3E\u8BF7\u672C\u4EBA\u6253\u5F00\u8D26\u5355\u5165\u53E3\u3002" : "\u4F59\u989D\u5C5E\u4E8E\u8D26\u53F7\uFF0C\u4E0D\u4EE3\u8868\u672C\u8FDE\u63A5\u6D88\u8D39\u989D\u5EA6\u3002\u4E3B\u52A8\u5145\u503C\u6253\u5F00\u5B98\u65B9\u5165\u53E3\uFF0C\u7531\u672C\u4EBA\u9009\u6863\u5E76\u4ED8\u6B3E\uFF1B\u94FE\u63A5\u4E0D\u662F\u5145\u503C\u8BA2\u5355\u3002", next: null, recovery: [] }, { jsonOutput: Boolean(options.jsonOutput) });
|
|
6170
|
+
writeCommandEnvelope({ status: "ready", result, instruction: options.usage || options.transactions ? "\u4EC5\u5F53\u524D\u51ED\u636E\u8303\u56F4\uFF1Bruns \u5305\u542B\u5931\u8D25\uFF0C\u4E0D\u662F\u6210\u529F\u4EA4\u4ED8\u6570\u3002\u673A\u5668\u6D41\u6C34\u53EA\u542B\u672C\u51ED\u636E\u6D88\u8D39/\u6D88\u8D39\u9000\u6B3E\uFF0C\u622A\u65AD\u4E0D\u80FD\u5F53\u5168\u91CF\u3002\u5168\u8D26\u53F7\u5145\u503C\u3001\u51BB\u7ED3\u53CA\u539F\u6B3E\u9000\u6B3E\u8BF7\u672C\u4EBA\u6253\u5F00\u8D26\u5355\u5165\u53E3\u3002" : "\u4F59\u989D\u5C5E\u4E8E\u8D26\u53F7\uFF0C\u4E0D\u4EE3\u8868\u672C\u8FDE\u63A5\u6D88\u8D39\u989D\u5EA6\u3002\u4E3B\u52A8\u5145\u503C\u6253\u5F00\u5B98\u65B9\u5165\u53E3\uFF0C\u7531\u672C\u4EBA\u9009\u6863\u5E76\u4ED8\u6B3E\uFF1B\u94FE\u63A5\u4E0D\u662F\u5145\u503C\u8BA2\u5355\u3002\u4E5F\u53EF\u4EE5\u544A\u8BC9\u6211\u91D1\u989D\uFF0C\u76F4\u63A5\u7ED9\u4F60\u626B\u7801\u4ED8\u6B3E\u3002", next: null, recovery: [] }, { jsonOutput: Boolean(options.jsonOutput) });
|
|
6171
6171
|
}
|
|
6172
6172
|
__name(runHubWallet, "runHubWallet");
|
|
6173
6173
|
async function runHubDoctor(options) {
|
|
@@ -7893,28 +7893,138 @@ function registerAgentCommand(program3) {
|
|
|
7893
7893
|
}
|
|
7894
7894
|
__name(registerAgentCommand, "registerAgentCommand");
|
|
7895
7895
|
|
|
7896
|
+
// src/commands/wallet_topup.ts
|
|
7897
|
+
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
7898
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync15, readdirSync as readdirSync5, readFileSync as readFileSync17, writeFileSync as writeFileSync14, renameSync as renameSync10 } from "node:fs";
|
|
7899
|
+
import { join as join9 } from "node:path";
|
|
7900
|
+
|
|
7901
|
+
// src/render/browser.ts
|
|
7902
|
+
import { spawn } from "node:child_process";
|
|
7903
|
+
import { platform } from "node:os";
|
|
7904
|
+
function isPresentableURL(raw, baseURL, env = process.env) {
|
|
7905
|
+
let url;
|
|
7906
|
+
try {
|
|
7907
|
+
url = new URL(raw);
|
|
7908
|
+
} catch {
|
|
7909
|
+
return false;
|
|
7910
|
+
}
|
|
7911
|
+
if (url.username || url.password) return false;
|
|
7912
|
+
if (env.ITPAY_CLI_DEV === "1" && /^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(url.origin)) return true;
|
|
7913
|
+
if (url.protocol !== "https:") return false;
|
|
7914
|
+
const allowedOrigins = new Set(OFFICIAL_BACKEND_URLS);
|
|
7915
|
+
if (baseURL) {
|
|
7916
|
+
try {
|
|
7917
|
+
allowedOrigins.add(new URL(baseURL).origin);
|
|
7918
|
+
} catch {
|
|
7919
|
+
}
|
|
7920
|
+
}
|
|
7921
|
+
return allowedOrigins.has(url.origin);
|
|
7922
|
+
}
|
|
7923
|
+
__name(isPresentableURL, "isPresentableURL");
|
|
7924
|
+
function openInSystemBrowser(raw, baseURL, env = process.env) {
|
|
7925
|
+
if (!isPresentableURL(raw, baseURL, env)) {
|
|
7926
|
+
return Promise.resolve({ status: "failed", reason: "url_rejected" });
|
|
7927
|
+
}
|
|
7928
|
+
const [command, args] = openerArgv(raw);
|
|
7929
|
+
if (!command) {
|
|
7930
|
+
return Promise.resolve({ status: "unavailable", reason: "no system opener on this platform" });
|
|
7931
|
+
}
|
|
7932
|
+
return new Promise((resolvePromise) => {
|
|
7933
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
7934
|
+
child.once("error", (error) => resolvePromise({ status: "failed", reason: error.message }));
|
|
7935
|
+
child.once("spawn", () => {
|
|
7936
|
+
child.unref();
|
|
7937
|
+
resolvePromise({ status: "dispatch_accepted", argv: [command, ...args] });
|
|
7938
|
+
});
|
|
7939
|
+
});
|
|
7940
|
+
}
|
|
7941
|
+
__name(openInSystemBrowser, "openInSystemBrowser");
|
|
7942
|
+
function openerArgv(url) {
|
|
7943
|
+
switch (platform()) {
|
|
7944
|
+
case "darwin":
|
|
7945
|
+
return ["open", [url]];
|
|
7946
|
+
case "win32":
|
|
7947
|
+
return ["cmd.exe", ["/c", "start", "", url]];
|
|
7948
|
+
case "linux":
|
|
7949
|
+
case "freebsd":
|
|
7950
|
+
case "openbsd":
|
|
7951
|
+
return ["xdg-open", [url]];
|
|
7952
|
+
default:
|
|
7953
|
+
return [void 0, []];
|
|
7954
|
+
}
|
|
7955
|
+
}
|
|
7956
|
+
__name(openerArgv, "openerArgv");
|
|
7957
|
+
|
|
7958
|
+
// src/commands/wallet_topup.ts
|
|
7959
|
+
async function walletTopup(options) {
|
|
7960
|
+
const cfg = loadHubConfig(), api = new HubClient2(cfg), me = await api.me();
|
|
7961
|
+
const root = join9(hubTasksDir(), ".funding");
|
|
7962
|
+
mkdirSync15(root, { recursive: true, mode: 448 });
|
|
7963
|
+
let path2;
|
|
7964
|
+
if (options.topupStatus) {
|
|
7965
|
+
const name = readdirSync5(root).find((name2) => {
|
|
7966
|
+
try {
|
|
7967
|
+
return JSON.parse(readFileSync17(join9(root, name2), "utf8")).topup_id === options.topupStatus;
|
|
7968
|
+
} catch {
|
|
7969
|
+
return false;
|
|
7970
|
+
}
|
|
7971
|
+
});
|
|
7972
|
+
if (!name) throw Error("not_found: original top-up is not in this installation");
|
|
7973
|
+
path2 = join9(root, name);
|
|
7974
|
+
} else {
|
|
7975
|
+
if (!options.topup || !/^\d+(?:\.\d{1,2})?$/.test(options.topup)) throw Error("invalid_input: enter a positive CNY amount with at most two decimal places");
|
|
7976
|
+
path2 = join9(root, encodeURIComponent(options.topup) + ".json");
|
|
7977
|
+
}
|
|
7978
|
+
await withAsyncFileLock(path2 + ".lock", async () => {
|
|
7979
|
+
let saved = existsSync12(path2) ? JSON.parse(readFileSync17(path2, "utf8")) : void 0;
|
|
7980
|
+
if (saved && saved.buyer_id !== me.buyer_id) throw Error("authorization_required: original top-up account changed");
|
|
7981
|
+
if (!options.topupStatus && saved?.topup_id && ["credited", "failed", "closed_unpaid"].includes(saved.status ?? "")) {
|
|
7982
|
+
writeFileSync14(join9(root, "receipt-" + encodeURIComponent(saved.topup_id) + ".json"), JSON.stringify(saved), { mode: 384 });
|
|
7983
|
+
}
|
|
7984
|
+
if (!options.topupStatus && (!saved || saved.status === "credited" || saved.status === "failed" || saved.status === "closed_unpaid")) saved = { key: "cli-wallet-" + randomUUID14(), amount: options.topup, buyer_id: me.buyer_id, ...options.request ? { request_id: options.request } : {} };
|
|
7985
|
+
if (!saved) throw Error("not_found: original top-up is unavailable");
|
|
7986
|
+
if (options.request) saved.request_id = options.request;
|
|
7987
|
+
const task = saved.request_id ? new HubTaskStore().get(saved.request_id) : void 0;
|
|
7988
|
+
if (saved.request_id && !task?.intent_id) throw Error("invalid_state: keep the original request; no funding intent is available");
|
|
7989
|
+
const save2 = /* @__PURE__ */ __name(() => {
|
|
7990
|
+
const tmp = path2 + ".tmp";
|
|
7991
|
+
writeFileSync14(tmp, JSON.stringify(saved), { mode: 384 });
|
|
7992
|
+
renameSync10(tmp, path2);
|
|
7993
|
+
}, "save");
|
|
7994
|
+
save2();
|
|
7995
|
+
const data = options.topupStatus ? await api.topupStatus(options.topupStatus) : await api.createTopup({ amount_cny: saved.amount, payer_currency: "CNY", ...task?.intent_id ? { continuation_id: task.intent_id } : {} }, saved.key);
|
|
7996
|
+
saved.topup_id = data.topup_id;
|
|
7997
|
+
saved.status = data.status;
|
|
7998
|
+
save2();
|
|
7999
|
+
const browser = data.page_url && !options.topupStatus && !options.noOpen ? await openInSystemBrowser(data.page_url, cfg.baseURL) : { status: "unavailable", reason: "host link presentation" };
|
|
8000
|
+
const next = data.status === "credited" ? saved.request_id ? { command: `itpay api call --pending ${saved.request_id}${options.jsonOutput ? " --json" : ""}`, reason: "\u5230\u8D26\u540E\u7EE7\u7EED\u81EA\u5DF1\u7684\u539F\u8BF7\u6C42" } : null : { command: `itpay api wallet --topup-status ${data.topup_id}${options.jsonOutput ? " --json" : ""}`, reason: "\u53EA\u8BFB\u6838\u5BF9\u539F\u5145\u503C\uFF1B\u672C\u4EBA\u4ED8\u6B3E\u540E\u7EE7\u7EED" };
|
|
8001
|
+
writeCommandEnvelope({ status: data.status, result: { ...data, browser, ...saved.request_id ? { request_id: saved.request_id } : {} }, instruction: data.status === "credited" ? `\u8D26\u53F7 ${data.account_hint ?? ""} \u5DF2\u5230\u8D26 \xA5${data.amount ?? saved.amount}\u3002\u4E0D\u8981\u91CD\u590D\u4ED8\u6B3E\uFF1B\u5982\u6709\u539F\u8BF7\u6C42\uFF0C\u7EE7\u7EED\u8FD4\u56DE\u7684\u539F call --pending\u3002\u5145\u503C\u4E0D\u589E\u52A0\u6D88\u8D39\u6388\u6743\u3002` : `${data.notice ? data.notice + "\u3002" : ""}\u4E3A\u8D26\u53F7 ${data.account_hint ?? ""} \u5145\u503C \xA5${data.amount ?? saved.amount}\u3002\u53EF\u626B\u7801\u6216\u6253\u5F00\u540C\u5355\u6536\u94F6\u53F0\uFF1B\u6309\u5BBF\u4E3B\u80FD\u529B\u5C55\u793A\u5B98\u65B9\u56FE\u7247\u3002${data.expires_at ? "\u6709\u6548\u81F3 " + data.expires_at + "\u3002" : ""}\u5C55\u793A\u80FD\u529B\u4E0D\u8DB3\u65F6\u7167\u5E38\u4F7F\u7528\u540C\u5355\u94FE\u63A5\u529E\u7406\u3002\u5145\u503C\u4E0D\u589E\u52A0\u6D88\u8D39\u6388\u6743\u3002`, next, recovery: [] }, { jsonOutput: options.jsonOutput });
|
|
8002
|
+
});
|
|
8003
|
+
}
|
|
8004
|
+
__name(walletTopup, "walletTopup");
|
|
8005
|
+
|
|
7896
8006
|
// src/state/hub_show_cache.ts
|
|
7897
8007
|
import { createHash as createHash13 } from "node:crypto";
|
|
7898
|
-
import { mkdirSync as
|
|
7899
|
-
import { dirname as dirname11, join as
|
|
8008
|
+
import { mkdirSync as mkdirSync16, readFileSync as readFileSync18, renameSync as renameSync11, unlinkSync as unlinkSync9, writeFileSync as writeFileSync15 } from "node:fs";
|
|
8009
|
+
import { dirname as dirname11, join as join10 } from "node:path";
|
|
7900
8010
|
function cachePath(operation) {
|
|
7901
8011
|
const origin2 = loadHubConfig().baseURL;
|
|
7902
8012
|
const key = createHash13("sha256").update(origin2 + "\0" + operation).digest("hex");
|
|
7903
|
-
return
|
|
8013
|
+
return join10(dirname11(hubCredentialsPath()), "shown", key + ".json");
|
|
7904
8014
|
}
|
|
7905
8015
|
__name(cachePath, "cachePath");
|
|
7906
8016
|
function saveShown(value) {
|
|
7907
8017
|
const path2 = cachePath(value.operation_id);
|
|
7908
|
-
|
|
8018
|
+
mkdirSync16(dirname11(path2), { recursive: true, mode: 448 });
|
|
7909
8019
|
withFileLock(path2 + ".lock", () => {
|
|
7910
8020
|
const temporary = path2 + "." + process.pid + ".tmp";
|
|
7911
8021
|
const publication = createHash13("sha256").update(value.current_publication_id).digest("hex");
|
|
7912
8022
|
const versioned = path2 + "." + publication;
|
|
7913
8023
|
try {
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
8024
|
+
writeFileSync15(temporary, JSON.stringify(value), { mode: 384 });
|
|
8025
|
+
renameSync11(temporary, versioned);
|
|
8026
|
+
writeFileSync15(temporary, JSON.stringify({ publication: versioned }), { mode: 384 });
|
|
8027
|
+
renameSync11(temporary, path2);
|
|
7918
8028
|
} finally {
|
|
7919
8029
|
try {
|
|
7920
8030
|
unlinkSync9(temporary);
|
|
@@ -7927,9 +8037,9 @@ __name(saveShown, "saveShown");
|
|
|
7927
8037
|
function loadShown(operation) {
|
|
7928
8038
|
try {
|
|
7929
8039
|
const path2 = cachePath(operation);
|
|
7930
|
-
const pointer = JSON.parse(
|
|
8040
|
+
const pointer = JSON.parse(readFileSync18(path2, "utf8"));
|
|
7931
8041
|
if (!pointer.publication.startsWith(path2 + ".") || !/^[a-f0-9]{64}$/.test(pointer.publication.slice(path2.length + 1))) throw new Error("invalid display pointer");
|
|
7932
|
-
const value = JSON.parse(
|
|
8042
|
+
const value = JSON.parse(readFileSync18(pointer.publication, "utf8"));
|
|
7933
8043
|
if (value.operation_id === operation && value.current_publication_id && value.input_schema && value.price_card.mode !== "unconfigured") return value;
|
|
7934
8044
|
} catch {
|
|
7935
8045
|
}
|
|
@@ -7984,6 +8094,9 @@ function render(task, snapshot, json) {
|
|
|
7984
8094
|
const run = snapshot.run;
|
|
7985
8095
|
if (run) {
|
|
7986
8096
|
const zero2 = run.captured_atoms === "0" && run.held_atoms === "0";
|
|
8097
|
+
const balanceNotice = run.low_balance;
|
|
8098
|
+
const firstNotice = Boolean(balanceNotice && !task.low_balance_notified);
|
|
8099
|
+
if (firstNotice) new HubTaskStore().update(task.request_id, { low_balance_notified: true });
|
|
7987
8100
|
writeCommandEnvelope({
|
|
7988
8101
|
status: run.execution_state === "succeeded" ? "succeeded" : snapshot.terminal ? "failed" : "pending",
|
|
7989
8102
|
result: {
|
|
@@ -7994,6 +8107,7 @@ function render(task, snapshot, json) {
|
|
|
7994
8107
|
delivery_state: run.delivery_state,
|
|
7995
8108
|
captured_atoms: run.captured_atoms ?? null,
|
|
7996
8109
|
held_atoms: run.held_atoms ?? null,
|
|
8110
|
+
...firstNotice ? { low_balance: balanceNotice } : {},
|
|
7997
8111
|
result: run.result ?? null,
|
|
7998
8112
|
result_expires_at: run.result_expires_at ?? null,
|
|
7999
8113
|
error: run.error ?? null,
|
|
@@ -8006,6 +8120,7 @@ function render(task, snapshot, json) {
|
|
|
8006
8120
|
return;
|
|
8007
8121
|
}
|
|
8008
8122
|
const reason = snapshot.intent?.blocking_reason ?? "unknown";
|
|
8123
|
+
const uncharged = ["insufficient_balance", "authorization_denied", "authorization_revoked", "budget_exceeded"].includes(reason);
|
|
8009
8124
|
const target2 = snapshot.required_action?.web_action?.target ?? snapshot.required_action?.target;
|
|
8010
8125
|
const changed = snapshot.terminal && reason === "publication_changed";
|
|
8011
8126
|
const price = safePriceChange(snapshot.price_change);
|
|
@@ -8013,9 +8128,9 @@ function render(task, snapshot, json) {
|
|
|
8013
8128
|
const label = price?.current_price ? "CNY " + priceLabel({ mode: "per_call", amount_atoms: price.current_price.amount_atoms }) : "\u6682\u4E0D\u53EF\u8BFB\u53D6";
|
|
8014
8129
|
writeCommandEnvelope({
|
|
8015
8130
|
status: changed ? "price_changed" : reason,
|
|
8016
|
-
result: { request_id: task.request_id, intent_id: task.intent_id, captured_atoms: zero ? "0" : null, held_atoms: zero ? "0" : null, ...price ? { price_change: price } : {}, ...price?.current_price ? { current_price: price.current_price } : {} },
|
|
8131
|
+
result: { request_id: task.request_id, intent_id: task.intent_id, account: snapshot.account_hint, captured_atoms: zero || uncharged ? "0" : null, held_atoms: zero || uncharged ? "0" : null, ...price ? { price_change: price } : {}, ...price?.current_price ? { current_price: price.current_price } : {} },
|
|
8017
8132
|
...target2 && !changed ? { handoff: { type: "open_url", url: target2, label: "\u6253\u5F00\u5B98\u65B9\u9875\u9762" } } : {},
|
|
8018
|
-
instruction: changed ? `\u5F53\u524D\u4EF7\u683C ${label}\u3002${zero ? "\u539F\u8BF7\u6C42\u5DF2\u7ED3\u675F\uFF0C\u6263\u8D390\u3001\u51BB\u7ED30\uFF0C\u672C\u6B21\u6CA1\u6263\u94B1\u3002\u5148 show \u6838\u5BF9\u5F53\u524D\u5546\u54C1\uFF0C\u8BF4\u660E\u65B0\u4EF7\u5E76\u53D6\u5F97\u660E\u786E\u540C\u610F\u540E\u518D\u7528\u65B0 call\u3002" : "\u539F\u8BF7\u6C42\u8D39\u7528\u4ECD\u987B\u6838\u5BF9\u3002\u5148 status \u539F\u8BF7\u6C42\uFF0C\u672A\u786E\u8BA4\u96F6\u6263\u8D39\u548C\u96F6\u51BB\u7ED3\u524D\u4E0D\u8981\u65B0 call\u3002"}` : reason === "insufficient_balance" ? "\u8BF7\u5728\u539F\u5355\u5B98\u65B9\u9875\u9762\u5145\u503C\uFF0C\u5230\u8D26\u540E\u7EE7\u7EED\u539F\u8BF7\u6C42\u3002" : "\u4FDD\u7559\u539F\u8BF7\u6C42\uFF0C\u6309\u5B98\u65B9\u64CD\u4F5C\u5B8C\u6210\u6388\u6743\u6216\u6838\u5BF9\u3002",
|
|
8133
|
+
instruction: changed ? `\u5F53\u524D\u4EF7\u683C ${label}\u3002${zero ? "\u539F\u8BF7\u6C42\u5DF2\u7ED3\u675F\uFF0C\u6263\u8D390\u3001\u51BB\u7ED30\uFF0C\u672C\u6B21\u6CA1\u6263\u94B1\u3002\u5148 show \u6838\u5BF9\u5F53\u524D\u5546\u54C1\uFF0C\u8BF4\u660E\u65B0\u4EF7\u5E76\u53D6\u5F97\u660E\u786E\u540C\u610F\u540E\u518D\u7528\u65B0 call\u3002" : "\u539F\u8BF7\u6C42\u8D39\u7528\u4ECD\u987B\u6838\u5BF9\u3002\u5148 status \u539F\u8BF7\u6C42\uFF0C\u672A\u786E\u8BA4\u96F6\u6263\u8D39\u548C\u96F6\u51BB\u7ED3\u524D\u4E0D\u8981\u65B0 call\u3002"}` : reason === "insufficient_balance" ? "\u8BF7\u5728\u539F\u5355\u5B98\u65B9\u9875\u9762\u5145\u503C\uFF0C\u5230\u8D26\u540E\u7EE7\u7EED\u539F\u8BF7\u6C42\u3002\u4E5F\u53EF\u4EE5\u544A\u8BC9\u6211\u91D1\u989D\uFF0C\u76F4\u63A5\u7ED9\u4F60\u626B\u7801\u4ED8\u6B3E\u3002" : "\u4FDD\u7559\u539F\u8BF7\u6C42\uFF0C\u6309\u5B98\u65B9\u64CD\u4F5C\u5B8C\u6210\u6388\u6743\u6216\u6838\u5BF9\u3002",
|
|
8019
8134
|
next: changed && zero ? { command: `itpay api show ${task.operation_id}${json ? " --json" : ""}`, reason: "\u67E5\u770B\u5F53\u524D\u7528\u9014\u4E0E\u4EF7\u683C" } : changed ? statusAction(task.request_id, json) : snapshot.terminal ? null : pendingAction(task.request_id, json),
|
|
8020
8135
|
recovery: []
|
|
8021
8136
|
}, { jsonOutput: json });
|
|
@@ -8168,7 +8283,7 @@ async function shelfCall(operation, options) {
|
|
|
8168
8283
|
__name(shelfCall, "shelfCall");
|
|
8169
8284
|
|
|
8170
8285
|
// src/commands/admin.ts
|
|
8171
|
-
import { readFileSync as
|
|
8286
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
8172
8287
|
var actions = /* @__PURE__ */ new Set(["operations_list", "ingestions_list", "import_github", "ingestion_status", "ingestion_retry", "credentials_list", "credential_put", "source_requirements", "source_bind", "acceptance_status", "acceptance_set"]);
|
|
8173
8288
|
function parseAdminInput(action, raw) {
|
|
8174
8289
|
if (!actions.has(action)) throw Error("unknown admin action");
|
|
@@ -8181,7 +8296,7 @@ __name(parseAdminInput, "parseAdminInput");
|
|
|
8181
8296
|
async function runAdminCommand(action, source) {
|
|
8182
8297
|
if (source !== "-" && !source.startsWith("@")) throw Error("Use stdin or a private file, never secret command arguments");
|
|
8183
8298
|
if (action === "credential_put" && source !== "-") throw Error("credential_put accepts stdin only");
|
|
8184
|
-
const raw =
|
|
8299
|
+
const raw = readFileSync19(source === "-" ? 0 : source.slice(1), "utf8");
|
|
8185
8300
|
const args = parseAdminInput(action, raw);
|
|
8186
8301
|
const cfg = loadHubConfig();
|
|
8187
8302
|
if (!cfg.apiKey || cfg.bearerToken) throw Error("Explicit scoped ITPAY_HUB_KEY required");
|
|
@@ -8322,8 +8437,12 @@ function registerAPICommands(program3) {
|
|
|
8322
8437
|
reportCLIError(error, { jsonOutput: Boolean(options.json), code: "hub_doctor_failed", instruction: "\u6309\u8F93\u51FA\u9010\u6761\u4FEE\u590D\u672C\u5730\u6808\u3002", recovery: [] });
|
|
8323
8438
|
}
|
|
8324
8439
|
});
|
|
8325
|
-
hubCmd.command("wallet").description("Read wallet,
|
|
8440
|
+
hubCmd.command("wallet").description("Read wallet or prepare an explicit own-wallet top-up").option("--topup <amount>", "prepare a CNY wallet top-up; minimum 10 CNY").option("--topup-status <topup_id>", "read the original top-up").option("--request <request_id>", "retain your original pending API request").option("--no-open", "this host does not open a browser; retain the same payment links").option("--usage", "read current Key/Consumer usage; not account-wide").option("--transactions", "read scoped transactions; owner history uses the official page").option("--json", "output JSON").action(async (options) => {
|
|
8326
8441
|
try {
|
|
8442
|
+
if (options.topup || options.topupStatus) {
|
|
8443
|
+
await walletTopup({ topup: options.topup, topupStatus: options.topupStatus, request: options.request, noOpen: options.open === false, jsonOutput: Boolean(options.json) });
|
|
8444
|
+
return;
|
|
8445
|
+
}
|
|
8327
8446
|
await runHubWallet({ jsonOutput: Boolean(options.json), usage: Boolean(options.usage), transactions: Boolean(options.transactions) });
|
|
8328
8447
|
} catch (error) {
|
|
8329
8448
|
reportCLIError(error, { jsonOutput: Boolean(options.json), code: "hub_wallet_failed", instruction: "\u786E\u8BA4\u539F\u5B89\u88C5\u7684\u8FDE\u63A5\u8EAB\u4EFD\u3002", recovery: [] });
|
|
@@ -15331,65 +15450,8 @@ function projectCheckoutPresentation(presentation, model) {
|
|
|
15331
15450
|
}
|
|
15332
15451
|
__name(projectCheckoutPresentation, "projectCheckoutPresentation");
|
|
15333
15452
|
|
|
15334
|
-
// src/render/browser.ts
|
|
15335
|
-
import { spawn } from "node:child_process";
|
|
15336
|
-
import { platform } from "node:os";
|
|
15337
|
-
function isPresentableURL(raw, baseURL, env = process.env) {
|
|
15338
|
-
let url;
|
|
15339
|
-
try {
|
|
15340
|
-
url = new URL(raw);
|
|
15341
|
-
} catch {
|
|
15342
|
-
return false;
|
|
15343
|
-
}
|
|
15344
|
-
if (url.username || url.password) return false;
|
|
15345
|
-
if (env.ITPAY_CLI_DEV === "1" && /^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(url.origin)) return true;
|
|
15346
|
-
if (url.protocol !== "https:") return false;
|
|
15347
|
-
const allowedOrigins = new Set(OFFICIAL_BACKEND_URLS);
|
|
15348
|
-
if (baseURL) {
|
|
15349
|
-
try {
|
|
15350
|
-
allowedOrigins.add(new URL(baseURL).origin);
|
|
15351
|
-
} catch {
|
|
15352
|
-
}
|
|
15353
|
-
}
|
|
15354
|
-
return allowedOrigins.has(url.origin);
|
|
15355
|
-
}
|
|
15356
|
-
__name(isPresentableURL, "isPresentableURL");
|
|
15357
|
-
function openInSystemBrowser(raw, baseURL, env = process.env) {
|
|
15358
|
-
if (!isPresentableURL(raw, baseURL, env)) {
|
|
15359
|
-
return Promise.resolve({ status: "failed", reason: "url_rejected" });
|
|
15360
|
-
}
|
|
15361
|
-
const [command, args] = openerArgv(raw);
|
|
15362
|
-
if (!command) {
|
|
15363
|
-
return Promise.resolve({ status: "unavailable", reason: "no system opener on this platform" });
|
|
15364
|
-
}
|
|
15365
|
-
return new Promise((resolvePromise) => {
|
|
15366
|
-
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
15367
|
-
child.once("error", (error) => resolvePromise({ status: "failed", reason: error.message }));
|
|
15368
|
-
child.once("spawn", () => {
|
|
15369
|
-
child.unref();
|
|
15370
|
-
resolvePromise({ status: "dispatch_accepted", argv: [command, ...args] });
|
|
15371
|
-
});
|
|
15372
|
-
});
|
|
15373
|
-
}
|
|
15374
|
-
__name(openInSystemBrowser, "openInSystemBrowser");
|
|
15375
|
-
function openerArgv(url) {
|
|
15376
|
-
switch (platform()) {
|
|
15377
|
-
case "darwin":
|
|
15378
|
-
return ["open", [url]];
|
|
15379
|
-
case "win32":
|
|
15380
|
-
return ["cmd.exe", ["/c", "start", "", url]];
|
|
15381
|
-
case "linux":
|
|
15382
|
-
case "freebsd":
|
|
15383
|
-
case "openbsd":
|
|
15384
|
-
return ["xdg-open", [url]];
|
|
15385
|
-
default:
|
|
15386
|
-
return [void 0, []];
|
|
15387
|
-
}
|
|
15388
|
-
}
|
|
15389
|
-
__name(openerArgv, "openerArgv");
|
|
15390
|
-
|
|
15391
15453
|
// src/render/terminal_image.ts
|
|
15392
|
-
import { readFileSync as
|
|
15454
|
+
import { readFileSync as readFileSync20 } from "node:fs";
|
|
15393
15455
|
import path from "node:path";
|
|
15394
15456
|
function supportsInlineTerminalImages() {
|
|
15395
15457
|
return Boolean(process.env.ITERM_SESSION_ID) || process.env.TERM_PROGRAM === "iTerm.app";
|
|
@@ -15398,7 +15460,7 @@ __name(supportsInlineTerminalImages, "supportsInlineTerminalImages");
|
|
|
15398
15460
|
function renderInlineTerminalImage(filePath, out) {
|
|
15399
15461
|
if (!supportsInlineTerminalImages()) return;
|
|
15400
15462
|
try {
|
|
15401
|
-
const buf =
|
|
15463
|
+
const buf = readFileSync20(filePath);
|
|
15402
15464
|
const name = path.basename(filePath);
|
|
15403
15465
|
const nameB64 = Buffer.from(name).toString("base64");
|
|
15404
15466
|
const dataB64 = buf.toString("base64");
|
|
@@ -17285,8 +17347,8 @@ var {
|
|
|
17285
17347
|
} = import_index.default;
|
|
17286
17348
|
|
|
17287
17349
|
// src/state/cart_session.ts
|
|
17288
|
-
import { chmodSync as chmodSync7, existsSync as
|
|
17289
|
-
import { randomUUID as
|
|
17350
|
+
import { chmodSync as chmodSync7, existsSync as existsSync13, mkdirSync as mkdirSync17, readFileSync as readFileSync21, renameSync as renameSync12, writeFileSync as writeFileSync16 } from "node:fs";
|
|
17351
|
+
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
17290
17352
|
import { dirname as dirname12 } from "node:path";
|
|
17291
17353
|
var CartSession = class _CartSession {
|
|
17292
17354
|
static {
|
|
@@ -17299,9 +17361,9 @@ var CartSession = class _CartSession {
|
|
|
17299
17361
|
}
|
|
17300
17362
|
static loadFromFile(path2, currency) {
|
|
17301
17363
|
const session = new _CartSession(currency);
|
|
17302
|
-
if (
|
|
17364
|
+
if (existsSync13(path2)) {
|
|
17303
17365
|
try {
|
|
17304
|
-
const raw =
|
|
17366
|
+
const raw = readFileSync21(path2, "utf-8");
|
|
17305
17367
|
const persisted = JSON.parse(raw);
|
|
17306
17368
|
if (Array.isArray(persisted.items)) {
|
|
17307
17369
|
persisted.items.forEach((item) => {
|
|
@@ -17335,12 +17397,12 @@ var CartSession = class _CartSession {
|
|
|
17335
17397
|
...this.state.lastCheckoutURL ? { lastCheckoutURL: this.state.lastCheckoutURL } : {}
|
|
17336
17398
|
};
|
|
17337
17399
|
const contents = JSON.stringify(toSave, null, 2);
|
|
17338
|
-
if (
|
|
17339
|
-
|
|
17340
|
-
const temporary = `${path2}.${process.pid}.${
|
|
17341
|
-
|
|
17400
|
+
if (existsSync13(path2) && readFileSync21(path2, "utf-8") === contents) return;
|
|
17401
|
+
mkdirSync17(dirname12(path2), { recursive: true, mode: 448 });
|
|
17402
|
+
const temporary = `${path2}.${process.pid}.${randomUUID15()}.tmp`;
|
|
17403
|
+
writeFileSync16(temporary, contents, { encoding: "utf-8", mode: 384 });
|
|
17342
17404
|
chmodSync7(temporary, 384);
|
|
17343
|
-
|
|
17405
|
+
renameSync12(temporary, path2);
|
|
17344
17406
|
chmodSync7(path2, 384);
|
|
17345
17407
|
}
|
|
17346
17408
|
add(item) {
|
|
@@ -17467,14 +17529,14 @@ import { createHash as createHash14 } from "node:crypto";
|
|
|
17467
17529
|
|
|
17468
17530
|
// src/state/skill_identity.generated.ts
|
|
17469
17531
|
var CANONICAL_SKILL_SHA256 = "8dd5c3a00d3ff35ce8490e3703d012dd4c936c1827b2a5541f630783e2877a67";
|
|
17470
|
-
var CANONICAL_API_SKILL_SHA256 = "
|
|
17532
|
+
var CANONICAL_API_SKILL_SHA256 = "c37e2efb288bf4545c828671afacb3f783f3e7020dbb4cccd0b7d273e636e84e";
|
|
17471
17533
|
|
|
17472
17534
|
// src/commands/skill.ts
|
|
17473
|
-
import { existsSync as
|
|
17535
|
+
import { existsSync as existsSync15, readFileSync as readFileSync23 } from "node:fs";
|
|
17474
17536
|
import { resolve as resolve10 } from "node:path";
|
|
17475
17537
|
|
|
17476
17538
|
// src/state/package_resources.ts
|
|
17477
|
-
import { existsSync as
|
|
17539
|
+
import { existsSync as existsSync14, readFileSync as readFileSync22 } from "node:fs";
|
|
17478
17540
|
import { basename as basename2, dirname as dirname13, resolve as resolve9 } from "node:path";
|
|
17479
17541
|
import { fileURLToPath } from "node:url";
|
|
17480
17542
|
function packageResourcePath(...segments) {
|
|
@@ -17482,7 +17544,7 @@ function packageResourcePath(...segments) {
|
|
|
17482
17544
|
const root = resolve9(dirname13(modulePath), "..", "..");
|
|
17483
17545
|
if (!basename2(modulePath).startsWith("package_resources.") && basename2(modulePath) !== "main.js") throw new Error("unsupported CLI resource layout");
|
|
17484
17546
|
const metadata = resolve9(root, "package.json");
|
|
17485
|
-
if (!
|
|
17547
|
+
if (!existsSync14(metadata) || JSON.parse(readFileSync22(metadata, "utf8")).name !== "@itpay/cli") throw new Error("current CLI package resources are unavailable");
|
|
17486
17548
|
return resolve9(root, ...segments);
|
|
17487
17549
|
}
|
|
17488
17550
|
__name(packageResourcePath, "packageResourcePath");
|
|
@@ -17506,7 +17568,7 @@ function runSkillShow(name, options = {}) {
|
|
|
17506
17568
|
const original = packagedSkill();
|
|
17507
17569
|
let content;
|
|
17508
17570
|
try {
|
|
17509
|
-
content =
|
|
17571
|
+
content = readFileSync23(findSkillPath(), "utf8");
|
|
17510
17572
|
} catch (error) {
|
|
17511
17573
|
if (process.env.ITPAY_CLI_SKILLS_DIR) {
|
|
17512
17574
|
const cause = error && typeof error === "object" && "code" in error ? String(error.code) : "read_failed";
|
|
@@ -17554,8 +17616,8 @@ ${CODEX_WAIT_GUIDANCE}
|
|
|
17554
17616
|
__name(runSkillShow, "runSkillShow");
|
|
17555
17617
|
function packagedSkill() {
|
|
17556
17618
|
let path2 = findSkillPath(false);
|
|
17557
|
-
if (!
|
|
17558
|
-
const content =
|
|
17619
|
+
if (!existsSync15(path2) && process.env.ITPAY_CLI_SKILLS_DIR) path2 = findSkillPath();
|
|
17620
|
+
const content = readFileSync23(path2, "utf8");
|
|
17559
17621
|
if (createHash14("sha256").update(content).digest("hex") !== CANONICAL_SKILL_SHA256) {
|
|
17560
17622
|
throw new CommandContractError("skill_override_mismatch", "Skill does not match this pinned CLI build", "\u4F7F\u7528\u5F53\u524D\u5305\u540C\u6E90\u7684\u5B8C\u6574 Skill\uFF1B\u4FDD\u7559\u539F\u4EFB\u52A1\u548C\u8EAB\u4EFD\uFF0C\u4E0D\u5B89\u88C5\u53E6\u4E00\u5957 CLI\u3002", []);
|
|
17561
17623
|
}
|
|
@@ -17567,7 +17629,7 @@ function findSkillPath(allowOverride = true) {
|
|
|
17567
17629
|
if (allowOverride && process.env.ITPAY_CLI_SKILLS_DIR) {
|
|
17568
17630
|
const configuredRoot = resolve10(process.env.ITPAY_CLI_SKILLS_DIR);
|
|
17569
17631
|
const directPath = resolve10(configuredRoot, "SKILL.md");
|
|
17570
|
-
if (
|
|
17632
|
+
if (existsSync15(directPath)) return directPath;
|
|
17571
17633
|
return resolve10(configuredRoot, ITPAY_SKILL, "SKILL.md");
|
|
17572
17634
|
}
|
|
17573
17635
|
return packageResourcePath("skills", ITPAY_SKILL, "SKILL.md");
|
|
@@ -17583,7 +17645,7 @@ __name(validateSkill, "validateSkill");
|
|
|
17583
17645
|
function showAPISkill(options) {
|
|
17584
17646
|
let content;
|
|
17585
17647
|
try {
|
|
17586
|
-
content =
|
|
17648
|
+
content = readFileSync23(packageResourcePath("skills", "itpay-api", "SKILL.md"), "utf8");
|
|
17587
17649
|
} catch {
|
|
17588
17650
|
throw new CommandContractError(
|
|
17589
17651
|
"skill_unavailable",
|
|
@@ -19695,7 +19757,7 @@ function writeCartNextEnvelope(envelope, options) {
|
|
|
19695
19757
|
__name(writeCartNextEnvelope, "writeCartNextEnvelope");
|
|
19696
19758
|
|
|
19697
19759
|
// src/commands/docs.ts
|
|
19698
|
-
import { readFileSync as
|
|
19760
|
+
import { readFileSync as readFileSync24, readdirSync as readdirSync6 } from "node:fs";
|
|
19699
19761
|
import { resolve as resolve11 } from "node:path";
|
|
19700
19762
|
function runDocsList(options = {}) {
|
|
19701
19763
|
const topics = loadDocs().map(({ topic, title, purpose }) => ({ topic, title, purpose }));
|
|
@@ -19770,12 +19832,12 @@ function runDocsSearch(query, options = {}) {
|
|
|
19770
19832
|
__name(runDocsSearch, "runDocsSearch");
|
|
19771
19833
|
function loadDocs() {
|
|
19772
19834
|
const docsDir = findDocsDir();
|
|
19773
|
-
const files =
|
|
19774
|
-
const docs = files.map((file) => parseDoc(
|
|
19835
|
+
const files = readdirSync6(docsDir).filter((file) => file.endsWith(".json")).sort();
|
|
19836
|
+
const docs = files.map((file) => parseDoc(readFileSync24(resolve11(docsDir, file), "utf8"), file));
|
|
19775
19837
|
if (process.env.ITPAY_CLI_DOCS_DIR) {
|
|
19776
19838
|
const canonical = packageResourcePath("docs", "agent", "buyer");
|
|
19777
|
-
const expectedFiles =
|
|
19778
|
-
if (JSON.stringify(files) !== JSON.stringify(expectedFiles) || files.some((file, index) => JSON.stringify(docs[index]) !== JSON.stringify(parseDoc(
|
|
19839
|
+
const expectedFiles = readdirSync6(canonical).filter((file) => file.endsWith(".json")).sort();
|
|
19840
|
+
if (JSON.stringify(files) !== JSON.stringify(expectedFiles) || files.some((file, index) => JSON.stringify(docs[index]) !== JSON.stringify(parseDoc(readFileSync24(resolve11(canonical, file), "utf8"), file)))) {
|
|
19779
19841
|
throw new CommandContractError(
|
|
19780
19842
|
"docs_override_mismatch",
|
|
19781
19843
|
"configured Buyer docs differ from the current package",
|
package/dist/src/state/config.js
CHANGED
|
@@ -27,3 +27,8 @@ A `price_changed` receipt includes `current_price` (CNY, scale 6, atoms) and ver
|
|
|
27
27
|
Use a secret manager to inject ITPAY_HUB_KEY or pipe a private secret source to `itpay api credentials import --json`. Never pass the secret in argv or chat. With --profile, import into that same installation; an explicit environment Key remains selected. It does not need OAuth. Saved OAuth and Key remain private; the last explicit import/connect selects the credential, while a Key environment override selects that Key. Supplying both environment bearer and Key is an error. Requests remain isolated by original credential; neither a new Key nor OAuth can take over old requests.
|
|
28
28
|
|
|
29
29
|
Check `wallet` identity.account_hint and credential_kind; matching hints identify the same wallet, not the same consumer. Invalid/revoked Key or insufficient Key scope points to the official /me/keys page, without OAuth fallback. Hub MCP accepts Key only through its existing X-ItPay-Key protocol header; normal ChatGPT MCP uses OAuth. Do not paste a Key into chat or put it in a Bearer field. Services identity is independent.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### 本人钱包定额充值
|
|
33
|
+
|
|
34
|
+
`itpay api wallet --topup 30 --json` 准备本人钱包的人民币充值,按分报价,最低 ¥10。若为已有挂起请求充值,加 `--request <原 request_id>`;付款后按返回 next 读取原充值并继续原 call --pending。CLI 在宿主可打开浏览器时主动打开官方 page_url,同时返回 checkout_url 和 image。`--no-open` 用于无浏览器打开能力的宿主。`wallet --topup-status <topup_id>` 只读原充值。OAuth/API Hub Key 准备充值不要求消费 grant;充值不会增加调用额度、商户收款或退款权限。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itpay/cli",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.67",
|
|
4
4
|
"description": "The ItPay CLI for services, orders, and human-authorized purchased content.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
"node": ">=18"
|
|
111
111
|
},
|
|
112
112
|
"devDependencies": {
|
|
113
|
-
"@itpay/hub-client": "file:dependencies/itpay-hub-client-0.1.0-integration.
|
|
113
|
+
"@itpay/hub-client": "file:dependencies/itpay-hub-client-0.1.0-integration.27c2f6955647.tgz",
|
|
114
114
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
115
115
|
"@types/node": "^22.7.5",
|
|
116
116
|
"@types/qrcode": "^1.5.5",
|
|
@@ -148,5 +148,5 @@
|
|
|
148
148
|
"services",
|
|
149
149
|
"api"
|
|
150
150
|
],
|
|
151
|
-
"gitHead": "
|
|
151
|
+
"gitHead": "d3e88d44ef74e7f8d762f04fb5105731c77b69cb"
|
|
152
152
|
}
|
|
@@ -48,3 +48,14 @@ New task-driven installations use one official Agent connection page. Each busin
|
|
|
48
48
|
A user may securely inject ITPAY_HUB_KEY or import the selected Key with api credentials import through private stdin. Never ask for it in chat, put it in argv/logs, or echo it. A Key authorizes API Hub only within its catalog, effects and spending grant. It does not authorize Services or payment writes. An explicit environment Key wins over a saved OAuth connection; importing a Key selects it while preserving OAuth privately. An explicit normal connect selects OAuth. Never switch credentials to resume an existing request: its original task namespace remains isolated.
|
|
49
49
|
|
|
50
50
|
For an API task with a selected Key, agent prepares only that API installation and reads its identity without OAuth. Read wallet identity.credential_kind and account_hint before work; all hosts in a shared-wallet test must show the same hint. If mismatched, stop that new task and have the owner select the intended Key or normally reconnect the intended account; keep all existing requests and do not revoke another host. Invalid/revoked Key or insufficient scope directs the owner to /me/keys and secure stdin import, never automatic OAuth fallback. A top-up URL is navigation, not a created top-up; /hub/mcp is a protocol connection URL, not a payment page.
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## Amount-based own-wallet top-up
|
|
54
|
+
|
|
55
|
+
When the user only asks to top up, return wallet.topup_url and say: “也可以告诉我金额,直接给你扫码付款。” For insufficient_balance, keep the original request and its continuation link with the same hint. Never turn these two cases into an unsolicited payment order.
|
|
56
|
+
|
|
57
|
+
When an amount is explicit, run `itpay api wallet --topup 30 --json` (use the user's amount). If continuing a saved insufficient-balance task, add `--request <original request_id>`. OAuth and valid api_hub Keys can prepare their own wallet top-up without a spending grant. The server quotes positive CNY amounts in cents, minimum ¥10; for a smaller amount say “最低 ¥10,本单按 ¥10”. The reply and official card must identify the masked credited account, actual amount and expiry.
|
|
58
|
+
|
|
59
|
+
When browser capability is available, immediately open the returned official page_url. Provide the same checkout_url and, where supported, the returned official image. Scan and click are user choices. Limited display capability does not stop payment; a link-only host can use the same checkout. Do not draw or reconstruct QR codes. `--no-open` expresses a host without browser opening. Poll the returned original top-up status; when credited follow the returned original call --pending, never create another business request. A shared funding card does not transfer another connection's task.
|
|
60
|
+
|
|
61
|
+
Report low_balance once per notification_id with available_atoms, compared_price_atoms and the official wallet link. Key authorization/budget failures use the exact server-issued Key settings link, never a top-up to bypass a grant. Funding never enables API spending or merchant payment/refund permission.
|