@itpay/cli 2.1.0 → 2.1.1
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/bin/darwin-amd64/itpay-sell +0 -0
- package/bin/darwin-arm64/itpay-sell +0 -0
- package/bin/linux-amd64/itpay-sell +0 -0
- package/bin/linux-arm64/itpay-sell +0 -0
- package/bin/windows-amd64/itpay-sell.exe +0 -0
- package/dist/src/client/backend.js +6 -0
- package/dist/src/commands/services.js +14 -1
- package/dist/src/main.js +14 -0
- package/dist/src/sell/commands.js +1 -1
- package/dist/src/{sell/auth.js → state/account_auth.js} +33 -13
- package/dist/src/state/config.js +3 -3
- package/dist/src/state/device_authority.js +1 -1
- package/docs/cli-reference/commands/auth.md +9 -0
- package/docs/cli-reference/index.md +2 -1
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -11,6 +11,12 @@ export class BackendClient {
|
|
|
11
11
|
throw new Error("Invalid Sell route");
|
|
12
12
|
return this.http.request(request.path, { method: request.method, ...(request.body !== undefined ? { body: request.body } : {}) });
|
|
13
13
|
}
|
|
14
|
+
agentAccountStatus() {
|
|
15
|
+
return this.http.get('/v1/agent-device-account-bindings');
|
|
16
|
+
}
|
|
17
|
+
bindAgentAccount(input) {
|
|
18
|
+
return this.http.post('/v1/agent-device-account-bindings', input);
|
|
19
|
+
}
|
|
14
20
|
readyz() {
|
|
15
21
|
return this.http.get("/v1/readyz");
|
|
16
22
|
}
|
|
@@ -656,6 +656,16 @@ function servicesNextEnvelope(model) {
|
|
|
656
656
|
const id = execution.service_execution_id;
|
|
657
657
|
const paymentVerified = model.payment_bindings.some((binding) => binding.status === "payment_verified") || model.checkout_bindings.some((binding) => binding.status === "payment_verified");
|
|
658
658
|
const state = model.workflow?.status === "payment" && paymentVerified ? "running" : model.workflow?.status ?? "input_required";
|
|
659
|
+
if (state === "failed" && ["login_required", "rate_limited"].includes(model.workflow?.error_code ?? "")) {
|
|
660
|
+
const login = model.workflow?.error_code === "login_required";
|
|
661
|
+
return {
|
|
662
|
+
status: login ? "login_required" : "rate_limited",
|
|
663
|
+
result: { service_execution_id: id, service_id: execution.service_id },
|
|
664
|
+
instruction: login ? "匿名免费额度已用完。使用官方网页登录并绑定当前 Agent,完成后重新发起查询;不需要付款。" : "已达到登录账号每分钟查询上限。请等到下一分钟再发起查询,不要连续重试。",
|
|
665
|
+
next: login ? { command: "itpay auth login --json", reason: "登录继续免费查询" } : null,
|
|
666
|
+
recovery: [],
|
|
667
|
+
};
|
|
668
|
+
}
|
|
659
669
|
const recovery = state === "recovery_required" || state === "failed";
|
|
660
670
|
let command = `itpay services next ${id} --json`;
|
|
661
671
|
if (state === "payment")
|
|
@@ -701,7 +711,7 @@ function servicesNextEnvelope(model) {
|
|
|
701
711
|
recovery: [],
|
|
702
712
|
};
|
|
703
713
|
}
|
|
704
|
-
if (isTerminalServiceExecutionStatus(execution.status) && !(model.workflow_entry && currentDelivery && ["completed", "delivery_completed"].includes(execution.status))) {
|
|
714
|
+
if (isTerminalServiceExecutionStatus(execution.status) && !(model.workflow_entry && (currentDelivery || serviceDeliveryMode(model) === "agent_visible_result") && ["completed", "delivery_completed"].includes(execution.status))) {
|
|
705
715
|
const paid = model.checkout_bindings.some((binding) => binding.status === "payment_verified") || Boolean(currentDelivery?.order_id);
|
|
706
716
|
const paidFailure = execution.status === "failed" && paid;
|
|
707
717
|
return {
|
|
@@ -905,6 +915,9 @@ function serviceAllowedActionCommand(model, action) {
|
|
|
905
915
|
}
|
|
906
916
|
function serviceDeliveryMode(model) {
|
|
907
917
|
const delivery = model.current_delivery ?? model.delivery_bindings.at(-1);
|
|
918
|
+
const entry = model.capabilities.find(capability => capability.capability_id === model.workflow_entry?.capability_id);
|
|
919
|
+
if (!delivery && model.workflow?.status === "completed" && entry?.requires_payment === false && !entry.vault_required)
|
|
920
|
+
return "agent_visible_result";
|
|
908
921
|
const explicit = String(delivery?.redacted_summary?.delivery_mode ?? "");
|
|
909
922
|
if (explicit)
|
|
910
923
|
return explicit;
|
package/dist/src/main.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { agentAuth } from "./state/account_auth.js";
|
|
1
2
|
import { registerSell } from "./sell/commands.js";
|
|
2
3
|
import { readFileSync as readWorkflowInputFile } from "node:fs";
|
|
3
4
|
import { runServicesRun } from "./commands/services.js";
|
|
@@ -291,6 +292,19 @@ program
|
|
|
291
292
|
});
|
|
292
293
|
}
|
|
293
294
|
});
|
|
295
|
+
const authCmd = program.command("auth").description("Log in and bind this enrolled Agent to your ItPay account");
|
|
296
|
+
for (const action of ["login", "status"]) {
|
|
297
|
+
authCmd.command(action).option("--json", "output JSON").action(async (options) => {
|
|
298
|
+
const config = loadConfig();
|
|
299
|
+
try {
|
|
300
|
+
const result = await agentAuth(action, config.baseURL, newBackendClient(config));
|
|
301
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
reportCLIError(error, { jsonOutput: Boolean(options.json), code: "account_login_failed", instruction: "完成官方网页登录后重试 itpay auth status;不要清除设备登记。" });
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
294
308
|
// --- device ---------------------------------------------------------------
|
|
295
309
|
const deviceCmd = program.command("device").description("Recover the current official Backend registration after an operator-confirmed reset");
|
|
296
310
|
deviceCmd
|
|
@@ -2,11 +2,11 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { dirname, resolve } from 'node:path';
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, rmSync } from 'node:fs';
|
|
5
|
-
export function sellerAuthPath(baseURL, env = process.env) {
|
|
6
|
-
return resolve(env.HOME || homedir(), '.itpay-v3',
|
|
5
|
+
export function sellerAuthPath(baseURL, env = process.env, purpose = "seller") {
|
|
6
|
+
return resolve(env.HOME || homedir(), '.itpay-v3', `${purpose}-${createHash('sha256').update(baseURL).digest('hex').slice(0, 16)}.json`);
|
|
7
7
|
}
|
|
8
|
-
function read(baseURL, env = process.env) {
|
|
9
|
-
const path = sellerAuthPath(baseURL, env);
|
|
8
|
+
function read(baseURL, env = process.env, purpose = "seller") {
|
|
9
|
+
const path = sellerAuthPath(baseURL, env, purpose);
|
|
10
10
|
if (!existsSync(path))
|
|
11
11
|
return;
|
|
12
12
|
const state = JSON.parse(readFileSync(path, 'utf8'));
|
|
@@ -14,8 +14,8 @@ function read(baseURL, env = process.env) {
|
|
|
14
14
|
throw new Error('Seller session backend mismatch');
|
|
15
15
|
return state;
|
|
16
16
|
}
|
|
17
|
-
function save(state, env = process.env) {
|
|
18
|
-
const path = sellerAuthPath(state.baseURL, env);
|
|
17
|
+
function save(state, env = process.env, purpose = "seller") {
|
|
18
|
+
const path = sellerAuthPath(state.baseURL, env, purpose);
|
|
19
19
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
20
20
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
21
21
|
writeFileSync(tmp, JSON.stringify(state), { mode: 0o600 });
|
|
@@ -25,15 +25,28 @@ export function sellerSessionToken(baseURL, env = process.env) {
|
|
|
25
25
|
const state = read(baseURL, env);
|
|
26
26
|
return state?.expiresAt && Date.parse(state.expiresAt) > Date.now() ? state.sessionToken : undefined;
|
|
27
27
|
}
|
|
28
|
-
export
|
|
28
|
+
export function sellerAuth(action, baseURL, env = process.env, fetcher = fetch) {
|
|
29
|
+
return accountAuth(action, baseURL, env, fetcher);
|
|
30
|
+
}
|
|
31
|
+
export function agentAuth(action, baseURL, backend, env = process.env, fetcher = fetch) {
|
|
32
|
+
return accountAuth(action, baseURL, env, fetcher, backend);
|
|
33
|
+
}
|
|
34
|
+
async function accountAuth(action, baseURL, env, fetcher, backend) {
|
|
35
|
+
const purpose = backend ? 'agent-login' : 'seller';
|
|
36
|
+
const command = backend ? 'itpay auth status' : 'itpay sell auth status';
|
|
37
|
+
if (backend) {
|
|
38
|
+
const current = await backend.agentAccountStatus();
|
|
39
|
+
if (current.status === 'authenticated')
|
|
40
|
+
return current;
|
|
41
|
+
}
|
|
29
42
|
async function request(path, init = {}) {
|
|
30
43
|
const response = await fetcher(baseURL + path, { ...init, redirect: 'error', signal: AbortSignal.timeout(15000) });
|
|
31
44
|
if (!response.ok)
|
|
32
|
-
throw new Error(`
|
|
45
|
+
throw new Error(`ItPay authorization failed (${response.status}); retry login if expired`);
|
|
33
46
|
return response;
|
|
34
47
|
}
|
|
35
48
|
if (action === 'login') {
|
|
36
|
-
const response = await request('/v1/dashboard/auth-sessions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ provider: 'alipay', return_to: '/seller' }) });
|
|
49
|
+
const response = await request('/v1/dashboard/auth-sessions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ provider: 'alipay', return_to: backend ? '/' : '/seller' }) });
|
|
37
50
|
const result = await response.json();
|
|
38
51
|
const url = new URL(result.start_url, baseURL);
|
|
39
52
|
if (url.origin !== new URL(baseURL).origin)
|
|
@@ -41,10 +54,10 @@ export async function sellerAuth(action, baseURL, env = process.env, fetcher = f
|
|
|
41
54
|
const startToken = url.searchParams.get('start_token');
|
|
42
55
|
if (!startToken || !result.poll_token || !result.dashboard_auth_session_id)
|
|
43
56
|
throw new Error('Incomplete authorization response');
|
|
44
|
-
save({ baseURL, sessionID: result.dashboard_auth_session_id, pollToken: result.poll_token, startToken }, env);
|
|
45
|
-
return { status: 'authorization_required', authorization_url: url.href, instruction:
|
|
57
|
+
save({ baseURL, sessionID: result.dashboard_auth_session_id, pollToken: result.poll_token, startToken }, env, purpose);
|
|
58
|
+
return { status: 'authorization_required', authorization_url: url.href, instruction: `Complete ItPay login in the browser, then run ${command}.` };
|
|
46
59
|
}
|
|
47
|
-
const state = read(baseURL, env);
|
|
60
|
+
const state = read(baseURL, env, purpose);
|
|
48
61
|
if (action === 'logout') {
|
|
49
62
|
const token = sellerSessionToken(baseURL, env);
|
|
50
63
|
if (token)
|
|
@@ -52,7 +65,7 @@ export async function sellerAuth(action, baseURL, env = process.env, fetcher = f
|
|
|
52
65
|
rmSync(sellerAuthPath(baseURL, env), { force: true });
|
|
53
66
|
return { status: 'logged_out' };
|
|
54
67
|
}
|
|
55
|
-
if (sellerSessionToken(baseURL, env))
|
|
68
|
+
if (!backend && sellerSessionToken(baseURL, env))
|
|
56
69
|
return { status: 'authenticated', base_url: baseURL, expires_at: state?.expiresAt };
|
|
57
70
|
if (!state?.sessionID || !state.pollToken || !state.startToken)
|
|
58
71
|
return { status: 'login_required' };
|
|
@@ -60,6 +73,13 @@ export async function sellerAuth(action, baseURL, env = process.env, fetcher = f
|
|
|
60
73
|
const progress = await (await request(`${path}?poll_token=${encodeURIComponent(state.pollToken)}`)).json();
|
|
61
74
|
if (progress.status !== 'completed')
|
|
62
75
|
return { status: progress.status, instruction: 'Finish login and email verification in the browser.' };
|
|
76
|
+
if (backend) {
|
|
77
|
+
const result = await backend.bindAgentAccount({ dashboard_auth_session_id: state.sessionID, start_token: state.startToken });
|
|
78
|
+
if (result.status !== 'authenticated')
|
|
79
|
+
throw new Error('Agent binding did not complete');
|
|
80
|
+
rmSync(sellerAuthPath(baseURL, env, purpose), { force: true });
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
63
83
|
const claimed = await request(`${path}/claim?start_token=${encodeURIComponent(state.startToken)}`, { method: 'POST' });
|
|
64
84
|
const token = /(?:^|[, ]+)itpay_buyer_session=([^;]+)/.exec(claimed.headers.get('set-cookie') ?? '')?.[1];
|
|
65
85
|
const session = await claimed.json();
|
package/dist/src/state/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sellerSessionToken } from "
|
|
1
|
+
import { sellerSessionToken } from "./account_auth.js";
|
|
2
2
|
// CLI configuration loader. Production defaults to app.itpay.ai; supported public overrides
|
|
3
3
|
// are the official sandbox and dev Backends. Checkout
|
|
4
4
|
// display-token persistence belongs to the cart session file, protected with
|
|
@@ -15,8 +15,8 @@ import { OperationJournal } from "./operation_journal.js";
|
|
|
15
15
|
export const DEFAULT_BASE_URL = "https://app.itpay.ai";
|
|
16
16
|
export const DEV_BASE_URL = "https://dev.itpay.ai";
|
|
17
17
|
export const SANDBOX_BASE_URL = "https://sandbox.itpay.ai";
|
|
18
|
-
export const CLI_VERSION = "2.1.
|
|
19
|
-
export const API_CONTRACT_REVISION = "sha256:
|
|
18
|
+
export const CLI_VERSION = "2.1.1";
|
|
19
|
+
export const API_CONTRACT_REVISION = "sha256:b8593e4d73782a3ddeb5d070df1db572e422dd0aa08083372bc8971b5412b643";
|
|
20
20
|
const CART_SESSION_DEFAULT_DIR = ".itpay-v3";
|
|
21
21
|
const CART_SESSION_FILENAME = "cart.json";
|
|
22
22
|
const OPERATION_JOURNAL_FILENAME = "operations.json";
|
|
@@ -2,7 +2,7 @@ import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, ran
|
|
|
2
2
|
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
5
|
-
const PROTECTED_PATHS = ["/v1/carts", "/v1/service-executions", "/v1/agent-instances", "/v1/orders", "/v1/refunds", "/v1/me", "/v1/vault"];
|
|
5
|
+
const PROTECTED_PATHS = ["/v1/agent-device-account-bindings", "/v1/carts", "/v1/service-executions", "/v1/agent-instances", "/v1/orders", "/v1/refunds", "/v1/me", "/v1/vault"];
|
|
6
6
|
export class DeviceAuthority {
|
|
7
7
|
baseURL;
|
|
8
8
|
backendKey;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Account login
|
|
2
|
+
|
|
3
|
+
Use `itpay auth login --json` to enroll the current Agent and open the official ItPay Web login. After the user finishes login and email verification, run `itpay auth status --json` to bind this enrolled Agent to the account. Keep declaring the actual `--agent-type` on both commands.
|
|
4
|
+
|
|
5
|
+
For dev, keep `ITPAY_BACKEND_URL=https://dev.itpay.ai` on every command. Login state and device registration are isolated from production.
|
|
6
|
+
|
|
7
|
+
Railway exact and smart query services each allow two anonymous queries after enrollment. After login, queries remain free within the published per-minute limits. On `login_required`, finish login and start a new query. On `rate_limited`, wait until the next minute; do not clear device state or retry in a loop.
|
|
8
|
+
|
|
9
|
+
The Agent receives no general account bearer token. This binding reuses the existing stable device-account ownership mechanism; switching account owners is not supported. Seller authoring continues to use `itpay sell auth` separately.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
本目录是 ItPay CLI 的规范性命令合同。它定义命令应向人和 Agent 返回什么、如何指导下一步,以及失败后如何恢复。当前实现与本文档不一致时,以本文档作为后续校准目标。
|
|
4
4
|
|
|
5
|
-
> **统一产品边界:** `itpay` 是唯一公开的 CLI 入口,`$itpay` 是对应的用户侧 Skill 调用方式。当前入口同时覆盖购买新服务、查询订单、查看经用户授权的已购内容和退款;Seller
|
|
5
|
+
> **统一产品边界:** `itpay` 是唯一公开的 CLI 入口,`$itpay` 是对应的用户侧 Skill 调用方式。当前入口同时覆盖购买新服务、查询订单、查看经用户授权的已购内容和退款;Seller 流程使用同一入口的 `itpay sell` 命令。不得为这些意图拆分新的产品入口。
|
|
6
6
|
|
|
7
7
|
企知道可以作为示例数据出现,但任何命令、字段、状态和 instruction 都不得依赖某个服务。服务差异只能来自 Catalog、Service Contract、Capability metadata 和服务端状态。
|
|
8
8
|
|
|
@@ -34,6 +34,7 @@ Commander 自动提供的 `itpay help [command]` 与 `itpay <group> help [subcom
|
|
|
34
34
|
- [`itpay catalog list`](commands/catalog/list.md)
|
|
35
35
|
- [`itpay install`](commands/install.md) - 查看指定 Agent 的安装说明
|
|
36
36
|
- [`itpay skill show`](commands/skill.md) - 一次读取完整内置 ItPay Skill
|
|
37
|
+
- [`itpay auth login / status`](commands/auth.md) - 官方登录并绑定当前已登记 Agent,继续免费查询
|
|
37
38
|
- [`itpay device recover`](commands/device.md) - 仅恢复运营已确认重建的 Backend registration
|
|
38
39
|
- [`itpay docs`](commands/docs/index.md)
|
|
39
40
|
- [`itpay docs list`](commands/docs/list.md)
|