@itpay/cli 0.1.9 → 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 +101 -22
- 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);
|
|
@@ -3850,9 +3906,9 @@ async function coreApi(pathname, options = {}, flags = {}) {
|
|
|
3850
3906
|
});
|
|
3851
3907
|
} catch (error) {
|
|
3852
3908
|
if (error?.name === "AbortError") {
|
|
3853
|
-
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)}`);
|
|
3854
3910
|
}
|
|
3855
|
-
throw error;
|
|
3911
|
+
throw new Error(`network request failed: ${safeRequestTarget(targetURL)}: ${safeErrorMessage(error)}`);
|
|
3856
3912
|
} finally {
|
|
3857
3913
|
clearTimeout(timer);
|
|
3858
3914
|
}
|
|
@@ -3868,11 +3924,27 @@ async function coreApi(pathname, options = {}, flags = {}) {
|
|
|
3868
3924
|
if (!response.ok || payload.success === false) {
|
|
3869
3925
|
const error = new Error(payload.error || payload.message || `request failed: ${response.status}`);
|
|
3870
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
|
+
}
|
|
3871
3934
|
throw error;
|
|
3872
3935
|
}
|
|
3873
3936
|
return payload.data ?? payload;
|
|
3874
3937
|
}
|
|
3875
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
|
+
|
|
3876
3948
|
function coreAgentFingerprint(flags = {}) {
|
|
3877
3949
|
const explicit = flags.agent_fingerprint || flags.agent_device_fingerprint || process.env.ITPAY_AGENT_FINGERPRINT || process.env.ITPAY_AGENT_DEVICE_FINGERPRINT;
|
|
3878
3950
|
if (explicit) return String(explicit);
|
|
@@ -4209,7 +4281,12 @@ async function withStateLock(fn) {
|
|
|
4209
4281
|
fd = fs.openSync(LOCK_PATH, "wx", 0o600);
|
|
4210
4282
|
fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, started_at: new Date().toISOString() }));
|
|
4211
4283
|
} catch {
|
|
4212
|
-
|
|
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;
|
|
4213
4290
|
} finally {
|
|
4214
4291
|
if (fd !== undefined) fs.closeSync(fd);
|
|
4215
4292
|
}
|
|
@@ -4559,7 +4636,9 @@ function output(value) {
|
|
|
4559
4636
|
}
|
|
4560
4637
|
|
|
4561
4638
|
function outputError(error) {
|
|
4562
|
-
|
|
4639
|
+
const payload = { success: false, message: safeErrorMessage(error) };
|
|
4640
|
+
if (error?.next) payload.next = error.next;
|
|
4641
|
+
console.error(JSON.stringify(payload, null, 2));
|
|
4563
4642
|
}
|
|
4564
4643
|
|
|
4565
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
|