@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 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
- let auth = { authenticated: Boolean(readSessionToken(credentials)), account_id: config.account_id || null, device_id: config.device_id || null };
1958
- if (auth.authenticated && flags.refresh) {
1959
- try {
1960
- auth = await api("/api/itp/auth/status", { method: "GET" }, flags);
1961
- } catch (error) {
1962
- auth = { authenticated: false, account_id: config.account_id || null, error: error.message };
1963
- }
1964
- }
1965
- output({
1966
- schema_version: "itp.agent.v1",
1967
- status: auth.authenticated ? "idle" : "unauthenticated",
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
- account_id: auth.account_id || null,
1971
- device_id: auth.device_id || null,
1972
- next: auth.authenticated
1973
- ? { type: "choose_credits_or_plan", command: cliCommand("plans", "--json"), safe_for_agent: true }
1974
- : { type: "start_auth", command: cliCommand("setup", "--plan", "credit-300", "--method", "alipay", "--json"), safe_for_agent: true },
1975
- secrets: { raw_key_included: false, session_token_included: false }
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
- throw new Error("another itp setup/status operation is running; retry shortly or remove stale ~/.itp/state.lock");
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
- console.error(JSON.stringify({ success: false, message: safeErrorMessage(error) }, null, 2));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itpay/cli",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "ItPay CLI, buyer skill, and agent-readable docs for agent-native commerce.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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