@itpay/cli 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/bin/itp +182 -25
- package/docs/agent/buyer/quickstart.json +7 -0
- package/package.json +1 -1
- package/skills/itpay-buyer/SKILL.md +5 -0
package/README.md
CHANGED
|
@@ -155,7 +155,7 @@ from the current state.
|
|
|
155
155
|
Before starting a new purchase, agents should inspect recoverable local state:
|
|
156
156
|
|
|
157
157
|
```bash
|
|
158
|
-
itp status --json
|
|
158
|
+
itp status --refresh --json
|
|
159
159
|
```
|
|
160
160
|
|
|
161
161
|
If an unfinished run exists, continue it:
|
|
@@ -241,6 +241,15 @@ itp buyer vault read --order <order_id> --artifact <vault_artifact_id> --json
|
|
|
241
241
|
Agents must not ask humans to paste claim links, claim tokens, raw API results,
|
|
242
242
|
provider keys, or grant ids into chat.
|
|
243
243
|
|
|
244
|
+
Order/account/refund commands require a server-verified buyer session, not a
|
|
245
|
+
vault grant. If they fail with a buyer session error, run:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
itp status --refresh --json
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Then follow the returned `next.command`.
|
|
252
|
+
|
|
244
253
|
## Agent Skill And Docs
|
|
245
254
|
|
|
246
255
|
Installed agents can read the buyer skill and docs graph at any time:
|
package/bin/itp
CHANGED
|
@@ -222,6 +222,7 @@ async function main() {
|
|
|
222
222
|
"buyer vault read --order <order_id> --artifact <vault_artifact_id> --json",
|
|
223
223
|
"account login-link --json",
|
|
224
224
|
"status --json",
|
|
225
|
+
"status --refresh --json",
|
|
225
226
|
"docs list --role buyer --json",
|
|
226
227
|
"docs show quickstart --role buyer --json",
|
|
227
228
|
"docs search <question> --role buyer --json",
|
|
@@ -1954,26 +1955,37 @@ async function agentStatus(flags) {
|
|
|
1954
1955
|
if (!run) {
|
|
1955
1956
|
const config = readConfig();
|
|
1956
1957
|
const credentials = readCredentials();
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1958
|
+
const hasLocalSession = Boolean(readSessionToken(credentials));
|
|
1959
|
+
const auth = flags.refresh
|
|
1960
|
+
? await refreshBuyerSessionForStatus(flags)
|
|
1961
|
+
: {
|
|
1962
|
+
authenticated: hasLocalSession,
|
|
1963
|
+
session_verified: false,
|
|
1964
|
+
account_id: config.account_id || null,
|
|
1965
|
+
device_id: config.device_id || null,
|
|
1966
|
+
auth_source: hasLocalSession ? "local_files_unverified" : "local_files"
|
|
1967
|
+
};
|
|
1968
|
+
output({
|
|
1969
|
+
schema_version: "itp.agent.v1",
|
|
1970
|
+
status: auth.authenticated
|
|
1971
|
+
? (auth.session_verified === false ? "local_session_unverified" : "idle")
|
|
1972
|
+
: "unauthenticated",
|
|
1968
1973
|
phase: auth.authenticated ? "idle" : "unauthenticated",
|
|
1969
1974
|
authenticated: Boolean(auth.authenticated),
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1975
|
+
session_verified: auth.session_verified !== false,
|
|
1976
|
+
auth_source: auth.auth_source,
|
|
1977
|
+
account_id: auth.account_id || null,
|
|
1978
|
+
device_id: auth.device_id || null,
|
|
1979
|
+
next: nextActionForAuthStatus(auth, flags),
|
|
1980
|
+
agent_next_actions: auth.authenticated && auth.session_verified !== false
|
|
1981
|
+
? ["search_catalog", "view_orders", "create_refund_if_needed", "list_agent_read_grants"]
|
|
1982
|
+
: ["run_status_refresh", "start_human_auth_if_unauthenticated"],
|
|
1983
|
+
note: auth.authenticated && auth.session_verified === false
|
|
1984
|
+
? "Local buyer session exists but the server has not verified it. Run the next.command before order/refund/account operations."
|
|
1985
|
+
: undefined,
|
|
1986
|
+
error: auth.error || undefined,
|
|
1987
|
+
secrets: { raw_key_included: false, session_token_included: false }
|
|
1988
|
+
});
|
|
1977
1989
|
return;
|
|
1978
1990
|
}
|
|
1979
1991
|
const refreshed = flags.refresh ? await refreshRun(run, flags) : run;
|
|
@@ -1981,6 +1993,50 @@ async function agentStatus(flags) {
|
|
|
1981
1993
|
output(agentRunResponse(refreshed));
|
|
1982
1994
|
}
|
|
1983
1995
|
|
|
1996
|
+
async function refreshBuyerSessionForStatus(flags = {}) {
|
|
1997
|
+
const config = readConfig();
|
|
1998
|
+
const credentials = readCredentials();
|
|
1999
|
+
if (!readSessionToken(credentials)) {
|
|
2000
|
+
return {
|
|
2001
|
+
authenticated: false,
|
|
2002
|
+
session_verified: true,
|
|
2003
|
+
account_id: config.account_id || null,
|
|
2004
|
+
device_id: config.device_id || null,
|
|
2005
|
+
auth_source: "core_no_session"
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
try {
|
|
2009
|
+
const status = await coreApi("/v1/me/auth/status", { method: "GET" }, flags);
|
|
2010
|
+
return {
|
|
2011
|
+
authenticated: true,
|
|
2012
|
+
session_verified: true,
|
|
2013
|
+
account_id: status.buyer_account_id || config.account_id || null,
|
|
2014
|
+
device_id: config.device_id || null,
|
|
2015
|
+
account_status: status.account_status || null,
|
|
2016
|
+
auth_source: "core"
|
|
2017
|
+
};
|
|
2018
|
+
} catch (error) {
|
|
2019
|
+
return {
|
|
2020
|
+
authenticated: false,
|
|
2021
|
+
session_verified: true,
|
|
2022
|
+
account_id: config.account_id || null,
|
|
2023
|
+
device_id: config.device_id || null,
|
|
2024
|
+
auth_source: "core",
|
|
2025
|
+
error: safeErrorMessage(error)
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
function nextActionForAuthStatus(auth = {}, flags = {}) {
|
|
2031
|
+
if (auth.authenticated && auth.session_verified === false) {
|
|
2032
|
+
return { type: "verify_buyer_session", command: cliCommand("status", "--refresh", "--json"), safe_for_agent: true };
|
|
2033
|
+
}
|
|
2034
|
+
if (auth.authenticated) {
|
|
2035
|
+
return { type: "buyer_ready", command: cliCommand("buyer", "auth", "status", "--json"), safe_for_agent: true };
|
|
2036
|
+
}
|
|
2037
|
+
return { type: "start_auth", command: cliCommand("setup", "--plan", "credit-300", "--method", "alipay", "--json"), safe_for_agent: true };
|
|
2038
|
+
}
|
|
2039
|
+
|
|
1984
2040
|
async function resume(flags) {
|
|
1985
2041
|
const runId = flags.run_id || readState().current_run_id;
|
|
1986
2042
|
const run = readRun(runId);
|
|
@@ -3422,6 +3478,15 @@ async function renderHumanAction(action, flags = {}) {
|
|
|
3422
3478
|
return { rendered: false, mode: "json-local-qr", outputs: [localPath] };
|
|
3423
3479
|
}
|
|
3424
3480
|
}
|
|
3481
|
+
if (!qrImageURL && shouldGenerateLocalQRFromActionURL(action, mode, flags)) {
|
|
3482
|
+
const localPath = await prepareLocalQRFromActionURL(action, flags, mode !== "file" && !flags.qr_file && !process.env.ITP_QR_FILE);
|
|
3483
|
+
if (localPath) {
|
|
3484
|
+
attachAgentLocalQR(action, localPath);
|
|
3485
|
+
annotateHumanActionPresentation(action, "");
|
|
3486
|
+
persistHumanAction(action, flags);
|
|
3487
|
+
return { rendered: false, mode: "json-local-url-qr", outputs: [localPath] };
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3425
3490
|
return { rendered: false, mode: flags.json ? "json" : mode };
|
|
3426
3491
|
}
|
|
3427
3492
|
const host = String(process.env.ITP_HOST || flags.host || "").toLowerCase();
|
|
@@ -3435,9 +3500,18 @@ async function renderHumanAction(action, flags = {}) {
|
|
|
3435
3500
|
persistHumanAction(action, flags);
|
|
3436
3501
|
return { rendered: false, mode: localPath ? "agent-local-image-qr" : "agent-image-qr", host, outputs: [localPath || "preferred_qr_url"] };
|
|
3437
3502
|
}
|
|
3438
|
-
|
|
3503
|
+
if (shouldGenerateLocalQRFromActionURL(action, mode, flags)) {
|
|
3504
|
+
const localPath = await prepareLocalQRFromActionURL(action, flags, true);
|
|
3505
|
+
if (localPath) {
|
|
3506
|
+
attachAgentLocalQR(action, localPath);
|
|
3507
|
+
annotateHumanActionPresentation(action, "");
|
|
3508
|
+
persistHumanAction(action, flags);
|
|
3509
|
+
return { rendered: false, mode: "agent-local-url-qr", host, outputs: [localPath] };
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
process.stderr.write(`No QR image available. Open action URL: ${action.url}\n`);
|
|
3439
3513
|
persistHumanAction(action, flags);
|
|
3440
|
-
return { rendered: false, mode: "agent-url-fallback", host, outputs: ["
|
|
3514
|
+
return { rendered: false, mode: "agent-url-fallback", host, outputs: ["action_url"] };
|
|
3441
3515
|
}
|
|
3442
3516
|
|
|
3443
3517
|
const renderResult = { rendered: false, mode, outputs: [] };
|
|
@@ -3461,7 +3535,19 @@ async function renderHumanAction(action, flags = {}) {
|
|
|
3461
3535
|
renderResult.outputs.push(file);
|
|
3462
3536
|
if (mode === "file") return renderResult;
|
|
3463
3537
|
} else {
|
|
3464
|
-
|
|
3538
|
+
const localPath = shouldGenerateLocalQRFromActionURL(action, mode, flags)
|
|
3539
|
+
? await prepareLocalQRFromActionURL(action, flags, false)
|
|
3540
|
+
: "";
|
|
3541
|
+
if (localPath) {
|
|
3542
|
+
attachAgentLocalQR(action, localPath);
|
|
3543
|
+
annotateHumanActionPresentation(action, "");
|
|
3544
|
+
process.stderr.write(`Alipay action QR image: ${localPath}\n`);
|
|
3545
|
+
renderResult.rendered = true;
|
|
3546
|
+
renderResult.outputs.push(localPath);
|
|
3547
|
+
if (mode === "file") return renderResult;
|
|
3548
|
+
} else {
|
|
3549
|
+
process.stderr.write(`No QR image available. Open action URL: ${action.url}\n`);
|
|
3550
|
+
}
|
|
3465
3551
|
}
|
|
3466
3552
|
}
|
|
3467
3553
|
|
|
@@ -3535,6 +3621,12 @@ function shouldPrepareLocalQRForJSON(mode, flags = {}, action = {}) {
|
|
|
3535
3621
|
return false;
|
|
3536
3622
|
}
|
|
3537
3623
|
|
|
3624
|
+
function shouldGenerateLocalQRFromActionURL(action = {}, mode = "", flags = {}) {
|
|
3625
|
+
if (!action?.url) return false;
|
|
3626
|
+
if (action.kind === "auth_qr") return true;
|
|
3627
|
+
return mode === "file" || Boolean(flags.qr_file || process.env.ITP_QR_FILE);
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3538
3630
|
async function prepareLocalQRFile(action, qrImageURL, flags = {}, optional = false) {
|
|
3539
3631
|
if (!qrImageURL) return "";
|
|
3540
3632
|
const file = flags.qr_file || process.env.ITP_QR_FILE || defaultQRFilePath(action, qrImageURL);
|
|
@@ -3550,11 +3642,37 @@ async function prepareLocalQRFile(action, qrImageURL, flags = {}, optional = fal
|
|
|
3550
3642
|
return file;
|
|
3551
3643
|
}
|
|
3552
3644
|
|
|
3645
|
+
async function prepareLocalQRFromActionURL(action, flags = {}, optional = false) {
|
|
3646
|
+
if (!action?.url) return "";
|
|
3647
|
+
const file = flags.qr_file || process.env.ITP_QR_FILE || defaultGeneratedQRFilePath(action);
|
|
3648
|
+
try {
|
|
3649
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
3650
|
+
await QRCode.toFile(file, action.url, {
|
|
3651
|
+
type: "png",
|
|
3652
|
+
errorCorrectionLevel: "M",
|
|
3653
|
+
margin: 2,
|
|
3654
|
+
width: 512
|
|
3655
|
+
});
|
|
3656
|
+
} catch (error) {
|
|
3657
|
+
if (!optional) throw error;
|
|
3658
|
+
action.local_qr_error = safeErrorMessage(error);
|
|
3659
|
+
return "";
|
|
3660
|
+
}
|
|
3661
|
+
action.local_qr_path = file;
|
|
3662
|
+
action.local_qr_mime = "image/png";
|
|
3663
|
+
return file;
|
|
3664
|
+
}
|
|
3665
|
+
|
|
3553
3666
|
function defaultQRFilePath(action, qrImageURL) {
|
|
3554
3667
|
const id = sanitizeFilename(action?.payment_intent_id || action?.id || "qr");
|
|
3555
3668
|
return path.join(os.tmpdir(), `itp-${id}.${qrFileExtension(qrImageURL, action)}`);
|
|
3556
3669
|
}
|
|
3557
3670
|
|
|
3671
|
+
function defaultGeneratedQRFilePath(action) {
|
|
3672
|
+
const id = sanitizeFilename(action?.payment_intent_id || action?.id || "qr");
|
|
3673
|
+
return path.join(os.tmpdir(), `itp-${id}.png`);
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3558
3676
|
function qrFileExtension(qrImageURL, action = {}) {
|
|
3559
3677
|
const mime = qrMimeType(qrImageURL, action);
|
|
3560
3678
|
if (mime === "image/png") return "png";
|
|
@@ -3620,6 +3738,22 @@ function attachAgentQRImage(action, qrImageURL, localPath = "") {
|
|
|
3620
3738
|
return action;
|
|
3621
3739
|
}
|
|
3622
3740
|
|
|
3741
|
+
function attachAgentLocalQR(action, localPath = "") {
|
|
3742
|
+
if (!action || !localPath) return action;
|
|
3743
|
+
if (!Array.isArray(action.display)) {
|
|
3744
|
+
action.display = [];
|
|
3745
|
+
}
|
|
3746
|
+
if (!action.display.some((item) => item?.type === "image" && item?.local_path === localPath)) {
|
|
3747
|
+
action.display.push({
|
|
3748
|
+
type: "image",
|
|
3749
|
+
format: "png",
|
|
3750
|
+
local_path: localPath,
|
|
3751
|
+
instructions: "Render this local QR image for the human to scan. It encodes the ItPay human action URL, not a provider raw QR payload."
|
|
3752
|
+
});
|
|
3753
|
+
}
|
|
3754
|
+
return action;
|
|
3755
|
+
}
|
|
3756
|
+
|
|
3623
3757
|
async function downloadQRImage(qrImageURL, file, flags = {}) {
|
|
3624
3758
|
const controller = new AbortController();
|
|
3625
3759
|
const timer = setTimeout(() => controller.abort(), apiTimeoutMs(flags));
|
|
@@ -3772,9 +3906,9 @@ async function coreApi(pathname, options = {}, flags = {}) {
|
|
|
3772
3906
|
});
|
|
3773
3907
|
} catch (error) {
|
|
3774
3908
|
if (error?.name === "AbortError") {
|
|
3775
|
-
throw new Error(`request timed out after ${Math.ceil(timeoutMs / 1000)}s: ${targetURL}`);
|
|
3909
|
+
throw new Error(`request timed out after ${Math.ceil(timeoutMs / 1000)}s: ${safeRequestTarget(targetURL)}`);
|
|
3776
3910
|
}
|
|
3777
|
-
throw error;
|
|
3911
|
+
throw new Error(`network request failed: ${safeRequestTarget(targetURL)}: ${safeErrorMessage(error)}`);
|
|
3778
3912
|
} finally {
|
|
3779
3913
|
clearTimeout(timer);
|
|
3780
3914
|
}
|
|
@@ -3790,11 +3924,27 @@ async function coreApi(pathname, options = {}, flags = {}) {
|
|
|
3790
3924
|
if (!response.ok || payload.success === false) {
|
|
3791
3925
|
const error = new Error(payload.error || payload.message || `request failed: ${response.status}`);
|
|
3792
3926
|
error.status = response.status;
|
|
3927
|
+
if (response.status === 401 && String(pathname).startsWith("/v1/me/")) {
|
|
3928
|
+
error.message = "buyer session required or expired; run status --refresh --json, then run setup --method alipay --json if unauthenticated";
|
|
3929
|
+
error.next = [
|
|
3930
|
+
{ type: "verify_buyer_session", command: cliCommand("status", "--refresh", "--json"), safe_for_agent: true },
|
|
3931
|
+
{ type: "start_auth_if_needed", command: cliCommand("setup", "--method", "alipay", "--json"), safe_for_agent: true }
|
|
3932
|
+
];
|
|
3933
|
+
}
|
|
3793
3934
|
throw error;
|
|
3794
3935
|
}
|
|
3795
3936
|
return payload.data ?? payload;
|
|
3796
3937
|
}
|
|
3797
3938
|
|
|
3939
|
+
function safeRequestTarget(target) {
|
|
3940
|
+
try {
|
|
3941
|
+
const url = new URL(String(target));
|
|
3942
|
+
return `${url.origin}${url.pathname}`;
|
|
3943
|
+
} catch {
|
|
3944
|
+
return String(target).split("?")[0];
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3798
3948
|
function coreAgentFingerprint(flags = {}) {
|
|
3799
3949
|
const explicit = flags.agent_fingerprint || flags.agent_device_fingerprint || process.env.ITPAY_AGENT_FINGERPRINT || process.env.ITPAY_AGENT_DEVICE_FINGERPRINT;
|
|
3800
3950
|
if (explicit) return String(explicit);
|
|
@@ -4131,7 +4281,12 @@ async function withStateLock(fn) {
|
|
|
4131
4281
|
fd = fs.openSync(LOCK_PATH, "wx", 0o600);
|
|
4132
4282
|
fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, started_at: new Date().toISOString() }));
|
|
4133
4283
|
} catch {
|
|
4134
|
-
|
|
4284
|
+
const error = new Error(`another itp setup/status operation is running; if no other ItPay CLI is active, remove the stale lock and retry: rm -f ${LOCK_PATH}`);
|
|
4285
|
+
error.next = [
|
|
4286
|
+
{ type: "check_status", command: cliCommand("status", "--refresh", "--json"), safe_for_agent: true },
|
|
4287
|
+
{ type: "clear_stale_lock", command: `rm -f ${shellQuote(LOCK_PATH)}`, safe_for_agent: true }
|
|
4288
|
+
];
|
|
4289
|
+
throw error;
|
|
4135
4290
|
} finally {
|
|
4136
4291
|
if (fd !== undefined) fs.closeSync(fd);
|
|
4137
4292
|
}
|
|
@@ -4481,7 +4636,9 @@ function output(value) {
|
|
|
4481
4636
|
}
|
|
4482
4637
|
|
|
4483
4638
|
function outputError(error) {
|
|
4484
|
-
|
|
4639
|
+
const payload = { success: false, message: safeErrorMessage(error) };
|
|
4640
|
+
if (error?.next) payload.next = error.next;
|
|
4641
|
+
console.error(JSON.stringify(payload, null, 2));
|
|
4485
4642
|
}
|
|
4486
4643
|
|
|
4487
4644
|
function maskSecret(secret) {
|
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
"must_not_need": ["ops token", "claim token", "raw key", "provider raw payload"]
|
|
15
15
|
},
|
|
16
16
|
"commands": [
|
|
17
|
+
{
|
|
18
|
+
"intent": "verify whether this agent/device already has a server-valid buyer session",
|
|
19
|
+
"command": "itp status --refresh --json",
|
|
20
|
+
"success_signal": "response.status is idle when authenticated, or response.next.command tells the agent how to start human auth"
|
|
21
|
+
},
|
|
17
22
|
{
|
|
18
23
|
"intent": "list the available agent docs topics",
|
|
19
24
|
"command": "itp docs list --role buyer --json",
|
|
@@ -37,6 +42,7 @@
|
|
|
37
42
|
],
|
|
38
43
|
"agent_rules": [
|
|
39
44
|
"Use --json for every ItPay command.",
|
|
45
|
+
"Use `itp status --refresh --json` before account, order, refund, or repeat-purchase decisions. Plain local memory is not identity.",
|
|
40
46
|
"Search and explain options before buying unless the user already named a variant.",
|
|
41
47
|
"Add the selected UCP Variant.id to cart; do not bypass cart for CORE-028 flows.",
|
|
42
48
|
"When explaining prices, remember ItPay JSON money fields use minor units. For CNY, amount=10 means CNY 0.10. Prefer display_amount when present.",
|
|
@@ -45,6 +51,7 @@
|
|
|
45
51
|
"For first purchase, show auth_qr as the ItPay first-purchase entry: the human first approves provider login/registration/profile sharing, then ItPay should continue the same checkout to payment.",
|
|
46
52
|
"After showing auth_qr, keep running/resuming the same checkout unless the human explicitly asks you to pause. Do not stop merely because a QR was displayed.",
|
|
47
53
|
"When a buyer command returns buyer_session.status=buyer_session_saved, the CLI has stored the buyer account session for this agent device. You may continue with checkout/payment or `buyer vault` commands without asking the human for a token.",
|
|
54
|
+
"Vault grants are only for reading approved delivered artifacts. They are not buyer session credentials and do not authorize refunds or account/order management.",
|
|
48
55
|
"Use high-level itp buy when possible; it is designed to show the first-purchase entry, wait/resume, continue to payment, wait for verification, and poll delivery.",
|
|
49
56
|
"Show the returned payment QR exactly as provided.",
|
|
50
57
|
"Payment truth comes only from payment_intent.verified.",
|
package/package.json
CHANGED
|
@@ -23,6 +23,7 @@ protocol from this file. Use the CLI docs graph whenever you need details.
|
|
|
23
23
|
Run these commands before buying:
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
+
itp status --refresh --json
|
|
26
27
|
itp docs show quickstart --role buyer --json
|
|
27
28
|
itp docs list --role buyer --json
|
|
28
29
|
```
|
|
@@ -37,6 +38,7 @@ itp docs search "<what you need to know>" --role buyer --json
|
|
|
37
38
|
|
|
38
39
|
```text
|
|
39
40
|
read this skill
|
|
41
|
+
-> run status --refresh and follow next.command if unauthenticated
|
|
40
42
|
-> read quickstart doc
|
|
41
43
|
-> search catalog
|
|
42
44
|
-> explain/recommend a variant
|
|
@@ -108,6 +110,9 @@ Refund commands use ItPay shared order state. If `itp buyer refund create`
|
|
|
108
110
|
returns `policy_risk_confirmation_required`, explain the returned
|
|
109
111
|
`refund_eligibility.policy` and `agent_guidance` to the human first. Only retry
|
|
110
112
|
with `--confirm-policy-risk true` after explicit human confirmation.
|
|
113
|
+
Refund commands require a server-verified buyer session, not a vault grant. If
|
|
114
|
+
the CLI says the buyer session is required or expired, run
|
|
115
|
+
`itp status --refresh --json` and follow the returned `next.command`.
|
|
111
116
|
Current buyer refunds are whole-order only; do not use line-item refund scope.
|
|
112
117
|
If the human cancels a refund before provider or money movement starts, use
|
|
113
118
|
`buyer refund cancel <refund_id> --json`; after cancel, the delivery claim can
|