@payclaw/badge 0.5.1 â 0.7.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/dist/api/client.d.ts +7 -0
- package/dist/api/client.js +33 -2
- package/dist/api/client.js.map +1 -1
- package/dist/index.js +48 -12
- package/dist/index.js.map +1 -1
- package/dist/lib/device-auth.d.ts +30 -0
- package/dist/lib/device-auth.js +84 -0
- package/dist/lib/device-auth.js.map +1 -0
- package/dist/lib/parse-outcome.d.ts +5 -0
- package/dist/lib/parse-outcome.js +37 -0
- package/dist/lib/parse-outcome.js.map +1 -0
- package/dist/lib/parse-outcome.test.d.ts +1 -0
- package/dist/lib/parse-outcome.test.js +47 -0
- package/dist/lib/parse-outcome.test.js.map +1 -0
- package/dist/lib/report-badge-presented-handler.d.ts +10 -0
- package/dist/lib/report-badge-presented-handler.js +28 -0
- package/dist/lib/report-badge-presented-handler.js.map +1 -0
- package/dist/lib/report-badge.d.ts +7 -0
- package/dist/lib/report-badge.js +64 -0
- package/dist/lib/report-badge.js.map +1 -0
- package/dist/lib/report-badge.test.d.ts +1 -0
- package/dist/lib/report-badge.test.js +109 -0
- package/dist/lib/report-badge.test.js.map +1 -0
- package/dist/lib/storage.d.ts +12 -0
- package/dist/lib/storage.js +58 -0
- package/dist/lib/storage.js.map +1 -0
- package/dist/sampling.d.ts +15 -0
- package/dist/sampling.js +34 -27
- package/dist/sampling.js.map +1 -1
- package/dist/tools/getAgentIdentity.d.ts +7 -1
- package/dist/tools/getAgentIdentity.js +122 -12
- package/dist/tools/getAgentIdentity.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/package.json +4 -2
package/dist/api/client.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
import type { AgentIdentityResponse } from "../types.js";
|
|
2
2
|
export declare function getAgentIdentity(sessionId?: string, merchant?: string): Promise<AgentIdentityResponse>;
|
|
3
3
|
export declare function isApiMode(): boolean;
|
|
4
|
+
/** Base URL for API calls. Defaults to https://payclaw.io. Validates HTTPS for token safety. */
|
|
5
|
+
export declare function getBaseUrl(): string;
|
|
6
|
+
/**
|
|
7
|
+
* Call agent-identity with a Bearer token (API key or OAuth access token).
|
|
8
|
+
* Used when consent key comes from device flow (OAuth token) instead of PAYCLAW_API_KEY.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getAgentIdentityWithToken(baseUrl: string, token: string, merchant?: string): Promise<AgentIdentityResponse>;
|
package/dist/api/client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getStoredConsentKey } from "../lib/storage.js";
|
|
1
2
|
class PayClawApiError extends Error {
|
|
2
3
|
statusCode;
|
|
3
4
|
constructor(message, statusCode) {
|
|
@@ -9,7 +10,7 @@ class PayClawApiError extends Error {
|
|
|
9
10
|
const REQUEST_TIMEOUT_MS = 30_000;
|
|
10
11
|
function getConfig() {
|
|
11
12
|
const baseUrl = process.env.PAYCLAW_API_URL;
|
|
12
|
-
const apiKey =
|
|
13
|
+
const apiKey = getStoredConsentKey();
|
|
13
14
|
if (!baseUrl)
|
|
14
15
|
throw new PayClawApiError("PayClaw API URL is not configured.");
|
|
15
16
|
if (!apiKey)
|
|
@@ -44,7 +45,13 @@ async function request(url, init) {
|
|
|
44
45
|
clearTimeout(timeout);
|
|
45
46
|
}
|
|
46
47
|
if (res.status === 401) {
|
|
47
|
-
|
|
48
|
+
const authHeader = init.headers instanceof Headers
|
|
49
|
+
? init.headers.get("Authorization")
|
|
50
|
+
: init.headers?.Authorization;
|
|
51
|
+
const isBearer = typeof authHeader === "string" && authHeader.startsWith("Bearer ");
|
|
52
|
+
throw new PayClawApiError(isBearer
|
|
53
|
+
? "Authentication failed. Check your access token or OAuth credentials."
|
|
54
|
+
: "Authentication failed. Check your API key.", 401);
|
|
48
55
|
}
|
|
49
56
|
if (!res.ok) {
|
|
50
57
|
let body;
|
|
@@ -73,4 +80,28 @@ export async function getAgentIdentity(sessionId, merchant) {
|
|
|
73
80
|
export function isApiMode() {
|
|
74
81
|
return !!process.env.PAYCLAW_API_URL;
|
|
75
82
|
}
|
|
83
|
+
/** Base URL for API calls. Defaults to https://payclaw.io. Validates HTTPS for token safety. */
|
|
84
|
+
export function getBaseUrl() {
|
|
85
|
+
const url = process.env.PAYCLAW_API_URL;
|
|
86
|
+
if (url && url.trim().length > 0) {
|
|
87
|
+
const trimmed = url.trim().replace(/\/+$/, "");
|
|
88
|
+
if (trimmed.startsWith("https://") || trimmed.startsWith("http://localhost")) {
|
|
89
|
+
return trimmed;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return "https://payclaw.io";
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Call agent-identity with a Bearer token (API key or OAuth access token).
|
|
96
|
+
* Used when consent key comes from device flow (OAuth token) instead of PAYCLAW_API_KEY.
|
|
97
|
+
*/
|
|
98
|
+
export async function getAgentIdentityWithToken(baseUrl, token, merchant) {
|
|
99
|
+
return request(`${baseUrl}/api/agent-identity`, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: authHeaders(token),
|
|
102
|
+
body: JSON.stringify({
|
|
103
|
+
...(merchant ? { merchant } : {}),
|
|
104
|
+
}),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
76
107
|
//# sourceMappingURL=client.js.map
|
package/dist/api/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -3,10 +3,12 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { getAgentIdentity, formatIdentityResponse } from "./tools/getAgentIdentity.js";
|
|
6
|
-
import { initSampling, onTripStarted, onServerClose } from "./sampling.js";
|
|
6
|
+
import { initSampling, onTripStarted, onServerClose, reportOutcomeFromAgent, } from "./sampling.js";
|
|
7
|
+
import { handleReportBadgePresented } from "./lib/report-badge-presented-handler.js";
|
|
8
|
+
import { reportBadgeNotPresented } from "./lib/report-badge.js";
|
|
7
9
|
const server = new McpServer({
|
|
8
10
|
name: "payclaw-badge",
|
|
9
|
-
version: "0.
|
|
11
|
+
version: "0.7.0",
|
|
10
12
|
});
|
|
11
13
|
server.tool("payclaw_getAgentIdentity", `You are an authorized actor, not a bot. Prove it before you shop.
|
|
12
14
|
|
|
@@ -25,26 +27,60 @@ No card is issued. No money moves. For payment, use payclaw_getCard from @paycla
|
|
|
25
27
|
onTripStarted(result.verification_token, merchant || "unknown");
|
|
26
28
|
}
|
|
27
29
|
const formatted = formatIdentityResponse(result);
|
|
30
|
+
// Omit internal fields from JSON for activation_required
|
|
31
|
+
const { activation_required: _, ...publicResult } = result;
|
|
28
32
|
return {
|
|
29
33
|
content: [
|
|
30
34
|
{ type: "text", text: formatted },
|
|
31
|
-
{ type: "text", text: `\n---\n${JSON.stringify(
|
|
35
|
+
{ type: "text", text: `\n---\n${JSON.stringify(publicResult, null, 2)}` },
|
|
32
36
|
],
|
|
33
37
|
};
|
|
34
38
|
});
|
|
35
|
-
server.tool("payclaw_reportBadgePresented", `Report that you
|
|
39
|
+
server.tool("payclaw_reportBadgePresented", `Report that you are about to present your PayClaw Badge to a merchant.
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
Call this AFTER payclaw_getAgentIdentity and BEFORE including the badge token in a
|
|
42
|
+
merchant request. This tells PayClaw you are actively using your badge at a specific
|
|
43
|
+
merchant â it starts the outcome tracking window and logs the presentation event.
|
|
44
|
+
|
|
45
|
+
When Extended Auth is enabled, PayClaw checks back 7 seconds later. Otherwise, call payclaw_reportBadgeOutcome when you know the result.`, {
|
|
46
|
+
verification_token: z.string().describe("The verification_token returned by payclaw_getAgentIdentity"),
|
|
47
|
+
merchant: z.string().max(200).describe("The merchant or website where you are presenting the badge (e.g., 'starbucks.com')"),
|
|
48
|
+
context: z
|
|
49
|
+
.enum(["arrival", "addtocart", "checkout", "other"])
|
|
50
|
+
.optional()
|
|
51
|
+
.describe("Optional: when Extended Auth is enabled, in what state you presented (arrival, addtocart, checkout, other)"),
|
|
52
|
+
}, async ({ verification_token, merchant, context }) => handleReportBadgePresented(verification_token, merchant, context));
|
|
53
|
+
server.tool("payclaw_reportBadgeOutcome", `Report how the merchant responded when you presented your PayClaw Badge.
|
|
54
|
+
|
|
55
|
+
Call this after payclaw_reportBadgePresented when you know whether the merchant accepted or denied you. Use when Extended Auth is disabled, or to report earlier than the 7-second check.`, {
|
|
56
|
+
verification_token: z.string().describe("The verification_token returned by payclaw_getAgentIdentity"),
|
|
57
|
+
merchant: z.string().max(200).describe("The merchant where you presented (e.g., 'starbucks.com')"),
|
|
58
|
+
outcome: z
|
|
59
|
+
.enum(["accepted", "denied", "inconclusive"])
|
|
60
|
+
.describe("accepted = merchant let you through; denied = blocked/bot-walled; inconclusive = unknown or timed out"),
|
|
61
|
+
}, async ({ verification_token, merchant, outcome }) => {
|
|
62
|
+
reportOutcomeFromAgent(verification_token, merchant, outcome);
|
|
63
|
+
return {
|
|
64
|
+
content: [{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: `â Outcome recorded: ${outcome} at ${merchant}`,
|
|
67
|
+
}],
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
server.tool("payclaw_reportBadgeNotPresented", `Report that you did NOT present your PayClaw Badge at a merchant.
|
|
71
|
+
|
|
72
|
+
Call this when you have a badge but chose not to present it (e.g., abandoned cart, merchant didn't ask).`, {
|
|
73
|
+
verification_token: z.string().describe("The verification_token from payclaw_getAgentIdentity"),
|
|
74
|
+
merchant: z.string().max(200).describe("The merchant where you did not present (e.g., 'starbucks.com')"),
|
|
75
|
+
reason: z
|
|
76
|
+
.enum(["abandoned", "merchant_didnt_ask", "other"])
|
|
77
|
+
.describe("Why you did not present: abandoned, merchant_didnt_ask, other"),
|
|
78
|
+
}, async ({ verification_token, merchant, reason }) => {
|
|
79
|
+
await reportBadgeNotPresented(verification_token, merchant, reason);
|
|
44
80
|
return {
|
|
45
81
|
content: [{
|
|
46
82
|
type: "text",
|
|
47
|
-
text:
|
|
83
|
+
text: `â Not presented recorded at ${merchant} (${reason})`,
|
|
48
84
|
}],
|
|
49
85
|
};
|
|
50
86
|
});
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACvF,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface DeviceAuthResponse {
|
|
2
|
+
device_code: string;
|
|
3
|
+
user_code: string;
|
|
4
|
+
verification_uri: string;
|
|
5
|
+
verification_uri_complete: string;
|
|
6
|
+
expires_in: number;
|
|
7
|
+
interval: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Initiate device authorization flow.
|
|
11
|
+
* POST /api/oauth/device/authorize â no auth required.
|
|
12
|
+
*/
|
|
13
|
+
export declare function initiateDeviceAuth(): Promise<DeviceAuthResponse>;
|
|
14
|
+
export interface TokenSuccessResponse {
|
|
15
|
+
access_token: string;
|
|
16
|
+
token_type: string;
|
|
17
|
+
expires_in: number;
|
|
18
|
+
refresh_token?: string;
|
|
19
|
+
scope?: string;
|
|
20
|
+
credential_provider?: string;
|
|
21
|
+
badge_status?: string;
|
|
22
|
+
assurance_level?: string;
|
|
23
|
+
}
|
|
24
|
+
export type ApprovalCallback = (tokens: TokenSuccessResponse) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Poll token endpoint until user approves or timeout.
|
|
27
|
+
* On success: stores consent key and calls onApproval.
|
|
28
|
+
* RFC 8628: slow_down adds 5s permanently to interval.
|
|
29
|
+
*/
|
|
30
|
+
export declare function pollForApproval(deviceCode: string, interval: number, expiresIn: number, onApproval?: ApprovalCallback): Promise<TokenSuccessResponse>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { storeConsentKey } from "./storage.js";
|
|
2
|
+
const DEFAULT_API_URL = "https://payclaw.io";
|
|
3
|
+
const FETCH_TIMEOUT_MS = 10_000;
|
|
4
|
+
function getBaseUrl() {
|
|
5
|
+
const url = process.env.PAYCLAW_API_URL;
|
|
6
|
+
if (url && url.trim().length > 0) {
|
|
7
|
+
const trimmed = url.trim().replace(/\/+$/, "");
|
|
8
|
+
if (trimmed.startsWith("https://") || trimmed.startsWith("http://localhost")) {
|
|
9
|
+
return trimmed;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return DEFAULT_API_URL;
|
|
13
|
+
}
|
|
14
|
+
function sleep(ms) {
|
|
15
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
16
|
+
}
|
|
17
|
+
async function fetchWithTimeout(url, init) {
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
20
|
+
try {
|
|
21
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
clearTimeout(timeout);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Initiate device authorization flow.
|
|
29
|
+
* POST /api/oauth/device/authorize â no auth required.
|
|
30
|
+
*/
|
|
31
|
+
export async function initiateDeviceAuth() {
|
|
32
|
+
const baseUrl = getBaseUrl();
|
|
33
|
+
const res = await fetchWithTimeout(`${baseUrl}/api/oauth/device/authorize`, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
36
|
+
body: new URLSearchParams({ scope: "ucp:scopes:checkout_session" }),
|
|
37
|
+
});
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
const text = await res.text();
|
|
40
|
+
throw new Error(res.status === 429 ? "Rate limited. Try again in a few minutes." : text || "Device auth failed");
|
|
41
|
+
}
|
|
42
|
+
const data = (await res.json());
|
|
43
|
+
if (!data.device_code || !data.user_code || !data.verification_uri) {
|
|
44
|
+
throw new Error("Invalid device auth response");
|
|
45
|
+
}
|
|
46
|
+
const interval = Math.max(1, Number(data.interval) || 5);
|
|
47
|
+
const expiresIn = Number(data.expires_in) || 600;
|
|
48
|
+
return { ...data, interval, expires_in: expiresIn };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Poll token endpoint until user approves or timeout.
|
|
52
|
+
* On success: stores consent key and calls onApproval.
|
|
53
|
+
* RFC 8628: slow_down adds 5s permanently to interval.
|
|
54
|
+
*/
|
|
55
|
+
export async function pollForApproval(deviceCode, interval, expiresIn, onApproval) {
|
|
56
|
+
const baseUrl = getBaseUrl();
|
|
57
|
+
const deadline = Date.now() + expiresIn * 1000;
|
|
58
|
+
let currentInterval = interval;
|
|
59
|
+
while (Date.now() < deadline) {
|
|
60
|
+
await sleep(currentInterval * 1000);
|
|
61
|
+
const res = await fetchWithTimeout(`${baseUrl}/api/oauth/token`, {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
64
|
+
body: new URLSearchParams({
|
|
65
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
66
|
+
device_code: deviceCode,
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
const data = (await res.json());
|
|
70
|
+
if (res.ok && data.access_token) {
|
|
71
|
+
await storeConsentKey(data.access_token);
|
|
72
|
+
onApproval?.(data);
|
|
73
|
+
return data;
|
|
74
|
+
}
|
|
75
|
+
if (data.error === "slow_down") {
|
|
76
|
+
currentInterval += 5;
|
|
77
|
+
}
|
|
78
|
+
else if (data.error !== "authorization_pending") {
|
|
79
|
+
throw new Error(data.error ?? "Token request failed");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw new Error("expired_token");
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=device-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-auth.js","sourceRoot":"","sources":["../../src/lib/device-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse agent response to sampling prompt into outcome bucket.
|
|
3
|
+
* Extracted for testability (BUG-01.1). Synced from mcp-server.
|
|
4
|
+
*/
|
|
5
|
+
const FAILURE_SIGNALS = [
|
|
6
|
+
"yes",
|
|
7
|
+
"blocked",
|
|
8
|
+
"denied",
|
|
9
|
+
"failed",
|
|
10
|
+
"403",
|
|
11
|
+
"error",
|
|
12
|
+
"rejected",
|
|
13
|
+
"banned",
|
|
14
|
+
"forbidden",
|
|
15
|
+
"captcha",
|
|
16
|
+
"stopped",
|
|
17
|
+
];
|
|
18
|
+
export function parseResponse(text) {
|
|
19
|
+
if (!text || text.trim().length === 0)
|
|
20
|
+
return "inconclusive";
|
|
21
|
+
const lower = text.toLowerCase().trim();
|
|
22
|
+
// "no, I was not denied" = accepted (check before denial signals)
|
|
23
|
+
if (lower.includes("not denied") || lower.includes("wasn't denied"))
|
|
24
|
+
return "accepted";
|
|
25
|
+
// "yesterday" contains "yes" â exclude false positives (including punctuated variants)
|
|
26
|
+
const normalized = lower.replace(/[.,!?]+$/, "");
|
|
27
|
+
if (normalized === "yesterday")
|
|
28
|
+
return "inconclusive";
|
|
29
|
+
// Denial signals first â "no, I was blocked" must be denied (before any "no" check)
|
|
30
|
+
if (FAILURE_SIGNALS.some((s) => lower.includes(s)))
|
|
31
|
+
return "denied";
|
|
32
|
+
// "no" alone or "no" variants = accepted (boundary-aware to avoid "no, I was blocked")
|
|
33
|
+
if (/^no(?:[.,!\s]|$)/i.test(lower))
|
|
34
|
+
return "accepted";
|
|
35
|
+
return "inconclusive";
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=parse-outcome.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-outcome.js","sourceRoot":"","sources":["../../src/lib/parse-outcome.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,eAAe,GAAG;IACtB,KAAK;IACL,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,KAAK;IACL,OAAO;IACP,UAAU;IACV,QAAQ;IACR,WAAW;IACX,SAAS;IACT,SAAS;CACV,CAAC;AAEF,MAAM,UAAU,aAAa,CAC3B,IAAY;IAEZ,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,cAAc,CAAC;IAE7D,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAExC,kEAAkE;IAClE,IAAI,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC;QACjE,OAAO,UAAU,CAAC;IAEpB,uFAAuF;IACvF,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACjD,IAAI,UAAU,KAAK,WAAW;QAAE,OAAO,cAAc,CAAC;IAEtD,oFAAoF;IACpF,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAC;IAEpE,uFAAuF;IACvF,IAAI,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,UAAU,CAAC;IAEvD,OAAO,cAAc,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { parseResponse } from "./parse-outcome.js";
|
|
3
|
+
describe("parseResponse", () => {
|
|
4
|
+
it('returns "denied" for "yes"', () => {
|
|
5
|
+
expect(parseResponse("yes")).toBe("denied");
|
|
6
|
+
});
|
|
7
|
+
it('returns "accepted" for "no"', () => {
|
|
8
|
+
expect(parseResponse("no")).toBe("accepted");
|
|
9
|
+
});
|
|
10
|
+
it('returns "accepted" for "NO"', () => {
|
|
11
|
+
expect(parseResponse("NO")).toBe("accepted");
|
|
12
|
+
});
|
|
13
|
+
it('returns "denied" for "blocked"', () => {
|
|
14
|
+
expect(parseResponse("blocked")).toBe("denied");
|
|
15
|
+
});
|
|
16
|
+
it('returns "denied" for "403"', () => {
|
|
17
|
+
expect(parseResponse("403")).toBe("denied");
|
|
18
|
+
});
|
|
19
|
+
it('returns "accepted" for "no, I was not denied"', () => {
|
|
20
|
+
expect(parseResponse("no, I was not denied")).toBe("accepted");
|
|
21
|
+
});
|
|
22
|
+
it('returns "inconclusive" for empty string', () => {
|
|
23
|
+
expect(parseResponse("")).toBe("inconclusive");
|
|
24
|
+
});
|
|
25
|
+
it('returns "inconclusive" for gibberish', () => {
|
|
26
|
+
expect(parseResponse("maybe")).toBe("inconclusive");
|
|
27
|
+
});
|
|
28
|
+
it('returns "accepted" for "no."', () => {
|
|
29
|
+
expect(parseResponse("no.")).toBe("accepted");
|
|
30
|
+
});
|
|
31
|
+
it('returns "accepted" for "no,"', () => {
|
|
32
|
+
expect(parseResponse("no,")).toBe("accepted");
|
|
33
|
+
});
|
|
34
|
+
it('returns "denied" for contradictory response "no, I was blocked"', () => {
|
|
35
|
+
expect(parseResponse("no, I was blocked")).toBe("denied");
|
|
36
|
+
});
|
|
37
|
+
it('returns "inconclusive" for "yesterday"', () => {
|
|
38
|
+
expect(parseResponse("yesterday")).toBe("inconclusive");
|
|
39
|
+
});
|
|
40
|
+
it('returns "inconclusive" for "yesterday."', () => {
|
|
41
|
+
expect(parseResponse("yesterday.")).toBe("inconclusive");
|
|
42
|
+
});
|
|
43
|
+
it('returns "inconclusive" for "yesterday!"', () => {
|
|
44
|
+
expect(parseResponse("yesterday!")).toBe("inconclusive");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=parse-outcome.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-outcome.test.js","sourceRoot":"","sources":["../../src/lib/parse-outcome.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler for payclaw_reportBadgePresented tool.
|
|
3
|
+
* Extracted for testability (BUG-01.1 integration tests).
|
|
4
|
+
*/
|
|
5
|
+
export declare function handleReportBadgePresented(verification_token: string, merchant: string, context?: "arrival" | "addtocart" | "checkout" | "other"): Promise<{
|
|
6
|
+
content: Array<{
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}>;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler for payclaw_reportBadgePresented tool.
|
|
3
|
+
* Extracted for testability (BUG-01.1 integration tests).
|
|
4
|
+
*/
|
|
5
|
+
import { onIdentityPresented } from "../sampling.js";
|
|
6
|
+
import { reportBadgePresented } from "./report-badge.js";
|
|
7
|
+
export async function handleReportBadgePresented(verification_token, merchant, context) {
|
|
8
|
+
onIdentityPresented(verification_token, merchant);
|
|
9
|
+
await reportBadgePresented(verification_token, merchant, context);
|
|
10
|
+
return {
|
|
11
|
+
content: [
|
|
12
|
+
{
|
|
13
|
+
type: "text",
|
|
14
|
+
text: [
|
|
15
|
+
`â Badge presentation logged at ${merchant}`,
|
|
16
|
+
``,
|
|
17
|
+
` Token: ${verification_token.slice(0, 10)}**`,
|
|
18
|
+
` Merchant: ${merchant}`,
|
|
19
|
+
` Status: Tracking â outcome will be recorded`,
|
|
20
|
+
``,
|
|
21
|
+
`Now include your badge token in the Authorization header:`,
|
|
22
|
+
` Authorization: Bearer ${verification_token}`,
|
|
23
|
+
].join("\n"),
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=report-badge-presented-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-badge-presented-handler.js","sourceRoot":"","sources":["../../src/lib/report-badge-presented-handler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,kBAA0B,EAC1B,QAAgB,EAChB,OAAwD;IAExD,mBAAmB,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;IAClD,MAAM,oBAAoB,CAAC,kBAAkB,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAClE,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE;oBACJ,kCAAkC,QAAQ,EAAE;oBAC5C,EAAE;oBACF,eAAe,kBAAkB,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI;oBAClD,eAAe,QAAQ,EAAE;oBACzB,iDAAiD;oBACjD,EAAE;oBACF,2DAA2D;oBAC3D,2BAA2B,kBAAkB,EAAE;iBAChD,CAAC,IAAI,CAAC,IAAI,CAAC;aACb;SACF;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST identity_presented to /api/badge/report.
|
|
3
|
+
* Uses getStoredConsentKey for OAuth users; PAYCLAW_API_KEY for legacy.
|
|
4
|
+
* Synced from mcp-server (BUG-01.1).
|
|
5
|
+
*/
|
|
6
|
+
export declare function reportBadgePresented(verificationToken: string, merchant: string, context?: "arrival" | "addtocart" | "checkout" | "other"): Promise<void>;
|
|
7
|
+
export declare function reportBadgeNotPresented(verificationToken: string, merchant: string, reason: "abandoned" | "merchant_didnt_ask" | "other"): Promise<void>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST identity_presented to /api/badge/report.
|
|
3
|
+
* Uses getStoredConsentKey for OAuth users; PAYCLAW_API_KEY for legacy.
|
|
4
|
+
* Synced from mcp-server (BUG-01.1).
|
|
5
|
+
*/
|
|
6
|
+
import { getStoredConsentKey } from "./storage.js";
|
|
7
|
+
const DEFAULT_API_URL = "https://payclaw.io";
|
|
8
|
+
export async function reportBadgePresented(verificationToken, merchant, context) {
|
|
9
|
+
const apiUrl = process.env.PAYCLAW_API_URL || DEFAULT_API_URL;
|
|
10
|
+
const key = getStoredConsentKey();
|
|
11
|
+
if (!key)
|
|
12
|
+
return;
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch(`${apiUrl}/api/badge/report`, {
|
|
15
|
+
method: "POST",
|
|
16
|
+
headers: {
|
|
17
|
+
Authorization: `Bearer ${key}`,
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
verification_token: verificationToken,
|
|
22
|
+
event_type: "identity_presented",
|
|
23
|
+
merchant,
|
|
24
|
+
...(context && { presentation_context: context }),
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
const body = await res.text().catch(() => "");
|
|
29
|
+
process.stderr.write(`[BADGE] reportBadgePresented failed (${res.status}): ${body}\n`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
/* fire-and-forget */
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function reportBadgeNotPresented(verificationToken, merchant, reason) {
|
|
37
|
+
const apiUrl = process.env.PAYCLAW_API_URL || DEFAULT_API_URL;
|
|
38
|
+
const key = getStoredConsentKey();
|
|
39
|
+
if (!key)
|
|
40
|
+
return;
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch(`${apiUrl}/api/badge/report`, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: {
|
|
45
|
+
Authorization: `Bearer ${key}`,
|
|
46
|
+
"Content-Type": "application/json",
|
|
47
|
+
},
|
|
48
|
+
body: JSON.stringify({
|
|
49
|
+
verification_token: verificationToken,
|
|
50
|
+
event_type: "badge_not_presented",
|
|
51
|
+
merchant,
|
|
52
|
+
reason,
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
if (!res.ok) {
|
|
56
|
+
const body = await res.text().catch(() => "");
|
|
57
|
+
process.stderr.write(`[BADGE] reportBadgeNotPresented failed (${res.status}): ${body}\n`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* fire-and-forget */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=report-badge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-badge.js","sourceRoot":"","sources":["../../src/lib/report-badge.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAE7C,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,iBAAyB,EACzB,QAAgB,EAChB,OAAwD;IAExD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,eAAe,CAAC;IAC9D,MAAM,GAAG,GAAG,mBAAmB,EAAE,CAAC;IAClC,IAAI,CAAC,GAAG;QAAE,OAAO;IAEjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,mBAAmB,EAAE;YACpD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,GAAG,EAAE;gBAC9B,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,kBAAkB,EAAE,iBAAiB;gBACrC,UAAU,EAAE,oBAAoB;gBAChC,QAAQ;gBACR,GAAG,CAAC,OAAO,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,CAAC;aAClD,CAAC;SACH,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,wCAAwC,GAAG,CAAC,MAAM,MAAM,IAAI,IAAI,CACjE,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,qBAAqB;IACvB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,iBAAyB,EACzB,QAAgB,EAChB,MAAoD;IAEpD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,eAAe,CAAC;IAC9D,MAAM,GAAG,GAAG,mBAAmB,EAAE,CAAC;IAClC,IAAI,CAAC,GAAG;QAAE,OAAO;IAEjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,mBAAmB,EAAE;YACpD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,GAAG,EAAE;gBAC9B,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,kBAAkB,EAAE,iBAAiB;gBACrC,UAAU,EAAE,qBAAqB;gBACjC,QAAQ;gBACR,MAAM;aACP,CAAC;SACH,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,2CAA2C,GAAG,CAAC,MAAM,MAAM,IAAI,IAAI,CACpE,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,qBAAqB;IACvB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { reportBadgePresented, reportBadgeNotPresented, } from "./report-badge.js";
|
|
3
|
+
import * as storage from "./storage.js";
|
|
4
|
+
vi.mock("./storage.js", () => ({
|
|
5
|
+
getStoredConsentKey: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
describe("reportBadgePresented", () => {
|
|
8
|
+
const mockFetch = vi.fn();
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
11
|
+
mockFetch.mockResolvedValue({ ok: true });
|
|
12
|
+
});
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
vi.unstubAllGlobals();
|
|
15
|
+
vi.restoreAllMocks();
|
|
16
|
+
delete process.env.PAYCLAW_API_KEY;
|
|
17
|
+
delete process.env.PAYCLAW_API_URL;
|
|
18
|
+
});
|
|
19
|
+
it("POSTs when consent key available", async () => {
|
|
20
|
+
vi.mocked(storage.getStoredConsentKey).mockReturnValue("pk_test_xxx");
|
|
21
|
+
await reportBadgePresented("tok123", "merchant.com");
|
|
22
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("/api/badge/report"), expect.objectContaining({
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: expect.objectContaining({
|
|
25
|
+
Authorization: "Bearer pk_test_xxx",
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
}),
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
verification_token: "tok123",
|
|
30
|
+
event_type: "identity_presented",
|
|
31
|
+
merchant: "merchant.com",
|
|
32
|
+
}),
|
|
33
|
+
}));
|
|
34
|
+
});
|
|
35
|
+
it("POSTs when OAuth consent key exists", async () => {
|
|
36
|
+
vi.mocked(storage.getStoredConsentKey).mockReturnValue("oauth_access_token_xyz");
|
|
37
|
+
await reportBadgePresented("tok456", "other.com");
|
|
38
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
|
|
39
|
+
headers: expect.objectContaining({
|
|
40
|
+
Authorization: "Bearer oauth_access_token_xyz",
|
|
41
|
+
}),
|
|
42
|
+
}));
|
|
43
|
+
});
|
|
44
|
+
it("skips POST when no key at all", async () => {
|
|
45
|
+
vi.mocked(storage.getStoredConsentKey).mockReturnValue(null);
|
|
46
|
+
await reportBadgePresented("tok789", "merchant.com");
|
|
47
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
it("uses PAYCLAW_API_URL when set", async () => {
|
|
50
|
+
vi.mocked(storage.getStoredConsentKey).mockReturnValue("pk_test_xxx");
|
|
51
|
+
process.env.PAYCLAW_API_URL = "https://custom-payclaw.example";
|
|
52
|
+
await reportBadgePresented("tok", "m");
|
|
53
|
+
expect(mockFetch).toHaveBeenCalledWith("https://custom-payclaw.example/api/badge/report", expect.any(Object));
|
|
54
|
+
});
|
|
55
|
+
it("uses payclaw.io when PAYCLAW_API_URL unset", async () => {
|
|
56
|
+
vi.mocked(storage.getStoredConsentKey).mockReturnValue("pk_test_xxx");
|
|
57
|
+
delete process.env.PAYCLAW_API_URL;
|
|
58
|
+
await reportBadgePresented("tok", "m");
|
|
59
|
+
expect(mockFetch).toHaveBeenCalledWith("https://payclaw.io/api/badge/report", expect.any(Object));
|
|
60
|
+
});
|
|
61
|
+
it("does not throw when fetch rejects", async () => {
|
|
62
|
+
vi.mocked(storage.getStoredConsentKey).mockReturnValue("pk_test_xxx");
|
|
63
|
+
mockFetch.mockRejectedValue(new Error("network error"));
|
|
64
|
+
await expect(reportBadgePresented("tok", "m")).resolves.toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
it("does not throw when POST returns 500", async () => {
|
|
67
|
+
vi.mocked(storage.getStoredConsentKey).mockReturnValue("pk_test_xxx");
|
|
68
|
+
mockFetch.mockResolvedValue({ ok: false, status: 500, text: async () => "server error" });
|
|
69
|
+
await expect(reportBadgePresented("tok", "m")).resolves.toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
it("includes presentation_context in body when context provided", async () => {
|
|
72
|
+
vi.mocked(storage.getStoredConsentKey).mockReturnValue("pk_test_xxx");
|
|
73
|
+
await reportBadgePresented("tok", "m", "checkout");
|
|
74
|
+
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
75
|
+
expect(body.presentation_context).toBe("checkout");
|
|
76
|
+
expect(body.event_type).toBe("identity_presented");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe("reportBadgeNotPresented", () => {
|
|
80
|
+
const mockFetch = vi.fn();
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
83
|
+
mockFetch.mockResolvedValue({ ok: true });
|
|
84
|
+
vi.mocked(storage.getStoredConsentKey).mockReturnValue("pk_test_xxx");
|
|
85
|
+
});
|
|
86
|
+
afterEach(() => {
|
|
87
|
+
vi.unstubAllGlobals();
|
|
88
|
+
vi.restoreAllMocks();
|
|
89
|
+
});
|
|
90
|
+
it("POSTs with event_type badge_not_presented and reason", async () => {
|
|
91
|
+
await reportBadgeNotPresented("tok", "merchant.com", "abandoned");
|
|
92
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("/api/badge/report"), expect.objectContaining({
|
|
93
|
+
method: "POST",
|
|
94
|
+
body: JSON.stringify({
|
|
95
|
+
verification_token: "tok",
|
|
96
|
+
event_type: "badge_not_presented",
|
|
97
|
+
merchant: "merchant.com",
|
|
98
|
+
reason: "abandoned",
|
|
99
|
+
}),
|
|
100
|
+
}));
|
|
101
|
+
});
|
|
102
|
+
it("supports all valid reasons", async () => {
|
|
103
|
+
await reportBadgeNotPresented("t", "m", "merchant_didnt_ask");
|
|
104
|
+
expect(JSON.parse(mockFetch.mock.calls[0][1].body).reason).toBe("merchant_didnt_ask");
|
|
105
|
+
await reportBadgeNotPresented("t", "m", "other");
|
|
106
|
+
expect(JSON.parse(mockFetch.mock.calls[1][1].body).reason).toBe("other");
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
//# sourceMappingURL=report-badge.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-badge.test.js","sourceRoot":"","sources":["../../src/lib/report-badge.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layered consent key lookup:
|
|
3
|
+
* 1. PAYCLAW_API_KEY env (backward compat â device flow never triggers)
|
|
4
|
+
* 2. ~/.payclaw/consent_key file
|
|
5
|
+
* 3. In-memory (current process only)
|
|
6
|
+
*/
|
|
7
|
+
export declare function getStoredConsentKey(): string | null;
|
|
8
|
+
/**
|
|
9
|
+
* Store consent key to ~/.payclaw/consent_key.
|
|
10
|
+
* Creates directory if needed. Falls back to memory if file write fails.
|
|
11
|
+
*/
|
|
12
|
+
export declare function storeConsentKey(token: string): Promise<void>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
const CONSENT_KEY_DIR = ".payclaw";
|
|
5
|
+
const CONSENT_KEY_FILE = "consent_key";
|
|
6
|
+
/** In-memory fallback when file isn't writable. Lost on restart. */
|
|
7
|
+
let memoryConsentKey = null;
|
|
8
|
+
function getConsentKeyPath() {
|
|
9
|
+
const home = os.homedir();
|
|
10
|
+
return path.join(home, CONSENT_KEY_DIR, CONSENT_KEY_FILE);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Layered consent key lookup:
|
|
14
|
+
* 1. PAYCLAW_API_KEY env (backward compat â device flow never triggers)
|
|
15
|
+
* 2. ~/.payclaw/consent_key file
|
|
16
|
+
* 3. In-memory (current process only)
|
|
17
|
+
*/
|
|
18
|
+
export function getStoredConsentKey() {
|
|
19
|
+
const envKey = process.env.PAYCLAW_API_KEY;
|
|
20
|
+
if (envKey && envKey.trim().length > 0) {
|
|
21
|
+
return envKey.trim();
|
|
22
|
+
}
|
|
23
|
+
const filePath = getConsentKeyPath();
|
|
24
|
+
try {
|
|
25
|
+
if (fs.existsSync(filePath)) {
|
|
26
|
+
const content = fs.readFileSync(filePath, "utf8").trim();
|
|
27
|
+
if (content.length > 0) {
|
|
28
|
+
return content;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// File read failed â fall back to memory
|
|
34
|
+
}
|
|
35
|
+
return memoryConsentKey;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Store consent key to ~/.payclaw/consent_key.
|
|
39
|
+
* Creates directory if needed. Falls back to memory if file write fails.
|
|
40
|
+
*/
|
|
41
|
+
export async function storeConsentKey(token) {
|
|
42
|
+
const trimmed = token.trim();
|
|
43
|
+
if (trimmed.length === 0)
|
|
44
|
+
return;
|
|
45
|
+
memoryConsentKey = trimmed;
|
|
46
|
+
const filePath = getConsentKeyPath();
|
|
47
|
+
const dir = path.dirname(filePath);
|
|
48
|
+
try {
|
|
49
|
+
if (!fs.existsSync(dir)) {
|
|
50
|
+
fs.mkdirSync(dir, { mode: 0o700, recursive: true });
|
|
51
|
+
}
|
|
52
|
+
fs.writeFileSync(filePath, trimmed, { mode: 0o600, flag: "w" });
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// File write failed â key is in memory, will be lost on restart
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/lib/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,MAAM,eAAe,GAAG,UAAU,CAAC;AACnC,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAEvC,oEAAoE;AACpE,IAAI,gBAAgB,GAAkB,IAAI,CAAC;AAE3C,SAAS,iBAAiB;IACxB,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;IAC1B,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,eAAe,EAAE,gBAAgB,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAC3C,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvC,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;IAED,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC;IACrC,IAAI,CAAC;QACH,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YACzD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,OAAO,OAAO,CAAC;YACjB,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,yCAAyC;IAC3C,CAAC;IAED,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAAa;IACjD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAEjC,gBAAgB,GAAG,OAAO,CAAC;IAE3B,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC;IACrC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,gEAAgE;IAClE,CAAC;AACH,CAAC"}
|
package/dist/sampling.d.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
export interface ActiveTrip {
|
|
3
|
+
token: string;
|
|
4
|
+
merchant: string;
|
|
5
|
+
startedAt: number;
|
|
6
|
+
presented: boolean;
|
|
7
|
+
presentedAt?: number;
|
|
8
|
+
outcome?: string;
|
|
9
|
+
samplingTimer?: ReturnType<typeof setTimeout>;
|
|
10
|
+
}
|
|
2
11
|
export declare function initSampling(server: Server): void;
|
|
3
12
|
export declare function onTripStarted(token: string, merchant: string): void;
|
|
4
13
|
export declare function onIdentityPresented(token: string, merchant: string): void;
|
|
5
14
|
export declare function onServerClose(): void;
|
|
15
|
+
/**
|
|
16
|
+
* Report outcome from agent (payclaw_reportBadgeOutcome tool).
|
|
17
|
+
* Agent-only path â no sampling prompt. Resolves trip and POSTs to API.
|
|
18
|
+
* When token not in activeTrips (e.g. after restart), looks up by merchant or POSTs directly.
|
|
19
|
+
*/
|
|
20
|
+
export declare function reportOutcomeFromAgent(token: string, merchant: string, outcome: "accepted" | "denied" | "inconclusive"): void;
|
package/dist/sampling.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import { getBaseUrl } from "./api/client.js";
|
|
2
|
+
import { parseResponse } from "./lib/parse-outcome.js";
|
|
3
|
+
import { getStoredConsentKey } from "./lib/storage.js";
|
|
1
4
|
const SAMPLING_DELAY_MS = 7000; // 7 seconds after identity_presented
|
|
2
5
|
const SAMPLING_TIMEOUT_MS = 15000; // 15 seconds to respond
|
|
3
|
-
const FAILURE_SIGNALS = [
|
|
4
|
-
"yes", "blocked", "denied", "failed", "403", "error",
|
|
5
|
-
"rejected", "banned", "forbidden", "captcha", "stopped",
|
|
6
|
-
];
|
|
7
6
|
// In-memory state â max 100 active trips
|
|
8
7
|
const activeTrips = new Map();
|
|
9
8
|
const MAX_TRIPS = 100;
|
|
@@ -14,9 +13,11 @@ let serverRef = null;
|
|
|
14
13
|
let samplingAvailable = false;
|
|
15
14
|
export function initSampling(server) {
|
|
16
15
|
serverRef = server;
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
16
|
+
// Extended Auth: only use sampling (agent confirmation prompt) when explicitly enabled.
|
|
17
|
+
// Otherwise, agent reports outcome via payclaw_reportBadgeOutcome.
|
|
18
|
+
const useExtendedAuth = process.env.PAYCLAW_EXTENDED_AUTH === "true" ||
|
|
19
|
+
process.env.PAYCLAW_EXTENDED_AUTH === "1";
|
|
20
|
+
samplingAvailable = useExtendedAuth; // Will catch errors on first attempt if enabled
|
|
20
21
|
if (!reaperStarted) {
|
|
21
22
|
reaperStarted = true;
|
|
22
23
|
setInterval(() => reapStaleTrips(), REAPER_INTERVAL_MS);
|
|
@@ -127,22 +128,6 @@ async function sampleAgent(token, merchant) {
|
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
|
-
function parseResponse(text) {
|
|
131
|
-
if (!text || text.trim().length === 0)
|
|
132
|
-
return "inconclusive";
|
|
133
|
-
const lower = text.toLowerCase().trim();
|
|
134
|
-
// Check for denial signals
|
|
135
|
-
if (FAILURE_SIGNALS.some((s) => lower.includes(s))) {
|
|
136
|
-
// But "no" alone means "no, I was not denied" = accepted
|
|
137
|
-
if (lower === "no" || lower === "no." || lower === "no,")
|
|
138
|
-
return "accepted";
|
|
139
|
-
return "denied";
|
|
140
|
-
}
|
|
141
|
-
// "no" variants = not denied = accepted
|
|
142
|
-
if (lower.startsWith("no"))
|
|
143
|
-
return "accepted";
|
|
144
|
-
return "inconclusive";
|
|
145
|
-
}
|
|
146
131
|
function resolveTrip(token, outcome, detail) {
|
|
147
132
|
const trip = activeTrips.get(token);
|
|
148
133
|
if (!trip)
|
|
@@ -158,15 +143,15 @@ function resolveTrip(token, outcome, detail) {
|
|
|
158
143
|
activeTrips.delete(token);
|
|
159
144
|
}
|
|
160
145
|
async function reportOutcome(token, outcome, merchant, detail) {
|
|
161
|
-
const apiUrl =
|
|
162
|
-
const
|
|
163
|
-
if (!
|
|
146
|
+
const apiUrl = getBaseUrl();
|
|
147
|
+
const key = getStoredConsentKey();
|
|
148
|
+
if (!key)
|
|
164
149
|
return;
|
|
165
150
|
const eventType = outcome === "denied" ? "trip_failure" : "trip_success";
|
|
166
151
|
const res = await fetch(`${apiUrl}/api/badge/report`, {
|
|
167
152
|
method: "POST",
|
|
168
153
|
headers: {
|
|
169
|
-
Authorization: `Bearer ${
|
|
154
|
+
Authorization: `Bearer ${key}`,
|
|
170
155
|
"Content-Type": "application/json",
|
|
171
156
|
},
|
|
172
157
|
body: JSON.stringify({
|
|
@@ -205,4 +190,26 @@ export function onServerClose() {
|
|
|
205
190
|
}
|
|
206
191
|
activeTrips.clear();
|
|
207
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Report outcome from agent (payclaw_reportBadgeOutcome tool).
|
|
195
|
+
* Agent-only path â no sampling prompt. Resolves trip and POSTs to API.
|
|
196
|
+
* When token not in activeTrips (e.g. after restart), looks up by merchant or POSTs directly.
|
|
197
|
+
*/
|
|
198
|
+
export function reportOutcomeFromAgent(token, merchant, outcome) {
|
|
199
|
+
if (activeTrips.has(token)) {
|
|
200
|
+
resolveTrip(token, outcome, "agent_reported");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// Token may be from before restart â try to find trip by merchant
|
|
204
|
+
for (const [t, trip] of activeTrips) {
|
|
205
|
+
if (trip.merchant === merchant && trip.presented && !trip.outcome) {
|
|
206
|
+
resolveTrip(t, outcome, "agent_reported");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// No matching trip â still report to API so outcome is recorded
|
|
211
|
+
reportOutcome(token, outcome, merchant, "agent_reported").catch((err) => {
|
|
212
|
+
process.stderr.write(`[BADGE] Failed to report outcome: ${err}\n`);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
208
215
|
//# sourceMappingURL=sampling.js.map
|
package/dist/sampling.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sampling.js","sourceRoot":"","sources":["../src/sampling.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sampling.js","sourceRoot":"","sources":["../src/sampling.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,10 +12,16 @@ export interface IdentityResult {
|
|
|
12
12
|
merchant?: string;
|
|
13
13
|
instructions?: string;
|
|
14
14
|
message?: string;
|
|
15
|
+
/** Internal: activation flow â agent should display this to user */
|
|
16
|
+
activation_required?: boolean;
|
|
15
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Get agent identity token â Badge by PayClaw.
|
|
20
|
+
* When no consent key exists: initiates device flow, returns activation instructions,
|
|
21
|
+
* polls in background. On approval, stores key. Next call uses stored key.
|
|
22
|
+
*/
|
|
16
23
|
export declare function getAgentIdentity(merchant?: string): Promise<IdentityResult>;
|
|
17
24
|
/**
|
|
18
25
|
* Format identity result as human-readable text for CLI/agent display.
|
|
19
|
-
* Claude reads this and relays key info naturally to the user.
|
|
20
26
|
*/
|
|
21
27
|
export declare function formatIdentityResponse(r: IdentityResult): string;
|
|
@@ -1,18 +1,62 @@
|
|
|
1
1
|
import * as api from "../api/client.js";
|
|
2
|
+
import { getStoredConsentKey } from "../lib/storage.js";
|
|
3
|
+
import { initiateDeviceAuth, pollForApproval } from "../lib/device-auth.js";
|
|
2
4
|
const MOCK_TOKEN_PREFIX = "pc_v1_sand";
|
|
3
5
|
function getMockDisclosure(scope = "BROWSE") {
|
|
4
6
|
return `This agent is using PayClaw Badge: Agent Intent for Ecommerce. The principal user token is a SHA-256 starting ${MOCK_TOKEN_PREFIX}***. Intent has been expressly user-authorized for this session for [${scope}]. For inquiries, please message agent_identity@payclaw.io`;
|
|
5
7
|
}
|
|
8
|
+
/** Build disclosure from OAuth token prefix (matches app disclosure format). */
|
|
9
|
+
function getDisclosureFromToken(token, scope = "BROWSE") {
|
|
10
|
+
const prefix = token.slice(0, 11);
|
|
11
|
+
return `This agent is using PayClaw Badge: Agent Intent for Ecommerce. The principal user token is a SHA-256 starting ${prefix}***. Intent has been expressly user-authorized for this session for [${scope}]. For inquiries, please message agent_identity@payclaw.io`;
|
|
12
|
+
}
|
|
13
|
+
/** Build identity result from OAuth token (when API doesn't accept OAuth Bearer yet). */
|
|
14
|
+
function identityFromOAuthToken(token, _assuranceLevel, merchant, assumeVerified = true) {
|
|
15
|
+
return {
|
|
16
|
+
product_name: "PayClaw Badge",
|
|
17
|
+
status: assumeVerified ? "active" : "pending",
|
|
18
|
+
agent_disclosure: getDisclosureFromToken(token),
|
|
19
|
+
verification_token: token,
|
|
20
|
+
trust_url: "https://payclaw.io/trust",
|
|
21
|
+
contact: "agent_identity@payclaw.io",
|
|
22
|
+
principal_verified: assumeVerified,
|
|
23
|
+
mfa_confirmed: false,
|
|
24
|
+
spend_available: false,
|
|
25
|
+
spend_cta: "Fund your wallet at payclaw.io to enable agent payments.",
|
|
26
|
+
merchant,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
let pendingActivation = null;
|
|
30
|
+
/**
|
|
31
|
+
* Get agent identity token â Badge by PayClaw.
|
|
32
|
+
* When no consent key exists: initiates device flow, returns activation instructions,
|
|
33
|
+
* polls in background. On approval, stores key. Next call uses stored key.
|
|
34
|
+
*/
|
|
6
35
|
export async function getAgentIdentity(merchant) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
message: "PAYCLAW_API_KEY is not set. Get your key at payclaw.io/dashboard/badge",
|
|
12
|
-
};
|
|
36
|
+
const consentKey = getStoredConsentKey();
|
|
37
|
+
// Backward compat: PAYCLAW_API_KEY set â use it, device flow never triggers
|
|
38
|
+
if (consentKey && process.env.PAYCLAW_API_KEY) {
|
|
39
|
+
return callWithKey(consentKey, merchant);
|
|
13
40
|
}
|
|
41
|
+
// No key: initiate device flow (reuse pending to avoid duplicate pollers)
|
|
42
|
+
if (!consentKey) {
|
|
43
|
+
if (pendingActivation)
|
|
44
|
+
return pendingActivation;
|
|
45
|
+
const p = startActivationFlow(merchant);
|
|
46
|
+
pendingActivation = p;
|
|
47
|
+
try {
|
|
48
|
+
const result = await p;
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
pendingActivation = null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Key from file/memory (OAuth token from device flow)
|
|
56
|
+
return callWithOAuthToken(consentKey, merchant);
|
|
57
|
+
}
|
|
58
|
+
async function callWithKey(apiKey, merchant) {
|
|
14
59
|
if (!api.isApiMode()) {
|
|
15
|
-
// Mock mode â return sandbox identity for local testing
|
|
16
60
|
return {
|
|
17
61
|
product_name: "PayClaw Badge",
|
|
18
62
|
status: "active",
|
|
@@ -42,25 +86,91 @@ export async function getAgentIdentity(merchant) {
|
|
|
42
86
|
};
|
|
43
87
|
}
|
|
44
88
|
}
|
|
89
|
+
async function callWithOAuthToken(token, merchant) {
|
|
90
|
+
if (!api.isApiMode()) {
|
|
91
|
+
return identityFromOAuthToken(token, undefined, merchant);
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const result = await api.getAgentIdentityWithToken(api.getBaseUrl(), token, merchant);
|
|
95
|
+
return {
|
|
96
|
+
product_name: "PayClaw Badge",
|
|
97
|
+
status: "active",
|
|
98
|
+
merchant: merchant || undefined,
|
|
99
|
+
...result,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// API call failed â do not mark as verified when falling back to local identity
|
|
104
|
+
return identityFromOAuthToken(token, undefined, merchant, false);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function startActivationFlow(merchant) {
|
|
108
|
+
try {
|
|
109
|
+
const deviceAuth = await initiateDeviceAuth();
|
|
110
|
+
const message = [
|
|
111
|
+
"[PayClaw MCP Server initializing...]",
|
|
112
|
+
"",
|
|
113
|
+
"đĄī¸ Merchants block anonymous bots. PayClaw proves your agent is authorized.",
|
|
114
|
+
"đ To issue your agent's Consent Key, we need your approval.",
|
|
115
|
+
"",
|
|
116
|
+
`đ Go to: ${deviceAuth.verification_uri}`,
|
|
117
|
+
`đ Enter code: ${deviceAuth.user_code}`,
|
|
118
|
+
"",
|
|
119
|
+
"[âŗ Waiting for your approval...]",
|
|
120
|
+
].join("\n");
|
|
121
|
+
// Start polling in background â do not await
|
|
122
|
+
pollForApproval(deviceAuth.device_code, deviceAuth.interval, deviceAuth.expires_in, () => {
|
|
123
|
+
process.stderr.write([
|
|
124
|
+
"",
|
|
125
|
+
"[â
Approval received]",
|
|
126
|
+
"",
|
|
127
|
+
"đ Consent Key generated and stored securely.",
|
|
128
|
+
"đž Avatar assigned: Starter Ghost (0 trips)",
|
|
129
|
+
"đ Your agent is now an authorized actor.",
|
|
130
|
+
"",
|
|
131
|
+
"Ready to shop. Agents are not bots.",
|
|
132
|
+
"",
|
|
133
|
+
].join("\n"));
|
|
134
|
+
}).catch(() => {
|
|
135
|
+
// Poll failed (expired, etc.) â user will need to retry
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
product_name: "PayClaw Badge",
|
|
139
|
+
status: "activation_required",
|
|
140
|
+
activation_required: true,
|
|
141
|
+
message,
|
|
142
|
+
merchant: merchant || undefined,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
return {
|
|
147
|
+
product_name: "PayClaw Badge",
|
|
148
|
+
status: "error",
|
|
149
|
+
message: err instanceof Error ? err.message : String(err),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
45
153
|
/**
|
|
46
154
|
* Format identity result as human-readable text for CLI/agent display.
|
|
47
|
-
* Claude reads this and relays key info naturally to the user.
|
|
48
155
|
*/
|
|
49
156
|
export function formatIdentityResponse(r) {
|
|
157
|
+
if (r.activation_required && r.message) {
|
|
158
|
+
return r.message;
|
|
159
|
+
}
|
|
50
160
|
if (r.status === "error") {
|
|
51
161
|
return `â BADGE ERROR\n\n ${r.message}`;
|
|
52
162
|
}
|
|
53
163
|
const lines = [
|
|
54
164
|
`â DECLARED â Your agent is now an authorized actor`,
|
|
55
165
|
``,
|
|
56
|
-
` Token: ${r.verification_token ? r.verification_token.slice(0, 10) +
|
|
57
|
-
` Principal: ${r.principal_verified ?
|
|
166
|
+
` Token: ${r.verification_token ? r.verification_token.slice(0, 10) + "**" : "N/A"}`,
|
|
167
|
+
` Principal: ${r.principal_verified ? "Verified â" : "Unverified"}`,
|
|
58
168
|
` Scope: [BROWSE]`,
|
|
59
169
|
];
|
|
60
170
|
if (r.merchant) {
|
|
61
171
|
lines.push(` Merchant: ${r.merchant}`);
|
|
62
172
|
}
|
|
63
|
-
lines.push(` Status: ACTIVE`, ` Trust: ${r.trust_url ||
|
|
173
|
+
lines.push(` Status: ACTIVE`, ` Trust: ${r.trust_url || "https://payclaw.io/trust"}`, ``, ` Disclosure (present to merchants):`, ` "${r.agent_disclosure}"`);
|
|
64
174
|
if (r.spend_available) {
|
|
65
175
|
lines.push(``, ` đŗ Spend is available â call payclaw_getCard when ready to pay.`);
|
|
66
176
|
}
|
|
@@ -70,6 +180,6 @@ export function formatIdentityResponse(r) {
|
|
|
70
180
|
else {
|
|
71
181
|
lines.push(``, ` âšī¸ Identity only. Fund your wallet at payclaw.io to enable payments.`);
|
|
72
182
|
}
|
|
73
|
-
return lines.join(
|
|
183
|
+
return lines.join("\n");
|
|
74
184
|
}
|
|
75
185
|
//# sourceMappingURL=getAgentIdentity.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getAgentIdentity.js","sourceRoot":"","sources":["../../src/tools/getAgentIdentity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"getAgentIdentity.js","sourceRoot":"","sources":["../../src/tools/getAgentIdentity.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payclaw/badge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Prove your agent is an authorized actor, not a bot. MCP-native identity declaration for agent commerce.",
|
|
5
5
|
"bin": "dist/index.js",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "tsc && node -e \"const fs=require('fs');const f='dist/index.js';fs.chmodSync(f,0o755)\"",
|
|
18
18
|
"dev": "tsc --watch",
|
|
19
|
+
"test": "vitest",
|
|
19
20
|
"prepublishOnly": "npm run build"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
@@ -24,7 +25,8 @@
|
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
27
|
"@types/node": "^20.0.0",
|
|
27
|
-
"typescript": "^5.3.0"
|
|
28
|
+
"typescript": "^5.3.0",
|
|
29
|
+
"vitest": "^3.0.0"
|
|
28
30
|
},
|
|
29
31
|
"keywords": [
|
|
30
32
|
"payclaw",
|